Compare commits

...

48 Commits

Author SHA1 Message Date
Michael S. Molina
067495d954 Updates CHANGELOG.md 2022-07-06 13:48:18 -03:00
Reese
bba486b08c fix: Allow dataset owners to explore their datasets (#20382)
* fix: Allow dataset owners to explore their datasets

* Re-order imports

* Give owners security manager permissions to their datasets

* Update test suite

* Add SqlaTable to is_owner types

* Add owners to datasource mock

* Fix VSCode import error

* Fix merge error
2022-07-06 11:43:19 -03:00
Daniel Vaz Gaspar
6e74f3e82c docs: fix link for Apache Superset source code (#20620)
(cherry picked from commit b39a3d8f78)
2022-07-06 11:40:43 -03:00
Daniel Vaz Gaspar
3e97c60c8d chore: bump FAB to 4.1.3 (#20621)
(cherry picked from commit 183871b002)
2022-07-06 11:40:43 -03:00
Rui Zhao
747b011bb7 fix(embedded): Retry when executing alert queries to avoid sending transient errors to users as alert failure notifications (#20419)
Co-authored-by: Rui Zhao <zhaorui@dropbox.com>
(cherry picked from commit 818962cc89)
2022-07-06 11:40:43 -03:00
Evan Rusackas
dc71454416 fix: Respecting max/min opacities, and adding tests. (#20555)
* Respecting max/min opacities, and adding tests.

* revising tests

* Adding missing test case for maximum coverage :)

* removing unnecessary logic and test

* adding another unit test for (hopefully) full coverage.

* no more ternary operator

* New approach with Math.min  - take THAT codecov.

* one more stab at making codecov happy... ignoring the file next.

* lint fixes

(cherry picked from commit ac8e502228)
2022-07-06 11:38:33 -03:00
Evan Rusackas
4dcc805dee Revert "feat(plugin-chart-echarts): Support stacking negative and positive values (#20408)" (#20571)
This reverts commit c959d92dd1.

(cherry picked from commit f5f8ddec3e)
2022-07-06 11:38:33 -03:00
Stephen Liu
3b5513be36 fix(database-modal): forms in database modal will be effected by external form values (#20487)
(cherry picked from commit 932e304ffb)
2022-07-06 11:38:33 -03:00
Stephen Liu
4b2397f0c7 fix(big-number): big number gets cut off on a Dashboard (#20488)
(cherry picked from commit 24a53c38c6)
2022-07-06 11:38:32 -03:00
yourssvk
9a26a211d4 fix: SQL Lab cancel query in Redshift database connection does not wo… (#16326)
* fix: SQL Lab cancel query in Redshift database connection does not work #16325

Co-authored-by: Venkata Krishnan Somasundaram <venkata_cred@Venkatas-MacBook-Pro.local>
Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
(cherry picked from commit 90d486a643)
2022-07-06 11:38:32 -03:00
Diego Medina
eedcefc64a fix: Unable to download the Dashboard as image in case there's an image added through Markdown (#20362)
* fix: Unable to download the Dashboard as image in case there's an image added through Markdown

* licence

(cherry picked from commit c5d3678a31)
2022-07-06 11:38:32 -03:00
Alex Lauderbaugh
eba63b4add Updated copy in chart drop down to "View as table" (#20486)
(cherry picked from commit 93fbfe9d28)
2022-07-06 11:38:32 -03:00
Michael S. Molina
edbbf886af Updates CHANGELOG.md 2022-06-29 09:27:48 -03:00
Michael S. Molina
80e2f1abe7 fix: Removes psycopg2 as a required dependency (#20543)
* fix: Removes psycopg2 as a required dependency

* Disables lint warning

(cherry picked from commit cb3cd41dcd)
2022-06-29 09:25:30 -03:00
Michael S. Molina
b5a4c06d82 Updates CHANGELOG.md, UPDATING.md and package.json 2022-06-28 15:55:58 -03:00
smileydev
789f99341b fix(db): Show the only db install guide when the db is already installed and error is existed while importing file. (#20442)
* fix(db): make to show the db error msg when db installed and error is exist

* fix(db): make to replace dbinstall str into showDbInstallInstructions

(cherry picked from commit 23e62d3782)
2022-06-28 14:22:22 -03:00
Daniel Vaz Gaspar
63229dcf56 fix: bump FAB to 4.1.2 (#20483) 2022-06-28 14:22:12 -03:00
Multazim Deshmukh
92038db579 fix: correction from mmsql to mssql in setup.py (#20493)
Co-authored-by: Multazim Deshmukh <multazim.deshmukh@morningstar.com>
(cherry picked from commit 5a2abfab65)
2022-06-28 14:21:25 -03:00
Yongjie Zhao
b8d9208b6e feat(standardized form data): keep all columns and metrics (#20377)
(cherry picked from commit bbbe102887)
2022-06-28 14:21:25 -03:00
Elizabeth Thompson
885bbdde95 remove autoflush for queries during dual write (#20460)
(cherry picked from commit 44f0b511dd)
2022-06-28 14:21:25 -03:00
John Bodley
25a6f02cd6 fix: Re-add filter-box time granularity/column (#20485)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
(cherry picked from commit 661ab35bd0)
2022-06-28 14:21:25 -03:00
Stephen Liu
bbca109ea3 fix(docs): prevent some symbols from being copied with (#20480)
(cherry picked from commit aa4068048a)
2022-06-28 14:19:23 -03:00
stevetracvc
bcc23bbacd fix: issue with sorting by multiple columns in a table (#19920)
Recent commit to sort alphanumeric columns via case insensitive
comparison broke the multi-column sort option. React-table only sorts
by the second (or third...) column if the first column matches.
Since the alphanumeric sort only returned -1 or 1, it never would move
to the subsequent columns when the earlier column values matched.

(cherry picked from commit a45d011e74)
2022-06-28 14:19:23 -03:00
John Bodley
23061d6822 fix(migration): Ensure key_value LargeBinary is encoded as a MEDIUMBLOB as opposed to BLOB for MySQL (#20385)
* fix(migration): Ensure key_value LargeBinary is encoded as a MEDIUMBLOB as opposed to BLOB for MySQL

* Update 2022-06-14_15-28_e09b4ae78457_resize_key_value_blob.py

Co-authored-by: John Bodley <john.bodley@airbnb.com>
(cherry picked from commit f5cb23e0a3)
2022-06-28 14:19:23 -03:00
Diego Medina
40a9257311 fix: alert & reports active toggle optimistic update (#20402)
(cherry picked from commit 4dc30441b7)
2022-06-28 14:19:23 -03:00
Michael S. Molina
ca0544a573 fix: Changes the return type of get_permissions to be JSON friendly (#20472)
* fix: Changes the return type of get_permissions to be JSON friendly

* Removes dangling comma

* Removes unused import

* Fixes typing errors

(cherry picked from commit a169b60712)
2022-06-28 14:19:23 -03:00
AAfghahi
d789f376b3 async queries limit bug (#20468)
(cherry picked from commit 2c16be42e1)
2022-06-28 14:19:23 -03:00
smileydev
3d850426ff fix(home): Show home page tabs as pills instead of links (#20257)
* fix(home): make to update the css style of links

* fix(home): make to fix lint issue

* fix(home): make to remove underline when tab is active

* fix(homes): make to fix the issue of tab

* fix(home): make to move styles to a tag

(cherry picked from commit a833674a8d)
2022-06-28 14:19:23 -03:00
Beto Dealmeida
4728d8f49b fix: ensure column name in description is string (#20340)
* fix: ensure column name in description is string

* Add unit test

(cherry picked from commit f3b289d3c3)
2022-06-28 14:19:23 -03:00
Diego Medina
9e6a3e1a4e fix(viz): BigQuery time grain 'minute'/'second' throws an error (#20350)
(cherry picked from commit 5afeba34bd)
2022-06-28 14:19:22 -03:00
smileydev
2d551faaf4 fix(chart & table): make to prevent dates from wrapping (#20384)
(cherry picked from commit 1ae935379f)
2022-06-28 14:19:22 -03:00
Yongjie Zhao
f58ad259ac fix: suppress translation warning in jest (#20404)
(cherry picked from commit 9fad26fa19)
2022-06-28 14:19:22 -03:00
Yongjie Zhao
4e93690e19 fix: should raise exception when apply a categorical axis (#20451)
(cherry picked from commit 8bbbd6f03f)
2022-06-28 14:19:22 -03:00
Diego Medina
d1ac6e5db4 fix: table viz sort icon bottom aligned (#20447)
(cherry picked from commit 93774d1860)
2022-06-28 14:19:22 -03:00
Samira El Aabidi
59fbf2a202 feat(chart): Enable caching per user when user impersonation is enabled (#20114)
* add username to extra cache keys when impersonation is enabled.

* don't put effective_user in extra_cache_key

* get_impersonation_key method in engine_spec class  to construct an impersonation key

* pass datasource when creating query objects

* adding an impersonation key when construction cache key

* add feature flag to control caching per user

* revert changes

* make precommit and pylint happy

* pass a User instance

* remove unnecessary import

(cherry picked from commit 68af5980ea)
2022-06-23 09:12:14 -03:00
Sam Firke
4841e8fb9c style(typo): occured -> occurred (#20116)
* Occured -> Occurred

* Occured -> Occurred

* Occured -> Occurred

* Occured - > Occurred

* Update FallbackComponent.tsx

* Update FallbackComponent.tsx

Co-authored-by: John Bodley <4567245+john-bodley@users.noreply.github.com>
(cherry picked from commit b7eb235440)
2022-06-23 09:12:14 -03:00
John Bodley
06592180ea [fbprophet] Fix frequencies (#20326)
(cherry picked from commit 8b0bee5e8b)
2022-06-23 09:12:14 -03:00
Simon Thelin
cb270034f3 fix(20428): Address-Presto/Trino-Poll-Issue-Refactor (#20434)
* fix(20428)-Address-Presto/Trino-Poll-Issue-Refacto
r

Update linter

* Update to only use BaseEngineSpec handle_cursor

* Fix CI

Co-authored-by: John Bodley <4567245+john-bodley@users.noreply.github.com>
(cherry picked from commit 8b7262fa90)
2022-06-23 09:12:14 -03:00
Diego Medina
7e48de484a fix(dashboard): new created chart did not have high lighted effect when using the permalink of chart share in dashboard (#20411)
(cherry picked from commit c2f01a676c)
2022-06-23 09:12:14 -03:00
Lily Kuang
a08499d88d fix(embedded): CSV download for chart (#20261)
* move postForm to superset client

* lint

* fix lint

* fix type

* update tests

* add tests

* add test for form submit

* add test for request form

* lint

* fix test

* fix tests

* more tests

* more tests

* test

* lint

* more test for postForm

* lint

* Update superset-frontend/packages/superset-ui-core/test/connection/SupersetClientClass.test.ts

Co-authored-by: David Aaron Suddjian <1858430+suddjian@users.noreply.github.com>

* update tests

* remove useless test

* make test cover happy

* make test cover happy

* make test cover happy

* make codecov happy

* make codecov happy

Co-authored-by: David Aaron Suddjian <1858430+suddjian@users.noreply.github.com>
(cherry picked from commit ab9f72f1a1)
2022-06-23 09:12:14 -03:00
jiAng
bd721cd86c fix(cosmetic): cannot find m-r-10 class in superset.less (#20276)
* fix(cosmetic): cannot find m-r-10 class in superset.less

* fix: remove .m-r-10 class and use emotion instead

* Update superset-frontend/src/components/Datasource/CollectionTable.tsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

* Update superset-frontend/src/components/Datasource/DatasourceEditor.jsx

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
(cherry picked from commit f6f93aad37)
2022-06-23 09:12:14 -03:00
Stephen Liu
67c853790d fix: rm eslint-plugin-translation-vars engine requirement (#20420)
(cherry picked from commit fa7f144a68)
2022-06-23 09:12:14 -03:00
Stephen Liu
3b3c3be9b2 fix(bar-chart-v2): remove marker from bar chart V2 (#20409)
(cherry picked from commit b32288fddf)
2022-06-23 09:12:13 -03:00
mohittt8
2f07a88c32 fix(presto): use correct timespec for presto (#20333)
(cherry picked from commit 41bbf62e58)
2022-06-23 09:12:13 -03:00
Elizabeth Thompson
128722085c fix key error on permalink fetch for old permalinks (#20414)
(cherry picked from commit 12436e47c9)
2022-06-23 09:12:13 -03:00
Smart-Codi
78c577b515 adding extra metrics after chart configuration (#20410)
(cherry picked from commit a8a6b732e9)
2022-06-23 09:12:13 -03:00
chuancy
9aa047cf12 Chinese translation and English translation do not match (#20405)
(cherry picked from commit 11d94ce56c)
2022-06-23 09:12:13 -03:00
Kamil Gabryjelski
ce9807941b feat(plugin-chart-echarts): Support stacking negative and positive values (#20408)
(cherry picked from commit c959d92dd1)
2022-06-16 09:03:57 -03:00
106 changed files with 2099 additions and 352 deletions

View File

@@ -19,6 +19,500 @@ under the License.
## Change Log
### 2.0 (Tue Jun 28 08:53:02 2022 -0400)
**Database Migrations**
- [#20385](https://github.com/apache/superset/pull/20385) fix(migration): Ensure key_value LargeBinary is encoded as a MEDIUMBLOB as opposed to BLOB for MySQL (@john-bodley)
- [#20284](https://github.com/apache/superset/pull/20284) chore(migrations): Renaming migration files so that they're easier to keep track of (@craig-rueda)
- [#20108](https://github.com/apache/superset/pull/20108) fix: None dataset and schema permissions (@dpgaspar)
- [#18794](https://github.com/apache/superset/pull/18794) feat(business-types): initial implementation of SIP-78 (@cccs-RyanS)
- [#20073](https://github.com/apache/superset/pull/20073) fix(dataset): handle missing sqla uri in migration (@villebro)
- [#19941](https://github.com/apache/superset/pull/19941) fix(reports): Clear last value when state is WORKING (@john-bodley)
- [#19675](https://github.com/apache/superset/pull/19675) chore(docs): Spelling (@jsoref)
- [#19793](https://github.com/apache/superset/pull/19793) fix(SIP-68): handle empty table name during migration (@ktmud)
- [#19786](https://github.com/apache/superset/pull/19786) fix(migrations): coalesce is_temporal when inserting into sl_columns (@cemremengu)
- [#19421](https://github.com/apache/superset/pull/19421) perf: refactor SIP-68 db migrations with INSERT SELECT FROM (@ktmud)
- [#19767](https://github.com/apache/superset/pull/19767) fix: Fix migration for removing time_range_endpoints 3 (@hughhhh)
- [#19728](https://github.com/apache/superset/pull/19728) fix: Removetime_range_endpoints from query context object pt 2 (@hughhhh)
- [#19630](https://github.com/apache/superset/pull/19630) chore: clean up unused imports in db migration scripts (@ktmud)
- [#19577](https://github.com/apache/superset/pull/19577) fix: merge multiple db heads (@eschutho)
- [#19243](https://github.com/apache/superset/pull/19243) fix: cannot delete a database if team member has SQL editor tab that uses that db (@diegomedina248)
- [#19537](https://github.com/apache/superset/pull/19537) chore: block unsafe functions (@betodealmeida)
- [#19513](https://github.com/apache/superset/pull/19513) chore: postpone timerange endpoint removal (@villebro)
- [#19495](https://github.com/apache/superset/pull/19495) perf: speed up db migration for deprecating time_range_endpoints (@ktmud)
- [#19474](https://github.com/apache/superset/pull/19474) fix: handle null params in #18936 migration (@serenajiang)
- [#19423](https://github.com/apache/superset/pull/19423) fix: Remove`time_range_endpoints` from query context object (@hughhhh)
- [#18936](https://github.com/apache/superset/pull/18936) chore: Remove legacy SIP-15 interim logic/flags (@john-bodley)
**Features**
- [#20377](https://github.com/apache/superset/pull/20377) feat(standardized form data): keep all columns and metrics (@zhaoyongjie)
- [#20114](https://github.com/apache/superset/pull/20114) feat(chart): Enable caching per user when user impersonation is enabled (@Samira-El)
- [#20408](https://github.com/apache/superset/pull/20408) feat(plugin-chart-echarts): Support stacking negative and positive values (@kgabryje)
- [#20278](https://github.com/apache/superset/pull/20278) feat: Prevent dataset edit modal closing on click-away in edit mode (@reesercollins)
- [#20392](https://github.com/apache/superset/pull/20392) feat: setting limit value when Pie chart switches (@zhaoyongjie)
- [#20373](https://github.com/apache/superset/pull/20373) feat: adding truncate metric control on timeseries charts (@zhaoyongjie)
- [#20248](https://github.com/apache/superset/pull/20248) feat(explore): Implement viz switcher redesign (@kgabryje)
- [#20113](https://github.com/apache/superset/pull/20113) feat(api): Added "kind" to dataset/<pk> endpoint (@reesercollins)
- [#20299](https://github.com/apache/superset/pull/20299) feat(explore): Dataset Panel Options when Source = Query II (@lyndsiWilliams)
- [#20320](https://github.com/apache/superset/pull/20320) feat: Databricks native driver (@betodealmeida)
- [#20313](https://github.com/apache/superset/pull/20313) feat(explore): Denormalize form data in echarts, world map and nvd3 bar and line charts (@kgabryje)
- [#20277](https://github.com/apache/superset/pull/20277) feat: multiple results pane on explore and dashboard (@zhaoyongjie)
- [#19898](https://github.com/apache/superset/pull/19898) feat: When editing the label/title in the Metrics popover, hitting Enter should save what you've typed (@diegomedina248)
- [#16493](https://github.com/apache/superset/pull/16493) feat(plugin-chart-echarts): [feature-parity] support extra control for the area chart V2 (@stephenLYZ)
- [#19855](https://github.com/apache/superset/pull/19855) feat(explore): Frontend implementation of dataset creation from infobox (@lyndsiWilliams)
- [#20165](https://github.com/apache/superset/pull/20165) feat: add modfied col and timezone info to schedule col (@pkdotson)
- [#20144](https://github.com/apache/superset/pull/20144) feat: showing results pane in dashboard (@zhaoyongjie)
- [#20242](https://github.com/apache/superset/pull/20242) feat: derived metrics use different line style (@zhaoyongjie)
- [#20010](https://github.com/apache/superset/pull/20010) feat: standardized form_data (@zhaoyongjie)
- [#19987](https://github.com/apache/superset/pull/19987) feat(superset-ui-core): add feature flag for the analogous colors (@stephenLYZ)
- [#19881](https://github.com/apache/superset/pull/19881) feat(world-map): support color by metric or country column (@stephenLYZ)
- [#19981](https://github.com/apache/superset/pull/19981) feat!: pass datasource_type and datasource_id to form_data (@eschutho)
- [#15241](https://github.com/apache/superset/pull/15241) feat: query datasets from SQL Lab (@betodealmeida)
- [#20129](https://github.com/apache/superset/pull/20129) feat(explore): Fill dashboard name when adding new chart from dashboard view (@kgabryje)
- [#20160](https://github.com/apache/superset/pull/20160) feat(explore): Add empty state to annotations (@kgabryje)
- [#20134](https://github.com/apache/superset/pull/20134) feat: add Query.columns for bootstrap_data (@hughhhh)
- [#20158](https://github.com/apache/superset/pull/20158) feat: add statsd metrics for notifications (@dpgaspar)
- [#20052](https://github.com/apache/superset/pull/20052) feat(Helm Chart): Support resource limits and requests for each component (@rathberm)
- [#20170](https://github.com/apache/superset/pull/20170) feat: add samples endpoint (@zhaoyongjie)
- [#19381](https://github.com/apache/superset/pull/19381) feat: add drag and drop column rearrangement for table viz (@stevetracvc)
- [#20136](https://github.com/apache/superset/pull/20136) feat: Add Certified filter to Datasets (@hughhhh)
- [#20111](https://github.com/apache/superset/pull/20111) feat(dashboard): Chart title click redirects to Explore in new tab (@kgabryje)
- [#20097](https://github.com/apache/superset/pull/20097) feat(plugin-chart-echarts): add support for generic axis to mixed chart (@villebro)
- [#20126](https://github.com/apache/superset/pull/20126) feat(dashboard): Add create chart button in dashboard edit mode (@kgabryje)
- [#20059](https://github.com/apache/superset/pull/20059) feat: Save column data into json_metadata for all Query executions (@hughhhh)
- [#19918](https://github.com/apache/superset/pull/19918) feat(plugin-chart-echarts): support horizontal bar chart (@stephenLYZ)
- [#19902](https://github.com/apache/superset/pull/19902) feat: Explore popovers should close on escape (@diegomedina248)
- [#20049](https://github.com/apache/superset/pull/20049) feat(dashboard): Rearrange items in chart header controls dropdown (@kgabryje)
- [#20030](https://github.com/apache/superset/pull/20030) feat(sip-68): Add DatasourceDAO class to manage querying different datasources easier (@hughhhh)
- [#19581](https://github.com/apache/superset/pull/19581) feat(viz-gallery): add search weight for viz-name (@stephenLYZ)
- [#19999](https://github.com/apache/superset/pull/19999) feat: RLS for SQL Lab (@betodealmeida)
- [#19993](https://github.com/apache/superset/pull/19993) feat(explore): Show confirmation modal if user exits Explore without saving changes (@kgabryje)
- [#19873](https://github.com/apache/superset/pull/19873) feat(css): adds `chartId`-based class to dashboard chart holder (@rusackas)
- [#20002](https://github.com/apache/superset/pull/20002) feat: deprecate /superset/testconn and migrate to api v1 (@zephyring)
- [#19935](https://github.com/apache/superset/pull/19935) feat: deprecate /superset/validate_sql_json migrate to api v1 (@dpgaspar)
- [#20015](https://github.com/apache/superset/pull/20015) feat: add new enums for datasource types (@hughhhh)
- [#19956](https://github.com/apache/superset/pull/19956) feat: Applitools Cypress workflow (@geido)
- [#19852](https://github.com/apache/superset/pull/19852) feat: Run Applitools on public Storybook (@geido)
- [#19963](https://github.com/apache/superset/pull/19963) feat: Add cypress test for downloading chart as image (@codemaster08240328)
- [#19957](https://github.com/apache/superset/pull/19957) feat: switch from `sqlalchemy-trino` to `trino-python-client` (@dungdm93)
- [#19921](https://github.com/apache/superset/pull/19921) feat: deprecate /superset/extra_table_metadata migrate to api v1 (@dpgaspar)
- [#19745](https://github.com/apache/superset/pull/19745) feat: simplify SQLite time grain (@betodealmeida)
- [#19927](https://github.com/apache/superset/pull/19927) feat(chart & legend): make to enable show legend by default (@prosdev0107)
- [#19754](https://github.com/apache/superset/pull/19754) feat: deprecate old API on core superset fave_dashboards (@dpgaspar)
- [#19905](https://github.com/apache/superset/pull/19905) feat: simplify `memoized_func` (@betodealmeida)
- [#19871](https://github.com/apache/superset/pull/19871) feat(filter): make to hide sort filter when time range (@prosdev0107)
- [#19851](https://github.com/apache/superset/pull/19851) feat: add Advanced Analytics into mixed time series chart (@zhaoyongjie)
- [#19692](https://github.com/apache/superset/pull/19692) feat: Update ShortKey for stop query running in SqlLab editor (@codemaster08240328)
- [#17903](https://github.com/apache/superset/pull/17903) feat: Adds plugin-chart-handlebars (@jdbranham)
- [#19748](https://github.com/apache/superset/pull/19748) feat(explore): improve UI in the control panel (@stephenLYZ)
- [#19724](https://github.com/apache/superset/pull/19724) feat: 10/15/30 min grain to Pinot (@hughhhh)
- [#19696](https://github.com/apache/superset/pull/19696) feat(explore): Replace overlay with alert banner when chart controls change (@kgabryje)
- [#19751](https://github.com/apache/superset/pull/19751) feat(explore): Implement data panel redesign (@kgabryje)
- [#19598](https://github.com/apache/superset/pull/19598) feat: add empty states to sqlab editor and select (@pkdotson)
- [#19450](https://github.com/apache/superset/pull/19450) feat: Remove legacy sql alchemy db connection link from G Sheet connection (@codemaster08240328)
- [#19710](https://github.com/apache/superset/pull/19710) feat: Enabling source maps full time (@rusackas)
- [#19671](https://github.com/apache/superset/pull/19671) feat: UI override registry (@suddjian)
- [#19691](https://github.com/apache/superset/pull/19691) feat(explore): More explicit labels of adhoc filter operators (@kgabryje)
- [#19558](https://github.com/apache/superset/pull/19558) feat(explore): Redesign of Run/Save buttons (@kgabryje)
- [#19650](https://github.com/apache/superset/pull/19650) feat(embedded): API get embedded dashboard config by uuid (@lilykuang)
- [#19310](https://github.com/apache/superset/pull/19310) feat(CRUD): add new empty state (@stephenLYZ)
- [#19622](https://github.com/apache/superset/pull/19622) feat(plugin-chart-echarts): add aggregate total for the Pie/Donuct chart (@stephenLYZ)
- [#19314](https://github.com/apache/superset/pull/19314) feat: Move Database Import option into DB Connection modal (@lyndsiWilliams)
- [#19434](https://github.com/apache/superset/pull/19434) feat: deprecate old API and create new API for dashes created by me (@dpgaspar)
- [#19482](https://github.com/apache/superset/pull/19482) feat: add success toast to alerts and reports (@pkdotson)
- [#19574](https://github.com/apache/superset/pull/19574) feat: add a `where_in` filter for Jinja2 (@betodealmeida)
- [#19458](https://github.com/apache/superset/pull/19458) feat(explore): Move timer, row counter and cached pills to chart container (@kgabryje)
- [#19529](https://github.com/apache/superset/pull/19529) feat(explore): Move chart header to top of the page (@kgabryje)
- [#19489](https://github.com/apache/superset/pull/19489) feat(CI): clean up Python tests output (@ktmud)
- [#19308](https://github.com/apache/superset/pull/19308) feat(explore): SQL popover in datasource panel (@kgabryje)
- [#19325](https://github.com/apache/superset/pull/19325) feat(color): support analogous colors to prevent color conflict (@stephenLYZ)
- [#19408](https://github.com/apache/superset/pull/19408) feat(dashboard): Implement empty states for empty tabs (@kgabryje)
- [#19446](https://github.com/apache/superset/pull/19446) feat(explore): Move chart actions into dropdown (@kgabryje)
- [#19394](https://github.com/apache/superset/pull/19394) feat(explore): UI changes in dataset panel on Explore page (@kgabryje)
**Fixes**
- [#20382](https://github.com/apache/superset/pull/20382) fix: Allow dataset owners to explore their datasets (@reesercollins)
- [#20419](https://github.com/apache/superset/pull/20419) fix(embedded): Retry when executing alert queries to avoid sending transient errors to users as alert failure notifications (@zhaorui2022)
- [#20555](https://github.com/apache/superset/pull/20555) fix: Respecting max/min opacities, and adding tests. (@rusackas)
- [#20571](https://github.com/apache/superset/pull/20571) fix: Revert #20408 (stacking negative values in echarts bar chart) (@rusackas)
- [#20487](https://github.com/apache/superset/pull/20487) fix(database-modal): form in database model effects results of the database list (@stephenLYZ)
- [#20488](https://github.com/apache/superset/pull/20488) fix(big-number): big number gets cut off on a Dashboard (@stephenLYZ)
- [#16326](https://github.com/apache/superset/pull/16326) fix: SQL Lab cancel query in Redshift database connection does not wo… (@yourssvk)
- [#20362](https://github.com/apache/superset/pull/20362) fix: Unable to download the Dashboard as image in case there's an image added through Markdown (@diegomedina248)
- [#20543](https://github.com/apache/superset/pull/20543) fix: Removes psycopg2 as a required dependency
- [#20442](https://github.com/apache/superset/pull/20442) fix(db): Show the only db install guide when the db is already installed and error is existed while importing file. (@prosdev0107)
- [#20483](https://github.com/apache/superset/pull/20483) fix: bump FAB to 4.1.2 (@dpgaspar)
- [#20493](https://github.com/apache/superset/pull/20493) fix: correction from mmsql to mssql in setup.py (@mdeshmu)
- [#20460](https://github.com/apache/superset/pull/20460) fix: new column UUID conflicts in dual write (@eschutho)
- [#20485](https://github.com/apache/superset/pull/20485) fix: Re-add filter-box time granularity/column (@john-bodley)
- [#20480](https://github.com/apache/superset/pull/20480) fix(docs): prevent some symbols from being copied in docs (@stephenLYZ)
- [#19920](https://github.com/apache/superset/pull/19920) fix(table viz): correctly sort by multiple columns in a table (@stevetracvc)
- [#20402](https://github.com/apache/superset/pull/20402) fix: alert & reports active toggle optimistic update (@diegomedina248)
- [#20472](https://github.com/apache/superset/pull/20472) fix: Changes the return type of get_permissions to be JSON friendly (@michael-s-molina)
- [#20468](https://github.com/apache/superset/pull/20468) fix: async queries limit bug (@AAfghahi)
- [#20257](https://github.com/apache/superset/pull/20257) fix(home): Show home page tabs as pills instead of links (@prosdev0107)
- [#20340](https://github.com/apache/superset/pull/20340) fix: ensure column name in description is string (@betodealmeida)
- [#20350](https://github.com/apache/superset/pull/20350) fix(viz): BigQuery time grain 'minute'/'second' throws an error (@diegomedina248)
- [#20384](https://github.com/apache/superset/pull/20384) fix(chart & table): Prevent the dates from wrapping in table chart (@prosdev0107)
- [#20404](https://github.com/apache/superset/pull/20404) fix: suppress translation warning in jest (@zhaoyongjie)
- [#20451](https://github.com/apache/superset/pull/20451) fix: should raise exception when apply a categorical axis (@zhaoyongjie)
- [#20447](https://github.com/apache/superset/pull/20447) fix: table viz sort icon bottom aligned (@diegomedina248)
- [#20326](https://github.com/apache/superset/pull/20326) fix(fbprophet): Fix weekly frequencies (@john-bodley)
- [#20434](https://github.com/apache/superset/pull/20434) fix(20428): Address-Presto/Trino-Poll-Issue-Refactor (@Thelin90)
- [#20411](https://github.com/apache/superset/pull/20411) fix(dashboard): new created chart did not have high lighted effect when using the permalink of chart share in dashboard (@diegomedina248)
- [#20261](https://github.com/apache/superset/pull/20261) fix(embedded): CSV download for chart (@lilykuang)
- [#20276](https://github.com/apache/superset/pull/20276) fix(cosmetic): cannot find m-r-10 class in superset.less (@Renderz)
- [#20420](https://github.com/apache/superset/pull/20420) fix: rm eslint-plugin-translation-vars engines requirement (@stephenLYZ)
- [#20409](https://github.com/apache/superset/pull/20409) fix(bar-chart-v2): remove marker control from bar chart V2 (@stephenLYZ)
- [#20333](https://github.com/apache/superset/pull/20333) fix(presto): use milliseconds timespec for presto (@mohittt8)
- [#20414](https://github.com/apache/superset/pull/20414) fix: key error on permalink fetch for old permalinks (@eschutho)
- [#20410](https://github.com/apache/superset/pull/20410) fix: Adding extra metrics issue after chart configuration (@codemaster08240328)
- [#20405](https://github.com/apache/superset/pull/20405) fix: Incorrect translations in Chinese in messages.po (@chuancyzhang)
- [#20396](https://github.com/apache/superset/pull/20396) fix(plugin-chart-pivot-table): color weight of Conditional formatting metrics not work (@stephenLYZ)
- [#20361](https://github.com/apache/superset/pull/20361) fix(fonts): Show the all the A's in our workspace correctly, not funky (@prosdev0107)
- [#20383](https://github.com/apache/superset/pull/20383) fix: Unable to export multiple Dashboards with the same name (@diegomedina248)
- [#20363](https://github.com/apache/superset/pull/20363) fix: A newly connected database doesn't appear in the databases list if user connected database using the 'plus' button (@diegomedina248)
- [#20372](https://github.com/apache/superset/pull/20372) fix: update connection modal to use existing catalog (@pkdotson)
- [#20368](https://github.com/apache/superset/pull/20368) fix(VERSIONED_EXPORTS): Ensure dashboards and charts adhere to the VERSIONED_EXPORTS feature flag (@john-bodley)
- [#20351](https://github.com/apache/superset/pull/20351) fix: catch some potential errors on dual write (@eschutho)
- [#20364](https://github.com/apache/superset/pull/20364) fix: query execution time is not fully displayed in bubble icon (@diegomedina248)
- [#20365](https://github.com/apache/superset/pull/20365) fix: Fix typo in Error handling message (@codemaster08240328)
- [#19967](https://github.com/apache/superset/pull/19967) fix: A newly connected database doesn't appear in the databases list if user connected database using the 'plus' button (@diegomedina248)
- [#20348](https://github.com/apache/superset/pull/20348) fix(docker): Make Gunicorn Keepalive Adjustable (@mdeshmu)
- [#19670](https://github.com/apache/superset/pull/19670) fix: Add serviceAccountName to celerybeat pods (@paulinjo)
- [#20315](https://github.com/apache/superset/pull/20315) fix(chart): chart gets cut off on the dashboard (@stephenLYZ)
- [#20324](https://github.com/apache/superset/pull/20324) fix: superset-ui/core coverage (@zhaoyongjie)
- [#20282](https://github.com/apache/superset/pull/20282) fix(explore): Make that see more/see less works correctly with scrolling when error msg is long text. (@prosdev0107)
- [#20296](https://github.com/apache/superset/pull/20296) fix: Alpha are unable to perform a second modification to a Dataset when in Explore (@hughhhh)
- [#20290](https://github.com/apache/superset/pull/20290) fix: Faulty datetime parser regex (@reesercollins)
- [#19761](https://github.com/apache/superset/pull/19761) fix(plugin-chart-echarts): [feature-parity] apply button of annotation layer doesn't work as expected (@stephenLYZ)
- [#20263](https://github.com/apache/superset/pull/20263) fix(embedded): accessing variable response before initialization (@zhaorui2022)
- [#20274](https://github.com/apache/superset/pull/20274) fix(codecov): improve core code coverage (@stephenLYZ)
- [#20187](https://github.com/apache/superset/pull/20187) fix: Database import with cancel_query.. extra field (@codemaster08240328)
- [#20237](https://github.com/apache/superset/pull/20237) fix(cosmetic): Fix Datasource Modal Out Of Box (@Renderz)
- [#20058](https://github.com/apache/superset/pull/20058) fix: Support the Clipboard API in modern browsers (@diegomedina248)
- [#20164](https://github.com/apache/superset/pull/20164) fix(sql lab): View result button is not showing consistently (@diegomedina248)
- [#20171](https://github.com/apache/superset/pull/20171) fix(charts list): do not trigger ListViewError exception for anonymous user (@trepmag)
- [#20178](https://github.com/apache/superset/pull/20178) fix: While exporting CSV , only the entries on first page are getting downloaded even when user is on other pages #17861 (@LahmerIlyas)
- [#20204](https://github.com/apache/superset/pull/20204) fix: Fixes issue where results panel height was incorrect [sc-49045] (@eric-briscoe)
- [#20235](https://github.com/apache/superset/pull/20235) fix: Box Plot Chart throws an error when the average (AVG) / SUM is being calculated on the Metrics (@diegomedina248)
- [#20088](https://github.com/apache/superset/pull/20088) fix: datatype tracking issue on virtual dataset (@codemaster08240328)
- [#20220](https://github.com/apache/superset/pull/20220) fix: dashbaord unable to refresh (@zhaoyongjie)
- [#20228](https://github.com/apache/superset/pull/20228) fix: failed samples should throw exception (@zhaoyongjie)
- [#20203](https://github.com/apache/superset/pull/20203) fix: move columns to datasource object for bootstrap data (@hughhhh)
- [#20151](https://github.com/apache/superset/pull/20151) fix(csv): Ensure df_to_escaped_csv does not coerce integer columns to float (@john-bodley)
- [#20221](https://github.com/apache/superset/pull/20221) fix(legacy-plugin-chart-sunburst): linear color scheme not work when secondary metric is provided (@stephenLYZ)
- [#20223](https://github.com/apache/superset/pull/20223) fix(legacy-plugin-chart-sunburst): chart broken when secondary metric is removed (@stephenLYZ)
- [#20147](https://github.com/apache/superset/pull/20147) fix(cosmetic): Limiting modal height (@rusackas)
- [#20206](https://github.com/apache/superset/pull/20206) fix(sql lab): SQL Lab Compile Query Delay (@diegomedina248)
- [#20201](https://github.com/apache/superset/pull/20201) fix: unable to set destroyOnClose on ModalTrigger (@zhaoyongjie)
- [#20186](https://github.com/apache/superset/pull/20186) fix(db): make to allow to show/hide the password when only creating (@prosdev0107)
- [#20127](https://github.com/apache/superset/pull/20127) fix(database): retrival of tables and views from schema for exasol backend (@Nicoretti)
- [#19899](https://github.com/apache/superset/pull/19899) fix: always create parameter json field (@pkdotson)
- [#20173](https://github.com/apache/superset/pull/20173) fix: avoid while cycle in computeMaxFontSize for big Number run forever when css rule applied (@diegomedina248)
- [#20086](https://github.com/apache/superset/pull/20086) fix(css): transparent linear gradient not working in safari (@stephenLYZ)
- [#19102](https://github.com/apache/superset/pull/19102) fix: string aggregation is incorrect in PivotTableV2 (@diegomedina248)
- [#20011](https://github.com/apache/superset/pull/20011) fix(chart & heatmap): make to fix that y label is rendering out of bounds (@prosdev0107)
- [#20142](https://github.com/apache/superset/pull/20142) fix(explore): handle null control sections (@villebro)
- [#20128](https://github.com/apache/superset/pull/20128) fix: advanced data type API spec and permission name (@dpgaspar)
- [#20107](https://github.com/apache/superset/pull/20107) fix(generic-chart-axes): set x-axis if unset and ff is enabled (@villebro)
- [#20018](https://github.com/apache/superset/pull/20018) fix(modal): add primary button loading state to modals (@kgopal492)
- [#20099](https://github.com/apache/superset/pull/20099) fix: Add cypress test for report page direct link issue (@codemaster08240328)
- [#20068](https://github.com/apache/superset/pull/20068) fix: dbmodal test connection error timeout (@pkdotson)
- [#20092](https://github.com/apache/superset/pull/20092) fix: Revert "feat(explore): Show confirmation modal if user exits Explore without saving changes (#19993) (@kgabryje)
- [#19939](https://github.com/apache/superset/pull/19939) fix(chart & alert): make to show metrics properly (@prosdev0107)
- [#20085](https://github.com/apache/superset/pull/20085) fix: typo in `importexport/api.py` OpenAPI (@betodealmeida)
- [#20051](https://github.com/apache/superset/pull/20051) fix(CRUD): make to fix the dancing when crud view is on hover (@prosdev0107)
- [#20064](https://github.com/apache/superset/pull/20064) fix(chart & gallery): make to add mixed time-series into recommended charts (@prosdev0107)
- [#20013](https://github.com/apache/superset/pull/20013) fix: The dynamic form to connect to Snowflake DB is not returning any errors (@diegomedina248)
- [#20029](https://github.com/apache/superset/pull/20029) fix(plugin-chart-echarts): tooltip of big number truncated at then bottom (@stephenLYZ)
- [#19914](https://github.com/apache/superset/pull/19914) fix: Refactor SQL engine username logic (@john-bodley)
- [#20050](https://github.com/apache/superset/pull/20050) fix: Fixes Tabs style (@michael-s-molina)
- [#20048](https://github.com/apache/superset/pull/20048) fix(homepage): make to show indicator when tab is chosen (@prosdev0107)
- [#20026](https://github.com/apache/superset/pull/20026) fix(chart & filters): make to padding between textarea and buttons (@prosdev0107)
- [#20019](https://github.com/apache/superset/pull/20019) fix(embedded): third party cookies (@lilykuang)
- [#20033](https://github.com/apache/superset/pull/20033) fix: Direct Linking issue on report list: 404 status code. (@codemaster08240328)
- [#19977](https://github.com/apache/superset/pull/19977) fix(word-cloud): fix randomness of each word's rotation (@ebaratte)
- [#20021](https://github.com/apache/superset/pull/20021) fix: native filter truncation rerendering loop on hover (@diegomedina248)
- [#20004](https://github.com/apache/superset/pull/20004) fix: URI form is blank when trying to connect from sql lab (@diegomedina248)
- [#19841](https://github.com/apache/superset/pull/19841) fix: Table chart column config issue (@codemaster08240328)
- [#19877](https://github.com/apache/superset/pull/19877) fix: Making chart update more truthful (@Gwitchr)
- [#19996](https://github.com/apache/superset/pull/19996) fix: Use pull_request_target in Cypress Applitools workflow (@geido)
- [#19972](https://github.com/apache/superset/pull/19972) fix: revert chore(deps): bump d3-svg-legend in /superset-frontend (#19846) (@villebro)
- [#19889](https://github.com/apache/superset/pull/19889) fix: Fix auto-reversion of label/title in the Metrics popover (@diegomedina248)
- [#19903](https://github.com/apache/superset/pull/19903) fix(explore): Explore data table tooltip (@Gwitchr)
- [#19938](https://github.com/apache/superset/pull/19938) fix(chart & table): make to allow highlight in case of numeric column (@prosdev0107)
- [#19839](https://github.com/apache/superset/pull/19839) fix(dashboard): allow users to resize the markdown widget easier (@cccs-Dustin)
- [#19887](https://github.com/apache/superset/pull/19887) fix(hive): Workaround for Python 3.9 s3 transfer issue (@john-bodley)
- [#19936](https://github.com/apache/superset/pull/19936) fix: OpenAPI docs small fixes (@dpgaspar)
- [#19932](https://github.com/apache/superset/pull/19932) fix: can not correctly set force in store (@zhaoyongjie)
- [#19930](https://github.com/apache/superset/pull/19930) fix: memoize primitives (@betodealmeida)
- [#19926](https://github.com/apache/superset/pull/19926) fix(dataset): DAO update (@betodealmeida)
- [#19826](https://github.com/apache/superset/pull/19826) fix: Missing `f` prefix on f-strings (@code-review-doctor)
- [#18988](https://github.com/apache/superset/pull/18988) fix(column-header-tooltip): make that hide the tooltip when the cloum… (@prosdev0107)
- [#19782](https://github.com/apache/superset/pull/19782) fix: chart import error with virtual dataset (@codemaster08240328)
- [#19485](https://github.com/apache/superset/pull/19485) fix: Set fixed maxWidth of the cron schedule modal (@codemaster08240328)
- [#19885](https://github.com/apache/superset/pull/19885) fix: Chart download as image issue (@codemaster08240328)
- [#19883](https://github.com/apache/superset/pull/19883) fix(allow-db-explore): make to check the allow virtual table explore option by default (@prosdev0107)
- [#19835](https://github.com/apache/superset/pull/19835) fix(helm): fix postgresql values (@benjamin-texier)
- [#19758](https://github.com/apache/superset/pull/19758) fix(plugin-chart-echarts): [feature parity] annotation line chart color does not work (@stephenLYZ)
- [#19879](https://github.com/apache/superset/pull/19879) fix(plugin-chart-handlebars): fix overflow, debounce and control reset (@villebro)
- [#19668](https://github.com/apache/superset/pull/19668) fix: Dates alignment in Table viz (@geido)
- [#19876](https://github.com/apache/superset/pull/19876) fix: Cannot re-order metrics by drag and drop (@diegomedina248)
- [#19840](https://github.com/apache/superset/pull/19840) fix(dashboard-css): make to load saved css template (@prosdev0107)
- [#19859](https://github.com/apache/superset/pull/19859) fix: Dashboard report creation error handling (@etr2460)
- [#19857](https://github.com/apache/superset/pull/19857) fix: Update eslint error message to reflect location of antd components (@etr2460)
- [#19605](https://github.com/apache/superset/pull/19605) fix: Query execution time is displayed as invalid date (@diegomedina248)
- [#19694](https://github.com/apache/superset/pull/19694) fix(db & connection): make to show/hide the password when only creating db connection (@prosdev0107)
- [#19778](https://github.com/apache/superset/pull/19778) fix: deck.gl GeoJsonLayer Autozoom & fill/stroke options (@diegomedina248)
- [#19850](https://github.com/apache/superset/pull/19850) fix: Regression on Data and Alerts & Reports Headers (@diegomedina248)
- [#19842](https://github.com/apache/superset/pull/19842) fix: count(distinct column_name) in metrics (@zhaoyongjie)
- [#19843](https://github.com/apache/superset/pull/19843) fix(explore): ignore temporary controls in altered pill (@villebro)
- [#19800](https://github.com/apache/superset/pull/19800) fix: Cypress tests reliability improvements (@diegomedina248)
- [#19575](https://github.com/apache/superset/pull/19575) fix: Show full long number in text email report for table chart. (@codemaster08240328)
- [#19429](https://github.com/apache/superset/pull/19429) fix(dashboard): make to filter the correct certified or non-certified… (@prosdev0107)
- [#13082](https://github.com/apache/superset/pull/13082) fix(sql_lab): Add custom timestamp type for literal casting for presto timestamps (@kekwan)
- [#19797](https://github.com/apache/superset/pull/19797) fix: add missing init files (@suddjian)
- [#19672](https://github.com/apache/superset/pull/19672) fix: trap SQLAlchemy common exceptions & throw 422 error instead (@diegomedina248)
- [#19288](https://github.com/apache/superset/pull/19288) fix: AlertReportCronScheduler tests (@diegomedina248)
- [#19781](https://github.com/apache/superset/pull/19781) fix(world-map): remove categorical color control (@serenajiang)
- [#19792](https://github.com/apache/superset/pull/19792) fix(plugin-chart-table): Resetting controls when switching query mode (@kgabryje)
- [#19755](https://github.com/apache/superset/pull/19755) fix: small cleanup for created by me dashboards API (@dpgaspar)
- [#19784](https://github.com/apache/superset/pull/19784) fix(readme): Remove broken link to legacy gallery (@drluckyspin)
- [#19722](https://github.com/apache/superset/pull/19722) fix: dashboard top level tabs edit (@diegomedina248)
- [#19777](https://github.com/apache/superset/pull/19777) fix(explore): Double divider if no permissions for adding reports (@kgabryje)
- [#19673](https://github.com/apache/superset/pull/19673) fix(import): Add the error alert on failed database import (@prosdev0107)
- [#19518](https://github.com/apache/superset/pull/19518) fix: alert/report created by filter inconsistency with table display (@diegomedina248)
- [#19700](https://github.com/apache/superset/pull/19700) fix: remove expose (@AAfghahi)
- [#19626](https://github.com/apache/superset/pull/19626) fix: deactivate embedding on a dashboard (@suddjian)
- [#19472](https://github.com/apache/superset/pull/19472) fix: Dashboard Edit View Tab Headers Hidden when Dashboard Name is Long (@diegomedina248)
- [#19311](https://github.com/apache/superset/pull/19311) fix(sql lab): add quotes when autocompleting table names with spaces in the editor (@diegomedina248)
- [#19290](https://github.com/apache/superset/pull/19290) fix(sql lab): select edit on query from history doesn't upload editor properly (@diegomedina248)
- [#19420](https://github.com/apache/superset/pull/19420) fix: sql lab ctrl t behaved differently from clicking (@Gwitchr)
- [#19357](https://github.com/apache/superset/pull/19357) fix: Redirect to full url on 401 (@geido)
- [#19001](https://github.com/apache/superset/pull/19001) fix: Line Chart Annotation Info Update (@codemaster08240328)
- [#19714](https://github.com/apache/superset/pull/19714) fix: create virtual table with exotic type (@villebro)
- [#19708](https://github.com/apache/superset/pull/19708) fix(nav): infinite redirect and upload dataset nav permissions (@ktmud)
- [#19430](https://github.com/apache/superset/pull/19430) fix(data-upload): make to change err message (@prosdev0107)
- [#19419](https://github.com/apache/superset/pull/19419) fix(alert & report): make to fix the issue when recreate report (@prosdev0107)
- [#19371](https://github.com/apache/superset/pull/19371) fix: Reset sorting bar issue in Barchart (@codemaster08240328)
- [#19362](https://github.com/apache/superset/pull/19362) fix(sql lab): display the 'View Results' button consistently in the history tab on sync mode (@diegomedina248)
- [#19294](https://github.com/apache/superset/pull/19294) fix: improve alerts & reports modal on small devices (@diegomedina248)
- [#19257](https://github.com/apache/superset/pull/19257) fix(sql lab): table selector should display all the selected tables (@diegomedina248)
- [#19686](https://github.com/apache/superset/pull/19686) fix(plugin-chart-echarts): xAxis scale is not correct when time grain is quarter (@stephenLYZ)
- [#19646](https://github.com/apache/superset/pull/19646) fix(explore): Change copy of cross filters checkbox (@kgabryje)
- [#19586](https://github.com/apache/superset/pull/19586) fix: Navbar styles and Welcome page text (@geido)
- [#19662](https://github.com/apache/superset/pull/19662) fix(database-api): allow search for all columns (@villebro)
- [#19656](https://github.com/apache/superset/pull/19656) fix: allow_browser_login in import/export API (@betodealmeida)
- [#19628](https://github.com/apache/superset/pull/19628) fix: Table Autosizing Has Unnecessary Horizontal Scroll Bars (@diegomedina248)
- [#19573](https://github.com/apache/superset/pull/19573) fix(chart & polygon): make to fix the issue the polygon chart (@prosdev0107)
- [#19051](https://github.com/apache/superset/pull/19051) fix: update Permissions for right nav (@AAfghahi)
- [#19625](https://github.com/apache/superset/pull/19625) fix(test): make test_clean_requests_after_schema_grant more idempotent (@ktmud)
- [#19571](https://github.com/apache/superset/pull/19571) fix: Catch literal colors when theme top level (@geido)
- [#19594](https://github.com/apache/superset/pull/19594) fix: spelling of following (@lzm0)
- [#19569](https://github.com/apache/superset/pull/19569) fix: check type of url before performing string actions (@eschutho)
- [#19570](https://github.com/apache/superset/pull/19570) fix: sqloxide optional (@betodealmeida)
- [#19397](https://github.com/apache/superset/pull/19397) fix: weight tooltip issue (@codemaster08240328)
- [#19313](https://github.com/apache/superset/pull/19313) fix(sql lab): increase the size of the action icons in the history tab (@diegomedina248)
- [#19039](https://github.com/apache/superset/pull/19039) fix(explore): clean data when hidding control (@stephenLYZ)
- [#19444](https://github.com/apache/superset/pull/19444) fix: Error Message is cut off in alerts & reports log page (@codemaster08240328)
- [#19312](https://github.com/apache/superset/pull/19312) fix: adaptive formatting typo in explore dropdowns (@diegomedina248)
- [#19534](https://github.com/apache/superset/pull/19534) fix(explore): Chart header icon paddings (@kgabryje)
- [#19399](https://github.com/apache/superset/pull/19399) fix: native filter dropdown not attached to parent node (@diegomedina248)
- [#19112](https://github.com/apache/superset/pull/19112) fix: Dashboard import holding issue (@codemaster08240328)
- [#19342](https://github.com/apache/superset/pull/19342) fix: Clean up custom css when dashboard unmounted (@codemaster08240328)
- [#19491](https://github.com/apache/superset/pull/19491) fix: Dynamic form to connect to Snowflake DB is not displaying authentication errors (@diegomedina248)
- [#19528](https://github.com/apache/superset/pull/19528) fix: Correct Ukraine map (@wacken89)
- [#19522](https://github.com/apache/superset/pull/19522) fix: add back view for report reload error (@pkdotson)
- [#19519](https://github.com/apache/superset/pull/19519) fix: GSheets rendering from global nav (@hughhhh)
- [#19358](https://github.com/apache/superset/pull/19358) fix(sqllab): make to hide the delete button of most recent query history (@prosdev0107)
- [#19307](https://github.com/apache/superset/pull/19307) fix: Logo resizing on page load (@geido)
- [#19166](https://github.com/apache/superset/pull/19166) fix: time filter should be [start, end) (@zhaoyongjie)
**Others**
- [#20620](https://github.com/apache/superset/pull/20620) docs: fix link for Apache Superset source code (@dpgaspar)
- [#20621](https://github.com/apache/superset/pull/20621) chore: bump FAB to 4.1.3 (@dpgaspar)
- [#20486](https://github.com/apache/superset/pull/20486) chore: Updated copy in chart drop down to "View as table" (@lauderbaugh)
- [#20116](https://github.com/apache/superset/pull/20116) style(typo): occured -> occurred (@sfirke)
- [#20401](https://github.com/apache/superset/pull/20401) chore: add action to welcome new users (@eschutho)
- [#20269](https://github.com/apache/superset/pull/20269) chore(docs): Remove cache warming documentation (@ajwhite)
- [#20194](https://github.com/apache/superset/pull/20194) chore: Removes unused vars (@michael-s-molina)
- [#20321](https://github.com/apache/superset/pull/20321) chore: add breaking change information about form_data datasource_type (@eschutho)
- [#20298](https://github.com/apache/superset/pull/20298) chore: Removes no-use-before-define warnings (@michael-s-molina)
- [#20337](https://github.com/apache/superset/pull/20337) chore(dashboard): update Edit Dashboard side panel tabs (@codyml)
- [#20318](https://github.com/apache/superset/pull/20318) chore: Updates the final steps of the release README (@michael-s-molina)
- [#20307](https://github.com/apache/superset/pull/20307) docs: Updates CHANGELOG.md with 1.5.1 fixes (@michael-s-molina)
- [#20308](https://github.com/apache/superset/pull/20308) docs(jinja): Detail how to use Jinja parameters (@EBoisseauSierra)
- [#20304](https://github.com/apache/superset/pull/20304) chore: superset-ui/core code coverage (@zhaoyongjie)
- [#20297](https://github.com/apache/superset/pull/20297) chore(deps): pinning pyjwt to 2.4.0 (@sadpandajoe)
- [#20287](https://github.com/apache/superset/pull/20287) chore(deps): bump numpy 1.22.1 and PyJWT to 2.4.0 (@sadpandajoe)
- [#20272](https://github.com/apache/superset/pull/20272) chore: remove unused codes for samples (@zhaoyongjie)
- [#20289](https://github.com/apache/superset/pull/20289) chore: Adjusts release emails (@michael-s-molina)
- [#20180](https://github.com/apache/superset/pull/20180) docs: facelift the docs (@mistercrunch)
- [#20249](https://github.com/apache/superset/pull/20249) chore: add event logger to reports/alerts CRUD (@AAfghahi)
- [#20273](https://github.com/apache/superset/pull/20273) chore: adjust the psycopg2 version of k8s installation guide (@ensky)
- [#20152](https://github.com/apache/superset/pull/20152) refactor(trino): Handful of updates for the Trino engine (@john-bodley)
- [#20252](https://github.com/apache/superset/pull/20252) chore: use exc_info to pass errors to log warnings (@eschutho)
- [#20154](https://github.com/apache/superset/pull/20154) chore(requirements): Cleanup of Python requirements (@john-bodley)
- [#20226](https://github.com/apache/superset/pull/20226) refactor: decouple DataTableControl (@zhaoyongjie)
- [#20243](https://github.com/apache/superset/pull/20243) docs: Add beans to users list (@kakoni)
- [#20231](https://github.com/apache/superset/pull/20231) docs: Updates release scripts and docs (@michael-s-molina)
- [#20196](https://github.com/apache/superset/pull/20196) chore: bumping min version of shillelagh (@AAfghahi)
- [#20192](https://github.com/apache/superset/pull/20192) chore: Moves date utils to utils folder (@michael-s-molina)
- [#20210](https://github.com/apache/superset/pull/20210) docs: update release instructions (@villebro)
- [#20205](https://github.com/apache/superset/pull/20205) chore(deps): bump swagger-ui-react from 4.1.2 to 4.1.3 in /docs (@dependabot[bot])
- [#20195](https://github.com/apache/superset/pull/20195) docs: correct case of ClickHouse (@DanRoscigno)
- [#20109](https://github.com/apache/superset/pull/20109) refactor: decouple DataTablesPane (@zhaoyongjie)
- [#20193](https://github.com/apache/superset/pull/20193) refactor: Removes embedded/index.tsx warnings (@michael-s-molina)
- [#20185](https://github.com/apache/superset/pull/20185) docs(security): a typo: Gamma should be in quotes (@jimmytheneutrino)
- [#20146](https://github.com/apache/superset/pull/20146) chore: Implement global header in Dashboard (@geido)
- [#20174](https://github.com/apache/superset/pull/20174) chore: Disable flaky assert in reports cypress test (@kgabryje)
- [#20163](https://github.com/apache/superset/pull/20163) chore: change button name in Sql Lab (@AAfghahi)
- [#20157](https://github.com/apache/superset/pull/20157) chore: filter undefined operators (@zhaoyongjie)
- [#20140](https://github.com/apache/superset/pull/20140) chore(data-table): make formatted dttm the default (@villebro)
- [#20104](https://github.com/apache/superset/pull/20104) chore: fix INTHEWILD sort order and indentation (@villebro)
- [#20093](https://github.com/apache/superset/pull/20093) chore: Add the tnum font property to Table components (@geido)
- [#20103](https://github.com/apache/superset/pull/20103) docs: Update INTHEWILD.md (@fccoelho)
- [#20102](https://github.com/apache/superset/pull/20102) chore: Update aiohttp to 3.8.1 (@diegomedina248)
- [#20066](https://github.com/apache/superset/pull/20066) chore: Set limit for a query in execute_sql_statement (@AAfghahi)
- [#20032](https://github.com/apache/superset/pull/20032) chore: Change copy to Edit chart in Dashboard dropdown (@geido)
- [#20071](https://github.com/apache/superset/pull/20071) chore: Fix and enhance Applitools workflows (@geido)
- [#19966](https://github.com/apache/superset/pull/19966) test: make tabbed dashboard a little more complex (@ktmud)
- [#19976](https://github.com/apache/superset/pull/19976) perf(plugin-chart-table): Add memoization to avoid rerenders (@kgabryje)
- [#20044](https://github.com/apache/superset/pull/20044) chore: Create a generic header component for Explore and Dashboard (@kgabryje)
- [#20046](https://github.com/apache/superset/pull/20046) docs: add changelog and updating entries for 1.5.0 (@villebro)
- [#19962](https://github.com/apache/superset/pull/19962) chore: add doc link for db migration conflict warning (@ktmud)
- [#20034](https://github.com/apache/superset/pull/20034) chore: Changes the no-literal-colors lint rule to throw errors instead of warnings (@michael-s-molina)
- [#20031](https://github.com/apache/superset/pull/20031) chore: Run Applitools + Cypress nightly (@geido)
- [#20006](https://github.com/apache/superset/pull/20006) chore: Removes hard-coded colors from the plugins - iteration 2 (@michael-s-molina)
- [#19130](https://github.com/apache/superset/pull/19130) refactor: Refactor reports for Charts and Dashboards (@AAfghahi)
- [#20016](https://github.com/apache/superset/pull/20016) chore: Removes hard-coded colors - iteration 3 (@michael-s-molina)
- [#19870](https://github.com/apache/superset/pull/19870) docs: Detail front-end development instructions (@EBoisseauSierra)
- [#19971](https://github.com/apache/superset/pull/19971) docs: Add config for running on a WSGI HTTP server (@thinhnd2104)
- [#20008](https://github.com/apache/superset/pull/20008) chore: Upgrades Storybook from 6.4.19 to 6.4.22 (@michael-s-molina)
- [#20009](https://github.com/apache/superset/pull/20009) docs: typo in chart-params markdown file (@JakobMiksch)
- [#19923](https://github.com/apache/superset/pull/19923) chore: Removes hard-coded colors from the plugins - iteration 1 (@michael-s-molina)
- [#19954](https://github.com/apache/superset/pull/19954) chore: convert URLShortLinkButton to typescript (@ktmud)
- [#19929](https://github.com/apache/superset/pull/19929) chore: change subject name from no_name to named for PNGs in (@AAfghahi)
- [#19942](https://github.com/apache/superset/pull/19942) refactor(ReportModal): simplify state reducer and improve error handling (@ktmud)
- [#19770](https://github.com/apache/superset/pull/19770) chore: remove druid datasource from the config (@eschutho)
- [#19911](https://github.com/apache/superset/pull/19911) chore: Fix broken link for DouroECI (@mavimo)
- [#19951](https://github.com/apache/superset/pull/19951) chore: Adds the theme object to chart properties (@michael-s-molina)
- [#19813](https://github.com/apache/superset/pull/19813) chore: get embedded user with roles and permissions (@suddjian)
- [#19897](https://github.com/apache/superset/pull/19897) chore: Adds a storybook to FilterableTable (@michael-s-molina)
- [#19924](https://github.com/apache/superset/pull/19924) chore(reports): Improving logging around failed scheduled reports (@craig-rueda)
- [#19906](https://github.com/apache/superset/pull/19906) revert: "fix(sql lab): display the 'View Results' button consistently in the history tab on sync mode" (@Gwitchr)
- [#19916](https://github.com/apache/superset/pull/19916) chore(deps): bump react-virtualized-auto-sizer from 1.0.2 to 1.0.6 in /superset-frontend (@dependabot[bot])
- [#19888](https://github.com/apache/superset/pull/19888) chore(deps): bump cross-fetch from 3.1.4 to 3.1.5 in /docs (@dependabot[bot])
- [#19894](https://github.com/apache/superset/pull/19894) chore(deps-dev): bump eslint-plugin-prettier from 3.3.1 to 4.0.0 in /superset-frontend (@dependabot[bot])
- [#19602](https://github.com/apache/superset/pull/19602) docs: Added gtag to docusaurus (@AAfghahi)
- [#19878](https://github.com/apache/superset/pull/19878) chore(deps-dev): bump @storybook/client-api from 6.4.19 to 6.4.22 in /superset-frontend (@dependabot[bot])
- [#19821](https://github.com/apache/superset/pull/19821) test(native filter): refactor and add new test (@jinghua-qa)
- [#19613](https://github.com/apache/superset/pull/19613) chore: Update line-height in SliceHeaderControl (@geido)
- [#19616](https://github.com/apache/superset/pull/19616) chore: Update font-sizes in DatabaseModal (@geido)
- [#19866](https://github.com/apache/superset/pull/19866) chore: fix explore pills (@villebro)
- [#19872](https://github.com/apache/superset/pull/19872) chore: Update aiohttp>=3.7.4 in requirements (@hughhhh)
- [#19874](https://github.com/apache/superset/pull/19874) chore: bump rockset>=0.8.10, <0.9 (@hughhhh)
- [#19864](https://github.com/apache/superset/pull/19864) chore(deps): bump react-syntax-highlighter from 15.4.5 to 15.5.0 in /superset-frontend (@dependabot[bot])
- [#19828](https://github.com/apache/superset/pull/19828) chore: add custom eslint plugin to prevent translation variables (@stephenLYZ)
- [#19845](https://github.com/apache/superset/pull/19845) chore(deps): bump react-split from 2.0.9 to 2.0.14 in /superset-frontend (@dependabot[bot])
- [#19846](https://github.com/apache/superset/pull/19846) chore(deps): bump d3-svg-legend from 1.13.0 to 2.25.6 in /superset-frontend (@dependabot[bot])
- [#19847](https://github.com/apache/superset/pull/19847) chore(deps-dev): bump eslint-plugin-jsx-a11y from 6.4.1 to 6.5.1 in /superset-frontend (@dependabot[bot])
- [#19853](https://github.com/apache/superset/pull/19853) chore(frontend-tests): Spelling (@jsoref)
- [#19823](https://github.com/apache/superset/pull/19823) docs: updated links for country map scripts (@ktmud)
- [#19829](https://github.com/apache/superset/pull/19829) chore(deps-dev): bump babel-loader from 8.2.4 to 8.2.5 in /superset-frontend (@dependabot[bot])
- [#19830](https://github.com/apache/superset/pull/19830) chore(deps): bump react-hot-loader from 4.12.20 to 4.13.0 in /superset-frontend (@dependabot[bot])
- [#19403](https://github.com/apache/superset/pull/19403) chore(deps-dev): bump babel-loader from 8.2.2 to 8.2.4 in /superset-frontend (@dependabot[bot])
- [#19637](https://github.com/apache/superset/pull/19637) chore(deps): bump moment from 2.29.1 to 2.29.2 in /superset-frontend (@dependabot[bot])
- [#19681](https://github.com/apache/superset/pull/19681) chore(deps): bump async from 3.2.0 to 3.2.3 in /superset-frontend/cypress-base (@dependabot[bot])
- [#19680](https://github.com/apache/superset/pull/19680) chore(deps): bump async from 3.2.0 to 3.2.3 in /superset-websocket (@dependabot[bot])
- [#19020](https://github.com/apache/superset/pull/19020) chore(deps): bump url-parse from 1.5.7 to 1.5.10 in /superset-frontend (@dependabot[bot])
- [#17978](https://github.com/apache/superset/pull/17978) chore(deps): bump @types/d3-time from 1.1.1 to 3.0.0 in /superset-frontend (@dependabot[bot])
- [#19727](https://github.com/apache/superset/pull/19727) chore(deps): bump async from 2.6.3 to 2.6.4 in /docs (@dependabot[bot])
- [#19551](https://github.com/apache/superset/pull/19551) chore(deps): bump minimist from 1.2.5 to 1.2.6 in /superset-websocket (@dependabot[bot])
- [#19165](https://github.com/apache/superset/pull/19165) chore: simplify error messaging in database modal (@pkdotson)
- [#19790](https://github.com/apache/superset/pull/19790) chore: bump postgres from 10 to 14 (@dpgaspar)
- [#19480](https://github.com/apache/superset/pull/19480) chore: Update UPDATING.md (@john-bodley)
- [#19740](https://github.com/apache/superset/pull/19740) chore: fix grammar error (@zhaoyongjie)
- [#19703](https://github.com/apache/superset/pull/19703) chore(build): upgrade less-loader (@ktmud)
- [#19736](https://github.com/apache/superset/pull/19736) chore: Updates the Select code owners (@michael-s-molina)
- [#19715](https://github.com/apache/superset/pull/19715) docs(install): ubuntu default-libmysqlclient-dev (@cemremengu)
- [#19726](https://github.com/apache/superset/pull/19726) chore: bumping shillelagh (@AAfghahi)
- [#19699](https://github.com/apache/superset/pull/19699) chore: fix typo (@betodealmeida)
- [#19674](https://github.com/apache/superset/pull/19674) chore: upgrade Pillow (@betodealmeida)
- [#19647](https://github.com/apache/superset/pull/19647) chore(explore): Change labels "Group by"/"Series" to "Dimensions" (@kgabryje)
- [#19679](https://github.com/apache/superset/pull/19679) chore(deps): bump urijs from 1.19.8 to 1.19.11 in /superset-frontend (@dependabot[bot])
- [#19638](https://github.com/apache/superset/pull/19638) chore(deps): bump moment from 2.29.1 to 2.29.2 in /docs (@dependabot[bot])
- [#19617](https://github.com/apache/superset/pull/19617) chore: updated two github issue templates (@srinify)
- [#19666](https://github.com/apache/superset/pull/19666) chore: Remove TwoTone icons (@geido)
- [#19614](https://github.com/apache/superset/pull/19614) chore: Remove wrong usage of font-size in ExploreViewContainer (@geido)
- [#19593](https://github.com/apache/superset/pull/19593) chore: Update font-sizes in ReportModal (@geido)
- [#19611](https://github.com/apache/superset/pull/19611) chore: Update font-sizes in ImportModal (@geido)
- [#19615](https://github.com/apache/superset/pull/19615) chore: Update font-sizes in AlertReportModal (@geido)
- [#19620](https://github.com/apache/superset/pull/19620) chore: Update font-sizes in QueryPreviewModal (@geido)
- [#19641](https://github.com/apache/superset/pull/19641) chore: clean up dynamic translation strings (@villebro)
- [#19635](https://github.com/apache/superset/pull/19635) refactor: consistent migration tests organization (@ktmud)
- [#19634](https://github.com/apache/superset/pull/19634) test: freeze time for dashboard export test (@ktmud)
- [#19606](https://github.com/apache/superset/pull/19606) test(jinja): refactor to functional tests (@villebro)
- [#19587](https://github.com/apache/superset/pull/19587) chore: cleanup as unknown conversion (@zhaoyongjie)
- [#19562](https://github.com/apache/superset/pull/19562) refactor: Removes the CSS files from the Horizon plugin (@michael-s-molina)
- [#19563](https://github.com/apache/superset/pull/19563) refactor: Removes the CSS files from the Paired T-Test plugin (@michael-s-molina)
- [#19539](https://github.com/apache/superset/pull/19539) refactor: Removes the CSS files from the Parallel Coordinates plugin (@michael-s-molina)
- [#19521](https://github.com/apache/superset/pull/19521) refactor: Removes the CSS files from the Partition plugin (@michael-s-molina)
- [#19493](https://github.com/apache/superset/pull/19493) chore: Removes hard-coded colors from legacy-plugin-chart-sankey (@michael-s-molina)
- [#19462](https://github.com/apache/superset/pull/19462) chore: Remove FilterBox.less (@geido)
- [#19438](https://github.com/apache/superset/pull/19438) chore: Remove crud.less from Datasource (@geido)
- [#19517](https://github.com/apache/superset/pull/19517) chore: Enhance ReactChord style with theme vars (@geido)
- [#19463](https://github.com/apache/superset/pull/19463) chore: Remove TimeTable.less (@geido)
- [#19550](https://github.com/apache/superset/pull/19550) chore(deps): bump minimist from 1.2.5 to 1.2.6 in /superset-embedded-sdk (@dependabot[bot])
- [#19566](https://github.com/apache/superset/pull/19566) chore(deps): bump node-forge from 1.2.1 to 1.3.1 in /docs (@dependabot[bot])
- [#19552](https://github.com/apache/superset/pull/19552) chore(deps): bump minimist from 1.2.5 to 1.2.6 in /docs (@dependabot[bot])
- [#19549](https://github.com/apache/superset/pull/19549) chore(deps): bump minimist from 1.2.5 to 1.2.6 in /superset-frontend/cypress-base (@dependabot[bot])
- [#19559](https://github.com/apache/superset/pull/19559) docs: update the typo in the documentation (@fatosmorina)
- [#19538](https://github.com/apache/superset/pull/19538) refactor: Removes the CSS files from the Country Map plugin (@michael-s-molina)
- [#19536](https://github.com/apache/superset/pull/19536) chore: Removes hard-coded opacity and spacing from the BigNumber plugin (@michael-s-molina)
- [#19494](https://github.com/apache/superset/pull/19494) refactor: Removes the CSS files from the Sankey Loop plugin (@michael-s-molina)
- [#19492](https://github.com/apache/superset/pull/19492) chore: Remove Legacy Force Directed viz plugin (@geido)
- [#19524](https://github.com/apache/superset/pull/19524) chore: Deprecating /my_queries endpoint (@AAfghahi)
- [#19467](https://github.com/apache/superset/pull/19467) chore(Explore): Change text when saving a chart in a new dashboard (@geido)
- [#19526](https://github.com/apache/superset/pull/19526) chore(database): Creating helper make_url_safe to wrap potential errors (@craig-rueda)
- [#19415](https://github.com/apache/superset/pull/19415) chore: Remove Control.less in Explore (@geido)
- [#19413](https://github.com/apache/superset/pull/19413) chore: Remove unused less file from profile (@geido)
- [#19460](https://github.com/apache/superset/pull/19460) chore: Switch to gender neutral terms (@inclusive-coding-bot)
- [#19486](https://github.com/apache/superset/pull/19486) refactor: Removes the CSS files from the Treemap plugin (@michael-s-molina)
- [#19488](https://github.com/apache/superset/pull/19488) refactor: Removes the CSS files from the Sunburst plugin (@michael-s-molina)
- [#19490](https://github.com/apache/superset/pull/19490) chore: Add theme color to ParallelCoordinates (@geido)
- [#19442](https://github.com/apache/superset/pull/19442) chore: Remove FilterbaleTableStyles.less (@geido)
- [#19441](https://github.com/apache/superset/pull/19441) chore: Remove StyledQueryButton.less (@geido)
- [#19473](https://github.com/apache/superset/pull/19473) refactor: Removes the CSS files from the Rose plugin (@michael-s-molina)
- [#19466](https://github.com/apache/superset/pull/19466) chore: Removes hard-coded colors from legacy-plugin-chart-world-map (@michael-s-molina)
- [#19465](https://github.com/apache/superset/pull/19465) refactor: Removes the CSS files from the DeckGL plugin (@michael-s-molina)
- [#19440](https://github.com/apache/superset/pull/19440) chore: Remove index.less from showSavedQuery (@geido)
- [#19230](https://github.com/apache/superset/pull/19230) chore!: remove `ROW_LEVEL_SECURITY` feature flag (permanently enable) (@suddjian)
- [#19361](https://github.com/apache/superset/pull/19361) chore: remove deprecated config keys and endpoints code 2.0 (@pkdotson)
- [#19261](https://github.com/apache/superset/pull/19261) chore: remove old alerts and configs keys (@pkdotson)
- [#19168](https://github.com/apache/superset/pull/19168) chore: bump celery and Flask (@dpgaspar)
- [#19049](https://github.com/apache/superset/pull/19049) chore: Remove logo forced width (@geido)
- [#19274](https://github.com/apache/superset/pull/19274) chore: remove PUBLIC_ROLE_LIKE_GAMMA deprecated config key (@dpgaspar)
- [#19273](https://github.com/apache/superset/pull/19273) chore: remove deprecated celery cli (@dpgaspar)
- [#19262](https://github.com/apache/superset/pull/19262) chore: update updating with druid no sql deprecation (@eschutho)
- [#19083](https://github.com/apache/superset/pull/19083) chore!: update mutator to take kwargs (@eschutho)
- [#19231](https://github.com/apache/superset/pull/19231) chore!: remove `ENABLE_REACT_CRUD_VIEWS` feature flag (permanently enable) (@suddjian)
- [#19241](https://github.com/apache/superset/pull/19241) chore(superset 2.0): remove front-end deprecated code (@graceguo-supercat)
- [#19107](https://github.com/apache/superset/pull/19107) chore: turn on SQLLAB_BACKEND_PERSISTENCE by default (@ktmud)
- [#19142](https://github.com/apache/superset/pull/19142) chore!: turn on Versioned Export in config.py (@AAfghahi)
- [#19108](https://github.com/apache/superset/pull/19108) chore: Update UPDATING.md with info about flipping dnd feature flag (@kgabryje)
- [#19146](https://github.com/apache/superset/pull/19146) chore!: Remove remove SQLALCHEMY_DOCS_URL and SQLALCHEMY_DISPLAY_TEXT from the config from the config (@hughhhh)
- [#19017](https://github.com/apache/superset/pull/19017) chore: Deprecate Python 3.7 (@john-bodley)
- [#19113](https://github.com/apache/superset/pull/19113) chore(config): Migrating `ENABLE_JAVASCRIPT_CONTROLS` from app config to a feature flag (@rusackas)
- [#19046](https://github.com/apache/superset/pull/19046) chore(explore): Set Drag&Drop feature flags to True by default (@kgabryje)
- [#19016](https://github.com/apache/superset/pull/19016) chore: Adding PR to Updating.md (@AAfghahi)
- [#18970](https://github.com/apache/superset/pull/18970) chore: Change Dataset legacy editor flag to true (@AAfghahi)
### 1.5.1 (Thu May 26 14:45:20 2022 +0300)
**Fixes**

View File

@@ -24,6 +24,14 @@ assists people when migrating to a new version.
## Next
### Breaking Changes
### Potential Downtime
### Other
## 2.0.0
- [19046](https://github.com/apache/superset/pull/19046): Enables the drag and drop interface in Explore control panel by default. Flips `ENABLE_EXPLORE_DRAG_AND_DROP` and `ENABLE_DND_WITH_CLICK_UX` feature flags to `True`.
- [18936](https://github.com/apache/superset/pull/18936): Removes legacy SIP-15 interim logic/flags—specifically the `SIP_15_ENABLED`, `SIP_15_GRACE_PERIOD_END`, `SIP_15_DEFAULT_TIME_RANGE_ENDPOINTS`, and `SIP_15_TOAST_MESSAGE` flags. Time range endpoints are no longer configurable and strictly adhere to the `[start, end)` paradigm, i.e., inclusive of the start and exclusive of the end. Additionally this change removes the now obsolete `time_range_endpoints` from the form-data and resulting in the cache being busted.
- [19570](https://github.com/apache/superset/pull/19570): makes [sqloxide](https://pypi.org/project/sqloxide/) optional so the SIP-68 migration can be run on aarch64. If the migration is taking too long installing sqloxide manually should improve the performance.
@@ -46,10 +54,6 @@ assists people when migrating to a new version.
- [19017](https://github.com/apache/superset/pull/19017): Removes Python 3.7 support.
- [18970](https://github.com/apache/superset/pull/18970): The `DISABLE_LEGACY_DATASOURCE_EDITOR` feature flag is now `True` by default which disables the legacy datasource editor from being shown in the client.
### Potential Downtime
### Other
## 1.5.0
### Breaking Changes
@@ -82,6 +86,7 @@ assists people when migrating to a new version.
## 1.4.1
### Breaking Changes
- [17984](https://github.com/apache/superset/pull/17984): Default Flask SECRET_KEY has changed for security reasons. You should always override with your own secret. Set `PREVIOUS_SECRET_KEY` (ex: PREVIOUS_SECRET_KEY = "\2\1thisismyscretkey\1\2\\e\\y\\y\\h") with your previous key and use `superset re-encrypt-secrets` to rotate you current secrets
### Potential Downtime

View File

@@ -14,7 +14,7 @@ You also need to install MySQL or [MariaDB](https://mariadb.com/downloads).
Ensure that you are using Python version 3.8 or 3.9, then proceed with:
````bash
```bash
# Create a virtual environment and activate it (recommended)
python3 -m venv venv # setup a python3 virtualenv
source venv/bin/activate
@@ -47,18 +47,18 @@ Or you can install via our Makefile
```bash
# Create a virtual environment and activate it (recommended)
$ python3 -m venv venv # setup a python3 virtualenv
$ source venv/bin/activate
python3 -m venv venv # setup a python3 virtualenv
source venv/bin/activate
# install pip packages + pre-commit
$ make install
make install
# Install superset pip packages and setup env only
$ make superset
make superset
# Setup pre-commit only
$ make pre-commit
````
make pre-commit
```
**Note: the FLASK_APP env var should not need to be set, as it's currently controlled
via `.flaskenv`, however if needed, it should be set to `superset.app:create_app()`**
@@ -103,4 +103,5 @@ app.logger.info(form_data)
```
### Frontend Assets
See [Building Frontend Assets Locally](https://github.com/apache/superset/blob/master/CONTRIBUTING.md#frontend)

View File

@@ -129,7 +129,7 @@ Finish installing by running through the following commands:
```
# Create an admin user in your metadata database (use `admin` as username to be able to load the examples)
$ export FLASK_APP=superset
export FLASK_APP=superset
superset fab create-admin
# Load some data to play with

View File

@@ -44,7 +44,7 @@ all of the required dependencies. Docker Desktop [recently added support for Win
following command:
```bash
$ git clone https://github.com/apache/superset.git
git clone https://github.com/apache/superset.git
```
Once that command completes successfully, you should see a new `superset` folder in your
@@ -55,14 +55,14 @@ current directory.
Navigate to the folder you created in step 1:
```bash
$ cd superset
cd superset
```
When working on master branch, run the following commands:
```bash
$ docker-compose -f docker-compose-non-dev.yml pull
$ docker-compose -f docker-compose-non-dev.yml up
docker-compose -f docker-compose-non-dev.yml pull
docker-compose -f docker-compose-non-dev.yml up
```
Alternatively, you can also run a specific version of Superset by first checking out
@@ -70,9 +70,9 @@ the branch/tag, and then starting `docker-compose` with the `TAG` variable.
For example, to run the 1.4.0 version, run the following commands:
```bash
% git checkout 1.4.0
$ TAG=1.4.0 docker-compose -f docker-compose-non-dev.yml pull
$ TAG=1.4.0 docker-compose -f docker-compose-non-dev.yml up
git checkout 1.4.0
TAG=1.4.0 docker-compose -f docker-compose-non-dev.yml pull
TAG=1.4.0 docker-compose -f docker-compose-non-dev.yml up
```
You should see a wall of logging output from the containers being launched on your machine. Once

View File

@@ -13,7 +13,7 @@ geospatial charts.
Here are a **few different ways you can get started with Superset**:
- Download the [source from Apache Foundation's website](https://dist.apache.org/repos/dist/release/superset/1.4.1/)
- Download the [source from Apache Foundation's website](https://dist.apache.org/repos/dist/release/superset/)
- Download the latest Superset version from [Pypi here](https://pypi.org/project/apache-superset/)
- Setup Superset locally with one command
using [Docker Compose](installation/installing-superset-using-docker-compose)

View File

@@ -86,7 +86,7 @@ flask==2.0.3
# flask-migrate
# flask-sqlalchemy
# flask-wtf
flask-appbuilder==4.0.0
flask-appbuilder==4.1.3
# via apache-superset
flask-babel==1.0.0
# via flask-appbuilder

View File

@@ -77,7 +77,7 @@ setup(
"cryptography>=3.3.2",
"deprecation>=2.1.0, <2.2.0",
"flask>=2.0.0, <3.0.0",
"flask-appbuilder>=4.0.0, <5.0.0",
"flask-appbuilder>=4.1.3, <5.0.0",
"flask-caching>=1.10.0",
"flask-compress",
"flask-talisman",
@@ -149,7 +149,7 @@ setup(
"impala": ["impyla>0.16.2, <0.17"],
"kusto": ["sqlalchemy-kusto>=1.0.1, <2"],
"kylin": ["kylinpy>=2.8.1, <2.9"],
"mmsql": ["pymssql>=2.1.4, <2.2"],
"mssql": ["pymssql>=2.1.4, <2.2"],
"mysql": ["mysqlclient>=2.1.0, <3"],
"oracle": ["cx-Oracle>8.0.0, <8.1"],
"pinot": ["pinotdb>=0.3.3, <0.4"],

View File

@@ -66,7 +66,7 @@
"d3-array": "^1.2.4",
"d3-color": "^1.2.0",
"d3-scale": "^2.1.2",
"dom-to-image": "^2.6.0",
"dom-to-image-more": "^2.10.1",
"emotion-rgba": "0.0.9",
"fast-glob": "^3.2.7",
"fontsource-fira-code": "^4.0.0",
@@ -177,7 +177,6 @@
"@testing-library/react-hooks": "^5.0.3",
"@testing-library/user-event": "^12.7.0",
"@types/classnames": "^2.2.10",
"@types/dom-to-image": "^2.6.0",
"@types/enzyme": "^3.10.5",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/fetch-mock": "^7.3.2",
@@ -16736,15 +16735,6 @@
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.1.tgz",
"integrity": "sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA=="
},
"node_modules/@types/dom-to-image": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.0.tgz",
"integrity": "sha512-X7qEh5AI1s/fb/Ijb1WU/tl7nZjD/A3b0PZiq3QjD31EZkgPooPdpte9MCJWQgqjxA0F8AJFuPd53YDrFJFE7w==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/enzyme": {
"version": "3.10.10",
"resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.10.tgz",
@@ -16970,9 +16960,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ=="
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
"integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA=="
},
"node_modules/@types/node-fetch": {
"version": "2.6.1",
@@ -26331,10 +26321,10 @@
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
},
"node_modules/dom-to-image": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz",
"integrity": "sha1-ilA2CAiMh7HCL5A0rgMuGJiVWGc="
"node_modules/dom-to-image-more": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.10.1.tgz",
"integrity": "sha512-gMG28V47WGj5/xvrsbSPJAWSaV7CBh4teLErn1iGD1sa29HsFsHxvnoLj8VxVvfqnjPgsiUGs2IV2VAxLJGb+A=="
},
"node_modules/dom-walk": {
"version": "0.1.1",
@@ -54322,6 +54312,7 @@
"@types/fetch-mock": "^7.3.3",
"@types/lodash": "^4.14.149",
"@types/math-expression-evaluator": "^1.2.1",
"@types/node": "^18.0.0",
"@types/prop-types": "^15.7.2",
"@types/rison": "0.0.6",
"@types/seedrandom": "^2.4.28",
@@ -67442,6 +67433,7 @@
"@types/fetch-mock": "^7.3.3",
"@types/lodash": "^4.14.149",
"@types/math-expression-evaluator": "^1.2.1",
"@types/node": "^18.0.0",
"@types/prop-types": "^15.7.2",
"@types/rison": "0.0.6",
"@types/seedrandom": "^2.4.28",
@@ -68802,15 +68794,6 @@
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.1.tgz",
"integrity": "sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA=="
},
"@types/dom-to-image": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.0.tgz",
"integrity": "sha512-X7qEh5AI1s/fb/Ijb1WU/tl7nZjD/A3b0PZiq3QjD31EZkgPooPdpte9MCJWQgqjxA0F8AJFuPd53YDrFJFE7w==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/enzyme": {
"version": "3.10.10",
"resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.10.tgz",
@@ -69036,9 +69019,9 @@
"dev": true
},
"@types/node": {
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ=="
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
"integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA=="
},
"@types/node-fetch": {
"version": "2.6.1",
@@ -76394,10 +76377,10 @@
}
}
},
"dom-to-image": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz",
"integrity": "sha1-ilA2CAiMh7HCL5A0rgMuGJiVWGc="
"dom-to-image-more": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.10.1.tgz",
"integrity": "sha512-gMG28V47WGj5/xvrsbSPJAWSaV7CBh4teLErn1iGD1sa29HsFsHxvnoLj8VxVvfqnjPgsiUGs2IV2VAxLJGb+A=="
},
"dom-walk": {
"version": "0.1.1",

View File

@@ -1,6 +1,6 @@
{
"name": "superset",
"version": "0.0.0dev",
"version": "2.0.0",
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
"keywords": [
"big",
@@ -126,7 +126,7 @@
"d3-array": "^1.2.4",
"d3-color": "^1.2.0",
"d3-scale": "^2.1.2",
"dom-to-image": "^2.6.0",
"dom-to-image-more": "^2.10.1",
"emotion-rgba": "0.0.9",
"fast-glob": "^3.2.7",
"fontsource-fira-code": "^4.0.0",
@@ -237,7 +237,6 @@
"@testing-library/react-hooks": "^5.0.3",
"@testing-library/user-event": "^12.7.0",
"@types/classnames": "^2.2.10",
"@types/dom-to-image": "^2.6.0",
"@types/enzyme": "^3.10.5",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/fetch-mock": "^7.3.2",

View File

@@ -363,6 +363,10 @@ export interface ControlPanelConfig {
standardizedFormData: StandardizedFormDataInterface;
},
) => QueryFormData;
updateStandardizedState?: (
prevState: StandardizedState,
currState: StandardizedState,
) => StandardizedState;
}
export type ControlOverrides = {

View File

@@ -37,16 +37,21 @@ export const getOpacity = (
extremeValue: number,
minOpacity = MIN_OPACITY_BOUNDED,
maxOpacity = MAX_OPACITY,
) =>
extremeValue === cutoffPoint
? maxOpacity
: round(
Math.abs(
((maxOpacity - minOpacity) / (extremeValue - cutoffPoint)) *
(value - cutoffPoint),
) + minOpacity,
2,
);
) => {
if (extremeValue === cutoffPoint) {
return maxOpacity;
}
return Math.min(
maxOpacity,
round(
Math.abs(
((maxOpacity - minOpacity) / (extremeValue - cutoffPoint)) *
(value - cutoffPoint),
) + minOpacity,
2,
),
);
};
export const getColorFunction = (
{

View File

@@ -50,6 +50,9 @@ describe('getOpacity', () => {
expect(getOpacity(100, 100, 50)).toEqual(0.05);
expect(getOpacity(100, 100, 100, 0, 0.8)).toEqual(0.8);
expect(getOpacity(100, 100, 50, 0, 1)).toEqual(0);
expect(getOpacity(999, 100, 50, 0, 1)).toEqual(1);
expect(getOpacity(100, 100, 50, 0.99, 1)).toEqual(0.99);
expect(getOpacity(99, 100, 50, 0, 1)).toEqual(0.02);
});
});

View File

@@ -40,6 +40,7 @@
"@types/d3-time-format": "^2.1.0",
"@types/lodash": "^4.14.149",
"@types/math-expression-evaluator": "^1.2.1",
"@types/node": "^18.0.0",
"@types/rison": "0.0.6",
"@types/seedrandom": "^2.4.28",
"@types/fetch-mock": "^7.3.3",

View File

@@ -41,7 +41,7 @@ export default function FallbackComponent({
>
<div>
<div>
<b>Oops! An error occured!</b>
<b>Oops! An error occurred!</b>
</div>
<code>{error ? error.toString() : 'Unknown Error'}</code>
</div>

View File

@@ -45,6 +45,7 @@ const SupersetClient: SupersetClientInterface = {
init: force => getInstance().init(force),
isAuthenticated: () => getInstance().isAuthenticated(),
post: request => getInstance().post(request),
postForm: (...args) => getInstance().postForm(...args),
put: request => getInstance().put(request),
reAuthenticate: () => getInstance().reAuthenticate(),
request: request => getInstance().request(request),

View File

@@ -119,6 +119,36 @@ export default class SupersetClientClass {
return this.getCSRFToken();
}
async postForm(url: string, payload: Record<string, any>, target = '_blank') {
if (url) {
await this.ensureAuth();
const hiddenForm = document.createElement('form');
hiddenForm.action = url;
hiddenForm.method = 'POST';
hiddenForm.target = target;
const payloadWithToken: Record<string, any> = {
...payload,
csrf_token: this.csrfToken!,
};
if (this.guestToken) {
payloadWithToken.guest_token = this.guestToken;
}
Object.entries(payloadWithToken).forEach(([key, value]) => {
const data = document.createElement('input');
data.type = 'hidden';
data.name = key;
data.value = value;
hiddenForm.appendChild(data);
});
document.body.appendChild(hiddenForm);
hiddenForm.submit();
document.body.removeChild(hiddenForm);
}
}
async reAuthenticate() {
return this.init(true);
}

View File

@@ -146,6 +146,7 @@ export interface SupersetClientInterface
| 'delete'
| 'get'
| 'post'
| 'postForm'
| 'put'
| 'request'
| 'init'

View File

@@ -56,7 +56,7 @@ export default class Translator {
*/
addTranslation(key: string, texts: ReadonlyArray<string>) {
const translations = this.i18n.options.locale_data.superset;
if (key in translations) {
if (process.env.WEBPACK_MODE !== 'test' && key in translations) {
logging.warn(`Duplicate translation key "${key}", will override.`);
}
translations[key] = texts;

View File

@@ -121,7 +121,7 @@ describe('SuperChart', () => {
);
await new Promise(resolve => setImmediate(resolve));
wrapper.update();
expect(wrapper.text()).toContain('Oops! An error occured!');
expect(wrapper.text()).toContain('Oops! An error occurred!');
});
it('renders custom FallbackComponent', () => {
expectedErrors = 1;

View File

@@ -30,21 +30,23 @@ describe('SupersetClient', () => {
afterEach(SupersetClient.reset);
it('exposes reset, configure, init, get, post, isAuthenticated, and reAuthenticate methods', () => {
it('exposes reset, configure, init, get, post, postForm, isAuthenticated, and reAuthenticate methods', () => {
expect(typeof SupersetClient.configure).toBe('function');
expect(typeof SupersetClient.init).toBe('function');
expect(typeof SupersetClient.get).toBe('function');
expect(typeof SupersetClient.post).toBe('function');
expect(typeof SupersetClient.postForm).toBe('function');
expect(typeof SupersetClient.isAuthenticated).toBe('function');
expect(typeof SupersetClient.reAuthenticate).toBe('function');
expect(typeof SupersetClient.request).toBe('function');
expect(typeof SupersetClient.reset).toBe('function');
});
it('throws if you call init, get, post, isAuthenticated, or reAuthenticate before configure', () => {
it('throws if you call init, get, post, postForm, isAuthenticated, or reAuthenticate before configure', () => {
expect(SupersetClient.init).toThrow();
expect(SupersetClient.get).toThrow();
expect(SupersetClient.post).toThrow();
expect(SupersetClient.postForm).toThrow();
expect(SupersetClient.isAuthenticated).toThrow();
expect(SupersetClient.reAuthenticate).toThrow();
expect(SupersetClient.request).toThrow();

View File

@@ -605,4 +605,107 @@ describe('SupersetClientClass', () => {
}
});
});
describe('.postForm()', () => {
const protocol = 'https:';
const host = 'host';
const mockPostFormEndpoint = '/post_form/url';
const mockPostFormUrl = `${protocol}//${host}${mockPostFormEndpoint}`;
const guestToken = 'test-guest-token';
const postFormPayload = { number: 123, array: [1, 2, 3] };
let authSpy: jest.SpyInstance;
let client: SupersetClientClass;
let appendChild: any;
let removeChild: any;
let submit: any;
let createElement: any;
beforeEach(async () => {
client = new SupersetClientClass({ protocol, host });
authSpy = jest.spyOn(SupersetClientClass.prototype, 'ensureAuth');
await client.init();
appendChild = jest.fn();
removeChild = jest.fn();
submit = jest.fn();
createElement = jest.fn(() => ({
appendChild: jest.fn(),
submit,
}));
document.createElement = createElement as any;
document.body.appendChild = appendChild;
document.body.removeChild = removeChild;
});
afterEach(() => {
jest.restoreAllMocks();
});
it('makes postForm request', async () => {
await client.postForm(mockPostFormUrl, {});
const hiddenForm = createElement.mock.results[0].value;
const csrfTokenInput = createElement.mock.results[1].value;
expect(createElement.mock.calls).toHaveLength(2);
expect(hiddenForm.action).toBe(mockPostFormUrl);
expect(hiddenForm.method).toBe('POST');
expect(hiddenForm.target).toBe('_blank');
expect(csrfTokenInput.type).toBe('hidden');
expect(csrfTokenInput.name).toBe('csrf_token');
expect(csrfTokenInput.value).toBe(1234);
expect(appendChild.mock.calls).toHaveLength(1);
expect(removeChild.mock.calls).toHaveLength(1);
expect(authSpy).toHaveBeenCalledTimes(1);
});
it('makes postForm request with guest token', async () => {
client = new SupersetClientClass({ protocol, host, guestToken });
await client.init();
await client.postForm(mockPostFormUrl, {});
const guestTokenInput = createElement.mock.results[2].value;
expect(createElement.mock.calls).toHaveLength(3);
expect(guestTokenInput.type).toBe('hidden');
expect(guestTokenInput.name).toBe('guest_token');
expect(guestTokenInput.value).toBe(guestToken);
expect(appendChild.mock.calls).toHaveLength(1);
expect(removeChild.mock.calls).toHaveLength(1);
expect(authSpy).toHaveBeenCalledTimes(1);
});
it('makes postForm request with payload', async () => {
await client.postForm(mockPostFormUrl, { form_data: postFormPayload });
const postFormPayloadInput = createElement.mock.results[1].value;
expect(createElement.mock.calls).toHaveLength(3);
expect(postFormPayloadInput.type).toBe('hidden');
expect(postFormPayloadInput.name).toBe('form_data');
expect(postFormPayloadInput.value).toBe(postFormPayload);
expect(appendChild.mock.calls).toHaveLength(1);
expect(removeChild.mock.calls).toHaveLength(1);
expect(submit.mock.calls).toHaveLength(1);
expect(authSpy).toHaveBeenCalledTimes(1);
});
it('should do nothing when url is empty string', async () => {
const result = await client.postForm('', {});
expect(result).toBeUndefined();
expect(createElement.mock.calls).toHaveLength(0);
expect(appendChild.mock.calls).toHaveLength(0);
expect(removeChild.mock.calls).toHaveLength(0);
expect(authSpy).toHaveBeenCalledTimes(0);
});
});
});

View File

@@ -41,10 +41,12 @@ describe('Translator', () => {
spy.mockImplementation((info: any) => {
throw new Error(info);
});
process.env.WEBPACK_MODE = 'production';
});
afterAll(() => {
spy.mockRestore();
process.env.WEBPACK_MODE = 'test';
});
describe('new Translator(config)', () => {

View File

@@ -100,4 +100,8 @@ export default {
...formData,
metric: formData.standardizedFormData.standardizedState.metrics[0],
}),
updateStandardizedState: (prevState, currState) => ({
...currState,
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
}),
} as ControlPanelConfig;

View File

@@ -152,7 +152,7 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
document.body.append(container);
const fontSize = computeMaxFontSize({
text,
maxWidth: width,
maxWidth: width - 8, // Decrease 8px for more precise font size
maxHeight,
className: 'header-line',
container,

View File

@@ -274,6 +274,10 @@ const config: ControlPanelConfig = {
...formData,
metric: formData.standardizedFormData.standardizedState.metrics[0],
}),
updateStandardizedState: (prevState, currState) => ({
...currState,
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
}),
};
export default config;

View File

@@ -148,6 +148,10 @@ const config: ControlPanelConfig = {
metric: formData.standardizedFormData.standardizedState.metrics[0],
groupby: formData.standardizedFormData.standardizedState.columns,
}),
updateStandardizedState: (prevState, currState) => ({
...currState,
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
}),
};
export default config;

View File

@@ -313,6 +313,10 @@ const config: ControlPanelConfig = {
metric: formData.standardizedFormData.standardizedState.metrics[0],
groupby: formData.standardizedFormData.standardizedState.columns,
}),
updateStandardizedState: (prevState, currState) => ({
...currState,
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
}),
};
export default config;

View File

@@ -324,6 +324,10 @@ const controlPanel: ControlPanelConfig = {
...formData,
metric: formData.standardizedFormData.standardizedState.metrics[0],
}),
updateStandardizedState: (prevState, currState) => ({
...currState,
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
}),
};
export default controlPanel;

View File

@@ -260,6 +260,10 @@ const config: ControlPanelConfig = {
row_limit:
ensureIsInt(formData.row_limit, 100) >= 100 ? 100 : formData.row_limit,
}),
updateStandardizedState: (prevState, currState) => ({
...currState,
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
}),
};
export default config;

View File

@@ -45,8 +45,6 @@ import {
const {
contributionMode,
logAxis,
markerEnabled,
markerSize,
minorSplitLine,
rowLimit,
truncateYAxis,
@@ -341,38 +339,6 @@ const config: ControlPanelConfig = {
controlSetRows: [
['color_scheme'],
...showValueSection,
[
{
name: 'markerEnabled',
config: {
type: 'CheckboxControl',
label: t('Marker'),
renderTrigger: true,
default: markerEnabled,
description: t(
'Draw a marker on data points. Only applicable for line types.',
),
},
},
],
[
{
name: 'markerSize',
config: {
type: 'SliderControl',
label: t('Marker Size'),
renderTrigger: true,
min: 0,
max: 20,
default: markerSize,
description: t(
'Size of marker. Also applies to forecast observations.',
),
visibility: ({ controls }: ControlPanelsContainerProps) =>
Boolean(controls?.markerEnabled?.value),
},
},
],
[
{
name: 'zoomable',

View File

@@ -289,6 +289,10 @@ const controlPanel: ControlPanelConfig = {
...formData,
metric: formData.standardizedFormData.standardizedState.metrics[0],
}),
updateStandardizedState: (prevState, currState) => ({
...currState,
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
}),
};
export default controlPanel;

View File

@@ -142,6 +142,10 @@ const config: ControlPanelConfig = {
metric: formData.standardizedFormData.standardizedState.metrics[0],
groupby: formData.standardizedFormData.standardizedState.columns,
}),
updateStandardizedState: (prevState, currState) => ({
...currState,
metrics: [currState.metrics[0], ...prevState.metrics.slice(1)],
}),
};
export default config;

View File

@@ -33,5 +33,5 @@ export const sortAlphanumericCaseInsensitive = <D extends {}>(
if (!valueB || typeof valueB !== 'string') {
return 1;
}
return valueA.localeCompare(valueB) > 0 ? 1 : -1;
return valueA.localeCompare(valueB);
};

View File

@@ -394,6 +394,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
colorPositiveNegative,
})
: undefined)};
white-space: ${value instanceof Date ? 'nowrap' : undefined};
`;
const cellProps = {
@@ -449,7 +450,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
data-column-name={col.id}
css={{
display: 'inline-flex',
alignItems: 'center',
alignItems: 'flex-end',
}}
>
<span data-column-name={col.id}>{label}</span>

View File

@@ -17,8 +17,13 @@
* under the License.
*/
import { defaultOrderByFn, Row } from 'react-table';
import { sortAlphanumericCaseInsensitive } from '../src/DataTable/utils/sortAlphanumericCaseInsensitive';
type RecursivePartial<T> = {
[P in keyof T]?: T[P] | RecursivePartial<T[P]>;
};
const testData = [
{
values: {
@@ -133,3 +138,106 @@ describe('sortAlphanumericCaseInsensitive', () => {
]);
});
});
const testDataMulti: Array<RecursivePartial<Row<object>>> = [
{
values: {
colA: 'group 1',
colB: '10',
},
},
{
values: {
colA: 'group 1',
colB: '15',
},
},
{
values: {
colA: 'group 1',
colB: '20',
},
},
{
values: {
colA: 'group 2',
colB: '10',
},
},
{
values: {
colA: 'group 3',
colB: '10',
},
},
{
values: {
colA: 'group 3',
colB: '15',
},
},
{
values: {
colA: 'group 3',
colB: '10',
},
},
];
describe('sortAlphanumericCaseInsensitiveMulti', () => {
it('Sort rows', () => {
const sorted = defaultOrderByFn(
[...testDataMulti] as Array<Row<object>>,
[
(a, b) => sortAlphanumericCaseInsensitive(a, b, 'colA'),
(a, b) => sortAlphanumericCaseInsensitive(a, b, 'colB'),
],
[true, false],
);
expect(sorted).toEqual([
{
values: {
colA: 'group 1',
colB: '20',
},
},
{
values: {
colA: 'group 1',
colB: '15',
},
},
{
values: {
colA: 'group 1',
colB: '10',
},
},
{
values: {
colA: 'group 2',
colB: '10',
},
},
{
values: {
colA: 'group 3',
colB: '15',
},
},
{
values: {
colA: 'group 3',
colB: '10',
},
},
{
values: {
colA: 'group 3',
colB: '10',
},
},
]);
});
});

View File

@@ -81,3 +81,5 @@ setupSupersetClient();
jest.mock('src/hooks/useTabId', () => ({
useTabId: () => 1,
}));
process.env.WEBPACK_MODE = 'test';

View File

@@ -27,7 +27,6 @@ import {
getExploreUrl,
getLegacyEndpointType,
buildV1ChartDataPayload,
postForm,
shouldUseLegacyApi,
getChartDataUri,
} from 'src/explore/exploreUtils';
@@ -40,6 +39,7 @@ import { addDangerToast } from 'src/components/MessageToasts/actions';
import { logEvent } from 'src/logger/actions';
import { Logger, LOG_ACTIONS_LOAD_CHART } from 'src/logger/LogUtils';
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import { safeStringify } from 'src/utils/safeStringify';
import { allowCrossDomain as domainShardingEnabled } from 'src/utils/hostNamesConfig';
import { updateDataMask } from 'src/dataMask/actions';
import { waitForAsyncData } from 'src/middleware/asyncEvent';
@@ -563,7 +563,9 @@ export function redirectSQLLab(formData) {
datasourceKey: formData.datasource,
sql: json.result[0].query,
};
postForm(redirectUrl, payload);
SupersetClient.postForm(redirectUrl, {
form_data: safeStringify(payload),
});
})
.catch(() =>
dispatch(addDangerToast(t('An error occurred while loading the SQL'))),

View File

@@ -130,6 +130,13 @@ const CrudButtonWrapper = styled.div`
${({ theme }) => `margin-bottom: ${theme.gridUnit * 2}px`}
`;
const StyledButtonWrapper = styled.span`
${({ theme }) => `
margin-top: ${theme.gridUnit * 3}px;
margin-left: ${theme.gridUnit * 3}px;
`}
`;
export default class CRUDCollection extends React.PureComponent<
CRUDCollectionProps,
CRUDCollectionState
@@ -424,7 +431,7 @@ export default class CRUDCollection extends React.PureComponent<
<>
<CrudButtonWrapper>
{this.props.allowAddItem && (
<span className="m-t-10 m-r-10">
<StyledButtonWrapper>
<Button
buttonSize="small"
buttonStyle="tertiary"
@@ -434,7 +441,7 @@ export default class CRUDCollection extends React.PureComponent<
<i data-test="crud-add-table-item" className="fa fa-plus" />{' '}
{t('Add item')}
</Button>
</span>
</StyledButtonWrapper>
)}
</CrudButtonWrapper>
<CrudTableWrapper

View File

@@ -123,6 +123,13 @@ const StyledColumnsTabWrapper = styled.div`
}
`;
const StyledButtonWrapper = styled.span`
${({ theme }) => `
margin-top: ${theme.gridUnit * 3}px;
margin-left: ${theme.gridUnit * 3}px;
`}
`;
const checkboxGenerator = (d, onChange) => (
<CheckboxControl value={d} onChange={onChange} />
);
@@ -1361,7 +1368,7 @@ class DatasourceEditor extends React.PureComponent {
>
<StyledColumnsTabWrapper>
<ColumnButtonWrapper>
<span className="m-t-10 m-r-10">
<StyledButtonWrapper>
<Button
buttonSize="small"
buttonStyle="tertiary"
@@ -1372,7 +1379,7 @@ class DatasourceEditor extends React.PureComponent {
<i className="fa fa-database" />{' '}
{t('Sync columns from source')}
</Button>
</span>
</StyledButtonWrapper>
</ColumnButtonWrapper>
<ColumnCollectionTable
className="columns-table"

View File

@@ -31,9 +31,13 @@ export const DOCUMENTATION_LINK = supersetTextDocs
export interface IProps {
errorMessage: string;
showDbInstallInstructions: boolean;
}
const ErrorAlert: FunctionComponent<IProps> = ({ errorMessage }) => (
const ErrorAlert: FunctionComponent<IProps> = ({
errorMessage,
showDbInstallInstructions,
}) => (
<Alert
closable={false}
css={(theme: SupersetTheme) => antdWarningAlertStyles(theme)}
@@ -41,21 +45,25 @@ const ErrorAlert: FunctionComponent<IProps> = ({ errorMessage }) => (
showIcon
message={errorMessage}
description={
<>
<br />
{t(
'Database driver for importing maybe not installed. Visit the Superset documentation page for installation instructions:',
)}
<a
href={DOCUMENTATION_LINK}
target="_blank"
rel="noopener noreferrer"
className="additional-fields-alert-description"
>
{t('here')}
</a>
.
</>
showDbInstallInstructions ? (
<>
<br />
{t(
'Database driver for importing maybe not installed. Visit the Superset documentation page for installation instructions:',
)}
<a
href={DOCUMENTATION_LINK}
target="_blank"
rel="noopener noreferrer"
className="additional-fields-alert-description"
>
{t('here')}
</a>
.
</>
) : (
''
)
}
/>
);

View File

@@ -300,7 +300,12 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({
<Button loading={importingModel}>Select file</Button>
</Upload>
</StyledInputContainer>
{errorMessage && <ErrorAlert errorMessage={errorMessage} />}
{errorMessage && (
<ErrorAlert
errorMessage={errorMessage}
showDbInstallInstructions={passwordFields.length > 0}
/>
)}
{renderPasswordFields()}
{renderOverwriteConfirmation()}
</Modal>

View File

@@ -25,8 +25,8 @@ const MenuItem = styled(AntdMenu.Item)`
}
&.ant-menu-item {
height: ${({ theme }) => theme.gridUnit * 7}px;
line-height: ${({ theme }) => theme.gridUnit * 7}px;
height: ${({ theme }) => theme.gridUnit * 8}px;
line-height: ${({ theme }) => theme.gridUnit * 8}px;
a {
border-bottom: none;
transition: background-color ${({ theme }) => theme.transitionTiming}s;

View File

@@ -325,7 +325,7 @@ export function saveDashboardRequest(data, id, saveType) {
const onError = async response => {
const { error, message } = await getClientErrorObject(response);
let errorText = t('Sorry, an unknown error occured');
let errorText = t('Sorry, an unknown error occurred');
if (error) {
errorText = t(

View File

@@ -127,6 +127,8 @@ export const hydrateDashboard =
const dashboardFilters = {};
const slices = {};
const sliceIds = new Set();
const slicesFromExploreCount = new Map();
chartData.forEach(slice => {
const key = slice.slice_id;
const form_data = {
@@ -182,6 +184,10 @@ export const hydrateDashboard =
(newSlicesContainer.parents || []).slice(),
);
const count = (slicesFromExploreCount.get(slice.slice_id) ?? 0) + 1;
chartHolder.id = `${CHART_TYPE}-explore-${slice.slice_id}-${count}`;
slicesFromExploreCount.set(slice.slice_id, count);
layout[chartHolder.id] = chartHolder;
newSlicesContainer.children.push(chartHolder.id);
chartIdToLayoutId[chartHolder.meta.chartId] = chartHolder.id;

View File

@@ -174,7 +174,6 @@ class HeaderActionsDropdown extends React.PureComponent {
downloadAsImage(
SCREENSHOT_NODE_SELECTOR,
this.props.dashboardTitle,
{},
true,
)(domEvent).then(() => {
menu.style.visibility = 'visible';

View File

@@ -334,7 +334,7 @@ class SliceHeaderControls extends React.PureComponent<
<ModalTrigger
triggerNode={
<span data-test="view-query-menu-item">
{t('Drill to detail')}
{t('View as table')}
</span>
}
modalTitle={t('Chart Data: %s', slice.slice_name)}

View File

@@ -21,7 +21,6 @@ import React from 'react';
import { render, screen, act } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { SupersetClient, DatasourceType } from '@superset-ui/core';
import * as Utils from 'src/explore/exploreUtils';
import DatasourceControl from '.';
const SupersetClientGet = jest.spyOn(SupersetClient, 'get');
@@ -142,7 +141,7 @@ test('Click on Edit dataset', async () => {
test('Click on View in SQL Lab', async () => {
const props = createProps();
const postFormSpy = jest.spyOn(Utils, 'postForm');
const postFormSpy = jest.spyOn(SupersetClient, 'postForm');
postFormSpy.mockImplementation(jest.fn());
render(<DatasourceControl {...props} />, {

View File

@@ -19,7 +19,13 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import { t, styled, withTheme, DatasourceType } from '@superset-ui/core';
import {
DatasourceType,
SupersetClient,
styled,
t,
withTheme,
} from '@superset-ui/core';
import { getUrlParam } from 'src/utils/urlUtils';
import { AntdDropdown } from 'src/components';
@@ -30,13 +36,13 @@ import {
ChangeDatasourceModal,
DatasourceModal,
} from 'src/components/Datasource';
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
import { postForm } from 'src/explore/exploreUtils';
import Button from 'src/components/Button';
import ErrorAlert from 'src/components/ErrorMessage/ErrorAlert';
import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
import { URL_PARAMS } from 'src/constants';
import { isUserAdmin } from 'src/dashboard/util/findPermission';
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
import { safeStringify } from 'src/utils/safeStringify';
const propTypes = {
actions: PropTypes.object.isRequired,
@@ -193,7 +199,9 @@ class DatasourceControl extends React.PureComponent {
datasourceKey: `${datasource.id}__${datasource.type}`,
sql: datasource.sql,
};
postForm('/superset/sqllab/', payload);
SupersetClient.postForm('/superset/sqllab/', {
form_data: safeStringify(payload),
});
}
break;

View File

@@ -203,7 +203,6 @@ export const useExploreAdditionalActionsMenu = (
'.panel-body .chart-container',
// eslint-disable-next-line camelcase
slice?.slice_name ?? t('New chart'),
{},
true,
)(domEvent);
setIsDropdownVisible(false);

View File

@@ -28,15 +28,46 @@ import {
import { xAxisControl } from '../../../plugins/plugin-chart-echarts/src/controls';
describe('should collect control values and create SFD', () => {
const sharedControlsFormData = {};
Object.entries(sharedControls).forEach(([, names]) => {
names.forEach(name => {
sharedControlsFormData[name] = name;
});
});
const publicControlsFormData = Object.fromEntries(
publicControls.map((name, idx) => [[name], idx]),
);
const sharedControlsFormData = {
// metrics
metric: 'm1',
metrics: ['m2'],
metric_2: 'm3',
// columns
groupby: ['c1'],
columns: ['c2'],
groupbyColumns: ['c3'],
groupbyRows: ['c4'],
};
const publicControlsFormData = {
// time section
granularity_sqla: 'time_column',
time_grain_sqla: 'P1D',
time_range: '2000 : today',
// filters
adhoc_filters: [],
// subquery limit(series limit)
limit: 5,
// order by clause
timeseries_limit_metric: 'orderby_metric',
series_limit_metric: 'orderby_metric',
// desc or asc in order by clause
order_desc: true,
// outer query limit
row_limit: 100,
// x asxs column
x_axis: 'x_axis_column',
// advanced analytics - rolling window
rolling_type: 'sum',
rolling_periods: 1,
min_periods: 0,
// advanced analytics - time comparison
time_compare: '1 year ago',
comparison_type: 'values',
// advanced analytics - resample
resample_rule: '1D',
resample_method: 'zerofill',
};
const sourceMockFormData: QueryFormData = {
...sharedControlsFormData,
...publicControlsFormData,
@@ -90,26 +121,45 @@ describe('should collect control values and create SFD', () => {
});
});
test('collect sharedControls', () => {
const sfd = new StandardizedFormData(sourceMockFormData);
expect(sfd.dumpSFD().standardizedState.metrics).toEqual(
sharedControls.metrics.map(controlName => controlName),
);
expect(sfd.dumpSFD().standardizedState.columns).toEqual(
sharedControls.columns.map(controlName => controlName),
);
test('should avoid to overlap', () => {
const sharedControlsSet = new Set(Object.keys(sharedControls));
const publicControlsSet = new Set(publicControls);
expect(
[...sharedControlsSet].filter((x: string) => publicControlsSet.has(x)),
).toEqual([]);
});
test('should transform all publicControls', () => {
test('should collect all sharedControls', () => {
expect(Object.entries(sharedControlsFormData).length).toBe(
Object.entries(sharedControls).length,
);
const sfd = new StandardizedFormData(sourceMockFormData);
expect(sfd.serialize().standardizedState.metrics).toEqual([
'm1',
'm2',
'm3',
]);
expect(sfd.serialize().standardizedState.columns).toEqual([
'c1',
'c2',
'c3',
'c4',
]);
});
test('should transform all publicControls and sharedControls', () => {
expect(Object.entries(publicControlsFormData).length).toBe(
publicControls.length,
);
const sfd = new StandardizedFormData(sourceMockFormData);
const { formData } = sfd.transform('target_viz', sourceMockStore);
Object.entries(publicControlsFormData).forEach(([key]) => {
Object.entries(publicControlsFormData).forEach(([key, value]) => {
expect(formData).toHaveProperty(key);
expect(value).toEqual(publicControlsFormData[key]);
});
Object.entries(sharedControls).forEach(([key, value]) => {
expect(formData[key]).toEqual(value);
});
expect(formData.columns).toEqual(['c1', 'c2', 'c3', 'c4']);
expect(formData.metrics).toEqual(['m1', 'm2', 'm3']);
});
test('should inherit standardizedFormData and memorizedFormData is LIFO', () => {
@@ -157,6 +207,7 @@ describe('should transform form_data between table and bigNumberTotal', () => {
const tableVizFormData = {
datasource: '30__table',
viz_type: 'table',
granularity_sqla: 'ds',
time_grain_sqla: 'P1D',
time_range: 'No filter',
query_mode: 'aggregate',
@@ -172,7 +223,6 @@ describe('should transform form_data between table and bigNumberTotal', () => {
table_timestamp_format: 'smart_date',
show_cell_bars: true,
color_pn: true,
applied_time_extras: {},
url_params: {
form_data_key:
'p3No_sqDW7k-kMTzlBPAPd9vwp1IXTf6stbyzjlrPPa0ninvdYUUiMC6F1iKit3Y',
@@ -197,7 +247,9 @@ describe('should transform form_data between table and bigNumberTotal', () => {
dataset_id: '30',
},
},
granularity_sqla: {},
granularity_sqla: {
value: 'ds',
},
time_grain_sqla: {
value: 'P1D',
},
@@ -271,6 +323,22 @@ describe('should transform form_data between table and bigNumberTotal', () => {
);
});
test('get and has', () => {
// table -> bigNumberTotal
const sfd = new StandardizedFormData(tableVizFormData);
const { formData: bntFormData } = sfd.transform(
'big_number_total',
tableVizStore,
);
// bigNumberTotal -> table
const sfd2 = new StandardizedFormData(bntFormData);
expect(sfd2.has('big_number_total')).toBeTruthy();
expect(sfd2.has('table')).toBeTruthy();
expect(sfd2.get('big_number_total').viz_type).toBe('big_number_total');
expect(sfd2.get('table').viz_type).toBe('table');
});
test('transform', () => {
// table -> bigNumberTotal
const sfd = new StandardizedFormData(tableVizFormData);
@@ -301,7 +369,7 @@ describe('should transform form_data between table and bigNumberTotal', () => {
);
expect(tblFormData.viz_type).toBe('table');
expect(tblFormData.metrics).toEqual(['sum(sales)']);
expect(tblFormData.groupby).toEqual([]);
expect(tblFormData.groupby).toEqual(['name']);
expect(tblFormData.time_range).toBe('2021 : 2022');
});
});

View File

@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { isEmpty, intersection } from 'lodash';
import {
ensureIsArray,
getChartControlPanelRegistry,
@@ -29,38 +30,52 @@ import {
import { getControlsState } from 'src/explore/store';
import { getFormDataFromControls } from './getFormDataFromControls';
export const sharedControls: Record<keyof StandardizedState, string[]> = {
metrics: ['metric', 'metrics', 'metric_2'],
columns: ['groupby', 'columns', 'groupbyColumns', 'groupbyRows'],
export const sharedControls: Record<string, keyof StandardizedState> = {
// metrics
metric: 'metrics', // via sharedControls, scalar
metrics: 'metrics', // via sharedControls, array
metric_2: 'metrics', // via sharedControls, scalar
// columns
groupby: 'columns', // via sharedControls, array
columns: 'columns', // via sharedControls, array
groupbyColumns: 'columns', // via pivot table v2, array
groupbyRows: 'columns', // via pivot table v2, array
};
const sharedControlsMap: Record<keyof StandardizedState, string[]> = {
metrics: [],
columns: [],
};
Object.entries(sharedControls).forEach(([key, value]) =>
sharedControlsMap[value].push(key),
);
export const publicControls = [
// time section
'granularity_sqla',
'time_grain_sqla',
'time_range',
'granularity_sqla', // via sharedControls
'time_grain_sqla', // via sharedControls
'time_range', // via sharedControls
// filters
'adhoc_filters',
'adhoc_filters', // via sharedControls
// subquery limit(series limit)
'limit',
'limit', // via sharedControls
// order by clause
'timeseries_limit_metric',
'series_limit_metric',
'timeseries_limit_metric', // via sharedControls
'series_limit_metric', // via sharedControls
// desc or asc in order by clause
'order_desc',
'order_desc', // via sharedControls
// outer query limit
'row_limit',
'row_limit', // via sharedControls
// x asxs column
'x_axis',
'x_axis', // via sharedControls
// advanced analytics - rolling window
'rolling_type',
'rolling_periods',
'min_periods',
'rolling_type', // via sections.advancedAnalytics
'rolling_periods', // via sections.advancedAnalytics
'min_periods', // via sections.advancedAnalytics
// advanced analytics - time comparison
'time_compare',
'comparison_type',
'time_compare', // via sections.advancedAnalytics
'comparison_type', // via sections.advancedAnalytics
// advanced analytics - resample
'resample_rule',
'resample_method',
'resample_rule', // via sections.advancedAnalytics
'resample_method', // via sections.advancedAnalytics
];
export class StandardizedFormData {
@@ -70,20 +85,10 @@ export class StandardizedFormData {
/*
* Support form_data for smooth switching between different viz
* */
const standardizedState = {
metrics: [],
columns: [],
};
const formData = Object.freeze(sourceFormData);
const reversedMap = StandardizedFormData.getReversedMap();
Object.entries(formData).forEach(([key, value]) => {
if (reversedMap.has(key)) {
standardizedState[reversedMap.get(key)].push(...ensureIsArray(value));
}
});
const memorizedFormData = Array.isArray(
// generates an ordered map, the key is viz_type and the value is form_data. the last item is current viz
const memorizedFormData: Map<string, QueryFormData> = Array.isArray(
formData?.standardizedFormData?.memorizedFormData,
)
? new Map(formData.standardizedFormData.memorizedFormData)
@@ -93,25 +98,72 @@ export class StandardizedFormData {
memorizedFormData.delete(vizType);
}
memorizedFormData.set(vizType, formData);
// calculate sharedControls
const standardizedState =
StandardizedFormData.getStandardizedState(formData);
this.sfd = {
standardizedState,
memorizedFormData,
};
}
static getReversedMap() {
const reversedMap = new Map();
Object.entries(sharedControls).forEach(([key, names]) => {
names.forEach(name => {
reversedMap.set(name, key);
});
static getStandardizedState(formData: QueryFormData): StandardizedState {
// 1. collect current sharedControls
let currState: StandardizedState = {
metrics: [],
columns: [],
};
Object.entries(formData).forEach(([key, value]) => {
if (key in sharedControls) {
currState[sharedControls[key]].push(...ensureIsArray(value));
}
});
return reversedMap;
// 2. get previous StandardizedState
let prevState: StandardizedState = {
metrics: [],
columns: [],
};
if (
formData?.standardizedFormData?.standardizedState &&
Array.isArray(formData.standardizedFormData.standardizedState.metrics) &&
Array.isArray(formData.standardizedFormData.standardizedState.columns)
) {
prevState = formData.standardizedFormData.standardizedState;
}
// the initial prevState should equal to currentState
if (isEmpty(prevState.metrics) && isEmpty(prevState.columns)) {
prevState = currState;
}
// 3. inherit SS from previous state if current viz hasn't columns-like controls or metrics-like controls
Object.keys(sharedControlsMap).forEach(key => {
if (
isEmpty(intersection(Object.keys(formData), sharedControlsMap[key]))
) {
currState[key] = prevState[key];
}
});
// 4. update hook
const controlPanel = getChartControlPanelRegistry().get(formData.viz_type);
if (controlPanel?.updateStandardizedState) {
currState = controlPanel.updateStandardizedState(prevState, currState);
}
// 5. clear up
Object.entries(currState).forEach(([key, value]) => {
currState[key] = value.filter(Boolean);
});
return currState;
}
private getLatestFormData(vizType: string): QueryFormData {
if (this.sfd.memorizedFormData.has(vizType)) {
return this.sfd.memorizedFormData.get(vizType) as QueryFormData;
if (this.has(vizType)) {
return this.get(vizType);
}
return this.memorizedFormData.slice(-1)[0][1];
@@ -125,13 +177,21 @@ export class StandardizedFormData {
return Array.from(this.sfd.memorizedFormData.entries());
}
dumpSFD() {
serialize() {
return {
standardizedState: this.standardizedState,
memorizedFormData: this.memorizedFormData,
};
}
has(vizType: string): boolean {
return this.sfd.memorizedFormData.has(vizType);
}
get(vizType: string): QueryFormData {
return this.sfd.memorizedFormData.get(vizType) as QueryFormData;
}
transform(
targetVizType: string,
exploreState: Record<string, any>,
@@ -162,7 +222,7 @@ export class StandardizedFormData {
});
const targetFormData = {
...getFormDataFromControls(targetControlsState),
standardizedFormData: this.dumpSFD(),
standardizedFormData: this.serialize(),
};
const controlPanel = getChartControlPanelRegistry().get(targetVizType);

View File

@@ -21,13 +21,14 @@ import sinon from 'sinon';
import URI from 'urijs';
import {
buildV1ChartDataPayload,
exploreChart,
getExploreUrl,
shouldUseLegacyApi,
getSimpleSQLExpression,
shouldUseLegacyApi,
} from 'src/explore/exploreUtils';
import { DashboardStandaloneMode } from 'src/dashboard/util/constants';
import * as hostNamesConfig from 'src/utils/hostNamesConfig';
import { getChartMetadataRegistry } from '@superset-ui/core';
import { getChartMetadataRegistry, SupersetClient } from '@superset-ui/core';
describe('exploreUtils', () => {
const { location } = window;
@@ -275,4 +276,16 @@ describe('exploreUtils', () => {
);
});
});
describe('.exploreChart()', () => {
it('postForm', () => {
const postFormSpy = jest.spyOn(SupersetClient, 'postForm');
postFormSpy.mockImplementation(jest.fn());
exploreChart({
formData: { ...formData, viz_type: 'my_custom_viz' },
});
expect(postFormSpy).toBeCalledTimes(1);
});
});
});

View File

@@ -25,6 +25,7 @@ import {
ensureIsArray,
getChartBuildQueryRegistry,
getChartMetadataRegistry,
SupersetClient,
} from '@superset-ui/core';
import { availableDomains } from 'src/utils/hostNamesConfig';
import { safeStringify } from 'src/utils/safeStringify';
@@ -234,31 +235,6 @@ export const buildV1ChartDataPayload = ({
export const getLegacyEndpointType = ({ resultType, resultFormat }) =>
resultFormat === 'csv' ? resultFormat : resultType;
export function postForm(url, payload, target = '_blank') {
if (!url) {
return;
}
const hiddenForm = document.createElement('form');
hiddenForm.action = url;
hiddenForm.method = 'POST';
hiddenForm.target = target;
const token = document.createElement('input');
token.type = 'hidden';
token.name = 'csrf_token';
token.value = (document.getElementById('csrf_token') || {}).value;
hiddenForm.appendChild(token);
const data = document.createElement('input');
data.type = 'hidden';
data.name = 'form_data';
data.value = safeStringify(payload);
hiddenForm.appendChild(data);
document.body.appendChild(hiddenForm);
hiddenForm.submit();
document.body.removeChild(hiddenForm);
}
export const exportChart = ({
formData,
resultFormat = 'json',
@@ -286,7 +262,8 @@ export const exportChart = ({
ownState,
});
}
postForm(url, payload);
SupersetClient.postForm(url, { form_data: safeStringify(payload) });
};
export const exploreChart = formData => {
@@ -295,7 +272,7 @@ export const exploreChart = formData => {
endpointType: 'base',
allowDomainSharding: false,
});
postForm(url, formData);
SupersetClient.postForm(url, { form_data: safeStringify(formData) });
};
export const useDebouncedEffect = (effect, delay, deps) => {

View File

@@ -141,8 +141,8 @@ export default function exploreReducer(state = {}, action) {
if (controlName === 'metrics' && old_metrics_data && new_column_config) {
value.forEach((item, index) => {
if (
item.label !== old_metrics_data[index].label &&
!!new_column_config[old_metrics_data[index].label]
item?.label !== old_metrics_data[index]?.label &&
!!new_column_config[old_metrics_data[index]?.label]
) {
new_column_config[item.label] =
new_column_config[old_metrics_data[index].label];

View File

@@ -0,0 +1,37 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
declare module 'dom-to-image-more' {
export interface Options {
filter?: ((node: Node) => boolean) | undefined;
bgcolor?: string | undefined;
width?: number | undefined;
height?: number | undefined;
style?: {} | undefined;
quality?: number | undefined;
imagePlaceholder?: string | undefined;
cacheBust?: boolean | undefined;
}
class DomToImageMore {
static toJpeg(node: Node, options?: Options): Promise<string>;
}
export default DomToImageMore;
}

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { SyntheticEvent } from 'react';
import domToImage from 'dom-to-image';
import domToImage from 'dom-to-image-more';
import kebabCase from 'lodash/kebabCase';
import { t } from '@superset-ui/core';
import { addWarningToast } from 'src/components/MessageToasts/actions';
@@ -43,7 +43,6 @@ const generateFileStem = (description: string, date = new Date()) =>
* @param selector css selector of the parent element which should be turned into image
* @param description name or a short description of what is being printed.
* Value will be normalized, and a date as well as a file extension will be added.
* @param domToImageOptions dom-to-image Options object.
* @param isExactSelector if false, searches for the closest ancestor that matches selector.
* @returns event handler
*/

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import React, { useState, useMemo, useEffect } from 'react';
import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { t, SupersetClient, makeApi, styled } from '@superset-ui/core';
import moment from 'moment';
@@ -109,6 +109,7 @@ function AlertList({
},
hasPerm,
fetchData,
setResourceCollection,
refreshData,
toggleBulkSelect,
} = useListViewResource<AlertObject>(
@@ -188,14 +189,32 @@ function AlertList({
const initialSort = [{ id: 'name', desc: true }];
const toggleActive = (data: AlertObject, checked: boolean) => {
if (data && data.id) {
const update_id = data.id;
updateResource(update_id, { active: checked }).then(() => {
refreshData();
});
}
};
const toggleActive = useCallback(
(data: AlertObject, checked: boolean) => {
if (data && data.id) {
const update_id = data.id;
const original = [...alerts];
setResourceCollection(
original.map(alert => {
if (alert?.id === data.id) {
return {
...alert,
active: checked,
};
}
return alert;
}),
);
updateResource(update_id, { active: checked }, false, false)
.then()
.catch(() => setResourceCollection(original));
}
},
[alerts, setResourceCollection, updateResource],
);
const columns = useMemo(
() => [
@@ -357,7 +376,7 @@ function AlertList({
size: 'xl',
},
],
[canDelete, canEdit, isReportEnabled],
[canDelete, canEdit, isReportEnabled, toggleActive],
);
const subMenuButtons: SubMenuProps['buttons'] = [];

View File

@@ -19,6 +19,7 @@
import React, { FormEvent } from 'react';
import { SupersetTheme, JsonObject } from '@superset-ui/core';
import { InputProps } from 'antd/lib/input';
import { Form } from 'src/components/Form';
import {
hostField,
portField,
@@ -130,7 +131,7 @@ const DatabaseConnectionForm = ({
validationErrors: JsonObject | null;
getValidation: () => void;
}) => (
<>
<Form>
<div
// @ts-ignore
css={(theme: SupersetTheme) => [
@@ -165,7 +166,7 @@ const DatabaseConnectionForm = ({
}),
)}
</div>
</>
</Form>
);
export const FormFieldMap = FORM_FIELD_MAP;

View File

@@ -316,11 +316,13 @@ export function useSingleViewResource<D extends object = any>(
);
const updateResource = useCallback(
(resourceID: number, resource: D, hideToast = false) => {
(resourceID: number, resource: D, hideToast = false, setLoading = true) => {
// Set loading state
updateState({
loading: true,
});
if (setLoading) {
updateState({
loading: true,
});
}
return SupersetClient.put({
endpoint: `/api/v1/${resourceName}/${resourceID}`,
@@ -354,11 +356,14 @@ export function useSingleViewResource<D extends object = any>(
}),
)
.finally(() => {
updateState({ loading: false });
if (setLoading) {
updateState({ loading: false });
}
});
},
[handleErrorMsg, resourceName, resourceLabel],
);
const clearError = () =>
updateState({
error: null,

View File

@@ -97,7 +97,8 @@ const StyledHeader = styled.div`
a {
margin: 0;
padding: ${({ theme }) => theme.gridUnit * 4}px;
padding: ${({ theme }) => theme.gridUnit * 2}px
${({ theme }) => theme.gridUnit * 4}px;
line-height: ${({ theme }) => theme.gridUnit * 5}px;
&:hover {
@@ -112,7 +113,8 @@ const StyledHeader = styled.div`
}
&.active a {
text-decoration: underline;
background: ${({ theme }) => theme.colors.secondary.light4};
border-radius: ${({ theme }) => theme.borderRadius}px;
}
}
@@ -122,7 +124,8 @@ const StyledHeader = styled.div`
li > a:hover,
li > a:focus,
li > div:hover,
div > div:hover {
div > div:hover,
div > a:hover {
background: ${({ theme }) => theme.colors.secondary.light4};
border-bottom: none;
border-radius: ${({ theme }) => theme.borderRadius}px;

View File

@@ -66,6 +66,28 @@ export default {
},
},
],
[
{
name: 'show_sqla_time_granularity',
config: {
type: 'CheckboxControl',
label: t('Show time grain dropdown'),
default: false,
description: t('Check to include time grain dropdown'),
},
},
],
[
{
name: 'show_sqla_time_column',
config: {
type: 'CheckboxControl',
label: t('Show time column'),
default: false,
description: t('Check to include time column dropdown'),
},
},
],
['adhoc_filters'],
],
},

View File

@@ -12,9 +12,5 @@
"dependencies": {},
"peerDependencies": {
"eslint": ">=0.8.0"
},
"engines": {
"node": "^16.9.1",
"npm": "^7.5.4"
}
}

View File

@@ -29,7 +29,8 @@
"types": [
"@emotion/react/types/css-prop",
"jest",
"@testing-library/jest-dom"
"@testing-library/jest-dom",
"@types/node"
],
/* Emit */

View File

@@ -20,7 +20,8 @@ from flask_babel import lazy_gettext as _
from sqlalchemy import and_, or_
from sqlalchemy.orm.query import Query
from superset import security_manager
from superset import db, security_manager
from superset.connectors.sqla import models
from superset.connectors.sqla.models import SqlaTable
from superset.models.slice import Slice
from superset.views.base import BaseFilter
@@ -77,6 +78,18 @@ class ChartFilter(BaseFilter): # pylint: disable=too-few-public-methods
return query
perms = security_manager.user_view_menu_names("datasource_access")
schema_perms = security_manager.user_view_menu_names("schema_access")
return query.filter(
or_(self.model.perm.in_(perms), self.model.schema_perm.in_(schema_perms))
owner_ids_query = (
db.session.query(models.SqlaTable.id)
.join(models.SqlaTable.owners)
.filter(
security_manager.user_model.id
== security_manager.user_model.get_user_id()
)
)
return query.filter(
or_(
self.model.perm.in_(perms),
self.model.schema_perm.in_(schema_perms),
models.SqlaTable.id.in_(owner_ids_query),
)
)

View File

@@ -58,7 +58,9 @@ class QueryContextFactory: # pylint: disable=too-few-public-methods
result_type = result_type or ChartDataResultType.FULL
result_format = result_format or ChartDataResultFormat.JSON
queries_ = [
self._query_object_factory.create(result_type, **query_obj)
self._query_object_factory.create(
result_type, datasource=datasource, **query_obj
)
for query_obj in queries
]
cache_values = {

View File

@@ -322,6 +322,16 @@ class QueryContextProcessor:
# multi-dimensional charts
granularity = query_object.granularity
index = granularity if granularity in df.columns else DTTM_ALIAS
if not pd.api.types.is_datetime64_any_dtype(
offset_metrics_df.get(index)
):
raise QueryObjectValidationError(
_(
"A time column must be specified "
"when using a Time Comparison."
)
)
offset_metrics_df[index] = offset_metrics_df[index] - DateOffset(
**normalize_time_delta(offset)
)

View File

@@ -23,9 +23,11 @@ from datetime import datetime, timedelta
from pprint import pformat
from typing import Any, Dict, List, NamedTuple, Optional, TYPE_CHECKING
from flask import g
from flask_babel import gettext as _
from pandas import DataFrame
from superset import feature_flag_manager
from superset.common.chart_data import ChartDataResultType
from superset.exceptions import (
InvalidPostProcessingError,
@@ -396,6 +398,24 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
if annotation_layers:
cache_dict["annotation_layers"] = annotation_layers
# Add an impersonation key to cache if impersonation is enabled on the db
if (
feature_flag_manager.is_feature_enabled("CACHE_IMPERSONATION")
and self.datasource
and hasattr(self.datasource, "database")
and self.datasource.database.impersonate_user
):
if key := self.datasource.database.db_engine_spec.get_impersonation_key(
getattr(g, "user", None)
):
logger.debug(
"Adding impersonation key to QueryObject cache dict: %s", key
)
cache_dict["impersonation_key"] = key
return md5_sha_from_dict(cache_dict, default=json_int_dttm_ser, ignore_nan=True)
def exec_post_processing(self, df: DataFrame) -> DataFrame:

View File

@@ -429,6 +429,9 @@ DEFAULT_FEATURE_FLAGS: Dict[str, bool] = {
# Apply RLS rules to SQL Lab queries. This requires parsing and manipulating the
# query, and might break queries and/or allow users to bypass RLS. Use with care!
"RLS_IN_SQLLAB": False,
# Enable caching per impersonation key (e.g username) in a datasource where user
# impersonation is enabled
"CACHE_IMPERSONATION": False,
}
# Feature flags may also be set via 'SUPERSET_FEATURE_' prefixed environment vars.
@@ -1072,6 +1075,9 @@ ALERT_REPORTS_WORKING_SOFT_TIME_OUT_LAG = int(timedelta(seconds=1).total_seconds
# If set to true no notification is sent, the worker will just log a message.
# Useful for debugging
ALERT_REPORTS_NOTIFICATION_DRY_RUN = False
# Max tries to run queries to prevent false errors caused by transient errors
# being returned to users. Set to a value >1 to enable retries.
ALERT_REPORTS_QUERY_EXECUTION_MAX_TRIES = 1
# A custom prefix to use on all Alerts & Reports emails
EMAIL_REPORTS_SUBJECT_PREFIX = "[Report] "

View File

@@ -438,6 +438,7 @@ class TableColumn(Model, BaseColumn, CertificationMixin):
self, known_columns: Optional[Dict[str, NewColumn]] = None
) -> NewColumn:
"""Convert a TableColumn to NewColumn"""
session: Session = inspect(self).session
column = known_columns.get(self.uuid) if known_columns else None
if not column:
column = NewColumn()
@@ -451,6 +452,21 @@ class TableColumn(Model, BaseColumn, CertificationMixin):
if value:
extra_json[attr] = value
if not column.id:
with session.no_autoflush:
saved_column = (
session.query(NewColumn).filter_by(uuid=self.uuid).one_or_none()
)
if saved_column:
logger.warning(
"sl_column already exists. Assigning existing id %s", self
)
# uuid isn't a primary key, so add the id of the existing column to
# ensure that the column is modified instead of created
# in order to avoid a uuid collision
column.id = saved_column.id
column.uuid = self.uuid
column.created_on = self.created_on
column.changed_on = self.changed_on
@@ -554,6 +570,7 @@ class SqlMetric(Model, BaseMetric, CertificationMixin):
) -> NewColumn:
"""Convert a SqlMetric to NewColumn. Find and update existing or
create a new one."""
session: Session = inspect(self).session
column = known_columns.get(self.uuid) if known_columns else None
if not column:
column = NewColumn()
@@ -567,6 +584,20 @@ class SqlMetric(Model, BaseMetric, CertificationMixin):
self.metric_type and self.metric_type.lower() in ADDITIVE_METRIC_TYPES_LOWER
)
if not column.id:
with session.no_autoflush:
saved_column = (
session.query(NewColumn).filter_by(uuid=self.uuid).one_or_none()
)
if saved_column:
logger.warning(
"sl_column already exists. Assigning existing id %s", self
)
# uuid isn't a primary key, so add the id of the existing column to
# ensure that the column is modified instead of created
# in order to avoid a uuid collision
column.id = saved_column.id
column.uuid = self.uuid
column.name = self.metric_name
column.created_on = self.created_on
@@ -2106,10 +2137,11 @@ class SqlaTable(Model, BaseDatasource): # pylint: disable=too-many-public-metho
uuids.remove(column.uuid)
if uuids:
# load those not found from db
existing_columns |= set(
session.query(NewColumn).filter(NewColumn.uuid.in_(uuids))
)
with session.no_autoflush:
# load those not found from db
existing_columns |= set(
session.query(NewColumn).filter(NewColumn.uuid.in_(uuids))
)
known_columns = {column.uuid: column for column in existing_columns}
return [
@@ -2149,9 +2181,10 @@ class SqlaTable(Model, BaseDatasource): # pylint: disable=too-many-public-metho
# update changed_on timestamp
session.execute(update(NewDataset).where(NewDataset.id == dataset.id))
try:
column = session.query(NewColumn).filter_by(uuid=target.uuid).one()
# update `Column` model as well
session.merge(target.to_sl_column({target.uuid: column}))
with session.no_autoflush:
column = session.query(NewColumn).filter_by(uuid=target.uuid).one()
# update `Column` model as well
session.merge(target.to_sl_column({target.uuid: column}))
except NoResultFound:
logger.warning("No column was found for %s", target)
# see if the column is in cache
@@ -2161,14 +2194,15 @@ class SqlaTable(Model, BaseDatasource): # pylint: disable=too-many-public-metho
),
None,
)
if column:
logger.warning("New column was found in cache: %s", column)
if not column:
else:
# to be safe, use a different uuid and create a new column
uuid = uuid4()
target.uuid = uuid
column = NewColumn(uuid=uuid)
session.add(target.to_sl_column({column.uuid: column}))
session.add(target.to_sl_column())
@staticmethod
def after_insert(

View File

@@ -26,7 +26,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.security.decorators import has_access
from flask_babel import lazy_gettext as _
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from wtforms.validators import Regexp
from wtforms.validators import DataRequired, Regexp
from superset import app, db
from superset.connectors.base.views import DatasourceModelView
@@ -47,7 +47,22 @@ from superset.views.base import (
logger = logging.getLogger(__name__)
class TableColumnInlineView(CompactCRUDMixin, SupersetModelView):
class SelectDataRequired(DataRequired): # pylint: disable=too-few-public-methods
"""
Select required flag on the input field will not work well on Chrome
Console error:
An invalid form control with name='tables' is not focusable.
This makes a simple override to the DataRequired to be used specifically with
select fields
"""
field_flags = ()
class TableColumnInlineView(
CompactCRUDMixin, SupersetModelView
): # pylint: disable=too-many-ancestors
datamodel = SQLAInterface(models.TableColumn)
# TODO TODO, review need for this on related_views
class_permission_name = "Dataset"
@@ -179,7 +194,9 @@ class TableColumnInlineView(CompactCRUDMixin, SupersetModelView):
edit_form_extra_fields = add_form_extra_fields
class SqlMetricInlineView(CompactCRUDMixin, SupersetModelView):
class SqlMetricInlineView(
CompactCRUDMixin, SupersetModelView
): # pylint: disable=too-many-ancestors
datamodel = SQLAInterface(models.SqlMetric)
class_permission_name = "Dataset"
method_permission_name = MODEL_VIEW_RW_METHOD_PERMISSION_MAP
@@ -261,7 +278,9 @@ class RowLevelSecurityListWidget(
super().__init__(**kwargs)
class RowLevelSecurityFiltersModelView(SupersetModelView, DeleteMixin):
class RowLevelSecurityFiltersModelView(
SupersetModelView, DeleteMixin
): # pylint: disable=too-many-ancestors
datamodel = SQLAInterface(models.RowLevelSecurityFilter)
list_widget = cast(SupersetListWidget, RowLevelSecurityListWidget)

View File

@@ -41,6 +41,7 @@ import sqlparse
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from flask import current_app
from flask_appbuilder.security.sqla.models import User
from flask_babel import gettext as __, lazy_gettext as _
from marshmallow import fields, Schema
from marshmallow.validate import Range
@@ -1537,6 +1538,17 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
def parse_sql(cls, sql: str) -> List[str]:
return [str(s).strip(" ;") for s in sqlparse.parse(sql)]
@classmethod
def get_impersonation_key(cls, user: Optional[User]) -> Any:
"""
Construct an impersonation key, by default it's the given username.
:param user: logged in user
:returns: username if given user is not null
"""
return user.username if user else None
# schema for adding a database by providing parameters instead of the
# full SQLAlchemy URI

View File

@@ -123,8 +123,12 @@ class BigQueryEngineSpec(BaseEngineSpec):
_time_grain_expressions = {
None: "{col}",
"PT1S": "{func}({col}, SECOND)",
"PT1M": "{func}({col}, MINUTE)",
"PT1S": "CAST(TIMESTAMP_SECONDS("
"UNIX_SECONDS(CAST({col} AS TIMESTAMP))"
") AS {type})",
"PT1M": "CAST(TIMESTAMP_SECONDS("
"60 * DIV(UNIX_SECONDS(CAST({col} AS TIMESTAMP)), 60)"
") AS {type})",
"PT5M": "CAST(TIMESTAMP_SECONDS("
"5*60 * DIV(UNIX_SECONDS(CAST({col} AS TIMESTAMP)), 5*60)"
") AS {type})",

View File

@@ -21,7 +21,6 @@ from datetime import datetime
from typing import Any, Dict, List, Optional, Pattern, Tuple, TYPE_CHECKING
from flask_babel import gettext as __
from psycopg2.extensions import binary_types, string_types
from sqlalchemy.dialects.postgresql import ARRAY, DOUBLE_PRECISION, ENUM, JSON
from sqlalchemy.dialects.postgresql.base import PGInspector
from sqlalchemy.types import String
@@ -290,6 +289,9 @@ class PostgresEngineSpec(PostgresBaseEngineSpec, BasicParametersMixin):
@classmethod
def get_datatype(cls, type_code: Any) -> Optional[str]:
# pylint: disable=import-outside-toplevel
from psycopg2.extensions import binary_types, string_types
types = binary_types.copy()
types.update(string_types)
if type_code in types:

View File

@@ -761,7 +761,7 @@ class PrestoEngineSpec(BaseEngineSpec): # pylint: disable=too-many-public-metho
utils.TemporalType.TIMESTAMP,
utils.TemporalType.TIMESTAMP_WITH_TIME_ZONE,
):
return f"""TIMESTAMP '{dttm.isoformat(timespec="microseconds", sep=" ")}'"""
return f"""TIMESTAMP '{dttm.isoformat(timespec="milliseconds", sep=" ")}'"""
return None
@classmethod
@@ -949,11 +949,7 @@ class PrestoEngineSpec(BaseEngineSpec): # pylint: disable=too-many-public-metho
sql = f"SHOW CREATE VIEW {schema}.{table}"
try:
cls.execute(cursor, sql)
polled = cursor.poll()
while polled:
time.sleep(0.2)
polled = cursor.poll()
except DatabaseError: # not a VIEW
return None
rows = cls.fetch_data(cursor, 1)

View File

@@ -14,14 +14,18 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import logging
import re
from typing import Any, Dict, Pattern, Tuple
from typing import Any, Dict, Optional, Pattern, Tuple
from flask_babel import gettext as __
from superset.db_engine_specs.base import BasicParametersMixin
from superset.db_engine_specs.postgres import PostgresBaseEngineSpec
from superset.errors import SupersetErrorType
from superset.models.sql_lab import Query
logger = logging.getLogger()
# Regular expressions to catch custom errors
CONNECTION_ACCESS_DENIED_REGEX = re.compile(
@@ -101,3 +105,39 @@ class RedshiftEngineSpec(PostgresBaseEngineSpec, BasicParametersMixin):
:return: Conditionally mutated label
"""
return label.lower()
@classmethod
def get_cancel_query_id(cls, cursor: Any, query: Query) -> Optional[str]:
"""
Get Redshift PID that will be used to cancel all other running
queries in the same session.
:param cursor: Cursor instance in which the query will be executed
:param query: Query instance
:return: Redshift PID
"""
cursor.execute("SELECT pg_backend_pid()")
row = cursor.fetchone()
return row[0]
@classmethod
def cancel_query(cls, cursor: Any, query: Query, cancel_query_id: str) -> bool:
"""
Cancel query in the underlying database.
:param cursor: New cursor instance to the db of the query
:param query: Query instance
:param cancel_query_id: Redshift PID
:return: True if query cancelled successfully, False otherwise
"""
try:
logger.info("Killing Redshift PID:%s", str(cancel_query_id))
cursor.execute(
"SELECT pg_cancel_backend(procpid) "
"FROM pg_stat_activity "
f"WHERE procpid='{cancel_query_id}'"
)
cursor.close()
except Exception: # pylint: disable=broad-except
return False
return True

View File

@@ -15,15 +15,18 @@
# specific language governing permissions and limitations
# under the License.
import logging
from typing import Any, Dict, Optional, TYPE_CHECKING
from typing import Any, Dict, List, Optional, TYPE_CHECKING
import simplejson as json
from flask import current_app
from sqlalchemy.engine.reflection import Inspector
from sqlalchemy.engine.url import URL
from sqlalchemy.orm import Session
from superset.databases.utils import make_url_safe
from superset.db_engine_specs.base import BaseEngineSpec
from superset.db_engine_specs.presto import PrestoEngineSpec
from superset.models.sql_lab import Query
from superset.utils import core as utils
if TYPE_CHECKING:
@@ -77,6 +80,37 @@ class TrinoEngineSpec(PrestoEngineSpec):
def get_allow_cost_estimate(cls, extra: Dict[str, Any]) -> bool:
return True
@classmethod
def get_table_names(
cls,
database: "Database",
inspector: Inspector,
schema: Optional[str],
) -> List[str]:
return BaseEngineSpec.get_table_names(
database=database,
inspector=inspector,
schema=schema,
)
@classmethod
def get_view_names(
cls,
database: "Database",
inspector: Inspector,
schema: Optional[str],
) -> List[str]:
return BaseEngineSpec.get_view_names(
database=database,
inspector=inspector,
schema=schema,
)
@classmethod
def handle_cursor(cls, cursor: Any, query: Query, session: Session) -> None:
"""Updates progress information"""
BaseEngineSpec.handle_cursor(cursor=cursor, query=query, session=session)
@staticmethod
def get_extra_params(database: "Database") -> Dict[str, Any]:
"""

View File

@@ -48,8 +48,13 @@ class GetExplorePermalinkCommand(BaseExplorePermalinkCommand):
).run()
if value:
chart_id: Optional[int] = value.get("chartId")
datasource_id: int = value["datasourceId"]
datasource_type = DatasourceType(value["datasourceType"])
# keep this backward compatible for old permalinks
datasource_id: int = (
value.get("datasourceId") or value.get("datasetId") or 0
)
datasource_type = DatasourceType(
value.get("datasourceType", DatasourceType.TABLE)
)
check_chart_access(datasource_id, chart_id, self.actor, datasource_type)
return value
return None

View File

@@ -24,7 +24,11 @@ class ExplorePermalinkState(TypedDict, total=False):
class ExplorePermalinkValue(TypedDict):
chartId: Optional[int]
datasourceId: int
# either datasetId or datasourceId is required
# TODO: deprecated - datasetId is deprecated
# and should be removed in next major release
datasetId: Optional[int]
datasourceId: Optional[int]
datasourceType: str
datasource: str
state: ExplorePermalinkState

View File

@@ -28,7 +28,7 @@ class KeyValueEntry(Model, AuditMixinNullable, ImportExportMixin):
__tablename__ = "key_value"
id = Column(Integer, primary_key=True)
resource = Column(String(32), nullable=False)
value = Column(LargeBinary(), nullable=False)
value = Column(LargeBinary(length=2**24 - 1), nullable=False)
created_on = Column(DateTime, nullable=True)
created_by_fk = Column(Integer, ForeignKey("ab_user.id"), nullable=True)
changed_on = Column(DateTime, nullable=True)

View File

@@ -0,0 +1,50 @@
# 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.
"""Resize key_value blob
Revision ID: e09b4ae78457
Revises: e786798587de
Create Date: 2022-06-14 15:28:42.746349
"""
# revision identifiers, used by Alembic.
revision = "e09b4ae78457"
down_revision = "e786798587de"
import sqlalchemy as sa
from alembic import op
def upgrade():
with op.batch_alter_table("key_value", schema=None) as batch_op:
batch_op.alter_column(
"value",
existing_nullable=False,
existing_type=sa.LargeBinary(),
type_=sa.LargeBinary(length=2**24 - 1),
)
def downgrade():
with op.batch_alter_table("key_value", schema=None) as batch_op:
batch_op.alter_column(
"value",
existing_nullable=False,
existing_type=sa.LargeBinary(length=2**24 - 1),
type_=sa.LargeBinary(),
)

View File

@@ -37,6 +37,7 @@ from superset.reports.commands.exceptions import (
AlertValidatorConfigError,
)
from superset.utils.core import override_user
from superset.utils.retries import retry_call
logger = logging.getLogger(__name__)
@@ -171,7 +172,13 @@ class AlertCommand(BaseCommand):
"""
Validate the query result as a Pandas DataFrame
"""
df = self._execute_query()
# When there are transient errors when executing queries, users will get
# notified with the error stacktrace which can be avoided by retrying
df = retry_call(
self._execute_query,
exception=AlertQueryError,
max_tries=app.config["ALERT_REPORTS_QUERY_EXECUTION_MAX_TRIES"],
)
if df.empty and self._is_validator_not_null:
self._result = None

View File

@@ -71,6 +71,19 @@ def destringify(obj: str) -> Any:
return json.loads(obj)
def convert_to_string(value: Any) -> str:
"""
Used to ensure column names from the cursor description are strings.
"""
if isinstance(value, str):
return value
if isinstance(value, bytes):
return value.decode("utf-8")
return str(value)
class SupersetResultSet:
def __init__( # pylint: disable=too-many-locals
self,
@@ -88,7 +101,9 @@ class SupersetResultSet:
if cursor_description:
# get deduped list of column names
column_names = dedup([col[0] for col in cursor_description])
column_names = dedup(
[convert_to_string(col[0]) for col in cursor_description]
)
# fix cursor descriptor with the deduped names
deduped_cursor_desc = [

View File

@@ -1033,6 +1033,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
from superset.connectors.sqla.models import SqlaTable
from superset.extensions import feature_flag_manager
from superset.sql_parse import Table
from superset.views.utils import is_owner
if database and table or query:
if query:
@@ -1063,7 +1064,9 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
# Access to any datasource is suffice.
for datasource_ in datasources:
if self.can_access("datasource_access", datasource_.perm):
if self.can_access(
"datasource_access", datasource_.perm
) or is_owner(datasource_, getattr(g, "user", None)):
break
else:
denied.add(table_)
@@ -1089,6 +1092,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
if not (
self.can_access_schema(datasource)
or self.can_access("datasource_access", datasource.perm or "")
or is_owner(datasource, getattr(g, "user", None))
or (
should_check_dashboard_access
and self.can_access_based_on_dashboard(datasource)
@@ -1383,7 +1387,9 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
:return: A guest user object
"""
raw_token = req.headers.get(current_app.config["GUEST_TOKEN_HEADER_NAME"])
raw_token = req.headers.get(
current_app.config["GUEST_TOKEN_HEADER_NAME"]
) or req.form.get("guest_token")
if raw_token is None:
return None

View File

@@ -140,6 +140,9 @@ class ExecuteSqlCommand(BaseCommand):
self._execution_context.set_query(query)
rendered_query = self._sql_query_render.render(self._execution_context)
self._set_query_limit_if_required(rendered_query)
self._query_dao.update(
query, {"limit": self._execution_context.query.limit}
)
return self._sql_json_executor.execute(
self._execution_context, rendered_query, self._log_params
)

View File

@@ -69,7 +69,13 @@ class ResultSetColumnType(TypedDict):
CacheConfig = Dict[str, Any]
DbapiDescriptionRow = Tuple[
str, str, Optional[str], Optional[str], Optional[int], Optional[int], bool
Union[str, bytes],
str,
Optional[str],
Optional[str],
Optional[int],
Optional[int],
bool,
]
DbapiDescription = Union[List[DbapiDescriptionRow], Tuple[DbapiDescriptionRow, ...]]
DbapiResult = Sequence[Union[List[Any], Tuple[Any, ...]]]

View File

@@ -3478,7 +3478,7 @@
"This dashboard was saved successfully.": [
"Dit dashboard is succesvol opgeslagen."
],
"Sorry, an unknown error occured": [""],
"Sorry, an unknown error occurred": [""],
"Sorry, there was an error saving this dashboard: %s": [""],
"You do not have permission to edit this dashboard": [
"U hebt geen toestemming om dit dashboard te bewerken"

View File

@@ -3071,7 +3071,7 @@ msgstr "字段作为数据文件的行标签使用。如果没有索引字段,
#: superset/views/database/forms.py:385
#, fuzzy
msgid "Columnar File"
msgstr "列"
msgstr "列式存储文件"
#: superset/views/database/views.py:550
#, fuzzy, python-format
@@ -3079,13 +3079,13 @@ msgid ""
"Columnar file \"%(columnar_filename)s\" uploaded to table "
"\"%(table_name)s\" in database \"%(db_name)s\""
msgstr ""
"Excel 文件 \"%(excel_filename)s\" 上传到数据库 \"%(db_name)s\" 中的表 "
"Excel 文件 \"%(columnar_filename)s\" 上传到数据库 \"%(db_name)s\" 中的表 "
"\"%(table_name)s\""
#: superset/views/database/views.py:414
#, fuzzy
msgid "Columnar to Database configuration"
msgstr "Excel 到数据库配置"
msgstr "列式存储文件到数据库配置"
#: superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx:55
#: superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx:214
@@ -10315,12 +10315,12 @@ msgstr "搜索指标和列"
#: superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx:703
#, fuzzy
msgid "Search all charts"
msgstr "所有图表"
msgstr "搜索所有图表"
#: superset-frontend/src/components/OmniContainer/index.tsx:102
#, fuzzy
msgid "Search all dashboards"
msgstr "刷新看板"
msgstr "搜索所有看板"
#: superset-frontend/src/explore/components/controls/FilterBoxItemControl/index.jsx:232
#: superset-frontend/src/filters/components/Select/controlPanel.ts:137
@@ -11522,7 +11522,7 @@ msgstr "报告计划意外错误。"
#: superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx:670
#, fuzzy
msgid "Supported databases"
msgstr "删除数据库"
msgstr "已支持数据库"
#: superset-frontend/plugins/legacy-plugin-chart-sankey/src/index.js:34
msgid "Survey Responses"
@@ -12324,14 +12324,14 @@ msgstr "详细提示显示了该时间点的所有序列的列表。"
msgid ""
"The schema \"%(schema)s\" does not exist. A valid schema must be used to "
"run this query."
msgstr "表 \"%(table_name)s\" 不存在。必须使用有效的表来运行此查询。"
msgstr "表 \"%(schema)s\" 不存在。必须使用有效的表来运行此查询。"
#: superset/db_engine_specs/presto.py:187
#, fuzzy, python-format
msgid ""
"The schema \"%(schema_name)s\" does not exist. A valid schema must be "
"used to run this query."
msgstr "表 \"%(table_name)s\" 不存在。必须使用有效的表来运行此查询。"
msgstr "表 \"%(schema_name)s\" 不存在。必须使用有效的表来运行此查询。"
#: superset/errors.py:111
#, fuzzy
@@ -12355,7 +12355,7 @@ msgstr ""
msgid ""
"The table \"%(table)s\" does not exist. A valid table must be used to run"
" this query."
msgstr "表 \"%(table_name)s\" 不存在。必须使用有效的表来运行此查询。"
msgstr "表 \"%(table)s\" 不存在。必须使用有效的表来运行此查询。"
#: superset/db_engine_specs/presto.py:179
#, python-format
@@ -13641,10 +13641,10 @@ msgstr "上传"
#: superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm/EncryptedField.tsx:134
#, fuzzy
msgid "Upload Credentials"
msgstr "上传Excel"
msgstr "上传验证文件"
#: superset/initialization/__init__.py:400
msgid "Upload Excel"
msgid "Upload Excel file to database"
msgstr "上传Excel"
#: superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm/EncryptedField.tsx:102
@@ -13652,12 +13652,12 @@ msgid "Upload JSON file"
msgstr ""
#: superset/initialization/__init__.py:369
msgid "Upload a CSV"
msgid "Upload CSV to database"
msgstr "上传CSV文件"
#: superset/initialization/__init__.py:383
msgid "Upload a Columnar File"
msgstr ""
msgstr "上传列式存储文件"
#: superset-frontend/plugins/legacy-plugin-chart-rose/src/controlPanel.tsx:112
#, fuzzy
@@ -14765,7 +14765,7 @@ msgid ""
"\"%(columnar_table.table)s\" and in the schema field: "
"\"%(columnar_table.schema)s\". Please remove one"
msgstr ""
"不能同时在表名 \"%(excel_table.table)s\" 和schema字段 \"%(excel_table.schema)s\" "
"不能同时在表名 \"%(columnar_table.table)s\" 和schema字段 \"%(columnar_table.schema)s\" "
"中指定命名空间。请删除一个。"
#: superset/views/database/views.py:146
@@ -15572,4 +15572,4 @@ msgstr "年"
#: superset-frontend/src/explore/components/controls/ConditionalFormattingControl/FormattingPopoverContent.tsx:42
msgid "yellow"
msgstr ""
msgstr "黄色"

View File

@@ -86,10 +86,10 @@ PROPHET_TIME_GRAIN_MAP = {
"P1M": "M",
"P3M": "Q",
"P1Y": "A",
"1969-12-28T00:00:00Z/P1W": "W",
"1969-12-29T00:00:00Z/P1W": "W",
"P1W/1970-01-03T00:00:00Z": "W",
"P1W/1970-01-04T00:00:00Z": "W",
"1969-12-28T00:00:00Z/P1W": "W-SUN",
"1969-12-29T00:00:00Z/P1W": "W-MON",
"P1W/1970-01-03T00:00:00Z": "W-SAT",
"P1W/1970-01-04T00:00:00Z": "W-SUN",
}
RESAMPLE_METHOD = ("asfreq", "bfill", "ffill", "linear", "median", "mean", "sum")

View File

@@ -25,7 +25,9 @@ from superset.views.base import DeleteMixin, SupersetModelView
from superset.views.core import DAR
class AccessRequestsModelView(SupersetModelView, DeleteMixin):
class AccessRequestsModelView(
SupersetModelView, DeleteMixin
): # pylint: disable=too-many-ancestors
datamodel = SQLAInterface(DAR)
include_route_methods = RouteMethod.CRUD_SET
list_columns = [

View File

@@ -47,7 +47,9 @@ class StartEndDttmValidator: # pylint: disable=too-few-public-methods
)
class AnnotationModelView(SupersetModelView, CompactCRUDMixin):
class AnnotationModelView(
SupersetModelView, CompactCRUDMixin
): # pylint: disable=too-many-ancestors
datamodel = SQLAInterface(Annotation)
include_route_methods = RouteMethod.CRUD_SET | {"annotation"}

View File

@@ -25,7 +25,9 @@ from superset.superset_typing import FlaskResponse
from superset.views.base import DeleteMixin, SupersetModelView
class CssTemplateModelView(SupersetModelView, DeleteMixin):
class CssTemplateModelView(
SupersetModelView, DeleteMixin
): # pylint: disable=too-many-ancestors
datamodel = SQLAInterface(models.CssTemplate)
include_route_methods = RouteMethod.CRUD_SET

View File

@@ -26,7 +26,7 @@ from superset.views.base import SupersetModelView
from . import LogMixin
class LogModelView(LogMixin, SupersetModelView):
class LogModelView(LogMixin, SupersetModelView): # pylint: disable=too-many-ancestors
datamodel = SQLAInterface(models.Log)
include_route_methods = {RouteMethod.LIST, RouteMethod.SHOW}
class_permission_name = "Log"

View File

@@ -35,7 +35,9 @@ from .base import BaseSupersetView, DeleteMixin, json_success, SupersetModelView
logger = logging.getLogger(__name__)
class SavedQueryView(SupersetModelView, DeleteMixin):
class SavedQueryView(
SupersetModelView, DeleteMixin
): # pylint: disable=too-many-ancestors
datamodel = SQLAInterface(SavedQuery)
include_route_methods = RouteMethod.CRUD_SET

View File

@@ -17,7 +17,7 @@
import logging
from collections import defaultdict
from functools import wraps
from typing import Any, Callable, DefaultDict, Dict, List, Optional, Set, Tuple, Union
from typing import Any, Callable, DefaultDict, Dict, List, Optional, Tuple, Union
from urllib import parse
import msgpack
@@ -33,6 +33,7 @@ import superset.models.core as models
from superset import app, dataframe, db, result_set, viz
from superset.common.db_query_status import QueryStatus
from superset.connectors.connector_registry import ConnectorRegistry
from superset.connectors.sqla.models import SqlaTable
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.exceptions import (
CacheLoadError,
@@ -102,7 +103,7 @@ def bootstrap_user_data(user: User, include_perms: bool = False) -> Dict[str, An
def get_permissions(
user: User,
) -> Tuple[Dict[str, List[List[str]]], DefaultDict[str, Set[str]]]:
) -> Tuple[Dict[str, List[List[str]]], DefaultDict[str, List[str]]]:
if not user.roles:
raise AttributeError("User object does not have roles")
@@ -115,8 +116,10 @@ def get_permissions(
if permission[0] in ("datasource_access", "database_access"):
permissions[permission[0]].add(permission[1])
roles[role.name].append([permission[0], permission[1]])
return roles, permissions
transformed_permissions = defaultdict(list)
for perm in permissions:
transformed_permissions[perm] = list(permissions[perm])
return roles, transformed_permissions
def get_viz(
@@ -421,7 +424,7 @@ def is_slice_in_container(
return False
def is_owner(obj: Union[Dashboard, Slice], user: User) -> bool:
def is_owner(obj: Union[Dashboard, Slice, SqlaTable], user: User) -> bool:
"""Check if user is owner of the slice"""
return obj and user in obj.owners

View File

@@ -21,7 +21,7 @@ import imp
import json
from contextlib import contextmanager
from typing import Any, Dict, Union, List, Optional
from unittest.mock import Mock, patch
from unittest.mock import Mock, patch, MagicMock
import pandas as pd
import pytest
@@ -252,7 +252,7 @@ class SupersetTestCase(TestCase):
@staticmethod
def get_datasource_mock() -> BaseDatasource:
datasource = Mock()
datasource = MagicMock()
results = Mock()
results.query = Mock()
results.status = Mock()
@@ -266,6 +266,7 @@ class SupersetTestCase(TestCase):
datasource.database = Mock()
datasource.database.db_engine_spec = Mock()
datasource.database.db_engine_spec.mutate_expression_label = lambda x: x
datasource.owners = MagicMock()
return datasource
def get_resp(

View File

@@ -17,8 +17,10 @@
from unittest import mock
import pytest
from sqlalchemy import inspect
from sqlalchemy.orm.exc import NoResultFound
from superset.columns.models import Column
from superset.connectors.sqla.models import SqlaTable, TableColumn
from superset.extensions import db
from tests.integration_tests.base_tests import SupersetTestCase
@@ -59,6 +61,10 @@ class SqlaTableModelTest(SupersetTestCase):
with mock.patch("sqlalchemy.orm.query.Query.one", side_effect=NoResultFound):
SqlaTable.update_column(None, None, target=column)
session = inspect(column).session
session.flush()
# refetch
dataset = db.session.query(SqlaTable).filter_by(id=dataset.id).one()
# it should create a new uuid
@@ -67,3 +73,15 @@ class SqlaTableModelTest(SupersetTestCase):
# reset
column.uuid = column_uuid
SqlaTable.update_column(None, None, target=column)
@pytest.mark.usefixtures("load_dataset_with_columns")
def test_to_sl_column_no_known_columns(self) -> None:
"""
Test that the function returns a new column
"""
dataset = db.session.query(SqlaTable).filter_by(table_name="students").first()
column = dataset.columns[0]
new_column = column.to_sl_column()
# it should use the same uuid
assert column.uuid == new_column.uuid

View File

@@ -0,0 +1,172 @@
# 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 json
from unittest.mock import patch
import pytest
from superset import app, db, security, security_manager
from superset.commands.exceptions import DatasourceTypeInvalidError
from superset.connectors.sqla.models import SqlaTable
from superset.explore.form_data.commands.parameters import CommandParameters
from superset.explore.permalink.commands.create import CreateExplorePermalinkCommand
from superset.explore.permalink.commands.get import GetExplorePermalinkCommand
from superset.key_value.utils import decode_permalink_id
from superset.models.slice import Slice
from superset.models.sql_lab import Query
from superset.utils.core import DatasourceType, get_example_default_schema
from superset.utils.database import get_example_database
from tests.integration_tests.base_tests import SupersetTestCase
class TestCreatePermalinkDataCommand(SupersetTestCase):
@pytest.fixture()
def create_dataset(self):
with self.create_app().app_context():
dataset = SqlaTable(
table_name="dummy_sql_table",
database=get_example_database(),
schema=get_example_default_schema(),
sql="select 123 as intcol, 'abc' as strcol",
)
session = db.session
session.add(dataset)
session.commit()
yield dataset
# rollback
session.delete(dataset)
session.commit()
@pytest.fixture()
def create_slice(self):
with self.create_app().app_context():
session = db.session
dataset = (
session.query(SqlaTable).filter_by(table_name="dummy_sql_table").first()
)
slice = Slice(
datasource_id=dataset.id,
datasource_type=DatasourceType.TABLE,
datasource_name="tmp_perm_table",
slice_name="slice_name",
)
session.add(slice)
session.commit()
yield slice
# rollback
session.delete(slice)
session.commit()
@pytest.fixture()
def create_query(self):
with self.create_app().app_context():
session = db.session
query = Query(
sql="select 1 as foo;",
client_id="sldkfjlk",
database=get_example_database(),
)
session.add(query)
session.commit()
yield query
# rollback
session.delete(query)
session.commit()
@patch("superset.security.manager.g")
@pytest.mark.usefixtures("create_dataset", "create_slice")
def test_create_permalink_command(self, mock_g):
mock_g.user = security_manager.find_user("admin")
dataset = (
db.session.query(SqlaTable).filter_by(table_name="dummy_sql_table").first()
)
slice = db.session.query(Slice).filter_by(slice_name="slice_name").first()
datasource = f"{dataset.id}__{DatasourceType.TABLE}"
command = CreateExplorePermalinkCommand(
mock_g.user, {"formData": {"datasource": datasource, "slice_id": slice.id}}
)
assert isinstance(command.run(), str)
@patch("superset.security.manager.g")
@pytest.mark.usefixtures("create_dataset", "create_slice")
def test_get_permalink_command(self, mock_g):
mock_g.user = security_manager.find_user("admin")
app.config["EXPLORE_FORM_DATA_CACHE_CONFIG"] = {
"REFRESH_TIMEOUT_ON_RETRIEVAL": True
}
dataset = (
db.session.query(SqlaTable).filter_by(table_name="dummy_sql_table").first()
)
slice = db.session.query(Slice).filter_by(slice_name="slice_name").first()
datasource = f"{dataset.id}__{DatasourceType.TABLE}"
key = CreateExplorePermalinkCommand(
mock_g.user, {"formData": {"datasource": datasource, "slice_id": slice.id}}
).run()
get_command = GetExplorePermalinkCommand(mock_g.user, key)
cache_data = get_command.run()
assert cache_data.get("datasource") == datasource
@patch("superset.security.manager.g")
@patch("superset.key_value.commands.get.GetKeyValueCommand.run")
@patch("superset.explore.permalink.commands.get.decode_permalink_id")
@pytest.mark.usefixtures("create_dataset", "create_slice")
def test_get_permalink_command_with_old_dataset_key(
self, decode_id_mock, get_kv_command_mock, mock_g
):
mock_g.user = security_manager.find_user("admin")
app.config["EXPLORE_FORM_DATA_CACHE_CONFIG"] = {
"REFRESH_TIMEOUT_ON_RETRIEVAL": True
}
dataset = (
db.session.query(SqlaTable).filter_by(table_name="dummy_sql_table").first()
)
slice = db.session.query(Slice).filter_by(slice_name="slice_name").first()
datasource_string = f"{dataset.id}__{DatasourceType.TABLE}"
decode_id_mock.return_value = "123456"
get_kv_command_mock.return_value = {
"chartId": slice.id,
"datasetId": dataset.id,
"datasource": datasource_string,
"state": {
"formData": {"datasource": datasource_string, "slice_id": slice.id}
},
}
get_command = GetExplorePermalinkCommand(mock_g.user, "thisisallmocked")
cache_data = get_command.run()
assert cache_data.get("datasource") == datasource_string

View File

@@ -0,0 +1,119 @@
# 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.
# pylint: disable=invalid-name, unused-argument, import-outside-toplevel
import pandas as pd
from pytest_mock import MockFixture
def test_execute_query_succeeded_no_retry(
mocker: MockFixture, app_context: None
) -> None:
from superset.reports.commands.alert import AlertCommand
execute_query_mock = mocker.patch(
"superset.reports.commands.alert.AlertCommand._execute_query",
side_effect=lambda: pd.DataFrame([{"sample_col": 0}]),
)
command = AlertCommand(report_schedule=mocker.Mock())
command.validate()
assert execute_query_mock.call_count == 1
def test_execute_query_succeeded_with_retries(
mocker: MockFixture, app_context: None
) -> None:
from superset.reports.commands.alert import AlertCommand, AlertQueryError
execute_query_mock = mocker.patch(
"superset.reports.commands.alert.AlertCommand._execute_query"
)
query_executed_count = 0
# Should match the value defined in superset_test_config.py
expected_max_retries = 3
def _mocked_execute_query() -> pd.DataFrame:
nonlocal query_executed_count
query_executed_count += 1
if query_executed_count < expected_max_retries:
raise AlertQueryError()
else:
return pd.DataFrame([{"sample_col": 0}])
execute_query_mock.side_effect = _mocked_execute_query
execute_query_mock.__name__ = "mocked_execute_query"
command = AlertCommand(report_schedule=mocker.Mock())
command.validate()
assert execute_query_mock.call_count == expected_max_retries
def test_execute_query_failed_no_retry(mocker: MockFixture, app_context: None) -> None:
from superset.reports.commands.alert import AlertCommand, AlertQueryTimeout
execute_query_mock = mocker.patch(
"superset.reports.commands.alert.AlertCommand._execute_query"
)
def _mocked_execute_query() -> None:
raise AlertQueryTimeout
execute_query_mock.side_effect = _mocked_execute_query
execute_query_mock.__name__ = "mocked_execute_query"
command = AlertCommand(report_schedule=mocker.Mock())
try:
command.validate()
except AlertQueryTimeout:
pass
assert execute_query_mock.call_count == 1
def test_execute_query_failed_max_retries(
mocker: MockFixture, app_context: None
) -> None:
from superset.reports.commands.alert import AlertCommand, AlertQueryError
execute_query_mock = mocker.patch(
"superset.reports.commands.alert.AlertCommand._execute_query"
)
def _mocked_execute_query() -> None:
raise AlertQueryError
execute_query_mock.side_effect = _mocked_execute_query
execute_query_mock.__name__ = "mocked_execute_query"
command = AlertCommand(report_schedule=mocker.Mock())
try:
command.validate()
except AlertQueryError:
pass
# Should match the value defined in superset_test_config.py
assert execute_query_mock.call_count == 3

View File

@@ -878,7 +878,10 @@ class TestSecurityManager(SupersetTestCase):
@patch("superset.security.SupersetSecurityManager.can_access")
@patch("superset.security.SupersetSecurityManager.can_access_schema")
def test_raise_for_access_datasource(self, mock_can_access_schema, mock_can_access):
@patch("superset.views.utils.is_owner")
def test_raise_for_access_datasource(
self, mock_can_access_schema, mock_can_access, mock_is_owner
):
datasource = self.get_datasource_mock()
mock_can_access_schema.return_value = True
@@ -886,12 +889,14 @@ class TestSecurityManager(SupersetTestCase):
mock_can_access.return_value = False
mock_can_access_schema.return_value = False
mock_is_owner.return_value = False
with self.assertRaises(SupersetSecurityException):
security_manager.raise_for_access(datasource=datasource)
@patch("superset.security.SupersetSecurityManager.can_access")
def test_raise_for_access_query(self, mock_can_access):
@patch("superset.views.utils.is_owner")
def test_raise_for_access_query(self, mock_can_access, mock_is_owner):
query = Mock(
database=get_example_database(), schema="bar", sql="SELECT * FROM foo"
)
@@ -900,6 +905,7 @@ class TestSecurityManager(SupersetTestCase):
security_manager.raise_for_access(query=query)
mock_can_access.return_value = False
mock_is_owner.return_value = False
with self.assertRaises(SupersetSecurityException):
security_manager.raise_for_access(query=query)
@@ -1064,6 +1070,7 @@ class TestDatasources(SupersetTestCase):
class FakeRequest:
headers: Any = {}
form: Any = {}
class TestGuestTokens(SupersetTestCase):
@@ -1111,6 +1118,17 @@ class TestGuestTokens(SupersetTestCase):
self.assertIsNotNone(guest_user)
self.assertEqual("test_guest", guest_user.username)
def test_get_guest_user_with_request_form(self):
token = self.create_guest_token()
fake_request = FakeRequest()
fake_request.headers[current_app.config["GUEST_TOKEN_HEADER_NAME"]] = None
fake_request.form["guest_token"] = token
guest_user = security_manager.get_guest_user_from_request(fake_request)
self.assertIsNotNone(guest_user)
self.assertEqual("test_guest", guest_user.username)
@patch("superset.security.SupersetSecurityManager._get_current_epoch_time")
def test_get_guest_user_expired_token(self, get_time_mock):
# make a just-expired token

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