mirror of
https://github.com/apache/superset.git
synced 2026-09-01 21:11:28 +00:00
Compare commits
65
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbb8c87e62 | ||
|
|
2fd965c6b3 | ||
|
|
315acf481f | ||
|
|
1f3a93b2c9 | ||
|
|
465572325b | ||
|
|
98329fbd6c | ||
|
|
c30c3b20d6 | ||
|
|
71fc7cbfcf | ||
|
|
30efcc964d | ||
|
|
698bad187f | ||
|
|
8ecfb18ac7 | ||
|
|
8fb6c8361c | ||
|
|
4d73490fa3 | ||
|
|
fa9a558354 | ||
|
|
421694bfa8 | ||
|
|
091c6ae316 | ||
|
|
25d9fb7544 | ||
|
|
029a70b8b9 | ||
|
|
fc94d74aed | ||
|
|
d06e023d88 | ||
|
|
f3bf6cebb7 | ||
|
|
92b3f88b1a | ||
|
|
820d07d6cf | ||
|
|
3aadbc4fde | ||
|
|
3293ef4b6b | ||
|
|
ebb3307c1d | ||
|
|
3b4882d836 | ||
|
|
bc80029f15 | ||
|
|
fb60d302c2 | ||
|
|
13928fcf2d | ||
|
|
1fb14e81f7 | ||
|
|
ddac4b8838 | ||
|
|
024ef9a9e3 | ||
|
|
9c62eb2a7b | ||
|
|
6a85a341ad | ||
|
|
fa787d2de4 | ||
|
|
a72903cb92 | ||
|
|
0dbc1dbb9a | ||
|
|
6d07273360 | ||
|
|
0f3670e1af | ||
|
|
bde453be79 | ||
|
|
7b6f88a272 | ||
|
|
f908770dbc | ||
|
|
1fc06a7443 | ||
|
|
070dd5359c | ||
|
|
f13629abd5 | ||
|
|
c74acdc381 | ||
|
|
5b4277e4fc | ||
|
|
e2690f0802 | ||
|
|
d9ec47bc63 | ||
|
|
3f284eaa09 | ||
|
|
08d43b1515 | ||
|
|
c57df314d7 | ||
|
|
2f2fb47721 | ||
|
|
9f0d456b6a | ||
|
|
2631b3882e | ||
|
|
791d787256 | ||
|
|
8d0e6676ef | ||
|
|
522ed20a20 | ||
|
|
38bc62db4b | ||
|
|
1bc26797ad | ||
|
|
1217cb05b3 | ||
|
|
6f41b6ef5f | ||
|
|
5693580dae | ||
|
|
727c1b8ce1 |
+460
@@ -18,6 +18,466 @@ under the License.
|
|||||||
-->
|
-->
|
||||||
## Change Log
|
## 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)
|
### 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)
|
- [#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)
|
- [#9428](https://github.com/apache/incubator-superset/pull/9428) [fix]some translation not work better (#9428) (@venter-zhu)
|
||||||
|
|||||||
+24
-18
@@ -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
|
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
|
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`
|
necessary environment variables. Change your current directory to `superset/RELEASING`
|
||||||
|
and execute the `set_release_env.sh` script with the relevant parameters:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# usage (BASH): . set_release_env.sh <SUPERSET_RC_VERSION> <PGP_KEY_FULLNAME>
|
# 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>
|
# 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
|
Set Release env variables
|
||||||
SUPERSET_VERSION=0.36.0
|
SUPERSET_VERSION=0.37.0
|
||||||
SUPERSET_RC=3
|
SUPERSET_RC=1
|
||||||
SUPERSET_GITHUB_BRANCH=0.36
|
SUPERSET_GITHUB_BRANCH=0.37
|
||||||
SUPERSET_PGP_FULLNAME=myid@apache.org
|
SUPERSET_PGP_FULLNAME=myid@apache.org
|
||||||
SUPERSET_VERSION_RC=0.36.0rc3
|
SUPERSET_VERSION_RC=0.37.0rc1
|
||||||
SUPERSET_RELEASE=apache-superset-incubating-0.36.0
|
SUPERSET_RELEASE=apache-superset-incubating-0.37.0
|
||||||
SUPERSET_RELEASE_RC=apache-superset-incubating-0.36.0rc3
|
SUPERSET_RELEASE_RC=apache-superset-incubating-0.37.0rc1
|
||||||
SUPERSET_RELEASE_TARBALL=apache-superset-incubating-0.36.0-source.tar.gz
|
SUPERSET_RELEASE_TARBALL=apache-superset-incubating-0.37.0-source.tar.gz
|
||||||
SUPERSET_RELEASE_RC_TARBALL=apache-superset-incubating-0.36.0rc3-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.36.0
|
SUPERSET_TMP_ASF_SITE_PATH=/tmp/incubator-superset-site-0.37.0
|
||||||
-------------------------------
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Crafting a source release
|
## Crafting a source release
|
||||||
|
|
||||||
When crafting a new minor or major release we create
|
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
|
This new branch will hold all PATCH and release candidates
|
||||||
that belong to the MAJOR.MINOR version.
|
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.
|
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
|
```bash
|
||||||
set your GITHUB_TOKEN environment variable.
|
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
|
```bash
|
||||||
# will overwrites the local CHANGELOG.md, somehow you need to merge it in
|
# 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
|
### Publishing a Convenience Release to PyPI
|
||||||
|
|
||||||
Using the final release tarball, unpack it and run `./pypi_push.sh`.
|
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
|
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
|
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
|
an account first if you don't have one, and reference your username
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ https://github.com/apache/incubator-{{ project_module }}/tree/{{ version_rc }}
|
|||||||
The Change Log for the release:
|
The Change Log for the release:
|
||||||
https://github.com/apache/incubator-{{ project_module }}/blob/{{ version_rc }}/CHANGELOG.md
|
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:
|
public keys are available at:
|
||||||
|
|
||||||
https://www.apache.org/dist/incubator/{{ project_module }}/KEYS
|
https://www.apache.org/dist/incubator/{{ project_module }}/KEYS
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ usage() {
|
|||||||
echo "usage (BASH): . set_release_env.sh <SUPERSET_RC_VERSION> <PGP_KEY_FULLNAME>"
|
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 "usage (ZSH): source set_release_env.sh <SUPERSET_RC_VERSION> <PGP_KEY_FULLNAME>"
|
||||||
echo
|
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
|
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||||
|
|||||||
+7
-1
@@ -21,7 +21,11 @@ under the License.
|
|||||||
This file documents any backwards-incompatible changes in Superset and
|
This file documents any backwards-incompatible changes in Superset and
|
||||||
assists people when migrating to a new version.
|
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).
|
* [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.
|
* [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.
|
* [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.
|
* [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.
|
||||||
|
|||||||
+4
-1
@@ -73,7 +73,10 @@ Superset's Jinja context:
|
|||||||
|
|
||||||
- ``time``: ``time``
|
- ``time``: ``time``
|
||||||
- ``datetime``: ``datetime.datetime``
|
- ``datetime``: ``datetime.datetime``
|
||||||
- ``uuid``: ``uuid``
|
- ``uuid1``: ``uuid1``
|
||||||
|
- ``uuid3``: ``uuid3``
|
||||||
|
- ``uuid4``: ``uuid4``
|
||||||
|
- ``uuid5``: ``uuid5``
|
||||||
- ``random``: ``random``
|
- ``random``: ``random``
|
||||||
- ``relativedelta``: ``dateutil.relativedelta.relativedelta``
|
- ``relativedelta``: ``dateutil.relativedelta.relativedelta``
|
||||||
|
|
||||||
|
|||||||
+14
-5
@@ -1959,7 +1959,9 @@ List of Countries
|
|||||||
Need to add a new Country?
|
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.
|
1. You need shapefiles which contain data of your map.
|
||||||
You can get this file on this site: https://www.diva-gis.org/gdata
|
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.
|
3. You need to convert shapefile to geojson file.
|
||||||
This action can make with ogr2ogr tools: https://www.gdal.org/ogr2ogr.html
|
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 :
|
Example :
|
||||||
|
|
||||||
.. code-block:: javascript
|
.. 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]),
|
].map(s => [s, s]),
|
||||||
description: 'The name of country that Superset should display',
|
description: 'The name of country that Superset should display',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
9. Rebuild the front end from source and restart superset.
|
||||||
|
|||||||
+2
-2
@@ -29,7 +29,7 @@ decorator==4.4.2 # via retry
|
|||||||
defusedxml==0.6.0 # via python3-openid
|
defusedxml==0.6.0 # via python3-openid
|
||||||
dnspython==1.16.0 # via email-validator
|
dnspython==1.16.0 # via email-validator
|
||||||
email-validator==1.1.0 # via flask-appbuilder
|
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-babel==1.0.0 # via flask-appbuilder
|
||||||
flask-caching==1.8.0 # via apache-superset (setup.py)
|
flask-caching==1.8.0 # via apache-superset (setup.py)
|
||||||
flask-compress==1.5.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)
|
selenium==3.141.0 # via apache-superset (setup.py)
|
||||||
simplejson==3.17.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
|
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-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
|
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)
|
sqlparse==0.3.0 # via apache-superset (setup.py)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ combine_as_imports = true
|
|||||||
include_trailing_comma = true
|
include_trailing_comma = true
|
||||||
line_length = 88
|
line_length = 88
|
||||||
known_first_party = superset
|
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
|
multi_line_output = 3
|
||||||
order_by_type = false
|
order_by_type = false
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ setup(
|
|||||||
"cryptography>=2.4.2",
|
"cryptography>=2.4.2",
|
||||||
"dataclasses<0.7",
|
"dataclasses<0.7",
|
||||||
"flask>=1.1.0, <2.0.0",
|
"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-caching",
|
||||||
"flask-compress",
|
"flask-compress",
|
||||||
"flask-talisman",
|
"flask-talisman",
|
||||||
@@ -102,7 +102,7 @@ setup(
|
|||||||
"retry>=0.9.2",
|
"retry>=0.9.2",
|
||||||
"selenium>=3.141.0",
|
"selenium>=3.141.0",
|
||||||
"simplejson>=3.15.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>=1.3.16, <2.0",
|
||||||
"sqlalchemy-utils>=0.36.6,<0.37",
|
"sqlalchemy-utils>=0.36.6,<0.37",
|
||||||
"sqlparse==0.3.0", # PINNED! see https://github.com/andialbrecht/sqlparse/issues/562
|
"sqlparse==0.3.0", # PINNED! see https://github.com/andialbrecht/sqlparse/issues/562
|
||||||
|
|||||||
+19
-16
@@ -45,6 +45,25 @@ describe('Visualization > Line', () => {
|
|||||||
cy.get('.alert-warning').should('not.exist');
|
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', () => {
|
it('should work with adhoc metric', () => {
|
||||||
const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] };
|
const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] };
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
@@ -54,9 +73,7 @@ describe('Visualization > Line', () => {
|
|||||||
it('should work with groupby', () => {
|
it('should work with groupby', () => {
|
||||||
const metrics = ['count'];
|
const metrics = ['count'];
|
||||||
const groupby = ['gender'];
|
const groupby = ['gender'];
|
||||||
|
|
||||||
const formData = { ...LINE_CHART_DEFAULTS, metrics, groupby };
|
const formData = { ...LINE_CHART_DEFAULTS, metrics, groupby };
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
});
|
});
|
||||||
@@ -64,13 +81,11 @@ describe('Visualization > Line', () => {
|
|||||||
it('should work with simple filter', () => {
|
it('should work with simple filter', () => {
|
||||||
const metrics = ['count'];
|
const metrics = ['count'];
|
||||||
const filters = [SIMPLE_FILTER];
|
const filters = [SIMPLE_FILTER];
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
...LINE_CHART_DEFAULTS,
|
...LINE_CHART_DEFAULTS,
|
||||||
metrics,
|
metrics,
|
||||||
adhoc_filters: filters,
|
adhoc_filters: filters,
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
});
|
});
|
||||||
@@ -83,7 +98,6 @@ describe('Visualization > Line', () => {
|
|||||||
groupby: ['name'],
|
groupby: ['name'],
|
||||||
timeseries_limit_metric: NUM_METRIC,
|
timeseries_limit_metric: NUM_METRIC,
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
});
|
});
|
||||||
@@ -97,28 +111,24 @@ describe('Visualization > Line', () => {
|
|||||||
timeseries_limit_metric: NUM_METRIC,
|
timeseries_limit_metric: NUM_METRIC,
|
||||||
order_desc: true,
|
order_desc: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with rolling avg', () => {
|
it('should work with rolling avg', () => {
|
||||||
const metrics = [NUM_METRIC];
|
const metrics = [NUM_METRIC];
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
...LINE_CHART_DEFAULTS,
|
...LINE_CHART_DEFAULTS,
|
||||||
metrics,
|
metrics,
|
||||||
rolling_type: 'mean',
|
rolling_type: 'mean',
|
||||||
rolling_periods: 10,
|
rolling_periods: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with time shift 1 year', () => {
|
it('should work with time shift 1 year', () => {
|
||||||
const metrics = [NUM_METRIC];
|
const metrics = [NUM_METRIC];
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
...LINE_CHART_DEFAULTS,
|
...LINE_CHART_DEFAULTS,
|
||||||
metrics,
|
metrics,
|
||||||
@@ -162,28 +172,24 @@ describe('Visualization > Line', () => {
|
|||||||
|
|
||||||
it('should work with time shift yoy', () => {
|
it('should work with time shift yoy', () => {
|
||||||
const metrics = [NUM_METRIC];
|
const metrics = [NUM_METRIC];
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
...LINE_CHART_DEFAULTS,
|
...LINE_CHART_DEFAULTS,
|
||||||
metrics,
|
metrics,
|
||||||
time_compare: ['1+year'],
|
time_compare: ['1+year'],
|
||||||
comparison_type: 'ratio',
|
comparison_type: 'ratio',
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with time shift percentage change', () => {
|
it('should work with time shift percentage change', () => {
|
||||||
const metrics = [NUM_METRIC];
|
const metrics = [NUM_METRIC];
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
...LINE_CHART_DEFAULTS,
|
...LINE_CHART_DEFAULTS,
|
||||||
metrics,
|
metrics,
|
||||||
time_compare: ['1+year'],
|
time_compare: ['1+year'],
|
||||||
comparison_type: 'percentage',
|
comparison_type: 'percentage',
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
});
|
});
|
||||||
@@ -193,7 +199,6 @@ describe('Visualization > Line', () => {
|
|||||||
...LINE_CHART_DEFAULTS,
|
...LINE_CHART_DEFAULTS,
|
||||||
metrics: ['count'],
|
metrics: ['count'],
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
cy.get('text.nv-legend-text').contains('COUNT(*)');
|
cy.get('text.nv-legend-text').contains('COUNT(*)');
|
||||||
@@ -224,7 +229,6 @@ describe('Visualization > Line', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
cy.get('.slice_container').within(() => {
|
cy.get('.slice_container').within(() => {
|
||||||
@@ -263,7 +267,6 @@ describe('Visualization > Line', () => {
|
|||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||||
cy.get('.slice_container').within(() => {
|
cy.get('.slice_container').within(() => {
|
||||||
cy.get('.nv-event-annotation-layer-0')
|
cy.get('.nv-event-annotation-layer-0')
|
||||||
+32
-11
@@ -29,12 +29,31 @@ import readResponseBlob from '../../../utils/readResponseBlob';
|
|||||||
describe('Visualization > Table', () => {
|
describe('Visualization > Table', () => {
|
||||||
const VIZ_DEFAULTS = { ...FORM_DATA_DEFAULTS, viz_type: '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(() => {
|
beforeEach(() => {
|
||||||
cy.login();
|
cy.login();
|
||||||
cy.server();
|
cy.server();
|
||||||
cy.route('POST', '/superset/explore_json/**').as('getJson');
|
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', () => {
|
it('Format non-numeric metrics correctly', () => {
|
||||||
cy.visitChartByParams({
|
cy.visitChartByParams({
|
||||||
...VIZ_DEFAULTS,
|
...VIZ_DEFAULTS,
|
||||||
@@ -110,7 +129,7 @@ describe('Visualization > Table', () => {
|
|||||||
it('Test table with percent metrics and groupby', () => {
|
it('Test table with percent metrics and groupby', () => {
|
||||||
const formData = {
|
const formData = {
|
||||||
...VIZ_DEFAULTS,
|
...VIZ_DEFAULTS,
|
||||||
percent_metrics: NUM_METRIC,
|
percent_metrics: PERCENT_METRIC,
|
||||||
metrics: [],
|
metrics: [],
|
||||||
groupby: ['name'],
|
groupby: ['name'],
|
||||||
};
|
};
|
||||||
@@ -156,9 +175,20 @@ describe('Visualization > Table', () => {
|
|||||||
metrics: [],
|
metrics: [],
|
||||||
row_limit: 10,
|
row_limit: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
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' });
|
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', () => {
|
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', () => {
|
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 = {
|
const formData = {
|
||||||
...VIZ_DEFAULTS,
|
...VIZ_DEFAULTS,
|
||||||
percent_metrics: PERCENT_METRIC,
|
percent_metrics: PERCENT_METRIC,
|
||||||
|
|||||||
Generated
+114
-73
@@ -6024,9 +6024,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@superset-ui/legacy-plugin-chart-pivot-table": {
|
"@superset-ui/legacy-plugin-chart-pivot-table": {
|
||||||
"version": "0.14.9",
|
"version": "0.14.21",
|
||||||
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.14.9.tgz",
|
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-pivot-table/-/legacy-plugin-chart-pivot-table-0.14.21.tgz",
|
||||||
"integrity": "sha512-czjbOIew4gDSeZ2yUkB3JHBf3FdwrNMj1Q96IpLLYcoJvWsIw8H6wSFXblCwNkoT8tiPVARZ2ltw4Qkjx0xUBg==",
|
"integrity": "sha512-gmj3iu+ibkXwshcSna1V9Tmbh+wBCHi3HKTuy6R9KrB+0585U0dsHro3xe0o14Uamhld6PIeWbZBSl3axXK+SQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3": "^3.5.17",
|
"d3": "^3.5.17",
|
||||||
"datatables.net-bs": "^1.10.15",
|
"datatables.net-bs": "^1.10.15",
|
||||||
@@ -6034,9 +6034,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@superset-ui/legacy-plugin-chart-rose": {
|
"@superset-ui/legacy-plugin-chart-rose": {
|
||||||
"version": "0.14.9",
|
"version": "0.14.14",
|
||||||
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.14.9.tgz",
|
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-rose/-/legacy-plugin-chart-rose-0.14.14.tgz",
|
||||||
"integrity": "sha512-sylNIX2PcICKEF3Xr0VzgivviFRJHLFq6akY0F/Mk3+GUctUGd8+xmSbKzMKLQlH+E9/9OLNqZ+tiODGI8bdQw==",
|
"integrity": "sha512-gln7uwUu6RzpoIs4HYxonUQ4Mn9ZtRl9TLzak1cI+ZznUIZqAlSvtrU39mx0+fQ/b69qpG3Qgq8C2wivCqgL3w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3": "^3.5.17",
|
"d3": "^3.5.17",
|
||||||
"nvd3": "1.8.6",
|
"nvd3": "1.8.6",
|
||||||
@@ -6073,9 +6073,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@superset-ui/legacy-plugin-chart-treemap": {
|
"@superset-ui/legacy-plugin-chart-treemap": {
|
||||||
"version": "0.14.9",
|
"version": "0.14.13",
|
||||||
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.14.9.tgz",
|
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-treemap/-/legacy-plugin-chart-treemap-0.14.13.tgz",
|
||||||
"integrity": "sha512-hHxySbtxK8BdDNR790UtdV676h9OpTO4F99qiW9nKCzCYiSEXPl6mBPrQtyPjX8i4lzwpm9GsB91hIj8zeZUSQ==",
|
"integrity": "sha512-/5nYtjmOO+CyYx+TMOoM8swu7NFmFQbiQFy1EYLF7gNx1DdboOxbAV8pxqlSrpwX5qw+Fz2mDITclCc03I2czA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3-hierarchy": "^1.1.8",
|
"d3-hierarchy": "^1.1.8",
|
||||||
"d3-selection": "^1.4.0",
|
"d3-selection": "^1.4.0",
|
||||||
@@ -6083,9 +6083,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@superset-ui/legacy-plugin-chart-world-map": {
|
"@superset-ui/legacy-plugin-chart-world-map": {
|
||||||
"version": "0.14.9",
|
"version": "0.14.16",
|
||||||
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.14.9.tgz",
|
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-plugin-chart-world-map/-/legacy-plugin-chart-world-map-0.14.16.tgz",
|
||||||
"integrity": "sha512-8QR5tUvA7D5fQOgVON0viTlbYSHpV1sQhW3igvL6uWrF3u4xc+WVIeyn4ggw4601P4UhE4E5EXyLuHHFvWvDXg==",
|
"integrity": "sha512-9vQOk+jRs4UxezXUh6Fg9x2QKRMmTiFxDzndMvq5HEo34mqZvjiYNy4D1HUTCNXtHKeJUfjNp5+uDJqD5q9CYg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"d3": "^3.5.17",
|
"d3": "^3.5.17",
|
||||||
"datamaps": "^0.5.8",
|
"datamaps": "^0.5.8",
|
||||||
@@ -6130,9 +6130,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@superset-ui/legacy-preset-chart-nvd3": {
|
"@superset-ui/legacy-preset-chart-nvd3": {
|
||||||
"version": "0.14.9",
|
"version": "0.14.21",
|
||||||
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.14.9.tgz",
|
"resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-nvd3/-/legacy-preset-chart-nvd3-0.14.21.tgz",
|
||||||
"integrity": "sha512-VUSOxpRXoTU49OofNzPoXTNpr2MzRRMRfbaPlbQQOwtY5/+9dFa2SfbsD0fVDMtWiX2jPBQeIunAhlrtNUhrBg==",
|
"integrity": "sha512-BbsVZnkkAL2a44XFYQtc24VNINGM5JwXAA9HbygdspumYTUu6cpH2nFVPwc06NREUeeN+EV/zF/AVW2O1IJ1tg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@data-ui/xy-chart": "^0.0.84",
|
"@data-ui/xy-chart": "^0.0.84",
|
||||||
"d3": "^3.5.17",
|
"d3": "^3.5.17",
|
||||||
@@ -6158,15 +6158,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@superset-ui/plugin-chart-table": {
|
"@superset-ui/plugin-chart-table": {
|
||||||
"version": "0.14.9",
|
"version": "0.14.16",
|
||||||
"resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.14.9.tgz",
|
"resolved": "https://registry.npmjs.org/@superset-ui/plugin-chart-table/-/plugin-chart-table-0.14.16.tgz",
|
||||||
"integrity": "sha512-A65LdejI4Vy99LLCVT20WrUGNIdp/FQBK2NsRIAMKNyhnZ1rp0R2F5xX0MG5AjxBUDQYlMQY4fptt1oB8V1fqA==",
|
"integrity": "sha512-N95ptdDNQatU2CmznsVbFWGumj9Z59oF694NXZkbqJB/Bp+4OPkvjybjOrlEMmay2zj2sXFHAxO0b3ZKKcCucg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@emotion/core": "^10.0.28",
|
"@emotion/core": "^10.0.28",
|
||||||
"@types/d3-array": "^2.0.0",
|
"@types/d3-array": "^2.0.0",
|
||||||
"@types/match-sorter": "^4.0.0",
|
"@types/match-sorter": "^4.0.0",
|
||||||
"@types/react": "^16.9.35",
|
"@types/react": "^16.9.43",
|
||||||
"@types/react-dom": "^16.9.8",
|
|
||||||
"@types/react-table": "^7.0.19",
|
"@types/react-table": "^7.0.19",
|
||||||
"d3-array": "^2.4.0",
|
"d3-array": "^2.4.0",
|
||||||
"match-sorter": "^4.1.0",
|
"match-sorter": "^4.1.0",
|
||||||
@@ -6337,9 +6336,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@superset-ui/query": {
|
"@superset-ui/query": {
|
||||||
"version": "0.14.9",
|
"version": "0.14.15",
|
||||||
"resolved": "https://registry.npmjs.org/@superset-ui/query/-/query-0.14.9.tgz",
|
"resolved": "https://registry.npmjs.org/@superset-ui/query/-/query-0.14.15.tgz",
|
||||||
"integrity": "sha512-tldCA8YzqG02oCz/mSY5xA2g3AAqdF5f2p755tPimUHCqtn5axnE38AaRDz+0PyKNyStx0JkYkOVgqvX2ggpzA=="
|
"integrity": "sha512-k89EuCkXp3LmbBSm8yYpmykeoJNy1HvMj3jNRwYS0kvV7nNd267oAdXl8UnFzl+htxqwLUIidcXN9vzydB4Whw=="
|
||||||
},
|
},
|
||||||
"@superset-ui/style": {
|
"@superset-ui/style": {
|
||||||
"version": "0.14.9",
|
"version": "0.14.9",
|
||||||
@@ -6369,6 +6368,13 @@
|
|||||||
"@superset-ui/time-format": "0.14.9",
|
"@superset-ui/time-format": "0.14.9",
|
||||||
"@superset-ui/translation": "0.14.9",
|
"@superset-ui/translation": "0.14.9",
|
||||||
"@superset-ui/validator": "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": {
|
"@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",
|
"resolved": "https://registry.npmjs.org/@types/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||||
"integrity": "sha512-mky/O83TXmGY39P1H9YbUpjV6l6voRYlufqfFCvel8l1phuy8HRjdWc1rrPuN53ITBJlbyMSV6z3niOySO5pgQ=="
|
"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": {
|
"@types/glob": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
|
||||||
@@ -7678,21 +7689,10 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/hoist-non-react-statics": {
|
"@types/history": {
|
||||||
"version": "3.3.1",
|
"version": "4.7.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.6.tgz",
|
||||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
"integrity": "sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w=="
|
||||||
"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/istanbul-lib-coverage": {
|
"@types/istanbul-lib-coverage": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
@@ -7794,9 +7794,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
"version": "16.9.38",
|
"version": "16.9.43",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.38.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.43.tgz",
|
||||||
"integrity": "sha512-pHAeZbjjNRa/hxyNuLrvbxhhnKyKNiLC6I5fRF2Zr/t/S6zS41MiyzH4+c+1I9vVfvuRt1VS2Lodjr4ZWnxrdA==",
|
"integrity": "sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^2.2.0"
|
"csstype": "^2.2.0"
|
||||||
@@ -7818,6 +7818,14 @@
|
|||||||
"@types/react": "*"
|
"@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": {
|
"@types/react-json-tree": {
|
||||||
"version": "0.6.11",
|
"version": "0.6.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-json-tree/-/react-json-tree-0.6.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-json-tree/-/react-json-tree-0.6.11.tgz",
|
||||||
@@ -7836,31 +7844,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/react-redux": {
|
"@types/react-redux": {
|
||||||
"version": "7.1.7",
|
"version": "5.0.21",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-5.0.21.tgz",
|
||||||
"integrity": "sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg==",
|
"integrity": "sha512-ewkOW4GjnyXq5L++T31utI8yRmwj8iCIahZohYi1Ef7Xkrw0V/q92ao7x20rm38FKgImDaCCsaRGWfCJmF/Ukg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/hoist-non-react-statics": "^3.3.0",
|
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"hoist-non-react-statics": "^3.3.0",
|
"redux": "^3.6.0"
|
||||||
"redux": "^4.0.0"
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"@types/react-router": {
|
||||||
"react-is": {
|
"version": "5.1.8",
|
||||||
"version": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz",
|
||||||
"integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA=="
|
"integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==",
|
||||||
},
|
"requires": {
|
||||||
"redux": {
|
"@types/history": "*",
|
||||||
"version": "4.0.5",
|
"@types/react": "*"
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
|
}
|
||||||
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
|
},
|
||||||
"dev": true,
|
"@types/react-router-dom": {
|
||||||
"requires": {
|
"version": "5.1.5",
|
||||||
"loose-envify": "^1.4.0",
|
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz",
|
||||||
"symbol-observable": "^1.2.0"
|
"integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==",
|
||||||
}
|
"requires": {
|
||||||
}
|
"@types/history": "*",
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-router": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/react-select": {
|
"@types/react-select": {
|
||||||
@@ -7923,6 +7932,25 @@
|
|||||||
"redux": "^3.6.0"
|
"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": {
|
"@types/rison": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/rison/-/rison-0.0.6.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
|
||||||
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps="
|
"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": {
|
"@types/sizzle": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
|
||||||
@@ -12774,9 +12815,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dompurify": {
|
"dompurify": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.12.tgz",
|
||||||
"integrity": "sha512-S3O0lk6rFJtO01ZTzMollCOGg+WAtCwS3U5E2WSDY/x/sy7q70RjEC4Dmrih5/UqzLLB9XoKJ8KqwBxaNvBu4A=="
|
"integrity": "sha512-Fl8KseK1imyhErHypFPA8qpq9gPzlsJ/EukA6yk9o0gX23p1TzC+rh9LqNg1qvErRTc0UNMYlKxEGSfSh43NDg=="
|
||||||
},
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
@@ -19746,18 +19787,18 @@
|
|||||||
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
|
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
|
||||||
},
|
},
|
||||||
"match-sorter": {
|
"match-sorter": {
|
||||||
"version": "4.1.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-4.2.0.tgz",
|
||||||
"integrity": "sha512-DCzT9JVO2FWVOTfsKqIqVhu/skFa3bK0lQom70j6Co9yKX9bPn2gRtn9BFD9ykkM8F/USjTQeId+nlFfTVvz+w==",
|
"integrity": "sha512-oEvLn8R+a30YZ9l5XdCTkYQuLsOs8frxEqQTAuxoqkQx/qV5pQpx/NqAWvJ5xbYecqfXoF/ZevaIS1+NkbRymg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.9.2",
|
"@babel/runtime": "^7.10.5",
|
||||||
"remove-accents": "0.4.2"
|
"remove-accents": "0.4.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.10.4",
|
"version": "7.10.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz",
|
||||||
"integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==",
|
"integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "superset",
|
"name": "superset",
|
||||||
"version": "0.999.0dev",
|
"version": "0.37.2",
|
||||||
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"directories": {
|
"directories": {
|
||||||
@@ -80,21 +80,21 @@
|
|||||||
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.14.9",
|
"@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-parallel-coordinates": "^0.14.9",
|
||||||
"@superset-ui/legacy-plugin-chart-partition": "^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-pivot-table": "^0.14.21",
|
||||||
"@superset-ui/legacy-plugin-chart-rose": "^0.14.9",
|
"@superset-ui/legacy-plugin-chart-rose": "^0.14.14",
|
||||||
"@superset-ui/legacy-plugin-chart-sankey": "^0.14.9",
|
"@superset-ui/legacy-plugin-chart-sankey": "^0.14.9",
|
||||||
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.14.9",
|
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.14.9",
|
||||||
"@superset-ui/legacy-plugin-chart-sunburst": "^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.13",
|
||||||
"@superset-ui/legacy-plugin-chart-treemap": "^0.14.9",
|
"@superset-ui/legacy-plugin-chart-world-map": "^0.14.16",
|
||||||
"@superset-ui/legacy-plugin-chart-world-map": "^0.14.9",
|
|
||||||
"@superset-ui/legacy-preset-chart-big-number": "^0.14.9",
|
"@superset-ui/legacy-preset-chart-big-number": "^0.14.9",
|
||||||
"@superset-ui/legacy-preset-chart-deckgl": "^0.2.4",
|
"@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/number-format": "^0.14.9",
|
||||||
|
"@superset-ui/plugin-chart-table": "^0.14.16",
|
||||||
"@superset-ui/plugin-chart-word-cloud": "^0.14.9",
|
"@superset-ui/plugin-chart-word-cloud": "^0.14.9",
|
||||||
"@superset-ui/preset-chart-xy": "^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/style": "^0.14.9",
|
||||||
"@superset-ui/superset-ui": "^0.14.9",
|
"@superset-ui/superset-ui": "^0.14.9",
|
||||||
"@superset-ui/time-format": "^0.14.9",
|
"@superset-ui/time-format": "^0.14.9",
|
||||||
@@ -102,15 +102,19 @@
|
|||||||
"@superset-ui/validator": "^0.14.9",
|
"@superset-ui/validator": "^0.14.9",
|
||||||
"@types/classnames": "^2.2.9",
|
"@types/classnames": "^2.2.9",
|
||||||
"@types/enzyme": "^3.10.5",
|
"@types/enzyme": "^3.10.5",
|
||||||
|
"@types/fetch-mock": "^7.3.2",
|
||||||
"@types/react-bootstrap": "^0.32.21",
|
"@types/react-bootstrap": "^0.32.21",
|
||||||
|
"@types/react-gravatar": "^2.6.8",
|
||||||
"@types/react-json-tree": "^0.6.11",
|
"@types/react-json-tree": "^0.6.11",
|
||||||
|
"@types/react-router-dom": "^5.1.5",
|
||||||
"@types/react-select": "^3.0.12",
|
"@types/react-select": "^3.0.12",
|
||||||
"@types/react-virtualized": "^9.21.10",
|
"@types/react-virtualized": "^9.21.10",
|
||||||
"@types/react-window": "^1.8.2",
|
"@types/react-window": "^1.8.2",
|
||||||
"@types/redux-localstorage": "^1.0.8",
|
"@types/redux-localstorage": "^1.0.8",
|
||||||
|
"@types/redux-mock-store": "^1.0.2",
|
||||||
"@types/rison": "0.0.6",
|
"@types/rison": "0.0.6",
|
||||||
|
"@types/sinon": "^9.0.4",
|
||||||
"@vx/responsive": "^0.0.195",
|
"@vx/responsive": "^0.0.195",
|
||||||
"memoize-one": "^5.1.1",
|
|
||||||
"abortcontroller-polyfill": "^1.1.9",
|
"abortcontroller-polyfill": "^1.1.9",
|
||||||
"aphrodite": "^2.3.1",
|
"aphrodite": "^2.3.1",
|
||||||
"array-move": "^2.2.1",
|
"array-move": "^2.2.1",
|
||||||
@@ -134,6 +138,7 @@
|
|||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"lodash-es": "^4.17.14",
|
"lodash-es": "^4.17.14",
|
||||||
"mathjs": "^3.20.2",
|
"mathjs": "^3.20.2",
|
||||||
|
"memoize-one": "^5.1.1",
|
||||||
"moment": "^2.20.1",
|
"moment": "^2.20.1",
|
||||||
"mousetrap": "^1.6.1",
|
"mousetrap": "^1.6.1",
|
||||||
"mustache": "^2.2.1",
|
"mustache": "^2.2.1",
|
||||||
@@ -205,10 +210,10 @@
|
|||||||
"@types/dom-to-image": "^2.6.0",
|
"@types/dom-to-image": "^2.6.0",
|
||||||
"@types/jest": "^26.0.3",
|
"@types/jest": "^26.0.3",
|
||||||
"@types/jquery": "^3.3.32",
|
"@types/jquery": "^3.3.32",
|
||||||
"@types/react": "^16.9.38",
|
"@types/react": "^16.9.43",
|
||||||
"@types/react-dom": "^16.9.8",
|
"@types/react-dom": "^16.9.8",
|
||||||
"@types/react-json-tree": "^0.6.11",
|
"@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-table": "^7.0.19",
|
||||||
"@types/react-ultimate-pagination": "^1.2.0",
|
"@types/react-ultimate-pagination": "^1.2.0",
|
||||||
"@types/yargs": "12 - 15",
|
"@types/yargs": "12 - 15",
|
||||||
|
|||||||
+9
-1
@@ -20,7 +20,7 @@ import React from 'react';
|
|||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import sinon from 'sinon';
|
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 SliceHeader from 'src/dashboard/components/SliceHeader';
|
||||||
import ChartContainer from 'src/chart/ChartContainer';
|
import ChartContainer from 'src/chart/ChartContainer';
|
||||||
|
|
||||||
@@ -44,6 +44,7 @@ describe('Chart', () => {
|
|||||||
slice: {
|
slice: {
|
||||||
...sliceEntities.slices[queryId],
|
...sliceEntities.slices[queryId],
|
||||||
description_markeddown: 'markdown',
|
description_markeddown: 'markdown',
|
||||||
|
owners: [],
|
||||||
},
|
},
|
||||||
sliceName: sliceEntities.slices[queryId].slice_name,
|
sliceName: sliceEntities.slices[queryId].slice_name,
|
||||||
timeout: 60,
|
timeout: 60,
|
||||||
@@ -52,6 +53,13 @@ describe('Chart', () => {
|
|||||||
toggleExpandSlice() {},
|
toggleExpandSlice() {},
|
||||||
addFilter() {},
|
addFilter() {},
|
||||||
logEvent() {},
|
logEvent() {},
|
||||||
|
handleToggleFullSize() {},
|
||||||
|
changeFilter() {},
|
||||||
|
setFocusedFilterField() {},
|
||||||
|
unsetFocusedFilterField() {},
|
||||||
|
addDangerToast() {},
|
||||||
|
componentId: 'test',
|
||||||
|
dashboardId: 111,
|
||||||
editMode: false,
|
editMode: false,
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
supersetCanExplore: false,
|
supersetCanExplore: false,
|
||||||
|
|||||||
@@ -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',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -19,12 +19,14 @@
|
|||||||
/* eslint-disable no-unused-expressions */
|
/* eslint-disable no-unused-expressions */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import Creatable from 'react-select/creatable';
|
import { Select } from 'src/components/Select';
|
||||||
import { getCategoricalSchemeRegistry } from '@superset-ui/color';
|
import { getCategoricalSchemeRegistry } from '@superset-ui/color';
|
||||||
|
|
||||||
import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl';
|
import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl';
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
name: 'color_scheme',
|
||||||
|
label: 'Color Scheme',
|
||||||
options: getCategoricalSchemeRegistry()
|
options: getCategoricalSchemeRegistry()
|
||||||
.keys()
|
.keys()
|
||||||
.map(s => [s, s]),
|
.map(s => [s, s]),
|
||||||
@@ -37,6 +39,6 @@ describe('ColorSchemeControl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders a Creatable', () => {
|
it('renders a Creatable', () => {
|
||||||
expect(wrapper.find(Creatable)).toHaveLength(1);
|
expect(wrapper.find(Select)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -35,6 +35,7 @@ describe('controlUtils', () => {
|
|||||||
columns: ['a', 'b', 'c'],
|
columns: ['a', 'b', 'c'],
|
||||||
metrics: [{ metric_name: 'first' }, { metric_name: 'second' }],
|
metrics: [{ metric_name: 'first' }, { metric_name: 'second' }],
|
||||||
},
|
},
|
||||||
|
controls: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@@ -250,9 +251,10 @@ describe('controlUtils', () => {
|
|||||||
it('should not apply mapStateToProps when initializing', () => {
|
it('should not apply mapStateToProps when initializing', () => {
|
||||||
const control = getControlState('metrics', 'table', {
|
const control = getControlState('metrics', 'table', {
|
||||||
...state,
|
...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);
|
const control = getControlState('metric', 'table', state, null);
|
||||||
expect(control.validationErrors).toEqual(['cannot be empty']);
|
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', () => {
|
describe('queryFields', () => {
|
||||||
|
|||||||
@@ -94,13 +94,7 @@ describe('ExploreResultsButton', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const badCols = wrapper.instance().getInvalidColumns();
|
const badCols = wrapper.instance().getInvalidColumns();
|
||||||
expect(badCols).toEqual([
|
expect(badCols).toEqual(['my_dupe_col__2', '__timestamp', '__TIMESTAMP']);
|
||||||
'COUNT(*)',
|
|
||||||
'1',
|
|
||||||
'123',
|
|
||||||
'CASE WHEN 1=1 THEN 1 ELSE 0 END',
|
|
||||||
'__TIMESTAMP',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const msgWrapper = shallow(wrapper.instance().renderInvalidColumnMessage());
|
const msgWrapper = shallow(wrapper.instance().renderInvalidColumnMessage());
|
||||||
expect(msgWrapper.find('div')).toHaveLength(1);
|
expect(msgWrapper.find('div')).toHaveLength(1);
|
||||||
|
|||||||
+20
-4
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import * as actions from 'src/SqlLab/actions/sqlLab';
|
import * as actions from 'src/SqlLab/actions/sqlLab';
|
||||||
|
import { ColumnKeyTypeType } from 'src/SqlLab/components/ColumnElement';
|
||||||
|
|
||||||
export const mockedActions = sinon.stub({ ...actions });
|
export const mockedActions = sinon.stub({ ...actions });
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ export const table = {
|
|||||||
keys: [
|
keys: [
|
||||||
{
|
{
|
||||||
column_names: ['id'],
|
column_names: ['id'],
|
||||||
type: 'pk',
|
type: 'pk' as ColumnKeyTypeType,
|
||||||
name: null,
|
name: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -84,14 +85,14 @@ export const table = {
|
|||||||
name: 'slices_ibfk_1',
|
name: 'slices_ibfk_1',
|
||||||
referred_columns: ['id'],
|
referred_columns: ['id'],
|
||||||
referred_table: 'datasources',
|
referred_table: 'datasources',
|
||||||
type: 'fk',
|
type: 'fk' as ColumnKeyTypeType,
|
||||||
referred_schema: 'carapal',
|
referred_schema: 'carapal',
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
unique: false,
|
unique: false,
|
||||||
column_names: ['druid_datasource_id'],
|
column_names: ['druid_datasource_id'],
|
||||||
type: 'index',
|
type: 'index' as ColumnKeyTypeType,
|
||||||
name: 'druid_datasource_id',
|
name: 'druid_datasource_id',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -283,7 +284,7 @@ export const queries = [
|
|||||||
export const queryWithBadColumns = {
|
export const queryWithBadColumns = {
|
||||||
...queries[0],
|
...queries[0],
|
||||||
results: {
|
results: {
|
||||||
data: queries[0].results.data,
|
data: queries[0].results?.data,
|
||||||
selected_columns: [
|
selected_columns: [
|
||||||
{
|
{
|
||||||
is_date: true,
|
is_date: true,
|
||||||
@@ -320,6 +321,21 @@ export const queryWithBadColumns = {
|
|||||||
name: '_TIMESTAMP',
|
name: '_TIMESTAMP',
|
||||||
type: '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,
|
is_date: true,
|
||||||
name: '__TIMESTAMP',
|
name: '__TIMESTAMP',
|
||||||
@@ -72,12 +72,16 @@ describe('utils/common', () => {
|
|||||||
});
|
});
|
||||||
it('changes formatting of temporal column', () => {
|
it('changes formatting of temporal column', () => {
|
||||||
const originalData = [
|
const originalData = [
|
||||||
{ __timestamp: 1594285437771, column1: 'lorem' },
|
{ __timestamp: null, column1: 'lorem' },
|
||||||
{ __timestamp: 1594285441675, column1: 'ipsum' },
|
{ __timestamp: 0, column1: 'ipsum' },
|
||||||
|
{ __timestamp: 1594285437771, column1: 'dolor' },
|
||||||
|
{ __timestamp: 1594285441675, column1: 'sit' },
|
||||||
];
|
];
|
||||||
const expectedData = [
|
const expectedData = [
|
||||||
{ __timestamp: '2020-07-09 09:03:57', column1: 'lorem' },
|
{ __timestamp: null, column1: 'lorem' },
|
||||||
{ __timestamp: '2020-07-09 09:04:01', column1: 'ipsum' },
|
{ __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);
|
expect(applyFormattingToTabularData(originalData)).toEqual(expectedData);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ describe('ChartList', () => {
|
|||||||
const callsD = fetchMock.calls(/chart\/\?q/);
|
const callsD = fetchMock.calls(/chart\/\?q/);
|
||||||
expect(callsD).toHaveLength(1);
|
expect(callsD).toHaveLength(1);
|
||||||
expect(callsD[0][0]).toMatchInlineSnapshot(
|
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)"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ const mockDashboards = [...new Array(3)].map((_, i) => ({
|
|||||||
changed_by_url: 'changed_by_url',
|
changed_by_url: 'changed_by_url',
|
||||||
changed_by_fk: 1,
|
changed_by_fk: 1,
|
||||||
published: true,
|
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' }],
|
owners: [{ first_name: 'admin', last_name: 'admin_user' }],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ describe('DashboardList', () => {
|
|||||||
const callsD = fetchMock.calls(/dashboard\/\?q/);
|
const callsD = fetchMock.calls(/dashboard\/\?q/);
|
||||||
expect(callsD).toHaveLength(1);
|
expect(callsD).toHaveLength(1);
|
||||||
expect(callsD[0][0]).toMatchInlineSnapshot(
|
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', () => {
|
it('edits', () => {
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ describe('DashboardTable', () => {
|
|||||||
expect(fetchMock.calls(dashboardsEndpoint)).toHaveLength(1);
|
expect(fetchMock.calls(dashboardsEndpoint)).toHaveLength(1);
|
||||||
// there's a delay between response and updating state, so manually set it
|
// there's a delay between response and updating state, so manually set it
|
||||||
// rather than adding a timeout which could introduce flakiness
|
// rather than adding a timeout which could introduce flakiness
|
||||||
wrapper.setState({ dashaboards: mockDashboards });
|
wrapper.setState({ dashboards: mockDashboards });
|
||||||
expect(wrapper.find(ListView)).toHaveLength(1);
|
expect(wrapper.find(ListView)).toHaveLength(1);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
+12
-2
@@ -23,11 +23,21 @@ import { shallow } from 'enzyme';
|
|||||||
import Welcome from 'src/welcome/Welcome';
|
import Welcome from 'src/welcome/Welcome';
|
||||||
|
|
||||||
describe('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', () => {
|
it('is valid', () => {
|
||||||
expect(React.isValidElement(<Welcome {...mockedProps} />)).toBe(true);
|
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} />);
|
const wrapper = shallow(<Welcome {...mockedProps} />);
|
||||||
expect(wrapper.find(Tab)).toHaveLength(3);
|
expect(wrapper.find(Tab)).toHaveLength(3);
|
||||||
expect(wrapper.find(Panel)).toHaveLength(3);
|
expect(wrapper.find(Panel)).toHaveLength(3);
|
||||||
+16
-7
@@ -36,13 +36,22 @@ const tooltipTitleMap = {
|
|||||||
index: 'Index',
|
index: 'Index',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ColumnElement(props) {
|
export type ColumnKeyTypeType = keyof typeof tooltipTitleMap;
|
||||||
const col = props.column;
|
|
||||||
let name = col.name;
|
interface ColumnElementProps {
|
||||||
|
column: {
|
||||||
|
name: string;
|
||||||
|
keys?: { type: ColumnKeyTypeType }[];
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ColumnElement({ column }: ColumnElementProps) {
|
||||||
|
let name: React.ReactNode = column.name;
|
||||||
let icons;
|
let icons;
|
||||||
if (col.keys && col.keys.length > 0) {
|
if (column.keys && column.keys.length > 0) {
|
||||||
name = <strong>{col.name}</strong>;
|
name = <strong>{column.name}</strong>;
|
||||||
icons = col.keys.map((key, i) => (
|
icons = column.keys.map((key, i) => (
|
||||||
<span key={i} className="ColumnElement">
|
<span key={i} className="ColumnElement">
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
placement="right"
|
placement="right"
|
||||||
@@ -68,7 +77,7 @@ export default function ColumnElement(props) {
|
|||||||
{icons}
|
{icons}
|
||||||
</div>
|
</div>
|
||||||
<div className="pull-right text-muted">
|
<div className="pull-right text-muted">
|
||||||
<small> {col.type}</small>
|
<small> {column.type}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -102,13 +102,12 @@ class ExploreResultsButton extends React.PureComponent {
|
|||||||
.asSeconds();
|
.asSeconds();
|
||||||
}
|
}
|
||||||
getInvalidColumns() {
|
getInvalidColumns() {
|
||||||
const re1 = /^[A-Za-z_]\w*$/; // starts with char or _, then only alphanum
|
const re1 = /__\d+$/; // duplicate column name pattern
|
||||||
const re2 = /__\d+$/; // does not finish with __ and then a number which screams dup col name
|
const re2 = /^__timestamp/i; // reserved temporal column alias
|
||||||
const re3 = /^__/; // is not a reserved column name e.g. __timestamp
|
|
||||||
|
|
||||||
return this.props.query.results.selected_columns
|
return this.props.query.results.selected_columns
|
||||||
.map(col => col.name)
|
.map(col => col.name)
|
||||||
.filter(col => !re1.test(col) || re2.test(col) || re3.test(col));
|
.filter(col => re1.test(col) || re2.test(col));
|
||||||
}
|
}
|
||||||
datasourceName() {
|
datasourceName() {
|
||||||
const { query } = this.props;
|
const { query } = this.props;
|
||||||
@@ -193,16 +192,11 @@ class ExploreResultsButton extends React.PureComponent {
|
|||||||
<code>
|
<code>
|
||||||
<strong>{invalidColumns.join(', ')} </strong>
|
<strong>{invalidColumns.join(', ')} </strong>
|
||||||
</code>
|
</code>
|
||||||
{t('cannot be used as a column name. Please use aliases (as in ')}
|
{t(`cannot be used as a column name. The column name/alias "__timestamp"
|
||||||
<code>
|
is reserved for the main temporal expression, and column aliases ending with
|
||||||
SELECT count(*)
|
double underscores followed by a numeric value (e.g. "my_col__1") are reserved
|
||||||
<strong>AS my_alias</strong>
|
for deduplicating duplicate column names. Please use aliases to rename the
|
||||||
</code>
|
invalid column names.`)}
|
||||||
){' '}
|
|
||||||
{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.
|
|
||||||
`)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ export default class ResultSet extends React.PureComponent<
|
|||||||
this.props.database &&
|
this.props.database &&
|
||||||
this.props.database.allows_virtual_table_explore && (
|
this.props.database.allows_virtual_table_explore && (
|
||||||
<ExploreResultsButton
|
<ExploreResultsButton
|
||||||
|
// @ts-ignore Redux types are difficult to work with, ignoring for now
|
||||||
query={this.props.query}
|
query={this.props.query}
|
||||||
database={this.props.database}
|
database={this.props.database}
|
||||||
actions={this.props.actions}
|
actions={this.props.actions}
|
||||||
@@ -246,6 +247,7 @@ export default class ResultSet extends React.PureComponent<
|
|||||||
{t('Query in a new tab')}
|
{t('Query in a new tab')}
|
||||||
</Button>
|
</Button>
|
||||||
<ExploreCtasResultsButton
|
<ExploreCtasResultsButton
|
||||||
|
// @ts-ignore Redux types are difficult to work with, ignoring for now
|
||||||
table={tempTable}
|
table={tempTable}
|
||||||
schema={tempSchema}
|
schema={tempSchema}
|
||||||
dbId={exploreDBId}
|
dbId={exploreDBId}
|
||||||
|
|||||||
+32
-42
@@ -17,58 +17,48 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import SyntaxHighlighter, {
|
import SyntaxHighlighter, {
|
||||||
registerLanguage,
|
registerLanguage,
|
||||||
|
// @ts-ignore
|
||||||
} from 'react-syntax-highlighter/dist/light';
|
} from 'react-syntax-highlighter/dist/light';
|
||||||
|
// @ts-ignore
|
||||||
import sql from 'react-syntax-highlighter/dist/languages/hljs/sql';
|
import sql from 'react-syntax-highlighter/dist/languages/hljs/sql';
|
||||||
|
// @ts-ignore
|
||||||
import github from 'react-syntax-highlighter/dist/styles/hljs/github';
|
import github from 'react-syntax-highlighter/dist/styles/hljs/github';
|
||||||
|
|
||||||
import { t } from '@superset-ui/translation';
|
|
||||||
|
|
||||||
import Link from '../../components/Link';
|
import Link from '../../components/Link';
|
||||||
import ModalTrigger from '../../components/ModalTrigger';
|
import ModalTrigger from '../../components/ModalTrigger';
|
||||||
|
|
||||||
registerLanguage('sql', sql);
|
registerLanguage('sql', sql);
|
||||||
|
|
||||||
const propTypes = {
|
interface ShowSQLProps {
|
||||||
tooltipText: PropTypes.string,
|
sql: string;
|
||||||
title: PropTypes.string,
|
title: string;
|
||||||
sql: PropTypes.string,
|
tooltipText: 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()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowSQL.propTypes = propTypes;
|
export default function ShowSQL({
|
||||||
ShowSQL.defaultProps = defaultProps;
|
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>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -62,6 +62,8 @@ const propTypes = {
|
|||||||
onQuery: PropTypes.func,
|
onQuery: PropTypes.func,
|
||||||
onFilterMenuOpen: PropTypes.func,
|
onFilterMenuOpen: PropTypes.func,
|
||||||
onFilterMenuClose: PropTypes.func,
|
onFilterMenuClose: PropTypes.func,
|
||||||
|
// id of the last mounted parent tab
|
||||||
|
mountedParent: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const BLANK = {};
|
const BLANK = {};
|
||||||
@@ -143,7 +145,7 @@ class Chart extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<ErrorMessageWithStackTrace
|
<ErrorMessageWithStackTrace
|
||||||
error={queryResponse?.errors?.[0]}
|
error={queryResponse?.errors?.[0]}
|
||||||
message={chartAlert}
|
message={chartAlert || queryResponse?.message}
|
||||||
link={queryResponse ? queryResponse.link : null}
|
link={queryResponse ? queryResponse.link : null}
|
||||||
stackTrace={chartStackTrace}
|
stackTrace={chartStackTrace}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -87,8 +87,7 @@ class ChartRenderer extends React.Component {
|
|||||||
if (resultsReady) {
|
if (resultsReady) {
|
||||||
this.hasQueryResponseChange =
|
this.hasQueryResponseChange =
|
||||||
nextProps.queryResponse !== this.props.queryResponse;
|
nextProps.queryResponse !== this.props.queryResponse;
|
||||||
|
return (
|
||||||
if (
|
|
||||||
this.hasQueryResponseChange ||
|
this.hasQueryResponseChange ||
|
||||||
nextProps.annotationData !== this.props.annotationData ||
|
nextProps.annotationData !== this.props.annotationData ||
|
||||||
nextProps.height !== this.props.height ||
|
nextProps.height !== this.props.height ||
|
||||||
@@ -96,9 +95,7 @@ class ChartRenderer extends React.Component {
|
|||||||
nextProps.triggerRender ||
|
nextProps.triggerRender ||
|
||||||
nextProps.formData.color_scheme !== this.props.formData.color_scheme ||
|
nextProps.formData.color_scheme !== this.props.formData.color_scheme ||
|
||||||
nextProps.cacheBusterProp !== this.props.cacheBusterProp
|
nextProps.cacheBusterProp !== this.props.cacheBusterProp
|
||||||
) {
|
);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,6 +321,14 @@ export function setDirectPathToChild(path) {
|
|||||||
return { type: SET_DIRECT_PATH, 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 const SET_FOCUSED_FILTER_FIELD = 'SET_FOCUSED_FILTER_FIELD';
|
||||||
export function setFocusedFilterField(chartId, column) {
|
export function setFocusedFilterField(chartId, column) {
|
||||||
return { type: SET_FOCUSED_FILTER_FIELD, chartId, column };
|
return { type: SET_FOCUSED_FILTER_FIELD, chartId, column };
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const propTypes = {
|
|||||||
handleComponentDrop: PropTypes.func.isRequired,
|
handleComponentDrop: PropTypes.func.isRequired,
|
||||||
directPathToChild: PropTypes.arrayOf(PropTypes.string),
|
directPathToChild: PropTypes.arrayOf(PropTypes.string),
|
||||||
setDirectPathToChild: PropTypes.func.isRequired,
|
setDirectPathToChild: PropTypes.func.isRequired,
|
||||||
|
setMountedTab: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
@@ -250,6 +251,12 @@ class DashboardBuilder extends React.Component {
|
|||||||
<TabPane
|
<TabPane
|
||||||
key={index === 0 ? DASHBOARD_GRID_ID : id}
|
key={index === 0 ? DASHBOARD_GRID_ID : id}
|
||||||
eventKey={index}
|
eventKey={index}
|
||||||
|
mountOnEnter
|
||||||
|
unmountOnExit={false}
|
||||||
|
onEntering={() => {
|
||||||
|
// Entering current tab, DOM is visible and has dimension
|
||||||
|
this.props.setMountedTab(id);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DashboardGrid
|
<DashboardGrid
|
||||||
gridComponent={dashboardLayout[id]}
|
gridComponent={dashboardLayout[id]}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { exploreChart, exportChart } from '../../../explore/exploreUtils';
|
import { exploreChart, exportChart } from '../../../explore/exploreUtils';
|
||||||
import SliceHeader from '../SliceHeader';
|
import SliceHeader from '../SliceHeader';
|
||||||
@@ -41,6 +42,8 @@ const propTypes = {
|
|||||||
height: PropTypes.number.isRequired,
|
height: PropTypes.number.isRequired,
|
||||||
updateSliceName: PropTypes.func.isRequired,
|
updateSliceName: PropTypes.func.isRequired,
|
||||||
isComponentVisible: PropTypes.bool,
|
isComponentVisible: PropTypes.bool,
|
||||||
|
// last switched tab
|
||||||
|
mountedParent: PropTypes.string,
|
||||||
handleToggleFullSize: PropTypes.func.isRequired,
|
handleToggleFullSize: PropTypes.func.isRequired,
|
||||||
|
|
||||||
// from redux
|
// from redux
|
||||||
@@ -70,6 +73,7 @@ const propTypes = {
|
|||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
isCached: false,
|
isCached: false,
|
||||||
isComponentVisible: true,
|
isComponentVisible: true,
|
||||||
|
mountedParent: 'ROOT',
|
||||||
};
|
};
|
||||||
|
|
||||||
// we use state + shouldComponentUpdate() logic to prevent perf-wrecking
|
// 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:
|
// allow chart update/re-render only if visible:
|
||||||
// under selected tab or no tab layout
|
// under selected tab or no tab layout
|
||||||
if (nextProps.isComponentVisible) {
|
if (nextProps.isComponentVisible) {
|
||||||
|
if (nextProps.mountedParent === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (nextProps.chart.triggerQuery) {
|
if (nextProps.chart.triggerQuery) {
|
||||||
return true;
|
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;
|
return this.props.cacheBusterProp !== nextProps.cacheBusterProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,4 +353,20 @@ class Chart extends React.Component {
|
|||||||
Chart.propTypes = propTypes;
|
Chart.propTypes = propTypes;
|
||||||
Chart.defaultProps = defaultProps;
|
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);
|
||||||
|
|||||||
@@ -47,9 +47,12 @@ const propTypes = {
|
|||||||
renderTabContent: PropTypes.bool, // whether to render tabs + content or just tabs
|
renderTabContent: PropTypes.bool, // whether to render tabs + content or just tabs
|
||||||
editMode: PropTypes.bool.isRequired,
|
editMode: PropTypes.bool.isRequired,
|
||||||
renderHoverMenu: PropTypes.bool,
|
renderHoverMenu: PropTypes.bool,
|
||||||
logEvent: PropTypes.func.isRequired,
|
|
||||||
directPathToChild: PropTypes.arrayOf(PropTypes.string),
|
directPathToChild: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
|
||||||
|
// actions (from DashboardComponent.jsx)
|
||||||
|
logEvent: PropTypes.func.isRequired,
|
||||||
|
setMountedTab: PropTypes.func.isRequired,
|
||||||
|
|
||||||
// grid related
|
// grid related
|
||||||
availableColumnCount: PropTypes.number,
|
availableColumnCount: PropTypes.number,
|
||||||
columnWidth: PropTypes.number,
|
columnWidth: PropTypes.number,
|
||||||
@@ -260,6 +263,12 @@ class Tabs extends React.PureComponent {
|
|||||||
onDeleteTab={this.handleDeleteTab}
|
onDeleteTab={this.handleDeleteTab}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
onEntering={() => {
|
||||||
|
// Entering current tab, DOM is visible and has dimension
|
||||||
|
if (renderTabContent) {
|
||||||
|
this.props.setMountedTab(tabId);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{renderTabContent && (
|
{renderTabContent && (
|
||||||
<DashboardComponent
|
<DashboardComponent
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
setColorSchemeAndUnsavedChanges,
|
setColorSchemeAndUnsavedChanges,
|
||||||
showBuilderPane,
|
showBuilderPane,
|
||||||
setDirectPathToChild,
|
setDirectPathToChild,
|
||||||
|
setMountedTab,
|
||||||
} from '../actions/dashboardState';
|
} from '../actions/dashboardState';
|
||||||
import {
|
import {
|
||||||
deleteTopLevelTabs,
|
deleteTopLevelTabs,
|
||||||
@@ -49,6 +50,7 @@ function mapDispatchToProps(dispatch) {
|
|||||||
showBuilderPane,
|
showBuilderPane,
|
||||||
setColorSchemeAndUnsavedChanges,
|
setColorSchemeAndUnsavedChanges,
|
||||||
setDirectPathToChild,
|
setDirectPathToChild,
|
||||||
|
setMountedTab,
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import {
|
|||||||
updateComponents,
|
updateComponents,
|
||||||
handleComponentDrop,
|
handleComponentDrop,
|
||||||
} from '../actions/dashboardLayout';
|
} from '../actions/dashboardLayout';
|
||||||
import { setDirectPathToChild } from '../actions/dashboardState';
|
import { setDirectPathToChild, setMountedTab } from '../actions/dashboardState';
|
||||||
import { logEvent } from '../../logger/actions';
|
import { logEvent } from '../../logger/actions';
|
||||||
import { addDangerToast } from '../../messageToasts/actions';
|
import { addDangerToast } from '../../messageToasts/actions';
|
||||||
|
|
||||||
@@ -106,6 +106,7 @@ function mapDispatchToProps(dispatch) {
|
|||||||
updateComponents,
|
updateComponents,
|
||||||
handleComponentDrop,
|
handleComponentDrop,
|
||||||
setDirectPathToChild,
|
setDirectPathToChild,
|
||||||
|
setMountedTab,
|
||||||
logEvent,
|
logEvent,
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
UPDATE_CSS,
|
UPDATE_CSS,
|
||||||
SET_REFRESH_FREQUENCY,
|
SET_REFRESH_FREQUENCY,
|
||||||
SET_DIRECT_PATH,
|
SET_DIRECT_PATH,
|
||||||
|
SET_MOUNTED_TAB,
|
||||||
SET_FOCUSED_FILTER_FIELD,
|
SET_FOCUSED_FILTER_FIELD,
|
||||||
} from '../actions/dashboardState';
|
} from '../actions/dashboardState';
|
||||||
import { BUILDER_PANE_TYPE } from '../util/constants';
|
import { BUILDER_PANE_TYPE } from '../util/constants';
|
||||||
@@ -127,10 +128,19 @@ export default function dashboardStateReducer(state = {}, action) {
|
|||||||
[SET_DIRECT_PATH]() {
|
[SET_DIRECT_PATH]() {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
// change of direct path (tabs) will reset current mounted tab
|
||||||
|
mountedTab: null,
|
||||||
directPathToChild: action.path,
|
directPathToChild: action.path,
|
||||||
directPathLastUpdated: Date.now(),
|
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]() {
|
[SET_FOCUSED_FILTER_FIELD]() {
|
||||||
const { focusedFilterField } = state;
|
const { focusedFilterField } = state;
|
||||||
if (action.chartId && action.column) {
|
if (action.chartId && action.column) {
|
||||||
|
|||||||
@@ -19,9 +19,11 @@
|
|||||||
import { DataRecordFilters } from '@superset-ui/chart';
|
import { DataRecordFilters } from '@superset-ui/chart';
|
||||||
|
|
||||||
export default function getEffectiveExtraFilters(filters: DataRecordFilters) {
|
export default function getEffectiveExtraFilters(filters: DataRecordFilters) {
|
||||||
return Object.entries(filters).map(([column, values]) => ({
|
return Object.entries(filters)
|
||||||
col: column,
|
.map(([column, values]) => ({
|
||||||
op: 'in',
|
col: column,
|
||||||
val: values,
|
op: Array.isArray(values) ? 'in' : '==',
|
||||||
}));
|
val: values,
|
||||||
|
}))
|
||||||
|
.filter(filter => filter.val !== null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ setupPlugins();
|
|||||||
const App = ({ store }) => (
|
const App = ({ store }) => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ThemeProvider theme={supersetTheme}>
|
<ThemeProvider theme={supersetTheme}>
|
||||||
<div>
|
<>
|
||||||
<ExploreViewContainer />
|
<ExploreViewContainer />
|
||||||
<ToastPresenter />
|
<ToastPresenter />
|
||||||
</div>
|
</>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -66,15 +66,14 @@ class ControlPanelsContainer extends React.Component {
|
|||||||
renderControl({ name, config }) {
|
renderControl({ name, config }) {
|
||||||
const { actions, controls, exploreState, form_data: formData } = this.props;
|
const { actions, controls, exploreState, form_data: formData } = this.props;
|
||||||
const { visibility } = config;
|
const { visibility } = config;
|
||||||
|
|
||||||
// If the control item is not an object, we have to look up the control data from
|
// If the control item is not an object, we have to look up the control data from
|
||||||
// the centralized controls file.
|
// the centralized controls file.
|
||||||
// When it is an object we read control data straight from `config` instead
|
// When it is an object we read control data straight from `config` instead
|
||||||
const controlData = {
|
const controlData = {
|
||||||
...controls[name],
|
|
||||||
...config,
|
...config,
|
||||||
|
...controls[name],
|
||||||
name,
|
name,
|
||||||
// apply current value in formData
|
|
||||||
value: formData[name],
|
|
||||||
};
|
};
|
||||||
const {
|
const {
|
||||||
validationErrors,
|
validationErrors,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { isFunction } from 'lodash';
|
import { isFunction } from 'lodash';
|
||||||
import { CreatableSelect } from 'src/components/Select';
|
import { Select } from 'src/components/Select';
|
||||||
import ControlHeader from '../ControlHeader';
|
import ControlHeader from '../ControlHeader';
|
||||||
import TooltipWrapper from '../../../components/TooltipWrapper';
|
import TooltipWrapper from '../../../components/TooltipWrapper';
|
||||||
|
|
||||||
@@ -50,7 +50,6 @@ export default class ColorSchemeControl extends React.PureComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
scheme: this.props.value,
|
scheme: this.props.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
this.renderOption = this.renderOption.bind(this);
|
this.renderOption = this.renderOption.bind(this);
|
||||||
}
|
}
|
||||||
@@ -62,9 +61,8 @@ export default class ColorSchemeControl extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderOption(key) {
|
renderOption(key) {
|
||||||
const { isLinear, schemes } = this.props;
|
const { isLinear } = this.props;
|
||||||
const schemeLookup = isFunction(schemes) ? schemes() : schemes;
|
const currentScheme = this.schemes[key.value];
|
||||||
const currentScheme = schemeLookup[key.value || defaultProps.value];
|
|
||||||
|
|
||||||
// For categorical scheme, display all the colors
|
// For categorical scheme, display all the colors
|
||||||
// For sequential scheme, show 10 or interpolate to 10.
|
// For sequential scheme, show 10 or interpolate to 10.
|
||||||
@@ -97,12 +95,16 @@ export default class ColorSchemeControl extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { choices } = this.props;
|
const { schemes, choices } = this.props;
|
||||||
const options = (isFunction(choices) ? choices() : choices).map(choice => ({
|
// save parsed schemes for later
|
||||||
value: choice[0],
|
this.schemes = isFunction(schemes) ? schemes() : schemes;
|
||||||
label: choice[1],
|
const options = (isFunction(choices) ? choices() : choices).map(
|
||||||
}));
|
([value, label]) => ({
|
||||||
|
value,
|
||||||
|
// use scheme label if available
|
||||||
|
label: this.schemes[value]?.label || label,
|
||||||
|
}),
|
||||||
|
);
|
||||||
const selectProps = {
|
const selectProps = {
|
||||||
multi: false,
|
multi: false,
|
||||||
name: `select-${this.props.name}`,
|
name: `select-${this.props.name}`,
|
||||||
@@ -119,7 +121,7 @@ export default class ColorSchemeControl extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ControlHeader {...this.props} />
|
<ControlHeader {...this.props} />
|
||||||
<CreatableSelect {...selectProps} />
|
<Select {...selectProps} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ const DEFAULT_SINCE = moment()
|
|||||||
const DEFAULT_UNTIL = moment().utc().startOf('day').format(MOMENT_FORMAT);
|
const DEFAULT_UNTIL = moment().utc().startOf('day').format(MOMENT_FORMAT);
|
||||||
const SEPARATOR = ' : ';
|
const SEPARATOR = ' : ';
|
||||||
const FREEFORM_TOOLTIP = t(
|
const FREEFORM_TOOLTIP = t(
|
||||||
'Superset supports smart date parsing. Strings like `last sunday` or ' +
|
'Superset supports smart date parsing. Strings like `3 weeks ago`, `last sunday`, or ' +
|
||||||
'`last october` can be used.',
|
'`2 weeks from now` can be used.',
|
||||||
);
|
);
|
||||||
|
|
||||||
const DATE_FILTER_POPOVER_STYLE = { width: '250px' };
|
const DATE_FILTER_POPOVER_STYLE = { width: '250px' };
|
||||||
|
|||||||
@@ -36,18 +36,17 @@ export function getFormDataFromControls(controlsState) {
|
|||||||
|
|
||||||
export function validateControl(control, processedState) {
|
export function validateControl(control, processedState) {
|
||||||
const validators = control.validators;
|
const validators = control.validators;
|
||||||
|
const validationErrors = [];
|
||||||
if (validators && validators.length > 0) {
|
if (validators && validators.length > 0) {
|
||||||
const validatedControl = { ...control };
|
|
||||||
const validationErrors = [];
|
|
||||||
validators.forEach(f => {
|
validators.forEach(f => {
|
||||||
const v = f.call(control, control.value, processedState);
|
const v = f.call(control, control.value, processedState);
|
||||||
if (v) {
|
if (v) {
|
||||||
validationErrors.push(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;
|
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) {
|
function handleMissingChoice(control) {
|
||||||
// If the value is not valid anymore based on choices, clear it
|
// If the value is not valid anymore based on choices, clear it
|
||||||
const value = control.value;
|
const value = control.value;
|
||||||
@@ -121,6 +109,38 @@ function handleMissingChoice(control) {
|
|||||||
return 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(
|
export function getControlStateFromControlConfig(
|
||||||
controlConfig,
|
controlConfig,
|
||||||
controlPanelState,
|
controlPanelState,
|
||||||
@@ -130,38 +150,16 @@ export function getControlStateFromControlConfig(
|
|||||||
if (!controlConfig) {
|
if (!controlConfig) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let controlState = { ...controlConfig };
|
const controlState = { ...controlConfig, value };
|
||||||
// only apply mapStateToProps when control states have been initialized
|
// only apply mapStateToProps when control states have been initialized
|
||||||
|
// or when explicitly didn't provide control panel state (mostly for testing)
|
||||||
if (
|
if (
|
||||||
controlPanelState &&
|
(controlPanelState && controlPanelState.controls) ||
|
||||||
(controlPanelState.controls || !controlPanelState.isInitializing)
|
controlPanelState === null
|
||||||
) {
|
) {
|
||||||
controlState = applyMapStateToPropsToControl(
|
return applyMapStateToPropsToControl(controlState, controlPanelState);
|
||||||
controlState,
|
|
||||||
controlPanelState,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return controlState;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getControlState(controlKey, vizType, state, value) {
|
export function getControlState(controlKey, vizType, state, value) {
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export default function getInitialState(bootstrapData) {
|
|||||||
filterColumnOpts: [],
|
filterColumnOpts: [],
|
||||||
isDatasourceMetaLoading: false,
|
isDatasourceMetaLoading: false,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
isInitializing: true,
|
|
||||||
};
|
};
|
||||||
const controls = getControlsState(bootstrappedState, rawFormData);
|
const controls = getControlsState(bootstrappedState, rawFormData);
|
||||||
bootstrappedState.controls = controls;
|
bootstrappedState.controls = controls;
|
||||||
@@ -55,7 +54,6 @@ export default function getInitialState(bootstrapData) {
|
|||||||
bootstrappedState,
|
bootstrappedState,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
bootstrappedState.isInitializing = false;
|
|
||||||
|
|
||||||
const sliceFormData = slice
|
const sliceFormData = slice
|
||||||
? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))
|
? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
} from '../actions';
|
} from '../actions';
|
||||||
|
|
||||||
// To work properly the redux state must have a `messageToasts` subtree
|
// 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 =>
|
return connect(null, dispatch =>
|
||||||
bindActionCreators(
|
bindActionCreators(
|
||||||
{
|
{
|
||||||
@@ -41,6 +41,6 @@ export default function withToasts(BaseComponent: ComponentType) {
|
|||||||
dispatch,
|
dispatch,
|
||||||
),
|
),
|
||||||
)(BaseComponent) as any;
|
)(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.
|
// If someone can fix the types, great, but for now it's just any.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ setupApp();
|
|||||||
|
|
||||||
const profileViewContainer = document.getElementById('app');
|
const profileViewContainer = document.getElementById('app');
|
||||||
const bootstrap = JSON.parse(
|
const bootstrap = JSON.parse(
|
||||||
profileViewContainer.getAttribute('data-bootstrap'),
|
profileViewContainer?.getAttribute('data-bootstrap') ?? '{}',
|
||||||
);
|
);
|
||||||
|
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
+10
-11
@@ -17,7 +17,6 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Col, Row, Tabs, Tab, Panel } from 'react-bootstrap';
|
import { Col, Row, Tabs, Tab, Panel } from 'react-bootstrap';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
|
|
||||||
@@ -26,17 +25,18 @@ import UserInfo from './UserInfo';
|
|||||||
import Security from './Security';
|
import Security from './Security';
|
||||||
import RecentActivity from './RecentActivity';
|
import RecentActivity from './RecentActivity';
|
||||||
import CreatedContent from './CreatedContent';
|
import CreatedContent from './CreatedContent';
|
||||||
|
import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes';
|
||||||
|
|
||||||
const propTypes = {
|
interface AppProps {
|
||||||
user: PropTypes.object.isRequired,
|
user: UserWithPermissionsAndRoles;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default function App(props) {
|
export default function App({ user }: AppProps) {
|
||||||
return (
|
return (
|
||||||
<div className="container app">
|
<div className="container app">
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={3}>
|
<Col md={3}>
|
||||||
<UserInfo user={props.user} />
|
<UserInfo user={user} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={9}>
|
<Col md={9}>
|
||||||
<Tabs id="options">
|
<Tabs id="options">
|
||||||
@@ -50,7 +50,7 @@ export default function App(props) {
|
|||||||
>
|
>
|
||||||
<Panel>
|
<Panel>
|
||||||
<Panel.Body>
|
<Panel.Body>
|
||||||
<Favorites user={props.user} />
|
<Favorites user={user} />
|
||||||
</Panel.Body>
|
</Panel.Body>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -64,7 +64,7 @@ export default function App(props) {
|
|||||||
>
|
>
|
||||||
<Panel>
|
<Panel>
|
||||||
<Panel.Body>
|
<Panel.Body>
|
||||||
<CreatedContent user={props.user} />
|
<CreatedContent user={user} />
|
||||||
</Panel.Body>
|
</Panel.Body>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -78,7 +78,7 @@ export default function App(props) {
|
|||||||
>
|
>
|
||||||
<Panel>
|
<Panel>
|
||||||
<Panel.Body>
|
<Panel.Body>
|
||||||
<RecentActivity user={props.user} />
|
<RecentActivity user={user} />
|
||||||
</Panel.Body>
|
</Panel.Body>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -92,7 +92,7 @@ export default function App(props) {
|
|||||||
>
|
>
|
||||||
<Panel>
|
<Panel>
|
||||||
<Panel.Body>
|
<Panel.Body>
|
||||||
<Security user={props.user} />
|
<Security user={user} />
|
||||||
</Panel.Body>
|
</Panel.Body>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -102,4 +102,3 @@ export default function App(props) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
App.propTypes = propTypes;
|
|
||||||
+8
-17
@@ -17,28 +17,20 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
|
|
||||||
import TableLoader from '../../components/TableLoader';
|
import TableLoader from '../../components/TableLoader';
|
||||||
|
import { Slice } from '../types';
|
||||||
|
import { User, Dashboard } from '../../types/bootstrapTypes';
|
||||||
|
|
||||||
const propTypes = {
|
interface CreatedContentProps {
|
||||||
user: PropTypes.object.isRequired,
|
user: User;
|
||||||
};
|
}
|
||||||
|
|
||||||
class CreatedContent extends React.PureComponent {
|
class CreatedContent extends React.PureComponent<CreatedContentProps> {
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
dashboardsLoading: true,
|
|
||||||
slicesLoading: true,
|
|
||||||
dashboards: [],
|
|
||||||
slices: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
renderSliceTable() {
|
renderSliceTable() {
|
||||||
const mutator = data =>
|
const mutator = (data: Slice[]) =>
|
||||||
data.map(slice => ({
|
data.map(slice => ({
|
||||||
slice: <a href={slice.url}>{slice.title}</a>,
|
slice: <a href={slice.url}>{slice.title}</a>,
|
||||||
favorited: moment.utc(slice.dttm).fromNow(),
|
favorited: moment.utc(slice.dttm).fromNow(),
|
||||||
@@ -56,7 +48,7 @@ class CreatedContent extends React.PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
renderDashboardTable() {
|
renderDashboardTable() {
|
||||||
const mutator = data =>
|
const mutator = (data: Dashboard[]) =>
|
||||||
data.map(dash => ({
|
data.map(dash => ({
|
||||||
dashboard: <a href={dash.url}>{dash.title}</a>,
|
dashboard: <a href={dash.url}>{dash.title}</a>,
|
||||||
favorited: moment.utc(dash.dttm).fromNow(),
|
favorited: moment.utc(dash.dttm).fromNow(),
|
||||||
@@ -85,6 +77,5 @@ class CreatedContent extends React.PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CreatedContent.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default CreatedContent;
|
export default CreatedContent;
|
||||||
+8
-17
@@ -17,28 +17,20 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
|
|
||||||
import TableLoader from '../../components/TableLoader';
|
import TableLoader from '../../components/TableLoader';
|
||||||
|
import { Slice } from '../types';
|
||||||
|
import { User, Dashboard } from '../../types/bootstrapTypes';
|
||||||
|
|
||||||
const propTypes = {
|
interface FavoritesProps {
|
||||||
user: PropTypes.object.isRequired,
|
user: User;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default class Favorites extends React.PureComponent {
|
export default class Favorites extends React.PureComponent<FavoritesProps> {
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
dashboardsLoading: true,
|
|
||||||
slicesLoading: true,
|
|
||||||
dashboards: [],
|
|
||||||
slices: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
renderSliceTable() {
|
renderSliceTable() {
|
||||||
const mutator = data =>
|
const mutator = (data: Slice[]) =>
|
||||||
data.map(slice => ({
|
data.map(slice => ({
|
||||||
slice: <a href={slice.url}>{slice.title}</a>,
|
slice: <a href={slice.url}>{slice.title}</a>,
|
||||||
creator: <a href={slice.creator_url}>{slice.creator}</a>,
|
creator: <a href={slice.creator_url}>{slice.creator}</a>,
|
||||||
@@ -57,7 +49,7 @@ export default class Favorites extends React.PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
renderDashboardTable() {
|
renderDashboardTable() {
|
||||||
const mutator = data =>
|
const mutator = (data: Dashboard[]) =>
|
||||||
data.map(dash => ({
|
data.map(dash => ({
|
||||||
dashboard: <a href={dash.url}>{dash.title}</a>,
|
dashboard: <a href={dash.url}>{dash.title}</a>,
|
||||||
creator: <a href={dash.creator_url}>{dash.creator}</a>,
|
creator: <a href={dash.creator_url}>{dash.creator}</a>,
|
||||||
@@ -86,4 +78,3 @@ export default class Favorites extends React.PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Favorites.propTypes = propTypes;
|
|
||||||
+27
-30
@@ -17,39 +17,36 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import TableLoader from '../../components/TableLoader';
|
import TableLoader from '../../components/TableLoader';
|
||||||
|
import { Activity } from '../types';
|
||||||
|
import { User } from '../../types/bootstrapTypes';
|
||||||
|
|
||||||
const propTypes = {
|
interface RecentActivityProps {
|
||||||
user: PropTypes.object,
|
user: User;
|
||||||
};
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
+6
-6
@@ -17,14 +17,15 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Badge, Label } from 'react-bootstrap';
|
import { Badge, Label } from 'react-bootstrap';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
|
import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes';
|
||||||
|
|
||||||
const propTypes = {
|
interface SecurityProps {
|
||||||
user: PropTypes.object.isRequired,
|
user: UserWithPermissionsAndRoles;
|
||||||
};
|
}
|
||||||
export default function Security({ user }) {
|
|
||||||
|
export default function Security({ user }: SecurityProps) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="roles">
|
<div className="roles">
|
||||||
@@ -66,4 +67,3 @@ export default function Security({ user }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Security.propTypes = propTypes;
|
|
||||||
@@ -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" />
|
|
||||||
|
|
||||||
<span className="text-muted">{t('id:')}</span>
|
|
||||||
<span className="user-id">{user.userId}</span>
|
|
||||||
</p>
|
|
||||||
</Panel.Body>
|
|
||||||
</Panel>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
UserInfo.propTypes = propTypes;
|
|
||||||
export default UserInfo;
|
|
||||||
@@ -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" />
|
||||||
|
|
||||||
|
<span className="text-muted">{t('id:')}</span>
|
||||||
|
<span className="user-id">{user.userId}</span>
|
||||||
|
</p>
|
||||||
|
</Panel.Body>
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -128,7 +128,11 @@ export function applyFormattingToTabularData(data) {
|
|||||||
}
|
}
|
||||||
return data.map(row => ({
|
return data.map(row => ({
|
||||||
...row,
|
...row,
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
/* eslint-disable no-underscore-dangle */
|
||||||
__timestamp: DATETIME_FORMATTER(new Date(row.__timestamp)),
|
__timestamp:
|
||||||
|
row.__timestamp === 0 || row.__timestamp
|
||||||
|
? DATETIME_FORMATTER(new Date(row.__timestamp))
|
||||||
|
: row.__timestamp,
|
||||||
|
/* eslint-enable no-underscore-dangle */
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
import { SupersetClient } from '@superset-ui/connection';
|
import { SupersetClient } from '@superset-ui/connection';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
import { getChartMetadataRegistry } from '@superset-ui/chart';
|
import { getChartMetadataRegistry } from '@superset-ui/chart';
|
||||||
import moment from 'moment';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
@@ -108,7 +107,7 @@ class ChartList extends React.PureComponent<Props, State> {
|
|||||||
return isFeatureEnabled(FeatureFlag.LIST_VIEWS_SIP34_FILTER_UI);
|
return isFeatureEnabled(FeatureFlag.LIST_VIEWS_SIP34_FILTER_UI);
|
||||||
}
|
}
|
||||||
|
|
||||||
initialSort = [{ id: 'changed_on', desc: true }];
|
initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
@@ -147,17 +146,17 @@ class ChartList extends React.PureComponent<Props, State> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
||||||
Header: t('Creator'),
|
Header: t('Modified By'),
|
||||||
accessor: 'changed_by_fk',
|
accessor: 'changed_by.first_name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
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'),
|
Header: t('Last Modified'),
|
||||||
accessor: 'changed_on',
|
accessor: 'changed_on_delta_humanized',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
import { SupersetClient } from '@superset-ui/connection';
|
import { SupersetClient } from '@superset-ui/connection';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
import moment from 'moment';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
@@ -60,7 +59,7 @@ interface Dashboard {
|
|||||||
changed_by: string;
|
changed_by: string;
|
||||||
changed_by_name: string;
|
changed_by_name: string;
|
||||||
changed_by_url: string;
|
changed_by_url: string;
|
||||||
changed_on: string;
|
changed_on_delta_humanized: string;
|
||||||
dashboard_title: string;
|
dashboard_title: string;
|
||||||
published: boolean;
|
published: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -123,7 +122,7 @@ class DashboardList extends React.PureComponent<Props, State> {
|
|||||||
return isFeatureEnabled(FeatureFlag.LIST_VIEWS_SIP34_FILTER_UI);
|
return isFeatureEnabled(FeatureFlag.LIST_VIEWS_SIP34_FILTER_UI);
|
||||||
}
|
}
|
||||||
|
|
||||||
initialSort = [{ id: 'changed_on', desc: true }];
|
initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
@@ -162,8 +161,8 @@ class DashboardList extends React.PureComponent<Props, State> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
||||||
Header: t('Creator'),
|
Header: t('Modified By'),
|
||||||
accessor: 'changed_by_fk',
|
accessor: 'changed_by.first_name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@@ -181,11 +180,11 @@ class DashboardList extends React.PureComponent<Props, State> {
|
|||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
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'),
|
Header: t('Modified'),
|
||||||
accessor: 'changed_on',
|
accessor: 'changed_on_delta_humanized',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'slug',
|
accessor: 'slug',
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ class FilterBox extends React.Component {
|
|||||||
? selectedValues[key]
|
? selectedValues[key]
|
||||||
: [selectedValues[key]];
|
: [selectedValues[key]];
|
||||||
selectedValuesForKey
|
selectedValuesForKey
|
||||||
.filter(value => !choiceIds.has(value))
|
.filter(value => value !== null && !choiceIds.has(value))
|
||||||
.forEach(value => {
|
.forEach(value => {
|
||||||
choices.unshift({
|
choices.unshift({
|
||||||
filter: key,
|
filter: key,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ setupApp();
|
|||||||
setupPlugins();
|
setupPlugins();
|
||||||
|
|
||||||
const container = document.getElementById('app');
|
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 user = { ...bootstrap.user };
|
||||||
const menu = { ...bootstrap.common.menu_data };
|
const menu = { ...bootstrap.common.menu_data };
|
||||||
const common = { ...bootstrap.common };
|
const common = { ...bootstrap.common };
|
||||||
+45
-16
@@ -17,35 +17,44 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
import { SupersetClient } from '@superset-ui/connection';
|
import { SupersetClient } from '@superset-ui/connection';
|
||||||
import moment from 'moment';
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import ListView from 'src/components/ListView/ListView';
|
import ListView from 'src/components/ListView/ListView';
|
||||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||||
|
import { Dashboard } from 'src/types/bootstrapTypes';
|
||||||
|
import { FetchDataConfig } from 'src/components/ListView/types';
|
||||||
|
|
||||||
const PAGE_SIZE = 25;
|
const PAGE_SIZE = 25;
|
||||||
|
|
||||||
class DashboardTable extends React.PureComponent {
|
interface DashboardTableProps {
|
||||||
static propTypes = {
|
addDangerToast: (message: string) => void;
|
||||||
addDangerToast: PropTypes.func.isRequired,
|
search?: string;
|
||||||
search: PropTypes.string,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
interface DashboardTableState {
|
||||||
|
dashboards: Dashboard[];
|
||||||
|
dashboard_count: number;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DashboardTable extends React.PureComponent<
|
||||||
|
DashboardTableProps,
|
||||||
|
DashboardTableState
|
||||||
|
> {
|
||||||
state = {
|
state = {
|
||||||
dashboards: [],
|
dashboards: [],
|
||||||
dashboard_count: 0,
|
dashboard_count: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps: DashboardTableProps) {
|
||||||
if (prevProps.search !== this.props.search) {
|
if (prevProps.search !== this.props.search) {
|
||||||
this.fetchDataDebounced({
|
this.fetchDataDebounced({
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
sortBy: this.initialSort,
|
sortBy: this.initialSort,
|
||||||
filters: {},
|
filters: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,31 +67,51 @@ class DashboardTable extends React.PureComponent {
|
|||||||
row: {
|
row: {
|
||||||
original: { url, dashboard_title: dashboardTitle },
|
original: { url, dashboard_title: dashboardTitle },
|
||||||
},
|
},
|
||||||
|
}: {
|
||||||
|
row: {
|
||||||
|
original: {
|
||||||
|
url: string;
|
||||||
|
dashboard_title: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
}) => <a href={url}>{dashboardTitle}</a>,
|
}) => <a href={url}>{dashboardTitle}</a>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'changed_by_fk',
|
accessor: 'changed_by.first_name',
|
||||||
Header: 'Creator',
|
Header: 'Modified By',
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
row: {
|
||||||
original: { changed_by_name: changedByName, changedByUrl },
|
original: { changed_by_name: changedByName, changedByUrl },
|
||||||
},
|
},
|
||||||
|
}: {
|
||||||
|
row: {
|
||||||
|
original: {
|
||||||
|
changed_by_name: string;
|
||||||
|
changedByUrl: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
}) => <a href={changedByUrl}>{changedByName}</a>,
|
}) => <a href={changedByUrl}>{changedByName}</a>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'changed_on',
|
accessor: 'changed_on_delta_humanized',
|
||||||
Header: 'Modified',
|
Header: 'Modified',
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
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 });
|
this.setState({ loading: true });
|
||||||
const filterExps = Object.keys(filters)
|
const filterExps = Object.keys(filters)
|
||||||
.map(fk => ({
|
.map(fk => ({
|
||||||
+17
-10
@@ -17,23 +17,30 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
|
import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
|
||||||
import { t } from '@superset-ui/translation';
|
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 RecentActivity from '../profile/components/RecentActivity';
|
||||||
import Favorites from '../profile/components/Favorites';
|
import Favorites from '../profile/components/Favorites';
|
||||||
import DashboardTable from './DashboardTable';
|
import DashboardTable from './DashboardTable';
|
||||||
|
|
||||||
const propTypes = {
|
interface WelcomeProps {
|
||||||
user: PropTypes.object.isRequired,
|
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 [queryState, setQueryState] = useQueryParam(queryParam, queryParamType);
|
||||||
const [state, setState] = useState(queryState || defaultState);
|
const [state, setState] = useState(queryState || defaultState);
|
||||||
|
|
||||||
const setQueryStateAndState = val => {
|
const setQueryStateAndState = (val: string) => {
|
||||||
setQueryState(val);
|
setQueryState(val);
|
||||||
setState(val);
|
setState(val);
|
||||||
};
|
};
|
||||||
@@ -41,7 +48,7 @@ function useSyncQueryState(queryParam, queryParamType, defaultState) {
|
|||||||
return [state, setQueryStateAndState];
|
return [state, setQueryStateAndState];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Welcome({ user }) {
|
export default function Welcome({ user }: WelcomeProps) {
|
||||||
const [activeTab, setActiveTab] = useSyncQueryState(
|
const [activeTab, setActiveTab] = useSyncQueryState(
|
||||||
'activeTab',
|
'activeTab',
|
||||||
StringParam,
|
StringParam,
|
||||||
@@ -58,6 +65,7 @@ export default function Welcome({ user }) {
|
|||||||
<div className="container welcome">
|
<div className="container welcome">
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={activeTab}
|
activeKey={activeTab}
|
||||||
|
// @ts-ignore React bootstrap types aren't quite right here
|
||||||
onSelect={setActiveTab}
|
onSelect={setActiveTab}
|
||||||
id="uncontrolled-tab-example"
|
id="uncontrolled-tab-example"
|
||||||
>
|
>
|
||||||
@@ -75,6 +83,7 @@ export default function Welcome({ user }) {
|
|||||||
style={{ marginTop: '25px' }}
|
style={{ marginTop: '25px' }}
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
|
// @ts-ignore React bootstrap types aren't quite right here
|
||||||
onChange={e => setSearchQuery(e.currentTarget.value)}
|
onChange={e => setSearchQuery(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -114,5 +123,3 @@ export default function Welcome({ user }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Welcome.propTypes = propTypes;
|
|
||||||
@@ -180,9 +180,9 @@ const config = {
|
|||||||
addSlice: addPreamble('/src/addSlice/index.tsx'),
|
addSlice: addPreamble('/src/addSlice/index.tsx'),
|
||||||
explore: addPreamble('/src/explore/index.jsx'),
|
explore: addPreamble('/src/explore/index.jsx'),
|
||||||
dashboard: addPreamble('/src/dashboard/index.jsx'),
|
dashboard: addPreamble('/src/dashboard/index.jsx'),
|
||||||
sqllab: addPreamble('/src/SqlLab/index.jsx'),
|
sqllab: addPreamble('/src/SqlLab/index.tsx'),
|
||||||
welcome: addPreamble('/src/welcome/index.jsx'),
|
welcome: addPreamble('/src/welcome/index.tsx'),
|
||||||
profile: addPreamble('/src/profile/index.jsx'),
|
profile: addPreamble('/src/profile/index.tsx'),
|
||||||
showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')],
|
showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')],
|
||||||
},
|
},
|
||||||
output,
|
output,
|
||||||
|
|||||||
+11
-10
@@ -101,19 +101,17 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
|||||||
"cache_timeout",
|
"cache_timeout",
|
||||||
]
|
]
|
||||||
show_select_columns = show_columns + ["table.id"]
|
show_select_columns = show_columns + ["table.id"]
|
||||||
|
|
||||||
list_columns = [
|
list_columns = [
|
||||||
"id",
|
"id",
|
||||||
"slice_name",
|
"slice_name",
|
||||||
"url",
|
"url",
|
||||||
"description",
|
"description",
|
||||||
"changed_by_fk",
|
|
||||||
"created_by_fk",
|
|
||||||
"changed_by_name",
|
"changed_by_name",
|
||||||
"changed_by_url",
|
"changed_by_url",
|
||||||
"changed_by.first_name",
|
"changed_by.first_name",
|
||||||
"changed_by.last_name",
|
"changed_by.last_name",
|
||||||
"changed_on",
|
"changed_on_utc",
|
||||||
|
"changed_on_delta_humanized",
|
||||||
"datasource_id",
|
"datasource_id",
|
||||||
"datasource_type",
|
"datasource_type",
|
||||||
"datasource_name_text",
|
"datasource_name_text",
|
||||||
@@ -124,13 +122,13 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
|||||||
"params",
|
"params",
|
||||||
"cache_timeout",
|
"cache_timeout",
|
||||||
]
|
]
|
||||||
|
list_select_columns = list_columns + ["changed_on", "changed_by_fk"]
|
||||||
order_columns = [
|
order_columns = [
|
||||||
"slice_name",
|
"slice_name",
|
||||||
"viz_type",
|
"viz_type",
|
||||||
"datasource_name",
|
"datasource_name",
|
||||||
"changed_by_fk",
|
"changed_by.first_name",
|
||||||
"changed_on",
|
"changed_on_delta_humanized",
|
||||||
]
|
]
|
||||||
search_columns = (
|
search_columns = (
|
||||||
"slice_name",
|
"slice_name",
|
||||||
@@ -419,7 +417,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
|||||||
@protect()
|
@protect()
|
||||||
@safe
|
@safe
|
||||||
@statsd_metrics
|
@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
|
Takes a query context constructed in the client and returns payload
|
||||||
data response for the given query.
|
data response for the given query.
|
||||||
@@ -462,13 +460,16 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
|||||||
return self.response_400(message="Request is incorrect")
|
return self.response_400(message="Request is incorrect")
|
||||||
except ValidationError as error:
|
except ValidationError as error:
|
||||||
return self.response_400(
|
return self.response_400(
|
||||||
_("Request is incorrect: %(error)s", error=error.messages)
|
message=_("Request is incorrect: %(error)s", error=error.messages)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
query_context.raise_for_access()
|
query_context.raise_for_access()
|
||||||
except SupersetSecurityException:
|
except SupersetSecurityException:
|
||||||
return self.response_401()
|
return self.response_401()
|
||||||
payload = query_context.get_payload()
|
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
|
result_format = query_context.result_format
|
||||||
if result_format == ChartDataResultFormat.CSV:
|
if result_format == ChartDataResultFormat.CSV:
|
||||||
# return the first result
|
# return the first result
|
||||||
@@ -488,7 +489,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
|||||||
resp.headers["Content-Type"] = "application/json; charset=utf-8"
|
resp.headers["Content-Type"] = "application/json; charset=utf-8"
|
||||||
return resp
|
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"])
|
@expose("/<pk>/cache_screenshot/", methods=["GET"])
|
||||||
@protect()
|
@protect()
|
||||||
|
|||||||
+11
-18
@@ -14,15 +14,15 @@
|
|||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
from typing import Any, Dict, Union
|
from typing import Any, Dict
|
||||||
|
|
||||||
from flask_babel import gettext as _
|
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 marshmallow.validate import Length, Range
|
||||||
|
|
||||||
from superset.common.query_context import QueryContext
|
from superset.common.query_context import QueryContext
|
||||||
from superset.exceptions import SupersetException
|
from superset.utils import schema as utils
|
||||||
from superset.utils import core as utils
|
from superset.utils.core import FilterOperator
|
||||||
|
|
||||||
#
|
#
|
||||||
# RISON/JSON schemas for query parameters
|
# 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):
|
class ChartPostSchema(Schema):
|
||||||
"""
|
"""
|
||||||
Schema to add a new chart.
|
Schema to add a new chart.
|
||||||
@@ -123,7 +116,7 @@ class ChartPostSchema(Schema):
|
|||||||
)
|
)
|
||||||
owners = fields.List(fields.Integer(description=owners_description))
|
owners = fields.List(fields.Integer(description=owners_description))
|
||||||
params = fields.String(
|
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(
|
cache_timeout = fields.Integer(
|
||||||
description=cache_timeout_description, allow_none=True
|
description=cache_timeout_description, allow_none=True
|
||||||
@@ -551,8 +544,8 @@ class ChartDataFilterSchema(Schema):
|
|||||||
)
|
)
|
||||||
op = fields.String( # pylint: disable=invalid-name
|
op = fields.String( # pylint: disable=invalid-name
|
||||||
description="The comparison operator.",
|
description="The comparison operator.",
|
||||||
validate=validate.OneOf(
|
validate=utils.OneOfCaseInsensitive(
|
||||||
choices=[filter_op.value for filter_op in utils.FilterOperator]
|
choices=[filter_op.value for filter_op in FilterOperator]
|
||||||
),
|
),
|
||||||
required=True,
|
required=True,
|
||||||
example="IN",
|
example="IN",
|
||||||
@@ -568,7 +561,7 @@ class ChartDataExtrasSchema(Schema):
|
|||||||
|
|
||||||
time_range_endpoints = fields.List(
|
time_range_endpoints = fields.List(
|
||||||
fields.String(
|
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 "
|
description="A list with two values, stating if start/end should be "
|
||||||
"inclusive/exclusive.",
|
"inclusive/exclusive.",
|
||||||
)
|
)
|
||||||
@@ -687,7 +680,7 @@ class ChartDataQueryObjectSchema(Schema):
|
|||||||
timeseries_limit = fields.Integer(
|
timeseries_limit = fields.Integer(
|
||||||
description="Maximum row count for timeseries queries. Default: `0`",
|
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,
|
description="Metric used to limit timeseries queries by.", allow_none=True,
|
||||||
)
|
)
|
||||||
row_limit = fields.Integer(
|
row_limit = fields.Integer(
|
||||||
@@ -725,10 +718,10 @@ class ChartDataQueryObjectSchema(Schema):
|
|||||||
deprecated=True,
|
deprecated=True,
|
||||||
)
|
)
|
||||||
having_filters = fields.List(
|
having_filters = fields.List(
|
||||||
fields.Dict(),
|
fields.Nested(ChartDataFilterSchema),
|
||||||
description="HAVING filters to be added to legacy Druid datasource queries. "
|
description="HAVING filters to be added to legacy Druid datasource queries. "
|
||||||
"This field is deprecated and should be passed to `extras` "
|
"This field is deprecated and should be passed to `extras` "
|
||||||
"as `filters_druid`.",
|
"as `having_druid`.",
|
||||||
deprecated=True,
|
deprecated=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ from typing import Any, ClassVar, Dict, List, Optional, Union
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from flask_babel import gettext as _
|
||||||
|
|
||||||
from superset import app, cache, db, security_manager
|
from superset import app, cache, db, security_manager
|
||||||
from superset.common.query_object import QueryObject
|
from superset.common.query_object import QueryObject
|
||||||
from superset.connectors.base.models import BaseDatasource
|
from superset.connectors.base.models import BaseDatasource
|
||||||
from superset.connectors.connector_registry import ConnectorRegistry
|
from superset.connectors.connector_registry import ConnectorRegistry
|
||||||
|
from superset.exceptions import QueryObjectValidationError
|
||||||
from superset.stats_logger import BaseStatsLogger
|
from superset.stats_logger import BaseStatsLogger
|
||||||
from superset.utils import core as utils
|
from superset.utils import core as utils
|
||||||
from superset.utils.core import DTTM_ALIAS
|
from superset.utils.core import DTTM_ALIAS
|
||||||
@@ -111,8 +113,7 @@ class QueryContext:
|
|||||||
self.df_metrics_to_num(df, query_object)
|
self.df_metrics_to_num(df, query_object)
|
||||||
|
|
||||||
df.replace([np.inf, -np.inf], np.nan)
|
df.replace([np.inf, -np.inf], np.nan)
|
||||||
|
df = query_object.exec_post_processing(df)
|
||||||
df = query_object.exec_post_processing(df)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"query": result.query,
|
"query": result.query,
|
||||||
@@ -160,10 +161,7 @@ class QueryContext:
|
|||||||
df = payload["df"]
|
df = payload["df"]
|
||||||
status = payload["status"]
|
status = payload["status"]
|
||||||
if status != utils.QueryStatus.FAILED:
|
if status != utils.QueryStatus.FAILED:
|
||||||
if df.empty:
|
payload["data"] = self.get_data(df)
|
||||||
payload["error"] = "No data"
|
|
||||||
else:
|
|
||||||
payload["data"] = self.get_data(df)
|
|
||||||
del payload["df"]
|
del payload["df"]
|
||||||
if self.result_type == utils.ChartDataResultType.RESULTS:
|
if self.result_type == utils.ChartDataResultType.RESULTS:
|
||||||
return {"data": payload["data"]}
|
return {"data": payload["data"]}
|
||||||
@@ -238,6 +236,21 @@ class QueryContext:
|
|||||||
|
|
||||||
if query_obj and not is_loaded:
|
if query_obj and not is_loaded:
|
||||||
try:
|
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)
|
query_result = self.get_query_result(query_obj)
|
||||||
status = query_result["status"]
|
status = query_result["status"]
|
||||||
query = query_result["query"]
|
query = query_result["query"]
|
||||||
@@ -248,10 +261,13 @@ class QueryContext:
|
|||||||
if not self.force:
|
if not self.force:
|
||||||
stats_logger.incr("loaded_from_source_without_force")
|
stats_logger.incr("loaded_from_source_without_force")
|
||||||
is_loaded = True
|
is_loaded = True
|
||||||
|
except QueryObjectValidationError as ex:
|
||||||
|
error_message = str(ex)
|
||||||
|
status = utils.QueryStatus.FAILED
|
||||||
except Exception as ex: # pylint: disable=broad-except
|
except Exception as ex: # pylint: disable=broad-except
|
||||||
logger.exception(ex)
|
logger.exception(ex)
|
||||||
if not error_message:
|
if not error_message:
|
||||||
error_message = "{}".format(ex)
|
error_message = str(ex)
|
||||||
status = utils.QueryStatus.FAILED
|
status = utils.QueryStatus.FAILED
|
||||||
stacktrace = utils.get_stacktrace()
|
stacktrace = utils.get_stacktrace()
|
||||||
|
|
||||||
|
|||||||
@@ -335,6 +335,12 @@ GET_FEATURE_FLAGS_FUNC: Optional[Callable[[Dict[str, bool]], Dict[str, bool]]] =
|
|||||||
THUMBNAIL_SELENIUM_USER = "Admin"
|
THUMBNAIL_SELENIUM_USER = "Admin"
|
||||||
THUMBNAIL_CACHE_CONFIG: CacheConfig = {"CACHE_TYPE": "null"}
|
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
|
# Image and file configuration
|
||||||
# ---------------------------------------------------
|
# ---------------------------------------------------
|
||||||
@@ -840,6 +846,16 @@ TALISMAN_CONFIG = {
|
|||||||
# a custom security config could potentially give access to setting filters on
|
# a custom security config could potentially give access to setting filters on
|
||||||
# tables that users do not have access to.
|
# tables that users do not have access to.
|
||||||
ENABLE_ROW_LEVEL_SECURITY = False
|
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
|
# Flask session cookie options
|
||||||
|
|||||||
@@ -1396,7 +1396,7 @@ class DruidDatasource(Model, BaseDatasource):
|
|||||||
if df is None:
|
if df is None:
|
||||||
df = pd.DataFrame()
|
df = pd.DataFrame()
|
||||||
qry["filter"] = self._add_filter_from_pre_query_data(
|
qry["filter"] = self._add_filter_from_pre_query_data(
|
||||||
df, pre_qry["dimensions"], qry["filter"]
|
df, pre_qry["dimensions"], filters
|
||||||
)
|
)
|
||||||
qry["limit_spec"] = None
|
qry["limit_spec"] = None
|
||||||
if row_limit:
|
if row_limit:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import sqlparse
|
|||||||
from flask import escape, Markup
|
from flask import escape, Markup
|
||||||
from flask_appbuilder import Model
|
from flask_appbuilder import Model
|
||||||
from flask_babel import lazy_gettext as _
|
from flask_babel import lazy_gettext as _
|
||||||
|
from jinja2.exceptions import TemplateError
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
and_,
|
and_,
|
||||||
asc,
|
asc,
|
||||||
@@ -40,7 +41,7 @@ from sqlalchemy import (
|
|||||||
Table,
|
Table,
|
||||||
Text,
|
Text,
|
||||||
)
|
)
|
||||||
from sqlalchemy.exc import CompileError
|
from sqlalchemy.exc import CompileError, SQLAlchemyError
|
||||||
from sqlalchemy.orm import backref, Query, relationship, RelationshipProperty, Session
|
from sqlalchemy.orm import backref, Query, relationship, RelationshipProperty, Session
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
from sqlalchemy.schema import UniqueConstraint
|
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.connectors.base.models import BaseColumn, BaseDatasource, BaseMetric
|
||||||
from superset.constants import NULL_STRING
|
from superset.constants import NULL_STRING
|
||||||
from superset.db_engine_specs.base import TimestampExpression
|
from superset.db_engine_specs.base import TimestampExpression
|
||||||
from superset.exceptions import DatabaseNotFound
|
from superset.exceptions import DatabaseNotFound, QueryObjectValidationError
|
||||||
from superset.jinja_context import (
|
from superset.jinja_context import (
|
||||||
BaseTemplateProcessor,
|
BaseTemplateProcessor,
|
||||||
ExtraCache,
|
ExtraCache,
|
||||||
@@ -89,6 +90,19 @@ class AnnotationDatasource(BaseDatasource):
|
|||||||
cache_timeout = 0
|
cache_timeout = 0
|
||||||
changed_on = None
|
changed_on = None
|
||||||
type = "annotation"
|
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:
|
def query(self, query_obj: QueryObjectDict) -> QueryResult:
|
||||||
error_message = None
|
error_message = None
|
||||||
@@ -217,15 +231,16 @@ class TableColumn(Model, BaseColumn):
|
|||||||
return and_(*l)
|
return and_(*l)
|
||||||
|
|
||||||
def get_timestamp_expression(
|
def get_timestamp_expression(
|
||||||
self, time_grain: Optional[str]
|
self, time_grain: Optional[str], label: Optional[str] = None
|
||||||
) -> Union[TimestampExpression, Label]:
|
) -> Union[TimestampExpression, Label]:
|
||||||
"""
|
"""
|
||||||
Return a SQLAlchemy Core element representation of self to be used in a query.
|
Return a SQLAlchemy Core element representation of self to be used in a query.
|
||||||
|
|
||||||
:param time_grain: Optional time grain, e.g. P1Y
|
: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
|
: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
|
db_ = self.table.database
|
||||||
pdf = self.python_date_format
|
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:
|
if self.fetch_values_predicate:
|
||||||
tp = self.get_template_processor()
|
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()
|
engine = self.database.get_sqla_engine()
|
||||||
sql = "{}".format(qry.compile(engine, compile_kwargs={"literal_binds": True}))
|
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:
|
if self.sql:
|
||||||
from_sql = self.sql
|
from_sql = self.sql
|
||||||
if template_processor:
|
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)
|
from_sql = sqlparse.format(from_sql, strip_comments=True)
|
||||||
return TextAsFrom(sa.text(from_sql), []).alias("expr_qry")
|
return TextAsFrom(sa.text(from_sql), []).alias("expr_qry")
|
||||||
return self.get_sqla_table()
|
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")
|
expression_type = metric.get("expressionType")
|
||||||
label = utils.get_metric_name(metric)
|
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")
|
column_name = metric["column"].get("column_name")
|
||||||
table_column = columns_by_name.get(column_name)
|
table_column = columns_by_name.get(column_name)
|
||||||
if table_column:
|
if table_column:
|
||||||
@@ -706,7 +738,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
|
|||||||
else:
|
else:
|
||||||
sqla_column = column(column_name)
|
sqla_column = column(column_name)
|
||||||
sqla_metric = self.sqla_aggregations[metric["aggregate"]](sqla_column)
|
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"))
|
sqla_metric = literal_column(metric.get("sqlExpression"))
|
||||||
else:
|
else:
|
||||||
return None
|
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.
|
:returns: A list of SQL clauses to be ANDed together.
|
||||||
:rtype: List[str]
|
:rtype: List[str]
|
||||||
"""
|
"""
|
||||||
return [
|
try:
|
||||||
text("({})".format(template_processor.process_template(f.clause)))
|
return [
|
||||||
for f in security_manager.get_rls_filters(self)
|
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
|
def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements
|
||||||
self,
|
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}
|
metrics_by_name: Dict[str, SqlMetric] = {m.metric_name: m for m in self.metrics}
|
||||||
|
|
||||||
if not granularity and is_timeseries:
|
if not granularity and is_timeseries:
|
||||||
raise Exception(
|
raise QueryObjectValidationError(
|
||||||
_(
|
_(
|
||||||
"Datetime column not provided as part table configuration "
|
"Datetime column not provided as part table configuration "
|
||||||
"and is required by this type of chart"
|
"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 not columns
|
||||||
and (is_sip_38 or (not is_sip_38 and not groupby))
|
and (is_sip_38 or (not is_sip_38 and not groupby))
|
||||||
):
|
):
|
||||||
raise Exception(_("Empty query?"))
|
raise QueryObjectValidationError(_("Empty query?"))
|
||||||
metrics_exprs: List[ColumnElement] = []
|
metrics_exprs: List[ColumnElement] = []
|
||||||
for metric in metrics:
|
for metric in metrics:
|
||||||
if utils.is_adhoc_metric(metric):
|
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:
|
elif isinstance(metric, str) and metric in metrics_by_name:
|
||||||
metrics_exprs.append(metrics_by_name[metric].get_sqla_col())
|
metrics_exprs.append(metrics_by_name[metric].get_sqla_col())
|
||||||
else:
|
else:
|
||||||
raise Exception(_("Metric '%(metric)s' does not exist", metric=metric))
|
raise QueryObjectValidationError(
|
||||||
|
_("Metric '%(metric)s' does not exist", metric=metric)
|
||||||
|
)
|
||||||
if metrics_exprs:
|
if metrics_exprs:
|
||||||
main_metric_expr = metrics_exprs[0]
|
main_metric_expr = metrics_exprs[0]
|
||||||
else:
|
else:
|
||||||
@@ -816,6 +855,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
|
|||||||
select_exprs: List[Column] = []
|
select_exprs: List[Column] = []
|
||||||
groupby_exprs_sans_timestamp = OrderedDict()
|
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):
|
if (is_sip_38 and metrics and columns) or (not is_sip_38 and groupby):
|
||||||
# dedup columns while preserving order
|
# dedup columns while preserving order
|
||||||
columns_ = columns if is_sip_38 else groupby
|
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 = []
|
select_exprs = []
|
||||||
for selected in groupby:
|
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()
|
outer = columns_by_name[selected].get_sqla_col()
|
||||||
else:
|
else:
|
||||||
outer = literal_column(f"({selected})")
|
outer = literal_column(f"({selected})")
|
||||||
@@ -841,7 +887,6 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
|
|||||||
)
|
)
|
||||||
metrics_exprs = []
|
metrics_exprs = []
|
||||||
|
|
||||||
assert extras is not None
|
|
||||||
time_range_endpoints = extras.get("time_range_endpoints")
|
time_range_endpoints = extras.get("time_range_endpoints")
|
||||||
groupby_exprs_with_timestamp = OrderedDict(groupby_exprs_sans_timestamp.items())
|
groupby_exprs_with_timestamp = OrderedDict(groupby_exprs_sans_timestamp.items())
|
||||||
if granularity:
|
if granularity:
|
||||||
@@ -947,7 +992,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
|
|||||||
!= None
|
!= None
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise QueryObjectValidationError(
|
||||||
_("Invalid filter operation type: %(op)s", op=op)
|
_("Invalid filter operation type: %(op)s", op=op)
|
||||||
)
|
)
|
||||||
if config["ENABLE_ROW_LEVEL_SECURITY"]:
|
if config["ENABLE_ROW_LEVEL_SECURITY"]:
|
||||||
@@ -955,11 +1000,27 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
|
|||||||
if extras:
|
if extras:
|
||||||
where = extras.get("where")
|
where = extras.get("where")
|
||||||
if 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))]
|
where_clause_and += [sa.text("({})".format(where))]
|
||||||
having = extras.get("having")
|
having = extras.get("having")
|
||||||
if 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))]
|
having_clause_and += [sa.text("({})".format(having))]
|
||||||
if granularity:
|
if granularity:
|
||||||
qry = qry.where(and_(*(time_filters + where_clause_and)))
|
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()
|
ob = metrics_by_name[timeseries_limit_metric].get_sqla_col()
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise QueryObjectValidationError(
|
||||||
_("Metric '%(metric)s' does not exist", metric=timeseries_limit_metric)
|
_("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
|
sql = query_str_ext.sql
|
||||||
status = utils.QueryStatus.SUCCESS
|
status = utils.QueryStatus.SUCCESS
|
||||||
errors = None
|
errors = None
|
||||||
|
error_message = None
|
||||||
|
|
||||||
def mutator(df: pd.DataFrame) -> 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
|
labels_expected = query_str_ext.labels_expected
|
||||||
if df is not None and not df.empty:
|
if df is not None and not df.empty:
|
||||||
if len(df.columns) != len(labels_expected):
|
if len(df.columns) != len(labels_expected):
|
||||||
raise Exception(
|
raise QueryObjectValidationError(
|
||||||
f"For {sql}, df.columns: {df.columns}"
|
f"For {sql}, df.columns: {df.columns}"
|
||||||
f" differs from {labels_expected}"
|
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
|
db_engine_spec = self.database.db_engine_spec
|
||||||
errors = db_engine_spec.extract_errors(ex)
|
errors = db_engine_spec.extract_errors(ex)
|
||||||
|
error_message = utils.error_msg_from_exception(ex)
|
||||||
|
|
||||||
return QueryResult(
|
return QueryResult(
|
||||||
status=status,
|
status=status,
|
||||||
@@ -1170,6 +1233,7 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
|
|||||||
duration=datetime.now() - qry_start_dttm,
|
duration=datetime.now() - qry_start_dttm,
|
||||||
query=sql,
|
query=sql,
|
||||||
errors=errors,
|
errors=errors,
|
||||||
|
error_message=error_message,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_sqla_table_object(self) -> Table:
|
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"""
|
"""Fetches the metadata for the table and merges it in"""
|
||||||
try:
|
try:
|
||||||
table_ = self.get_sqla_table_object()
|
table_ = self.get_sqla_table_object()
|
||||||
except Exception as ex:
|
except SQLAlchemyError:
|
||||||
logger.exception(ex)
|
raise QueryObjectValidationError(
|
||||||
raise Exception(
|
|
||||||
_(
|
_(
|
||||||
"Table [{}] doesn't seem to exist in the specified database, "
|
"Table %(table)s doesn't seem to exist in the specified database, "
|
||||||
"couldn't fetch column information"
|
"couldn't fetch column information",
|
||||||
).format(self.table_name)
|
table=self.table_name,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
metrics = []
|
metrics = []
|
||||||
@@ -1332,6 +1396,10 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
|
|||||||
templatable_statements.append(extras["where"])
|
templatable_statements.append(extras["where"])
|
||||||
if "having" in extras:
|
if "having" in extras:
|
||||||
templatable_statements.append(extras["having"])
|
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:
|
for statement in templatable_statements:
|
||||||
if ExtraCache.regex.search(statement):
|
if ExtraCache.regex.search(statement):
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -263,6 +263,9 @@ class RowLevelSecurityFiltersModelView( # pylint: disable=too-many-ancestors
|
|||||||
"creator": _("Creator"),
|
"creator": _("Creator"),
|
||||||
"modified": _("Modified"),
|
"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
|
class TableModelView( # pylint: disable=too-many-ancestors
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
|||||||
"table_names",
|
"table_names",
|
||||||
"thumbnail_url",
|
"thumbnail_url",
|
||||||
]
|
]
|
||||||
order_columns = ["dashboard_title", "changed_on", "published", "changed_by_fk"]
|
|
||||||
list_columns = [
|
list_columns = [
|
||||||
"id",
|
"id",
|
||||||
"published",
|
"published",
|
||||||
@@ -112,13 +111,22 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
|||||||
"changed_by.id",
|
"changed_by.id",
|
||||||
"changed_by_name",
|
"changed_by_name",
|
||||||
"changed_by_url",
|
"changed_by_url",
|
||||||
"changed_on",
|
"changed_on_utc",
|
||||||
|
"changed_on_delta_humanized",
|
||||||
"dashboard_title",
|
"dashboard_title",
|
||||||
"owners.id",
|
"owners.id",
|
||||||
"owners.username",
|
"owners.username",
|
||||||
"owners.first_name",
|
"owners.first_name",
|
||||||
"owners.last_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 = [
|
add_columns = [
|
||||||
"dashboard_title",
|
"dashboard_title",
|
||||||
"slug",
|
"slug",
|
||||||
|
|||||||
@@ -46,10 +46,27 @@ class DeleteDatasetCommand(BaseCommand):
|
|||||||
def run(self) -> Model:
|
def run(self) -> Model:
|
||||||
self.validate()
|
self.validate()
|
||||||
try:
|
try:
|
||||||
dataset = DatasetDAO.delete(self._model, commit=False)
|
view_menu = (
|
||||||
security_manager.del_permission_view_menu(
|
security_manager.find_view_menu(self._model.get_perm())
|
||||||
"datasource_access", dataset.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()
|
db.session.commit()
|
||||||
except (SQLAlchemyError, DAODeleteFailedError) as ex:
|
except (SQLAlchemyError, DAODeleteFailedError) as ex:
|
||||||
logger.exception(ex)
|
logger.exception(ex)
|
||||||
|
|||||||
@@ -296,7 +296,9 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
|||||||
return select_exprs
|
return select_exprs
|
||||||
|
|
||||||
@classmethod
|
@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
|
:param cursor: Cursor instance
|
||||||
@@ -305,7 +307,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
|||||||
"""
|
"""
|
||||||
if cls.arraysize:
|
if cls.arraysize:
|
||||||
cursor.arraysize = 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.fetchmany(limit)
|
||||||
return cursor.fetchall()
|
return cursor.fetchall()
|
||||||
|
|
||||||
@@ -438,10 +440,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
|||||||
"""
|
"""
|
||||||
kwargs["encoding"] = "utf-8"
|
kwargs["encoding"] = "utf-8"
|
||||||
kwargs["iterator"] = True
|
kwargs["iterator"] = True
|
||||||
chunks = pd.io.excel.read_excel(
|
df = pd.read_excel(**kwargs)
|
||||||
io=kwargs["filepath_or_buffer"], sheet_name=kwargs["sheet_name"]
|
|
||||||
)
|
|
||||||
df = pd.concat(chunk for chunk in chunks.values())
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
@staticmethod
|
@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
|
Create table from contents of a excel. Note: this method does not create
|
||||||
metadata for the table.
|
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)
|
engine = cls.get_engine(database)
|
||||||
if table.schema:
|
if table.schema:
|
||||||
# only add schema when it is preset and non empty
|
# only add schema when it is preset and non empty
|
||||||
|
|||||||
@@ -84,7 +84,9 @@ class BigQueryEngineSpec(BaseEngineSpec):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@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)
|
data = super().fetch_data(cursor, limit)
|
||||||
# Support type BigQuery Row, introduced here PR #4071
|
# Support type BigQuery Row, introduced here PR #4071
|
||||||
# google.cloud.bigquery.table.Row
|
# google.cloud.bigquery.table.Row
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
from typing import Any, List, Tuple
|
from typing import Any, List, Optional, Tuple
|
||||||
|
|
||||||
from superset.db_engine_specs.base import BaseEngineSpec
|
from superset.db_engine_specs.base import BaseEngineSpec
|
||||||
|
|
||||||
@@ -39,7 +39,9 @@ class ExasolEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@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)
|
data = super().fetch_data(cursor, limit)
|
||||||
# Lists of `pyodbc.Row` need to be unpacked further
|
# Lists of `pyodbc.Row` need to be unpacked further
|
||||||
return cls.pyodbc_rows_to_tuples(data)
|
return cls.pyodbc_rows_to_tuples(data)
|
||||||
|
|||||||
@@ -108,7 +108,9 @@ class HiveEngineSpec(PrestoEngineSpec):
|
|||||||
return BaseEngineSpec.get_all_datasource_names(database, datasource_type)
|
return BaseEngineSpec.get_all_datasource_names(database, datasource_type)
|
||||||
|
|
||||||
@classmethod
|
@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
|
import pyhive
|
||||||
from TCLIService import ttypes
|
from TCLIService import ttypes
|
||||||
|
|
||||||
@@ -116,7 +118,7 @@ class HiveEngineSpec(PrestoEngineSpec):
|
|||||||
if state.operationState == ttypes.TOperationState.ERROR_STATE:
|
if state.operationState == ttypes.TOperationState.ERROR_STATE:
|
||||||
raise Exception("Query error", state.errorMessage)
|
raise Exception("Query error", state.errorMessage)
|
||||||
try:
|
try:
|
||||||
return super(HiveEngineSpec, cls).fetch_data(cursor, limit)
|
return super().fetch_data(cursor, limit)
|
||||||
except pyhive.exc.ProgrammingError:
|
except pyhive.exc.ProgrammingError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ class MssqlEngineSpec(BaseEngineSpec):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@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)
|
data = super().fetch_data(cursor, limit)
|
||||||
# Lists of `pyodbc.Row` need to be unpacked further
|
# Lists of `pyodbc.Row` need to be unpacked further
|
||||||
return cls.pyodbc_rows_to_tuples(data)
|
return cls.pyodbc_rows_to_tuples(data)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
from datetime import datetime
|
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.db_engine_specs.base import BaseEngineSpec, LimitMethod
|
||||||
from superset.utils import core as utils
|
from superset.utils import core as utils
|
||||||
@@ -57,3 +57,16 @@ class OracleEngineSpec(BaseEngineSpec):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def epoch_ms_to_dttm(cls) -> str:
|
def epoch_ms_to_dttm(cls) -> str:
|
||||||
return "TO_DATE('1970-01-01','YYYY-MM-DD')+(1/24/60/60/1000)*{col}"
|
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)
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ class PostgresBaseEngineSpec(BaseEngineSpec):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@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
|
cursor.tzinfo_factory = FixedOffsetTimezone
|
||||||
if not cursor.description:
|
if not cursor.description:
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -48,7 +48,10 @@ class JinjaContextManager:
|
|||||||
"relativedelta": relativedelta,
|
"relativedelta": relativedelta,
|
||||||
"time": time,
|
"time": time,
|
||||||
"timedelta": timedelta,
|
"timedelta": timedelta,
|
||||||
"uuid": uuid,
|
"uuid1": uuid.uuid1,
|
||||||
|
"uuid3": uuid.uuid3,
|
||||||
|
"uuid4": uuid.uuid4,
|
||||||
|
"uuid5": uuid.uuid5,
|
||||||
}
|
}
|
||||||
self._template_processors: Dict[str, Type["BaseTemplateProcessor"]] = {}
|
self._template_processors: Dict[str, Type["BaseTemplateProcessor"]] = {}
|
||||||
|
|
||||||
|
|||||||
+18
-18
@@ -213,17 +213,17 @@ class BaseTemplateProcessor: # pylint: disable=too-few-public-methods
|
|||||||
extra_cache_keys: Optional[List[Any]] = None,
|
extra_cache_keys: Optional[List[Any]] = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.database = database
|
self._database = database
|
||||||
self.query = query
|
self._query = query
|
||||||
self.schema = None
|
self._schema = None
|
||||||
if query and query.schema:
|
if query and query.schema:
|
||||||
self.schema = query.schema
|
self._schema = query.schema
|
||||||
elif table:
|
elif table:
|
||||||
self.schema = table.schema
|
self._schema = table.schema
|
||||||
|
|
||||||
extra_cache = ExtraCache(extra_cache_keys)
|
extra_cache = ExtraCache(extra_cache_keys)
|
||||||
|
|
||||||
self.context = {
|
self._context = {
|
||||||
"url_param": extra_cache.url_param,
|
"url_param": extra_cache.url_param,
|
||||||
"current_user_id": extra_cache.current_user_id,
|
"current_user_id": extra_cache.current_user_id,
|
||||||
"current_username": extra_cache.current_username,
|
"current_username": extra_cache.current_username,
|
||||||
@@ -231,11 +231,11 @@ class BaseTemplateProcessor: # pylint: disable=too-few-public-methods
|
|||||||
"filter_values": filter_values,
|
"filter_values": filter_values,
|
||||||
"form_data": {},
|
"form_data": {},
|
||||||
}
|
}
|
||||||
self.context.update(kwargs)
|
self._context.update(kwargs)
|
||||||
self.context.update(jinja_base_context)
|
self._context.update(jinja_base_context)
|
||||||
if self.engine:
|
if self.engine:
|
||||||
self.context[self.engine] = self
|
self._context[self.engine] = self
|
||||||
self.env = SandboxedEnvironment()
|
self._env = SandboxedEnvironment()
|
||||||
|
|
||||||
def process_template(self, sql: str, **kwargs: Any) -> str:
|
def process_template(self, sql: str, **kwargs: Any) -> str:
|
||||||
"""Processes a sql template
|
"""Processes a sql template
|
||||||
@@ -244,8 +244,8 @@ class BaseTemplateProcessor: # pylint: disable=too-few-public-methods
|
|||||||
>>> process_template(sql)
|
>>> process_template(sql)
|
||||||
"SELECT '2017-01-01T00:00:00'"
|
"SELECT '2017-01-01T00:00:00'"
|
||||||
"""
|
"""
|
||||||
template = self.env.from_string(sql)
|
template = self._env.from_string(sql)
|
||||||
kwargs.update(self.context)
|
kwargs.update(self._context)
|
||||||
return template.render(kwargs)
|
return template.render(kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -288,20 +288,20 @@ class PrestoTemplateProcessor(BaseTemplateProcessor):
|
|||||||
|
|
||||||
from superset.db_engine_specs.presto import PrestoEngineSpec
|
from superset.db_engine_specs.presto import PrestoEngineSpec
|
||||||
|
|
||||||
table_name, schema = self._schema_table(table_name, self.schema)
|
table_name, schema = self._schema_table(table_name, self._schema)
|
||||||
return cast(PrestoEngineSpec, self.database.db_engine_spec).latest_partition(
|
return cast(PrestoEngineSpec, self._database.db_engine_spec).latest_partition(
|
||||||
table_name, schema, self.database
|
table_name, schema, self._database
|
||||||
)[1]
|
)[1]
|
||||||
|
|
||||||
def latest_sub_partition(self, table_name: str, **kwargs: Any) -> Any:
|
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
|
from superset.db_engine_specs.presto import PrestoEngineSpec
|
||||||
|
|
||||||
return cast(
|
return cast(
|
||||||
PrestoEngineSpec, self.database.db_engine_spec
|
PrestoEngineSpec, self._database.db_engine_spec
|
||||||
).latest_sub_partition(
|
).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
|
latest_partition = first_latest_partition
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ from superset.db_engine_specs.base import TimeGrain
|
|||||||
from superset.models.dashboard import Dashboard
|
from superset.models.dashboard import Dashboard
|
||||||
from superset.models.helpers import AuditMixinNullable, ImportMixin
|
from superset.models.helpers import AuditMixinNullable, ImportMixin
|
||||||
from superset.models.tags import DashboardUpdater, FavStarUpdater
|
from superset.models.tags import DashboardUpdater, FavStarUpdater
|
||||||
|
from superset.result_set import SupersetResultSet
|
||||||
from superset.utils import cache as cache_util, core as utils
|
from superset.utils import cache as cache_util, core as utils
|
||||||
|
|
||||||
config = app.config
|
config = app.config
|
||||||
@@ -392,21 +393,18 @@ class Database(
|
|||||||
_log_query(sqls[-1])
|
_log_query(sqls[-1])
|
||||||
self.db_engine_spec.execute(cursor, sqls[-1])
|
self.db_engine_spec.execute(cursor, sqls[-1])
|
||||||
|
|
||||||
if cursor.description is not None:
|
data = self.db_engine_spec.fetch_data(cursor)
|
||||||
columns = [col_desc[0] for col_desc in cursor.description]
|
result_set = SupersetResultSet(
|
||||||
else:
|
data, cursor.description, self.db_engine_spec
|
||||||
columns = []
|
|
||||||
|
|
||||||
df = pd.DataFrame.from_records(
|
|
||||||
data=list(cursor.fetchall()), columns=columns, coerce_float=True
|
|
||||||
)
|
)
|
||||||
|
df = result_set.to_pandas_df()
|
||||||
if mutator:
|
if mutator:
|
||||||
mutator(df)
|
mutator(df)
|
||||||
|
|
||||||
for k, v in df.dtypes.items():
|
for k, v in df.dtypes.items():
|
||||||
if v.type == numpy.object_ and needs_conversion(df[k]):
|
if v.type == numpy.object_ and needs_conversion(df[k]):
|
||||||
df[k] = df[k].apply(utils.json_dumps_w_dates)
|
df[k] = df[k].apply(utils.json_dumps_w_dates)
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def compile_sqla_query(self, qry: Select, schema: Optional[str] = None) -> str:
|
def compile_sqla_query(self, qry: Select, schema: Optional[str] = None) -> str:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from typing import Any, Dict, List, Optional, Set, Union
|
|||||||
# pylint: disable=ungrouped-imports
|
# pylint: disable=ungrouped-imports
|
||||||
import humanize
|
import humanize
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import pytz
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import yaml
|
import yaml
|
||||||
from flask import escape, g, Markup
|
from flask import escape, g, Markup
|
||||||
@@ -365,8 +366,8 @@ class AuditMixinNullable(AuditMixin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def changed_by_name(self) -> str:
|
def changed_by_name(self) -> str:
|
||||||
if self.created_by:
|
if self.changed_by:
|
||||||
return escape("{}".format(self.created_by))
|
return escape("{}".format(self.changed_by))
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@renders("created_by")
|
@renders("created_by")
|
||||||
@@ -381,6 +382,15 @@ class AuditMixinNullable(AuditMixin):
|
|||||||
def changed_on_(self) -> Markup:
|
def changed_on_(self) -> Markup:
|
||||||
return Markup(f'<span class="no-wrap">{self.changed_on}</span>')
|
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
|
@property
|
||||||
def changed_on_humanized(self) -> str:
|
def changed_on_humanized(self) -> str:
|
||||||
return humanize.naturaltime(datetime.now() - self.changed_on)
|
return humanize.naturaltime(datetime.now() - self.changed_on)
|
||||||
|
|||||||
@@ -155,10 +155,12 @@ class Slice(
|
|||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
@utils.memoized
|
@utils.memoized
|
||||||
def viz(self) -> BaseViz:
|
def viz(self) -> Optional[BaseViz]:
|
||||||
form_data = json.loads(self.params)
|
form_data = json.loads(self.params)
|
||||||
viz_class = viz_types[self.viz_type]
|
viz_class = viz_types.get(self.viz_type)
|
||||||
return viz_class(datasource=self.datasource, form_data=form_data)
|
if viz_class:
|
||||||
|
return viz_class(datasource=self.datasource, form_data=form_data)
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description_markeddown(self) -> str:
|
def description_markeddown(self) -> str:
|
||||||
@@ -170,8 +172,9 @@ class Slice(
|
|||||||
data: Dict[str, Any] = {}
|
data: Dict[str, Any] = {}
|
||||||
self.token = ""
|
self.token = ""
|
||||||
try:
|
try:
|
||||||
data = self.viz.data
|
viz = self.viz
|
||||||
self.token = data.get("token") # type: ignore
|
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
|
except Exception as ex: # pylint: disable=broad-except
|
||||||
logger.exception(ex)
|
logger.exception(ex)
|
||||||
data["error"] = str(ex)
|
data["error"] = str(ex)
|
||||||
|
|||||||
@@ -18,15 +18,13 @@ import logging
|
|||||||
from io import IOBase
|
from io import IOBase
|
||||||
from typing import cast, Union
|
from typing import cast, Union
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
from retry.api import retry
|
from retry.api import retry
|
||||||
from slack import WebClient
|
from slack import WebClient
|
||||||
from slack.errors import SlackApiError
|
from slack.errors import SlackApiError
|
||||||
from slack.web.slack_response import SlackResponse
|
from slack.web.slack_response import SlackResponse
|
||||||
|
|
||||||
from superset import app
|
|
||||||
|
|
||||||
# Globals
|
# Globals
|
||||||
config = app.config # type: ignore
|
|
||||||
logger = logging.getLogger("tasks.slack_util")
|
logger = logging.getLogger("tasks.slack_util")
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +32,7 @@ logger = logging.getLogger("tasks.slack_util")
|
|||||||
def deliver_slack_msg(
|
def deliver_slack_msg(
|
||||||
slack_channel: str, subject: str, body: str, file: Union[str, IOBase]
|
slack_channel: str, subject: str, body: str, file: Union[str, IOBase]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
config = current_app.config
|
||||||
client = WebClient(token=config["SLACK_API_TOKEN"], proxy=config["SLACK_PROXY"])
|
client = WebClient(token=config["SLACK_API_TOKEN"], proxy=config["SLACK_PROXY"])
|
||||||
# files_upload returns SlackResponse as we run it in sync mode.
|
# files_upload returns SlackResponse as we run it in sync mode.
|
||||||
response = cast(
|
response = cast(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user