mirror of
https://github.com/apache/superset.git
synced 2026-06-27 18:35:32 +00:00
Compare commits
48 Commits
chore/ci/s
...
2.0.0rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
067495d954 | ||
|
|
bba486b08c | ||
|
|
6e74f3e82c | ||
|
|
3e97c60c8d | ||
|
|
747b011bb7 | ||
|
|
dc71454416 | ||
|
|
4dcc805dee | ||
|
|
3b5513be36 | ||
|
|
4b2397f0c7 | ||
|
|
9a26a211d4 | ||
|
|
eedcefc64a | ||
|
|
eba63b4add | ||
|
|
edbbf886af | ||
|
|
80e2f1abe7 | ||
|
|
b5a4c06d82 | ||
|
|
789f99341b | ||
|
|
63229dcf56 | ||
|
|
92038db579 | ||
|
|
b8d9208b6e | ||
|
|
885bbdde95 | ||
|
|
25a6f02cd6 | ||
|
|
bbca109ea3 | ||
|
|
bcc23bbacd | ||
|
|
23061d6822 | ||
|
|
40a9257311 | ||
|
|
ca0544a573 | ||
|
|
d789f376b3 | ||
|
|
3d850426ff | ||
|
|
4728d8f49b | ||
|
|
9e6a3e1a4e | ||
|
|
2d551faaf4 | ||
|
|
f58ad259ac | ||
|
|
4e93690e19 | ||
|
|
d1ac6e5db4 | ||
|
|
59fbf2a202 | ||
|
|
4841e8fb9c | ||
|
|
06592180ea | ||
|
|
cb270034f3 | ||
|
|
7e48de484a | ||
|
|
a08499d88d | ||
|
|
bd721cd86c | ||
|
|
67c853790d | ||
|
|
3b3c3be9b2 | ||
|
|
2f07a88c32 | ||
|
|
128722085c | ||
|
|
78c577b515 | ||
|
|
9aa047cf12 | ||
|
|
ce9807941b |
494
CHANGELOG.md
494
CHANGELOG.md
@@ -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**
|
||||
|
||||
13
UPDATING.md
13
UPDATING.md
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
4
setup.py
4
setup.py
@@ -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"],
|
||||
|
||||
51
superset-frontend/package-lock.json
generated
51
superset-frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -363,6 +363,10 @@ export interface ControlPanelConfig {
|
||||
standardizedFormData: StandardizedFormDataInterface;
|
||||
},
|
||||
) => QueryFormData;
|
||||
updateStandardizedState?: (
|
||||
prevState: StandardizedState,
|
||||
currState: StandardizedState,
|
||||
) => StandardizedState;
|
||||
}
|
||||
|
||||
export type ControlOverrides = {
|
||||
|
||||
@@ -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 = (
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ export interface SupersetClientInterface
|
||||
| 'delete'
|
||||
| 'get'
|
||||
| 'post'
|
||||
| 'postForm'
|
||||
| 'put'
|
||||
| 'request'
|
||||
| 'init'
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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)', () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,3 +81,5 @@ setupSupersetClient();
|
||||
jest.mock('src/hooks/useTabId', () => ({
|
||||
useTabId: () => 1,
|
||||
}));
|
||||
|
||||
process.env.WEBPACK_MODE = 'test';
|
||||
|
||||
@@ -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'))),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
.
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -174,7 +174,6 @@ class HeaderActionsDropdown extends React.PureComponent {
|
||||
downloadAsImage(
|
||||
SCREENSHOT_NODE_SELECTOR,
|
||||
this.props.dashboardTitle,
|
||||
{},
|
||||
true,
|
||||
)(domEvent).then(() => {
|
||||
menu.style.visibility = 'visible';
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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} />, {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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];
|
||||
|
||||
37
superset-frontend/src/types/dom-to-image-more.d.ts
vendored
Normal file
37
superset-frontend/src/types/dom-to-image-more.d.ts
vendored
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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'] = [];
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'],
|
||||
],
|
||||
},
|
||||
|
||||
@@ -12,9 +12,5 @@
|
||||
"dependencies": {},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=0.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.9.1",
|
||||
"npm": "^7.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
"types": [
|
||||
"@emotion/react/types/css-prop",
|
||||
"jest",
|
||||
"@testing-library/jest-dom"
|
||||
"@testing-library/jest-dom",
|
||||
"@types/node"
|
||||
],
|
||||
|
||||
/* Emit */
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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] "
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]:
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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, ...]]]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 "黄色"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
172
tests/integration_tests/explore/permalink/commands_tests.py
Normal file
172
tests/integration_tests/explore/permalink/commands_tests.py
Normal 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
|
||||
119
tests/integration_tests/reports/alert_tests.py
Normal file
119
tests/integration_tests/reports/alert_tests.py
Normal 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
|
||||
@@ -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
Reference in New Issue
Block a user