Compare commits

...

65 Commits

Author SHA1 Message Date
Ville Brofeldt
bbb8c87e62 docs: update changelog 2020-09-21 14:47:37 +03:00
Ville Brofeldt
2fd965c6b3 fix: simply is_adhoc_metric (#10964)
* fix: simply is_adhoc_metric

* address comment
2020-09-20 13:23:35 +03:00
Daniel Vaz Gaspar
315acf481f fix(jinja): make context attrs private on SQL templates (#10934)
* fix(jinja): make SQLAlchemy models private on SQL templates

* add missing privates

* fix test
2020-09-19 10:06:29 +03:00
Ville Brofeldt
1f3a93b2c9 fix(legacy-druid): undefined filter key (#10931) 2020-09-19 10:06:03 +03:00
Ville Brofeldt
465572325b fix(chart-data-api): assert referenced columns are present in datasource (#10451)
* fix(chart-data-api): assert requested columns are present in datasource

* add filter tests

* add column_names to AnnotationDatasource

* add assertion for simple metrics

* lint
2020-09-19 10:05:26 +03:00
Ville Brofeldt
98329fbd6c chore: update version and changelog 2020-09-16 10:52:43 +03:00
Ville Brofeldt
c30c3b20d6 fix: unit tests 2020-09-16 10:08:16 +03:00
Ville Brofeldt
71fc7cbfcf fix(sql-lab): relax column name restrictions (#10816) 2020-09-16 09:34:06 +03:00
Jesse Yang
30efcc964d fix: ColorSchemeControl should not use CreatableSelect (#10814)
* fix: ColorSchemeControl should not be CreatableSelect

   Currently if you type to search in ColorSchemeControl it crashes the
whole page.

* Make it possible to filter by label

* Fix ColorSchemeControl unit test
2020-09-16 09:33:40 +03:00
gtg472b
698bad187f Fix: Include RLS filters for cache keys (#10805)
* Fix: Include RLS filters for cache keys

This fix makes sure that RLS filters are searched for templatable jinja content, ensuring cached visualizations aren't shown to the wrong user.

* Fix: Include RLS filters for cache keys

This fix makes sure that RLS filters are searched for templatable jinja content, ensuring cached visualizations aren't shown to the wrong user.

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
2020-09-16 09:30:11 +03:00
Ville Brofeldt
8ecfb18ac7 fix: pivot table timestamp grouping (#10774)
* fix: pivot table timestamp grouping

* address comments
2020-09-16 09:30:07 +03:00
chuancy
8fb6c8361c fix(db-engine-spec): execute oracle DML statement bug in sqllab (#10706)
* fix execute oracle DML statement bug in sqllab

when i execute oracle sql statements like update in SQLLAB, get "oracle error: not a query" error. 

Refer https://www.python.org/dev/peps/pep-0249/, superset old version use
`cursor.description` ,because this attribute will be None for operations that do not return rows or if the cursor has not had an operation invoked via the .execute*() method yet.

* Apply suggestions from code review

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>

* Update oracle.py

* Update oracle.py

* Update oracle.py

* Apply suggestions from code review

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>

* Update oracle.py

* Update superset/db_engine_specs/oracle.py

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
2020-09-16 09:30:01 +03:00
Ville Brofeldt
4d73490fa3 fix(jinja): extract form_data from json body (#10684)
* fix(jinja): extract form_data from json body

* add test

* disable test for presto
2020-09-16 09:29:44 +03:00
Ville Brofeldt
fa9a558354 refactor(database): use SupersetResultSet on SqlaTable.get_df() (#10707)
* refactor(database): use SupersetResultSet on SqlaTable.get_df()

* lint

* change cypress test
2020-09-16 09:28:31 +03:00
Ville Brofeldt
421694bfa8 fix(filter-box): don't add empty filter to filtersChoices (#10687)
* fix(filter-box): don't add empty filter to filtersChoices

* add test
2020-09-16 09:28:23 +03:00
Ville Brofeldt
091c6ae316 feat(row-level-security): add hook for customizing form dropdowns (#10683) 2020-09-16 09:28:17 +03:00
Maxime Beauchemin
25d9fb7544 fix: dedup groupby in viz.py while preserving order (#10633) 2020-09-16 09:27:39 +03:00
Ville Brofeldt
029a70b8b9 feat(viz-plugins): add date formatting to pivot-table (#10637)
* feat: make pivot table dates formattable

* Bump npm packages
2020-09-16 09:19:40 +03:00
Cory Zue
fc94d74aed improve documentation for country maps (#10621) 2020-09-16 09:19:36 +03:00
Ville Brofeldt
d06e023d88 fix: show error if rolling window returns empty df (#10572)
* fix: show error if rolling window returns empty df

* add test
2020-09-16 09:19:27 +03:00
Daniel Vaz Gaspar
f3bf6cebb7 fix: dataset delete and perm delete (#10578) 2020-09-16 09:18:42 +03:00
Daniel Vaz Gaspar
92b3f88b1a fix: disable false positive error (#10576) 2020-09-16 09:18:28 +03:00
Jesse Yang
820d07d6cf fix(dashboard): add animation state to fix tab switch re-renders (#10475) 2020-09-16 09:18:15 +03:00
Jesse Yang
3aadbc4fde fix: table viz query mode switch not working (#10552) 2020-09-16 09:18:09 +03:00
Erik Ritter
3293ef4b6b fix: embedded chart height (#10551) 2020-09-16 09:18:00 +03:00
Ville Brofeldt
ebb3307c1d fix: handle query exceptions gracefully (#10548)
* fix: handle query exceptions gracefully

* add more recasts

* add test

* disable test for presto

* switch to SQLA error
2020-09-16 09:17:54 +03:00
Jesse Yang
3b4882d836 bugfix: table chart query mode initial value (#10544) 2020-09-16 09:17:49 +03:00
Ville Brofeldt
bc80029f15 fix: remove unnecessary exception when exploring non-legacy viz plugins (#10538)
* fix: remove unnecessary exception when exploring non-legacy viz plugins

* lint
2020-09-16 09:17:44 +03:00
Daniel Vaz Gaspar
fb60d302c2 fix(log): don't log exceptions on test connection (#10522)
* fix(log): don't log exceptions on test connection

* fix lint
2020-09-16 09:17:39 +03:00
Jason Davis
13928fcf2d feat: make screenshot timeout configurable (#10517)
* made screenshot timeout configurable

* added default value to config and refractored use

* black

* updated config comment

* moves config variables to thumbnail section

Co-authored-by: Jason Davis <@dropbox.com>
2020-09-16 09:17:29 +03:00
Moriah Kreeger
1fb14e81f7 fix: update time range select tooltip (#10458) 2020-09-16 09:17:14 +03:00
Ville Brofeldt
ddac4b8838 fix: support non-string groupbys for pie chart (#10493)
* chore: add unit tests to pie chart

* refine logic for floats and nans and add more tests
2020-09-16 09:17:05 +03:00
Daniel Vaz Gaspar
024ef9a9e3 fix(log): log endpoint authentication (#10435)
* fix(log): log crashes if expired or not authenticated

* add auth to log endpoint
2020-09-16 09:16:58 +03:00
Stuart Hu
9c62eb2a7b update code (#10430)
Signed-off-by: Stuart Hu <shijiehu@improbable.io>
2020-09-16 09:16:52 +03:00
Ville Brofeldt
6a85a341ad fix: pie chart multiple groupbys (#10391) 2020-09-16 09:16:45 +03:00
Ville Brofeldt
fa787d2de4 bump version and update changelog 2020-09-06 13:41:57 +03:00
Daniel Vaz Gaspar
a72903cb92 security: disallow uuid package on jinja1 (#10794)
* fix: disallow uuid package on jinja2

* update UPDATING.md

* Update UPDATING.md

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
2020-09-05 16:54:09 +03:00
Ville Brofeldt
0dbc1dbb9a add changelog entries 2020-08-01 10:01:07 +03:00
pphszx
6d07273360 fix: excel sheet upload is not working (#10450)
* remove conflicts with csv upload

* revert StringField

* change description

* remove redundant space

* apply string approach
2020-07-29 10:21:22 +03:00
Ville Brofeldt
0f3670e1af feat: support non-numeric columns in pivot table (#10389)
* fix: support non-numeric columns in pivot table

* bump package and add unit tests

* mypy
2020-07-28 10:59:12 +03:00
Jesse Yang
bde453be79 fix(dashboard): chart rerender when switching tabs (#10432) 2020-07-27 11:53:00 +03:00
Ville Brofeldt
7b6f88a272 fix: incorrect filter operator emitted by Filter Box (#10421)
* fix: equals operator in filter box

* fix time range endpoint schema

* fix test

* bump packages

* fix tests

* lint
2020-07-26 14:50:06 +03:00
Ville Brofeldt
f908770dbc fix: bump pivot-table and rose (#10400) 2020-07-26 14:49:48 +03:00
Ville Brofeldt
1fc06a7443 fix: treemap template literal (#10382) 2020-07-26 14:47:46 +03:00
Daniel Vaz Gaspar
070dd5359c fix: group by with timestamp granularity (#10344)
* fix, group by with timestamp granularity

* fix, bug found by mypy

* lint

* comment

* Following ville's solution

* lint and comments
2020-07-26 14:30:35 +03:00
Daniel Vaz Gaspar
f13629abd5 fix: modified by column on charts and dashboards (#10340) 2020-07-26 14:30:09 +03:00
Ville Brofeldt
c74acdc381 Add changelog 2020-07-21 08:42:45 +03:00
Ville Brofeldt
5b4277e4fc fix: extra filters for chart data endpoint (#10359)
* fix: extra filters

* fix old test and add new test

* add test for null filter value

* leave lowercase until all operators are fully uppercased

* bump packages

* rename test
2020-07-21 08:27:55 +03:00
Bogdan
e2690f0802 Pin slack client, breaking change (#10345)
Co-authored-by: bogdan kyryliuk <bogdankyryliuk@dropbox.com>
2020-07-21 08:27:05 +03:00
Ville Brofeldt
d9ec47bc63 chore: add cherries to CHANGELOG.md 2020-07-16 11:53:01 +03:00
Ville Brofeldt
3f284eaa09 fix: make __time an ok column name in SQL Lab (#10336) 2020-07-16 11:20:10 +03:00
Jesse Yang
08d43b1515 fix(table-viz): value "undefined" for column.name (#10325)
Bump table viz plugin to fix a bug: apache-superset/superset-ui#686
2020-07-16 09:15:25 +03:00
Daniel Vaz Gaspar
c57df314d7 fix: humanised changed on UTC on dashboards and charts (#10321)
* fix: API marshmallow3 drop utc for naive datetime fields

* fix: API marshmallow3 drop utc for naive datetime fields

* fix, tests

* isort and test

* black

* add and fix test

* fix comment
2020-07-16 09:15:12 +03:00
Erik Ritter
2f2fb47721 chore: type welcome (#10317) 2020-07-16 09:14:55 +03:00
Erik Ritter
9f0d456b6a chore: add typing to more sqllab components (#10278) 2020-07-16 09:14:17 +03:00
Erik Ritter
2631b3882e chore: add typing to profile (#10282) 2020-07-16 09:12:40 +03:00
Daniel Vaz Gaspar
791d787256 fix: Bump FAB to 3.0.1 fix superset init (#10310) 2020-07-14 19:13:25 +03:00
Ville Brofeldt
8d0e6676ef fix: leave null timestamp unformatted in view results table (#10313) 2020-07-14 19:12:51 +03:00
Ville Brofeldt
522ed20a20 fix: fix csv and query result type and QueryObject schema (#10312) 2020-07-14 16:37:56 +03:00
Ville Brofeldt
38bc62db4b fix(chart-data-api): improve chart data endpoint errors (#10300)
* fix: improve chart data error response

* Populate error_message in QueryResult

* add tests

* Lint + fix incorrect raise

* add more tests
2020-07-14 15:49:20 +03:00
Jesse Yang
1bc26797ad fix(table-viz): table chart time column should use default (#10293) 2020-07-14 15:48:50 +03:00
Ville Brofeldt
1217cb05b3 fix(chart-data-api): case insensitive evaluation of filter op (#10299)
* fix(chart-data-api): case insensitive evaluation of filter op

* fix(chart-data-api): case insensitive evaluation of filter op

* mypy

* remove print statement

* add test
2020-07-14 15:48:32 +03:00
Ville Brofeldt
6f41b6ef5f chore: improve release documentation (#10292)
* fix: releasing readme.md

* fix: improvements to release documentation
2020-07-14 15:48:12 +03:00
Ville Brofeldt
5693580dae fix: add updating headers for 0.36.0 and 0.37.0 2020-07-11 12:14:39 +03:00
Ville Brofeldt
727c1b8ce1 chore: update changelog and bump version 2020-07-11 11:15:35 +03:00
125 changed files with 2509 additions and 768 deletions

View File

@@ -18,6 +18,466 @@ under the License.
-->
## Change Log
### 0.37.2 (2020/09/21 11:45 +00:00)
- [#10964](https://github.com/apache/incubator-superset/pull/10964) fix: simply is_adhoc_metric (#10964) (@villebro)
- [#10934](https://github.com/apache/incubator-superset/pull/10934) fix(jinja): make context attrs private on SQL templates (#10934) (@dpgaspar)
- [#10931](https://github.com/apache/incubator-superset/pull/10931) fix(legacy-druid): undefined filter key (#10931) (@villebro)
- [#10451](https://github.com/apache/incubator-superset/pull/10451) fix(chart-data-api): assert referenced columns are present in datasource (#10451) (@villebro)
- [#10816](https://github.com/apache/incubator-superset/pull/10816) fix(sql-lab): relax column name restrictions (#10816) (@villebro)
- [#10814](https://github.com/apache/incubator-superset/pull/10814) fix: ColorSchemeControl should not use CreatableSelect (#10814) (@ktmud)
- [#10805](https://github.com/apache/incubator-superset/pull/10805) Fix: Include RLS filters for cache keys (#10805) (@gtg472b)
- [#10774](https://github.com/apache/incubator-superset/pull/10774) fix: pivot table timestamp grouping (#10774) (@villebro)
- [#10706](https://github.com/apache/incubator-superset/pull/10706) fix(db-engine-spec): execute oracle DML statement bug in sqllab (#10706) (@chuancyzhang)
- [#10684](https://github.com/apache/incubator-superset/pull/10684) fix(jinja): extract form_data from json body (#10684) (@villebro)
- [#10707](https://github.com/apache/incubator-superset/pull/10707) refactor(database): use SupersetResultSet on SqlaTable.get_df() (#10707) (@villebro)
- [#10687](https://github.com/apache/incubator-superset/pull/10687) fix(filter-box): don't add empty filter to filtersChoices (#10687) (@villebro)
- [#10683](https://github.com/apache/incubator-superset/pull/10683) feat(row-level-security): add hook for customizing form dropdowns (#10683) (@villebro)
- [#10633](https://github.com/apache/incubator-superset/pull/10633) fix: dedup groupby in viz.py while preserving order (#10633) (@mistercrunch)
- [#10637](https://github.com/apache/incubator-superset/pull/10637) feat(viz-plugins): add date formatting to pivot-table (#10637) (@villebro)
- [#10621](https://github.com/apache/incubator-superset/pull/10621) improve documentation for country maps (#10621) (@czue)
- [#10572](https://github.com/apache/incubator-superset/pull/10572) fix: show error if rolling window returns empty df (#10572) (@villebro)
- [#10578](https://github.com/apache/incubator-superset/pull/10578) fix: dataset delete and perm delete (#10578) (@dpgaspar)
- [#10576](https://github.com/apache/incubator-superset/pull/10576) fix: disable false positive error (#10576) (@dpgaspar)
- [#10475](https://github.com/apache/incubator-superset/pull/10475) fix(dashboard): add animation state to fix tab switch re-renders (#10475) (@ktmud)
- [#10552](https://github.com/apache/incubator-superset/pull/10552) fix: table viz query mode switch not working (#10552) (@ktmud)
- [#10551](https://github.com/apache/incubator-superset/pull/10551) fix: embedded chart height (#10551) (@etr2460)
- [#10548](https://github.com/apache/incubator-superset/pull/10548) fix: handle query exceptions gracefully (#10548) (@villebro)
- [#10544](https://github.com/apache/incubator-superset/pull/10544) bugfix: table chart query mode initial value (#10544) (@ktmud)
- [#10538](https://github.com/apache/incubator-superset/pull/10538) fix: remove unnecessary exception when exploring non-legacy viz plugins (#10538) (@villebro)
- [#10522](https://github.com/apache/incubator-superset/pull/10522) fix(log): don't log exceptions on test connection (#10522) (@dpgaspar)
- [#10517](https://github.com/apache/incubator-superset/pull/10517) feat: make screenshot timeout configurable (#10517) (@JasonD28)
- [#10458](https://github.com/apache/incubator-superset/pull/10458) fix: update time range select tooltip (#10458) (@riahk)
- [#10493](https://github.com/apache/incubator-superset/pull/10493) fix: support non-string groupbys for pie chart (#10493) (@villebro)
- [#10435](https://github.com/apache/incubator-superset/pull/10435) fix(log): log endpoint authentication (#10435) (@dpgaspar)
- [#10430](https://github.com/apache/incubator-superset/pull/10430) update code (#10430) (@stuarthu)
- [#10391](https://github.com/apache/incubator-superset/pull/10391) fix: pie chart multiple groupbys (#10391) (@villebro)
### 0.37.1 (2020/09/05 17:28 +00:00)
- [#10794](https://github.com/apache/incubator-superset/pull/10794) security: disallow uuid package on jinja1 (#10794) (@dpgaspar)
### 0.37.0 (2020/07/11 08:07 +00:00)
- [#10450](https://github.com/apache/incubator-superset/pull/10450) fix: excel sheet upload is not working (#10450) (@pphszx)
- [#10389](https://github.com/apache/incubator-superset/pull/10389) feat: support non-numeric columns in pivot table (#10389) (@villebro)
- [#10432](https://github.com/apache/incubator-superset/pull/10432) fix(dashboard): chart rerender when switching tabs (#10432) (@ktmud)
- [#10421](https://github.com/apache/incubator-superset/pull/10421) fix: incorrect filter operator emitted by Filter Box (#10421) (@villebro)
- [#10400](https://github.com/apache/incubator-superset/pull/10400) fix: bump pivot-table and rose (#10400) (@villebro)
- [#10382](https://github.com/apache/incubator-superset/pull/10382) fix: treemap template literal (#10382) (@villebro)
- [#10344](https://github.com/apache/incubator-superset/pull/10344) fix: group by with timestamp granularity (#10344) (@dpgaspar)
- [#10340](https://github.com/apache/incubator-superset/pull/10340) fix: modified by column on charts and dashboards (#10340) (@dpgaspar)
- [#10359](https://github.com/apache/incubator-superset/pull/10359) fix: extra filters for chart data endpoint (#10359) (@villebro)
- [#10345](https://github.com/apache/incubator-superset/pull/10345) Pin slack client, breaking change (#10345) (@bkyryliuk)
- [#10336](https://github.com/apache/incubator-superset/pull/10336) fix: make __time an ok column name in SQL Lab (#10336) (@villebro)
- [#10325](https://github.com/apache/incubator-superset/pull/10325) fix(table-viz): value "undefined" for column.name (#10325) (@ktmud)
- [#10321](https://github.com/apache/incubator-superset/pull/10321) fix: humanised changed on UTC on dashboards and charts (#10321) (@dpgaspar)
- [#10317](https://github.com/apache/incubator-superset/pull/10317) chore: type welcome (#10317) (@etr2460)
- [#10278](https://github.com/apache/incubator-superset/pull/10278) chore: add typing to more sqllab components (#10278) (@etr2460)
- [#10282](https://github.com/apache/incubator-superset/pull/10282) chore: add typing to profile (#10282) (@etr2460)
- [#10310](https://github.com/apache/incubator-superset/pull/10310) fix: Bump FAB to 3.0.1 fix superset init (#10310) (@dpgaspar)
- [#10313](https://github.com/apache/incubator-superset/pull/10313) fix: leave null timestamp unformatted in view results table (#10313) (@villebro)
- [#10312](https://github.com/apache/incubator-superset/pull/10312) fix: fix csv and query result type and QueryObject schema (#10312) (@villebro)
- [#10300](https://github.com/apache/incubator-superset/pull/10300) fix(chart-data-api): improve chart data endpoint errors (#10300) (@villebro)
- [#10293](https://github.com/apache/incubator-superset/pull/10293) fix(table-viz): table chart time column should use default (#10293) (@ktmud)
- [#10299](https://github.com/apache/incubator-superset/pull/10299) fix(chart-data-api): case insensitive evaluation of filter op (#10299) (@villebro)
- [#10292](https://github.com/apache/incubator-superset/pull/10292) chore: improve release documentation (#10292) (@villebro)
- [#10281](https://github.com/apache/incubator-superset/pull/10281) logo gets the vertically-centered flexbox treatment in jinja template (#10281) (@rusackas)
- [#10178](https://github.com/apache/incubator-superset/pull/10178) style: new toast design closer to SIP-34 (#10178) (@lilykuang)
- [#10258](https://github.com/apache/incubator-superset/pull/10258) feat: update delete modal for dataset (#10258) (@lilykuang)
- [#10284](https://github.com/apache/incubator-superset/pull/10284) fix(explore): edit datasource does not update control states (#10284) (@ktmud)
- [#10279](https://github.com/apache/incubator-superset/pull/10279) chore: Releasing SQL Lab Celery results (#10279) (@john-bodley)
- [#10280](https://github.com/apache/incubator-superset/pull/10280) chore: Bumping Celery (#10280) (@john-bodley)
- [#10286](https://github.com/apache/incubator-superset/pull/10286) feat: add contribution operation and fix cache_key bug (#10286) (@villebro)
- [#10283](https://github.com/apache/incubator-superset/pull/10283) fix(explore): 'Edit Datasource' is missing from btn-dropdown (#10283) (@mistercrunch)
- [#10277](https://github.com/apache/incubator-superset/pull/10277) fix: deckgl dimension select missing options (#10277) (@ktmud)
- [#10276](https://github.com/apache/incubator-superset/pull/10276) docs(fix): /swaggerview/v1 has moved to /swagger/v1 (#10276) (@mistercrunch)
- [#10273](https://github.com/apache/incubator-superset/pull/10273) fix(csv upload): hive params typo (#10273) (@serenajiang)
- [#10268](https://github.com/apache/incubator-superset/pull/10268) feat: show formatted dates instead of epoch on results (#10268) (@villebro)
- [#10269](https://github.com/apache/incubator-superset/pull/10269) fix: Fixed bug for issue #9967 (#10269) (@Nj-kol)
- [#10220](https://github.com/apache/incubator-superset/pull/10220) fix: dashboard endpoint sig changed (#10220) (@dpgaspar)
- [#10264](https://github.com/apache/incubator-superset/pull/10264) fix: optimize mapStateToProps for chart controls (#10264) (@ktmud)
- [#10263](https://github.com/apache/incubator-superset/pull/10263) feat: revised icons (#10263) (@rusackas)
- [#10262](https://github.com/apache/incubator-superset/pull/10262) fix: issue in getting filter_box config (#10262) (@graceguo-supercat)
- [#10259](https://github.com/apache/incubator-superset/pull/10259) chore: nixing requiresTime (#10259) (@rusackas)
- [#10260](https://github.com/apache/incubator-superset/pull/10260) [fix] disable search all options by default (#10260) (@graceguo-supercat)
- [#10257](https://github.com/apache/incubator-superset/pull/10257) feat: minor reorder SQL Lab Tab controls (#10257) (@graceguo-supercat)
- [#10255](https://github.com/apache/incubator-superset/pull/10255) feat(chart-data-api): make pivoted columns flattenable (#10255) (@villebro)
- [#10253](https://github.com/apache/incubator-superset/pull/10253) docs: update upgrading for FAB3 OAuth change (#10253) (@dpgaspar)
- [#10252](https://github.com/apache/incubator-superset/pull/10252) fix: datasets related objects, apply filter and openapi spec (#10252) (@dpgaspar)
- [#10250](https://github.com/apache/incubator-superset/pull/10250) fix(table-viz): JS error when sort time column (#10250) (@ktmud)
- [#10210](https://github.com/apache/incubator-superset/pull/10210) feat: Typeahead searchable filter_box for dashboard (#10210) (@graceguo-supercat)
- [#9964](https://github.com/apache/incubator-superset/pull/9964) feat(api): bump marshmallow and FAB to version 3 (#9964) (@dpgaspar)
- [#10248](https://github.com/apache/incubator-superset/pull/10248) chore: Cleaning up types and names for SQLA models (#10248) (@john-bodley)
- [#10237](https://github.com/apache/incubator-superset/pull/10237) chore: Revised logo (new color), w/CSS for vert alignment (#10237) (@rusackas)
- [#10235](https://github.com/apache/incubator-superset/pull/10235) feat: dataset api endpoint for charts and dashboards count (#10235) (@lilykuang)
- [#10208](https://github.com/apache/incubator-superset/pull/10208) feat: support nulls in the csv uploads (#10208) (@bkyryliuk)
- [#9944](https://github.com/apache/incubator-superset/pull/9944) feat: Alerts! allowing users to set SQL-based email alerts with screenshots (#9944) (@mistercrunch)
- [#10165](https://github.com/apache/incubator-superset/pull/10165) fix: downgrade sqlparse and add unit test (#10165) (@bkyryliuk)
- [#10243](https://github.com/apache/incubator-superset/pull/10243) feat: support new errors payload in SQL Lab (#10243) (@etr2460)
- [#10118](https://github.com/apache/incubator-superset/pull/10118) feat: add database dropdown to dashboard import (#10118) (@mistercrunch)
- [#10146](https://github.com/apache/incubator-superset/pull/10146) refactor: Using self.get_session in security manager (#10146) (@john-bodley)
- [#10197](https://github.com/apache/incubator-superset/pull/10197) [log] Updating form-data logic (#10197) (@john-bodley)
- [#10233](https://github.com/apache/incubator-superset/pull/10233) chore: Deprecating ENABLE_FLASK_COMPRESS (#10233) (@john-bodley)
- [#10236](https://github.com/apache/incubator-superset/pull/10236) Removing node_modules volume in docker-compose.yml (#10236) (@craig-rueda)
- [#9825](https://github.com/apache/incubator-superset/pull/9825) Upload excel (#9825) (@blcksrx)
- [#10222](https://github.com/apache/incubator-superset/pull/10222) fix: Remove double pickling for cached payloads (#10222) (@john-bodley)
- [#10234](https://github.com/apache/incubator-superset/pull/10234) fix(table-viz): format non-numeric metrics (#10234) (@ktmud)
- [#10226](https://github.com/apache/incubator-superset/pull/10226) chore: type ResultSet.tsx (#10226) (@etr2460)
- [#10223](https://github.com/apache/incubator-superset/pull/10223) fix(listview): use disableSortBy to disable sorting in table columns (#10223) (@nytai)
- [#10221](https://github.com/apache/incubator-superset/pull/10221) Update README.md (#10221) (@ceohockey60)
- [#10225](https://github.com/apache/incubator-superset/pull/10225) fix(table-vis): bump to v0.14.6 to fix missing anchor links (#10225) (@ktmud)
- [#10224](https://github.com/apache/incubator-superset/pull/10224) fix: chart validation error not cleared on control value update (#10224) (@ktmud)
- [#10219](https://github.com/apache/incubator-superset/pull/10219) fix: better backward compatibility for table viz (#10219) (@ktmud)
- [#10192](https://github.com/apache/incubator-superset/pull/10192) docs: update release instructions for Github releases (#10192) (@etr2460)
- [#10209](https://github.com/apache/incubator-superset/pull/10209) docs: added slack community guidelines (#10209) (@srinify)
- [#10211](https://github.com/apache/incubator-superset/pull/10211) fix: tooltips on nvd3 charts rendering in the wrong location (#10211) (@etr2460)
- [#10206](https://github.com/apache/incubator-superset/pull/10206) build: enable Cypress tests for visualizations (#10206) (@ktmud)
- [#10205](https://github.com/apache/incubator-superset/pull/10205) fix: datatable dependencies for pivot-table in dev (#10205) (@ktmud)
- [#10191](https://github.com/apache/incubator-superset/pull/10191) feat: add configuration for Presto cursor poll interval (#10191) (@etr2460)
- [#10195](https://github.com/apache/incubator-superset/pull/10195) s/assertEqual/assertEquals (#10195) (@bkyryliuk)
- [#10177](https://github.com/apache/incubator-superset/pull/10177) feat: Switch from nosetest to pytest (#10177) (@bkyryliuk)
- [#10174](https://github.com/apache/incubator-superset/pull/10174) added explore database for ctas/cvas (#10174) (@JasonD28)
- [#10170](https://github.com/apache/incubator-superset/pull/10170) build: enable typescript for cypress (#10170) (@ktmud)
- [#10188](https://github.com/apache/incubator-superset/pull/10188) update to last pyathena >= 1.10.8, to fix athena CSV upload (#10188) (@thibault-ketterer)
- [#10187](https://github.com/apache/incubator-superset/pull/10187) fix: make time grain nullable in chart data endpoint (#10187) (@villebro)
- [#10172](https://github.com/apache/incubator-superset/pull/10172) fix: make auth for hive optional (#10172) (@0xBADBAC0N)
- [#10113](https://github.com/apache/incubator-superset/pull/10113) feat(viz): add query mode switch to table chart (#10113) (@ktmud)
- [#10180](https://github.com/apache/incubator-superset/pull/10180) refactor: better naming for config flags (#10180) (@nytai)
- [#10179](https://github.com/apache/incubator-superset/pull/10179) fix: row count container alignment (#10179) (@etr2460)
- [#10175](https://github.com/apache/incubator-superset/pull/10175) Break some static methods out of superset.views.core.Superset (#10175) (@willbarrett)
- [#10155](https://github.com/apache/incubator-superset/pull/10155) chore: Updating UPDATING.md (#10155) (@john-bodley)
- [#10121](https://github.com/apache/incubator-superset/pull/10121) style: bring new SIP-34 palettes in controls (#10121) (@mistercrunch)
- [#10164](https://github.com/apache/incubator-superset/pull/10164) feat: Added configuration to SQL Lab results "Explore" button (#10164) (@JasonD28)
- [#9210](https://github.com/apache/incubator-superset/pull/9210) Add maximize and minimize feature to charts (#9210) (@oashton)
- [#10120](https://github.com/apache/incubator-superset/pull/10120) refactor: Fix lint on superset/utils/core.py (#10120) (@willbarrett)
- [#10171](https://github.com/apache/incubator-superset/pull/10171) fix: return slice owners for /user_slices ep (#10171) (@villebro)
- [#10161](https://github.com/apache/incubator-superset/pull/10161) build: dont prefer ts for cypress tests (#10161) (@ktmud)
- [#10158](https://github.com/apache/incubator-superset/pull/10158) build: upgrade Cypress and re-enable visualization tests (#10158) (@ktmud)
- [#10128](https://github.com/apache/incubator-superset/pull/10128) fix: [search query view] edit link is broken (#10128) (@mistercrunch)
- [#10106](https://github.com/apache/incubator-superset/pull/10106) refactor: Re-enable pylint on 5 files (#10106) (@willbarrett)
- [#10135](https://github.com/apache/incubator-superset/pull/10135) fix: implement legacy druid dashboard import (#10135) (@villebro)
- [#10160](https://github.com/apache/incubator-superset/pull/10160) feat: Add new timegrains and convert_dttm to Druid engine spec (#10160) (@villebro)
- [#10157](https://github.com/apache/incubator-superset/pull/10157) refactor: import emotion-theming methods from @superset-ui/style (#10157) (@ktmud)
- [#10124](https://github.com/apache/incubator-superset/pull/10124) refactor: Re-enable lint for 3 files (#10124) (@willbarrett)
- [#10105](https://github.com/apache/incubator-superset/pull/10105) fix: Loading overlay bugfix and cleanup (#10105) (@rusackas)
- [#10154](https://github.com/apache/incubator-superset/pull/10154) fix: Fixed Attribute Error when running core_tests.py (#10154) (@JasonD28)
- [#10156](https://github.com/apache/incubator-superset/pull/10156) Fix alembic migrations order (#10156) (@bkyryliuk)
- [#10130](https://github.com/apache/incubator-superset/pull/10130) fix(security): dbs/clusters perm (#10130) (@john-bodley)
- [#9794](https://github.com/apache/incubator-superset/pull/9794) Implement create view as functionality (#9794) (@bkyryliuk)
- [#10148](https://github.com/apache/incubator-superset/pull/10148) fix: refine shouldUseLegacyApi and add tests (#10148) (@villebro)
- [#10143](https://github.com/apache/incubator-superset/pull/10143) fix: FilterBox JS when no results (#10143) (@ktmud)
- [#10147](https://github.com/apache/incubator-superset/pull/10147) Fix CTAS explore flow (#10147) (@bkyryliuk)
- [#10034](https://github.com/apache/incubator-superset/pull/10034) chore(security): Updating assert logic (#10034) (@john-bodley)
- [#10094](https://github.com/apache/incubator-superset/pull/10094) style: listviews closer to SIP-34 (#10094) (@nytai)
- [#10104](https://github.com/apache/incubator-superset/pull/10104) feat: dataset add modal (#10104) (@lilykuang)
- [#10138](https://github.com/apache/incubator-superset/pull/10138) Break down Superset 101 Getting Started content (#10138) (@ceohockey60)
- [#10112](https://github.com/apache/incubator-superset/pull/10112) chore: bump superset-ui to v0.14.1 (#10112) (@ktmud)
- [#10114](https://github.com/apache/incubator-superset/pull/10114) fix: dashboard filter scope bug (#10114) (@graceguo-supercat)
- [#10080](https://github.com/apache/incubator-superset/pull/10080) [logging] add selected tab name into perf logging (#10080) (@graceguo-supercat)
- [#10127](https://github.com/apache/incubator-superset/pull/10127) chore: type src/addSlice (#10127) (@etr2460)
- [#10129](https://github.com/apache/incubator-superset/pull/10129) chore: fix add datasource help string (#10129) (@etr2460)
- [#10131](https://github.com/apache/incubator-superset/pull/10131) chore: extract translateable strings (#10131) (@etr2460)
- [#10117](https://github.com/apache/incubator-superset/pull/10117) chore: Bump sqlalchemy-utils and cachelib and refresh requirements.txt (#10117) (@villebro)
- [#9751](https://github.com/apache/incubator-superset/pull/9751) feat: Add multiple table filters for Row Level Security (#9751) (@axelet)
- [#10111](https://github.com/apache/incubator-superset/pull/10111) perf(datasets): improve datasets API performance for attr kind (#10111) (@dpgaspar)
- [#10123](https://github.com/apache/incubator-superset/pull/10123) style: replace broken glyphs with font-awesome (#10123) (@mistercrunch)
- [#10122](https://github.com/apache/incubator-superset/pull/10122) Add Nielsen to users list (#10122) (@amitNielsen)
- [#10119](https://github.com/apache/incubator-superset/pull/10119) chore(mypy): Fixing can_access_database types (#10119) (@john-bodley)
- [#10115](https://github.com/apache/incubator-superset/pull/10115) fix: dashboard should not add extra_filters onto chart annotation (#10115) (@graceguo-supercat)
- [#10092](https://github.com/apache/incubator-superset/pull/10092) fix: Visualization settings were lost when editing a datasource from Explore (#10092) (@willbarrett)
- [#10073](https://github.com/apache/incubator-superset/pull/10073) chore: type FilterableTable (#10073) (@etr2460)
- [#10056](https://github.com/apache/incubator-superset/pull/10056) style: push bootstrap theme towards SIP-34 styles (#10056) (@mistercrunch)
- [#10101](https://github.com/apache/incubator-superset/pull/10101) chore: Upgrade pylint to 2.5.3 and fix most new rules (#10101) (@willbarrett)
- [#10077](https://github.com/apache/incubator-superset/pull/10077) chore(metricoption): remove metricoption and columntypelable dependency (#10077) (@pkdotson)
- [#10090](https://github.com/apache/incubator-superset/pull/10090) style: Change logo color to new branding (#10090) (@willbarrett)
- [#10035](https://github.com/apache/incubator-superset/pull/10035) fix: adds pagination/infinite scroll to owners select in DashboardList and ChartList (#10035) (@nytai)
- [#9444](https://github.com/apache/incubator-superset/pull/9444) feat: implement dttm column configuration through db extra config (#9444) (@bkyryliuk)
- [#10078](https://github.com/apache/incubator-superset/pull/10078) refactor: Break up superset/views/core.py (#10078) (@willbarrett)
- [#10068](https://github.com/apache/incubator-superset/pull/10068) chore: type src/dashboard/util/charts (#10068) (@etr2460)
- [#10076](https://github.com/apache/incubator-superset/pull/10076) fix(sqllab): table refresh API endpoint (#10076) (@ktmud)
- [#9810](https://github.com/apache/incubator-superset/pull/9810) feat: superset report slack integration (#9810) (@bkyryliuk)
- [#10071](https://github.com/apache/incubator-superset/pull/10071) docs: FAQ entry for 'Does Superset have an API' (#10071) (@mistercrunch)
- [#10079](https://github.com/apache/incubator-superset/pull/10079) fix: Ensuring queries route accepts float or int (#10079) (@john-bodley)
- [#10075](https://github.com/apache/incubator-superset/pull/10075) add GrowthSimple to list of users on readme (#10075) (@AnimeshAgrawal)
- [#10074](https://github.com/apache/incubator-superset/pull/10074) other: add TypeScript tracker generation script (#10074) (@etr2460)
- [#10043](https://github.com/apache/incubator-superset/pull/10043) feat: add more columns and icons to dataset listview (#10043) (@nytai)
- [#10059](https://github.com/apache/incubator-superset/pull/10059) chore: bumping superset-ui theme version (#10059) (@rusackas)
- [#10070](https://github.com/apache/incubator-superset/pull/10070) fix: use custom int parsing over flask int parsing in sqllab queries endpoint (#10070) (@nytai)
- [#10069](https://github.com/apache/incubator-superset/pull/10069) Update prefer_typescript.yml (#10069) (@etr2460)
- [#10062](https://github.com/apache/incubator-superset/pull/10062) fix: chart export fails when buildQuery not present (#10062) (@villebro)
- [#10061](https://github.com/apache/incubator-superset/pull/10061) fix: caching on viz with relative time ranges and time compare (#10061) (@etr2460)
- [#10065](https://github.com/apache/incubator-superset/pull/10065) docs: Add DouroECI to list of users on readme (#10065) (@nunohelibeires)
- [#10060](https://github.com/apache/incubator-superset/pull/10060) fix: dashboard export raises schema issues (#10060) (@mistercrunch)
- [#10041](https://github.com/apache/incubator-superset/pull/10041) feat: owners profile icon on dataset list view (#10041) (@lilykuang)
- [#10037](https://github.com/apache/incubator-superset/pull/10037) fix: native annotations (#10037) (@villebro)
- [#10046](https://github.com/apache/incubator-superset/pull/10046) fix: bump pydruid version (#10046) (@villebro)
- [#10048](https://github.com/apache/incubator-superset/pull/10048) feat: add deafult buildQuery for V1 chart data requests (#10048) (@villebro)
- [#10042](https://github.com/apache/incubator-superset/pull/10042) other: [logging] add dashboard is_published into perf logging (#10042) (@graceguo-supercat)
- [#10031](https://github.com/apache/incubator-superset/pull/10031) chore(security): Renaming access methods (#10031) (@john-bodley)
- [#10030](https://github.com/apache/incubator-superset/pull/10030) chore(security): Renaming schemas_accessible_by_user (#10030) (@john-bodley)
- [#10039](https://github.com/apache/incubator-superset/pull/10039) fix(security): can_access with None crashes on builtin roles (#10039) (@dpgaspar)
- [#9971](https://github.com/apache/incubator-superset/pull/9971) [csv upload][hive] support other delimiters (#9971) (@serenajiang)
- [#10036](https://github.com/apache/incubator-superset/pull/10036) Fix chart annotation source type showing perpetual loading when re-selecting the same value. (#10036) (@nruhe)
- [#10033](https://github.com/apache/incubator-superset/pull/10033) fix: Resolves a regression from #9939 (#10033) (@john-bodley)
- [#10014](https://github.com/apache/incubator-superset/pull/10014) fix: bump pydruid to 0.6.0 (#10014) (@karen-pal)
- [#10025](https://github.com/apache/incubator-superset/pull/10025) feat: Add owners and physical/virtual status to Datasets API list endpoint (#10025) (@willbarrett)
- [#9982](https://github.com/apache/incubator-superset/pull/9982) feat: implement secondary navigation for datasets (#9982) (@lilykuang)
- [#9764](https://github.com/apache/incubator-superset/pull/9764) feat: add replace option to hive csv upload (#9764) (@etr2460)
- [#9859](https://github.com/apache/incubator-superset/pull/9859) fix: raise error in sqllab when using reserved column name (#9859) (@villebro)
- [#10018](https://github.com/apache/incubator-superset/pull/10018) chore: bump superset-ui to 0.13.27 (#10018) (@villebro)
- [#9673](https://github.com/apache/incubator-superset/pull/9673) refactor(sql): optimize sql query parser (#9673) (@lilykuang)
- [#9997](https://github.com/apache/incubator-superset/pull/9997) chore: remove markup from viz.py (#9997) (@villebro)
- [#10003](https://github.com/apache/incubator-superset/pull/10003) fix(SankeyViz): enforce source/target order (#10003) (@serenajiang)
- [#10021](https://github.com/apache/incubator-superset/pull/10021) fix: flaky test_copy_dash test in dashboard_tests.py (#10021) (@villebro)
- [#10010](https://github.com/apache/incubator-superset/pull/10010) feat: add support for query offset (#10010) (@villebro)
- [#10015](https://github.com/apache/incubator-superset/pull/10015) Quick fix for bad regex in GH Workflow (#10015) (@craig-rueda)
- [#10001](https://github.com/apache/incubator-superset/pull/10001) style(mypy): Spit-and-polish pass (#10001) (@john-bodley)
- [#10000](https://github.com/apache/incubator-superset/pull/10000) fix: [logging] fix render chart error type (#10000) (@graceguo-supercat)
- [#9939](https://github.com/apache/incubator-superset/pull/9939) style(mypy): Enforcing typing for superset.views (#9939) (@john-bodley)
- [#9975](https://github.com/apache/incubator-superset/pull/9975) feat: finalize Word Cloud move to new chart data endpoint (#9975) (@villebro)
- [#9989](https://github.com/apache/incubator-superset/pull/9989) fix: remove -1,-1 from owners request in charts, dashboards list and propertiesModal (#9989) (@nytai)
- [#9965](https://github.com/apache/incubator-superset/pull/9965) fix(api): Wrong parameter name on database openapi spec (#9965) (@dpgaspar)
- [#9960](https://github.com/apache/incubator-superset/pull/9960) docs: add ELMO Cloud HR & Payroll to list of users on readme (#9960) (@rayzor65)
- [#9934](https://github.com/apache/incubator-superset/pull/9934) fix: display python_date_format in react views as well (#9934) (@bkyryliuk)
- [#9784](https://github.com/apache/incubator-superset/pull/9784) fix: fetch all owners for dashboard, chart listview filters and properties modal (#9784) (@nytai)
- [#9943](https://github.com/apache/incubator-superset/pull/9943) style(mypy): Enforcing typing for superset (#9943) (@john-bodley)
- [#9886](https://github.com/apache/incubator-superset/pull/9886) feat: [dashboard] notification and warning for auto force refresh (#9886) (@graceguo-supercat)
- [#9973](https://github.com/apache/incubator-superset/pull/9973) fix(mypy): Resolves regression introducted in #9824 (#9973) (@john-bodley)
- [#9456](https://github.com/apache/incubator-superset/pull/9456) fix #8302, disabling save button when can_add: false, can_overwrite: false (#9456) (@micimize)
- [#9954](https://github.com/apache/incubator-superset/pull/9954) fix: pinot select query logic (#9954) (@fx19880617)
- [#9888](https://github.com/apache/incubator-superset/pull/9888) feat: make CRUD annotations inline (#9888) (@mistercrunch)
- [#9969](https://github.com/apache/incubator-superset/pull/9969) Revert "[caching] Using request context rather than globals" (#9969) (@john-bodley)
- [#9970](https://github.com/apache/incubator-superset/pull/9970) style(mypy): Fix memoize watch type (#9970) (@john-bodley)
- [#9962](https://github.com/apache/incubator-superset/pull/9962) feat: implement new version of word cloud (#9962) (@villebro)
- [#9903](https://github.com/apache/incubator-superset/pull/9903) feat: expand new chart data endpoint coverage (#9903) (@villebro)
- [#9901](https://github.com/apache/incubator-superset/pull/9901) build: add workflow preferring TypeScript files (#9901) (@etr2460)
- [#9952](https://github.com/apache/incubator-superset/pull/9952) [filter_box] disable instant_filtering by defult (#9952) (@graceguo-supercat)
- [#9940](https://github.com/apache/incubator-superset/pull/9940) fix: FilterBox Select should be Creatable (#9940) (@ktmud)
- [#9905](https://github.com/apache/incubator-superset/pull/9905) [mypy] Enforcing typing for superset.utils (#9905) (@john-bodley)
- [#9912](https://github.com/apache/incubator-superset/pull/9912) style(mypy): Enforcing mypy typing for views.chart (#9912) (@john-bodley)
- [#9920](https://github.com/apache/incubator-superset/pull/9920) style(mypy): Enforcing typing for views.database (#9920) (@john-bodley)
- [#9921](https://github.com/apache/incubator-superset/pull/9921) style(mypy): Enforcing typing for views.dashboard (#9921) (@john-bodley)
- [#9933](https://github.com/apache/incubator-superset/pull/9933) fix(react-select): FilterBox focus event and adhoc filter popup height (#9933) (@ktmud)
- [#9908](https://github.com/apache/incubator-superset/pull/9908) Revert "feat: bump superset-ui and implement queryFields in formData (#9908)" (#9931) (@villebro)
- [#9926](https://github.com/apache/incubator-superset/pull/9926) feat: bump sinon to latest (#9926) (@villebro)
- [#9915](https://github.com/apache/incubator-superset/pull/9915) fix: annotation layer json (#9915) (@etr2460)
- [#9890](https://github.com/apache/incubator-superset/pull/9890) css: beautify roles CRUD (#9890) (@mistercrunch)
- [#9919](https://github.com/apache/incubator-superset/pull/9919) fix: bump nvd3 plugin for annotation styles (#9919) (@etr2460)
- [#9916](https://github.com/apache/incubator-superset/pull/9916) feat: paired t testchart control migration (#9916) (@pkdotson)
- [#9851](https://github.com/apache/incubator-superset/pull/9851) feat: remove para controls (#9851) (@pkdotson)
- [#9908](https://github.com/apache/incubator-superset/pull/9908) feat: bump superset-ui and implement queryFields in formData (#9908) (@villebro)
- [#9891](https://github.com/apache/incubator-superset/pull/9891) fix: [dashboard] should not trigger chart refresh when filter not applicable (#9891) (@graceguo-supercat)
- [#9824](https://github.com/apache/incubator-superset/pull/9824) style: enforcing mypy typing for connectors (#9824) (@john-bodley)
- [#9826](https://github.com/apache/incubator-superset/pull/9826) chore(ts): convert base js files to typescript (#9826) (@etr2460)
- [#9895](https://github.com/apache/incubator-superset/pull/9895) Fix missing line break in Database 'extra' instructions (#9895) (@tooptoop4)
- [#9883](https://github.com/apache/incubator-superset/pull/9883) [mypy] Enforcing typing for superset.models (#9883) (@john-bodley)
- [#9884](https://github.com/apache/incubator-superset/pull/9884) fix: reenable table chart page length (#9884) (@ktmud)
- [#9881](https://github.com/apache/incubator-superset/pull/9881) fix: don't create examples db on 'superset init' (#9881) (@mistercrunch)
- [#9734](https://github.com/apache/incubator-superset/pull/9734) feat: implement csv upload configuration func for the schema enforcement (#9734) (@bkyryliuk)
- [#9847](https://github.com/apache/incubator-superset/pull/9847) feat: tablechart plugin controls migration (#9847) (@pkdotson)
- [#9864](https://github.com/apache/incubator-superset/pull/9864) Update README Resources section (#9864) (@ceohockey60)
- [#9839](https://github.com/apache/incubator-superset/pull/9839) fix: chart datasource explore URL showing datasource name for druid (#9839) (@dpgaspar)
- [#9820](https://github.com/apache/incubator-superset/pull/9820) refactor(frontend): move utils to TypeScript (#9820) (@ChristianMurphy)
- [#9865](https://github.com/apache/incubator-superset/pull/9865) only skip fossa on PRs (#9865) (@nytai)
- [#9811](https://github.com/apache/incubator-superset/pull/9811) fix: Emit a warning message rather than an exception on query failure (#9811) (@willbarrett)
- [#9853](https://github.com/apache/incubator-superset/pull/9853) feat: Adding Emotion ThemeProviders for all React apps (#9853) (@rusackas)
- [#9848](https://github.com/apache/incubator-superset/pull/9848) upgrade plugins, remove unnecessary code (#9848) (@suddjian)
- [#9841](https://github.com/apache/incubator-superset/pull/9841) feat: Add new result formats and types to chart data API (#9841) (@villebro)
- [#9832](https://github.com/apache/incubator-superset/pull/9832) fix: Removing the logic to add timeseries_limit_metric to the data for table (#9832) (@michellethomas)
- [#9862](https://github.com/apache/incubator-superset/pull/9862) Revert "[sqllab] fix, strip comments before parsing statements" (#9862) (@john-bodley)
- [#9850](https://github.com/apache/incubator-superset/pull/9850) fix: Fix for updated cache dependency (#9850) (@craig-rueda)
- [#9816](https://github.com/apache/incubator-superset/pull/9816) fixes: limit no authorization error for sentry (#9816) (@lilykuang)
- [#9846](https://github.com/apache/incubator-superset/pull/9846) docs: Disable sphinx-autodoc-typehints (#9846) (@john-bodley)
- [#9806](https://github.com/apache/incubator-superset/pull/9806) feat: markupchart plugin controls migration (#9806) (@pkdotson)
- [#9628](https://github.com/apache/incubator-superset/pull/9628) feat: upgrade react-select and make multi-select sortable (#9628) (@ktmud)
- [#9814](https://github.com/apache/incubator-superset/pull/9814) build: conditionally run fossa check if dependency files change (#9814) (@nytai)
- [#9837](https://github.com/apache/incubator-superset/pull/9837) fix: issue with duplicated dependencies (#9837) (@craig-rueda)
- [#9817](https://github.com/apache/incubator-superset/pull/9817) [explore view] inline edit slice name should not overwrite (#9817) (@graceguo-supercat)
- [#9833](https://github.com/apache/incubator-superset/pull/9833) build: Updating the sphinx configuration (#9833) (@john-bodley)
- [#9829](https://github.com/apache/incubator-superset/pull/9829) fix: [filter_box] fix 2 issues in single value filter_box (#9829) (@graceguo-supercat)
- [#9834](https://github.com/apache/incubator-superset/pull/9834) Update the link to ClickHouse official website (#9834) (@blinkov)
- [#9768](https://github.com/apache/incubator-superset/pull/9768) feat: partitionchart controls migration (#9768) (@pkdotson)
- [#9808](https://github.com/apache/incubator-superset/pull/9808) feat: bump superset-ui/time-format and big-number (#9808) (@ktmud)
- [#9835](https://github.com/apache/incubator-superset/pull/9835) fix: use snake_case for error_type (#9835) (@etr2460)
- [#9831](https://github.com/apache/incubator-superset/pull/9831) refactor: simplify getExploreUrl functions (#9831) (@villebro)
- [#9828](https://github.com/apache/incubator-superset/pull/9828) build: tox leveraging conditional factors (#9828) (@john-bodley)
- [#9819](https://github.com/apache/incubator-superset/pull/9819) feat(frontend): add ability to download dashboard and chart as image (#9819) (@ChristianMurphy)
- [#9827](https://github.com/apache/incubator-superset/pull/9827) build: Add python 3.8 to the build matrix (#9827) (@etr2460)
- [#9800](https://github.com/apache/incubator-superset/pull/9800) [mypy] Enforcing typing for translations (#9800) (@john-bodley)
- [#9789](https://github.com/apache/incubator-superset/pull/9789) FilterBox,BigNumber,WorldMap: Handle empty results - second attempt (#9789) (@elukey)
- [#9790](https://github.com/apache/incubator-superset/pull/9790) fix(babel): broken babel extract (#9790) (@dpgaspar)
- [#9774](https://github.com/apache/incubator-superset/pull/9774) upgrade migrated plugins, remove controlPanel configs (#9774) (@suddjian)
- [#9803](https://github.com/apache/incubator-superset/pull/9803) fix: invalid JSON in FR and DE translations (#9803) (@villebro)
- [#9752](https://github.com/apache/incubator-superset/pull/9752) fix(mssql): reverts #9644 and displays a better error msg (#9752) (@dpgaspar)
- [#9775](https://github.com/apache/incubator-superset/pull/9775) tests(celery): improve celery tests infra (#9775) (@dpgaspar)
- [#9796](https://github.com/apache/incubator-superset/pull/9796) feat: return security errors in the SIP-40 format (#9796) (@etr2460)
- [#9799](https://github.com/apache/incubator-superset/pull/9799) fix: construct SupersetErrors properly (#9799) (@etr2460)
- [#9798](https://github.com/apache/incubator-superset/pull/9798) fix: add flag for time series table (#9798) (@kristw)
- [#9342](https://github.com/apache/incubator-superset/pull/9342) Add extra configration to the email reports (#9342) (@bkyryliuk)
- [#9786](https://github.com/apache/incubator-superset/pull/9786) chore: bump python dependencies (#9786) (@villebro)
- [#9795](https://github.com/apache/incubator-superset/pull/9795) docs: add whale.im to list of users (#9795) (@Fullstop000)
- [#9753](https://github.com/apache/incubator-superset/pull/9753) feat: convert backend chart errors to the new error type (#9753) (@etr2460)
- [#9791](https://github.com/apache/incubator-superset/pull/9791) build: disable webpack progress for CI (#9791) (@ktmud)
- [#9655](https://github.com/apache/incubator-superset/pull/9655) chore: add support for prlint: a commit msg linter (#9655) (@mistercrunch)
- [#9715](https://github.com/apache/incubator-superset/pull/9715) [caching] Using request context rather than globals (#9715) (@john-bodley)
- [#9773](https://github.com/apache/incubator-superset/pull/9773) chore: move bullet string manipulation to frontend (#9773) (@villebro)
- [#9782](https://github.com/apache/incubator-superset/pull/9782) fix: make tests work individually (#9782) (@villebro)
- [#9779](https://github.com/apache/incubator-superset/pull/9779) tests: Fix, script to help run single tests (#9779) (@dpgaspar)
- [#9778](https://github.com/apache/incubator-superset/pull/9778) Add QueryContext deserialization test (#9778) (@villebro)
- [#9767](https://github.com/apache/incubator-superset/pull/9767) [docs] Add SSL config options for postgres (#9767) (@nytai)
- [#9756](https://github.com/apache/incubator-superset/pull/9756) Fix SQL Lab schema permission checks (#9756) (@bkyryliuk)
- [#9691](https://github.com/apache/incubator-superset/pull/9691) fix: Catch db_engine_spec.get_function_names exceptions (#9691) (@bkyryliuk)
- [#9714](https://github.com/apache/incubator-superset/pull/9714) fix bug where error at import dashboard fails to show toast in "welcome" app (#9714) (@pkdotson)
- [#9771](https://github.com/apache/incubator-superset/pull/9771) use the builtin calendar control panel (#9771) (@suddjian)
- [#9770](https://github.com/apache/incubator-superset/pull/9770) use word cloud's built in control panel (#9770) (@suddjian)
- [#9761](https://github.com/apache/incubator-superset/pull/9761) test(frontend): use absolute path for src imports (#9761) (@ktmud)
- [#9766](https://github.com/apache/incubator-superset/pull/9766) [docs] add postgres documentation (#9766) (@nytai)
- [#9762](https://github.com/apache/incubator-superset/pull/9762) fix: implement force refresh in chart data request (#9762) (@villebro)
- [#9710](https://github.com/apache/incubator-superset/pull/9710) Query the new chart data api for charts that support it (#9710) (@suddjian)
- [#9758](https://github.com/apache/incubator-superset/pull/9758) fix: removing controls from incubator-superset that are now in the plugin (#9758) (@rusackas)
- [#9757](https://github.com/apache/incubator-superset/pull/9757) build: bump superset-ui to 0.13 (#9757) (@kristw)
- [#9731](https://github.com/apache/incubator-superset/pull/9731) Update README.md (#9731) (@ceohockey60)
- [#9671](https://github.com/apache/incubator-superset/pull/9671) Revert "FilterBox,BigNumber,WorldMap: Handle empty results (#9671)" (#9755) (@etr2460)
- [#9745](https://github.com/apache/incubator-superset/pull/9745) [perf logging] Add extra logging for new/editMode dash (#9745) (@graceguo-supercat)
- [#9652](https://github.com/apache/incubator-superset/pull/9652) fix: adhoc filter 'equals' doesn't let you save (#9652) (@mistercrunch)
- [#9709](https://github.com/apache/incubator-superset/pull/9709) Use monospace (#9709) (@bkyryliuk)
- [#9740](https://github.com/apache/incubator-superset/pull/9740) feat: Add controlGroups to formData (#9740) (@villebro)
- [#9739](https://github.com/apache/incubator-superset/pull/9739) perf(dataset): improve performance on get list (#9739) (@dpgaspar)
- [#9749](https://github.com/apache/incubator-superset/pull/9749) fix: Add force flag to QueryContext schema (#9749) (@villebro)
- [#9654](https://github.com/apache/incubator-superset/pull/9654) fix: autocomplete search in AdhocFilter operator dropdown (#9654) (@mistercrunch)
- [#9744](https://github.com/apache/incubator-superset/pull/9744) fix: re-add all time grain options to the new chart API (#9744) (@etr2460)
- [#9728](https://github.com/apache/incubator-superset/pull/9728) chore: update README screenshots (#9728) (@etr2460)
- [#9733](https://github.com/apache/incubator-superset/pull/9733) [perf logging] Add timing event when browser tab is hidden (#9733) (@graceguo-supercat)
- [#9742](https://github.com/apache/incubator-superset/pull/9742) fix: css prop is now overwhelmed with emotion (#9742) (@suddjian)
- [#9724](https://github.com/apache/incubator-superset/pull/9724) docs(api): improve openapi documentation for dash, charts and queries (#9724) (@dpgaspar)
- [#9703](https://github.com/apache/incubator-superset/pull/9703) fix(dependencies): Bump FAB to 2.3.4 (#9703) (@dpgaspar)
- [#9704](https://github.com/apache/incubator-superset/pull/9704) perf(dashboards): improve API performance for dashboards (#9704) (@dpgaspar)
- [#9735](https://github.com/apache/incubator-superset/pull/9735) Fix bad test (#9735) (@bkyryliuk)
- [#9671](https://github.com/apache/incubator-superset/pull/9671) FilterBox,BigNumber,WorldMap: Handle empty results (#9671) (@elukey)
- [#9692](https://github.com/apache/incubator-superset/pull/9692) [sqllab] fix, strip comments before parsing statements (#9692) (@nytai)
- [#9716](https://github.com/apache/incubator-superset/pull/9716) [hive][csv upload] make INTs BIGINTs (#9716) (@serenajiang)
- [#9712](https://github.com/apache/incubator-superset/pull/9712) feat: add logging to warm_up_cache endpoint (#9712) (@etr2460)
- [#9695](https://github.com/apache/incubator-superset/pull/9695) Fix email reports (#9695) (@bkyryliuk)
- [#9707](https://github.com/apache/incubator-superset/pull/9707) migrate bignumber to use builtin controls (#9707) (@suddjian)
- [#9706](https://github.com/apache/incubator-superset/pull/9706) [table editor] hide Edit Datasource option when no onDatasourceSave (#9706) (@graceguo-supercat)
- [#9693](https://github.com/apache/incubator-superset/pull/9693) chore(ts): type getClientErrorObject (#9693) (@etr2460)
- [#9696](https://github.com/apache/incubator-superset/pull/9696) chore: Bump PyArrow to latest stable version (#9696) (@villebro)
- [#9694](https://github.com/apache/incubator-superset/pull/9694) [Helm] - Allow for customization of release name (#9694) (@craig-rueda)
- [#9702](https://github.com/apache/incubator-superset/pull/9702) tests(engine_specs): full bigquery engine coverage (#9702) (@dpgaspar)
- [#9705](https://github.com/apache/incubator-superset/pull/9705) fix(security) Fixing regression in #9689 (#9705) (@john-bodley)
- [#9619](https://github.com/apache/incubator-superset/pull/9619) perf(charts): improve performance on GET list (#9619) (@dpgaspar)
- [#9684](https://github.com/apache/incubator-superset/pull/9684) fix: migrate all slices off the old time grain format (#9684) (@etr2460)
- [#9649](https://github.com/apache/incubator-superset/pull/9649) [sql] Adding lighweight Table class (#9649) (@john-bodley)
- [#9651](https://github.com/apache/incubator-superset/pull/9651) [Chart & Dashboard] improve listview filter ui and add expandable list support (#9651) (@nytai)
- [#9677](https://github.com/apache/incubator-superset/pull/9677) feat(errors): add client scaffolding for custom error messages (#9677) (@etr2460)
- [#9688](https://github.com/apache/incubator-superset/pull/9688) Make cache work again for annotations (#9688) (@bkyryliuk)
- [#9689](https://github.com/apache/incubator-superset/pull/9689) Fix ENABLE_ROW_LEVEL_SECURITY flag usage (#9689) (@bkyryliuk)
- [#9665](https://github.com/apache/incubator-superset/pull/9665) [debug] Debugging caching issue (#9665) (@john-bodley)
- [#9685](https://github.com/apache/incubator-superset/pull/9685) [fix] reduce table metadata fetch for latest_partition check (#9685) (@graceguo-supercat)
- [#9682](https://github.com/apache/incubator-superset/pull/9682) tests(engine_specs): full postgres engine coverage (#9682) (@dpgaspar)
- [#9679](https://github.com/apache/incubator-superset/pull/9679) fix(tests): custom filter flaky tests on dash and charts (#9679) (@dpgaspar)
- [#9637](https://github.com/apache/incubator-superset/pull/9637) [explore view] add partition as adhoc filter option (#9637) (@graceguo-supercat)
- [#9669](https://github.com/apache/incubator-superset/pull/9669) fix: catch viz exceptions (#9669) (@villebro)
- [#9662](https://github.com/apache/incubator-superset/pull/9662) [logging] deprecation notices for SQLite (#9662) (@lilykuang)
- [#9676](https://github.com/apache/incubator-superset/pull/9676) gotta catch 'em all (#9676) (@rusackas)
- [#9666](https://github.com/apache/incubator-superset/pull/9666) [fix] Support APP_ICON_WIDTH configuration parameter in SPA menu (#9666) (@nruhe)
- [#9661](https://github.com/apache/incubator-superset/pull/9661) feat: Add geospatial post processing operations (#9661) (@villebro)
- [#9624](https://github.com/apache/incubator-superset/pull/9624) [fix] Push browser history on pagination in react listviews (#9624) (@lilykuang)
- [#9663](https://github.com/apache/incubator-superset/pull/9663) chore(ts): refactor and migrate StackTraceMessage to TypeScript (#9663) (@etr2460)
- [#9634](https://github.com/apache/incubator-superset/pull/9634) fix(database): test connection error message for module not found (#9634) (@dpgaspar)
- [#9644](https://github.com/apache/incubator-superset/pull/9644) fix(mssql): apply limit and set alias for functions (#9644) (@dpgaspar)
- [#9578](https://github.com/apache/incubator-superset/pull/9578) Controls cleanup pass (#9578) (@rusackas)
- [#9640](https://github.com/apache/incubator-superset/pull/9640) [dashboard] increase tab count limit (#9640) (@graceguo-supercat)
- [#9586](https://github.com/apache/incubator-superset/pull/9586) [mypy] Enforcing typing for a number of modules (#9586) (@john-bodley)
- [#9639](https://github.com/apache/incubator-superset/pull/9639) docs: remove references to superset-ui-plugins (#9639) (@ktmud)
- [#9577](https://github.com/apache/incubator-superset/pull/9577) [datesets] feat: add statsd to datasets api (#9577) (@lilykuang)
- [#9572](https://github.com/apache/incubator-superset/pull/9572) [fix] Automatically add relevant Jinja methods to cache key if present (#9572) (@john-bodley)
- [#9643](https://github.com/apache/incubator-superset/pull/9643) Cast raw 'fetch_values_predicate' to Sqlalchemy Text (#9643) (@vnnw)
- [#9641](https://github.com/apache/incubator-superset/pull/9641) [fix] Fixing regression from #9161 (#9641) (@john-bodley)
- [#9599](https://github.com/apache/incubator-superset/pull/9599) chore: Improve chart data API + schemas + tests (#9599) (@villebro)
- [#9612](https://github.com/apache/incubator-superset/pull/9612) [sql_lab] Improve performance, only use slow func when needed (#9612) (@dpgaspar)
- [#9605](https://github.com/apache/incubator-superset/pull/9605) use new @superset-ui/style package for theming (#9605) (@suddjian)
- [#9608](https://github.com/apache/incubator-superset/pull/9608) fix: change number format to original value to "~g" (#9608) (@ktmud)
- [#9613](https://github.com/apache/incubator-superset/pull/9613) fix: broken IS NULL and IS NOT NULL operator (#9613) (@villebro)
- [#9592](https://github.com/apache/incubator-superset/pull/9592) fix: move docs image symlink to _static (#9592) (@villebro)
- [#9614](https://github.com/apache/incubator-superset/pull/9614) [statsd] Send time metrics in ms not seconds (#9614) (@dpgaspar)
- [#9346](https://github.com/apache/incubator-superset/pull/9346) improve helm chart (#9346) (@fbalicchia)
- [#9587](https://github.com/apache/incubator-superset/pull/9587) [SQL Lab] Lock result set controls to be always visible (#9587) (@etr2460)
- [#9530](https://github.com/apache/incubator-superset/pull/9530) [charts] adds new filters ui (#9530) (@nytai)
- [#9547](https://github.com/apache/incubator-superset/pull/9547) [tests] Helper script to run single tests (#9547) (@dpgaspar)
- [#9571](https://github.com/apache/incubator-superset/pull/9571) [charts] feat: add statsd to charts api (#9571) (@lilykuang)
- [#9602](https://github.com/apache/incubator-superset/pull/9602) Bump superset-ui-connection to 0.12.22 (#9602) (@etr2460)
- [#9562](https://github.com/apache/incubator-superset/pull/9562) Add raw number/integer option to d3 number formats (#9562) (@ktmud)
- [#9569](https://github.com/apache/incubator-superset/pull/9569) Add documentation build to Github Actions (#9569) (@willbarrett)
- [#9591](https://github.com/apache/incubator-superset/pull/9591) [Build] moves prettier check to separate script (#9591) (@nytai)
- [#9582](https://github.com/apache/incubator-superset/pull/9582) [fix] Fixing issue with Jinja filter_value (#9582) (@john-bodley)
- [#9598](https://github.com/apache/incubator-superset/pull/9598) [docs] fix, elastic.co elasticsearch product location (#9598) (@dpgaspar)
- [#9585](https://github.com/apache/incubator-superset/pull/9585) [config] Enable dashboard bootstrap payload reduction by default (#9585) (@etr2460)
- [#9443](https://github.com/apache/incubator-superset/pull/9443) Re-enable the AnnotationLayerModelView read API (#9443) (@etr2460)
- [#9583](https://github.com/apache/incubator-superset/pull/9583) [mypy] Enforcing typing for superset.migrations (#9583) (@john-bodley)
- [#9579](https://github.com/apache/incubator-superset/pull/9579) fix: Add deprecated fields to QueryObject schema (#9579) (@villebro)
- [#9525](https://github.com/apache/incubator-superset/pull/9525) Migrating shared NVD3 controls to new module (#9525) (@rusackas)
- [#9570](https://github.com/apache/incubator-superset/pull/9570) doc: Add changelog for 0.36.0 (#9570) (@villebro)
- [#9567](https://github.com/apache/incubator-superset/pull/9567) [pypi] Include compiled translations on Pypi pkg (#9567) (@dpgaspar)
- [#9556](https://github.com/apache/incubator-superset/pull/9556) chore: Add OpenAPI docs to /api/v1/chart/data EP (#9556) (@villebro)
- [#9566](https://github.com/apache/incubator-superset/pull/9566) chore: Migrate unique FilterBox controls from controls.jsx (#9566) (@villebro)
- [#9555](https://github.com/apache/incubator-superset/pull/9555) [Build] Collect frontend code coverage from Cypress tests (#9555) (@ktmud)
- [#9550](https://github.com/apache/incubator-superset/pull/9550) [fix] dashboard filter scope bug (#9550) (@graceguo-supercat)
- [#9560](https://github.com/apache/incubator-superset/pull/9560) [fix] warm up cache error handling (#9560) (@john-bodley)
- [#9519](https://github.com/apache/incubator-superset/pull/9519) [dashboard] New, add statsd metrics to the API (#9519) (@dpgaspar)
- [#9549](https://github.com/apache/incubator-superset/pull/9549) Bump @superset-ui/legacy-plugin-chart-table to 0.12.14 (#9549) (@ktmud)
- [#9548](https://github.com/apache/incubator-superset/pull/9548) Ditching travis config in favor of Github Actions (#9548) (@craig-rueda)
- [#9539](https://github.com/apache/incubator-superset/pull/9539) Bump copyright notice (#9539) (@villebro)
- [#9536](https://github.com/apache/incubator-superset/pull/9536) docs: point our README the maintained Docker image (#9536) (@mistercrunch)
- [#9523](https://github.com/apache/incubator-superset/pull/9523) Make email parsing more robust (#9523) (@bkyryliuk)
- [#9541](https://github.com/apache/incubator-superset/pull/9541) [copy] fix: Row Level Security get_rls_filters func SELECT statement (#9541) (@axelet)
- [#8947](https://github.com/apache/incubator-superset/pull/8947) [thumbnails] API and celery task for dashboards and charts (#8947) (@dpgaspar)
- [#9537](https://github.com/apache/incubator-superset/pull/9537) [list views] add work-break css for table layouts (#9537) (@nytai)
- [#9538](https://github.com/apache/incubator-superset/pull/9538) Run CI on all pushes / PR's (#9538) (@craig-rueda)
- [#9535](https://github.com/apache/incubator-superset/pull/9535) Disabling recording in Cypress tests (#9535) (@craig-rueda)
- [#9517](https://github.com/apache/incubator-superset/pull/9517) [Build] Add Github workflows (#9517) (@ktmud)
- [#9533](https://github.com/apache/incubator-superset/pull/9533) Fix typo in viz.py (#9533) (@willbarrett)
- [#9465](https://github.com/apache/incubator-superset/pull/9465) Importing validators module from superset-ui (#9465) (@rusackas)
- [#9520](https://github.com/apache/incubator-superset/pull/9520) [api] refactor, remove unnecessary code, using command pattern now (#9520) (@dpgaspar)
- [#9521](https://github.com/apache/incubator-superset/pull/9521) [dashboard] Fix, improve test for custom filter (#9521) (@dpgaspar)
- [#9366](https://github.com/apache/incubator-superset/pull/9366) deprecate groupby controls in query_obj (#9366) (@villebro)
- [#9522](https://github.com/apache/incubator-superset/pull/9522) Another attempt to fix a viz.py bug (#9522) (@willbarrett)
- [#9500](https://github.com/apache/incubator-superset/pull/9500) control to turn off table bar-chart backgrounds (#9500) (@rusackas)
- [#9507](https://github.com/apache/incubator-superset/pull/9507) chore: auto label issues based on the template chosen (#9507) (@mistercrunch)
- [#9462](https://github.com/apache/incubator-superset/pull/9462) [Dashboard] new listview filters & emotion infra (#9462) (@nytai)
- [#9509](https://github.com/apache/incubator-superset/pull/9509) docker node_modules in its own volume (#9509) (@octaviancorlade)
- [#9492](https://github.com/apache/incubator-superset/pull/9492) [charts] New, custom filter for name OR description (#9492) (@dpgaspar)
- [#9503](https://github.com/apache/incubator-superset/pull/9503) Bump sqlalchemy and dremio deps (#9503) (@villebro)
- [#9370](https://github.com/apache/incubator-superset/pull/9370) Add visualization flow to the CTA queries (#9370) (@bkyryliuk)
- [#9427](https://github.com/apache/incubator-superset/pull/9427) feat: Add post processing to QueryObject (#9427) (@villebro)
- [#9496](https://github.com/apache/incubator-superset/pull/9496) fix: add lineWidth to Shared_DeckGL.jsx (#9496) (@villebro)
- [#9435](https://github.com/apache/incubator-superset/pull/9435) [dashboards] New, tittle and slug OR filter (#9435) (@dpgaspar)
- [#9484](https://github.com/apache/incubator-superset/pull/9484) [dashboards] Fix, update dashboard owners not propagating to charts o… (#9484) (@dpgaspar)
- [#9491](https://github.com/apache/incubator-superset/pull/9491) Bump FAB to 2.3.2 (#9491) (@dpgaspar)
- [#9479](https://github.com/apache/incubator-superset/pull/9479) [query] Migrate api v1 query to new location (#9479) (@dpgaspar)
- [#9495](https://github.com/apache/incubator-superset/pull/9495) fix: add explore control tabOverride at the section level (#9495) (@ktmud)
- [#9486](https://github.com/apache/incubator-superset/pull/9486) feat: change default time range in sql lab explore (#9486) (@ktmud)
- [#9493](https://github.com/apache/incubator-superset/pull/9493) Upgrade table chart plugin to 0.12.13 (#9493) (@ktmud)
- [#9460](https://github.com/apache/incubator-superset/pull/9460) pylint: accept specific 2 character names by default (#9460) (@villebro)
- [#9487](https://github.com/apache/incubator-superset/pull/9487) [tests] refactor, change datasets and charts to it's own folder (#9487) (@dpgaspar)
- [#9376](https://github.com/apache/incubator-superset/pull/9376) [sqllab] Add CUSTOM_TEMPLATE_PROCESSOR config (#9376) (@dandanhub)
- [#9480](https://github.com/apache/incubator-superset/pull/9480) Handle empty dataframes in TableViz (#9480) (@elukey)
- [#9337](https://github.com/apache/incubator-superset/pull/9337) Filter owners select by text input (#9337) (@suddjian)
- [#9437](https://github.com/apache/incubator-superset/pull/9437) [datasets] Add strict type annotation (#9437) (@dpgaspar)
- [#9418](https://github.com/apache/incubator-superset/pull/9418) [mypy] Enforcing typing for superset.dashboards (#9418) (@dpgaspar)
- [#9464](https://github.com/apache/incubator-superset/pull/9464) [Doc] Update installation doc for Dremio (#9464) (@narendrans)
- [#9455](https://github.com/apache/incubator-superset/pull/9455) Migrating shared DeckGL controls (#9455) (@rusackas)
- [#9469](https://github.com/apache/incubator-superset/pull/9469) [mypy] Enforcing typing for superset.examples (#9469) (@john-bodley)
- [#9403](https://github.com/apache/incubator-superset/pull/9403) [query] New, readonly API (#9403) (@dpgaspar)
- [#9472](https://github.com/apache/incubator-superset/pull/9472) Added dremio (#9472) (@narendrans)
- [#9451](https://github.com/apache/incubator-superset/pull/9451) release: Add support for ZSH in RELEASING and add tagging instructions (#9451) (@villebro)
- [#9378](https://github.com/apache/incubator-superset/pull/9378) chore: bump black to 19.10b0 and mypy to 0.770 (#9378) (@ktmud)
- [#9416](https://github.com/apache/incubator-superset/pull/9416) [mypy] Enforcing typing for some modules (#9416) (@john-bodley)
- [#9466](https://github.com/apache/incubator-superset/pull/9466) Eslint prefer-object-spread (#9466) (@rusackas)
- [#9454](https://github.com/apache/incubator-superset/pull/9454) Migrating NVD3 Area's stacked_style config (#9454) (@rusackas)
- [#9445](https://github.com/apache/incubator-superset/pull/9445) Migrating unique NVD3 viz controls (#9445) (@rusackas)
- [#9440](https://github.com/apache/incubator-superset/pull/9440) Migrating unique bigNumber(total) controls (#9440) (@rusackas)
- [#9356](https://github.com/apache/incubator-superset/pull/9356) Enforcing linting of LESS (#9356) (@rusackas)
- [#9446](https://github.com/apache/incubator-superset/pull/9446) migrating unique controls (#9446) (@rusackas)
### 0.36.0 (2020/04/02 07:57 +00:00)
- [#9436](https://github.com/apache/incubator-superset/pull/9436) Add check for SSL certificate and add form validators (#9436) (@villebro)
- [#9428](https://github.com/apache/incubator-superset/pull/9428) [fix]some translation not work better (#9428) (@venter-zhu)

View File

@@ -63,44 +63,50 @@ final release. Therefore, it's a good idea to do the following every time you
work on a new phase of the release process to make sure you aren't releasing
the wrong files/using wrong names. There's a script to help you set correctly all the
necessary environment variables. Change your current directory to `superset/RELEASING`
and execute the `set_release_env.sh` script with the relevant parameters:
```bash
# usage (BASH): . set_release_env.sh <SUPERSET_RC_VERSION> <PGP_KEY_FULLNAME>
# usage (ZSH): source set_release_env.sh <SUPERSET_RC_VERSION> <PGP_KEY_FULLNAME>
#
# example: source set_relese_env.sh 0.36.0rc3 myid@apache.org
# example: source set_release_env.sh 0.37.0rc1 myid@apache.org
```
The script will output the exported variables. Here's example for 0.36.0rc3:
The script will output the exported variables. Here's example for 0.37.0rc1:
```
-------------------------------
Set Release env variables
SUPERSET_VERSION=0.36.0
SUPERSET_RC=3
SUPERSET_GITHUB_BRANCH=0.36
SUPERSET_VERSION=0.37.0
SUPERSET_RC=1
SUPERSET_GITHUB_BRANCH=0.37
SUPERSET_PGP_FULLNAME=myid@apache.org
SUPERSET_VERSION_RC=0.36.0rc3
SUPERSET_RELEASE=apache-superset-incubating-0.36.0
SUPERSET_RELEASE_RC=apache-superset-incubating-0.36.0rc3
SUPERSET_RELEASE_TARBALL=apache-superset-incubating-0.36.0-source.tar.gz
SUPERSET_RELEASE_RC_TARBALL=apache-superset-incubating-0.36.0rc3-source.tar.gz
SUPERSET_TMP_ASF_SITE_PATH=/tmp/incubator-superset-site-0.36.0
-------------------------------
SUPERSET_VERSION_RC=0.37.0rc1
SUPERSET_RELEASE=apache-superset-incubating-0.37.0
SUPERSET_RELEASE_RC=apache-superset-incubating-0.37.0rc1
SUPERSET_RELEASE_TARBALL=apache-superset-incubating-0.37.0-source.tar.gz
SUPERSET_RELEASE_RC_TARBALL=apache-superset-incubating-0.37.0rc1-source.tar.gz
SUPERSET_TMP_ASF_SITE_PATH=/tmp/incubator-superset-site-0.37.0
```
## Crafting a source release
When crafting a new minor or major release we create
a branch named with the release MAJOR.MINOR version (on this example 0.35).
a branch named with the release MAJOR.MINOR version (on this example 0.37).
This new branch will hold all PATCH and release candidates
that belong to the MAJOR.MINOR version.
The MAJOR.MINOR branch is normally a "cut" from a specific point in time from the master branch.
Then (if needed) apply all cherries that will make the PATCH
Then (if needed) apply all cherries that will make the PATCH.
Next update the `CHANGELOG.md` with all the changes that are included in the release. Make sure you have
set your GITHUB_TOKEN environment variable.
```bash
git checkout -b $SUPERSET_GITHUB_BRANCH
git push upstream $SUPERSET_GITHUB_BRANCH
```
Next, update the `CHANGELOG.md` with all the changes that are included in the release.
Make sure the branch has been pushed to `upstream` to ensure the changelog generator
can pick up changes since the previous release (otherwise `github-changes` will raise
an `Error: Not Found` exception).
```bash
# will overwrites the local CHANGELOG.md, somehow you need to merge it in
@@ -293,7 +299,7 @@ with the changes on `CHANGELOG.md` and `UPDATING.md`.
### Publishing a Convenience Release to PyPI
Using the final release tarball, unpack it and run `./pypi_push.sh`.
This script will build the Javascript bundle and echo the twine command
This script will build the Javascript bundle and echo the twine command
allowing you to publish to PyPI. You may need to ask a fellow committer to grant
you access to it if you don't have access already. Make sure to create
an account first if you don't have one, and reference your username

View File

@@ -34,6 +34,9 @@ https://github.com/apache/incubator-{{ project_module }}/tree/{{ version_rc }}
The Change Log for the release:
https://github.com/apache/incubator-{{ project_module }}/blob/{{ version_rc }}/CHANGELOG.md
The Updating instructions for the release:
https://github.com/apache/incubator-{{ project_module }}/blob/{{ version_rc }}/UPDATING.md
public keys are available at:
https://www.apache.org/dist/incubator/{{ project_module }}/KEYS

View File

@@ -19,7 +19,7 @@ usage() {
echo "usage (BASH): . set_release_env.sh <SUPERSET_RC_VERSION> <PGP_KEY_FULLNAME>"
echo "usage (ZSH): source set_release_env.sh <SUPERSET_RC_VERSION> <PGP_KEY_FULLNAME>"
echo
echo "example: source set_relese_env.sh 0.36.0rc3 myid@apache.org"
echo "example: source set_release_env.sh 0.37.0rc1 myid@apache.org"
}
if [ -z "$1" ] || [ -z "$2" ]; then

View File

@@ -21,7 +21,11 @@ under the License.
This file documents any backwards-incompatible changes in Superset and
assists people when migrating to a new version.
## Next
## 0.37.1
* [10794](https://github.com/apache/incubator-superset/pull/10794): Breaking change: `uuid` python package is not supported on Jinja2 anymore, only uuid functions are exposed eg: `uuid1`, `uuid3`, `uuid4`, `uuid5`.
## 0.37.0
* [9964](https://github.com/apache/incubator-superset/pull/9964): Breaking change on Flask-AppBuilder 3. If you're using OAuth, find out what needs to be changed [here](https://github.com/dpgaspar/Flask-AppBuilder/blob/master/README.rst#change-log).
@@ -44,6 +48,8 @@ assists people when migrating to a new version.
* [9572](https://github.com/apache/incubator-superset/pull/9572): a change which by default means that the Jinja `current_user_id`, `current_username`, and `url_param` context calls no longer need to be wrapped via `cache_key_wrapper` in order to be included in the cache key. The `cache_key_wrapper` function should only be required for Jinja add-ons.
## 0.36.0
* [8867](https://github.com/apache/incubator-superset/pull/8867): a change which adds the `tmp_schema_name` column to the `query` table which requires locking the table. Given the `query` table is heavily used performance may be degraded during the migration. Scheduled downtime may be advised.
* [9238](https://github.com/apache/incubator-superset/pull/9238): the config option `TIME_GRAIN_FUNCTIONS` has been renamed to `TIME_GRAIN_EXPRESSIONS` to better reflect the content of the dictionary.

View File

@@ -73,7 +73,10 @@ Superset's Jinja context:
- ``time``: ``time``
- ``datetime``: ``datetime.datetime``
- ``uuid``: ``uuid``
- ``uuid1``: ``uuid1``
- ``uuid3``: ``uuid3``
- ``uuid4``: ``uuid4``
- ``uuid5``: ``uuid5``
- ``random``: ``random``
- ``relativedelta``: ``dateutil.relativedelta.relativedelta``

View File

@@ -1959,7 +1959,9 @@ List of Countries
Need to add a new Country?
-------------------------------
To add a new country in country map tools, we need to follow the following steps :
Warning: adding a new country is not easy and requires building superset from source!
To add a new country in country map tools, you need to follow the following steps :
1. You need shapefiles which contain data of your map.
You can get this file on this site: https://www.diva-gis.org/gdata
@@ -1970,13 +1972,17 @@ To add a new country in country map tools, we need to follow the following steps
3. You need to convert shapefile to geojson file.
This action can make with ogr2ogr tools: https://www.gdal.org/ogr2ogr.html
4. Put your geojson file in next folder : superset-frontend/src/visualizations/CountryMap/countries with the next name : nameofyourcountries.geojson
4. You can to reduce size of geojson file on this site: https://mapshaper.org/
5. You can to reduce size of geojson file on this site: https://mapshaper.org/
5. You will need to put your geojson file in the right place in the @superset-ui npm package. This is the "countries" folder in
./superset-frontend/node_modules/@superset-ui/legacy-plugin-chart-country-map/esm/.
The .geojson files for other countries are already in this folder.
6. Go in file superset-frontend/src/explore/controls.jsx
6. You will also need to edit the "countries.js" file in the same directory, following the pattern of the other countries.
7. Add your country in component 'select_country'
7. Then go to the file superset-frontend/src/explore/controls.jsx
8. Add your country in component 'select_country'
Example :
.. code-block:: javascript
@@ -2005,3 +2011,6 @@ To add a new country in country map tools, we need to follow the following steps
].map(s => [s, s]),
description: 'The name of country that Superset should display',
},
9. Rebuild the front end from source and restart superset.

View File

@@ -29,7 +29,7 @@ decorator==4.4.2 # via retry
defusedxml==0.6.0 # via python3-openid
dnspython==1.16.0 # via email-validator
email-validator==1.1.0 # via flask-appbuilder
flask-appbuilder==3.0.0 # via apache-superset (setup.py)
flask-appbuilder==3.0.1 # via apache-superset (setup.py)
flask-babel==1.0.0 # via flask-appbuilder
flask-caching==1.8.0 # via apache-superset (setup.py)
flask-compress==1.5.0 # via apache-superset (setup.py)
@@ -86,7 +86,7 @@ retry==0.9.2 # via apache-superset (setup.py)
selenium==3.141.0 # via apache-superset (setup.py)
simplejson==3.17.0 # via apache-superset (setup.py)
six==1.14.0 # via bleach, cryptography, flask-jwt-extended, flask-talisman, isodate, jsonschema, packaging, pathlib2, polyline, prison, pyrsistent, python-dateutil, sqlalchemy-utils, wtforms-json
slackclient==2.6.2 # via apache-superset (setup.py)
slackclient==2.5.0 # via apache-superset (setup.py)
sqlalchemy-utils==0.36.6 # via apache-superset (setup.py), flask-appbuilder
sqlalchemy==1.3.16 # via alembic, apache-superset (setup.py), flask-sqlalchemy, marshmallow-sqlalchemy, sqlalchemy-utils
sqlparse==0.3.0 # via apache-superset (setup.py)

View File

@@ -38,7 +38,7 @@ combine_as_imports = true
include_trailing_comma = true
line_length = 88
known_first_party = superset
known_third_party =alembic,apispec,backoff,bleach,cachelib,celery,click,colorama,contextlib2,croniter,cryptography,dataclasses,dateutil,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,geohash,geopy,humanize,isodate,jinja2,markdown,markupsafe,marshmallow,msgpack,numpy,pandas,parameterized,parsedatetime,pathlib2,polyline,prison,pyarrow,pyhive,pytest,pytz,retry,selenium,setuptools,simplejson,slack,sphinx_rtd_theme,sqlalchemy,sqlalchemy_utils,sqlparse,werkzeug,wtforms,wtforms_json,yaml
known_third_party =alembic,apispec,backoff,bleach,cachelib,celery,click,colorama,contextlib2,croniter,cryptography,dateutil,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,geohash,geopy,humanize,isodate,jinja2,markdown,markupsafe,marshmallow,msgpack,numpy,pandas,parameterized,parsedatetime,pathlib2,polyline,prison,pyarrow,pyhive,pytest,pytz,retry,selenium,setuptools,simplejson,slack,sphinx_rtd_theme,sqlalchemy,sqlalchemy_utils,sqlparse,werkzeug,wtforms,wtforms_json,yaml
multi_line_output = 3
order_by_type = false

View File

@@ -78,7 +78,7 @@ setup(
"cryptography>=2.4.2",
"dataclasses<0.7",
"flask>=1.1.0, <2.0.0",
"flask-appbuilder>=3.0.0, <4.0.0",
"flask-appbuilder>=3.0.1, <4.0.0",
"flask-caching",
"flask-compress",
"flask-talisman",
@@ -102,7 +102,7 @@ setup(
"retry>=0.9.2",
"selenium>=3.141.0",
"simplejson>=3.15.0",
"slackclient>=2.6.2",
"slackclient==2.5.0", # PINNED! slack changes file upload api in the future versions
"sqlalchemy>=1.3.16, <2.0",
"sqlalchemy-utils>=0.36.6,<0.37",
"sqlparse==0.3.0", # PINNED! see https://github.com/andialbrecht/sqlparse/issues/562

View File

@@ -45,6 +45,25 @@ describe('Visualization > Line', () => {
cy.get('.alert-warning').should('not.exist');
});
it('should allow negative values in Y bounds', () => {
cy.get('#controlSections-tab-display').click();
cy.get('span').contains('Y Axis Bounds').scrollIntoView();
cy.get('input[placeholder="Min"]').type('-0.1', { delay: 100 });
cy.get('.alert-warning').should('not.exist');
});
it('should allow type to search color schemes', () => {
cy.get('#controlSections-tab-display').click();
cy.get('.Control[data-test="color_scheme"]').scrollIntoView();
cy.get('.Control[data-test="color_scheme"] input[type="text"]')
.focus()
.type('air{enter}');
cy.get('input[name="select-color_scheme"]').should(
'have.value',
'bnbColors',
);
});
it('should work with adhoc metric', () => {
const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] };
cy.visitChartByParams(JSON.stringify(formData));
@@ -54,9 +73,7 @@ describe('Visualization > Line', () => {
it('should work with groupby', () => {
const metrics = ['count'];
const groupby = ['gender'];
const formData = { ...LINE_CHART_DEFAULTS, metrics, groupby };
cy.visitChartByParams(JSON.stringify(formData));
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -64,13 +81,11 @@ describe('Visualization > Line', () => {
it('should work with simple filter', () => {
const metrics = ['count'];
const filters = [SIMPLE_FILTER];
const formData = {
...LINE_CHART_DEFAULTS,
metrics,
adhoc_filters: filters,
};
cy.visitChartByParams(JSON.stringify(formData));
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -83,7 +98,6 @@ describe('Visualization > Line', () => {
groupby: ['name'],
timeseries_limit_metric: NUM_METRIC,
};
cy.visitChartByParams(JSON.stringify(formData));
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -97,28 +111,24 @@ describe('Visualization > Line', () => {
timeseries_limit_metric: NUM_METRIC,
order_desc: true,
};
cy.visitChartByParams(JSON.stringify(formData));
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
it('should work with rolling avg', () => {
const metrics = [NUM_METRIC];
const formData = {
...LINE_CHART_DEFAULTS,
metrics,
rolling_type: 'mean',
rolling_periods: 10,
};
cy.visitChartByParams(JSON.stringify(formData));
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
it('should work with time shift 1 year', () => {
const metrics = [NUM_METRIC];
const formData = {
...LINE_CHART_DEFAULTS,
metrics,
@@ -162,28 +172,24 @@ describe('Visualization > Line', () => {
it('should work with time shift yoy', () => {
const metrics = [NUM_METRIC];
const formData = {
...LINE_CHART_DEFAULTS,
metrics,
time_compare: ['1+year'],
comparison_type: 'ratio',
};
cy.visitChartByParams(JSON.stringify(formData));
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
it('should work with time shift percentage change', () => {
const metrics = [NUM_METRIC];
const formData = {
...LINE_CHART_DEFAULTS,
metrics,
time_compare: ['1+year'],
comparison_type: 'percentage',
};
cy.visitChartByParams(JSON.stringify(formData));
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
});
@@ -193,7 +199,6 @@ describe('Visualization > Line', () => {
...LINE_CHART_DEFAULTS,
metrics: ['count'],
};
cy.visitChartByParams(JSON.stringify(formData));
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
cy.get('text.nv-legend-text').contains('COUNT(*)');
@@ -224,7 +229,6 @@ describe('Visualization > Line', () => {
},
],
};
cy.visitChartByParams(JSON.stringify(formData));
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
cy.get('.slice_container').within(() => {
@@ -263,7 +267,6 @@ describe('Visualization > Line', () => {
cy.visitChartByParams(JSON.stringify(formData));
},
);
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
cy.get('.slice_container').within(() => {
cy.get('.nv-event-annotation-layer-0')

View File

@@ -29,12 +29,31 @@ import readResponseBlob from '../../../utils/readResponseBlob';
describe('Visualization > Table', () => {
const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: 'table' };
const PERCENT_METRIC = {
expressionType: 'SQL',
sqlExpression: 'CAST(SUM(sum_girls)+AS+FLOAT)/SUM(num)',
column: null,
aggregate: null,
hasCustomLabel: true,
label: 'Girls',
optionName: 'metric_6qwzgc8bh2v_zox7hil1mzs',
};
beforeEach(() => {
cy.login();
cy.server();
cy.route('POST', '/superset/explore_json/**').as('getJson');
});
it('Use default time column', () => {
cy.visitChartByParams({
...VIZ_DEFAULTS,
granularity_sqla: undefined,
metrics: ['count'],
});
cy.get('input[name="select-granularity_sqla"]').should('have.value', 'ds');
});
it('Format non-numeric metrics correctly', () => {
cy.visitChartByParams({
...VIZ_DEFAULTS,
@@ -110,7 +129,7 @@ describe('Visualization > Table', () => {
it('Test table with percent metrics and groupby', () => {
const formData = {
...VIZ_DEFAULTS,
percent_metrics: NUM_METRIC,
percent_metrics: PERCENT_METRIC,
metrics: [],
groupby: ['name'],
};
@@ -156,9 +175,20 @@ describe('Visualization > Table', () => {
metrics: [],
row_limit: 10,
};
cy.visitChartByParams(JSON.stringify(formData));
// should display in raw records mode
cy.get('div[data-test="query_mode"] .btn.active').contains('Raw Records');
cy.get('div[data-test="all_columns"]').should('be.visible');
cy.get('div[data-test="groupby"]').should('not.be.visible');
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'table' });
// should allow switch to aggregate mode
cy.get('div[data-test="query_mode"] .btn').contains('Aggregate').click();
cy.get('div[data-test="query_mode"] .btn.active').contains('Aggregate');
cy.get('div[data-test="all_columns"]').should('not.be.visible');
cy.get('div[data-test="groupby"]').should('be.visible');
});
it('Test table with columns, ordering, and row limit', () => {
@@ -194,15 +224,6 @@ describe('Visualization > Table', () => {
});
it('Tests table number formatting with % in metric name', () => {
const PERCENT_METRIC = {
expressionType: 'SQL',
sqlExpression: 'CAST(SUM(sum_girls)+AS+FLOAT)/SUM(num)',
column: null,
aggregate: null,
hasCustomLabel: true,
label: 'Girls',
optionName: 'metric_6qwzgc8bh2v_zox7hil1mzs',
};
const formData = {
...VIZ_DEFAULTS,
percent_metrics: PERCENT_METRIC,

View File

@@ -6024,9 +6024,9 @@
}
},
"@superset-ui/legacy-plugin-chart-pivot-table": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.14.9.tgz",
"integrity": "sha512-czjbOIew4gDSeZ2yUkB3JHBf3FdwrNMj1Q96IpLLYcoJvWsIw8H6wSFXblCwNkoT8tiPVARZ2ltw4Qkjx0xUBg==",
"version": "0.14.21",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.14.21.tgz",
"integrity": "sha512-gmj3iu+ibkXwshcSna1V9Tmbh+wBCHi3HKTuy6R9KrB+0585U0dsHro3xe0o14Uamhld6PIeWbZBSl3axXK+SQ==",
"requires": {
"d3": "^3.5.17",
"datatables.net-bs": "^1.10.15",
@@ -6034,9 +6034,9 @@
}
},
"@superset-ui/legacy-plugin-chart-rose": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.14.9.tgz",
"integrity": "sha512-sylNIX2PcICKEF3Xr0VzgivviFRJHLFq6akY0F/Mk3+GUctUGd8+xmSbKzMKLQlH+E9/9OLNqZ+tiODGI8bdQw==",
"version": "0.14.14",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.14.14.tgz",
"integrity": "sha512-gln7uwUu6RzpoIs4HYxonUQ4Mn9ZtRl9TLzak1cI+ZznUIZqAlSvtrU39mx0+fQ/b69qpG3Qgq8C2wivCqgL3w==",
"requires": {
"d3": "^3.5.17",
"nvd3": "1.8.6",
@@ -6073,9 +6073,9 @@
}
},
"@superset-ui/legacy-plugin-chart-treemap": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.14.9.tgz",
"integrity": "sha512-hHxySbtxK8BdDNR790UtdV676h9OpTO4F99qiW9nKCzCYiSEXPl6mBPrQtyPjX8i4lzwpm9GsB91hIj8zeZUSQ==",
"version": "0.14.13",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.14.13.tgz",
"integrity": "sha512-/5nYtjmOO+CyYx+TMOoM8swu7NFmFQbiQFy1EYLF7gNx1DdboOxbAV8pxqlSrpwX5qw+Fz2mDITclCc03I2czA==",
"requires": {
"d3-hierarchy": "^1.1.8",
"d3-selection": "^1.4.0",
@@ -6083,9 +6083,9 @@
}
},
"@superset-ui/legacy-plugin-chart-world-map": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.14.9.tgz",
"integrity": "sha512-8QR5tUvA7D5fQOgVON0viTlbYSHpV1sQhW3igvL6uWrF3u4xc+WVIeyn4ggw4601P4UhE4E5EXyLuHHFvWvDXg==",
"version": "0.14.16",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.14.16.tgz",
"integrity": "sha512-9vQOk+jRs4UxezXUh6Fg9x2QKRMmTiFxDzndMvq5HEo34mqZvjiYNy4D1HUTCNXtHKeJUfjNp5+uDJqD5q9CYg==",
"requires": {
"d3": "^3.5.17",
"datamaps": "^0.5.8",
@@ -6130,9 +6130,9 @@
}
},
"@superset-ui/legacy-preset-chart-nvd3": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.14.9.tgz",
"integrity": "sha512-VUSOxpRXoTU49OofNzPoXTNpr2MzRRMRfbaPlbQQOwtY5/+9dFa2SfbsD0fVDMtWiX2jPBQeIunAhlrtNUhrBg==",
"version": "0.14.21",
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.14.21.tgz",
"integrity": "sha512-BbsVZnkkAL2a44XFYQtc24VNINGM5JwXAA9HbygdspumYTUu6cpH2nFVPwc06NREUeeN+EV/zF/AVW2O1IJ1tg==",
"requires": {
"@data-ui/xy-chart": "^0.0.84",
"d3": "^3.5.17",
@@ -6158,15 +6158,14 @@
}
},
"@superset-ui/plugin-chart-table": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.14.9.tgz",
"integrity": "sha512-A65LdejI4Vy99LLCVT20WrUGNIdp/FQBK2NsRIAMKNyhnZ1rp0R2F5xX0MG5AjxBUDQYlMQY4fptt1oB8V1fqA==",
"version": "0.14.16",
"resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.14.16.tgz",
"integrity": "sha512-N95ptdDNQatU2CmznsVbFWGumj9Z59oF694NXZkbqJB/Bp+4OPkvjybjOrlEMmay2zj2sXFHAxO0b3ZKKcCucg==",
"requires": {
"@emotion/core": "^10.0.28",
"@types/d3-array": "^2.0.0",
"@types/match-sorter": "^4.0.0",
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
"@types/react": "^16.9.43",
"@types/react-table": "^7.0.19",
"d3-array": "^2.4.0",
"match-sorter": "^4.1.0",
@@ -6337,9 +6336,9 @@
}
},
"@superset-ui/query": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/@superset-ui/query/-/query-0.14.9.tgz",
"integrity": "sha512-tldCA8YzqG02oCz/mSY5xA2g3AAqdF5f2p755tPimUHCqtn5axnE38AaRDz+0PyKNyStx0JkYkOVgqvX2ggpzA=="
"version": "0.14.15",
"resolved": "https://registry.npmjs.org/@superset-ui/query/-/query-0.14.15.tgz",
"integrity": "sha512-k89EuCkXp3LmbBSm8yYpmykeoJNy1HvMj3jNRwYS0kvV7nNd267oAdXl8UnFzl+htxqwLUIidcXN9vzydB4Whw=="
},
"@superset-ui/style": {
"version": "0.14.9",
@@ -6369,6 +6368,13 @@
"@superset-ui/time-format": "0.14.9",
"@superset-ui/translation": "0.14.9",
"@superset-ui/validator": "0.14.9"
},
"dependencies": {
"@superset-ui/query": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/@superset-ui/query/-/query-0.14.9.tgz",
"integrity": "sha512-tldCA8YzqG02oCz/mSY5xA2g3AAqdF5f2p755tPimUHCqtn5axnE38AaRDz+0PyKNyStx0JkYkOVgqvX2ggpzA=="
}
}
},
"@superset-ui/time-format": {
@@ -7658,6 +7664,11 @@
"resolved": "https://registry.npmjs.org/@types/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha512-mky/O83TXmGY39P1H9YbUpjV6l6voRYlufqfFCvel8l1phuy8HRjdWc1rrPuN53ITBJlbyMSV6z3niOySO5pgQ=="
},
"@types/fetch-mock": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@types/fetch-mock/-/fetch-mock-7.3.2.tgz",
"integrity": "sha512-NCEfv49jmDsBAixjMjEHKVgmVQlJ+uK56FOc+2roYPExnXCZDpi6mJOHQ3v23BiO84hBDStND9R2itJr7PNoow=="
},
"@types/glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
@@ -7678,21 +7689,10 @@
"@types/node": "*"
}
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dev": true,
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
},
"dependencies": {
"react-is": {
"version": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz",
"integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA=="
}
}
"@types/history": {
"version": "4.7.6",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.6.tgz",
"integrity": "sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w=="
},
"@types/istanbul-lib-coverage": {
"version": "2.0.3",
@@ -7794,9 +7794,9 @@
"dev": true
},
"@types/react": {
"version": "16.9.38",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.38.tgz",
"integrity": "sha512-pHAeZbjjNRa/hxyNuLrvbxhhnKyKNiLC6I5fRF2Zr/t/S6zS41MiyzH4+c+1I9vVfvuRt1VS2Lodjr4ZWnxrdA==",
"version": "16.9.43",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.43.tgz",
"integrity": "sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg==",
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
@@ -7818,6 +7818,14 @@
"@types/react": "*"
}
},
"@types/react-gravatar": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/@types/react-gravatar/-/react-gravatar-2.6.8.tgz",
"integrity": "sha512-VMk0bF0w72l+opBm+EqLs0JqUG+hPowMBWCVGrbTwUWm/oDncvwNrf7P/ImwYwkTCKiLnU8Rc+/lyhehaIE/Rw==",
"requires": {
"@types/react": "*"
}
},
"@types/react-json-tree": {
"version": "0.6.11",
"resolved": "https://registry.npmjs.org/@types/react-json-tree/-/react-json-tree-0.6.11.tgz",
@@ -7836,31 +7844,32 @@
}
},
"@types/react-redux": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.7.tgz",
"integrity": "sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg==",
"version": "5.0.21",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-5.0.21.tgz",
"integrity": "sha512-ewkOW4GjnyXq5L++T31utI8yRmwj8iCIahZohYi1Ef7Xkrw0V/q92ao7x20rm38FKgImDaCCsaRGWfCJmF/Ukg==",
"dev": true,
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
},
"dependencies": {
"react-is": {
"version": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz",
"integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA=="
},
"redux": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
}
}
"redux": "^3.6.0"
}
},
"@types/react-router": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz",
"integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==",
"requires": {
"@types/history": "*",
"@types/react": "*"
}
},
"@types/react-router-dom": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz",
"integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==",
"requires": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
}
},
"@types/react-select": {
@@ -7923,6 +7932,25 @@
"redux": "^3.6.0"
}
},
"@types/redux-mock-store": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.0.2.tgz",
"integrity": "sha512-6LBtAQBN34i7SI5X+Qs4zpTEZO1tTDZ6sZ9fzFjYwTl3nLQXaBtwYdoV44CzNnyKu438xJ1lSIYyw0YMvunESw==",
"requires": {
"redux": "^4.0.5"
},
"dependencies": {
"redux": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
}
}
}
},
"@types/rison": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/rison/-/rison-0.0.6.tgz",
@@ -7933,6 +7961,19 @@
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps="
},
"@types/sinon": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.4.tgz",
"integrity": "sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw==",
"requires": {
"@types/sinonjs__fake-timers": "*"
}
},
"@types/sinonjs__fake-timers": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz",
"integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA=="
},
"@types/sizzle": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
@@ -12774,9 +12815,9 @@
}
},
"dompurify": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.7.tgz",
"integrity": "sha512-S3O0lk6rFJtO01ZTzMollCOGg+WAtCwS3U5E2WSDY/x/sy7q70RjEC4Dmrih5/UqzLLB9XoKJ8KqwBxaNvBu4A=="
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.12.tgz",
"integrity": "sha512-Fl8KseK1imyhErHypFPA8qpq9gPzlsJ/EukA6yk9o0gX23p1TzC+rh9LqNg1qvErRTc0UNMYlKxEGSfSh43NDg=="
},
"domutils": {
"version": "1.5.1",
@@ -19746,18 +19787,18 @@
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
},
"match-sorter": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-4.1.0.tgz",
"integrity": "sha512-DCzT9JVO2FWVOTfsKqIqVhu/skFa3bK0lQom70j6Co9yKX9bPn2gRtn9BFD9ykkM8F/USjTQeId+nlFfTVvz+w==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-4.2.0.tgz",
"integrity": "sha512-oEvLn8R+a30YZ9l5XdCTkYQuLsOs8frxEqQTAuxoqkQx/qV5pQpx/NqAWvJ5xbYecqfXoF/ZevaIS1+NkbRymg==",
"requires": {
"@babel/runtime": "^7.9.2",
"@babel/runtime": "^7.10.5",
"remove-accents": "0.4.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz",
"integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==",
"version": "7.10.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz",
"integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}

View File

@@ -1,6 +1,6 @@
{
"name": "superset",
"version": "0.999.0dev",
"version": "0.37.2",
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
"license": "Apache-2.0",
"directories": {
@@ -80,21 +80,21 @@
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.14.9",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.14.9",
"@superset-ui/legacy-plugin-chart-partition": "^0.14.9",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.14.9",
"@superset-ui/legacy-plugin-chart-rose": "^0.14.9",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.14.21",
"@superset-ui/legacy-plugin-chart-rose": "^0.14.14",
"@superset-ui/legacy-plugin-chart-sankey": "^0.14.9",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.14.9",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.14.9",
"@superset-ui/plugin-chart-table": "^0.14.9",
"@superset-ui/legacy-plugin-chart-treemap": "^0.14.9",
"@superset-ui/legacy-plugin-chart-world-map": "^0.14.9",
"@superset-ui/legacy-plugin-chart-treemap": "^0.14.13",
"@superset-ui/legacy-plugin-chart-world-map": "^0.14.16",
"@superset-ui/legacy-preset-chart-big-number": "^0.14.9",
"@superset-ui/legacy-preset-chart-deckgl": "^0.2.4",
"@superset-ui/legacy-preset-chart-nvd3": "^0.14.9",
"@superset-ui/legacy-preset-chart-nvd3": "^0.14.21",
"@superset-ui/number-format": "^0.14.9",
"@superset-ui/plugin-chart-table": "^0.14.16",
"@superset-ui/plugin-chart-word-cloud": "^0.14.9",
"@superset-ui/preset-chart-xy": "^0.14.9",
"@superset-ui/query": "^0.14.9",
"@superset-ui/query": "^0.14.15",
"@superset-ui/style": "^0.14.9",
"@superset-ui/superset-ui": "^0.14.9",
"@superset-ui/time-format": "^0.14.9",
@@ -102,15 +102,19 @@
"@superset-ui/validator": "^0.14.9",
"@types/classnames": "^2.2.9",
"@types/enzyme": "^3.10.5",
"@types/fetch-mock": "^7.3.2",
"@types/react-bootstrap": "^0.32.21",
"@types/react-gravatar": "^2.6.8",
"@types/react-json-tree": "^0.6.11",
"@types/react-router-dom": "^5.1.5",
"@types/react-select": "^3.0.12",
"@types/react-virtualized": "^9.21.10",
"@types/react-window": "^1.8.2",
"@types/redux-localstorage": "^1.0.8",
"@types/redux-mock-store": "^1.0.2",
"@types/rison": "0.0.6",
"@types/sinon": "^9.0.4",
"@vx/responsive": "^0.0.195",
"memoize-one": "^5.1.1",
"abortcontroller-polyfill": "^1.1.9",
"aphrodite": "^2.3.1",
"array-move": "^2.2.1",
@@ -134,6 +138,7 @@
"lodash": "^4.17.15",
"lodash-es": "^4.17.14",
"mathjs": "^3.20.2",
"memoize-one": "^5.1.1",
"moment": "^2.20.1",
"mousetrap": "^1.6.1",
"mustache": "^2.2.1",
@@ -205,10 +210,10 @@
"@types/dom-to-image": "^2.6.0",
"@types/jest": "^26.0.3",
"@types/jquery": "^3.3.32",
"@types/react": "^16.9.38",
"@types/react": "^16.9.43",
"@types/react-dom": "^16.9.8",
"@types/react-json-tree": "^0.6.11",
"@types/react-redux": "^7.1.7",
"@types/react-redux": "^5.0.2",
"@types/react-table": "^7.0.19",
"@types/react-ultimate-pagination": "^1.2.0",
"@types/yargs": "12 - 15",

View File

@@ -20,7 +20,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import Chart from 'src/dashboard/components/gridComponents/Chart';
import { ChartUnconnected as Chart } from 'src/dashboard/components/gridComponents/Chart';
import SliceHeader from 'src/dashboard/components/SliceHeader';
import ChartContainer from 'src/chart/ChartContainer';
@@ -44,6 +44,7 @@ describe('Chart', () => {
slice: {
...sliceEntities.slices[queryId],
description_markeddown: 'markdown',
owners: [],
},
sliceName: sliceEntities.slices[queryId].slice_name,
timeout: 60,
@@ -52,6 +53,13 @@ describe('Chart', () => {
toggleExpandSlice() {},
addFilter() {},
logEvent() {},
handleToggleFullSize() {},
changeFilter() {},
setFocusedFilterField() {},
unsetFocusedFilterField() {},
addDangerToast() {},
componentId: 'test',
dashboardId: 111,
editMode: false,
isExpanded: false,
supersetCanExplore: false,

View File

@@ -0,0 +1,41 @@
/**
* 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 getEffectiveExtraFilters from 'src/dashboard/util/charts/getEffectiveExtraFilters';
describe('getEffectiveExtraFilters', () => {
it('should create valid filters', () => {
const result = getEffectiveExtraFilters({
gender: ['girl'],
name: null,
__time_range: ' : 2020-07-17T00:00:00',
});
expect(result).toMatchObject([
{
col: 'gender',
op: 'in',
val: ['girl'],
},
{
col: '__time_range',
op: '==',
val: ' : 2020-07-17T00:00:00',
},
]);
});
});

View File

@@ -19,12 +19,14 @@
/* eslint-disable no-unused-expressions */
import React from 'react';
import { mount } from 'enzyme';
import Creatable from 'react-select/creatable';
import { Select } from 'src/components/Select';
import { getCategoricalSchemeRegistry } from '@superset-ui/color';
import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl';
const defaultProps = {
name: 'color_scheme',
label: 'Color Scheme',
options: getCategoricalSchemeRegistry()
.keys()
.map(s => [s, s]),
@@ -37,6 +39,6 @@ describe('ColorSchemeControl', () => {
});
it('renders a Creatable', () => {
expect(wrapper.find(Creatable)).toHaveLength(1);
expect(wrapper.find(Select)).toHaveLength(1);
});
});

View File

@@ -0,0 +1,61 @@
/**
* 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 { shallow } from 'enzyme';
import FilterBox from 'src/visualizations/FilterBox/FilterBox';
describe('FilterBox', () => {
it('should only add defined non-predefined options to filtersChoices', () => {
const wrapper = shallow(
<FilterBox
chartId={1001}
datasource={{ id: 1 }}
filtersChoices={{
name: [
{ id: 'John', text: 'John', metric: 1234 },
{ id: 'Jane', text: 'Jane', metric: 345678 },
],
}}
filtersFields={[
{
asc: false,
clearable: true,
column: 'name',
key: 'name',
label: 'name',
metric: 'sum__COUNT',
multiple: true,
},
]}
origSelectedValues={{}}
/>,
);
const inst = wrapper.instance();
// choose a predefined value
inst.setState({ selectedValues: { name: ['John'] } });
expect(inst.props.filtersChoices.name.length).toEqual(2);
// reset selection
inst.setState({ selectedValues: { name: null } });
expect(inst.props.filtersChoices.name.length).toEqual(2);
// Add a new name
inst.setState({ selectedValues: { name: 'James' } });
expect(inst.props.filtersChoices.name.length).toEqual(3);
});
});

View File

@@ -35,6 +35,7 @@ describe('controlUtils', () => {
columns: ['a', 'b', 'c'],
metrics: [{ metric_name: 'first' }, { metric_name: 'second' }],
},
controls: {},
};
beforeAll(() => {
@@ -250,9 +251,10 @@ describe('controlUtils', () => {
it('should not apply mapStateToProps when initializing', () => {
const control = getControlState('metrics', 'table', {
...state,
isInitializing: true,
controls: undefined,
});
expect(control.default).toEqual(null);
expect(typeof control.default).toBe('function');
expect(control.value).toBe(undefined);
});
});
@@ -261,6 +263,15 @@ describe('controlUtils', () => {
const control = getControlState('metric', 'table', state, null);
expect(control.validationErrors).toEqual(['cannot be empty']);
});
it('should not validate if control panel is initializing', () => {
const control = getControlState(
'metric',
'table',
{ ...state, controls: undefined },
undefined,
);
expect(control.validationErrors).toBeUndefined();
});
});
describe('queryFields', () => {

View File

@@ -94,13 +94,7 @@ describe('ExploreResultsButton', () => {
});
const badCols = wrapper.instance().getInvalidColumns();
expect(badCols).toEqual([
'COUNT(*)',
'1',
'123',
'CASE WHEN 1=1 THEN 1 ELSE 0 END',
'__TIMESTAMP',
]);
expect(badCols).toEqual(['my_dupe_col__2', '__timestamp', '__TIMESTAMP']);
const msgWrapper = shallow(wrapper.instance().renderInvalidColumnMessage());
expect(msgWrapper.find('div')).toHaveLength(1);

View File

@@ -18,6 +18,7 @@
*/
import sinon from 'sinon';
import * as actions from 'src/SqlLab/actions/sqlLab';
import { ColumnKeyTypeType } from 'src/SqlLab/components/ColumnElement';
export const mockedActions = sinon.stub({ ...actions });
@@ -68,7 +69,7 @@ export const table = {
keys: [
{
column_names: ['id'],
type: 'pk',
type: 'pk' as ColumnKeyTypeType,
name: null,
},
],
@@ -84,14 +85,14 @@ export const table = {
name: 'slices_ibfk_1',
referred_columns: ['id'],
referred_table: 'datasources',
type: 'fk',
type: 'fk' as ColumnKeyTypeType,
referred_schema: 'carapal',
options: {},
},
{
unique: false,
column_names: ['druid_datasource_id'],
type: 'index',
type: 'index' as ColumnKeyTypeType,
name: 'druid_datasource_id',
},
],
@@ -283,7 +284,7 @@ export const queries = [
export const queryWithBadColumns = {
...queries[0],
results: {
data: queries[0].results.data,
data: queries[0].results?.data,
selected_columns: [
{
is_date: true,
@@ -320,6 +321,21 @@ export const queryWithBadColumns = {
name: '_TIMESTAMP',
type: 'TIMESTAMP',
},
{
is_date: true,
name: '__TIME',
type: 'TIMESTAMP',
},
{
is_date: false,
name: 'my_dupe_col__2',
type: 'STRING',
},
{
is_date: true,
name: '__timestamp',
type: 'TIMESTAMP',
},
{
is_date: true,
name: '__TIMESTAMP',

View File

@@ -72,12 +72,16 @@ describe('utils/common', () => {
});
it('changes formatting of temporal column', () => {
const originalData = [
{ __timestamp: 1594285437771, column1: 'lorem' },
{ __timestamp: 1594285441675, column1: 'ipsum' },
{ __timestamp: null, column1: 'lorem' },
{ __timestamp: 0, column1: 'ipsum' },
{ __timestamp: 1594285437771, column1: 'dolor' },
{ __timestamp: 1594285441675, column1: 'sit' },
];
const expectedData = [
{ __timestamp: '2020-07-09 09:03:57', column1: 'lorem' },
{ __timestamp: '2020-07-09 09:04:01', column1: 'ipsum' },
{ __timestamp: null, column1: 'lorem' },
{ __timestamp: '1970-01-01 00:00:00', column1: 'ipsum' },
{ __timestamp: '2020-07-09 09:03:57', column1: 'dolor' },
{ __timestamp: '2020-07-09 09:04:01', column1: 'sit' },
];
expect(applyFormattingToTabularData(originalData)).toEqual(expectedData);
});

View File

@@ -105,7 +105,7 @@ describe('ChartList', () => {
const callsD = fetchMock.calls(/chart\/\?q/);
expect(callsD).toHaveLength(1);
expect(callsD[0][0]).toMatchInlineSnapshot(
`"http://localhost/api/v1/chart/?q=(order_column:changed_on,order_direction:desc,page:0,page_size:25)"`,
`"http://localhost/api/v1/chart/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
);
});
});

View File

@@ -43,7 +43,8 @@ const mockDashboards = [...new Array(3)].map((_, i) => ({
changed_by_url: 'changed_by_url',
changed_by_fk: 1,
published: true,
changed_on: new Date().toISOString(),
changed_on_utc: new Date().toISOString(),
changed_on_delta_humanized: '5 minutes ago',
owners: [{ first_name: 'admin', last_name: 'admin_user' }],
}));
@@ -95,7 +96,7 @@ describe('DashboardList', () => {
const callsD = fetchMock.calls(/dashboard\/\?q/);
expect(callsD).toHaveLength(1);
expect(callsD[0][0]).toMatchInlineSnapshot(
`"http://localhost/api/v1/dashboard/?q=(order_column:changed_on,order_direction:desc,page:0,page_size:25)"`,
`"http://localhost/api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
);
});
it('edits', () => {

View File

@@ -55,7 +55,7 @@ describe('DashboardTable', () => {
expect(fetchMock.calls(dashboardsEndpoint)).toHaveLength(1);
// there's a delay between response and updating state, so manually set it
// rather than adding a timeout which could introduce flakiness
wrapper.setState({ dashaboards: mockDashboards });
wrapper.setState({ dashboards: mockDashboards });
expect(wrapper.find(ListView)).toHaveLength(1);
done();
});

View File

@@ -23,11 +23,21 @@ import { shallow } from 'enzyme';
import Welcome from 'src/welcome/Welcome';
describe('Welcome', () => {
const mockedProps = {};
const mockedProps = {
user: {
username: 'alpha',
firstName: 'alpha',
lastName: 'alpha',
createdOn: '2016-11-11T12:34:17',
userId: 5,
email: 'alpha@alpha.com',
isActive: true,
},
};
it('is valid', () => {
expect(React.isValidElement(<Welcome {...mockedProps} />)).toBe(true);
});
it('renders 4 Tab, Panel, and Row components', () => {
it('renders 3 Tab, Panel, and Row components', () => {
const wrapper = shallow(<Welcome {...mockedProps} />);
expect(wrapper.find(Tab)).toHaveLength(3);
expect(wrapper.find(Panel)).toHaveLength(3);

View File

@@ -36,13 +36,22 @@ const tooltipTitleMap = {
index: 'Index',
};
export default function ColumnElement(props) {
const col = props.column;
let name = col.name;
export type ColumnKeyTypeType = keyof typeof tooltipTitleMap;
interface ColumnElementProps {
column: {
name: string;
keys?: { type: ColumnKeyTypeType }[];
type: string;
};
}
export default function ColumnElement({ column }: ColumnElementProps) {
let name: React.ReactNode = column.name;
let icons;
if (col.keys && col.keys.length > 0) {
name = <strong>{col.name}</strong>;
icons = col.keys.map((key, i) => (
if (column.keys && column.keys.length > 0) {
name = <strong>{column.name}</strong>;
icons = column.keys.map((key, i) => (
<span key={i} className="ColumnElement">
<OverlayTrigger
placement="right"
@@ -68,7 +77,7 @@ export default function ColumnElement(props) {
{icons}
</div>
<div className="pull-right text-muted">
<small> {col.type}</small>
<small> {column.type}</small>
</div>
</div>
);

View File

@@ -102,13 +102,12 @@ class ExploreResultsButton extends React.PureComponent {
.asSeconds();
}
getInvalidColumns() {
const re1 = /^[A-Za-z_]\w*$/; // starts with char or _, then only alphanum
const re2 = /__\d+$/; // does not finish with __ and then a number which screams dup col name
const re3 = /^__/; // is not a reserved column name e.g. __timestamp
const re1 = /__\d+$/; // duplicate column name pattern
const re2 = /^__timestamp/i; // reserved temporal column alias
return this.props.query.results.selected_columns
.map(col => col.name)
.filter(col => !re1.test(col) || re2.test(col) || re3.test(col));
.filter(col => re1.test(col) || re2.test(col));
}
datasourceName() {
const { query } = this.props;
@@ -193,16 +192,11 @@ class ExploreResultsButton extends React.PureComponent {
<code>
<strong>{invalidColumns.join(', ')} </strong>
</code>
{t('cannot be used as a column name. Please use aliases (as in ')}
<code>
SELECT count(*)&nbsp;
<strong>AS my_alias</strong>
</code>
){' '}
{t(`limited to alphanumeric characters and underscores. Column aliases starting
with double underscores or ending with double underscores followed by a
numeric value are not allowed for reasons discussed in Github issue #5739.
`)}
{t(`cannot be used as a column name. The column name/alias "__timestamp"
is reserved for the main temporal expression, and column aliases ending with
double underscores followed by a numeric value (e.g. "my_col__1") are reserved
for deduplicating duplicate column names. Please use aliases to rename the
invalid column names.`)}
</div>
);
}

View File

@@ -158,6 +158,7 @@ export default class ResultSet extends React.PureComponent<
this.props.database &&
this.props.database.allows_virtual_table_explore && (
<ExploreResultsButton
// @ts-ignore Redux types are difficult to work with, ignoring for now
query={this.props.query}
database={this.props.database}
actions={this.props.actions}
@@ -246,6 +247,7 @@ export default class ResultSet extends React.PureComponent<
{t('Query in a new tab')}
</Button>
<ExploreCtasResultsButton
// @ts-ignore Redux types are difficult to work with, ignoring for now
table={tempTable}
schema={tempSchema}
dbId={exploreDBId}

View File

@@ -17,58 +17,48 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import SyntaxHighlighter, {
registerLanguage,
// @ts-ignore
} from 'react-syntax-highlighter/dist/light';
// @ts-ignore
import sql from 'react-syntax-highlighter/dist/languages/hljs/sql';
// @ts-ignore
import github from 'react-syntax-highlighter/dist/styles/hljs/github';
import { t } from '@superset-ui/translation';
import Link from '../../components/Link';
import ModalTrigger from '../../components/ModalTrigger';
registerLanguage('sql', sql);
const propTypes = {
tooltipText: PropTypes.string,
title: PropTypes.string,
sql: PropTypes.string,
};
const defaultProps = {
tooltipText: t('Show SQL'),
title: t('SQL statement'),
sql: '',
};
export default class ShowSQL extends React.PureComponent {
renderModalBody() {
return (
<div>
<SyntaxHighlighter language="sql" style={github}>
{this.props.sql}
</SyntaxHighlighter>
</div>
);
}
render() {
return (
<ModalTrigger
modalTitle={this.props.title}
triggerNode={
<Link
className="fa fa-eye pull-left m-l-2"
tooltip={this.props.tooltipText}
href="#"
/>
}
modalBody={this.renderModalBody()}
/>
);
}
interface ShowSQLProps {
sql: string;
title: string;
tooltipText: string;
}
ShowSQL.propTypes = propTypes;
ShowSQL.defaultProps = defaultProps;
export default function ShowSQL({
tooltipText,
title,
sql: sqlString,
}: ShowSQLProps) {
return (
<ModalTrigger
modalTitle={title}
triggerNode={
<Link
className="fa fa-eye pull-left m-l-2"
tooltip={tooltipText}
href="#"
/>
}
modalBody={
<div>
<SyntaxHighlighter language="sql" style={github}>
{sqlString}
</SyntaxHighlighter>
</div>
}
/>
);
}

View File

@@ -62,6 +62,8 @@ const propTypes = {
onQuery: PropTypes.func,
onFilterMenuOpen: PropTypes.func,
onFilterMenuClose: PropTypes.func,
// id of the last mounted parent tab
mountedParent: PropTypes.string,
};
const BLANK = {};
@@ -143,7 +145,7 @@ class Chart extends React.PureComponent {
return (
<ErrorMessageWithStackTrace
error={queryResponse?.errors?.[0]}
message={chartAlert}
message={chartAlert || queryResponse?.message}
link={queryResponse ? queryResponse.link : null}
stackTrace={chartStackTrace}
/>

View File

@@ -87,8 +87,7 @@ class ChartRenderer extends React.Component {
if (resultsReady) {
this.hasQueryResponseChange =
nextProps.queryResponse !== this.props.queryResponse;
if (
return (
this.hasQueryResponseChange ||
nextProps.annotationData !== this.props.annotationData ||
nextProps.height !== this.props.height ||
@@ -96,9 +95,7 @@ class ChartRenderer extends React.Component {
nextProps.triggerRender ||
nextProps.formData.color_scheme !== this.props.formData.color_scheme ||
nextProps.cacheBusterProp !== this.props.cacheBusterProp
) {
return true;
}
);
}
return false;
}

View File

@@ -321,6 +321,14 @@ export function setDirectPathToChild(path) {
return { type: SET_DIRECT_PATH, path };
}
export const SET_MOUNTED_TAB = 'SET_MOUNTED_TAB';
/**
* Set if tab switch animation is in progress
*/
export function setMountedTab(mountedTab) {
return { type: SET_MOUNTED_TAB, mountedTab };
}
export const SET_FOCUSED_FILTER_FIELD = 'SET_FOCUSED_FILTER_FIELD';
export function setFocusedFilterField(chartId, column) {
return { type: SET_FOCUSED_FILTER_FIELD, chartId, column };

View File

@@ -62,6 +62,7 @@ const propTypes = {
handleComponentDrop: PropTypes.func.isRequired,
directPathToChild: PropTypes.arrayOf(PropTypes.string),
setDirectPathToChild: PropTypes.func.isRequired,
setMountedTab: PropTypes.func.isRequired,
};
const defaultProps = {
@@ -250,6 +251,12 @@ class DashboardBuilder extends React.Component {
<TabPane
key={index === 0 ? DASHBOARD_GRID_ID : id}
eventKey={index}
mountOnEnter
unmountOnExit={false}
onEntering={() => {
// Entering current tab, DOM is visible and has dimension
this.props.setMountedTab(id);
}}
>
<DashboardGrid
gridComponent={dashboardLayout[id]}

View File

@@ -18,6 +18,7 @@
*/
import cx from 'classnames';
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { exploreChart, exportChart } from '../../../explore/exploreUtils';
import SliceHeader from '../SliceHeader';
@@ -41,6 +42,8 @@ const propTypes = {
height: PropTypes.number.isRequired,
updateSliceName: PropTypes.func.isRequired,
isComponentVisible: PropTypes.bool,
// last switched tab
mountedParent: PropTypes.string,
handleToggleFullSize: PropTypes.func.isRequired,
// from redux
@@ -70,6 +73,7 @@ const propTypes = {
const defaultProps = {
isCached: false,
isComponentVisible: true,
mountedParent: 'ROOT',
};
// we use state + shouldComponentUpdate() logic to prevent perf-wrecking
@@ -114,6 +118,9 @@ class Chart extends React.Component {
// allow chart update/re-render only if visible:
// under selected tab or no tab layout
if (nextProps.isComponentVisible) {
if (nextProps.mountedParent === null) {
return false;
}
if (nextProps.chart.triggerQuery) {
return true;
}
@@ -140,7 +147,7 @@ class Chart extends React.Component {
}
}
// `cacheBusterProp` is nnjected by react-hot-loader
// `cacheBusterProp` is jected by react-hot-loader
return this.props.cacheBusterProp !== nextProps.cacheBusterProp;
}
@@ -346,4 +353,20 @@ class Chart extends React.Component {
Chart.propTypes = propTypes;
Chart.defaultProps = defaultProps;
export default Chart;
function mapStateToProps({ dashboardState }) {
return {
// needed to prevent chart from rendering while tab switch animation in progress
// when undefined, default to have mounted the root tab
mountedParent: dashboardState?.mountedTab,
};
}
/**
* The original Chart component not connected to state.
*/
export const ChartUnconnected = Chart;
/**
* Redux connected Chart component.
*/
export default connect(mapStateToProps, null)(Chart);

View File

@@ -47,9 +47,12 @@ const propTypes = {
renderTabContent: PropTypes.bool, // whether to render tabs + content or just tabs
editMode: PropTypes.bool.isRequired,
renderHoverMenu: PropTypes.bool,
logEvent: PropTypes.func.isRequired,
directPathToChild: PropTypes.arrayOf(PropTypes.string),
// actions (from DashboardComponent.jsx)
logEvent: PropTypes.func.isRequired,
setMountedTab: PropTypes.func.isRequired,
// grid related
availableColumnCount: PropTypes.number,
columnWidth: PropTypes.number,
@@ -260,6 +263,12 @@ class Tabs extends React.PureComponent {
onDeleteTab={this.handleDeleteTab}
/>
}
onEntering={() => {
// Entering current tab, DOM is visible and has dimension
if (renderTabContent) {
this.props.setMountedTab(tabId);
}
}}
>
{renderTabContent && (
<DashboardComponent

View File

@@ -24,6 +24,7 @@ import {
setColorSchemeAndUnsavedChanges,
showBuilderPane,
setDirectPathToChild,
setMountedTab,
} from '../actions/dashboardState';
import {
deleteTopLevelTabs,
@@ -49,6 +50,7 @@ function mapDispatchToProps(dispatch) {
showBuilderPane,
setColorSchemeAndUnsavedChanges,
setDirectPathToChild,
setMountedTab,
},
dispatch,
);

View File

@@ -33,7 +33,7 @@ import {
updateComponents,
handleComponentDrop,
} from '../actions/dashboardLayout';
import { setDirectPathToChild } from '../actions/dashboardState';
import { setDirectPathToChild, setMountedTab } from '../actions/dashboardState';
import { logEvent } from '../../logger/actions';
import { addDangerToast } from '../../messageToasts/actions';
@@ -106,6 +106,7 @@ function mapDispatchToProps(dispatch) {
updateComponents,
handleComponentDrop,
setDirectPathToChild,
setMountedTab,
logEvent,
},
dispatch,

View File

@@ -33,6 +33,7 @@ import {
UPDATE_CSS,
SET_REFRESH_FREQUENCY,
SET_DIRECT_PATH,
SET_MOUNTED_TAB,
SET_FOCUSED_FILTER_FIELD,
} from '../actions/dashboardState';
import { BUILDER_PANE_TYPE } from '../util/constants';
@@ -127,10 +128,19 @@ export default function dashboardStateReducer(state = {}, action) {
[SET_DIRECT_PATH]() {
return {
...state,
// change of direct path (tabs) will reset current mounted tab
mountedTab: null,
directPathToChild: action.path,
directPathLastUpdated: Date.now(),
};
},
[SET_MOUNTED_TAB]() {
// set current mounted tab after tab is really mounted to DOM
return {
...state,
mountedTab: action.mountedTab,
};
},
[SET_FOCUSED_FILTER_FIELD]() {
const { focusedFilterField } = state;
if (action.chartId && action.column) {

View File

@@ -19,9 +19,11 @@
import { DataRecordFilters } from '@superset-ui/chart';
export default function getEffectiveExtraFilters(filters: DataRecordFilters) {
return Object.entries(filters).map(([column, values]) => ({
col: column,
op: 'in',
val: values,
}));
return Object.entries(filters)
.map(([column, values]) => ({
col: column,
op: Array.isArray(values) ? 'in' : '==',
val: values,
}))
.filter(filter => filter.val !== null);
}

View File

@@ -33,10 +33,10 @@ setupPlugins();
const App = ({ store }) => (
<Provider store={store}>
<ThemeProvider theme={supersetTheme}>
<div>
<>
<ExploreViewContainer />
<ToastPresenter />
</div>
</>
</ThemeProvider>
</Provider>
);

View File

@@ -66,15 +66,14 @@ class ControlPanelsContainer extends React.Component {
renderControl({ name, config }) {
const { actions, controls, exploreState, form_data: formData } = this.props;
const { visibility } = config;
// If the control item is not an object, we have to look up the control data from
// the centralized controls file.
// When it is an object we read control data straight from `config` instead
const controlData = {
...controls[name],
...config,
...controls[name],
name,
// apply current value in formData
value: formData[name],
};
const {
validationErrors,

View File

@@ -19,7 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { isFunction } from 'lodash';
import { CreatableSelect } from 'src/components/Select';
import { Select } from 'src/components/Select';
import ControlHeader from '../ControlHeader';
import TooltipWrapper from '../../../components/TooltipWrapper';
@@ -50,7 +50,6 @@ export default class ColorSchemeControl extends React.PureComponent {
this.state = {
scheme: this.props.value,
};
this.onChange = this.onChange.bind(this);
this.renderOption = this.renderOption.bind(this);
}
@@ -62,9 +61,8 @@ export default class ColorSchemeControl extends React.PureComponent {
}
renderOption(key) {
const { isLinear, schemes } = this.props;
const schemeLookup = isFunction(schemes) ? schemes() : schemes;
const currentScheme = schemeLookup[key.value || defaultProps.value];
const { isLinear } = this.props;
const currentScheme = this.schemes[key.value];
// For categorical scheme, display all the colors
// For sequential scheme, show 10 or interpolate to 10.
@@ -97,12 +95,16 @@ export default class ColorSchemeControl extends React.PureComponent {
}
render() {
const { choices } = this.props;
const options = (isFunction(choices) ? choices() : choices).map(choice => ({
value: choice[0],
label: choice[1],
}));
const { schemes, choices } = this.props;
// save parsed schemes for later
this.schemes = isFunction(schemes) ? schemes() : schemes;
const options = (isFunction(choices) ? choices() : choices).map(
([value, label]) => ({
value,
// use scheme label if available
label: this.schemes[value]?.label || label,
}),
);
const selectProps = {
multi: false,
name: `select-${this.props.name}`,
@@ -119,7 +121,7 @@ export default class ColorSchemeControl extends React.PureComponent {
return (
<div>
<ControlHeader {...this.props} />
<CreatableSelect {...selectProps} />
<Select {...selectProps} />
</div>
);
}

View File

@@ -83,8 +83,8 @@ const DEFAULT_SINCE = moment()
const DEFAULT_UNTIL = moment().utc().startOf('day').format(MOMENT_FORMAT);
const SEPARATOR = ' : ';
const FREEFORM_TOOLTIP = t(
'Superset supports smart date parsing. Strings like `last sunday` or ' +
'`last october` can be used.',
'Superset supports smart date parsing. Strings like `3 weeks ago`, `last sunday`, or ' +
'`2 weeks from now` can be used.',
);
const DATE_FILTER_POPOVER_STYLE = { width: '250px' };

View File

@@ -36,18 +36,17 @@ export function getFormDataFromControls(controlsState) {
export function validateControl(control, processedState) {
const validators = control.validators;
const validationErrors = [];
if (validators && validators.length > 0) {
const validatedControl = { ...control };
const validationErrors = [];
validators.forEach(f => {
const v = f.call(control, control.value, processedState);
if (v) {
validationErrors.push(v);
}
});
return { ...validatedControl, validationErrors };
}
return control;
// always reset validation errors even when there is no validator
return { ...control, validationErrors };
}
/**
@@ -88,17 +87,6 @@ export const getControlConfig = memoizeOne(function getControlConfig(
return control?.config || control;
});
export function applyMapStateToPropsToControl(controlState, controlPanelState) {
const { mapStateToProps } = controlState;
if (mapStateToProps && controlPanelState) {
return {
...controlState,
...mapStateToProps(controlPanelState, controlState),
};
}
return controlState;
}
function handleMissingChoice(control) {
// If the value is not valid anymore based on choices, clear it
const value = control.value;
@@ -121,6 +109,38 @@ function handleMissingChoice(control) {
return control;
}
export function applyMapStateToPropsToControl(controlState, controlPanelState) {
const { mapStateToProps } = controlState;
let state = { ...controlState };
let { value } = state; // value is current user-input value
if (mapStateToProps && controlPanelState) {
state = {
...controlState,
...mapStateToProps(controlPanelState, controlState),
};
// `mapStateToProps` may also provide a value
value = value || state.value;
}
// If default is a function, evaluate it
if (typeof state.default === 'function') {
state.default = state.default(state, controlPanelState);
// if default is still a function, discard
if (typeof state.default === 'function') {
delete state.default;
}
}
// If no current value, set it as default
if (state.default && value === undefined) {
value = state.default;
}
// If a choice control went from multi=false to true, wrap value in array
if (value && state.multi && !Array.isArray(value)) {
value = [value];
}
state.value = value;
return validateControl(handleMissingChoice(state), state);
}
export function getControlStateFromControlConfig(
controlConfig,
controlPanelState,
@@ -130,38 +150,16 @@ export function getControlStateFromControlConfig(
if (!controlConfig) {
return null;
}
let controlState = { ...controlConfig };
const controlState = { ...controlConfig, value };
// only apply mapStateToProps when control states have been initialized
// or when explicitly didn't provide control panel state (mostly for testing)
if (
controlPanelState &&
(controlPanelState.controls || !controlPanelState.isInitializing)
(controlPanelState && controlPanelState.controls) ||
controlPanelState === null
) {
controlState = applyMapStateToPropsToControl(
controlState,
controlPanelState,
);
return applyMapStateToPropsToControl(controlState, controlPanelState);
}
// If default is a function, evaluate it
if (typeof controlState.default === 'function') {
controlState.default = controlState.default(
controlState,
controlPanelState,
);
// if default is still a function, discard
if (typeof controlState.default === 'function') {
delete controlState.default;
}
}
// If a choice control went from multi=false to true, wrap value in array
const controlValue =
controlConfig.multi && value && !Array.isArray(value) ? [value] : value;
controlState.value =
typeof controlValue === 'undefined' ? controlState.default : controlValue;
return validateControl(handleMissingChoice(controlState), controlState);
return controlState;
}
export function getControlState(controlKey, vizType, state, value) {

View File

@@ -41,7 +41,6 @@ export default function getInitialState(bootstrapData) {
filterColumnOpts: [],
isDatasourceMetaLoading: false,
isStarred: false,
isInitializing: true,
};
const controls = getControlsState(bootstrappedState, rawFormData);
bootstrappedState.controls = controls;
@@ -55,7 +54,6 @@ export default function getInitialState(bootstrapData) {
bootstrappedState,
);
});
bootstrappedState.isInitializing = false;
const sliceFormData = slice
? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))

View File

@@ -29,7 +29,7 @@ import {
} from '../actions';
// To work properly the redux state must have a `messageToasts` subtree
export default function withToasts(BaseComponent: ComponentType) {
export default function withToasts(BaseComponent: ComponentType<any>) {
return connect(null, dispatch =>
bindActionCreators(
{
@@ -41,6 +41,6 @@ export default function withToasts(BaseComponent: ComponentType) {
dispatch,
),
)(BaseComponent) as any;
// Rsedux has some confusing typings that cause problems for consumers of this function.
// Redux has some confusing typings that cause problems for consumers of this function.
// If someone can fix the types, great, but for now it's just any.
}

View File

@@ -33,7 +33,7 @@ setupApp();
const profileViewContainer = document.getElementById('app');
const bootstrap = JSON.parse(
profileViewContainer.getAttribute('data-bootstrap'),
profileViewContainer?.getAttribute('data-bootstrap') ?? '{}',
);
const store = createStore(

View File

@@ -17,7 +17,6 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Col, Row, Tabs, Tab, Panel } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
@@ -26,17 +25,18 @@ import UserInfo from './UserInfo';
import Security from './Security';
import RecentActivity from './RecentActivity';
import CreatedContent from './CreatedContent';
import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes';
const propTypes = {
user: PropTypes.object.isRequired,
};
interface AppProps {
user: UserWithPermissionsAndRoles;
}
export default function App(props) {
export default function App({ user }: AppProps) {
return (
<div className="container app">
<Row>
<Col md={3}>
<UserInfo user={props.user} />
<UserInfo user={user} />
</Col>
<Col md={9}>
<Tabs id="options">
@@ -50,7 +50,7 @@ export default function App(props) {
>
<Panel>
<Panel.Body>
<Favorites user={props.user} />
<Favorites user={user} />
</Panel.Body>
</Panel>
</Tab>
@@ -64,7 +64,7 @@ export default function App(props) {
>
<Panel>
<Panel.Body>
<CreatedContent user={props.user} />
<CreatedContent user={user} />
</Panel.Body>
</Panel>
</Tab>
@@ -78,7 +78,7 @@ export default function App(props) {
>
<Panel>
<Panel.Body>
<RecentActivity user={props.user} />
<RecentActivity user={user} />
</Panel.Body>
</Panel>
</Tab>
@@ -92,7 +92,7 @@ export default function App(props) {
>
<Panel>
<Panel.Body>
<Security user={props.user} />
<Security user={user} />
</Panel.Body>
</Panel>
</Tab>
@@ -102,4 +102,3 @@ export default function App(props) {
</div>
);
}
App.propTypes = propTypes;

View File

@@ -17,28 +17,20 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { t } from '@superset-ui/translation';
import TableLoader from '../../components/TableLoader';
import { Slice } from '../types';
import { User, Dashboard } from '../../types/bootstrapTypes';
const propTypes = {
user: PropTypes.object.isRequired,
};
interface CreatedContentProps {
user: User;
}
class CreatedContent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
dashboardsLoading: true,
slicesLoading: true,
dashboards: [],
slices: [],
};
}
class CreatedContent extends React.PureComponent<CreatedContentProps> {
renderSliceTable() {
const mutator = data =>
const mutator = (data: Slice[]) =>
data.map(slice => ({
slice: <a href={slice.url}>{slice.title}</a>,
favorited: moment.utc(slice.dttm).fromNow(),
@@ -56,7 +48,7 @@ class CreatedContent extends React.PureComponent {
);
}
renderDashboardTable() {
const mutator = data =>
const mutator = (data: Dashboard[]) =>
data.map(dash => ({
dashboard: <a href={dash.url}>{dash.title}</a>,
favorited: moment.utc(dash.dttm).fromNow(),
@@ -85,6 +77,5 @@ class CreatedContent extends React.PureComponent {
);
}
}
CreatedContent.propTypes = propTypes;
export default CreatedContent;

View File

@@ -17,28 +17,20 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { t } from '@superset-ui/translation';
import TableLoader from '../../components/TableLoader';
import { Slice } from '../types';
import { User, Dashboard } from '../../types/bootstrapTypes';
const propTypes = {
user: PropTypes.object.isRequired,
};
interface FavoritesProps {
user: User;
}
export default class Favorites extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
dashboardsLoading: true,
slicesLoading: true,
dashboards: [],
slices: [],
};
}
export default class Favorites extends React.PureComponent<FavoritesProps> {
renderSliceTable() {
const mutator = data =>
const mutator = (data: Slice[]) =>
data.map(slice => ({
slice: <a href={slice.url}>{slice.title}</a>,
creator: <a href={slice.creator_url}>{slice.creator}</a>,
@@ -57,7 +49,7 @@ export default class Favorites extends React.PureComponent {
);
}
renderDashboardTable() {
const mutator = data =>
const mutator = (data: Dashboard[]) =>
data.map(dash => ({
dashboard: <a href={dash.url}>{dash.title}</a>,
creator: <a href={dash.creator_url}>{dash.creator}</a>,
@@ -86,4 +78,3 @@ export default class Favorites extends React.PureComponent {
);
}
}
Favorites.propTypes = propTypes;

View File

@@ -17,39 +17,36 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import TableLoader from '../../components/TableLoader';
import { Activity } from '../types';
import { User } from '../../types/bootstrapTypes';
const propTypes = {
user: PropTypes.object,
};
export default class RecentActivity extends React.PureComponent {
render() {
const rowLimit = 50;
const mutator = function (data) {
return data
.filter(row => row.action === 'dashboard' || row.action === 'explore')
.map(row => ({
name: <a href={row.item_url}>{row.item_title}</a>,
type: row.action,
time: moment.utc(row.time).fromNow(),
_time: row.time,
}));
};
return (
<div>
<TableLoader
className="table table-condensed"
mutator={mutator}
sortable
dataEndpoint={`/superset/recent_activity/${this.props.user.userId}/?limit=${rowLimit}`}
/>
</div>
);
}
interface RecentActivityProps {
user: User;
}
RecentActivity.propTypes = propTypes;
export default function RecentActivity({ user }: RecentActivityProps) {
const rowLimit = 50;
const mutator = function (data: Activity[]) {
return data
.filter(row => row.action === 'dashboard' || row.action === 'explore')
.map(row => ({
name: <a href={row.item_url}>{row.item_title}</a>,
type: row.action,
time: moment.utc(row.time).fromNow(),
_time: row.time,
}));
};
return (
<div>
<TableLoader
className="table table-condensed"
mutator={mutator}
sortable
dataEndpoint={`/superset/recent_activity/${user.userId}/?limit=${rowLimit}`}
/>
</div>
);
}

View File

@@ -17,14 +17,15 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Badge, Label } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes';
const propTypes = {
user: PropTypes.object.isRequired,
};
export default function Security({ user }) {
interface SecurityProps {
user: UserWithPermissionsAndRoles;
}
export default function Security({ user }: SecurityProps) {
return (
<div>
<div className="roles">
@@ -66,4 +67,3 @@ export default function Security({ user }) {
</div>
);
}
Security.propTypes = propTypes;

View File

@@ -1,75 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import Gravatar from 'react-gravatar';
import moment from 'moment';
import { Panel } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
const propTypes = {
user: PropTypes.object.isRequired,
};
const UserInfo = ({ user }) => (
<div>
<a href="https://en.gravatar.com/">
<Gravatar
email={user.email}
width="100%"
height=""
size={220}
alt={t('Profile picture provided by Gravatar')}
className="img-rounded"
style={{ borderRadius: 15 }}
/>
</a>
<hr />
<Panel>
<Panel.Body>
<h3>
<strong>
{user.firstName} {user.lastName}
</strong>
</h3>
<h4 className="username">
<i className="fa fa-user-o" /> {user.username}
</h4>
<hr />
<p>
<i className="fa fa-clock-o" /> {t('joined')}{' '}
{moment(user.createdOn, 'YYYYMMDD').fromNow()}
</p>
<p className="email">
<i className="fa fa-envelope-o" /> {user.email}
</p>
<p className="roles">
<i className="fa fa-lock" /> {Object.keys(user.roles).join(', ')}
</p>
<p>
<i className="fa fa-key" />
&nbsp;
<span className="text-muted">{t('id:')}</span>&nbsp;
<span className="user-id">{user.userId}</span>
</p>
</Panel.Body>
</Panel>
</div>
);
UserInfo.propTypes = propTypes;
export default UserInfo;

View File

@@ -0,0 +1,76 @@
/**
* 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 Gravatar from 'react-gravatar';
import moment from 'moment';
import { Panel } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes';
interface UserInfoProps {
user: UserWithPermissionsAndRoles;
}
export default function UserInfo({ user }: UserInfoProps) {
return (
<div>
<a href="https://en.gravatar.com/">
<Gravatar
email={user.email}
width="100%"
height=""
size={220}
alt={t('Profile picture provided by Gravatar')}
className="img-rounded"
style={{ borderRadius: 15 }}
/>
</a>
<hr />
<Panel>
<Panel.Body>
<h3>
<strong>
{user.firstName} {user.lastName}
</strong>
</h3>
<h4 className="username">
<i className="fa fa-user-o" /> {user.username}
</h4>
<hr />
<p>
<i className="fa fa-clock-o" /> {t('joined')}{' '}
{moment(user.createdOn, 'YYYYMMDD').fromNow()}
</p>
<p className="email">
<i className="fa fa-envelope-o" /> {user.email}
</p>
<p className="roles">
<i className="fa fa-lock" /> {Object.keys(user.roles).join(', ')}
</p>
<p>
<i className="fa fa-key" />
&nbsp;
<span className="text-muted">{t('id:')}</span>&nbsp;
<span className="user-id">{user.userId}</span>
</p>
</Panel.Body>
</Panel>
</div>
);
}

View File

@@ -0,0 +1,34 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export type Slice = {
dttm: number;
id: number;
url: string;
title: string;
creator?: string;
creator_url?: string;
viz_type: string;
};
export type Activity = {
action: string;
item_title: string;
item_url: string;
time: number;
};

View File

@@ -0,0 +1,44 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export type User = {
createdOn: string;
email: string;
firstName: string;
isActive: boolean;
lastName: string;
userId: number;
username: string;
};
export interface UserWithPermissionsAndRoles extends User {
permissions: {
database_access?: string[];
datasource_access?: string[];
};
roles: Record<string, string[][]>;
}
export type Dashboard = {
dttm: number;
id: number;
url: string;
title: string;
creator?: string;
creator_url?: string;
};

View File

@@ -128,7 +128,11 @@ export function applyFormattingToTabularData(data) {
}
return data.map(row => ({
...row,
// eslint-disable-next-line no-underscore-dangle
__timestamp: DATETIME_FORMATTER(new Date(row.__timestamp)),
/* eslint-disable no-underscore-dangle */
__timestamp:
row.__timestamp === 0 || row.__timestamp
? DATETIME_FORMATTER(new Date(row.__timestamp))
: row.__timestamp,
/* eslint-enable no-underscore-dangle */
}));
}

View File

@@ -19,7 +19,6 @@
import { SupersetClient } from '@superset-ui/connection';
import { t } from '@superset-ui/translation';
import { getChartMetadataRegistry } from '@superset-ui/chart';
import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
import rison from 'rison';
@@ -108,7 +107,7 @@ class ChartList extends React.PureComponent<Props, State> {
return isFeatureEnabled(FeatureFlag.LIST_VIEWS_SIP34_FILTER_UI);
}
initialSort = [{ id: 'changed_on', desc: true }];
initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
columns = [
{
@@ -147,17 +146,17 @@ class ChartList extends React.PureComponent<Props, State> {
},
},
}: any) => <a href={changedByUrl}>{changedByName}</a>,
Header: t('Creator'),
accessor: 'changed_by_fk',
Header: t('Modified By'),
accessor: 'changed_by.first_name',
},
{
Cell: ({
row: {
original: { changed_on: changedOn },
original: { changed_on_delta_humanized: changedOn },
},
}: any) => <span className="no-wrap">{moment(changedOn).fromNow()}</span>,
}: any) => <span className="no-wrap">{changedOn}</span>,
Header: t('Last Modified'),
accessor: 'changed_on',
accessor: 'changed_on_delta_humanized',
},
{
accessor: 'description',

View File

@@ -18,7 +18,6 @@
*/
import { SupersetClient } from '@superset-ui/connection';
import { t } from '@superset-ui/translation';
import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
import rison from 'rison';
@@ -60,7 +59,7 @@ interface Dashboard {
changed_by: string;
changed_by_name: string;
changed_by_url: string;
changed_on: string;
changed_on_delta_humanized: string;
dashboard_title: string;
published: boolean;
url: string;
@@ -123,7 +122,7 @@ class DashboardList extends React.PureComponent<Props, State> {
return isFeatureEnabled(FeatureFlag.LIST_VIEWS_SIP34_FILTER_UI);
}
initialSort = [{ id: 'changed_on', desc: true }];
initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
columns = [
{
@@ -162,8 +161,8 @@ class DashboardList extends React.PureComponent<Props, State> {
},
},
}: any) => <a href={changedByUrl}>{changedByName}</a>,
Header: t('Creator'),
accessor: 'changed_by_fk',
Header: t('Modified By'),
accessor: 'changed_by.first_name',
},
{
Cell: ({
@@ -181,11 +180,11 @@ class DashboardList extends React.PureComponent<Props, State> {
{
Cell: ({
row: {
original: { changed_on: changedOn },
original: { changed_on_delta_humanized: changedOn },
},
}: any) => <span className="no-wrap">{moment(changedOn).fromNow()}</span>,
}: any) => <span className="no-wrap">{changedOn}</span>,
Header: t('Modified'),
accessor: 'changed_on',
accessor: 'changed_on_delta_humanized',
},
{
accessor: 'slug',

View File

@@ -331,7 +331,7 @@ class FilterBox extends React.Component {
? selectedValues[key]
: [selectedValues[key]];
selectedValuesForKey
.filter(value => !choiceIds.has(value))
.filter(value => value !== null && !choiceIds.has(value))
.forEach(value => {
choices.unshift({
filter: key,

View File

@@ -44,7 +44,7 @@ setupApp();
setupPlugins();
const container = document.getElementById('app');
const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));
const bootstrap = JSON.parse(container?.getAttribute('data-bootstrap') ?? '{}');
const user = { ...bootstrap.user };
const menu = { ...bootstrap.common.menu_data };
const common = { ...bootstrap.common };

View File

@@ -17,35 +17,44 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection';
import moment from 'moment';
import { debounce } from 'lodash';
import ListView from 'src/components/ListView/ListView';
import withToasts from 'src/messageToasts/enhancers/withToasts';
import { Dashboard } from 'src/types/bootstrapTypes';
import { FetchDataConfig } from 'src/components/ListView/types';
const PAGE_SIZE = 25;
class DashboardTable extends React.PureComponent {
static propTypes = {
addDangerToast: PropTypes.func.isRequired,
search: PropTypes.string,
};
interface DashboardTableProps {
addDangerToast: (message: string) => void;
search?: string;
}
interface DashboardTableState {
dashboards: Dashboard[];
dashboard_count: number;
loading: boolean;
}
class DashboardTable extends React.PureComponent<
DashboardTableProps,
DashboardTableState
> {
state = {
dashboards: [],
dashboard_count: 0,
loading: false,
};
componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: DashboardTableProps) {
if (prevProps.search !== this.props.search) {
this.fetchDataDebounced({
pageSize: PAGE_SIZE,
pageIndex: 0,
sortBy: this.initialSort,
filters: {},
filters: [],
});
}
}
@@ -58,31 +67,51 @@ class DashboardTable extends React.PureComponent {
row: {
original: { url, dashboard_title: dashboardTitle },
},
}: {
row: {
original: {
url: string;
dashboard_title: string;
};
};
}) => <a href={url}>{dashboardTitle}</a>,
},
{
accessor: 'changed_by_fk',
Header: 'Creator',
accessor: 'changed_by.first_name',
Header: 'Modified By',
Cell: ({
row: {
original: { changed_by_name: changedByName, changedByUrl },
},
}: {
row: {
original: {
changed_by_name: string;
changedByUrl: string;
};
};
}) => <a href={changedByUrl}>{changedByName}</a>,
},
{
accessor: 'changed_on',
accessor: 'changed_on_delta_humanized',
Header: 'Modified',
Cell: ({
row: {
original: { changed_on: changedOn },
original: { changed_on_delta_humanized: changedOn },
},
}) => <span className="no-wrap">{moment(changedOn).fromNow()}</span>,
}: {
row: {
original: {
changed_on_delta_humanized: string;
};
};
}) => <span className="no-wrap">{changedOn}</span>,
},
];
initialSort = [{ id: 'changed_on', desc: true }];
initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
fetchData = ({ pageIndex, pageSize, sortBy, filters }) => {
fetchData = ({ pageIndex, pageSize, sortBy, filters }: FetchDataConfig) => {
this.setState({ loading: true });
const filterExps = Object.keys(filters)
.map(fk => ({

View File

@@ -17,23 +17,30 @@
* under the License.
*/
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import { useQueryParam, StringParam } from 'use-query-params';
import { useQueryParam, StringParam, QueryParamConfig } from 'use-query-params';
import { User } from 'src/types/bootstrapTypes';
import RecentActivity from '../profile/components/RecentActivity';
import Favorites from '../profile/components/Favorites';
import DashboardTable from './DashboardTable';
const propTypes = {
user: PropTypes.object.isRequired,
};
interface WelcomeProps {
user: User;
}
function useSyncQueryState(queryParam, queryParamType, defaultState) {
function useSyncQueryState(
queryParam: string,
queryParamType: QueryParamConfig<
string | null | undefined,
string | undefined
>,
defaultState: string,
): [string, (val: string) => void] {
const [queryState, setQueryState] = useQueryParam(queryParam, queryParamType);
const [state, setState] = useState(queryState || defaultState);
const setQueryStateAndState = val => {
const setQueryStateAndState = (val: string) => {
setQueryState(val);
setState(val);
};
@@ -41,7 +48,7 @@ function useSyncQueryState(queryParam, queryParamType, defaultState) {
return [state, setQueryStateAndState];
}
export default function Welcome({ user }) {
export default function Welcome({ user }: WelcomeProps) {
const [activeTab, setActiveTab] = useSyncQueryState(
'activeTab',
StringParam,
@@ -58,6 +65,7 @@ export default function Welcome({ user }) {
<div className="container welcome">
<Tabs
activeKey={activeTab}
// @ts-ignore React bootstrap types aren't quite right here
onSelect={setActiveTab}
id="uncontrolled-tab-example"
>
@@ -75,6 +83,7 @@ export default function Welcome({ user }) {
style={{ marginTop: '25px' }}
placeholder="Search"
value={searchQuery}
// @ts-ignore React bootstrap types aren't quite right here
onChange={e => setSearchQuery(e.currentTarget.value)}
/>
</Col>
@@ -114,5 +123,3 @@ export default function Welcome({ user }) {
</div>
);
}
Welcome.propTypes = propTypes;

View File

@@ -180,9 +180,9 @@ const config = {
addSlice: addPreamble('/src/addSlice/index.tsx'),
explore: addPreamble('/src/explore/index.jsx'),
dashboard: addPreamble('/src/dashboard/index.jsx'),
sqllab: addPreamble('/src/SqlLab/index.jsx'),
welcome: addPreamble('/src/welcome/index.jsx'),
profile: addPreamble('/src/profile/index.jsx'),
sqllab: addPreamble('/src/SqlLab/index.tsx'),
welcome: addPreamble('/src/welcome/index.tsx'),
profile: addPreamble('/src/profile/index.tsx'),
showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')],
},
output,

View File

@@ -101,19 +101,17 @@ class ChartRestApi(BaseSupersetModelRestApi):
"cache_timeout",
]
show_select_columns = show_columns + ["table.id"]
list_columns = [
"id",
"slice_name",
"url",
"description",
"changed_by_fk",
"created_by_fk",
"changed_by_name",
"changed_by_url",
"changed_by.first_name",
"changed_by.last_name",
"changed_on",
"changed_on_utc",
"changed_on_delta_humanized",
"datasource_id",
"datasource_type",
"datasource_name_text",
@@ -124,13 +122,13 @@ class ChartRestApi(BaseSupersetModelRestApi):
"params",
"cache_timeout",
]
list_select_columns = list_columns + ["changed_on", "changed_by_fk"]
order_columns = [
"slice_name",
"viz_type",
"datasource_name",
"changed_by_fk",
"changed_on",
"changed_by.first_name",
"changed_on_delta_humanized",
]
search_columns = (
"slice_name",
@@ -419,7 +417,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
def data(self) -> Response:
def data(self) -> Response: # pylint: disable=too-many-return-statements
"""
Takes a query context constructed in the client and returns payload
data response for the given query.
@@ -462,13 +460,16 @@ class ChartRestApi(BaseSupersetModelRestApi):
return self.response_400(message="Request is incorrect")
except ValidationError as error:
return self.response_400(
_("Request is incorrect: %(error)s", error=error.messages)
message=_("Request is incorrect: %(error)s", error=error.messages)
)
try:
query_context.raise_for_access()
except SupersetSecurityException:
return self.response_401()
payload = query_context.get_payload()
for query in payload:
if query.get("error"):
return self.response_400(message=f"Error: {query['error']}")
result_format = query_context.result_format
if result_format == ChartDataResultFormat.CSV:
# return the first result
@@ -488,7 +489,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
resp.headers["Content-Type"] = "application/json; charset=utf-8"
return resp
raise self.response_400(message=f"Unsupported result_format: {result_format}")
return self.response_400(message=f"Unsupported result_format: {result_format}")
@expose("/<pk>/cache_screenshot/", methods=["GET"])
@protect()

View File

@@ -14,15 +14,15 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from typing import Any, Dict, Union
from typing import Any, Dict
from flask_babel import gettext as _
from marshmallow import fields, post_load, Schema, validate, ValidationError
from marshmallow import fields, post_load, Schema, validate
from marshmallow.validate import Length, Range
from superset.common.query_context import QueryContext
from superset.exceptions import SupersetException
from superset.utils import core as utils
from superset.utils import schema as utils
from superset.utils.core import FilterOperator
#
# RISON/JSON schemas for query parameters
@@ -100,13 +100,6 @@ openapi_spec_methods_override = {
}
def validate_json(value: Union[bytes, bytearray, str]) -> None:
try:
utils.validate_json(value)
except SupersetException:
raise ValidationError("JSON not valid")
class ChartPostSchema(Schema):
"""
Schema to add a new chart.
@@ -123,7 +116,7 @@ class ChartPostSchema(Schema):
)
owners = fields.List(fields.Integer(description=owners_description))
params = fields.String(
description=params_description, allow_none=True, validate=validate_json
description=params_description, allow_none=True, validate=utils.validate_json
)
cache_timeout = fields.Integer(
description=cache_timeout_description, allow_none=True
@@ -551,8 +544,8 @@ class ChartDataFilterSchema(Schema):
)
op = fields.String( # pylint: disable=invalid-name
description="The comparison operator.",
validate=validate.OneOf(
choices=[filter_op.value for filter_op in utils.FilterOperator]
validate=utils.OneOfCaseInsensitive(
choices=[filter_op.value for filter_op in FilterOperator]
),
required=True,
example="IN",
@@ -568,7 +561,7 @@ class ChartDataExtrasSchema(Schema):
time_range_endpoints = fields.List(
fields.String(
validate=validate.OneOf(choices=("INCLUSIVE", "EXCLUSIVE")),
validate=validate.OneOf(choices=("unknown", "inclusive", "exclusive")),
description="A list with two values, stating if start/end should be "
"inclusive/exclusive.",
)
@@ -687,7 +680,7 @@ class ChartDataQueryObjectSchema(Schema):
timeseries_limit = fields.Integer(
description="Maximum row count for timeseries queries. Default: `0`",
)
timeseries_limit_metric = fields.Integer(
timeseries_limit_metric = fields.Raw(
description="Metric used to limit timeseries queries by.", allow_none=True,
)
row_limit = fields.Integer(
@@ -725,10 +718,10 @@ class ChartDataQueryObjectSchema(Schema):
deprecated=True,
)
having_filters = fields.List(
fields.Dict(),
fields.Nested(ChartDataFilterSchema),
description="HAVING filters to be added to legacy Druid datasource queries. "
"This field is deprecated and should be passed to `extras` "
"as `filters_druid`.",
"as `having_druid`.",
deprecated=True,
)

View File

@@ -22,11 +22,13 @@ from typing import Any, ClassVar, Dict, List, Optional, Union
import numpy as np
import pandas as pd
from flask_babel import gettext as _
from superset import app, cache, db, security_manager
from superset.common.query_object import QueryObject
from superset.connectors.base.models import BaseDatasource
from superset.connectors.connector_registry import ConnectorRegistry
from superset.exceptions import QueryObjectValidationError
from superset.stats_logger import BaseStatsLogger
from superset.utils import core as utils
from superset.utils.core import DTTM_ALIAS
@@ -111,8 +113,7 @@ class QueryContext:
self.df_metrics_to_num(df, query_object)
df.replace([np.inf, -np.inf], np.nan)
df = query_object.exec_post_processing(df)
df = query_object.exec_post_processing(df)
return {
"query": result.query,
@@ -160,10 +161,7 @@ class QueryContext:
df = payload["df"]
status = payload["status"]
if status != utils.QueryStatus.FAILED:
if df.empty:
payload["error"] = "No data"
else:
payload["data"] = self.get_data(df)
payload["data"] = self.get_data(df)
del payload["df"]
if self.result_type == utils.ChartDataResultType.RESULTS:
return {"data": payload["data"]}
@@ -238,6 +236,21 @@ class QueryContext:
if query_obj and not is_loaded:
try:
invalid_columns = [
col
for col in query_obj.columns
+ query_obj.groupby
+ [flt["col"] for flt in query_obj.filter]
+ utils.get_column_names_from_metrics(query_obj.metrics)
if col not in self.datasource.column_names
]
if invalid_columns:
raise QueryObjectValidationError(
_(
"Columns missing in datasource: %(invalid_columns)s",
invalid_columns=invalid_columns,
)
)
query_result = self.get_query_result(query_obj)
status = query_result["status"]
query = query_result["query"]
@@ -248,10 +261,13 @@ class QueryContext:
if not self.force:
stats_logger.incr("loaded_from_source_without_force")
is_loaded = True
except QueryObjectValidationError as ex:
error_message = str(ex)
status = utils.QueryStatus.FAILED
except Exception as ex: # pylint: disable=broad-except
logger.exception(ex)
if not error_message:
error_message = "{}".format(ex)
error_message = str(ex)
status = utils.QueryStatus.FAILED
stacktrace = utils.get_stacktrace()

View File

@@ -335,6 +335,12 @@ GET_FEATURE_FLAGS_FUNC: Optional[Callable[[Dict[str, bool]], Dict[str, bool]]] =
THUMBNAIL_SELENIUM_USER = "Admin"
THUMBNAIL_CACHE_CONFIG: CacheConfig = {"CACHE_TYPE": "null"}
# Used for thumbnails and other api: Time in seconds before selenium
# times out after trying to locate an element on the page and wait
# for that element to load for an alert screenshot.
SCREENSHOT_LOCATE_WAIT = 10
SCREENSHOT_LOAD_WAIT = 60
# ---------------------------------------------------
# Image and file configuration
# ---------------------------------------------------
@@ -840,6 +846,16 @@ TALISMAN_CONFIG = {
# a custom security config could potentially give access to setting filters on
# tables that users do not have access to.
ENABLE_ROW_LEVEL_SECURITY = False
# It is possible to customize which tables and roles are featured in the RLS
# dropdown. When set, this dict is assigned to `add_form_query_rel_fields` and
# `edit_form_query_rel_fields` on `RowLevelSecurityFiltersModelView`. Example:
#
# from flask_appbuilder.models.sqla import filters
# RLS_FORM_QUERY_REL_FIELDS = {
# "roles": [["name", filters.FilterStartsWith, "RlsRole"]]
# "tables": [["table_name", filters.FilterContains, "rls"]]
# }
RLS_FORM_QUERY_REL_FIELDS: Optional[Dict[str, List[List[Any]]]] = None
#
# Flask session cookie options

View File

@@ -1396,7 +1396,7 @@ class DruidDatasource(Model, BaseDatasource):
if df is None:
df = pd.DataFrame()
qry["filter"] = self._add_filter_from_pre_query_data(
df, pre_qry["dimensions"], qry["filter"]
df, pre_qry["dimensions"], filters
)
qry["limit_spec"] = None
if row_limit:

View File

@@ -25,6 +25,7 @@ import sqlparse
from flask import escape, Markup
from flask_appbuilder import Model
from flask_babel import lazy_gettext as _
from jinja2.exceptions import TemplateError
from sqlalchemy import (
and_,
asc,
@@ -40,7 +41,7 @@ from sqlalchemy import (
Table,
Text,
)
from sqlalchemy.exc import CompileError
from sqlalchemy.exc import CompileError, SQLAlchemyError
from sqlalchemy.orm import backref, Query, relationship, RelationshipProperty, Session
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.schema import UniqueConstraint
@@ -51,7 +52,7 @@ from superset import app, db, is_feature_enabled, security_manager
from superset.connectors.base.models import BaseColumn, BaseDatasource, BaseMetric
from superset.constants import NULL_STRING
from superset.db_engine_specs.base import TimestampExpression
from superset.exceptions import DatabaseNotFound
from superset.exceptions import DatabaseNotFound, QueryObjectValidationError
from superset.jinja_context import (
BaseTemplateProcessor,
ExtraCache,
@@ -89,6 +90,19 @@ class AnnotationDatasource(BaseDatasource):
cache_timeout = 0
changed_on = None
type = "annotation"
column_names = [
"created_on",
"changed_on",
"id",
"start_dttm",
"end_dttm",
"layer_id",
"short_descr",
"long_descr",
"json_metadata",
"created_by_fk",
"changed_by_fk",
]
def query(self, query_obj: QueryObjectDict) -> QueryResult:
error_message = None
@@ -217,15 +231,16 @@ class TableColumn(Model, BaseColumn):
return and_(*l)
def get_timestamp_expression(
self, time_grain: Optional[str]
self, time_grain: Optional[str], label: Optional[str] = None
) -> Union[TimestampExpression, Label]:
"""
Return a SQLAlchemy Core element representation of self to be used in a query.
:param time_grain: Optional time grain, e.g. P1Y
:param label: alias/label that column is expected to have
:return: A TimeExpression object wrapped in a Label if supported by db
"""
label = utils.DTTM_ALIAS
label = label or utils.DTTM_ALIAS
db_ = self.table.database
pdf = self.python_date_format
@@ -629,7 +644,15 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
if self.fetch_values_predicate:
tp = self.get_template_processor()
qry = qry.where(text(tp.process_template(self.fetch_values_predicate)))
try:
qry = qry.where(text(tp.process_template(self.fetch_values_predicate)))
except TemplateError as ex:
raise QueryObjectValidationError(
_(
"Error in jinja expression in fetch values predicate: %(msg)s",
msg=ex.message,
)
)
engine = self.database.get_sqla_engine()
sql = "{}".format(qry.compile(engine, compile_kwargs={"literal_binds": True}))
@@ -679,7 +702,16 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
if self.sql:
from_sql = self.sql
if template_processor:
from_sql = template_processor.process_template(from_sql)
try:
from_sql = template_processor.process_template(from_sql)
except TemplateError as ex:
raise QueryObjectValidationError(
_(
"Error in jinja expression in FROM clause: %(msg)s",
msg=ex.message,
)
)
from_sql = sqlparse.format(from_sql, strip_comments=True)
return TextAsFrom(sa.text(from_sql), []).alias("expr_qry")
return self.get_sqla_table()
@@ -698,7 +730,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
expression_type = metric.get("expressionType")
label = utils.get_metric_name(metric)
if expression_type == utils.ADHOC_METRIC_EXPRESSION_TYPES["SIMPLE"]:
if expression_type == utils.AdhocMetricExpressionType.SIMPLE:
column_name = metric["column"].get("column_name")
table_column = columns_by_name.get(column_name)
if table_column:
@@ -706,7 +738,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
else:
sqla_column = column(column_name)
sqla_metric = self.sqla_aggregations[metric["aggregate"]](sqla_column)
elif expression_type == utils.ADHOC_METRIC_EXPRESSION_TYPES["SQL"]:
elif expression_type == utils.AdhocMetricExpressionType.SQL:
sqla_metric = literal_column(metric.get("sqlExpression"))
else:
return None
@@ -725,10 +757,15 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
:returns: A list of SQL clauses to be ANDed together.
:rtype: List[str]
"""
return [
text("({})".format(template_processor.process_template(f.clause)))
for f in security_manager.get_rls_filters(self)
]
try:
return [
text("({})".format(template_processor.process_template(f.clause)))
for f in security_manager.get_rls_filters(self)
]
except TemplateError as ex:
raise QueryObjectValidationError(
_("Error in jinja expression in RLS filters: %(msg)s", msg=ex.message,)
)
def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements
self,
@@ -786,7 +823,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
metrics_by_name: Dict[str, SqlMetric] = {m.metric_name: m for m in self.metrics}
if not granularity and is_timeseries:
raise Exception(
raise QueryObjectValidationError(
_(
"Datetime column not provided as part table configuration "
"and is required by this type of chart"
@@ -797,7 +834,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
and not columns
and (is_sip_38 or (not is_sip_38 and not groupby))
):
raise Exception(_("Empty query?"))
raise QueryObjectValidationError(_("Empty query?"))
metrics_exprs: List[ColumnElement] = []
for metric in metrics:
if utils.is_adhoc_metric(metric):
@@ -806,7 +843,9 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
elif isinstance(metric, str) and metric in metrics_by_name:
metrics_exprs.append(metrics_by_name[metric].get_sqla_col())
else:
raise Exception(_("Metric '%(metric)s' does not exist", metric=metric))
raise QueryObjectValidationError(
_("Metric '%(metric)s' does not exist", metric=metric)
)
if metrics_exprs:
main_metric_expr = metrics_exprs[0]
else:
@@ -816,6 +855,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
select_exprs: List[Column] = []
groupby_exprs_sans_timestamp = OrderedDict()
assert extras is not None
if (is_sip_38 and metrics and columns) or (not is_sip_38 and groupby):
# dedup columns while preserving order
columns_ = columns if is_sip_38 else groupby
@@ -824,7 +864,13 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
select_exprs = []
for selected in groupby:
if selected in columns_by_name:
# if groupby field/expr equals granularity field/expr
if selected == granularity:
time_grain = extras.get("time_grain_sqla")
sqla_col = columns_by_name[selected]
outer = sqla_col.get_timestamp_expression(time_grain, selected)
# if groupby field equals a selected column
elif selected in columns_by_name:
outer = columns_by_name[selected].get_sqla_col()
else:
outer = literal_column(f"({selected})")
@@ -841,7 +887,6 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
)
metrics_exprs = []
assert extras is not None
time_range_endpoints = extras.get("time_range_endpoints")
groupby_exprs_with_timestamp = OrderedDict(groupby_exprs_sans_timestamp.items())
if granularity:
@@ -947,7 +992,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
!= None
)
else:
raise Exception(
raise QueryObjectValidationError(
_("Invalid filter operation type: %(op)s", op=op)
)
if config["ENABLE_ROW_LEVEL_SECURITY"]:
@@ -955,11 +1000,27 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
if extras:
where = extras.get("where")
if where:
where = template_processor.process_template(where)
try:
where = template_processor.process_template(where)
except TemplateError as ex:
raise QueryObjectValidationError(
_(
"Error in jinja expression in WHERE clause: %(msg)s",
msg=ex.message,
)
)
where_clause_and += [sa.text("({})".format(where))]
having = extras.get("having")
if having:
having = template_processor.process_template(having)
try:
having = template_processor.process_template(having)
except TemplateError as ex:
raise QueryObjectValidationError(
_(
"Error in jinja expression in HAVING clause: %(msg)s",
msg=ex.message,
)
)
having_clause_and += [sa.text("({})".format(having))]
if granularity:
qry = qry.where(and_(*(time_filters + where_clause_and)))
@@ -1106,7 +1167,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
):
ob = metrics_by_name[timeseries_limit_metric].get_sqla_col()
else:
raise Exception(
raise QueryObjectValidationError(
_("Metric '%(metric)s' does not exist", metric=timeseries_limit_metric)
)
@@ -1133,6 +1194,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
sql = query_str_ext.sql
status = utils.QueryStatus.SUCCESS
errors = None
error_message = None
def mutator(df: pd.DataFrame) -> None:
"""
@@ -1147,7 +1209,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
labels_expected = query_str_ext.labels_expected
if df is not None and not df.empty:
if len(df.columns) != len(labels_expected):
raise Exception(
raise QueryObjectValidationError(
f"For {sql}, df.columns: {df.columns}"
f" differs from {labels_expected}"
)
@@ -1163,6 +1225,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
)
db_engine_spec = self.database.db_engine_spec
errors = db_engine_spec.extract_errors(ex)
error_message = utils.error_msg_from_exception(ex)
return QueryResult(
status=status,
@@ -1170,6 +1233,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
duration=datetime.now() - qry_start_dttm,
query=sql,
errors=errors,
error_message=error_message,
)
def get_sqla_table_object(self) -> Table:
@@ -1179,13 +1243,13 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
"""Fetches the metadata for the table and merges it in"""
try:
table_ = self.get_sqla_table_object()
except Exception as ex:
logger.exception(ex)
raise Exception(
except SQLAlchemyError:
raise QueryObjectValidationError(
_(
"Table [{}] doesn't seem to exist in the specified database, "
"couldn't fetch column information"
).format(self.table_name)
"Table %(table)s doesn't seem to exist in the specified database, "
"couldn't fetch column information",
table=self.table_name,
)
)
metrics = []
@@ -1332,6 +1396,10 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
templatable_statements.append(extras["where"])
if "having" in extras:
templatable_statements.append(extras["having"])
if config["ENABLE_ROW_LEVEL_SECURITY"] and self.is_rls_supported:
templatable_statements += [
f.clause for f in security_manager.get_rls_filters(self)
]
for statement in templatable_statements:
if ExtraCache.regex.search(statement):
return True

View File

@@ -263,6 +263,9 @@ class RowLevelSecurityFiltersModelView( # pylint: disable=too-many-ancestors
"creator": _("Creator"),
"modified": _("Modified"),
}
if app.config["RLS_FORM_QUERY_REL_FIELDS"]:
add_form_query_rel_fields = app.config["RLS_FORM_QUERY_REL_FIELDS"]
edit_form_query_rel_fields = add_form_query_rel_fields
class TableModelView( # pylint: disable=too-many-ancestors

View File

@@ -96,7 +96,6 @@ class DashboardRestApi(BaseSupersetModelRestApi):
"table_names",
"thumbnail_url",
]
order_columns = ["dashboard_title", "changed_on", "published", "changed_by_fk"]
list_columns = [
"id",
"published",
@@ -112,13 +111,22 @@ class DashboardRestApi(BaseSupersetModelRestApi):
"changed_by.id",
"changed_by_name",
"changed_by_url",
"changed_on",
"changed_on_utc",
"changed_on_delta_humanized",
"dashboard_title",
"owners.id",
"owners.username",
"owners.first_name",
"owners.last_name",
]
list_select_columns = list_columns + ["changed_on", "changed_by_fk"]
order_columns = [
"dashboard_title",
"changed_on_delta_humanized",
"published",
"changed_by.first_name",
]
add_columns = [
"dashboard_title",
"slug",

View File

@@ -46,10 +46,27 @@ class DeleteDatasetCommand(BaseCommand):
def run(self) -> Model:
self.validate()
try:
dataset = DatasetDAO.delete(self._model, commit=False)
security_manager.del_permission_view_menu(
"datasource_access", dataset.get_perm()
view_menu = (
security_manager.find_view_menu(self._model.get_perm())
if self._model
else None
)
if not view_menu:
logger.error(
"Could not find the data access permission for the dataset"
)
raise DatasetDeleteFailedError()
permission_views = (
db.session.query(security_manager.permissionview_model)
.filter_by(view_menu=view_menu)
.all()
)
dataset = DatasetDAO.delete(self._model, commit=False)
for permission_view in permission_views:
db.session.delete(permission_view)
if view_menu:
db.session.delete(view_menu)
db.session.commit()
except (SQLAlchemyError, DAODeleteFailedError) as ex:
logger.exception(ex)

View File

@@ -296,7 +296,9 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
return select_exprs
@classmethod
def fetch_data(cls, cursor: Any, limit: int) -> List[Tuple[Any, ...]]:
def fetch_data(
cls, cursor: Any, limit: Optional[int] = None
) -> List[Tuple[Any, ...]]:
"""
:param cursor: Cursor instance
@@ -305,7 +307,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
"""
if cls.arraysize:
cursor.arraysize = cls.arraysize
if cls.limit_method == LimitMethod.FETCH_MANY:
if cls.limit_method == LimitMethod.FETCH_MANY and limit:
return cursor.fetchmany(limit)
return cursor.fetchall()
@@ -438,10 +440,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
"""
kwargs["encoding"] = "utf-8"
kwargs["iterator"] = True
chunks = pd.io.excel.read_excel(
io=kwargs["filepath_or_buffer"], sheet_name=kwargs["sheet_name"]
)
df = pd.concat(chunk for chunk in chunks.values())
df = pd.read_excel(**kwargs)
return df
@staticmethod
@@ -513,7 +512,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
Create table from contents of a excel. Note: this method does not create
metadata for the table.
"""
df = cls.excel_to_df(filepath_or_buffer=filename, **excel_to_df_kwargs,)
df = cls.excel_to_df(io=filename, **excel_to_df_kwargs,)
engine = cls.get_engine(database)
if table.schema:
# only add schema when it is preset and non empty

View File

@@ -84,7 +84,9 @@ class BigQueryEngineSpec(BaseEngineSpec):
return None
@classmethod
def fetch_data(cls, cursor: Any, limit: int) -> List[Tuple[Any, ...]]:
def fetch_data(
cls, cursor: Any, limit: Optional[int] = None
) -> List[Tuple[Any, ...]]:
data = super().fetch_data(cursor, limit)
# Support type BigQuery Row, introduced here PR #4071
# google.cloud.bigquery.table.Row

View File

@@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from typing import Any, List, Tuple
from typing import Any, List, Optional, Tuple
from superset.db_engine_specs.base import BaseEngineSpec
@@ -39,7 +39,9 @@ class ExasolEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
}
@classmethod
def fetch_data(cls, cursor: Any, limit: int) -> List[Tuple[Any, ...]]:
def fetch_data(
cls, cursor: Any, limit: Optional[int] = None
) -> List[Tuple[Any, ...]]:
data = super().fetch_data(cursor, limit)
# Lists of `pyodbc.Row` need to be unpacked further
return cls.pyodbc_rows_to_tuples(data)

View File

@@ -108,7 +108,9 @@ class HiveEngineSpec(PrestoEngineSpec):
return BaseEngineSpec.get_all_datasource_names(database, datasource_type)
@classmethod
def fetch_data(cls, cursor: Any, limit: int) -> List[Tuple[Any, ...]]:
def fetch_data(
cls, cursor: Any, limit: Optional[int] = None
) -> List[Tuple[Any, ...]]:
import pyhive
from TCLIService import ttypes
@@ -116,7 +118,7 @@ class HiveEngineSpec(PrestoEngineSpec):
if state.operationState == ttypes.TOperationState.ERROR_STATE:
raise Exception("Query error", state.errorMessage)
try:
return super(HiveEngineSpec, cls).fetch_data(cursor, limit)
return super().fetch_data(cursor, limit)
except pyhive.exc.ProgrammingError:
return []

View File

@@ -67,7 +67,9 @@ class MssqlEngineSpec(BaseEngineSpec):
return None
@classmethod
def fetch_data(cls, cursor: Any, limit: int) -> List[Tuple[Any, ...]]:
def fetch_data(
cls, cursor: Any, limit: Optional[int] = None
) -> List[Tuple[Any, ...]]:
data = super().fetch_data(cursor, limit)
# Lists of `pyodbc.Row` need to be unpacked further
return cls.pyodbc_rows_to_tuples(data)

View File

@@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
from datetime import datetime
from typing import Optional
from typing import Any, List, Optional, Tuple
from superset.db_engine_specs.base import BaseEngineSpec, LimitMethod
from superset.utils import core as utils
@@ -57,3 +57,16 @@ class OracleEngineSpec(BaseEngineSpec):
@classmethod
def epoch_ms_to_dttm(cls) -> str:
return "TO_DATE('1970-01-01','YYYY-MM-DD')+(1/24/60/60/1000)*{col}"
@classmethod
def fetch_data(
cls, cursor: Any, limit: Optional[int] = None
) -> List[Tuple[Any, ...]]:
"""
:param cursor: Cursor instance
:param limit: Maximum number of rows to be returned by the cursor
:return: Result of query
"""
if not cursor.description:
return []
return super().fetch_data(cursor, limit)

View File

@@ -52,7 +52,9 @@ class PostgresBaseEngineSpec(BaseEngineSpec):
}
@classmethod
def fetch_data(cls, cursor: Any, limit: int) -> List[Tuple[Any, ...]]:
def fetch_data(
cls, cursor: Any, limit: Optional[int] = None
) -> List[Tuple[Any, ...]]:
cursor.tzinfo_factory = FixedOffsetTimezone
if not cursor.description:
return []

View File

@@ -48,7 +48,10 @@ class JinjaContextManager:
"relativedelta": relativedelta,
"time": time,
"timedelta": timedelta,
"uuid": uuid,
"uuid1": uuid.uuid1,
"uuid3": uuid.uuid3,
"uuid4": uuid.uuid4,
"uuid5": uuid.uuid5,
}
self._template_processors: Dict[str, Type["BaseTemplateProcessor"]] = {}

View File

@@ -213,17 +213,17 @@ class BaseTemplateProcessor: # pylint: disable=too-few-public-methods
extra_cache_keys: Optional[List[Any]] = None,
**kwargs: Any,
) -> None:
self.database = database
self.query = query
self.schema = None
self._database = database
self._query = query
self._schema = None
if query and query.schema:
self.schema = query.schema
self._schema = query.schema
elif table:
self.schema = table.schema
self._schema = table.schema
extra_cache = ExtraCache(extra_cache_keys)
self.context = {
self._context = {
"url_param": extra_cache.url_param,
"current_user_id": extra_cache.current_user_id,
"current_username": extra_cache.current_username,
@@ -231,11 +231,11 @@ class BaseTemplateProcessor: # pylint: disable=too-few-public-methods
"filter_values": filter_values,
"form_data": {},
}
self.context.update(kwargs)
self.context.update(jinja_base_context)
self._context.update(kwargs)
self._context.update(jinja_base_context)
if self.engine:
self.context[self.engine] = self
self.env = SandboxedEnvironment()
self._context[self.engine] = self
self._env = SandboxedEnvironment()
def process_template(self, sql: str, **kwargs: Any) -> str:
"""Processes a sql template
@@ -244,8 +244,8 @@ class BaseTemplateProcessor: # pylint: disable=too-few-public-methods
>>> process_template(sql)
"SELECT '2017-01-01T00:00:00'"
"""
template = self.env.from_string(sql)
kwargs.update(self.context)
template = self._env.from_string(sql)
kwargs.update(self._context)
return template.render(kwargs)
@@ -288,20 +288,20 @@ class PrestoTemplateProcessor(BaseTemplateProcessor):
from superset.db_engine_specs.presto import PrestoEngineSpec
table_name, schema = self._schema_table(table_name, self.schema)
return cast(PrestoEngineSpec, self.database.db_engine_spec).latest_partition(
table_name, schema, self.database
table_name, schema = self._schema_table(table_name, self._schema)
return cast(PrestoEngineSpec, self._database.db_engine_spec).latest_partition(
table_name, schema, self._database
)[1]
def latest_sub_partition(self, table_name: str, **kwargs: Any) -> Any:
table_name, schema = self._schema_table(table_name, self.schema)
table_name, schema = self._schema_table(table_name, self._schema)
from superset.db_engine_specs.presto import PrestoEngineSpec
return cast(
PrestoEngineSpec, self.database.db_engine_spec
PrestoEngineSpec, self._database.db_engine_spec
).latest_sub_partition(
table_name=table_name, schema=schema, database=self.database, **kwargs
table_name=table_name, schema=schema, database=self._database, **kwargs
)
latest_partition = first_latest_partition

View File

@@ -57,6 +57,7 @@ from superset.db_engine_specs.base import TimeGrain
from superset.models.dashboard import Dashboard
from superset.models.helpers import AuditMixinNullable, ImportMixin
from superset.models.tags import DashboardUpdater, FavStarUpdater
from superset.result_set import SupersetResultSet
from superset.utils import cache as cache_util, core as utils
config = app.config
@@ -392,21 +393,18 @@ class Database(
_log_query(sqls[-1])
self.db_engine_spec.execute(cursor, sqls[-1])
if cursor.description is not None:
columns = [col_desc[0] for col_desc in cursor.description]
else:
columns = []
df = pd.DataFrame.from_records(
data=list(cursor.fetchall()), columns=columns, coerce_float=True
data = self.db_engine_spec.fetch_data(cursor)
result_set = SupersetResultSet(
data, cursor.description, self.db_engine_spec
)
df = result_set.to_pandas_df()
if mutator:
mutator(df)
for k, v in df.dtypes.items():
if v.type == numpy.object_ and needs_conversion(df[k]):
df[k] = df[k].apply(utils.json_dumps_w_dates)
return df
def compile_sqla_query(self, qry: Select, schema: Optional[str] = None) -> str:

View File

@@ -25,6 +25,7 @@ from typing import Any, Dict, List, Optional, Set, Union
# pylint: disable=ungrouped-imports
import humanize
import pandas as pd
import pytz
import sqlalchemy as sa
import yaml
from flask import escape, g, Markup
@@ -365,8 +366,8 @@ class AuditMixinNullable(AuditMixin):
@property
def changed_by_name(self) -> str:
if self.created_by:
return escape("{}".format(self.created_by))
if self.changed_by:
return escape("{}".format(self.changed_by))
return ""
@renders("created_by")
@@ -381,6 +382,15 @@ class AuditMixinNullable(AuditMixin):
def changed_on_(self) -> Markup:
return Markup(f'<span class="no-wrap">{self.changed_on}</span>')
@renders("changed_on")
def changed_on_delta_humanized(self) -> str:
return self.changed_on_humanized
@renders("changed_on")
def changed_on_utc(self) -> str:
# Convert naive datetime to UTC
return self.changed_on.astimezone(pytz.utc).strftime("%Y-%m-%dT%H:%M:%S.%f%z")
@property
def changed_on_humanized(self) -> str:
return humanize.naturaltime(datetime.now() - self.changed_on)

View File

@@ -155,10 +155,12 @@ class Slice(
@property # type: ignore
@utils.memoized
def viz(self) -> BaseViz:
def viz(self) -> Optional[BaseViz]:
form_data = json.loads(self.params)
viz_class = viz_types[self.viz_type]
return viz_class(datasource=self.datasource, form_data=form_data)
viz_class = viz_types.get(self.viz_type)
if viz_class:
return viz_class(datasource=self.datasource, form_data=form_data)
return None
@property
def description_markeddown(self) -> str:
@@ -170,8 +172,9 @@ class Slice(
data: Dict[str, Any] = {}
self.token = ""
try:
data = self.viz.data
self.token = data.get("token") # type: ignore
viz = self.viz
data = viz.data if viz else self.form_data
self.token = utils.get_form_data_token(data)
except Exception as ex: # pylint: disable=broad-except
logger.exception(ex)
data["error"] = str(ex)

View File

@@ -18,15 +18,13 @@ import logging
from io import IOBase
from typing import cast, Union
from flask import current_app
from retry.api import retry
from slack import WebClient
from slack.errors import SlackApiError
from slack.web.slack_response import SlackResponse
from superset import app
# Globals
config = app.config # type: ignore
logger = logging.getLogger("tasks.slack_util")
@@ -34,6 +32,7 @@ logger = logging.getLogger("tasks.slack_util")
def deliver_slack_msg(
slack_channel: str, subject: str, body: str, file: Union[str, IOBase]
) -> None:
config = current_app.config
client = WebClient(token=config["SLACK_API_TOKEN"], proxy=config["SLACK_PROXY"])
# files_upload returns SlackResponse as we run it in sync mode.
response = cast(

Some files were not shown because too many files have changed in this diff Show More