Compare commits

...
Author SHA1 Message Date
amitmiran137 387d93302f chore: update CHANGELOG.md 2021-05-25 12:45:59 +03:00
Beto Dealmeidaandamitmiran137 47c27ce880 fix: select country in examples chart (#14561)
(cherry picked from commit 09050aee62)
2021-05-24 17:19:05 +03:00
Ville Brofeldt bc1e208c1d update changelog, updating and version number 2021-05-14 14:22:31 +03:00
Ville Brofeldtandamitmiran137 09e871eb63 feat(native-filters): add sort metric to select (#14590)
(cherry picked from commit f1c32b9576)
2021-05-13 09:48:43 +03:00
Phillip Kelley-Dotsonandamitmiran137 69e24bb029 fix: dashboard side actions (#14587)
* fix dashboard side actions

* lint being lint

(cherry picked from commit bfbf767663)
2021-05-13 09:48:43 +03:00
Ville Brofeldtandamitmiran137 f2236fcb8f fix(chart-data): handle url_params in csv export and native filters (#14526)
(cherry picked from commit 66a4c94a1e)
2021-05-07 21:36:59 +03:00
Ville Brofeldtandamitmiran137 d0a6706527 feat(dremio): implement convert_dttm method (#14519)
(cherry picked from commit d1d98d81b0)
2021-05-07 21:36:59 +03:00
simcha90andamitmiran137 d0b683cb63 feat(native-filters): Auto apply changes in FiltersConfigModal (#14461)
* fix:fix get permission function

* feat: auto apply changes after save filters config modal

* fix: repopulate default values

* test: fix tests

* test: fix tests

* fix: fix CR notes

* fix: fix CR notes

(cherry picked from commit e8e838e279)
2021-05-07 21:36:59 +03:00
David Aaron Suddjianandamitmiran137 c2d24782d9 fix: parameterize titles correctly (#14509)
(cherry picked from commit 52dbe311c7)
2021-05-07 21:36:58 +03:00
cccs-rcandamitmiran137 099514d3c0 fix: SQL Statement on QUERY_LOGGER prints none to log (#14358)
(cherry picked from commit f55882e756)
2021-05-07 21:36:58 +03:00
Ville Brofeldtandamitmiran137 f8491dfc0a fix(annotations): pass force param to annotation request (#14483)
* fix(annotations): pass force param to annotation request

* use strtobool

* add util for parsing bools

(cherry picked from commit 93c7f5bb44)
2021-05-07 21:36:58 +03:00
Amit Miranandamitmiran137 963e79f423 chore: bump table plugin to 0.17.42 (#14460)
(cherry picked from commit 158ac302d8)
2021-05-04 22:55:30 +03:00
simcha90andamitmiran137 93322aa424 perf(native-filters): Load native filters after charts (#14443)
* fix:fix get permission function

* refactor: filter default value

* refactor: update default value loading

* refactor: apply defaultValues

* lint: fix lint

* lint: fix lint

* test: fix test

* refactor: use extraFormData for reload charts

* feat: load native filters after after charts

* feat: load filters after charts

* fix: revert changes

* test: fix timers

* test: fix tests

(cherry picked from commit 582900c170)
2021-05-04 22:55:30 +03:00
Jesse Yangandamitmiran137 19ca78fd94 fix(viz): bump superset-ui to fix bugs in table and graph chart (#14438)
(cherry picked from commit a75e4af99b)
2021-05-04 14:37:42 +03:00
Ville Brofeldtandamitmiran137 6316a35fed add migration (#14446)
(cherry picked from commit 2f9efb2e23)
2021-05-04 14:37:42 +03:00
simcha90andamitmiran137 c0cb5a8c26 perf(native-filters): avoid unnecessary reloading of charts (#14408)
* fix:fix get permission function

* refactor: filter default value

* refactor: update default value loading

* refactor: apply defaultValues

* lint: fix lint

* lint: fix lint

* test: fix test

* refactor: use extraFormData for reload charts

* test: fix tests

* test: fix tests

* test: fix tests

(cherry picked from commit bbb1f2d757)
2021-05-04 14:37:41 +03:00
simcha90andamitmiran137 eb8b863a86 refactor(native-filters): allow cascading only for filter_select (#14441)
* fix:fix get permission function

* refactor: allow cascading only for filter_select

(cherry picked from commit abbf4bf05a)
2021-05-04 14:37:41 +03:00
simcha90andamitmiran137 a9b83d3871 test(maximize-chart): Add tests to maximize chart action (#14371)
* fixed FullSize charts broken #13600

* Update ChartHolder.jsx

* fix:fix get permission function

* test: adding tests to full screen

* lint: fix lint

* fix: fix CR notes

* fix: fix CR notes

* fix: fix CR notes

Co-authored-by: toop <mytoop@163.com>
(cherry picked from commit 1f8de1d06c)
2021-05-04 14:37:41 +03:00
AAfghahiandamitmiran137 b5f988f5e9 feat: Logic added to limiting factor column in Query model (#13521)
* Sqllab limit

* Add migration script

* Set default values

* initial push

* revisions

* Update superset/views/core.py

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

* moving migration to separate PR

* with migration

* revisions

* Fix apply_limit_to_sql

* all but tests

* added unit tests

* revisions

* Update superset/sql_lab.py

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

* Update superset/sql_parse.py

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

* fixed black issue

* Update superset/views/core.py

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

* updated logic

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
(cherry picked from commit e507508b48)
2021-05-04 14:37:40 +03:00
75 changed files with 2559 additions and 3681 deletions
+510
View File
@@ -18,6 +18,516 @@ under the License.
-->
## Change Log
### 1.2 (Tue May 11 09:05:39 2021 -0700)
**Database Migrations**
- [#14446](https://github.com/apache/superset/pull/14446) fix(native-filters): add migration to update metadata (@villebro)
- [#14234](https://github.com/apache/superset/pull/14234) feat: Adding limiting_factor column to Query model (@AAfghahi)
- [#14109](https://github.com/apache/superset/pull/14109) feat: Adding encrypted field factory (@craig-rueda)
- [#13828](https://github.com/apache/superset/pull/13828) feat(alert/report): chart as csv format attachment for email and slack (@lilykuang)
- [#13983](https://github.com/apache/superset/pull/13983) refactor(native-filters): update dataMask and ExtraFormData schema (@simcha90)
- [#14063](https://github.com/apache/superset/pull/14063) fix(country-map): update geojson and control layout (@ktmud)
- [#13980](https://github.com/apache/superset/pull/13980) fix: Issue 13956 (@john-bodley)
- [#13752](https://github.com/apache/superset/pull/13752) feat(alerts & reports): Easier to read execution logs (@jfrag1)
- [#13769](https://github.com/apache/superset/pull/13769) fix(dashboard): rename native filter configuration property (@villebro)
- [#13603](https://github.com/apache/superset/pull/13603) fix: default when adding tab_state.hide_left_bar (@betodealmeida)
- [#13493](https://github.com/apache/superset/pull/13493) fix: merge 67da9ef1ef9c and 1412ec1e5a7b migrations (@hughhhh)
- [#13288](https://github.com/apache/superset/pull/13288) fix(sqllab): per-tab hide left bar (@betodealmeida)
**Features**
- [#14590](https://github.com/apache/superset/pull/14590) feat(native-filters): add sort metric to select (@villebro)
- [#14519](https://github.com/apache/superset/pull/14519) feat(dremio): implement convert_dttm method (@villebro)
- [#14461](https://github.com/apache/superset/pull/14461) feat(native-filters): Auto apply changes in FiltersConfigModal (@simcha90)
- [#13521](https://github.com/apache/superset/pull/13521) feat: Logic added to limiting factor column in Query model (@AAfghahi)
- [#14357](https://github.com/apache/superset/pull/14357) feat: Add etag caching to dashboard APIs (@etr2460)
- [#14318](https://github.com/apache/superset/pull/14318) feat: Dynamic imports for the Icons component (@geido)
- [#14398](https://github.com/apache/superset/pull/14398) feat: Support env vars configuration for WebSocket server (@benjreinhart)
- [#14261](https://github.com/apache/superset/pull/14261) feat(native-filters): Show/Hide filter bar by metdata ff (@simcha90)
- [#14256](https://github.com/apache/superset/pull/14256) feat: Setup `supersetText` configuration to overwrite errors (@hughhhh)
- [#14346](https://github.com/apache/superset/pull/14346) feat(native-filters): add optional sort metric to select filter (@villebro)
- [#14375](https://github.com/apache/superset/pull/14375) feat(native-filters): add refresh button to default value picker (@villebro)
- [#14374](https://github.com/apache/superset/pull/14374) feat(native-filters): Use datasets in dashboard as default options for native filters (@kgabryje)
- [#14313](https://github.com/apache/superset/pull/14313) feat(native-filters): Implement adhoc filters and time picker in Range and Select native filters (@kgabryje)
- [#14363](https://github.com/apache/superset/pull/14363) feat: update default cron settings for new alerts and reports (@samtfm)
- [#14235](https://github.com/apache/superset/pull/14235) feat(alert/report): update content format radio buttons (@lilykuang)
- [#14340](https://github.com/apache/superset/pull/14340) feat(filter-box): sort by metric on backend (@villebro)
- [#14314](https://github.com/apache/superset/pull/14314) feat(native-filters): add option to create value in select filter (@villebro)
- [#14208](https://github.com/apache/superset/pull/14208) feat: add endpoint to fetch available DBs (@betodealmeida)
- [#14312](https://github.com/apache/superset/pull/14312) feat(native-filters): add tooltip to control values (@villebro)
- [#14267](https://github.com/apache/superset/pull/14267) feat: add alerts & reports to docker compose (@dpgaspar)
- [#14250](https://github.com/apache/superset/pull/14250) feat: catch errors on do_ping (@betodealmeida)
- [#14092](https://github.com/apache/superset/pull/14092) feat: restyle database modal (@eschutho)
- [#14217](https://github.com/apache/superset/pull/14217) feat(native-filters): select group by support (@amitmiran137)
- [#14204](https://github.com/apache/superset/pull/14204) feat: improve engine spec discoverability (@betodealmeida)
- [#14122](https://github.com/apache/superset/pull/14122) feat: TrinoEngineSpec.adjust_database_uri (@dungdm93)
- [#14172](https://github.com/apache/superset/pull/14172) feat: error messages for Presto connections (@betodealmeida)
- [#14171](https://github.com/apache/superset/pull/14171) feat: error messages when connecting to mssql (@AAfghahi)
- [#14163](https://github.com/apache/superset/pull/14163) feat: publish superset helm chart (@jawabuu)
- [#14197](https://github.com/apache/superset/pull/14197) feat(viz): add mixed timeseries and radar chart (@villebro)
- [#14187](https://github.com/apache/superset/pull/14187) feat(explore): Enable the new pivot table (@kgabryje)
- [#13561](https://github.com/apache/superset/pull/13561) feat: script to benchmark DB migrations (@betodealmeida)
- [#14110](https://github.com/apache/superset/pull/14110) feat: Add health endpoint to WebSocket server (@benjreinhart)
- [#14146](https://github.com/apache/superset/pull/14146) feat: invalid DB name error messages (MySQL/Postgres/Redshift) (@AAfghahi)
- [#14091](https://github.com/apache/superset/pull/14091) feat: FE: Import for Queries II (@lyndsiWilliams)
- [#14093](https://github.com/apache/superset/pull/14093) feat: error messages when connecting to MSSQL (@betodealmeida)
- [#14072](https://github.com/apache/superset/pull/14072) feat: add Echarts gauge chart (@krsnik93)
- [#14111](https://github.com/apache/superset/pull/14111) feat: invalid hostname and password error messages (Redshift) (@AAfghahi)
- [#14089](https://github.com/apache/superset/pull/14089) feat: invalid hostname and password error messages (MySQL) (@AAfghahi)
- [#13992](https://github.com/apache/superset/pull/13992) feat(dashboard_rbac): provide data access based on dashboard access (@amitmiran137)
- [#14076](https://github.com/apache/superset/pull/14076) feat(can_share): can share chart and dashboard (@amitmiran137)
- [#14038](https://github.com/apache/superset/pull/14038) feat: invalid password error message (Postgres) (@betodealmeida)
- [#13740](https://github.com/apache/superset/pull/13740) feat: Implement Celery SoftTimeLimit handling (@lilykuang)
- [#13862](https://github.com/apache/superset/pull/13862) feat: only send alert error emails to owners of the alert (@samtfm)
- [#13827](https://github.com/apache/superset/pull/13827) feat: add descriptions to report emails (@samtfm)
- [#13726](https://github.com/apache/superset/pull/13726) feat(native-filters): Add default first value to select filter (@simcha90)
- [#14007](https://github.com/apache/superset/pull/14007) feat: Make async query JWT cookie domain configurable (@benjreinhart)
- [#14040](https://github.com/apache/superset/pull/14040) feat: handle chart/data API errors (@etr2460)
- [#14030](https://github.com/apache/superset/pull/14030) feat(helm): add pod and deployment annotations to template (@rasta-rocket)
- [#13997](https://github.com/apache/superset/pull/13997) feat: add extract_errors to Postgres (@betodealmeida)
- [#13893](https://github.com/apache/superset/pull/13893) feat: create backend routes and API for importing saved queries (@AAfghahi)
- [#11498](https://github.com/apache/superset/pull/11498) feat(SIP-39): Websocket sidecar app (@robdiciuccio)
- [#14008](https://github.com/apache/superset/pull/14008) feat: Support feature flag overrides in ephemeral test envs (@robdiciuccio)
- [#13981](https://github.com/apache/superset/pull/13981) feat: FE: Export for Queries II (@lyndsiWilliams)
- [#13960](https://github.com/apache/superset/pull/13960) feat: initial work to make v1 API compatible with SIP-40 and SIP-41 (@betodealmeida)
- [#13758](https://github.com/apache/superset/pull/13758) feat(explore): adhoc column formatting for Table chart (@ktmud)
- [#13894](https://github.com/apache/superset/pull/13894) feat(alert/report): add ALERTS_ATTACH_REPORTS feature flags + feature (@lilykuang)
- [#13871](https://github.com/apache/superset/pull/13871) feat: create table with long name (@betodealmeida)
- [#13610](https://github.com/apache/superset/pull/13610) feat: added support to configure the default explorer viz (@cccs-jc)
- [#13772](https://github.com/apache/superset/pull/13772) feat(rls): enable row level security by default (@villebro)
- [#13728](https://github.com/apache/superset/pull/13728) feat: Helm Chart: add extraSecrets config to allow specifying extra secret files (@andreas-eberle)
- [#13720](https://github.com/apache/superset/pull/13720) feat: sort time grain configs (@eschutho)
- [#13683](https://github.com/apache/superset/pull/13683) feat(homescreen and cards): Toggle thumbnails off or on and feature flag (@pkdotson)
- [#13743](https://github.com/apache/superset/pull/13743) feat: use cross-env when running dev-server (@cemremengu)
- [#13696](https://github.com/apache/superset/pull/13696) feat: Refactor asyncEvent middleware and add websocket support (@robdiciuccio)
- [#12862](https://github.com/apache/superset/pull/12862) feat: Make initial user configurable in helm chart (@andreas-eberle)
- [#13662](https://github.com/apache/superset/pull/13662) feat: add 'extra' column to saved_queries GET (@mistercrunch)
- [#13687](https://github.com/apache/superset/pull/13687) feat(cross-filter): Cross filter badge (@simcha90)
- [#13678](https://github.com/apache/superset/pull/13678) feat(helm): Add optional apt-get install to superset_bootstrap.sh (@danielewood)
- [#13622](https://github.com/apache/superset/pull/13622) feat(native-filters): add temporal support to select filter (@villebro)
- [#13682](https://github.com/apache/superset/pull/13682) feat: add Databricks DB engine spec (@betodealmeida)
- [#13606](https://github.com/apache/superset/pull/13606) feat: add warning metadata to tables and metrics (@etr2460)
- [#13601](https://github.com/apache/superset/pull/13601) feat: better error message when adding DBs (@betodealmeida)
- [#13625](https://github.com/apache/superset/pull/13625) feat(cross-filters): Add scoping for cross filters (@simcha90)
- [#13634](https://github.com/apache/superset/pull/13634) feat(reports): SLACK_API_TOKEN as callable or str (@nytai)
- [#13584](https://github.com/apache/superset/pull/13584) feat: add ErrorBoundary to chart controls (@ktmud)
- [#13576](https://github.com/apache/superset/pull/13576) feat(filter-set): Filter set edge cases (@simcha90)
- [#13598](https://github.com/apache/superset/pull/13598) feat(explore): Drag and drop UX improvements (@kgabryje)
- [#13586](https://github.com/apache/superset/pull/13586) feat: add more big data for testing (@betodealmeida)
- [#13487](https://github.com/apache/superset/pull/13487) feat: create hooks to redux component (@AAfghahi)
- [#13294](https://github.com/apache/superset/pull/13294) feat(explore): Postgres datatype conversion (@nikolagigic)
- [#13575](https://github.com/apache/superset/pull/13575) feat: Implement drag and drop for metrics (@kgabryje)
- [#13523](https://github.com/apache/superset/pull/13523) feat(dashboard): dashboard/id/datasets endpoint (@suddjian)
- [#13448](https://github.com/apache/superset/pull/13448) feat: add option to load big/wide tables (@betodealmeida)
- [#13569](https://github.com/apache/superset/pull/13569) feat(native-filters): add sort option to select filter (@villebro)
- [#13152](https://github.com/apache/superset/pull/13152) feat: add connector for CrateDB (@srinify)
- [#13545](https://github.com/apache/superset/pull/13545) feat(filter-set): Update existing filter set (@simcha90)
- [#13529](https://github.com/apache/superset/pull/13529) feat(filter-set): Filter set history (@simcha90)
- [#13484](https://github.com/apache/superset/pull/13484) feat(native-filters): add timegrain and column filter (@villebro)
- [#13468](https://github.com/apache/superset/pull/13468) feat: add event_logger to test_connection and create_database commands (@hughhhh)
- [#13515](https://github.com/apache/superset/pull/13515) feat(filter-set): adding new filters (@simcha90)
- [#13507](https://github.com/apache/superset/pull/13507) feat(native-filters): Filter set tabs (@simcha90)
- [#13340](https://github.com/apache/superset/pull/13340) feat: Implement drag and drop columns for filters (@kgabryje)
- [#13506](https://github.com/apache/superset/pull/13506) feat(native-filters): Update filter bar buttons (@simcha90)
- [#13441](https://github.com/apache/superset/pull/13441) feat: refactor on DBEventLogger to allow for context management (@hughhhh)
- [#13144](https://github.com/apache/superset/pull/13144) feat: Add error pages (@samtfm)
- [#13190](https://github.com/apache/superset/pull/13190) feat: Added Rows Returned (@AAfghahi)
- [#13145](https://github.com/apache/superset/pull/13145) feat(dashboard_rbac): manage roles for dashboard (@simcha90)
- [#13377](https://github.com/apache/superset/pull/13377) feat(viz): implement time picker on filter box (@zhaoyongjie)
- [#13364](https://github.com/apache/superset/pull/13364) feat: add unit test for LimitMethod.FETCH_MANY (@betodealmeida)
- [#13210](https://github.com/apache/superset/pull/13210) feat(explore): ColumnSelectControl with drag-and-drop (@zhaoyongjie)
- [#13135](https://github.com/apache/superset/pull/13135) feat(reports): send notification on error with grace (@dpgaspar)
**Fixes**
- [#14561](https://github.com/apache/superset/pull/14561) fix: select country in examples chart (@betodealmeida)
- [#14587](https://github.com/apache/superset/pull/14587) fix: dashboard side actions (@pkdotson)
- [#14526](https://github.com/apache/superset/pull/14526) fix(chart-data): handle url_params in csv export and native filters (@villebro)
- [#14509](https://github.com/apache/superset/pull/14509) fix: parameterize titles correctly (@suddjian)
- [#14358](https://github.com/apache/superset/pull/14358) fix: SQL Statement on QUERY_LOGGER prints none to log (@cccs-rc)
- [#14483](https://github.com/apache/superset/pull/14483) fix(annotations): pass force param to annotation request (@villebro)
- [#14438](https://github.com/apache/superset/pull/14438) fix(viz): bump superset-ui to fix bugs in table and graph chart (@ktmud)
- [#14426](https://github.com/apache/superset/pull/14426) fix(native-filters): fix filter scope error (@villebro)
- [#14372](https://github.com/apache/superset/pull/14372) fix: SQLLab role permissions (@dpgaspar)
- [#14409](https://github.com/apache/superset/pull/14409) fix(native-filters): always show filters without dataset (@villebro)
- [#14369](https://github.com/apache/superset/pull/14369) fix: error getting partitionQuery from table.partition (@eriendeng)
- [#14401](https://github.com/apache/superset/pull/14401) fix(native-filters): default value not populated on second opening (@villebro)
- [#14291](https://github.com/apache/superset/pull/14291) fix: Fix unintended cache misses with async queries (@benjreinhart)
- [#14390](https://github.com/apache/superset/pull/14390) fix(sqllab): fix error due to anonymous user (@villebro)
- [#14385](https://github.com/apache/superset/pull/14385) fix(native-filters): force update control value on change (@villebro)
- [#14387](https://github.com/apache/superset/pull/14387) fix(native-filters): fix lint (@simcha90)
- [#14362](https://github.com/apache/superset/pull/14362) fix(emotion): add separate namespace for menu app (@nytai)
- [#14370](https://github.com/apache/superset/pull/14370) fix(native-filters): Update filter saving (@simcha90)
- [#14317](https://github.com/apache/superset/pull/14317) fix: Cleanup serialization and hashing code (@benjreinhart)
- [#14338](https://github.com/apache/superset/pull/14338) fix: pybabel extract fails (@bungoume)
- [#14347](https://github.com/apache/superset/pull/14347) fix: flaky test for alerts and reports (@dpgaspar)
- [#14353](https://github.com/apache/superset/pull/14353) fix(native-filters): allowClear only when required not checked (@villebro)
- [#14287](https://github.com/apache/superset/pull/14287) fix: Make g.user attribute access safe for public users (@robdiciuccio)
- [#14334](https://github.com/apache/superset/pull/14334) fix(native-filters): Fix indicators (@simcha90)
- [#14332](https://github.com/apache/superset/pull/14332) fix(native-filters): Fix first loading of charts (@simcha90)
- [#14240](https://github.com/apache/superset/pull/14240) fix(hive): Use parquet rather than textfile when uploading CSV files to Hive (@john-bodley)
- [#14306](https://github.com/apache/superset/pull/14306) fix(dashboard): Add caching for dashboard datasets (@suddjian)
- [#14255](https://github.com/apache/superset/pull/14255) fix(hive): Update CSV to Hive upload prefix (@john-bodley)
- [#14252](https://github.com/apache/superset/pull/14252) fix: SHOW FUNCTIONS for Databricks (@betodealmeida)
- [#14319](https://github.com/apache/superset/pull/14319) fix: unbreak CI (@etr2460)
- [#13921](https://github.com/apache/superset/pull/13921) fix: new import/export CLI (@betodealmeida)
- [#14084](https://github.com/apache/superset/pull/14084) fix: custom TimeRange can not input value (@zhaoyongjie)
- [#14297](https://github.com/apache/superset/pull/14297) fix(sqla): timeseries limit not applied when using columns (@villebro)
- [#14295](https://github.com/apache/superset/pull/14295) fix(db2): change engine name and add legacy alias (@villebro)
- [#14207](https://github.com/apache/superset/pull/14207) fix(dashboard): draft dashboards should be viewable (@ktmud)
- [#14194](https://github.com/apache/superset/pull/14194) fix(explore): CUSTOM SQL tab should automatically update (@stephenLYZ)
- [#14258](https://github.com/apache/superset/pull/14258) fix: incorrect module reference(Tooltip) (@zhaoyongjie)
- [#14192](https://github.com/apache/superset/pull/14192) fix: Center each import icon and add a tooltip II (@lyndsiWilliams)
- [#14147](https://github.com/apache/superset/pull/14147) fix: Handle bad permission errors for bigquery test connections (@hughhhh)
- [#14244](https://github.com/apache/superset/pull/14244) fix(native-filters): merge_extra_form_data extras processing (@villebro)
- [#14243](https://github.com/apache/superset/pull/14243) fix(explore): Filter edit popover not opening in DnD mode (@kgabryje)
- [#14218](https://github.com/apache/superset/pull/14218) fix(native-filters): fix remove native filters (@simcha90)
- [#13830](https://github.com/apache/superset/pull/13830) fix: Use utils.json_iso_dttm_ser to dump jsons when async query execution (@cabo40)
- [#14220](https://github.com/apache/superset/pull/14220) fix(cross-filters): Fix missed metadata (@simcha90)
- [#14170](https://github.com/apache/superset/pull/14170) fix: Switching to local codecov script (@craig-rueda)
- [#14180](https://github.com/apache/superset/pull/14180) fix: Add extra requires for shillelagh (@etr2460)
- [#14191](https://github.com/apache/superset/pull/14191) fix(viz): time shift read-only error (@villebro)
- [#14129](https://github.com/apache/superset/pull/14129) fix(dashboard): filter set hydration not working (@villebro)
- [#14140](https://github.com/apache/superset/pull/14140) fix: QueryPane horizontal scroll (@AAfghahi)
- [#14148](https://github.com/apache/superset/pull/14148) fix(dashboard): incorrect perm for users with multiple roles (@ktmud)
- [#14113](https://github.com/apache/superset/pull/14113) fix: add missing __init__.py (@betodealmeida)
- [#14099](https://github.com/apache/superset/pull/14099) fix(SPA): get permission function (@simcha90)
- [#14086](https://github.com/apache/superset/pull/14086) fix: Fixed saved query export (@lyndsiWilliams)
- [#14057](https://github.com/apache/superset/pull/14057) fix: logs table - user_id is NULL (@hughhhh)
- [#14095](https://github.com/apache/superset/pull/14095) fix(sqla): labels_expected contains mutated label (@villebro)
- [#14074](https://github.com/apache/superset/pull/14074) fix: unable to apply logging format (@zhaoyongjie)
- [#13906](https://github.com/apache/superset/pull/13906) fix(listview): update listview feature flag (@pkdotson)
- [#14082](https://github.com/apache/superset/pull/14082) fix: flacky test in test_update_dataset_item_w_override_columns (@hughhhh)
- [#14046](https://github.com/apache/superset/pull/14046) fix(cross-filters): Make chart emitting cross-filter exclude itself from filtering (@kgabryje)
- [#14081](https://github.com/apache/superset/pull/14081) fix(cross-filters): fix bug when remove chart not removing it's related cross filter data (@simcha90)
- [#14036](https://github.com/apache/superset/pull/14036) fix: add exception to catch session not having JWT (@hughhhh)
- [#14031](https://github.com/apache/superset/pull/14031) fix: Use consistent chart value (@thomasv314)
- [#13974](https://github.com/apache/superset/pull/13974) fix: Use superset generic db to catch external_metadata queries (@hughhhh)
- [#14045](https://github.com/apache/superset/pull/14045) fix(viz): bump superset-ui 0.17.30 (@villebro)
- [#14039](https://github.com/apache/superset/pull/14039) fix: broken port-forward instructions (@caleb15)
- [#14009](https://github.com/apache/superset/pull/14009) fix: use dynamic time_grains for schema (@eschutho)
- [#13998](https://github.com/apache/superset/pull/13998) fix: execution log crashes for logs with no uuid (@jfrag1)
- [#13911](https://github.com/apache/superset/pull/13911) fix: working timeout with celery kill and logic fix (@dpgaspar)
- [#14014](https://github.com/apache/superset/pull/14014) fix(sqla): apply sqla type on calculated columns (@villebro)
- [#13969](https://github.com/apache/superset/pull/13969) fix: annotation modal dateRangePicker saved invalid value (@zhaoyongjie)
- [#13987](https://github.com/apache/superset/pull/13987) fix: Pie chart not displayed in viz type selection (@zhaoyongjie)
- [#13979](https://github.com/apache/superset/pull/13979) fix: import dataset/dashboard empty keys (@betodealmeida)
- [#13801](https://github.com/apache/superset/pull/13801) fix(explore): strip semicolons in virtual table SQL (@pkdotson)
- [#13972](https://github.com/apache/superset/pull/13972) fix: Data table z index in sql Editor (@AAfghahi)
- [#13897](https://github.com/apache/superset/pull/13897) fix: updates load_examples to load-examples (@alexandersimoes)
- [#13955](https://github.com/apache/superset/pull/13955) fix: null exception from adhoc metric popover (@graceguo-supercat)
- [#13936](https://github.com/apache/superset/pull/13936) fix: use the right variable for Celery results in Redis (@octaviancorlade)
- [#13858](https://github.com/apache/superset/pull/13858) fix: Floating Menu in SQL Left Bar (@AAfghahi)
- [#13946](https://github.com/apache/superset/pull/13946) fix: SQL -> Explore Overwrite flow (@hughhhh)
- [#13737](https://github.com/apache/superset/pull/13737) fix: log exceptions for thumbnail generation as warnings (@samtfm)
- [#13945](https://github.com/apache/superset/pull/13945) fix(colors): fix color schemes (@simcha90)
- [#13927](https://github.com/apache/superset/pull/13927) fix: trigger collapse with onchange (@eschutho)
- [#13926](https://github.com/apache/superset/pull/13926) fix: enable installing docker/requirements-local.txt in docker-compos… (@nytai)
- [#13822](https://github.com/apache/superset/pull/13822) fix: adjusted tab height (@AAfghahi)
- [#13917](https://github.com/apache/superset/pull/13917) fix: do not run containers as root by default in Helm chart (@megakoresh)
- [#13739](https://github.com/apache/superset/pull/13739) fix(sqla-query): order by aggregations in Presto and Hive (@ktmud)
- [#13852](https://github.com/apache/superset/pull/13852) fix(dependency): Pin pystan after breaking prophet install (@EBoisseauSierra)
- [#13905](https://github.com/apache/superset/pull/13905) fix: consistent left margin for dashboard layout. (@rusackas)
- [#13901](https://github.com/apache/superset/pull/13901) fix: Revert "fix: select table overlay" (@etr2460)
- [#13878](https://github.com/apache/superset/pull/13878) fix(#13378): Ensure g.user is set for impersonation (@benjreinhart)
- [#13859](https://github.com/apache/superset/pull/13859) fix: Check for permissions in FilterBox (@etr2460)
- [#13868](https://github.com/apache/superset/pull/13868) fix: change status code for generic errors to 400 (@hughhhh)
- [#13800](https://github.com/apache/superset/pull/13800) fix: allow spaces in DB names (@betodealmeida)
- [#13749](https://github.com/apache/superset/pull/13749) fix: Disallows negative options remaining in SelectControl (@michael-s-molina)
- [#13837](https://github.com/apache/superset/pull/13837) fix(native-filters): add global async query support to native filters (@villebro)
- [#13826](https://github.com/apache/superset/pull/13826) fix(errors): Downgrade error on stop query to a warning (@willbarrett)
- [#13718](https://github.com/apache/superset/pull/13718) fix(alerts and reports): Unify timestamp format on execution log view (@jfrag1)
- [#13841](https://github.com/apache/superset/pull/13841) fix(sqllab): warning message when displayed rows limited (@zuzana-vej)
- [#13794](https://github.com/apache/superset/pull/13794) fix(native-filters): improve loading styles for filter component (@villebro)
- [#13825](https://github.com/apache/superset/pull/13825) fix(cypress): avoid referencing detached dom nodes (@suddjian)
- [#13694](https://github.com/apache/superset/pull/13694) fix: select table overlay (@AAfghahi)
- [#13780](https://github.com/apache/superset/pull/13780) fix: restart superset container in test script (@benjreinhart)
- [#13790](https://github.com/apache/superset/pull/13790) fix: bad copy-and-paste in API spec (@betodealmeida)
- [#13566](https://github.com/apache/superset/pull/13566) fix(explore): make horizontal scroll appears in data panel (@stephenLYZ)
- [#13756](https://github.com/apache/superset/pull/13756) fix(CI): use Presto docker image from starburst (@ktmud)
- [#13652](https://github.com/apache/superset/pull/13652) fix(datasets): log create exceptions as warning (@nytai)
- [#13704](https://github.com/apache/superset/pull/13704) fix(cross-filters): only apply filters if ff is set (@villebro)
- [#13695](https://github.com/apache/superset/pull/13695) fix: dashboard filter scope bug (@graceguo-supercat)
- [#13703](https://github.com/apache/superset/pull/13703) fix(cross-filters): expand relevant indicator sections (@villebro)
- [#13624](https://github.com/apache/superset/pull/13624) fix: data column in SQL lab left panel open by default (@AAfghahi)
- [#13688](https://github.com/apache/superset/pull/13688) fix(native-filters): fix removing native filter (@simcha90)
- [#13488](https://github.com/apache/superset/pull/13488) fix: allow option to generate new query (@eschutho)
- [#13691](https://github.com/apache/superset/pull/13691) fix(dashboard): missing parents in directPathToFilter (@villebro)
- [#13692](https://github.com/apache/superset/pull/13692) fix(cross-filters): add a control panel to range filter (@villebro)
- [#13679](https://github.com/apache/superset/pull/13679) fix: use FeatureFlags in @superset-ui/core (@ktmud)
- [#13681](https://github.com/apache/superset/pull/13681) fix: clean up incorrect usage of TypeScript type in PropTypes (@ktmud)
- [#13665](https://github.com/apache/superset/pull/13665) fix(cross-filter): fix scheme py (@simcha90)
- [#13621](https://github.com/apache/superset/pull/13621) fix(explore): Add int and bool regex pattern (@nikolagigic)
- [#13657](https://github.com/apache/superset/pull/13657) fix: 'Run in SQL Lab' redirect bug (@mistercrunch)
- [#13434](https://github.com/apache/superset/pull/13434) fix(query): order by adhoc metrics should trigger group by (@ktmud)
- [#13636](https://github.com/apache/superset/pull/13636) fix(sqllab): Table name and schema are encoded twice during fetching table metadata on SQL Lab page. (@didva)
- [#13655](https://github.com/apache/superset/pull/13655) fix: Slack dashboard has stale charts (@betodealmeida)
- [#13642](https://github.com/apache/superset/pull/13642) fix: bump FAB to 3.2.1, SQLAlchemy fix (@dpgaspar)
- [#13608](https://github.com/apache/superset/pull/13608) fix: Add SASL dependency module (@danielewood)
- [#13463](https://github.com/apache/superset/pull/13463) fix(alert/report): add notification method in alert report modal (@lilykuang)
- [#13585](https://github.com/apache/superset/pull/13585) fix: AntD select dropdown scroll issue (@lilykuang)
- [#13578](https://github.com/apache/superset/pull/13578) fix: better handle datasource exceptions (@dpgaspar)
- [#13607](https://github.com/apache/superset/pull/13607) fix: link update for report and old alerts deprecations (@betodealmeida)
- [#13626](https://github.com/apache/superset/pull/13626) fix(explore): Dynamic dttm in crate test (@nikolagigic)
- [#13537](https://github.com/apache/superset/pull/13537) fix(alert/report): switch message content between chart and dashboard (@lilykuang)
- [#13389](https://github.com/apache/superset/pull/13389) fix: delete dataset columns and metrics on the REST API (@dpgaspar)
- [#13613](https://github.com/apache/superset/pull/13613) fix(helm): Add missing extraConfigs template volume mapping and fix type error on template. (@danielewood)
- [#13563](https://github.com/apache/superset/pull/13563) fix: always pass a string as a value to ace editor (@eschutho)
- [#13602](https://github.com/apache/superset/pull/13602) fix: ParsedQuery subselect edge case (@etr2460)
- [#13570](https://github.com/apache/superset/pull/13570) fix: UI port in dockerfile set to same port in config (@iercan)
- [#13496](https://github.com/apache/superset/pull/13496) fix: `IS NULL` filter operator for numeric columns (@ktmud)
- [#13546](https://github.com/apache/superset/pull/13546) fix(native-filters): fix initial filter loading (@simcha90)
- [#13505](https://github.com/apache/superset/pull/13505) fix: Prevents KeyError in 13504 by making kwarg explicit (@spookymotion)
- [#13436](https://github.com/apache/superset/pull/13436) fix(alerts&reports): add celery soft timeout support (@dpgaspar)
- [#13473](https://github.com/apache/superset/pull/13473) fix(explore): make sure sort by metric is not duplicated (@ktmud)
- [#13352](https://github.com/apache/superset/pull/13352) fix(dashboard): Get dashboard by slug (@suddjian)
- [#13444](https://github.com/apache/superset/pull/13444) fix: API to allow importing old exports (JSON/YAML) (@betodealmeida)
- [#13464](https://github.com/apache/superset/pull/13464) fix: SHOW is not DML (@betodealmeida)
- [#13469](https://github.com/apache/superset/pull/13469) fix: Remove view in sql lab from druid datasources (@etr2460)
- [#13425](https://github.com/apache/superset/pull/13425) fix: Adding v0 import/export fields to dashboard metadata schema (@michellethomas)
- [#13424](https://github.com/apache/superset/pull/13424) fix: use semver for badge sort (@eschutho)
- [#12341](https://github.com/apache/superset/pull/12341) fix: annotation layer modal err handling (@riahk)
- [#13331](https://github.com/apache/superset/pull/13331) fix(query-object): extra time-range-endpoints (@john-bodley)
- [#13174](https://github.com/apache/superset/pull/13174) fix: update SQL Lab Buttons to better wording (@AAfghahi)
- [#13330](https://github.com/apache/superset/pull/13330) fix: date picker support date unit with singular and plural (@zhaoyongjie)
**Others**
- [#14460](https://github.com/apache/superset/pull/14460) chore: bump table plugin to 0.17.42 (@amitmiran137)
- [#14443](https://github.com/apache/superset/pull/14443) perf(native-filters): Load native filters after charts (@simcha90)
- [#14408](https://github.com/apache/superset/pull/14408) perf(native-filters): avoid unnecessary reloading of charts (@simcha90)
- [#14441](https://github.com/apache/superset/pull/14441) refactor(native-filters): allow cascading only for filter_select (@simcha90)
- [#14371](https://github.com/apache/superset/pull/14371) test(maximize-chart): Add tests to maximize chart action (@simcha90)
- [#14139](https://github.com/apache/superset/pull/14139) chore: Moves Card to the components folder (@michael-s-molina)
- [#14048](https://github.com/apache/superset/pull/14048) refactor: Boostrap to AntD - Tabs (@michael-s-molina)
- [#14166](https://github.com/apache/superset/pull/14166) chore: Moves the branding folder to the assets folder (@michael-s-molina)
- [#14394](https://github.com/apache/superset/pull/14394) docs: improved clickhouse connection details (@srinify)
- [#14321](https://github.com/apache/superset/pull/14321) docs: multiple small fixes around databases (@srinify)
- [#14366](https://github.com/apache/superset/pull/14366) refactor(db_engine_specs): Removing top-level import of app (@john-bodley)
- [#14219](https://github.com/apache/superset/pull/14219) docs: Update trino connection docs (@aakashnand)
- [#14203](https://github.com/apache/superset/pull/14203) chore: Bumping lodash version in Superset (@rusackas)
- [#14339](https://github.com/apache/superset/pull/14339) chore: Improve Japanese translation (@bungoume)
- [#14342](https://github.com/apache/superset/pull/14342) chore(docker-compose): use dev image for docker-compose-non-dev.yml (@nytai)
- [#14136](https://github.com/apache/superset/pull/14136) chore: Moves Tabs to the components folder (@michael-s-molina)
- [#14355](https://github.com/apache/superset/pull/14355) chore: bump 0.17.38 (@amitmiran137)
- [#14035](https://github.com/apache/superset/pull/14035) refactor(addslicecontainer): move slice container panel from bootstrap panel to ant-d (@pkdotson)
- [#14106](https://github.com/apache/superset/pull/14106) refactor: Bootstrap to AntD - Form - iteration 1 (@michael-s-molina)
- [#14354](https://github.com/apache/superset/pull/14354) chore: add some basic instrumentation to Node WebSocket server (@benjreinhart)
- [#14274](https://github.com/apache/superset/pull/14274) chore: Add Redis password option to helm chart (@robdiciuccio)
- [#14350](https://github.com/apache/superset/pull/14350) ci: set remote URL to https and bump sha (@dpgaspar)
- [#14331](https://github.com/apache/superset/pull/14331) chore(docs): remove .py from db migrate -m (@amitmiran137)
- [#14323](https://github.com/apache/superset/pull/14323) chore(spelling): fixing spelling: `clint` -> `client` (@ChrisCarini)
- [#14202](https://github.com/apache/superset/pull/14202) chore: Moves spec files to the src folder - iteration 3 (@michael-s-molina)
- [#14201](https://github.com/apache/superset/pull/14201) chore: Moves spec files to the src folder - iteration 2 (@michael-s-molina)
- [#14200](https://github.com/apache/superset/pull/14200) chore: Moves spec files to the src folder - iteration 1 (@michael-s-molina)
- [#14193](https://github.com/apache/superset/pull/14193) chore: Improves Icons storybook (@michael-s-molina)
- [#14304](https://github.com/apache/superset/pull/14304) docs: add publish step (@nytai)
- [#14309](https://github.com/apache/superset/pull/14309) refactor: reorganize dateFilterUtils.tx (@zhaoyongjie)
- [#14100](https://github.com/apache/superset/pull/14100) refactor: Boostrap to AntD - Row/Col (@michael-s-molina)
- [#13970](https://github.com/apache/superset/pull/13970) chore: starter make file (@hughhhh)
- [#14123](https://github.com/apache/superset/pull/14123) chore: Moves WarningIconWithTooltip to own folder (@michael-s-molina)
- [#14298](https://github.com/apache/superset/pull/14298) chore: SQLLab row limit warning using Alert component (@zuzana-vej)
- [#14196](https://github.com/apache/superset/pull/14196) chore: Code cleanup in DatabaseModal (@lyndsiWilliams)
- [#14257](https://github.com/apache/superset/pull/14257) chore: WebSocket server improvements (@benjreinhart)
- [#14253](https://github.com/apache/superset/pull/14253) chore: bump superset-ui (@etr2460)
- [#14126](https://github.com/apache/superset/pull/14126) chore: Moves Dropdown to the components folder (@michael-s-molina)
- [#14133](https://github.com/apache/superset/pull/14133) chore: Moves Radio to the components folder (@michael-s-molina)
- [#14137](https://github.com/apache/superset/pull/14137) chore: Moves Tooltip to the components folder (@michael-s-molina)
- [#14167](https://github.com/apache/superset/pull/14167) chore: Moves the staticPages folder to the assets folder (@michael-s-molina)
- [#14228](https://github.com/apache/superset/pull/14228) chore(prophet): bump prophet to 1.0.1 (@villebro)
- [#14245](https://github.com/apache/superset/pull/14245) test(native-filters): Filter config modal test (@simcha90)
- [#14098](https://github.com/apache/superset/pull/14098) test(native-filters): add integration tests for filter bar (@simcha90)
- [#14238](https://github.com/apache/superset/pull/14238) chore: Add Preset code owners for ephemeral test env workflows (@robdiciuccio)
- [#14128](https://github.com/apache/superset/pull/14128) chore: Moves InfoTooltip to the components folder (@michael-s-molina)
- [#14222](https://github.com/apache/superset/pull/14222) chore: bump package.json (@amitmiran137)
- [#14223](https://github.com/apache/superset/pull/14223) docs: add helm repo to kubernetes installation instructions (@jawabuu)
- [#14211](https://github.com/apache/superset/pull/14211) ci: use git submodules for Chart Actions (@jawabuu)
- [#14124](https://github.com/apache/superset/pull/14124) chore: Moves Collapse to the components folder (@michael-s-molina)
- [#14130](https://github.com/apache/superset/pull/14130) chore: Moves Modal to the components folder (@michael-s-molina)
- [#14178](https://github.com/apache/superset/pull/14178) chore: type some SQL Lab components (@etr2460)
- [#14164](https://github.com/apache/superset/pull/14164) chore: Configures Jest to ignore storybook files (@michael-s-molina)
- [#14135](https://github.com/apache/superset/pull/14135) chore: Moves Switch to the components folder (@michael-s-molina)
- [#14127](https://github.com/apache/superset/pull/14127) chore: Moves DropdownButton to the components folder (@michael-s-molina)
- [#14125](https://github.com/apache/superset/pull/14125) chore: Moves CronPicker to the components folder (@michael-s-molina)
- [#14013](https://github.com/apache/superset/pull/14013) chore(toggle thumbnails): use localstorage for toggle (@pkdotson)
- [#14169](https://github.com/apache/superset/pull/14169) chore: rename connection errors (@betodealmeida)
- [#14090](https://github.com/apache/superset/pull/14090) refactor(userInfo): userinfo panel to styled component (@pkdotson)
- [#14152](https://github.com/apache/superset/pull/14152) chore: update RELEASING readme (@lilykuang)
- [#13973](https://github.com/apache/superset/pull/13973) test: Add tests for Dashboard Header and HeaderActionsDropdown components (@geido)
- [#13393](https://github.com/apache/superset/pull/13393) build(deps): bump urijs from 1.19.4 to 1.19.6 in /superset-frontend (@dependabot[bot])
- [#13705](https://github.com/apache/superset/pull/13705) test: Adds tests for the FixedOrMetricControl component (@michael-s-molina)
- [#13996](https://github.com/apache/superset/pull/13996) refactor: Bootstrap to AntD - ListGroup (@michael-s-molina)
- [#12636](https://github.com/apache/superset/pull/12636) build(deps): bump socket.io from 2.3.0 to 2.4.1 in /docs (@dependabot[bot])
- [#13391](https://github.com/apache/superset/pull/13391) build(deps): bump three from 0.68.87 to 0.125.0 in /docs (@dependabot[bot])
- [#13874](https://github.com/apache/superset/pull/13874) build(deps): bump y18n from 4.0.0 to 4.0.1 in /docs (@dependabot[bot])
- [#13886](https://github.com/apache/superset/pull/13886) build(deps): bump y18n from 4.0.0 to 4.0.1 in /superset-frontend (@dependabot[bot])
- [#14088](https://github.com/apache/superset/pull/14088) chore: stop logging `SupersetExceptions` if status < 500 (@hughhhh)
- [#13919](https://github.com/apache/superset/pull/13919) test: Adds tests to the UndoRedoKeyListeners component (@michael-s-molina)
- [#13951](https://github.com/apache/superset/pull/13951) chore: Adds dataMask reducer to reducerIndex (@michael-s-molina)
- [#13916](https://github.com/apache/superset/pull/13916) test: Tests audit for the Dashboard FilterBar (@geido)
- [#13989](https://github.com/apache/superset/pull/13989) refactor: Bootstrap to AntD - Slider (@michael-s-molina)
- [#13306](https://github.com/apache/superset/pull/13306) chore(spa refactor): refactoring dashboard to use api's instead of bootstrapdata (@pkdotson)
- [#13961](https://github.com/apache/superset/pull/13961) docs: details on configuring Docker Compose setup (@alexkreidler)
- [#14027](https://github.com/apache/superset/pull/14027) docs: update outdated country map tools instructions (@ktmud)
- [#14028](https://github.com/apache/superset/pull/14028) test: FilterSets-utils (@yardz)
- [#14033](https://github.com/apache/superset/pull/14033) chore: well component to card component(Updating PR #12200) (@yardz)
- [#13975](https://github.com/apache/superset/pull/13975) test: ControlItems (@yardz)
- [#13941](https://github.com/apache/superset/pull/13941) test: Tabs (@yardz)
- [#13937](https://github.com/apache/superset/pull/13937) test: Tab (@yardz)
- [#13888](https://github.com/apache/superset/pull/13888) test: SliceHeader (@yardz)
- [#13860](https://github.com/apache/superset/pull/13860) test: ShareMenuItems (@yardz)
- [#13991](https://github.com/apache/superset/pull/13991) test: ColumnSelect (@yardz)
- [#14025](https://github.com/apache/superset/pull/14025) chore: update docs to allow user to properly load examples (@hughhhh)
- [#13458](https://github.com/apache/superset/pull/13458) chore: Moves Fade component into TableElement (@michael-s-molina)
- [#13933](https://github.com/apache/superset/pull/13933) test: Adds tests to the CssEditor component (@michael-s-molina)
- [#13502](https://github.com/apache/superset/pull/13502) test: Tests and Storybook entry for the IndeterminateCheckbox Component (@geido)
- [#13736](https://github.com/apache/superset/pull/13736) test: DataTablesPane (@yardz)
- [#13787](https://github.com/apache/superset/pull/13787) test: AdhocMetricEditPopover (@yardz)
- [#13501](https://github.com/apache/superset/pull/13501) test: Tests and Storybook entry for the TableView Component (@geido)
- [#13984](https://github.com/apache/superset/pull/13984) test: prevent flaky frontend unit test case (@zhaoyongjie)
- [#13719](https://github.com/apache/superset/pull/13719) test: exploreUtils (@yardz)
- [#13887](https://github.com/apache/superset/pull/13887) test: Adds tests to the filter scope components (@michael-s-molina)
- [#13931](https://github.com/apache/superset/pull/13931) test: PropertiesModal (Dashboard) (@yardz)
- [#13950](https://github.com/apache/superset/pull/13950) test: Add tests for the Dashboard FilterBar CascadeFilterControl component (@geido)
- [#13934](https://github.com/apache/superset/pull/13934) docs: replace deprecated EMAIL_REPORTS_WEBDRIVER with the new WEBDRIVER_TYPE (@octaviancorlade)
- [#13953](https://github.com/apache/superset/pull/13953) chore: bump @superset-ui/plugin-chart-table to 0.17.28 (@ktmud)
- [#13877](https://github.com/apache/superset/pull/13877) refactor: move CTAS/CVAS field II (@hughhhh)
- [#13895](https://github.com/apache/superset/pull/13895) test: SliceHeaderControls (@yardz)
- [#13553](https://github.com/apache/superset/pull/13553) chore: Dedicated dir and Storybook for the DatePicker Component (@geido)
- [#13818](https://github.com/apache/superset/pull/13818) test: PropertiesModal (@yardz)
- [#13875](https://github.com/apache/superset/pull/13875) test: CrossFilterScopingForm (@yardz)
- [#13873](https://github.com/apache/superset/pull/13873) test: FilterIndicator (@yardz)
- [#13872](https://github.com/apache/superset/pull/13872) test: DetailsPanel (@yardz)
- [#13745](https://github.com/apache/superset/pull/13745) test: Enhance tests and directory structure for FilterControl components (@geido)
- [#13712](https://github.com/apache/superset/pull/13712) test: Adds tests to the TimeSeriesColumnControl component (@michael-s-molina)
- [#13753](https://github.com/apache/superset/pull/13753) test: FilterBoxItemControl (@yardz)
- [#13656](https://github.com/apache/superset/pull/13656) test: CollectionControl (@yardz)
- [#13482](https://github.com/apache/superset/pull/13482) test: Adds storybook to Dropdown and DropdownButton components (@michael-s-molina)
- [#13876](https://github.com/apache/superset/pull/13876) test: CrossFilterScopingModal/utils (@yardz)
- [#13939](https://github.com/apache/superset/pull/13939) chore(dashboard): Datasource -> Dataset (@suddjian)
- [#13892](https://github.com/apache/superset/pull/13892) test: Adds tests to the PublishedStatus component (@michael-s-molina)
- [#13932](https://github.com/apache/superset/pull/13932) chore(dashboard_rbac): bump superset-ui 0.17.27 (@amitmiran137)
- [#13548](https://github.com/apache/superset/pull/13548) test: Fixes PropertiesModal_spec (@michael-s-molina)
- [#13650](https://github.com/apache/superset/pull/13650) test: Adds tests to dnd controls (@michael-s-molina)
- [#13748](https://github.com/apache/superset/pull/13748) test: Adds tests to the AnnotationLayer component (@michael-s-molina)
- [#13799](https://github.com/apache/superset/pull/13799) test: Refactor and enhance tests for the Explore DatasourcePanel Component (@geido)
- [#13778](https://github.com/apache/superset/pull/13778) test: Tests and dedicated dir for Explore TextControl component (@geido)
- [#13750](https://github.com/apache/superset/pull/13750) test: DisplayQueryButton (@yardz)
- [#13729](https://github.com/apache/superset/pull/13729) test: Adds tests to the OptionControls component (@michael-s-molina)
- [#13605](https://github.com/apache/superset/pull/13605) test: DatasourceControl (@yardz)
- [#13896](https://github.com/apache/superset/pull/13896) test: handleScroll (@yardz)
- [#13675](https://github.com/apache/superset/pull/13675) test: Adds tests to the CustomFrame component (@michael-s-molina)
- [#13664](https://github.com/apache/superset/pull/13664) test: Adds tests to the AdvancedFrame component (@michael-s-molina)
- [#13668](https://github.com/apache/superset/pull/13668) test: DataTableControl (@yardz)
- [#13477](https://github.com/apache/superset/pull/13477) chore: Removes Select duplication (@michael-s-molina)
- [#13813](https://github.com/apache/superset/pull/13813) docs: Documenting how to debug Flask app (@cccs-jc)
- [#13457](https://github.com/apache/superset/pull/13457) test: Adds tests and storybook to CertifiedIcon component (@michael-s-molina)
- [#13459](https://github.com/apache/superset/pull/13459) chore: Moves CheckboxIcons to Checkbox folder (@michael-s-molina)
- [#13462](https://github.com/apache/superset/pull/13462) chore: Removes Popover duplication (@michael-s-molina)
- [#13527](https://github.com/apache/superset/pull/13527) build(deps): bump elliptic from 6.5.3 to 6.5.4 in /docs (@dependabot[bot])
- [#13855](https://github.com/apache/superset/pull/13855) chore: Update PR template for SIP-59 DB migrations process (@robdiciuccio)
- [#13759](https://github.com/apache/superset/pull/13759) chore: Add CODEOWNERS for superset/migrations (@robdiciuccio)
- [#13861](https://github.com/apache/superset/pull/13861) docs: fixed a link (@ericmand)
- [#13854](https://github.com/apache/superset/pull/13854) chore: Displays row limit warning with the Alert component (@michael-s-molina)
- [#13811](https://github.com/apache/superset/pull/13811) chore: add success log whenever a connection is working (@hughhhh)
- [#13767](https://github.com/apache/superset/pull/13767) chore(git): ignore app folder under superset (@cemremengu)
- [#13784](https://github.com/apache/superset/pull/13784) chore(cypress): Make the e2e tests more behavior-driven (@suddjian)
- [#13797](https://github.com/apache/superset/pull/13797) refactor: New schedule query button (@AAfghahi)
- [#13547](https://github.com/apache/superset/pull/13547) test: Adds storybook and tests to PopoverDropdown component (@michael-s-molina)
- [#13757](https://github.com/apache/superset/pull/13757) chore(CI): dont run Hive tests on Py 3.7 (@ktmud)
- [#13638](https://github.com/apache/superset/pull/13638) test: SelectAsyncControl (@yardz)
- [#13581](https://github.com/apache/superset/pull/13581) test: DatabaseSelector (@yardz)
- [#13534](https://github.com/apache/superset/pull/13534) test: Tests, dedicated directory and Storybook for the ProgressBar component (@geido)
- [#13781](https://github.com/apache/superset/pull/13781) docs: update setup instructions (@benjreinhart)
- [#13676](https://github.com/apache/superset/pull/13676) refactor: SouthPane into functional component (@AAfghahi)
- [#13785](https://github.com/apache/superset/pull/13785) refactor: Revert "refactor: ScheduleQueryButton into functional component (#134… (@hughhhh)
- [#13443](https://github.com/apache/superset/pull/13443) refactor: ScheduleQueryButton into functional component (@AAfghahi)
- [#13738](https://github.com/apache/superset/pull/13738) chore: Update presto.mdx (@hughhhh)
- [#13751](https://github.com/apache/superset/pull/13751) test: changing logger.exception to logger.errors in databases api (@AAfghahi)
- [#13770](https://github.com/apache/superset/pull/13770) docs(contributing): fix backend port (@cemremengu)
- [#13627](https://github.com/apache/superset/pull/13627) test: DatasourcePanelDragWrapper (@yardz)
- [#13722](https://github.com/apache/superset/pull/13722) chore: Improve Korean translation (@kimsehwan96)
- [#13500](https://github.com/apache/superset/pull/13500) chore(homepage): separate out api calls to make homepage load more dynamically (@pkdotson)
- [#13730](https://github.com/apache/superset/pull/13730) refactor: helm chart: Use stringData instead of data in Secrets and remove manual base64 encoding (@andreas-eberle)
- [#13677](https://github.com/apache/superset/pull/13677) build: implement codecov carryforward flags (@jfrag1)
- [#13723](https://github.com/apache/superset/pull/13723) refactor(native-filters): refactor filter bar (@simcha90)
- [#12932](https://github.com/apache/superset/pull/12932) docs: update helm documentation (@anthcor)
- [#13554](https://github.com/apache/superset/pull/13554) test: usePrevious hook (@yardz)
- [#13630](https://github.com/apache/superset/pull/13630) refactor: Share sql lab query (@AAfghahi)
- [#13555](https://github.com/apache/superset/pull/13555) test: useChangeEffect (@yardz)
- [#13658](https://github.com/apache/superset/pull/13658) chore: bump pyyaml to latest (@robdiciuccio)
- [#13643](https://github.com/apache/superset/pull/13643) docs: Make suggested changes related to curl use in CONTRIBUTING.md (@slim-patchy)
- [#13660](https://github.com/apache/superset/pull/13660) chore(explore): added debounce in DateFilterControl (@zhaoyongjie)
- [#13342](https://github.com/apache/superset/pull/13342) chore: improve modal error handling (@lilykuang)
- [#13508](https://github.com/apache/superset/pull/13508) test: Tests and dedicated directory for the SupersetResourceSelect Component (@geido)
- [#13611](https://github.com/apache/superset/pull/13611) docs: correct sentence (@ErfanMirzapour)
- [#13587](https://github.com/apache/superset/pull/13587) chore: create better links for latest release (@eschutho)
- [#13596](https://github.com/apache/superset/pull/13596) chore: metric refactor iteration 1 (@zhaoyongjie)
- [#13593](https://github.com/apache/superset/pull/13593) chore(explore): bump superset-ui 0.17.19 (@zhaoyongjie)
- [#13520](https://github.com/apache/superset/pull/13520) refactor: convert controlUtils to TypeScript (2 of 2) (@ktmud)
- [#13549](https://github.com/apache/superset/pull/13549) test: CronPicker (@yardz)
- [#13583](https://github.com/apache/superset/pull/13583) test: TableCollection (@yardz)
- [#13498](https://github.com/apache/superset/pull/13498) build: check potential db migration conflict for open PRs (@ktmud)
- [#13513](https://github.com/apache/superset/pull/13513) test: Tests, dedicated directory and Storybook for the Badge component (@geido)
- [#13421](https://github.com/apache/superset/pull/13421) build: ignore Storybook stories in CodeCov (@ktmud)
- [#13438](https://github.com/apache/superset/pull/13438) test: Adds tests and storybook to PopoverSection component (@michael-s-molina)
- [#13517](https://github.com/apache/superset/pull/13517) test: Adds storybook and tests to AsyncEsmComponent (@michael-s-molina)
- [#13516](https://github.com/apache/superset/pull/13516) test: Adds storybook to AlteredSliceTag component (@michael-s-molina)
- [#13452](https://github.com/apache/superset/pull/13452) build(test env): Shutdown test environments on PR close (@robdiciuccio)
- [#13518](https://github.com/apache/superset/pull/13518) ci: increase page size of GH PR API call (@villebro)
- [#13412](https://github.com/apache/superset/pull/13412) test: Adds storybook to ConfirmStatusChange component (@michael-s-molina)
- [#13365](https://github.com/apache/superset/pull/13365) docs: add more documentation around python testing and linting (@eschutho)
- [#13437](https://github.com/apache/superset/pull/13437) refactor(native-filters): Move `filtersState` to `dataMask` redux root (@simcha90)
- [#13454](https://github.com/apache/superset/pull/13454) chore: Moves ChartIcon to own folder (@michael-s-molina)
- [#13409](https://github.com/apache/superset/pull/13409) test: Adds tests and storybook to RefreshLabel (@michael-s-molina)
- [#13324](https://github.com/apache/superset/pull/13324) chore: Moves ListView test to component's folder (@michael-s-molina)
- [#13485](https://github.com/apache/superset/pull/13485) chore: Moves InfoTooltip component to own folder (@michael-s-molina)
- [#13479](https://github.com/apache/superset/pull/13479) test: Adds storybook to Switch component (@michael-s-molina)
- [#13410](https://github.com/apache/superset/pull/13410) test: Adds storybook to SearchInput component (@michael-s-molina)
- [#13460](https://github.com/apache/superset/pull/13460) chore: Moves BootstrapSliderWrapper to own folder (@michael-s-molina)
- [#13440](https://github.com/apache/superset/pull/13440) docs: deprecate old alerts and dash/charts reports (@dpgaspar)
- [#13361](https://github.com/apache/superset/pull/13361) chore: Migrating dashboard/components/menu from jsx to tsx (@ayanginet)
- [#13455](https://github.com/apache/superset/pull/13455) docs: Fix typo in footer (@thinkh)
- [#13376](https://github.com/apache/superset/pull/13376) chore: Improve Japanese translation (@tesshy)
- [#13417](https://github.com/apache/superset/pull/13417) refactor: add set data mask to build query (@simcha90)
- [#13450](https://github.com/apache/superset/pull/13450) chore: disable drag metrics and columns in datasourcePanel (@zhaoyongjie)
- [#13411](https://github.com/apache/superset/pull/13411) chore: Moves TableSelector tests to component's folder (@michael-s-molina)
- [#13401](https://github.com/apache/superset/pull/13401) refactor: convert controlUtils to TypeScript (1 of 2) (@ktmud)
- [#13102](https://github.com/apache/superset/pull/13102) refactor: Query search into functional component (@AAfghahi)
- [#13418](https://github.com/apache/superset/pull/13418) chore: Added rockset and firebird (@srinify)
- [#13408](https://github.com/apache/superset/pull/13408) chore: Removes unused Hotkeys component (@michael-s-molina)
- [#13407](https://github.com/apache/superset/pull/13407) test: Adds storybook to FormRow component (@michael-s-molina)
- [#13406](https://github.com/apache/superset/pull/13406) test: Adds storybook to AnchorLink component (@michael-s-molina)
- [#13415](https://github.com/apache/superset/pull/13415) test: Adds storybook to IconTooltip component (@michael-s-molina)
- [#13362](https://github.com/apache/superset/pull/13362) test: Adds storybook to EditableTitle component (@michael-s-molina)
- [#13359](https://github.com/apache/superset/pull/13359) test: Adds tests and storybook to CopyToClipboard component (@michael-s-molina)
- [#13358](https://github.com/apache/superset/pull/13358) test: ErrorMessage components tests (@geido)
- [#13374](https://github.com/apache/superset/pull/13374) chore: convert chartReducer to TypeScript (@ktmud)
- [#13137](https://github.com/apache/superset/pull/13137) refactor(self-trigger): Split native filters state (@simcha90)
- [#13367](https://github.com/apache/superset/pull/13367) chore: npm audit fix (@eschutho)
- [#13343](https://github.com/apache/superset/pull/13343) test: Adds storybook to Timer component (@michael-s-molina)
- [#13338](https://github.com/apache/superset/pull/13338) chore: Moves OnPasteSelect tests to component's folder (@michael-s-molina)
- [#13322](https://github.com/apache/superset/pull/13322) chore: Moves FilterableTable test to component's folder (@michael-s-molina)
- [#13276](https://github.com/apache/superset/pull/13276) test: ErrorBoundary dedicated directory and tests (@geido)
- [#13274](https://github.com/apache/superset/pull/13274) chore: Migrates RefreshChartOverlay into Chart (@michael-s-molina)
- [#13273](https://github.com/apache/superset/pull/13273) test: Improves ButtonGroup tests (@michael-s-molina)
- [#13375](https://github.com/apache/superset/pull/13375) build: fix codecov informational config (@ktmud)
- [#13270](https://github.com/apache/superset/pull/13270) test: FormLabel dedicated directory and tests (@geido)
- [#13221](https://github.com/apache/superset/pull/13221) refactor(explore): convert ControlPanelsContainer to typescript (@ktmud)
- [#13305](https://github.com/apache/superset/pull/13305) test: Tests for OmniContainer (@yardz)
- [#13104](https://github.com/apache/superset/pull/13104) chore: update docs for new alerts and reporting feature (@leocape)
- [#13277](https://github.com/apache/superset/pull/13277) test: Pagination component (@yardz)
- [#13332](https://github.com/apache/superset/pull/13332) chore: Update OAuth2 configuration guide to match with FlaskAppBuilder (@liamnv)
- [#13299](https://github.com/apache/superset/pull/13299) test: Adds tests to TableLoader component (@michael-s-molina)
- [#13314](https://github.com/apache/superset/pull/13314) chore: Moves AsyncSelect component and tests to own folder (@michael-s-molina)
- [#13302](https://github.com/apache/superset/pull/13302) test: Menu component tests (@geido)
- [#13318](https://github.com/apache/superset/pull/13318) test: CacheLabel (@yardz)
- [#13320](https://github.com/apache/superset/pull/13320) test: tests for component FaveStar (@yardz)
- [#13296](https://github.com/apache/superset/pull/13296) chore: migrating some LESS to Emotions for ResultSet components (@rusackas)
- [#13218](https://github.com/apache/superset/pull/13218) refactor: Introduce api resource hooks, fetch owners for chart errors (@suddjian)
- [#13319](https://github.com/apache/superset/pull/13319) test: Adds tests to URLShortLinkButton component (@michael-s-molina)
- [#13286](https://github.com/apache/superset/pull/13286) test: Tests for DeleteModal component (@yardz)
- [#13329](https://github.com/apache/superset/pull/13329) test: optimize codecov config (@ktmud)
- [#13323](https://github.com/apache/superset/pull/13323) build: Use redis:latest in docker-compose workflow (@robdiciuccio)
- [#12229](https://github.com/apache/superset/pull/12229) refactor: New Icon system with Enhanced Antd Custom Icon (@geido)
- [#13189](https://github.com/apache/superset/pull/13189) build: Ephemeral environments for PRs via slash command (@robdiciuccio)
### 1.1 (Wed Feb 24 12:08:28 2021 +0200)
**Database Migrations**
+7 -8
View File
@@ -22,16 +22,10 @@ under the License.
This file documents any backwards-incompatible changes in Superset and
assists people when migrating to a new version.
## Next
- [13772](https://github.com/apache/superset/pull/13772): Row level security (RLS) is now enabled by default. To activate the feature, please run `superset init` to expose the RLS menus to Admin users.
## 1.2.0
- [13980](https://github.com/apache/superset/pull/13980): Data health checks no longer use the metadata database as an interim cache. Though non-breaking, deployments which implement complex logic should likely memoize the callback function. Refer to documentation in the confg.py file for more detail.
- [14255](https://github.com/apache/superset/pull/14255): The default `CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC` callable logic has been updated to leverage the specified database and schema to ensure the upload S3 key prefix is unique. Previously tables generated via upload from CSV with the same name but differ schema and/or cluster would use the same S3 key prefix. Note this change does not impact previously imported tables.
### Breaking Changes
### Potential Downtime
### Deprecations
- [13440](https://github.com/apache/superset/pull/13440): Dashboard/Charts reports and old Alerts is deprecated. The following config keys are deprecated:
- ENABLE_ALERTS
- SCHEDULED_EMAIL_DEBUG_MODE
@@ -39,8 +33,13 @@ assists people when migrating to a new version.
- EMAIL_ASYNC_TIME_LIMIT_SEC
- EMAIL_REPORT_BCC_ADDRESS
- EMAIL_REPORTS_USER
### Other
- [13772](https://github.com/apache/superset/pull/13772): Row level security (RLS) is now enabled by default. To activate the feature, please run `superset init` to expose the RLS menus to Admin users.
- [13980](https://github.com/apache/superset/pull/13980): Data health checks no longer use the metadata database as an interim cache. Though non-breaking, deployments which implement complex logic should likely memoize the callback function. Refer to documentation in the confg.py file for more detail.
- [14255](https://github.com/apache/superset/pull/14255): The default `CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC` callable logic has been updated to leverage the specified database and schema to ensure the upload S3 key prefix is unique. Previously tables generated via upload from CSV with the same name but differ schema and/or cluster would use the same S3 key prefix. Note this change does not impact previously imported tables.
## 1.1.0
### Breaking Changes
+766 -3258
View File
File diff suppressed because it is too large Load Diff
+30 -29
View File
@@ -1,6 +1,6 @@
{
"name": "superset",
"version": "0.999.0dev",
"version": "1.2.0",
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
"license": "Apache-2.0",
"directories": {
@@ -66,35 +66,36 @@
"@data-ui/sparkline": "^0.0.84",
"@emotion/cache": "^11.1.3",
"@emotion/core": "^10.0.35",
"@superset-ui/chart-controls": "^0.17.36",
"@superset-ui/core": "^0.17.32",
"@superset-ui/legacy-plugin-chart-calendar": "^0.17.36",
"@superset-ui/legacy-plugin-chart-chord": "^0.17.36",
"@superset-ui/legacy-plugin-chart-country-map": "^0.17.36",
"@superset-ui/legacy-plugin-chart-event-flow": "^0.17.36",
"@superset-ui/legacy-plugin-chart-force-directed": "^0.17.36",
"@superset-ui/legacy-plugin-chart-heatmap": "^0.17.36",
"@superset-ui/legacy-plugin-chart-histogram": "^0.17.36",
"@superset-ui/legacy-plugin-chart-horizon": "^0.17.36",
"@superset-ui/legacy-plugin-chart-map-box": "^0.17.36",
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.36",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.36",
"@superset-ui/legacy-plugin-chart-partition": "^0.17.36",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.36",
"@superset-ui/legacy-plugin-chart-rose": "^0.17.36",
"@superset-ui/legacy-plugin-chart-sankey": "^0.17.36",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.36",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.17.36",
"@superset-ui/legacy-plugin-chart-treemap": "^0.17.36",
"@superset-ui/legacy-plugin-chart-world-map": "^0.17.36",
"@superset-ui/legacy-preset-chart-big-number": "^0.17.36",
"@emotion/react": "^11.1.5",
"@superset-ui/chart-controls": "^0.17.41",
"@superset-ui/core": "^0.17.40",
"@superset-ui/legacy-plugin-chart-calendar": "^0.17.41",
"@superset-ui/legacy-plugin-chart-chord": "^0.17.41",
"@superset-ui/legacy-plugin-chart-country-map": "^0.17.41",
"@superset-ui/legacy-plugin-chart-event-flow": "^0.17.41",
"@superset-ui/legacy-plugin-chart-force-directed": "^0.17.41",
"@superset-ui/legacy-plugin-chart-heatmap": "^0.17.41",
"@superset-ui/legacy-plugin-chart-histogram": "^0.17.41",
"@superset-ui/legacy-plugin-chart-horizon": "^0.17.41",
"@superset-ui/legacy-plugin-chart-map-box": "^0.17.41",
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.41",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.41",
"@superset-ui/legacy-plugin-chart-partition": "^0.17.41",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.41",
"@superset-ui/legacy-plugin-chart-rose": "^0.17.41",
"@superset-ui/legacy-plugin-chart-sankey": "^0.17.41",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.41",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.17.41",
"@superset-ui/legacy-plugin-chart-treemap": "^0.17.41",
"@superset-ui/legacy-plugin-chart-world-map": "^0.17.41",
"@superset-ui/legacy-preset-chart-big-number": "^0.17.41",
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.6",
"@superset-ui/legacy-preset-chart-nvd3": "^0.17.36",
"@superset-ui/plugin-chart-echarts": "^0.17.38",
"@superset-ui/plugin-chart-pivot-table": "^0.17.36",
"@superset-ui/plugin-chart-table": "^0.17.37",
"@superset-ui/plugin-chart-word-cloud": "^0.17.36",
"@superset-ui/preset-chart-xy": "^0.17.36",
"@superset-ui/legacy-preset-chart-nvd3": "^0.17.41",
"@superset-ui/plugin-chart-echarts": "^0.17.41",
"@superset-ui/plugin-chart-pivot-table": "^0.17.41",
"@superset-ui/plugin-chart-table": "^0.17.42",
"@superset-ui/plugin-chart-word-cloud": "^0.17.41",
"@superset-ui/preset-chart-xy": "^0.17.41",
"@vx/responsive": "^0.0.195",
"abortcontroller-polyfill": "^1.1.9",
"antd": "^4.9.4",
+1 -1
View File
@@ -29,7 +29,7 @@ export default {
chartUpdateStartTime: 1525852454838,
latestQueryFormData: {},
queryRequest: {},
queryResponse: {},
queriesResponse: [{}],
triggerQuery: false,
lastRendered: 0,
form_data: {
+15 -5
View File
@@ -21,7 +21,6 @@ import { NativeFiltersState } from 'src/dashboard/reducers/types';
import { DataMaskStateWithId } from '../../src/dataMask/types';
export const nativeFilters: NativeFiltersState = {
isInitialized: true,
filterSets: {},
filters: {
'NATIVE_FILTER-e7Q8zKixx': {
@@ -36,7 +35,11 @@ export const nativeFilters: NativeFiltersState = {
},
},
],
defaultValue: null,
defaultDataMask: {
filterState: {
value: null,
},
},
cascadeParentIds: [],
scope: {
rootPath: ['ROOT_ID'],
@@ -61,7 +64,11 @@ export const nativeFilters: NativeFiltersState = {
},
},
],
defaultValue: null,
defaultDataMask: {
filterState: {
value: null,
},
},
cascadeParentIds: [],
scope: {
rootPath: ['ROOT_ID'],
@@ -115,14 +122,17 @@ export const extraFormData: ExtraFormData = {
export const NATIVE_FILTER_ID = 'NATIVE_FILTER-p4LImrSgA';
export const singleNativeFiltersState = {
isInitialized: true,
filters: {
[NATIVE_FILTER_ID]: {
id: [NATIVE_FILTER_ID],
name: 'eth',
type: 'text',
targets: [{ datasetId: 13, column: { name: 'ethnic_minority' } }],
defaultValue: null,
defaultDataMask: {
filterState: {
value: null,
},
},
cascadeParentIds: [],
scope: { rootPath: ['ROOT_ID'], excluded: [227, 229] },
inverseSelection: false,
@@ -167,7 +167,7 @@ describe('Dashboard', () => {
...OVERRIDE_FILTERS,
[NATIVE_FILTER_ID]: {
scope: [230],
values: [extraFormData],
values: extraFormData,
},
});
});
@@ -30,7 +30,6 @@ export const mockDataMaskInfo: DataMaskStateWithId = {
};
export const nativeFiltersInfo: NativeFiltersState = {
isInitialized: true,
filterSets: {
'set-id': {
id: 'DefaultsID',
@@ -54,7 +53,11 @@ export const nativeFiltersInfo: NativeFiltersState = {
},
},
],
defaultValue: null,
defaultDataMask: {
filterState: {
value: null,
},
},
scope: {
rootPath: [],
excluded: [],
@@ -29,6 +29,16 @@ describe('getFormDataWithExtraFilters', () => {
const filterId = 'native-filter-1';
const mockChart = {
id: chartId,
chartAlert: null,
chartStatus: null,
chartUpdateEndTime: null,
chartUpdateStartTime: 1,
lastRendered: 1,
latestQueryFormData: {},
sliceFormData: null,
queryController: null,
queriesResponse: null,
triggerQuery: false,
formData: {
viz_type: 'filter_select',
filters: [
@@ -43,7 +53,7 @@ describe('getFormDataWithExtraFilters', () => {
const mockArgs: GetFormDataWithExtraFiltersArguments = {
chartConfiguration: {},
charts: {
[chartId]: mockChart,
[chartId as number]: mockChart,
},
chart: mockChart,
filters: {
@@ -52,7 +62,6 @@ describe('getFormDataWithExtraFilters', () => {
},
sliceId: chartId,
nativeFilters: {
isInitialized: true,
filterSets: {},
filters: {
[filterId]: ({
@@ -129,21 +129,15 @@ describe('Filter utils', () => {
);
});
it('getSelectExtraFormData - col: "testCol", value: [], emptyFilter: false, inverseSelection: false', () => {
expect(getSelectExtraFormData('testCol', [], false, false)).toEqual({
filters: [],
});
expect(getSelectExtraFormData('testCol', [], false, false)).toEqual({});
});
it('getSelectExtraFormData - col: "testCol", value: undefined, emptyFilter: false, inverseSelection: false', () => {
expect(
getSelectExtraFormData('testCol', undefined, false, false),
).toEqual({
filters: [],
});
).toEqual({});
});
it('getSelectExtraFormData - col: "testCol", value: null, emptyFilter: false, inverseSelection: false', () => {
expect(getSelectExtraFormData('testCol', null, false, false)).toEqual({
filters: [],
});
expect(getSelectExtraFormData('testCol', null, false, false)).toEqual({});
});
});
@@ -334,6 +334,7 @@ export default function sqlLabReducer(state = {}, action) {
results: action.results,
rows: action?.results?.data?.length,
state: 'success',
limitingFactor: action?.results?.query?.limitingFactor,
tempSchema: action?.results?.query?.tempSchema,
tempTable: action?.results?.query?.tempTable,
errorMessage: null,
+1
View File
@@ -65,4 +65,5 @@ export type Query = {
templateParams: any;
rows: number;
queryLimit: number;
limitingFactor: string;
};
+1 -7
View File
@@ -121,13 +121,6 @@ class Chart extends React.PureComponent {
}
runQuery() {
if (
this.props.dashboardId && // we on dashboard screen
isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) &&
!this.props.isFiltersInitialized
) {
return;
}
if (this.props.chartId > 0 && isFeatureEnabled(FeatureFlag.CLIENT_CACHE)) {
// Load saved chart with a GET request
this.props.actions.getSavedChart(
@@ -221,6 +214,7 @@ class Chart extends React.PureComponent {
showMessage={false}
>
<Styles
data-ui-anchor="chart"
className="chart-container"
data-test="chart-container"
height={height}
@@ -37,9 +37,4 @@ function mapDispatchToProps(dispatch) {
};
}
export default connect(
({ nativeFilters }) => ({
isFiltersInitialized: nativeFilters?.isInitialized,
}),
mapDispatchToProps,
)(Chart);
export default connect(null, mapDispatchToProps)(Chart);
+15 -2
View File
@@ -248,6 +248,7 @@ export function runAnnotationQuery(
formData = null,
key,
isDashboardRequest = false,
force = false,
) {
return function (dispatch, getState) {
const sliceKey = key || Object.keys(getState().charts)[0];
@@ -286,7 +287,12 @@ export function runAnnotationQuery(
}
const isNative = annotation.sourceType === ANNOTATION_SOURCE_TYPES.NATIVE;
const url = getAnnotationJsonUrl(annotation.value, sliceFormData, isNative);
const url = getAnnotationJsonUrl(
annotation.value,
sliceFormData,
isNative,
force,
);
const controller = new AbortController();
const { signal } = controller;
@@ -462,7 +468,14 @@ export function exploreJSON(
dispatch(updateQueryFormData(formData, key)),
...annotationLayers.map(x =>
dispatch(
runAnnotationQuery(x, timeout, formData, key, isDashboardRequest),
runAnnotationQuery(
x,
timeout,
formData,
key,
isDashboardRequest,
force,
),
),
),
]);
@@ -24,7 +24,6 @@ import {
CategoricalColorNamespace,
getChartMetadataRegistry,
} from '@superset-ui/core';
import querystring from 'query-string';
import { chart } from 'src/chart/chartReducer';
import { initSliceEntities } from 'src/dashboard/reducers/sliceEntities';
@@ -55,27 +54,7 @@ import getLocationHash from 'src/dashboard/util/getLocationHash';
import newComponentFactory from 'src/dashboard/util/newComponentFactory';
import { TIME_RANGE } from 'src/visualizations/FilterBox/FilterBox';
import { FeatureFlag, isFeatureEnabled } from '../../featureFlags';
const reservedQueryParams = new Set(['standalone', 'edit']);
/**
* Returns the url params that are used to customize queries
* in datasets built using sql lab.
* We may want to extract this to some kind of util in the future.
*/
const extractUrlParams = queryParams =>
Object.entries(queryParams).reduce((acc, [key, value]) => {
if (reservedQueryParams.has(key)) return acc;
// if multiple url params share the same key (?foo=bar&foo=baz), they will appear as an array.
// Only one value can be used for a given query param, so we just take the first one.
if (Array.isArray(value)) {
return {
...acc,
[key]: value[0],
};
}
return { ...acc, [key]: value };
}, {});
import extractUrlParams from '../util/extractUrlParams';
export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD';
@@ -85,9 +64,9 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => (
) => {
const { user, common } = getState();
let { metadata } = dashboardData;
const queryParams = querystring.parse(window.location.search);
const urlParams = extractUrlParams(queryParams);
const editMode = queryParams.edit === 'true';
const regularUrlParams = extractUrlParams('regular');
const reservedUrlParams = extractUrlParams('reserved');
const editMode = reservedUrlParams.edit === 'true';
let preselectFilters = {};
@@ -154,7 +133,7 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => (
...slice.form_data,
url_params: {
...slice.form_data.url_params,
...urlParams,
...regularUrlParams,
},
};
chartQueries[key] = {
@@ -22,8 +22,8 @@ import { Dispatch } from 'redux';
import { FilterConfiguration } from 'src/dashboard/components/nativeFilters/types';
import { DataMaskType, DataMaskStateWithId } from 'src/dataMask/types';
import {
SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE,
SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL,
setDataMaskForFilterConfigComplete,
} from 'src/dataMask/actions';
import { HYDRATE_DASHBOARD } from './hydrate';
import { dashboardInfoChanged } from './dashboardInfo';
@@ -65,14 +65,6 @@ export interface SetFilterSetsConfigFail {
type: typeof SET_FILTER_SETS_CONFIG_FAIL;
filterSetsConfig: FilterSet[];
}
export const SET_FILTERS_INITIALIZED = 'SET_FILTERS_INITIALIZED';
export interface SetFiltersInitialized {
type: typeof SET_FILTERS_INITIALIZED;
}
export const setFiltersInitialized = (): SetFiltersInitialized => ({
type: SET_FILTERS_INITIALIZED,
});
export const setFilterConfiguration = (
filterConfig: FilterConfiguration,
@@ -82,6 +74,7 @@ export const setFilterConfiguration = (
filterConfig,
});
const { id, metadata } = getState().dashboardInfo;
const oldFilters = getState().nativeFilters?.filters;
// TODO extract this out when makeApi supports url parameters
const updateDashboard = makeApi<
@@ -108,11 +101,7 @@ export const setFilterConfiguration = (
type: SET_FILTER_CONFIG_COMPLETE,
filterConfig,
});
dispatch({
type: SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE,
unitName: DataMaskType.NativeFilters,
filterConfig,
});
dispatch(setDataMaskForFilterConfigComplete(filterConfig, oldFilters));
} catch (err) {
dispatch({ type: SET_FILTER_CONFIG_FAIL, filterConfig });
dispatch({ type: SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL, filterConfig });
@@ -200,6 +189,5 @@ export type AnyFilterAction =
| SetFilterSetsConfigBegin
| SetFilterSetsConfigComplete
| SetFilterSetsConfigFail
| SetFiltersInitialized
| SaveFilterSets
| SetBooststapData;
@@ -18,7 +18,7 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { t } from '@superset-ui/core';
import { isFeatureEnabled, t, FeatureFlag } from '@superset-ui/core';
import { PluginContext } from 'src/components/DynamicPlugins';
import Loading from 'src/components/Loading';
@@ -56,6 +56,7 @@ const propTypes = {
charts: PropTypes.objectOf(chartPropShape).isRequired,
slices: PropTypes.objectOf(slicePropShape).isRequired,
activeFilters: PropTypes.object.isRequired,
chartConfiguration: PropTypes.object.isRequired,
datasources: PropTypes.object.isRequired,
ownDataCharts: PropTypes.object.isRequired,
layout: PropTypes.object.isRequired,
@@ -120,6 +121,11 @@ class Dashboard extends React.PureComponent {
};
}
window.addEventListener('visibilitychange', this.onVisibilityChange);
this.applyCharts();
}
componentDidUpdate() {
this.applyCharts();
}
UNSAFE_componentWillReceiveProps(nextProps) {
@@ -147,15 +153,28 @@ class Dashboard extends React.PureComponent {
}
}
componentDidUpdate() {
applyCharts() {
const { hasUnsavedChanges, editMode } = this.props.dashboardState;
const { appliedFilters, appliedOwnDataCharts } = this;
const { activeFilters, ownDataCharts } = this.props;
const { activeFilters, ownDataCharts, chartConfiguration } = this.props;
if (
isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS) &&
!chartConfiguration
) {
// For a first loading we need to wait for cross filters charts data loaded to get all active filters
// for correct comparing of filters to avoid unnecessary requests
return;
}
if (
!editMode &&
(!areObjectsEqual(appliedOwnDataCharts, ownDataCharts) ||
!areObjectsEqual(appliedFilters, activeFilters))
(!areObjectsEqual(appliedOwnDataCharts, ownDataCharts, {
ignoreUndefined: true,
}) ||
!areObjectsEqual(appliedFilters, activeFilters, {
ignoreUndefined: true,
}))
) {
this.applyFilters();
}
@@ -223,6 +242,9 @@ class Dashboard extends React.PureComponent {
!areObjectsEqual(
appliedFilters[filterKey].values,
activeFilters[filterKey].values,
{
ignoreUndefined: true,
},
)
) {
affectedChartIds.push(...activeFilters[filterKey].scope);
@@ -22,7 +22,6 @@ import React, { FC, SyntheticEvent, useEffect, useState } from 'react';
import { Sticky, StickyContainer } from 'react-sticky';
import { TabContainer } from 'react-bootstrap';
import { JsonObject, styled } from '@superset-ui/core';
import ErrorBoundary from 'src/components/ErrorBoundary';
import BuilderComponentPane from 'src/dashboard/components/BuilderComponentPane';
import DashboardHeader from 'src/dashboard/containers/DashboardHeader';
@@ -31,7 +30,6 @@ import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
import ToastPresenter from 'src/messageToasts/containers/ToastPresenter';
import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
import getDirectPathToTabIndex from 'src/dashboard/util/getDirectPathToTabIndex';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import { URL_PARAMS } from 'src/constants';
@@ -72,6 +72,7 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
activeKey={activeKey}
renderTabBar={() => <></>}
fullWidth={false}
allowOverflow
>
{childIds.map((id, index) => (
// Matching the key of the first TabPane irrespective of topLevelTabs
@@ -32,6 +32,7 @@ import getDashboardUrl from 'src/dashboard/util/getDashboardUrl';
import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import CrossFilterScopingModal from 'src/dashboard/components/CrossFilterScopingModal/CrossFilterScopingModal';
import Icons from 'src/components/Icons';
const propTypes = {
slice: PropTypes.object.isRequired,
@@ -295,6 +296,14 @@ class SliceHeaderControls extends React.PureComponent {
isOpen={this.state.showCrossFilterScopingModal}
onClose={() => this.setState({ showCrossFilterScopingModal: false })}
/>
{isFullSize && (
<Icons.FullscreenExitOutlined
style={{ fontSize: 22 }}
onClick={() => {
this.props.handleToggleFullSize();
}}
/>
)}
<NoAnimationDropdown
overlay={menu}
trigger={['click']}
@@ -245,8 +245,8 @@ class ChartHolder extends React.Component {
let chartHeight = 0;
if (this.state.isFullSize) {
chartWidth = document.body.clientWidth - CHART_MARGIN;
chartHeight = document.body.clientHeight - CHART_MARGIN;
chartWidth = window.innerWidth - CHART_MARGIN;
chartHeight = window.innerHeight - CHART_MARGIN;
} else {
chartWidth = Math.floor(
widthMultiple * columnWidth +
@@ -0,0 +1,88 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import mockState from 'spec/fixtures/mockState';
import { sliceId as chartId } from 'spec/fixtures/mockChartQueries';
import newComponentFactory from 'src/dashboard/util/newComponentFactory';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/react';
import { ChartHolder } from './index';
import { CHART_TYPE, ROW_TYPE } from '../../util/componentTypes';
describe('ChartHolder', () => {
const defaultProps = {
component: {
...newComponentFactory(CHART_TYPE),
id: 'CHART_ID',
parents: ['ROOT_ID', 'TABS_ID', 'TAB_ID', 'ROW_ID'],
meta: {
chartId,
width: 3,
height: 10,
chartName: 'Mock chart name',
},
},
parentComponent: {
...newComponentFactory(ROW_TYPE),
id: 'ROW_ID',
children: ['COLUMN_ID'],
},
index: 0,
depth: 0,
id: 'CHART_ID',
parentId: 'ROW_ID',
availableColumnCount: 12,
columnWidth: 300,
onResizeStart: () => {},
onResize: () => {},
onResizeStop: () => {},
handleComponentDrop: () => {},
deleteComponent: () => {},
updateComponents: () => {},
editMode: false,
isComponentVisible: true,
dashboardId: 123,
};
const renderWrapper = (props = defaultProps, state = mockState) =>
render(<ChartHolder {...props} />, {
useRedux: true,
initialState: state,
useDnd: true,
});
it('toggle full size', async () => {
renderWrapper();
let chart = (screen.getByTestId('slice-container')
.firstChild as HTMLElement).style;
expect(chart?.width).toBe('900px');
expect(chart?.height).toBe('26px');
userEvent.click(screen.getByRole('button'));
userEvent.click(screen.getByText('Maximize chart'));
chart = (screen.getByTestId('slice-container').firstChild as HTMLElement)
.style;
await waitFor(() => expect(chart?.width).toBe('992px'));
expect(chart?.height).toBe('714px');
});
});
@@ -28,6 +28,7 @@ import FilterControl from 'src/dashboard/components/nativeFilters/FilterBar/Filt
import CascadeFilterControl from 'src/dashboard/components/nativeFilters/FilterBar/CascadeFilters/CascadeFilterControl';
import { CascadeFilter } from 'src/dashboard/components/nativeFilters/FilterBar/CascadeFilters/types';
import { Filter } from 'src/dashboard/components/nativeFilters/types';
import { RootState } from 'src/dashboard/types';
interface CascadePopoverProps {
filter: CascadeFilter;
@@ -82,7 +83,7 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({
directPathToChild,
}) => {
const [currentPathToChild, setCurrentPathToChild] = useState<string[]>();
const dataMask = useSelector<any, DataMaskWithId>(
const dataMask = useSelector<RootState, DataMaskWithId>(
state => state.dataMask[filter.id] ?? getInitialDataMask(filter.id),
);
@@ -17,8 +17,9 @@
* under the License.
*/
import { DataMask } from '@superset-ui/core';
import { Filter } from '../../types';
export interface CascadeFilter extends Filter {
export type CascadeFilter = Filter & { dataMask?: DataMask } & {
cascadeChildren: CascadeFilter[];
}
};
@@ -18,7 +18,7 @@
*/
import React from 'react';
import { render, screen, cleanup } from 'spec/helpers/testing-library';
import { render, screen } from 'spec/helpers/testing-library';
import { Provider } from 'react-redux';
import userEvent from '@testing-library/user-event';
import {
@@ -41,6 +41,7 @@ import mockDatasource, {
import FilterBar, { FILTER_BAR_TEST_ID } from '.';
import { FILTERS_CONFIG_MODAL_TEST_ID } from '../FiltersConfigModal/FiltersConfigModal';
jest.useFakeTimers();
// @ts-ignore
mockCore.makeApi = jest.fn();
@@ -70,7 +71,7 @@ const getDateControlTestId = testWithId<string>(
const FILTER_NAME = 'Time filter 1';
const FILTER_SET_NAME = 'New filter set';
const addFilterFlow = () => {
const addFilterFlow = async () => {
// open filter config modal
userEvent.click(screen.getByTestId(getTestId('collapsable')));
userEvent.click(screen.getByTestId(getTestId('create-filter')));
@@ -79,12 +80,12 @@ const addFilterFlow = () => {
userEvent.click(screen.getByText('Time filter'));
userEvent.type(screen.getByTestId(getModalTestId('name-input')), FILTER_NAME);
userEvent.click(screen.getByText('Save'));
await screen.findByText('All Filters (1)');
};
const addFilterSetFlow = async () => {
// add filter set
userEvent.click(screen.getByText('Filter Sets (0)'));
expect(screen.getByTestId(getTestId('new-filter-set-button'))).toBeDisabled();
// check description
expect(screen.getByText('Filters (1)')).toBeInTheDocument();
@@ -92,7 +93,6 @@ const addFilterSetFlow = async () => {
expect(screen.getAllByText('Last week').length).toBe(2);
// apply filters
userEvent.click(screen.getByTestId(getTestId('apply-button')));
expect(screen.getByTestId(getTestId('new-filter-set-button'))).toBeEnabled();
// create filter set
@@ -139,7 +139,7 @@ describe('FilterBar', () => {
"name":"${FILTER_NAME}",
"filterType":"filter_time",
"targets":[{"datasetId":11,"column":{"name":"color"}}],
"defaultValue":null,
"defaultDataMask":{"filterState":{"value":null}},
"controlValues":{},
"cascadeParentIds":[],
"scope":{"rootPath":["ROOT_ID"],"excluded":[]},
@@ -154,7 +154,7 @@ describe('FilterBar', () => {
"name":"${FILTER_NAME}",
"filterType":"filter_time",
"targets":[{}],
"defaultValue":"Last week",
"defaultDataMask":{"filterState":{"value":"Last week"},"extraFormData":{"time_range":"Last week"}},
"controlValues":{},
"cascadeParentIds":[],
"scope":{"rootPath":["ROOT_ID"],"excluded":[]},
@@ -163,7 +163,7 @@ describe('FilterBar', () => {
},
"dataMask":{
"${filterId}":{
"extraFormData":{"override_form_data":{"time_range":"Last week"}},
"extraFormData":{"time_range":"Last week"},
"filterState":{"value":"Last week"},
"ownState":{},
"id":"${filterId}"
@@ -176,7 +176,7 @@ describe('FilterBar', () => {
});
beforeEach(() => {
toggleFiltersBar.mockClear();
jest.clearAllMocks();
fetchMock.get(
'http://localhost/api/v1/time_range/?q=%27Last%20day%27',
{
@@ -204,11 +204,6 @@ describe('FilterBar', () => {
mockCore.makeApi = jest.fn(() => mockApi);
});
afterEach(() => {
cleanup();
jest.clearAllMocks();
});
const renderWrapper = (props = closedBarProps, state?: object) =>
render(
<Provider store={state ? getMockStore(state) : mockStore}>
@@ -246,9 +241,11 @@ describe('FilterBar', () => {
expect(screen.getByRole('img', { name: 'filter' })).toBeInTheDocument();
});
it('should render the filter control name', () => {
it('should render the filter control name', async () => {
renderWrapper();
expect(screen.getByText('test')).toBeInTheDocument();
expect(
await screen.findByText('test', {}, { timeout: 2000 }),
).toBeInTheDocument();
});
it('should toggle', () => {
@@ -305,42 +302,34 @@ describe('FilterBar', () => {
renderWrapper(openedBarProps, stateWithoutNativeFilters);
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
addFilterFlow();
await addFilterFlow();
await screen.findByText('All Filters (1)');
// apply filter
expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
userEvent.click(screen.getByTestId(getTestId('apply-button')));
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
});
// TODO: fix flakiness and re-enable
it.skip('add and apply filter set', async () => {
it('add and apply filter set', async () => {
// @ts-ignore
global.featureFlags = {
[FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true,
[FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true,
};
renderWrapper(openedBarProps, stateWithoutNativeFilters);
addFilterFlow();
await screen.findByText('All Filters (1)');
expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
await addFilterFlow();
await addFilterSetFlow();
// change filter
userEvent.click(screen.getByText('All Filters (1)'));
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
userEvent.click(await screen.findByText('All Filters (1)'));
await changeFilterValue();
await waitFor(() => expect(screen.getAllByText('Last day').length).toBe(2));
// apply new filter value
expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
userEvent.click(screen.getByTestId(getTestId('apply-button')));
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
await waitFor(() =>
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled(),
);
// applying filter set
userEvent.click(screen.getByText('Filter Sets (1)'));
@@ -352,13 +341,11 @@ describe('FilterBar', () => {
).not.toHaveAttribute('data-selected', 'true');
userEvent.click(screen.getByTestId(getTestId('filter-set-wrapper')));
expect(await screen.findByText('Last week')).toBeInTheDocument();
expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
userEvent.click(screen.getByTestId(getTestId('apply-button')));
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
});
// TODO: fix flakiness and re-enable
it.skip('add and edit filter set', async () => {
it('add and edit filter set', async () => {
// @ts-ignore
global.featureFlags = {
[FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true,
@@ -366,10 +353,7 @@ describe('FilterBar', () => {
};
renderWrapper(openedBarProps, stateWithoutNativeFilters);
addFilterFlow();
await screen.findByText('All Filters (1)');
expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
await addFilterFlow();
await addFilterSetFlow();
@@ -377,12 +361,13 @@ describe('FilterBar', () => {
userEvent.click(screen.getByText('Edit'));
await changeFilterValue();
await waitFor(() => expect(screen.getAllByText('Last day').length).toBe(1));
// apply new changes and save them
expect(
screen.getByTestId(getTestId('filter-set-edit-save')),
).toBeDisabled();
await waitFor(() =>
expect(
screen.getByTestId(getTestId('filter-set-edit-save')),
).toBeDisabled(),
);
expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled();
userEvent.click(screen.getByTestId(getTestId('apply-button')));
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
@@ -19,7 +19,7 @@
import { useSelector } from 'react-redux';
import { NativeFiltersState } from 'src/dashboard/reducers/types';
import { mergeExtraFormData } from '../../utils';
import { useDataMask } from '../state';
import { useNativeFiltersDataMask } from '../state';
// eslint-disable-next-line import/prefer-default-export
export function useCascadingFilters(id: string) {
@@ -29,7 +29,7 @@ export function useCascadingFilters(id: string) {
const filter = filters[id];
const cascadeParentIds: string[] = filter?.cascadeParentIds ?? [];
let cascadedFilters = {};
const nativeFiltersDataMask = useDataMask();
const nativeFiltersDataMask = useNativeFiltersDataMask();
cascadeParentIds.forEach(parentId => {
const parentState = nativeFiltersDataMask[parentId] || {};
const { extraFormData: parentExtra = {} } = parentState;
@@ -21,7 +21,9 @@ import { DataMask } from '@superset-ui/core';
import { Filter } from '../../types';
export interface FilterProps {
filter: Filter;
filter: Filter & {
dataMask?: DataMask;
};
icon?: React.ReactElement;
directPathToChild?: string[];
onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void;
@@ -25,7 +25,7 @@ import { setFilterSetsConfiguration } from 'src/dashboard/actions/nativeFilters'
import { DataMaskState } from 'src/dataMask/types';
import { WarningOutlined } from '@ant-design/icons';
import { ActionButtons } from './Footer';
import { useDataMask, useFilters, useFilterSets } from '../state';
import { useNativeFiltersDataMask, useFilters, useFilterSets } from '../state';
import { APPLY_FILTERS_HINT, findExistingFilterSet } from './utils';
import { useFilterSetNameDuplicated } from './state';
import { getFilterBarTestId } from '../index';
@@ -72,7 +72,7 @@ const EditSection: FC<EditSectionProps> = ({
dataMaskSelected,
disabled,
}) => {
const dataMaskApplied = useDataMask();
const dataMaskApplied = useNativeFiltersDataMask();
const dispatch = useDispatch();
const filterSets = useFilterSets();
const filters = useFilters();
@@ -26,7 +26,7 @@ import { Filters, FilterSet, FilterSets } from 'src/dashboard/reducers/types';
import { areObjectsEqual } from 'src/reduxUtils';
import { findExistingFilterSet, generateFiltersSetId } from './utils';
import { Filter } from '../../types';
import { useFilters, useDataMask, useFilterSets } from '../state';
import { useFilters, useNativeFiltersDataMask, useFilterSets } from '../state';
import Footer from './Footer';
import FilterSetUnit from './FilterSetUnit';
import { getFilterBarTestId } from '..';
@@ -85,7 +85,7 @@ const FilterSets: React.FC<FilterSetsProps> = ({
const dispatch = useDispatch();
const [filterSetName, setFilterSetName] = useState(DEFAULT_FILTER_SET_NAME);
const [editMode, setEditMode] = useState(false);
const dataMaskApplied = useDataMask();
const dataMaskApplied = useNativeFiltersDataMask();
const filterSets = useFilterSets();
const filterSetFilterValues = Object.values(filterSets);
const filters = useFilters();
@@ -111,7 +111,9 @@ const FilterSets: React.FC<FilterSetsProps> = ({
filterSet?: FilterSet,
) =>
!filterValues.find(filter => filter?.id === id) ||
!areObjectsEqual(filters[id], filterSet?.nativeFilters?.[id]);
!areObjectsEqual(filters[id], filterSet?.nativeFilters?.[id], {
ignoreUndefined: true,
});
const takeFilterSet = (id: string, target?: HTMLElement) => {
const ignoreSelectorHeader = 'ant-collapse-header';
@@ -137,13 +139,15 @@ const FilterSets: React.FC<FilterSetsProps> = ({
const filterSet = filterSets[id];
Object.values(filterSet?.dataMask ?? []).forEach(dataMask => {
const { extraFormData, filterState, id } = dataMask as DataMaskWithId;
if (isFilterMissingOrContainsInvalidMetadata(id, filterSet)) {
return;
}
onFilterSelectionChange({ id }, { extraFormData, filterState });
});
(Object.values(filterSet?.dataMask) ?? []).forEach(
(dataMask: DataMaskWithId) => {
const { extraFormData, filterState, id } = dataMask;
if (isFilterMissingOrContainsInvalidMetadata(id, filterSet)) {
return;
}
onFilterSelectionChange({ id }, { extraFormData, filterState });
},
);
};
const handleRebuild = (id: string) => {
@@ -168,7 +172,7 @@ const FilterSets: React.FC<FilterSetsProps> = ({
dataMask: Object.keys(newFilters).reduce(
(prev, nextFilterId) => ({
...prev,
[nextFilterId]: filterSet.dataMask?.nativeFilters?.[nextFilterId],
[nextFilterId]: filterSet.dataMask?.[nextFilterId],
}),
{},
),
@@ -19,27 +19,36 @@
/* eslint-disable no-param-reassign */
import { HandlerFunction, styled, t } from '@superset-ui/core';
import React, { useEffect, useMemo, useState } from 'react';
import React, { useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import cx from 'classnames';
import Icon from 'src/components/Icon';
import { Tabs } from 'src/common/components';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import { updateDataMask } from 'src/dataMask/actions';
import { DataMaskState } from 'src/dataMask/types';
import {
DataMaskState,
DataMaskStateWithId,
DataMaskWithId,
} from 'src/dataMask/types';
import { useImmer } from 'use-immer';
import { areObjectsEqual } from 'src/reduxUtils';
import { testWithId } from 'src/utils/testUtils';
import { Filter } from 'src/dashboard/components/nativeFilters/types';
import { setFiltersInitialized } from 'src/dashboard/actions/nativeFilters';
import { mapParentFiltersToChildren, TabIds } from './utils';
import Loading from 'src/components/Loading';
import { getInitialDataMask } from 'src/dataMask/reducer';
import {
getOnlyExtraFormData,
mapParentFiltersToChildren,
TabIds,
} from './utils';
import FilterSets from './FilterSets';
import {
useDataMask,
useNativeFiltersDataMask,
useFilters,
useFilterSets,
useFiltersInitialisation,
useFilterUpdates,
useInitialization,
} from './state';
import EditSection from './FilterSets/EditSection';
import Header from './Header';
@@ -155,18 +164,16 @@ const FilterBar: React.FC<FiltersBarProps> = ({
directPathToChild,
}) => {
const [editFilterSetId, setEditFilterSetId] = useState<string | null>(null);
const [dataMaskSelected, setDataMaskSelected] = useImmer<DataMaskState>({});
const [
lastAppliedFilterData,
setLastAppliedFilterData,
] = useImmer<DataMaskState>({});
const [dataMaskSelected, setDataMaskSelected] = useImmer<DataMaskStateWithId>(
{},
);
const dispatch = useDispatch();
const filterSets = useFilterSets();
const filterSetFilterValues = Object.values(filterSets);
const [tab, setTab] = useState(TabIds.AllFilters);
const filters = useFilters();
const filterValues = Object.values<Filter>(filters);
const dataMaskApplied = useDataMask();
const dataMaskApplied: DataMaskStateWithId = useNativeFiltersDataMask();
const [isFilterSetChanged, setIsFilterSetChanged] = useState(false);
const cascadeChildren = useMemo(
() => mapParentFiltersToChildren(filterValues),
@@ -185,7 +192,10 @@ const FilterBar: React.FC<FiltersBarProps> = ({
dispatch(updateDataMask(filter.id, dataMask));
}
draft[filter.id] = dataMask;
draft[filter.id] = {
...(getInitialDataMask(filter.id) as DataMaskWithId),
...dataMask,
};
});
};
@@ -196,28 +206,20 @@ const FilterBar: React.FC<FiltersBarProps> = ({
dispatch(updateDataMask(filterId, dataMaskSelected[filterId]));
}
});
setLastAppliedFilterData(() => dataMaskSelected);
};
const { isInitialized } = useFiltersInitialisation(
dataMaskSelected,
handleApply,
);
useEffect(() => {
if (isInitialized) {
dispatch(setFiltersInitialized());
}
}, [dispatch, isInitialized]);
useFilterUpdates(
dataMaskSelected,
setDataMaskSelected,
setLastAppliedFilterData,
);
useFilterUpdates(dataMaskSelected, setDataMaskSelected);
const dataSelectedValues = Object.values(dataMaskSelected);
const dataAppliedValues = Object.values(dataMaskApplied);
const isApplyDisabled =
!isInitialized || areObjectsEqual(dataMaskSelected, lastAppliedFilterData);
areObjectsEqual(
getOnlyExtraFormData(dataMaskSelected),
getOnlyExtraFormData(dataMaskApplied),
{ ignoreUndefined: true },
) || dataSelectedValues.length !== dataAppliedValues.length;
const isInitialized = useInitialization();
return (
<BarWrapper {...getFilterBarTestId()} className={cx({ open: filtersOpen })}>
@@ -241,7 +243,9 @@ const FilterBar: React.FC<FiltersBarProps> = ({
dataMaskSelected={dataMaskSelected}
dataMaskApplied={dataMaskApplied}
/>
{isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET) ? (
{!isInitialized ? (
<Loading />
) : isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET) ? (
<StyledTabs
centered
onChange={setTab as HandlerFunction}
@@ -22,10 +22,14 @@ import {
Filters,
FilterSets as FilterSetsType,
} from 'src/dashboard/reducers/types';
import { DataMaskState, DataMaskStateWithId } from 'src/dataMask/types';
import {
DataMaskState,
DataMaskStateWithId,
DataMaskWithId,
} from 'src/dataMask/types';
import { useEffect, useState } from 'react';
import { areObjectsEqual } from 'src/reduxUtils';
import { Filter } from '../types';
import { ChartsState, RootState } from 'src/dashboard/types';
import { NATIVE_FILTER_PREFIX } from '../FiltersConfigModal/utils';
export const useFilterSets = () =>
useSelector<any, FilterSetsType>(
@@ -35,44 +39,27 @@ export const useFilterSets = () =>
export const useFilters = () =>
useSelector<any, Filters>(state => state.nativeFilters.filters);
export const useDataMask = () =>
useSelector<any, DataMaskStateWithId>(state => state.dataMask);
export const useNativeFiltersDataMask = () => {
const dataMask = useSelector<RootState, DataMaskStateWithId>(
state => state.dataMask,
);
export const useFiltersInitialisation = (
dataMaskSelected: DataMaskState,
handleApply: () => void,
) => {
const [isInitialized, setIsInitialized] = useState<boolean>(false);
const filters = useFilters();
const filterValues = Object.values<Filter>(filters);
useEffect(() => {
if (isInitialized) {
return;
}
const areFiltersInitialized = filterValues.every(filterValue =>
areObjectsEqual(
filterValue?.defaultValue,
dataMaskSelected[filterValue?.id]?.filterState?.value,
),
);
if (areFiltersInitialized) {
handleApply();
setIsInitialized(true);
}
}, [filterValues, dataMaskSelected, isInitialized]);
return {
isInitialized,
};
return Object.values(dataMask)
.filter((item: DataMaskWithId) =>
String(item.id).startsWith(NATIVE_FILTER_PREFIX),
)
.reduce(
(prev, next: DataMaskWithId) => ({ ...prev, [next.id]: next }),
{},
) as DataMaskStateWithId;
};
export const useFilterUpdates = (
dataMaskSelected: DataMaskState,
setDataMaskSelected: (arg0: (arg0: DataMaskState) => void) => void,
setLastAppliedFilterData: (arg0: (arg0: DataMaskState) => void) => void,
) => {
const filters = useFilters();
const dataMaskApplied = useDataMask();
const dataMaskApplied = useNativeFiltersDataMask();
useEffect(() => {
// Remove deleted filters from local state
@@ -83,18 +70,50 @@ export const useFilterUpdates = (
});
}
});
Object.keys(dataMaskApplied).forEach(appliedId => {
if (!filters[appliedId]) {
setLastAppliedFilterData(draft => {
delete draft[appliedId];
});
}
});
}, [
dataMaskApplied,
dataMaskSelected,
filters,
setDataMaskSelected,
setLastAppliedFilterData,
]);
}, [dataMaskApplied, dataMaskSelected, filters, setDataMaskSelected]);
};
// Load filters after charts loaded
export const useInitialization = () => {
const [isInitialized, setIsInitialized] = useState<boolean>(false);
const charts = useSelector<RootState, ChartsState>(state => state.charts);
// We need to know how much charts now shown on dashboard to know how many of all charts should be loaded
let numberOfLoadingCharts = 0;
if (!isInitialized) {
numberOfLoadingCharts = document.querySelectorAll(
'[data-ui-anchor="chart"]',
).length;
}
useEffect(() => {
if (isInitialized) {
return;
}
// For some dashboards may be there are no charts on first page,
// so we check up to 1 sec if there is at least on chart to load
let filterTimeout: NodeJS.Timeout;
if (numberOfLoadingCharts === 0) {
filterTimeout = setTimeout(() => {
setIsInitialized(true);
}, 1000);
}
// @ts-ignore
if (numberOfLoadingCharts > 0 && filterTimeout !== undefined) {
clearTimeout(filterTimeout);
}
const numberOfLoadedCharts = Object.values(charts).filter(
({ chartStatus }) => chartStatus !== 'loading',
).length;
if (
numberOfLoadingCharts > 0 &&
numberOfLoadedCharts >= numberOfLoadingCharts
) {
setIsInitialized(true);
}
}, [charts, isInitialized, numberOfLoadingCharts]);
return isInitialized;
};
@@ -17,6 +17,7 @@
* under the License.
*/
import { DataMaskStateWithId } from 'src/dataMask/types';
import { Filter } from '../types';
export enum TabIds {
@@ -39,3 +40,9 @@ export function mapParentFiltersToChildren(
});
return cascadeChildren;
}
export const getOnlyExtraFormData = (data: DataMaskStateWithId) =>
Object.values(data).reduce(
(prev, next) => ({ ...prev, [next.id]: next.extraFormData }),
{},
);
@@ -23,13 +23,15 @@ import {
import React, { FC } from 'react';
import { Checkbox } from 'src/common/components';
import { FormInstance } from 'antd/lib/form';
import { getChartControlPanelRegistry } from '@superset-ui/core';
import { getChartControlPanelRegistry, t } from '@superset-ui/core';
import { Tooltip } from 'src/components/Tooltip';
import { getControlItems, setNativeFilterFieldValues } from './utils';
import { NativeFiltersForm, NativeFiltersFormItem } from '../types';
import { StyledCheckboxFormItem } from './FiltersConfigForm';
import { Filter } from '../../types';
type ControlItemsProps = {
disabled: boolean;
filterId: string;
forceUpdate: Function;
filterToEdit?: Filter;
@@ -38,6 +40,7 @@ type ControlItemsProps = {
};
const ControlItems: FC<ControlItemsProps> = ({
disabled,
forceUpdate,
form,
filterId,
@@ -59,38 +62,48 @@ const ControlItems: FC<ControlItemsProps> = ({
controlItem?.config?.renderTrigger,
)
.map(controlItem => (
<StyledCheckboxFormItem
key={controlItem.name}
name={['filters', filterId, 'controlValues', controlItem.name]}
initialValue={
filterToEdit?.controlValues?.[controlItem.name] ??
controlItem?.config?.default
<Tooltip
placement="left"
title={
controlItem.config.affectsDataMask &&
disabled &&
t('Populate "Default value" to enable this control')
}
valuePropName="checked"
colon={false}
>
<Checkbox
onChange={() => {
if (!controlItem.config.resetConfig) {
forceUpdate();
return;
}
setNativeFilterFieldValues(form, filterId, {
defaultValue: null,
});
forceUpdate();
}}
<StyledCheckboxFormItem
key={controlItem.name}
name={['filters', filterId, 'controlValues', controlItem.name]}
initialValue={
filterToEdit?.controlValues?.[controlItem.name] ??
controlItem?.config?.default
}
valuePropName="checked"
colon={false}
>
{controlItem.config.label}{' '}
{controlItem.config.description && (
<InfoTooltipWithTrigger
placement="top"
label={controlItem.config.name}
tooltip={controlItem.config.description}
/>
)}
</Checkbox>
</StyledCheckboxFormItem>
<Checkbox
disabled={controlItem.config.affectsDataMask && disabled}
onChange={() => {
if (!controlItem.config.resetConfig) {
forceUpdate();
return;
}
setNativeFilterFieldValues(form, filterId, {
defaultDataMask: null,
});
forceUpdate();
}}
>
{controlItem.config.label}{' '}
{controlItem.config.description && (
<InfoTooltipWithTrigger
placement="top"
label={controlItem.config.name}
tooltip={controlItem.config.description}
/>
)}
</Checkbox>
</StyledCheckboxFormItem>
</Tooltip>
))}
</>
);
@@ -62,7 +62,10 @@ import ControlItems from './ControlItems';
import FilterScope from './FilterScope/FilterScope';
import RemovedFilter from './RemovedFilter';
import DefaultValue from './DefaultValue';
import { getFiltersConfigModalTestId } from '../FiltersConfigModal';
import {
CASCADING_FILTERS,
getFiltersConfigModalTestId,
} from '../FiltersConfigModal';
// TODO: move styles from AdhocFilterControl to emotion and delete this ./main.less
import './main.less';
@@ -123,9 +126,7 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
const [metrics, setMetrics] = useState<Metric[]>([]);
const forceUpdate = useForceUpdate();
const [datasetDetails, setDatasetDetails] = useState<Record<string, any>>();
const formFilter = form.getFieldValue('filters')?.[filterId] || {};
const nativeFilterItems = getChartMetadataRegistry().items;
const nativeFilterVizTypes = Object.entries(nativeFilterItems)
// @ts-ignore
@@ -174,6 +175,8 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
formFilter?.filterType,
);
const isCascadingFilter = CASCADING_FILTERS.includes(formFilter?.filterType);
const isDataDirty = formFilter?.isDataDirty ?? true;
useBackendFormUpdate(form, filterId);
@@ -186,7 +189,6 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
const formData = getFormData({
datasetId: formFilter?.dataset?.value,
groupby: formFilter?.column,
defaultValue: formFilter?.defaultValue,
...formFilter,
});
setNativeFilterFieldValues(form, filterId, {
@@ -237,7 +239,6 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
const newFormData = getFormData({
datasetId,
groupby: hasColumn ? formFilter?.column : undefined,
defaultValue: formFilter?.defaultValue,
...formFilter,
});
@@ -261,6 +262,8 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
label: filter.title,
}));
const showDefaultValue = !hasDataset || (!isDataDirty && hasFilledDataset);
return (
<>
<Typography.Title level={5}>{t('Settings')}</Typography.Title>
@@ -289,7 +292,7 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
onChange={({ value }: { value: string }) => {
setNativeFilterFieldValues(form, filterId, {
filterType: value,
defaultValue: null,
defaultDataMask: null,
});
forceUpdate();
}}
@@ -319,7 +322,7 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
// We need reset column when dataset changed
if (datasetId && e?.value !== datasetId) {
setNativeFilterFieldValues(form, filterId, {
defaultValue: null,
defaultDataMask: null,
column: null,
});
}
@@ -341,10 +344,10 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
form={form}
filterId={filterId}
datasetId={datasetId}
onChange={e => {
onChange={() => {
// We need reset default value when when column changed
setNativeFilterFieldValues(form, filterId, {
defaultValue: null,
defaultDataMask: null,
});
forceUpdate();
}}
@@ -405,20 +408,22 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
hidden
initialValue={null}
/>
<StyledFormItem
name={['filters', filterId, 'parentFilter']}
label={<StyledLabel>{t('Parent filter')}</StyledLabel>}
initialValue={parentFilterOptions.find(
({ value }) => value === filterToEdit?.cascadeParentIds[0],
)}
data-test="parent-filter-input"
>
<Select
placeholder={t('None')}
options={parentFilterOptions}
isClearable
/>
</StyledFormItem>
{isCascadingFilter && (
<StyledFormItem
name={['filters', filterId, 'parentFilter']}
label={<StyledLabel>{t('Parent filter')}</StyledLabel>}
initialValue={parentFilterOptions.find(
({ value }) => value === filterToEdit?.cascadeParentIds[0],
)}
data-test="parent-filter-input"
>
<Select
placeholder={t('None')}
options={parentFilterOptions}
isClearable
/>
</StyledFormItem>
)}
<StyledContainer>
<StyledFormItem className="bottom" label={<StyledLabel />}>
{hasDataset && hasFilledDataset && (
@@ -428,16 +433,16 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
)}
</StyledFormItem>
<StyledFormItem
name={['filters', filterId, 'defaultValue']}
initialValue={filterToEdit?.defaultValue}
name={['filters', filterId, 'defaultDataMask']}
initialValue={filterToEdit?.defaultDataMask}
data-test="default-input"
label={<StyledLabel>{t('Default Value')}</StyledLabel>}
>
{(!hasDataset || (!isDataDirty && hasFilledDataset)) && (
{showDefaultValue ? (
<DefaultValue
setDataMask={({ filterState }) => {
setDataMask={dataMask => {
setNativeFilterFieldValues(form, filterId, {
defaultValue: filterState?.value,
defaultDataMask: dataMask,
});
forceUpdate();
}}
@@ -446,6 +451,10 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
form={form}
formData={newFormData}
/>
) : hasFilledDataset ? (
t('Click "Populate" to get "Default Value" ->')
) : (
t('Fill all required fields to enable "Default Value"')
)}
</StyledFormItem>
</StyledContainer>
@@ -460,6 +469,7 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
</Checkbox>
</StyledCheckboxFormItem>
<ControlItems
disabled={!showDefaultValue}
filterToEdit={filterToEdit}
formFilter={formFilter}
filterId={filterId}
@@ -62,6 +62,7 @@ export interface FiltersConfigModalProps {
onSave: (filterConfig: FilterConfiguration) => Promise<void>;
onCancel: () => void;
}
export const CASCADING_FILTERS = ['filter_select'];
/**
* This is the modal to configure all the dashboard-native filters.
@@ -170,6 +171,9 @@ export function FiltersConfigModal({
const getParentFilters = (id: string) =>
filterIds
.filter(filterId => filterId !== id && !removedFilters[filterId])
.filter(filterId =>
CASCADING_FILTERS.includes(formValues.filters[filterId]?.filterType),
)
.map(id => ({
id,
title: getFilterTitle(id),
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { AdhocFilter } from '@superset-ui/core';
import { AdhocFilter, DataMask } from '@superset-ui/core';
import { Scope } from '../types';
export interface NativeFiltersFormItem {
@@ -32,6 +32,7 @@ export interface NativeFiltersFormItem {
[key: string]: any;
};
defaultValue: any;
defaultDataMask: DataMask;
parentFilter: {
value: string;
label: string;
@@ -18,6 +18,7 @@
*/
import { FormInstance } from 'antd/lib/form';
import shortid from 'shortid';
import { getInitialDataMask } from 'src/dataMask/reducer';
import { FilterRemoval, NativeFiltersForm } from './types';
import { Filter, FilterConfiguration, Target } from '../types';
@@ -41,13 +42,20 @@ export const validateForm = async (
errors: [error],
};
form.setFields([fieldError]);
// eslint-disable-next-line no-throw-literal
throw { errorFields: [fieldError] };
};
try {
const formValues = (await form.validateFields()) as NativeFiltersForm;
let formValues: NativeFiltersForm;
try {
formValues = (await form.validateFields()) as NativeFiltersForm;
} catch (error) {
// In Jest tests in chain of tests, Ant generate `outOfDate` error so need to catch it here
if (!error?.errorFields?.length && error?.outOfDate) {
formValues = error.values;
} else {
throw error;
}
}
const validateInstant = (filterId: string) => {
const isInstant = formValues.filters[filterId]
? formValues.filters[filterId].isInstant
@@ -148,7 +156,7 @@ export const createHandleSave = (
filterType: formInputs.filterType,
// for now there will only ever be one target
targets: [target],
defaultValue: formInputs.defaultValue || null,
defaultDataMask: formInputs.defaultDataMask ?? getInitialDataMask(),
cascadeParentIds: formInputs.parentFilter
? [formInputs.parentFilter.value]
: [],
@@ -41,8 +41,7 @@ export interface Target {
export interface Filter {
cascadeParentIds: string[];
defaultValue: any;
dataMask?: DataMask;
defaultDataMask: DataMask;
isInstant: boolean;
id: string; // randomly generated at filter creation
name: string;
@@ -28,6 +28,7 @@ import {
import { Charts } from 'src/dashboard/types';
import { RefObject } from 'react';
import { DataMaskStateWithId } from 'src/dataMask/types';
import extractUrlParams from 'src/dashboard/util/extractUrlParams';
import { Filter } from './types';
export const getFormData = ({
@@ -35,7 +36,7 @@ export const getFormData = ({
cascadingFilters = {},
groupby,
inputRef,
defaultValue,
defaultDataMask,
controlValues,
filterType,
sortMetric,
@@ -73,10 +74,10 @@ export const getFormData = ({
metrics: ['count'],
row_limit: 10000,
showSearch: true,
defaultValue,
defaultValue: defaultDataMask?.filterState?.value,
time_range,
time_range_endpoints: ['inclusive', 'exclusive'],
url_params: {},
url_params: extractUrlParams('regular'),
viz_type: filterType,
inputRef,
};
@@ -18,9 +18,7 @@
*/
import { bindActionCreators, Dispatch } from 'redux';
import { connect } from 'react-redux';
import Dashboard from '../components/Dashboard';
import {
addSliceToDashboard,
removeSliceFromDashboard,
@@ -66,11 +64,12 @@ function mapStateToProps(state: RootState) {
// eslint-disable-next-line camelcase
chartConfiguration: dashboardInfo.metadata?.chart_configuration,
nativeFilters: nativeFilters.filters,
dataMask: getRelevantDataMask(dataMask, 'isApplied'),
dataMask,
layout: dashboardLayout.present,
}),
},
ownDataCharts: getRelevantDataMask(dataMask, 'ownState', 'ownState'),
chartConfiguration: dashboardInfo.metadata?.chart_configuration,
ownDataCharts: getRelevantDataMask(dataMask, 'ownState'),
slices: sliceEntities.slices,
layout: dashboardLayout.present,
impressionId,
@@ -21,7 +21,6 @@ import {
SAVE_FILTER_SETS,
SET_FILTER_CONFIG_COMPLETE,
SET_FILTER_SETS_CONFIG_COMPLETE,
SET_FILTERS_INITIALIZED,
} from 'src/dashboard/actions/nativeFilters';
import { FilterSet, NativeFiltersState } from './types';
import { FilterConfiguration } from '../components/nativeFilters/types';
@@ -36,9 +35,7 @@ export function getInitialState({
filterConfig?: FilterConfiguration;
state?: NativeFiltersState;
}): NativeFiltersState {
const state: Partial<NativeFiltersState> = {
isInitialized: prevState?.isInitialized,
};
const state: Partial<NativeFiltersState> = {};
const filters = {};
if (filterConfig) {
@@ -66,7 +63,6 @@ export function getInitialState({
export default function nativeFilterReducer(
state: NativeFiltersState = {
isInitialized: false,
filters: {},
filterSets: {},
},
@@ -95,12 +91,6 @@ export default function nativeFilterReducer(
case SET_FILTER_CONFIG_COMPLETE:
return getInitialState({ filterConfig: action.filterConfig, state });
case SET_FILTERS_INITIALIZED:
return {
...state,
isInitialized: true,
};
case SET_FILTER_SETS_CONFIG_COMPLETE:
return getInitialState({
filterSetsConfig: action.filterSetsConfig,
@@ -97,7 +97,6 @@ export type Filters = {
};
export type NativeFiltersState = {
isInitialized: boolean;
filters: Filters;
filterSets: FilterSets;
};
+7 -5
View File
@@ -16,11 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ChartProps, JsonObject } from '@superset-ui/core';
import { ChartProps, ExtraFormData, JsonObject } from '@superset-ui/core';
import { chart } from 'src/chart/chartReducer';
import componentTypes from 'src/dashboard/util/componentTypes';
import { DataMaskStateWithId } from '../dataMask/types';
import { NativeFiltersState } from './reducers/types';
import { ChartState } from '../explore/types';
export type ChartReducerInitialState = typeof chart;
@@ -34,8 +35,7 @@ export interface ChartQueryPayload extends Partial<ChartReducerInitialState> {
}
/** Chart state of redux */
export type Chart = {
id: number;
export type Chart = ChartState & {
formData: {
viz_type: string;
};
@@ -54,11 +54,13 @@ export type DashboardInfo = {
metadata: { show_native_filters: boolean; chart_configuration: JsonObject };
};
export type ChartsState = { [key: string]: Chart };
/** Root state of redux */
export type RootState = {
datasources: JsonObject;
sliceEntities: JsonObject;
charts: { [key: string]: Chart };
charts: ChartsState;
dashboardLayout: DashboardLayoutState;
dashboardFilters: {};
dashboardState: DashboardState;
@@ -95,7 +97,7 @@ export type LayoutItem = {
type ActiveFilter = {
scope: number[];
values: any[];
values: ExtraFormData;
};
export type ActiveFilters = {
@@ -20,7 +20,7 @@ import { DataMaskStateWithId } from 'src/dataMask/types';
import { JsonObject } from '@superset-ui/core';
import { CHART_TYPE } from './componentTypes';
import { Scope } from '../components/nativeFilters/types';
import { ActiveFilters, LayoutItem } from '../types';
import { ActiveFilters, Layout, LayoutItem } from '../types';
import { ChartConfiguration, Filters } from '../reducers/types';
import { DASHBOARD_ROOT_ID } from './constants';
@@ -51,12 +51,11 @@ export const findAffectedCharts = ({
// eslint-disable-next-line no-param-reassign
activeFilters[filterId] = {
scope: [],
values: [],
values: extraFormData,
};
}
// Add not excluded chart scopes(to know what charts refresh) and values(refresh only if its value changed)
activeFilters[filterId].scope.push(chartId);
activeFilters[filterId].values.push(extraFormData);
return;
}
// If child is not chart, recursive iterate over its children
@@ -74,11 +73,10 @@ export const findAffectedCharts = ({
export const getRelevantDataMask = (
dataMask: DataMaskStateWithId,
filterBy: string,
prop?: string,
prop: string,
): JsonObject | DataMaskStateWithId =>
Object.values(dataMask)
.filter(item => item[filterBy])
.filter(item => item[prop])
.reduce(
(prev, next) => ({ ...prev, [next.id]: prop ? next[prop] : next }),
{},
@@ -93,7 +91,7 @@ export const getAllActiveFilters = ({
chartConfiguration: ChartConfiguration;
dataMask: DataMaskStateWithId;
nativeFilters: Filters;
layout: { [key: string]: LayoutItem };
layout: Layout;
}): ActiveFilters => {
const activeFilters = {};
@@ -0,0 +1,53 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import extractUrlParams from './extractUrlParams';
const originalWindowLocation = window.location;
describe('extractUrlParams', () => {
beforeAll(() => {
// @ts-ignore
delete window.location;
// @ts-ignore
window.location = { search: '?edit=true&abc=123' };
});
afterAll(() => {
window.location = originalWindowLocation;
});
it('returns all urlParams', () => {
expect(extractUrlParams('all')).toEqual({
edit: 'true',
abc: '123',
});
});
it('returns reserved urlParams', () => {
expect(extractUrlParams('reserved')).toEqual({
edit: 'true',
});
});
it('returns regular urlParams', () => {
expect(extractUrlParams('regular')).toEqual({
abc: '123',
});
});
});
@@ -0,0 +1,49 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import querystring from 'query-string';
import { JsonObject } from '@superset-ui/core';
const reservedQueryParams = new Set(['standalone', 'edit']);
export type UrlParamType = 'reserved' | 'regular' | 'all';
/**
* Returns the url params that are used to customize queries
*/
export default function extractUrlParams(
urlParamType: UrlParamType,
): JsonObject {
const queryParams = querystring.parse(window.location.search);
return Object.entries(queryParams).reduce((acc, [key, value]) => {
if (
(urlParamType === 'regular' && reservedQueryParams.has(key)) ||
(urlParamType === 'reserved' && !reservedQueryParams.has(key))
)
return acc;
// if multiple url params share the same key (?foo=bar&foo=baz), they will appear as an array.
// Only one value can be used for a given query param, so we just take the first one.
if (Array.isArray(value)) {
return {
...acc,
[key]: value[0],
};
}
return { ...acc, [key]: value };
}, {});
}
+12 -1
View File
@@ -19,6 +19,7 @@
import { DataMask } from '@superset-ui/core';
import { FilterConfiguration } from '../dashboard/components/nativeFilters/types';
import { FeatureFlag, isFeatureEnabled } from '../featureFlags';
import { Filters } from '../dashboard/reducers/types';
export const UPDATE_DATA_MASK = 'UPDATE_DATA_MASK';
export interface UpdateDataMask {
@@ -33,6 +34,7 @@ export const SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE =
export interface SetDataMaskForFilterConfigComplete {
type: typeof SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE;
filterConfig: FilterConfiguration;
filters?: Filters;
}
export const SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL =
@@ -42,7 +44,16 @@ export interface SetDataMaskForFilterConfigFail {
type: typeof SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL;
filterConfig: FilterConfiguration;
}
export function setDataMaskForFilterConfigComplete(
filterConfig: FilterConfiguration,
filters?: Filters,
): SetDataMaskForFilterConfigComplete {
return {
type: SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE,
filterConfig,
filters,
};
}
export function updateDataMask(
filterId: string,
dataMask: DataMask,
+85 -18
View File
@@ -20,23 +20,75 @@
/* eslint-disable no-param-reassign */
// <- When we work with Immer, we need reassign, so disabling lint
import produce from 'immer';
import { DataMask, FeatureFlag } from '@superset-ui/core';
import { NATIVE_FILTER_PREFIX } from 'src/dashboard/components/nativeFilters/FiltersConfigModal/utils';
import { HYDRATE_DASHBOARD } from 'src/dashboard/actions/hydrate';
import { isFeatureEnabled } from 'src/featureFlags';
import { DataMaskStateWithId, DataMaskWithId } from './types';
import {
AnyDataMaskAction,
SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE,
UPDATE_DATA_MASK,
} from './actions';
import { NATIVE_FILTER_PREFIX } from '../dashboard/components/nativeFilters/FiltersConfigModal/utils';
import { Filter } from '../dashboard/components/nativeFilters/types';
import {
Filter,
FilterConfiguration,
} from '../dashboard/components/nativeFilters/types';
import { areObjectsEqual } from '../reduxUtils';
import { Filters } from '../dashboard/reducers/types';
export function getInitialDataMask(id?: string): DataMask;
export function getInitialDataMask(id: string): DataMaskWithId {
let otherProps = {};
if (id) {
otherProps = {
id,
};
}
return {
id,
...otherProps,
extraFormData: {},
filterState: {},
filterState: {
value: null,
},
ownState: {},
isApplied: false,
};
} as DataMaskWithId;
}
function fillNativeFilters(
filterConfig: FilterConfiguration,
mergedDataMask: DataMaskStateWithId,
draftDataMask: DataMaskStateWithId,
currentFilters?: Filters,
) {
filterConfig.forEach((filter: Filter) => {
mergedDataMask[filter.id] = {
...getInitialDataMask(filter.id), // take initial data
...filter.defaultDataMask, // if something new came from BE - take it
...draftDataMask[filter.id], // keep local filter data
};
// if we came from filters config modal and particular filters changed take it's dataMask
if (
currentFilters &&
!areObjectsEqual(
filter.defaultDataMask,
currentFilters[filter.id]?.defaultDataMask,
{ ignoreUndefined: true },
)
) {
mergedDataMask[filter.id] = {
...mergedDataMask[filter.id],
...filter.defaultDataMask,
};
}
});
// Get back all other non-native filters
Object.values(draftDataMask).forEach(filter => {
if (!String(filter?.id).startsWith(NATIVE_FILTER_PREFIX)) {
mergedDataMask[filter?.id] = filter;
}
});
}
const dataMaskReducer = produce(
@@ -48,21 +100,36 @@ const dataMaskReducer = produce(
...getInitialDataMask(action.filterId),
...draft[action.filterId],
...action.dataMask,
isApplied: true,
};
return draft;
// TODO: update hydrate to .ts
// @ts-ignore
case HYDRATE_DASHBOARD:
if (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)) {
Object.keys(
// @ts-ignore
action.data.dashboardInfo?.metadata?.chart_configuration,
).forEach(id => {
cleanState[id] = {
...getInitialDataMask(id), // take initial data
};
});
}
fillNativeFilters(
// @ts-ignore
action.data.dashboardInfo?.metadata?.native_filter_configuration ??
[],
cleanState,
draft,
);
return cleanState;
case SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE:
(action.filterConfig ?? []).forEach((filter: Filter) => {
cleanState[filter.id] =
draft[filter.id] ?? getInitialDataMask(filter.id);
});
// Get back all other non-native filters
Object.values(draft).forEach(filter => {
if (!String(filter?.id).startsWith(NATIVE_FILTER_PREFIX)) {
cleanState[filter?.id] = filter;
}
});
fillNativeFilters(
action.filterConfig ?? [],
cleanState,
draft,
action.filters,
);
return cleanState;
default:
+1 -1
View File
@@ -25,5 +25,5 @@ export enum DataMaskType {
export type DataMaskState = { [id: string]: DataMask };
export type DataMaskWithId = { id: string; isApplied?: boolean } & DataMask;
export type DataMaskWithId = { id: string } & DataMask;
export type DataMaskStateWithId = { [filterId: string]: DataMaskWithId };
@@ -57,7 +57,7 @@ export function getHostName(allowDomainSharding = false) {
return availableDomains[currentIndex];
}
export function getAnnotationJsonUrl(slice_id, form_data, isNative) {
export function getAnnotationJsonUrl(slice_id, form_data, isNative, force) {
if (slice_id === null || slice_id === undefined) {
return null;
}
@@ -69,6 +69,7 @@ export function getAnnotationJsonUrl(slice_id, form_data, isNative) {
form_data: safeStringify(form_data, (key, value) =>
value === null ? undefined : value,
),
force,
})
.toString();
}
@@ -37,6 +37,7 @@ const config: ControlPanelConfig = {
type: 'CheckboxControl',
label: t('Multiple select'),
default: multiSelect,
affectsDataMask: true,
resetConfig: true,
renderTrigger: true,
description: t('Allow selecting multiple values'),
@@ -0,0 +1,59 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import buildQuery from './buildQuery';
describe('Select buildQuery', () => {
const formData = {
datasource: '5__table',
groupby: ['my_col'],
viz_type: 'filter_select',
sortAscending: false,
sortMetric: undefined,
filters: undefined,
enableEmptyFilter: false,
inverseSelection: false,
multiSelect: false,
defaultToFirstItem: false,
height: 100,
width: 100,
};
it('should build a default query', () => {
const queryContext = buildQuery(formData);
expect(queryContext.queries.length).toEqual(1);
const [query] = queryContext.queries;
expect(query.groupby).toEqual(['my_col']);
expect(query.metrics).toEqual([]);
expect(query.apply_fetch_values_predicate).toEqual(true);
expect(query.orderby).toEqual([]);
});
it('should handle sort metric correctly', () => {
const queryContext = buildQuery({
...formData,
sortMetric: 'my_metric',
sortAscending: false,
});
expect(queryContext.queries.length).toEqual(1);
const [query] = queryContext.queries;
expect(query.groupby).toEqual(['my_col']);
expect(query.metrics).toEqual(['my_metric']);
expect(query.orderby).toEqual([['my_metric', false]]);
});
});
@@ -30,6 +30,7 @@ export default function buildQuery(formData: PluginFilterSelectQueryFormData) {
...baseQueryObject,
apply_fetch_values_predicate: true,
groupby: columns,
metrics: sortMetric ? [sortMetric] : [],
filters: filters.concat(
columns.map(column => ({ col: column, op: 'IS NOT NULL' })),
),
@@ -61,6 +61,7 @@ const config: ControlPanelConfig = {
label: t('Multiple select'),
default: multiSelect,
resetConfig: true,
affectsDataMask: true,
renderTrigger: true,
description: t('Allow selecting multiple values'),
},
@@ -89,6 +90,7 @@ const config: ControlPanelConfig = {
label: t('Default to first item'),
default: defaultToFirstItem,
resetConfig: true,
affectsDataMask: true,
renderTrigger: true,
description: t('Select first item by default'),
},
@@ -100,6 +102,7 @@ const config: ControlPanelConfig = {
config: {
type: 'CheckboxControl',
renderTrigger: true,
affectsDataMask: true,
label: t('Inverse selection'),
default: inverseSelection,
description: t('Exclude selected values'),
+13 -14
View File
@@ -41,17 +41,14 @@ export const getSelectExtraFormData = (
sqlExpression: '1 = 0',
},
];
} else {
extra.filters =
value === undefined || value === null || value.length === 0
? []
: [
{
col,
op: inverseSelection ? ('NOT IN' as const) : ('IN' as const),
val: value,
},
];
} else if (value !== undefined && value !== null && value.length !== 0) {
extra.filters = [
{
col,
op: inverseSelection ? ('NOT IN' as const) : ('IN' as const),
val: value,
},
];
}
return extra;
};
@@ -69,9 +66,11 @@ export const getRangeExtraFormData = (
filters.push({ col, op: '<=', val: upper });
}
return {
filters,
};
return filters.length
? {
filters,
}
: {};
};
export interface DataRecordValueFormatter {
+9 -1
View File
@@ -295,7 +295,6 @@ const config = {
// Both `@emotion/core` and `@superset-ui/core` remember some globals within
// module after imported, which will not be available everywhere if two
// different copies of the same module are imported in different places.
'@emotion/core': path.resolve(APP_DIR, './node_modules/@emotion/core'),
'@superset-ui/core': path.resolve(
APP_DIR,
'./node_modules/@superset-ui/core',
@@ -484,6 +483,15 @@ if (isDevMode) {
contentBase: path.join(process.cwd(), '../static/assets'),
};
// make sure to use @emotion/* modules in the root directory
fs.readdirSync(path.resolve(APP_DIR, './node_modules/@emotion'), pkg => {
config.resolve.alias[pkg] = path.resolve(
APP_DIR,
'./node_modules/@emotion',
pkg,
);
});
// find all the symlinked plugins and use their source code for imports
let hasSymlink = false;
Object.entries(packageConfig.dependencies).forEach(([pkg, version]) => {
+4 -2
View File
@@ -569,7 +569,9 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
return {}
@classmethod
def apply_limit_to_sql(cls, sql: str, limit: int, database: "Database") -> str:
def apply_limit_to_sql(
cls, sql: str, limit: int, database: "Database", force: bool = False
) -> str:
"""
Alters the SQL statement to apply a LIMIT clause
@@ -590,7 +592,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
if cls.limit_method == LimitMethod.FORCE_LIMIT:
parsed_query = sql_parse.ParsedQuery(sql)
sql = parsed_query.set_or_update_query_limit(limit)
sql = parsed_query.set_or_update_query_limit(limit, force=force)
return sql
+15 -1
View File
@@ -14,10 +14,14 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from datetime import datetime
from typing import Optional
from superset.db_engine_specs.base import BaseEngineSpec
from superset.utils import core as utils
class DremioBaseEngineSpec(BaseEngineSpec):
class DremioEngineSpec(BaseEngineSpec):
engine = "dremio"
engine_name = "Dremio"
@@ -37,3 +41,13 @@ class DremioBaseEngineSpec(BaseEngineSpec):
@classmethod
def epoch_to_dttm(cls) -> str:
return "TO_DATE({col})"
@classmethod
def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]:
tt = target_type.upper()
if tt == utils.TemporalType.DATE:
return f"TO_DATE('{dttm.date().isoformat()}', 'YYYY-MM-DD')"
if tt == utils.TemporalType.TIMESTAMP:
dttm_formatted = dttm.isoformat(sep=" ", timespec="milliseconds")
return f"""TO_TIMESTAMP('{dttm_formatted}', 'YYYY-MM-DD HH24:MI:SS.FFF')"""
return None
+1
View File
@@ -100,6 +100,7 @@ def load_country_map_data(only_metadata: bool = False, force: bool = False) -> N
"optionName": "metric_112342",
},
"row_limit": 500000,
"select_country": "france",
}
print("Creating a slice")
@@ -0,0 +1,163 @@
# 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.
"""migrate native filters to new schema
Revision ID: f1410ed7ec95
Revises: d416d0d715cc
Create Date: 2021-04-29 15:32:21.939018
"""
# revision identifiers, used by Alembic.
revision = "f1410ed7ec95"
down_revision = "d416d0d715cc"
import json
from typing import Any, Dict, Iterable, Tuple
from alembic import op
from sqlalchemy import Column, Integer, Text
from sqlalchemy.ext.declarative import declarative_base
from superset import db
Base = declarative_base()
class Dashboard(Base):
"""Declarative class to do query in upgrade"""
__tablename__ = "dashboards"
id = Column(Integer, primary_key=True)
json_metadata = Column(Text)
def upgrade_filters(native_filters: Iterable[Dict[str, Any]]) -> int:
"""
Move `defaultValue` into `defaultDataMask.filterState`
"""
changed_filters = 0
for native_filter in native_filters:
default_value = native_filter.pop("defaultValue", None)
if default_value is not None:
changed_filters += 1
default_data_mask = {}
default_data_mask["filterState"] = {"value": default_value}
native_filter["defaultDataMask"] = default_data_mask
return changed_filters
def downgrade_filters(native_filters: Iterable[Dict[str, Any]]) -> int:
"""
Move `defaultDataMask.filterState` into `defaultValue`
"""
changed_filters = 0
for native_filter in native_filters:
default_data_mask = native_filter.pop("defaultDataMask", {})
filter_state = default_data_mask.get("filterState")
if filter_state is not None:
changed_filters += 1
value = filter_state["value"]
native_filter["defaultValue"] = value
return changed_filters
def upgrade_dashboard(dashboard: Dict[str, Any]) -> Tuple[int, int]:
changed_filters, changed_filter_sets = 0, 0
# upgrade native select filter metadata
# upgrade native select filter metadata
native_filters = dashboard.get("native_filter_configuration")
if native_filters:
changed_filters += upgrade_filters(native_filters)
# upgrade filter sets
filter_sets = dashboard.get("filter_sets_configuration", [])
for filter_set in filter_sets:
if upgrade_filters(filter_set.get("nativeFilters", {}).values()):
changed_filter_sets += 1
return changed_filters, changed_filter_sets
def upgrade():
bind = op.get_bind()
session = db.Session(bind=bind)
dashboards = (
session.query(Dashboard)
.filter(Dashboard.json_metadata.like('%"native_filter_configuration"%'))
.all()
)
changed_filters, changed_filter_sets = 0, 0
for dashboard in dashboards:
try:
json_metadata = json.loads(dashboard.json_metadata)
dashboard.json_metadata = json.dumps(json_metadata, sort_keys=True)
upgrades = upgrade_dashboard(json_metadata)
changed_filters += upgrades[0]
changed_filter_sets += upgrades[1]
dashboard.json_metadata = json.dumps(json_metadata, sort_keys=True)
except Exception as e:
print(f"Parsing json_metadata for dashboard {dashboard.id} failed.")
raise e
session.commit()
session.close()
print(f"Upgraded {changed_filters} filters and {changed_filter_sets} filter sets.")
def downgrade_dashboard(dashboard: Dict[str, Any]) -> Tuple[int, int]:
changed_filters, changed_filter_sets = 0, 0
# upgrade native select filter metadata
native_filters = dashboard.get("native_filter_configuration")
if native_filters:
changed_filters += downgrade_filters(native_filters)
# upgrade filter sets
filter_sets = dashboard.get("filter_sets_configuration", [])
for filter_set in filter_sets:
if downgrade_filters(filter_set.get("nativeFilters", {}).values()):
changed_filter_sets += 1
return changed_filters, changed_filter_sets
def downgrade():
bind = op.get_bind()
session = db.Session(bind=bind)
dashboards = (
session.query(Dashboard)
.filter(Dashboard.json_metadata.like('%"native_filter_configuration"%'))
.all()
)
changed_filters, changed_filter_sets = 0, 0
for dashboard in dashboards:
try:
json_metadata = json.loads(dashboard.json_metadata)
downgrades = downgrade_dashboard(json_metadata)
changed_filters += downgrades[0]
changed_filter_sets += downgrades[1]
dashboard.json_metadata = json.dumps(json_metadata, sort_keys=True)
except Exception as e:
print(f"Parsing json_metadata for dashboard {dashboard.id} failed.")
raise e
session.commit()
session.close()
print(
f"Downgraded {changed_filters} filters and {changed_filter_sets} filter sets."
)
+4 -2
View File
@@ -437,8 +437,10 @@ class Database(
cols=cols,
)
def apply_limit_to_sql(self, sql: str, limit: int = 1000) -> str:
return self.db_engine_spec.apply_limit_to_sql(sql, limit, self)
def apply_limit_to_sql(
self, sql: str, limit: int = 1000, force: bool = False
) -> str:
return self.db_engine_spec.apply_limit_to_sql(sql, limit, self, force=force)
def safe_sqlalchemy_uri(self) -> str:
return self.sqlalchemy_uri
+14
View File
@@ -15,6 +15,7 @@
# specific language governing permissions and limitations
# under the License.
"""A collection of ORM sqlalchemy models for SQL Lab"""
import enum
import re
from datetime import datetime
from typing import Any, Dict, List
@@ -29,6 +30,7 @@ from sqlalchemy import (
Boolean,
Column,
DateTime,
Enum,
ForeignKey,
Integer,
Numeric,
@@ -49,6 +51,14 @@ from superset.sql_parse import CtasMethod, ParsedQuery, Table
from superset.utils.core import QueryStatus, user_label
class LimitingFactor(str, enum.Enum):
QUERY = "QUERY"
DROPDOWN = "DROPDOWN"
QUERY_AND_DROPDOWN = "QUERY_AND_DROPDOWN"
NOT_LIMITED = "NOT_LIMITED"
UNKNOWN = "UNKNOWN"
class Query(Model, ExtraJSONMixin):
"""ORM model for SQL query
@@ -76,6 +86,9 @@ class Query(Model, ExtraJSONMixin):
executed_sql = Column(Text)
# Could be configured in the superset config.
limit = Column(Integer)
limiting_factor = Column(
Enum(LimitingFactor), server_default=LimitingFactor.UNKNOWN
)
select_as_cta = Column(Boolean)
select_as_cta_used = Column(Boolean, default=False)
ctas_method = Column(String(16), default=CtasMethod.TABLE)
@@ -120,6 +133,7 @@ class Query(Model, ExtraJSONMixin):
"id": self.client_id,
"queryId": self.id,
"limit": self.limit,
"limitingFactor": self.limiting_factor,
"progress": self.progress,
"rows": self.rows,
"schema": self.schema,
+18 -6
View File
@@ -36,7 +36,7 @@ from superset.dataframe import df_to_records
from superset.db_engine_specs import BaseEngineSpec
from superset.extensions import celery_app
from superset.models.core import Database
from superset.models.sql_lab import Query
from superset.models.sql_lab import LimitingFactor, Query
from superset.result_set import SupersetResultSet
from superset.sql_parse import CtasMethod, ParsedQuery
from superset.utils.celery import session_scope
@@ -175,7 +175,7 @@ def get_sql_results( # pylint: disable=too-many-arguments
return handle_query_error(str(ex), query, session)
# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, too-many-locals
def execute_sql_statement(
sql_statement: str,
query: Query,
@@ -190,6 +190,10 @@ def execute_sql_statement(
db_engine_spec = database.db_engine_spec
parsed_query = ParsedQuery(sql_statement)
sql = parsed_query.stripped()
# This is a test to see if the query is being
# limited by either the dropdown or the sql.
# We are testing to see if more rows exist than the limit.
increased_limit = None if query.limit is None else query.limit + 1
if not db_engine_spec.is_readonly_query(parsed_query) and not database.allow_dml:
raise SqlLabSecurityException(
@@ -215,12 +219,16 @@ def execute_sql_statement(
if SQL_MAX_ROW and (not query.limit or query.limit > SQL_MAX_ROW):
query.limit = SQL_MAX_ROW
if query.limit:
sql = database.apply_limit_to_sql(sql, query.limit)
# We are fetching one more than the requested limit in order
# to test whether there are more rows than the limit.
# Later, the extra row will be dropped before sending
# the results back to the user.
sql = database.apply_limit_to_sql(sql, increased_limit, force=True)
# Hook to allow environment-specific mutation (usually comments) to the SQL
sql = SQL_QUERY_MUTATOR(sql, user_name, security_manager, database)
try:
query.executed_sql = sql
if log_query:
log_query(
query.database.sqlalchemy_uri,
@@ -231,7 +239,6 @@ def execute_sql_statement(
security_manager,
log_params,
)
query.executed_sql = sql
session.commit()
with stats_timing("sqllab.query.time_executing_query", stats_logger):
logger.debug("Query %d: Running query: %s", query.id, sql)
@@ -245,7 +252,12 @@ def execute_sql_statement(
query.id,
str(query.to_dict()),
)
data = db_engine_spec.fetch_data(cursor, query.limit)
data = db_engine_spec.fetch_data(cursor, increased_limit)
if query.limit is None or len(data) <= query.limit:
query.limiting_factor = LimitingFactor.NOT_LIMITED
else:
# return 1 row less than increased_query
data = data[:-1]
except Exception as ex:
logger.error("Query %d: %s", query.id, type(ex))
logger.debug("Query %d: %s", query.id, ex)
+3 -3
View File
@@ -311,7 +311,7 @@ class ParsedQuery:
if any(not self._is_identifier(token2) for token2 in item.tokens):
self._extract_from_token(item)
def set_or_update_query_limit(self, new_limit: int) -> str:
def set_or_update_query_limit(self, new_limit: int, force: bool = False) -> str:
"""Returns the query with the specified limit.
Does not change the underlying query if user did not apply the limit,
@@ -332,8 +332,8 @@ class ParsedQuery:
break
_, limit = statement.token_next(idx=limit_pos)
# Override the limit only when it exceeds the configured value.
if limit.ttype == sqlparse.tokens.Literal.Number.Integer and new_limit < int(
limit.value
if limit.ttype == sqlparse.tokens.Literal.Number.Integer and (
force or new_limit < int(limit.value)
):
limit.value = new_limit
elif limit.is_group:
+32
View File
@@ -1681,3 +1681,35 @@ def normalize_dttm_col(
df[DTTM_ALIAS] += timedelta(hours=offset)
if time_shift is not None:
df[DTTM_ALIAS] += time_shift
def parse_boolean_string(bool_str: Optional[str]) -> bool:
"""
Convert a string representation of a true/false value into a boolean
>>> parse_boolean_string(None)
False
>>> parse_boolean_string('false')
False
>>> parse_boolean_string('true')
True
>>> parse_boolean_string('False')
False
>>> parse_boolean_string('True')
True
>>> parse_boolean_string('foo')
False
>>> parse_boolean_string('0')
False
>>> parse_boolean_string('1')
True
:param bool_str: string representation of a value that is assumed to be boolean
:return: parsed boolean value
"""
if bool_str is None:
return False
try:
return bool(strtobool(bool_str.lower()))
except ValueError:
return False
+31 -7
View File
@@ -90,7 +90,7 @@ from superset.models.core import Database, FavStar, Log
from superset.models.dashboard import Dashboard
from superset.models.datasource_access_request import DatasourceAccessRequest
from superset.models.slice import Slice
from superset.models.sql_lab import Query, TabState
from superset.models.sql_lab import LimitingFactor, Query, TabState
from superset.models.user_attributes import UserAttribute
from superset.queries.dao import QueryDAO
from superset.security.analytics_db_safety import check_sqlalchemy_uri
@@ -481,6 +481,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
self, layer_id: int
) -> FlaskResponse:
form_data = get_form_data()[0]
force = utils.parse_boolean_string(request.args.get("force"))
form_data["layer_id"] = layer_id
form_data["filters"] = [{"col": "layer_id", "op": "==", "val": layer_id}]
# Set all_columns to ensure the TableViz returns the necessary columns to the
@@ -499,7 +501,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
"changed_by_fk",
]
datasource = AnnotationDatasource()
viz_obj = viz.viz_types["table"](datasource, form_data=form_data, force=False)
viz_obj = viz.viz_types["table"](datasource, form_data=form_data, force=force)
payload = viz_obj.get_payload()
return data_payload_response(*viz_obj.payload_json_and_has_error(payload))
@@ -822,7 +824,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
bootstrap_data, default=utils.pessimistic_json_iso_dttm_ser
),
entry="explore",
title=title,
title=title.__str__(),
standalone_mode=standalone_mode,
)
@@ -2393,6 +2395,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
# Update saved query if needed
QueryDAO.update_saved_query_exec_info(query_id)
# TODO: set LimitingFactor to display?
payload = json.dumps(
apply_display_max_row_limit(data),
default=utils.pessimistic_json_iso_dttm_ser,
@@ -2548,6 +2551,12 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
if not (config.get("SQLLAB_CTAS_NO_LIMIT") and select_as_cta):
# set LIMIT after template processing
limits = [mydb.db_engine_spec.get_limit_from_sql(rendered_query), limit]
if limits[0] is None or limits[0] > limits[1]:
query.limiting_factor = LimitingFactor.DROPDOWN
elif limits[1] > limits[0]:
query.limiting_factor = LimitingFactor.QUERY
else: # limits[0] == limits[1]
query.limiting_factor = LimitingFactor.QUERY_AND_DROPDOWN
query.limit = min(lim for lim in limits if lim is not None)
# Flag for whether or not to expand data
@@ -2571,7 +2580,9 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@has_access
@event_logger.log_this
@expose("/csv/<client_id>")
def csv(self, client_id: str) -> FlaskResponse: # pylint: disable=no-self-use
def csv( # pylint: disable=no-self-use,too-many-locals
self, client_id: str
) -> FlaskResponse:
"""Download the query results as csv."""
logger.info("Exporting CSV file [%s]", client_id)
query = db.session.query(Query).filter_by(client_id=client_id).one()
@@ -2599,8 +2610,21 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
logger.info("Using pandas to convert to CSV")
else:
logger.info("Running a query to turn into CSV")
sql = query.select_sql or query.executed_sql
df = query.database.get_df(sql, query.schema)
if query.select_sql:
sql = query.select_sql
limit = None
else:
sql = query.executed_sql
limit = ParsedQuery(sql).limit
if limit is not None and query.limiting_factor in {
LimitingFactor.QUERY,
LimitingFactor.DROPDOWN,
LimitingFactor.QUERY_AND_DROPDOWN,
}:
# remove extra row from `increased_limit`
limit -= 1
df = query.database.get_df(sql, query.schema)[:limit]
csv_data = csv.df_to_escaped_csv(df, index=False, **config["CSV_EXPORT"])
quoted_csv_name = parse.quote(query.name)
response = CsvResponse(
@@ -2797,7 +2821,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
return self.render_template(
"superset/basic.html",
title=_("%(user)s's profile", user=username),
title=_("%(user)s's profile", user=username).__str__(),
entry="profile",
bootstrap_data=json.dumps(
payload, default=utils.pessimistic_json_iso_dttm_ser
+8 -2
View File
@@ -129,7 +129,7 @@ def loads_request_json(request_json_data: str) -> Dict[Any, Any]:
return {}
def get_form_data(
def get_form_data( # pylint: disable=too-many-locals
slice_id: Optional[int] = None, use_slice_data: bool = False
) -> Tuple[Dict[str, Any], Optional[Slice]]:
form_data = {}
@@ -144,7 +144,13 @@ def get_form_data(
if request_json_data:
form_data.update(request_json_data)
if request_form_data:
form_data.update(loads_request_json(request_form_data))
parsed_form_data = loads_request_json(request_form_data)
# some chart data api requests are form_data
queries = parsed_form_data.get("queries")
if isinstance(queries, list):
form_data.update(queries[0])
else:
form_data.update(parsed_form_data)
# request params can overwrite the body
if request_args_data:
form_data.update(loads_request_json(request_args_data))
@@ -81,6 +81,19 @@ class TestDbEngineSpecs(TestDbEngineSpec):
"SELECT * FROM (SELECT * FROM a LIMIT 10) LIMIT 1000",
)
def test_limit_query_without_force(self):
self.sql_limit_regex(
"SELECT * FROM a LIMIT 10", "SELECT * FROM a LIMIT 10", limit=11,
)
def test_limit_query_with_force(self):
self.sql_limit_regex(
"SELECT * FROM a LIMIT 10",
"SELECT * FROM a LIMIT 11",
limit=11,
force=True,
)
def test_limit_with_expr(self):
self.sql_limit_regex(
"""
+7 -2
View File
@@ -25,8 +25,13 @@ from superset.models.core import Database
class TestDbEngineSpec(SupersetTestCase):
def sql_limit_regex(
self, sql, expected_sql, engine_spec_class=MySQLEngineSpec, limit=1000
self,
sql,
expected_sql,
engine_spec_class=MySQLEngineSpec,
limit=1000,
force=False,
):
main = Database(database_name="test_database", sqlalchemy_uri="sqlite://")
limited = engine_spec_class.apply_limit_to_sql(sql, limit, main)
limited = engine_spec_class.apply_limit_to_sql(sql, limit, main, force)
self.assertEqual(expected_sql, limited)
+33
View File
@@ -0,0 +1,33 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from superset.db_engine_specs.dremio import DremioEngineSpec
from tests.db_engine_specs.base_tests import TestDbEngineSpec
class TestDremioDbEngineSpec(TestDbEngineSpec):
def test_convert_dttm(self):
dttm = self.get_dttm()
self.assertEqual(
DremioEngineSpec.convert_dttm("DATE", dttm),
"TO_DATE('2019-01-02', 'YYYY-MM-DD')",
)
self.assertEqual(
DremioEngineSpec.convert_dttm("TIMESTAMP", dttm),
"TO_TIMESTAMP('2019-01-02 03:04:05.678', 'YYYY-MM-DD HH24:MI:SS.FFF')",
)
+89
View File
@@ -0,0 +1,89 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from copy import deepcopy
from superset.migrations.versions.f1410ed7ec95_migrate_native_filters_to_new_schema import (
downgrade_dashboard,
upgrade_dashboard,
)
dashboard_v1 = {
"native_filter_configuration": [
{
"filterType": "filter_select",
"cascadingFilters": True,
"defaultValue": ["Albania", "Algeria"],
},
],
"filter_sets_configuration": [
{
"nativeFilters": {
"FILTER": {
"filterType": "filter_select",
"cascadingFilters": True,
"defaultValue": ["Albania", "Algeria"],
},
},
},
],
}
dashboard_v2 = {
"native_filter_configuration": [
{
"filterType": "filter_select",
"cascadingFilters": True,
"defaultDataMask": {"filterState": {"value": ["Albania", "Algeria"],},},
}
],
"filter_sets_configuration": [
{
"nativeFilters": {
"FILTER": {
"filterType": "filter_select",
"cascadingFilters": True,
"defaultDataMask": {
"filterState": {"value": ["Albania", "Algeria"],},
},
},
},
},
],
}
def test_upgrade_dashboard():
"""
ensure that dashboard upgrade operation produces a correct dashboard object
"""
converted_dashboard = deepcopy(dashboard_v1)
filters, filter_sets = upgrade_dashboard(converted_dashboard)
assert filters == 1
assert filter_sets == 1
assert dashboard_v2 == converted_dashboard
def test_downgrade_dashboard():
"""
ensure that dashboard downgrade operation produces a correct dashboard object
"""
converted_dashboard = deepcopy(dashboard_v2)
filters, filter_sets = downgrade_dashboard(converted_dashboard)
assert filters == 1
assert filter_sets == 1
assert dashboard_v1 == converted_dashboard
+28 -2
View File
@@ -31,10 +31,11 @@ from superset.connectors.sqla.models import SqlaTable
from superset.db_engine_specs import BaseEngineSpec
from superset.errors import ErrorLevel, SupersetErrorType
from superset.models.core import Database
from superset.models.sql_lab import Query, SavedQuery
from superset.models.sql_lab import LimitingFactor, Query, SavedQuery
from superset.result_set import SupersetResultSet
from superset.sql_lab import (
execute_sql_statements,
execute_sql_statement,
get_sql_results,
SqlLabException,
SqlLabTimeoutException,
@@ -119,7 +120,6 @@ class TestSqlLab(SupersetTestCase):
)
assert saved_query_.rows is not None
assert saved_query_.last_run == datetime.now()
# Rollback changes
db.session.delete(saved_query_)
db.session.commit()
@@ -507,18 +507,44 @@ class TestSqlLab(SupersetTestCase):
"SELECT * FROM birth_names", client_id="sql_limit_2", query_limit=test_limit
)
self.assertEqual(len(data["data"]), test_limit)
data = self.run_sql(
"SELECT * FROM birth_names LIMIT {}".format(test_limit),
client_id="sql_limit_3",
query_limit=test_limit + 1,
)
self.assertEqual(len(data["data"]), test_limit)
self.assertEqual(data["query"]["limitingFactor"], LimitingFactor.QUERY)
data = self.run_sql(
"SELECT * FROM birth_names LIMIT {}".format(test_limit + 1),
client_id="sql_limit_4",
query_limit=test_limit,
)
self.assertEqual(len(data["data"]), test_limit)
self.assertEqual(data["query"]["limitingFactor"], LimitingFactor.DROPDOWN)
data = self.run_sql(
"SELECT * FROM birth_names LIMIT {}".format(test_limit),
client_id="sql_limit_5",
query_limit=test_limit,
)
self.assertEqual(len(data["data"]), test_limit)
self.assertEqual(
data["query"]["limitingFactor"], LimitingFactor.QUERY_AND_DROPDOWN
)
data = self.run_sql(
"SELECT * FROM birth_names", client_id="sql_limit_6", query_limit=10000,
)
self.assertEqual(len(data["data"]), 1200)
self.assertEqual(data["query"]["limitingFactor"], LimitingFactor.NOT_LIMITED)
data = self.run_sql(
"SELECT * FROM birth_names", client_id="sql_limit_7", query_limit=1200,
)
self.assertEqual(len(data["data"]), 1200)
self.assertEqual(data["query"]["limitingFactor"], LimitingFactor.NOT_LIMITED)
def test_query_api_filter(self) -> None:
"""
+1 -1
View File
@@ -47,7 +47,7 @@ if "sqlite" in SQLALCHEMY_DATABASE_URI:
PRESTO_POLL_INTERVAL = 0.1
HIVE_POLL_INTERVAL = 0.1
SQL_MAX_ROW = 666
SQL_MAX_ROW = 10000
SQLLAB_CTAS_NO_LIMIT = True # SQL_MAX_ROW will not take affect for the CTA queries
FEATURE_FLAGS = {
**FEATURE_FLAGS,
+20
View File
@@ -1015,6 +1015,26 @@ class TestUtils(SupersetTestCase):
self.assertEqual(slc, None)
def test_get_form_data_request_form_with_queries(self) -> None:
# the CSV export uses for requests, even when sending requests to
# /api/v1/chart/data
with app.test_request_context(
data={
"form_data": json.dumps({"queries": [{"url_params": {"foo": "bar"}}]})
}
):
form_data, slc = get_form_data()
self.assertEqual(
form_data,
{
"url_params": {"foo": "bar"},
"time_range_endpoints": get_time_range_endpoints(form_data={}),
},
)
self.assertEqual(slc, None)
def test_get_form_data_request_args_and_form(self) -> None:
with app.test_request_context(
data={"form_data": json.dumps({"foo": "bar"})},