mirror of
https://github.com/apache/superset.git
synced 2026-08-13 19:50:39 +00:00
Compare commits
19
Commits
+510
@@ -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
@@ -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
|
||||
|
||||
Generated
+766
-3258
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -29,7 +29,7 @@ export default {
|
||||
chartUpdateStartTime: 1525852454838,
|
||||
latestQueryFormData: {},
|
||||
queryRequest: {},
|
||||
queryResponse: {},
|
||||
queriesResponse: [{}],
|
||||
triggerQuery: false,
|
||||
lastRendered: 0,
|
||||
form_data: {
|
||||
|
||||
+15
-5
@@ -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: [],
|
||||
|
||||
+11
-2
@@ -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,
|
||||
|
||||
@@ -65,4 +65,5 @@ export type Query = {
|
||||
templateParams: any;
|
||||
rows: number;
|
||||
queryLimit: number;
|
||||
limitingFactor: string;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
+2
-1
@@ -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),
|
||||
);
|
||||
|
||||
|
||||
+3
-2
@@ -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[];
|
||||
}
|
||||
};
|
||||
|
||||
+27
-42
@@ -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();
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+15
-11
@@ -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 }),
|
||||
{},
|
||||
);
|
||||
|
||||
+43
-30
@@ -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>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
+38
-28
@@ -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}
|
||||
|
||||
+4
@@ -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),
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+13
-5
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
}, {});
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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(
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')",
|
||||
)
|
||||
@@ -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
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"})},
|
||||
|
||||
Reference in New Issue
Block a user