mirror of
https://github.com/apache/superset.git
synced 2026-04-29 13:04:22 +00:00
Compare commits
91 Commits
prefer-bin
...
2.0.1rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7000c72d5 | ||
|
|
f76755e72b | ||
|
|
42c9eacaeb | ||
|
|
af6887a84f | ||
|
|
2fbda195e8 | ||
|
|
8432c64b60 | ||
|
|
cea8e4daf2 | ||
|
|
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 |
538
CHANGELOG.md
538
CHANGELOG.md
@@ -19,6 +19,544 @@ under the License.
|
||||
|
||||
## Change Log
|
||||
|
||||
### 2.0.1 (Mon Oct 10 09:38:33 2022 +0530)
|
||||
**Database Migrations**
|
||||
|
||||
**Features**
|
||||
|
||||
**Fixes**
|
||||
- [#21729](https://github.com/apache/superset/pull/21729) fix: allow adhoc columns in non-aggregate query (@mayurnewase)
|
||||
- [#21576](https://github.com/apache/superset/pull/21576) fix(dashboard): dashboard doesn't load properly if it has tabs (@stephenLYZ)
|
||||
- [#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**
|
||||
- [#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)
|
||||
|
||||
**Fixes**
|
||||
|
||||
@@ -22,7 +22,7 @@ under the License.
|
||||
This file documents any backwards-incompatible changes in Superset and
|
||||
assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
## 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.
|
||||
@@ -46,10 +46,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 +78,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
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"],
|
||||
|
||||
@@ -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',
|
||||
|
||||
25877
superset-frontend/package-lock.json
generated
25877
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",
|
||||
@@ -237,7 +237,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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"@types/d3-time-format": "^2.1.0",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/math-expression-evaluator": "^1.2.1",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/rison": "0.0.6",
|
||||
"@types/seedrandom": "^2.4.28",
|
||||
"@types/fetch-mock": "^7.3.3",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,3 +81,5 @@ setupSupersetClient();
|
||||
jest.mock('src/hooks/useTabId', () => ({
|
||||
useTabId: () => 1,
|
||||
}));
|
||||
|
||||
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;
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -112,15 +112,7 @@ const StyledTabsContainer = styled.div`
|
||||
export class Tabs extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const tabIndex = Math.max(
|
||||
0,
|
||||
findTabIndexByComponentId({
|
||||
currentComponent: props.component,
|
||||
directPathToChild: props.directPathToChild,
|
||||
}),
|
||||
);
|
||||
const { children: tabIds } = props.component;
|
||||
const activeKey = tabIds[tabIndex];
|
||||
const { tabIndex, activeKey } = this.getTabInfo(props);
|
||||
|
||||
this.state = {
|
||||
tabIndex,
|
||||
@@ -155,6 +147,15 @@ export class Tabs extends React.PureComponent {
|
||||
this.setState(() => ({ tabIndex: maxIndex }));
|
||||
}
|
||||
|
||||
// reset tab index if dashboard was changed
|
||||
if (nextProps.dashboardId !== this.props.dashboardId) {
|
||||
const { tabIndex, activeKey } = this.getTabInfo(nextProps);
|
||||
this.setState(() => ({
|
||||
tabIndex,
|
||||
activeKey,
|
||||
}));
|
||||
}
|
||||
|
||||
if (nextProps.isComponentVisible) {
|
||||
const nextFocusComponent = getLeafComponentIdFromPath(
|
||||
nextProps.directPathToChild,
|
||||
@@ -187,6 +188,30 @@ export class Tabs extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
getTabInfo = props => {
|
||||
let tabIndex = Math.max(
|
||||
0,
|
||||
findTabIndexByComponentId({
|
||||
currentComponent: props.component,
|
||||
directPathToChild: props.directPathToChild,
|
||||
}),
|
||||
);
|
||||
if (tabIndex === 0 && props.activeTabs?.length) {
|
||||
props.component.children.forEach((tabId, index) => {
|
||||
if (tabIndex === 0 && props.activeTabs.includes(tabId)) {
|
||||
tabIndex = index;
|
||||
}
|
||||
});
|
||||
}
|
||||
const { children: tabIds } = props.component;
|
||||
const activeKey = tabIds[tabIndex];
|
||||
|
||||
return {
|
||||
tabIndex,
|
||||
activeKey,
|
||||
};
|
||||
};
|
||||
|
||||
showDeleteConfirmModal = key => {
|
||||
const { component, deleteComponent } = this.props;
|
||||
AntdModal.confirm({
|
||||
|
||||
@@ -53,6 +53,7 @@ describe('Tabs', () => {
|
||||
editMode: false,
|
||||
availableColumnCount: 12,
|
||||
columnWidth: 50,
|
||||
dashboardId: 1,
|
||||
onResizeStart() {},
|
||||
onResize() {},
|
||||
onResizeStop() {},
|
||||
@@ -202,4 +203,15 @@ describe('Tabs', () => {
|
||||
expect(modalMock.mock.calls).toHaveLength(1);
|
||||
expect(deleteComponent.callCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should set new tab key if dashboardId was changed', () => {
|
||||
const wrapper = shallow(<Tabs {...props} />);
|
||||
expect(wrapper.state('activeKey')).toBe('TAB_ID');
|
||||
wrapper.setProps({
|
||||
...props,
|
||||
dashboardId: 2,
|
||||
component: dashboardLayoutWithTabs.present.TAB_ID,
|
||||
});
|
||||
expect(wrapper.state('activeKey')).toBe('ROW_ID');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,17 @@ 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' },
|
||||
});
|
||||
|
||||
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();
|
||||
@@ -40,13 +52,21 @@ test('renders with default props', () => {
|
||||
});
|
||||
|
||||
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 +74,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 +104,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 +117,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 +153,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 +167,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 +179,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,10 @@ export function CustomFrame(props: FrameComponentProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const localFromFlaskBabel =
|
||||
useSelector((state: ExplorePageState) => state.common.locale) || 'en';
|
||||
const currentLocale = locales[LOCALE_MAPPING[localFromFlaskBabel]].DatePicker;
|
||||
|
||||
return (
|
||||
<div data-test="custom-frame">
|
||||
<div className="section-title">{t('Configure custom time range')}</div>
|
||||
@@ -132,6 +141,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
||||
onChange('sinceDatetime', datetime.format(MOMENT_FORMAT))
|
||||
}
|
||||
allowClear={false}
|
||||
locale={currentLocale}
|
||||
/>
|
||||
</Row>
|
||||
)}
|
||||
@@ -184,6 +194,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
||||
onChange('untilDatetime', datetime.format(MOMENT_FORMAT))
|
||||
}
|
||||
allowClear={false}
|
||||
locale={currentLocale}
|
||||
/>
|
||||
</Row>
|
||||
)}
|
||||
@@ -241,6 +252,7 @@ export function CustomFrame(props: FrameComponentProps) {
|
||||
}
|
||||
allowClear={false}
|
||||
className="control-anchor-to-datetime"
|
||||
locale={currentLocale}
|
||||
/>
|
||||
</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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ensureIsArray,
|
||||
getChartBuildQueryRegistry,
|
||||
getChartMetadataRegistry,
|
||||
SupersetClient,
|
||||
} from '@superset-ui/core';
|
||||
import { availableDomains } from 'src/utils/hostNamesConfig';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
@@ -234,31 +235,6 @@ export const buildV1ChartDataPayload = ({
|
||||
export const getLegacyEndpointType = ({ resultType, resultFormat }) =>
|
||||
resultFormat === 'csv' ? resultFormat : resultType;
|
||||
|
||||
export function postForm(url, payload, target = '_blank') {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hiddenForm = document.createElement('form');
|
||||
hiddenForm.action = url;
|
||||
hiddenForm.method = 'POST';
|
||||
hiddenForm.target = target;
|
||||
const token = document.createElement('input');
|
||||
token.type = 'hidden';
|
||||
token.name = 'csrf_token';
|
||||
token.value = (document.getElementById('csrf_token') || {}).value;
|
||||
hiddenForm.appendChild(token);
|
||||
const data = document.createElement('input');
|
||||
data.type = 'hidden';
|
||||
data.name = 'form_data';
|
||||
data.value = safeStringify(payload);
|
||||
hiddenForm.appendChild(data);
|
||||
|
||||
document.body.appendChild(hiddenForm);
|
||||
hiddenForm.submit();
|
||||
document.body.removeChild(hiddenForm);
|
||||
}
|
||||
|
||||
export const exportChart = ({
|
||||
formData,
|
||||
resultFormat = 'json',
|
||||
@@ -286,7 +262,8 @@ export const exportChart = ({
|
||||
ownState,
|
||||
});
|
||||
}
|
||||
postForm(url, payload);
|
||||
|
||||
SupersetClient.postForm(url, { form_data: safeStringify(payload) });
|
||||
};
|
||||
|
||||
export const exploreChart = formData => {
|
||||
@@ -295,7 +272,7 @@ export const exploreChart = formData => {
|
||||
endpointType: 'base',
|
||||
allowDomainSharding: false,
|
||||
});
|
||||
postForm(url, formData);
|
||||
SupersetClient.postForm(url, { form_data: safeStringify(formData) });
|
||||
};
|
||||
|
||||
export const useDebouncedEffect = (effect, delay, deps) => {
|
||||
|
||||
@@ -141,8 +141,8 @@ export default function exploreReducer(state = {}, action) {
|
||||
if (controlName === 'metrics' && old_metrics_data && new_column_config) {
|
||||
value.forEach((item, index) => {
|
||||
if (
|
||||
item.label !== old_metrics_data[index].label &&
|
||||
!!new_column_config[old_metrics_data[index].label]
|
||||
item?.label !== old_metrics_data[index]?.label &&
|
||||
!!new_column_config[old_metrics_data[index]?.label]
|
||||
) {
|
||||
new_column_config[item.label] =
|
||||
new_column_config[old_metrics_data[index].label];
|
||||
|
||||
@@ -21,11 +21,17 @@ import {
|
||||
QueryFormData,
|
||||
AnnotationData,
|
||||
AdhocMetric,
|
||||
JsonObject,
|
||||
} from '@superset-ui/core';
|
||||
import { ColumnMeta, Dataset } from '@superset-ui/chart-controls';
|
||||
import {
|
||||
ColumnMeta,
|
||||
Dataset,
|
||||
ControlStateMapping,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import { DatabaseObject } from 'src/views/CRUD/types';
|
||||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
||||
import { toastState } from 'src/SqlLab/types';
|
||||
import { Slice } from 'src/types/Chart';
|
||||
|
||||
export { Slice, Chart } from 'src/types/Chart';
|
||||
|
||||
@@ -90,3 +96,41 @@ export type ExploreRootState = {
|
||||
messageToasts: toastState[];
|
||||
common: {};
|
||||
};
|
||||
|
||||
export interface ExplorePageInitialData {
|
||||
dataset: Dataset;
|
||||
form_data: QueryFormData;
|
||||
slice: Slice | null;
|
||||
}
|
||||
|
||||
export interface ExploreResponsePayload {
|
||||
result: ExplorePageInitialData & { message: string };
|
||||
}
|
||||
|
||||
export interface ExplorePageState {
|
||||
user: UserWithPermissionsAndRoles;
|
||||
common: {
|
||||
flash_messages: string[];
|
||||
conf: JsonObject;
|
||||
locale: string;
|
||||
};
|
||||
charts: { [key: number]: ChartState };
|
||||
datasources: { [key: string]: Dataset };
|
||||
explore: {
|
||||
can_add: boolean;
|
||||
can_download: boolean;
|
||||
can_overwrite: boolean;
|
||||
isDatasourceMetaLoading: boolean;
|
||||
isStarred: boolean;
|
||||
triggerRender: boolean;
|
||||
// duplicate datasource in exploreState - it's needed by getControlsState
|
||||
datasource: Dataset;
|
||||
controls: ControlStateMapping;
|
||||
form_data: QueryFormData;
|
||||
slice: Slice;
|
||||
controlsTransferred: string[];
|
||||
standalone: boolean;
|
||||
force: boolean;
|
||||
};
|
||||
sliceEntities?: JsonObject; // propagated from Dashboard view
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) {
|
||||
}, [JSON.stringify(defaultValue), multiSelect]);
|
||||
|
||||
const groupbys = ensureIsArray(formData.groupby).map(getColumnLabel);
|
||||
const groupby = groupbys[0].length ? groupbys[0] : null;
|
||||
const groupby = groupbys[0]?.length ? groupbys[0] : null;
|
||||
|
||||
const withData = groupby
|
||||
? data.filter(row => groupby.includes(row.column_name as string))
|
||||
|
||||
37
superset-frontend/src/types/dom-to-image-more.d.ts
vendored
Normal file
37
superset-frontend/src/types/dom-to-image-more.d.ts
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
declare module 'dom-to-image-more' {
|
||||
export interface Options {
|
||||
filter?: ((node: Node) => boolean) | undefined;
|
||||
bgcolor?: string | undefined;
|
||||
width?: number | undefined;
|
||||
height?: number | undefined;
|
||||
style?: {} | undefined;
|
||||
quality?: number | undefined;
|
||||
imagePlaceholder?: string | undefined;
|
||||
cacheBust?: boolean | undefined;
|
||||
}
|
||||
|
||||
class DomToImageMore {
|
||||
static toJpeg(node: Node, options?: Options): Promise<string>;
|
||||
}
|
||||
|
||||
export default DomToImageMore;
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export function prepareCopyToClipboardTabularData(data, columns) {
|
||||
// JavaScript does not maintain the order of a mixed set of keys (i.e integers and strings)
|
||||
// the below function orders the keys based on the column names.
|
||||
const key = columns[j].name || columns[j];
|
||||
if (data[i][key]) {
|
||||
if (key in data[i]) {
|
||||
row[j] = data[i][key];
|
||||
} else {
|
||||
row[j] = data[i][parseFloat(key)];
|
||||
|
||||
@@ -59,6 +59,16 @@ describe('utils/common', () => {
|
||||
'lorem\tipsum\t\ndolor\tsit\tamet\n',
|
||||
);
|
||||
});
|
||||
it('includes 0 values', () => {
|
||||
const array = [
|
||||
{ column1: 0, column2: 0 },
|
||||
{ column1: 1, column2: -1, 0: 0 },
|
||||
];
|
||||
const column = ['column1', 'column2', '0'];
|
||||
expect(prepareCopyToClipboardTabularData(array, column)).toEqual(
|
||||
'0\t0\t\n1\t-1\t0\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('applyFormattingToTabularData', () => {
|
||||
it('does not mutate empty array', () => {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { SyntheticEvent } from 'react';
|
||||
import domToImage from 'dom-to-image';
|
||||
import domToImage from 'dom-to-image-more';
|
||||
import kebabCase from 'lodash/kebabCase';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { addWarningToast } from 'src/components/MessageToasts/actions';
|
||||
@@ -43,7 +43,6 @@ const generateFileStem = (description: string, date = new Date()) =>
|
||||
* @param selector css selector of the parent element which should be turned into image
|
||||
* @param description name or a short description of what is being printed.
|
||||
* Value will be normalized, and a date as well as a file extension will be added.
|
||||
* @param domToImageOptions dom-to-image Options object.
|
||||
* @param isExactSelector if false, searches for the closest ancestor that matches selector.
|
||||
* @returns event handler
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,7 @@ import setupApp from 'src/setup/setupApp';
|
||||
import { routes, isFrontendRoute } from 'src/views/routes';
|
||||
import { Logger } from 'src/logger/LogUtils';
|
||||
import { RootContextProviders } from './RootContextProviders';
|
||||
import { ScrollToTop } from './ScrollToTop';
|
||||
|
||||
setupApp();
|
||||
|
||||
@@ -60,6 +61,7 @@ const LocationPathnameLogger = () => {
|
||||
|
||||
const App = () => (
|
||||
<Router>
|
||||
<ScrollToTop />
|
||||
<LocationPathnameLogger />
|
||||
<RootContextProviders>
|
||||
<GlobalStyles />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user