mirror of
https://github.com/apache/superset.git
synced 2026-07-02 12:55:35 +00:00
Compare commits
316 Commits
codex/fix-
...
3.0.3rc3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc5235829e | ||
|
|
439979c73a | ||
|
|
2a30d59b05 | ||
|
|
52109e5f24 | ||
|
|
333b18db5a | ||
|
|
26b26e46e4 | ||
|
|
3589f7ece4 | ||
|
|
f467310cea | ||
|
|
37ad33bf5b | ||
|
|
3f97daca09 | ||
|
|
9b99303c5c | ||
|
|
8deb9b21d9 | ||
|
|
5de0ef904e | ||
|
|
aaa458bf9d | ||
|
|
a679237c36 | ||
|
|
643cb9df2b | ||
|
|
71bf15e870 | ||
|
|
0e9cbd4f00 | ||
|
|
05bf190d87 | ||
|
|
f360a59f2c | ||
|
|
9a63b6b32d | ||
|
|
ad23deb373 | ||
|
|
d5a5d5f388 | ||
|
|
e5ce25d91d | ||
|
|
9b21c93c47 | ||
|
|
91e970537b | ||
|
|
5f92c2f0dc | ||
|
|
c878e2e102 | ||
|
|
c99c6301c7 | ||
|
|
cefca2f000 | ||
|
|
b0905ce0bd | ||
|
|
8eb6bbba91 | ||
|
|
52f12ba06e | ||
|
|
c6c71123ee | ||
|
|
2b7766afa6 | ||
|
|
2532fa5dd9 | ||
|
|
03abfba0f5 | ||
|
|
b9e8cc958a | ||
|
|
f4873860fc | ||
|
|
4cba277795 | ||
|
|
0b040b4824 | ||
|
|
ec6d31c817 | ||
|
|
a4c5340c7e | ||
|
|
a5c842c876 | ||
|
|
02188d8401 | ||
|
|
3edbb9fdbd | ||
|
|
a3212ccba8 | ||
|
|
00db4dfb06 | ||
|
|
b900cb7c3a | ||
|
|
ea65c2467c | ||
|
|
ff5de25478 | ||
|
|
7916778585 | ||
|
|
79d8865ac6 | ||
|
|
0c0bb50d41 | ||
|
|
44fb6b5551 | ||
|
|
961eba6d97 | ||
|
|
08604cc686 | ||
|
|
49661bcc59 | ||
|
|
da06206ea6 | ||
|
|
e3fbb01bb8 | ||
|
|
ee1ba7e172 | ||
|
|
8d873e6da6 | ||
|
|
1d2a564d4e | ||
|
|
1c287dfc74 | ||
|
|
fb1919a483 | ||
|
|
e07eed10a2 | ||
|
|
a7fbdd607a | ||
|
|
6da18f8451 | ||
|
|
eea6a8ed4f | ||
|
|
078b78f30b | ||
|
|
c655a3e7dd | ||
|
|
d265bd2ffc | ||
|
|
8c099a3f6f | ||
|
|
81f7c63763 | ||
|
|
756324d713 | ||
|
|
5198279a2b | ||
|
|
4534a070df | ||
|
|
d5901140a7 | ||
|
|
925c63d4a6 | ||
|
|
28272527fc | ||
|
|
2d574963f0 | ||
|
|
04c11b477b | ||
|
|
c216b3efdf | ||
|
|
1d403dab98 | ||
|
|
2f468900c8 | ||
|
|
fbe7e6265d | ||
|
|
01d3ac20c7 | ||
|
|
fd2c2725d4 | ||
|
|
9b31d97ac3 | ||
|
|
8da27eda40 | ||
|
|
8483ab6c42 | ||
|
|
315e75811f | ||
|
|
5293f5521d | ||
|
|
293568ad5a | ||
|
|
b380495516 | ||
|
|
b0f229ea7e | ||
|
|
b95ff2da23 | ||
|
|
236aef8126 | ||
|
|
af1e71352a | ||
|
|
ec3bed709e | ||
|
|
701ee30d1e | ||
|
|
d7cbdca081 | ||
|
|
890bf59ce4 | ||
|
|
cd1b7a4c06 | ||
|
|
c44f1a3299 | ||
|
|
a0b2dc4266 | ||
|
|
732c5b1f08 | ||
|
|
254cc36b17 | ||
|
|
53b84b9664 | ||
|
|
69c2378747 | ||
|
|
286c095506 | ||
|
|
dd769eb7a0 | ||
|
|
8b66603566 | ||
|
|
220dc58fe0 | ||
|
|
1367d7b954 | ||
|
|
ae700d13cf | ||
|
|
4ad2a05333 | ||
|
|
9d1ab460c3 | ||
|
|
455b3d8a67 | ||
|
|
0dd1a3bea5 | ||
|
|
615d7f5ccc | ||
|
|
f682dbae52 | ||
|
|
d8e87aa3cc | ||
|
|
58778a78b2 | ||
|
|
731cd65111 | ||
|
|
7b2b696a19 | ||
|
|
b83bd5dc1c | ||
|
|
8dfe95f89d | ||
|
|
0c6db230af | ||
|
|
1757ce49a9 | ||
|
|
4a65d41ce8 | ||
|
|
eacdbdd877 | ||
|
|
721db8a1a9 | ||
|
|
c508a335e6 | ||
|
|
88e6f22180 | ||
|
|
5390e2d826 | ||
|
|
0d53446562 | ||
|
|
61dcc70db4 | ||
|
|
8ca49d4e6f | ||
|
|
2d1f1e3d71 | ||
|
|
807a027a5f | ||
|
|
40d9c4c81f | ||
|
|
f55962fbb8 | ||
|
|
28e944ae86 | ||
|
|
1498c21d42 | ||
|
|
69db484d2d | ||
|
|
39dcb29a69 | ||
|
|
6d1b969602 | ||
|
|
2f1ce7f721 | ||
|
|
65a2ca9e6f | ||
|
|
10e781d0ff | ||
|
|
dfd699f440 | ||
|
|
2ae9d2ef05 | ||
|
|
80df8bc558 | ||
|
|
dac3009cc6 | ||
|
|
2ac03c3bcc | ||
|
|
d8c72b86bc | ||
|
|
408708be62 | ||
|
|
e77bc066bd | ||
|
|
90a66ee76c | ||
|
|
331fefd3dd | ||
|
|
c56948e1f1 | ||
|
|
b53042a7be | ||
|
|
f778b62712 | ||
|
|
76d7bf9011 | ||
|
|
af8f074919 | ||
|
|
dd0475d63c | ||
|
|
9837dabb2c | ||
|
|
372004d0e6 | ||
|
|
46e6d32260 | ||
|
|
e2f89d7aa8 | ||
|
|
798b493f3a | ||
|
|
caa3b6d5ba | ||
|
|
90e7e769ce | ||
|
|
429ff9b0f8 | ||
|
|
6a461260fc | ||
|
|
f34e21be69 | ||
|
|
9ceba619c3 | ||
|
|
76da1b59f9 | ||
|
|
7d5cd72e43 | ||
|
|
0caaad7b0a | ||
|
|
55c57b9277 | ||
|
|
2c99366333 | ||
|
|
e4affbfc95 | ||
|
|
f5d2075ace | ||
|
|
2554a89cc9 | ||
|
|
ad89ea549b | ||
|
|
931e1b2139 | ||
|
|
b5f7f54c7f | ||
|
|
1af6df3190 | ||
|
|
8cb5142f87 | ||
|
|
6003aa2485 | ||
|
|
80f1eaf6d7 | ||
|
|
34bc86a484 | ||
|
|
994fd2301f | ||
|
|
a5a027d8d1 | ||
|
|
fad872fffb | ||
|
|
88383ded80 | ||
|
|
9ff1a63c3b | ||
|
|
696917905e | ||
|
|
f63cb47f35 | ||
|
|
c2c5f232c8 | ||
|
|
ea27cf13d3 | ||
|
|
e20c2967c4 | ||
|
|
387549f69c | ||
|
|
1569f0177f | ||
|
|
8d3a919f5e | ||
|
|
3ffc5b69f8 | ||
|
|
4b07b5d628 | ||
|
|
dd53b334d6 | ||
|
|
5c931b1951 | ||
|
|
52319201f9 | ||
|
|
ff2ec23102 | ||
|
|
5d8c65ae6f | ||
|
|
7a7fa748f5 | ||
|
|
2574e11544 | ||
|
|
1e20c0bf8a | ||
|
|
9b3ec806cd | ||
|
|
309582516d | ||
|
|
dba72c4197 | ||
|
|
804cc36080 | ||
|
|
df92cc2d55 | ||
|
|
42451880a8 | ||
|
|
ed56375d5f | ||
|
|
abead484e1 | ||
|
|
c8c7539ff1 | ||
|
|
215b3b5a4b | ||
|
|
7fe61ccf7d | ||
|
|
e46f10a8a6 | ||
|
|
b272814ff5 | ||
|
|
84035badab | ||
|
|
21764f9ae3 | ||
|
|
8cf702bf3d | ||
|
|
e47377e576 | ||
|
|
21ded992ad | ||
|
|
29528e9783 | ||
|
|
34adeb4f4f | ||
|
|
bbe4e016d8 | ||
|
|
161e05445c | ||
|
|
b5df3f9e4e | ||
|
|
a9b8c8e3ec | ||
|
|
1a0d270e5b | ||
|
|
7d2da96e81 | ||
|
|
b89d7387a2 | ||
|
|
571f33536e | ||
|
|
651c13b934 | ||
|
|
af8c813cb6 | ||
|
|
ad1a425269 | ||
|
|
84488b7c5e | ||
|
|
d86ae30958 | ||
|
|
d92f9a73d2 | ||
|
|
98ec90938d | ||
|
|
25d42538d1 | ||
|
|
a85da81815 | ||
|
|
d95241df9d | ||
|
|
d336431559 | ||
|
|
e3d8ecb478 | ||
|
|
a066ebbb5e | ||
|
|
6947983d5f | ||
|
|
00516c2088 | ||
|
|
2e978bba14 | ||
|
|
a8f808f7a8 | ||
|
|
dd002a8cf6 | ||
|
|
157cc621c5 | ||
|
|
299dbb7012 | ||
|
|
2e222865bf | ||
|
|
d87724a6e3 | ||
|
|
6281f50bab | ||
|
|
06258a06ed | ||
|
|
a7221c6dd4 | ||
|
|
66792beb8b | ||
|
|
aa5f0b1019 | ||
|
|
cc7983cd4a | ||
|
|
1625fe4104 | ||
|
|
91919569ba | ||
|
|
befe41df14 | ||
|
|
a3138f2bbe | ||
|
|
82311edaf6 | ||
|
|
757741942d | ||
|
|
65822cab13 | ||
|
|
4390968dd7 | ||
|
|
2e913492c9 | ||
|
|
c94bee47aa | ||
|
|
c6ecbc81ac | ||
|
|
60385ed059 | ||
|
|
b0c90cb261 | ||
|
|
49605e763c | ||
|
|
7243332082 | ||
|
|
1872a651da | ||
|
|
f60ab456f3 | ||
|
|
96cf6406f1 | ||
|
|
71eff954ba | ||
|
|
79a42c6509 | ||
|
|
d8bf955515 | ||
|
|
a2fdc84073 | ||
|
|
5a4996c226 | ||
|
|
359bbe26f7 | ||
|
|
27dcc3e025 | ||
|
|
d6296c1ad1 | ||
|
|
846d3c03aa | ||
|
|
2bd9ca3c94 | ||
|
|
a15e809c0f | ||
|
|
b81ee82d3c | ||
|
|
6b366a2cff | ||
|
|
9a96d8cf8b | ||
|
|
744cf2ec29 | ||
|
|
26909bf517 | ||
|
|
967ca0470b | ||
|
|
0976b5e51e | ||
|
|
67008f2c35 | ||
|
|
af2689d501 | ||
|
|
a5f3cfc726 | ||
|
|
9ab61c1103 | ||
|
|
6de6f47728 | ||
|
|
1b41653d3b | ||
|
|
eb42fa7a2e |
38
.github/SECURITY.md
vendored
Normal file
38
.github/SECURITY.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# Security Policy
|
||||
|
||||
This is a project of the [Apache Software Foundation](https://apache.org) and follows the
|
||||
ASF [vulnerability handling process](https://apache.org/security/#vulnerability-handling).
|
||||
|
||||
## Reporting Vulnerabilities
|
||||
|
||||
**⚠️ Please do not file GitHub issues for security vulnerabilities as they are public! ⚠️**
|
||||
|
||||
|
||||
Apache Software Foundation takes a rigorous standpoint in annihilating the security issues
|
||||
in its software projects. Apache Superset is highly sensitive and forthcoming to issues
|
||||
pertaining to its features and functionality.
|
||||
If you have any concern or believe you have found a vulnerability in Apache Superset,
|
||||
please get in touch with the Apache Security Team privately at
|
||||
e-mail address [security@apache.org](mailto:security@apache.org).
|
||||
|
||||
More details can be found on the ASF website at
|
||||
[ASF vulnerability reporting process](https://apache.org/security/#reporting-a-vulnerability)
|
||||
|
||||
We kindly ask you to include the following information in your report:
|
||||
- Apache Superset version that you are using
|
||||
- A sanitized copy of your `superset_config.py` file or any config overrides
|
||||
- Detailed steps to reproduce the vulnerability
|
||||
|
||||
Note that Apache Superset is not responsible for any third-party dependencies that may
|
||||
have security issues. Any vulnerabilities found in third-party dependencies should be
|
||||
reported to the maintainers of those projects. Results from security scans of Apache
|
||||
Superset dependencies found on its official Docker image can be remediated at release time
|
||||
by extending the image itself.
|
||||
|
||||
**Your responsible disclosure and collaboration are invaluable.**
|
||||
|
||||
## Extra Information
|
||||
|
||||
- [Apache Superset documentation](https://superset.apache.org/docs/security)
|
||||
- [Common Vulnerabilities and Exposures by release](https://superset.apache.org/docs/security/cves)
|
||||
- [How Security Vulnerabilities are Reported & Handled in Apache Superset (Blog)](https://preset.io/blog/how-security-vulnerabilities-are-reported-and-handled-in-apache-superset/)
|
||||
8
.github/workflows/ecs-task-definition.json
vendored
8
.github/workflows/ecs-task-definition.json
vendored
@@ -25,8 +25,12 @@
|
||||
"value": "8080"
|
||||
},
|
||||
{
|
||||
"name": "SUPERSET_SECRET_KEY",
|
||||
"value": "super-secret-for-ephemerals"
|
||||
"name": "SUPERSET_SECRET_KEY",
|
||||
"value": "super-secret-for-ephemerals"
|
||||
},
|
||||
{
|
||||
"name": "TALISMAN_ENABLED",
|
||||
"value": "False"
|
||||
}
|
||||
],
|
||||
"mountPoints": [],
|
||||
|
||||
@@ -42,12 +42,13 @@ repos:
|
||||
hooks:
|
||||
- id: mypy
|
||||
args: [--check-untyped-defs]
|
||||
additional_dependencies:
|
||||
[
|
||||
additional_dependencies: [
|
||||
types-simplejson,
|
||||
types-python-dateutil,
|
||||
types-requests,
|
||||
types-redis,
|
||||
# types-redis 4.6.0.5 is failing mypy
|
||||
# because of https://github.com/python/typeshed/pull/10531
|
||||
types-redis==4.6.0.4,
|
||||
types-pytz,
|
||||
types-croniter,
|
||||
types-PyYAML,
|
||||
|
||||
@@ -83,6 +83,7 @@ enable=
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=
|
||||
no-member, # re-enable once this no longer raises false positives. This will become redundant after the min required version is 3.11
|
||||
missing-docstring,
|
||||
duplicate-code,
|
||||
unspecified-encoding,
|
||||
|
||||
1064
CHANGELOG.md
1064
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
100
CONTRIBUTING.md
100
CONTRIBUTING.md
@@ -577,6 +577,19 @@ cd superset-frontend
|
||||
npm ci
|
||||
```
|
||||
|
||||
Note that Superset uses [Scarf](https://docs.scarf.sh) to capture telemetry/analytics about versions being installed, including the `scarf-js` npm package. As noted elsewhere in this documentation, Scarf gathers aggregated stats for the sake of security/release strategy, and does not capture/retain PII. [You can read here](https://docs.scarf.sh/package-analytics/) about the package, and various means to opt out of it, but one easy way to opt out is to add this setting in `superset-frontent/package.json`:
|
||||
|
||||
```json
|
||||
// your-package/package.json
|
||||
{
|
||||
// ...
|
||||
"scarfSettings": {
|
||||
"enabled": false
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Build assets
|
||||
|
||||
There are three types of assets you can build:
|
||||
@@ -586,10 +599,13 @@ There are three types of assets you can build:
|
||||
3. `npm run build-instrumented`: instrumented application code for collecting code coverage from Cypress tests
|
||||
|
||||
If this type of error comes while building assets(i.e using above commands):
|
||||
|
||||
```bash
|
||||
Error: You must provide the URL of lib/mappings.wasm by calling SourceMapConsumer.initialize
|
||||
```
|
||||
|
||||
Then put this:
|
||||
|
||||
```bash
|
||||
export NODE_OPTIONS=--no-experimental-fetch
|
||||
```
|
||||
@@ -913,28 +929,22 @@ For debugging locally using VSCode, you can configure a launch configuration fil
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Flask",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "flask",
|
||||
"env": {
|
||||
"FLASK_APP": "superset",
|
||||
"SUPERSET_ENV": "development"
|
||||
},
|
||||
"args": [
|
||||
"run",
|
||||
"-p 8088",
|
||||
"--with-threads",
|
||||
"--reload",
|
||||
"--debugger"
|
||||
],
|
||||
"jinja": true,
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Flask",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "flask",
|
||||
"env": {
|
||||
"FLASK_APP": "superset",
|
||||
"SUPERSET_ENV": "development"
|
||||
},
|
||||
"args": ["run", "-p 8088", "--with-threads", "--reload", "--debugger"],
|
||||
"jinja": true,
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1019,24 +1029,24 @@ You are now ready to attach a debugger to the process. Using VSCode you can conf
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Attach to Superset App in Docker Container",
|
||||
"type": "python",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 5678
|
||||
},
|
||||
"pathMappings": [
|
||||
{
|
||||
"name": "Attach to Superset App in Docker Container",
|
||||
"type": "python",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 5678
|
||||
},
|
||||
"pathMappings": [
|
||||
{
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "/app"
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "/app"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1337,7 +1347,7 @@ To do this, you'll need to:
|
||||
but perfect for testing (stores cache in `/tmp`)
|
||||
|
||||
```python
|
||||
from cachelib.file import FileSystemCache
|
||||
from flask_caching.backends.filesystemcache import FileSystemCache
|
||||
RESULTS_BACKEND = FileSystemCache('/tmp/sqllab')
|
||||
```
|
||||
|
||||
@@ -1403,11 +1413,11 @@ Note not all fields are correctly categorized. The fields vary based on visualiz
|
||||
|
||||
### Time
|
||||
|
||||
| Field | Type | Notes |
|
||||
| ------------------ | -------- | ------------------------------------- |
|
||||
| `granularity_sqla` | _string_ | The SQLA **Time Column** widget |
|
||||
| `time_grain_sqla` | _string_ | The SQLA **Time Grain** widget |
|
||||
| `time_range` | _string_ | The **Time range** widget |
|
||||
| Field | Type | Notes |
|
||||
| ------------------ | -------- | ------------------------------- |
|
||||
| `granularity_sqla` | _string_ | The SQLA **Time Column** widget |
|
||||
| `time_grain_sqla` | _string_ | The SQLA **Time Grain** widget |
|
||||
| `time_range` | _string_ | The **Time range** widget |
|
||||
|
||||
### GROUP BY
|
||||
|
||||
|
||||
12
Dockerfile
12
Dockerfile
@@ -25,8 +25,16 @@ ARG BUILDPLATFORM=${BUILDPLATFORM:-amd64}
|
||||
FROM --platform=${BUILDPLATFORM} node:16-slim AS superset-node
|
||||
|
||||
ARG NPM_BUILD_CMD="build"
|
||||
ENV BUILD_CMD=${NPM_BUILD_CMD}
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
|
||||
RUN apt-get update -q \
|
||||
&& apt-get install -yq --no-install-recommends \
|
||||
python3 \
|
||||
make \
|
||||
gcc \
|
||||
g++
|
||||
|
||||
ENV BUILD_CMD=${NPM_BUILD_CMD} \
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
|
||||
# NPM ci first, as to NOT invalidate previous steps except for when package.json changes
|
||||
WORKDIR /app/superset-frontend
|
||||
|
||||
@@ -81,9 +81,9 @@
|
||||
|can available domains on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can request access on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can dashboard on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can post on TableSchemaView|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can expanded on TableSchemaView|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can delete on TableSchemaView|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can post on TableSchemaView|:heavy_check_mark:|:heavy_check_mark:|O|O|
|
||||
|can expanded on TableSchemaView|:heavy_check_mark:|:heavy_check_mark:|O|O|
|
||||
|can delete on TableSchemaView|:heavy_check_mark:|:heavy_check_mark:|O|O|
|
||||
|can get on TabStateView|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||
|can post on TabStateView|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||
|can delete query on TabStateView|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||
|
||||
28
UPDATING.md
28
UPDATING.md
@@ -24,9 +24,25 @@ assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### Potential Downtime
|
||||
|
||||
### Other
|
||||
|
||||
## 3.0.3
|
||||
|
||||
- [26034](https://github.com/apache/superset/issues/26034): Fixes a problem where numeric x-axes were being treated as categorical values. As a consequence of that, the way labels are displayed might change given that ECharts has a different treatment for numerical and categorical values. To revert to the old behavior, users need to manually convert numerical columns to text so that they are treated as categories. Check https://github.com/apache/superset/issues/26159 for more details.
|
||||
|
||||
## 3.0.0
|
||||
|
||||
- [25053](https://github.com/apache/superset/pull/25053): Extends the `ab_user.email` column from 64 to 320 characters which has an associated unique key constraint. This will be problematic for MySQL metadata databases which use the InnoDB storage engine with the `innodb_large_prefix` parameter disabled as the key prefix limit is 767 bytes. Enabling said parameter and ensuring that the table uses either the `DYNAMIC` or `COMPRESSED` row format should remedy the problem. See [here](https://dev.mysql.com/doc/refman/5.7/en/innodb-limits.html) for more details.
|
||||
- [24911](https://github.com/apache/superset/pull/24911): Changes the column type from `TEXT` to `MediumText` in table `logs`, potentially requiring a table lock on MySQL dbs or taking some time to complete on large deployments.
|
||||
- [24939](https://github.com/apache/superset/pull/24939): Augments the foreign key constraints for the `embedded_dashboards` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dashboard is deleted. Scheduled downtime may be advised.
|
||||
- [24938](https://github.com/apache/superset/pull/24938): Augments the foreign key constraints for the `dashboard_slices` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dashboard or slice is deleted. Scheduled downtime may be advised.
|
||||
- [24628]https://github.com/apache/superset/pull/24628): Augments the foreign key constraints for the `dashboard_owner`, `report_schedule_owner`, and `slice_owner` tables to include an explicit CASCADE ON DELETE to ensure the relevant ownership records are deleted when a dataset is deleted. Scheduled downtime may be advised.
|
||||
- [24488](https://github.com/apache/superset/pull/24488): Augments the foreign key constraints for the `sql_metrics`, `sqlatable_user`, and `table_columns` tables which reference the `tables` table to include an explicit CASCADE ON DELETE to ensure the relevant records are deleted when a dataset is deleted. Scheduled downtime may be advised.
|
||||
- [24335](https://github.com/apache/superset/pull/24335): Removed deprecated API `/superset/filter/<datasource_type>/<int:datasource_id>/<column>/`
|
||||
- [24185](https://github.com/apache/superset/pull/24185): `/api/v1/database/test_connection` and `api/v1/database/validate_parameters` permissions changed from `can_read` to `can_write`. Only Admin user's have access.
|
||||
- [24232](https://github.com/apache/superset/pull/24232): Enables ENABLE_TEMPLATE_REMOVE_FILTERS, DRILL_TO_DETAIL, DASHBOARD_CROSS_FILTERS by default, marks VERSIONED_EXPORT and ENABLE_TEMPLATE_REMOVE_FILTERS as deprecated.
|
||||
- [23652](https://github.com/apache/superset/pull/23652): Enables GENERIC_CHART_AXES feature flag by default.
|
||||
- [23226](https://github.com/apache/superset/pull/23226): Migrated endpoint `/estimate_query_cost/<int:database_id>` to `/api/v1/sqllab/estimate/`. Corresponding permissions are can estimate query cost on SQLLab. Make sure you add/replace the necessary permissions on any custom roles you may have.
|
||||
@@ -37,9 +53,11 @@ assists people when migrating to a new version.
|
||||
make it more clear which envrionment your are in.
|
||||
`SUPERSET_ENV=production` and `SUPERSET_ENV=development` are the two
|
||||
supported switches based on the default config.
|
||||
- [19242](https://github.com/apache/superset/pull/19242): Adhoc subqueries are now disabled by default for security reasons. To enable them, set the feature flag `ALLOW_ADHOC_SUBQUERY` to `True`.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [24686]https://github.com/apache/superset/pull/24686): All dataset's custom explore_url are handled as relative URLs on the frontend, behaviour controlled by PREVENT_UNSAFE_DEFAULT_URLS_ON_DATASET.
|
||||
- [24262](https://github.com/apache/superset/pull/24262): Enabled `TALISMAN_ENABLED` flag by default and provided stricter default Content Security Policy
|
||||
- [24415](https://github.com/apache/superset/pull/24415): Removed the obsolete Druid NoSQL REGEX operator.
|
||||
- [24423](https://github.com/apache/superset/pull/24423): Removed deprecated APIs `/superset/slice_json/...`, `/superset/annotation_json/...`
|
||||
@@ -70,10 +88,16 @@ assists people when migrating to a new version.
|
||||
- [23663](https://github.com/apache/superset/pull/23663): Removes deprecated feature flags `ALLOW_DASHBOARD_DOMAIN_SHARDING`, `DISPLAY_MARKDOWN_HTML`, and `FORCE_DATABASE_CONNECTIONS_SSL`.
|
||||
- [22325](https://github.com/apache/superset/pull/22325): "RLS_FORM_QUERY_REL_FIELDS" is replaced by "RLS_BASE_RELATED_FIELD_FILTERS" feature flag. Its value format stays same.
|
||||
|
||||
### Potential Downtime
|
||||
## 2.1.1
|
||||
|
||||
- [24185](https://github.com/apache/superset/pull/24185): `/api/v1/database/test_connection` and `api/v1/database/validate_parameters` permissions changed from `can_read` to `can_write`. Only Admin user's have access.
|
||||
- [24256](https://github.com/apache/superset/pull/24256): `Flask-Login` session validation is now set to `strong` by default. Previous setting was `basic`.
|
||||
|
||||
### Other
|
||||
|
||||
- [24982](https://github.com/apache/superset/pull/24982): By default, physical datasets on Oracle-like dialects like Snowflake will now use denormalized column names. However, existing datasets won't be affected. To change this behavior, the "Advanced" section on the dataset modal has a "Normalize column names" flag which can be changed to change this behavior.
|
||||
- [23888](https://github.com/apache/superset/pull/23888): Database Migration for json serialization instead of pickle should upgrade/downgrade correctly when bumping to/from this patch version
|
||||
|
||||
## 2.1.0
|
||||
|
||||
- [22809](https://github.com/apache/superset/pull/22809): Migrated endpoint `/superset/sql_json` and `/superset/results/` to `/api/v1/sqllab/execute/` and `/api/v1/sqllab/results/` respectively. Corresponding permissions are `can sql_json on Superset` to `can execute on SQLLab`, `can results on Superset` to `can results on SQLLab`. Make sure you add/replace the necessary permissions on any custom roles you may have.
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
x-superset-image: &superset-image apache/superset:${TAG:-latest-dev}
|
||||
x-superset-image: &superset-image apachesuperset.docker.scarf.sh/apache/superset:${TAG:-latest-dev}
|
||||
x-superset-depends-on: &superset-depends-on
|
||||
- db
|
||||
- redis
|
||||
x-superset-volumes: &superset-volumes
|
||||
# /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
|
||||
x-superset-volumes:
|
||||
&superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
|
||||
- ./docker:/app/docker
|
||||
- superset_home:/app/superset_home
|
||||
|
||||
@@ -39,6 +39,7 @@ services:
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db_home:/var/lib/postgresql/data
|
||||
- ./docker/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
|
||||
|
||||
superset:
|
||||
env_file: docker/.env-non-dev
|
||||
@@ -73,7 +74,11 @@ services:
|
||||
user: "root"
|
||||
volumes: *superset-volumes
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "celery -A superset.tasks.celery_app:app inspect ping -d celery@$$HOSTNAME"]
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"celery -A superset.tasks.celery_app:app inspect ping -d celery@$$HOSTNAME",
|
||||
]
|
||||
|
||||
superset-worker-beat:
|
||||
image: *superset-image
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
x-superset-image: &superset-image apache/superset:${TAG:-latest-dev}
|
||||
x-superset-image: &superset-image apachesuperset.docker.scarf.sh/apache/superset:${TAG:-latest-dev}
|
||||
x-superset-user: &superset-user root
|
||||
x-superset-depends-on: &superset-depends-on
|
||||
- db
|
||||
@@ -47,6 +47,7 @@ services:
|
||||
- "127.0.0.1:5432:5432"
|
||||
volumes:
|
||||
- db_home:/var/lib/postgresql/data
|
||||
- ./docker/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
|
||||
|
||||
superset:
|
||||
env_file: docker/.env
|
||||
|
||||
@@ -22,6 +22,12 @@ DATABASE_HOST=db
|
||||
DATABASE_PASSWORD=superset
|
||||
DATABASE_USER=superset
|
||||
|
||||
EXAMPLES_DB=examples
|
||||
EXAMPLES_HOST=db
|
||||
EXAMPLES_USER=examples
|
||||
EXAMPLES_PASSWORD=examples
|
||||
EXAMPLES_PORT=5432
|
||||
|
||||
# database engine specific environment variables
|
||||
# change the below if you prefer another database engine
|
||||
DATABASE_PORT=5432
|
||||
|
||||
@@ -21,11 +21,17 @@ DATABASE_DB=superset
|
||||
DATABASE_HOST=db
|
||||
DATABASE_PASSWORD=superset
|
||||
DATABASE_USER=superset
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_DIALECT=postgresql
|
||||
|
||||
EXAMPLES_DB=examples
|
||||
EXAMPLES_HOST=db
|
||||
EXAMPLES_USER=examples
|
||||
EXAMPLES_PASSWORD=examples
|
||||
EXAMPLES_PORT=5432
|
||||
|
||||
# database engine specific environment variables
|
||||
# change the below if you prefer another database engine
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_DIALECT=postgresql
|
||||
POSTGRES_DB=superset
|
||||
POSTGRES_USER=superset
|
||||
POSTGRES_PASSWORD=superset
|
||||
|
||||
15
docker/docker-entrypoint-initdb.d/examples-init.sh
Executable file
15
docker/docker-entrypoint-initdb.d/examples-init.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
# ------------------------------------------------------------------------
|
||||
# Creates the examples database and repective user. This database location
|
||||
# and access credentials are defined on the environment variables
|
||||
# ------------------------------------------------------------------------
|
||||
set -e
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "${POSTGRES_USER}" <<-EOSQL
|
||||
CREATE USER ${EXAMPLES_USER} WITH PASSWORD '${EXAMPLES_PASSWORD}';
|
||||
CREATE DATABASE ${EXAMPLES_DB};
|
||||
GRANT ALL PRIVILEGES ON DATABASE ${EXAMPLES_DB} TO ${EXAMPLES_USER};
|
||||
EOSQL
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "${POSTGRES_USER}" -d "${EXAMPLES_DB}" <<-EOSQL
|
||||
GRANT ALL ON SCHEMA public TO ${EXAMPLES_USER};
|
||||
EOSQL
|
||||
@@ -22,49 +22,42 @@
|
||||
#
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from cachelib.file import FileSystemCache
|
||||
from celery.schedules import crontab
|
||||
from flask_caching.backends.filesystemcache import FileSystemCache
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
DATABASE_DIALECT = os.getenv("DATABASE_DIALECT")
|
||||
DATABASE_USER = os.getenv("DATABASE_USER")
|
||||
DATABASE_PASSWORD = os.getenv("DATABASE_PASSWORD")
|
||||
DATABASE_HOST = os.getenv("DATABASE_HOST")
|
||||
DATABASE_PORT = os.getenv("DATABASE_PORT")
|
||||
DATABASE_DB = os.getenv("DATABASE_DB")
|
||||
|
||||
def get_env_variable(var_name: str, default: Optional[str] = None) -> str:
|
||||
"""Get the environment variable or raise exception."""
|
||||
try:
|
||||
return os.environ[var_name]
|
||||
except KeyError:
|
||||
if default is not None:
|
||||
return default
|
||||
else:
|
||||
error_msg = "The environment variable {} was missing, abort...".format(
|
||||
var_name
|
||||
)
|
||||
raise OSError(error_msg)
|
||||
|
||||
|
||||
DATABASE_DIALECT = get_env_variable("DATABASE_DIALECT")
|
||||
DATABASE_USER = get_env_variable("DATABASE_USER")
|
||||
DATABASE_PASSWORD = get_env_variable("DATABASE_PASSWORD")
|
||||
DATABASE_HOST = get_env_variable("DATABASE_HOST")
|
||||
DATABASE_PORT = get_env_variable("DATABASE_PORT")
|
||||
DATABASE_DB = get_env_variable("DATABASE_DB")
|
||||
EXAMPLES_USER = os.getenv("EXAMPLES_USER")
|
||||
EXAMPLES_PASSWORD = os.getenv("EXAMPLES_PASSWORD")
|
||||
EXAMPLES_HOST = os.getenv("EXAMPLES_HOST")
|
||||
EXAMPLES_PORT = os.getenv("EXAMPLES_PORT")
|
||||
EXAMPLES_DB = os.getenv("EXAMPLES_DB")
|
||||
|
||||
# The SQLAlchemy connection string.
|
||||
SQLALCHEMY_DATABASE_URI = "{}://{}:{}@{}:{}/{}".format(
|
||||
DATABASE_DIALECT,
|
||||
DATABASE_USER,
|
||||
DATABASE_PASSWORD,
|
||||
DATABASE_HOST,
|
||||
DATABASE_PORT,
|
||||
DATABASE_DB,
|
||||
SQLALCHEMY_DATABASE_URI = (
|
||||
f"{DATABASE_DIALECT}://"
|
||||
f"{DATABASE_USER}:{DATABASE_PASSWORD}@"
|
||||
f"{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_DB}"
|
||||
)
|
||||
|
||||
REDIS_HOST = get_env_variable("REDIS_HOST")
|
||||
REDIS_PORT = get_env_variable("REDIS_PORT")
|
||||
REDIS_CELERY_DB = get_env_variable("REDIS_CELERY_DB", "0")
|
||||
REDIS_RESULTS_DB = get_env_variable("REDIS_RESULTS_DB", "1")
|
||||
SQLALCHEMY_EXAMPLES_URI = (
|
||||
f"{DATABASE_DIALECT}://"
|
||||
f"{EXAMPLES_USER}:{EXAMPLES_PASSWORD}@"
|
||||
f"{EXAMPLES_HOST}:{EXAMPLES_PORT}/{EXAMPLES_DB}"
|
||||
)
|
||||
|
||||
REDIS_HOST = os.getenv("REDIS_HOST", "redis")
|
||||
REDIS_PORT = os.getenv("REDIS_PORT", "6379")
|
||||
REDIS_CELERY_DB = os.getenv("REDIS_CELERY_DB", "0")
|
||||
REDIS_RESULTS_DB = os.getenv("REDIS_RESULTS_DB", "1")
|
||||
|
||||
RESULTS_BACKEND = FileSystemCache("/app/superset_home/sqllab")
|
||||
|
||||
|
||||
2
docs/.gitignore
vendored
2
docs/.gitignore
vendored
@@ -18,3 +18,5 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
docs/.zshrc
|
||||
|
||||
@@ -15,6 +15,8 @@ The Superset project is always happy to review proposals for new high quality vi
|
||||
plugins. However, for highly custom viz types it is recommended to maintain a fork
|
||||
of Superset, and add the custom built viz plugins by hand.
|
||||
|
||||
**Note:** Additional community-generated resources about creating and deploying custom visualization plugins can be found on the [Superset Wiki](https://github.com/apache/superset/wiki/Community-Resource-Library#creating-custom-data-visualizations)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
In order to create a new viz plugin, you need the following:
|
||||
|
||||
@@ -10,13 +10,13 @@ version: 1
|
||||
To use ClickHouse with Superset, you will need to add the following Python library:
|
||||
|
||||
```
|
||||
clickhouse-connect>=0.4.1
|
||||
clickhouse-connect>=0.6.8
|
||||
```
|
||||
|
||||
If running Superset using Docker Compose, add the following to your `./docker/requirements-local.txt` file:
|
||||
|
||||
```
|
||||
clickhouse-connect>=0.4.1
|
||||
clickhouse-connect>=0.6.8
|
||||
```
|
||||
|
||||
The recommended connector library for ClickHouse is
|
||||
|
||||
@@ -22,46 +22,47 @@ as well as the packages needed to connect to the databases you want to access th
|
||||
|
||||
Some of the recommended packages are shown below. Please refer to [setup.py](https://github.com/apache/superset/blob/master/setup.py) for the versions that are compatible with Superset.
|
||||
|
||||
| Database | PyPI package | Connection String |
|
||||
| --------------------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| [Amazon Athena](/docs/databases/athena) | `pip install pyathena[pandas]` , `pip install PyAthenaJDBC` | `awsathena+rest://{aws_access_key_id}:{aws_secret_access_key}@athena.{region_name}.amazonaws.com/{ ` |
|
||||
| [Amazon DynamoDB](/docs/databases/dynamodb) | `pip install pydynamodb` | `dynamodb://{access_key_id}:{secret_access_key}@dynamodb.{region_name}.amazonaws.com?connector=superset` |
|
||||
| [Amazon Redshift](/docs/databases/redshift) | `pip install sqlalchemy-redshift` | ` redshift+psycopg2://<userName>:<DBPassword>@<AWS End Point>:5439/<Database Name>` |
|
||||
| [Apache Drill](/docs/databases/drill) | `pip install sqlalchemy-drill` | `drill+sadrill:// For JDBC drill+jdbc://` |
|
||||
| [Apache Druid](/docs/databases/druid) | `pip install pydruid` | `druid://<User>:<password>@<Host>:<Port-default-9088>/druid/v2/sql` |
|
||||
| [Apache Hive](/docs/databases/hive) | `pip install pyhive` | `hive://hive@{hostname}:{port}/{database}` |
|
||||
| [Apache Impala](/docs/databases/impala) | `pip install impyla` | `impala://{hostname}:{port}/{database}` |
|
||||
| [Apache Kylin](/docs/databases/kylin) | `pip install kylinpy` | `kylin://<username>:<password>@<hostname>:<port>/<project>?<param1>=<value1>&<param2>=<value2>` |
|
||||
| [Apache Pinot](/docs/databases/pinot) | `pip install pinotdb` | `pinot://BROKER:5436/query?server=http://CONTROLLER:5983/` |
|
||||
| [Apache Solr](/docs/databases/solr) | `pip install sqlalchemy-solr` | `solr://{username}:{password}@{hostname}:{port}/{server_path}/{collection}` |
|
||||
| [Apache Spark SQL](/docs/databases/spark-sql) | `pip install pyhive` | `hive://hive@{hostname}:{port}/{database}` |
|
||||
| [Ascend.io](/docs/databases/ascend) | `pip install impyla` | `ascend://{username}:{password}@{hostname}:{port}/{database}?auth_mechanism=PLAIN;use_ssl=true` |
|
||||
| [Azure MS SQL](/docs/databases/sql-server) | `pip install pymssql` | `mssql+pymssql://UserName@presetSQL:TestPassword@presetSQL.database.windows.net:1433/TestSchema` |
|
||||
| [Big Query](/docs/databases/bigquery) | `pip install sqlalchemy-bigquery` | `bigquery://{project_id}` |
|
||||
| [ClickHouse](/docs/databases/clickhouse) | `pip install clickhouse-connect` | `clickhousedb://{username}:{password}@{hostname}:{port}/{database}` |
|
||||
| [CockroachDB](/docs/databases/cockroachdb) | `pip install cockroachdb` | `cockroachdb://root@{hostname}:{port}/{database}?sslmode=disable` |
|
||||
| [Dremio](/docs/databases/dremio) | `pip install sqlalchemy_dremio` | `dremio://user:pwd@host:31010/` |
|
||||
| [Elasticsearch](/docs/databases/elasticsearch) | `pip install elasticsearch-dbapi` | `elasticsearch+http://{user}:{password}@{host}:9200/` |
|
||||
| [Exasol](/docs/databases/exasol) | `pip install sqlalchemy-exasol` | `exa+pyodbc://{username}:{password}@{hostname}:{port}/my_schema?CONNECTIONLCALL=en_US.UTF-8&driver=EXAODBC` |
|
||||
| [Google Sheets](/docs/databases/google-sheets) | `pip install shillelagh[gsheetsapi]` | `gsheets://` |
|
||||
| [Firebolt](/docs/databases/firebolt) | `pip install firebolt-sqlalchemy` | `firebolt://{username}:{password}@{database} or firebolt://{username}:{password}@{database}/{engine_name}` |
|
||||
| [Hologres](/docs/databases/hologres) | `pip install psycopg2` | `postgresql+psycopg2://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [IBM Db2](/docs/databases/ibm-db2) | `pip install ibm_db_sa` | `db2+ibm_db://` |
|
||||
| [IBM Netezza Performance Server](/docs/databases/netezza) | `pip install nzalchemy` | `netezza+nzpy://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [MySQL](/docs/databases/mysql) | `pip install mysqlclient` | `mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [Oracle](/docs/databases/oracle) | `pip install cx_Oracle` | `oracle://` |
|
||||
| [PostgreSQL](/docs/databases/postgres) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [Trino](/docs/databases/trino) | `pip install trino` | `trino://{username}:{password}@{hostname}:{port}/{catalog}` |
|
||||
| [Presto](/docs/databases/presto) | `pip install pyhive` | `presto://` |
|
||||
| [SAP Hana](/docs/databases/hana) | `pip install hdbcli sqlalchemy-hana or pip install apache-superset[hana]` | `hana://{username}:{password}@{host}:{port}` |
|
||||
| [StarRocks](/docs/databases/starrocks) | `pip install starrocks` | `starrocks://<User>:<Password>@<Host>:<Port>/<Catalog>.<Database>` |
|
||||
| [Snowflake](/docs/databases/snowflake) | `pip install snowflake-sqlalchemy` | `snowflake://{user}:{password}@{account}.{region}/{database}?role={role}&warehouse={warehouse}` |
|
||||
| SQLite | No additional library needed | `sqlite://` |
|
||||
| [SQL Server](/docs/databases/sql-server) | `pip install pymssql` | `mssql+pymssql://` |
|
||||
| [Teradata](/docs/databases/teradata) | `pip install teradatasqlalchemy` | `teradatasql://{user}:{password}@{host}` |
|
||||
| [TimescaleDB](/docs/databases/timescaledb) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>:<Port>/<Database Name>` |
|
||||
| [Vertica](/docs/databases/vertica) | `pip install sqlalchemy-vertica-python` | `vertica+vertica_python://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [YugabyteDB](/docs/databases/yugabytedb) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| Database | PyPI package | Connection String |
|
||||
| --------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| [Amazon Athena](/docs/databases/athena) | `pip install pyathena[pandas]` , `pip install PyAthenaJDBC` | `awsathena+rest://{aws_access_key_id}:{aws_secret_access_key}@athena.{region_name}.amazonaws.com/{ ` |
|
||||
| [Amazon DynamoDB](/docs/databases/dynamodb) | `pip install pydynamodb` | `dynamodb://{access_key_id}:{secret_access_key}@dynamodb.{region_name}.amazonaws.com?connector=superset` |
|
||||
| [Amazon Redshift](/docs/databases/redshift) | `pip install sqlalchemy-redshift` | ` redshift+psycopg2://<userName>:<DBPassword>@<AWS End Point>:5439/<Database Name>` |
|
||||
| [Apache Drill](/docs/databases/drill) | `pip install sqlalchemy-drill` | `drill+sadrill:// For JDBC drill+jdbc://` |
|
||||
| [Apache Druid](/docs/databases/druid) | `pip install pydruid` | `druid://<User>:<password>@<Host>:<Port-default-9088>/druid/v2/sql` |
|
||||
| [Apache Hive](/docs/databases/hive) | `pip install pyhive` | `hive://hive@{hostname}:{port}/{database}` |
|
||||
| [Apache Impala](/docs/databases/impala) | `pip install impyla` | `impala://{hostname}:{port}/{database}` |
|
||||
| [Apache Kylin](/docs/databases/kylin) | `pip install kylinpy` | `kylin://<username>:<password>@<hostname>:<port>/<project>?<param1>=<value1>&<param2>=<value2>` |
|
||||
| [Apache Pinot](/docs/databases/pinot) | `pip install pinotdb` | `pinot://BROKER:5436/query?server=http://CONTROLLER:5983/` |
|
||||
| [Apache Solr](/docs/databases/solr) | `pip install sqlalchemy-solr` | `solr://{username}:{password}@{hostname}:{port}/{server_path}/{collection}` |
|
||||
| [Apache Spark SQL](/docs/databases/spark-sql) | `pip install pyhive` | `hive://hive@{hostname}:{port}/{database}` |
|
||||
| [Ascend.io](/docs/databases/ascend) | `pip install impyla` | `ascend://{username}:{password}@{hostname}:{port}/{database}?auth_mechanism=PLAIN;use_ssl=true` |
|
||||
| [Azure MS SQL](/docs/databases/sql-server) | `pip install pymssql` | `mssql+pymssql://UserName@presetSQL:TestPassword@presetSQL.database.windows.net:1433/TestSchema` |
|
||||
| [Big Query](/docs/databases/bigquery) | `pip install sqlalchemy-bigquery` | `bigquery://{project_id}` |
|
||||
| [ClickHouse](/docs/databases/clickhouse) | `pip install clickhouse-connect` | `clickhousedb://{username}:{password}@{hostname}:{port}/{database}` |
|
||||
| [CockroachDB](/docs/databases/cockroachdb) | `pip install cockroachdb` | `cockroachdb://root@{hostname}:{port}/{database}?sslmode=disable` |
|
||||
| [Dremio](/docs/databases/dremio) | `pip install sqlalchemy_dremio` | `dremio://user:pwd@host:31010/` |
|
||||
| [Elasticsearch](/docs/databases/elasticsearch) | `pip install elasticsearch-dbapi` | `elasticsearch+http://{user}:{password}@{host}:9200/` |
|
||||
| [Exasol](/docs/databases/exasol) | `pip install sqlalchemy-exasol` | `exa+pyodbc://{username}:{password}@{hostname}:{port}/my_schema?CONNECTIONLCALL=en_US.UTF-8&driver=EXAODBC` |
|
||||
| [Google Sheets](/docs/databases/google-sheets) | `pip install shillelagh[gsheetsapi]` | `gsheets://` |
|
||||
| [Firebolt](/docs/databases/firebolt) | `pip install firebolt-sqlalchemy` | `firebolt://{username}:{password}@{database} or firebolt://{username}:{password}@{database}/{engine_name}` |
|
||||
| [Hologres](/docs/databases/hologres) | `pip install psycopg2` | `postgresql+psycopg2://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [IBM Db2](/docs/databases/ibm-db2) | `pip install ibm_db_sa` | `db2+ibm_db://` |
|
||||
| [IBM Netezza Performance Server](/docs/databases/netezza) | `pip install nzalchemy` | `netezza+nzpy://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [MySQL](/docs/databases/mysql) | `pip install mysqlclient` | `mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [Oracle](/docs/databases/oracle) | `pip install cx_Oracle` | `oracle://` |
|
||||
| [PostgreSQL](/docs/databases/postgres) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [Trino](/docs/databases/trino) | `pip install trino` | `trino://{username}:{password}@{hostname}:{port}/{catalog}` |
|
||||
| [Presto](/docs/databases/presto) | `pip install pyhive` | `presto://` |
|
||||
| [SAP Hana](/docs/databases/hana) | `pip install hdbcli sqlalchemy-hana or pip install apache-superset[hana]` | `hana://{username}:{password}@{host}:{port}` |
|
||||
| [StarRocks](/docs/databases/starrocks) | `pip install starrocks` | `starrocks://<User>:<Password>@<Host>:<Port>/<Catalog>.<Database>` |
|
||||
| [Snowflake](/docs/databases/snowflake) | `pip install snowflake-sqlalchemy` | `snowflake://{user}:{password}@{account}.{region}/{database}?role={role}&warehouse={warehouse}` |
|
||||
| SQLite | No additional library needed | `sqlite://path/to/file.db?check_same_thread=false` |
|
||||
| [SQL Server](/docs/databases/sql-server) | `pip install pymssql` | `mssql+pymssql://` |
|
||||
| [Teradata](/docs/databases/teradata) | `pip install teradatasqlalchemy` | `teradatasql://{user}:{password}@{host}` |
|
||||
| [TimescaleDB](/docs/databases/timescaledb) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>:<Port>/<Database Name>` |
|
||||
| [Vertica](/docs/databases/vertica) | `pip install sqlalchemy-vertica-python` | `vertica+vertica_python://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [YugabyteDB](/docs/databases/yugabytedb) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
|
||||
---
|
||||
|
||||
Note that many other databases are supported, the main criteria being the existence of a functional
|
||||
|
||||
@@ -168,7 +168,7 @@ Another workaround is to change where superset stores the sqlite database by add
|
||||
`superset_config.py`:
|
||||
|
||||
```
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:////new/location/superset.db'
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:////new/location/superset.db?check_same_thread=false'
|
||||
```
|
||||
|
||||
You can read more about customizing Superset using the configuration file
|
||||
@@ -282,3 +282,9 @@ guarantees and are not recommended but may fit your use case temporarily:
|
||||
In the Edit Dataset view, you can specify a time offset. This field lets you configure the
|
||||
number of hours to be added or subtracted from the time column.
|
||||
This can be used, for example, to convert UTC time to local time.
|
||||
|
||||
### Does Superset collect any telemetry data?
|
||||
|
||||
Superset uses [Scarf](https://about.scarf.sh/) by default to collect basic telemetry data upon installing and/or running Superset. This data helps the maintainers of Superset better understand which versions of Superset are being used, in order to prioritize patch/minor releases and security fixes.
|
||||
We use the [Scarf Gateway](https://docs.scarf.sh/gateway/) to sit in front of container registries, and the [scarf-js](https://about.scarf.sh/package-sdks) package to track `npm` installations.
|
||||
Scarf purges PII and provides aggregated statistics. Superset users can easily opt out of analytics in various ways documented [here](https://docs.scarf.sh/gateway/#do-not-track) and [here](https://docs.scarf.sh/package-analytics/#as-a-user-of-a-package-using-scarf-js-how-can-i-opt-out-of-analytics). Additional opt-out instructions for Docker users are available on the [Docker Installation](https://superset.apache.org/docs/installation/installing-superset-using-docker-compose) page.
|
||||
|
||||
@@ -66,7 +66,7 @@ celery --app=superset.tasks.celery_app:app beat
|
||||
```
|
||||
|
||||
To setup a result backend, you need to pass an instance of a derivative of from
|
||||
cachelib.base.BaseCache to the RESULTS_BACKEND configuration key in your superset_config.py. You can
|
||||
from flask_caching.backends.base import BaseCache to the RESULTS_BACKEND configuration key in your superset_config.py. You can
|
||||
use Memcached, Redis, S3 (https://pypi.python.org/pypi/s3werkzeugcache), memory or the file system
|
||||
(in a single server-type setup or for testing), or to write your own caching interface. Your
|
||||
`superset_config.py` may look something like:
|
||||
@@ -79,7 +79,7 @@ S3_CACHE_KEY_PREFIX = 'sql_lab_result'
|
||||
RESULTS_BACKEND = S3Cache(S3_CACHE_BUCKET, S3_CACHE_KEY_PREFIX)
|
||||
|
||||
# On Redis
|
||||
from cachelib.redis import RedisCache
|
||||
from flask_caching.backends.rediscache import RedisCache
|
||||
RESULTS_BACKEND = RedisCache(
|
||||
host='localhost', port=6379, key_prefix='superset_results')
|
||||
```
|
||||
|
||||
@@ -13,6 +13,7 @@ To configure your application, you need to create a file `superset_config.py` an
|
||||
`PYTHONPATH`. If your application was installed using docker-compose an alternative configuration is required. See [https://github.com/apache/superset/tree/master/docker#readme](https://github.com/apache/superset/tree/master/docker#readme) for details.
|
||||
|
||||
The following is an example of just a few of the parameters you can set in your `superset_config.py` file:
|
||||
|
||||
```
|
||||
# Superset specific config
|
||||
ROW_LIMIT = 5000
|
||||
@@ -31,7 +32,9 @@ SECRET_KEY = 'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY'
|
||||
# superset metadata (slices, connections, tables, dashboards, ...).
|
||||
# Note that the connection information to connect to the datasources
|
||||
# you want to explore are managed directly in the web UI
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:////path/to/superset.db'
|
||||
# The check_same_thread=false property ensures the sqlite client does not attempt
|
||||
# to enforce single-threaded access, which may be problematic in some edge cases
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:////path/to/superset.db?check_same_thread=false'
|
||||
|
||||
# Flask-WTF flag for CSRF
|
||||
WTF_CSRF_ENABLED = True
|
||||
@@ -72,7 +75,7 @@ WTF_CSRF_EXEMPT_LIST = [‘’]
|
||||
|
||||
#### Adding an initial SECRET_KEY
|
||||
|
||||
Superset requires a user-specified SECRET_KEY to start up. This requirement was [added in version 2.1.0 to force secure configurations](https://preset.io/blog/superset-security-update-default-secret_key-vulnerability/). Add a strong SECRET_KEY to your `superset_config.py` file like:
|
||||
Superset requires a user-specified SECRET_KEY to start up. This requirement was [added in version 2.1.0 to force secure configurations](https://preset.io/blog/superset-security-update-default-secret_key-vulnerability/). Add a strong SECRET_KEY to your `superset_config.py` file like:
|
||||
|
||||
```python
|
||||
SECRET_KEY = 'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY'
|
||||
@@ -83,7 +86,7 @@ You can generate a strong secure key with `openssl rand -base64 42`.
|
||||
#### Rotating to a newer SECRET_KEY
|
||||
|
||||
If you wish to change your existing SECRET_KEY, add the existing SECRET_KEY to your `superset_config.py` file as
|
||||
`PREVIOUS_SECRET_KEY = `and provide your new key as `SECRET_KEY =`. You can find your current SECRET_KEY with these
|
||||
`PREVIOUS_SECRET_KEY = `and provide your new key as `SECRET_KEY =`. You can find your current SECRET_KEY with these
|
||||
commands - if running Superset with Docker, execute from within the Superset application container:
|
||||
|
||||
```python
|
||||
@@ -103,23 +106,21 @@ database engine on a separate host or container.
|
||||
|
||||
Superset supports the following database engines/versions:
|
||||
|
||||
| Database Engine | Supported Versions |
|
||||
| --------------------------------------------------------- | --------------------------------- |
|
||||
| [PostgreSQL](https://www.postgresql.org/) | 10.X, 11.X, 12.X, 13.X, 14.X |
|
||||
| [MySQL](https://www.mysql.com/) | 5.X |
|
||||
|
||||
| Database Engine | Supported Versions |
|
||||
| ----------------------------------------- | ---------------------------------- |
|
||||
| [PostgreSQL](https://www.postgresql.org/) | 10.X, 11.X, 12.X, 13.X, 14.X, 15.X |
|
||||
| [MySQL](https://www.mysql.com/) | 5.7, 8.X |
|
||||
|
||||
Use the following database drivers and connection strings:
|
||||
|
||||
| Database | PyPI package | Connection String |
|
||||
| ----------------------------------------- | --------------------------------- | ------------------------------------------------------------------------ |
|
||||
| [PostgreSQL](https://www.postgresql.org/) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [MySQL](https://www.mysql.com/) | `pip install mysqlclient` | `mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| Database | PyPI package | Connection String |
|
||||
| ----------------------------------------- | ------------------------- | ---------------------------------------------------------------------- |
|
||||
| [PostgreSQL](https://www.postgresql.org/) | `pip install psycopg2` | `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
| [MySQL](https://www.mysql.com/) | `pip install mysqlclient` | `mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>` |
|
||||
|
||||
To configure Superset metastore set `SQLALCHEMY_DATABASE_URI` config key on `superset_config`
|
||||
to the appropriate connection string.
|
||||
|
||||
|
||||
### Running on a WSGI HTTP Server
|
||||
|
||||
While you can run Superset on NGINX or Apache, we recommend using Gunicorn in async mode. This
|
||||
|
||||
@@ -96,7 +96,14 @@ You can configure the Docker Compose environment varirables for dev and non-dev
|
||||
|
||||
One important variable is `SUPERSET_LOAD_EXAMPLES` which determines whether the `superset_init` container will load example data and visualizations into the database and Superset. These examples are quite helpful for most people, but probably unnecessary for experienced users. The loading process can sometimes take a few minutes and a good amount of CPU, so you may want to disable it on a resource-constrained device.
|
||||
|
||||
**Note:** Users often want to connect to other databases from Superset. Currently, the easiest way to do this is to modify the `docker-compose-non-dev.yml` file and add your database as a service that the other services depend on (via `x-superset-depends-on`). Others have attempted to set `network_mode: host` on the Superset services, but these generally break the installation, because the configuration requires use of the Docker Compose DNS resolver for the service names. If you have a good solution for this, let us know!
|
||||
|
||||
:::note
|
||||
Users often want to connect to other databases from Superset. Currently, the easiest way to do this is to modify the `docker-compose-non-dev.yml` file and add your database as a service that the other services depend on (via `x-superset-depends-on`). Others have attempted to set `network_mode: host` on the Superset services, but these generally break the installation, because the configuration requires use of the Docker Compose DNS resolver for the service names. If you have a good solution for this, let us know!
|
||||
:::
|
||||
|
||||
:::note
|
||||
Superset uses [Scarf Gateway](https://about.scarf.sh/scarf-gateway) to collect telmetry data to better understand and support the need for patch versions of Sueprset. Scarf purges PII and provides aggregated statistics. Superset users can easily opt out of analytics in various ways documented [here](https://docs.scarf.sh/gateway/#do-not-track). However, if you wish to opt-out of this in your Docker-based installation, you can simply edit your `docker-compose.yml` or `docker-compose-non-dev.yml` file and remove `apachesuperset.docker.scarf.sh/` from the `x-superset-image` setting, so that it's simply pulling `apache/superset:${TAG:-latest-dev}`
|
||||
:::
|
||||
|
||||
### 4. Log in to Superset
|
||||
|
||||
|
||||
@@ -121,6 +121,10 @@ init:
|
||||
. {{ .Values.configMountPath }}/superset_init.sh
|
||||
```
|
||||
|
||||
:::note
|
||||
Superset uses [Scarf Gateway](https://about.scarf.sh/scarf-gateway) to collect telmetry data to better understand and support the need for patch versions of Sueprset. Scarf purges PII and provides aggregated statistics. Superset users can easily opt out of analytics in various ways documented [here](https://docs.scarf.sh/gateway/#do-not-track). However, if you wish to opt-out of this in your Helm-based installation, you can simply edit your `helm/superset/values.yaml` file and remove `apachesuperset.docker.scarf.sh/` from the `repository` field, so that it's simply pulling `apache/superset`
|
||||
:::
|
||||
|
||||
#### Dependencies
|
||||
|
||||
Install additional packages and do any other bootstrap configuration in the bootstrap script.
|
||||
|
||||
4
docs/docs/security/_category_.json
Normal file
4
docs/docs/security/_category_.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Security",
|
||||
"position": 10
|
||||
}
|
||||
27
docs/docs/security/cves.mdx
Normal file
27
docs/docs/security/cves.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: CVEs by release
|
||||
hide_title: true
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
#### Version 2.1.0
|
||||
|
||||
| CVE | Title | Affected |
|
||||
| :------------- | :---------------------------------------------------------------------- | -----------------:|
|
||||
| CVE-2023-25504 | Possible SSRF on import datasets | <= 2.1.0 |
|
||||
| CVE-2023-27524 | Session validation vulnerability when using provided default SECRET_KEY | <= 2.1.0 |
|
||||
| CVE-2023-27525 | Incorrect default permissions for Gamma role | <= 2.1.0 |
|
||||
| CVE-2023-30776 | Database connection password leak | <= 2.1.0 |
|
||||
|
||||
|
||||
#### Version 2.0.1
|
||||
|
||||
| CVE | Title | Affected |
|
||||
| :------------- | :---------------------------------------------------------- | -----------------:|
|
||||
| CVE-2022-41703 | SQL injection vulnerability in adhoc clauses | < 2.0.1 or <1.5.2 |
|
||||
| CVE-2022-43717 | Cross-Site Scripting on dashboards | < 2.0.1 or <1.5.2 |
|
||||
| CVE-2022-43718 | Cross-Site Scripting vulnerability on upload forms | < 2.0.1 or <1.5.2 |
|
||||
| CVE-2022-43719 | Cross Site Request Forgery (CSRF) on accept, request access | < 2.0.1 or <1.5.2 |
|
||||
| CVE-2022-43720 | Improper rendering of user input | < 2.0.1 or <1.5.2 |
|
||||
| CVE-2022-43721 | Open Redirect Vulnerability | < 2.0.1 or <1.5.2 |
|
||||
| CVE-2022-45438 | Dashboard metadata information leak | < 2.0.1 or <1.5.2 |
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Security
|
||||
title: Role based Access
|
||||
hide_title: true
|
||||
sidebar_position: 10
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
### Roles
|
||||
@@ -68,7 +68,7 @@ const config = {
|
||||
from: '/usertutorial.html',
|
||||
},
|
||||
{
|
||||
to: '/docs/security',
|
||||
to: '/docs/security/',
|
||||
from: '/security.html',
|
||||
},
|
||||
{
|
||||
|
||||
5026
docs/static/resources/openapi.json
vendored
5026
docs/static/resources/openapi.json
vendored
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
apiVersion: v2
|
||||
appVersion: "2.1.0"
|
||||
appVersion: "3.0.3"
|
||||
description: Apache Superset is a modern, enterprise-ready business intelligence web application
|
||||
name: superset
|
||||
icon: https://artifacthub.io/image/68c1d717-0e97-491f-b046-754e46f46922@2x
|
||||
@@ -29,7 +29,7 @@ maintainers:
|
||||
- name: craig-rueda
|
||||
email: craig@craigrueda.com
|
||||
url: https://github.com/craig-rueda
|
||||
version: 0.10.3
|
||||
version: 0.11.2
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: 12.1.6
|
||||
|
||||
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
|
||||
|
||||
# superset
|
||||
|
||||

|
||||

|
||||
|
||||
Apache Superset is a modern, enterprise-ready business intelligence web application
|
||||
|
||||
@@ -40,6 +40,12 @@ helm repo add superset http://apache.github.io/superset/
|
||||
helm install my-superset superset/superset
|
||||
```
|
||||
|
||||
Make sure you set your own `SECRET_KEY` to something unique and secret. This secret key is used by Flask for
|
||||
securely signing the session cookie and will be used to encrypt sensitive data on Superset's metadata database.
|
||||
It should be a long random bytes or str.
|
||||
|
||||
On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverrides.secrets`
|
||||
|
||||
## Requirements
|
||||
|
||||
| Repository | Name | Version |
|
||||
@@ -70,7 +76,7 @@ helm install my-superset superset/superset
|
||||
| fullnameOverride | string | `nil` | Provide a name to override the full names of resources |
|
||||
| hostAliases | list | `[]` | Custom hostAliases for all superset pods # https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/ |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||
| image.repository | string | `"apache/superset"` | |
|
||||
| image.repository | string | `"apachesuperset.docker.scarf.sh/apache/superset"` | |
|
||||
| image.tag | string | `""` | |
|
||||
| imagePullSecrets | list | `[]` | |
|
||||
| ingress.annotations | object | `{}` | |
|
||||
|
||||
@@ -39,6 +39,12 @@ helm repo add superset http://apache.github.io/superset/
|
||||
helm install my-superset superset/superset
|
||||
```
|
||||
|
||||
Make sure you set your own `SECRET_KEY` to something unique and secret. This secret key is used by Flask for
|
||||
securely signing the session cookie and will be used to encrypt sensitive data on Superset's metadata database.
|
||||
It should be a long random bytes or str.
|
||||
|
||||
On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverrides.secrets`
|
||||
|
||||
{{ template "chart.requirementsSection" . }}
|
||||
|
||||
{{ template "chart.valuesSection" . }}
|
||||
|
||||
@@ -63,7 +63,7 @@ Create chart name and version as used by the chart label.
|
||||
|
||||
{{- define "superset-config" }}
|
||||
import os
|
||||
from cachelib.redis import RedisCache
|
||||
from flask_caching.backends.rediscache import RedisCache
|
||||
|
||||
def env(key, default=None):
|
||||
return os.getenv(key, default)
|
||||
@@ -82,7 +82,6 @@ DATA_CACHE_CONFIG = CACHE_CONFIG
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{env('DB_USER')}:{env('DB_PASS')}@{env('DB_HOST')}:{env('DB_PORT')}/{env('DB_NAME')}"
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = True
|
||||
SECRET_KEY = env('SECRET_KEY', 'thisISaSECRET_1234')
|
||||
|
||||
class CeleryConfig(object):
|
||||
CELERY_IMPORTS = ('superset.sql_lab', )
|
||||
|
||||
@@ -42,6 +42,7 @@ spec:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/superset_config.py: {{ include "superset-config" . | sha256sum }}
|
||||
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
|
||||
checksum/connections: {{ .Values.supersetNode.connections | toYaml | sha256sum }}
|
||||
checksum/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
|
||||
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
|
||||
|
||||
@@ -46,6 +46,7 @@ spec:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/superset_config.py: {{ include "superset-config" . | sha256sum }}
|
||||
checksum/superset_bootstrap.sh: {{ tpl .Values.bootstrapScript . | sha256sum }}
|
||||
checksum/connections: {{ .Values.supersetNode.connections | toYaml | sha256sum }}
|
||||
checksum/extraConfigs: {{ .Values.extraConfigs | toYaml | sha256sum }}
|
||||
checksum/extraSecrets: {{ .Values.extraSecrets | toYaml | sha256sum }}
|
||||
|
||||
@@ -63,7 +63,7 @@ spec:
|
||||
name: {{ tpl .Values.envFromSecret . }}
|
||||
{{- range .Values.envFromSecrets }}
|
||||
- secretRef:
|
||||
name: {{ tpl . $ }}
|
||||
name: {{ tpl . $ | quote }}
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- if .Values.init.containerSecurityContext }}
|
||||
|
||||
@@ -93,6 +93,8 @@ extraSecretEnv: {}
|
||||
# # Google API Keys: https://console.cloud.google.com/apis/credentials
|
||||
# GOOGLE_KEY: ...
|
||||
# GOOGLE_SECRET: ...
|
||||
# # Generate your own secret key for encryption. Use openssl rand -base64 42 to generate a good key
|
||||
# SUPERSET_SECRET_KEY: 'CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET'
|
||||
|
||||
# -- Extra files to mount on `/app/pythonpath`
|
||||
extraConfigs: {}
|
||||
@@ -176,7 +178,7 @@ configMountPath: "/app/pythonpath"
|
||||
extraConfigMountPath: "/app/configs"
|
||||
|
||||
image:
|
||||
repository: apache/superset
|
||||
repository: apachesuperset.docker.scarf.sh/apache/superset
|
||||
tag: ""
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
|
||||
@@ -27,8 +27,9 @@ billiard==3.6.4.0
|
||||
# via celery
|
||||
brotli==1.0.9
|
||||
# via flask-compress
|
||||
cachelib==0.4.1
|
||||
# via apache-superset
|
||||
cachelib==0.9.0
|
||||
# via
|
||||
# flask-caching
|
||||
celery==5.2.2
|
||||
# via apache-superset
|
||||
cffi==1.15.1
|
||||
@@ -88,11 +89,11 @@ flask==2.2.5
|
||||
# flask-migrate
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
flask-appbuilder==4.3.3
|
||||
flask-appbuilder==4.3.10
|
||||
# via apache-superset
|
||||
flask-babel==1.0.0
|
||||
# via flask-appbuilder
|
||||
flask-caching==1.10.1
|
||||
flask-caching==2.1.0
|
||||
# via apache-superset
|
||||
flask-compress==1.13
|
||||
# via apache-superset
|
||||
@@ -112,7 +113,7 @@ flask-sqlalchemy==2.5.1
|
||||
# flask-migrate
|
||||
flask-talisman==1.0.0
|
||||
# via apache-superset
|
||||
flask-wtf==1.0.1
|
||||
flask-wtf==1.1.1
|
||||
# via
|
||||
# apache-superset
|
||||
# flask-appbuilder
|
||||
@@ -126,7 +127,7 @@ gunicorn==20.1.0
|
||||
# via apache-superset
|
||||
hashids==1.3.1
|
||||
# via apache-superset
|
||||
hijri-converter==2.2.4
|
||||
hijri-converter==2.3.1
|
||||
# via holidays
|
||||
holidays==0.23
|
||||
# via apache-superset
|
||||
@@ -135,9 +136,7 @@ humanize==3.11.0
|
||||
idna==3.2
|
||||
# via email-validator
|
||||
importlib-metadata==6.6.0
|
||||
# via
|
||||
# apache-superset
|
||||
# flask
|
||||
# via apache-superset
|
||||
importlib-resources==5.12.0
|
||||
# via limits
|
||||
isodate==0.6.0
|
||||
@@ -154,7 +153,7 @@ jsonschema==4.17.3
|
||||
# via flask-appbuilder
|
||||
kombu==5.2.4
|
||||
# via celery
|
||||
korean-lunar-calendar==0.2.1
|
||||
korean-lunar-calendar==0.3.1
|
||||
# via holidays
|
||||
limits==3.4.0
|
||||
# via flask-limiter
|
||||
@@ -212,7 +211,7 @@ prison==0.2.1
|
||||
# via flask-appbuilder
|
||||
prompt-toolkit==3.0.38
|
||||
# via click-repl
|
||||
pyarrow==12.0.0
|
||||
pyarrow==14.0.1
|
||||
# via apache-superset
|
||||
pycparser==2.20
|
||||
# via cffi
|
||||
@@ -223,7 +222,7 @@ pyjwt==2.4.0
|
||||
# apache-superset
|
||||
# flask-appbuilder
|
||||
# flask-jwt-extended
|
||||
pymeeus==0.5.11
|
||||
pymeeus==0.5.12
|
||||
# via convertdate
|
||||
pynacl==1.5.0
|
||||
# via paramiko
|
||||
@@ -251,7 +250,7 @@ pytz==2021.3
|
||||
# celery
|
||||
# flask-babel
|
||||
# pandas
|
||||
pyyaml==5.4.1
|
||||
pyyaml==6.0.1
|
||||
# via
|
||||
# apache-superset
|
||||
# apispec
|
||||
@@ -311,6 +310,7 @@ werkzeug==2.3.3
|
||||
# via
|
||||
# apache-superset
|
||||
# flask
|
||||
# flask-appbuilder
|
||||
# flask-jwt-extended
|
||||
# flask-login
|
||||
wrapt==1.12.1
|
||||
@@ -326,9 +326,7 @@ wtforms-json==0.3.5
|
||||
xlsxwriter==3.0.7
|
||||
# via apache-superset
|
||||
zipp==3.15.0
|
||||
# via
|
||||
# importlib-metadata
|
||||
# importlib-resources
|
||||
# via importlib-metadata
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# setuptools
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
# -r requirements/development.in
|
||||
appnope==0.1.3
|
||||
# via ipython
|
||||
astroid==2.6.6
|
||||
# via pylint
|
||||
asttokens==2.2.1
|
||||
|
||||
@@ -38,11 +38,11 @@ pip-compile-multi==2.6.3
|
||||
# via -r integration.in
|
||||
pip-tools==6.13.0
|
||||
# via pip-compile-multi
|
||||
platformdirs==3.5.3
|
||||
platformdirs==3.8.1
|
||||
# via
|
||||
# tox
|
||||
# virtualenv
|
||||
pluggy==1.0.0
|
||||
pluggy==1.2.0
|
||||
# via tox
|
||||
pre-commit==3.3.3
|
||||
# via -r integration.in
|
||||
@@ -50,12 +50,12 @@ pyproject-api==1.5.2
|
||||
# via tox
|
||||
pyproject-hooks==1.0.0
|
||||
# via build
|
||||
pyyaml==5.4.1
|
||||
pyyaml==6.0.1
|
||||
# via pre-commit
|
||||
toposort==1.10
|
||||
# via pip-compile-multi
|
||||
tox==4.6.3
|
||||
# via -r integration.in
|
||||
tox==4.6.4
|
||||
# via -r requirements/integration.in
|
||||
virtualenv==20.23.1
|
||||
# via
|
||||
# pre-commit
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#
|
||||
-r development.in
|
||||
-r integration.in
|
||||
-e file:.[bigquery,hive,presto,trino]
|
||||
-e file:.[bigquery,hive,presto,prophet,trino]
|
||||
docker
|
||||
flask-testing
|
||||
freezegun
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SHA1:623feb0dd2b6bd376238ecf75069bc82136c2d70
|
||||
# SHA1:78fe89f88adf34ac75513d363d7d9d0b5cc8cd1c
|
||||
#
|
||||
# This file is autogenerated by pip-compile-multi
|
||||
# To update, run:
|
||||
@@ -12,16 +12,26 @@
|
||||
# -r requirements/base.in
|
||||
# -r requirements/development.in
|
||||
# -r requirements/testing.in
|
||||
cmdstanpy==1.1.0
|
||||
# via prophet
|
||||
contourpy==1.0.7
|
||||
# via matplotlib
|
||||
coverage[toml]==7.2.5
|
||||
# via pytest-cov
|
||||
cycler==0.11.0
|
||||
# via matplotlib
|
||||
db-dtypes==1.1.1
|
||||
# via pandas-gbq
|
||||
docker==6.1.1
|
||||
# via -r requirements/testing.in
|
||||
ephem==4.1.4
|
||||
# via lunarcalendar
|
||||
exceptiongroup==1.1.1
|
||||
# via pytest
|
||||
flask-testing==0.8.1
|
||||
# via -r requirements/testing.in
|
||||
fonttools==4.39.4
|
||||
# via matplotlib
|
||||
freezegun==1.2.2
|
||||
# via -r requirements/testing.in
|
||||
google-api-core[grpc]==2.11.0
|
||||
@@ -73,6 +83,12 @@ iniconfig==2.0.0
|
||||
# via pytest
|
||||
jsonschema-spec==0.1.4
|
||||
# via openapi-spec-validator
|
||||
kiwisolver==1.4.4
|
||||
# via matplotlib
|
||||
lunarcalendar==0.0.9
|
||||
# via prophet
|
||||
matplotlib==3.7.1
|
||||
# via prophet
|
||||
oauthlib==3.2.2
|
||||
# via requests-oauthlib
|
||||
openapi-schema-validator==0.4.4
|
||||
@@ -85,6 +101,8 @@ parameterized==0.9.0
|
||||
# via -r requirements/testing.in
|
||||
pathable==0.4.3
|
||||
# via jsonschema-spec
|
||||
prophet==1.1.1
|
||||
# via apache-superset
|
||||
proto-plus==1.22.2
|
||||
# via
|
||||
# google-cloud-bigquery
|
||||
@@ -97,12 +115,6 @@ protobuf==4.23.0
|
||||
# googleapis-common-protos
|
||||
# grpcio-status
|
||||
# proto-plus
|
||||
pyasn1==0.5.0
|
||||
# via
|
||||
# pyasn1-modules
|
||||
# rsa
|
||||
pyasn1-modules==0.3.0
|
||||
# via google-auth
|
||||
pydata-google-auth==1.7.0
|
||||
# via pandas-gbq
|
||||
pyfakefs==5.2.2
|
||||
@@ -126,10 +138,16 @@ rfc3339-validator==0.1.4
|
||||
# via openapi-schema-validator
|
||||
rsa==4.9
|
||||
# via google-auth
|
||||
setuptools-git==1.2
|
||||
# via prophet
|
||||
sqlalchemy-bigquery==1.6.1
|
||||
# via apache-superset
|
||||
statsd==4.0.1
|
||||
# via -r requirements/testing.in
|
||||
tqdm==4.65.0
|
||||
# via
|
||||
# cmdstanpy
|
||||
# prophet
|
||||
trino==0.324.0
|
||||
# via apache-superset
|
||||
tzdata==2023.3
|
||||
|
||||
@@ -30,7 +30,7 @@ combine_as_imports = true
|
||||
include_trailing_comma = true
|
||||
line_length = 88
|
||||
known_first_party = superset
|
||||
known_third_party =alembic,apispec,backoff,cachelib,celery,click,colorama,cron_descriptor,croniter,cryptography,dateutil,deprecation,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_jwt_extended,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,freezegun,geohash,geopy,holidays,humanize,isodate,jinja2,jwt,markdown,markupsafe,marshmallow,msgpack,nh3,numpy,pandas,parameterized,parsedatetime,pgsanity,pkg_resources,polyline,prison,progress,pyarrow,sqlalchemy_bigquery,pyhive,pyparsing,pytest,pytest_mock,pytz,redis,requests,selenium,setuptools,simplejson,slack,sqlalchemy,sqlalchemy_utils,sqlparse,typing_extensions,urllib3,werkzeug,wtforms,wtforms_json,yaml
|
||||
known_third_party =alembic,apispec,backoff,celery,click,colorama,cron_descriptor,croniter,cryptography,dateutil,deprecation,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_jwt_extended,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,freezegun,geohash,geopy,holidays,humanize,isodate,jinja2,jwt,markdown,markupsafe,marshmallow,msgpack,nh3,numpy,pandas,parameterized,parsedatetime,pgsanity,pkg_resources,polyline,prison,progress,pyarrow,sqlalchemy_bigquery,pyhive,pyparsing,pytest,pytest_mock,pytz,redis,requests,selenium,setuptools,simplejson,slack,sqlalchemy,sqlalchemy_utils,sqlparse,typing_extensions,urllib3,werkzeug,wtforms,wtforms_json,yaml
|
||||
multi_line_output = 3
|
||||
order_by_type = false
|
||||
|
||||
|
||||
19
setup.py
19
setup.py
@@ -71,7 +71,6 @@ setup(
|
||||
},
|
||||
install_requires=[
|
||||
"backoff>=1.8.0",
|
||||
"cachelib>=0.4.1,<0.5",
|
||||
"celery>=5.2.2, <6.0.0",
|
||||
"click>=8.0.3",
|
||||
"click-option-group",
|
||||
@@ -81,13 +80,13 @@ setup(
|
||||
"cryptography>=39.0.1, <40",
|
||||
"deprecation>=2.1.0, <2.2.0",
|
||||
"flask>=2.2.5, <3.0.0",
|
||||
"flask-appbuilder>=4.3.3, <5.0.0",
|
||||
"flask-caching>=1.10.1, <1.11",
|
||||
"flask-appbuilder>=4.3.10, <5.0.0",
|
||||
"flask-caching>=2.1.0, <3",
|
||||
"flask-compress>=1.13, <2.0",
|
||||
"flask-talisman>=1.0.0, <2.0",
|
||||
"flask-login==0.6.0",
|
||||
"flask-login>=0.6.0, < 1.0",
|
||||
"flask-migrate>=3.1.0, <4.0",
|
||||
"flask-wtf>=1.0.1, <1.1",
|
||||
"flask-wtf>=1.1.0, <2.0",
|
||||
"func_timeout",
|
||||
"geopy",
|
||||
"gunicorn>=20.1.0; sys_platform != 'win32'",
|
||||
@@ -110,8 +109,8 @@ setup(
|
||||
"python-dateutil",
|
||||
"python-dotenv",
|
||||
"python-geohash",
|
||||
"pyarrow>=12.0.0, <13",
|
||||
"pyyaml>=5.4",
|
||||
"pyarrow>=14.0.1, <15",
|
||||
"pyyaml>=6.0.0, <7.0.0",
|
||||
"PyJWT>=2.4.0, <3.0",
|
||||
"redis>=4.5.4, <5.0",
|
||||
"selenium>=3.141.0, <4.10.0",
|
||||
@@ -167,7 +166,7 @@ setup(
|
||||
"mysql": ["mysqlclient>=2.1.0, <3"],
|
||||
"ocient": [
|
||||
"sqlalchemy-ocient>=1.0.0",
|
||||
"pyocient>=1.0.15",
|
||||
"pyocient>=1.0.15, <2",
|
||||
"shapely",
|
||||
"geojson",
|
||||
],
|
||||
@@ -176,7 +175,7 @@ setup(
|
||||
"postgres": ["psycopg2-binary==2.9.6"],
|
||||
"presto": ["pyhive[presto]>=0.6.5"],
|
||||
"trino": ["trino>=0.324.0"],
|
||||
"prophet": ["prophet>=1.0.1, <1.1", "pystan<3.0"],
|
||||
"prophet": ["prophet==1.1.1"],
|
||||
"redshift": ["sqlalchemy-redshift>=0.8.1, < 0.9"],
|
||||
"rockset": ["rockset>=0.8.10, <0.9"],
|
||||
"shillelagh": [
|
||||
@@ -185,7 +184,7 @@ setup(
|
||||
"snowflake": ["snowflake-sqlalchemy>=1.2.4, <2"],
|
||||
"spark": ["pyhive[hive]>=0.6.5", "tableschema", "thrift>=0.14.1, <1.0.0"],
|
||||
"teradata": ["teradatasql>=16.20.0.23"],
|
||||
"thumbnails": ["Pillow>=9.5.0, <10.0.0"],
|
||||
"thumbnails": ["Pillow>=10.0.1, <11"],
|
||||
"vertica": ["sqlalchemy-vertica-python>=0.5.9, < 0.6"],
|
||||
"netezza": ["nzalchemy>=11.0.2"],
|
||||
"starrocks": ["starrocks>=1.0.0"],
|
||||
|
||||
4
superset-embedded-sdk/package-lock.json
generated
4
superset-embedded-sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@superset-ui/embedded-sdk",
|
||||
"version": "0.1.0-alpha.9",
|
||||
"version": "0.1.0-alpha.10",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@superset-ui/embedded-sdk",
|
||||
"version": "0.1.0-alpha.9",
|
||||
"version": "0.1.0-alpha.10",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@superset-ui/switchboard": "^0.18.26-0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@superset-ui/embedded-sdk",
|
||||
"version": "0.1.0-alpha.9",
|
||||
"version": "0.1.0-alpha.10",
|
||||
"description": "SDK for embedding resources from Superset into your own application",
|
||||
"access": "public",
|
||||
"keywords": [
|
||||
|
||||
@@ -89,6 +89,10 @@ export async function embedDashboard({
|
||||
|
||||
log('embedding');
|
||||
|
||||
if (supersetDomain.endsWith("/")) {
|
||||
supersetDomain = supersetDomain.slice(0, -1);
|
||||
}
|
||||
|
||||
function calculateConfig() {
|
||||
let configNumber = 0
|
||||
if(dashboardUiConfig) {
|
||||
|
||||
@@ -38,6 +38,23 @@ export default defineConfig({
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents(on, config) {
|
||||
// ECONNRESET on Chrome/Chromium 117.0.5851.0 when using Cypress <12.15.0
|
||||
// Check https://github.com/cypress-io/cypress/issues/27804 for context
|
||||
// TODO: This workaround should be removed when upgrading Cypress
|
||||
on('before:browser:launch', (browser, launchOptions) => {
|
||||
if (browser.name === 'chrome' && browser.isHeadless) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
launchOptions.args = launchOptions.args.map(arg => {
|
||||
if (arg === '--headless') {
|
||||
return '--headless=new';
|
||||
}
|
||||
|
||||
return arg;
|
||||
});
|
||||
}
|
||||
return launchOptions;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line global-require,import/extensions
|
||||
return require('./cypress/plugins/index.js')(on, config);
|
||||
},
|
||||
|
||||
@@ -422,17 +422,17 @@ describe('Dashboard edit', () => {
|
||||
'[data-test-chart-name="Top 10 California Names Timeseries"] .line .nv-legend-symbol',
|
||||
)
|
||||
.first()
|
||||
.should('have.css', 'fill', 'rgb(252, 199, 0)');
|
||||
.should('have.css', 'fill', 'rgb(69, 78, 124)');
|
||||
cy.get(
|
||||
'[data-test-chart-name="Top 10 California Names Timeseries"] .line .nv-legend-symbol',
|
||||
)
|
||||
.eq(1)
|
||||
.should('have.css', 'fill', 'rgb(143, 211, 228)');
|
||||
.should('have.css', 'fill', 'rgb(224, 67, 85)');
|
||||
cy.get(
|
||||
'[data-test-chart-name="Top 10 California Names Timeseries"] .line .nv-legend-symbol',
|
||||
)
|
||||
.eq(2)
|
||||
.should('have.css', 'fill', 'rgb(172, 225, 196)');
|
||||
.should('have.css', 'fill', 'rgb(163, 143, 121)');
|
||||
});
|
||||
|
||||
it('should show the same colors in Explore', () => {
|
||||
@@ -463,7 +463,7 @@ describe('Dashboard edit', () => {
|
||||
'[data-test-chart-name="Top 10 California Names Timeseries"] .line .nv-legend-symbol',
|
||||
)
|
||||
.eq(1)
|
||||
.should('have.css', 'fill', 'rgb(51, 61, 71)');
|
||||
.should('have.css', 'fill', 'rgb(172, 32, 119)');
|
||||
|
||||
openExplore('Top 10 California Names Timeseries');
|
||||
|
||||
@@ -474,7 +474,7 @@ describe('Dashboard edit', () => {
|
||||
// label Christopher
|
||||
cy.get('[data-test="chart-container"] .line .nv-legend-symbol')
|
||||
.eq(1)
|
||||
.should('have.css', 'fill', 'rgb(108, 131, 142)');
|
||||
.should('have.css', 'fill', 'rgb(172, 32, 119)');
|
||||
});
|
||||
|
||||
it('should change color scheme multiple times', () => {
|
||||
@@ -515,7 +515,7 @@ describe('Dashboard edit', () => {
|
||||
// label Anthony
|
||||
cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol')
|
||||
.eq(2)
|
||||
.should('have.css', 'fill', 'rgb(0, 122, 135)');
|
||||
.should('have.css', 'fill', 'rgb(244, 176, 42)');
|
||||
|
||||
// open main tab and nested tab
|
||||
openTab(0, 0);
|
||||
@@ -526,7 +526,7 @@ describe('Dashboard edit', () => {
|
||||
'[data-test-chart-name="Top 10 California Names Timeseries"] .line .nv-legend-symbol',
|
||||
)
|
||||
.first()
|
||||
.should('have.css', 'fill', 'rgb(0, 122, 135)');
|
||||
.should('have.css', 'fill', 'rgb(244, 176, 42)');
|
||||
});
|
||||
|
||||
it('should apply the color scheme across main tabs', () => {
|
||||
@@ -557,7 +557,7 @@ describe('Dashboard edit', () => {
|
||||
|
||||
cy.get('[data-test-chart-name="Trends"] .line .nv-legend-symbol')
|
||||
.first()
|
||||
.should('have.css', 'fill', 'rgb(204, 0, 134)');
|
||||
.should('have.css', 'fill', 'rgb(156, 52, 152)');
|
||||
|
||||
// change scheme now that charts are rendered across the main tabs
|
||||
editDashboard();
|
||||
|
||||
@@ -113,7 +113,7 @@ function prepareDashboardFilters(
|
||||
},
|
||||
type: 'NATIVE_FILTER',
|
||||
description: '',
|
||||
chartsInScope: [6],
|
||||
chartsInScope: [5],
|
||||
tabsInScope: [],
|
||||
});
|
||||
});
|
||||
@@ -150,7 +150,7 @@ function prepareDashboardFilters(
|
||||
meta: {
|
||||
width: 4,
|
||||
height: 50,
|
||||
chartId: 6,
|
||||
chartId: 5,
|
||||
sliceName: 'Most Populated Countries',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,7 +25,6 @@ import { TABBED_DASHBOARD } from 'cypress/utils/urls';
|
||||
import { expandFilterOnLeftPanel } from './utils';
|
||||
|
||||
const TREEMAP = { name: 'Treemap', viz: 'treemap_v2' };
|
||||
const FILTER_BOX = { name: 'Region Filter', viz: 'filter_box' };
|
||||
const LINE_CHART = { name: 'Growth Rate', viz: 'line' };
|
||||
const BOX_PLOT = { name: 'Box plot', viz: 'box_plot' };
|
||||
const BIG_NUMBER = { name: 'Number of Girls', viz: 'big_number_total' };
|
||||
@@ -41,7 +40,6 @@ function topLevelTabs() {
|
||||
function resetTabs() {
|
||||
topLevelTabs();
|
||||
cy.get('@top-level-tabs').first().click();
|
||||
waitForChartLoad(FILTER_BOX);
|
||||
waitForChartLoad(TREEMAP);
|
||||
waitForChartLoad(BIG_NUMBER);
|
||||
waitForChartLoad(TABLE);
|
||||
@@ -96,7 +94,6 @@ describe('Dashboard tabs', () => {
|
||||
|
||||
it.skip('should send new queries when tab becomes visible', () => {
|
||||
// landing in first tab
|
||||
waitForChartLoad(FILTER_BOX);
|
||||
waitForChartLoad(TREEMAP);
|
||||
|
||||
getChartAliasBySpec(TREEMAP).then(treemapAlias => {
|
||||
|
||||
@@ -23,7 +23,6 @@ import { ChartSpec, waitForChartLoad } from 'cypress/utils';
|
||||
export const WORLD_HEALTH_CHARTS = [
|
||||
{ name: '% Rural', viz: 'world_map' },
|
||||
{ name: 'Most Populated Countries', viz: 'table' },
|
||||
{ name: 'Region Filter', viz: 'filter_box' },
|
||||
{ name: "World's Population", viz: 'big_number' },
|
||||
{ name: 'Growth Rate', viz: 'line' },
|
||||
{ name: 'Rural Breakdown', viz: 'sunburst' },
|
||||
@@ -322,7 +321,7 @@ export function applyNativeFilterValueWithIndex(index: number, value: string) {
|
||||
cy.get(nativeFilters.filterFromDashboardView.filterValueInput)
|
||||
.eq(index)
|
||||
.should('exist', { timeout: 10000 })
|
||||
.type(`${value}{enter}`);
|
||||
.type(`${value}{enter}`, { force: true });
|
||||
// click the title to dismiss shown options
|
||||
cy.get(nativeFilters.filterFromDashboardView.filterName)
|
||||
.eq(index)
|
||||
|
||||
@@ -62,8 +62,8 @@ describe('Add database', () => {
|
||||
it('show error alerts on dynamic form for bad host', () => {
|
||||
// click postgres dynamic form
|
||||
cy.get('.preferred > :nth-child(1)').click();
|
||||
cy.get('input[name="host"]').focus().type('badhost');
|
||||
cy.get('input[name="port"]').focus().type('5432');
|
||||
cy.get('input[name="host"]').focus().type('badhost', { force: true });
|
||||
cy.get('input[name="port"]').focus().type('5432', { force: true });
|
||||
cy.get('.ant-form-item-explain-error').contains(
|
||||
"The hostname provided can't be resolved",
|
||||
);
|
||||
@@ -72,8 +72,8 @@ describe('Add database', () => {
|
||||
it('show error alerts on dynamic form for bad port', () => {
|
||||
// click postgres dynamic form
|
||||
cy.get('.preferred > :nth-child(1)').click();
|
||||
cy.get('input[name="host"]').focus().type('localhost');
|
||||
cy.get('input[name="port"]').focus().type('123');
|
||||
cy.get('input[name="host"]').focus().type('localhost', { force: true });
|
||||
cy.get('input[name="port"]').focus().type('123', { force: true });
|
||||
cy.get('input[name="database"]').focus();
|
||||
cy.get('.ant-form-item-explain-error').contains('The port is closed');
|
||||
});
|
||||
|
||||
@@ -39,6 +39,7 @@ describe('Advanced analytics', () => {
|
||||
|
||||
cy.get('[data-test=time_compare]')
|
||||
.find('input[type=search]')
|
||||
.clear()
|
||||
.type('1 year{enter}');
|
||||
|
||||
cy.get('button[data-test="run-query-button"]').click();
|
||||
|
||||
@@ -32,13 +32,13 @@ function openDashboardsAddedTo() {
|
||||
cy.getBySel('actions-trigger').click();
|
||||
cy.get('.ant-dropdown-menu-submenu-title')
|
||||
.contains('Dashboards added to')
|
||||
.trigger('mouseover');
|
||||
.trigger('mouseover', { force: true });
|
||||
}
|
||||
|
||||
function closeDashboardsAddedTo() {
|
||||
cy.get('.ant-dropdown-menu-submenu-title')
|
||||
.contains('Dashboards added to')
|
||||
.trigger('mouseout');
|
||||
.trigger('mouseout', { force: true });
|
||||
cy.getBySel('actions-trigger').click();
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,6 @@ describe('Visualization > Distribution bar chart', () => {
|
||||
).should('exist');
|
||||
cy.get('.dist_bar .nv-legend .nv-legend-symbol')
|
||||
.first()
|
||||
.should('have.css', 'fill', 'rgb(255, 90, 95)');
|
||||
.should('have.css', 'fill', 'rgb(41, 105, 107)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('Visualization > Line', () => {
|
||||
).should('exist');
|
||||
cy.get('.line .nv-legend .nv-legend-symbol')
|
||||
.first()
|
||||
.should('have.css', 'fill', 'rgb(255, 90, 95)');
|
||||
.should('have.css', 'fill', 'rgb(41, 105, 107)');
|
||||
});
|
||||
|
||||
it('should work with adhoc metric', () => {
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// timezone for unit tests
|
||||
process.env.TZ = 'America/New_York';
|
||||
|
||||
module.exports = {
|
||||
testRegex:
|
||||
'\\/superset-frontend\\/(spec|src|plugins|packages|tools)\\/.*(_spec|\\.test)\\.[jt]sx?$',
|
||||
|
||||
14
superset-frontend/package-lock.json
generated
14
superset-frontend/package-lock.json
generated
@@ -21,6 +21,7 @@
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@fontsource/inter": "^4.0.0",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@scarf/scarf": "^1.1.1",
|
||||
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
|
||||
"@superset-ui/core": "file:./packages/superset-ui-core",
|
||||
"@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar",
|
||||
@@ -256,7 +257,7 @@
|
||||
"less-loader": "^10.2.0",
|
||||
"mini-css-extract-plugin": "^2.3.0",
|
||||
"mock-socket": "^9.0.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"prettier": "^2.4.1",
|
||||
"prettier-plugin-packagejson": "^2.2.15",
|
||||
"process": "^0.11.10",
|
||||
@@ -13827,6 +13828,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@scarf/scarf": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.1.1.tgz",
|
||||
"integrity": "sha512-VGbKDbk1RFIaSmdVb0cNjjWJoRWRI/Weo23AjRCC2nryO0iAS8pzsToJfPVPtVs74WHw4L1UTADNdIYRLkirZQ==",
|
||||
"hasInstallScript": true
|
||||
},
|
||||
"node_modules/@sinonjs/commons": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
|
||||
@@ -72301,6 +72308,11 @@
|
||||
"any-observable": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"@scarf/scarf": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.1.1.tgz",
|
||||
"integrity": "sha512-VGbKDbk1RFIaSmdVb0cNjjWJoRWRI/Weo23AjRCC2nryO0iAS8pzsToJfPVPtVs74WHw4L1UTADNdIYRLkirZQ=="
|
||||
},
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superset",
|
||||
"version": "0.0.0-dev",
|
||||
"version": "3.0.3",
|
||||
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
||||
"keywords": [
|
||||
"big",
|
||||
@@ -86,6 +86,7 @@
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@fontsource/inter": "^4.0.0",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@scarf/scarf": "^1.1.1",
|
||||
"@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls",
|
||||
"@superset-ui/core": "file:./packages/superset-ui-core",
|
||||
"@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar",
|
||||
@@ -321,7 +322,7 @@
|
||||
"less-loader": "^10.2.0",
|
||||
"mini-css-extract-plugin": "^2.3.0",
|
||||
"mock-socket": "^9.0.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"prettier": "^2.4.1",
|
||||
"prettier-plugin-packagejson": "^2.2.15",
|
||||
"process": "^0.11.10",
|
||||
@@ -357,5 +358,8 @@
|
||||
}
|
||||
},
|
||||
"readme": "ERROR: No README data found!",
|
||||
"scarfSettings": {
|
||||
"allowTopLevel": true
|
||||
},
|
||||
"_id": "superset@0.0.0-dev"
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ const TypeIconWrapper = styled.div`
|
||||
&& svg {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`};
|
||||
`;
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Slider, InputNumber, Input } from 'antd';
|
||||
import Checkbox, { CheckboxProps } from 'antd/lib/checkbox';
|
||||
import Select, { SelectOption } from '../Select';
|
||||
import RadioButtonControl, {
|
||||
RadioButtonOption,
|
||||
} from '../../shared-controls/components/RadioButtonControl';
|
||||
|
||||
export const ControlFormItemComponents = {
|
||||
Slider,
|
||||
InputNumber,
|
||||
Input,
|
||||
Select,
|
||||
// Directly export Checkbox will result in "using name from external module" error
|
||||
// ref: https://stackoverflow.com/questions/43900035/ts4023-exported-variable-x-has-or-is-using-name-y-from-external-module-but
|
||||
Checkbox: Checkbox as React.ForwardRefExoticComponent<
|
||||
CheckboxProps & React.RefAttributes<HTMLInputElement>
|
||||
>,
|
||||
RadioButtonControl,
|
||||
};
|
||||
|
||||
export type ControlType = keyof typeof ControlFormItemComponents;
|
||||
|
||||
export type ControlFormValueValidator<V> = (value: V) => string | false;
|
||||
|
||||
export type ControlFormItemSpec<T extends ControlType = ControlType> = {
|
||||
controlType: T;
|
||||
label: ReactNode;
|
||||
description: ReactNode;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
validators?: ControlFormValueValidator<any>[];
|
||||
width?: number | string;
|
||||
/**
|
||||
* Time to delay change propagation.
|
||||
*/
|
||||
debounceDelay?: number;
|
||||
} & (T extends 'Select'
|
||||
? {
|
||||
options: SelectOption<any>[];
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
creatable?: boolean;
|
||||
minWidth?: number | string;
|
||||
validators?: ControlFormValueValidator<string>[];
|
||||
}
|
||||
: T extends 'RadioButtonControl'
|
||||
? {
|
||||
options: RadioButtonOption[];
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
: T extends 'Checkbox'
|
||||
? {
|
||||
value?: boolean;
|
||||
defaultValue?: boolean;
|
||||
}
|
||||
: T extends 'InputNumber' | 'Slider'
|
||||
? {
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
value?: number;
|
||||
defaultValue?: number;
|
||||
validators?: ControlFormValueValidator<number>[];
|
||||
}
|
||||
: T extends 'Input'
|
||||
? {
|
||||
controlType: 'Input';
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
validators?: ControlFormValueValidator<string>[];
|
||||
}
|
||||
: {});
|
||||
@@ -16,22 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { FeatureFlag, FeatureFlagMap } from '@superset-ui/core';
|
||||
import { styled } from '@superset-ui/core';
|
||||
|
||||
export function initFeatureFlags(featureFlags?: FeatureFlagMap) {
|
||||
if (!window.featureFlags) {
|
||||
window.featureFlags = featureFlags || {};
|
||||
}
|
||||
}
|
||||
|
||||
export function isFeatureEnabled(feature: FeatureFlag) {
|
||||
try {
|
||||
return !!window.featureFlags[feature];
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Failed to query feature flag ${feature} (see error below)`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export const ControlSubSectionHeader = styled.div`
|
||||
font-weight: ${({ theme }) => theme.typography.weights.bold};
|
||||
font-size: ${({ theme }) => theme.typography.sizes.s};
|
||||
margin-bottom: ${({ theme }) => theme.gridUnit}px;
|
||||
`;
|
||||
export default ControlSubSectionHeader;
|
||||
@@ -17,15 +17,16 @@
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
t,
|
||||
QueryMode,
|
||||
DTTM_ALIAS,
|
||||
GenericDataType,
|
||||
QueryColumn,
|
||||
DatasourceType,
|
||||
QueryMode,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import { ColumnMeta, SortSeriesData, SortSeriesType } from './types';
|
||||
|
||||
export const DEFAULT_MAX_ROW = 100000;
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const TIME_FILTER_LABELS = {
|
||||
time_range: t('Time Range'),
|
||||
@@ -41,6 +42,7 @@ export const COLUMN_NAME_ALIASES: Record<string, string> = {
|
||||
export const DATASET_TIME_COLUMN_OPTION: ColumnMeta = {
|
||||
verbose_name: COLUMN_NAME_ALIASES[DTTM_ALIAS],
|
||||
column_name: DTTM_ALIAS,
|
||||
type: 'TIMESTAMP',
|
||||
type_generic: GenericDataType.TEMPORAL,
|
||||
description: t(
|
||||
'A reference to the [Time] configuration, taking granularity into account',
|
||||
@@ -49,8 +51,9 @@ export const DATASET_TIME_COLUMN_OPTION: ColumnMeta = {
|
||||
|
||||
export const QUERY_TIME_COLUMN_OPTION: QueryColumn = {
|
||||
column_name: DTTM_ALIAS,
|
||||
type: DatasourceType.Query,
|
||||
is_dttm: false,
|
||||
is_dttm: true,
|
||||
type: 'TIMESTAMP',
|
||||
type_generic: GenericDataType.TEMPORAL,
|
||||
};
|
||||
|
||||
export const QueryModeLabel = {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { DatasourceType } from '@superset-ui/core';
|
||||
import { DatasourceType, GenericDataType } from '@superset-ui/core';
|
||||
import { Dataset } from './types';
|
||||
|
||||
export const TestDataset: Dataset = {
|
||||
@@ -37,7 +37,7 @@ export const TestDataset: Dataset = {
|
||||
is_dttm: false,
|
||||
python_date_format: null,
|
||||
type: 'BIGINT',
|
||||
type_generic: 0,
|
||||
type_generic: GenericDataType.NUMERIC,
|
||||
verbose_name: null,
|
||||
warning_markdown: null,
|
||||
},
|
||||
@@ -55,7 +55,7 @@ export const TestDataset: Dataset = {
|
||||
is_dttm: false,
|
||||
python_date_format: null,
|
||||
type: 'VARCHAR(16)',
|
||||
type_generic: 1,
|
||||
type_generic: GenericDataType.STRING,
|
||||
verbose_name: '',
|
||||
warning_markdown: null,
|
||||
},
|
||||
@@ -73,7 +73,7 @@ export const TestDataset: Dataset = {
|
||||
is_dttm: false,
|
||||
python_date_format: null,
|
||||
type: 'VARCHAR(10)',
|
||||
type_generic: 1,
|
||||
type_generic: GenericDataType.STRING,
|
||||
verbose_name: null,
|
||||
warning_markdown: null,
|
||||
},
|
||||
@@ -91,7 +91,7 @@ export const TestDataset: Dataset = {
|
||||
is_dttm: true,
|
||||
python_date_format: null,
|
||||
type: 'TIMESTAMP WITHOUT TIME ZONE',
|
||||
type_generic: 2,
|
||||
type_generic: GenericDataType.TEMPORAL,
|
||||
verbose_name: null,
|
||||
warning_markdown: null,
|
||||
},
|
||||
@@ -109,7 +109,7 @@ export const TestDataset: Dataset = {
|
||||
is_dttm: false,
|
||||
python_date_format: null,
|
||||
type: 'VARCHAR(255)',
|
||||
type_generic: 1,
|
||||
type_generic: GenericDataType.STRING,
|
||||
verbose_name: null,
|
||||
warning_markdown: null,
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ export * from './components/InfoTooltipWithTrigger';
|
||||
export * from './components/ColumnOption';
|
||||
export * from './components/ColumnTypeLabel/ColumnTypeLabel';
|
||||
export * from './components/MetricOption';
|
||||
export * from './components/ControlSubSectionHeader';
|
||||
|
||||
export * from './shared-controls';
|
||||
export * from './types';
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { t, RollingType, ComparisonType } from '@superset-ui/core';
|
||||
|
||||
import { ControlSubSectionHeader } from '../components/ControlSubSectionHeader';
|
||||
import { ControlPanelSectionConfig } from '../types';
|
||||
import { formatSelectOptions } from '../utils';
|
||||
|
||||
@@ -30,7 +32,7 @@ export const advancedAnalyticsControls: ControlPanelSectionConfig = {
|
||||
'of query results',
|
||||
),
|
||||
controlSetRows: [
|
||||
[<div className="section-header">{t('Rolling window')}</div>],
|
||||
[<ControlSubSectionHeader>{t('Rolling window')}</ControlSubSectionHeader>],
|
||||
[
|
||||
{
|
||||
name: 'rolling_type',
|
||||
@@ -99,7 +101,7 @@ export const advancedAnalyticsControls: ControlPanelSectionConfig = {
|
||||
},
|
||||
},
|
||||
],
|
||||
[<div className="section-header">{t('Time comparison')}</div>],
|
||||
[<ControlSubSectionHeader>{t('Time comparison')}</ControlSubSectionHeader>],
|
||||
[
|
||||
{
|
||||
name: 'time_compare',
|
||||
@@ -150,7 +152,7 @@ export const advancedAnalyticsControls: ControlPanelSectionConfig = {
|
||||
},
|
||||
},
|
||||
],
|
||||
[<div className="section-header">{t('Resample')}</div>],
|
||||
[<ControlSubSectionHeader>{t('Resample')}</ControlSubSectionHeader>],
|
||||
[
|
||||
{
|
||||
name: 'resample_rule',
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import { ControlSubSectionHeader } from '../components/ControlSubSectionHeader';
|
||||
import { ControlPanelSectionConfig } from '../types';
|
||||
import { formatSelectOptions } from '../utils';
|
||||
|
||||
@@ -33,7 +35,7 @@ export const titleControls: ControlPanelSectionConfig = {
|
||||
tabOverride: 'customize',
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[<div className="section-header">{t('X Axis')}</div>],
|
||||
[<ControlSubSectionHeader>{t('X Axis')}</ControlSubSectionHeader>],
|
||||
[
|
||||
{
|
||||
name: 'x_axis_title',
|
||||
@@ -61,7 +63,7 @@ export const titleControls: ControlPanelSectionConfig = {
|
||||
},
|
||||
},
|
||||
],
|
||||
[<div className="section-header">{t('Y Axis')}</div>],
|
||||
[<ControlSubSectionHeader>{t('Y Axis')}</ControlSubSectionHeader>],
|
||||
[
|
||||
{
|
||||
name: 'y_axis_title',
|
||||
@@ -81,7 +83,7 @@ export const titleControls: ControlPanelSectionConfig = {
|
||||
type: 'SelectControl',
|
||||
freeForm: true,
|
||||
clearable: true,
|
||||
label: t('Y AXIS TITLE MARGIN'),
|
||||
label: t('Y Axis Title Margin'),
|
||||
renderTrigger: true,
|
||||
default: TITLE_MARGIN_OPTIONS[0],
|
||||
choices: formatSelectOptions(TITLE_MARGIN_OPTIONS),
|
||||
@@ -96,7 +98,7 @@ export const titleControls: ControlPanelSectionConfig = {
|
||||
type: 'SelectControl',
|
||||
freeForm: true,
|
||||
clearable: false,
|
||||
label: t('Y AXIS TITLE POSITION'),
|
||||
label: t('Y Axis Title Position'),
|
||||
renderTrigger: true,
|
||||
default: TITLE_POSITION_OPTIONS[0][0],
|
||||
choices: TITLE_POSITION_OPTIONS,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { hasGenericChartAxes, t } from '@superset-ui/core';
|
||||
import { ControlPanelSectionConfig, ControlSetRow } from '../types';
|
||||
import {
|
||||
contributionModeControl,
|
||||
xAxisForceCategoricalControl,
|
||||
xAxisSortAscControl,
|
||||
xAxisSortControl,
|
||||
xAxisSortSeriesAscendingControl,
|
||||
@@ -55,6 +56,7 @@ export const echartsTimeSeriesQueryWithXAxisSort: ControlPanelSectionConfig = {
|
||||
controlSetRows: [
|
||||
[hasGenericChartAxes ? 'x_axis' : null],
|
||||
[hasGenericChartAxes ? 'time_grain_sqla' : null],
|
||||
[hasGenericChartAxes ? xAxisForceCategoricalControl : null],
|
||||
[hasGenericChartAxes ? xAxisSortControl : null],
|
||||
[hasGenericChartAxes ? xAxisSortAscControl : null],
|
||||
[hasGenericChartAxes ? xAxisSortSeriesControl : null],
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { GenericDataType } from '@superset-ui/core';
|
||||
import ControlForm, {
|
||||
ControlFormRow,
|
||||
ControlFormItem,
|
||||
ControlFormItemSpec,
|
||||
} from '../../../components/ControlForm';
|
||||
import {
|
||||
SHARED_COLUMN_CONFIG_PROPS,
|
||||
SharedColumnConfigProp,
|
||||
} from './constants';
|
||||
import {
|
||||
ColumnConfig,
|
||||
ColumnConfigFormLayout,
|
||||
ColumnConfigInfo,
|
||||
} from './types';
|
||||
|
||||
export type ColumnConfigPopoverProps = {
|
||||
column: ColumnConfigInfo;
|
||||
configFormLayout: ColumnConfigFormLayout;
|
||||
onChange: (value: ColumnConfig) => void;
|
||||
};
|
||||
|
||||
export default function ColumnConfigPopover({
|
||||
column,
|
||||
configFormLayout,
|
||||
onChange,
|
||||
}: ColumnConfigPopoverProps) {
|
||||
return (
|
||||
<ControlForm onChange={onChange} value={column.config}>
|
||||
{configFormLayout[
|
||||
column.type === undefined ? GenericDataType.STRING : column.type
|
||||
].map((row, i) => (
|
||||
<ControlFormRow key={i}>
|
||||
{row.map(meta => {
|
||||
const key = typeof meta === 'string' ? meta : meta.name;
|
||||
const override =
|
||||
typeof meta === 'string'
|
||||
? {}
|
||||
: 'override' in meta
|
||||
? meta.override
|
||||
: meta.config;
|
||||
const props = {
|
||||
...(key in SHARED_COLUMN_CONFIG_PROPS
|
||||
? SHARED_COLUMN_CONFIG_PROPS[key as SharedColumnConfigProp]
|
||||
: undefined),
|
||||
...override,
|
||||
} as ControlFormItemSpec;
|
||||
return <ControlFormItem key={key} name={key} {...props} />;
|
||||
})}
|
||||
</ControlFormRow>
|
||||
))}
|
||||
</ControlForm>
|
||||
);
|
||||
}
|
||||
@@ -17,10 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import RadioButtonControl from './RadioButtonControl';
|
||||
import ColumnConfigControl from './ColumnConfigControl';
|
||||
|
||||
export * from './RadioButtonControl';
|
||||
export * from './ColumnConfigControl';
|
||||
|
||||
/**
|
||||
* Shared chart controls. Can be referred via string shortcuts in chart control
|
||||
@@ -28,5 +26,4 @@ export * from './ColumnConfigControl';
|
||||
*/
|
||||
export default {
|
||||
RadioButtonControl,
|
||||
ColumnConfigControl,
|
||||
};
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
import {
|
||||
ContributionType,
|
||||
ensureIsArray,
|
||||
GenericDataType,
|
||||
getColumnLabel,
|
||||
getMetricLabel,
|
||||
isDefined,
|
||||
QueryFormColumn,
|
||||
QueryFormMetric,
|
||||
t,
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
DEFAULT_XAXIS_SORT_SERIES_DATA,
|
||||
SORT_SERIES_CHOICES,
|
||||
} from '../constants';
|
||||
import { checkColumnType } from '../utils/checkColumnType';
|
||||
|
||||
export const contributionModeControl = {
|
||||
name: 'contributionMode',
|
||||
@@ -54,18 +55,29 @@ export const contributionModeControl = {
|
||||
},
|
||||
};
|
||||
|
||||
function isTemporal(controls: ControlStateMapping): boolean {
|
||||
return !(
|
||||
isDefined(controls?.x_axis?.value) &&
|
||||
!isTemporalColumn(
|
||||
function isForcedCategorical(controls: ControlStateMapping): boolean {
|
||||
return (
|
||||
checkColumnType(
|
||||
getColumnLabel(controls?.x_axis?.value as QueryFormColumn),
|
||||
controls?.datasource?.datasource,
|
||||
[GenericDataType.NUMERIC],
|
||||
) && !!controls?.xAxisForceCategorical?.value
|
||||
);
|
||||
}
|
||||
|
||||
function isSortable(controls: ControlStateMapping): boolean {
|
||||
return (
|
||||
isForcedCategorical(controls) ||
|
||||
checkColumnType(
|
||||
getColumnLabel(controls?.x_axis?.value as QueryFormColumn),
|
||||
controls?.datasource?.datasource,
|
||||
[GenericDataType.STRING, GenericDataType.BOOLEAN],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const xAxisSortVisibility = ({ controls }: { controls: ControlStateMapping }) =>
|
||||
!isTemporal(controls) &&
|
||||
isSortable(controls) &&
|
||||
ensureIsArray(controls?.groupby?.value).length === 0 &&
|
||||
ensureIsArray(controls?.metrics?.value).length === 1;
|
||||
|
||||
@@ -74,7 +86,7 @@ const xAxisMultiSortVisibility = ({
|
||||
}: {
|
||||
controls: ControlStateMapping;
|
||||
}) =>
|
||||
!isTemporal(controls) &&
|
||||
isSortable(controls) &&
|
||||
(!!ensureIsArray(controls?.groupby?.value).length ||
|
||||
ensureIsArray(controls?.metrics?.value).length > 1);
|
||||
|
||||
@@ -141,7 +153,29 @@ export const xAxisSortAscControl = {
|
||||
: t('X-Axis Sort Ascending'),
|
||||
default: true,
|
||||
description: t('Whether to sort ascending or descending on the base Axis.'),
|
||||
visibility: xAxisSortVisibility,
|
||||
visibility: ({ controls }: { controls: ControlStateMapping }) =>
|
||||
controls?.x_axis_sort?.value !== undefined &&
|
||||
xAxisSortVisibility({ controls }),
|
||||
},
|
||||
};
|
||||
|
||||
export const xAxisForceCategoricalControl = {
|
||||
name: 'xAxisForceCategorical',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: () => t('Force categorical'),
|
||||
default: false,
|
||||
description: t('Treat values as categorical.'),
|
||||
initialValue: (control: ControlState, state: ControlPanelState | null) =>
|
||||
state?.form_data?.x_axis_sort !== undefined || control.value,
|
||||
renderTrigger: true,
|
||||
visibility: ({ controls }: { controls: ControlStateMapping }) =>
|
||||
checkColumnType(
|
||||
getColumnLabel(controls?.x_axis?.value as QueryFormColumn),
|
||||
controls?.datasource?.datasource,
|
||||
[GenericDataType.NUMERIC],
|
||||
),
|
||||
shouldMapStateToProps: () => true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -173,6 +207,8 @@ export const xAxisSortSeriesAscendingControl = {
|
||||
default: DEFAULT_XAXIS_SORT_SERIES_DATA.sort_series_ascending,
|
||||
description: t('Whether to sort ascending or descending on the base Axis.'),
|
||||
renderTrigger: true,
|
||||
visibility: xAxisMultiSortVisibility,
|
||||
visibility: ({ controls }: { controls: ControlStateMapping }) =>
|
||||
controls?.x_axis_sort_series?.value !== undefined &&
|
||||
xAxisMultiSortVisibility({ controls }),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
isDefined,
|
||||
hasGenericChartAxes,
|
||||
NO_TIME_RANGE,
|
||||
validateMaxValue,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
import {
|
||||
@@ -58,7 +59,7 @@ import {
|
||||
DEFAULT_TIME_FORMAT,
|
||||
DEFAULT_NUMBER_FORMAT,
|
||||
} from '../utils';
|
||||
import { TIME_FILTER_LABELS } from '../constants';
|
||||
import { DEFAULT_MAX_ROW, TIME_FILTER_LABELS } from '../constants';
|
||||
import {
|
||||
SharedControlConfig,
|
||||
Dataset,
|
||||
@@ -245,7 +246,12 @@ const row_limit: SharedControlConfig<'SelectControl'> = {
|
||||
type: 'SelectControl',
|
||||
freeForm: true,
|
||||
label: t('Row limit'),
|
||||
validators: [legacyValidateInteger],
|
||||
clearable: false,
|
||||
validators: [
|
||||
legacyValidateInteger,
|
||||
(v, state) =>
|
||||
validateMaxValue(v, state?.common?.conf?.SQL_MAX_ROW || DEFAULT_MAX_ROW),
|
||||
],
|
||||
default: 10000,
|
||||
choices: formatSelectOptions(ROW_LIMIT_OPTIONS),
|
||||
description: t('Limits the number of rows that get displayed.'),
|
||||
@@ -317,6 +323,12 @@ const y_axis_format: SharedControlConfig<'SelectControl', SelectDefaultOption> =
|
||||
},
|
||||
};
|
||||
|
||||
const currency_format: SharedControlConfig<'CurrencyControl'> = {
|
||||
type: 'CurrencyControl',
|
||||
label: t('Currency format'),
|
||||
renderTrigger: true,
|
||||
};
|
||||
|
||||
const x_axis_time_format: SharedControlConfig<
|
||||
'SelectControl',
|
||||
SelectDefaultOption
|
||||
@@ -406,4 +418,5 @@ export default {
|
||||
x_axis: dndXAxisControl,
|
||||
show_empty_columns,
|
||||
temporal_columns_lookup,
|
||||
currency_format,
|
||||
};
|
||||
|
||||
@@ -34,7 +34,6 @@ import type {
|
||||
import { sharedControls, sharedControlComponents } from './shared-controls';
|
||||
|
||||
export type { Metric } from '@superset-ui/core';
|
||||
export type { ControlFormItemSpec } from './components/ControlForm';
|
||||
export type { ControlComponentProps } from './shared-controls/components/types';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -167,6 +166,12 @@ export type InternalControlType =
|
||||
| 'DndColumnSelect'
|
||||
| 'DndFilterSelect'
|
||||
| 'DndMetricSelect'
|
||||
| 'CurrencyControl'
|
||||
| 'InputNumber'
|
||||
| 'Checkbox'
|
||||
| 'Select'
|
||||
| 'Slider'
|
||||
| 'Input'
|
||||
| keyof SharedControlComponents; // expanded in `expandControlConfig`
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -474,13 +479,15 @@ export function isControlPanelSectionConfig(
|
||||
export function isDataset(
|
||||
datasource: Dataset | QueryResponse | null | undefined,
|
||||
): datasource is Dataset {
|
||||
return !!datasource && 'columns' in datasource;
|
||||
return (
|
||||
!!datasource && 'columns' in datasource && !('sqlEditorId' in datasource)
|
||||
);
|
||||
}
|
||||
|
||||
export function isQueryResponse(
|
||||
datasource: Dataset | QueryResponse | null | undefined,
|
||||
): datasource is QueryResponse {
|
||||
return !!datasource && 'results' in datasource && 'sql' in datasource;
|
||||
return !!datasource && 'results' in datasource && 'sqlEditorId' in datasource;
|
||||
}
|
||||
|
||||
export enum SortSeriesType {
|
||||
@@ -495,3 +502,60 @@ export type SortSeriesData = {
|
||||
sort_series_type: SortSeriesType;
|
||||
sort_series_ascending: boolean;
|
||||
};
|
||||
|
||||
export type ControlFormValueValidator<V> = (value: V) => string | false;
|
||||
|
||||
export type ControlFormItemSpec<T extends ControlType = ControlType> = {
|
||||
controlType: T;
|
||||
label: ReactNode;
|
||||
description: ReactNode;
|
||||
placeholder?: string;
|
||||
validators?: ControlFormValueValidator<any>[];
|
||||
width?: number | string;
|
||||
/**
|
||||
* Time to delay change propagation.
|
||||
*/
|
||||
debounceDelay?: number;
|
||||
} & (T extends 'Select'
|
||||
? {
|
||||
options: any;
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
creatable?: boolean;
|
||||
minWidth?: number | string;
|
||||
validators?: ControlFormValueValidator<string>[];
|
||||
}
|
||||
: T extends 'RadioButtonControl'
|
||||
? {
|
||||
options: [string, ReactNode][];
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
: T extends 'Checkbox'
|
||||
? {
|
||||
value?: boolean;
|
||||
defaultValue?: boolean;
|
||||
}
|
||||
: T extends 'InputNumber' | 'Slider'
|
||||
? {
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
value?: number;
|
||||
defaultValue?: number;
|
||||
validators?: ControlFormValueValidator<number>[];
|
||||
}
|
||||
: T extends 'Input'
|
||||
? {
|
||||
controlType: 'Input';
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
validators?: ControlFormValueValidator<string>[];
|
||||
}
|
||||
: T extends 'CurrencyControl'
|
||||
? {
|
||||
controlType: 'CurrencyControl';
|
||||
value?: Currency;
|
||||
defaultValue?: Currency;
|
||||
}
|
||||
: {});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { ensureIsArray, GenericDataType, ValueOf } from '@superset-ui/core';
|
||||
import {
|
||||
ControlPanelState,
|
||||
isDataset,
|
||||
isQueryResponse,
|
||||
} from '@superset-ui/chart-controls';
|
||||
|
||||
export function checkColumnType(
|
||||
columnName: string,
|
||||
datasource: ValueOf<Pick<ControlPanelState, 'datasource'>>,
|
||||
columnTypes: GenericDataType[],
|
||||
): boolean {
|
||||
if (isDataset(datasource)) {
|
||||
return ensureIsArray(datasource.columns).some(
|
||||
c =>
|
||||
c.type_generic !== undefined &&
|
||||
columnTypes.includes(c.type_generic) &&
|
||||
columnName === c.column_name,
|
||||
);
|
||||
}
|
||||
if (isQueryResponse(datasource)) {
|
||||
return ensureIsArray(datasource.columns)
|
||||
.filter(
|
||||
c =>
|
||||
c.type_generic !== undefined && columnTypes.includes(c.type_generic),
|
||||
)
|
||||
.map(c => c.column_name)
|
||||
.some(c => columnName === c);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { QueryResponse } from '@superset-ui/core';
|
||||
import { Dataset, isColumnMeta, isDataset } from '../types';
|
||||
import { Dataset, isDataset, isQueryResponse } from '../types';
|
||||
|
||||
/**
|
||||
* Convert Datasource columns to column choices
|
||||
@@ -25,11 +25,13 @@ import { Dataset, isColumnMeta, isDataset } from '../types';
|
||||
export default function columnChoices(
|
||||
datasource?: Dataset | QueryResponse | null,
|
||||
): [string, string][] {
|
||||
if (isDataset(datasource) && isColumnMeta(datasource.columns[0])) {
|
||||
if (isDataset(datasource) || isQueryResponse(datasource)) {
|
||||
return datasource.columns
|
||||
.map((col): [string, string] => [
|
||||
col.column_name,
|
||||
col.verbose_name || col.column_name,
|
||||
'verbose_name' in col
|
||||
? col.verbose_name || col.column_name
|
||||
: col.column_name,
|
||||
])
|
||||
.sort((opt1, opt2) =>
|
||||
opt1[1].toLowerCase() > opt2[1].toLowerCase() ? 1 : -1,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export * from './checkColumnType';
|
||||
export * from './selectOptions';
|
||||
export * from './D3Formatting';
|
||||
export * from './expandControlConfig';
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { GenericDataType, testQueryResponse } from '@superset-ui/core';
|
||||
import { checkColumnType, TestDataset } from '../../src';
|
||||
|
||||
test('checkColumnType columns from a Dataset', () => {
|
||||
expect(
|
||||
checkColumnType('num', TestDataset, [GenericDataType.NUMERIC]),
|
||||
).toEqual(true);
|
||||
expect(checkColumnType('num', TestDataset, [GenericDataType.STRING])).toEqual(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
checkColumnType('gender', TestDataset, [GenericDataType.STRING]),
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkColumnType('gender', TestDataset, [GenericDataType.NUMERIC]),
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
test('checkColumnType from a QueryResponse', () => {
|
||||
expect(
|
||||
checkColumnType('Column 1', testQueryResponse, [GenericDataType.STRING]),
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkColumnType('Column 1', testQueryResponse, [GenericDataType.NUMERIC]),
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
test('checkColumnType from null', () => {
|
||||
expect(checkColumnType('col', null, [])).toEqual(false);
|
||||
});
|
||||
@@ -16,7 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { DatasourceType, testQueryResponse } from '@superset-ui/core';
|
||||
import {
|
||||
DatasourceType,
|
||||
GenericDataType,
|
||||
testQueryResponse,
|
||||
} from '@superset-ui/core';
|
||||
import { columnChoices } from '../../src';
|
||||
|
||||
describe('columnChoices()', () => {
|
||||
@@ -31,14 +35,20 @@ describe('columnChoices()', () => {
|
||||
columns: [
|
||||
{
|
||||
column_name: 'fiz',
|
||||
type: 'INT',
|
||||
type_generic: GenericDataType.NUMERIC,
|
||||
},
|
||||
{
|
||||
column_name: 'about',
|
||||
verbose_name: 'right',
|
||||
type: 'VARCHAR',
|
||||
type_generic: GenericDataType.STRING,
|
||||
},
|
||||
{
|
||||
column_name: 'foo',
|
||||
verbose_name: 'bar',
|
||||
verbose_name: undefined,
|
||||
type: 'TIMESTAMP',
|
||||
type_generic: GenericDataType.TEMPORAL,
|
||||
},
|
||||
],
|
||||
verbose_map: {},
|
||||
@@ -48,8 +58,8 @@ describe('columnChoices()', () => {
|
||||
description: 'this is my datasource',
|
||||
}),
|
||||
).toEqual([
|
||||
['foo', 'bar'],
|
||||
['fiz', 'fiz'],
|
||||
['foo', 'foo'],
|
||||
['about', 'right'],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { testQueryResponse, testQueryResults } from '@superset-ui/core';
|
||||
import {
|
||||
GenericDataType,
|
||||
testQueryResponse,
|
||||
testQueryResults,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
Dataset,
|
||||
getTemporalColumns,
|
||||
@@ -55,8 +59,9 @@ test('get temporal columns from a QueryResponse', () => {
|
||||
temporalColumns: [
|
||||
{
|
||||
column_name: 'Column 2',
|
||||
type: 'TIMESTAMP',
|
||||
is_dttm: true,
|
||||
type: 'TIMESTAMP',
|
||||
type_generic: GenericDataType.TEMPORAL,
|
||||
},
|
||||
],
|
||||
defaultTemporalColumn: 'Column 2',
|
||||
|
||||
@@ -78,15 +78,20 @@ class CategoricalColorScale extends ExtensibleFunction {
|
||||
cleanedValue: string,
|
||||
) {
|
||||
// make sure we don't overwrite the origin colors
|
||||
const updatedRange = [...this.originColors];
|
||||
const updatedRange = new Set(this.originColors);
|
||||
// remove the color option from shared color
|
||||
sharedColorMap.forEach((value: string, key: string) => {
|
||||
if (key !== cleanedValue) {
|
||||
const index = updatedRange.indexOf(value);
|
||||
updatedRange.splice(index, 1);
|
||||
updatedRange.delete(value);
|
||||
}
|
||||
});
|
||||
this.range(updatedRange.length > 0 ? updatedRange : this.originColors);
|
||||
// remove the color option from forced colors
|
||||
Object.entries(this.parentForcedColors).forEach(([key, value]) => {
|
||||
if (key !== cleanedValue) {
|
||||
updatedRange.delete(value);
|
||||
}
|
||||
});
|
||||
this.range(updatedRange.size > 0 ? [...updatedRange] : this.originColors);
|
||||
}
|
||||
|
||||
getColor(value?: string, sliceId?: number) {
|
||||
|
||||
@@ -24,27 +24,19 @@ const schemes = [
|
||||
id: 'bnbColors',
|
||||
label: 'Airbnb Colors',
|
||||
colors: [
|
||||
'#ff5a5f', // rausch
|
||||
'#7b0051', // hackb
|
||||
'#007A87', // kazan
|
||||
'#00d1c1', // babu
|
||||
'#8ce071', // lima
|
||||
'#ffb400', // beach
|
||||
'#b4a76c', // barol
|
||||
'#ff8083',
|
||||
'#cc0086',
|
||||
'#00a1b3',
|
||||
'#00ffeb',
|
||||
'#bbedab',
|
||||
'#ffd266',
|
||||
'#cbc29a',
|
||||
'#ff3339',
|
||||
'#ff1ab1',
|
||||
'#005c66',
|
||||
'#00b3a5',
|
||||
'#55d12e',
|
||||
'#b37e00',
|
||||
'#988b4e',
|
||||
'#29696B',
|
||||
'#5BCACE',
|
||||
'#F4B02A',
|
||||
'#F1826A',
|
||||
'#792EB2',
|
||||
'#C96EC6',
|
||||
'#921E50',
|
||||
'#B27700',
|
||||
'#9C3498',
|
||||
'#9C3498',
|
||||
'#E4679D',
|
||||
'#C32F0E',
|
||||
'#9D63CA',
|
||||
],
|
||||
},
|
||||
].map(s => new CategoricalScheme(s));
|
||||
|
||||
@@ -67,6 +67,7 @@ function SafeMarkdown({
|
||||
rehypePlugins={rehypePlugins}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
skipHtml={false}
|
||||
transformLinkUri={null}
|
||||
>
|
||||
{source}
|
||||
</ReactMarkdown>
|
||||
|
||||
@@ -31,7 +31,7 @@ interface CurrencyFormatter {
|
||||
(value: number | null | undefined): string;
|
||||
}
|
||||
|
||||
export const getCurrencySymbol = (currency: Currency) =>
|
||||
export const getCurrencySymbol = (currency: Partial<Currency>) =>
|
||||
new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: currency.symbol,
|
||||
|
||||
@@ -19,3 +19,4 @@
|
||||
|
||||
export { default as CurrencyFormatter } from './CurrencyFormatter';
|
||||
export * from './CurrencyFormatter';
|
||||
export * from './utils';
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
Currency,
|
||||
CurrencyFormatter,
|
||||
ensureIsArray,
|
||||
getNumberFormatter,
|
||||
isSavedMetric,
|
||||
QueryFormMetric,
|
||||
ValueFormatter,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
export const buildCustomFormatters = (
|
||||
metrics: QueryFormMetric | QueryFormMetric[] | undefined,
|
||||
savedCurrencyFormats: Record<string, Currency>,
|
||||
savedColumnFormats: Record<string, string>,
|
||||
d3Format: string | undefined,
|
||||
currencyFormat: Currency | undefined,
|
||||
) => {
|
||||
const metricsArray = ensureIsArray(metrics);
|
||||
return metricsArray.reduce((acc, metric) => {
|
||||
if (isSavedMetric(metric)) {
|
||||
const actualD3Format = d3Format ?? savedColumnFormats[metric];
|
||||
const actualCurrencyFormat = currencyFormat?.symbol
|
||||
? currencyFormat
|
||||
: savedCurrencyFormats[metric];
|
||||
return actualCurrencyFormat
|
||||
? {
|
||||
...acc,
|
||||
[metric]: new CurrencyFormatter({
|
||||
d3Format: actualD3Format,
|
||||
currency: actualCurrencyFormat,
|
||||
}),
|
||||
}
|
||||
: {
|
||||
...acc,
|
||||
[metric]: getNumberFormatter(actualD3Format),
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const getCustomFormatter = (
|
||||
customFormatters: Record<string, ValueFormatter>,
|
||||
metrics: QueryFormMetric | QueryFormMetric[] | undefined,
|
||||
key?: string,
|
||||
) => {
|
||||
const metricsArray = ensureIsArray(metrics);
|
||||
if (metricsArray.length === 1 && isSavedMetric(metricsArray[0])) {
|
||||
return customFormatters[metricsArray[0]];
|
||||
}
|
||||
return key ? customFormatters[key] : undefined;
|
||||
};
|
||||
|
||||
export const getValueFormatter = (
|
||||
metrics: QueryFormMetric | QueryFormMetric[] | undefined,
|
||||
savedCurrencyFormats: Record<string, Currency>,
|
||||
savedColumnFormats: Record<string, string>,
|
||||
d3Format: string | undefined,
|
||||
currencyFormat: Currency | undefined,
|
||||
key?: string,
|
||||
) => {
|
||||
const customFormatter = getCustomFormatter(
|
||||
buildCustomFormatters(
|
||||
metrics,
|
||||
savedCurrencyFormats,
|
||||
savedColumnFormats,
|
||||
d3Format,
|
||||
currencyFormat,
|
||||
),
|
||||
metrics,
|
||||
key,
|
||||
);
|
||||
|
||||
if (customFormatter) {
|
||||
return customFormatter;
|
||||
}
|
||||
if (currencyFormat?.symbol) {
|
||||
return new CurrencyFormatter({ currency: currencyFormat, d3Format });
|
||||
}
|
||||
return getNumberFormatter(d3Format);
|
||||
};
|
||||
@@ -70,10 +70,7 @@ export function normalizeTimeColumn(
|
||||
};
|
||||
}
|
||||
|
||||
const newQueryObject = omit(queryObject, [
|
||||
'extras.time_grain_sqla',
|
||||
'is_timeseries',
|
||||
]);
|
||||
const newQueryObject = omit(queryObject, ['is_timeseries']);
|
||||
newQueryObject.columns = mutatedColumns;
|
||||
|
||||
return newQueryObject;
|
||||
|
||||
@@ -159,7 +159,6 @@ export function isTableAnnotationLayer(
|
||||
}
|
||||
|
||||
export type RecordAnnotationResult = {
|
||||
columns: string[];
|
||||
records: DataRecord[];
|
||||
};
|
||||
|
||||
@@ -181,7 +180,7 @@ export function isTimeseriesAnnotationResult(
|
||||
export function isRecordAnnotationResult(
|
||||
result: any,
|
||||
): result is RecordAnnotationResult {
|
||||
return Array.isArray(result?.columns) && Array.isArray(result?.records);
|
||||
return Array.isArray(result?.records);
|
||||
}
|
||||
|
||||
export type AnnotationData = { [key: string]: AnnotationResult };
|
||||
|
||||
@@ -31,6 +31,7 @@ import { Maybe } from '../../types';
|
||||
import { PostProcessingRule } from './PostProcessing';
|
||||
import { JsonObject } from '../../connection';
|
||||
import { TimeGranularity } from '../../time-format';
|
||||
import { GenericDataType } from './QueryResponse';
|
||||
|
||||
export type BaseQueryObjectFilterClause = {
|
||||
col: QueryFormColumn;
|
||||
@@ -250,6 +251,7 @@ export type QueryColumn = {
|
||||
name?: string;
|
||||
column_name: string;
|
||||
type: string | null;
|
||||
type_generic: GenericDataType;
|
||||
is_dttm: boolean;
|
||||
};
|
||||
|
||||
@@ -383,16 +385,19 @@ export const testQuery: Query = {
|
||||
column_name: 'Column 1',
|
||||
type: 'STRING',
|
||||
is_dttm: false,
|
||||
type_generic: GenericDataType.STRING,
|
||||
},
|
||||
{
|
||||
column_name: 'Column 3',
|
||||
type: 'STRING',
|
||||
is_dttm: false,
|
||||
type_generic: GenericDataType.STRING,
|
||||
},
|
||||
{
|
||||
column_name: 'Column 2',
|
||||
type: 'TIMESTAMP',
|
||||
is_dttm: true,
|
||||
type_generic: GenericDataType.TEMPORAL,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -404,16 +409,19 @@ export const testQueryResults = {
|
||||
{
|
||||
column_name: 'Column 1',
|
||||
type: 'STRING',
|
||||
type_generic: GenericDataType.STRING,
|
||||
is_dttm: false,
|
||||
},
|
||||
{
|
||||
column_name: 'Column 3',
|
||||
type: 'STRING',
|
||||
type_generic: GenericDataType.STRING,
|
||||
is_dttm: false,
|
||||
},
|
||||
{
|
||||
column_name: 'Column 2',
|
||||
type: 'TIMESTAMP',
|
||||
type_generic: GenericDataType.TEMPORAL,
|
||||
is_dttm: true,
|
||||
},
|
||||
],
|
||||
@@ -425,16 +433,19 @@ export const testQueryResults = {
|
||||
{
|
||||
column_name: 'Column 1',
|
||||
type: 'STRING',
|
||||
type_generic: GenericDataType.STRING,
|
||||
is_dttm: false,
|
||||
},
|
||||
{
|
||||
column_name: 'Column 3',
|
||||
type: 'STRING',
|
||||
type_generic: GenericDataType.STRING,
|
||||
is_dttm: false,
|
||||
},
|
||||
{
|
||||
column_name: 'Column 2',
|
||||
type: 'TIMESTAMP',
|
||||
type_generic: GenericDataType.TEMPORAL,
|
||||
is_dttm: true,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0,
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import finestTemporalGrain from './finestTemporalGrain';
|
||||
|
||||
test('finestTemporalGrain', () => {
|
||||
const monthFormatter = finestTemporalGrain([
|
||||
new Date('2003-01-01 00:00:00Z').getTime(),
|
||||
new Date('2003-02-01 00:00:00Z').getTime(),
|
||||
]);
|
||||
expect(monthFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
|
||||
'2003-01-01',
|
||||
);
|
||||
expect(monthFormatter(new Date('2003-02-01 00:00:00Z').getTime())).toBe(
|
||||
'2003-02-01',
|
||||
);
|
||||
|
||||
const yearFormatter = finestTemporalGrain([
|
||||
new Date('2003-01-01 00:00:00Z').getTime(),
|
||||
new Date('2004-01-01 00:00:00Z').getTime(),
|
||||
]);
|
||||
expect(yearFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
|
||||
'2003',
|
||||
);
|
||||
expect(yearFormatter(new Date('2004-01-01 00:00:00Z').getTime())).toBe(
|
||||
'2004',
|
||||
);
|
||||
|
||||
const milliSecondFormatter = finestTemporalGrain([
|
||||
new Date('2003-01-01 00:00:00Z').getTime(),
|
||||
new Date('2003-04-05 06:07:08.123Z').getTime(),
|
||||
]);
|
||||
expect(milliSecondFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
|
||||
'2003-01-01 00:00:00.000',
|
||||
);
|
||||
|
||||
const localTimeFormatter = finestTemporalGrain(
|
||||
[
|
||||
new Date('2003-01-01 00:00:00Z').getTime(),
|
||||
new Date('2003-02-01 00:00:00Z').getTime(),
|
||||
],
|
||||
true,
|
||||
);
|
||||
expect(localTimeFormatter(new Date('2003-01-01 00:00:00Z').getTime())).toBe(
|
||||
'2002-12-31 19:00',
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { utcFormat, timeFormat } from 'd3-time-format';
|
||||
import { utcUtils, localTimeUtils } from '../utils/d3Time';
|
||||
import TimeFormatter from '../TimeFormatter';
|
||||
|
||||
/*
|
||||
* A formatter that examines all the values, and uses the finest temporal grain.
|
||||
*/
|
||||
export default function finestTemporalGrain(
|
||||
values: any[],
|
||||
useLocalTime = false,
|
||||
) {
|
||||
const format = useLocalTime ? timeFormat : utcFormat;
|
||||
|
||||
const formatMillisecond = format('%Y-%m-%d %H:%M:%S.%L');
|
||||
const formatSecond = format('%Y-%m-%d %H:%M:%S');
|
||||
const formatMinute = format('%Y-%m-%d %H:%M');
|
||||
const formatHour = format('%Y-%m-%d %H:%M');
|
||||
const formatDay = format('%Y-%m-%d');
|
||||
const formatMonth = format('%Y-%m-%d');
|
||||
const formatYear = format('%Y');
|
||||
|
||||
const {
|
||||
hasMillisecond,
|
||||
hasSecond,
|
||||
hasMinute,
|
||||
hasHour,
|
||||
isNotFirstDayOfMonth,
|
||||
isNotFirstMonth,
|
||||
} = useLocalTime ? localTimeUtils : utcUtils;
|
||||
|
||||
let formatFunc = formatYear;
|
||||
values.forEach((value: any) => {
|
||||
if (formatFunc === formatYear && isNotFirstMonth(value)) {
|
||||
formatFunc = formatMonth;
|
||||
}
|
||||
if (formatFunc === formatMonth && isNotFirstDayOfMonth(value)) {
|
||||
formatFunc = formatDay;
|
||||
}
|
||||
if (formatFunc === formatDay && hasHour(value)) {
|
||||
formatFunc = formatHour;
|
||||
}
|
||||
if (formatFunc === formatHour && hasMinute(value)) {
|
||||
formatFunc = formatMinute;
|
||||
}
|
||||
if (formatFunc === formatMinute && hasSecond(value)) {
|
||||
formatFunc = formatSecond;
|
||||
}
|
||||
if (formatFunc === formatSecond && hasMillisecond(value)) {
|
||||
formatFunc = formatMillisecond;
|
||||
}
|
||||
});
|
||||
|
||||
return new TimeFormatter({
|
||||
description:
|
||||
'Use the finest grain in an array of dates to format all dates in the array',
|
||||
formatFunc,
|
||||
id: 'finest_temporal_grain',
|
||||
label: 'Format temporal columns with the finest grain',
|
||||
useLocalTime,
|
||||
});
|
||||
}
|
||||
@@ -35,6 +35,7 @@ export { default as createMultiFormatter } from './factories/createMultiFormatte
|
||||
export { default as smartDateFormatter } from './formatters/smartDate';
|
||||
export { default as smartDateDetailedFormatter } from './formatters/smartDateDetailed';
|
||||
export { default as smartDateVerboseFormatter } from './formatters/smartDateVerbose';
|
||||
export { default as finestTemporalGrainFormatter } from './formatters/finestTemporalGrain';
|
||||
|
||||
export { default as normalizeTimestamp } from './utils/normalizeTimestamp';
|
||||
export { default as denormalizeTimestamp } from './utils/denormalizeTimestamp';
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import logger from './logging';
|
||||
|
||||
// We can codegen the enum definition based on a list of supported flags that we
|
||||
// check into source control. We're hardcoding the supported flags for now.
|
||||
export enum FeatureFlag {
|
||||
@@ -85,11 +87,17 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export function initFeatureFlags(featureFlags?: FeatureFlagMap) {
|
||||
if (!window.featureFlags) {
|
||||
window.featureFlags = featureFlags || {};
|
||||
}
|
||||
}
|
||||
|
||||
export function isFeatureEnabled(feature: FeatureFlag): boolean {
|
||||
try {
|
||||
return !!window.featureFlags[feature];
|
||||
} catch (error) {
|
||||
console.error(`Failed to query feature flag ${feature}`);
|
||||
logger.error(`Failed to query feature flag ${feature}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ describe('isProbablyHTML', () => {
|
||||
const plainText = 'Just a plain text';
|
||||
const isHTML = isProbablyHTML(plainText);
|
||||
expect(isHTML).toBe(false);
|
||||
|
||||
const trickyText = 'a <= 10 and b > 10';
|
||||
expect(isProbablyHTML(trickyText)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ export function sanitizeHtml(htmlString: string) {
|
||||
}
|
||||
|
||||
export function isProbablyHTML(text: string) {
|
||||
return /<[^>]+>/.test(text);
|
||||
return Array.from(
|
||||
new DOMParser().parseFromString(text, 'text/html').body.childNodes,
|
||||
).some(({ nodeType }) => nodeType === 1);
|
||||
}
|
||||
|
||||
export function sanitizeHtmlIfNeeded(htmlString: string) {
|
||||
|
||||
@@ -22,3 +22,4 @@ export { default as legacyValidateNumber } from './legacyValidateNumber';
|
||||
export { default as validateInteger } from './validateInteger';
|
||||
export { default as validateNumber } from './validateNumber';
|
||||
export { default as validateNonEmpty } from './validateNonEmpty';
|
||||
export { default as validateMaxValue } from './validateMaxValue';
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { t } from '../translation';
|
||||
|
||||
export default function validateMaxValue(v: unknown, max: Number) {
|
||||
if (Number(v) > +max) {
|
||||
return t('Value cannot exceed %s', max);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -273,5 +273,21 @@ describe('CategoricalColorScale', () => {
|
||||
expect(scale.range()).toEqual(['blue', 'green', 'red']);
|
||||
sharedLabelColor.clear();
|
||||
});
|
||||
|
||||
it('should remove parentForcedColors from range', () => {
|
||||
const parentForcedColors = { house: 'blue', cow: 'red' };
|
||||
const scale = new CategoricalColorScale(
|
||||
['blue', 'red', 'green'],
|
||||
parentForcedColors,
|
||||
);
|
||||
const sharedLabelColor = getSharedLabelColor();
|
||||
sharedLabelColor.clear();
|
||||
const colorMap = sharedLabelColor.getColorMap();
|
||||
scale.removeSharedLabelColorFromRange(colorMap, 'pig');
|
||||
expect(scale.range()).toEqual(['green']);
|
||||
scale.removeSharedLabelColorFromRange(colorMap, 'cow');
|
||||
expect(scale.range()).toEqual(['red', 'green']);
|
||||
sharedLabelColor.clear();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
buildCustomFormatters,
|
||||
CurrencyFormatter,
|
||||
getCustomFormatter,
|
||||
getNumberFormatter,
|
||||
getValueFormatter,
|
||||
NumberFormatter,
|
||||
ValueFormatter,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
test('buildCustomFormatters without saved metrics returns empty object', () => {
|
||||
expect(
|
||||
buildCustomFormatters(
|
||||
[
|
||||
{
|
||||
expressionType: 'SIMPLE',
|
||||
aggregate: 'COUNT',
|
||||
column: { column_name: 'test' },
|
||||
},
|
||||
],
|
||||
{
|
||||
sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
|
||||
},
|
||||
{},
|
||||
',.1f',
|
||||
undefined,
|
||||
),
|
||||
).toEqual({});
|
||||
|
||||
expect(
|
||||
buildCustomFormatters(
|
||||
undefined,
|
||||
{
|
||||
sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
|
||||
},
|
||||
{},
|
||||
',.1f',
|
||||
undefined,
|
||||
),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
test('buildCustomFormatters with saved metrics returns custom formatters object', () => {
|
||||
const customFormatters: Record<string, ValueFormatter> =
|
||||
buildCustomFormatters(
|
||||
[
|
||||
{
|
||||
expressionType: 'SIMPLE',
|
||||
aggregate: 'COUNT',
|
||||
column: { column_name: 'test' },
|
||||
},
|
||||
'sum__num',
|
||||
'count',
|
||||
],
|
||||
{
|
||||
sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
|
||||
},
|
||||
{ sum__num: ',.2' },
|
||||
',.1f',
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(customFormatters).toEqual({
|
||||
sum__num: expect.any(Function),
|
||||
count: expect.any(Function),
|
||||
});
|
||||
|
||||
expect(customFormatters.sum__num).toBeInstanceOf(CurrencyFormatter);
|
||||
expect(customFormatters.count).toBeInstanceOf(NumberFormatter);
|
||||
expect((customFormatters.sum__num as CurrencyFormatter).d3Format).toEqual(
|
||||
',.1f',
|
||||
);
|
||||
});
|
||||
|
||||
test('buildCustomFormatters uses dataset d3 format if not provided in control panel', () => {
|
||||
const customFormatters: Record<string, ValueFormatter> =
|
||||
buildCustomFormatters(
|
||||
[
|
||||
{
|
||||
expressionType: 'SIMPLE',
|
||||
aggregate: 'COUNT',
|
||||
column: { column_name: 'test' },
|
||||
},
|
||||
'sum__num',
|
||||
'count',
|
||||
],
|
||||
{
|
||||
sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
|
||||
},
|
||||
{ sum__num: ',.2' },
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect((customFormatters.sum__num as CurrencyFormatter).d3Format).toEqual(
|
||||
',.2',
|
||||
);
|
||||
});
|
||||
|
||||
test('getCustomFormatter', () => {
|
||||
const customFormatters = {
|
||||
sum__num: new CurrencyFormatter({
|
||||
currency: { symbol: 'USD', symbolPosition: 'prefix' },
|
||||
}),
|
||||
count: getNumberFormatter(),
|
||||
};
|
||||
expect(getCustomFormatter(customFormatters, 'count')).toEqual(
|
||||
customFormatters.count,
|
||||
);
|
||||
expect(
|
||||
getCustomFormatter(customFormatters, ['count', 'sum__num'], 'count'),
|
||||
).toEqual(customFormatters.count);
|
||||
expect(getCustomFormatter(customFormatters, ['count', 'sum__num'])).toEqual(
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
test('getValueFormatter', () => {
|
||||
expect(
|
||||
getValueFormatter(['count', 'sum__num'], {}, {}, ',.1f', undefined),
|
||||
).toBeInstanceOf(NumberFormatter);
|
||||
|
||||
expect(
|
||||
getValueFormatter(
|
||||
['count', 'sum__num'],
|
||||
{},
|
||||
{},
|
||||
',.1f',
|
||||
undefined,
|
||||
'count',
|
||||
),
|
||||
).toBeInstanceOf(NumberFormatter);
|
||||
|
||||
expect(
|
||||
getValueFormatter(
|
||||
['count', 'sum__num'],
|
||||
{ count: { symbol: 'USD', symbolPosition: 'prefix' } },
|
||||
{},
|
||||
',.1f',
|
||||
undefined,
|
||||
'count',
|
||||
),
|
||||
).toBeInstanceOf(CurrencyFormatter);
|
||||
});
|
||||
|
||||
test('getValueFormatter with currency from control panel', () => {
|
||||
const countFormatter = getValueFormatter(
|
||||
['count', 'sum__num'],
|
||||
{ count: { symbol: 'USD', symbolPosition: 'prefix' } },
|
||||
{},
|
||||
',.1f',
|
||||
{ symbol: 'EUR', symbolPosition: 'suffix' },
|
||||
'count',
|
||||
);
|
||||
expect(countFormatter).toBeInstanceOf(CurrencyFormatter);
|
||||
expect((countFormatter as CurrencyFormatter).currency).toEqual({
|
||||
symbol: 'EUR',
|
||||
symbolPosition: 'suffix',
|
||||
});
|
||||
});
|
||||
|
||||
test('getValueFormatter with currency from control panel when no saved currencies', () => {
|
||||
const formatter = getValueFormatter(
|
||||
['count', 'sum__num'],
|
||||
{},
|
||||
{},
|
||||
',.1f',
|
||||
{ symbol: 'EUR', symbolPosition: 'suffix' },
|
||||
undefined,
|
||||
);
|
||||
expect(formatter).toBeInstanceOf(CurrencyFormatter);
|
||||
expect((formatter as CurrencyFormatter).currency).toEqual({
|
||||
symbol: 'EUR',
|
||||
symbolPosition: 'suffix',
|
||||
});
|
||||
});
|
||||
|
||||
test('getValueFormatter return NumberFormatter when no currency formatters', () => {
|
||||
const formatter = getValueFormatter(
|
||||
['count', 'sum__num'],
|
||||
{},
|
||||
{},
|
||||
',.1f',
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(formatter).toBeInstanceOf(NumberFormatter);
|
||||
});
|
||||
@@ -92,7 +92,9 @@ describe('GENERIC_CHART_AXES is disabled', () => {
|
||||
datasource: '5__table',
|
||||
viz_type: 'table',
|
||||
granularity: 'time_column',
|
||||
extras: {},
|
||||
extras: {
|
||||
time_grain_sqla: 'P1Y',
|
||||
},
|
||||
time_range: '1 year ago : 2013',
|
||||
orderby: [['count(*)', true]],
|
||||
columns: [
|
||||
@@ -182,7 +184,7 @@ describe('GENERIC_CHART_AXES is enabled', () => {
|
||||
datasource: '5__table',
|
||||
viz_type: 'table',
|
||||
granularity: 'time_column',
|
||||
extras: { where: '', having: '' },
|
||||
extras: { where: '', having: '', time_grain_sqla: 'P1Y' },
|
||||
time_range: '1 year ago : 2013',
|
||||
orderby: [['count(*)', true]],
|
||||
columns: [
|
||||
@@ -240,7 +242,7 @@ describe('GENERIC_CHART_AXES is enabled', () => {
|
||||
datasource: '5__table',
|
||||
viz_type: 'table',
|
||||
granularity: 'time_column',
|
||||
extras: { where: '', having: '' },
|
||||
extras: { where: '', having: '', time_grain_sqla: 'P1Y' },
|
||||
time_range: '1 year ago : 2013',
|
||||
orderby: [['count(*)', true]],
|
||||
columns: [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user