mirror of
https://github.com/apache/superset.git
synced 2026-04-29 04:54:21 +00:00
Compare commits
116 Commits
docs/testi
...
2.0.1rc5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b12703b98 | ||
|
|
0f5216b7c6 | ||
|
|
d319b2b56f | ||
|
|
8237558d62 | ||
|
|
c2f6256e90 | ||
|
|
5667078d89 | ||
|
|
095a22dd5d | ||
|
|
11a40a417e | ||
|
|
cdf03dfbcb | ||
|
|
9321bc6ba2 | ||
|
|
44f19a5c99 | ||
|
|
1c77f3f8f0 | ||
|
|
4327fbc794 | ||
|
|
149ad56aa8 | ||
|
|
e682bae27f | ||
|
|
80a306eaad | ||
|
|
176995eac9 | ||
|
|
dff5be1a58 | ||
|
|
52cc90d741 | ||
|
|
bf40df2f81 | ||
|
|
a8a92d7a69 | ||
|
|
d779789652 | ||
|
|
aa11e5486b | ||
|
|
68a389642c | ||
|
|
b238d04f29 | ||
|
|
6e325ac73e | ||
|
|
cceb9f1794 | ||
|
|
c13fbd4f5d | ||
|
|
a3694f6ea1 | ||
|
|
5b686bc6de | ||
|
|
9f33519312 | ||
|
|
b43c047887 | ||
|
|
930148dec6 | ||
|
|
f7e664bd68 | ||
|
|
b1c9adb52b | ||
|
|
9e907be7a8 | ||
|
|
61b64492a2 | ||
|
|
2c65ef967e | ||
|
|
ad52469a04 | ||
|
|
548311c424 | ||
|
|
40c5b2a688 | ||
|
|
511cafa790 | ||
|
|
dd919bc176 | ||
|
|
143c5f1ecc | ||
|
|
dfcb66a098 | ||
|
|
440ab64c53 | ||
|
|
5b662f7874 | ||
|
|
08052a7db3 | ||
|
|
2583f7fde3 | ||
|
|
db661ec17f | ||
|
|
7f8d6b3400 | ||
|
|
2b760d0775 | ||
|
|
4298690e16 | ||
|
|
32736680da | ||
|
|
9337dec038 | ||
|
|
05d7c3d74d | ||
|
|
c3e04d7ebf | ||
|
|
47c3cd10bd | ||
|
|
b4df82591e | ||
|
|
884e2f1ca7 | ||
|
|
e91222eb65 | ||
|
|
2db82d578c | ||
|
|
346c035690 | ||
|
|
6a5b12ec8c | ||
|
|
8c2ca2d8d8 | ||
|
|
43b8f18a21 | ||
|
|
5efee17def | ||
|
|
56137ebbe5 | ||
|
|
067495d954 | ||
|
|
bba486b08c | ||
|
|
6e74f3e82c | ||
|
|
3e97c60c8d | ||
|
|
747b011bb7 | ||
|
|
dc71454416 | ||
|
|
4dcc805dee | ||
|
|
3b5513be36 | ||
|
|
4b2397f0c7 | ||
|
|
9a26a211d4 | ||
|
|
eedcefc64a | ||
|
|
eba63b4add | ||
|
|
edbbf886af | ||
|
|
80e2f1abe7 | ||
|
|
b5a4c06d82 | ||
|
|
789f99341b | ||
|
|
63229dcf56 | ||
|
|
92038db579 | ||
|
|
b8d9208b6e | ||
|
|
885bbdde95 | ||
|
|
25a6f02cd6 | ||
|
|
bbca109ea3 | ||
|
|
bcc23bbacd | ||
|
|
23061d6822 | ||
|
|
40a9257311 | ||
|
|
ca0544a573 | ||
|
|
d789f376b3 | ||
|
|
3d850426ff | ||
|
|
4728d8f49b | ||
|
|
9e6a3e1a4e | ||
|
|
2d551faaf4 | ||
|
|
f58ad259ac | ||
|
|
4e93690e19 | ||
|
|
d1ac6e5db4 | ||
|
|
59fbf2a202 | ||
|
|
4841e8fb9c | ||
|
|
06592180ea | ||
|
|
cb270034f3 | ||
|
|
7e48de484a | ||
|
|
a08499d88d | ||
|
|
bd721cd86c | ||
|
|
67c853790d | ||
|
|
3b3c3be9b2 | ||
|
|
2f07a88c32 | ||
|
|
128722085c | ||
|
|
78c577b515 | ||
|
|
9aa047cf12 | ||
|
|
ce9807941b |
@@ -37,7 +37,7 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'requirements/testing.txt'
|
||||
# TODO: separated requiermentes.txt file just for unit tests
|
||||
# TODO: separated requirements.txt file just for unit tests
|
||||
- name: Install dependencies
|
||||
if: steps.check.outcome == 'failure'
|
||||
uses: ./.github/actions/cached-dependencies
|
||||
|
||||
552
CHANGELOG.md
552
CHANGELOG.md
@@ -18,6 +18,558 @@ under the License.
|
||||
-->
|
||||
|
||||
## Change Log
|
||||
### 2.0.1 (Thu Oct 13 09:52:35 2022 -0700)
|
||||
**Database Migrations**
|
||||
|
||||
**Features**
|
||||
|
||||
**Fixes**
|
||||
- [#21895](https://github.com/apache/superset/pull/21895) feat: Improves SafeMarkdown HTML sanitization (@michael-s-molina) (security fix)
|
||||
- [#21874](https://github.com/apache/superset/pull/21874) feat: Adds a Content Security Policy (CSP) check for production environments (@michael-s-molina) (security fix)
|
||||
- [#21853](https://github.com/apache/superset/pull/21853) feat: Disables HTML rendering in Toast by default (@michael-s-molina) (security fix)
|
||||
- [#21776](https://github.com/apache/superset/pull/21776) fix(CustomFrame): Resolves issue #21731 where date range in explore throws runtime error (@eric-briscoe)
|
||||
- [#21637](https://github.com/apache/superset/pull/21637) fix: respect chart cache timeout setting (@mayurnewase)
|
||||
- [#21729](https://github.com/apache/superset/pull/21729) fix: allow adhoc columns in non-aggregate query (@mayurnewase)
|
||||
- [#21441](https://github.com/apache/superset/pull/21441) fix(cache): respect default cache timeout on v1 chart data requests (@villebro)
|
||||
- [#22038](https://github.com/apache/superset/pull/22038) fix: datasource save, improve data validation (@dpgaspar)
|
||||
- [#22022](https://github.com/apache/superset/pull/22022) fix: deprecate approve and request_access endpoint (@dpgaspar)
|
||||
- [#21964](https://github.com/apache/superset/pull/21964) fix: dashboard api cache decorator (@dpgaspar)
|
||||
- [#21875](https://github.com/apache/superset/pull/21875) fix: check that imports are ZIPs (@betodealmeida)
|
||||
- [#21761](https://github.com/apache/superset/pull/21761) fix: flash message on database data upload forms (@dpgaspar)
|
||||
- [#21759](https://github.com/apache/superset/pull/21759) fix: database schema selector on import data (@dpgaspar)
|
||||
- [#21729](https://github.com/apache/superset/pull/21729) fix: allow adhoc columns in non-aggregate query (@mayurnewase)
|
||||
- [#21549](https://github.com/apache/superset/pull/21549) fix(dashboard): show correct roles for dashboard access dropdown (@mayurnewase)
|
||||
- [#21773](https://github.com/apache/superset/pull/21773) fix: remove deprecated ETagResponseMixin (@eschutho)
|
||||
- [#21561](https://github.com/apache/superset/pull/21561) fix(report): Fix permission check for set up email report on charts/dashboards. Fixes #21559 (@zhaorui2022)
|
||||
- [#20651](https://github.com/apache/superset/pull/20651) fix: annotation broken (@zhaoyongjie)
|
||||
- [#20830](https://github.com/apache/superset/pull/20830) fix: remove element reference in alerts report fetchs (@hughhhh)
|
||||
- [#20063](https://github.com/apache/superset/pull/20063) fix: Add locale for DatePicker component (@aehanno)
|
||||
- [#21302](https://github.com/apache/superset/pull/21302) fix: disallow users from viewing other user's profile on config (@dpgaspar)
|
||||
- [#21154](https://github.com/apache/superset/pull/21154) fix(explore): Prevent unnecessary series limit subquery (@codyml)
|
||||
- [#21498](https://github.com/apache/superset/pull/21498) fix: set correct favicon from config for login and FAB list views (@mayurnewase)
|
||||
- [#21380](https://github.com/apache/superset/pull/21380) fix(sqllab): Fix cursor alignment in SQL lab editor by avoiding Lucida Console font on Windows (@MichaelHintz)
|
||||
- [#20061](https://github.com/apache/superset/pull/20061) fix: Add french translation missing (@aehanno)
|
||||
- [#21044](https://github.com/apache/superset/pull/21044) fix(plugin-chart-echarts): missing value format in mixed timeseries (@justinpark)
|
||||
- [#21419](https://github.com/apache/superset/pull/21419) fix: cached common bootstrap Revert (#21018) (@dpgaspar)
|
||||
- [#21296](https://github.com/apache/superset/pull/21296) fix(plugin-chart-echarts): show zero value in tooltip (@villebro)
|
||||
- [#21294](https://github.com/apache/superset/pull/21294) fix(explore): Time column label not formatted when GENERIC_X_AXES enabled (@kgabryje)
|
||||
- [#21272](https://github.com/apache/superset/pull/21272) fix: adds TLS certificate validation option for SMTP (@dpgaspar)
|
||||
- [#21076](https://github.com/apache/superset/pull/21076) fix(celery cache warmup): add auth and use warm_up_cache endpoint (@nytai)
|
||||
- [#21216](https://github.com/apache/superset/pull/21216) fix(database-list): hide upload file button if no permission (@stephenLYZ)
|
||||
- [#21153](https://github.com/apache/superset/pull/21153) fix(sqllab): missing zero values while copy-to-clipboard (@justinpark)
|
||||
- [#21084](https://github.com/apache/superset/pull/21084) fix(native filters): groupby filter issue (@stevetracvc)
|
||||
- [#21005](https://github.com/apache/superset/pull/21005) fix(plugin-chart-handlebars): Sort-By and Sort-By-Descending control not work (@stephenLYZ)
|
||||
- [#20969](https://github.com/apache/superset/pull/20969) fix(dashboard): Fix scroll behaviour in DashboardBuilderSidepane (@EugeneTorap)
|
||||
- [#21007](https://github.com/apache/superset/pull/21007) fix(plugin-chart-echarts): gauge chart enhancements and fixes (@stephenLYZ)
|
||||
- [#21032](https://github.com/apache/superset/pull/21032) fix(plugin-chart-echarts): invalid total label location for negative values in stacked bar chart (@justinpark)
|
||||
- [#20962](https://github.com/apache/superset/pull/20962) fix: Explore scrolled down when navigating from dashboard (@kgabryje)
|
||||
- [#20946](https://github.com/apache/superset/pull/20946) fix(viz): Show zero percent changes in Big Number Viz (@Antonio-RiveroMartnez)
|
||||
- [#20819](https://github.com/apache/superset/pull/20819) fix: Temporal X Axis values are not properly displayed if the time column has a custom label defined (@diegomedina248)
|
||||
- [#20736](https://github.com/apache/superset/pull/20736) fix: getting default value in run-server.sh (@zhaoyongjie)
|
||||
- [#20733](https://github.com/apache/superset/pull/20733) fix(docker): Make Gunicorn max_requests and max_requests_jitter adjustable (@mdeshmu)
|
||||
- [#20714](https://github.com/apache/superset/pull/20714) fix: logger message (@betodealmeida)
|
||||
|
||||
**Others**
|
||||
- [#21811](https://github.com/apache/superset/pull/21811) chore(sqla): refactor query utils (@villebro)
|
||||
- [#21811](https://github.com/apache/superset/pull/21811) chore(sqla): refactor query utils (@villebro)
|
||||
- [#20644](https://github.com/apache/superset/pull/20644) chore(deps): bump moment from 2.29.2 to 2.29.4 in /superset-frontend (@dependabot[bot])
|
||||
- [#21721](https://github.com/apache/superset/pull/21721) build: changelog for 2.0.1 (@AAfghahi)
|
||||
- [#21018](https://github.com/apache/superset/pull/21018) perf: Memoize the common_bootstrap_payload (@bkyryliuk)
|
||||
- [#21091](https://github.com/apache/superset/pull/21091) chore(deps): unpin holidays dependency version (@ecederstrand)
|
||||
|
||||
### 2.0 (Tue Jun 28 08:53:02 2022 -0400)
|
||||
|
||||
**Database Migrations**
|
||||
|
||||
- [#20385](https://github.com/apache/superset/pull/20385) fix(migration): Ensure key_value LargeBinary is encoded as a MEDIUMBLOB as opposed to BLOB for MySQL (@john-bodley)
|
||||
- [#20284](https://github.com/apache/superset/pull/20284) chore(migrations): Renaming migration files so that they're easier to keep track of (@craig-rueda)
|
||||
- [#20108](https://github.com/apache/superset/pull/20108) fix: None dataset and schema permissions (@dpgaspar)
|
||||
- [#18794](https://github.com/apache/superset/pull/18794) feat(business-types): initial implementation of SIP-78 (@cccs-RyanS)
|
||||
- [#20073](https://github.com/apache/superset/pull/20073) fix(dataset): handle missing sqla uri in migration (@villebro)
|
||||
- [#19941](https://github.com/apache/superset/pull/19941) fix(reports): Clear last value when state is WORKING (@john-bodley)
|
||||
- [#19675](https://github.com/apache/superset/pull/19675) chore(docs): Spelling (@jsoref)
|
||||
- [#19793](https://github.com/apache/superset/pull/19793) fix(SIP-68): handle empty table name during migration (@ktmud)
|
||||
- [#19786](https://github.com/apache/superset/pull/19786) fix(migrations): coalesce is_temporal when inserting into sl_columns (@cemremengu)
|
||||
- [#19421](https://github.com/apache/superset/pull/19421) perf: refactor SIP-68 db migrations with INSERT SELECT FROM (@ktmud)
|
||||
- [#19767](https://github.com/apache/superset/pull/19767) fix: Fix migration for removing time_range_endpoints 3 (@hughhhh)
|
||||
- [#19728](https://github.com/apache/superset/pull/19728) fix: Removetime_range_endpoints from query context object pt 2 (@hughhhh)
|
||||
- [#19630](https://github.com/apache/superset/pull/19630) chore: clean up unused imports in db migration scripts (@ktmud)
|
||||
- [#19577](https://github.com/apache/superset/pull/19577) fix: merge multiple db heads (@eschutho)
|
||||
- [#19243](https://github.com/apache/superset/pull/19243) fix: cannot delete a database if team member has SQL editor tab that uses that db (@diegomedina248)
|
||||
- [#19537](https://github.com/apache/superset/pull/19537) chore: block unsafe functions (@betodealmeida)
|
||||
- [#19513](https://github.com/apache/superset/pull/19513) chore: postpone timerange endpoint removal (@villebro)
|
||||
- [#19495](https://github.com/apache/superset/pull/19495) perf: speed up db migration for deprecating time_range_endpoints (@ktmud)
|
||||
- [#19474](https://github.com/apache/superset/pull/19474) fix: handle null params in #18936 migration (@serenajiang)
|
||||
- [#19423](https://github.com/apache/superset/pull/19423) fix: Remove`time_range_endpoints` from query context object (@hughhhh)
|
||||
- [#18936](https://github.com/apache/superset/pull/18936) chore: Remove legacy SIP-15 interim logic/flags (@john-bodley)
|
||||
|
||||
**Features**
|
||||
|
||||
- [#20377](https://github.com/apache/superset/pull/20377) feat(standardized form data): keep all columns and metrics (@zhaoyongjie)
|
||||
- [#20114](https://github.com/apache/superset/pull/20114) feat(chart): Enable caching per user when user impersonation is enabled (@Samira-El)
|
||||
- [#20408](https://github.com/apache/superset/pull/20408) feat(plugin-chart-echarts): Support stacking negative and positive values (@kgabryje)
|
||||
- [#20278](https://github.com/apache/superset/pull/20278) feat: Prevent dataset edit modal closing on click-away in edit mode (@reesercollins)
|
||||
- [#20392](https://github.com/apache/superset/pull/20392) feat: setting limit value when Pie chart switches (@zhaoyongjie)
|
||||
- [#20373](https://github.com/apache/superset/pull/20373) feat: adding truncate metric control on timeseries charts (@zhaoyongjie)
|
||||
- [#20248](https://github.com/apache/superset/pull/20248) feat(explore): Implement viz switcher redesign (@kgabryje)
|
||||
- [#20113](https://github.com/apache/superset/pull/20113) feat(api): Added "kind" to dataset/<pk> endpoint (@reesercollins)
|
||||
- [#20299](https://github.com/apache/superset/pull/20299) feat(explore): Dataset Panel Options when Source = Query II (@lyndsiWilliams)
|
||||
- [#20320](https://github.com/apache/superset/pull/20320) feat: Databricks native driver (@betodealmeida)
|
||||
- [#20313](https://github.com/apache/superset/pull/20313) feat(explore): Denormalize form data in echarts, world map and nvd3 bar and line charts (@kgabryje)
|
||||
- [#20277](https://github.com/apache/superset/pull/20277) feat: multiple results pane on explore and dashboard (@zhaoyongjie)
|
||||
- [#19898](https://github.com/apache/superset/pull/19898) feat: When editing the label/title in the Metrics popover, hitting Enter should save what you've typed (@diegomedina248)
|
||||
- [#16493](https://github.com/apache/superset/pull/16493) feat(plugin-chart-echarts): [feature-parity] support extra control for the area chart V2 (@stephenLYZ)
|
||||
- [#19855](https://github.com/apache/superset/pull/19855) feat(explore): Frontend implementation of dataset creation from infobox (@lyndsiWilliams)
|
||||
- [#20165](https://github.com/apache/superset/pull/20165) feat: add modfied col and timezone info to schedule col (@pkdotson)
|
||||
- [#20144](https://github.com/apache/superset/pull/20144) feat: showing results pane in dashboard (@zhaoyongjie)
|
||||
- [#20242](https://github.com/apache/superset/pull/20242) feat: derived metrics use different line style (@zhaoyongjie)
|
||||
- [#20010](https://github.com/apache/superset/pull/20010) feat: standardized form_data (@zhaoyongjie)
|
||||
- [#19987](https://github.com/apache/superset/pull/19987) feat(superset-ui-core): add feature flag for the analogous colors (@stephenLYZ)
|
||||
- [#19881](https://github.com/apache/superset/pull/19881) feat(world-map): support color by metric or country column (@stephenLYZ)
|
||||
- [#19981](https://github.com/apache/superset/pull/19981) feat!: pass datasource_type and datasource_id to form_data (@eschutho)
|
||||
- [#15241](https://github.com/apache/superset/pull/15241) feat: query datasets from SQL Lab (@betodealmeida)
|
||||
- [#20129](https://github.com/apache/superset/pull/20129) feat(explore): Fill dashboard name when adding new chart from dashboard view (@kgabryje)
|
||||
- [#20160](https://github.com/apache/superset/pull/20160) feat(explore): Add empty state to annotations (@kgabryje)
|
||||
- [#20134](https://github.com/apache/superset/pull/20134) feat: add Query.columns for bootstrap_data (@hughhhh)
|
||||
- [#20158](https://github.com/apache/superset/pull/20158) feat: add statsd metrics for notifications (@dpgaspar)
|
||||
- [#20052](https://github.com/apache/superset/pull/20052) feat(Helm Chart): Support resource limits and requests for each component (@rathberm)
|
||||
- [#20170](https://github.com/apache/superset/pull/20170) feat: add samples endpoint (@zhaoyongjie)
|
||||
- [#19381](https://github.com/apache/superset/pull/19381) feat: add drag and drop column rearrangement for table viz (@stevetracvc)
|
||||
- [#20136](https://github.com/apache/superset/pull/20136) feat: Add Certified filter to Datasets (@hughhhh)
|
||||
- [#20111](https://github.com/apache/superset/pull/20111) feat(dashboard): Chart title click redirects to Explore in new tab (@kgabryje)
|
||||
- [#20097](https://github.com/apache/superset/pull/20097) feat(plugin-chart-echarts): add support for generic axis to mixed chart (@villebro)
|
||||
- [#20126](https://github.com/apache/superset/pull/20126) feat(dashboard): Add create chart button in dashboard edit mode (@kgabryje)
|
||||
- [#20059](https://github.com/apache/superset/pull/20059) feat: Save column data into json_metadata for all Query executions (@hughhhh)
|
||||
- [#19918](https://github.com/apache/superset/pull/19918) feat(plugin-chart-echarts): support horizontal bar chart (@stephenLYZ)
|
||||
- [#19902](https://github.com/apache/superset/pull/19902) feat: Explore popovers should close on escape (@diegomedina248)
|
||||
- [#20049](https://github.com/apache/superset/pull/20049) feat(dashboard): Rearrange items in chart header controls dropdown (@kgabryje)
|
||||
- [#20030](https://github.com/apache/superset/pull/20030) feat(sip-68): Add DatasourceDAO class to manage querying different datasources easier (@hughhhh)
|
||||
- [#19581](https://github.com/apache/superset/pull/19581) feat(viz-gallery): add search weight for viz-name (@stephenLYZ)
|
||||
- [#19999](https://github.com/apache/superset/pull/19999) feat: RLS for SQL Lab (@betodealmeida)
|
||||
- [#19993](https://github.com/apache/superset/pull/19993) feat(explore): Show confirmation modal if user exits Explore without saving changes (@kgabryje)
|
||||
- [#19873](https://github.com/apache/superset/pull/19873) feat(css): adds `chartId`-based class to dashboard chart holder (@rusackas)
|
||||
- [#20002](https://github.com/apache/superset/pull/20002) feat: deprecate /superset/testconn and migrate to api v1 (@zephyring)
|
||||
- [#19935](https://github.com/apache/superset/pull/19935) feat: deprecate /superset/validate_sql_json migrate to api v1 (@dpgaspar)
|
||||
- [#20015](https://github.com/apache/superset/pull/20015) feat: add new enums for datasource types (@hughhhh)
|
||||
- [#19956](https://github.com/apache/superset/pull/19956) feat: Applitools Cypress workflow (@geido)
|
||||
- [#19852](https://github.com/apache/superset/pull/19852) feat: Run Applitools on public Storybook (@geido)
|
||||
- [#19963](https://github.com/apache/superset/pull/19963) feat: Add cypress test for downloading chart as image (@codemaster08240328)
|
||||
- [#19957](https://github.com/apache/superset/pull/19957) feat: switch from `sqlalchemy-trino` to `trino-python-client` (@dungdm93)
|
||||
- [#19921](https://github.com/apache/superset/pull/19921) feat: deprecate /superset/extra_table_metadata migrate to api v1 (@dpgaspar)
|
||||
- [#19745](https://github.com/apache/superset/pull/19745) feat: simplify SQLite time grain (@betodealmeida)
|
||||
- [#19927](https://github.com/apache/superset/pull/19927) feat(chart & legend): make to enable show legend by default (@prosdev0107)
|
||||
- [#19754](https://github.com/apache/superset/pull/19754) feat: deprecate old API on core superset fave_dashboards (@dpgaspar)
|
||||
- [#19905](https://github.com/apache/superset/pull/19905) feat: simplify `memoized_func` (@betodealmeida)
|
||||
- [#19871](https://github.com/apache/superset/pull/19871) feat(filter): make to hide sort filter when time range (@prosdev0107)
|
||||
- [#19851](https://github.com/apache/superset/pull/19851) feat: add Advanced Analytics into mixed time series chart (@zhaoyongjie)
|
||||
- [#19692](https://github.com/apache/superset/pull/19692) feat: Update ShortKey for stop query running in SqlLab editor (@codemaster08240328)
|
||||
- [#17903](https://github.com/apache/superset/pull/17903) feat: Adds plugin-chart-handlebars (@jdbranham)
|
||||
- [#19748](https://github.com/apache/superset/pull/19748) feat(explore): improve UI in the control panel (@stephenLYZ)
|
||||
- [#19724](https://github.com/apache/superset/pull/19724) feat: 10/15/30 min grain to Pinot (@hughhhh)
|
||||
- [#19696](https://github.com/apache/superset/pull/19696) feat(explore): Replace overlay with alert banner when chart controls change (@kgabryje)
|
||||
- [#19751](https://github.com/apache/superset/pull/19751) feat(explore): Implement data panel redesign (@kgabryje)
|
||||
- [#19598](https://github.com/apache/superset/pull/19598) feat: add empty states to sqlab editor and select (@pkdotson)
|
||||
- [#19450](https://github.com/apache/superset/pull/19450) feat: Remove legacy sql alchemy db connection link from G Sheet connection (@codemaster08240328)
|
||||
- [#19710](https://github.com/apache/superset/pull/19710) feat: Enabling source maps full time (@rusackas)
|
||||
- [#19671](https://github.com/apache/superset/pull/19671) feat: UI override registry (@suddjian)
|
||||
- [#19691](https://github.com/apache/superset/pull/19691) feat(explore): More explicit labels of adhoc filter operators (@kgabryje)
|
||||
- [#19558](https://github.com/apache/superset/pull/19558) feat(explore): Redesign of Run/Save buttons (@kgabryje)
|
||||
- [#19650](https://github.com/apache/superset/pull/19650) feat(embedded): API get embedded dashboard config by uuid (@lilykuang)
|
||||
- [#19310](https://github.com/apache/superset/pull/19310) feat(CRUD): add new empty state (@stephenLYZ)
|
||||
- [#19622](https://github.com/apache/superset/pull/19622) feat(plugin-chart-echarts): add aggregate total for the Pie/Donuct chart (@stephenLYZ)
|
||||
- [#19314](https://github.com/apache/superset/pull/19314) feat: Move Database Import option into DB Connection modal (@lyndsiWilliams)
|
||||
- [#19434](https://github.com/apache/superset/pull/19434) feat: deprecate old API and create new API for dashes created by me (@dpgaspar)
|
||||
- [#19482](https://github.com/apache/superset/pull/19482) feat: add success toast to alerts and reports (@pkdotson)
|
||||
- [#19574](https://github.com/apache/superset/pull/19574) feat: add a `where_in` filter for Jinja2 (@betodealmeida)
|
||||
- [#19458](https://github.com/apache/superset/pull/19458) feat(explore): Move timer, row counter and cached pills to chart container (@kgabryje)
|
||||
- [#19529](https://github.com/apache/superset/pull/19529) feat(explore): Move chart header to top of the page (@kgabryje)
|
||||
- [#19489](https://github.com/apache/superset/pull/19489) feat(CI): clean up Python tests output (@ktmud)
|
||||
- [#19308](https://github.com/apache/superset/pull/19308) feat(explore): SQL popover in datasource panel (@kgabryje)
|
||||
- [#19325](https://github.com/apache/superset/pull/19325) feat(color): support analogous colors to prevent color conflict (@stephenLYZ)
|
||||
- [#19408](https://github.com/apache/superset/pull/19408) feat(dashboard): Implement empty states for empty tabs (@kgabryje)
|
||||
- [#19446](https://github.com/apache/superset/pull/19446) feat(explore): Move chart actions into dropdown (@kgabryje)
|
||||
- [#19394](https://github.com/apache/superset/pull/19394) feat(explore): UI changes in dataset panel on Explore page (@kgabryje)
|
||||
|
||||
**Fixes**
|
||||
|
||||
- [#20382](https://github.com/apache/superset/pull/20382) fix: Allow dataset owners to explore their datasets (@reesercollins)
|
||||
- [#20419](https://github.com/apache/superset/pull/20419) fix(embedded): Retry when executing alert queries to avoid sending transient errors to users as alert failure notifications (@zhaorui2022)
|
||||
- [#20555](https://github.com/apache/superset/pull/20555) fix: Respecting max/min opacities, and adding tests. (@rusackas)
|
||||
- [#20571](https://github.com/apache/superset/pull/20571) fix: Revert #20408 (stacking negative values in echarts bar chart) (@rusackas)
|
||||
- [#20487](https://github.com/apache/superset/pull/20487) fix(database-modal): form in database model effects results of the database list (@stephenLYZ)
|
||||
- [#20488](https://github.com/apache/superset/pull/20488) fix(big-number): big number gets cut off on a Dashboard (@stephenLYZ)
|
||||
- [#16326](https://github.com/apache/superset/pull/16326) fix: SQL Lab cancel query in Redshift database connection does not wo… (@yourssvk)
|
||||
- [#20362](https://github.com/apache/superset/pull/20362) fix: Unable to download the Dashboard as image in case there's an image added through Markdown (@diegomedina248)
|
||||
- [#20543](https://github.com/apache/superset/pull/20543) fix: Removes psycopg2 as a required dependency
|
||||
- [#20442](https://github.com/apache/superset/pull/20442) fix(db): Show the only db install guide when the db is already installed and error is existed while importing file. (@prosdev0107)
|
||||
- [#20483](https://github.com/apache/superset/pull/20483) fix: bump FAB to 4.1.2 (@dpgaspar)
|
||||
- [#20493](https://github.com/apache/superset/pull/20493) fix: correction from mmsql to mssql in setup.py (@mdeshmu)
|
||||
- [#20460](https://github.com/apache/superset/pull/20460) fix: new column UUID conflicts in dual write (@eschutho)
|
||||
- [#20485](https://github.com/apache/superset/pull/20485) fix: Re-add filter-box time granularity/column (@john-bodley)
|
||||
- [#20480](https://github.com/apache/superset/pull/20480) fix(docs): prevent some symbols from being copied in docs (@stephenLYZ)
|
||||
- [#19920](https://github.com/apache/superset/pull/19920) fix(table viz): correctly sort by multiple columns in a table (@stevetracvc)
|
||||
- [#20402](https://github.com/apache/superset/pull/20402) fix: alert & reports active toggle optimistic update (@diegomedina248)
|
||||
- [#20472](https://github.com/apache/superset/pull/20472) fix: Changes the return type of get_permissions to be JSON friendly (@michael-s-molina)
|
||||
- [#20468](https://github.com/apache/superset/pull/20468) fix: async queries limit bug (@AAfghahi)
|
||||
- [#20257](https://github.com/apache/superset/pull/20257) fix(home): Show home page tabs as pills instead of links (@prosdev0107)
|
||||
- [#20340](https://github.com/apache/superset/pull/20340) fix: ensure column name in description is string (@betodealmeida)
|
||||
- [#20350](https://github.com/apache/superset/pull/20350) fix(viz): BigQuery time grain 'minute'/'second' throws an error (@diegomedina248)
|
||||
- [#20384](https://github.com/apache/superset/pull/20384) fix(chart & table): Prevent the dates from wrapping in table chart (@prosdev0107)
|
||||
- [#20404](https://github.com/apache/superset/pull/20404) fix: suppress translation warning in jest (@zhaoyongjie)
|
||||
- [#20451](https://github.com/apache/superset/pull/20451) fix: should raise exception when apply a categorical axis (@zhaoyongjie)
|
||||
- [#20447](https://github.com/apache/superset/pull/20447) fix: table viz sort icon bottom aligned (@diegomedina248)
|
||||
- [#20326](https://github.com/apache/superset/pull/20326) fix(fbprophet): Fix weekly frequencies (@john-bodley)
|
||||
- [#20434](https://github.com/apache/superset/pull/20434) fix(20428): Address-Presto/Trino-Poll-Issue-Refactor (@Thelin90)
|
||||
- [#20411](https://github.com/apache/superset/pull/20411) fix(dashboard): new created chart did not have high lighted effect when using the permalink of chart share in dashboard (@diegomedina248)
|
||||
- [#20261](https://github.com/apache/superset/pull/20261) fix(embedded): CSV download for chart (@lilykuang)
|
||||
- [#20276](https://github.com/apache/superset/pull/20276) fix(cosmetic): cannot find m-r-10 class in superset.less (@Renderz)
|
||||
- [#20420](https://github.com/apache/superset/pull/20420) fix: rm eslint-plugin-translation-vars engines requirement (@stephenLYZ)
|
||||
- [#20409](https://github.com/apache/superset/pull/20409) fix(bar-chart-v2): remove marker control from bar chart V2 (@stephenLYZ)
|
||||
- [#20333](https://github.com/apache/superset/pull/20333) fix(presto): use milliseconds timespec for presto (@mohittt8)
|
||||
- [#20414](https://github.com/apache/superset/pull/20414) fix: key error on permalink fetch for old permalinks (@eschutho)
|
||||
- [#20410](https://github.com/apache/superset/pull/20410) fix: Adding extra metrics issue after chart configuration (@codemaster08240328)
|
||||
- [#20405](https://github.com/apache/superset/pull/20405) fix: Incorrect translations in Chinese in messages.po (@chuancyzhang)
|
||||
- [#20396](https://github.com/apache/superset/pull/20396) fix(plugin-chart-pivot-table): color weight of Conditional formatting metrics not work (@stephenLYZ)
|
||||
- [#20361](https://github.com/apache/superset/pull/20361) fix(fonts): Show the all the A's in our workspace correctly, not funky (@prosdev0107)
|
||||
- [#20383](https://github.com/apache/superset/pull/20383) fix: Unable to export multiple Dashboards with the same name (@diegomedina248)
|
||||
- [#20363](https://github.com/apache/superset/pull/20363) fix: A newly connected database doesn't appear in the databases list if user connected database using the 'plus' button (@diegomedina248)
|
||||
- [#20372](https://github.com/apache/superset/pull/20372) fix: update connection modal to use existing catalog (@pkdotson)
|
||||
- [#20368](https://github.com/apache/superset/pull/20368) fix(VERSIONED_EXPORTS): Ensure dashboards and charts adhere to the VERSIONED_EXPORTS feature flag (@john-bodley)
|
||||
- [#20351](https://github.com/apache/superset/pull/20351) fix: catch some potential errors on dual write (@eschutho)
|
||||
- [#20364](https://github.com/apache/superset/pull/20364) fix: query execution time is not fully displayed in bubble icon (@diegomedina248)
|
||||
- [#20365](https://github.com/apache/superset/pull/20365) fix: Fix typo in Error handling message (@codemaster08240328)
|
||||
- [#19967](https://github.com/apache/superset/pull/19967) fix: A newly connected database doesn't appear in the databases list if user connected database using the 'plus' button (@diegomedina248)
|
||||
- [#20348](https://github.com/apache/superset/pull/20348) fix(docker): Make Gunicorn Keepalive Adjustable (@mdeshmu)
|
||||
- [#19670](https://github.com/apache/superset/pull/19670) fix: Add serviceAccountName to celerybeat pods (@paulinjo)
|
||||
- [#20315](https://github.com/apache/superset/pull/20315) fix(chart): chart gets cut off on the dashboard (@stephenLYZ)
|
||||
- [#20324](https://github.com/apache/superset/pull/20324) fix: superset-ui/core coverage (@zhaoyongjie)
|
||||
- [#20282](https://github.com/apache/superset/pull/20282) fix(explore): Make that see more/see less works correctly with scrolling when error msg is long text. (@prosdev0107)
|
||||
- [#20296](https://github.com/apache/superset/pull/20296) fix: Alpha are unable to perform a second modification to a Dataset when in Explore (@hughhhh)
|
||||
- [#20290](https://github.com/apache/superset/pull/20290) fix: Faulty datetime parser regex (@reesercollins)
|
||||
- [#19761](https://github.com/apache/superset/pull/19761) fix(plugin-chart-echarts): [feature-parity] apply button of annotation layer doesn't work as expected (@stephenLYZ)
|
||||
- [#20263](https://github.com/apache/superset/pull/20263) fix(embedded): accessing variable response before initialization (@zhaorui2022)
|
||||
- [#20274](https://github.com/apache/superset/pull/20274) fix(codecov): improve core code coverage (@stephenLYZ)
|
||||
- [#20187](https://github.com/apache/superset/pull/20187) fix: Database import with cancel_query.. extra field (@codemaster08240328)
|
||||
- [#20237](https://github.com/apache/superset/pull/20237) fix(cosmetic): Fix Datasource Modal Out Of Box (@Renderz)
|
||||
- [#20058](https://github.com/apache/superset/pull/20058) fix: Support the Clipboard API in modern browsers (@diegomedina248)
|
||||
- [#20164](https://github.com/apache/superset/pull/20164) fix(sql lab): View result button is not showing consistently (@diegomedina248)
|
||||
- [#20171](https://github.com/apache/superset/pull/20171) fix(charts list): do not trigger ListViewError exception for anonymous user (@trepmag)
|
||||
- [#20178](https://github.com/apache/superset/pull/20178) fix: While exporting CSV , only the entries on first page are getting downloaded even when user is on other pages #17861 (@LahmerIlyas)
|
||||
- [#20204](https://github.com/apache/superset/pull/20204) fix: Fixes issue where results panel height was incorrect [sc-49045] (@eric-briscoe)
|
||||
- [#20235](https://github.com/apache/superset/pull/20235) fix: Box Plot Chart throws an error when the average (AVG) / SUM is being calculated on the Metrics (@diegomedina248)
|
||||
- [#20088](https://github.com/apache/superset/pull/20088) fix: datatype tracking issue on virtual dataset (@codemaster08240328)
|
||||
- [#20220](https://github.com/apache/superset/pull/20220) fix: dashbaord unable to refresh (@zhaoyongjie)
|
||||
- [#20228](https://github.com/apache/superset/pull/20228) fix: failed samples should throw exception (@zhaoyongjie)
|
||||
- [#20203](https://github.com/apache/superset/pull/20203) fix: move columns to datasource object for bootstrap data (@hughhhh)
|
||||
- [#20151](https://github.com/apache/superset/pull/20151) fix(csv): Ensure df_to_escaped_csv does not coerce integer columns to float (@john-bodley)
|
||||
- [#20221](https://github.com/apache/superset/pull/20221) fix(legacy-plugin-chart-sunburst): linear color scheme not work when secondary metric is provided (@stephenLYZ)
|
||||
- [#20223](https://github.com/apache/superset/pull/20223) fix(legacy-plugin-chart-sunburst): chart broken when secondary metric is removed (@stephenLYZ)
|
||||
- [#20147](https://github.com/apache/superset/pull/20147) fix(cosmetic): Limiting modal height (@rusackas)
|
||||
- [#20206](https://github.com/apache/superset/pull/20206) fix(sql lab): SQL Lab Compile Query Delay (@diegomedina248)
|
||||
- [#20201](https://github.com/apache/superset/pull/20201) fix: unable to set destroyOnClose on ModalTrigger (@zhaoyongjie)
|
||||
- [#20186](https://github.com/apache/superset/pull/20186) fix(db): make to allow to show/hide the password when only creating (@prosdev0107)
|
||||
- [#20127](https://github.com/apache/superset/pull/20127) fix(database): retrival of tables and views from schema for exasol backend (@Nicoretti)
|
||||
- [#19899](https://github.com/apache/superset/pull/19899) fix: always create parameter json field (@pkdotson)
|
||||
- [#20173](https://github.com/apache/superset/pull/20173) fix: avoid while cycle in computeMaxFontSize for big Number run forever when css rule applied (@diegomedina248)
|
||||
- [#20086](https://github.com/apache/superset/pull/20086) fix(css): transparent linear gradient not working in safari (@stephenLYZ)
|
||||
- [#19102](https://github.com/apache/superset/pull/19102) fix: string aggregation is incorrect in PivotTableV2 (@diegomedina248)
|
||||
- [#20011](https://github.com/apache/superset/pull/20011) fix(chart & heatmap): make to fix that y label is rendering out of bounds (@prosdev0107)
|
||||
- [#20142](https://github.com/apache/superset/pull/20142) fix(explore): handle null control sections (@villebro)
|
||||
- [#20128](https://github.com/apache/superset/pull/20128) fix: advanced data type API spec and permission name (@dpgaspar)
|
||||
- [#20107](https://github.com/apache/superset/pull/20107) fix(generic-chart-axes): set x-axis if unset and ff is enabled (@villebro)
|
||||
- [#20018](https://github.com/apache/superset/pull/20018) fix(modal): add primary button loading state to modals (@kgopal492)
|
||||
- [#20099](https://github.com/apache/superset/pull/20099) fix: Add cypress test for report page direct link issue (@codemaster08240328)
|
||||
- [#20068](https://github.com/apache/superset/pull/20068) fix: dbmodal test connection error timeout (@pkdotson)
|
||||
- [#20092](https://github.com/apache/superset/pull/20092) fix: Revert "feat(explore): Show confirmation modal if user exits Explore without saving changes (#19993) (@kgabryje)
|
||||
- [#19939](https://github.com/apache/superset/pull/19939) fix(chart & alert): make to show metrics properly (@prosdev0107)
|
||||
- [#20085](https://github.com/apache/superset/pull/20085) fix: typo in `importexport/api.py` OpenAPI (@betodealmeida)
|
||||
- [#20051](https://github.com/apache/superset/pull/20051) fix(CRUD): make to fix the dancing when crud view is on hover (@prosdev0107)
|
||||
- [#20064](https://github.com/apache/superset/pull/20064) fix(chart & gallery): make to add mixed time-series into recommended charts (@prosdev0107)
|
||||
- [#20013](https://github.com/apache/superset/pull/20013) fix: The dynamic form to connect to Snowflake DB is not returning any errors (@diegomedina248)
|
||||
- [#20029](https://github.com/apache/superset/pull/20029) fix(plugin-chart-echarts): tooltip of big number truncated at then bottom (@stephenLYZ)
|
||||
- [#19914](https://github.com/apache/superset/pull/19914) fix: Refactor SQL engine username logic (@john-bodley)
|
||||
- [#20050](https://github.com/apache/superset/pull/20050) fix: Fixes Tabs style (@michael-s-molina)
|
||||
- [#20048](https://github.com/apache/superset/pull/20048) fix(homepage): make to show indicator when tab is chosen (@prosdev0107)
|
||||
- [#20026](https://github.com/apache/superset/pull/20026) fix(chart & filters): make to padding between textarea and buttons (@prosdev0107)
|
||||
- [#20019](https://github.com/apache/superset/pull/20019) fix(embedded): third party cookies (@lilykuang)
|
||||
- [#20033](https://github.com/apache/superset/pull/20033) fix: Direct Linking issue on report list: 404 status code. (@codemaster08240328)
|
||||
- [#19977](https://github.com/apache/superset/pull/19977) fix(word-cloud): fix randomness of each word's rotation (@ebaratte)
|
||||
- [#20021](https://github.com/apache/superset/pull/20021) fix: native filter truncation rerendering loop on hover (@diegomedina248)
|
||||
- [#20004](https://github.com/apache/superset/pull/20004) fix: URI form is blank when trying to connect from sql lab (@diegomedina248)
|
||||
- [#19841](https://github.com/apache/superset/pull/19841) fix: Table chart column config issue (@codemaster08240328)
|
||||
- [#19877](https://github.com/apache/superset/pull/19877) fix: Making chart update more truthful (@Gwitchr)
|
||||
- [#19996](https://github.com/apache/superset/pull/19996) fix: Use pull_request_target in Cypress Applitools workflow (@geido)
|
||||
- [#19972](https://github.com/apache/superset/pull/19972) fix: revert chore(deps): bump d3-svg-legend in /superset-frontend (#19846) (@villebro)
|
||||
- [#19889](https://github.com/apache/superset/pull/19889) fix: Fix auto-reversion of label/title in the Metrics popover (@diegomedina248)
|
||||
- [#19903](https://github.com/apache/superset/pull/19903) fix(explore): Explore data table tooltip (@Gwitchr)
|
||||
- [#19938](https://github.com/apache/superset/pull/19938) fix(chart & table): make to allow highlight in case of numeric column (@prosdev0107)
|
||||
- [#19839](https://github.com/apache/superset/pull/19839) fix(dashboard): allow users to resize the markdown widget easier (@cccs-Dustin)
|
||||
- [#19887](https://github.com/apache/superset/pull/19887) fix(hive): Workaround for Python 3.9 s3 transfer issue (@john-bodley)
|
||||
- [#19936](https://github.com/apache/superset/pull/19936) fix: OpenAPI docs small fixes (@dpgaspar)
|
||||
- [#19932](https://github.com/apache/superset/pull/19932) fix: can not correctly set force in store (@zhaoyongjie)
|
||||
- [#19930](https://github.com/apache/superset/pull/19930) fix: memoize primitives (@betodealmeida)
|
||||
- [#19926](https://github.com/apache/superset/pull/19926) fix(dataset): DAO update (@betodealmeida)
|
||||
- [#19826](https://github.com/apache/superset/pull/19826) fix: Missing `f` prefix on f-strings (@code-review-doctor)
|
||||
- [#18988](https://github.com/apache/superset/pull/18988) fix(column-header-tooltip): make that hide the tooltip when the cloum… (@prosdev0107)
|
||||
- [#19782](https://github.com/apache/superset/pull/19782) fix: chart import error with virtual dataset (@codemaster08240328)
|
||||
- [#19485](https://github.com/apache/superset/pull/19485) fix: Set fixed maxWidth of the cron schedule modal (@codemaster08240328)
|
||||
- [#19885](https://github.com/apache/superset/pull/19885) fix: Chart download as image issue (@codemaster08240328)
|
||||
- [#19883](https://github.com/apache/superset/pull/19883) fix(allow-db-explore): make to check the allow virtual table explore option by default (@prosdev0107)
|
||||
- [#19835](https://github.com/apache/superset/pull/19835) fix(helm): fix postgresql values (@benjamin-texier)
|
||||
- [#19758](https://github.com/apache/superset/pull/19758) fix(plugin-chart-echarts): [feature parity] annotation line chart color does not work (@stephenLYZ)
|
||||
- [#19879](https://github.com/apache/superset/pull/19879) fix(plugin-chart-handlebars): fix overflow, debounce and control reset (@villebro)
|
||||
- [#19668](https://github.com/apache/superset/pull/19668) fix: Dates alignment in Table viz (@geido)
|
||||
- [#19876](https://github.com/apache/superset/pull/19876) fix: Cannot re-order metrics by drag and drop (@diegomedina248)
|
||||
- [#19840](https://github.com/apache/superset/pull/19840) fix(dashboard-css): make to load saved css template (@prosdev0107)
|
||||
- [#19859](https://github.com/apache/superset/pull/19859) fix: Dashboard report creation error handling (@etr2460)
|
||||
- [#19857](https://github.com/apache/superset/pull/19857) fix: Update eslint error message to reflect location of antd components (@etr2460)
|
||||
- [#19605](https://github.com/apache/superset/pull/19605) fix: Query execution time is displayed as invalid date (@diegomedina248)
|
||||
- [#19694](https://github.com/apache/superset/pull/19694) fix(db & connection): make to show/hide the password when only creating db connection (@prosdev0107)
|
||||
- [#19778](https://github.com/apache/superset/pull/19778) fix: deck.gl GeoJsonLayer Autozoom & fill/stroke options (@diegomedina248)
|
||||
- [#19850](https://github.com/apache/superset/pull/19850) fix: Regression on Data and Alerts & Reports Headers (@diegomedina248)
|
||||
- [#19842](https://github.com/apache/superset/pull/19842) fix: count(distinct column_name) in metrics (@zhaoyongjie)
|
||||
- [#19843](https://github.com/apache/superset/pull/19843) fix(explore): ignore temporary controls in altered pill (@villebro)
|
||||
- [#19800](https://github.com/apache/superset/pull/19800) fix: Cypress tests reliability improvements (@diegomedina248)
|
||||
- [#19575](https://github.com/apache/superset/pull/19575) fix: Show full long number in text email report for table chart. (@codemaster08240328)
|
||||
- [#19429](https://github.com/apache/superset/pull/19429) fix(dashboard): make to filter the correct certified or non-certified… (@prosdev0107)
|
||||
- [#13082](https://github.com/apache/superset/pull/13082) fix(sql_lab): Add custom timestamp type for literal casting for presto timestamps (@kekwan)
|
||||
- [#19797](https://github.com/apache/superset/pull/19797) fix: add missing init files (@suddjian)
|
||||
- [#19672](https://github.com/apache/superset/pull/19672) fix: trap SQLAlchemy common exceptions & throw 422 error instead (@diegomedina248)
|
||||
- [#19288](https://github.com/apache/superset/pull/19288) fix: AlertReportCronScheduler tests (@diegomedina248)
|
||||
- [#19781](https://github.com/apache/superset/pull/19781) fix(world-map): remove categorical color control (@serenajiang)
|
||||
- [#19792](https://github.com/apache/superset/pull/19792) fix(plugin-chart-table): Resetting controls when switching query mode (@kgabryje)
|
||||
- [#19755](https://github.com/apache/superset/pull/19755) fix: small cleanup for created by me dashboards API (@dpgaspar)
|
||||
- [#19784](https://github.com/apache/superset/pull/19784) fix(readme): Remove broken link to legacy gallery (@drluckyspin)
|
||||
- [#19722](https://github.com/apache/superset/pull/19722) fix: dashboard top level tabs edit (@diegomedina248)
|
||||
- [#19777](https://github.com/apache/superset/pull/19777) fix(explore): Double divider if no permissions for adding reports (@kgabryje)
|
||||
- [#19673](https://github.com/apache/superset/pull/19673) fix(import): Add the error alert on failed database import (@prosdev0107)
|
||||
- [#19518](https://github.com/apache/superset/pull/19518) fix: alert/report created by filter inconsistency with table display (@diegomedina248)
|
||||
- [#19700](https://github.com/apache/superset/pull/19700) fix: remove expose (@AAfghahi)
|
||||
- [#19626](https://github.com/apache/superset/pull/19626) fix: deactivate embedding on a dashboard (@suddjian)
|
||||
- [#19472](https://github.com/apache/superset/pull/19472) fix: Dashboard Edit View Tab Headers Hidden when Dashboard Name is Long (@diegomedina248)
|
||||
- [#19311](https://github.com/apache/superset/pull/19311) fix(sql lab): add quotes when autocompleting table names with spaces in the editor (@diegomedina248)
|
||||
- [#19290](https://github.com/apache/superset/pull/19290) fix(sql lab): select edit on query from history doesn't upload editor properly (@diegomedina248)
|
||||
- [#19420](https://github.com/apache/superset/pull/19420) fix: sql lab ctrl t behaved differently from clicking (@Gwitchr)
|
||||
- [#19357](https://github.com/apache/superset/pull/19357) fix: Redirect to full url on 401 (@geido)
|
||||
- [#19001](https://github.com/apache/superset/pull/19001) fix: Line Chart Annotation Info Update (@codemaster08240328)
|
||||
- [#19714](https://github.com/apache/superset/pull/19714) fix: create virtual table with exotic type (@villebro)
|
||||
- [#19708](https://github.com/apache/superset/pull/19708) fix(nav): infinite redirect and upload dataset nav permissions (@ktmud)
|
||||
- [#19430](https://github.com/apache/superset/pull/19430) fix(data-upload): make to change err message (@prosdev0107)
|
||||
- [#19419](https://github.com/apache/superset/pull/19419) fix(alert & report): make to fix the issue when recreate report (@prosdev0107)
|
||||
- [#19371](https://github.com/apache/superset/pull/19371) fix: Reset sorting bar issue in Barchart (@codemaster08240328)
|
||||
- [#19362](https://github.com/apache/superset/pull/19362) fix(sql lab): display the 'View Results' button consistently in the history tab on sync mode (@diegomedina248)
|
||||
- [#19294](https://github.com/apache/superset/pull/19294) fix: improve alerts & reports modal on small devices (@diegomedina248)
|
||||
- [#19257](https://github.com/apache/superset/pull/19257) fix(sql lab): table selector should display all the selected tables (@diegomedina248)
|
||||
- [#19686](https://github.com/apache/superset/pull/19686) fix(plugin-chart-echarts): xAxis scale is not correct when time grain is quarter (@stephenLYZ)
|
||||
- [#19646](https://github.com/apache/superset/pull/19646) fix(explore): Change copy of cross filters checkbox (@kgabryje)
|
||||
- [#19586](https://github.com/apache/superset/pull/19586) fix: Navbar styles and Welcome page text (@geido)
|
||||
- [#19662](https://github.com/apache/superset/pull/19662) fix(database-api): allow search for all columns (@villebro)
|
||||
- [#19656](https://github.com/apache/superset/pull/19656) fix: allow_browser_login in import/export API (@betodealmeida)
|
||||
- [#19628](https://github.com/apache/superset/pull/19628) fix: Table Autosizing Has Unnecessary Horizontal Scroll Bars (@diegomedina248)
|
||||
- [#19573](https://github.com/apache/superset/pull/19573) fix(chart & polygon): make to fix the issue the polygon chart (@prosdev0107)
|
||||
- [#19051](https://github.com/apache/superset/pull/19051) fix: update Permissions for right nav (@AAfghahi)
|
||||
- [#19625](https://github.com/apache/superset/pull/19625) fix(test): make test_clean_requests_after_schema_grant more idempotent (@ktmud)
|
||||
- [#19571](https://github.com/apache/superset/pull/19571) fix: Catch literal colors when theme top level (@geido)
|
||||
- [#19594](https://github.com/apache/superset/pull/19594) fix: spelling of following (@lzm0)
|
||||
- [#19569](https://github.com/apache/superset/pull/19569) fix: check type of url before performing string actions (@eschutho)
|
||||
- [#19570](https://github.com/apache/superset/pull/19570) fix: sqloxide optional (@betodealmeida)
|
||||
- [#19397](https://github.com/apache/superset/pull/19397) fix: weight tooltip issue (@codemaster08240328)
|
||||
- [#19313](https://github.com/apache/superset/pull/19313) fix(sql lab): increase the size of the action icons in the history tab (@diegomedina248)
|
||||
- [#19039](https://github.com/apache/superset/pull/19039) fix(explore): clean data when hidding control (@stephenLYZ)
|
||||
- [#19444](https://github.com/apache/superset/pull/19444) fix: Error Message is cut off in alerts & reports log page (@codemaster08240328)
|
||||
- [#19312](https://github.com/apache/superset/pull/19312) fix: adaptive formatting typo in explore dropdowns (@diegomedina248)
|
||||
- [#19534](https://github.com/apache/superset/pull/19534) fix(explore): Chart header icon paddings (@kgabryje)
|
||||
- [#19399](https://github.com/apache/superset/pull/19399) fix: native filter dropdown not attached to parent node (@diegomedina248)
|
||||
- [#19112](https://github.com/apache/superset/pull/19112) fix: Dashboard import holding issue (@codemaster08240328)
|
||||
- [#19342](https://github.com/apache/superset/pull/19342) fix: Clean up custom css when dashboard unmounted (@codemaster08240328)
|
||||
- [#19491](https://github.com/apache/superset/pull/19491) fix: Dynamic form to connect to Snowflake DB is not displaying authentication errors (@diegomedina248)
|
||||
- [#19528](https://github.com/apache/superset/pull/19528) fix: Correct Ukraine map (@wacken89)
|
||||
- [#19522](https://github.com/apache/superset/pull/19522) fix: add back view for report reload error (@pkdotson)
|
||||
- [#19519](https://github.com/apache/superset/pull/19519) fix: GSheets rendering from global nav (@hughhhh)
|
||||
- [#19358](https://github.com/apache/superset/pull/19358) fix(sqllab): make to hide the delete button of most recent query history (@prosdev0107)
|
||||
- [#19307](https://github.com/apache/superset/pull/19307) fix: Logo resizing on page load (@geido)
|
||||
- [#19166](https://github.com/apache/superset/pull/19166) fix: time filter should be [start, end) (@zhaoyongjie)
|
||||
|
||||
**Others**
|
||||
|
||||
- [#20620](https://github.com/apache/superset/pull/20620) docs: fix link for Apache Superset source code (@dpgaspar)
|
||||
- [#20621](https://github.com/apache/superset/pull/20621) chore: bump FAB to 4.1.3 (@dpgaspar)
|
||||
- [#20486](https://github.com/apache/superset/pull/20486) chore: Updated copy in chart drop down to "View as table" (@lauderbaugh)
|
||||
- [#20116](https://github.com/apache/superset/pull/20116) style(typo): occured -> occurred (@sfirke)
|
||||
- [#20401](https://github.com/apache/superset/pull/20401) chore: add action to welcome new users (@eschutho)
|
||||
- [#20269](https://github.com/apache/superset/pull/20269) chore(docs): Remove cache warming documentation (@ajwhite)
|
||||
- [#20194](https://github.com/apache/superset/pull/20194) chore: Removes unused vars (@michael-s-molina)
|
||||
- [#20321](https://github.com/apache/superset/pull/20321) chore: add breaking change information about form_data datasource_type (@eschutho)
|
||||
- [#20298](https://github.com/apache/superset/pull/20298) chore: Removes no-use-before-define warnings (@michael-s-molina)
|
||||
- [#20337](https://github.com/apache/superset/pull/20337) chore(dashboard): update Edit Dashboard side panel tabs (@codyml)
|
||||
- [#20318](https://github.com/apache/superset/pull/20318) chore: Updates the final steps of the release README (@michael-s-molina)
|
||||
- [#20307](https://github.com/apache/superset/pull/20307) docs: Updates CHANGELOG.md with 1.5.1 fixes (@michael-s-molina)
|
||||
- [#20308](https://github.com/apache/superset/pull/20308) docs(jinja): Detail how to use Jinja parameters (@EBoisseauSierra)
|
||||
- [#20304](https://github.com/apache/superset/pull/20304) chore: superset-ui/core code coverage (@zhaoyongjie)
|
||||
- [#20297](https://github.com/apache/superset/pull/20297) chore(deps): pinning pyjwt to 2.4.0 (@sadpandajoe)
|
||||
- [#20287](https://github.com/apache/superset/pull/20287) chore(deps): bump numpy 1.22.1 and PyJWT to 2.4.0 (@sadpandajoe)
|
||||
- [#20272](https://github.com/apache/superset/pull/20272) chore: remove unused codes for samples (@zhaoyongjie)
|
||||
- [#20289](https://github.com/apache/superset/pull/20289) chore: Adjusts release emails (@michael-s-molina)
|
||||
- [#20180](https://github.com/apache/superset/pull/20180) docs: facelift the docs (@mistercrunch)
|
||||
- [#20249](https://github.com/apache/superset/pull/20249) chore: add event logger to reports/alerts CRUD (@AAfghahi)
|
||||
- [#20273](https://github.com/apache/superset/pull/20273) chore: adjust the psycopg2 version of k8s installation guide (@ensky)
|
||||
- [#20152](https://github.com/apache/superset/pull/20152) refactor(trino): Handful of updates for the Trino engine (@john-bodley)
|
||||
- [#20252](https://github.com/apache/superset/pull/20252) chore: use exc_info to pass errors to log warnings (@eschutho)
|
||||
- [#20154](https://github.com/apache/superset/pull/20154) chore(requirements): Cleanup of Python requirements (@john-bodley)
|
||||
- [#20226](https://github.com/apache/superset/pull/20226) refactor: decouple DataTableControl (@zhaoyongjie)
|
||||
- [#20243](https://github.com/apache/superset/pull/20243) docs: Add beans to users list (@kakoni)
|
||||
- [#20231](https://github.com/apache/superset/pull/20231) docs: Updates release scripts and docs (@michael-s-molina)
|
||||
- [#20196](https://github.com/apache/superset/pull/20196) chore: bumping min version of shillelagh (@AAfghahi)
|
||||
- [#20192](https://github.com/apache/superset/pull/20192) chore: Moves date utils to utils folder (@michael-s-molina)
|
||||
- [#20210](https://github.com/apache/superset/pull/20210) docs: update release instructions (@villebro)
|
||||
- [#20205](https://github.com/apache/superset/pull/20205) chore(deps): bump swagger-ui-react from 4.1.2 to 4.1.3 in /docs (@dependabot[bot])
|
||||
- [#20195](https://github.com/apache/superset/pull/20195) docs: correct case of ClickHouse (@DanRoscigno)
|
||||
- [#20109](https://github.com/apache/superset/pull/20109) refactor: decouple DataTablesPane (@zhaoyongjie)
|
||||
- [#20193](https://github.com/apache/superset/pull/20193) refactor: Removes embedded/index.tsx warnings (@michael-s-molina)
|
||||
- [#20185](https://github.com/apache/superset/pull/20185) docs(security): a typo: Gamma should be in quotes (@jimmytheneutrino)
|
||||
- [#20146](https://github.com/apache/superset/pull/20146) chore: Implement global header in Dashboard (@geido)
|
||||
- [#20174](https://github.com/apache/superset/pull/20174) chore: Disable flaky assert in reports cypress test (@kgabryje)
|
||||
- [#20163](https://github.com/apache/superset/pull/20163) chore: change button name in Sql Lab (@AAfghahi)
|
||||
- [#20157](https://github.com/apache/superset/pull/20157) chore: filter undefined operators (@zhaoyongjie)
|
||||
- [#20140](https://github.com/apache/superset/pull/20140) chore(data-table): make formatted dttm the default (@villebro)
|
||||
- [#20104](https://github.com/apache/superset/pull/20104) chore: fix INTHEWILD sort order and indentation (@villebro)
|
||||
- [#20093](https://github.com/apache/superset/pull/20093) chore: Add the tnum font property to Table components (@geido)
|
||||
- [#20103](https://github.com/apache/superset/pull/20103) docs: Update INTHEWILD.md (@fccoelho)
|
||||
- [#20102](https://github.com/apache/superset/pull/20102) chore: Update aiohttp to 3.8.1 (@diegomedina248)
|
||||
- [#20066](https://github.com/apache/superset/pull/20066) chore: Set limit for a query in execute_sql_statement (@AAfghahi)
|
||||
- [#20032](https://github.com/apache/superset/pull/20032) chore: Change copy to Edit chart in Dashboard dropdown (@geido)
|
||||
- [#20071](https://github.com/apache/superset/pull/20071) chore: Fix and enhance Applitools workflows (@geido)
|
||||
- [#19966](https://github.com/apache/superset/pull/19966) test: make tabbed dashboard a little more complex (@ktmud)
|
||||
- [#19976](https://github.com/apache/superset/pull/19976) perf(plugin-chart-table): Add memoization to avoid rerenders (@kgabryje)
|
||||
- [#20044](https://github.com/apache/superset/pull/20044) chore: Create a generic header component for Explore and Dashboard (@kgabryje)
|
||||
- [#20046](https://github.com/apache/superset/pull/20046) docs: add changelog and updating entries for 1.5.0 (@villebro)
|
||||
- [#19962](https://github.com/apache/superset/pull/19962) chore: add doc link for db migration conflict warning (@ktmud)
|
||||
- [#20034](https://github.com/apache/superset/pull/20034) chore: Changes the no-literal-colors lint rule to throw errors instead of warnings (@michael-s-molina)
|
||||
- [#20031](https://github.com/apache/superset/pull/20031) chore: Run Applitools + Cypress nightly (@geido)
|
||||
- [#20006](https://github.com/apache/superset/pull/20006) chore: Removes hard-coded colors from the plugins - iteration 2 (@michael-s-molina)
|
||||
- [#19130](https://github.com/apache/superset/pull/19130) refactor: Refactor reports for Charts and Dashboards (@AAfghahi)
|
||||
- [#20016](https://github.com/apache/superset/pull/20016) chore: Removes hard-coded colors - iteration 3 (@michael-s-molina)
|
||||
- [#19870](https://github.com/apache/superset/pull/19870) docs: Detail front-end development instructions (@EBoisseauSierra)
|
||||
- [#19971](https://github.com/apache/superset/pull/19971) docs: Add config for running on a WSGI HTTP server (@thinhnd2104)
|
||||
- [#20008](https://github.com/apache/superset/pull/20008) chore: Upgrades Storybook from 6.4.19 to 6.4.22 (@michael-s-molina)
|
||||
- [#20009](https://github.com/apache/superset/pull/20009) docs: typo in chart-params markdown file (@JakobMiksch)
|
||||
- [#19923](https://github.com/apache/superset/pull/19923) chore: Removes hard-coded colors from the plugins - iteration 1 (@michael-s-molina)
|
||||
- [#19954](https://github.com/apache/superset/pull/19954) chore: convert URLShortLinkButton to typescript (@ktmud)
|
||||
- [#19929](https://github.com/apache/superset/pull/19929) chore: change subject name from no_name to named for PNGs in (@AAfghahi)
|
||||
- [#19942](https://github.com/apache/superset/pull/19942) refactor(ReportModal): simplify state reducer and improve error handling (@ktmud)
|
||||
- [#19770](https://github.com/apache/superset/pull/19770) chore: remove druid datasource from the config (@eschutho)
|
||||
- [#19911](https://github.com/apache/superset/pull/19911) chore: Fix broken link for DouroECI (@mavimo)
|
||||
- [#19951](https://github.com/apache/superset/pull/19951) chore: Adds the theme object to chart properties (@michael-s-molina)
|
||||
- [#19813](https://github.com/apache/superset/pull/19813) chore: get embedded user with roles and permissions (@suddjian)
|
||||
- [#19897](https://github.com/apache/superset/pull/19897) chore: Adds a storybook to FilterableTable (@michael-s-molina)
|
||||
- [#19924](https://github.com/apache/superset/pull/19924) chore(reports): Improving logging around failed scheduled reports (@craig-rueda)
|
||||
- [#19906](https://github.com/apache/superset/pull/19906) revert: "fix(sql lab): display the 'View Results' button consistently in the history tab on sync mode" (@Gwitchr)
|
||||
- [#19916](https://github.com/apache/superset/pull/19916) chore(deps): bump react-virtualized-auto-sizer from 1.0.2 to 1.0.6 in /superset-frontend (@dependabot[bot])
|
||||
- [#19888](https://github.com/apache/superset/pull/19888) chore(deps): bump cross-fetch from 3.1.4 to 3.1.5 in /docs (@dependabot[bot])
|
||||
- [#19894](https://github.com/apache/superset/pull/19894) chore(deps-dev): bump eslint-plugin-prettier from 3.3.1 to 4.0.0 in /superset-frontend (@dependabot[bot])
|
||||
- [#19602](https://github.com/apache/superset/pull/19602) docs: Added gtag to docusaurus (@AAfghahi)
|
||||
- [#19878](https://github.com/apache/superset/pull/19878) chore(deps-dev): bump @storybook/client-api from 6.4.19 to 6.4.22 in /superset-frontend (@dependabot[bot])
|
||||
- [#19821](https://github.com/apache/superset/pull/19821) test(native filter): refactor and add new test (@jinghua-qa)
|
||||
- [#19613](https://github.com/apache/superset/pull/19613) chore: Update line-height in SliceHeaderControl (@geido)
|
||||
- [#19616](https://github.com/apache/superset/pull/19616) chore: Update font-sizes in DatabaseModal (@geido)
|
||||
- [#19866](https://github.com/apache/superset/pull/19866) chore: fix explore pills (@villebro)
|
||||
- [#19872](https://github.com/apache/superset/pull/19872) chore: Update aiohttp>=3.7.4 in requirements (@hughhhh)
|
||||
- [#19874](https://github.com/apache/superset/pull/19874) chore: bump rockset>=0.8.10, <0.9 (@hughhhh)
|
||||
- [#19864](https://github.com/apache/superset/pull/19864) chore(deps): bump react-syntax-highlighter from 15.4.5 to 15.5.0 in /superset-frontend (@dependabot[bot])
|
||||
- [#19828](https://github.com/apache/superset/pull/19828) chore: add custom eslint plugin to prevent translation variables (@stephenLYZ)
|
||||
- [#19845](https://github.com/apache/superset/pull/19845) chore(deps): bump react-split from 2.0.9 to 2.0.14 in /superset-frontend (@dependabot[bot])
|
||||
- [#19846](https://github.com/apache/superset/pull/19846) chore(deps): bump d3-svg-legend from 1.13.0 to 2.25.6 in /superset-frontend (@dependabot[bot])
|
||||
- [#19847](https://github.com/apache/superset/pull/19847) chore(deps-dev): bump eslint-plugin-jsx-a11y from 6.4.1 to 6.5.1 in /superset-frontend (@dependabot[bot])
|
||||
- [#19853](https://github.com/apache/superset/pull/19853) chore(frontend-tests): Spelling (@jsoref)
|
||||
- [#19823](https://github.com/apache/superset/pull/19823) docs: updated links for country map scripts (@ktmud)
|
||||
- [#19829](https://github.com/apache/superset/pull/19829) chore(deps-dev): bump babel-loader from 8.2.4 to 8.2.5 in /superset-frontend (@dependabot[bot])
|
||||
- [#19830](https://github.com/apache/superset/pull/19830) chore(deps): bump react-hot-loader from 4.12.20 to 4.13.0 in /superset-frontend (@dependabot[bot])
|
||||
- [#19403](https://github.com/apache/superset/pull/19403) chore(deps-dev): bump babel-loader from 8.2.2 to 8.2.4 in /superset-frontend (@dependabot[bot])
|
||||
- [#19637](https://github.com/apache/superset/pull/19637) chore(deps): bump moment from 2.29.1 to 2.29.2 in /superset-frontend (@dependabot[bot])
|
||||
- [#19681](https://github.com/apache/superset/pull/19681) chore(deps): bump async from 3.2.0 to 3.2.3 in /superset-frontend/cypress-base (@dependabot[bot])
|
||||
- [#19680](https://github.com/apache/superset/pull/19680) chore(deps): bump async from 3.2.0 to 3.2.3 in /superset-websocket (@dependabot[bot])
|
||||
- [#19020](https://github.com/apache/superset/pull/19020) chore(deps): bump url-parse from 1.5.7 to 1.5.10 in /superset-frontend (@dependabot[bot])
|
||||
- [#17978](https://github.com/apache/superset/pull/17978) chore(deps): bump @types/d3-time from 1.1.1 to 3.0.0 in /superset-frontend (@dependabot[bot])
|
||||
- [#19727](https://github.com/apache/superset/pull/19727) chore(deps): bump async from 2.6.3 to 2.6.4 in /docs (@dependabot[bot])
|
||||
- [#19551](https://github.com/apache/superset/pull/19551) chore(deps): bump minimist from 1.2.5 to 1.2.6 in /superset-websocket (@dependabot[bot])
|
||||
- [#19165](https://github.com/apache/superset/pull/19165) chore: simplify error messaging in database modal (@pkdotson)
|
||||
- [#19790](https://github.com/apache/superset/pull/19790) chore: bump postgres from 10 to 14 (@dpgaspar)
|
||||
- [#19480](https://github.com/apache/superset/pull/19480) chore: Update UPDATING.md (@john-bodley)
|
||||
- [#19740](https://github.com/apache/superset/pull/19740) chore: fix grammar error (@zhaoyongjie)
|
||||
- [#19703](https://github.com/apache/superset/pull/19703) chore(build): upgrade less-loader (@ktmud)
|
||||
- [#19736](https://github.com/apache/superset/pull/19736) chore: Updates the Select code owners (@michael-s-molina)
|
||||
- [#19715](https://github.com/apache/superset/pull/19715) docs(install): ubuntu default-libmysqlclient-dev (@cemremengu)
|
||||
- [#19726](https://github.com/apache/superset/pull/19726) chore: bumping shillelagh (@AAfghahi)
|
||||
- [#19699](https://github.com/apache/superset/pull/19699) chore: fix typo (@betodealmeida)
|
||||
- [#19674](https://github.com/apache/superset/pull/19674) chore: upgrade Pillow (@betodealmeida)
|
||||
- [#19647](https://github.com/apache/superset/pull/19647) chore(explore): Change labels "Group by"/"Series" to "Dimensions" (@kgabryje)
|
||||
- [#19679](https://github.com/apache/superset/pull/19679) chore(deps): bump urijs from 1.19.8 to 1.19.11 in /superset-frontend (@dependabot[bot])
|
||||
- [#19638](https://github.com/apache/superset/pull/19638) chore(deps): bump moment from 2.29.1 to 2.29.2 in /docs (@dependabot[bot])
|
||||
- [#19617](https://github.com/apache/superset/pull/19617) chore: updated two github issue templates (@srinify)
|
||||
- [#19666](https://github.com/apache/superset/pull/19666) chore: Remove TwoTone icons (@geido)
|
||||
- [#19614](https://github.com/apache/superset/pull/19614) chore: Remove wrong usage of font-size in ExploreViewContainer (@geido)
|
||||
- [#19593](https://github.com/apache/superset/pull/19593) chore: Update font-sizes in ReportModal (@geido)
|
||||
- [#19611](https://github.com/apache/superset/pull/19611) chore: Update font-sizes in ImportModal (@geido)
|
||||
- [#19615](https://github.com/apache/superset/pull/19615) chore: Update font-sizes in AlertReportModal (@geido)
|
||||
- [#19620](https://github.com/apache/superset/pull/19620) chore: Update font-sizes in QueryPreviewModal (@geido)
|
||||
- [#19641](https://github.com/apache/superset/pull/19641) chore: clean up dynamic translation strings (@villebro)
|
||||
- [#19635](https://github.com/apache/superset/pull/19635) refactor: consistent migration tests organization (@ktmud)
|
||||
- [#19634](https://github.com/apache/superset/pull/19634) test: freeze time for dashboard export test (@ktmud)
|
||||
- [#19606](https://github.com/apache/superset/pull/19606) test(jinja): refactor to functional tests (@villebro)
|
||||
- [#19587](https://github.com/apache/superset/pull/19587) chore: cleanup as unknown conversion (@zhaoyongjie)
|
||||
- [#19562](https://github.com/apache/superset/pull/19562) refactor: Removes the CSS files from the Horizon plugin (@michael-s-molina)
|
||||
- [#19563](https://github.com/apache/superset/pull/19563) refactor: Removes the CSS files from the Paired T-Test plugin (@michael-s-molina)
|
||||
- [#19539](https://github.com/apache/superset/pull/19539) refactor: Removes the CSS files from the Parallel Coordinates plugin (@michael-s-molina)
|
||||
- [#19521](https://github.com/apache/superset/pull/19521) refactor: Removes the CSS files from the Partition plugin (@michael-s-molina)
|
||||
- [#19493](https://github.com/apache/superset/pull/19493) chore: Removes hard-coded colors from legacy-plugin-chart-sankey (@michael-s-molina)
|
||||
- [#19462](https://github.com/apache/superset/pull/19462) chore: Remove FilterBox.less (@geido)
|
||||
- [#19438](https://github.com/apache/superset/pull/19438) chore: Remove crud.less from Datasource (@geido)
|
||||
- [#19517](https://github.com/apache/superset/pull/19517) chore: Enhance ReactChord style with theme vars (@geido)
|
||||
- [#19463](https://github.com/apache/superset/pull/19463) chore: Remove TimeTable.less (@geido)
|
||||
- [#19550](https://github.com/apache/superset/pull/19550) chore(deps): bump minimist from 1.2.5 to 1.2.6 in /superset-embedded-sdk (@dependabot[bot])
|
||||
- [#19566](https://github.com/apache/superset/pull/19566) chore(deps): bump node-forge from 1.2.1 to 1.3.1 in /docs (@dependabot[bot])
|
||||
- [#19552](https://github.com/apache/superset/pull/19552) chore(deps): bump minimist from 1.2.5 to 1.2.6 in /docs (@dependabot[bot])
|
||||
- [#19549](https://github.com/apache/superset/pull/19549) chore(deps): bump minimist from 1.2.5 to 1.2.6 in /superset-frontend/cypress-base (@dependabot[bot])
|
||||
- [#19559](https://github.com/apache/superset/pull/19559) docs: update the typo in the documentation (@fatosmorina)
|
||||
- [#19538](https://github.com/apache/superset/pull/19538) refactor: Removes the CSS files from the Country Map plugin (@michael-s-molina)
|
||||
- [#19536](https://github.com/apache/superset/pull/19536) chore: Removes hard-coded opacity and spacing from the BigNumber plugin (@michael-s-molina)
|
||||
- [#19494](https://github.com/apache/superset/pull/19494) refactor: Removes the CSS files from the Sankey Loop plugin (@michael-s-molina)
|
||||
- [#19492](https://github.com/apache/superset/pull/19492) chore: Remove Legacy Force Directed viz plugin (@geido)
|
||||
- [#19524](https://github.com/apache/superset/pull/19524) chore: Deprecating /my_queries endpoint (@AAfghahi)
|
||||
- [#19467](https://github.com/apache/superset/pull/19467) chore(Explore): Change text when saving a chart in a new dashboard (@geido)
|
||||
- [#19526](https://github.com/apache/superset/pull/19526) chore(database): Creating helper make_url_safe to wrap potential errors (@craig-rueda)
|
||||
- [#19415](https://github.com/apache/superset/pull/19415) chore: Remove Control.less in Explore (@geido)
|
||||
- [#19413](https://github.com/apache/superset/pull/19413) chore: Remove unused less file from profile (@geido)
|
||||
- [#19460](https://github.com/apache/superset/pull/19460) chore: Switch to gender neutral terms (@inclusive-coding-bot)
|
||||
- [#19486](https://github.com/apache/superset/pull/19486) refactor: Removes the CSS files from the Treemap plugin (@michael-s-molina)
|
||||
- [#19488](https://github.com/apache/superset/pull/19488) refactor: Removes the CSS files from the Sunburst plugin (@michael-s-molina)
|
||||
- [#19490](https://github.com/apache/superset/pull/19490) chore: Add theme color to ParallelCoordinates (@geido)
|
||||
- [#19442](https://github.com/apache/superset/pull/19442) chore: Remove FilterbaleTableStyles.less (@geido)
|
||||
- [#19441](https://github.com/apache/superset/pull/19441) chore: Remove StyledQueryButton.less (@geido)
|
||||
- [#19473](https://github.com/apache/superset/pull/19473) refactor: Removes the CSS files from the Rose plugin (@michael-s-molina)
|
||||
- [#19466](https://github.com/apache/superset/pull/19466) chore: Removes hard-coded colors from legacy-plugin-chart-world-map (@michael-s-molina)
|
||||
- [#19465](https://github.com/apache/superset/pull/19465) refactor: Removes the CSS files from the DeckGL plugin (@michael-s-molina)
|
||||
- [#19440](https://github.com/apache/superset/pull/19440) chore: Remove index.less from showSavedQuery (@geido)
|
||||
- [#19230](https://github.com/apache/superset/pull/19230) chore!: remove `ROW_LEVEL_SECURITY` feature flag (permanently enable) (@suddjian)
|
||||
- [#19361](https://github.com/apache/superset/pull/19361) chore: remove deprecated config keys and endpoints code 2.0 (@pkdotson)
|
||||
- [#19261](https://github.com/apache/superset/pull/19261) chore: remove old alerts and configs keys (@pkdotson)
|
||||
- [#19168](https://github.com/apache/superset/pull/19168) chore: bump celery and Flask (@dpgaspar)
|
||||
- [#19049](https://github.com/apache/superset/pull/19049) chore: Remove logo forced width (@geido)
|
||||
- [#19274](https://github.com/apache/superset/pull/19274) chore: remove PUBLIC_ROLE_LIKE_GAMMA deprecated config key (@dpgaspar)
|
||||
- [#19273](https://github.com/apache/superset/pull/19273) chore: remove deprecated celery cli (@dpgaspar)
|
||||
- [#19262](https://github.com/apache/superset/pull/19262) chore: update updating with druid no sql deprecation (@eschutho)
|
||||
- [#19083](https://github.com/apache/superset/pull/19083) chore!: update mutator to take kwargs (@eschutho)
|
||||
- [#19231](https://github.com/apache/superset/pull/19231) chore!: remove `ENABLE_REACT_CRUD_VIEWS` feature flag (permanently enable) (@suddjian)
|
||||
- [#19241](https://github.com/apache/superset/pull/19241) chore(superset 2.0): remove front-end deprecated code (@graceguo-supercat)
|
||||
- [#19107](https://github.com/apache/superset/pull/19107) chore: turn on SQLLAB_BACKEND_PERSISTENCE by default (@ktmud)
|
||||
- [#19142](https://github.com/apache/superset/pull/19142) chore!: turn on Versioned Export in config.py (@AAfghahi)
|
||||
- [#19108](https://github.com/apache/superset/pull/19108) chore: Update UPDATING.md with info about flipping dnd feature flag (@kgabryje)
|
||||
- [#19146](https://github.com/apache/superset/pull/19146) chore!: Remove remove SQLALCHEMY_DOCS_URL and SQLALCHEMY_DISPLAY_TEXT from the config from the config (@hughhhh)
|
||||
- [#19017](https://github.com/apache/superset/pull/19017) chore: Deprecate Python 3.7 (@john-bodley)
|
||||
- [#19113](https://github.com/apache/superset/pull/19113) chore(config): Migrating `ENABLE_JAVASCRIPT_CONTROLS` from app config to a feature flag (@rusackas)
|
||||
- [#19046](https://github.com/apache/superset/pull/19046) chore(explore): Set Drag&Drop feature flags to True by default (@kgabryje)
|
||||
- [#19016](https://github.com/apache/superset/pull/19016) chore: Adding PR to Updating.md (@AAfghahi)
|
||||
- [#18970](https://github.com/apache/superset/pull/18970) chore: Change Dataset legacy editor flag to true (@AAfghahi)
|
||||
|
||||
### 1.5.1 (Thu May 26 14:45:20 2022 +0300)
|
||||
|
||||
|
||||
23
UPDATING.md
23
UPDATING.md
@@ -24,6 +24,24 @@ assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
|
||||
- [22022](https://github.com/apache/superset/pull/22022): HTTP API endpoints `/superset/approve` and `/superset/request_access` have been deprecated and their HTTP methods were changed from GET to POST
|
||||
- [21895](https://github.com/apache/superset/pull/21895): Markdown components had their security increased by adhering to the same sanitization process enforced by Github. This means that some HTML elements found in markdowns are not allowed anymore due to the security risks they impose. If you're deploying Superset in a trusted environment and wish to use some of the blocked elements, then you can use the HTML_SANITIZATION_SCHEMA_EXTENSIONS configuration to extend the default sanitization schema. There's also the option to disable HTML sanitization using the HTML_SANITIZATION configuration but we do not recommend this approach because of the security risks. Given the provided configurations, we don't view the improved sanitization as a breaking change but as a security patch.
|
||||
- [20606](https://github.com/apache/superset/pull/20606): When user clicks on chart title or "Edit chart" button in Dashboard page, Explore opens in the same tab. Clicking while holding cmd/ctrl opens Explore in a new tab. To bring back the old behaviour (always opening Explore in a new tab), flip feature flag `DASHBOARD_EDIT_CHART_IN_NEW_TAB` to `True`.
|
||||
- [20799](https://github.com/apache/superset/pull/20799): Presto and Trino engine will now display tracking URL for running queries in SQL Lab. If for some reason you don't want to show the tracking URL (for example, when your data warehouse hasn't enable access for to Presto or Trino UI), update `TRACKING_URL_TRANSFORMER` in `config.py` to return `None`.
|
||||
- [21002](https://github.com/apache/superset/pull/21002): Support Python 3.10 and bump pandas 1.4 and pyarrow 6.
|
||||
- [21163](https://github.com/apache/superset/pull/21163): When `GENERIC_CHART_AXES` feature flags set to `True`, the Time Grain control will move below the X-Axis control.
|
||||
- [21284](https://github.com/apache/superset/pull/21284): The non-functional `MAX_TABLE_NAMES` config key has been removed.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [21765](https://github.com/apache/superset/pull/21765): For deployments that have enabled the "ALERT_REPORTS" feature flag, Gamma users will no longer have read and write access to Alerts & Reports by default. To give Gamma users the ability to schedule reports from the Dashboard and Explore view like before, create an additional role with "can read on ReportSchedule" and "can write on ReportSchedule" permissions. To further give Gamma users access to the "Alerts & Reports" menu and CRUD view, add "menu access on Manage" and "menu access on Alerts & Report" permissions to the role.
|
||||
|
||||
### Potential Downtime
|
||||
|
||||
### Other
|
||||
|
||||
## 2.0.0
|
||||
|
||||
- [19046](https://github.com/apache/superset/pull/19046): Enables the drag and drop interface in Explore control panel by default. Flips `ENABLE_EXPLORE_DRAG_AND_DROP` and `ENABLE_DND_WITH_CLICK_UX` feature flags to `True`.
|
||||
- [18936](https://github.com/apache/superset/pull/18936): Removes legacy SIP-15 interim logic/flags—specifically the `SIP_15_ENABLED`, `SIP_15_GRACE_PERIOD_END`, `SIP_15_DEFAULT_TIME_RANGE_ENDPOINTS`, and `SIP_15_TOAST_MESSAGE` flags. Time range endpoints are no longer configurable and strictly adhere to the `[start, end)` paradigm, i.e., inclusive of the start and exclusive of the end. Additionally this change removes the now obsolete `time_range_endpoints` from the form-data and resulting in the cache being busted.
|
||||
- [19570](https://github.com/apache/superset/pull/19570): makes [sqloxide](https://pypi.org/project/sqloxide/) optional so the SIP-68 migration can be run on aarch64. If the migration is taking too long installing sqloxide manually should improve the performance.
|
||||
@@ -46,10 +64,6 @@ assists people when migrating to a new version.
|
||||
- [19017](https://github.com/apache/superset/pull/19017): Removes Python 3.7 support.
|
||||
- [18970](https://github.com/apache/superset/pull/18970): The `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag is now `True` by default which disables the legacy datasource editor from being shown in the client.
|
||||
|
||||
### Potential Downtime
|
||||
|
||||
### Other
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Breaking Changes
|
||||
@@ -82,6 +96,7 @@ assists people when migrating to a new version.
|
||||
## 1.4.1
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [17984](https://github.com/apache/superset/pull/17984): Default Flask SECRET_KEY has changed for security reasons. You should always override with your own secret. Set `PREVIOUS_SECRET_KEY` (ex: PREVIOUS_SECRET_KEY = "\2\1thisismyscretkey\1\2\\e\\y\\y\\h") with your previous key and use `superset re-encrypt-secrets` to rotate you current secrets
|
||||
|
||||
### Potential Downtime
|
||||
|
||||
@@ -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 apache/superset:2.0.0
|
||||
x-superset-user: &superset-user root
|
||||
x-superset-depends-on: &superset-depends-on
|
||||
- db
|
||||
|
||||
@@ -69,6 +69,16 @@ REDIS_RESULTS_DB = get_env_variable("REDIS_RESULTS_DB", "1")
|
||||
|
||||
RESULTS_BACKEND = FileSystemCache("/app/superset_home/sqllab")
|
||||
|
||||
CACHE_CONFIG = {
|
||||
"CACHE_TYPE": "redis",
|
||||
"CACHE_DEFAULT_TIMEOUT": 300,
|
||||
"CACHE_KEY_PREFIX": "superset_",
|
||||
"CACHE_REDIS_HOST": REDIS_HOST,
|
||||
"CACHE_REDIS_PORT": REDIS_PORT,
|
||||
"CACHE_REDIS_DB": REDIS_RESULTS_DB,
|
||||
}
|
||||
DATA_CACHE_CONFIG = CACHE_CONFIG
|
||||
|
||||
|
||||
class CeleryConfig(object):
|
||||
BROKER_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}"
|
||||
|
||||
@@ -28,6 +28,8 @@ gunicorn \
|
||||
--threads ${SERVER_THREADS_AMOUNT:-20} \
|
||||
--timeout ${GUNICORN_TIMEOUT:-60} \
|
||||
--keep-alive ${GUNICORN_KEEPALIVE:-2} \
|
||||
--max-requests ${WORKER_MAX_REQUESTS:-0} \
|
||||
--max-requests-jitter ${WORKER_MAX_REQUESTS_JITTER:-0} \
|
||||
--limit-request-line ${SERVER_LIMIT_REQUEST_LINE:-0} \
|
||||
--limit-request-field_size ${SERVER_LIMIT_REQUEST_FIELD_SIZE:-0} \
|
||||
"${FLASK_APP}"
|
||||
|
||||
@@ -14,7 +14,7 @@ You also need to install MySQL or [MariaDB](https://mariadb.com/downloads).
|
||||
|
||||
Ensure that you are using Python version 3.8 or 3.9, then proceed with:
|
||||
|
||||
````bash
|
||||
```bash
|
||||
# Create a virtual environment and activate it (recommended)
|
||||
python3 -m venv venv # setup a python3 virtualenv
|
||||
source venv/bin/activate
|
||||
@@ -47,18 +47,18 @@ Or you can install via our Makefile
|
||||
|
||||
```bash
|
||||
# Create a virtual environment and activate it (recommended)
|
||||
$ python3 -m venv venv # setup a python3 virtualenv
|
||||
$ source venv/bin/activate
|
||||
python3 -m venv venv # setup a python3 virtualenv
|
||||
source venv/bin/activate
|
||||
|
||||
# install pip packages + pre-commit
|
||||
$ make install
|
||||
make install
|
||||
|
||||
# Install superset pip packages and setup env only
|
||||
$ make superset
|
||||
make superset
|
||||
|
||||
# Setup pre-commit only
|
||||
$ make pre-commit
|
||||
````
|
||||
make pre-commit
|
||||
```
|
||||
|
||||
**Note: the FLASK_APP env var should not need to be set, as it's currently controlled
|
||||
via `.flaskenv`, however if needed, it should be set to `superset.app:create_app()`**
|
||||
@@ -103,4 +103,5 @@ app.logger.info(form_data)
|
||||
```
|
||||
|
||||
### Frontend Assets
|
||||
|
||||
See [Building Frontend Assets Locally](https://github.com/apache/superset/blob/master/CONTRIBUTING.md#frontend)
|
||||
|
||||
@@ -126,6 +126,7 @@ SLACK_API_TOKEN = "xoxb-"
|
||||
# Email configuration
|
||||
SMTP_HOST = "smtp.sendgrid.net" #change to your host
|
||||
SMTP_STARTTLS = True
|
||||
SMTP_SSL_SERVER_AUTH = True # If your using an SMTP server with a valid certificate
|
||||
SMTP_SSL = False
|
||||
SMTP_USER = "your_user"
|
||||
SMTP_PORT = 2525 # your port eg. 587
|
||||
|
||||
@@ -129,7 +129,7 @@ Finish installing by running through the following commands:
|
||||
|
||||
```
|
||||
# Create an admin user in your metadata database (use `admin` as username to be able to load the examples)
|
||||
$ export FLASK_APP=superset
|
||||
export FLASK_APP=superset
|
||||
superset fab create-admin
|
||||
|
||||
# Load some data to play with
|
||||
|
||||
@@ -44,7 +44,7 @@ all of the required dependencies. Docker Desktop [recently added support for Win
|
||||
following command:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/apache/superset.git
|
||||
git clone https://github.com/apache/superset.git
|
||||
```
|
||||
|
||||
Once that command completes successfully, you should see a new `superset` folder in your
|
||||
@@ -55,14 +55,14 @@ current directory.
|
||||
Navigate to the folder you created in step 1:
|
||||
|
||||
```bash
|
||||
$ cd superset
|
||||
cd superset
|
||||
```
|
||||
|
||||
When working on master branch, run the following commands:
|
||||
|
||||
```bash
|
||||
$ docker-compose -f docker-compose-non-dev.yml pull
|
||||
$ docker-compose -f docker-compose-non-dev.yml up
|
||||
docker-compose -f docker-compose-non-dev.yml pull
|
||||
docker-compose -f docker-compose-non-dev.yml up
|
||||
```
|
||||
|
||||
Alternatively, you can also run a specific version of Superset by first checking out
|
||||
@@ -70,9 +70,9 @@ the branch/tag, and then starting `docker-compose` with the `TAG` variable.
|
||||
For example, to run the 1.4.0 version, run the following commands:
|
||||
|
||||
```bash
|
||||
% git checkout 1.4.0
|
||||
$ TAG=1.4.0 docker-compose -f docker-compose-non-dev.yml pull
|
||||
$ TAG=1.4.0 docker-compose -f docker-compose-non-dev.yml up
|
||||
git checkout 1.4.0
|
||||
TAG=1.4.0 docker-compose -f docker-compose-non-dev.yml pull
|
||||
TAG=1.4.0 docker-compose -f docker-compose-non-dev.yml up
|
||||
```
|
||||
|
||||
You should see a wall of logging output from the containers being launched on your machine. Once
|
||||
|
||||
@@ -13,7 +13,7 @@ geospatial charts.
|
||||
|
||||
Here are a **few different ways you can get started with Superset**:
|
||||
|
||||
- Download the [source from Apache Foundation's website](https://dist.apache.org/repos/dist/release/superset/1.4.1/)
|
||||
- Download the [source from Apache Foundation's website](https://dist.apache.org/repos/dist/release/superset/)
|
||||
- Download the latest Superset version from [Pypi here](https://pypi.org/project/apache-superset/)
|
||||
- Setup Superset locally with one command
|
||||
using [Docker Compose](installation/installing-superset-using-docker-compose)
|
||||
|
||||
@@ -131,6 +131,28 @@ For example, the filters `client_id=4` and `client_id=5`, applied to a role,
|
||||
will result in users of that role having `client_id=4` AND `client_id=5`
|
||||
added to their query, which can never be true.
|
||||
|
||||
### Content Security Policiy (CSP)
|
||||
|
||||
[Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) is an added
|
||||
layer of security that helps to detect and mitigate certain types of attacks, including
|
||||
Cross-Site Scripting (XSS) and data injection attacks.
|
||||
|
||||
CSP makes it possible for server administrators to reduce or eliminate the vectors by which XSS can
|
||||
occur by specifying the domains that the browser should consider to be valid sources of executable scripts.
|
||||
A CSP compatible browser will then only execute scripts loaded in source files received from those allowed domains,
|
||||
ignoring all other scripts (including inline scripts and event-handling HTML attributes).
|
||||
|
||||
A policy is described using a series of policy directives, each of which describes the policy for
|
||||
a certain resource type or policy area. You can check possible directives
|
||||
[here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy).
|
||||
|
||||
It's extremely important to correclty configure a Content Security Policy when deploying Superset to
|
||||
prevent many types of attacks. For that matter, Superset provides the ` TALISMAN_CONFIG` key in `config.py`
|
||||
where admnistrators can define the policy. When running in production mode, Superset will check for the presence
|
||||
of a policy and if it's not able to find one, it will issue a warning with the security risks. For environments
|
||||
where CSP policies are defined outside of Superset using other software, administrators can disable
|
||||
the warning using the `CONTENT_SECURITY_POLICY_WARNING` key in `config.py`.
|
||||
|
||||
### Reporting Security Vulnerabilities
|
||||
|
||||
Apache Software Foundation takes a rigorous standpoint in annihilating the security issues in its
|
||||
|
||||
@@ -60,7 +60,7 @@ colorama==0.4.4
|
||||
# via
|
||||
# apache-superset
|
||||
# flask-appbuilder
|
||||
convertdate==2.3.2
|
||||
convertdate==2.4.0
|
||||
# via holidays
|
||||
cron-descriptor==1.2.24
|
||||
# via apache-superset
|
||||
@@ -86,7 +86,7 @@ flask==2.0.3
|
||||
# flask-migrate
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
flask-appbuilder==4.0.0
|
||||
flask-appbuilder==4.1.3
|
||||
# via apache-superset
|
||||
flask-babel==1.0.0
|
||||
# via flask-appbuilder
|
||||
@@ -126,7 +126,7 @@ gunicorn==20.1.0
|
||||
# via apache-superset
|
||||
hashids==1.3.1
|
||||
# via apache-superset
|
||||
holidays==0.10.3
|
||||
holidays==0.14.2
|
||||
# via apache-superset
|
||||
humanize==3.11.0
|
||||
# via apache-superset
|
||||
|
||||
@@ -32,4 +32,4 @@ superset init
|
||||
|
||||
echo "Running tests"
|
||||
|
||||
pytest --durations-min=2 --maxfail=1 --cov-report= --cov=superset "$@"
|
||||
pytest --durations-min=2 --maxfail=1 --cov-report= --cov=superset ./tests/integration_tests "$@"
|
||||
|
||||
6
setup.py
6
setup.py
@@ -77,7 +77,7 @@ setup(
|
||||
"cryptography>=3.3.2",
|
||||
"deprecation>=2.1.0, <2.2.0",
|
||||
"flask>=2.0.0, <3.0.0",
|
||||
"flask-appbuilder>=4.0.0, <5.0.0",
|
||||
"flask-appbuilder>=4.1.3, <5.0.0",
|
||||
"flask-caching>=1.10.0",
|
||||
"flask-compress",
|
||||
"flask-talisman",
|
||||
@@ -88,7 +88,7 @@ setup(
|
||||
"graphlib-backport",
|
||||
"gunicorn>=20.1.0",
|
||||
"hashids>=1.3.1, <2",
|
||||
"holidays==0.10.3", # PINNED! https://github.com/dr-prodigy/python-holidays/issues/406
|
||||
"holidays==0.14.2",
|
||||
"humanize",
|
||||
"isodate",
|
||||
"markdown>=3.0",
|
||||
@@ -149,7 +149,7 @@ setup(
|
||||
"impala": ["impyla>0.16.2, <0.17"],
|
||||
"kusto": ["sqlalchemy-kusto>=1.0.1, <2"],
|
||||
"kylin": ["kylinpy>=2.8.1, <2.9"],
|
||||
"mmsql": ["pymssql>=2.1.4, <2.2"],
|
||||
"mssql": ["pymssql>=2.1.4, <2.2"],
|
||||
"mysql": ["mysqlclient>=2.1.0, <3"],
|
||||
"oracle": ["cx-Oracle>8.0.0, <8.1"],
|
||||
"pinot": ["pinotdb>=0.3.3, <0.4"],
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('Dashboard edit markdown', () => {
|
||||
cy.get('[data-test="dashboard-markdown-editor"]')
|
||||
.should(
|
||||
'have.text',
|
||||
'✨Markdown✨Markdown✨MarkdownClick here to edit markdown',
|
||||
'✨Markdown\n✨Markdown\n✨Markdown\n\nClick here to edit markdown',
|
||||
)
|
||||
.click();
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ describe('Time range filter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Custom time_range params', () => {
|
||||
xit('Custom time_range params', () => {
|
||||
const formData = {
|
||||
...FORM_DATA_DEFAULTS,
|
||||
viz_type: 'line',
|
||||
|
||||
27864
superset-frontend/package-lock.json
generated
27864
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superset",
|
||||
"version": "0.0.0dev",
|
||||
"version": "2.0.1",
|
||||
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
||||
"keywords": [
|
||||
"big",
|
||||
@@ -126,7 +126,7 @@
|
||||
"d3-array": "^1.2.4",
|
||||
"d3-color": "^1.2.0",
|
||||
"d3-scale": "^2.1.2",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"dom-to-image-more": "^2.10.1",
|
||||
"emotion-rgba": "0.0.9",
|
||||
"fast-glob": "^3.2.7",
|
||||
"fontsource-fira-code": "^4.0.0",
|
||||
@@ -170,7 +170,6 @@
|
||||
"react-jsonschema-form": "^1.2.0",
|
||||
"react-lines-ellipsis": "^0.15.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-resize-detector": "^6.7.6",
|
||||
"react-reverse-portal": "^2.0.1",
|
||||
@@ -237,7 +236,6 @@
|
||||
"@testing-library/react-hooks": "^5.0.3",
|
||||
"@testing-library/user-event": "^12.7.0",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/dom-to-image": "^2.6.0",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.6",
|
||||
"@types/fetch-mock": "^7.3.2",
|
||||
|
||||
@@ -34,7 +34,7 @@ export const annotationsAndLayersControls: ControlPanelSectionConfig = {
|
||||
label: '',
|
||||
default: annotationLayers,
|
||||
description: t('Annotation Layers'),
|
||||
renderTrigger: true,
|
||||
renderTrigger: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -363,6 +363,10 @@ export interface ControlPanelConfig {
|
||||
standardizedFormData: StandardizedFormDataInterface;
|
||||
},
|
||||
) => QueryFormData;
|
||||
updateStandardizedState?: (
|
||||
prevState: StandardizedState,
|
||||
currState: StandardizedState,
|
||||
) => StandardizedState;
|
||||
}
|
||||
|
||||
export type ControlOverrides = {
|
||||
|
||||
@@ -37,16 +37,21 @@ export const getOpacity = (
|
||||
extremeValue: number,
|
||||
minOpacity = MIN_OPACITY_BOUNDED,
|
||||
maxOpacity = MAX_OPACITY,
|
||||
) =>
|
||||
extremeValue === cutoffPoint
|
||||
? maxOpacity
|
||||
: round(
|
||||
Math.abs(
|
||||
((maxOpacity - minOpacity) / (extremeValue - cutoffPoint)) *
|
||||
(value - cutoffPoint),
|
||||
) + minOpacity,
|
||||
2,
|
||||
);
|
||||
) => {
|
||||
if (extremeValue === cutoffPoint) {
|
||||
return maxOpacity;
|
||||
}
|
||||
return Math.min(
|
||||
maxOpacity,
|
||||
round(
|
||||
Math.abs(
|
||||
((maxOpacity - minOpacity) / (extremeValue - cutoffPoint)) *
|
||||
(value - cutoffPoint),
|
||||
) + minOpacity,
|
||||
2,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const getColorFunction = (
|
||||
{
|
||||
|
||||
@@ -50,6 +50,9 @@ describe('getOpacity', () => {
|
||||
expect(getOpacity(100, 100, 50)).toEqual(0.05);
|
||||
expect(getOpacity(100, 100, 100, 0, 0.8)).toEqual(0.8);
|
||||
expect(getOpacity(100, 100, 50, 0, 1)).toEqual(0);
|
||||
expect(getOpacity(999, 100, 50, 0, 1)).toEqual(1);
|
||||
expect(getOpacity(100, 100, 50, 0.99, 1)).toEqual(0.99);
|
||||
expect(getOpacity(99, 100, 50, 0, 1)).toEqual(0.02);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
"name": "@superset-ui/core",
|
||||
"version": "0.18.25",
|
||||
"description": "Superset UI core",
|
||||
"keywords": [
|
||||
"superset"
|
||||
],
|
||||
"homepage": "https://github.com/apache-superset/superset-ui#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/apache-superset/superset-ui/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/apache-superset/superset-ui.git"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "Superset",
|
||||
"sideEffects": false,
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
@@ -9,28 +22,6 @@
|
||||
"esm",
|
||||
"lib"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/apache-superset/superset-ui.git"
|
||||
},
|
||||
"keywords": [
|
||||
"superset"
|
||||
],
|
||||
"author": "Superset",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/apache-superset/superset-ui/issues"
|
||||
},
|
||||
"homepage": "https://github.com/apache-superset/superset-ui#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"fetch-mock": "^6.5.2",
|
||||
"jest-mock-console": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"@types/d3-format": "^1.3.0",
|
||||
@@ -38,13 +29,14 @@
|
||||
"@types/d3-scale": "^2.1.1",
|
||||
"@types/d3-time": "^3.0.0",
|
||||
"@types/d3-time-format": "^2.1.0",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/fetch-mock": "^7.3.3",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/math-expression-evaluator": "^1.2.1",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/prop-types": "^15.7.2",
|
||||
"@types/rison": "0.0.6",
|
||||
"@types/seedrandom": "^2.4.28",
|
||||
"@types/fetch-mock": "^7.3.3",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/prop-types": "^15.7.2",
|
||||
"@vx/responsive": "^0.0.199",
|
||||
"csstype": "^2.6.4",
|
||||
"d3-format": "^1.3.2",
|
||||
@@ -58,12 +50,20 @@
|
||||
"math-expression-evaluator": "^1.3.8",
|
||||
"pretty-ms": "^7.0.0",
|
||||
"react-error-boundary": "^1.2.5",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-markdown": "^8.0.3",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"rehype-sanitize": "^5.0.1",
|
||||
"reselect": "^4.0.0",
|
||||
"rison": "^0.1.1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"fetch-mock": "^6.5.2",
|
||||
"jest-mock-console": "^1.0.0",
|
||||
"resize-observer-polyfill": "1.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
@@ -71,8 +71,11 @@
|
||||
"@types/react": "*",
|
||||
"@types/react-loadable": "*",
|
||||
"@types/tinycolor2": "*",
|
||||
"tinycolor2": "*",
|
||||
"react": "^16.13.1",
|
||||
"react-loadable": "^5.5.0"
|
||||
"react-loadable": "^5.5.0",
|
||||
"tinycolor2": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function FallbackComponent({
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<b>Oops! An error occured!</b>
|
||||
<b>Oops! An error occurred!</b>
|
||||
</div>
|
||||
<code>{error ? error.toString() : 'Unknown Error'}</code>
|
||||
</div>
|
||||
|
||||
@@ -16,38 +16,44 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactMarkdown, { MarkdownAbstractSyntaxTree } from 'react-markdown';
|
||||
// @ts-ignore no types available
|
||||
import htmlParser from 'react-markdown/plugins/html-parser';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import { merge } from 'lodash';
|
||||
import { FeatureFlag, isFeatureEnabled } from '../utils';
|
||||
|
||||
interface SafeMarkdownProps {
|
||||
source: string;
|
||||
htmlSanitization?: boolean;
|
||||
htmlSchemaOverrides?: typeof defaultSchema;
|
||||
}
|
||||
|
||||
function isSafeMarkup(node: MarkdownAbstractSyntaxTree) {
|
||||
return node.type === 'html' && node.value
|
||||
? /href="(javascript|vbscript|file):.*"/gim.test(node.value) === false
|
||||
: true;
|
||||
}
|
||||
function SafeMarkdown({
|
||||
source,
|
||||
htmlSanitization = true,
|
||||
htmlSchemaOverrides = {},
|
||||
}: SafeMarkdownProps) {
|
||||
const displayHtml = isFeatureEnabled(FeatureFlag.DISPLAY_MARKDOWN_HTML);
|
||||
const escapeHtml = isFeatureEnabled(FeatureFlag.ESCAPE_MARKDOWN_HTML);
|
||||
|
||||
function SafeMarkdown({ source }: SafeMarkdownProps) {
|
||||
const rehypePlugins = useMemo(() => {
|
||||
const rehypePlugins: any = [];
|
||||
if (displayHtml && !escapeHtml) {
|
||||
rehypePlugins.push(rehypeRaw);
|
||||
if (htmlSanitization) {
|
||||
const schema = merge(defaultSchema, htmlSchemaOverrides);
|
||||
rehypePlugins.push([rehypeSanitize, schema]);
|
||||
}
|
||||
}
|
||||
return rehypePlugins;
|
||||
}, [displayHtml, escapeHtml, htmlSanitization, htmlSchemaOverrides]);
|
||||
|
||||
// React Markdown escapes HTML by default
|
||||
return (
|
||||
<ReactMarkdown
|
||||
source={source}
|
||||
escapeHtml={isFeatureEnabled(FeatureFlag.ESCAPE_MARKDOWN_HTML)}
|
||||
skipHtml={!isFeatureEnabled(FeatureFlag.DISPLAY_MARKDOWN_HTML)}
|
||||
allowNode={isSafeMarkup}
|
||||
astPlugins={[
|
||||
htmlParser({
|
||||
isValidNode: (node: MarkdownAbstractSyntaxTree) =>
|
||||
node.type !== 'script',
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
<ReactMarkdown rehypePlugins={rehypePlugins} skipHtml={!displayHtml}>
|
||||
{source}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ const SupersetClient: SupersetClientInterface = {
|
||||
init: force => getInstance().init(force),
|
||||
isAuthenticated: () => getInstance().isAuthenticated(),
|
||||
post: request => getInstance().post(request),
|
||||
postForm: (...args) => getInstance().postForm(...args),
|
||||
put: request => getInstance().put(request),
|
||||
reAuthenticate: () => getInstance().reAuthenticate(),
|
||||
request: request => getInstance().request(request),
|
||||
|
||||
@@ -119,6 +119,36 @@ export default class SupersetClientClass {
|
||||
return this.getCSRFToken();
|
||||
}
|
||||
|
||||
async postForm(url: string, payload: Record<string, any>, target = '_blank') {
|
||||
if (url) {
|
||||
await this.ensureAuth();
|
||||
const hiddenForm = document.createElement('form');
|
||||
hiddenForm.action = url;
|
||||
hiddenForm.method = 'POST';
|
||||
hiddenForm.target = target;
|
||||
const payloadWithToken: Record<string, any> = {
|
||||
...payload,
|
||||
csrf_token: this.csrfToken!,
|
||||
};
|
||||
|
||||
if (this.guestToken) {
|
||||
payloadWithToken.guest_token = this.guestToken;
|
||||
}
|
||||
|
||||
Object.entries(payloadWithToken).forEach(([key, value]) => {
|
||||
const data = document.createElement('input');
|
||||
data.type = 'hidden';
|
||||
data.name = key;
|
||||
data.value = value;
|
||||
hiddenForm.appendChild(data);
|
||||
});
|
||||
|
||||
document.body.appendChild(hiddenForm);
|
||||
hiddenForm.submit();
|
||||
document.body.removeChild(hiddenForm);
|
||||
}
|
||||
}
|
||||
|
||||
async reAuthenticate() {
|
||||
return this.init(true);
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ export interface SupersetClientInterface
|
||||
| 'delete'
|
||||
| 'get'
|
||||
| 'post'
|
||||
| 'postForm'
|
||||
| 'put'
|
||||
| 'request'
|
||||
| 'init'
|
||||
|
||||
@@ -178,9 +178,9 @@ export function isTimeseriesAnnotationResult(
|
||||
}
|
||||
|
||||
export function isRecordAnnotationResult(
|
||||
result: AnnotationResult,
|
||||
result: any,
|
||||
): result is RecordAnnotationResult {
|
||||
return 'columns' in result && 'records' in result;
|
||||
return Array.isArray(result?.columns) && Array.isArray(result?.records);
|
||||
}
|
||||
|
||||
export type AnnotationData = { [key: string]: AnnotationResult };
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface ChartDataResponseResult {
|
||||
/**
|
||||
* Data for the annotation layer.
|
||||
*/
|
||||
annotation_data: AnnotationData[] | null;
|
||||
annotation_data: AnnotationData | null;
|
||||
cache_key: string | null;
|
||||
cache_timeout: number | null;
|
||||
cached_dttm: string | null;
|
||||
|
||||
@@ -56,7 +56,7 @@ export default class Translator {
|
||||
*/
|
||||
addTranslation(key: string, texts: ReadonlyArray<string>) {
|
||||
const translations = this.i18n.options.locale_data.superset;
|
||||
if (key in translations) {
|
||||
if (process.env.WEBPACK_MODE !== 'test' && key in translations) {
|
||||
logging.warn(`Duplicate translation key "${key}", will override.`);
|
||||
}
|
||||
translations[key] = texts;
|
||||
|
||||
@@ -121,7 +121,7 @@ describe('SuperChart', () => {
|
||||
);
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
wrapper.update();
|
||||
expect(wrapper.text()).toContain('Oops! An error occured!');
|
||||
expect(wrapper.text()).toContain('Oops! An error occurred!');
|
||||
});
|
||||
it('renders custom FallbackComponent', () => {
|
||||
expectedErrors = 1;
|
||||
|
||||
@@ -30,21 +30,23 @@ describe('SupersetClient', () => {
|
||||
|
||||
afterEach(SupersetClient.reset);
|
||||
|
||||
it('exposes reset, configure, init, get, post, isAuthenticated, and reAuthenticate methods', () => {
|
||||
it('exposes reset, configure, init, get, post, postForm, isAuthenticated, and reAuthenticate methods', () => {
|
||||
expect(typeof SupersetClient.configure).toBe('function');
|
||||
expect(typeof SupersetClient.init).toBe('function');
|
||||
expect(typeof SupersetClient.get).toBe('function');
|
||||
expect(typeof SupersetClient.post).toBe('function');
|
||||
expect(typeof SupersetClient.postForm).toBe('function');
|
||||
expect(typeof SupersetClient.isAuthenticated).toBe('function');
|
||||
expect(typeof SupersetClient.reAuthenticate).toBe('function');
|
||||
expect(typeof SupersetClient.request).toBe('function');
|
||||
expect(typeof SupersetClient.reset).toBe('function');
|
||||
});
|
||||
|
||||
it('throws if you call init, get, post, isAuthenticated, or reAuthenticate before configure', () => {
|
||||
it('throws if you call init, get, post, postForm, isAuthenticated, or reAuthenticate before configure', () => {
|
||||
expect(SupersetClient.init).toThrow();
|
||||
expect(SupersetClient.get).toThrow();
|
||||
expect(SupersetClient.post).toThrow();
|
||||
expect(SupersetClient.postForm).toThrow();
|
||||
expect(SupersetClient.isAuthenticated).toThrow();
|
||||
expect(SupersetClient.reAuthenticate).toThrow();
|
||||
expect(SupersetClient.request).toThrow();
|
||||
|
||||
@@ -605,4 +605,107 @@ describe('SupersetClientClass', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('.postForm()', () => {
|
||||
const protocol = 'https:';
|
||||
const host = 'host';
|
||||
const mockPostFormEndpoint = '/post_form/url';
|
||||
const mockPostFormUrl = `${protocol}//${host}${mockPostFormEndpoint}`;
|
||||
const guestToken = 'test-guest-token';
|
||||
const postFormPayload = { number: 123, array: [1, 2, 3] };
|
||||
|
||||
let authSpy: jest.SpyInstance;
|
||||
let client: SupersetClientClass;
|
||||
let appendChild: any;
|
||||
let removeChild: any;
|
||||
let submit: any;
|
||||
let createElement: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
client = new SupersetClientClass({ protocol, host });
|
||||
authSpy = jest.spyOn(SupersetClientClass.prototype, 'ensureAuth');
|
||||
await client.init();
|
||||
appendChild = jest.fn();
|
||||
removeChild = jest.fn();
|
||||
submit = jest.fn();
|
||||
createElement = jest.fn(() => ({
|
||||
appendChild: jest.fn(),
|
||||
submit,
|
||||
}));
|
||||
|
||||
document.createElement = createElement as any;
|
||||
document.body.appendChild = appendChild;
|
||||
document.body.removeChild = removeChild;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('makes postForm request', async () => {
|
||||
await client.postForm(mockPostFormUrl, {});
|
||||
|
||||
const hiddenForm = createElement.mock.results[0].value;
|
||||
const csrfTokenInput = createElement.mock.results[1].value;
|
||||
|
||||
expect(createElement.mock.calls).toHaveLength(2);
|
||||
|
||||
expect(hiddenForm.action).toBe(mockPostFormUrl);
|
||||
expect(hiddenForm.method).toBe('POST');
|
||||
expect(hiddenForm.target).toBe('_blank');
|
||||
|
||||
expect(csrfTokenInput.type).toBe('hidden');
|
||||
expect(csrfTokenInput.name).toBe('csrf_token');
|
||||
expect(csrfTokenInput.value).toBe(1234);
|
||||
|
||||
expect(appendChild.mock.calls).toHaveLength(1);
|
||||
expect(removeChild.mock.calls).toHaveLength(1);
|
||||
expect(authSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('makes postForm request with guest token', async () => {
|
||||
client = new SupersetClientClass({ protocol, host, guestToken });
|
||||
await client.init();
|
||||
|
||||
await client.postForm(mockPostFormUrl, {});
|
||||
|
||||
const guestTokenInput = createElement.mock.results[2].value;
|
||||
|
||||
expect(createElement.mock.calls).toHaveLength(3);
|
||||
|
||||
expect(guestTokenInput.type).toBe('hidden');
|
||||
expect(guestTokenInput.name).toBe('guest_token');
|
||||
expect(guestTokenInput.value).toBe(guestToken);
|
||||
|
||||
expect(appendChild.mock.calls).toHaveLength(1);
|
||||
expect(removeChild.mock.calls).toHaveLength(1);
|
||||
expect(authSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('makes postForm request with payload', async () => {
|
||||
await client.postForm(mockPostFormUrl, { form_data: postFormPayload });
|
||||
|
||||
const postFormPayloadInput = createElement.mock.results[1].value;
|
||||
|
||||
expect(createElement.mock.calls).toHaveLength(3);
|
||||
|
||||
expect(postFormPayloadInput.type).toBe('hidden');
|
||||
expect(postFormPayloadInput.name).toBe('form_data');
|
||||
expect(postFormPayloadInput.value).toBe(postFormPayload);
|
||||
|
||||
expect(appendChild.mock.calls).toHaveLength(1);
|
||||
expect(removeChild.mock.calls).toHaveLength(1);
|
||||
expect(submit.mock.calls).toHaveLength(1);
|
||||
expect(authSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should do nothing when url is empty string', async () => {
|
||||
const result = await client.postForm('', {});
|
||||
expect(result).toBeUndefined();
|
||||
expect(createElement.mock.calls).toHaveLength(0);
|
||||
expect(appendChild.mock.calls).toHaveLength(0);
|
||||
expect(removeChild.mock.calls).toHaveLength(0);
|
||||
expect(authSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,10 +41,12 @@ describe('Translator', () => {
|
||||
spy.mockImplementation((info: any) => {
|
||||
throw new Error(info);
|
||||
});
|
||||
process.env.WEBPACK_MODE = 'production';
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
spy.mockRestore();
|
||||
process.env.WEBPACK_MODE = 'test';
|
||||
});
|
||||
|
||||
describe('new Translator(config)', () => {
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
MixedTimeseriesTransformProps,
|
||||
} from '@superset-ui/plugin-chart-echarts';
|
||||
import data from '../Timeseries/data';
|
||||
import negativeNumData from './negativeData';
|
||||
import { withResizableChartDemo } from '../../../../shared/components/ResizableChartDemo';
|
||||
|
||||
new EchartsTimeseriesChartPlugin()
|
||||
@@ -57,6 +58,8 @@ export const Timeseries = ({ width, height }) => {
|
||||
Boston: row.Boston,
|
||||
}))
|
||||
.filter(row => !!row.Boston),
|
||||
colnames: ['__timestamp'],
|
||||
coltypes: [2],
|
||||
},
|
||||
{
|
||||
data: data
|
||||
@@ -82,8 +85,13 @@ export const Timeseries = ({ width, height }) => {
|
||||
logAxis: boolean('Log axis', false),
|
||||
xAxisTimeFormat: 'smart_date',
|
||||
tooltipTimeFormat: 'smart_date',
|
||||
yAxisFormat: 'SMART_NUMBER',
|
||||
yAxisFormat: select(
|
||||
'y-axis format',
|
||||
['$,.2f', 'SMART_NUMBER'],
|
||||
'$,.2f',
|
||||
),
|
||||
yAxisTitle: text('Y Axis title', ''),
|
||||
yAxisIndexB: select('yAxisIndexB', [0, 1], 1),
|
||||
minorSplitLine: boolean('Query 1: Minor splitline', false),
|
||||
seriesType: select(
|
||||
'Query 1: Line type',
|
||||
@@ -105,7 +113,61 @@ export const Timeseries = ({ width, height }) => {
|
||||
markerEnabledB: boolean('Query 2: Enable markers', false),
|
||||
markerSizeB: number('Query 2: Marker Size', 6),
|
||||
opacityB: number('Query 2: Opacity', 0.2),
|
||||
showValue: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithNegativeNumbers = ({ width, height }) => (
|
||||
<SuperChart
|
||||
chartType="mixed-timeseries"
|
||||
width={width}
|
||||
height={height}
|
||||
queriesData={[
|
||||
{
|
||||
data: negativeNumData,
|
||||
colnames: ['__timestamp'],
|
||||
coltypes: [2],
|
||||
},
|
||||
{
|
||||
data: negativeNumData.map(({ __timestamp, Boston }) => ({
|
||||
__timestamp,
|
||||
avgRate: Boston / 100,
|
||||
})),
|
||||
},
|
||||
]}
|
||||
formData={{
|
||||
contributionMode: undefined,
|
||||
colorScheme: 'supersetColors',
|
||||
seriesType: select(
|
||||
'Line type',
|
||||
['line', 'scatter', 'smooth', 'bar', 'start', 'middle', 'end'],
|
||||
'line',
|
||||
),
|
||||
xAxisTimeFormat: 'smart_date',
|
||||
yAxisFormat: select(
|
||||
'y-axis format',
|
||||
{
|
||||
'Original value': '~g',
|
||||
'Smart number': 'SMART_NUMBER',
|
||||
'(12345.432 => $12,345.43)': '$,.2f',
|
||||
},
|
||||
'$,.2f',
|
||||
),
|
||||
stack: true,
|
||||
showValue: boolean('Query 1: Show Value', true),
|
||||
showValueB: boolean('Query 2: Show Value', false),
|
||||
showLegend: true,
|
||||
markerEnabledB: true,
|
||||
yAxisIndexB: select(
|
||||
'Query 2: Y Axis',
|
||||
{
|
||||
Primary: 0,
|
||||
Secondary: 1,
|
||||
},
|
||||
1,
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export default [
|
||||
{
|
||||
__timestamp: 1619827200000,
|
||||
Boston: 10.8812312312,
|
||||
Washington: -45.3089432023,
|
||||
JerseyCity: -23.0509234029834,
|
||||
},
|
||||
{
|
||||
__timestamp: 1622505600000,
|
||||
Boston: 80.81029340234,
|
||||
Washington: -10.299023489023,
|
||||
JerseyCity: 53.54239402349,
|
||||
},
|
||||
{
|
||||
__timestamp: 1625097600000,
|
||||
Boston: 30.9129034924,
|
||||
Washington: 100.25234902349,
|
||||
JerseyCity: 27.17239402394,
|
||||
},
|
||||
{
|
||||
__timestamp: 1627776000000,
|
||||
Boston: 42.6129034924,
|
||||
Washington: 90.23234902349,
|
||||
JerseyCity: -32.23239402394,
|
||||
},
|
||||
];
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
TimeseriesTransformProps,
|
||||
} from '@superset-ui/plugin-chart-echarts';
|
||||
import data from './data';
|
||||
import negativeNumData from './negativeNumData';
|
||||
import { withResizableChartDemo } from '../../../../shared/components/ResizableChartDemo';
|
||||
|
||||
new EchartsTimeseriesChartPlugin()
|
||||
@@ -61,7 +62,9 @@ export const Timeseries = ({ width, height }) => {
|
||||
chartType="echarts-timeseries"
|
||||
width={width}
|
||||
height={height}
|
||||
queriesData={[{ data: queryData }]}
|
||||
queriesData={[
|
||||
{ data: queryData, colnames: ['__timestamp'], coltypes: [2] },
|
||||
]}
|
||||
formData={{
|
||||
contributionMode: undefined,
|
||||
forecastEnabled,
|
||||
@@ -87,3 +90,33 @@ export const Timeseries = ({ width, height }) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithNegativeNumbers = ({ width, height }) => (
|
||||
<SuperChart
|
||||
chartType="echarts-timeseries"
|
||||
width={width}
|
||||
height={height}
|
||||
queriesData={[
|
||||
{ data: negativeNumData, colnames: ['__timestamp'], coltypes: [2] },
|
||||
]}
|
||||
formData={{
|
||||
contributionMode: undefined,
|
||||
colorScheme: 'supersetColors',
|
||||
seriesType: select(
|
||||
'Line type',
|
||||
['line', 'scatter', 'smooth', 'bar', 'start', 'middle', 'end'],
|
||||
'line',
|
||||
),
|
||||
yAxisFormat: '$,.2f',
|
||||
stack: boolean('Stack', true),
|
||||
showValue: true,
|
||||
showLegend: true,
|
||||
onlyTotal: boolean('Only Total', true),
|
||||
orientation: select(
|
||||
'Orientation',
|
||||
['vertical', 'horizontal'],
|
||||
'vertical',
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export default [
|
||||
{
|
||||
__timestamp: 1619827200000,
|
||||
Boston: -0.88,
|
||||
NewYork: null,
|
||||
Washington: -0.3,
|
||||
JerseyCity: -3.05,
|
||||
Denver: -8.25,
|
||||
SF: -0.13,
|
||||
},
|
||||
{
|
||||
__timestamp: 1622505600000,
|
||||
Boston: -0.81,
|
||||
NewYork: null,
|
||||
Washington: -0.29,
|
||||
JerseyCity: -3.54,
|
||||
Denver: -13.4,
|
||||
SF: -0.12,
|
||||
},
|
||||
{
|
||||
__timestamp: 1625097600000,
|
||||
Boston: 0.91,
|
||||
NewYork: null,
|
||||
Washington: 0.25,
|
||||
JerseyCity: 7.17,
|
||||
Denver: 7.69,
|
||||
SF: 0.05,
|
||||
},
|
||||
{
|
||||
__timestamp: 1627776000000,
|
||||
Boston: -1.05,
|
||||
NewYork: -1.04,
|
||||
Washington: -0.19,
|
||||
JerseyCity: -8.99,
|
||||
Denver: -7.99,
|
||||
SF: -0.01,
|
||||
},
|
||||
{
|
||||
__timestamp: 1630454400000,
|
||||
Boston: -0.92,
|
||||
NewYork: -1.09,
|
||||
Washington: -0.17,
|
||||
JerseyCity: -8.75,
|
||||
Denver: -7.55,
|
||||
SF: -0.01,
|
||||
},
|
||||
{
|
||||
__timestamp: 1633046400000,
|
||||
Boston: 0.79,
|
||||
NewYork: -0.85,
|
||||
Washington: 0.13,
|
||||
JerseyCity: 12.59,
|
||||
Denver: 3.34,
|
||||
SF: -0.05,
|
||||
},
|
||||
{
|
||||
__timestamp: 1635724800000,
|
||||
Boston: 0.72,
|
||||
NewYork: 0.54,
|
||||
Washington: 0.15,
|
||||
JerseyCity: 11.03,
|
||||
Denver: 7.24,
|
||||
SF: -0.14,
|
||||
},
|
||||
{
|
||||
__timestamp: 1638316800000,
|
||||
Boston: 0.61,
|
||||
NewYork: 0.73,
|
||||
Washington: 0.15,
|
||||
JerseyCity: 13.45,
|
||||
Denver: 5.98,
|
||||
SF: -0.22,
|
||||
},
|
||||
{
|
||||
__timestamp: 1640995200000,
|
||||
Boston: 0.51,
|
||||
NewYork: 1.8,
|
||||
Washington: 0.15,
|
||||
JerseyCity: 12.96,
|
||||
Denver: 3.22,
|
||||
SF: -0.02,
|
||||
},
|
||||
{
|
||||
__timestamp: 1643673600000,
|
||||
Boston: -0.47,
|
||||
NewYork: null,
|
||||
Washington: -0.18,
|
||||
JerseyCity: -14.27,
|
||||
Denver: -6.24,
|
||||
SF: -0.04,
|
||||
},
|
||||
];
|
||||
@@ -100,4 +100,8 @@ export default {
|
||||
...formData,
|
||||
metric: formData.standardizedFormData.standardizedState.metrics[0],
|
||||
}),
|
||||
updateStandardizedState: (prevState, currState) => ({
|
||||
...currState,
|
||||
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
|
||||
}),
|
||||
} as ControlPanelConfig;
|
||||
|
||||
@@ -152,7 +152,7 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
||||
document.body.append(container);
|
||||
const fontSize = computeMaxFontSize({
|
||||
text,
|
||||
maxWidth: width,
|
||||
maxWidth: width - 8, // Decrease 8px for more precise font size
|
||||
maxHeight,
|
||||
className: 'header-line',
|
||||
container,
|
||||
|
||||
@@ -274,6 +274,10 @@ const config: ControlPanelConfig = {
|
||||
...formData,
|
||||
metric: formData.standardizedFormData.standardizedState.metrics[0],
|
||||
}),
|
||||
updateStandardizedState: (prevState, currState) => ({
|
||||
...currState,
|
||||
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -125,8 +125,10 @@ export default function transformProps(
|
||||
if (compareIndex < sortedData.length) {
|
||||
const compareValue = sortedData[compareIndex][1];
|
||||
// compare values must both be non-nulls
|
||||
if (bigNumber !== null && compareValue !== null && compareValue !== 0) {
|
||||
percentChange = (bigNumber - compareValue) / Math.abs(compareValue);
|
||||
if (bigNumber !== null && compareValue !== null) {
|
||||
percentChange = compareValue
|
||||
? (bigNumber - compareValue) / Math.abs(compareValue)
|
||||
: 0;
|
||||
formattedSubheader = `${formatPercentChange(
|
||||
percentChange,
|
||||
)} ${compareSuffix}`;
|
||||
|
||||
@@ -148,6 +148,10 @@ const config: ControlPanelConfig = {
|
||||
metric: formData.standardizedFormData.standardizedState.metrics[0],
|
||||
groupby: formData.standardizedFormData.standardizedState.columns,
|
||||
}),
|
||||
updateStandardizedState: (prevState, currState) => ({
|
||||
...currState,
|
||||
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { t, validateNonEmpty, validateInteger } from '@superset-ui/core';
|
||||
import { t } from '@superset-ui/core';
|
||||
import {
|
||||
sharedControls,
|
||||
ControlPanelConfig,
|
||||
@@ -81,8 +81,7 @@ const config: ControlPanelConfig = {
|
||||
config: {
|
||||
type: 'TextControl',
|
||||
isInt: true,
|
||||
default: String(DEFAULT_FORM_DATA.minVal),
|
||||
validators: [validateNonEmpty, validateInteger],
|
||||
default: DEFAULT_FORM_DATA.minVal,
|
||||
renderTrigger: true,
|
||||
label: t('Min'),
|
||||
description: t('Minimum value on the gauge axis'),
|
||||
@@ -94,7 +93,6 @@ const config: ControlPanelConfig = {
|
||||
type: 'TextControl',
|
||||
isInt: true,
|
||||
default: DEFAULT_FORM_DATA.maxVal,
|
||||
validators: [validateNonEmpty, validateInteger],
|
||||
renderTrigger: true,
|
||||
label: t('Max'),
|
||||
description: t('Maximum value on the gauge axis'),
|
||||
@@ -313,6 +311,10 @@ const config: ControlPanelConfig = {
|
||||
metric: formData.standardizedFormData.standardizedState.metrics[0],
|
||||
groupby: formData.standardizedFormData.standardizedState.columns,
|
||||
}),
|
||||
updateStandardizedState: (prevState, currState) => ({
|
||||
...currState,
|
||||
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from '@superset-ui/core';
|
||||
import { EChartsCoreOption, GaugeSeriesOption } from 'echarts';
|
||||
import { GaugeDataItemOption } from 'echarts/types/src/chart/gauge/GaugeSeries';
|
||||
import { CallbackDataParams } from 'echarts/types/src/util/types';
|
||||
import range from 'lodash/range';
|
||||
import { parseNumbersList } from '../utils/controls';
|
||||
import {
|
||||
@@ -80,6 +81,12 @@ const calculateAxisLineWidth = (
|
||||
overlap: boolean,
|
||||
): number => (overlap ? fontSize : data.length * fontSize);
|
||||
|
||||
const calculateMin = (data: GaugeDataItemOption[]) =>
|
||||
2 * Math.min(...data.map(d => d.value as number).concat([0]));
|
||||
|
||||
const calculateMax = (data: GaugeDataItemOption[]) =>
|
||||
2 * Math.max(...data.map(d => d.value as number).concat([0]));
|
||||
|
||||
export default function transformProps(
|
||||
chartProps: EchartsGaugeChartProps,
|
||||
): GaugeChartTransformedProps {
|
||||
@@ -115,12 +122,7 @@ export default function transformProps(
|
||||
const data = (queriesData[0]?.data || []) as DataRecord[];
|
||||
const numberFormatter = getNumberFormatter(numberFormat);
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||
const normalizer = maxVal;
|
||||
const axisLineWidth = calculateAxisLineWidth(data, fontSize, overlap);
|
||||
const axisLabels = range(minVal, maxVal, (maxVal - minVal) / splitNumber);
|
||||
const axisLabelLength = Math.max(
|
||||
...axisLabels.map(label => numberFormatter(label).length).concat([1]),
|
||||
);
|
||||
const groupbyLabels = groupby.map(getColumnLabel);
|
||||
const formatValue = (value: number) =>
|
||||
valueFormatter.replace('{value}', numberFormatter(value));
|
||||
@@ -130,12 +132,6 @@ export default function transformProps(
|
||||
FONT_SIZE_MULTIPLIERS.titleOffsetFromTitle * fontSize;
|
||||
const detailOffsetFromTitle =
|
||||
FONT_SIZE_MULTIPLIERS.detailOffsetFromTitle * fontSize;
|
||||
const intervalBoundsAndColors = setIntervalBoundsAndColors(
|
||||
intervals,
|
||||
intervalColorIndices,
|
||||
colorFn,
|
||||
normalizer,
|
||||
);
|
||||
const columnsLabelMap = new Map<string, DataRecordValue[]>();
|
||||
|
||||
const transformedData: GaugeDataItemOption[] = data.map(
|
||||
@@ -196,6 +192,33 @@ export default function transformProps(
|
||||
|
||||
const { setDataMask = () => {} } = hooks;
|
||||
|
||||
const min = minVal ?? calculateMin(transformedData);
|
||||
const max = maxVal ?? calculateMax(transformedData);
|
||||
const axisLabels = range(min, max, (max - min) / splitNumber);
|
||||
const axisLabelLength = Math.max(
|
||||
...axisLabels.map(label => numberFormatter(label).length).concat([1]),
|
||||
);
|
||||
const normalizer = max;
|
||||
const intervalBoundsAndColors = setIntervalBoundsAndColors(
|
||||
intervals,
|
||||
intervalColorIndices,
|
||||
colorFn,
|
||||
normalizer,
|
||||
);
|
||||
const splitLineDistance =
|
||||
axisLineWidth + splitLineLength + OFFSETS.ticksFromLine;
|
||||
const axisLabelDistance =
|
||||
FONT_SIZE_MULTIPLIERS.axisLabelDistance *
|
||||
fontSize *
|
||||
FONT_SIZE_MULTIPLIERS.axisLabelLength *
|
||||
axisLabelLength +
|
||||
(showSplitLine ? splitLineLength : 0) +
|
||||
(showAxisTick ? axisTickLength : 0) +
|
||||
OFFSETS.ticksFromLine -
|
||||
axisLineWidth;
|
||||
const axisTickDistance =
|
||||
axisLineWidth + axisTickLength + OFFSETS.ticksFromLine;
|
||||
|
||||
const progress = {
|
||||
show: showProgress,
|
||||
overlap,
|
||||
@@ -204,7 +227,7 @@ export default function transformProps(
|
||||
};
|
||||
const splitLine = {
|
||||
show: showSplitLine,
|
||||
distance: -axisLineWidth - splitLineLength - OFFSETS.ticksFromLine,
|
||||
distance: -splitLineDistance,
|
||||
length: splitLineLength,
|
||||
lineStyle: {
|
||||
width: FONT_SIZE_MULTIPLIERS.splitLineWidth * fontSize,
|
||||
@@ -219,22 +242,14 @@ export default function transformProps(
|
||||
},
|
||||
};
|
||||
const axisLabel = {
|
||||
distance:
|
||||
axisLineWidth -
|
||||
FONT_SIZE_MULTIPLIERS.axisLabelDistance *
|
||||
fontSize *
|
||||
FONT_SIZE_MULTIPLIERS.axisLabelLength *
|
||||
axisLabelLength -
|
||||
(showSplitLine ? splitLineLength : 0) -
|
||||
(showAxisTick ? axisTickLength : 0) -
|
||||
OFFSETS.ticksFromLine,
|
||||
distance: -axisLabelDistance,
|
||||
fontSize,
|
||||
formatter: numberFormatter,
|
||||
color: gaugeSeriesOptions.axisLabel?.color,
|
||||
};
|
||||
const axisTick = {
|
||||
show: showAxisTick,
|
||||
distance: -axisLineWidth - axisTickLength - OFFSETS.ticksFromLine,
|
||||
distance: -axisTickDistance,
|
||||
length: axisTickLength,
|
||||
lineStyle: gaugeSeriesOptions.axisTick?.lineStyle as AxisTickLineStyle,
|
||||
};
|
||||
@@ -243,8 +258,14 @@ export default function transformProps(
|
||||
formatter: (value: number) => formatValue(value),
|
||||
color: gaugeSeriesOptions.detail?.color,
|
||||
};
|
||||
let pointer;
|
||||
const tooltip = {
|
||||
formatter: (params: CallbackDataParams) => {
|
||||
const { name, value } = params;
|
||||
return `${name} : ${formatValue(value as number)}`;
|
||||
},
|
||||
};
|
||||
|
||||
let pointer;
|
||||
if (intervalBoundsAndColors.length) {
|
||||
splitLine.lineStyle.color =
|
||||
INTERVAL_GAUGE_SERIES_OPTION.splitLine?.lineStyle?.color;
|
||||
@@ -269,8 +290,8 @@ export default function transformProps(
|
||||
type: 'gauge',
|
||||
startAngle,
|
||||
endAngle,
|
||||
min: minVal,
|
||||
max: maxVal,
|
||||
min,
|
||||
max,
|
||||
progress,
|
||||
animation,
|
||||
axisLine: axisLine as GaugeSeriesOption['axisLine'],
|
||||
@@ -280,11 +301,19 @@ export default function transformProps(
|
||||
axisTick,
|
||||
pointer,
|
||||
detail,
|
||||
tooltip,
|
||||
radius:
|
||||
Math.min(width, height) / 2 - axisLabelDistance - axisTickDistance,
|
||||
center: ['50%', '55%'],
|
||||
data: transformedData,
|
||||
},
|
||||
];
|
||||
|
||||
const echartOptions: EChartsCoreOption = {
|
||||
tooltip: {
|
||||
appendToBody: true,
|
||||
trigger: 'item',
|
||||
},
|
||||
series,
|
||||
};
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ export type EchartsGaugeFormData = QueryFormData & {
|
||||
groupby: QueryFormColumn[];
|
||||
metric?: string;
|
||||
rowLimit: number;
|
||||
minVal: number;
|
||||
maxVal: number;
|
||||
minVal: number | null;
|
||||
maxVal: number | null;
|
||||
fontSize: number;
|
||||
numberFormat: string;
|
||||
animation: boolean;
|
||||
@@ -58,8 +58,8 @@ export const DEFAULT_FORM_DATA: Partial<EchartsGaugeFormData> = {
|
||||
...DEFAULT_LEGEND_FORM_DATA,
|
||||
groupby: [],
|
||||
rowLimit: 10,
|
||||
minVal: 0,
|
||||
maxVal: 100,
|
||||
minVal: null,
|
||||
maxVal: null,
|
||||
fontSize: 15,
|
||||
numberFormat: 'SMART_NUMBER',
|
||||
animation: true,
|
||||
|
||||
@@ -324,6 +324,10 @@ const controlPanel: ControlPanelConfig = {
|
||||
...formData,
|
||||
metric: formData.standardizedFormData.standardizedState.metrics[0],
|
||||
}),
|
||||
updateStandardizedState: (prevState, currState) => ({
|
||||
...currState,
|
||||
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
|
||||
}),
|
||||
};
|
||||
|
||||
export default controlPanel;
|
||||
|
||||
@@ -47,8 +47,13 @@ import {
|
||||
getAxisType,
|
||||
getColtypesMapping,
|
||||
getLegendProps,
|
||||
extractDataTotalValues,
|
||||
extractShowValueIndexes,
|
||||
} from '../utils/series';
|
||||
import { extractAnnotationLabels } from '../utils/annotation';
|
||||
import {
|
||||
extractAnnotationLabels,
|
||||
getAnnotationData,
|
||||
} from '../utils/annotation';
|
||||
import {
|
||||
extractForecastSeriesContext,
|
||||
extractForecastValuesFromTooltipParams,
|
||||
@@ -81,11 +86,11 @@ export default function transformProps(
|
||||
filterState,
|
||||
datasource,
|
||||
theme,
|
||||
annotationData = {},
|
||||
} = chartProps;
|
||||
const { verboseMap = {} } = datasource;
|
||||
const data1 = (queriesData[0].data || []) as TimeseriesDataRecord[];
|
||||
const data2 = (queriesData[1].data || []) as TimeseriesDataRecord[];
|
||||
const annotationData = getAnnotationData(chartProps);
|
||||
|
||||
const {
|
||||
area,
|
||||
@@ -136,6 +141,7 @@ export default function transformProps(
|
||||
yAxisTitlePosition,
|
||||
sliceId,
|
||||
timeGrainSqla,
|
||||
percentageThreshold,
|
||||
}: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
||||
|
||||
const colorScale = CategoricalColorNamespace.getScale(colorScheme as string);
|
||||
@@ -155,7 +161,7 @@ export default function transformProps(
|
||||
});
|
||||
|
||||
const dataTypes = getColtypesMapping(queriesData[0]);
|
||||
const xAxisDataType = dataTypes?.[xAxisCol];
|
||||
const xAxisDataType = dataTypes?.[xAxisCol] ?? dataTypes?.[xAxisOrig];
|
||||
const xAxisType = getAxisType(xAxisDataType);
|
||||
const series: SeriesOption[] = [];
|
||||
const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat);
|
||||
@@ -181,7 +187,28 @@ export default function transformProps(
|
||||
rawSeriesB.forEach(seriesOption =>
|
||||
mapSeriesIdToAxis(seriesOption, yAxisIndexB),
|
||||
);
|
||||
|
||||
const showValueIndexesA = extractShowValueIndexes(rawSeriesA, {
|
||||
stack,
|
||||
});
|
||||
const showValueIndexesB = extractShowValueIndexes(rawSeriesB, {
|
||||
stack,
|
||||
});
|
||||
const { totalStackedValues, thresholdValues } = extractDataTotalValues(
|
||||
rebasedDataA,
|
||||
{
|
||||
stack,
|
||||
percentageThreshold,
|
||||
xAxisCol,
|
||||
},
|
||||
);
|
||||
const {
|
||||
totalStackedValues: totalStackedValuesB,
|
||||
thresholdValues: thresholdValuesB,
|
||||
} = extractDataTotalValues(rebasedDataB, {
|
||||
stack: Boolean(stackB),
|
||||
percentageThreshold,
|
||||
xAxisCol,
|
||||
});
|
||||
rawSeriesA.forEach(entry => {
|
||||
const transformedSeries = transformSeries(entry, colorScale, {
|
||||
area,
|
||||
@@ -195,6 +222,10 @@ export default function transformProps(
|
||||
filterState,
|
||||
seriesKey: entry.name,
|
||||
sliceId,
|
||||
formatter,
|
||||
showValueIndexes: showValueIndexesA,
|
||||
totalStackedValues,
|
||||
thresholdValues,
|
||||
});
|
||||
if (transformedSeries) series.push(transformedSeries);
|
||||
});
|
||||
@@ -214,6 +245,10 @@ export default function transformProps(
|
||||
? `${entry.name} (1)`
|
||||
: entry.name,
|
||||
sliceId,
|
||||
formatter: formatterSecondary,
|
||||
showValueIndexes: showValueIndexesB,
|
||||
totalStackedValues: totalStackedValuesB,
|
||||
thresholdValues: thresholdValuesB,
|
||||
});
|
||||
if (transformedSeries) series.push(transformedSeries);
|
||||
});
|
||||
|
||||
@@ -260,6 +260,10 @@ const config: ControlPanelConfig = {
|
||||
row_limit:
|
||||
ensureIsInt(formData.row_limit, 100) >= 100 ? 100 : formData.row_limit,
|
||||
}),
|
||||
updateStandardizedState: (prevState, currState) => ({
|
||||
...currState,
|
||||
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -45,8 +45,6 @@ import {
|
||||
const {
|
||||
contributionMode,
|
||||
logAxis,
|
||||
markerEnabled,
|
||||
markerSize,
|
||||
minorSplitLine,
|
||||
rowLimit,
|
||||
truncateYAxis,
|
||||
@@ -341,38 +339,6 @@ const config: ControlPanelConfig = {
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
...showValueSection,
|
||||
[
|
||||
{
|
||||
name: 'markerEnabled',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Marker'),
|
||||
renderTrigger: true,
|
||||
default: markerEnabled,
|
||||
description: t(
|
||||
'Draw a marker on data points. Only applicable for line types.',
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'markerSize',
|
||||
config: {
|
||||
type: 'SliderControl',
|
||||
label: t('Marker Size'),
|
||||
renderTrigger: true,
|
||||
min: 0,
|
||||
max: 20,
|
||||
default: markerSize,
|
||||
description: t(
|
||||
'Size of marker. Also applies to forecast observations.',
|
||||
),
|
||||
visibility: ({ controls }: ControlPanelsContainerProps) =>
|
||||
Boolean(controls?.markerEnabled?.value),
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'zoomable',
|
||||
|
||||
@@ -55,7 +55,10 @@ import {
|
||||
extractDataTotalValues,
|
||||
extractShowValueIndexes,
|
||||
} from '../utils/series';
|
||||
import { extractAnnotationLabels } from '../utils/annotation';
|
||||
import {
|
||||
extractAnnotationLabels,
|
||||
getAnnotationData,
|
||||
} from '../utils/annotation';
|
||||
import {
|
||||
extractForecastSeriesContext,
|
||||
extractForecastSeriesContexts,
|
||||
@@ -93,12 +96,12 @@ export default function transformProps(
|
||||
queriesData,
|
||||
datasource,
|
||||
theme,
|
||||
annotationData = {},
|
||||
} = chartProps;
|
||||
const { verboseMap = {} } = datasource;
|
||||
const [queryData] = queriesData;
|
||||
const { data = [] } = queryData as TimeseriesChartDataResponseResult;
|
||||
const dataTypes = getColtypesMapping(queryData);
|
||||
const annotationData = getAnnotationData(chartProps);
|
||||
|
||||
const {
|
||||
area,
|
||||
@@ -165,12 +168,15 @@ export default function transformProps(
|
||||
});
|
||||
const showValueIndexes = extractShowValueIndexes(rawSeries, {
|
||||
stack,
|
||||
onlyTotal,
|
||||
isHorizontal,
|
||||
});
|
||||
const seriesContexts = extractForecastSeriesContexts(
|
||||
Object.values(rawSeries).map(series => series.name as string),
|
||||
);
|
||||
const isAreaExpand = stack === AreaChartExtraControlsValue.Expand;
|
||||
const xAxisDataType = dataTypes?.[xAxisCol];
|
||||
const xAxisDataType = dataTypes?.[xAxisCol] ?? dataTypes?.[xAxisOrig];
|
||||
|
||||
const xAxisType = getAxisType(xAxisDataType);
|
||||
const series: SeriesOption[] = [];
|
||||
const formatter = getNumberFormatter(
|
||||
|
||||
@@ -233,7 +233,10 @@ export function transformSeries(
|
||||
if (!formatter) return numericValue;
|
||||
if (!stack || isSelectedLegend) return formatter(numericValue);
|
||||
if (!onlyTotal) {
|
||||
if (numericValue >= thresholdValues[dataIndex]) {
|
||||
if (
|
||||
numericValue >=
|
||||
(thresholdValues[dataIndex] || Number.MIN_SAFE_INTEGER)
|
||||
) {
|
||||
return formatter(numericValue);
|
||||
}
|
||||
return '';
|
||||
|
||||
@@ -289,6 +289,10 @@ const controlPanel: ControlPanelConfig = {
|
||||
...formData,
|
||||
metric: formData.standardizedFormData.standardizedState.metrics[0],
|
||||
}),
|
||||
updateStandardizedState: (prevState, currState) => ({
|
||||
...currState,
|
||||
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
|
||||
}),
|
||||
};
|
||||
|
||||
export default controlPanel;
|
||||
|
||||
@@ -142,6 +142,10 @@ const config: ControlPanelConfig = {
|
||||
metric: formData.standardizedFormData.standardizedState.metrics[0],
|
||||
groupby: formData.standardizedFormData.standardizedState.columns,
|
||||
}),
|
||||
updateStandardizedState: (prevState, currState) => ({
|
||||
...currState,
|
||||
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
import {
|
||||
Annotation,
|
||||
AnnotationData,
|
||||
@@ -30,6 +32,8 @@ import {
|
||||
isTimeseriesAnnotationResult,
|
||||
TimeseriesDataRecord,
|
||||
} from '@superset-ui/core';
|
||||
import { EchartsTimeseriesChartProps } from '../types';
|
||||
import { EchartsMixedTimeseriesProps } from '../MixedTimeseries/types';
|
||||
|
||||
export function evalFormula(
|
||||
formula: FormulaAnnotationLayer,
|
||||
@@ -130,3 +134,13 @@ export function extractAnnotationLabels(
|
||||
|
||||
return formulaAnnotationLabels.concat(timeseriesAnnotationLabels);
|
||||
}
|
||||
|
||||
export function getAnnotationData(
|
||||
chartProps: EchartsTimeseriesChartProps | EchartsMixedTimeseriesProps,
|
||||
): AnnotationData {
|
||||
const data = chartProps?.queriesData[0]?.annotation_data as AnnotationData;
|
||||
if (!isEmpty(data)) {
|
||||
return data;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { isNumber } from 'lodash';
|
||||
import { DataRecord, DTTM_ALIAS, NumberFormatter } from '@superset-ui/core';
|
||||
import { OptionName } from 'echarts/types/src/util/types';
|
||||
import { TooltipMarker } from 'echarts/types/src/util/format';
|
||||
@@ -60,7 +61,7 @@ export const extractForecastValuesFromTooltipParams = (
|
||||
const { marker, seriesId, value } = param;
|
||||
const context = extractForecastSeriesContext(seriesId);
|
||||
const numericValue = isHorizontal ? value[0] : value[1];
|
||||
if (numericValue) {
|
||||
if (isNumber(numericValue)) {
|
||||
if (!(context.name in values))
|
||||
values[context.name] = {
|
||||
marker: marker || '',
|
||||
@@ -94,7 +95,7 @@ export const formatForecastTooltipSeries = ({
|
||||
}): string => {
|
||||
let row = `${marker}${sanitizeHtml(seriesName)}: `;
|
||||
let isObservation = false;
|
||||
if (observation) {
|
||||
if (isNumber(observation)) {
|
||||
isObservation = true;
|
||||
row += `${formatter(observation)}`;
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ export function extractShowValueIndexes(
|
||||
series: SeriesOption[],
|
||||
opts: {
|
||||
stack: StackType;
|
||||
onlyTotal?: boolean;
|
||||
isHorizontal?: boolean;
|
||||
},
|
||||
): number[] {
|
||||
const showValueIndexes: number[] = [];
|
||||
@@ -84,9 +86,20 @@ export function extractShowValueIndexes(
|
||||
series.forEach((entry, seriesIndex) => {
|
||||
const { data = [] } = entry;
|
||||
(data as [any, number][]).forEach((datum, dataIndex) => {
|
||||
if (datum[1] !== null) {
|
||||
if (!opts.onlyTotal && datum[opts.isHorizontal ? 0 : 1] !== null) {
|
||||
showValueIndexes[dataIndex] = seriesIndex;
|
||||
}
|
||||
if (opts.onlyTotal) {
|
||||
if (datum[opts.isHorizontal ? 0 : 1] > 0) {
|
||||
showValueIndexes[dataIndex] = seriesIndex;
|
||||
}
|
||||
if (
|
||||
!showValueIndexes[dataIndex] &&
|
||||
datum[opts.isHorizontal ? 0 : 1] !== null
|
||||
) {
|
||||
showValueIndexes[dataIndex] = seriesIndex;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -174,60 +174,62 @@ describe('EchartsTimeseries transformProps', () => {
|
||||
titleColumn: '',
|
||||
value: 3,
|
||||
};
|
||||
const annotationData = {
|
||||
'My Event': {
|
||||
columns: [
|
||||
'start_dttm',
|
||||
'end_dttm',
|
||||
'short_descr',
|
||||
'long_descr',
|
||||
'json_metadata',
|
||||
],
|
||||
records: [
|
||||
{
|
||||
start_dttm: 0,
|
||||
end_dttm: 1000,
|
||||
short_descr: '',
|
||||
long_descr: '',
|
||||
json_metadata: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
'My Interval': {
|
||||
columns: ['start', 'end', 'title'],
|
||||
records: [
|
||||
{
|
||||
start: 2000,
|
||||
end: 3000,
|
||||
title: 'My Title',
|
||||
},
|
||||
],
|
||||
},
|
||||
'My Timeseries': [
|
||||
{
|
||||
key: 'My Line',
|
||||
values: [
|
||||
{
|
||||
x: 10000,
|
||||
y: 11000,
|
||||
},
|
||||
{
|
||||
x: 20000,
|
||||
y: 21000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const chartProps = new ChartProps({
|
||||
...chartPropsConfig,
|
||||
formData: {
|
||||
...formData,
|
||||
annotationLayers: [event, interval, timeseries],
|
||||
},
|
||||
annotationData: {
|
||||
'My Event': {
|
||||
columns: [
|
||||
'start_dttm',
|
||||
'end_dttm',
|
||||
'short_descr',
|
||||
'long_descr',
|
||||
'json_metadata',
|
||||
],
|
||||
records: [
|
||||
{
|
||||
start_dttm: 0,
|
||||
end_dttm: 1000,
|
||||
short_descr: '',
|
||||
long_descr: '',
|
||||
json_metadata: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
'My Interval': {
|
||||
columns: ['start', 'end', 'title'],
|
||||
records: [
|
||||
{
|
||||
start: 2000,
|
||||
end: 3000,
|
||||
title: 'My Title',
|
||||
},
|
||||
],
|
||||
},
|
||||
'My Timeseries': [
|
||||
{
|
||||
key: 'My Line',
|
||||
values: [
|
||||
{
|
||||
x: 10000,
|
||||
y: 11000,
|
||||
},
|
||||
{
|
||||
x: 20000,
|
||||
y: 21000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
annotationData,
|
||||
queriesData: [
|
||||
{
|
||||
...queriesData[0],
|
||||
annotation_data: annotationData,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -154,103 +154,148 @@ describe('rebaseForecastDatum', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractForecastValuesFromTooltipParams', () => {
|
||||
it('should extract the proper data from tooltip params', () => {
|
||||
expect(
|
||||
extractForecastValuesFromTooltipParams([
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'abc',
|
||||
value: [new Date(0), 10],
|
||||
},
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'abc__yhat',
|
||||
value: [new Date(0), 1],
|
||||
},
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'abc__yhat_lower',
|
||||
value: [new Date(0), 5],
|
||||
},
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'abc__yhat_upper',
|
||||
value: [new Date(0), 6],
|
||||
},
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'qwerty',
|
||||
value: [new Date(0), 2],
|
||||
},
|
||||
]),
|
||||
).toEqual({
|
||||
abc: {
|
||||
test('extractForecastValuesFromTooltipParams should extract the proper data from tooltip params', () => {
|
||||
expect(
|
||||
extractForecastValuesFromTooltipParams([
|
||||
{
|
||||
marker: '<img>',
|
||||
observation: 10,
|
||||
forecastTrend: 1,
|
||||
forecastLower: 5,
|
||||
forecastUpper: 6,
|
||||
seriesId: 'abc',
|
||||
value: [new Date(0), 10],
|
||||
},
|
||||
qwerty: {
|
||||
{
|
||||
marker: '<img>',
|
||||
observation: 2,
|
||||
seriesId: 'abc__yhat',
|
||||
value: [new Date(0), 1],
|
||||
},
|
||||
});
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'abc__yhat_lower',
|
||||
value: [new Date(0), 5],
|
||||
},
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'abc__yhat_upper',
|
||||
value: [new Date(0), 6],
|
||||
},
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'qwerty',
|
||||
value: [new Date(0), 2],
|
||||
},
|
||||
]),
|
||||
).toEqual({
|
||||
abc: {
|
||||
marker: '<img>',
|
||||
observation: 10,
|
||||
forecastTrend: 1,
|
||||
forecastLower: 5,
|
||||
forecastUpper: 6,
|
||||
},
|
||||
qwerty: {
|
||||
marker: '<img>',
|
||||
observation: 2,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('extractForecastValuesFromTooltipParams should extract valid values', () => {
|
||||
expect(
|
||||
extractForecastValuesFromTooltipParams([
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'foo',
|
||||
value: [0, 10],
|
||||
},
|
||||
{
|
||||
marker: '<img>',
|
||||
seriesId: 'bar',
|
||||
value: [100, 0],
|
||||
},
|
||||
]),
|
||||
).toEqual({
|
||||
foo: {
|
||||
marker: '<img>',
|
||||
observation: 10,
|
||||
},
|
||||
bar: {
|
||||
marker: '<img>',
|
||||
observation: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const formatter = getNumberFormatter(NumberFormats.INTEGER);
|
||||
|
||||
describe('formatForecastTooltipSeries', () => {
|
||||
it('should generate a proper series tooltip', () => {
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'abc',
|
||||
marker: '<img>',
|
||||
observation: 10.1,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>abc: 10');
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'qwerty',
|
||||
marker: '<img>',
|
||||
observation: 10.1,
|
||||
forecastTrend: 20.1,
|
||||
forecastLower: 5.1,
|
||||
forecastUpper: 7.1,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>qwerty: 10, ŷ = 20 (5, 12)');
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'qwerty',
|
||||
marker: '<img>',
|
||||
forecastTrend: 20,
|
||||
forecastLower: 5,
|
||||
forecastUpper: 7,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>qwerty: ŷ = 20 (5, 12)');
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'qwerty',
|
||||
marker: '<img>',
|
||||
observation: 10.1,
|
||||
forecastLower: 6,
|
||||
forecastUpper: 7,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>qwerty: 10 (6, 13)');
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'qwerty',
|
||||
marker: '<img>',
|
||||
forecastLower: 7,
|
||||
forecastUpper: 8,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>qwerty: (7, 15)');
|
||||
});
|
||||
test('formatForecastTooltipSeries should apply format to value', () => {
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'abc',
|
||||
marker: '<img>',
|
||||
observation: 10.1,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>abc: 10');
|
||||
});
|
||||
|
||||
test('formatForecastTooltipSeries should show falsy value', () => {
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'abc',
|
||||
marker: '<img>',
|
||||
observation: 0,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>abc: 0');
|
||||
});
|
||||
|
||||
test('formatForecastTooltipSeries should format full forecast', () => {
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'qwerty',
|
||||
marker: '<img>',
|
||||
observation: 10.1,
|
||||
forecastTrend: 20.1,
|
||||
forecastLower: 5.1,
|
||||
forecastUpper: 7.1,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>qwerty: 10, ŷ = 20 (5, 12)');
|
||||
});
|
||||
|
||||
test('formatForecastTooltipSeries should format forecast without observation', () => {
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'qwerty',
|
||||
marker: '<img>',
|
||||
forecastTrend: 20,
|
||||
forecastLower: 5,
|
||||
forecastUpper: 7,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>qwerty: ŷ = 20 (5, 12)');
|
||||
});
|
||||
|
||||
test('formatForecastTooltipSeries should format forecast without point estimate', () => {
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'qwerty',
|
||||
marker: '<img>',
|
||||
observation: 10.1,
|
||||
forecastLower: 6,
|
||||
forecastUpper: 7,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>qwerty: 10 (6, 13)');
|
||||
});
|
||||
|
||||
test('formatForecastTooltipSeries should format forecast with only confidence band', () => {
|
||||
expect(
|
||||
formatForecastTooltipSeries({
|
||||
seriesName: 'qwerty',
|
||||
marker: '<img>',
|
||||
forecastLower: 7,
|
||||
forecastUpper: 8,
|
||||
formatter,
|
||||
}),
|
||||
).toEqual('<img>qwerty: (7, 15)');
|
||||
});
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
getChartPadding,
|
||||
getLegendProps,
|
||||
sanitizeHtml,
|
||||
extractShowValueIndexes,
|
||||
} from '../../src/utils/series';
|
||||
import { LegendOrientation, LegendType } from '../../src/types';
|
||||
import { defaultLegendPadding } from '../../src/defaults';
|
||||
@@ -206,6 +207,124 @@ describe('extractGroupbyLabel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractShowValueIndexes', () => {
|
||||
it('should return the latest index for stack', () => {
|
||||
expect(
|
||||
extractShowValueIndexes(
|
||||
[
|
||||
{
|
||||
id: 'abc',
|
||||
name: 'abc',
|
||||
data: [
|
||||
['2000-01-01', null],
|
||||
['2000-02-01', 0],
|
||||
['2000-03-01', 1],
|
||||
['2000-04-01', 0],
|
||||
['2000-05-01', null],
|
||||
['2000-06-01', 0],
|
||||
['2000-07-01', 2],
|
||||
['2000-08-01', 3],
|
||||
['2000-09-01', null],
|
||||
['2000-10-01', null],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'def',
|
||||
name: 'def',
|
||||
data: [
|
||||
['2000-01-01', null],
|
||||
['2000-02-01', 0],
|
||||
['2000-03-01', null],
|
||||
['2000-04-01', 0],
|
||||
['2000-05-01', null],
|
||||
['2000-06-01', 0],
|
||||
['2000-07-01', 2],
|
||||
['2000-08-01', 3],
|
||||
['2000-09-01', null],
|
||||
['2000-10-01', 0],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'def',
|
||||
name: 'def',
|
||||
data: [
|
||||
['2000-01-01', null],
|
||||
['2000-02-01', null],
|
||||
['2000-03-01', null],
|
||||
['2000-04-01', null],
|
||||
['2000-05-01', null],
|
||||
['2000-06-01', 3],
|
||||
['2000-07-01', null],
|
||||
['2000-08-01', null],
|
||||
['2000-09-01', null],
|
||||
['2000-10-01', null],
|
||||
],
|
||||
},
|
||||
],
|
||||
{ stack: true, onlyTotal: false, isHorizontal: false },
|
||||
),
|
||||
).toEqual([undefined, 1, 0, 1, undefined, 2, 1, 1, undefined, 1]);
|
||||
});
|
||||
|
||||
it('should handle the negative numbers for total only', () => {
|
||||
expect(
|
||||
extractShowValueIndexes(
|
||||
[
|
||||
{
|
||||
id: 'abc',
|
||||
name: 'abc',
|
||||
data: [
|
||||
['2000-01-01', null],
|
||||
['2000-02-01', 0],
|
||||
['2000-03-01', -1],
|
||||
['2000-04-01', 0],
|
||||
['2000-05-01', null],
|
||||
['2000-06-01', 0],
|
||||
['2000-07-01', -2],
|
||||
['2000-08-01', -3],
|
||||
['2000-09-01', null],
|
||||
['2000-10-01', null],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'def',
|
||||
name: 'def',
|
||||
data: [
|
||||
['2000-01-01', null],
|
||||
['2000-02-01', 0],
|
||||
['2000-03-01', null],
|
||||
['2000-04-01', 0],
|
||||
['2000-05-01', null],
|
||||
['2000-06-01', 0],
|
||||
['2000-07-01', 2],
|
||||
['2000-08-01', -3],
|
||||
['2000-09-01', null],
|
||||
['2000-10-01', 0],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'def',
|
||||
name: 'def',
|
||||
data: [
|
||||
['2000-01-01', null],
|
||||
['2000-02-01', 0],
|
||||
['2000-03-01', null],
|
||||
['2000-04-01', 1],
|
||||
['2000-05-01', null],
|
||||
['2000-06-01', 0],
|
||||
['2000-07-01', -2],
|
||||
['2000-08-01', 3],
|
||||
['2000-09-01', null],
|
||||
['2000-10-01', 0],
|
||||
],
|
||||
},
|
||||
],
|
||||
{ stack: true, onlyTotal: true, isHorizontal: false },
|
||||
),
|
||||
).toEqual([undefined, 1, 0, 2, undefined, 1, 1, 2, undefined, 1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatSeriesName', () => {
|
||||
const numberFormatter = getNumberFormatter();
|
||||
const timeFormatter = getTimeFormatter();
|
||||
|
||||
@@ -16,15 +16,19 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { buildQueryContext, QueryFormData } from '@superset-ui/core';
|
||||
import {
|
||||
buildQueryContext,
|
||||
normalizeOrderBy,
|
||||
QueryFormData,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
export default function buildQuery(formData: QueryFormData) {
|
||||
const { metric, sort_by_metric, groupby } = formData;
|
||||
const { groupby } = formData;
|
||||
|
||||
return buildQueryContext(formData, baseQueryObject => [
|
||||
{
|
||||
...baseQueryObject,
|
||||
...(sort_by_metric && { orderby: [[metric, false]] }),
|
||||
orderby: normalizeOrderBy(baseQueryObject).orderby,
|
||||
...(groupby && { groupby }),
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -61,9 +61,10 @@ const config: ControlPanelConfig = {
|
||||
[metricsControlSetItem, allColumnsControlSetItem],
|
||||
[percentMetricsControlSetItem],
|
||||
[timeSeriesLimitMetricControlSetItem, orderByControlSetItem],
|
||||
[orderDescendingControlSetItem],
|
||||
serverPaginationControlSetRow,
|
||||
[rowLimitControlSetItem, serverPageLengthControlSetItem],
|
||||
[includeTimeControlSetItem, orderDescendingControlSetItem],
|
||||
[includeTimeControlSetItem],
|
||||
[showTotalsControlSetItem],
|
||||
['adhoc_filters'],
|
||||
emitFilterControl,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
import { ControlSetItem, Dataset } from '@superset-ui/chart-controls';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { isAggMode, isRawMode } from './shared';
|
||||
|
||||
export const orderByControlSetItem: ControlSetItem = {
|
||||
@@ -45,7 +46,12 @@ export const orderDescendingControlSetItem: ControlSetItem = {
|
||||
label: t('Sort descending'),
|
||||
default: true,
|
||||
description: t('Whether to sort descending or ascending'),
|
||||
visibility: isAggMode,
|
||||
visibility: ({ controls }) =>
|
||||
!!(
|
||||
isAggMode({ controls }) &&
|
||||
controls?.timeseries_limit_metric.value &&
|
||||
!isEmpty(controls?.timeseries_limit_metric.value)
|
||||
),
|
||||
resetOnHide: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -33,5 +33,5 @@ export const sortAlphanumericCaseInsensitive = <D extends {}>(
|
||||
if (!valueB || typeof valueB !== 'string') {
|
||||
return 1;
|
||||
}
|
||||
return valueA.localeCompare(valueB) > 0 ? 1 : -1;
|
||||
return valueA.localeCompare(valueB);
|
||||
};
|
||||
|
||||
@@ -394,6 +394,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
colorPositiveNegative,
|
||||
})
|
||||
: undefined)};
|
||||
white-space: ${value instanceof Date ? 'nowrap' : undefined};
|
||||
`;
|
||||
|
||||
const cellProps = {
|
||||
@@ -449,7 +450,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
data-column-name={col.id}
|
||||
css={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
alignItems: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<span data-column-name={col.id}>{label}</span>
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { defaultOrderByFn, Row } from 'react-table';
|
||||
import { sortAlphanumericCaseInsensitive } from '../src/DataTable/utils/sortAlphanumericCaseInsensitive';
|
||||
|
||||
type RecursivePartial<T> = {
|
||||
[P in keyof T]?: T[P] | RecursivePartial<T[P]>;
|
||||
};
|
||||
|
||||
const testData = [
|
||||
{
|
||||
values: {
|
||||
@@ -133,3 +138,106 @@ describe('sortAlphanumericCaseInsensitive', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
const testDataMulti: Array<RecursivePartial<Row<object>>> = [
|
||||
{
|
||||
values: {
|
||||
colA: 'group 1',
|
||||
colB: '10',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 1',
|
||||
colB: '15',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 1',
|
||||
colB: '20',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 2',
|
||||
colB: '10',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 3',
|
||||
colB: '10',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 3',
|
||||
colB: '15',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 3',
|
||||
colB: '10',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('sortAlphanumericCaseInsensitiveMulti', () => {
|
||||
it('Sort rows', () => {
|
||||
const sorted = defaultOrderByFn(
|
||||
[...testDataMulti] as Array<Row<object>>,
|
||||
[
|
||||
(a, b) => sortAlphanumericCaseInsensitive(a, b, 'colA'),
|
||||
(a, b) => sortAlphanumericCaseInsensitive(a, b, 'colB'),
|
||||
],
|
||||
[true, false],
|
||||
);
|
||||
|
||||
expect(sorted).toEqual([
|
||||
{
|
||||
values: {
|
||||
colA: 'group 1',
|
||||
colB: '20',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 1',
|
||||
colB: '15',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 1',
|
||||
colB: '10',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 2',
|
||||
colB: '10',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 3',
|
||||
colB: '15',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 3',
|
||||
colB: '10',
|
||||
},
|
||||
},
|
||||
{
|
||||
values: {
|
||||
colA: 'group 3',
|
||||
colB: '10',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
5
superset-frontend/spec/fixtures/mockState.js
vendored
5
superset-frontend/spec/fixtures/mockState.js
vendored
@@ -35,6 +35,11 @@ export default {
|
||||
sliceEntities: sliceEntitiesForChart,
|
||||
charts: chartQueries,
|
||||
nativeFilters: nativeFiltersInfo,
|
||||
common: {
|
||||
conf: {
|
||||
SAMPLES_ROW_LIMIT: 10,
|
||||
},
|
||||
},
|
||||
dataMask: mockDataMaskInfo,
|
||||
dashboardInfo,
|
||||
dashboardFilters: emptyFilters,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
|
||||
@@ -81,3 +82,10 @@ setupSupersetClient();
|
||||
jest.mock('src/hooks/useTabId', () => ({
|
||||
useTabId: () => 1,
|
||||
}));
|
||||
|
||||
// Check https://github.com/remarkjs/react-markdown/issues/635
|
||||
jest.mock('react-markdown', () => (props: any) => <>{props.children}</>);
|
||||
jest.mock('rehype-sanitize', () => () => jest.fn());
|
||||
jest.mock('rehype-raw', () => () => jest.fn());
|
||||
|
||||
process.env.WEBPACK_MODE = 'test';
|
||||
@@ -370,8 +370,8 @@ div.tablePopover {
|
||||
border: 1px solid @gray-light;
|
||||
font-feature-settings: @font-feature-settings;
|
||||
// Fira Code causes problem with Ace under Firefox
|
||||
font-family: 'Menlo', 'Lucida Console', 'Courier New', 'Ubuntu Mono',
|
||||
'Consolas', 'source-code-pro', monospace;
|
||||
font-family: 'Menlo', 'Consolas', 'Courier New', 'Ubuntu Mono',
|
||||
'source-code-pro', 'Lucida Console', monospace;
|
||||
|
||||
&.ace_autocomplete {
|
||||
// Use !important because Ace Editor applies extra CSS at the last second
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
getExploreUrl,
|
||||
getLegacyEndpointType,
|
||||
buildV1ChartDataPayload,
|
||||
postForm,
|
||||
shouldUseLegacyApi,
|
||||
getChartDataUri,
|
||||
} from 'src/explore/exploreUtils';
|
||||
@@ -40,6 +39,7 @@ import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
import { logEvent } from 'src/logger/actions';
|
||||
import { Logger, LOG_ACTIONS_LOAD_CHART } from 'src/logger/LogUtils';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
import { allowCrossDomain as domainShardingEnabled } from 'src/utils/hostNamesConfig';
|
||||
import { updateDataMask } from 'src/dataMask/actions';
|
||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||
@@ -563,7 +563,9 @@ export function redirectSQLLab(formData) {
|
||||
datasourceKey: formData.datasource,
|
||||
sql: json.result[0].query,
|
||||
};
|
||||
postForm(redirectUrl, payload);
|
||||
SupersetClient.postForm(redirectUrl, {
|
||||
form_data: safeStringify(payload),
|
||||
});
|
||||
})
|
||||
.catch(() =>
|
||||
dispatch(addDangerToast(t('An error occurred while loading the SQL'))),
|
||||
|
||||
@@ -130,6 +130,13 @@ const CrudButtonWrapper = styled.div`
|
||||
${({ theme }) => `margin-bottom: ${theme.gridUnit * 2}px`}
|
||||
`;
|
||||
|
||||
const StyledButtonWrapper = styled.span`
|
||||
${({ theme }) => `
|
||||
margin-top: ${theme.gridUnit * 3}px;
|
||||
margin-left: ${theme.gridUnit * 3}px;
|
||||
`}
|
||||
`;
|
||||
|
||||
export default class CRUDCollection extends React.PureComponent<
|
||||
CRUDCollectionProps,
|
||||
CRUDCollectionState
|
||||
@@ -424,7 +431,7 @@ export default class CRUDCollection extends React.PureComponent<
|
||||
<>
|
||||
<CrudButtonWrapper>
|
||||
{this.props.allowAddItem && (
|
||||
<span className="m-t-10 m-r-10">
|
||||
<StyledButtonWrapper>
|
||||
<Button
|
||||
buttonSize="small"
|
||||
buttonStyle="tertiary"
|
||||
@@ -434,7 +441,7 @@ export default class CRUDCollection extends React.PureComponent<
|
||||
<i data-test="crud-add-table-item" className="fa fa-plus" />{' '}
|
||||
{t('Add item')}
|
||||
</Button>
|
||||
</span>
|
||||
</StyledButtonWrapper>
|
||||
)}
|
||||
</CrudButtonWrapper>
|
||||
<CrudTableWrapper
|
||||
|
||||
@@ -123,6 +123,13 @@ const StyledColumnsTabWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButtonWrapper = styled.span`
|
||||
${({ theme }) => `
|
||||
margin-top: ${theme.gridUnit * 3}px;
|
||||
margin-left: ${theme.gridUnit * 3}px;
|
||||
`}
|
||||
`;
|
||||
|
||||
const checkboxGenerator = (d, onChange) => (
|
||||
<CheckboxControl value={d} onChange={onChange} />
|
||||
);
|
||||
@@ -1361,7 +1368,7 @@ class DatasourceEditor extends React.PureComponent {
|
||||
>
|
||||
<StyledColumnsTabWrapper>
|
||||
<ColumnButtonWrapper>
|
||||
<span className="m-t-10 m-r-10">
|
||||
<StyledButtonWrapper>
|
||||
<Button
|
||||
buttonSize="small"
|
||||
buttonStyle="tertiary"
|
||||
@@ -1372,7 +1379,7 @@ class DatasourceEditor extends React.PureComponent {
|
||||
<i className="fa fa-database" />{' '}
|
||||
{t('Sync columns from source')}
|
||||
</Button>
|
||||
</span>
|
||||
</StyledButtonWrapper>
|
||||
</ColumnButtonWrapper>
|
||||
<ColumnCollectionTable
|
||||
className="columns-table"
|
||||
|
||||
@@ -31,9 +31,13 @@ export const DOCUMENTATION_LINK = supersetTextDocs
|
||||
|
||||
export interface IProps {
|
||||
errorMessage: string;
|
||||
showDbInstallInstructions: boolean;
|
||||
}
|
||||
|
||||
const ErrorAlert: FunctionComponent<IProps> = ({ errorMessage }) => (
|
||||
const ErrorAlert: FunctionComponent<IProps> = ({
|
||||
errorMessage,
|
||||
showDbInstallInstructions,
|
||||
}) => (
|
||||
<Alert
|
||||
closable={false}
|
||||
css={(theme: SupersetTheme) => antdWarningAlertStyles(theme)}
|
||||
@@ -41,21 +45,25 @@ const ErrorAlert: FunctionComponent<IProps> = ({ errorMessage }) => (
|
||||
showIcon
|
||||
message={errorMessage}
|
||||
description={
|
||||
<>
|
||||
<br />
|
||||
{t(
|
||||
'Database driver for importing maybe not installed. Visit the Superset documentation page for installation instructions:',
|
||||
)}
|
||||
<a
|
||||
href={DOCUMENTATION_LINK}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="additional-fields-alert-description"
|
||||
>
|
||||
{t('here')}
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
showDbInstallInstructions ? (
|
||||
<>
|
||||
<br />
|
||||
{t(
|
||||
'Database driver for importing maybe not installed. Visit the Superset documentation page for installation instructions:',
|
||||
)}
|
||||
<a
|
||||
href={DOCUMENTATION_LINK}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="additional-fields-alert-description"
|
||||
>
|
||||
{t('here')}
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -300,7 +300,12 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({
|
||||
<Button loading={importingModel}>Select file</Button>
|
||||
</Upload>
|
||||
</StyledInputContainer>
|
||||
{errorMessage && <ErrorAlert errorMessage={errorMessage} />}
|
||||
{errorMessage && (
|
||||
<ErrorAlert
|
||||
errorMessage={errorMessage}
|
||||
showDbInstallInstructions={passwordFields.length > 0}
|
||||
/>
|
||||
)}
|
||||
{renderPasswordFields()}
|
||||
{renderOverwriteConfirmation()}
|
||||
</Modal>
|
||||
|
||||
@@ -25,8 +25,8 @@ const MenuItem = styled(AntdMenu.Item)`
|
||||
}
|
||||
|
||||
&.ant-menu-item {
|
||||
height: ${({ theme }) => theme.gridUnit * 7}px;
|
||||
line-height: ${({ theme }) => theme.gridUnit * 7}px;
|
||||
height: ${({ theme }) => theme.gridUnit * 8}px;
|
||||
line-height: ${({ theme }) => theme.gridUnit * 8}px;
|
||||
a {
|
||||
border-bottom: none;
|
||||
transition: background-color ${({ theme }) => theme.transitionTiming}s;
|
||||
|
||||
@@ -97,7 +97,7 @@ export default function Toast({ toast, onCloseToast }: ToastPresenterProps) {
|
||||
role="alert"
|
||||
>
|
||||
{icon}
|
||||
<Interweave content={toast.text} />
|
||||
<Interweave content={toast.text} noHtml={!toast.allowHtml} />
|
||||
<i
|
||||
className="fa fa-close pull-right pointer"
|
||||
role="button"
|
||||
|
||||
@@ -31,4 +31,6 @@ export interface ToastMeta {
|
||||
/** Whether to skip displaying this message if there are another toast
|
||||
* with the same message. */
|
||||
noDuplicate?: boolean;
|
||||
/** For security reasons, HTML rendering is disabled by default. Use this property to enable it. */
|
||||
allowHtml?: boolean;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,43 @@ const stateWithOnlyUser = {
|
||||
reports: {},
|
||||
};
|
||||
|
||||
const stateWithNonAdminUser = {
|
||||
explore: {
|
||||
user: {
|
||||
email: 'nonadmin@test.com',
|
||||
firstName: 'nonadmin',
|
||||
isActive: true,
|
||||
lastName: 'nonadmin',
|
||||
permissions: {},
|
||||
createdOn: '2022-01-12T10:17:37.801361',
|
||||
roles: {
|
||||
Gamme: [['no_menu_access', 'Manage']],
|
||||
OtherRole: [['menu_access', 'Manage']],
|
||||
},
|
||||
userId: 1,
|
||||
username: 'nonadmin',
|
||||
},
|
||||
},
|
||||
reports: {},
|
||||
};
|
||||
|
||||
const stateWithNonMenuAccessOnManage = {
|
||||
explore: {
|
||||
user: {
|
||||
email: 'nonaccess@test.com',
|
||||
firstName: 'nonaccess',
|
||||
isActive: true,
|
||||
lastName: 'nonaccess',
|
||||
permissions: {},
|
||||
createdOn: '2022-01-12T10:17:37.801361',
|
||||
roles: { Gamma: [['no_menu_access', 'Manage']] },
|
||||
userId: 1,
|
||||
username: 'nonaccess',
|
||||
},
|
||||
},
|
||||
reports: {},
|
||||
};
|
||||
|
||||
const stateWithUserAndReport = {
|
||||
explore: {
|
||||
user: {
|
||||
@@ -195,4 +232,32 @@ describe('Header Report Dropdown', () => {
|
||||
});
|
||||
expect(screen.getByText('Set up an email report')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Schedule Email Reports as long as user has permission through any role', () => {
|
||||
let mockedProps = createProps();
|
||||
mockedProps = {
|
||||
...mockedProps,
|
||||
useTextMenu: true,
|
||||
isDropdownVisible: true,
|
||||
};
|
||||
act(() => {
|
||||
setup(mockedProps, stateWithNonAdminUser);
|
||||
});
|
||||
expect(screen.getByText('Set up an email report')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('do not render Schedule Email Reports if user no permission', () => {
|
||||
let mockedProps = createProps();
|
||||
mockedProps = {
|
||||
...mockedProps,
|
||||
useTextMenu: true,
|
||||
isDropdownVisible: true,
|
||||
};
|
||||
act(() => {
|
||||
setup(mockedProps, stateWithNonMenuAccessOnManage);
|
||||
});
|
||||
expect(
|
||||
screen.queryByText('Set up an email report'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,7 +81,6 @@ export interface HeaderReportProps {
|
||||
setShowReportSubMenu?: (show: boolean) => void;
|
||||
setIsDropdownVisible?: (visible: boolean) => void;
|
||||
isDropdownVisible?: boolean;
|
||||
showReportSubMenu?: boolean;
|
||||
}
|
||||
|
||||
export default function HeaderReportDropDown({
|
||||
@@ -120,7 +119,7 @@ export default function HeaderReportDropDown({
|
||||
perms => perms[0] === 'menu_access' && perms[1] === 'Manage',
|
||||
),
|
||||
);
|
||||
return permissions[0].length > 0;
|
||||
return permissions.some(permission => permission.length > 0);
|
||||
};
|
||||
|
||||
const [currentReportDeleting, setCurrentReportDeleting] =
|
||||
@@ -156,10 +155,8 @@ export default function HeaderReportDropDown({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const showReportSubMenu = report && setShowReportSubMenu && canAddReports();
|
||||
|
||||
useEffect(() => {
|
||||
if (showReportSubMenu) {
|
||||
if (report && setShowReportSubMenu && canAddReports()) {
|
||||
setShowReportSubMenu(true);
|
||||
} else if (!report && setShowReportSubMenu) {
|
||||
setShowReportSubMenu(false);
|
||||
|
||||
@@ -325,7 +325,7 @@ export function saveDashboardRequest(data, id, saveType) {
|
||||
|
||||
const onError = async response => {
|
||||
const { error, message } = await getClientErrorObject(response);
|
||||
let errorText = t('Sorry, an unknown error occured');
|
||||
let errorText = t('Sorry, an unknown error occurred');
|
||||
|
||||
if (error) {
|
||||
errorText = t(
|
||||
|
||||
@@ -127,6 +127,8 @@ export const hydrateDashboard =
|
||||
const dashboardFilters = {};
|
||||
const slices = {};
|
||||
const sliceIds = new Set();
|
||||
const slicesFromExploreCount = new Map();
|
||||
|
||||
chartData.forEach(slice => {
|
||||
const key = slice.slice_id;
|
||||
const form_data = {
|
||||
@@ -182,6 +184,10 @@ export const hydrateDashboard =
|
||||
(newSlicesContainer.parents || []).slice(),
|
||||
);
|
||||
|
||||
const count = (slicesFromExploreCount.get(slice.slice_id) ?? 0) + 1;
|
||||
chartHolder.id = `${CHART_TYPE}-explore-${slice.slice_id}-${count}`;
|
||||
slicesFromExploreCount.set(slice.slice_id, count);
|
||||
|
||||
layout[chartHolder.id] = chartHolder;
|
||||
newSlicesContainer.children.push(chartHolder.id);
|
||||
chartIdToLayoutId[chartHolder.meta.chartId] = chartHolder.id;
|
||||
|
||||
@@ -41,8 +41,7 @@ export interface BCPProps {
|
||||
|
||||
const SUPERSET_HEADER_HEIGHT = 59;
|
||||
const SIDEPANE_ADJUST_OFFSET = 4;
|
||||
const SIDEPANE_HEADER_HEIGHT = 64; // including margins
|
||||
const SIDEPANE_FILTERBAR_HEIGHT = 56;
|
||||
const TOP_PANEL_OFFSET = 210;
|
||||
|
||||
const BuilderComponentPaneTabs = styled(Tabs)`
|
||||
line-height: inherit;
|
||||
@@ -52,20 +51,10 @@ const BuilderComponentPaneTabs = styled(Tabs)`
|
||||
const DashboardBuilderSidepane = styled.div<{
|
||||
topOffset: number;
|
||||
}>`
|
||||
height: 100%;
|
||||
height: calc(100% - ${TOP_PANEL_OFFSET}px);
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
.ReactVirtualized__List {
|
||||
padding-bottom: ${({ topOffset }) =>
|
||||
`${
|
||||
SIDEPANE_HEADER_HEIGHT +
|
||||
SIDEPANE_FILTERBAR_HEIGHT +
|
||||
SIDEPANE_ADJUST_OFFSET +
|
||||
topOffset
|
||||
}px`};
|
||||
}
|
||||
`;
|
||||
|
||||
const BuilderComponentPane: React.FC<BCPProps> = ({
|
||||
|
||||
@@ -174,7 +174,6 @@ class HeaderActionsDropdown extends React.PureComponent {
|
||||
downloadAsImage(
|
||||
SCREENSHOT_NODE_SELECTOR,
|
||||
this.props.dashboardTitle,
|
||||
{},
|
||||
true,
|
||||
)(domEvent).then(() => {
|
||||
menu.style.visibility = 'visible';
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -129,12 +129,14 @@ const PropertiesModal = ({
|
||||
return SupersetClient.get({
|
||||
endpoint: `/api/v1/dashboard/related/${accessType}?q=${query}`,
|
||||
}).then(response => ({
|
||||
data: response.json.result.map(
|
||||
(item: { value: number; text: string }) => ({
|
||||
data: response.json.result
|
||||
.filter((item: { extra: { active: boolean } }) =>
|
||||
item.extra.active !== undefined ? item.extra.active : true,
|
||||
)
|
||||
.map((item: { value: number; text: string }) => ({
|
||||
value: item.value,
|
||||
label: item.text,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
totalCount: response.json.count,
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -334,7 +334,7 @@ class SliceHeaderControls extends React.PureComponent<
|
||||
<ModalTrigger
|
||||
triggerNode={
|
||||
<span data-test="view-query-menu-item">
|
||||
{t('Drill to detail')}
|
||||
{t('View as table')}
|
||||
</span>
|
||||
}
|
||||
modalTitle={t('Chart Data: %s', slice.slice_name)}
|
||||
|
||||
@@ -65,6 +65,10 @@ const propTypes = {
|
||||
deleteComponent: PropTypes.func.isRequired,
|
||||
handleComponentDrop: PropTypes.func.isRequired,
|
||||
updateComponents: PropTypes.func.isRequired,
|
||||
|
||||
// HTML sanitization
|
||||
htmlSanitization: PropTypes.bool,
|
||||
htmlSchemaOverrides: PropTypes.object,
|
||||
};
|
||||
|
||||
const defaultProps = {};
|
||||
@@ -265,6 +269,8 @@ class Markdown extends React.PureComponent {
|
||||
? MARKDOWN_ERROR_MESSAGE
|
||||
: this.state.markdownSource || MARKDOWN_PLACE_HOLDER
|
||||
}
|
||||
htmlSanitization={this.props.htmlSanitization}
|
||||
htmlSchemaOverrides={this.props.htmlSchemaOverrides}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -373,6 +379,8 @@ function mapStateToProps(state) {
|
||||
return {
|
||||
undoLength: state.dashboardLayout.past.length,
|
||||
redoLength: state.dashboardLayout.future.length,
|
||||
htmlSanitization: state.common.conf.HTML_SANITIZATION,
|
||||
htmlSchemaOverrides: state.common.conf.HTML_SANITIZATION_SCHEMA_EXTENSIONS,
|
||||
};
|
||||
}
|
||||
export default connect(mapStateToProps)(Markdown);
|
||||
|
||||
@@ -20,9 +20,9 @@ import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import sinon from 'sinon';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { SafeMarkdown } from '@superset-ui/core';
|
||||
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MarkdownEditor } from 'src/components/AsyncAceEditor';
|
||||
@@ -112,26 +112,26 @@ describe('Markdown', () => {
|
||||
it('should render an Markdown when NOT focused', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(MarkdownEditor)).not.toExist();
|
||||
expect(wrapper.find(ReactMarkdown)).toExist();
|
||||
expect(wrapper.find(SafeMarkdown)).toExist();
|
||||
});
|
||||
|
||||
it('should render an AceEditor when focused and editMode=true and editorMode=edit', async () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
expect(wrapper.find(MarkdownEditor)).not.toExist();
|
||||
expect(wrapper.find(ReactMarkdown)).toExist();
|
||||
expect(wrapper.find(SafeMarkdown)).toExist();
|
||||
act(() => {
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus + edit
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(MarkdownEditor)).toExist();
|
||||
expect(wrapper.find(ReactMarkdown)).not.toExist();
|
||||
expect(wrapper.find(SafeMarkdown)).not.toExist();
|
||||
});
|
||||
|
||||
it('should render a ReactMarkdown when focused and editMode=true and editorMode=preview', () => {
|
||||
it('should render a SafeMarkdown when focused and editMode=true and editorMode=preview', () => {
|
||||
const wrapper = setup({ editMode: true });
|
||||
wrapper.find(WithPopoverMenu).simulate('click'); // focus + edit
|
||||
expect(wrapper.find(MarkdownEditor)).toExist();
|
||||
expect(wrapper.find(ReactMarkdown)).not.toExist();
|
||||
expect(wrapper.find(SafeMarkdown)).not.toExist();
|
||||
|
||||
// we can't call setState on Markdown bc it's not the root component, so call
|
||||
// the mode dropdown onchange instead
|
||||
@@ -139,7 +139,7 @@ describe('Markdown', () => {
|
||||
dropdown.prop('onChange')('preview');
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find(ReactMarkdown)).toExist();
|
||||
expect(wrapper.find(SafeMarkdown)).toExist();
|
||||
expect(wrapper.find(MarkdownEditor)).not.toExist();
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import { Input } from 'src/components/Input';
|
||||
import { Form, FormItem } from 'src/components/Form';
|
||||
import Alert from 'src/components/Alert';
|
||||
import { JsonObject, t, styled } from '@superset-ui/core';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import Modal from 'src/components/Modal';
|
||||
import { Radio } from 'src/components/Radio';
|
||||
import Button from 'src/components/Button';
|
||||
@@ -32,7 +31,6 @@ import { connect } from 'react-redux';
|
||||
|
||||
// Session storage key for recent dashboard
|
||||
const SK_DASHBOARD_ID = 'save_chart_recent_dashboard';
|
||||
const SELECT_PLACEHOLDER = t('**Select** a dashboard OR **create** a new one');
|
||||
|
||||
type SaveModalProps = {
|
||||
onHide: () => void;
|
||||
@@ -282,11 +280,12 @@ class SaveModal extends React.Component<SaveModalProps, SaveModalState> {
|
||||
onChange={this.onDashboardSelectChange}
|
||||
value={dashboardSelectValue || undefined}
|
||||
placeholder={
|
||||
// Using markdown to allow for good i18n
|
||||
<ReactMarkdown
|
||||
source={SELECT_PLACEHOLDER}
|
||||
renderers={{ paragraph: 'span' }}
|
||||
/>
|
||||
<div>
|
||||
<b>{t('Select')}</b>
|
||||
{t(' a dashboard OR ')}
|
||||
<b>{t('create')}</b>
|
||||
{t(' a new one')}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
@@ -21,7 +21,6 @@ import React from 'react';
|
||||
import { render, screen, act } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { SupersetClient, DatasourceType } from '@superset-ui/core';
|
||||
import * as Utils from 'src/explore/exploreUtils';
|
||||
import DatasourceControl from '.';
|
||||
|
||||
const SupersetClientGet = jest.spyOn(SupersetClient, 'get');
|
||||
@@ -142,7 +141,7 @@ test('Click on Edit dataset', async () => {
|
||||
|
||||
test('Click on View in SQL Lab', async () => {
|
||||
const props = createProps();
|
||||
const postFormSpy = jest.spyOn(Utils, 'postForm');
|
||||
const postFormSpy = jest.spyOn(SupersetClient, 'postForm');
|
||||
postFormSpy.mockImplementation(jest.fn());
|
||||
|
||||
render(<DatasourceControl {...props} />, {
|
||||
|
||||
@@ -19,7 +19,13 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { t, styled, withTheme, DatasourceType } from '@superset-ui/core';
|
||||
import {
|
||||
DatasourceType,
|
||||
SupersetClient,
|
||||
styled,
|
||||
t,
|
||||
withTheme,
|
||||
} from '@superset-ui/core';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
|
||||
import { AntdDropdown } from 'src/components';
|
||||
@@ -30,13 +36,13 @@ import {
|
||||
ChangeDatasourceModal,
|
||||
DatasourceModal,
|
||||
} from 'src/components/Datasource';
|
||||
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
|
||||
import { postForm } from 'src/explore/exploreUtils';
|
||||
import Button from 'src/components/Button';
|
||||
import ErrorAlert from 'src/components/ErrorMessage/ErrorAlert';
|
||||
import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { isUserAdmin } from 'src/dashboard/util/findPermission';
|
||||
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
|
||||
const propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
@@ -193,7 +199,9 @@ class DatasourceControl extends React.PureComponent {
|
||||
datasourceKey: `${datasource.id}__${datasource.type}`,
|
||||
sql: datasource.sql,
|
||||
};
|
||||
postForm('/superset/sqllab/', payload);
|
||||
SupersetClient.postForm('/superset/sqllab/', {
|
||||
form_data: safeStringify(payload),
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { CustomFrame } from '.';
|
||||
@@ -29,8 +32,23 @@ const specificValue = '2021-03-16T00:00:00 : 2021-03-17T00:00:00';
|
||||
const relativeNowValue = `DATEADD(DATETIME("now"), -7, day) : DATEADD(DATETIME("now"), 7, day)`;
|
||||
const relativeTodayValue = `DATEADD(DATETIME("today"), -7, day) : DATEADD(DATETIME("today"), 7, day)`;
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({
|
||||
common: { locale: 'en' },
|
||||
});
|
||||
|
||||
// case when common.locale is not populated
|
||||
const emptyStore = mockStore({});
|
||||
|
||||
// case when common.locale is populated with invalid locale
|
||||
const invalidStore = mockStore({ common: { locale: 'invalid_locale' } });
|
||||
|
||||
test('renders with default props', () => {
|
||||
render(<CustomFrame onChange={jest.fn()} value={emptyValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={jest.fn()} value={emptyValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getByText('Configure custom time range')).toBeInTheDocument();
|
||||
expect(screen.getByText('Relative Date/Time')).toBeInTheDocument();
|
||||
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
||||
@@ -39,14 +57,70 @@ test('renders with default props', () => {
|
||||
expect(screen.getByRole('img', { name: 'calendar' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders with empty store', () => {
|
||||
render(
|
||||
<Provider store={emptyStore}>
|
||||
<CustomFrame onChange={jest.fn()} value={emptyValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getByText('Configure custom time range')).toBeInTheDocument();
|
||||
expect(screen.getByText('Relative Date/Time')).toBeInTheDocument();
|
||||
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
||||
expect(screen.getByText('Days Before')).toBeInTheDocument();
|
||||
expect(screen.getByText('Specific Date/Time')).toBeInTheDocument();
|
||||
expect(screen.getByRole('img', { name: 'calendar' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders since and until with specific date/time with default locale', () => {
|
||||
render(
|
||||
<Provider store={emptyStore}>
|
||||
<CustomFrame onChange={jest.fn()} value={specificValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getAllByText('Specific Date/Time').length).toBe(2);
|
||||
expect(screen.getAllByRole('img', { name: 'calendar' }).length).toBe(2);
|
||||
});
|
||||
|
||||
test('renders with invalid locale', () => {
|
||||
render(
|
||||
<Provider store={invalidStore}>
|
||||
<CustomFrame onChange={jest.fn()} value={emptyValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getByText('Configure custom time range')).toBeInTheDocument();
|
||||
expect(screen.getByText('Relative Date/Time')).toBeInTheDocument();
|
||||
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
||||
expect(screen.getByText('Days Before')).toBeInTheDocument();
|
||||
expect(screen.getByText('Specific Date/Time')).toBeInTheDocument();
|
||||
expect(screen.getByRole('img', { name: 'calendar' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders since and until with specific date/time with invalid locale', () => {
|
||||
render(
|
||||
<Provider store={invalidStore}>
|
||||
<CustomFrame onChange={jest.fn()} value={specificValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getAllByText('Specific Date/Time').length).toBe(2);
|
||||
expect(screen.getAllByRole('img', { name: 'calendar' }).length).toBe(2);
|
||||
});
|
||||
|
||||
test('renders since and until with specific date/time', () => {
|
||||
render(<CustomFrame onChange={jest.fn()} value={specificValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={jest.fn()} value={specificValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getAllByText('Specific Date/Time').length).toBe(2);
|
||||
expect(screen.getAllByRole('img', { name: 'calendar' }).length).toBe(2);
|
||||
});
|
||||
|
||||
test('renders since and until with relative date/time', () => {
|
||||
render(<CustomFrame onChange={jest.fn()} value={relativeNowValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={jest.fn()} value={relativeNowValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getAllByText('Relative Date/Time').length).toBe(2);
|
||||
expect(screen.getAllByRole('spinbutton').length).toBe(2);
|
||||
expect(screen.getByText('Days Before')).toBeInTheDocument();
|
||||
@@ -54,17 +128,29 @@ test('renders since and until with relative date/time', () => {
|
||||
});
|
||||
|
||||
test('renders since and until with Now option', () => {
|
||||
render(<CustomFrame onChange={jest.fn()} value={nowValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={jest.fn()} value={nowValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getAllByText('Now').length).toBe(2);
|
||||
});
|
||||
|
||||
test('renders since and until with Midnight option', () => {
|
||||
render(<CustomFrame onChange={jest.fn()} value={todayValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={jest.fn()} value={todayValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getAllByText('Midnight').length).toBe(2);
|
||||
});
|
||||
|
||||
test('renders anchor with now option', () => {
|
||||
render(<CustomFrame onChange={jest.fn()} value={relativeNowValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={jest.fn()} value={relativeNowValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getByText('Anchor to')).toBeInTheDocument();
|
||||
expect(screen.getByRole('radio', { name: 'NOW' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('radio', { name: 'Date/Time' })).toBeInTheDocument();
|
||||
@@ -72,7 +158,11 @@ test('renders anchor with now option', () => {
|
||||
});
|
||||
|
||||
test('renders anchor with date/time option', () => {
|
||||
render(<CustomFrame onChange={jest.fn()} value={relativeTodayValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={jest.fn()} value={relativeTodayValue} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(screen.getByText('Anchor to')).toBeInTheDocument();
|
||||
expect(screen.getByRole('radio', { name: 'NOW' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('radio', { name: 'Date/Time' })).toBeInTheDocument();
|
||||
@@ -81,21 +171,33 @@ test('renders anchor with date/time option', () => {
|
||||
|
||||
test('triggers onChange when the anchor changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<CustomFrame onChange={onChange} value={relativeNowValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={onChange} value={relativeNowValue} />
|
||||
</Provider>,
|
||||
);
|
||||
userEvent.click(screen.getByRole('radio', { name: 'Date/Time' }));
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('triggers onChange when the value changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<CustomFrame onChange={onChange} value={emptyValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={onChange} value={emptyValue} />
|
||||
</Provider>,
|
||||
);
|
||||
userEvent.click(screen.getByRole('img', { name: 'up' }));
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('triggers onChange when the mode changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<CustomFrame onChange={onChange} value={todayNowValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={onChange} value={todayNowValue} />
|
||||
</Provider>,
|
||||
);
|
||||
userEvent.click(screen.getByTitle('Midnight'));
|
||||
userEvent.click(screen.getByTitle('Relative Date/Time'));
|
||||
userEvent.click(screen.getAllByTitle('Now')[1]);
|
||||
@@ -105,7 +207,11 @@ test('triggers onChange when the mode changes', () => {
|
||||
|
||||
test('triggers onChange when the grain changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
render(<CustomFrame onChange={onChange} value={relativeNowValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={onChange} value={relativeNowValue} />
|
||||
</Provider>,
|
||||
);
|
||||
userEvent.click(screen.getByText('Days Before'));
|
||||
userEvent.click(screen.getByText('Weeks Before'));
|
||||
userEvent.click(screen.getByText('Days After'));
|
||||
@@ -115,7 +221,11 @@ test('triggers onChange when the grain changes', async () => {
|
||||
|
||||
test('triggers onChange when the date changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
render(<CustomFrame onChange={onChange} value={specificValue} />);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={onChange} value={specificValue} />
|
||||
</Provider>,
|
||||
);
|
||||
const inputs = screen.getAllByPlaceholderText('Select date');
|
||||
userEvent.click(inputs[0]);
|
||||
userEvent.click(screen.getAllByText('Now')[0]);
|
||||
@@ -123,3 +233,24 @@ test('triggers onChange when the date changes', async () => {
|
||||
userEvent.click(screen.getAllByText('Now')[1]);
|
||||
expect(onChange).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('should translate Date Picker', () => {
|
||||
const onChange = jest.fn();
|
||||
const store = mockStore({
|
||||
common: { locale: 'fr' },
|
||||
});
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<CustomFrame onChange={onChange} value={specificValue} />
|
||||
</Provider>,
|
||||
);
|
||||
userEvent.click(screen.getAllByRole('img', { name: 'calendar' })[0]);
|
||||
expect(screen.getByText('2021')).toBeInTheDocument();
|
||||
expect(screen.getByText('lu')).toBeInTheDocument();
|
||||
expect(screen.getByText('ma')).toBeInTheDocument();
|
||||
expect(screen.getByText('me')).toBeInTheDocument();
|
||||
expect(screen.getByText('je')).toBeInTheDocument();
|
||||
expect(screen.getByText('ve')).toBeInTheDocument();
|
||||
expect(screen.getByText('sa')).toBeInTheDocument();
|
||||
expect(screen.getByText('di')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { Moment } from 'moment';
|
||||
import { isInteger } from 'lodash';
|
||||
// @ts-ignore
|
||||
import { locales } from 'antd/dist/antd-with-locales';
|
||||
import { Col, Row } from 'src/components';
|
||||
import { InputNumber } from 'src/components/Input';
|
||||
import { DatePicker } from 'src/components/DatePicker';
|
||||
@@ -36,11 +39,13 @@ import {
|
||||
customTimeRangeDecode,
|
||||
customTimeRangeEncode,
|
||||
dttmToMoment,
|
||||
LOCALE_MAPPING,
|
||||
} from 'src/explore/components/controls/DateFilterControl/utils';
|
||||
import {
|
||||
CustomRangeKey,
|
||||
FrameComponentProps,
|
||||
} from 'src/explore/components/controls/DateFilterControl/types';
|
||||
import { ExplorePageState } from 'src/explore/types';
|
||||
|
||||
export function CustomFrame(props: FrameComponentProps) {
|
||||
const { customRange, matchedFlag } = customTimeRangeDecode(props.value);
|
||||
@@ -105,6 +110,16 @@ export function CustomFrame(props: FrameComponentProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// check if there is a locale defined for explore
|
||||
const localFromFlaskBabel = useSelector(
|
||||
(state: ExplorePageState) => state?.common?.locale,
|
||||
);
|
||||
// An undefined datePickerLocale is acceptable if no match is found in the LOCALE_MAPPING[localFromFlaskBabel] lookup
|
||||
// and will fall back to antd's default locale when the antd DataPicker's prop locale === undefined
|
||||
// This also protects us from the case where state is populated with a locale that antd locales does not recognize
|
||||
const datePickerLocale =
|
||||
locales[LOCALE_MAPPING[localFromFlaskBabel]]?.DatePicker;
|
||||
|
||||
return (
|
||||
<div data-test="custom-frame">
|
||||
<div className="section-title">{t('Configure custom time range')}</div>
|
||||
@@ -132,6 +147,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
||||
onChange('sinceDatetime', datetime.format(MOMENT_FORMAT))
|
||||
}
|
||||
allowClear={false}
|
||||
locale={datePickerLocale}
|
||||
/>
|
||||
</Row>
|
||||
)}
|
||||
@@ -184,6 +200,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
||||
onChange('untilDatetime', datetime.format(MOMENT_FORMAT))
|
||||
}
|
||||
allowClear={false}
|
||||
locale={datePickerLocale}
|
||||
/>
|
||||
</Row>
|
||||
)}
|
||||
@@ -241,6 +258,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
||||
}
|
||||
allowClear={false}
|
||||
className="control-anchor-to-datetime"
|
||||
locale={datePickerLocale}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
|
||||
@@ -114,3 +114,20 @@ export const SEVEN_DAYS_AGO = moment()
|
||||
.subtract(7, 'days')
|
||||
.format(MOMENT_FORMAT);
|
||||
export const MIDNIGHT = moment().utc().startOf('day').format(MOMENT_FORMAT);
|
||||
|
||||
export const LOCALE_MAPPING = {
|
||||
en: 'en_US',
|
||||
fr: 'fr_FR',
|
||||
es: 'es_ES',
|
||||
it: 'it_IT',
|
||||
zh: 'zh_CN',
|
||||
ja: 'ja_JP',
|
||||
de: 'de_DE',
|
||||
pt: 'pt_PT',
|
||||
pt_BR: 'pt_BR',
|
||||
ru: 'ru_RU',
|
||||
ko: 'ko_KR',
|
||||
sk: 'sk_SK',
|
||||
sl: 'sl_SI',
|
||||
nl: 'nl_NL',
|
||||
};
|
||||
|
||||
@@ -203,7 +203,6 @@ export const useExploreAdditionalActionsMenu = (
|
||||
'.panel-body .chart-container',
|
||||
// eslint-disable-next-line camelcase
|
||||
slice?.slice_name ?? t('New chart'),
|
||||
{},
|
||||
true,
|
||||
)(domEvent);
|
||||
setIsDropdownVisible(false);
|
||||
|
||||
@@ -28,15 +28,46 @@ import {
|
||||
import { xAxisControl } from '../../../plugins/plugin-chart-echarts/src/controls';
|
||||
|
||||
describe('should collect control values and create SFD', () => {
|
||||
const sharedControlsFormData = {};
|
||||
Object.entries(sharedControls).forEach(([, names]) => {
|
||||
names.forEach(name => {
|
||||
sharedControlsFormData[name] = name;
|
||||
});
|
||||
});
|
||||
const publicControlsFormData = Object.fromEntries(
|
||||
publicControls.map((name, idx) => [[name], idx]),
|
||||
);
|
||||
const sharedControlsFormData = {
|
||||
// metrics
|
||||
metric: 'm1',
|
||||
metrics: ['m2'],
|
||||
metric_2: 'm3',
|
||||
// columns
|
||||
groupby: ['c1'],
|
||||
columns: ['c2'],
|
||||
groupbyColumns: ['c3'],
|
||||
groupbyRows: ['c4'],
|
||||
};
|
||||
const publicControlsFormData = {
|
||||
// time section
|
||||
granularity_sqla: 'time_column',
|
||||
time_grain_sqla: 'P1D',
|
||||
time_range: '2000 : today',
|
||||
// filters
|
||||
adhoc_filters: [],
|
||||
// subquery limit(series limit)
|
||||
limit: 5,
|
||||
// order by clause
|
||||
timeseries_limit_metric: 'orderby_metric',
|
||||
series_limit_metric: 'orderby_metric',
|
||||
// desc or asc in order by clause
|
||||
order_desc: true,
|
||||
// outer query limit
|
||||
row_limit: 100,
|
||||
// x asxs column
|
||||
x_axis: 'x_axis_column',
|
||||
// advanced analytics - rolling window
|
||||
rolling_type: 'sum',
|
||||
rolling_periods: 1,
|
||||
min_periods: 0,
|
||||
// advanced analytics - time comparison
|
||||
time_compare: '1 year ago',
|
||||
comparison_type: 'values',
|
||||
// advanced analytics - resample
|
||||
resample_rule: '1D',
|
||||
resample_method: 'zerofill',
|
||||
};
|
||||
const sourceMockFormData: QueryFormData = {
|
||||
...sharedControlsFormData,
|
||||
...publicControlsFormData,
|
||||
@@ -90,26 +121,45 @@ describe('should collect control values and create SFD', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('collect sharedControls', () => {
|
||||
const sfd = new StandardizedFormData(sourceMockFormData);
|
||||
|
||||
expect(sfd.dumpSFD().standardizedState.metrics).toEqual(
|
||||
sharedControls.metrics.map(controlName => controlName),
|
||||
);
|
||||
expect(sfd.dumpSFD().standardizedState.columns).toEqual(
|
||||
sharedControls.columns.map(controlName => controlName),
|
||||
);
|
||||
test('should avoid to overlap', () => {
|
||||
const sharedControlsSet = new Set(Object.keys(sharedControls));
|
||||
const publicControlsSet = new Set(publicControls);
|
||||
expect(
|
||||
[...sharedControlsSet].filter((x: string) => publicControlsSet.has(x)),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
test('should transform all publicControls', () => {
|
||||
test('should collect all sharedControls', () => {
|
||||
expect(Object.entries(sharedControlsFormData).length).toBe(
|
||||
Object.entries(sharedControls).length,
|
||||
);
|
||||
const sfd = new StandardizedFormData(sourceMockFormData);
|
||||
expect(sfd.serialize().standardizedState.metrics).toEqual([
|
||||
'm1',
|
||||
'm2',
|
||||
'm3',
|
||||
]);
|
||||
expect(sfd.serialize().standardizedState.columns).toEqual([
|
||||
'c1',
|
||||
'c2',
|
||||
'c3',
|
||||
'c4',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should transform all publicControls and sharedControls', () => {
|
||||
expect(Object.entries(publicControlsFormData).length).toBe(
|
||||
publicControls.length,
|
||||
);
|
||||
|
||||
const sfd = new StandardizedFormData(sourceMockFormData);
|
||||
const { formData } = sfd.transform('target_viz', sourceMockStore);
|
||||
Object.entries(publicControlsFormData).forEach(([key]) => {
|
||||
Object.entries(publicControlsFormData).forEach(([key, value]) => {
|
||||
expect(formData).toHaveProperty(key);
|
||||
expect(value).toEqual(publicControlsFormData[key]);
|
||||
});
|
||||
Object.entries(sharedControls).forEach(([key, value]) => {
|
||||
expect(formData[key]).toEqual(value);
|
||||
});
|
||||
expect(formData.columns).toEqual(['c1', 'c2', 'c3', 'c4']);
|
||||
expect(formData.metrics).toEqual(['m1', 'm2', 'm3']);
|
||||
});
|
||||
|
||||
test('should inherit standardizedFormData and memorizedFormData is LIFO', () => {
|
||||
@@ -157,6 +207,7 @@ describe('should transform form_data between table and bigNumberTotal', () => {
|
||||
const tableVizFormData = {
|
||||
datasource: '30__table',
|
||||
viz_type: 'table',
|
||||
granularity_sqla: 'ds',
|
||||
time_grain_sqla: 'P1D',
|
||||
time_range: 'No filter',
|
||||
query_mode: 'aggregate',
|
||||
@@ -172,7 +223,6 @@ describe('should transform form_data between table and bigNumberTotal', () => {
|
||||
table_timestamp_format: 'smart_date',
|
||||
show_cell_bars: true,
|
||||
color_pn: true,
|
||||
applied_time_extras: {},
|
||||
url_params: {
|
||||
form_data_key:
|
||||
'p3No_sqDW7k-kMTzlBPAPd9vwp1IXTf6stbyzjlrPPa0ninvdYUUiMC6F1iKit3Y',
|
||||
@@ -197,7 +247,9 @@ describe('should transform form_data between table and bigNumberTotal', () => {
|
||||
dataset_id: '30',
|
||||
},
|
||||
},
|
||||
granularity_sqla: {},
|
||||
granularity_sqla: {
|
||||
value: 'ds',
|
||||
},
|
||||
time_grain_sqla: {
|
||||
value: 'P1D',
|
||||
},
|
||||
@@ -271,6 +323,22 @@ describe('should transform form_data between table and bigNumberTotal', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('get and has', () => {
|
||||
// table -> bigNumberTotal
|
||||
const sfd = new StandardizedFormData(tableVizFormData);
|
||||
const { formData: bntFormData } = sfd.transform(
|
||||
'big_number_total',
|
||||
tableVizStore,
|
||||
);
|
||||
|
||||
// bigNumberTotal -> table
|
||||
const sfd2 = new StandardizedFormData(bntFormData);
|
||||
expect(sfd2.has('big_number_total')).toBeTruthy();
|
||||
expect(sfd2.has('table')).toBeTruthy();
|
||||
expect(sfd2.get('big_number_total').viz_type).toBe('big_number_total');
|
||||
expect(sfd2.get('table').viz_type).toBe('table');
|
||||
});
|
||||
|
||||
test('transform', () => {
|
||||
// table -> bigNumberTotal
|
||||
const sfd = new StandardizedFormData(tableVizFormData);
|
||||
@@ -301,7 +369,7 @@ describe('should transform form_data between table and bigNumberTotal', () => {
|
||||
);
|
||||
expect(tblFormData.viz_type).toBe('table');
|
||||
expect(tblFormData.metrics).toEqual(['sum(sales)']);
|
||||
expect(tblFormData.groupby).toEqual([]);
|
||||
expect(tblFormData.groupby).toEqual(['name']);
|
||||
expect(tblFormData.time_range).toBe('2021 : 2022');
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { isEmpty, intersection } from 'lodash';
|
||||
import {
|
||||
ensureIsArray,
|
||||
getChartControlPanelRegistry,
|
||||
@@ -29,38 +30,52 @@ import {
|
||||
import { getControlsState } from 'src/explore/store';
|
||||
import { getFormDataFromControls } from './getFormDataFromControls';
|
||||
|
||||
export const sharedControls: Record<keyof StandardizedState, string[]> = {
|
||||
metrics: ['metric', 'metrics', 'metric_2'],
|
||||
columns: ['groupby', 'columns', 'groupbyColumns', 'groupbyRows'],
|
||||
export const sharedControls: Record<string, keyof StandardizedState> = {
|
||||
// metrics
|
||||
metric: 'metrics', // via sharedControls, scalar
|
||||
metrics: 'metrics', // via sharedControls, array
|
||||
metric_2: 'metrics', // via sharedControls, scalar
|
||||
// columns
|
||||
groupby: 'columns', // via sharedControls, array
|
||||
columns: 'columns', // via sharedControls, array
|
||||
groupbyColumns: 'columns', // via pivot table v2, array
|
||||
groupbyRows: 'columns', // via pivot table v2, array
|
||||
};
|
||||
const sharedControlsMap: Record<keyof StandardizedState, string[]> = {
|
||||
metrics: [],
|
||||
columns: [],
|
||||
};
|
||||
Object.entries(sharedControls).forEach(([key, value]) =>
|
||||
sharedControlsMap[value].push(key),
|
||||
);
|
||||
export const publicControls = [
|
||||
// time section
|
||||
'granularity_sqla',
|
||||
'time_grain_sqla',
|
||||
'time_range',
|
||||
'granularity_sqla', // via sharedControls
|
||||
'time_grain_sqla', // via sharedControls
|
||||
'time_range', // via sharedControls
|
||||
// filters
|
||||
'adhoc_filters',
|
||||
'adhoc_filters', // via sharedControls
|
||||
// subquery limit(series limit)
|
||||
'limit',
|
||||
'limit', // via sharedControls
|
||||
// order by clause
|
||||
'timeseries_limit_metric',
|
||||
'series_limit_metric',
|
||||
'timeseries_limit_metric', // via sharedControls
|
||||
'series_limit_metric', // via sharedControls
|
||||
// desc or asc in order by clause
|
||||
'order_desc',
|
||||
'order_desc', // via sharedControls
|
||||
// outer query limit
|
||||
'row_limit',
|
||||
'row_limit', // via sharedControls
|
||||
// x asxs column
|
||||
'x_axis',
|
||||
'x_axis', // via sharedControls
|
||||
// advanced analytics - rolling window
|
||||
'rolling_type',
|
||||
'rolling_periods',
|
||||
'min_periods',
|
||||
'rolling_type', // via sections.advancedAnalytics
|
||||
'rolling_periods', // via sections.advancedAnalytics
|
||||
'min_periods', // via sections.advancedAnalytics
|
||||
// advanced analytics - time comparison
|
||||
'time_compare',
|
||||
'comparison_type',
|
||||
'time_compare', // via sections.advancedAnalytics
|
||||
'comparison_type', // via sections.advancedAnalytics
|
||||
// advanced analytics - resample
|
||||
'resample_rule',
|
||||
'resample_method',
|
||||
'resample_rule', // via sections.advancedAnalytics
|
||||
'resample_method', // via sections.advancedAnalytics
|
||||
];
|
||||
|
||||
export class StandardizedFormData {
|
||||
@@ -70,20 +85,10 @@ export class StandardizedFormData {
|
||||
/*
|
||||
* Support form_data for smooth switching between different viz
|
||||
* */
|
||||
const standardizedState = {
|
||||
metrics: [],
|
||||
columns: [],
|
||||
};
|
||||
const formData = Object.freeze(sourceFormData);
|
||||
const reversedMap = StandardizedFormData.getReversedMap();
|
||||
|
||||
Object.entries(formData).forEach(([key, value]) => {
|
||||
if (reversedMap.has(key)) {
|
||||
standardizedState[reversedMap.get(key)].push(...ensureIsArray(value));
|
||||
}
|
||||
});
|
||||
|
||||
const memorizedFormData = Array.isArray(
|
||||
// generates an ordered map, the key is viz_type and the value is form_data. the last item is current viz
|
||||
const memorizedFormData: Map<string, QueryFormData> = Array.isArray(
|
||||
formData?.standardizedFormData?.memorizedFormData,
|
||||
)
|
||||
? new Map(formData.standardizedFormData.memorizedFormData)
|
||||
@@ -93,25 +98,72 @@ export class StandardizedFormData {
|
||||
memorizedFormData.delete(vizType);
|
||||
}
|
||||
memorizedFormData.set(vizType, formData);
|
||||
|
||||
// calculate sharedControls
|
||||
const standardizedState =
|
||||
StandardizedFormData.getStandardizedState(formData);
|
||||
|
||||
this.sfd = {
|
||||
standardizedState,
|
||||
memorizedFormData,
|
||||
};
|
||||
}
|
||||
|
||||
static getReversedMap() {
|
||||
const reversedMap = new Map();
|
||||
Object.entries(sharedControls).forEach(([key, names]) => {
|
||||
names.forEach(name => {
|
||||
reversedMap.set(name, key);
|
||||
});
|
||||
static getStandardizedState(formData: QueryFormData): StandardizedState {
|
||||
// 1. collect current sharedControls
|
||||
let currState: StandardizedState = {
|
||||
metrics: [],
|
||||
columns: [],
|
||||
};
|
||||
Object.entries(formData).forEach(([key, value]) => {
|
||||
if (key in sharedControls) {
|
||||
currState[sharedControls[key]].push(...ensureIsArray(value));
|
||||
}
|
||||
});
|
||||
return reversedMap;
|
||||
|
||||
// 2. get previous StandardizedState
|
||||
let prevState: StandardizedState = {
|
||||
metrics: [],
|
||||
columns: [],
|
||||
};
|
||||
if (
|
||||
formData?.standardizedFormData?.standardizedState &&
|
||||
Array.isArray(formData.standardizedFormData.standardizedState.metrics) &&
|
||||
Array.isArray(formData.standardizedFormData.standardizedState.columns)
|
||||
) {
|
||||
prevState = formData.standardizedFormData.standardizedState;
|
||||
}
|
||||
// the initial prevState should equal to currentState
|
||||
if (isEmpty(prevState.metrics) && isEmpty(prevState.columns)) {
|
||||
prevState = currState;
|
||||
}
|
||||
|
||||
// 3. inherit SS from previous state if current viz hasn't columns-like controls or metrics-like controls
|
||||
Object.keys(sharedControlsMap).forEach(key => {
|
||||
if (
|
||||
isEmpty(intersection(Object.keys(formData), sharedControlsMap[key]))
|
||||
) {
|
||||
currState[key] = prevState[key];
|
||||
}
|
||||
});
|
||||
|
||||
// 4. update hook
|
||||
const controlPanel = getChartControlPanelRegistry().get(formData.viz_type);
|
||||
if (controlPanel?.updateStandardizedState) {
|
||||
currState = controlPanel.updateStandardizedState(prevState, currState);
|
||||
}
|
||||
|
||||
// 5. clear up
|
||||
Object.entries(currState).forEach(([key, value]) => {
|
||||
currState[key] = value.filter(Boolean);
|
||||
});
|
||||
|
||||
return currState;
|
||||
}
|
||||
|
||||
private getLatestFormData(vizType: string): QueryFormData {
|
||||
if (this.sfd.memorizedFormData.has(vizType)) {
|
||||
return this.sfd.memorizedFormData.get(vizType) as QueryFormData;
|
||||
if (this.has(vizType)) {
|
||||
return this.get(vizType);
|
||||
}
|
||||
|
||||
return this.memorizedFormData.slice(-1)[0][1];
|
||||
@@ -125,13 +177,21 @@ export class StandardizedFormData {
|
||||
return Array.from(this.sfd.memorizedFormData.entries());
|
||||
}
|
||||
|
||||
dumpSFD() {
|
||||
serialize() {
|
||||
return {
|
||||
standardizedState: this.standardizedState,
|
||||
memorizedFormData: this.memorizedFormData,
|
||||
};
|
||||
}
|
||||
|
||||
has(vizType: string): boolean {
|
||||
return this.sfd.memorizedFormData.has(vizType);
|
||||
}
|
||||
|
||||
get(vizType: string): QueryFormData {
|
||||
return this.sfd.memorizedFormData.get(vizType) as QueryFormData;
|
||||
}
|
||||
|
||||
transform(
|
||||
targetVizType: string,
|
||||
exploreState: Record<string, any>,
|
||||
@@ -162,7 +222,7 @@ export class StandardizedFormData {
|
||||
});
|
||||
const targetFormData = {
|
||||
...getFormDataFromControls(targetControlsState),
|
||||
standardizedFormData: this.dumpSFD(),
|
||||
standardizedFormData: this.serialize(),
|
||||
};
|
||||
|
||||
const controlPanel = getChartControlPanelRegistry().get(targetVizType);
|
||||
|
||||
@@ -21,13 +21,14 @@ import sinon from 'sinon';
|
||||
import URI from 'urijs';
|
||||
import {
|
||||
buildV1ChartDataPayload,
|
||||
exploreChart,
|
||||
getExploreUrl,
|
||||
shouldUseLegacyApi,
|
||||
getSimpleSQLExpression,
|
||||
shouldUseLegacyApi,
|
||||
} from 'src/explore/exploreUtils';
|
||||
import { DashboardStandaloneMode } from 'src/dashboard/util/constants';
|
||||
import * as hostNamesConfig from 'src/utils/hostNamesConfig';
|
||||
import { getChartMetadataRegistry } from '@superset-ui/core';
|
||||
import { getChartMetadataRegistry, SupersetClient } from '@superset-ui/core';
|
||||
|
||||
describe('exploreUtils', () => {
|
||||
const { location } = window;
|
||||
@@ -275,4 +276,16 @@ describe('exploreUtils', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.exploreChart()', () => {
|
||||
it('postForm', () => {
|
||||
const postFormSpy = jest.spyOn(SupersetClient, 'postForm');
|
||||
postFormSpy.mockImplementation(jest.fn());
|
||||
|
||||
exploreChart({
|
||||
formData: { ...formData, viz_type: 'my_custom_viz' },
|
||||
});
|
||||
expect(postFormSpy).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user