mirror of
https://github.com/apache/superset.git
synced 2026-06-11 02:29:19 +00:00
Compare commits
104 Commits
fix/chart-
...
1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
286ba5d37d | ||
|
|
457a9e7d21 | ||
|
|
768a1dcc12 | ||
|
|
acda8a68bc | ||
|
|
c53eaaec57 | ||
|
|
b04a9f8ca1 | ||
|
|
ddc01a9b76 | ||
|
|
296b925112 | ||
|
|
73a7f6b3df | ||
|
|
8e9b16d7f8 | ||
|
|
b95e697ef9 | ||
|
|
523c97b04f | ||
|
|
555ba7d04b | ||
|
|
693b428228 | ||
|
|
711975a39f | ||
|
|
6653954b92 | ||
|
|
6994d0012e | ||
|
|
dd99bf2ea4 | ||
|
|
d8cae30f8c | ||
|
|
c621deeb8f | ||
|
|
72fe94842d | ||
|
|
4a92ba0b12 | ||
|
|
f78928eba1 | ||
|
|
f96189421b | ||
|
|
b932e5e458 | ||
|
|
0f81b04235 | ||
|
|
acb271e5ee | ||
|
|
69143b60f5 | ||
|
|
241aef8617 | ||
|
|
3097f86bf8 | ||
|
|
b8c3f61097 | ||
|
|
b7b35ab5ba | ||
|
|
97ef416381 | ||
|
|
13de2422d5 | ||
|
|
9e78db81c3 | ||
|
|
df5951eedf | ||
|
|
ad9b0f973b | ||
|
|
e9ac57e3c2 | ||
|
|
97f86ffe1e | ||
|
|
cd4583331d | ||
|
|
745325beb2 | ||
|
|
c92c6761b6 | ||
|
|
7f799ed00a | ||
|
|
6d19de8f66 | ||
|
|
0e7b220917 | ||
|
|
3bfb4d689a | ||
|
|
6599262cfe | ||
|
|
9ca7e0b752 | ||
|
|
90ad28bbe6 | ||
|
|
52f747b3fc | ||
|
|
56897ab14c | ||
|
|
f36a7f0d2c | ||
|
|
90f432e18c | ||
|
|
f749834a1a | ||
|
|
31c57db0aa | ||
|
|
f56a25d2d9 | ||
|
|
185eaf7636 | ||
|
|
4c6a2597b4 | ||
|
|
bc5e05b1df | ||
|
|
52ae86de05 | ||
|
|
9dc44a443e | ||
|
|
79ad7e8529 | ||
|
|
a9298ecb9e | ||
|
|
61a6795961 | ||
|
|
93de225f19 | ||
|
|
2b6a8652ec | ||
|
|
077c6378ed | ||
|
|
bab7e7a842 | ||
|
|
77415685ce | ||
|
|
3bf5852d67 | ||
|
|
cf922e1dcf | ||
|
|
630b49f4a3 | ||
|
|
a38115d517 | ||
|
|
74b37c3631 | ||
|
|
2d59be3fd3 | ||
|
|
cfeb5580b8 | ||
|
|
33b56bb31d | ||
|
|
8146a5cad3 | ||
|
|
7fa5a26147 | ||
|
|
762945b3f3 | ||
|
|
80d09e63b2 | ||
|
|
f59fe808f8 | ||
|
|
8383b2c03d | ||
|
|
951bbcf276 | ||
|
|
60ad321dcd | ||
|
|
3e98e9cd79 | ||
|
|
1273cfb6b5 | ||
|
|
5b04835d6e | ||
|
|
eb2d8882cf | ||
|
|
d5602a6017 | ||
|
|
95679d7594 | ||
|
|
0771475383 | ||
|
|
a688da5aa7 | ||
|
|
0bacb2780b | ||
|
|
dec3ff770f | ||
|
|
4caecd7202 | ||
|
|
8506e4f555 | ||
|
|
b6e4b0d6c1 | ||
|
|
88ee092fb2 | ||
|
|
7cfa0e7b8c | ||
|
|
d320a97ae6 | ||
|
|
5c2835c266 | ||
|
|
aec507d35c | ||
|
|
3c4f4dbf06 |
792
CHANGELOG.md
792
CHANGELOG.md
@@ -17,7 +17,797 @@ specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
## Change Log
|
||||
### 1.2 (Date TBD)
|
||||
### 1.3.2
|
||||
**Fixes**
|
||||
- [#16928](https://github.com/apache/superset/pull/16928) fix: handle mixed time-series error (@yougyoung94)
|
||||
- [#16955](https://github.com/apache/superset/pull/16955) fix: don't log invalid redirect URL sent by user (@dpgaspar)
|
||||
- [#16953](https://github.com/apache/superset/pull/16953) fix(examples): incorrect covid row component id (@villebro)
|
||||
- [#16949](https://github.com/apache/superset/pull/16949) fix(explore): Ensuring parameters key is included (@craig-rueda)
|
||||
- [#16933](https://github.com/apache/superset/pull/16933) fix(dashboard): recursive parent on dashboard components (@villebro)
|
||||
- [#16893](https://github.com/apache/superset/pull/16893) fix: Clear native filters state (@simcha90)
|
||||
|
||||
### 1.3.1
|
||||
**Features**
|
||||
- [#16711](https://github.com/apache/superset/pull/16711) feat(jinja): improve url parameter formatting (@villebro)
|
||||
- [#14955](https://github.com/apache/superset/pull/14955) feat: show build number value in the About if present in the config (@cccs-joel)
|
||||
- [#16594](https://github.com/apache/superset/pull/16594) feat: Experimental cross-filter plugins (@simcha90)
|
||||
- [#16416](https://github.com/apache/superset/pull/16416) feat: add Shillelagh DB engine spec (@betodealmeida)
|
||||
- [#16167](https://github.com/apache/superset/pull/16167) feat: Adding Rockset db engine spec (@srinify)
|
||||
|
||||
**Fixes**
|
||||
- [#16776](https://github.com/apache/superset/pull/16776) fix(dataset): retain is_dttm if set on metadata sync (@villebro)
|
||||
- [#16716](https://github.com/apache/superset/pull/16716) fix(pandas-postprocessing): percentage compare to use correct column (@villebro)
|
||||
- [#16692](https://github.com/apache/superset/pull/16692) fix: catch exception when create connection (@zhaoyongjie)
|
||||
- [#16699](https://github.com/apache/superset/pull/16699) fix(explore): only refresh data panel on relevant changes (@villebro)
|
||||
- [#16687](https://github.com/apache/superset/pull/16687) fix: don't send invalid URLs back to the user (@dpgaspar)
|
||||
- [#16662](https://github.com/apache/superset/pull/16662) fix: fix assignment in FilterBoxViz (@tianhe1986)
|
||||
- [#16634](https://github.com/apache/superset/pull/16634) fix(sqla): support for date adhoc filter (@villebro)
|
||||
- [#16536](https://github.com/apache/superset/pull/16536) fix: params in sql lab are jumpy in the ace editor (@eschutho)
|
||||
- [#16614](https://github.com/apache/superset/pull/16614) fix: TemporalWrapperType string representation (@villebro)
|
||||
- [#16452](https://github.com/apache/superset/pull/16452) fix: queryEditor bug (@AAfghahi)
|
||||
- [#16374](https://github.com/apache/superset/pull/16374) fix: update table ID in query context on chart import (@betodealmeida)
|
||||
- [#16289](https://github.com/apache/superset/pull/16289) fix: improve pivot post-processing (@betodealmeida)
|
||||
- [#16262](https://github.com/apache/superset/pull/16262) fix: pivot col names in post_process (@betodealmeida)
|
||||
- [#16592](https://github.com/apache/superset/pull/16592) fix: Remove export CSV in old filter box (@duynguyenhoang)
|
||||
- [#16573](https://github.com/apache/superset/pull/16573) fix: impersonate user label/tooltip (@betodealmeida)
|
||||
- [#16412](https://github.com/apache/superset/pull/16412) fix: Support Jinja template functions in global async queries (@robdiciuccio)
|
||||
- [#16482](https://github.com/apache/superset/pull/16482) fix: can't drop column when name overlap (@zhaoyongjie)
|
||||
- [#16526](https://github.com/apache/superset/pull/16526) fix: Set correct comparison operator for snowflake-sqlalchemy pinning (@danielewood)
|
||||
- [#16372](https://github.com/apache/superset/pull/16372) fix: ensure setting operator to `None` (#16371) (@grumpy-miner)
|
||||
- [#16515](https://github.com/apache/superset/pull/16515) fix: Pin snowflake-sqlalchemy to 1.2.4 (@danielewood)
|
||||
- [#16468](https://github.com/apache/superset/pull/16468) fix(native-filters): handle undefined control value gracefully (@villebro)
|
||||
- [#16464](https://github.com/apache/superset/pull/16464) fix: prevent page crash when chart can't render (@zhaoyongjie)
|
||||
- [#16460](https://github.com/apache/superset/pull/16460) fix(native-filters): handle null values in value filter (@villebro)
|
||||
- [#16299](https://github.com/apache/superset/pull/16299) fix: copy to Clipboard order (@AAfghahi)
|
||||
- [#16369](https://github.com/apache/superset/pull/16369) fix: call external metadata endpoint with correct rison object (@villebro)
|
||||
- [#16293](https://github.com/apache/superset/pull/16293) fix(sqlite): week grain refer to day of week (@villebro)
|
||||
|
||||
**Others**
|
||||
- [#16702](https://github.com/apache/superset/pull/16702) perf(dashboard): native filter select will be stuck if there has a filter box. (@stephenLYZ)
|
||||
- [#16648](https://github.com/apache/superset/pull/16648) chore: Bump Flask-OpenID to 1.3.0 (@dpgaspar)
|
||||
- [#16193](https://github.com/apache/superset/pull/16193) refactor: external metadata fetch API (@zhaoyongjie)
|
||||
|
||||
### 1.3.0 (Fri Aug 13 20:41:03 2021 -0700)
|
||||
**Database Migrations**
|
||||
- [#16160](https://github.com/apache/superset/pull/16160) feat: change query predicate to text (@eschutho)
|
||||
- [#16077](https://github.com/apache/superset/pull/16077) fix: ensure that users viewing chart does not automatically save edit data (@pkdotson)
|
||||
- [#16098](https://github.com/apache/superset/pull/16098) fix: migrate_roles (@betodealmeida)
|
||||
- [#16078](https://github.com/apache/superset/pull/16078) chore: simplify chart permissions (@betodealmeida)
|
||||
- [#16045](https://github.com/apache/superset/pull/16045) feat(explore): add automatic conditional formatter to pivot table v2 (@villebro)
|
||||
- [#16038](https://github.com/apache/superset/pull/16038) fix: handle schemas_allowed_for_csv_upload serde (@betodealmeida)
|
||||
- [#15909](https://github.com/apache/superset/pull/15909) fix: Ensure table uniqueness on update (@john-bodley)
|
||||
- [#15747](https://github.com/apache/superset/pull/15747) feat: migration to add timezone to report schedule (@eschutho)
|
||||
- [#15824](https://github.com/apache/superset/pull/15824) feat: store query context when saving charts (@betodealmeida)
|
||||
- [#15822](https://github.com/apache/superset/pull/15822) fix: benchmark_migration.py needs to close sssion (@betodealmeida)
|
||||
- [#15807](https://github.com/apache/superset/pull/15807) fix: migration script can't drop constraint (@betodealmeida)
|
||||
- [#15791](https://github.com/apache/superset/pull/15791) fix: migration downgrade references wrong column (@betodealmeida)
|
||||
- [#15725](https://github.com/apache/superset/pull/15725) fix: change to alerts_reports (@AAfghahi)
|
||||
- [#15683](https://github.com/apache/superset/pull/15683) feat: add Column to reports model (@AAfghahi)
|
||||
- [#15507](https://github.com/apache/superset/pull/15507) chore(python-testing): move memoized tests to unit tests (@amitmiran137)
|
||||
- [#15032](https://github.com/apache/superset/pull/15032) fix: benchmark migration script (@betodealmeida)
|
||||
- [#14433](https://github.com/apache/superset/pull/14433) feat: Adding configuration_method column to Database Model (@AAfghahi)
|
||||
|
||||
**Features**
|
||||
- [#16199](https://github.com/apache/superset/pull/16199) feat: Changing Dataset names (@AAfghahi)
|
||||
- [#16183](https://github.com/apache/superset/pull/16183) feat: update covid dashboard (@eschutho)
|
||||
- [#16178](https://github.com/apache/superset/pull/16178) feat: CLI cleanup (@AAfghahi)
|
||||
- [#16170](https://github.com/apache/superset/pull/16170) feat: Added multi-regional IPs to Database Connections (@AAfghahi)
|
||||
- [#16158](https://github.com/apache/superset/pull/16158) feat: add chart image info to reports from charts (@eschutho)
|
||||
- [#16139](https://github.com/apache/superset/pull/16139) feat(cross-filters): add support for temporal filters (@villebro)
|
||||
- [#16156](https://github.com/apache/superset/pull/16156) feat: add config to hide some user menu items (@eschutho)
|
||||
- [#16102](https://github.com/apache/superset/pull/16102) feat: add sticky state to tables and loadingcards state. (@pkdotson)
|
||||
- [#16131](https://github.com/apache/superset/pull/16131) feat: better errors for report in charts and dashboard (@AAfghahi)
|
||||
- [#16095](https://github.com/apache/superset/pull/16095) feat: added google alert to DB Connection Form (@AAfghahi)
|
||||
- [#16052](https://github.com/apache/superset/pull/16052) feat: handle subtle bug with load-examples (@betodealmeida)
|
||||
- [#16027](https://github.com/apache/superset/pull/16027) feat: Self subscribe reports (@eschutho)
|
||||
- [#15887](https://github.com/apache/superset/pull/15887) feat: auto sync table columns when change dataset (@zhaoyongjie)
|
||||
- [#15953](https://github.com/apache/superset/pull/15953) feat: send post-processed data in reports (@betodealmeida)
|
||||
- [#15853](https://github.com/apache/superset/pull/15853) feat(homepage): add more cards and new layout (@pkdotson)
|
||||
- [#15879](https://github.com/apache/superset/pull/15879) feat: post-processing for pivot table v2 (@betodealmeida)
|
||||
- [#15806](https://github.com/apache/superset/pull/15806) feat: send report data to Slack (@betodealmeida)
|
||||
- [#15801](https://github.com/apache/superset/pull/15801) feat(dbc ui): Adding Google Sheets Dynamic Form (@hughhhh)
|
||||
- [#15920](https://github.com/apache/superset/pull/15920) feat: add timezone selector to alerts and reports (@eschutho)
|
||||
- [#15805](https://github.com/apache/superset/pull/15805) feat: send data embedded in report email (@betodealmeida)
|
||||
- [#15279](https://github.com/apache/superset/pull/15279) feat: run extra query on QueryObject and add compare operator for post_processing (@zhaoyongjie)
|
||||
- [#15849](https://github.com/apache/superset/pull/15849) feat: add timezones to report cron (@eschutho)
|
||||
- [#15846](https://github.com/apache/superset/pull/15846) feat: call screenshot to store `query_context` (@betodealmeida)
|
||||
- [#15864](https://github.com/apache/superset/pull/15864) feat(explore): new datasets have autocomplete filters enabled if UX_BETA is set (@kgabryje)
|
||||
- [#15843](https://github.com/apache/superset/pull/15843) feat: apply post processing to chart data (@betodealmeida)
|
||||
- [#15880](https://github.com/apache/superset/pull/15880) feat: add timezone selector component (@eschutho)
|
||||
- [#15882](https://github.com/apache/superset/pull/15882) feat: deprecate plugins by their metadata (@suddjian)
|
||||
- [#15792](https://github.com/apache/superset/pull/15792) feat(homepage): conditionally render viewed tab and move examples to chart and dashboard table (@pkdotson)
|
||||
- [#15798](https://github.com/apache/superset/pull/15798) feat(explore): default aggregate for string/numeric columns when creating metric (@kgabryje)
|
||||
- [#15830](https://github.com/apache/superset/pull/15830) feat: use new API endpoint to build CSV reports (@betodealmeida)
|
||||
- [#15827](https://github.com/apache/superset/pull/15827) feat: add `GET /api/v1/chart/{chart_id}/data/?format{format}` API (@betodealmeida)
|
||||
- [#15719](https://github.com/apache/superset/pull/15719) feat: adding Progress Bar to Benchmark script (@AAfghahi)
|
||||
- [#15712](https://github.com/apache/superset/pull/15712) feat: add show columns to Reports model (@AAfghahi)
|
||||
- [#15740](https://github.com/apache/superset/pull/15740) feat(explore): UX improvements for drag'n'dropping time column (@kgabryje)
|
||||
- [#15685](https://github.com/apache/superset/pull/15685) feat: add logic to creation_method for reports schedule (@AAfghahi)
|
||||
- [#15711](https://github.com/apache/superset/pull/15711) feat(homepage): move savequeries table and render open conditionally (@pkdotson)
|
||||
- [#15628](https://github.com/apache/superset/pull/15628) feat(menu): expand support for custom branding (@nytai)
|
||||
- [#15403](https://github.com/apache/superset/pull/15403) feat: cancel db query on stop (@koszti)
|
||||
- [#15651](https://github.com/apache/superset/pull/15651) feat(explore): Implement conditional formatting component (@kgabryje)
|
||||
- [#15303](https://github.com/apache/superset/pull/15303) feat(explore): Upgraded viz select gallery (@suddjian)
|
||||
- [#15578](https://github.com/apache/superset/pull/15578) feat: validate_parameters for GSheets (@betodealmeida)
|
||||
- [#15502](https://github.com/apache/superset/pull/15502) feat: supporting jinja templating in saved metrics (@guydou)
|
||||
- [#15500](https://github.com/apache/superset/pull/15500) feat(cross-filters): add option to clear set cross filters (@villebro)
|
||||
- [#14775](https://github.com/apache/superset/pull/14775) feat: extra table metadata for Google Sheets (@betodealmeida)
|
||||
- [#14881](https://github.com/apache/superset/pull/14881) feat: Database Connection UI (@hughhhh)
|
||||
- [#15419](https://github.com/apache/superset/pull/15419) feat(native-filters): add null option to value filter (@mironovmeow)
|
||||
- [#15273](https://github.com/apache/superset/pull/15273) feat: update ingress api version to v1 (@mvoitko)
|
||||
- [#15454](https://github.com/apache/superset/pull/15454) feat(dashboard-groupby): group by - add ability to exclude columns (@einatbar)
|
||||
- [#15482](https://github.com/apache/superset/pull/15482) feat: more SIP-40 errors (@betodealmeida)
|
||||
- [#15475](https://github.com/apache/superset/pull/15475) feat(add IBM Netezza support): documentation changes for Netezza (@abhishekjog)
|
||||
- [#15450](https://github.com/apache/superset/pull/15450) feat(add Netezza database): Add IBM Netezza support (@abhishekjog)
|
||||
- [#15177](https://github.com/apache/superset/pull/15177) feat(trino): add support for query cost estimate #15166 (@rijojoseph07)
|
||||
- [#15426](https://github.com/apache/superset/pull/15426) feat: add env vars from multiple secrets in Helm chart (@mvoitko)
|
||||
- [#15436](https://github.com/apache/superset/pull/15436) feat: add more SIP-40 errors to SQL Lab (@betodealmeida)
|
||||
- [#15432](https://github.com/apache/superset/pull/15432) feat: Better Errors in SQL Lab (@AAfghahi)
|
||||
- [#15427](https://github.com/apache/superset/pull/15427) feat(native-filters): add support for preselect filters (@villebro)
|
||||
- [#15409](https://github.com/apache/superset/pull/15409) feat: more SIP-40 error messages for SQL Lab (@betodealmeida)
|
||||
- [#15153](https://github.com/apache/superset/pull/15153) feat: Adding a show all button to the column/metrics list in the explore view (Allow more than 50 columns to be shown) (@cccs-RyanS)
|
||||
- [#15385](https://github.com/apache/superset/pull/15385) feat(native-filters): Hide non-numeric columns in numeric range filter (@kgabryje)
|
||||
- [#15340](https://github.com/apache/superset/pull/15340) feat: add possibility to specify Service Account name for the Deployment in the Helm chart (@mvoitko)
|
||||
- [#15342](https://github.com/apache/superset/pull/15342) feat: custom error SQL Lab timeout (@betodealmeida)
|
||||
- [#15302](https://github.com/apache/superset/pull/15302) feat(native-filters): Set default scope by filters' and charts' datasets (@kgabryje)
|
||||
- [#15206](https://github.com/apache/superset/pull/15206) feat: implement specific errors for SQL Lab (@betodealmeida)
|
||||
- [#15270](https://github.com/apache/superset/pull/15270) feat(editable-title): move cursor and scroll to the end (@stephenLYZ)
|
||||
- [#15157](https://github.com/apache/superset/pull/15157) feat: Synchronously return cached charts (@benjreinhart)
|
||||
- [#15276](https://github.com/apache/superset/pull/15276) feat(native-filters): Show/Highlight errored/focused status (@simcha90)
|
||||
- [#15253](https://github.com/apache/superset/pull/15253) feat(native-filters): add support for import/export dashboard (@villebro)
|
||||
- [#15247](https://github.com/apache/superset/pull/15247) feat(sql): add jinja support to metrics and expressions (@villebro)
|
||||
- [#14682](https://github.com/apache/superset/pull/14682) feat(db_engine_specs): Add quirks to support Ascend.io/HiveServer2 with Impala driver. (@danielewood)
|
||||
- [#15225](https://github.com/apache/superset/pull/15225) feat(native-filters): Hide time filters if loaded datasets don't have temporal columns (@kgabryje)
|
||||
- [#15222](https://github.com/apache/superset/pull/15222) feat(native-filters): Disable Apply button if filter required (@simcha90)
|
||||
- [#15158](https://github.com/apache/superset/pull/15158) feat: show rich error messages on past failed queries (@betodealmeida)
|
||||
- [#15121](https://github.com/apache/superset/pull/15121) feat: Select component (Iteration 1) (@geido)
|
||||
- [#15188](https://github.com/apache/superset/pull/15188) feat(api): add featured datatypes to dashboard dataset ep (@villebro)
|
||||
- [#14703](https://github.com/apache/superset/pull/14703) feat(helm): Make local admin optional (@danielewood)
|
||||
- [#15117](https://github.com/apache/superset/pull/15117) feat(native-filters): add optional time col to time range (@villebro)
|
||||
- [#15105](https://github.com/apache/superset/pull/15105) feat(webpack): configure publicPath via ASSET_BASE_URL env var (@nytai)
|
||||
- [#14872](https://github.com/apache/superset/pull/14872) feat(Explore): add sort to edit dataset modal (@pkdotson)
|
||||
- [#15046](https://github.com/apache/superset/pull/15046) feat(dashboard): Let users download full CSV of a table (@m-ajay)
|
||||
- [#15107](https://github.com/apache/superset/pull/15107) feat: show spinner on exports (@betodealmeida)
|
||||
- [#15120](https://github.com/apache/superset/pull/15120) feat(native-filters): Defer loading filters data until filter is visible (@kgabryje)
|
||||
- [#15063](https://github.com/apache/superset/pull/15063) feat(native-filters): Hide filters which don't affect any visible charts (@kgabryje)
|
||||
- [#15055](https://github.com/apache/superset/pull/15055) feat: spinner for imports (@betodealmeida)
|
||||
- [#15057](https://github.com/apache/superset/pull/15057) feat: style import button (@betodealmeida)
|
||||
- [#14868](https://github.com/apache/superset/pull/14868) feat: add more timeout configuration on screenshots (@dpgaspar)
|
||||
- [#14921](https://github.com/apache/superset/pull/14921) feat(filter-box): hide druid options if druid not enabled (@villebro)
|
||||
- [#14869](https://github.com/apache/superset/pull/14869) feat(native-filters): Support default to first value in select filter (@simcha90)
|
||||
- [#14981](https://github.com/apache/superset/pull/14981) feat(native-filters): add markers and number formatter to range filter (@villebro)
|
||||
- [#14966](https://github.com/apache/superset/pull/14966) feat(native-filters): apply cascading without instant filtering (@villebro)
|
||||
- [#14863](https://github.com/apache/superset/pull/14863) feat: add type_generic and is_dttm to table metadata (@zhaoyongjie)
|
||||
- [#14934](https://github.com/apache/superset/pull/14934) feat: Adding FORCE_SSL as feature flag in config.py (@AAfghahi)
|
||||
- [#14933](https://github.com/apache/superset/pull/14933) feat(dashboard/native-filters): Hide filters out of scope of current tab (@kgabryje)
|
||||
- [#14818](https://github.com/apache/superset/pull/14818) feat: Icon Button (@lyndsiWilliams)
|
||||
- [#14832](https://github.com/apache/superset/pull/14832) feat: validation db modal (@eschutho)
|
||||
- [#14765](https://github.com/apache/superset/pull/14765) feat: add support for filters in sqlLab (@cccs-jc)
|
||||
- [#14784](https://github.com/apache/superset/pull/14784) feat(native-filter): Hide native filters (@simcha90)
|
||||
- [#14843](https://github.com/apache/superset/pull/14843) feat(trino): add support for user impersonation (@rijojoseph07)
|
||||
- [#14865](https://github.com/apache/superset/pull/14865) feat(dashboard): Highlight tabs that contain a chart in scope of focused native filter (@kgabryje)
|
||||
- [#14873](https://github.com/apache/superset/pull/14873) feat(native-filters): improve inverse selection indicators (@villebro)
|
||||
- [#14883](https://github.com/apache/superset/pull/14883) feat: validate database parameters (@betodealmeida)
|
||||
- [#14842](https://github.com/apache/superset/pull/14842) feat(native-filters): sort selected values on blur (@villebro)
|
||||
- [#14486](https://github.com/apache/superset/pull/14486) feat(native-filter): limit max tag count for selected filter values (@einatbar)
|
||||
- [#14686](https://github.com/apache/superset/pull/14686) feat: ability to pull from your own docker registry using a secret (@jmistry)
|
||||
- [#14803](https://github.com/apache/superset/pull/14803) feat: return parameters only for DB with default driver (@betodealmeida)
|
||||
- [#14484](https://github.com/apache/superset/pull/14484) feat: chart gallery search improvement (@einatbar)
|
||||
- [#14661](https://github.com/apache/superset/pull/14661) feat(explore): Remove default for time range filter and Metrics (@kgabryje)
|
||||
- [#14710](https://github.com/apache/superset/pull/14710) feat(native-filters): add search all filter options (@villebro)
|
||||
- [#14721](https://github.com/apache/superset/pull/14721) feat: Create BigQuery Parameters for DatabaseModal (@hughhhh)
|
||||
- [#14767](https://github.com/apache/superset/pull/14767) feat: enable user impersonation in GSheets (@betodealmeida)
|
||||
- [#14583](https://github.com/apache/superset/pull/14583) feat: save database with new dynamic form (@eschutho)
|
||||
- [#14695](https://github.com/apache/superset/pull/14695) feat: make tabs sticky in homepage (@pkdotson)
|
||||
- [#14507](https://github.com/apache/superset/pull/14507) feat: Add a remove_filter flag to the jinja filter_values function and add a new get_filters function (see issue 13943 for more details) (@cccs-jc)
|
||||
- [#14693](https://github.com/apache/superset/pull/14693) feat(native-filters): Highlight charts affected by focused native filter (@kgabryje)
|
||||
- [#14530](https://github.com/apache/superset/pull/14530) feat: Labeled Error-bound Input (@lyndsiWilliams)
|
||||
- [#14652](https://github.com/apache/superset/pull/14652) feat: Add `make update` cmd (@hughhhh)
|
||||
- [#14724](https://github.com/apache/superset/pull/14724) feat: do not redirect on 404/500 (@betodealmeida)
|
||||
- [#14647](https://github.com/apache/superset/pull/14647) feat: Add headers for DatabaseModal (@hughhhh)
|
||||
- [#14680](https://github.com/apache/superset/pull/14680) feat: Expanded Parameters for Mysql (@AAfghahi)
|
||||
- [#14653](https://github.com/apache/superset/pull/14653) feat: Add Parameters fields to GET Database (@hughhhh)
|
||||
- [#14677](https://github.com/apache/superset/pull/14677) feat: redirect 404/500 to static pages (@betodealmeida)
|
||||
- [#14675](https://github.com/apache/superset/pull/14675) feat: redirect to /login when CSRF expired (@betodealmeida)
|
||||
- [#14667](https://github.com/apache/superset/pull/14667) feat(dashboard): View query of the chart in dashboard (@m-ajay)
|
||||
- [#14381](https://github.com/apache/superset/pull/14381) feat: Better return messages in SQL Editor (@AAfghahi)
|
||||
- [#14673](https://github.com/apache/superset/pull/14673) feat: add SSL to new DB parameters (@betodealmeida)
|
||||
- [#14668](https://github.com/apache/superset/pull/14668) feat: make config method optional (@AAfghahi)
|
||||
- [#14451](https://github.com/apache/superset/pull/14451) feat: Configuration Method and expanded parameters for Database Model (@AAfghahi)
|
||||
- [#14560](https://github.com/apache/superset/pull/14560) feat: bumping echarts plugin, adding new treemap plugin (@rusackas)
|
||||
- [#14547](https://github.com/apache/superset/pull/14547) feat: add generic type to column payload (@villebro)
|
||||
- [#14420](https://github.com/apache/superset/pull/14420) feat: API endpoint to validate databases using separate parameters (@betodealmeida)
|
||||
- [#14527](https://github.com/apache/superset/pull/14527) feat(alert&report): set image width for email (@lilykuang)
|
||||
- [#14514](https://github.com/apache/superset/pull/14514) feat: Containerize WebSocket server (@benjreinhart)
|
||||
- [#14493](https://github.com/apache/superset/pull/14493) feat(explore): collapse time section if no ts columns (@villebro)
|
||||
- [#14516](https://github.com/apache/superset/pull/14516) feat(viz): new tree chart (@mayurnewase)
|
||||
- [#14491](https://github.com/apache/superset/pull/14491) feat: add `make format` command (@hughhhh)
|
||||
- [#14470](https://github.com/apache/superset/pull/14470) feat: db modal split (@eschutho)
|
||||
- [#14480](https://github.com/apache/superset/pull/14480) feat(viz): add funnel chart (@villebro)
|
||||
|
||||
**Fixes**
|
||||
- [#16260](https://github.com/apache/superset/pull/16260) fix: check roles before fetching reports (@eschutho)
|
||||
- [#16259](https://github.com/apache/superset/pull/16259) fix: pivot columns with ints for name (@betodealmeida)
|
||||
- [#16253](https://github.com/apache/superset/pull/16253) fix: Homepage dashboard examples tab does not show user created objects (@pkdotson)
|
||||
- [#16233](https://github.com/apache/superset/pull/16233) fix(dashboard): cross filter chart highlight when filters badge icon clicked (@kgabryje)
|
||||
- [#16241](https://github.com/apache/superset/pull/16241) fix: validate_parameters and query (@betodealmeida)
|
||||
- [#16240](https://github.com/apache/superset/pull/16240) fix: Remove Advanced Analytics tag for 2 charts (@rusackas)
|
||||
- [#16214](https://github.com/apache/superset/pull/16214) fix: remove encryption from db params (@eschutho)
|
||||
- [#16230](https://github.com/apache/superset/pull/16230) fix(explore): conditional formatting value validators (@kgabryje)
|
||||
- [#16212](https://github.com/apache/superset/pull/16212) fix: example tabs filter (@pkdotson)
|
||||
- [#16208](https://github.com/apache/superset/pull/16208) fix: sorting on "Modified By" in chart table (@pkdotson)
|
||||
- [#16196](https://github.com/apache/superset/pull/16196) fix(explore): adhoc metrics popover resets label after hovering outside (@kgabryje)
|
||||
- [#16190](https://github.com/apache/superset/pull/16190) fix(explore): metric label disappearing in some scenarios (@kgabryje)
|
||||
- [#16171](https://github.com/apache/superset/pull/16171) fix: change listivew card layouts to the new homepage card layout (@pkdotson)
|
||||
- [#16176](https://github.com/apache/superset/pull/16176) fix: ensure created user entities do not show inside examples (@pkdotson)
|
||||
- [#16175](https://github.com/apache/superset/pull/16175) fix: isDynamic function (@betodealmeida)
|
||||
- [#16162](https://github.com/apache/superset/pull/16162) fix: revert data endpoint name (@betodealmeida)
|
||||
- [#16151](https://github.com/apache/superset/pull/16151) fix: turn on SSL in database edit form show 500 error (@hughhhh)
|
||||
- [#16089](https://github.com/apache/superset/pull/16089) fix: Safari is not showing scroll bars in Explore (@michael-s-molina)
|
||||
- [#16094](https://github.com/apache/superset/pull/16094) fix: Multiple dashboard refresh triggers for the same session (@michael-s-molina)
|
||||
- [#16107](https://github.com/apache/superset/pull/16107) fix: boolean type into SQL 'in' operator (@zhaoyongjie)
|
||||
- [#16053](https://github.com/apache/superset/pull/16053) fix(dashboard): 500 error caused by data_for_slices API (@ktmud)
|
||||
- [#16161](https://github.com/apache/superset/pull/16161) fix: Big Query additional parameters field doesn't keep value (@lyndsiWilliams)
|
||||
- [#16118](https://github.com/apache/superset/pull/16118) fix: change Alert Permissions (@AAfghahi)
|
||||
- [#16088](https://github.com/apache/superset/pull/16088) fix(explore): dnd error when dragging metric if multi: false (@kgabryje)
|
||||
- [#16092](https://github.com/apache/superset/pull/16092) fix(Explore): Adjust width of cell in Time-Series Table chart (@geido)
|
||||
- [#16132](https://github.com/apache/superset/pull/16132) fix: virtual dataset wont work (@zhaoyongjie)
|
||||
- [#16115](https://github.com/apache/superset/pull/16115) fix(explore): revert dnd column dependency array change to fix infinite rerenders (@kgabryje)
|
||||
- [#16097](https://github.com/apache/superset/pull/16097) fix: move watermark to about section (@nytai)
|
||||
- [#16073](https://github.com/apache/superset/pull/16073) fix(explore): drag & drop column select component triggering onChange unnecessarily (@suddjian)
|
||||
- [#15592](https://github.com/apache/superset/pull/15592) fix(dashboard): user id can be null when there is an anonymous user (@suddjian)
|
||||
- [#16091](https://github.com/apache/superset/pull/16091) fix: load tabbed dash only for tests (@betodealmeida)
|
||||
- [#16093](https://github.com/apache/superset/pull/16093) fix: Calendar color change (@AAfghahi)
|
||||
- [#16054](https://github.com/apache/superset/pull/16054) fix: Remove grey bar for TableElement component when `metadata` is empty (@hughhhh)
|
||||
- [#16065](https://github.com/apache/superset/pull/16065) fix: Adding report bug (@AAfghahi)
|
||||
- [#16061](https://github.com/apache/superset/pull/16061) fix(native-filters): add support for boolean cols to select (@villebro)
|
||||
- [#16062](https://github.com/apache/superset/pull/16062) fix: Fixes the Select unselect for object values (@michael-s-molina)
|
||||
- [#16042](https://github.com/apache/superset/pull/16042) fix: sync columns in explore page (@zhaoyongjie)
|
||||
- [#16044](https://github.com/apache/superset/pull/16044) fix: make dataset update methods static instead of global (@etr2460)
|
||||
- [#16037](https://github.com/apache/superset/pull/16037) fix: DB exported with incorrect type (@betodealmeida)
|
||||
- [#15954](https://github.com/apache/superset/pull/15954) fix: Fix long dashboards screenshot emails (@hughhhh)
|
||||
- [#16024](https://github.com/apache/superset/pull/16024) fix(explore): filter popover opening after removing a filter (@kgabryje)
|
||||
- [#16035](https://github.com/apache/superset/pull/16035) fix: Modal is blinking when opening (@michael-s-molina)
|
||||
- [#16022](https://github.com/apache/superset/pull/16022) fix: Select the right section when categories and tags have the same name in Viz Gallery (@geido)
|
||||
- [#16026](https://github.com/apache/superset/pull/16026) fix: missing mulitiple metrics on pivot operator (@zhaoyongjie)
|
||||
- [#16031](https://github.com/apache/superset/pull/16031) fix: add feature flag to header bar (@eschutho)
|
||||
- [#16020](https://github.com/apache/superset/pull/16020) fix(explore): fix undefined error when using dnd (@kgabryje)
|
||||
- [#16017](https://github.com/apache/superset/pull/16017) fix(native-filters): add support for versioned import/export (@villebro)
|
||||
- [#15971](https://github.com/apache/superset/pull/15971) fix: Sort Metrics by ID DESC (order of creation) in the Datasource Editor (@geido)
|
||||
- [#15994](https://github.com/apache/superset/pull/15994) fix(explore): highlight Run button correctly when query is stale (@suddjian)
|
||||
- [#15991](https://github.com/apache/superset/pull/15991) fix: save DB with with query (@betodealmeida)
|
||||
- [#15975](https://github.com/apache/superset/pull/15975) fix: eliminate cartesian product columns in pivot operator (@zhaoyongjie)
|
||||
- [#15993](https://github.com/apache/superset/pull/15993) fix(dashboard): FilterBox JS error when datasets API is slow (@ktmud)
|
||||
- [#15978](https://github.com/apache/superset/pull/15978) fix: three button styles to tertiary (@stellalc7)
|
||||
- [#15992](https://github.com/apache/superset/pull/15992) fix(sqllab): revert #15891 to fix sqllab delay (@serenajiang)
|
||||
- [#15986](https://github.com/apache/superset/pull/15986) fix: remove select width (@eschutho)
|
||||
- [#15985](https://github.com/apache/superset/pull/15985) fix: Dataset field required 2 clicks to select when dashboard was empty (@michael-s-molina)
|
||||
- [#15981](https://github.com/apache/superset/pull/15981) fix: DB add modal (@betodealmeida)
|
||||
- [#15925](https://github.com/apache/superset/pull/15925) fix: Cancel alert is not appearing to all native filters modal fields (@michael-s-molina)
|
||||
- [#15952](https://github.com/apache/superset/pull/15952) fix: Name change is not lost in left side of modal when return to edition after aborting changes (@michael-s-molina)
|
||||
- [#15946](https://github.com/apache/superset/pull/15946) fix: Drag inner tab to outer tab while editing a dashboard will show an error (@michael-s-molina)
|
||||
- [#15941](https://github.com/apache/superset/pull/15941) fix(dashboard): Add required message in the tooltip for the time range filter (@geido)
|
||||
- [#15949](https://github.com/apache/superset/pull/15949) fix: Update gsheets mapping for documentation (@hughhhh)
|
||||
- [#15933](https://github.com/apache/superset/pull/15933) fix(dashboard): Show the filters popover behind the dashboard header when scrolling (@geido)
|
||||
- [#15921](https://github.com/apache/superset/pull/15921) fix(dashboard): check dashboard id before calling redux methods (@suddjian)
|
||||
- [#15918](https://github.com/apache/superset/pull/15918) fix: Keep chosen columns sort option when changing a column (@geido)
|
||||
- [#14969](https://github.com/apache/superset/pull/14969) fix: remove unused time column when update dataset (@zhaoyongjie)
|
||||
- [#15891](https://github.com/apache/superset/pull/15891) fix: Select a query from History and Run (@graceguo-supercat)
|
||||
- [#15585](https://github.com/apache/superset/pull/15585) fix: no roles being returned for anonymous user (@aspedrosa)
|
||||
- [#15893](https://github.com/apache/superset/pull/15893) fix: dashboard url error when edit slug (@zhaoyongjie)
|
||||
- [#15813](https://github.com/apache/superset/pull/15813) fix: Incorrect translations in Chinese in messages.po (@chuancyzhang)
|
||||
- [#15841](https://github.com/apache/superset/pull/15841) fix(Explore): "Customize" tab rendering behavior (@geido)
|
||||
- [#15890](https://github.com/apache/superset/pull/15890) fix(dashboard): Refresh Native Filters when Dashboard refreshes (@geido)
|
||||
- [#15865](https://github.com/apache/superset/pull/15865) fix: Update Query Context on Explore loading (@hughhhh)
|
||||
- [#15896](https://github.com/apache/superset/pull/15896) fix: Charts sort by in edit mode gets cut off (@michael-s-molina)
|
||||
- [#15897](https://github.com/apache/superset/pull/15897) fix: Download as image of dashboard chart did not work (@michael-s-molina)
|
||||
- [#15888](https://github.com/apache/superset/pull/15888) fix: Side menu of the dashboard component will scroll out of dashboard (@michael-s-molina)
|
||||
- [#15889](https://github.com/apache/superset/pull/15889) fix: New time range filter initially show advance section (@michael-s-molina)
|
||||
- [#15878](https://github.com/apache/superset/pull/15878) fix(15403): Re-enable canceling query for Hive and Presto (@john-bodley)
|
||||
- [#15874](https://github.com/apache/superset/pull/15874) fix(15482): Propagate SupersetSecurityException error (@john-bodley)
|
||||
- [#15848](https://github.com/apache/superset/pull/15848) fix: Ensure DatabaseErrorMessage works when extra is undefined (@john-bodley)
|
||||
- [#15760](https://github.com/apache/superset/pull/15760) fix: Unable to create alerts/report after introduced creation_method (@duynguyenhoang)
|
||||
- [#15869](https://github.com/apache/superset/pull/15869) fix: revert DEFAULT_SQLLAB_LIMIT to default (@betodealmeida)
|
||||
- [#15840](https://github.com/apache/superset/pull/15840) fix(explore): show multi queries results in View query modal and data pane (@kgabryje)
|
||||
- [#15709](https://github.com/apache/superset/pull/15709) fix(dashboard): Remove edit from url params when discarding changes (@geido)
|
||||
- [#15820](https://github.com/apache/superset/pull/15820) fix: Render value immediately on SQL Editor for Calculated Columns in Edit Dataset modal (@geido)
|
||||
- [#15817](https://github.com/apache/superset/pull/15817) fix: dataTablesPane cell render undefine when the dot in metric label (@zhaoyongjie)
|
||||
- [#15803](https://github.com/apache/superset/pull/15803) fix: publish the new example dashboards (@betodealmeida)
|
||||
- [#15828](https://github.com/apache/superset/pull/15828) fix: Report Schema fix (@AAfghahi)
|
||||
- [#15821](https://github.com/apache/superset/pull/15821) fix(Explore): Cell height and spacing for Data panel (@geido)
|
||||
- [#15786](https://github.com/apache/superset/pull/15786) fix: Bust chart cache when metric/column is changed (@etr2460)
|
||||
- [#15804](https://github.com/apache/superset/pull/15804) fix: create fk model in benchmark script (@betodealmeida)
|
||||
- [#15800](https://github.com/apache/superset/pull/15800) fix(dashboard): Add z-index to dashboard only when maximizing chart (@geido)
|
||||
- [#15778](https://github.com/apache/superset/pull/15778) fix(dashboard): Add resize handles to right and bottom of component (@kgabryje)
|
||||
- [#15715](https://github.com/apache/superset/pull/15715) fix: margin right on warning icon to 8px (@stellalc7)
|
||||
- [#15770](https://github.com/apache/superset/pull/15770) fix: Edit physical dataset from the Edit Dataset modal (@geido)
|
||||
- [#15781](https://github.com/apache/superset/pull/15781) fix(explore): dnd multiple columns doesn't work (@kgabryje)
|
||||
- [#15752](https://github.com/apache/superset/pull/15752) fix: Revert "quote column name if db requires (#15465)" (@eschutho)
|
||||
- [#15750](https://github.com/apache/superset/pull/15750) fix: Fixing `schemas_allowed_for_upload` field in database connection UX (@hughhhh)
|
||||
- [#15710](https://github.com/apache/superset/pull/15710) fix(explore): Set label max width with Drag&Drop in Datasource panel (@geido)
|
||||
- [#15732](https://github.com/apache/superset/pull/15732) fix(explore): wrong error message in conditional formatting (@kgabryje)
|
||||
- [#15721](https://github.com/apache/superset/pull/15721) fix: Reduce js bundle size (@etr2460)
|
||||
- [#15731](https://github.com/apache/superset/pull/15731) fix(explore): DndColumnSelect sometimes not working with multi: false (@kgabryje)
|
||||
- [#15707](https://github.com/apache/superset/pull/15707) fix: use expected label in metrics map (@zhaoyongjie)
|
||||
- [#15727](https://github.com/apache/superset/pull/15727) fix: Incorrect translations in the SQLLab in Chinese (@chuancyzhang)
|
||||
- [#15610](https://github.com/apache/superset/pull/15610) fix: Add waiting time for chart animation when screenshot (@u-aiaa)
|
||||
- [#15662](https://github.com/apache/superset/pull/15662) fix: Action icons on Antd Card in Homepage (@geido)
|
||||
- [#15669](https://github.com/apache/superset/pull/15669) fix: no lazy translation on SupersetError (@betodealmeida)
|
||||
- [#15680](https://github.com/apache/superset/pull/15680) fix: remove form title (@xiezhongfu)
|
||||
- [#15668](https://github.com/apache/superset/pull/15668) fix(dashboard): Make the View Chart In Explore menu option a link (@suddjian)
|
||||
- [#15634](https://github.com/apache/superset/pull/15634) fix: Show affected charts when interacting with the filters (@michael-s-molina)
|
||||
- [#15661](https://github.com/apache/superset/pull/15661) fix(dashboard): Filters panel height (@geido)
|
||||
- [#15670](https://github.com/apache/superset/pull/15670) fix: small fixes for Makefile (@betodealmeida)
|
||||
- [#15650](https://github.com/apache/superset/pull/15650) fix: safe removal of empty tab with scoped filters (@rusackas)
|
||||
- [#15645](https://github.com/apache/superset/pull/15645) fix: Fix test connection for extra fields (@hughhhh)
|
||||
- [#15642](https://github.com/apache/superset/pull/15642) fix: change sslmode to require for Postgres (@hughhhh)
|
||||
- [#15635](https://github.com/apache/superset/pull/15635) fix: Remove default values for engine and schemas (@hughhhh)
|
||||
- [#15614](https://github.com/apache/superset/pull/15614) fix: duplicate DB names (@betodealmeida)
|
||||
- [#15572](https://github.com/apache/superset/pull/15572) fix(native-filters): Fix required filters (@simcha90)
|
||||
- [#15590](https://github.com/apache/superset/pull/15590) fix: avoid fetching favorite status for anonymous user (@aspedrosa)
|
||||
- [#15623](https://github.com/apache/superset/pull/15623) fix: clear errors on closing DB Connection Modal (@AAfghahi)
|
||||
- [#15620](https://github.com/apache/superset/pull/15620) fix: error page status codes (@etr2460)
|
||||
- [#15619](https://github.com/apache/superset/pull/15619) fix: Database List Sorted (@AAfghahi)
|
||||
- [#15609](https://github.com/apache/superset/pull/15609) fix: update db for expose in sqllab param (@hughhhh)
|
||||
- [#15612](https://github.com/apache/superset/pull/15612) fix: Database Connection Modal - corrected tooltip alignment and info alert width (@lyndsiWilliams)
|
||||
- [#15186](https://github.com/apache/superset/pull/15186) fix(dashboard-list): change name of dashboard is not reflected instantly (@stephenLYZ)
|
||||
- [#15588](https://github.com/apache/superset/pull/15588) fix: Revert "fix(dashboard): Open "View Chart in Explore" in the same window" (@rusackas)
|
||||
- [#15589](https://github.com/apache/superset/pull/15589) fix: Revert "chore: Bump @svgr/webpack to 5.5.0" (@rusackas)
|
||||
- [#15594](https://github.com/apache/superset/pull/15594) fix: Hide Dynamic Link when editing (@AAfghahi)
|
||||
- [#15595](https://github.com/apache/superset/pull/15595) fix: DBC UI tooltip aligment (@hughhhh)
|
||||
- [#15587](https://github.com/apache/superset/pull/15587) fix: available endpoint showing specs without drivers (@betodealmeida)
|
||||
- [#15581](https://github.com/apache/superset/pull/15581) fix: downloadasimage for dashboard (@pkdotson)
|
||||
- [#15558](https://github.com/apache/superset/pull/15558) fix: revert #15405 #15435 #15444 (@betodealmeida)
|
||||
- [#15546](https://github.com/apache/superset/pull/15546) fix: examples remove app context at the module level (@dpgaspar)
|
||||
- [#15547](https://github.com/apache/superset/pull/15547) fix: indentation in helm chart (@mvoitko)
|
||||
- [#15511](https://github.com/apache/superset/pull/15511) fix: variable context (@jeffreykoetsier)
|
||||
- [#15526](https://github.com/apache/superset/pull/15526) fix: chartlist card-link to 404 (@xiezhongfu)
|
||||
- [#15506](https://github.com/apache/superset/pull/15506) fix(native-filters): Fix native filters config modal (@simcha90)
|
||||
- [#15527](https://github.com/apache/superset/pull/15527) fix: base requirements missing deprecation pkg (@dpgaspar)
|
||||
- [#15534](https://github.com/apache/superset/pull/15534) fix: show all DBs in available endpoint (@betodealmeida)
|
||||
- [#15465](https://github.com/apache/superset/pull/15465) fix: quote column name if db requires (@eschutho)
|
||||
- [#15486](https://github.com/apache/superset/pull/15486) fix: Database connection R6 fixes (@lyndsiWilliams)
|
||||
- [#15519](https://github.com/apache/superset/pull/15519) fix: Utilizing dashboard native filter feature flag (@john-bodley)
|
||||
- [#15492](https://github.com/apache/superset/pull/15492) fix(sqllab): add new tab when add sql query (@stephenLYZ)
|
||||
- [#15487](https://github.com/apache/superset/pull/15487) fix: GSheets supports JOINs (@betodealmeida)
|
||||
- [#15498](https://github.com/apache/superset/pull/15498) fix(native-filters): chartsInScope were not recalculated in some cases (@kgabryje)
|
||||
- [#15493](https://github.com/apache/superset/pull/15493) fix: skip set and log when use NullCache (@zhaoyongjie)
|
||||
- [#15430](https://github.com/apache/superset/pull/15430) fix: bump Redis minor version (@mvoitko)
|
||||
- [#15438](https://github.com/apache/superset/pull/15438) fix(native-filters): Fix clear all button (@simcha90)
|
||||
- [#15318](https://github.com/apache/superset/pull/15318) fix: nvd3 bar chart sortby metric (@zhaoyongjie)
|
||||
- [#15353](https://github.com/apache/superset/pull/15353) fix: raise unexpected error when orderby is empty (@zhaoyongjie)
|
||||
- [#15455](https://github.com/apache/superset/pull/15455) fix(dashboard): native filters highlight with multiple tabs jumps to first tab (@kgabryje)
|
||||
- [#15448](https://github.com/apache/superset/pull/15448) fix: refactor(feature_flags configurations): revert of "remove redundant addi… (@villebro)
|
||||
- [#15446](https://github.com/apache/superset/pull/15446) fix(native-filters): filter type check when using experimental flag (@villebro)
|
||||
- [#15422](https://github.com/apache/superset/pull/15422) fix: remove unnecessary app context on celery (@dpgaspar)
|
||||
- [#15444](https://github.com/apache/superset/pull/15444) fix: import superset_config (@villebro)
|
||||
- [#15314](https://github.com/apache/superset/pull/15314) fix(explore): switch to correct scheme registry for custom sequential color schemes (@sonyasha)
|
||||
- [#15418](https://github.com/apache/superset/pull/15418) fix: add dashboard markdown id (@krsnik93)
|
||||
- [#14778](https://github.com/apache/superset/pull/14778) fix(dashboard): Open "View Chart in Explore" in the same window (@geido)
|
||||
- [#15435](https://github.com/apache/superset/pull/15435) fix: remove pydash merge (@betodealmeida)
|
||||
- [#15328](https://github.com/apache/superset/pull/15328) fix: downgrade selenium log level on timeout (@dpgaspar)
|
||||
- [#15429](https://github.com/apache/superset/pull/15429) fix: Select item when allowNewOptions is true and Enter is pressed (@michael-s-molina)
|
||||
- [#15390](https://github.com/apache/superset/pull/15390) fix: Cascading filter popover widens automatically (@michael-s-molina)
|
||||
- [#15400](https://github.com/apache/superset/pull/15400) fix: double click slq lab table cell (@graceguo-supercat)
|
||||
- [#13467](https://github.com/apache/superset/pull/13467) fix: Typo in the `positionJSON` too large warning (@mrshu)
|
||||
- [#15411](https://github.com/apache/superset/pull/15411) fix(native-filters): show human readable time grain label in indicator (@villebro)
|
||||
- [#15297](https://github.com/apache/superset/pull/15297) fix: bootstrapScript in values.yaml of the helm chart (@mvoitko)
|
||||
- [#15407](https://github.com/apache/superset/pull/15407) fix(regression): removed flask_app property can break derived class (@ofekisr)
|
||||
- [#15373](https://github.com/apache/superset/pull/15373) fix: Enlarged select filter value (@michael-s-molina)
|
||||
- [#15355](https://github.com/apache/superset/pull/15355) fix: follow up pr 15343 (@zhaoyongjie)
|
||||
- [#15343](https://github.com/apache/superset/pull/15343) fix: missing orderby in query on the nvd3 timeseries chart (@zhaoyongjie)
|
||||
- [#15351](https://github.com/apache/superset/pull/15351) fix(native-filters): show default text on filter scoping tree (@villebro)
|
||||
- [#15339](https://github.com/apache/superset/pull/15339) fix(explore): Fix issue #15335 - Filter comparator losing focus (@m-ajay)
|
||||
- [#15315](https://github.com/apache/superset/pull/15315) fix(api): handle undefined column type_generic (@serenajiang)
|
||||
- [#15285](https://github.com/apache/superset/pull/15285) fix: Revert "build(webpack): use [contenthash] instead of [chunkhash]" (@etr2460)
|
||||
- [#15319](https://github.com/apache/superset/pull/15319) fix: Capitalize time granularity weekdays (@john-bodley)
|
||||
- [#15184](https://github.com/apache/superset/pull/15184) fix: datasource payload is incorrect (@betodealmeida)
|
||||
- [#15324](https://github.com/apache/superset/pull/15324) fix(native-filters): Assume that temporal columns exist if column_types is undefined (@kgabryje)
|
||||
- [#15305](https://github.com/apache/superset/pull/15305) fix(dashboard): Close FiltersBadge popover on window resize (@kgabryje)
|
||||
- [#15207](https://github.com/apache/superset/pull/15207) fix: return query if it already exists (@eschutho)
|
||||
- [#15301](https://github.com/apache/superset/pull/15301) fix: Revert "fix: SQL Lab show "Refetch Results" button while fetching new query results" (@graceguo-supercat)
|
||||
- [#15238](https://github.com/apache/superset/pull/15238) fix: adding new feature flag (@AAfghahi)
|
||||
- [#15295](https://github.com/apache/superset/pull/15295) fix(native-filters): improve time range filter performance (@villebro)
|
||||
- [#14704](https://github.com/apache/superset/pull/14704) fix(helm): Use import_datasources.yaml, if it exists (@danielewood)
|
||||
- [#15257](https://github.com/apache/superset/pull/15257) fix(native-filters): default value checkbox in config modal (@villebro)
|
||||
- [#15228](https://github.com/apache/superset/pull/15228) fix: Filter bar not occupying 100% height when filter sets FF unset (@michael-s-molina)
|
||||
- [#15173](https://github.com/apache/superset/pull/15173) fix(examples): calendar chart metric should be metrics (@villebro)
|
||||
- [#15219](https://github.com/apache/superset/pull/15219) fix(native-filters): Fix Select `Default First Value` by clicked `Clear All` (@simcha90)
|
||||
- [#15134](https://github.com/apache/superset/pull/15134) fix(dashboard): charts in nested tab is missing control and filter indicator (@stephenLYZ)
|
||||
- [#15203](https://github.com/apache/superset/pull/15203) fix: fix dataset select list (@pkdotson)
|
||||
- [#15198](https://github.com/apache/superset/pull/15198) fix: Fix dremio dialect not having a `driver` field (@hughhhh)
|
||||
- [#15109](https://github.com/apache/superset/pull/15109) fix: SQL Lab show "Refetch Results" button while fetching new query results (@graceguo-supercat)
|
||||
- [#15123](https://github.com/apache/superset/pull/15123) fix(logging): downgrade csv export log to debug (@nytai)
|
||||
- [#14891](https://github.com/apache/superset/pull/14891) fix(aarch64): Bump pyarrow version to 4.0.1 (@danielewood)
|
||||
- [#13614](https://github.com/apache/superset/pull/13614) fix(helm): Set working defaults for google OAuth2 example (@danielewood)
|
||||
- [#15138](https://github.com/apache/superset/pull/15138) fix(dashboard): avoid duplicated toast component (@stephenLYZ)
|
||||
- [#15007](https://github.com/apache/superset/pull/15007) fix(docker/helm): Make webserver query timeout adjustable (@danielewood)
|
||||
- [#15181](https://github.com/apache/superset/pull/15181) fix: ignore errors in GetLog (@betodealmeida)
|
||||
- [#15175](https://github.com/apache/superset/pull/15175) fix: trim string value of spaces in listview search (@pkdotson)
|
||||
- [#15108](https://github.com/apache/superset/pull/15108) fix: add another wait for chart element (@eschutho)
|
||||
- [#15160](https://github.com/apache/superset/pull/15160) fix: use npm v7 in docker compose (@suddjian)
|
||||
- [#15163](https://github.com/apache/superset/pull/15163) fix: Presto postgres test (@betodealmeida)
|
||||
- [#15172](https://github.com/apache/superset/pull/15172) fix(dashboard): Prevent rerendering View Query modal on window resize (@kgabryje)
|
||||
- [#15155](https://github.com/apache/superset/pull/15155) fix: validate DB-specific parameters (@betodealmeida)
|
||||
- [#15152](https://github.com/apache/superset/pull/15152) fix(typo): in contributing.md (@stellalc7)
|
||||
- [#15100](https://github.com/apache/superset/pull/15100) fix: Test connection before starting on create transaction (@hughhhh)
|
||||
- [#15140](https://github.com/apache/superset/pull/15140) fix: Filter overlay in dashboard when scrolling (@michael-s-molina)
|
||||
- [#15146](https://github.com/apache/superset/pull/15146) fix(native-filters): Don't send unnecessary PUT request on dashboard render (@kgabryje)
|
||||
- [#15139](https://github.com/apache/superset/pull/15139) fix: improve dashboard fullscreen text (@xiezhongfu)
|
||||
- [#15091](https://github.com/apache/superset/pull/15091) fix(explore): fix y-axis lower bound 0 value (@stephenLYZ)
|
||||
- [#15112](https://github.com/apache/superset/pull/15112) fix(native-filters): handle descending sorting correctly (@villebro)
|
||||
- [#15090](https://github.com/apache/superset/pull/15090) fix(native-filters): fix Select filter crashing when changing filter type (@kgabryje)
|
||||
- [#14959](https://github.com/apache/superset/pull/14959) fix: show custom errors in SQL Lab (@betodealmeida)
|
||||
- [#14952](https://github.com/apache/superset/pull/14952) fix(explore): Explore page boolean filter is broken for Presto DB (@m-ajay)
|
||||
- [#15084](https://github.com/apache/superset/pull/15084) fix(native-filters): empty label indicator (@villebro)
|
||||
- [#15005](https://github.com/apache/superset/pull/15005) fix(native-filters): show error if default value query failed (@villebro)
|
||||
- [#15015](https://github.com/apache/superset/pull/15015) fix(native-filters): remove hard-coded default time range (@villebro)
|
||||
- [#15080](https://github.com/apache/superset/pull/15080) fix(dnd): add isExtra prop to Option (@villebro)
|
||||
- [#15014](https://github.com/apache/superset/pull/15014) fix(datasets): consistent dataset list (@zhaoyongjie)
|
||||
- [#15073](https://github.com/apache/superset/pull/15073) fix: disappearing tooltips on dashboards (@etr2460)
|
||||
- [#15056](https://github.com/apache/superset/pull/15056) fix: confirm overwrite and password on import (@betodealmeida)
|
||||
- [#15069](https://github.com/apache/superset/pull/15069) fix: move metric parsing to state instantiation (@eschutho)
|
||||
- [#15047](https://github.com/apache/superset/pull/15047) fix: import metrics with extra (@betodealmeida)
|
||||
- [#14960](https://github.com/apache/superset/pull/14960) fix: font regression in SQL Lab (@betodealmeida)
|
||||
- [#15048](https://github.com/apache/superset/pull/15048) fix: edit BQ w/o encrypted_extra (@betodealmeida)
|
||||
- [#15024](https://github.com/apache/superset/pull/15024) fix: Adds left padding to dashboard edit mode when filter bar is closed (@michael-s-molina)
|
||||
- [#15038](https://github.com/apache/superset/pull/15038) fix(native-filters): show overridden chart name on scoping tree (@villebro)
|
||||
- [#15033](https://github.com/apache/superset/pull/15033) fix(explore): Datepicker glitch on hover outside the modal (@kgabryje)
|
||||
- [#14878](https://github.com/apache/superset/pull/14878) fix: Empty tab component in Dashboard cannot be deleted (@geido)
|
||||
- [#14954](https://github.com/apache/superset/pull/14954) fix(explore): Long labels should wrap to new line (@geido)
|
||||
- [#15031](https://github.com/apache/superset/pull/15031) fix: display all metric results in editor (@eschutho)
|
||||
- [#15025](https://github.com/apache/superset/pull/15025) fix(dashboard): custom css should be removed on unmount (@suddjian)
|
||||
- [#14995](https://github.com/apache/superset/pull/14995) fix: adding additional configs and colors for queryHistory (@AAfghahi)
|
||||
- [#15012](https://github.com/apache/superset/pull/15012) fix(native-filters): avoid double load on select initialization (@villebro)
|
||||
- [#14996](https://github.com/apache/superset/pull/14996) fix: apply template_params on external_metadata (@betodealmeida)
|
||||
- [#14979](https://github.com/apache/superset/pull/14979) fix: toggle fullscreen on the dashboard (@zhaoyongjie)
|
||||
- [#14984](https://github.com/apache/superset/pull/14984) fix(native-filters): Fix "undefined" error after editing a filter (@kgabryje)
|
||||
- [#14982](https://github.com/apache/superset/pull/14982) fix(native-filters): remove implied fetch predicate (@villebro)
|
||||
- [#14980](https://github.com/apache/superset/pull/14980) fix(native-filters): update cascaded filter state on change (@villebro)
|
||||
- [#14900](https://github.com/apache/superset/pull/14900) fix(filter box): replace freeform where clause with ilike (@villebro)
|
||||
- [#14971](https://github.com/apache/superset/pull/14971) fix: renamed sqllab filters to _filters (@cccs-jc)
|
||||
- [#14964](https://github.com/apache/superset/pull/14964) fix(native-filters): cascading filters not rendering in tab (@villebro)
|
||||
- [#14953](https://github.com/apache/superset/pull/14953) fix: additional safeguard for ResultSet (@AAfghahi)
|
||||
- [#14945](https://github.com/apache/superset/pull/14945) fix: time parser truncate to first day of year/month (@zhaoyongjie)
|
||||
- [#14894](https://github.com/apache/superset/pull/14894) fix: is_temporal should overwrite is_dttm (@zhaoyongjie)
|
||||
- [#14902](https://github.com/apache/superset/pull/14902) fix: Remove Icon and align close button on DatasetModal (@AAfghahi)
|
||||
- [#14885](https://github.com/apache/superset/pull/14885) fix: Query History cosmetic issues (@AAfghahi)
|
||||
- [#14840](https://github.com/apache/superset/pull/14840) fix: Full width tabs flaky behavior (@geido)
|
||||
- [#14903](https://github.com/apache/superset/pull/14903) fix: permission denied when starting docker with uid 1000 (@shawnzhu)
|
||||
- [#14855](https://github.com/apache/superset/pull/14855) fix: leverage qs to create new tab (@hughhhh)
|
||||
- [#14888](https://github.com/apache/superset/pull/14888) fix: Redshift parameters not rendering (@hughhhh)
|
||||
- [#14839](https://github.com/apache/superset/pull/14839) fix(explore): Icons in "Customize Columns" in "Customize" tab break to a new line (@geido)
|
||||
- [#14890](https://github.com/apache/superset/pull/14890) fix: time range in filter box error (@zhaoyongjie)
|
||||
- [#14851](https://github.com/apache/superset/pull/14851) fix: show error on invalid import (@betodealmeida)
|
||||
- [#14687](https://github.com/apache/superset/pull/14687) fix(explore): Don't run data panel query when control panel has errors (@kgabryje)
|
||||
- [#14849](https://github.com/apache/superset/pull/14849) fix: DatabaseConnection Modal Margin Bottom (@AAfghahi)
|
||||
- [#14756](https://github.com/apache/superset/pull/14756) fix: small code review fix (@cccs-jc)
|
||||
- [#14816](https://github.com/apache/superset/pull/14816) fix(native-filter): Default value multi-select height in native filters (@geido)
|
||||
- [#14841](https://github.com/apache/superset/pull/14841) fix: filterbox apply single value (@zhaoyongjie)
|
||||
- [#14838](https://github.com/apache/superset/pull/14838) fix(native-filters): remove indicators outside scope (@villebro)
|
||||
- [#14852](https://github.com/apache/superset/pull/14852) fix: report dropdown (@AAfghahi)
|
||||
- [#14850](https://github.com/apache/superset/pull/14850) fix: Big Query Edit Form (@hughhhh)
|
||||
- [#14813](https://github.com/apache/superset/pull/14813) fix: the calculated columns explicit type convert into date (@zhaoyongjie)
|
||||
- [#14736](https://github.com/apache/superset/pull/14736) fix(docker): superset permissions and firefox config (@dpgaspar)
|
||||
- [#14827](https://github.com/apache/superset/pull/14827) fix: OpenAPI boolean type (@betodealmeida)
|
||||
- [#14822](https://github.com/apache/superset/pull/14822) fix: Fix Big Query API for POST w/ no parameters (@hughhhh)
|
||||
- [#14489](https://github.com/apache/superset/pull/14489) fix: set table name width to not hide icons when name is too long (@einatbar)
|
||||
- [#14788](https://github.com/apache/superset/pull/14788) fix(native-filters): fix loop caused by external state handler (@villebro)
|
||||
- [#14785](https://github.com/apache/superset/pull/14785) fix(native-filters): Manage default value of filters by superset (@simcha90)
|
||||
- [#14741](https://github.com/apache/superset/pull/14741) fix: Additional ResultSet tests (@AAfghahi)
|
||||
- [#14528](https://github.com/apache/superset/pull/14528) fix: make dataset list sort case insensitive (@mistercrunch)
|
||||
- [#14790](https://github.com/apache/superset/pull/14790) fix: use encodeURIComponent when getting table metadata (@betodealmeida)
|
||||
- [#14787](https://github.com/apache/superset/pull/14787) fix: ensure engine is outside parameters (@betodealmeida)
|
||||
- [#14771](https://github.com/apache/superset/pull/14771) fix: database modal should close on connect with tab layout (@eschutho)
|
||||
- [#14770](https://github.com/apache/superset/pull/14770) fix: extra query in Dashboard when native filter enabled (@zhaoyongjie)
|
||||
- [#14779](https://github.com/apache/superset/pull/14779) fix(native filters): Fix explore state - backend pagination checkbox in table (@simcha90)
|
||||
- [#14737](https://github.com/apache/superset/pull/14737) fix(explore): DndColumnSelect not handling controls with "multi: false" (@kgabryje)
|
||||
- [#14766](https://github.com/apache/superset/pull/14766) fix: add DB should not say it's Postgres (@betodealmeida)
|
||||
- [#14759](https://github.com/apache/superset/pull/14759) fix: save non-parameter DBs (@betodealmeida)
|
||||
- [#14717](https://github.com/apache/superset/pull/14717) fix(explore): Icons width (@geido)
|
||||
- [#14742](https://github.com/apache/superset/pull/14742) fix: Set g.user to anon user in Celery (@benjreinhart)
|
||||
- [#14702](https://github.com/apache/superset/pull/14702) fix: Use g.user for getting the user_id for async queries (@benjreinhart)
|
||||
- [#14689](https://github.com/apache/superset/pull/14689) fix: Fixes right menu layout in different screen sizes (@michael-s-molina)
|
||||
- [#14734](https://github.com/apache/superset/pull/14734) fix(dashboard): multiple query trigger when native filter enabled (@zhaoyongjie)
|
||||
- [#14748](https://github.com/apache/superset/pull/14748) fix(pivot): default missing series to NULL_STRING (@villebro)
|
||||
- [#14725](https://github.com/apache/superset/pull/14725) fix: homepage card layout (@pkdotson)
|
||||
- [#14739](https://github.com/apache/superset/pull/14739) fix(native-filters): Unable to clear default value in native select filter (@michael-s-molina)
|
||||
- [#14722](https://github.com/apache/superset/pull/14722) fix(sqllab): don't store user in localstorage (@suddjian)
|
||||
- [#14708](https://github.com/apache/superset/pull/14708) fix: reindex when combine metric in legacy pivot table (@zhaoyongjie)
|
||||
- [#14221](https://github.com/apache/superset/pull/14221) fix(explore): fix clearing select data causes popover dismiss (@stephenLYZ)
|
||||
- [#14719](https://github.com/apache/superset/pull/14719) fix: check limiting factor on query results (@eschutho)
|
||||
- [#14679](https://github.com/apache/superset/pull/14679) fix(explore): add margin to the adhoc filter value select (@suddjian)
|
||||
- [#14701](https://github.com/apache/superset/pull/14701) fix(explore): checkbox form control formatting (@suddjian)
|
||||
- [#14657](https://github.com/apache/superset/pull/14657) fix(explore): Filter box full chart height (@geido)
|
||||
- [#14529](https://github.com/apache/superset/pull/14529) fix(Explore): fixes broken layout of tooltips (@rusackas)
|
||||
- [#14698](https://github.com/apache/superset/pull/14698) fix: import dataset with extra; Vertica URI (@betodealmeida)
|
||||
- [#14567](https://github.com/apache/superset/pull/14567) fix(explore): #10098 boolean filter not working (@m-ajay)
|
||||
- [#14664](https://github.com/apache/superset/pull/14664) fix: Fixes email body when sharing a chart by email (@michael-s-molina)
|
||||
- [#14651](https://github.com/apache/superset/pull/14651) fix(sqllab): fix error message (@stephenLYZ)
|
||||
- [#14656](https://github.com/apache/superset/pull/14656) fix: Tooltip position of table title (@geido)
|
||||
- [#14663](https://github.com/apache/superset/pull/14663) fix(explore): Tag component overlap (@geido)
|
||||
- [#14665](https://github.com/apache/superset/pull/14665) fix(explore): Fix column number calculation (@geido)
|
||||
- [#14580](https://github.com/apache/superset/pull/14580) fix: nav submenu dropdown styles (@pkdotson)
|
||||
- [#14674](https://github.com/apache/superset/pull/14674) fix: Fixes group by control icon colors (@michael-s-molina)
|
||||
- [#14655](https://github.com/apache/superset/pull/14655) fix: Clear search on deleting search keyword (@geido)
|
||||
- [#14631](https://github.com/apache/superset/pull/14631) fix: fix submenu header double line (@pkdotson)
|
||||
- [#14648](https://github.com/apache/superset/pull/14648) fix: roles undefined on public dashboards (@suddjian)
|
||||
- [#14636](https://github.com/apache/superset/pull/14636) fix: DB parameter validation API (@betodealmeida)
|
||||
- [#14626](https://github.com/apache/superset/pull/14626) fix(dashboard): check edit permissions correctly on frontend (@suddjian)
|
||||
- [#14624](https://github.com/apache/superset/pull/14624) fix: Fixes top level tabs and automatic scroll (@michael-s-molina)
|
||||
- [#14609](https://github.com/apache/superset/pull/14609) fix(explore): Missing border in the Popover SQL Editor (@geido)
|
||||
- [#14120](https://github.com/apache/superset/pull/14120) fix: do not render favorite favStars and filters for anonymous user (@trepmag)
|
||||
- [#14637](https://github.com/apache/superset/pull/14637) fix: Removing specific column widths, letting things flex naturally. (@rusackas)
|
||||
- [#14478](https://github.com/apache/superset/pull/14478) fix: fix adhocpopovers tab animate. (@pkdotson)
|
||||
- [#14627](https://github.com/apache/superset/pull/14627) fix: Pin itsdangerous (@john-bodley)
|
||||
- [#14557](https://github.com/apache/superset/pull/14557) fix: Consolidating dropdown/NavDropdown user experience (removing React-bootstrap, using AntD) (@michael-s-molina)
|
||||
- [#14525](https://github.com/apache/superset/pull/14525) fix: add action buttons to time series column popover (@michael-s-molina)
|
||||
- [#14618](https://github.com/apache/superset/pull/14618) fix(explore): Filters Tooltip is not showing the full content (@geido)
|
||||
- [#14585](https://github.com/apache/superset/pull/14585) fix: don't show busted label for unknown data types (@rusackas)
|
||||
- [#14597](https://github.com/apache/superset/pull/14597) fix: error icon spacing in explore (@pkdotson)
|
||||
- [#14579](https://github.com/apache/superset/pull/14579) fix: properly keep state on queryEditorSetSql on tabstateview PUT (@hughhhh)
|
||||
- [#14584](https://github.com/apache/superset/pull/14584) fix: bring back dashboard perf logger (@graceguo-supercat)
|
||||
- [#14582](https://github.com/apache/superset/pull/14582) fix: Adds space under dataset change warning (@rusackas)
|
||||
- [#14566](https://github.com/apache/superset/pull/14566) fix: Menu does not appear on scroll in Dashboard (@geido)
|
||||
- [#14551](https://github.com/apache/superset/pull/14551) fix: Column name and icons alignment in the Datasource Panel (Explore) (@geido)
|
||||
- [#14561](https://github.com/apache/superset/pull/14561) fix: select country in examples chart (@betodealmeida)
|
||||
- [#14495](https://github.com/apache/superset/pull/14495) fix: White space between Chart and Data panel in Explore (@geido)
|
||||
- [#14539](https://github.com/apache/superset/pull/14539) fix(viz): apply uniform sorting to all nvd3 timeseries charts (@villebro)
|
||||
- [#14544](https://github.com/apache/superset/pull/14544) fix: flaky test on reports (@dpgaspar)
|
||||
- [#14531](https://github.com/apache/superset/pull/14531) fix: bringing metric type icon styles into SelectControl (@rusackas)
|
||||
- [#14492](https://github.com/apache/superset/pull/14492) fix: Add extra check to loggerMiddleware (@geido)
|
||||
- [#14506](https://github.com/apache/superset/pull/14506) fix: disable pylint error breaking CI (@hughhhh)
|
||||
- [#14498](https://github.com/apache/superset/pull/14498) fix: Query History (@AAfghahi)
|
||||
- [#14490](https://github.com/apache/superset/pull/14490) fix: Fix #13831 (@michael-s-molina)
|
||||
- [#14467](https://github.com/apache/superset/pull/14467) fix(dashboard): Prevent char overflow when displaying chart description (@m-ajay)
|
||||
- [#14481](https://github.com/apache/superset/pull/14481) fix: Explore layout is sometimes too short for the viewport (@rusackas)
|
||||
- [#14471](https://github.com/apache/superset/pull/14471) fix: dashboard datasources filter None (@etr2460)
|
||||
- [#14465](https://github.com/apache/superset/pull/14465) fix: Ignore database extra fields when saving (@michael-s-molina)
|
||||
- [#14466](https://github.com/apache/superset/pull/14466) fix: Revert "refactor: split db modal file (#14436)" (@eschutho)
|
||||
- [#13713](https://github.com/apache/superset/pull/13713) fix(explore): CSV Export Permission is incorrect on Explore page (@duynguyenhoang)
|
||||
- [#14450](https://github.com/apache/superset/pull/14450) fix: dashboard changed on calculation (@etr2460)
|
||||
- [#14399](https://github.com/apache/superset/pull/14399) fix(logging): log unexpected exceptions as exceptions (@nytai)
|
||||
- [#14416](https://github.com/apache/superset/pull/14416) fix: fixing mysql error message (@AAfghahi)
|
||||
- [#14435](https://github.com/apache/superset/pull/14435) fix: Change relationship filter for datasets to relationOneMany (@hughhhh)
|
||||
- [#14348](https://github.com/apache/superset/pull/14348) fix: bootstrap data permissions (@dpgaspar)
|
||||
- [#14360](https://github.com/apache/superset/pull/14360) fix: parse simple string error message values (@samtfm)
|
||||
|
||||
**Others**
|
||||
- [#16251](https://github.com/apache/superset/pull/16251) chore: bump superset-ui packages to 0.17.84 (@pkdotson)
|
||||
- [#16186](https://github.com/apache/superset/pull/16186) chore: bump superset-ui to 0.17.81 (@villebro)
|
||||
- [#16174](https://github.com/apache/superset/pull/16174) chore: switch back tag name to popular from highly-used (@junlincc)
|
||||
- [#16116](https://github.com/apache/superset/pull/16116) chore(explore): change dnd placeholders (@kgabryje)
|
||||
- [#16133](https://github.com/apache/superset/pull/16133) chore: add stats logging to thumbnail api (@mistercrunch)
|
||||
- [#16086](https://github.com/apache/superset/pull/16086) chore(explore): bump deckgl to 0.4.9 (@kgabryje)
|
||||
- [#15942](https://github.com/apache/superset/pull/15942) chore(explore): Create new entrypoints for Echarts Timeseries (@kgabryje)
|
||||
- [#16058](https://github.com/apache/superset/pull/16058) chore: bump superset-ui to 0.17.78 (@villebro)
|
||||
- [#16039](https://github.com/apache/superset/pull/16039) chore: Revert Celery 5 upgrade (@robdiciuccio)
|
||||
- [#16029](https://github.com/apache/superset/pull/16029) chore: Use datetime.timedelta for defining durations in config (@john-bodley)
|
||||
- [#16034](https://github.com/apache/superset/pull/16034) chore: bump superset-ui to 0.17.77 (@villebro)
|
||||
- [#16025](https://github.com/apache/superset/pull/16025) chore: Auto focus the viz gallery select (@michael-s-molina)
|
||||
- [#16032](https://github.com/apache/superset/pull/16032) docs: update api 2 (@nytai)
|
||||
- [#15927](https://github.com/apache/superset/pull/15927) chore: Bump Celery to 5.1.2 (@john-bodley)
|
||||
- [#15936](https://github.com/apache/superset/pull/15936) docs: add instructions for how to connect to local database from docker container (@sarthak)
|
||||
- [#15950](https://github.com/apache/superset/pull/15950) docs: add Hydrolix to users list (@dsztykman)
|
||||
- [#16005](https://github.com/apache/superset/pull/16005) docs: update api (@nytai)
|
||||
- [#15958](https://github.com/apache/superset/pull/15958) chore: change dropdown icons from horizontal to vertical (@pkdotson)
|
||||
- [#15987](https://github.com/apache/superset/pull/15987) chore: Add feature flags to bug report template (@suddjian)
|
||||
- [#15929](https://github.com/apache/superset/pull/15929) chore: Changes the main menu order as defined in SIP-34 (@michael-s-molina)
|
||||
- [#15931](https://github.com/apache/superset/pull/15931) docs: add .asf.yaml (@nytai)
|
||||
- [#15823](https://github.com/apache/superset/pull/15823) chore: Mypy fix **kwargs type (@john-bodley)
|
||||
- [#15923](https://github.com/apache/superset/pull/15923) chore: bump superset-ui to 0.17.74 (@villebro)
|
||||
- [#15900](https://github.com/apache/superset/pull/15900) chore: small viz gallery tweaks (@suddjian)
|
||||
- [#15907](https://github.com/apache/superset/pull/15907) chore: Improves the layout of the VizTypeGallery component (@michael-s-molina)
|
||||
- [#15904](https://github.com/apache/superset/pull/15904) docs: Slack integration requires "chat:write" permissions scope (@jpuris)
|
||||
- [#15901](https://github.com/apache/superset/pull/15901) chore: bumping superset-ui 0.17.73 (@zhaoyongjie)
|
||||
- [#15724](https://github.com/apache/superset/pull/15724) chore: freeze the UUID of examples DB (@betodealmeida)
|
||||
- [#15868](https://github.com/apache/superset/pull/15868) chore: implement new mockup to the new viz gallery (2nd iteration) (@stephenLYZ)
|
||||
- [#15895](https://github.com/apache/superset/pull/15895) chore: bumping superset-ui 0.17.72 (@zhaoyongjie)
|
||||
- [#15885](https://github.com/apache/superset/pull/15885) chore: Adds the tests that need to be coded for the Select component (@michael-s-molina)
|
||||
- [#15847](https://github.com/apache/superset/pull/15847) chore: bump typescript (@eschutho)
|
||||
- [#15799](https://github.com/apache/superset/pull/15799) chore: Adds lazy loading and fetchOnlyOnSearch to the Select component (@michael-s-molina)
|
||||
- [#15839](https://github.com/apache/superset/pull/15839) chore: bump superset-ui to 0.17.71 (@kgabryje)
|
||||
- [#15802](https://github.com/apache/superset/pull/15802) chore: Changes the pagination API of the Select component (@michael-s-molina)
|
||||
- [#15787](https://github.com/apache/superset/pull/15787) chore: remove unnecessary deps (@betodealmeida)
|
||||
- [#15757](https://github.com/apache/superset/pull/15757) chore: Enforce Mypy for non-tests (@john-bodley)
|
||||
- [#15775](https://github.com/apache/superset/pull/15775) chore: Pylint reenable bad-option-value message (@john-bodley)
|
||||
- [#15772](https://github.com/apache/superset/pull/15772) chore: Pylint reenable non-problematic messages (@john-bodley)
|
||||
- [#15788](https://github.com/apache/superset/pull/15788) chore: remove `retry` dependency in favor of `backoff` (@betodealmeida)
|
||||
- [#15480](https://github.com/apache/superset/pull/15480) chore: Improves the Select component UI/UX - iteration 4 (@michael-s-molina)
|
||||
- [#15794](https://github.com/apache/superset/pull/15794) docs: Adding Sunbird to users list (@kumarks1122)
|
||||
- [#15795](https://github.com/apache/superset/pull/15795) chore: bump superset-ui 0.17.70 (@zhaoyongjie)
|
||||
- [#15734](https://github.com/apache/superset/pull/15734) chore: Add tags to the new viz gallery (@zhaoyongjie)
|
||||
- [#15776](https://github.com/apache/superset/pull/15776) docs: Update INTHEWILD.md (@MaiTiano)
|
||||
- [#15555](https://github.com/apache/superset/pull/15555) chore: Select component refactoring - ColorSchemeControl - Iteration 5 (@geido)
|
||||
- [#15571](https://github.com/apache/superset/pull/15571) chore: Select component refactoring - DateFilterControl - Iteration 5 (@geido)
|
||||
- [#15753](https://github.com/apache/superset/pull/15753) chore: Reformat Pylint disable checks to multiline (@john-bodley)
|
||||
- [#15767](https://github.com/apache/superset/pull/15767) chore: bump superet-ui 0.17.69 (@zhaoyongjie)
|
||||
- [#15742](https://github.com/apache/superset/pull/15742) chore(explore): Bump plugin-chart-pivot-table (@kgabryje)
|
||||
- [#15718](https://github.com/apache/superset/pull/15718) docs: update dev superset version (@nytai)
|
||||
- [#15699](https://github.com/apache/superset/pull/15699) perf(dashboard): make loading datasets non-blocking (@ktmud)
|
||||
- [#15714](https://github.com/apache/superset/pull/15714) chore: better copy for SQL dialog (@betodealmeida)
|
||||
- [#15690](https://github.com/apache/superset/pull/15690) refactor: remove old Icon component (@pkdotson)
|
||||
- [#15647](https://github.com/apache/superset/pull/15647) refactor: icon to icons for IconButton and Header component (@pkdotson)
|
||||
- [#15688](https://github.com/apache/superset/pull/15688) chore: Removes unnecessary uses of preselectNativeFilters (@michael-s-molina)
|
||||
- [#15648](https://github.com/apache/superset/pull/15648) perf: Refactor Dashboard.datasets_trimmed_for_slices et al. (@john-bodley)
|
||||
- [#15599](https://github.com/apache/superset/pull/15599) chore: Add documentation for DB Connection UI (@hughhhh)
|
||||
- [#15646](https://github.com/apache/superset/pull/15646) chore: Add metrics_b as viable metric form data parameter (@john-bodley)
|
||||
- [#15665](https://github.com/apache/superset/pull/15665) refactor: icon to icons for Querytable, datasource test, and copyclipboard story (@pkdotson)
|
||||
- [#15574](https://github.com/apache/superset/pull/15574) docs: Add section about updating Python requirements (@john-bodley)
|
||||
- [#15583](https://github.com/apache/superset/pull/15583) chore: Preserve native filters selection after refresh (@michael-s-molina)
|
||||
- [#15643](https://github.com/apache/superset/pull/15643) refactor: icon to icons for navbar (@pkdotson)
|
||||
- [#15644](https://github.com/apache/superset/pull/15644) chore: Reverts reset form in native filters (@michael-s-molina)
|
||||
- [#15633](https://github.com/apache/superset/pull/15633) chore: bump superset-ui/plugin-chart-echarts 0.17.65 (@zhaoyongjie)
|
||||
- [#15618](https://github.com/apache/superset/pull/15618) refactor: icon to icons for syntaxhighlighter and querylist components (@pkdotson)
|
||||
- [#15593](https://github.com/apache/superset/pull/15593) refactor: icon to icons for sqllab (@pkdotson)
|
||||
- [#15624](https://github.com/apache/superset/pull/15624) refactor: icon to icons for homepage and card compompents (@pkdotson)
|
||||
- [#15113](https://github.com/apache/superset/pull/15113) docs: fix typos in docs (@jacobhjkim)
|
||||
- [#15410](https://github.com/apache/superset/pull/15410) docs: Small addition in add new databases with docker (@JavierLopezT)
|
||||
- [#15607](https://github.com/apache/superset/pull/15607) docs(docker): update README (@jhult)
|
||||
- [#15579](https://github.com/apache/superset/pull/15579) refactor: icon to icons for toasts component (@pkdotson)
|
||||
- [#15611](https://github.com/apache/superset/pull/15611) refactor: icon to icons for annotations & css templates modals (@pkdotson)
|
||||
- [#15597](https://github.com/apache/superset/pull/15597) refactor: icon to icons for Alert & Reports (@pkdotson)
|
||||
- [#15608](https://github.com/apache/superset/pull/15608) chore: update dataset count badge and tash icon sizing (@andrewbastian)
|
||||
- [#15615](https://github.com/apache/superset/pull/15615) chore: bumping superset-ui 0.17.64 (@zhaoyongjie)
|
||||
- [#15359](https://github.com/apache/superset/pull/15359) refactor(annotation): improve annotation modal (@stephenLYZ)
|
||||
- [#15568](https://github.com/apache/superset/pull/15568) refactor: icon to icons for controls (@pkdotson)
|
||||
- [#15567](https://github.com/apache/superset/pull/15567) refactor: icon to icons for sliceheader component (@pkdotson)
|
||||
- [#15591](https://github.com/apache/superset/pull/15591) chore: results of npm audit fix on superset-websocket (@rusackas)
|
||||
- [#15557](https://github.com/apache/superset/pull/15557) refactor: icon to icons for filterbadge components (@pkdotson)
|
||||
- [#15542](https://github.com/apache/superset/pull/15542) chore: add changed_on_delta_humanized field on dashboard schema (@zhaoyongjie)
|
||||
- [#15528](https://github.com/apache/superset/pull/15528) refactor: icon to icons for nativeFilter components (@pkdotson)
|
||||
- [#15575](https://github.com/apache/superset/pull/15575) docs: updated dremio connection string (@srinify)
|
||||
- [#15551](https://github.com/apache/superset/pull/15551) refactor: icon to icons for tabs (@pkdotson)
|
||||
- [#15560](https://github.com/apache/superset/pull/15560) refactor: icon to icons for headeractionsdropdown (@pkdotson)
|
||||
- [#15550](https://github.com/apache/superset/pull/15550) chore: Enhance Select component (@geido)
|
||||
- [#15540](https://github.com/apache/superset/pull/15540) refactor: type hints should not be load in runtime (@ofekisr)
|
||||
- [#15461](https://github.com/apache/superset/pull/15461) refactor: icon to icons for popoversection (@pkdotson)
|
||||
- [#15521](https://github.com/apache/superset/pull/15521) chore: add metadata for filter box plugin (@suddjian)
|
||||
- [#15533](https://github.com/apache/superset/pull/15533) refactor: migrate to icons for searchinput icons (@pkdotson)
|
||||
- [#15523](https://github.com/apache/superset/pull/15523) chore(release-docs): svn update after svn commit (@amitmiran137)
|
||||
- [#15514](https://github.com/apache/superset/pull/15514) chore(docs): update DASHBOARD_RBAC (@amitmiran137)
|
||||
- [#15451](https://github.com/apache/superset/pull/15451) refactor: icon to icons for popovercomponent (@pkdotson)
|
||||
- [#15384](https://github.com/apache/superset/pull/15384) refactor(dashboard): [chart-maximize-mode]put chart full-size state in redux (@stephenLYZ)
|
||||
- [#15408](https://github.com/apache/superset/pull/15408) refactor: icon to icons for listviewcomponent (@pkdotson)
|
||||
- [#15398](https://github.com/apache/superset/pull/15398) refactor: icon to icons for infotooltip component (@pkdotson)
|
||||
- [#15473](https://github.com/apache/superset/pull/15473) refactor(tests): decouple unittests from integration tests (@ofekisr)
|
||||
- [#14102](https://github.com/apache/superset/pull/14102) docs: improve docs on running tests locally (@EBoisseauSierra)
|
||||
- [#15365](https://github.com/apache/superset/pull/15365) chore(native-filters): remove instant filtering option (@villebro)
|
||||
- [#15467](https://github.com/apache/superset/pull/15467) refactor: Moving get_user_datasources to security manager (@john-bodley)
|
||||
- [#15466](https://github.com/apache/superset/pull/15466) refactor: icon to icons for refreshlabel (@pkdotson)
|
||||
- [#15437](https://github.com/apache/superset/pull/15437) chore: simplify errors and issue codes (@betodealmeida)
|
||||
- [#15424](https://github.com/apache/superset/pull/15424) perf(dashboard): Improve perf of highlighting charts in scope of active filter (@kgabryje)
|
||||
- [#15413](https://github.com/apache/superset/pull/15413) chore(docs): Manage access to Dashboards (@amitmiran137)
|
||||
- [#14994](https://github.com/apache/superset/pull/14994) docs: add missing logging import (@Bonifacio2)
|
||||
- [#14630](https://github.com/apache/superset/pull/14630) docs: fix wrong filename mentioned in INSTALL.md (@jeverling)
|
||||
- [#15401](https://github.com/apache/superset/pull/15401) refactor: icon to icons for lastupdated component (@pkdotson)
|
||||
- [#15397](https://github.com/apache/superset/pull/15397) refactor: icon to icons for inderteminatecheckbox icon (@pkdotson)
|
||||
- [#15433](https://github.com/apache/superset/pull/15433) chore: Uses mixed case for native filters headers (@michael-s-molina)
|
||||
- [#14880](https://github.com/apache/superset/pull/14880) chore: Update CONTRIBUTING.md (@Ibby-B)
|
||||
- [#15425](https://github.com/apache/superset/pull/15425) refactor(feature_flags configurations): remove defaults values (@ofekisr)
|
||||
- [#15405](https://github.com/apache/superset/pull/15405) chore(refactor): load configuration and merge recursively (@ofekisr)
|
||||
- [#15417](https://github.com/apache/superset/pull/15417) chore: add DASHBOARD_FILTERS_EXPERIMENTAL ff to BE default value (@amitmiran137)
|
||||
- [#14278](https://github.com/apache/superset/pull/14278) docs: Update SIP template (@john-bodley)
|
||||
- [#14908](https://github.com/apache/superset/pull/14908) chore: Add Slovenian (sl_SI) translation (@dkrat7)
|
||||
- [#14572](https://github.com/apache/superset/pull/14572) docs: release documentation for 1.2 (@garden-of-delete)
|
||||
- [#15392](https://github.com/apache/superset/pull/15392) chore: remove unused icon from ImportModal (@pkdotson)
|
||||
- [#14174](https://github.com/apache/superset/pull/14174) chore: Rewrites dashboard IconButton component (@michael-s-molina)
|
||||
- [#15265](https://github.com/apache/superset/pull/15265) chore: Migrates ControlHeader icons (@michael-s-molina)
|
||||
- [#15363](https://github.com/apache/superset/pull/15363) chore: Improves the Select component UI/UX - iteration 3 (@michael-s-molina)
|
||||
- [#15371](https://github.com/apache/superset/pull/15371) refactor: icon to icons for favestar component (@pkdotson)
|
||||
- [#15380](https://github.com/apache/superset/pull/15380) refactor: icon to icons infotooltip (@pkdotson)
|
||||
- [#15320](https://github.com/apache/superset/pull/15320) chore: Add Druid SQL time grains for parity with Druid NoSQL (@john-bodley)
|
||||
- [#15349](https://github.com/apache/superset/pull/15349) chore: Bump @svgr/webpack to 5.5.0 (@kgabryje)
|
||||
- [#15341](https://github.com/apache/superset/pull/15341) refactor: icon to icons in erroralert component (@pkdotson)
|
||||
- [#15336](https://github.com/apache/superset/pull/15336) refactor: icon to icons for basicerror componenet (@pkdotson)
|
||||
- [#15200](https://github.com/apache/superset/pull/15200) style(sqllab): update table count styling (@stellalc7)
|
||||
- [#15306](https://github.com/apache/superset/pull/15306) refactor: Icon to icons for certifiedIcon (@pkdotson)
|
||||
- [#15245](https://github.com/apache/superset/pull/15245) docs: fix hyperlink (@kamalkeshavani-aiinside)
|
||||
- [#15309](https://github.com/apache/superset/pull/15309) refactor: migrate icon to icons in tablecollection (@pkdotson)
|
||||
- [#15240](https://github.com/apache/superset/pull/15240) refactor: icon to icons in DatasourceEditor (@pkdotson)
|
||||
- [#15235](https://github.com/apache/superset/pull/15235) chore: Improves the Select component UI/UX - iteration 2 (@michael-s-molina)
|
||||
- [#15281](https://github.com/apache/superset/pull/15281) chore: encapsulate flask app into superset app (@ofekisr)
|
||||
- [#15278](https://github.com/apache/superset/pull/15278) refactor: move SupersetAppInitializer to specific initialization package (@ofekisr)
|
||||
- [#15223](https://github.com/apache/superset/pull/15223) chore: move calling configure_feature_flags more earlier (@ofekisr)
|
||||
- [#14691](https://github.com/apache/superset/pull/14691) chore: add dry false config to CleanWebpackPlugin (@MatanBobi)
|
||||
- [#15040](https://github.com/apache/superset/pull/15040) chore(docs): update releasing docs (@amitmiran137)
|
||||
- [#15261](https://github.com/apache/superset/pull/15261) refactor: icon to icons for alerts component (@pkdotson)
|
||||
- [#14956](https://github.com/apache/superset/pull/14956) chore: Add height/width TrashIcon SVG(18px/18px)-Edit Dataset modal (@andrewbastian)
|
||||
- [#15233](https://github.com/apache/superset/pull/15233) docs: fix naming: Flask-Cache -> Flask-Caching (@shawnzhu)
|
||||
- [#15220](https://github.com/apache/superset/pull/15220) chore: bump superset-ui to 0.17.58 (@villebro)
|
||||
- [#14463](https://github.com/apache/superset/pull/14463) refactor: refactor Icon to Icons in sqlEditor component (@pkdotson)
|
||||
- [#15192](https://github.com/apache/superset/pull/15192) chore: Changes the dashboard highlight color when selecting a filter (@michael-s-molina)
|
||||
- [#15194](https://github.com/apache/superset/pull/15194) chore: Makes the refresh button only appear when the filter has a datasource (@michael-s-molina)
|
||||
- [#15178](https://github.com/apache/superset/pull/15178) chore: Allows the user to force fetch the default values (@michael-s-molina)
|
||||
- [#15115](https://github.com/apache/superset/pull/15115) chore: Improves the native filters bar layout (@michael-s-molina)
|
||||
- [#14978](https://github.com/apache/superset/pull/14978) refactor: Convert TableElement to TypeScript (@corbinrobb)
|
||||
- [#15147](https://github.com/apache/superset/pull/15147) refactor(explore): remove side effect in render from CalendarFrame for DatePicker (@MatanBobi)
|
||||
- [#15168](https://github.com/apache/superset/pull/15168) chore: Bump plugin-chart-pivot-table to 0.17.57 (@kgabryje)
|
||||
- [#15141](https://github.com/apache/superset/pull/15141) chore: Scrolls top when opening a select filter (@michael-s-molina)
|
||||
- [#15156](https://github.com/apache/superset/pull/15156) chore: Disable comment logging for ephemeral envs (@robdiciuccio)
|
||||
- [#15093](https://github.com/apache/superset/pull/15093) chore: update documentation for frontend feature flags (@etr2460)
|
||||
- [#14823](https://github.com/apache/superset/pull/14823) chore: Homepage cleanup (@pkdotson)
|
||||
- [#14928](https://github.com/apache/superset/pull/14928) docs: add Ontruck to users list (@rc-ontruck)
|
||||
- [#15062](https://github.com/apache/superset/pull/15062) refactor: stop using deprecated celery task API (@shawnzhu)
|
||||
- [#15078](https://github.com/apache/superset/pull/15078) chore: rename 'tables' to 'datasets' in error message (@mistercrunch)
|
||||
- [#15017](https://github.com/apache/superset/pull/15017) chore: Improves the native filters UI/UX - iteration 7 (@michael-s-molina)
|
||||
- [#15064](https://github.com/apache/superset/pull/15064) chore: Add 'fetching' status to QueryStatus (@AAfghahi)
|
||||
- [#14942](https://github.com/apache/superset/pull/14942) build(webpack): use [contenthash] instead of [chunkhash] (@nytai)
|
||||
- [#15019](https://github.com/apache/superset/pull/15019) docs: jinja (@srinify)
|
||||
- [#15053](https://github.com/apache/superset/pull/15053) refactor: adopt --app as celery global option (@shawnzhu)
|
||||
- [#15044](https://github.com/apache/superset/pull/15044) docs: provide config option for openid-connect provider #13948 (@shawnzhu)
|
||||
- [#14870](https://github.com/apache/superset/pull/14870) chore: Bulk Select X Button Alignment (@lyndsiWilliams)
|
||||
- [#14846](https://github.com/apache/superset/pull/14846) chore: Align down icons on header (@lyndsiWilliams)
|
||||
- [#15013](https://github.com/apache/superset/pull/15013) refactor: Add "is_select_query" method to base engine spec to unlock non-SQL dialects (@Ceridan)
|
||||
- [#15021](https://github.com/apache/superset/pull/15021) chore: rename 'Source' to 'Database' for consistency (@mistercrunch)
|
||||
- [#15016](https://github.com/apache/superset/pull/15016) chore(ci): fix ci conflict (@villebro)
|
||||
- [#15010](https://github.com/apache/superset/pull/15010) docs: required information for OAuth2 configuration (@shawnzhu)
|
||||
- [#14990](https://github.com/apache/superset/pull/14990) docs: Updates index.mdx (@brian-childress)
|
||||
- [#14950](https://github.com/apache/superset/pull/14950) docs: fix typos on installation.rst (@Bonifacio2)
|
||||
- [#14997](https://github.com/apache/superset/pull/14997) docs: fix custom oauth config (@nytai)
|
||||
- [#14830](https://github.com/apache/superset/pull/14830) refactor: Convert TableElement.jsx component from class to functional with hooks (@corbinrobb)
|
||||
- [#14968](https://github.com/apache/superset/pull/14968) chore: bump superset-ui to 0.17.53 (@villebro)
|
||||
- [#14932](https://github.com/apache/superset/pull/14932) chore: Improves the native filters UI/UX - iteration 6 (@michael-s-molina)
|
||||
- [#14896](https://github.com/apache/superset/pull/14896) chore: customize adhoc filter icon and fix creatable label (@villebro)
|
||||
- [#14919](https://github.com/apache/superset/pull/14919) test(native-filters): add test for Select filter (@simcha90)
|
||||
- [#14907](https://github.com/apache/superset/pull/14907) chore: upgrade bleach dependency (@willbarrett)
|
||||
- [#14906](https://github.com/apache/superset/pull/14906) chore: Upgrade cryptography package (@willbarrett)
|
||||
- [#14871](https://github.com/apache/superset/pull/14871) chore: Bump @superset-ui/legacy-preset-chart-deckgl to 0.4.7 (@kgabryje)
|
||||
- [#14882](https://github.com/apache/superset/pull/14882) chore: Improves the native filters UI/UX - iteration 5 (@michael-s-molina)
|
||||
- [#14854](https://github.com/apache/superset/pull/14854) chore: Improves the native filters UI/UX - iteration 4 (@michael-s-molina)
|
||||
- [#14814](https://github.com/apache/superset/pull/14814) chore(native-filters): introduce experimental feature flag (@villebro)
|
||||
- [#14824](https://github.com/apache/superset/pull/14824) chore: Improves the native filters UI/UX - iteration 3 (@michael-s-molina)
|
||||
- [#14826](https://github.com/apache/superset/pull/14826) chore: Update docs on MySQL recommended driver (@betodealmeida)
|
||||
- [#14752](https://github.com/apache/superset/pull/14752) chore: added BasicParametersMixin to Redshift (@AAfghahi)
|
||||
- [#14753](https://github.com/apache/superset/pull/14753) chore: Improves the native filters UI/UX - iteration 2 (@michael-s-molina)
|
||||
- [#14762](https://github.com/apache/superset/pull/14762) other: "revert fix(dashboard): multiple query trigger when native filter enabled" (@zhaoyongjie)
|
||||
- [#14199](https://github.com/apache/superset/pull/14199) chore: Removes ColorSchemeControl.less (@michael-s-molina)
|
||||
- [#14684](https://github.com/apache/superset/pull/14684) chore: Perform feature/config condition checks at request time (@benjreinhart)
|
||||
- [#14723](https://github.com/apache/superset/pull/14723) chore: Update pull request template (@suddjian)
|
||||
- [#14714](https://github.com/apache/superset/pull/14714) chore: Improves the native filters UI/UX - iteration 1 (@michael-s-molina)
|
||||
- [#14448](https://github.com/apache/superset/pull/14448) chore: Removes less from SliceAdder (@michael-s-molina)
|
||||
- [#14558](https://github.com/apache/superset/pull/14558) docs: Update docs for GLOBAL_ASYNC_QUERIES_TRANSPORT (@robdiciuccio)
|
||||
- [#14650](https://github.com/apache/superset/pull/14650) chore: Register dynamic plugins and add feature checks (@benjreinhart)
|
||||
- [#14469](https://github.com/apache/superset/pull/14469) chore: Replaces Icon with Icons component - iteration 1 (@michael-s-molina)
|
||||
- [#14644](https://github.com/apache/superset/pull/14644) chore: Adjust language picker theme to match other menus (@michael-s-molina)
|
||||
- [#14641](https://github.com/apache/superset/pull/14641) chore: Removes react-bootstrap and react-bootstrap-slider (@michael-s-molina)
|
||||
- [#14568](https://github.com/apache/superset/pull/14568) chore: use before_request hook for dynamic routes (@benjreinhart)
|
||||
- [#14633](https://github.com/apache/superset/pull/14633) chore: Bump pip-compile-multi (@john-bodley)
|
||||
- [#14638](https://github.com/apache/superset/pull/14638) perf: memoize db_engine_spec in database (@villebro)
|
||||
- [#14625](https://github.com/apache/superset/pull/14625) chore: Change name to `BaseParameters` to `BasicParameters` (@hughhhh)
|
||||
- [#14546](https://github.com/apache/superset/pull/14546) chore: Consolidating form experience (Bootstrap to AntD) - iteration 4 (@michael-s-molina)
|
||||
- [#14419](https://github.com/apache/superset/pull/14419) refactor: change xsm Icons and dnd icons to new dynamic icons (@pkdotson)
|
||||
- [#14581](https://github.com/apache/superset/pull/14581) chore: Update `make py-format` to use pre-commit (@hughhhh)
|
||||
- [#14227](https://github.com/apache/superset/pull/14227) build(deps): bump ssri from 6.0.1 to 6.0.2 in /docs (@dependabot[bot])
|
||||
- [#14569](https://github.com/apache/superset/pull/14569) style: adds padding badges to look less claustrophobic (@rusackas)
|
||||
- [#14536](https://github.com/apache/superset/pull/14536) docs: typo on CONTRIBUTING.md (@Bonifacio2)
|
||||
- [#14556](https://github.com/apache/superset/pull/14556) chore: Update FAB to 3.3.0 (@benjreinhart)
|
||||
- [#14476](https://github.com/apache/superset/pull/14476) chore: Adding logging for datasource/save requests (@michellethomas)
|
||||
- [#13095](https://github.com/apache/superset/pull/13095) chore: update CONTRIBUTING.md tag --lts (@bawantha)
|
||||
- [#14364](https://github.com/apache/superset/pull/14364) refactor: remove panel from userinfo component (@pkdotson)
|
||||
- [#14184](https://github.com/apache/superset/pull/14184) refactor(navbar): migrate Bootstrap navbar to AntD menus (@pkdotson)
|
||||
- [#14496](https://github.com/apache/superset/pull/14496) chore: Removes tabs pane animation by default (@michael-s-molina)
|
||||
- [#14502](https://github.com/apache/superset/pull/14502) refactor: Bootstrap to AntD - Form - iteration 3 (@michael-s-molina)
|
||||
- [#14513](https://github.com/apache/superset/pull/14513) docs(UPDATING): Adding downtime for #14234 (@john-bodley)
|
||||
- [#14412](https://github.com/apache/superset/pull/14412) chore: Improved translation into Russian language (@aleksandrgordienko)
|
||||
- [#14515](https://github.com/apache/superset/pull/14515) chore: bump superset-ui to 0.17.44 (@villebro)
|
||||
- [#14494](https://github.com/apache/superset/pull/14494) refactor(utils): replace strtobool with parse_boolean_string (@villebro)
|
||||
- [#14380](https://github.com/apache/superset/pull/14380) chore: Moves spec files to the src folder - iteration 4 (@michael-s-molina)
|
||||
- [#14379](https://github.com/apache/superset/pull/14379) refactor: Bootstrap to AntD - Form - iteration 2 (@michael-s-molina)
|
||||
- [#14195](https://github.com/apache/superset/pull/14195) chore: Adds QueryParamProvider to the testing helper (@michael-s-molina)
|
||||
- [#14497](https://github.com/apache/superset/pull/14497) test: Attempt to reduce asyncEvent test flakiness (@robdiciuccio)
|
||||
- [#14477](https://github.com/apache/superset/pull/14477) chore: bump sankey and pivot table chart to 0.17.43 (@ktmud)
|
||||
- [#14418](https://github.com/apache/superset/pull/14418) chore: Removes common storybook (@michael-s-molina)
|
||||
- [#14485](https://github.com/apache/superset/pull/14485) chore: Move styles from .less stylesheet to emotion in Explore (@kgabryje)
|
||||
- [#14417](https://github.com/apache/superset/pull/14417) chore: Update WebSocket server code from feedback (@benjreinhart)
|
||||
- [#14454](https://github.com/apache/superset/pull/14454) chore: upgrade @emotion (@ktmud)
|
||||
- [#14356](https://github.com/apache/superset/pull/14356) chore(dashboard): Integrate dashboard app into the SPA bundle (@suddjian)
|
||||
- [#14436](https://github.com/apache/superset/pull/14436) refactor: split db modal file (@eschutho)
|
||||
- [#14382](https://github.com/apache/superset/pull/14382) chore: add stack trace to all calls of logger.error (@samtfm)
|
||||
- [#14432](https://github.com/apache/superset/pull/14432) docs: update README with new docs and recordings (@srinify)
|
||||
- [#14425](https://github.com/apache/superset/pull/14425) docs: Renamed impyla from implya and updated PIP name to impyla from impala. (@jagamts1)
|
||||
|
||||
### 1.2.0 (2021-06-04)
|
||||
**Features**
|
||||
- [11498](https://github.com/apache/superset/pull/11498) feat(SIP-39): Websocket sidecar app (#11498) (@Rob DiCiuccio)
|
||||
- [13894](https://github.com/apache/superset/pull/13894) feat(alert/report): add ALERTS_ATTACH_REPORTS feature flags + feature (#13894) (@Lily Kuang)
|
||||
|
||||
26
UPDATING.md
26
UPDATING.md
@@ -22,21 +22,26 @@ under the License.
|
||||
This file documents any backwards-incompatible changes in Superset and
|
||||
assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
## 1.3.1
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [16711](https://github.com/apache/incubator-superset/pull/16711): The `url_param` Jinja function will now by default escape the result. For instance, the value `O'Brien` will now be changed to `O''Brien`. To disable this behavior, call `url_param` with `escape_result` set to `False`: `url_param("my_key", "my default", escape_result=False)`.
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [15909](https://github.com/apache/incubator-superset/pull/15909): a change which
|
||||
drops a uniqueness criterion (which may or may not have existed) to the tables table. This constraint was obsolete as it is handled by the ORM due to differences in how MySQL, PostgreSQL, etc. handle uniqueness for NULL values.
|
||||
|
||||
- [13772](https://github.com/apache/superset/pull/13772): Row level security (RLS) is now enabled by default. To activate the feature, please run `superset init` to expose the RLS menus to Admin users.
|
||||
|
||||
- [13980](https://github.com/apache/superset/pull/13980): Data health checks no longer use the metadata database as an interim cache. Though non-breaking, deployments which implement complex logic should likely memoize the callback function. Refer to documentation in the confg.py file for more detail.
|
||||
|
||||
- [14255](https://github.com/apache/superset/pull/14255): The default `CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC` callable logic has been updated to leverage the specified database and schema to ensure the upload S3 key prefix is unique. Previously tables generated via upload from CSV with the same name but differ schema and/or cluster would use the same S3 key prefix. Note this change does not impact previously imported tables.
|
||||
|
||||
### Breaking Changes
|
||||
### Potential Downtime
|
||||
- [14234](https://github.com/apache/superset/pull/14234): Adds the `limiting_factor` column to the `query` table. Give the migration includes a DDL operation on a heavily trafficed table, potential service downtime may be required.
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Deprecations
|
||||
|
||||
- [13440](https://github.com/apache/superset/pull/13440): Dashboard/Charts reports and old Alerts is deprecated. The following config keys are deprecated:
|
||||
- ENABLE_ALERTS
|
||||
- SCHEDULED_EMAIL_DEBUG_MODE
|
||||
@@ -44,8 +49,13 @@ drops a uniqueness criterion (which may or may not have existed) to the tables t
|
||||
- EMAIL_ASYNC_TIME_LIMIT_SEC
|
||||
- EMAIL_REPORT_BCC_ADDRESS
|
||||
- EMAIL_REPORTS_USER
|
||||
|
||||
### Other
|
||||
|
||||
- [13772](https://github.com/apache/superset/pull/13772): Row level security (RLS) is now enabled by default. To activate the feature, please run `superset init` to expose the RLS menus to Admin users.
|
||||
- [13980](https://github.com/apache/superset/pull/13980): Data health checks no longer use the metadata database as an interim cache. Though non-breaking, deployments which implement complex logic should likely memoize the callback function. Refer to documentation in the confg.py file for more detail.
|
||||
- [14255](https://github.com/apache/superset/pull/14255): The default `CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC` callable logic has been updated to leverage the specified database and schema to ensure the upload S3 key prefix is unique. Previously tables generated via upload from CSV with the same name but differ schema and/or cluster would use the same S3 key prefix. Note this change does not impact previously imported tables.
|
||||
|
||||
## 1.1.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -118,6 +118,9 @@ services:
|
||||
depends_on: *superset-depends-on
|
||||
user: *superset-user
|
||||
volumes: *superset-volumes
|
||||
# Bump memory limit if processing selenium / thumbails on superset-worker
|
||||
# mem_limit: 2038m
|
||||
# mem_reservation: 128M
|
||||
|
||||
superset-worker-beat:
|
||||
image: *superset-image
|
||||
|
||||
@@ -9,7 +9,7 @@ version: 1
|
||||
## Snowflake
|
||||
|
||||
The recommended connector library for Snowflake is
|
||||
[snowflake-sqlalchemy](https://pypi.org/project/snowflake-sqlalchemy/).
|
||||
[snowflake-sqlalchemy](https://pypi.org/project/snowflake-sqlalchemy/1.2.4/)<=1.2.4. (This version is required until Superset migrates to sqlalchemy>=1.4.0)
|
||||
|
||||
The connection string for Snowflake looks like this:
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ Navigate to **Data ‣ Datasets** and select the **+ Dataset** button in the top
|
||||
|
||||
A modal window should pop up in front of you. Select your **Database**,
|
||||
**Schema**, and **Table** using the drop downs that appear. In the following example,
|
||||
we register the **cleaned_sales_data** table from the **examples** database.
|
||||
we register the **Vehicle Sales** table from the **examples** database.
|
||||
|
||||
<img src="/images/tutorial_09_add_new_table.png" />
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ flask-login==0.4.1
|
||||
# via flask-appbuilder
|
||||
flask-migrate==2.5.3
|
||||
# via apache-superset
|
||||
flask-openid==1.2.5
|
||||
flask-openid==1.3.0
|
||||
# via flask-appbuilder
|
||||
flask-sqlalchemy==2.4.4
|
||||
# via
|
||||
|
||||
10
setup.py
10
setup.py
@@ -133,7 +133,7 @@ setup(
|
||||
"exasol": ["sqlalchemy-exasol>=2.1.0, <2.2"],
|
||||
"excel": ["xlrd>=1.2.0, <1.3"],
|
||||
"firebird": ["sqlalchemy-firebird>=0.7.0, <0.8"],
|
||||
"gsheets": ["shillelagh[gsheetsapi]>=0.7.1, <0.8"],
|
||||
"gsheets": ["shillelagh[gsheetsapi]>=1.0.3, <2"],
|
||||
"hana": ["hdbcli==2.4.162", "sqlalchemy_hana==0.4.0"],
|
||||
"hive": ["pyhive[hive]>=0.6.1", "tableschema", "thrift>=0.11.0, <1.0.0"],
|
||||
"impala": ["impyla>0.16.2, <0.17"],
|
||||
@@ -147,7 +147,13 @@ setup(
|
||||
"trino": ["sqlalchemy-trino>=0.2"],
|
||||
"prophet": ["prophet>=1.0.1, <1.1", "pystan<3.0"],
|
||||
"redshift": ["sqlalchemy-redshift>=0.8.1, < 0.9"],
|
||||
"snowflake": ["snowflake-sqlalchemy>=1.2.3, <1.3"],
|
||||
"rockset": ["rockset>=0.7.68, <0.8"],
|
||||
"shillelagh": [
|
||||
"shillelagh[datasetteapi,gsheetsapi,socrata,weatherapi]>=1.0.3, <2"
|
||||
],
|
||||
"snowflake": [
|
||||
"snowflake-sqlalchemy==1.2.4"
|
||||
], # PINNED! 1.2.5 introduced breaking changes requiring sqlalchemy>=1.4.0
|
||||
"teradata": ["sqlalchemy-teradata==0.9.0.dev0"],
|
||||
"thumbnails": ["Pillow>=7.0.0, <8.0.0"],
|
||||
"vertica": ["sqlalchemy-vertica-python>=0.5.9, < 0.6"],
|
||||
|
||||
2583
superset-frontend/package-lock.json
generated
2583
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superset",
|
||||
"version": "0.0.0dev",
|
||||
"version": "1.3.2",
|
||||
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
||||
"license": "Apache-2.0",
|
||||
"directories": {
|
||||
@@ -67,35 +67,35 @@
|
||||
"@emotion/babel-preset-css-prop": "^11.2.0",
|
||||
"@emotion/cache": "^11.1.3",
|
||||
"@emotion/react": "^11.1.5",
|
||||
"@superset-ui/chart-controls": "^0.17.77",
|
||||
"@superset-ui/core": "^0.17.75",
|
||||
"@superset-ui/legacy-plugin-chart-calendar": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-chord": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-country-map": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-event-flow": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-force-directed": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-heatmap": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-histogram": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-horizon": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-map-box": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-partition": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-rose": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-sankey": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-sunburst": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-treemap": "^0.17.77",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "^0.17.77",
|
||||
"@superset-ui/legacy-preset-chart-big-number": "^0.17.77",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.7",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "^0.17.77",
|
||||
"@superset-ui/plugin-chart-echarts": "^0.17.77",
|
||||
"@superset-ui/plugin-chart-pivot-table": "^0.17.77",
|
||||
"@superset-ui/plugin-chart-table": "^0.17.77",
|
||||
"@superset-ui/plugin-chart-word-cloud": "^0.17.77",
|
||||
"@superset-ui/preset-chart-xy": "^0.17.77",
|
||||
"@superset-ui/chart-controls": "^0.17.84",
|
||||
"@superset-ui/core": "^0.17.81",
|
||||
"@superset-ui/legacy-plugin-chart-calendar": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-chord": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-country-map": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-event-flow": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-force-directed": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-heatmap": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-histogram": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-horizon": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-map-box": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-partition": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-rose": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-sankey": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-sunburst": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-treemap": "^0.17.84",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "^0.17.84",
|
||||
"@superset-ui/legacy-preset-chart-big-number": "^0.17.84",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.10",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "^0.17.84",
|
||||
"@superset-ui/plugin-chart-echarts": "^0.17.84",
|
||||
"@superset-ui/plugin-chart-pivot-table": "^0.17.84",
|
||||
"@superset-ui/plugin-chart-table": "^0.17.84",
|
||||
"@superset-ui/plugin-chart-word-cloud": "^0.17.84",
|
||||
"@superset-ui/preset-chart-xy": "^0.17.84",
|
||||
"@vx/responsive": "^0.0.195",
|
||||
"abortcontroller-polyfill": "^1.1.9",
|
||||
"antd": "^4.9.4",
|
||||
|
||||
@@ -94,7 +94,11 @@ describe('newEntitiesFromDrop', () => {
|
||||
|
||||
expect(result.a.children).toHaveLength(1);
|
||||
expect(Object.keys(result)).toHaveLength(3);
|
||||
expect(result[newRowId].type).toBe(ROW_TYPE);
|
||||
expect(result[newChartId].type).toBe(CHART_TYPE);
|
||||
const newRow = result[newRowId];
|
||||
expect(newRow.type).toBe(ROW_TYPE);
|
||||
expect(newRow.parents).toEqual(['a']);
|
||||
const newChart = result[newChartId];
|
||||
expect(newChart.type).toBe(CHART_TYPE);
|
||||
expect(newChart.parents).toEqual(['a', newRowId]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,60 +67,14 @@ const sumValueAdhocMetric = new AdhocMetric({
|
||||
label: 'SUM(value)',
|
||||
});
|
||||
|
||||
describe('MetricsControl', () => {
|
||||
// TODO: rewrite the tests to RTL
|
||||
describe.skip('MetricsControl', () => {
|
||||
it('renders Select', () => {
|
||||
const { component } = setup();
|
||||
expect(component.find(LabelsContainer)).toExist();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('unifies options for the dropdown select with aggregates', () => {
|
||||
const { component } = setup();
|
||||
expect(component.state('options')).toEqual([
|
||||
{
|
||||
optionName: '_col_source',
|
||||
type: 'VARCHAR(255)',
|
||||
column_name: 'source',
|
||||
},
|
||||
{
|
||||
optionName: '_col_target',
|
||||
type: 'VARCHAR(255)',
|
||||
column_name: 'target',
|
||||
},
|
||||
{ optionName: '_col_value', type: 'DOUBLE', column_name: 'value' },
|
||||
...Object.keys(AGGREGATES).map(aggregate => ({
|
||||
aggregate_name: aggregate,
|
||||
optionName: `_aggregate_${aggregate}`,
|
||||
})),
|
||||
{
|
||||
optionName: 'sum__value',
|
||||
metric_name: 'sum__value',
|
||||
expression: 'SUM(energy_usage.value)',
|
||||
},
|
||||
{
|
||||
optionName: 'avg__value',
|
||||
metric_name: 'avg__value',
|
||||
expression: 'AVG(energy_usage.value)',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not show aggregates in options if no columns', () => {
|
||||
const { component } = setup({ columns: [] });
|
||||
expect(component.state('options')).toEqual([
|
||||
{
|
||||
optionName: 'sum__value',
|
||||
metric_name: 'sum__value',
|
||||
expression: 'SUM(energy_usage.value)',
|
||||
},
|
||||
{
|
||||
optionName: 'avg__value',
|
||||
metric_name: 'avg__value',
|
||||
expression: 'AVG(energy_usage.value)',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('coerces Adhoc Metrics from form data into instances of the AdhocMetric class and leaves saved metrics', () => {
|
||||
const { component } = setup({
|
||||
value: [
|
||||
@@ -178,194 +132,7 @@ describe('MetricsControl', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkIfAggregateInInput', () => {
|
||||
it('handles an aggregate in the input', () => {
|
||||
const { component } = setup();
|
||||
|
||||
expect(component.state('aggregateInInput')).toBeNull();
|
||||
component.instance().checkIfAggregateInInput('AVG(');
|
||||
expect(component.state('aggregateInInput')).toBe(AGGREGATES.AVG);
|
||||
});
|
||||
|
||||
it('handles no aggregate in the input', () => {
|
||||
const { component } = setup();
|
||||
|
||||
expect(component.state('aggregateInInput')).toBeNull();
|
||||
component.instance().checkIfAggregateInInput('colu');
|
||||
expect(component.state('aggregateInInput')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('option filter', () => {
|
||||
it('includes user defined metrics', () => {
|
||||
const { component } = setup({ datasourceType: 'druid' });
|
||||
|
||||
expect(
|
||||
!!component.instance().selectFilterOption(
|
||||
{
|
||||
data: {
|
||||
metric_name: 'a_metric',
|
||||
optionName: 'a_metric',
|
||||
expression: 'SUM(FANCY(metric))',
|
||||
},
|
||||
},
|
||||
'a',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('includes auto generated avg metrics for druid', () => {
|
||||
const { component } = setup({ datasourceType: 'druid' });
|
||||
|
||||
expect(
|
||||
!!component.instance().selectFilterOption(
|
||||
{
|
||||
data: {
|
||||
metric_name: 'avg__metric',
|
||||
optionName: 'avg__metric',
|
||||
expression: 'AVG(metric)',
|
||||
},
|
||||
},
|
||||
'a',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('includes columns and aggregates', () => {
|
||||
const { component } = setup();
|
||||
|
||||
expect(
|
||||
!!component.instance().selectFilterOption(
|
||||
{
|
||||
data: {
|
||||
type: 'VARCHAR(255)',
|
||||
column_name: 'source',
|
||||
optionName: '_col_source',
|
||||
},
|
||||
},
|
||||
'sou',
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
!!component
|
||||
.instance()
|
||||
.selectFilterOption(
|
||||
{ data: { aggregate_name: 'AVG', optionName: '_aggregate_AVG' } },
|
||||
'av',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('includes columns based on verbose_name', () => {
|
||||
const { component } = setup();
|
||||
|
||||
expect(
|
||||
!!component.instance().selectFilterOption(
|
||||
{
|
||||
data: {
|
||||
metric_name: 'sum__num',
|
||||
verbose_name: 'babies',
|
||||
optionName: '_col_sum_num',
|
||||
},
|
||||
},
|
||||
'bab',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('excludes auto generated avg metrics for sqla', () => {
|
||||
const { component } = setup();
|
||||
|
||||
expect(
|
||||
!!component.instance().selectFilterOption(
|
||||
{
|
||||
data: {
|
||||
metric_name: 'avg__metric',
|
||||
optionName: 'avg__metric',
|
||||
expression: 'AVG(metric)',
|
||||
},
|
||||
},
|
||||
'a',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('includes custom made simple saved metrics', () => {
|
||||
const { component } = setup();
|
||||
|
||||
expect(
|
||||
!!component.instance().selectFilterOption(
|
||||
{
|
||||
data: {
|
||||
metric_name: 'my_fancy_sum_metric',
|
||||
optionName: 'my_fancy_sum_metric',
|
||||
expression: 'SUM(value)',
|
||||
},
|
||||
},
|
||||
'sum',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('excludes auto generated metrics', () => {
|
||||
const { component } = setup();
|
||||
|
||||
expect(
|
||||
!!component.instance().selectFilterOption(
|
||||
{
|
||||
data: {
|
||||
metric_name: 'sum__value',
|
||||
optionName: 'sum__value',
|
||||
expression: 'SUM(value)',
|
||||
},
|
||||
},
|
||||
'sum',
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
!!component.instance().selectFilterOption(
|
||||
{
|
||||
data: {
|
||||
metric_name: 'sum__value',
|
||||
optionName: 'sum__value',
|
||||
expression: 'SUM("table"."value")',
|
||||
},
|
||||
},
|
||||
'sum',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('filters out metrics if the input begins with an aggregate', () => {
|
||||
const { component } = setup();
|
||||
component.setState({ aggregateInInput: true });
|
||||
|
||||
expect(
|
||||
!!component.instance().selectFilterOption(
|
||||
{
|
||||
data: { metric_name: 'metric', expression: 'SUM(FANCY(metric))' },
|
||||
},
|
||||
'SUM(',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('includes columns if the input begins with an aggregate', () => {
|
||||
const { component } = setup();
|
||||
component.setState({ aggregateInInput: true });
|
||||
|
||||
expect(
|
||||
!!component
|
||||
.instance()
|
||||
.selectFilterOption(
|
||||
{ data: { type: 'DOUBLE', column_name: 'value' } },
|
||||
'SUM(',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('Removes metrics if savedMetrics changes', () => {
|
||||
const { props, component, onChange } = setup({
|
||||
value: [
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('VizTypeControl', () => {
|
||||
new ChartMetadata({
|
||||
name: 'vis1',
|
||||
thumbnail: '',
|
||||
tags: ['Highly-used'],
|
||||
tags: ['Popular'],
|
||||
}),
|
||||
)
|
||||
.registerValue(
|
||||
|
||||
@@ -399,7 +399,7 @@ describe('async actions', () => {
|
||||
|
||||
let isFeatureEnabledMock;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(
|
||||
@@ -407,7 +407,7 @@ describe('async actions', () => {
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
afterEach(() => {
|
||||
isFeatureEnabledMock.mockRestore();
|
||||
});
|
||||
|
||||
@@ -612,9 +612,29 @@ describe('async actions', () => {
|
||||
});
|
||||
|
||||
describe('queryEditorSetSql', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
expect.assertions(2);
|
||||
describe('with backend persistence flag on', () => {
|
||||
it('updates the tab state in the backend', () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const sql = 'SELECT * ';
|
||||
const store = mockStore({});
|
||||
|
||||
return store
|
||||
.dispatch(actions.queryEditorSetSql(queryEditor, sql))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toHaveLength(0);
|
||||
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('with backend persistence flag off', () => {
|
||||
it('does not update the tab state in the backend', () => {
|
||||
const backendPersistenceOffMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(
|
||||
feature => !(feature === 'SQLLAB_BACKEND_PERSISTENCE'),
|
||||
);
|
||||
const sql = 'SELECT * ';
|
||||
const store = mockStore({});
|
||||
const expectedActions = [
|
||||
@@ -624,12 +644,12 @@ describe('async actions', () => {
|
||||
sql,
|
||||
},
|
||||
];
|
||||
return store
|
||||
.dispatch(actions.queryEditorSetSql(queryEditor, sql))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
|
||||
});
|
||||
|
||||
store.dispatch(actions.queryEditorSetSql(queryEditor, sql));
|
||||
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(0);
|
||||
backendPersistenceOffMock.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -898,16 +898,11 @@ export function updateSavedQuery(query) {
|
||||
|
||||
export function queryEditorSetSql(queryEditor, sql) {
|
||||
return function (dispatch) {
|
||||
const sync = isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE)
|
||||
? SupersetClient.put({
|
||||
endpoint: encodeURI(`/tabstateview/${queryEditor.id}`),
|
||||
postPayload: { sql, latest_query_id: queryEditor.latestQueryId },
|
||||
})
|
||||
: Promise.resolve();
|
||||
|
||||
return sync
|
||||
.then(() => dispatch({ type: QUERY_EDITOR_SET_SQL, queryEditor, sql }))
|
||||
.catch(() =>
|
||||
if (isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE)) {
|
||||
return SupersetClient.put({
|
||||
endpoint: encodeURI(`/tabstateview/${queryEditor.id}`),
|
||||
postPayload: { sql, latest_query_id: queryEditor.latestQueryId },
|
||||
}).catch(() =>
|
||||
dispatch(
|
||||
addDangerToast(
|
||||
t(
|
||||
@@ -918,6 +913,8 @@ export function queryEditorSetSql(queryEditor, sql) {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return dispatch({ type: QUERY_EDITOR_SET_SQL, queryEditor, sql });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -952,6 +949,11 @@ export function queryEditorSetQueryLimit(queryEditor, queryLimit) {
|
||||
|
||||
export function queryEditorSetTemplateParams(queryEditor, templateParams) {
|
||||
return function (dispatch) {
|
||||
dispatch({
|
||||
type: QUERY_EDITOR_SET_TEMPLATE_PARAMS,
|
||||
queryEditor,
|
||||
templateParams,
|
||||
});
|
||||
const sync = isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE)
|
||||
? SupersetClient.put({
|
||||
endpoint: encodeURI(`/tabstateview/${queryEditor.id}`),
|
||||
@@ -959,24 +961,16 @@ export function queryEditorSetTemplateParams(queryEditor, templateParams) {
|
||||
})
|
||||
: Promise.resolve();
|
||||
|
||||
return sync
|
||||
.then(() =>
|
||||
dispatch({
|
||||
type: QUERY_EDITOR_SET_TEMPLATE_PARAMS,
|
||||
queryEditor,
|
||||
templateParams,
|
||||
}),
|
||||
)
|
||||
.catch(() =>
|
||||
dispatch(
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while setting the tab template parameters. ' +
|
||||
'Please contact your administrator.',
|
||||
),
|
||||
return sync.catch(() =>
|
||||
dispatch(
|
||||
addDangerToast(
|
||||
t(
|
||||
'An error occurred while setting the tab template parameters. ' +
|
||||
'Please contact your administrator.',
|
||||
),
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -448,7 +448,7 @@ export default class ResultSet extends React.PureComponent<
|
||||
if (this.props.cache && this.props.query.cached) {
|
||||
({ data } = this.state);
|
||||
}
|
||||
|
||||
const { columns } = this.props.query.results;
|
||||
// Added compute logic to stop user from being able to Save & Explore
|
||||
const {
|
||||
saveDatasetRadioBtnState,
|
||||
@@ -508,7 +508,7 @@ export default class ResultSet extends React.PureComponent<
|
||||
)}
|
||||
|
||||
<CopyToClipboard
|
||||
text={prepareCopyToClipboardTabularData(data)}
|
||||
text={prepareCopyToClipboardTabularData(data, columns)}
|
||||
wrapped={false}
|
||||
copyNode={
|
||||
<Button buttonSize="small">
|
||||
|
||||
@@ -133,7 +133,8 @@ const TableElement = ({ table, actions, ...props }: TableElementProps) => {
|
||||
));
|
||||
}
|
||||
|
||||
if (!partitions && !metadata) {
|
||||
if (!partitions && (!metadata || !metadata.length)) {
|
||||
// hide partition and metadata card view
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -395,13 +395,16 @@ export function exploreJSON(
|
||||
.then(({ response, json }) => {
|
||||
if (isFeatureEnabled(FeatureFlag.GLOBAL_ASYNC_QUERIES)) {
|
||||
// deal with getChartDataRequest transforming the response data
|
||||
const result = 'result' in json ? json.result[0] : json;
|
||||
const result = 'result' in json ? json.result : json;
|
||||
switch (response.status) {
|
||||
case 200:
|
||||
// Query results returned synchronously, meaning query was already cached.
|
||||
return Promise.resolve([result]);
|
||||
return Promise.resolve(result);
|
||||
case 202:
|
||||
// Query is running asynchronously and we must await the results
|
||||
if (shouldUseLegacyApi(formData)) {
|
||||
return waitForAsyncData(result[0]);
|
||||
}
|
||||
return waitForAsyncData(result);
|
||||
default:
|
||||
throw new Error(
|
||||
|
||||
@@ -32,3 +32,5 @@ const StyledForm = styled(AntDForm)`
|
||||
export default function Form(props: FormProps) {
|
||||
return <StyledForm {...props} />;
|
||||
}
|
||||
|
||||
export { FormProps };
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import Form from './Form';
|
||||
import Form, { FormProps } from './Form';
|
||||
import FormItem from './FormItem';
|
||||
import FormLabel from './FormLabel';
|
||||
import LabeledErrorBoundInput from './LabeledErrorBoundInput';
|
||||
|
||||
export { Form, FormItem, FormLabel, LabeledErrorBoundInput };
|
||||
export { Form, FormItem, FormLabel, LabeledErrorBoundInput, FormProps };
|
||||
|
||||
@@ -27,12 +27,21 @@ interface CardCollectionProps {
|
||||
prepareRow: TableInstance['prepareRow'];
|
||||
renderCard?: (row: any) => React.ReactNode;
|
||||
rows: TableInstance['rows'];
|
||||
showThumbnails?: boolean;
|
||||
}
|
||||
|
||||
const CardContainer = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(459px, 1fr));
|
||||
grid-gap: ${({ theme }) => theme.gridUnit * 8}px;
|
||||
const CardContainer = styled.div<{ showThumbnails?: boolean }>`
|
||||
${({ theme, showThumbnails }) => `
|
||||
display: grid;
|
||||
grid-gap: ${theme.gridUnit * 12}px ${theme.gridUnit * 4}px;
|
||||
grid-template-columns: repeat(auto-fit, 300px);
|
||||
margin-top: ${theme.gridUnit * -6}px;
|
||||
padding: ${
|
||||
showThumbnails
|
||||
? `${theme.gridUnit * 8 + 3}px ${theme.gridUnit * 9}px`
|
||||
: `${theme.gridUnit * 8 + 1}px ${theme.gridUnit * 9}px`
|
||||
};
|
||||
`}
|
||||
`;
|
||||
|
||||
const CardWrapper = styled.div`
|
||||
@@ -51,6 +60,7 @@ export default function CardCollection({
|
||||
prepareRow,
|
||||
renderCard,
|
||||
rows,
|
||||
showThumbnails,
|
||||
}: CardCollectionProps) {
|
||||
function handleClick(
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
@@ -65,7 +75,7 @@ export default function CardCollection({
|
||||
|
||||
if (!renderCard) return null;
|
||||
return (
|
||||
<CardContainer>
|
||||
<CardContainer showThumbnails={showThumbnails}>
|
||||
{loading &&
|
||||
rows.length === 0 &&
|
||||
[...new Array(25)].map((e, i) => (
|
||||
|
||||
@@ -221,6 +221,7 @@ export interface ListViewProps<T extends object = any> {
|
||||
cardSortSelectOptions?: Array<CardSortSelectOption>;
|
||||
defaultViewMode?: ViewModeType;
|
||||
highlightRowId?: number;
|
||||
showThumbnails?: boolean;
|
||||
emptyState?: {
|
||||
message?: string;
|
||||
slot?: React.ReactNode;
|
||||
@@ -242,6 +243,7 @@ function ListView<T extends object = any>({
|
||||
disableBulkSelect = () => {},
|
||||
renderBulkSelectCopy = selected => t('%s Selected', selected.length),
|
||||
renderCard,
|
||||
showThumbnails,
|
||||
cardSortSelectOptions,
|
||||
defaultViewMode = 'card',
|
||||
highlightRowId,
|
||||
@@ -376,6 +378,7 @@ function ListView<T extends object = any>({
|
||||
renderCard={renderCard}
|
||||
rows={rows}
|
||||
loading={loading}
|
||||
showThumbnails={showThumbnails}
|
||||
/>
|
||||
)}
|
||||
{viewMode === 'table' && (
|
||||
|
||||
@@ -103,6 +103,7 @@ const mockedProps = {
|
||||
locale: 'en',
|
||||
version_string: '1.0.0',
|
||||
version_sha: 'randomSHA',
|
||||
build_number: 'randomBuildNumber',
|
||||
},
|
||||
settings: [
|
||||
{
|
||||
@@ -280,10 +281,10 @@ test('should render the Profile link when available', async () => {
|
||||
expect(profile).toHaveAttribute('href', user_profile_url);
|
||||
});
|
||||
|
||||
test('should render the About section and version_string or sha when available', async () => {
|
||||
test('should render the About section and version_string, sha or build_number when available', async () => {
|
||||
const {
|
||||
data: {
|
||||
navbar_right: { version_sha, version_string },
|
||||
navbar_right: { version_sha, version_string, build_number },
|
||||
},
|
||||
} = mockedProps;
|
||||
|
||||
@@ -292,9 +293,11 @@ test('should render the About section and version_string or sha when available',
|
||||
const about = await screen.findByText('About');
|
||||
const version = await screen.findByText(`Version: ${version_string}`);
|
||||
const sha = await screen.findByText(`SHA: ${version_sha}`);
|
||||
const build = await screen.findByText(`Build: ${build_number}`);
|
||||
expect(about).toBeInTheDocument();
|
||||
expect(version).toBeInTheDocument();
|
||||
expect(sha).toBeInTheDocument();
|
||||
expect(build).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the Documentation link when available', async () => {
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface NavBarProps {
|
||||
bug_report_url?: string;
|
||||
version_string?: string;
|
||||
version_sha?: string;
|
||||
build_number?: string;
|
||||
documentation_url?: string;
|
||||
languages: Languages;
|
||||
show_language_picker: boolean;
|
||||
|
||||
@@ -69,15 +69,6 @@ const StyledAnchor = styled.a`
|
||||
padding-left: ${({ theme }) => theme.gridUnit}px;
|
||||
`;
|
||||
|
||||
const WaterMark = styled.span`
|
||||
font-size: 13px;
|
||||
color: #b0b4c3;
|
||||
margin: 0 ${({ theme }) => theme.gridUnit * 4}px;
|
||||
@media (max-width: 1070px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const { SubMenu } = Menu;
|
||||
|
||||
interface RightMenuProps {
|
||||
@@ -95,9 +86,6 @@ const RightMenu = ({
|
||||
}: RightMenuProps) => (
|
||||
<StyledDiv align={align}>
|
||||
<Menu mode="horizontal">
|
||||
{navbarRight.show_watermark && (
|
||||
<WaterMark>{t('Powered by Apache Superset')}</WaterMark>
|
||||
)}
|
||||
{!navbarRight.user_is_anonymous && (
|
||||
<SubMenu
|
||||
data-test="new-dropdown"
|
||||
@@ -148,18 +136,27 @@ const RightMenu = ({
|
||||
<a href={navbarRight.user_profile_url}>{t('Profile')}</a>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item key="info">
|
||||
<a href={navbarRight.user_info_url}>{t('Info')}</a>
|
||||
</Menu.Item>
|
||||
{navbarRight.user_info_url && (
|
||||
<Menu.Item key="info">
|
||||
<a href={navbarRight.user_info_url}>{t('Info')}</a>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item key="logout">
|
||||
<a href={navbarRight.user_logout_url}>{t('Logout')}</a>
|
||||
</Menu.Item>
|
||||
</Menu.ItemGroup>,
|
||||
]}
|
||||
{(navbarRight.version_string || navbarRight.version_sha) && [
|
||||
{(navbarRight.version_string ||
|
||||
navbarRight.version_sha ||
|
||||
navbarRight.build_number) && [
|
||||
<Menu.Divider key="version-info-divider" />,
|
||||
<Menu.ItemGroup key="about-section" title={t('About')}>
|
||||
<div className="about-section">
|
||||
{navbarRight.show_watermark && (
|
||||
<div css={versionInfoStyles}>
|
||||
{t('Powered by Apache Superset')}
|
||||
</div>
|
||||
)}
|
||||
{navbarRight.version_string && (
|
||||
<div css={versionInfoStyles}>
|
||||
Version: {navbarRight.version_string}
|
||||
@@ -170,6 +167,11 @@ const RightMenu = ({
|
||||
SHA: {navbarRight.version_sha}
|
||||
</div>
|
||||
)}
|
||||
{navbarRight.build_number && (
|
||||
<div css={versionInfoStyles}>
|
||||
Build: {navbarRight.build_number}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Menu.ItemGroup>,
|
||||
]}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { t, SupersetTheme, css } from '@superset-ui/core';
|
||||
import { t, SupersetTheme, css, useTheme } from '@superset-ui/core';
|
||||
import Icons from 'src/components/Icons';
|
||||
import { Switch } from 'src/components/Switch';
|
||||
import { AlertObject } from 'src/views/CRUD/alert/types';
|
||||
@@ -47,6 +47,7 @@ export default function HeaderReportActionsDropDown({
|
||||
currentReportDeleting,
|
||||
setCurrentReportDeleting,
|
||||
] = useState<AlertObject | null>(null);
|
||||
const theme = useTheme();
|
||||
|
||||
const toggleActiveKey = async (data: AlertObject, checked: boolean) => {
|
||||
if (data?.id) {
|
||||
@@ -60,7 +61,7 @@ export default function HeaderReportActionsDropDown({
|
||||
};
|
||||
|
||||
const menu = () => (
|
||||
<Menu selectable={false}>
|
||||
<Menu selectable={false} css={{ width: '200px' }}>
|
||||
<Menu.Item>
|
||||
{t('Email reports active')}
|
||||
<Switch
|
||||
@@ -68,6 +69,7 @@ export default function HeaderReportActionsDropDown({
|
||||
checked={report?.active}
|
||||
onClick={(checked: boolean) => toggleActiveKey(report, checked)}
|
||||
size="small"
|
||||
css={{ marginLeft: theme.gridUnit * 2 }}
|
||||
/>
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={showReportModal}>{t('Edit email report')}</Menu.Item>
|
||||
|
||||
@@ -38,6 +38,13 @@ const defaultProps = {
|
||||
userEmail: 'test@test.com',
|
||||
dashboardId: 1,
|
||||
creationMethod: 'charts_dashboards',
|
||||
props: {
|
||||
chart: {
|
||||
sliceFormData: {
|
||||
viz_type: 'table',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Email Report Modal', () => {
|
||||
|
||||
@@ -29,22 +29,28 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { addReport, editReport } from 'src/reports/actions/reports';
|
||||
import { AlertObject } from 'src/views/CRUD/alert/types';
|
||||
import LabeledErrorBoundInput from 'src/components/Form/LabeledErrorBoundInput';
|
||||
|
||||
import TimezoneSelector from 'src/components/TimezoneSelector';
|
||||
import LabeledErrorBoundInput from 'src/components/Form/LabeledErrorBoundInput';
|
||||
import Icons from 'src/components/Icons';
|
||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||
import { CronPicker, CronError } from 'src/components/CronPicker';
|
||||
import { CronError } from 'src/components/CronPicker';
|
||||
import { RadioChangeEvent } from 'src/common/components';
|
||||
import {
|
||||
StyledModal,
|
||||
StyledTopSection,
|
||||
StyledBottomSection,
|
||||
StyledIconWrapper,
|
||||
StyledScheduleTitle,
|
||||
StyledCronPicker,
|
||||
StyledCronError,
|
||||
noBottomMargin,
|
||||
StyledFooterButton,
|
||||
TimezoneHeaderStyle,
|
||||
SectionHeaderStyle,
|
||||
StyledMessageContentTitle,
|
||||
StyledRadio,
|
||||
StyledRadioGroup,
|
||||
} from './styles';
|
||||
|
||||
interface ReportObject {
|
||||
@@ -67,6 +73,19 @@ interface ReportObject {
|
||||
creation_method: string;
|
||||
}
|
||||
|
||||
interface ChartObject {
|
||||
id: number;
|
||||
chartAlert: string;
|
||||
chartStatus: string;
|
||||
chartUpdateEndTime: number;
|
||||
chartUpdateStartTime: number;
|
||||
latestQueryFormData: object;
|
||||
queryController: { abort: () => {} };
|
||||
queriesResponse: object;
|
||||
triggerQuery: boolean;
|
||||
lastRendered: number;
|
||||
}
|
||||
|
||||
interface ReportProps {
|
||||
addDangerToast: (msg: string) => void;
|
||||
addSuccessToast: (msg: string) => void;
|
||||
@@ -77,26 +96,25 @@ interface ReportProps {
|
||||
userId: number;
|
||||
userEmail: string;
|
||||
dashboardId?: number;
|
||||
chartId?: number;
|
||||
chart?: ChartObject;
|
||||
creationMethod: string;
|
||||
props: any;
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
textChange,
|
||||
inputChange,
|
||||
fetched,
|
||||
reset,
|
||||
}
|
||||
|
||||
interface ReportPayloadType {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
inputChange,
|
||||
fetched,
|
||||
reset,
|
||||
}
|
||||
|
||||
type ReportActionType =
|
||||
| {
|
||||
type: ActionType.textChange | ActionType.inputChange;
|
||||
type: ActionType.inputChange;
|
||||
payload: ReportPayloadType;
|
||||
}
|
||||
| {
|
||||
@@ -107,17 +125,26 @@ type ReportActionType =
|
||||
type: ActionType.reset;
|
||||
};
|
||||
|
||||
const DEFAULT_NOTIFICATION_FORMAT = 'TEXT';
|
||||
const TEXT_BASED_VISUALIZATION_TYPES = [
|
||||
'pivot_table',
|
||||
'pivot_table_v2',
|
||||
'table',
|
||||
'paired_ttest',
|
||||
];
|
||||
|
||||
const reportReducer = (
|
||||
state: Partial<ReportObject> | null,
|
||||
action: ReportActionType,
|
||||
): Partial<ReportObject> | null => {
|
||||
const initialState = {
|
||||
name: state?.name || 'Weekly Report',
|
||||
report_format: state?.report_format || DEFAULT_NOTIFICATION_FORMAT,
|
||||
...(state || {}),
|
||||
};
|
||||
|
||||
switch (action.type) {
|
||||
case ActionType.textChange:
|
||||
case ActionType.inputChange:
|
||||
return {
|
||||
...initialState,
|
||||
[action.payload.name]: action.payload.value,
|
||||
@@ -139,6 +166,7 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
show = false,
|
||||
...props
|
||||
}) => {
|
||||
const vizType = props.props.chart?.sliceFormData?.viz_type;
|
||||
const [currentReport, setCurrentReport] = useReducer<
|
||||
Reducer<Partial<ReportObject> | null, ReportActionType>
|
||||
>(reportReducer, null);
|
||||
@@ -166,7 +194,6 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
}
|
||||
}, [reports]);
|
||||
const onClose = () => {
|
||||
// setLoading(false);
|
||||
onHide();
|
||||
};
|
||||
const onSave = async () => {
|
||||
@@ -174,7 +201,7 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
const newReportValues: Partial<ReportObject> = {
|
||||
crontab: currentReport?.crontab,
|
||||
dashboard: props.props.dashboardId,
|
||||
chart: props.props.chartId,
|
||||
chart: props.props.chart?.id,
|
||||
description: currentReport?.description,
|
||||
name: currentReport?.name,
|
||||
owners: [props.props.userId],
|
||||
@@ -187,9 +214,9 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
type: 'Report',
|
||||
creation_method: props.props.creationMethod,
|
||||
active: true,
|
||||
report_format: currentReport?.report_format,
|
||||
};
|
||||
|
||||
// setLoading(true);
|
||||
if (isEditMode) {
|
||||
await dispatch(
|
||||
editReport(currentReport?.id, newReportValues as ReportObject),
|
||||
@@ -217,7 +244,7 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
const renderModalFooter = (
|
||||
<>
|
||||
<StyledFooterButton key="back" onClick={onClose}>
|
||||
Cancel
|
||||
{t('Cancel')}
|
||||
</StyledFooterButton>
|
||||
<StyledFooterButton
|
||||
key="submit"
|
||||
@@ -225,11 +252,42 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
onClick={onSave}
|
||||
disabled={!currentReport?.name}
|
||||
>
|
||||
Add
|
||||
{isEditMode ? t('Save') : t('Add')}
|
||||
</StyledFooterButton>
|
||||
</>
|
||||
);
|
||||
|
||||
const renderMessageContentSection = (
|
||||
<>
|
||||
<StyledMessageContentTitle>
|
||||
<h4>{t('Message Content')}</h4>
|
||||
</StyledMessageContentTitle>
|
||||
<div className="inline-container">
|
||||
<StyledRadioGroup
|
||||
onChange={(event: RadioChangeEvent) => {
|
||||
onChange(ActionType.inputChange, {
|
||||
name: 'report_format',
|
||||
value: event.target.value,
|
||||
});
|
||||
}}
|
||||
value={currentReport?.report_format || DEFAULT_NOTIFICATION_FORMAT}
|
||||
>
|
||||
{TEXT_BASED_VISUALIZATION_TYPES.includes(vizType) && (
|
||||
<StyledRadio value="TEXT">
|
||||
{t('Text embedded in email')}
|
||||
</StyledRadio>
|
||||
)}
|
||||
<StyledRadio value="PNG">
|
||||
{t('Image (PNG) embedded in email')}
|
||||
</StyledRadio>
|
||||
<StyledRadio value="CSV">
|
||||
{t('Formatted CSV attached in email')}
|
||||
</StyledRadio>
|
||||
</StyledRadioGroup>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledModal
|
||||
show={show}
|
||||
@@ -248,7 +306,7 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
required
|
||||
validationMethods={{
|
||||
onChange: ({ target }: { target: HTMLInputElement }) =>
|
||||
onChange(ActionType.textChange, {
|
||||
onChange(ActionType.inputChange, {
|
||||
name: target.name,
|
||||
value: target.value,
|
||||
}),
|
||||
@@ -266,7 +324,7 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
value={currentReport?.description || ''}
|
||||
validationMethods={{
|
||||
onChange: ({ target }: { target: HTMLInputElement }) =>
|
||||
onChange(ActionType.textChange, {
|
||||
onChange(ActionType.inputChange, {
|
||||
name: target.name,
|
||||
value: target.value,
|
||||
}),
|
||||
@@ -284,16 +342,16 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
<StyledBottomSection>
|
||||
<StyledScheduleTitle>
|
||||
<h4 css={(theme: SupersetTheme) => SectionHeaderStyle(theme)}>
|
||||
Schedule
|
||||
{t('Schedule')}
|
||||
</h4>
|
||||
<p>Scheduled reports will be sent to your email as a PNG</p>
|
||||
<p>{t('Scheduled reports will be sent to your email as a PNG')}</p>
|
||||
</StyledScheduleTitle>
|
||||
|
||||
<CronPicker
|
||||
<StyledCronPicker
|
||||
clearButton={false}
|
||||
value={currentReport?.crontab || '0 12 * * 1'}
|
||||
setValue={(newValue: string) => {
|
||||
onChange(ActionType.textChange, {
|
||||
onChange(ActionType.inputChange, {
|
||||
name: 'crontab',
|
||||
value: newValue,
|
||||
});
|
||||
@@ -310,12 +368,13 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
||||
<TimezoneSelector
|
||||
onTimezoneChange={value => {
|
||||
setCurrentReport({
|
||||
type: ActionType.textChange,
|
||||
type: ActionType.inputChange,
|
||||
payload: { name: 'timezone', value },
|
||||
});
|
||||
}}
|
||||
timezone={currentReport?.timezone}
|
||||
/>
|
||||
{props.props.chart && renderMessageContentSection}
|
||||
</StyledBottomSection>
|
||||
</StyledModal>
|
||||
);
|
||||
|
||||
@@ -20,11 +20,17 @@
|
||||
import { styled, css, SupersetTheme } from '@superset-ui/core';
|
||||
import Modal from 'src/components/Modal';
|
||||
import Button from 'src/components/Button';
|
||||
import { Radio } from 'src/components/Radio';
|
||||
import { CronPicker } from 'src/components/CronPicker';
|
||||
|
||||
export const StyledModal = styled(Modal)`
|
||||
.ant-modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-weight: 600;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledTopSection = styled.div`
|
||||
@@ -61,6 +67,14 @@ export const StyledIconWrapper = styled.span`
|
||||
|
||||
export const StyledScheduleTitle = styled.div`
|
||||
margin-bottom: ${({ theme }) => theme.gridUnit * 7}px;
|
||||
|
||||
h4 {
|
||||
margin-bottom: ${({ theme }) => theme.gridUnit * 3}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledCronPicker = styled(CronPicker)`
|
||||
margin-bottom: ${({ theme }) => theme.gridUnit * 3}px;
|
||||
`;
|
||||
|
||||
export const StyledCronError = styled.p`
|
||||
@@ -83,3 +97,17 @@ export const SectionHeaderStyle = (theme: SupersetTheme) => css`
|
||||
margin: ${theme.gridUnit * 3}px 0;
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
`;
|
||||
|
||||
export const StyledMessageContentTitle = styled.div`
|
||||
margin: ${({ theme }) => theme.gridUnit * 8}px 0
|
||||
${({ theme }) => theme.gridUnit * 4}px;
|
||||
`;
|
||||
|
||||
export const StyledRadio = styled(Radio)`
|
||||
display: block;
|
||||
line-height: ${({ theme }) => theme.gridUnit * 8}px;
|
||||
`;
|
||||
|
||||
export const StyledRadioGroup = styled(Radio.Group)`
|
||||
margin-left: ${({ theme }) => theme.gridUnit * 0.5}px;
|
||||
`;
|
||||
|
||||
@@ -249,8 +249,15 @@ function styled<
|
||||
if (forceOverflow) {
|
||||
Object.assign(restProps, {
|
||||
closeMenuOnScroll: (e: Event) => {
|
||||
// ensure menu is open
|
||||
const menuIsOpen = (stateManager as BasicSelect<OptionType>)?.state
|
||||
?.menuIsOpen;
|
||||
const target = e.target as HTMLElement;
|
||||
return target && !target.classList?.contains('Select__menu-list');
|
||||
return (
|
||||
menuIsOpen &&
|
||||
target &&
|
||||
!target.classList?.contains('Select__menu-list')
|
||||
);
|
||||
},
|
||||
menuPosition: 'fixed',
|
||||
});
|
||||
|
||||
@@ -47,7 +47,6 @@ type PickedSelectProps = Pick<
|
||||
AntdSelectAllProps,
|
||||
| 'allowClear'
|
||||
| 'autoFocus'
|
||||
| 'value'
|
||||
| 'disabled'
|
||||
| 'filterOption'
|
||||
| 'notFoundContent'
|
||||
@@ -310,10 +309,13 @@ const Select = ({
|
||||
|
||||
const handleOnDeselect = (value: string | number | AntdLabeledValue) => {
|
||||
if (Array.isArray(selectValue)) {
|
||||
const selectedValues = [
|
||||
...(selectValue as []).filter(opt => opt !== value),
|
||||
];
|
||||
setSelectValue(selectedValues);
|
||||
if (typeof value === 'number' || typeof value === 'string') {
|
||||
const array = selectValue as (string | number)[];
|
||||
setSelectValue(array.filter(element => element !== value));
|
||||
} else {
|
||||
const array = selectValue as AntdLabeledValue[];
|
||||
setSelectValue(array.filter(element => element.value !== value.value));
|
||||
}
|
||||
}
|
||||
setSearchedValue('');
|
||||
};
|
||||
|
||||
@@ -156,7 +156,6 @@ const TableView = ({
|
||||
useSortBy,
|
||||
usePagination,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (serverPagination && pageIndex !== initialState.pageIndex) {
|
||||
onServerPagination({
|
||||
|
||||
@@ -23,7 +23,7 @@ import moment from 'moment-timezone';
|
||||
import { NativeGraySelect as Select } from 'src/components/Select';
|
||||
|
||||
const DEFAULT_TIMEZONE = 'GMT Standard Time';
|
||||
const MIN_SELECT_WIDTH = '375px';
|
||||
const MIN_SELECT_WIDTH = '400px';
|
||||
|
||||
const offsetsToName = {
|
||||
'-300-240': ['Eastern Standard Time', 'Eastern Daylight Time'],
|
||||
|
||||
@@ -187,7 +187,7 @@ export const Table = styled.table`
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
max-width: 300px;
|
||||
max-width: 320px;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
&:first-of-type {
|
||||
|
||||
@@ -335,7 +335,7 @@ export const hydrateDashboard = (dashboardData, chartData) => (
|
||||
dashboardInfo: {
|
||||
...dashboardData,
|
||||
metadata,
|
||||
userId: String(user.userId), // legacy, please use state.user instead
|
||||
userId: user.userId ? String(user.userId) : null, // legacy, please use state.user instead
|
||||
dash_edit_perm: canEdit,
|
||||
dash_save_perm: findPermission('can_save_dash', 'Superset', roles),
|
||||
dash_share_perm: findPermission(
|
||||
|
||||
@@ -50,6 +50,7 @@ const propTypes = {
|
||||
removeSliceFromDashboard: PropTypes.func.isRequired,
|
||||
triggerQuery: PropTypes.func.isRequired,
|
||||
logEvent: PropTypes.func.isRequired,
|
||||
clearDataMaskState: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
dashboardInfo: dashboardInfoPropShape.isRequired,
|
||||
dashboardState: dashboardStatePropShape.isRequired,
|
||||
@@ -193,6 +194,7 @@ class Dashboard extends React.PureComponent {
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('visibilitychange', this.onVisibilityChange);
|
||||
this.props.actions.clearDataMaskState();
|
||||
}
|
||||
|
||||
onVisibilityChange() {
|
||||
|
||||
@@ -96,6 +96,7 @@ test('Should render "appliedCrossFilterIndicators"', () => {
|
||||
<DetailsPanel {...props}>
|
||||
<div data-test="details-panel-content">Content</div>
|
||||
</DetailsPanel>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByTestId('details-panel-content'));
|
||||
@@ -129,6 +130,7 @@ test('Should render "appliedIndicators"', () => {
|
||||
<DetailsPanel {...props}>
|
||||
<div data-test="details-panel-content">Content</div>
|
||||
</DetailsPanel>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByTestId('details-panel-content'));
|
||||
@@ -160,6 +162,7 @@ test('Should render "incompatibleIndicators"', () => {
|
||||
<DetailsPanel {...props}>
|
||||
<div data-test="details-panel-content">Content</div>
|
||||
</DetailsPanel>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByTestId('details-panel-content'));
|
||||
@@ -193,6 +196,7 @@ test('Should render "unsetIndicators"', () => {
|
||||
<DetailsPanel {...props}>
|
||||
<div data-test="details-panel-content">Content</div>
|
||||
</DetailsPanel>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
|
||||
userEvent.click(screen.getByTestId('details-panel-content'));
|
||||
@@ -227,6 +231,7 @@ test('Should render empty', () => {
|
||||
<DetailsPanel {...props}>
|
||||
<div data-test="details-panel-content">Content</div>
|
||||
</DetailsPanel>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('details-panel-content')).toBeInTheDocument();
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Global, css } from '@emotion/react';
|
||||
import { t, useTheme } from '@superset-ui/core';
|
||||
import {
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
} from 'src/dashboard/components/FiltersBadge/Styles';
|
||||
import { Indicator } from 'src/dashboard/components/FiltersBadge/selectors';
|
||||
import FilterIndicator from 'src/dashboard/components/FiltersBadge/FilterIndicator';
|
||||
import { RootState } from 'src/dashboard/types';
|
||||
|
||||
export interface DetailsPanelProps {
|
||||
appliedCrossFilterIndicators: Indicator[];
|
||||
@@ -55,6 +57,9 @@ const DetailsPanelPopover = ({
|
||||
}: DetailsPanelProps) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const theme = useTheme();
|
||||
const activeTabs = useSelector<RootState>(
|
||||
state => state.dashboardState?.activeTabs,
|
||||
);
|
||||
|
||||
// we don't need to clean up useEffect, setting { once: true } removes the event listener after handle function is called
|
||||
useEffect(() => {
|
||||
@@ -65,6 +70,11 @@ const DetailsPanelPopover = ({
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
// if tabs change, popover doesn't close automatically
|
||||
useEffect(() => {
|
||||
setVisible(false);
|
||||
}, [activeTabs]);
|
||||
|
||||
const getDefaultActivePanel = () => {
|
||||
const result = [];
|
||||
if (appliedCrossFilterIndicators.length) {
|
||||
|
||||
@@ -20,7 +20,12 @@ import { NO_TIME_RANGE, TIME_FILTER_MAP } from 'src/explore/constants';
|
||||
import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters';
|
||||
import { ChartConfiguration, Filters } from 'src/dashboard/reducers/types';
|
||||
import { DataMaskStateWithId, DataMaskType } from 'src/dataMask/types';
|
||||
import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core';
|
||||
import {
|
||||
ensureIsArray,
|
||||
FeatureFlag,
|
||||
FilterState,
|
||||
isFeatureEnabled,
|
||||
} from '@superset-ui/core';
|
||||
import { Layout } from '../../types';
|
||||
import { getTreeCheckedItems } from '../nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils';
|
||||
|
||||
@@ -50,6 +55,16 @@ type Filter = {
|
||||
datasourceId: string;
|
||||
};
|
||||
|
||||
const extractLabel = (filter?: FilterState): string | null => {
|
||||
if (filter?.label) {
|
||||
return filter.label;
|
||||
}
|
||||
if (filter?.value) {
|
||||
return ensureIsArray(filter?.value).join(', ');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const selectIndicatorValue = (
|
||||
columnKey: string,
|
||||
filter: Filter,
|
||||
@@ -182,16 +197,16 @@ export const selectNativeIndicatorsForChart = (
|
||||
const rejectedColumns = getRejectedColumns(chart);
|
||||
|
||||
const getStatus = ({
|
||||
value,
|
||||
label,
|
||||
column,
|
||||
type = DataMaskType.NativeFilters,
|
||||
}: {
|
||||
value: any;
|
||||
label: string | null;
|
||||
column?: string;
|
||||
type?: DataMaskType;
|
||||
}): IndicatorStatus => {
|
||||
// a filter is only considered unset if it's value is null
|
||||
const hasValue = value !== null;
|
||||
const hasValue = label !== null;
|
||||
if (type === DataMaskType.CrossFilters && hasValue) {
|
||||
return IndicatorStatus.CrossFilterApplied;
|
||||
}
|
||||
@@ -220,19 +235,14 @@ export const selectNativeIndicatorsForChart = (
|
||||
)
|
||||
.map(nativeFilter => {
|
||||
const column = nativeFilter.targets[0]?.column?.name;
|
||||
let value =
|
||||
dataMask[nativeFilter.id]?.filterState?.label ??
|
||||
dataMask[nativeFilter.id]?.filterState?.value ??
|
||||
null;
|
||||
if (!Array.isArray(value) && value !== null) {
|
||||
value = [value];
|
||||
}
|
||||
const filterState = dataMask[nativeFilter.id]?.filterState;
|
||||
const label = extractLabel(filterState);
|
||||
return {
|
||||
column,
|
||||
name: nativeFilter.name,
|
||||
path: [nativeFilter.id],
|
||||
status: getStatus({ value, column }),
|
||||
value,
|
||||
status: getStatus({ label, column }),
|
||||
value: label,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -249,23 +259,26 @@ export const selectNativeIndicatorsForChart = (
|
||||
),
|
||||
)
|
||||
.map(chartConfig => {
|
||||
let value =
|
||||
dataMask[chartConfig.id]?.filterState?.label ??
|
||||
dataMask[chartConfig.id]?.filterState?.value ??
|
||||
null;
|
||||
if (!Array.isArray(value) && value !== null) {
|
||||
value = [value];
|
||||
}
|
||||
const filterState = dataMask[chartConfig.id]?.filterState;
|
||||
const label = extractLabel(filterState);
|
||||
const filtersState = filterState?.filters;
|
||||
const column = filtersState && Object.keys(filtersState)[0];
|
||||
|
||||
const dashboardLayoutItem = Object.values(dashboardLayout).find(
|
||||
layoutItem => layoutItem?.meta?.chartId === chartConfig.id,
|
||||
);
|
||||
return {
|
||||
name: Object.values(dashboardLayout).find(
|
||||
layoutItem => layoutItem?.meta?.chartId === chartConfig.id,
|
||||
)?.meta?.sliceName as string,
|
||||
path: [`${chartConfig.id}`],
|
||||
column,
|
||||
name: dashboardLayoutItem?.meta?.sliceName as string,
|
||||
path: [
|
||||
...(dashboardLayoutItem?.parents ?? []),
|
||||
dashboardLayoutItem?.id,
|
||||
],
|
||||
status: getStatus({
|
||||
value,
|
||||
label,
|
||||
type: DataMaskType.CrossFilters,
|
||||
}),
|
||||
value,
|
||||
value: label,
|
||||
};
|
||||
})
|
||||
.filter(filter => filter.status === IndicatorStatus.CrossFilterApplied);
|
||||
|
||||
@@ -48,7 +48,9 @@ import {
|
||||
SAVE_TYPE_OVERWRITE,
|
||||
DASHBOARD_POSITION_DATA_LIMIT,
|
||||
} from 'src/dashboard/util/constants';
|
||||
import setPeriodicRunner from 'src/dashboard/util/setPeriodicRunner';
|
||||
import setPeriodicRunner, {
|
||||
stopPeriodicRender,
|
||||
} from 'src/dashboard/util/setPeriodicRunner';
|
||||
import { options as PeriodicRefreshOptions } from 'src/dashboard/components/RefreshIntervalModal';
|
||||
|
||||
const propTypes = {
|
||||
@@ -168,18 +170,20 @@ class Header extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
const { refreshFrequency, user, dashboardInfo } = this.props;
|
||||
this.startPeriodicRender(refreshFrequency * 1000);
|
||||
if (user && isFeatureEnabled(FeatureFlag.ALERT_REPORTS)) {
|
||||
if (this.canAddReports()) {
|
||||
// this is in case there is an anonymous user.
|
||||
this.props.fetchUISpecificReport(
|
||||
user.userId,
|
||||
'dashboard_id',
|
||||
'dashboards',
|
||||
dashboardInfo.id,
|
||||
user.email,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
const { user } = this.props;
|
||||
if (
|
||||
UNDO_LIMIT - nextProps.undoLength <= 0 &&
|
||||
!this.state.didNotifyMaxUndoHistoryToast
|
||||
@@ -193,9 +197,24 @@ class Header extends React.PureComponent {
|
||||
) {
|
||||
this.props.setMaxUndoHistoryExceeded();
|
||||
}
|
||||
if (
|
||||
this.canAddReports() &&
|
||||
nextProps.dashboardInfo.id !== this.props.dashboardInfo.id
|
||||
) {
|
||||
// this is in case there is an anonymous user.
|
||||
this.props.fetchUISpecificReport(
|
||||
user.userId,
|
||||
'dashboard_id',
|
||||
'dashboards',
|
||||
nextProps.dashboardInfo.id,
|
||||
user.email,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
stopPeriodicRender(this.refreshTimer);
|
||||
this.props.setRefreshFrequency(0);
|
||||
clearTimeout(this.ctrlYTimeout);
|
||||
clearTimeout(this.ctrlZTimeout);
|
||||
}
|
||||
@@ -383,32 +402,31 @@ class Header extends React.PureComponent {
|
||||
|
||||
renderReportModal() {
|
||||
const attachedReportExists = !!Object.keys(this.props.reports).length;
|
||||
const canAddReports = isFeatureEnabled(FeatureFlag.ALERT_REPORTS);
|
||||
return (
|
||||
(canAddReports || null) &&
|
||||
(attachedReportExists ? (
|
||||
<HeaderReportActionsDropdown
|
||||
showReportModal={this.showReportModal}
|
||||
toggleActive={this.props.toggleActive}
|
||||
deleteActiveReport={this.props.deleteActiveReport}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
role="button"
|
||||
title={t('Schedule email report')}
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={this.showReportModal}
|
||||
>
|
||||
<Icons.Calendar />
|
||||
</span>
|
||||
</>
|
||||
))
|
||||
return attachedReportExists ? (
|
||||
<HeaderReportActionsDropdown
|
||||
showReportModal={this.showReportModal}
|
||||
toggleActive={this.props.toggleActive}
|
||||
deleteActiveReport={this.props.deleteActiveReport}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
role="button"
|
||||
title={t('Schedule email report')}
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={this.showReportModal}
|
||||
>
|
||||
<Icons.Calendar />
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
canAddReports() {
|
||||
if (!isFeatureEnabled(FeatureFlag.ALERT_REPORTS)) {
|
||||
return false;
|
||||
}
|
||||
const { user } = this.props;
|
||||
if (!user) {
|
||||
// this is in the case that there is an anonymous user.
|
||||
@@ -417,7 +435,7 @@ class Header extends React.PureComponent {
|
||||
const roles = Object.keys(user.roles || []);
|
||||
const permissions = roles.map(key =>
|
||||
user.roles[key].filter(
|
||||
perms => perms[0] === 'can_add' && perms[1] === 'AlertModelView',
|
||||
perms => perms[0] === 'menu_access' && perms[1] === 'Manage',
|
||||
),
|
||||
);
|
||||
return permissions[0].length > 0;
|
||||
|
||||
@@ -36,7 +36,7 @@ jest.mock('src/common/components', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const createProps = () => ({
|
||||
const createProps = (viz_type = 'sunburst') => ({
|
||||
addDangerToast: jest.fn(),
|
||||
addSuccessToast: jest.fn(),
|
||||
exploreChart: jest.fn(),
|
||||
@@ -67,9 +67,9 @@ const createProps = () => ({
|
||||
time_range: 'No filter',
|
||||
time_range_endpoints: ['inclusive', 'exclusive'],
|
||||
url_params: {},
|
||||
viz_type: 'sunburst',
|
||||
viz_type,
|
||||
},
|
||||
viz_type: 'sunburst',
|
||||
viz_type,
|
||||
datasource: '58__table',
|
||||
description: 'test-description',
|
||||
description_markeddown: '',
|
||||
@@ -152,25 +152,30 @@ test('Should "export to CSV"', () => {
|
||||
expect(props.exportCSV).toBeCalledWith(371);
|
||||
});
|
||||
|
||||
test('Should not show "Export to CSV" if slice is filter box', () => {
|
||||
const props = createProps('filter_box');
|
||||
render(<SliceHeaderControls {...props} />, { useRedux: true });
|
||||
expect(screen.queryByRole('menuitem', { name: 'Export CSV' })).toBe(null);
|
||||
});
|
||||
|
||||
test('Export full CSV is under featureflag', () => {
|
||||
// @ts-ignore
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.ALLOW_FULL_CSV_EXPORT]: false,
|
||||
};
|
||||
const props = createProps();
|
||||
props.slice.viz_type = 'table';
|
||||
const props = createProps('table');
|
||||
render(<SliceHeaderControls {...props} />, { useRedux: true });
|
||||
expect(screen.queryByRole('menuitem', { name: 'Export full CSV' })).toBe(
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('Should "export full CSV"', () => {
|
||||
// @ts-ignore
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.ALLOW_FULL_CSV_EXPORT]: true,
|
||||
};
|
||||
const props = createProps();
|
||||
props.slice.viz_type = 'table';
|
||||
const props = createProps('table');
|
||||
render(<SliceHeaderControls {...props} />, { useRedux: true });
|
||||
expect(screen.queryByRole('menuitem', { name: 'Export full CSV' })).not.toBe(
|
||||
null,
|
||||
@@ -193,6 +198,18 @@ test('Should not show export full CSV if report is not table', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('Should not show export full CSV if slice is filter box', () => {
|
||||
// @ts-ignore
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.ALLOW_FULL_CSV_EXPORT]: true,
|
||||
};
|
||||
const props = createProps('filter_box');
|
||||
render(<SliceHeaderControls {...props} />, { useRedux: true });
|
||||
expect(screen.queryByRole('menuitem', { name: 'Export full CSV' })).toBe(
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('Should "Toggle chart description"', () => {
|
||||
const props = createProps();
|
||||
render(<SliceHeaderControls {...props} />, { useRedux: true });
|
||||
|
||||
@@ -324,16 +324,20 @@ class SliceHeaderControls extends React.PureComponent<
|
||||
{t('Download as image')}
|
||||
</Menu.Item>
|
||||
|
||||
{this.props.supersetCanCSV && (
|
||||
<Menu.Item key={MENU_KEYS.EXPORT_CSV}>{t('Export CSV')}</Menu.Item>
|
||||
)}
|
||||
{isFeatureEnabled(FeatureFlag.ALLOW_FULL_CSV_EXPORT) &&
|
||||
{this.props.slice.viz_type !== 'filter_box' &&
|
||||
this.props.supersetCanCSV && (
|
||||
<Menu.Item key={MENU_KEYS.EXPORT_CSV}>{t('Export CSV')}</Menu.Item>
|
||||
)}
|
||||
|
||||
{this.props.slice.viz_type !== 'filter_box' &&
|
||||
isFeatureEnabled(FeatureFlag.ALLOW_FULL_CSV_EXPORT) &&
|
||||
this.props.supersetCanCSV &&
|
||||
isTable && (
|
||||
<Menu.Item key={MENU_KEYS.EXPORT_FULL_CSV}>
|
||||
{t('Export full CSV')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
{isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS) &&
|
||||
isCrossFilter && (
|
||||
<Menu.Item key={MENU_KEYS.CROSS_FILTER_SCOPING}>
|
||||
|
||||
@@ -56,7 +56,7 @@ export const checkIsMissingRequiredValue = (
|
||||
const value = filterState?.value;
|
||||
// TODO: this property should be unhardcoded
|
||||
return (
|
||||
filter.controlValues.enableEmptyFilter &&
|
||||
filter.controlValues?.enableEmptyFilter &&
|
||||
(value === null || value === undefined)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ export const FILTER_SUPPORTED_TYPES = {
|
||||
filter_timegrain: [GenericDataType.TEMPORAL],
|
||||
filter_timecolumn: [GenericDataType.TEMPORAL],
|
||||
filter_select: [
|
||||
GenericDataType.BOOLEAN,
|
||||
GenericDataType.STRING,
|
||||
GenericDataType.NUMERIC,
|
||||
GenericDataType.TEMPORAL,
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
getAllActiveFilters,
|
||||
getRelevantDataMask,
|
||||
} from 'src/dashboard/util/activeAllDashboardFilters';
|
||||
import { clearDataMaskState } from '../../dataMask/actions';
|
||||
|
||||
function mapStateToProps(state: RootState) {
|
||||
const {
|
||||
@@ -83,6 +84,7 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
||||
actions: bindActionCreators(
|
||||
{
|
||||
setDatasources,
|
||||
clearDataMaskState,
|
||||
addSliceToDashboard,
|
||||
removeSliceFromDashboard,
|
||||
triggerQuery,
|
||||
|
||||
@@ -51,8 +51,8 @@ export default function newEntitiesFromDrop({ dropResult, layout }) {
|
||||
rowWrapper.children = [newDropChild.id];
|
||||
rowWrapper.parents = (dropEntity.parents || []).concat(dropEntity.id);
|
||||
newEntities[rowWrapper.id] = rowWrapper;
|
||||
newDropChild = rowWrapper;
|
||||
newDropChild.parents = rowWrapper.parents.concat(rowWrapper.id);
|
||||
newDropChild = rowWrapper;
|
||||
} else if (dragType === TABS_TYPE) {
|
||||
// create a new tab component
|
||||
const tabChild = newComponentFactory(TAB_TYPE);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
const stopPeriodicRender = (refreshTimer?: number) => {
|
||||
export const stopPeriodicRender = (refreshTimer?: number) => {
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@ import { FeatureFlag, isFeatureEnabled } from '../featureFlags';
|
||||
import { Filters } from '../dashboard/reducers/types';
|
||||
import { getInitialDataMask } from './reducer';
|
||||
|
||||
export const CLEAR_DATA_MASK_STATE = 'CLEAR_DATA_MASK_STATE';
|
||||
export interface ClearDataMaskState {
|
||||
type: typeof CLEAR_DATA_MASK_STATE;
|
||||
}
|
||||
|
||||
export const UPDATE_DATA_MASK = 'UPDATE_DATA_MASK';
|
||||
export interface UpdateDataMask {
|
||||
type: typeof UPDATE_DATA_MASK;
|
||||
@@ -81,7 +86,14 @@ export function clearDataMask(filterId: string | number) {
|
||||
);
|
||||
}
|
||||
|
||||
export function clearDataMaskState(): ClearDataMaskState {
|
||||
return {
|
||||
type: CLEAR_DATA_MASK_STATE,
|
||||
};
|
||||
}
|
||||
|
||||
export type AnyDataMaskAction =
|
||||
| ClearDataMaskState
|
||||
| UpdateDataMask
|
||||
| SetDataMaskForFilterConfigFail
|
||||
| SetDataMaskForFilterConfigComplete;
|
||||
|
||||
@@ -29,6 +29,7 @@ import { URL_PARAMS } from 'src/constants';
|
||||
import { DataMaskStateWithId, DataMaskWithId } from './types';
|
||||
import {
|
||||
AnyDataMaskAction,
|
||||
CLEAR_DATA_MASK_STATE,
|
||||
SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE,
|
||||
UPDATE_DATA_MASK,
|
||||
} from './actions';
|
||||
@@ -104,6 +105,8 @@ const dataMaskReducer = produce(
|
||||
(draft: DataMaskStateWithId, action: AnyDataMaskAction) => {
|
||||
const cleanState = {};
|
||||
switch (action.type) {
|
||||
case CLEAR_DATA_MASK_STATE:
|
||||
return cleanState;
|
||||
case UPDATE_DATA_MASK:
|
||||
draft[action.filterId] = {
|
||||
...getInitialDataMask(action.filterId),
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import rison from 'rison';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Row, Col } from 'src/common/components';
|
||||
@@ -112,7 +113,7 @@ const ColumnButtonWrapper = styled.div`
|
||||
const checkboxGenerator = (d, onChange) => (
|
||||
<CheckboxControl value={d} onChange={onChange} />
|
||||
);
|
||||
const DATA_TYPES = ['STRING', 'NUMERIC', 'DATETIME'];
|
||||
const DATA_TYPES = ['STRING', 'NUMERIC', 'DATETIME', 'BOOLEAN'];
|
||||
|
||||
const DATASOURCE_TYPES_ARR = [
|
||||
{ key: 'physical', label: t('Physical (table or view)') },
|
||||
@@ -390,7 +391,7 @@ class DatasourceEditor extends React.PureComponent {
|
||||
this.setState(prevState => ({ isEditMode: !prevState.isEditMode }));
|
||||
}
|
||||
|
||||
onDatasourceChange(datasource, callback) {
|
||||
onDatasourceChange(datasource, callback = this.validateAndChange) {
|
||||
this.setState({ datasource }, callback);
|
||||
}
|
||||
|
||||
@@ -459,13 +460,13 @@ class DatasourceEditor extends React.PureComponent {
|
||||
results.added.push(col.name);
|
||||
} else if (
|
||||
currentCol.type !== col.type ||
|
||||
currentCol.is_dttm !== col.is_dttm
|
||||
(!currentCol.is_dttm && col.is_dttm)
|
||||
) {
|
||||
// modified column
|
||||
finalColumns.push({
|
||||
...currentCol,
|
||||
type: col.type,
|
||||
is_dttm: col.is_dttm,
|
||||
is_dttm: currentCol.is_dttm || col.is_dttm,
|
||||
});
|
||||
results.modified.push(col.name);
|
||||
} else {
|
||||
@@ -485,11 +486,22 @@ class DatasourceEditor extends React.PureComponent {
|
||||
|
||||
syncMetadata() {
|
||||
const { datasource } = this.state;
|
||||
const endpoint = `/datasource/external_metadata_by_name/${
|
||||
datasource.type || datasource.datasource_type
|
||||
}/${datasource.database.database_name || datasource.database.name}/${
|
||||
datasource.schema
|
||||
}/${datasource.table_name}/`;
|
||||
const params = {
|
||||
datasource_type: datasource.type || datasource.datasource_type,
|
||||
database_name:
|
||||
datasource.database.database_name || datasource.database.name,
|
||||
schema_name: datasource.schema,
|
||||
table_name: datasource.table_name,
|
||||
};
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
// rison can't encode the undefined value
|
||||
if (value === undefined) {
|
||||
params[key] = null;
|
||||
}
|
||||
});
|
||||
const endpoint = `/datasource/external_metadata_by_name/?q=${rison.encode(
|
||||
params,
|
||||
)}`;
|
||||
this.setState({ metadataLoading: true });
|
||||
|
||||
SupersetClient.get({ endpoint })
|
||||
@@ -616,7 +628,13 @@ class DatasourceEditor extends React.PureComponent {
|
||||
'values from the table. Typically the intent would be to limit the scan ' +
|
||||
'by applying a relative time filter on a partitioned or indexed time-related field.',
|
||||
)}
|
||||
control={<TextControl controlId="fetch_values_predicate" />}
|
||||
control={
|
||||
<TextAreaControl
|
||||
language="sql"
|
||||
controlId="fetch_values_predicate"
|
||||
minLines={5}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{this.state.isSqla && (
|
||||
|
||||
@@ -55,11 +55,15 @@ const CopyNode = (
|
||||
|
||||
export const CopyToClipboardButton = ({
|
||||
data,
|
||||
columns,
|
||||
}: {
|
||||
data?: Record<string, any>;
|
||||
columns?: string[];
|
||||
}) => (
|
||||
<CopyToClipboard
|
||||
text={data ? prepareCopyToClipboardTabularData(data) : ''}
|
||||
text={
|
||||
data && columns ? prepareCopyToClipboardTabularData(data, columns) : ''
|
||||
}
|
||||
wrapped={false}
|
||||
copyNode={CopyNode}
|
||||
/>
|
||||
@@ -113,29 +117,32 @@ export const useFilteredTableData = (
|
||||
}, [data, filterText]);
|
||||
|
||||
export const useTableColumns = (
|
||||
colnames?: string[],
|
||||
data?: Record<string, any>[],
|
||||
moreConfigs?: { [key: string]: Partial<Column> },
|
||||
) =>
|
||||
useMemo(
|
||||
() =>
|
||||
data?.length
|
||||
? Object.keys(data[0]).map(
|
||||
key =>
|
||||
({
|
||||
accessor: row => row[key],
|
||||
Header: key,
|
||||
Cell: ({ value }) => {
|
||||
if (value === true) {
|
||||
return BOOL_TRUE_DISPLAY;
|
||||
}
|
||||
if (value === false) {
|
||||
return BOOL_FALSE_DISPLAY;
|
||||
}
|
||||
return String(value);
|
||||
},
|
||||
...moreConfigs?.[key],
|
||||
} as Column),
|
||||
)
|
||||
colnames && data?.length
|
||||
? colnames
|
||||
.filter((column: string) => Object.keys(data[0]).includes(column))
|
||||
.map(
|
||||
key =>
|
||||
({
|
||||
accessor: row => row[key],
|
||||
Header: key,
|
||||
Cell: ({ value }) => {
|
||||
if (value === true) {
|
||||
return BOOL_TRUE_DISPLAY;
|
||||
}
|
||||
if (value === false) {
|
||||
return BOOL_FALSE_DISPLAY;
|
||||
}
|
||||
return String(value);
|
||||
},
|
||||
...moreConfigs?.[key],
|
||||
} as Column),
|
||||
)
|
||||
: [],
|
||||
[data, moreConfigs],
|
||||
);
|
||||
|
||||
@@ -42,9 +42,10 @@ const data = [
|
||||
[unicodeKey]: unicodeKey,
|
||||
},
|
||||
];
|
||||
const all_columns = ['col01', 'col02', 'col03', asciiKey, unicodeKey];
|
||||
|
||||
test('useTableColumns with no options', () => {
|
||||
const hook = renderHook(() => useTableColumns(data));
|
||||
const hook = renderHook(() => useTableColumns(all_columns, data));
|
||||
expect(hook.result.current).toEqual([
|
||||
{
|
||||
Cell: expect.any(Function),
|
||||
@@ -83,7 +84,7 @@ test('useTableColumns with no options', () => {
|
||||
|
||||
test('use only the first record columns', () => {
|
||||
const newData = [data[3], data[0]];
|
||||
const hook = renderHook(() => useTableColumns(newData));
|
||||
const hook = renderHook(() => useTableColumns(all_columns, newData));
|
||||
expect(hook.result.current).toEqual([
|
||||
{
|
||||
Cell: expect.any(Function),
|
||||
@@ -134,7 +135,9 @@ test('use only the first record columns', () => {
|
||||
});
|
||||
|
||||
test('useTableColumns with options', () => {
|
||||
const hook = renderHook(() => useTableColumns(data, { col01: { id: 'ID' } }));
|
||||
const hook = renderHook(() =>
|
||||
useTableColumns(all_columns, data, { col01: { id: 'ID' } }),
|
||||
);
|
||||
expect(hook.result.current).toEqual([
|
||||
{
|
||||
Cell: expect.any(Function),
|
||||
|
||||
@@ -58,6 +58,11 @@ const createProps = () => ({
|
||||
tableSectionHeight: 156.9,
|
||||
chartStatus: 'rendered',
|
||||
onCollapseChange: jest.fn(),
|
||||
queriesResponse: [
|
||||
{
|
||||
colnames: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
||||
@@ -112,6 +112,7 @@ export const DataTablesPane = ({
|
||||
chartStatus,
|
||||
ownState,
|
||||
errorMessage,
|
||||
queriesResponse,
|
||||
}: {
|
||||
queryFormData: Record<string, any>;
|
||||
tableSectionHeight: number;
|
||||
@@ -119,6 +120,7 @@ export const DataTablesPane = ({
|
||||
ownState?: JsonObject;
|
||||
onCollapseChange: (openPanelName: string) => void;
|
||||
errorMessage?: JSX.Element;
|
||||
queriesResponse: Record<string, any>;
|
||||
}) => {
|
||||
const [data, setData] = useState<{
|
||||
[RESULT_TYPES.results]?: Record<string, any>[];
|
||||
@@ -128,6 +130,7 @@ export const DataTablesPane = ({
|
||||
[RESULT_TYPES.results]: true,
|
||||
[RESULT_TYPES.samples]: true,
|
||||
});
|
||||
const [columnNames, setColumnNames] = useState<string[]>([]);
|
||||
const [error, setError] = useState(NULLISH_RESULTS_STATE);
|
||||
const [filterText, setFilterText] = useState('');
|
||||
const [activeTabKey, setActiveTabKey] = useState<string>(
|
||||
@@ -218,7 +221,14 @@ export const DataTablesPane = ({
|
||||
...prevState,
|
||||
[RESULT_TYPES.samples]: true,
|
||||
}));
|
||||
}, [queryFormData.adhoc_filters, queryFormData.datasource]);
|
||||
}, [queryFormData?.adhoc_filters, queryFormData?.datasource]);
|
||||
|
||||
useEffect(() => {
|
||||
if (queriesResponse && chartStatus === 'success') {
|
||||
const { colnames } = queriesResponse[0];
|
||||
setColumnNames([...colnames]);
|
||||
}
|
||||
}, [queriesResponse]);
|
||||
|
||||
useEffect(() => {
|
||||
if (panelOpen && isRequestPending[RESULT_TYPES.results]) {
|
||||
@@ -277,9 +287,17 @@ export const DataTablesPane = ({
|
||||
),
|
||||
};
|
||||
|
||||
// this is to preserve the order of the columns, even if there are integer values,
|
||||
// while also only grabbing the first column's keys
|
||||
const columns = {
|
||||
[RESULT_TYPES.results]: useTableColumns(data[RESULT_TYPES.results]),
|
||||
[RESULT_TYPES.samples]: useTableColumns(data[RESULT_TYPES.samples]),
|
||||
[RESULT_TYPES.results]: useTableColumns(
|
||||
columnNames,
|
||||
data[RESULT_TYPES.results],
|
||||
),
|
||||
[RESULT_TYPES.samples]: useTableColumns(
|
||||
columnNames,
|
||||
data[RESULT_TYPES.samples],
|
||||
),
|
||||
};
|
||||
|
||||
const renderDataTable = (type: string) => {
|
||||
@@ -316,7 +334,7 @@ export const DataTablesPane = ({
|
||||
const TableControls = (
|
||||
<TableControlsWrapper>
|
||||
<RowCount data={data[activeTabKey]} loading={isLoading[activeTabKey]} />
|
||||
<CopyToClipboardButton data={data[activeTabKey]} />
|
||||
<CopyToClipboardButton data={data[activeTabKey]} columns={columnNames} />
|
||||
<FilterInput onChangeHandler={setFilterText} />
|
||||
</TableControlsWrapper>
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@ const createProps = () => ({
|
||||
cache_timeout: null,
|
||||
changed_on: '2021-03-19T16:30:56.750230',
|
||||
changed_on_humanized: '3 days ago',
|
||||
datasource: 'FCC 2018 Survey',
|
||||
datasource: 'FCC Survey Results',
|
||||
description: null,
|
||||
description_markeddown: '',
|
||||
edit_url: '/chart/edit/318',
|
||||
|
||||
@@ -91,6 +91,7 @@ const StyledHeader = styled.div`
|
||||
}
|
||||
|
||||
.action-button {
|
||||
color: ${({ theme }) => theme.colors.grayscale.base};
|
||||
margin: 0 ${({ theme }) => theme.gridUnit * 1.5}px 0
|
||||
${({ theme }) => theme.gridUnit}px;
|
||||
}
|
||||
@@ -116,8 +117,8 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { user, chart } = this.props;
|
||||
if (user && isFeatureEnabled(FeatureFlag.ALERT_REPORTS)) {
|
||||
if (this.canAddReports()) {
|
||||
const { user, chart } = this.props;
|
||||
// this is in the case that there is an anonymous user.
|
||||
this.props.fetchUISpecificReport(
|
||||
user.userId,
|
||||
@@ -164,33 +165,32 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
|
||||
renderReportModal() {
|
||||
const attachedReportExists = !!Object.keys(this.props.reports).length;
|
||||
const canAddReports = isFeatureEnabled(FeatureFlag.ALERT_REPORTS);
|
||||
return (
|
||||
(canAddReports || null) &&
|
||||
(attachedReportExists ? (
|
||||
<HeaderReportActionsDropdown
|
||||
showReportModal={this.showReportModal}
|
||||
hideReportModal={this.hideReportModal}
|
||||
toggleActive={this.props.toggleActive}
|
||||
deleteActiveReport={this.props.deleteActiveReport}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
role="button"
|
||||
title={t('Schedule email report')}
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={this.showReportModal}
|
||||
>
|
||||
<Icons.Calendar />
|
||||
</span>
|
||||
</>
|
||||
))
|
||||
return attachedReportExists ? (
|
||||
<HeaderReportActionsDropdown
|
||||
showReportModal={this.showReportModal}
|
||||
hideReportModal={this.hideReportModal}
|
||||
toggleActive={this.props.toggleActive}
|
||||
deleteActiveReport={this.props.deleteActiveReport}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
role="button"
|
||||
title={t('Schedule email report')}
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={this.showReportModal}
|
||||
>
|
||||
<Icons.Calendar />
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
canAddReports() {
|
||||
if (!isFeatureEnabled(FeatureFlag.ALERT_REPORTS)) {
|
||||
return false;
|
||||
}
|
||||
const { user } = this.props;
|
||||
if (!user) {
|
||||
// this is in the case that there is an anonymous user.
|
||||
@@ -199,7 +199,7 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
const roles = Object.keys(user.roles || []);
|
||||
const permissions = roles.map(key =>
|
||||
user.roles[key].filter(
|
||||
perms => perms[0] === 'can_add' && perms[1] === 'AlertModelView',
|
||||
perms => perms[0] === 'menu_access' && perms[1] === 'Manage',
|
||||
),
|
||||
);
|
||||
return permissions[0].length > 0;
|
||||
@@ -294,7 +294,7 @@ export class ExploreChartHeader extends React.PureComponent {
|
||||
props={{
|
||||
userId: this.props.user.userId,
|
||||
userEmail: this.props.user.email,
|
||||
chartId: this.props.chart.id,
|
||||
chart: this.props.chart,
|
||||
creationMethod: 'charts',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -116,7 +116,6 @@ const ExploreChartPanel = props => {
|
||||
const theme = useTheme();
|
||||
const gutterMargin = theme.gridUnit * GUTTER_SIZE_FACTOR;
|
||||
const gutterHeight = theme.gridUnit * GUTTER_SIZE_FACTOR;
|
||||
|
||||
const { height: hHeight, ref: headerRef } = useResizeDetector({
|
||||
refreshMode: 'debounce',
|
||||
refreshRate: 300,
|
||||
@@ -128,11 +127,10 @@ const ExploreChartPanel = props => {
|
||||
const [splitSizes, setSplitSizes] = useState(
|
||||
getFromLocalStorage(STORAGE_KEYS.sizes, INITIAL_SIZES),
|
||||
);
|
||||
|
||||
const { slice } = props;
|
||||
const updateQueryContext = useCallback(
|
||||
async function fetchChartData() {
|
||||
if (slice && slice.query_context === null) {
|
||||
if (props.can_overwrite && slice && slice.query_context === null) {
|
||||
const queryContext = buildV1ChartDataPayload({
|
||||
formData: slice.form_data,
|
||||
force: false,
|
||||
@@ -147,12 +145,14 @@ const ExploreChartPanel = props => {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query_context: JSON.stringify(queryContext),
|
||||
query_context_generation: true,
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
[slice],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateQueryContext();
|
||||
}, [updateQueryContext]);
|
||||
@@ -210,7 +210,6 @@ const ExploreChartPanel = props => {
|
||||
}
|
||||
setSplitSizes(splitSizes);
|
||||
};
|
||||
|
||||
const renderChart = useCallback(() => {
|
||||
const { chart, vizType } = props;
|
||||
const newHeight =
|
||||
@@ -259,6 +258,22 @@ const ExploreChartPanel = props => {
|
||||
[chartPanelRef, renderChart],
|
||||
);
|
||||
|
||||
const [queryFormData, setQueryFormData] = useState(
|
||||
props.chart.latestQueryFormData,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// only update when `latestQueryFormData` changes AND `triggerRender`
|
||||
// is false. No update should be done when only `triggerRender` changes,
|
||||
// as this can trigger a query downstream based on incomplete form data.
|
||||
// (`latestQueryFormData` is only updated when a a valid request has been
|
||||
// triggered).
|
||||
if (!props.triggerRender) {
|
||||
setQueryFormData(props.chart.latestQueryFormData);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.chart.latestQueryFormData]);
|
||||
|
||||
if (props.standalone) {
|
||||
// dom manipulation hack to get rid of the boostrap theme's body background
|
||||
const standaloneClass = 'background-transparent';
|
||||
@@ -311,11 +326,12 @@ const ExploreChartPanel = props => {
|
||||
{panelBody}
|
||||
<DataTablesPane
|
||||
ownState={props.ownState}
|
||||
queryFormData={props.chart.latestQueryFormData}
|
||||
queryFormData={queryFormData}
|
||||
tableSectionHeight={tableSectionHeight}
|
||||
onCollapseChange={onCollapseChange}
|
||||
chartStatus={props.chart.chartStatus}
|
||||
errorMessage={props.errorMessage}
|
||||
queriesResponse={props.chart.queriesResponse}
|
||||
/>
|
||||
</Split>
|
||||
)}
|
||||
|
||||
@@ -81,7 +81,7 @@ const Styles = styled.div`
|
||||
text-align: left;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
@@ -448,6 +448,7 @@ function ExploreViewContainer(props) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
body {
|
||||
height: 100vh;
|
||||
max-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -458,7 +459,7 @@ function ExploreViewContainer(props) {
|
||||
#app {
|
||||
flex-basis: 100%;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
}
|
||||
#app-menu {
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -29,7 +29,7 @@ const createProps = () => ({
|
||||
cache_timeout: null,
|
||||
changed_on: '2021-03-19T16:30:56.750230',
|
||||
changed_on_humanized: '7 days ago',
|
||||
datasource: 'FCC 2018 Survey',
|
||||
datasource: 'FCC Survey Results',
|
||||
description: null,
|
||||
description_markeddown: '',
|
||||
edit_url: '/chart/edit/318',
|
||||
|
||||
@@ -125,6 +125,8 @@ const ConditionalFormattingControl = ({
|
||||
}: ConditionalFormattingConfig) => {
|
||||
const columnName = (column && verboseMap?.[column]) ?? column;
|
||||
switch (operator) {
|
||||
case COMPARATOR.NONE:
|
||||
return `${columnName}`;
|
||||
case COMPARATOR.BETWEEN:
|
||||
return `${targetValueLeft} ${COMPARATOR.LESS_THAN} ${columnName} ${COMPARATOR.LESS_THAN} ${targetValueRight}`;
|
||||
case COMPARATOR.BETWEEN_OR_EQUAL:
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { Form, FormItem } from 'src/components/Form';
|
||||
import { Form, FormItem, FormProps } from 'src/components/Form';
|
||||
import { Select } from 'src/components';
|
||||
import { Col, InputNumber, Row } from 'src/common/components';
|
||||
import Button from 'src/components/Button';
|
||||
@@ -44,6 +44,7 @@ const colorSchemeOptions = [
|
||||
];
|
||||
|
||||
const operatorOptions = [
|
||||
{ value: COMPARATOR.NONE, label: 'None' },
|
||||
{ value: COMPARATOR.GREATER_THAN, label: '>' },
|
||||
{ value: COMPARATOR.LESS_THAN, label: '<' },
|
||||
{ value: COMPARATOR.GREATER_OR_EQUAL, label: '≥' },
|
||||
@@ -56,6 +57,127 @@ const operatorOptions = [
|
||||
{ value: COMPARATOR.BETWEEN_OR_RIGHT_EQUAL, label: '< x ≤' },
|
||||
];
|
||||
|
||||
const targetValueValidator = (
|
||||
compare: (targetValue: number, compareValue: number) => boolean,
|
||||
rejectMessage: string,
|
||||
) => (targetValue: number | string) => (
|
||||
_: any,
|
||||
compareValue: number | string,
|
||||
) => {
|
||||
if (
|
||||
!targetValue ||
|
||||
!compareValue ||
|
||||
compare(Number(targetValue), Number(compareValue))
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(rejectMessage));
|
||||
};
|
||||
|
||||
const targetValueLeftValidator = targetValueValidator(
|
||||
(target: number, val: number) => target > val,
|
||||
t('This value should be smaller than the right target value'),
|
||||
);
|
||||
|
||||
const targetValueRightValidator = targetValueValidator(
|
||||
(target: number, val: number) => target < val,
|
||||
t('This value should be greater than the left target value'),
|
||||
);
|
||||
|
||||
const isOperatorMultiValue = (operator?: COMPARATOR) =>
|
||||
operator && MULTIPLE_VALUE_COMPARATORS.includes(operator);
|
||||
|
||||
const isOperatorNone = (operator?: COMPARATOR) =>
|
||||
!operator || operator === COMPARATOR.NONE;
|
||||
|
||||
const rulesRequired = [{ required: true, message: t('Required') }];
|
||||
|
||||
type GetFieldValue = Pick<Required<FormProps>['form'], 'getFieldValue'>;
|
||||
const rulesTargetValueLeft = [
|
||||
{ required: true, message: t('Required') },
|
||||
({ getFieldValue }: GetFieldValue) => ({
|
||||
validator: targetValueLeftValidator(getFieldValue('targetValueRight')),
|
||||
}),
|
||||
];
|
||||
|
||||
const rulesTargetValueRight = [
|
||||
{ required: true, message: t('Required') },
|
||||
({ getFieldValue }: GetFieldValue) => ({
|
||||
validator: targetValueRightValidator(getFieldValue('targetValueLeft')),
|
||||
}),
|
||||
];
|
||||
|
||||
const targetValueLeftDeps = ['targetValueRight'];
|
||||
const targetValueRightDeps = ['targetValueLeft'];
|
||||
|
||||
const shouldFormItemUpdate = (
|
||||
prevValues: ConditionalFormattingConfig,
|
||||
currentValues: ConditionalFormattingConfig,
|
||||
) =>
|
||||
isOperatorNone(prevValues.operator) !==
|
||||
isOperatorNone(currentValues.operator) ||
|
||||
isOperatorMultiValue(prevValues.operator) !==
|
||||
isOperatorMultiValue(currentValues.operator);
|
||||
|
||||
const operatorField = (
|
||||
<FormItem
|
||||
name="operator"
|
||||
label={t('Operator')}
|
||||
rules={rulesRequired}
|
||||
initialValue={operatorOptions[0].value}
|
||||
>
|
||||
<Select ariaLabel={t('Operator')} options={operatorOptions} />
|
||||
</FormItem>
|
||||
);
|
||||
|
||||
const renderOperatorFields = ({ getFieldValue }: GetFieldValue) =>
|
||||
isOperatorNone(getFieldValue('operator')) ? (
|
||||
<Row gutter={12}>
|
||||
<Col span={6}>{operatorField}</Col>
|
||||
</Row>
|
||||
) : isOperatorMultiValue(getFieldValue('operator')) ? (
|
||||
<Row gutter={12}>
|
||||
<Col span={9}>
|
||||
<FormItem
|
||||
name="targetValueLeft"
|
||||
label={t('Left value')}
|
||||
rules={rulesTargetValueLeft}
|
||||
dependencies={targetValueLeftDeps}
|
||||
validateTrigger="onBlur"
|
||||
trigger="onBlur"
|
||||
>
|
||||
<FullWidthInputNumber />
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={6}>{operatorField}</Col>
|
||||
<Col span={9}>
|
||||
<FormItem
|
||||
name="targetValueRight"
|
||||
label={t('Right value')}
|
||||
rules={rulesTargetValueRight}
|
||||
dependencies={targetValueRightDeps}
|
||||
validateTrigger="onBlur"
|
||||
trigger="onBlur"
|
||||
>
|
||||
<FullWidthInputNumber />
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<Row gutter={12}>
|
||||
<Col span={6}>{operatorField}</Col>
|
||||
<Col span={18}>
|
||||
<FormItem
|
||||
name="targetValue"
|
||||
label={t('Target value')}
|
||||
rules={rulesRequired}
|
||||
>
|
||||
<FullWidthInputNumber />
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
export const FormattingPopoverContent = ({
|
||||
config,
|
||||
onChange,
|
||||
@@ -64,158 +186,44 @@ export const FormattingPopoverContent = ({
|
||||
config?: ConditionalFormattingConfig;
|
||||
onChange: (config: ConditionalFormattingConfig) => void;
|
||||
columns: { label: string; value: string }[];
|
||||
}) => {
|
||||
const isOperatorMultiValue = (operator?: COMPARATOR) =>
|
||||
operator && MULTIPLE_VALUE_COMPARATORS.includes(operator);
|
||||
|
||||
const operatorField = useMemo(
|
||||
() => (
|
||||
<FormItem
|
||||
name="operator"
|
||||
label={t('Operator')}
|
||||
rules={[{ required: true, message: t('Required') }]}
|
||||
initialValue={operatorOptions[0].value}
|
||||
>
|
||||
<Select ariaLabel={t('Operator')} options={operatorOptions} />
|
||||
</FormItem>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
const targetValueLeftValidator = useCallback(
|
||||
(rightValue?: number) => (_: any, value?: number) => {
|
||||
if (!value || !rightValue || rightValue > value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
t('This value should be smaller than the right target value'),
|
||||
),
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const targetValueRightValidator = useCallback(
|
||||
(leftValue?: number) => (_: any, value?: number) => {
|
||||
if (!value || !leftValue || leftValue < value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error(t('This value should be greater than the left target value')),
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Form
|
||||
onFinish={onChange}
|
||||
initialValues={config}
|
||||
requiredMark="optional"
|
||||
layout="vertical"
|
||||
>
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<FormItem
|
||||
name="column"
|
||||
label={t('Column')}
|
||||
rules={[{ required: true, message: t('Required') }]}
|
||||
initialValue={columns[0]?.value}
|
||||
>
|
||||
<Select ariaLabel={t('Select column')} options={columns} />
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<FormItem
|
||||
name="colorScheme"
|
||||
label={t('Color scheme')}
|
||||
rules={[{ required: true, message: t('Required') }]}
|
||||
initialValue={colorSchemeOptions[0].value}
|
||||
>
|
||||
<Select
|
||||
ariaLabel={t('Color scheme')}
|
||||
options={colorSchemeOptions}
|
||||
/>
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
<FormItem
|
||||
noStyle
|
||||
shouldUpdate={(
|
||||
prevValues: ConditionalFormattingConfig,
|
||||
currentValues: ConditionalFormattingConfig,
|
||||
) =>
|
||||
isOperatorMultiValue(prevValues.operator) !==
|
||||
isOperatorMultiValue(currentValues.operator)
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) =>
|
||||
isOperatorMultiValue(getFieldValue('operator')) ? (
|
||||
<Row gutter={12}>
|
||||
<Col span={9}>
|
||||
<FormItem
|
||||
name="targetValueLeft"
|
||||
label={t('Left value')}
|
||||
rules={[
|
||||
{ required: true, message: t('Required') },
|
||||
({ getFieldValue }) => ({
|
||||
validator: targetValueLeftValidator(
|
||||
getFieldValue('targetValueRight'),
|
||||
),
|
||||
}),
|
||||
]}
|
||||
dependencies={['targetValueRight']}
|
||||
validateTrigger="onBlur"
|
||||
trigger="onBlur"
|
||||
>
|
||||
<FullWidthInputNumber />
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={6}>{operatorField}</Col>
|
||||
<Col span={9}>
|
||||
<FormItem
|
||||
name="targetValueRight"
|
||||
label={t('Right value')}
|
||||
rules={[
|
||||
{ required: true, message: t('Required') },
|
||||
({ getFieldValue }) => ({
|
||||
validator: targetValueRightValidator(
|
||||
getFieldValue('targetValueLeft'),
|
||||
),
|
||||
}),
|
||||
]}
|
||||
dependencies={['targetValueLeft']}
|
||||
validateTrigger="onBlur"
|
||||
trigger="onBlur"
|
||||
>
|
||||
<FullWidthInputNumber />
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<Row gutter={12}>
|
||||
<Col span={6}>{operatorField}</Col>
|
||||
<Col span={18}>
|
||||
<FormItem
|
||||
name="targetValue"
|
||||
label={t('Target value')}
|
||||
rules={[{ required: true, message: t('Required') }]}
|
||||
>
|
||||
<FullWidthInputNumber />
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<JustifyEnd>
|
||||
<Button htmlType="submit" buttonStyle="primary">
|
||||
{t('Apply')}
|
||||
</Button>
|
||||
</JustifyEnd>
|
||||
</FormItem>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<Form
|
||||
onFinish={onChange}
|
||||
initialValues={config}
|
||||
requiredMark="optional"
|
||||
layout="vertical"
|
||||
>
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<FormItem
|
||||
name="column"
|
||||
label={t('Column')}
|
||||
rules={rulesRequired}
|
||||
initialValue={columns[0]?.value}
|
||||
>
|
||||
<Select ariaLabel={t('Select column')} options={columns} />
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<FormItem
|
||||
name="colorScheme"
|
||||
label={t('Color scheme')}
|
||||
rules={rulesRequired}
|
||||
initialValue={colorSchemeOptions[0].value}
|
||||
>
|
||||
<Select ariaLabel={t('Color scheme')} options={colorSchemeOptions} />
|
||||
</FormItem>
|
||||
</Col>
|
||||
</Row>
|
||||
<FormItem noStyle shouldUpdate={shouldFormItemUpdate}>
|
||||
{renderOperatorFields}
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<JustifyEnd>
|
||||
<Button htmlType="submit" buttonStyle="primary">
|
||||
{t('Apply')}
|
||||
</Button>
|
||||
</JustifyEnd>
|
||||
</FormItem>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -22,6 +22,7 @@ import { PopoverProps } from 'antd/lib/popover';
|
||||
import { ControlComponentProps } from '@superset-ui/chart-controls/lib/shared-controls/components/types';
|
||||
|
||||
export enum COMPARATOR {
|
||||
NONE = 'None',
|
||||
GREATER_THAN = '>',
|
||||
LESS_THAN = '<',
|
||||
GREATER_OR_EQUAL = '≥',
|
||||
|
||||
@@ -29,7 +29,7 @@ const defaultProps: LabelProps = {
|
||||
|
||||
test('renders with default props', () => {
|
||||
render(<DndColumnSelect {...defaultProps} />, { useDnd: true });
|
||||
expect(screen.getByText('Drop columns')).toBeInTheDocument();
|
||||
expect(screen.getByText('Drop columns here')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders with value', () => {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { tn } from '@superset-ui/core';
|
||||
import { ColumnMeta } from '@superset-ui/chart-controls';
|
||||
import { isEmpty } from 'lodash';
|
||||
@@ -27,6 +27,7 @@ import { OptionSelector } from 'src/explore/components/controls/DndColumnSelectC
|
||||
import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/types';
|
||||
import { DndItemType } from 'src/explore/components/DndItemType';
|
||||
import { StyledColumnOption } from 'src/explore/components/optionRenderers';
|
||||
import { useComponentDidUpdate } from 'src/common/hooks/useComponentDidUpdate';
|
||||
|
||||
export const DndColumnSelect = (props: LabelProps) => {
|
||||
const {
|
||||
@@ -45,7 +46,7 @@ export const DndColumnSelect = (props: LabelProps) => {
|
||||
);
|
||||
|
||||
// synchronize values in case of dataset changes
|
||||
useEffect(() => {
|
||||
const handleOptionsChange = useCallback(() => {
|
||||
const optionSelectorValues = optionSelector.getValues();
|
||||
if (typeof value !== typeof optionSelectorValues) {
|
||||
onChange(optionSelectorValues);
|
||||
@@ -65,9 +66,12 @@ export const DndColumnSelect = (props: LabelProps) => {
|
||||
) {
|
||||
onChange(optionSelectorValues);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(value), JSON.stringify(optionSelector.getValues())]);
|
||||
|
||||
// useComponentDidUpdate to avoid running this for the first render, to avoid
|
||||
// calling onChange when the initial value is not valid for the dataset
|
||||
useComponentDidUpdate(handleOptionsChange);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(item: DatasourcePanelDndItem) => {
|
||||
const column = item.value as ColumnMeta;
|
||||
@@ -139,7 +143,8 @@ export const DndColumnSelect = (props: LabelProps) => {
|
||||
accept={DndItemType.Column}
|
||||
displayGhostButton={multi || optionSelector.values.length === 0}
|
||||
ghostButtonText={
|
||||
ghostButtonText || tn('Drop column', 'Drop columns', multi ? 2 : 1)
|
||||
ghostButtonText ||
|
||||
tn('Drop column here', 'Drop columns here', multi ? 2 : 1)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -38,7 +38,7 @@ const defaultProps = {
|
||||
|
||||
test('renders with default props', () => {
|
||||
render(<DndFilterSelect {...defaultProps} />, { useDnd: true });
|
||||
expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument();
|
||||
expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders with value', () => {
|
||||
@@ -56,7 +56,7 @@ test('renders options with saved metric', () => {
|
||||
render(<DndFilterSelect {...defaultProps} formData={['saved_metric']} />, {
|
||||
useDnd: true,
|
||||
});
|
||||
expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument();
|
||||
expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders options with column', () => {
|
||||
@@ -76,7 +76,7 @@ test('renders options with column', () => {
|
||||
useDnd: true,
|
||||
},
|
||||
);
|
||||
expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument();
|
||||
expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders options with adhoc metric', () => {
|
||||
@@ -87,5 +87,5 @@ test('renders options with adhoc metric', () => {
|
||||
render(<DndFilterSelect {...defaultProps} formData={[adhocMetric]} />, {
|
||||
useDnd: true,
|
||||
});
|
||||
expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument();
|
||||
expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -374,7 +374,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => {
|
||||
canDrop={canDrop}
|
||||
valuesRenderer={valuesRenderer}
|
||||
accept={DND_ACCEPTED_TYPES}
|
||||
ghostButtonText={t('Drop columns or metrics')}
|
||||
ghostButtonText={t('Drop columns or metrics here')}
|
||||
{...props}
|
||||
/>
|
||||
<AdhocFilterPopoverTrigger
|
||||
|
||||
@@ -31,10 +31,10 @@ const defaultProps = {
|
||||
|
||||
test('renders with default props', () => {
|
||||
render(<DndMetricSelect {...defaultProps} />, { useDnd: true });
|
||||
expect(screen.getByText('Drop column or metric')).toBeInTheDocument();
|
||||
expect(screen.getByText('Drop column or metric here')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders with default props and multi = true', () => {
|
||||
render(<DndMetricSelect {...defaultProps} multi />, { useDnd: true });
|
||||
expect(screen.getByText('Drop columns or metrics')).toBeInTheDocument();
|
||||
expect(screen.getByText('Drop columns or metrics here')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -185,6 +185,9 @@ export const DndMetricSelect = (props: any) => {
|
||||
|
||||
const onMetricEdit = useCallback(
|
||||
(changedMetric: Metric | AdhocMetric, oldMetric: Metric | AdhocMetric) => {
|
||||
if (oldMetric instanceof AdhocMetric && oldMetric.equals(changedMetric)) {
|
||||
return;
|
||||
}
|
||||
const newValue = value.map(value => {
|
||||
if (
|
||||
// compare saved metrics
|
||||
@@ -245,7 +248,10 @@ export const DndMetricSelect = (props: any) => {
|
||||
[props.savedMetrics, props.value],
|
||||
);
|
||||
|
||||
const handleDropLabel = useCallback(() => onChange(value), [onChange, value]);
|
||||
const handleDropLabel = useCallback(
|
||||
() => onChange(multi ? value : value[0]),
|
||||
[multi, onChange, value],
|
||||
);
|
||||
|
||||
const valueRenderer = useCallback(
|
||||
(option: Metric | AdhocMetric | string, index: number) => (
|
||||
@@ -262,12 +268,14 @@ export const DndMetricSelect = (props: any) => {
|
||||
onMoveLabel={moveLabel}
|
||||
onDropLabel={handleDropLabel}
|
||||
type={`${DndItemType.AdhocMetricOption}_${props.name}_${props.label}`}
|
||||
multi={multi}
|
||||
/>
|
||||
),
|
||||
[
|
||||
getSavedMetricOptionsForMetric,
|
||||
handleDropLabel,
|
||||
moveLabel,
|
||||
multi,
|
||||
onMetricEdit,
|
||||
onRemoveMetric,
|
||||
props.columns,
|
||||
@@ -334,8 +342,8 @@ export const DndMetricSelect = (props: any) => {
|
||||
valuesRenderer={valuesRenderer}
|
||||
accept={DND_ACCEPTED_TYPES}
|
||||
ghostButtonText={tn(
|
||||
'Drop column or metric',
|
||||
'Drop columns or metrics',
|
||||
'Drop column or metric here',
|
||||
'Drop columns or metrics here',
|
||||
multi ? 2 : 1,
|
||||
)}
|
||||
displayGhostButton={multi || value.length === 0}
|
||||
|
||||
@@ -33,7 +33,7 @@ const defaultProps = {
|
||||
|
||||
test('renders with default props', async () => {
|
||||
render(<DndSelectLabel {...defaultProps} />, { useDnd: true });
|
||||
expect(await screen.findByText('Drop columns')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Drop columns here')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders ghost button when empty', async () => {
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function DndSelectLabel<T, O>({
|
||||
return (
|
||||
<AddControlLabel cancelHover>
|
||||
<Icons.PlusSmall iconColor={theme.colors.grayscale.light1} />
|
||||
{t(props.ghostButtonText || 'Drop columns')}
|
||||
{t(props.ghostButtonText || 'Drop columns here')}
|
||||
</AddControlLabel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export class OptionSelector {
|
||||
}
|
||||
|
||||
has(value: string): boolean {
|
||||
return !!this.getValues()?.includes(value);
|
||||
return ensureIsArray(this.getValues()).includes(value);
|
||||
}
|
||||
|
||||
getValues(): string[] | string | undefined {
|
||||
|
||||
@@ -37,6 +37,7 @@ const propTypes = {
|
||||
onDropLabel: PropTypes.func,
|
||||
index: PropTypes.number,
|
||||
type: PropTypes.string,
|
||||
multi: PropTypes.bool,
|
||||
};
|
||||
|
||||
class AdhocMetricOption extends React.PureComponent {
|
||||
@@ -62,6 +63,7 @@ class AdhocMetricOption extends React.PureComponent {
|
||||
onDropLabel,
|
||||
index,
|
||||
type,
|
||||
multi,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -84,6 +86,7 @@ class AdhocMetricOption extends React.PureComponent {
|
||||
type={type ?? DndItemType.AdhocMetricOption}
|
||||
withCaret
|
||||
isFunction
|
||||
multi={multi}
|
||||
/>
|
||||
</AdhocMetricPopoverTrigger>
|
||||
);
|
||||
|
||||
@@ -49,6 +49,7 @@ export default function MetricDefinitionValue({
|
||||
onDropLabel,
|
||||
index,
|
||||
type,
|
||||
multi,
|
||||
}) {
|
||||
const getSavedMetricByName = metricName =>
|
||||
savedMetrics.find(metric => metric.metric_name === metricName);
|
||||
@@ -76,6 +77,7 @@ export default function MetricDefinitionValue({
|
||||
index,
|
||||
savedMetric: savedMetric ?? {},
|
||||
type,
|
||||
multi,
|
||||
};
|
||||
|
||||
return <AdhocMetricOption {...metricOptionProps} />;
|
||||
|
||||
@@ -16,16 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { t, withTheme } from '@superset-ui/core';
|
||||
import { isEqual } from 'lodash';
|
||||
import { ensureIsArray, t, useTheme } from '@superset-ui/core';
|
||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||
import {
|
||||
AGGREGATES_OPTIONS,
|
||||
sqlaAutoGeneratedMetricNameRegex,
|
||||
druidAutoGeneratedMetricRegex,
|
||||
} from 'src/explore/constants';
|
||||
import Icons from 'src/components/Icons';
|
||||
import {
|
||||
AddIconButton,
|
||||
@@ -34,7 +28,6 @@ import {
|
||||
LabelsContainer,
|
||||
} from 'src/explore/components/controls/OptionControls';
|
||||
import columnType from './columnType';
|
||||
import MetricDefinitionOption from './MetricDefinitionOption';
|
||||
import MetricDefinitionValue from './MetricDefinitionValue';
|
||||
import AdhocMetric from './AdhocMetric';
|
||||
import savedMetricType from './savedMetricType';
|
||||
@@ -82,9 +75,9 @@ function isDictionaryForAdhocMetric(value) {
|
||||
return value && !(value instanceof AdhocMetric) && value.expressionType;
|
||||
}
|
||||
|
||||
function columnsContainAllMetrics(value, nextProps) {
|
||||
function columnsContainAllMetrics(value, columns, savedMetrics) {
|
||||
const columnNames = new Set(
|
||||
[...(nextProps.columns || []), ...(nextProps.savedMetrics || [])]
|
||||
[...(columns || []), ...(savedMetrics || [])]
|
||||
// eslint-disable-next-line camelcase
|
||||
.map(({ column_name, metric_name }) => column_name || metric_name),
|
||||
);
|
||||
@@ -123,294 +116,228 @@ function coerceAdhocMetrics(value) {
|
||||
});
|
||||
}
|
||||
|
||||
class MetricsControl extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onMetricEdit = this.onMetricEdit.bind(this);
|
||||
this.onNewMetric = this.onNewMetric.bind(this);
|
||||
this.onRemoveMetric = this.onRemoveMetric.bind(this);
|
||||
this.moveLabel = this.moveLabel.bind(this);
|
||||
this.checkIfAggregateInInput = this.checkIfAggregateInInput.bind(this);
|
||||
this.optionsForSelect = this.optionsForSelect.bind(this);
|
||||
this.selectFilterOption = this.selectFilterOption.bind(this);
|
||||
this.isAutoGeneratedMetric = this.isAutoGeneratedMetric.bind(this);
|
||||
this.optionRenderer = option => <MetricDefinitionOption option={option} />;
|
||||
this.valueRenderer = (option, index) => (
|
||||
const emptySavedMetric = { metric_name: '', expression: '' };
|
||||
|
||||
const MetricsControl = ({
|
||||
onChange,
|
||||
multi,
|
||||
value: propsValue,
|
||||
columns,
|
||||
savedMetrics,
|
||||
datasource,
|
||||
datasourceType,
|
||||
...props
|
||||
}) => {
|
||||
const [value, setValue] = useState(coerceAdhocMetrics(propsValue));
|
||||
const theme = useTheme();
|
||||
|
||||
const handleChange = useCallback(
|
||||
opts => {
|
||||
// if clear out options
|
||||
if (opts === null) {
|
||||
onChange(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const transformedOpts = ensureIsArray(opts);
|
||||
const optionValues = transformedOpts
|
||||
.map(option => {
|
||||
// pre-defined metric
|
||||
if (option.metric_name) {
|
||||
return option.metric_name;
|
||||
}
|
||||
return option;
|
||||
})
|
||||
.filter(option => option);
|
||||
onChange(multi ? optionValues : optionValues[0]);
|
||||
},
|
||||
[multi, onChange],
|
||||
);
|
||||
|
||||
const onNewMetric = useCallback(
|
||||
newMetric => {
|
||||
const newValue = [...value, newMetric];
|
||||
setValue(newValue);
|
||||
handleChange(newValue);
|
||||
},
|
||||
[handleChange, value],
|
||||
);
|
||||
|
||||
const onMetricEdit = useCallback(
|
||||
(changedMetric, oldMetric) => {
|
||||
const newValue = value.map(val => {
|
||||
if (
|
||||
// compare saved metrics
|
||||
val === oldMetric.metric_name ||
|
||||
// compare adhoc metrics
|
||||
typeof val.optionName !== 'undefined'
|
||||
? val.optionName === oldMetric.optionName
|
||||
: false
|
||||
) {
|
||||
return changedMetric;
|
||||
}
|
||||
return val;
|
||||
});
|
||||
setValue(newValue);
|
||||
handleChange(newValue);
|
||||
},
|
||||
[handleChange, value],
|
||||
);
|
||||
|
||||
const onRemoveMetric = useCallback(
|
||||
index => {
|
||||
if (!Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
const valuesCopy = [...value];
|
||||
valuesCopy.splice(index, 1);
|
||||
setValue(valuesCopy);
|
||||
handleChange(valuesCopy);
|
||||
},
|
||||
[handleChange, value],
|
||||
);
|
||||
|
||||
const moveLabel = useCallback(
|
||||
(dragIndex, hoverIndex) => {
|
||||
const newValues = [...value];
|
||||
[newValues[hoverIndex], newValues[dragIndex]] = [
|
||||
newValues[dragIndex],
|
||||
newValues[hoverIndex],
|
||||
];
|
||||
setValue(newValues);
|
||||
},
|
||||
[value],
|
||||
);
|
||||
|
||||
const isAddNewMetricDisabled = useCallback(() => !multi && value.length > 0, [
|
||||
multi,
|
||||
value.length,
|
||||
]);
|
||||
|
||||
const savedMetricOptions = useMemo(
|
||||
() => getOptionsForSavedMetrics(savedMetrics, propsValue, null),
|
||||
[propsValue, savedMetrics],
|
||||
);
|
||||
|
||||
const newAdhocMetric = useMemo(() => new AdhocMetric({ isNew: true }), [
|
||||
value,
|
||||
]);
|
||||
const addNewMetricPopoverTrigger = useCallback(
|
||||
trigger => {
|
||||
if (isAddNewMetricDisabled()) {
|
||||
return trigger;
|
||||
}
|
||||
return (
|
||||
<AdhocMetricPopoverTrigger
|
||||
adhocMetric={newAdhocMetric}
|
||||
onMetricEdit={onNewMetric}
|
||||
columns={columns}
|
||||
savedMetricsOptions={savedMetricOptions}
|
||||
datasource={datasource}
|
||||
savedMetric={emptySavedMetric}
|
||||
datasourceType={datasourceType}
|
||||
createNew
|
||||
>
|
||||
{trigger}
|
||||
</AdhocMetricPopoverTrigger>
|
||||
);
|
||||
},
|
||||
[
|
||||
columns,
|
||||
datasource,
|
||||
datasourceType,
|
||||
isAddNewMetricDisabled,
|
||||
newAdhocMetric,
|
||||
onNewMetric,
|
||||
savedMetricOptions,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Remove all metrics if selected value no longer a valid column
|
||||
// in the dataset. Must use `nextProps` here because Redux reducers may
|
||||
// have already updated the value for this control.
|
||||
if (!columnsContainAllMetrics(propsValue, columns, savedMetrics)) {
|
||||
handleChange([]);
|
||||
}
|
||||
}, [columns, savedMetrics]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(coerceAdhocMetrics(propsValue));
|
||||
}, [propsValue]);
|
||||
|
||||
const onDropLabel = useCallback(() => handleChange(value), [
|
||||
handleChange,
|
||||
value,
|
||||
]);
|
||||
|
||||
const valueRenderer = useCallback(
|
||||
(option, index) => (
|
||||
<MetricDefinitionValue
|
||||
key={index}
|
||||
index={index}
|
||||
option={option}
|
||||
onMetricEdit={this.onMetricEdit}
|
||||
onRemoveMetric={this.onRemoveMetric}
|
||||
columns={this.props.columns}
|
||||
datasource={this.props.datasource}
|
||||
savedMetrics={this.props.savedMetrics}
|
||||
onMetricEdit={onMetricEdit}
|
||||
onRemoveMetric={onRemoveMetric}
|
||||
columns={columns}
|
||||
datasource={datasource}
|
||||
savedMetrics={savedMetrics}
|
||||
savedMetricsOptions={getOptionsForSavedMetrics(
|
||||
this.props.savedMetrics,
|
||||
this.props.value,
|
||||
this.props.value?.[index],
|
||||
savedMetrics,
|
||||
value,
|
||||
value?.[index],
|
||||
)}
|
||||
datasourceType={this.props.datasourceType}
|
||||
onMoveLabel={this.moveLabel}
|
||||
onDropLabel={() => this.props.onChange(this.state.value)}
|
||||
datasourceType={datasourceType}
|
||||
onMoveLabel={moveLabel}
|
||||
onDropLabel={onDropLabel}
|
||||
multi={multi}
|
||||
/>
|
||||
);
|
||||
this.select = null;
|
||||
this.selectRef = ref => {
|
||||
if (ref) {
|
||||
this.select = ref.select;
|
||||
} else {
|
||||
this.select = null;
|
||||
}
|
||||
};
|
||||
this.state = {
|
||||
aggregateInInput: null,
|
||||
options: this.optionsForSelect(this.props),
|
||||
value: coerceAdhocMetrics(this.props.value),
|
||||
};
|
||||
}
|
||||
),
|
||||
[
|
||||
columns,
|
||||
datasource,
|
||||
datasourceType,
|
||||
moveLabel,
|
||||
multi,
|
||||
onDropLabel,
|
||||
onMetricEdit,
|
||||
onRemoveMetric,
|
||||
savedMetrics,
|
||||
value,
|
||||
],
|
||||
);
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
const { value } = this.props;
|
||||
if (
|
||||
!isEqual(this.props.columns, nextProps.columns) ||
|
||||
!isEqual(this.props.savedMetrics, nextProps.savedMetrics)
|
||||
) {
|
||||
this.setState({ options: this.optionsForSelect(nextProps) });
|
||||
|
||||
// Remove all metrics if selected value no longer a valid column
|
||||
// in the dataset. Must use `nextProps` here because Redux reducers may
|
||||
// have already updated the value for this control.
|
||||
if (!columnsContainAllMetrics(nextProps.value, nextProps)) {
|
||||
this.props.onChange([]);
|
||||
}
|
||||
}
|
||||
if (value !== nextProps.value) {
|
||||
this.setState({ value: coerceAdhocMetrics(nextProps.value) });
|
||||
}
|
||||
}
|
||||
|
||||
onNewMetric(newMetric) {
|
||||
this.setState(
|
||||
prevState => ({
|
||||
...prevState,
|
||||
value: [...prevState.value, newMetric],
|
||||
}),
|
||||
() => {
|
||||
this.onChange(this.state.value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onMetricEdit(changedMetric, oldMetric) {
|
||||
this.setState(
|
||||
prevState => ({
|
||||
value: prevState.value.map(value => {
|
||||
if (
|
||||
// compare saved metrics
|
||||
value === oldMetric.metric_name ||
|
||||
// compare adhoc metrics
|
||||
typeof value.optionName !== 'undefined'
|
||||
? value.optionName === oldMetric.optionName
|
||||
: false
|
||||
) {
|
||||
return changedMetric;
|
||||
}
|
||||
return value;
|
||||
}),
|
||||
}),
|
||||
() => {
|
||||
this.onChange(this.state.value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onRemoveMetric(index) {
|
||||
if (!Array.isArray(this.state.value)) {
|
||||
return;
|
||||
}
|
||||
const valuesCopy = [...this.state.value];
|
||||
valuesCopy.splice(index, 1);
|
||||
this.setState(prevState => ({
|
||||
...prevState,
|
||||
value: valuesCopy,
|
||||
}));
|
||||
this.props.onChange(valuesCopy);
|
||||
}
|
||||
|
||||
onChange(opts) {
|
||||
// if clear out options
|
||||
if (opts === null) {
|
||||
this.props.onChange(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let transformedOpts;
|
||||
if (Array.isArray(opts)) {
|
||||
transformedOpts = opts;
|
||||
} else {
|
||||
transformedOpts = opts ? [opts] : [];
|
||||
}
|
||||
const optionValues = transformedOpts
|
||||
.map(option => {
|
||||
// pre-defined metric
|
||||
if (option.metric_name) {
|
||||
return option.metric_name;
|
||||
}
|
||||
return option;
|
||||
})
|
||||
.filter(option => option);
|
||||
this.props.onChange(this.props.multi ? optionValues : optionValues[0]);
|
||||
}
|
||||
|
||||
moveLabel(dragIndex, hoverIndex) {
|
||||
const { value } = this.state;
|
||||
|
||||
const newValues = [...value];
|
||||
[newValues[hoverIndex], newValues[dragIndex]] = [
|
||||
newValues[dragIndex],
|
||||
newValues[hoverIndex],
|
||||
];
|
||||
this.setState({ value: newValues });
|
||||
}
|
||||
|
||||
isAddNewMetricDisabled() {
|
||||
return !this.props.multi && this.state.value.length > 0;
|
||||
}
|
||||
|
||||
addNewMetricPopoverTrigger(trigger) {
|
||||
if (this.isAddNewMetricDisabled()) {
|
||||
return trigger;
|
||||
}
|
||||
return (
|
||||
<AdhocMetricPopoverTrigger
|
||||
adhocMetric={new AdhocMetric({ isNew: true })}
|
||||
onMetricEdit={this.onNewMetric}
|
||||
columns={this.props.columns}
|
||||
savedMetricsOptions={getOptionsForSavedMetrics(
|
||||
this.props.savedMetrics,
|
||||
this.props.value,
|
||||
null,
|
||||
return (
|
||||
<div className="metrics-select">
|
||||
<HeaderContainer>
|
||||
<ControlHeader {...props} />
|
||||
{addNewMetricPopoverTrigger(
|
||||
<AddIconButton
|
||||
disabled={isAddNewMetricDisabled()}
|
||||
data-test="add-metric-button"
|
||||
>
|
||||
<Icons.PlusLarge
|
||||
iconSize="s"
|
||||
iconColor={theme.colors.grayscale.light5}
|
||||
/>
|
||||
</AddIconButton>,
|
||||
)}
|
||||
datasource={this.props.datasource}
|
||||
savedMetric={{ metric_name: '', expression: '' }}
|
||||
datasourceType={this.props.datasourceType}
|
||||
createNew
|
||||
>
|
||||
{trigger}
|
||||
</AdhocMetricPopoverTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
checkIfAggregateInInput(input) {
|
||||
const lowercaseInput = input.toLowerCase();
|
||||
const aggregateInInput =
|
||||
AGGREGATES_OPTIONS.find(x =>
|
||||
lowercaseInput.startsWith(`${x.toLowerCase()}(`),
|
||||
) || null;
|
||||
this.clearedAggregateInInput = this.state.aggregateInInput;
|
||||
this.setState({ aggregateInInput });
|
||||
}
|
||||
|
||||
optionsForSelect(props) {
|
||||
const { columns, savedMetrics } = props;
|
||||
const aggregates =
|
||||
columns && columns.length
|
||||
? AGGREGATES_OPTIONS.map(aggregate => ({
|
||||
aggregate_name: aggregate,
|
||||
}))
|
||||
: [];
|
||||
const options = [
|
||||
...(columns || []),
|
||||
...aggregates,
|
||||
...(savedMetrics || []),
|
||||
];
|
||||
|
||||
return options.reduce((results, option) => {
|
||||
if (option.metric_name) {
|
||||
results.push({ ...option, optionName: option.metric_name });
|
||||
} else if (option.column_name) {
|
||||
results.push({ ...option, optionName: `_col_${option.column_name}` });
|
||||
} else if (option.aggregate_name) {
|
||||
results.push({
|
||||
...option,
|
||||
optionName: `_aggregate_${option.aggregate_name}`,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}, []);
|
||||
}
|
||||
|
||||
isAutoGeneratedMetric(savedMetric) {
|
||||
if (this.props.datasourceType === 'druid') {
|
||||
return druidAutoGeneratedMetricRegex.test(savedMetric.verbose_name);
|
||||
}
|
||||
return sqlaAutoGeneratedMetricNameRegex.test(savedMetric.metric_name);
|
||||
}
|
||||
|
||||
selectFilterOption({ data: option }, filterValue) {
|
||||
if (this.state.aggregateInInput) {
|
||||
let endIndex = filterValue.length;
|
||||
if (filterValue.endsWith(')')) {
|
||||
endIndex = filterValue.length - 1;
|
||||
}
|
||||
const valueAfterAggregate = filterValue.substring(
|
||||
filterValue.indexOf('(') + 1,
|
||||
endIndex,
|
||||
);
|
||||
return (
|
||||
option.column_name &&
|
||||
option.column_name.toLowerCase().indexOf(valueAfterAggregate) >= 0
|
||||
);
|
||||
}
|
||||
return (
|
||||
option.optionName &&
|
||||
(!option.metric_name ||
|
||||
!this.isAutoGeneratedMetric(option) ||
|
||||
option.verbose_name) &&
|
||||
(option.optionName.toLowerCase().indexOf(filterValue) >= 0 ||
|
||||
(option.verbose_name &&
|
||||
option.verbose_name.toLowerCase().indexOf(filterValue) >= 0))
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<div className="metrics-select">
|
||||
<HeaderContainer>
|
||||
<ControlHeader {...this.props} />
|
||||
{this.addNewMetricPopoverTrigger(
|
||||
<AddIconButton
|
||||
disabled={this.isAddNewMetricDisabled()}
|
||||
data-test="add-metric-button"
|
||||
>
|
||||
<Icons.PlusLarge
|
||||
iconSize="s"
|
||||
iconColor={theme.colors.grayscale.light5}
|
||||
/>
|
||||
</AddIconButton>,
|
||||
)}
|
||||
</HeaderContainer>
|
||||
<LabelsContainer>
|
||||
{this.state.value.length > 0
|
||||
? this.state.value.map((value, index) =>
|
||||
this.valueRenderer(value, index),
|
||||
)
|
||||
: this.addNewMetricPopoverTrigger(
|
||||
<AddControlLabel>
|
||||
<Icons.PlusSmall iconColor={theme.colors.grayscale.light1} />
|
||||
{t('Add metric')}
|
||||
</AddControlLabel>,
|
||||
)}
|
||||
</LabelsContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</HeaderContainer>
|
||||
<LabelsContainer>
|
||||
{value.length > 0
|
||||
? value.map((value, index) => valueRenderer(value, index))
|
||||
: addNewMetricPopoverTrigger(
|
||||
<AddControlLabel>
|
||||
<Icons.PlusSmall iconColor={theme.colors.grayscale.light1} />
|
||||
{t('Add metric')}
|
||||
</AddControlLabel>,
|
||||
)}
|
||||
</LabelsContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
MetricsControl.propTypes = propTypes;
|
||||
MetricsControl.defaultProps = defaultProps;
|
||||
|
||||
export default withTheme(MetricsControl);
|
||||
export default MetricsControl;
|
||||
|
||||
@@ -177,6 +177,7 @@ export const OptionControlLabel = ({
|
||||
index,
|
||||
isExtra,
|
||||
tooltipTitle,
|
||||
multi = true,
|
||||
...props
|
||||
}: {
|
||||
label: string | React.ReactNode;
|
||||
@@ -192,15 +193,22 @@ export const OptionControlLabel = ({
|
||||
index: number;
|
||||
isExtra?: boolean;
|
||||
tooltipTitle: string;
|
||||
multi?: boolean;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [, drop] = useDrop({
|
||||
accept: type,
|
||||
drop() {
|
||||
if (!multi) {
|
||||
return;
|
||||
}
|
||||
onDropLabel?.();
|
||||
},
|
||||
hover(item: DragItem, monitor: DropTargetMonitor) {
|
||||
if (!multi) {
|
||||
return;
|
||||
}
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ describe('VizTypeControl', () => {
|
||||
|
||||
expect(visualizations).toHaveTextContent(/Time-series Table/);
|
||||
expect(visualizations).toHaveTextContent(/Time-series Chart/);
|
||||
expect(visualizations).toHaveTextContent(/Mixed timeseries chart/);
|
||||
expect(visualizations).toHaveTextContent(/Mixed Time-Series/);
|
||||
expect(visualizations).not.toHaveTextContent(/Line Chart/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,17 +62,22 @@ enum SECTIONS {
|
||||
const DEFAULT_ORDER = [
|
||||
'line',
|
||||
'big_number',
|
||||
'big_number_total',
|
||||
'table',
|
||||
'pivot_table_v2',
|
||||
'echarts_timeseries_line',
|
||||
'echarts_area',
|
||||
'echarts_timeseries_bar',
|
||||
'echarts_timeseries_scatter',
|
||||
'pie',
|
||||
'mixed_timeseries',
|
||||
'filter_box',
|
||||
'dist_bar',
|
||||
'area',
|
||||
'bar',
|
||||
'deck_polygon',
|
||||
'pie',
|
||||
'time_table',
|
||||
'pivot_table_v2',
|
||||
'histogram',
|
||||
'big_number_total',
|
||||
'deck_scatter',
|
||||
'deck_hex',
|
||||
'time_pivot',
|
||||
@@ -116,11 +121,7 @@ const OTHER_CATEGORY = t('Other');
|
||||
|
||||
const ALL_CHARTS = t('All charts');
|
||||
|
||||
const RECOMMENDED_TAGS = [
|
||||
t('Highly-used'),
|
||||
t('ECharts'),
|
||||
t('Advanced-Analytics'),
|
||||
];
|
||||
const RECOMMENDED_TAGS = [t('Popular'), t('ECharts'), t('Advanced-Analytics')];
|
||||
|
||||
export const VIZ_TYPE_CONTROL_TEST_ID = 'viz-type-control';
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export default class FilterGroupByPlugin extends ChartPlugin {
|
||||
name: t('Group By'),
|
||||
description: t('Group By filter plugin'),
|
||||
behaviors: [Behavior.INTERACTIVE_CHART, Behavior.NATIVE_FILTER],
|
||||
tags: [t('Experimental')],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export default class RangeFilterPlugin extends ChartPlugin {
|
||||
name: t('Range filter'),
|
||||
description: t('Range filter plugin using AntD'),
|
||||
behaviors: [Behavior.INTERACTIVE_CHART, Behavior.NATIVE_FILTER],
|
||||
tags: [t('Experimental')],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import {
|
||||
AppSection,
|
||||
DataMask,
|
||||
DataRecordValue,
|
||||
ensureIsArray,
|
||||
ExtraFormData,
|
||||
GenericDataType,
|
||||
@@ -36,7 +37,11 @@ import { useImmerReducer } from 'use-immer';
|
||||
import { FormItemProps } from 'antd/lib/form';
|
||||
import { PluginFilterSelectProps, SelectValue } from './types';
|
||||
import { StyledFormItem, FilterPluginStyle, StatusMessage } from '../common';
|
||||
import { getDataRecordFormatter, getSelectExtraFormData } from '../../utils';
|
||||
import {
|
||||
formatFilterValue,
|
||||
getDataRecordFormatter,
|
||||
getSelectExtraFormData,
|
||||
} from '../../utils';
|
||||
|
||||
type DataMaskAction =
|
||||
| { type: 'ownState'; ownState: JsonObject }
|
||||
@@ -119,7 +124,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
|
||||
filterState: {
|
||||
...filterState,
|
||||
label: values?.length
|
||||
? `${(values || []).join(', ')}${suffix}`
|
||||
? `${(values || []).map(formatFilterValue).join(', ')}${suffix}`
|
||||
: undefined,
|
||||
value:
|
||||
appSection === AppSection.FILTER_CONFIG_MODAL && defaultToFirstItem
|
||||
@@ -249,12 +254,12 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
|
||||
}
|
||||
|
||||
const options = useMemo(() => {
|
||||
const options: { label: string; value: string | number }[] = [];
|
||||
const options: { label: string; value: DataRecordValue }[] = [];
|
||||
data.forEach(row => {
|
||||
const [value] = groupby.map(col => row[col]);
|
||||
options.push({
|
||||
label: labelFormatter(value, datatype),
|
||||
value: typeof value === 'number' ? value : String(value),
|
||||
value,
|
||||
});
|
||||
});
|
||||
return options;
|
||||
@@ -286,6 +291,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
|
||||
loading={isRefreshing}
|
||||
maxTagCount={5}
|
||||
invertSelection={inverseSelection}
|
||||
// @ts-ignore
|
||||
options={options}
|
||||
/>
|
||||
</StyledFormItem>
|
||||
|
||||
@@ -29,6 +29,7 @@ export default class FilterSelectPlugin extends ChartPlugin {
|
||||
description: t('Select filter plugin using AntD'),
|
||||
behaviors: [Behavior.INTERACTIVE_CHART, Behavior.NATIVE_FILTER],
|
||||
enableNoResults: false,
|
||||
tags: [t('Experimental')],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export default class TimeFilterPlugin extends ChartPlugin {
|
||||
description: t('Custom time filter plugin'),
|
||||
behaviors: [Behavior.INTERACTIVE_CHART, Behavior.NATIVE_FILTER],
|
||||
thumbnail,
|
||||
tags: [t('Experimental')],
|
||||
datasourceCount: 0,
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export default class FilterTimeColumnPlugin extends ChartPlugin {
|
||||
name: t('Time column'),
|
||||
description: t('Time column filter plugin'),
|
||||
behaviors: [Behavior.INTERACTIVE_CHART, Behavior.NATIVE_FILTER],
|
||||
tags: [t('Experimental')],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export default class FilterTimeGrainPlugin extends ChartPlugin {
|
||||
name: t('Time grain'),
|
||||
description: t('Time grain filter plugin'),
|
||||
behaviors: [Behavior.INTERACTIVE_CHART, Behavior.NATIVE_FILTER],
|
||||
tags: [t('Experimental')],
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import { FALSE_STRING, NULL_STRING, TRUE_STRING } from 'src/utils/common';
|
||||
|
||||
export const getSelectExtraFormData = (
|
||||
col: string,
|
||||
value?: null | (string | number)[],
|
||||
value?: null | (string | number | boolean | null)[],
|
||||
emptyFilter = false,
|
||||
inverseSelection = false,
|
||||
): ExtraFormData => {
|
||||
@@ -46,6 +46,7 @@ export const getSelectExtraFormData = (
|
||||
{
|
||||
col,
|
||||
op: inverseSelection ? ('NOT IN' as const) : ('IN' as const),
|
||||
// @ts-ignore
|
||||
val: value,
|
||||
},
|
||||
];
|
||||
@@ -116,3 +117,18 @@ export function getDataRecordFormatter({
|
||||
return String(value);
|
||||
};
|
||||
}
|
||||
|
||||
export function formatFilterValue(
|
||||
value: string | number | boolean | null,
|
||||
): string {
|
||||
if (value === null) {
|
||||
return NULL_STRING;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return String(value);
|
||||
}
|
||||
return value ? TRUE_STRING : FALSE_STRING;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
/* eslint camelcase: 0 */
|
||||
import { t, SupersetClient } from '@superset-ui/core';
|
||||
import rison from 'rison';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { addDangerToast, addSuccessToast } from '../../messageToasts/actions';
|
||||
|
||||
export const SET_REPORT = 'SET_REPORT';
|
||||
@@ -102,15 +103,21 @@ export const addReport = report => dispatch => {
|
||||
endpoint: `/api/v1/report/`,
|
||||
jsonPayload: report,
|
||||
})
|
||||
.then(() => {
|
||||
dispatch({ type: ADD_REPORT, report });
|
||||
.then(({ json }) => {
|
||||
dispatch({ type: ADD_REPORT, json });
|
||||
dispatch(addSuccessToast(t('The report has been created')));
|
||||
})
|
||||
.catch(() =>
|
||||
.catch(async e => {
|
||||
const parsedError = await getClientErrorObject(e);
|
||||
const errorMessage = parsedError.message;
|
||||
const errorArr = Object.keys(errorMessage);
|
||||
const error = errorMessage[errorArr[0]][0];
|
||||
dispatch(
|
||||
addDangerToast(t('An error occurred while creating this report.')),
|
||||
),
|
||||
);
|
||||
addDangerToast(
|
||||
t('An error occurred while editing this report: %s', error),
|
||||
),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const EDIT_REPORT = 'EDIT_REPORT';
|
||||
|
||||
@@ -87,10 +87,21 @@ export function optionFromValue(opt) {
|
||||
return { value: optionValue(opt), label: optionLabel(opt) };
|
||||
}
|
||||
|
||||
export function prepareCopyToClipboardTabularData(data) {
|
||||
export function prepareCopyToClipboardTabularData(data, columns) {
|
||||
let result = '';
|
||||
for (let i = 0; i < data.length; i += 1) {
|
||||
result += `${Object.values(data[i]).join('\t')}\n`;
|
||||
const row = {};
|
||||
for (let j = 0; j < columns.length; j += 1) {
|
||||
// JavaScript does not mantain the order of a mixed set of keys (i.e integers and strings)
|
||||
// the below function orders the keys based on the column names.
|
||||
const key = columns[j].name || columns[j];
|
||||
if (data[i][key]) {
|
||||
row[j] = data[i][key];
|
||||
} else {
|
||||
row[j] = data[i][parseFloat(key)];
|
||||
}
|
||||
}
|
||||
result += `${Object.values(row).join('\t')}\n`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -46,15 +46,18 @@ describe('utils/common', () => {
|
||||
describe('prepareCopyToClipboardTabularData', () => {
|
||||
it('converts empty array', () => {
|
||||
const array = [];
|
||||
expect(prepareCopyToClipboardTabularData(array)).toEqual('');
|
||||
const column = [];
|
||||
expect(prepareCopyToClipboardTabularData(array, column)).toEqual('');
|
||||
});
|
||||
it('converts non empty array', () => {
|
||||
const array = [
|
||||
{ column1: 'lorem', column2: 'ipsum' },
|
||||
{ column1: 'dolor', column2: 'sit', column3: 'amet' },
|
||||
];
|
||||
expect(prepareCopyToClipboardTabularData(array)).toEqual(
|
||||
'lorem\tipsum\ndolor\tsit\tamet\n',
|
||||
const column = ['column1', 'column2', 'column3'];
|
||||
console.log(prepareCopyToClipboardTabularData(array, column));
|
||||
expect(prepareCopyToClipboardTabularData(array, column)).toEqual(
|
||||
'lorem\tipsum\t\ndolor\tsit\tamet\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,12 +25,12 @@ import {
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import rison from 'rison';
|
||||
import { uniqBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import {
|
||||
createErrorHandler,
|
||||
createFetchRelated,
|
||||
handleChartDelete,
|
||||
CardStylesOverrides,
|
||||
} from 'src/views/CRUD/utils';
|
||||
import {
|
||||
useChartEditModal,
|
||||
@@ -160,6 +160,9 @@ function ChartList(props: ChartListProps) {
|
||||
const [passwordFields, setPasswordFields] = useState<string[]>([]);
|
||||
const [preparingExport, setPreparingExport] = useState<boolean>(false);
|
||||
|
||||
const { userId } = props.user;
|
||||
const userKey = getFromLocalStorage(userId.toString(), null);
|
||||
|
||||
const openChartImportModal = () => {
|
||||
showImportModal(true);
|
||||
};
|
||||
@@ -270,23 +273,33 @@ function ChartList(props: ChartListProps) {
|
||||
Cell: ({
|
||||
row: {
|
||||
original: {
|
||||
changed_by_name: changedByName,
|
||||
last_saved_by: lastSavedBy,
|
||||
changed_by_url: changedByUrl,
|
||||
},
|
||||
},
|
||||
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
||||
}: any) => (
|
||||
<a href={changedByUrl}>
|
||||
{lastSavedBy?.first_name
|
||||
? `${lastSavedBy?.first_name} ${lastSavedBy?.last_name}`
|
||||
: null}
|
||||
</a>
|
||||
),
|
||||
Header: t('Modified by'),
|
||||
accessor: 'changed_by.first_name',
|
||||
accessor: 'last_saved_by.first_name',
|
||||
size: 'xl',
|
||||
},
|
||||
{
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { changed_on_delta_humanized: changedOn },
|
||||
original: { last_saved_at: lastSavedAt },
|
||||
},
|
||||
}: any) => <span className="no-wrap">{changedOn}</span>,
|
||||
}: any) => (
|
||||
<span className="no-wrap">
|
||||
{lastSavedAt ? moment.utc(lastSavedAt).fromNow() : null}
|
||||
</span>
|
||||
),
|
||||
Header: t('Last modified'),
|
||||
accessor: 'changed_on_delta_humanized',
|
||||
accessor: 'last_saved_at',
|
||||
size: 'xl',
|
||||
},
|
||||
{
|
||||
@@ -532,29 +545,25 @@ function ChartList(props: ChartListProps) {
|
||||
];
|
||||
|
||||
function renderCard(chart: Chart) {
|
||||
const { userId } = props.user;
|
||||
const userKey = getFromLocalStorage(userId.toString(), null);
|
||||
return (
|
||||
<CardStylesOverrides>
|
||||
<ChartCard
|
||||
chart={chart}
|
||||
showThumbnails={
|
||||
userKey
|
||||
? userKey.thumbnails
|
||||
: isFeatureEnabled(FeatureFlag.THUMBNAILS)
|
||||
}
|
||||
hasPerm={hasPerm}
|
||||
openChartEditModal={openChartEditModal}
|
||||
bulkSelectEnabled={bulkSelectEnabled}
|
||||
addDangerToast={addDangerToast}
|
||||
addSuccessToast={addSuccessToast}
|
||||
refreshData={refreshData}
|
||||
loading={loading}
|
||||
favoriteStatus={favoriteStatus[chart.id]}
|
||||
saveFavoriteStatus={saveFavoriteStatus}
|
||||
handleBulkChartExport={handleBulkChartExport}
|
||||
/>
|
||||
</CardStylesOverrides>
|
||||
<ChartCard
|
||||
chart={chart}
|
||||
showThumbnails={
|
||||
userKey
|
||||
? userKey.thumbnails
|
||||
: isFeatureEnabled(FeatureFlag.THUMBNAILS)
|
||||
}
|
||||
hasPerm={hasPerm}
|
||||
openChartEditModal={openChartEditModal}
|
||||
bulkSelectEnabled={bulkSelectEnabled}
|
||||
addDangerToast={addDangerToast}
|
||||
addSuccessToast={addSuccessToast}
|
||||
refreshData={refreshData}
|
||||
loading={loading}
|
||||
favoriteStatus={favoriteStatus[chart.id]}
|
||||
saveFavoriteStatus={saveFavoriteStatus}
|
||||
handleBulkChartExport={handleBulkChartExport}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const subMenuButtons: SubMenuProps['buttons'] = [];
|
||||
@@ -644,6 +653,11 @@ function ChartList(props: ChartListProps) {
|
||||
loading={loading}
|
||||
pageSize={PAGE_SIZE}
|
||||
renderCard={renderCard}
|
||||
showThumbnails={
|
||||
userKey
|
||||
? userKey.thumbnails
|
||||
: isFeatureEnabled(FeatureFlag.THUMBNAILS)
|
||||
}
|
||||
defaultViewMode={
|
||||
isFeatureEnabled(FeatureFlag.LISTVIEWS_DEFAULT_CARD_VIEW)
|
||||
? 'card'
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
createFetchRelated,
|
||||
createErrorHandler,
|
||||
handleDashboardDelete,
|
||||
CardStylesOverrides,
|
||||
} from 'src/views/CRUD/utils';
|
||||
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
@@ -140,6 +139,9 @@ function DashboardList(props: DashboardListProps) {
|
||||
refreshData();
|
||||
};
|
||||
|
||||
const { userId } = props.user;
|
||||
const userKey = getFromLocalStorage(userId.toString(), null);
|
||||
|
||||
const canCreate = hasPerm('can_write');
|
||||
const canEdit = hasPerm('can_write');
|
||||
const canDelete = hasPerm('can_write');
|
||||
@@ -499,29 +501,25 @@ function DashboardList(props: DashboardListProps) {
|
||||
];
|
||||
|
||||
function renderCard(dashboard: Dashboard) {
|
||||
const { userId } = props.user;
|
||||
const userKey = getFromLocalStorage(userId.toString(), null);
|
||||
return (
|
||||
<CardStylesOverrides>
|
||||
<DashboardCard
|
||||
dashboard={dashboard}
|
||||
hasPerm={hasPerm}
|
||||
bulkSelectEnabled={bulkSelectEnabled}
|
||||
refreshData={refreshData}
|
||||
showThumbnails={
|
||||
userKey
|
||||
? userKey.thumbnails
|
||||
: isFeatureEnabled(FeatureFlag.THUMBNAILS)
|
||||
}
|
||||
loading={loading}
|
||||
addDangerToast={addDangerToast}
|
||||
addSuccessToast={addSuccessToast}
|
||||
openDashboardEditModal={openDashboardEditModal}
|
||||
saveFavoriteStatus={saveFavoriteStatus}
|
||||
favoriteStatus={favoriteStatus[dashboard.id]}
|
||||
handleBulkDashboardExport={handleBulkDashboardExport}
|
||||
/>
|
||||
</CardStylesOverrides>
|
||||
<DashboardCard
|
||||
dashboard={dashboard}
|
||||
hasPerm={hasPerm}
|
||||
bulkSelectEnabled={bulkSelectEnabled}
|
||||
refreshData={refreshData}
|
||||
showThumbnails={
|
||||
userKey
|
||||
? userKey.thumbnails
|
||||
: isFeatureEnabled(FeatureFlag.THUMBNAILS)
|
||||
}
|
||||
loading={loading}
|
||||
addDangerToast={addDangerToast}
|
||||
addSuccessToast={addSuccessToast}
|
||||
openDashboardEditModal={openDashboardEditModal}
|
||||
saveFavoriteStatus={saveFavoriteStatus}
|
||||
favoriteStatus={favoriteStatus[dashboard.id]}
|
||||
handleBulkDashboardExport={handleBulkDashboardExport}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -614,6 +612,11 @@ function DashboardList(props: DashboardListProps) {
|
||||
initialSort={initialSort}
|
||||
loading={loading}
|
||||
pageSize={PAGE_SIZE}
|
||||
showThumbnails={
|
||||
userKey
|
||||
? userKey.thumbnails
|
||||
: isFeatureEnabled(FeatureFlag.THUMBNAILS)
|
||||
}
|
||||
renderCard={renderCard}
|
||||
defaultViewMode={
|
||||
isFeatureEnabled(FeatureFlag.LISTVIEWS_DEFAULT_CARD_VIEW)
|
||||
|
||||
@@ -61,6 +61,8 @@ interface FieldPropTypes {
|
||||
onParametersUploadFileChange: (value: any) => string;
|
||||
changeMethods: { onParametersChange: (value: any) => string } & {
|
||||
onChange: (value: any) => string;
|
||||
} & {
|
||||
onQueryChange: (value: any) => string;
|
||||
} & { onParametersUploadFileChange: (value: any) => string } & {
|
||||
onAddTableCatalog: () => void;
|
||||
onRemoveTableCatalog: (idx: number) => void;
|
||||
@@ -415,15 +417,15 @@ const queryField = ({
|
||||
db,
|
||||
}: FieldPropTypes) => (
|
||||
<ValidatedInput
|
||||
id="query"
|
||||
name="query"
|
||||
id="query_input"
|
||||
name="query_input"
|
||||
required={required}
|
||||
value={db?.parameters?.query}
|
||||
value={db?.query_input || ''}
|
||||
validationMethods={{ onBlur: getValidation }}
|
||||
errorMessage={validationErrors?.query}
|
||||
placeholder="e.g. param1=value1¶m2=value2"
|
||||
label="Additional Parameters"
|
||||
onChange={changeMethods.onParametersChange}
|
||||
onChange={changeMethods.onQueryChange}
|
||||
helpText={t('Add additional custom parameters')}
|
||||
/>
|
||||
);
|
||||
@@ -475,6 +477,7 @@ const DatabaseConnectionForm = ({
|
||||
dbModel: { parameters },
|
||||
onParametersChange,
|
||||
onChange,
|
||||
onQueryChange,
|
||||
onParametersUploadFileChange,
|
||||
onAddTableCatalog,
|
||||
onRemoveTableCatalog,
|
||||
@@ -496,6 +499,9 @@ const DatabaseConnectionForm = ({
|
||||
onChange: (
|
||||
event: FormEvent<InputProps> | { target: HTMLInputElement },
|
||||
) => void;
|
||||
onQueryChange: (
|
||||
event: FormEvent<InputProps> | { target: HTMLInputElement },
|
||||
) => void;
|
||||
onParametersUploadFileChange?: (
|
||||
event: FormEvent<InputProps> | { target: HTMLInputElement },
|
||||
) => void;
|
||||
@@ -523,6 +529,7 @@ const DatabaseConnectionForm = ({
|
||||
changeMethods: {
|
||||
onParametersChange,
|
||||
onChange,
|
||||
onQueryChange,
|
||||
onParametersUploadFileChange,
|
||||
onAddTableCatalog,
|
||||
onRemoveTableCatalog,
|
||||
|
||||
@@ -169,9 +169,9 @@ const ExtraOptions = ({
|
||||
<StyledInputContainer css={no_margin_bottom}>
|
||||
<div className="input-container">
|
||||
<IndeterminateCheckbox
|
||||
id="cost_query_enabled"
|
||||
id="cost_estimate_enabled"
|
||||
indeterminate={false}
|
||||
checked={!!db?.extra_json?.cost_query_enabled}
|
||||
checked={!!db?.extra_json?.cost_estimate_enabled}
|
||||
onChange={onExtraInputChange}
|
||||
labelText={t('Enable query cost estimation')}
|
||||
/>
|
||||
@@ -392,11 +392,13 @@ const ExtraOptions = ({
|
||||
indeterminate={false}
|
||||
checked={!!db?.impersonate_user}
|
||||
onChange={onInputChange}
|
||||
labelText={t('Impersonate logged in user (Presto & Hive)')}
|
||||
labelText={t(
|
||||
'Impersonate logged in user (Presto, Trino, Hive, and GSheets)',
|
||||
)}
|
||||
/>
|
||||
<InfoTooltip
|
||||
tooltip={t(
|
||||
'If Presto, all the queries in SQL Lab are going to be executed as the ' +
|
||||
'If Presto or Trino, all the queries in SQL Lab are going to be executed as the ' +
|
||||
'currently logged on user who must have permission to run them. If Hive ' +
|
||||
'and hive.server2.enable.doAs is enabled, will run the queries as ' +
|
||||
'service account, but impersonate the currently logged on user via ' +
|
||||
@@ -490,8 +492,8 @@ const ExtraOptions = ({
|
||||
</div>
|
||||
<div className="helper">
|
||||
{t(
|
||||
'Specify this database’s version. This should be used with ' +
|
||||
'Presto databases so that the syntax is correct.',
|
||||
'Specify the database version. This should be used with ' +
|
||||
'Presto in order to enable query cost estimation.',
|
||||
)}
|
||||
</div>
|
||||
</StyledInputContainer>
|
||||
|
||||
@@ -76,6 +76,18 @@ import {
|
||||
} from './styles';
|
||||
import ModalHeader, { DOCUMENTATION_LINK } from './ModalHeader';
|
||||
|
||||
const engineSpecificAlertMapping = {
|
||||
gsheets: {
|
||||
message: 'Why do I need to create a database?',
|
||||
description:
|
||||
'To begin using your Google Sheets, you need to create a database first. ' +
|
||||
'Databases are used as a way to identify ' +
|
||||
'your data so that it can be queried and visualized. This ' +
|
||||
'database will hold all of your individual Google Sheets ' +
|
||||
'you choose to connect here.',
|
||||
},
|
||||
};
|
||||
|
||||
const errorAlertMapping = {
|
||||
CONNECTION_MISSING_PARAMETERS_ERROR: {
|
||||
message: 'Missing Required Fields',
|
||||
@@ -134,6 +146,7 @@ enum ActionType {
|
||||
extraEditorChange,
|
||||
addTableCatalogSheet,
|
||||
removeTableCatalogSheet,
|
||||
queryChange,
|
||||
}
|
||||
|
||||
interface DBReducerPayloadType {
|
||||
@@ -151,6 +164,7 @@ type DBReducerActionType =
|
||||
| ActionType.extraEditorChange
|
||||
| ActionType.extraInputChange
|
||||
| ActionType.textChange
|
||||
| ActionType.queryChange
|
||||
| ActionType.inputChange
|
||||
| ActionType.editorChange
|
||||
| ActionType.parametersChange;
|
||||
@@ -193,7 +207,8 @@ function dbReducer(
|
||||
const trimmedState = {
|
||||
...(state || {}),
|
||||
};
|
||||
let query = '';
|
||||
let query = {};
|
||||
let query_input = '';
|
||||
let deserializeExtraJSON = {};
|
||||
let extra_json: DatabaseObject['extra_json'];
|
||||
|
||||
@@ -306,6 +321,15 @@ function dbReducer(
|
||||
...trimmedState,
|
||||
[action.payload.name]: action.payload.json,
|
||||
};
|
||||
case ActionType.queryChange:
|
||||
return {
|
||||
...trimmedState,
|
||||
parameters: {
|
||||
...trimmedState.parameters,
|
||||
query: Object.fromEntries(new URLSearchParams(action.payload.value)),
|
||||
},
|
||||
query_input: action.payload.value,
|
||||
};
|
||||
case ActionType.textChange:
|
||||
return {
|
||||
...trimmedState,
|
||||
@@ -327,6 +351,12 @@ function dbReducer(
|
||||
};
|
||||
}
|
||||
|
||||
// convert query to a string and store in query_input
|
||||
query = action.payload?.parameters?.query || {};
|
||||
query_input = Object.entries(query)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('&');
|
||||
|
||||
if (
|
||||
action.payload.backend === 'bigquery' &&
|
||||
action.payload.configuration_method ===
|
||||
@@ -338,11 +368,12 @@ function dbReducer(
|
||||
configuration_method: action.payload.configuration_method,
|
||||
extra_json: deserializeExtraJSON,
|
||||
parameters: {
|
||||
query,
|
||||
credentials_info: JSON.stringify(
|
||||
action.payload?.parameters?.credentials_info || '',
|
||||
),
|
||||
query,
|
||||
},
|
||||
query_input,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -364,37 +395,18 @@ function dbReducer(
|
||||
name: e,
|
||||
value: engineParamsCatalog[e],
|
||||
})),
|
||||
query_input,
|
||||
} as DatabaseObject;
|
||||
}
|
||||
|
||||
if (action.payload?.parameters?.query) {
|
||||
// convert query into URI params string
|
||||
query = new URLSearchParams(
|
||||
action.payload.parameters.query as string,
|
||||
).toString();
|
||||
|
||||
return {
|
||||
...action.payload,
|
||||
encrypted_extra: action.payload.encrypted_extra || '',
|
||||
engine: action.payload.backend || trimmedState.engine,
|
||||
configuration_method: action.payload.configuration_method,
|
||||
extra_json: deserializeExtraJSON,
|
||||
parameters: {
|
||||
...action.payload.parameters,
|
||||
query,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...action.payload,
|
||||
encrypted_extra: action.payload.encrypted_extra || '',
|
||||
engine: action.payload.backend || trimmedState.engine,
|
||||
configuration_method: action.payload.configuration_method,
|
||||
extra_json: deserializeExtraJSON,
|
||||
parameters: {
|
||||
...action.payload.parameters,
|
||||
},
|
||||
parameters: action.payload.parameters,
|
||||
query_input,
|
||||
};
|
||||
|
||||
case ActionType.dbSelected:
|
||||
@@ -454,10 +466,11 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
const sslForced = isFeatureEnabled(
|
||||
FeatureFlag.FORCE_DATABASE_CONNECTIONS_SSL,
|
||||
);
|
||||
const hasAlert =
|
||||
connectionAlert || !!(db?.engine && engineSpecificAlertMapping[db.engine]);
|
||||
const useSqlAlchemyForm =
|
||||
db?.configuration_method === CONFIGURATION_METHOD.SQLALCHEMY_URI;
|
||||
const useTabLayout = isEditMode || useSqlAlchemyForm;
|
||||
|
||||
// Database fetch logic
|
||||
const {
|
||||
state: { loading: dbLoading, resource: dbFetched, error: dbErrors },
|
||||
@@ -471,9 +484,9 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
addDangerToast,
|
||||
);
|
||||
const isDynamic = (engine: string | undefined) =>
|
||||
availableDbs?.databases.filter(
|
||||
availableDbs?.databases?.find(
|
||||
(DB: DatabaseObject) => DB.backend === engine || DB.engine === engine,
|
||||
)[0].parameters !== undefined;
|
||||
)?.parameters !== undefined;
|
||||
const showDBError = validationErrors || dbErrors;
|
||||
const isEmpty = (data?: Object | null) =>
|
||||
data && Object.keys(data).length === 0;
|
||||
@@ -527,21 +540,6 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (dbToUpdate?.parameters?.query) {
|
||||
// convert query params into dictionary
|
||||
dbToUpdate.parameters.query = JSON.parse(
|
||||
`{"${decodeURI((dbToUpdate?.parameters?.query as string) || '')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/&/g, '","')
|
||||
.replace(/=/g, '":"')}"}`,
|
||||
);
|
||||
} else if (
|
||||
dbToUpdate?.parameters?.query === '' &&
|
||||
'query' in dbModel.parameters.properties
|
||||
) {
|
||||
dbToUpdate.parameters.query = {};
|
||||
}
|
||||
|
||||
const engine = dbToUpdate.backend || dbToUpdate.engine;
|
||||
if (engine === 'bigquery' && dbToUpdate.parameters?.credentials_info) {
|
||||
// wrap encrypted_extra in credentials_info only for BigQuery
|
||||
@@ -834,6 +832,37 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
setTabKey(key);
|
||||
};
|
||||
|
||||
const renderStepTwoAlert = () => {
|
||||
const { hostname } = window.location;
|
||||
let ipAlert = connectionAlert?.REGIONAL_IPS?.default || '';
|
||||
const regionalIPs = connectionAlert?.REGIONAL_IPS || {};
|
||||
Object.entries(regionalIPs).forEach(([regex, ipRange]) => {
|
||||
if (regex.match(hostname)) {
|
||||
ipAlert = ipRange;
|
||||
}
|
||||
});
|
||||
return (
|
||||
db?.engine && (
|
||||
<StyledAlertMargin>
|
||||
<Alert
|
||||
closable={false}
|
||||
css={(theme: SupersetTheme) => antDAlertStyles(theme)}
|
||||
type="info"
|
||||
showIcon
|
||||
message={
|
||||
engineSpecificAlertMapping[db.engine]?.message ||
|
||||
connectionAlert?.DEFAULT?.message
|
||||
}
|
||||
description={
|
||||
engineSpecificAlertMapping[db.engine]?.description ||
|
||||
connectionAlert?.DEFAULT?.description + ipAlert
|
||||
}
|
||||
/>
|
||||
</StyledAlertMargin>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const errorAlert = () => {
|
||||
if (
|
||||
isEmpty(dbErrors) ||
|
||||
@@ -929,6 +958,12 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
value: target.value,
|
||||
})
|
||||
}
|
||||
onQueryChange={({ target }: { target: HTMLInputElement }) =>
|
||||
onChange(ActionType.queryChange, {
|
||||
name: target.name,
|
||||
value: target.value,
|
||||
})
|
||||
}
|
||||
onAddTableCatalog={() =>
|
||||
setDB({ type: ActionType.addTableCatalogSheet })
|
||||
}
|
||||
@@ -1050,6 +1085,12 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
value: target.value,
|
||||
})
|
||||
}
|
||||
onQueryChange={({ target }: { target: HTMLInputElement }) =>
|
||||
onChange(ActionType.queryChange, {
|
||||
name: target.name,
|
||||
value: target.value,
|
||||
})
|
||||
}
|
||||
onAddTableCatalog={() =>
|
||||
setDB({ type: ActionType.addTableCatalogSheet })
|
||||
}
|
||||
@@ -1188,18 +1229,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
dbName={dbName}
|
||||
dbModel={dbModel}
|
||||
/>
|
||||
{connectionAlert && (
|
||||
<StyledAlertMargin>
|
||||
<Alert
|
||||
closable={false}
|
||||
css={(theme: SupersetTheme) => antDAlertStyles(theme)}
|
||||
type="info"
|
||||
showIcon
|
||||
message={t('IP Allowlist')}
|
||||
description={connectionAlert.ALLOWED_IPS}
|
||||
/>
|
||||
</StyledAlertMargin>
|
||||
)}
|
||||
{hasAlert && renderStepTwoAlert()}
|
||||
<DatabaseConnectionForm
|
||||
db={db}
|
||||
sslForced={sslForced}
|
||||
@@ -1207,6 +1237,12 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
onAddTableCatalog={() => {
|
||||
setDB({ type: ActionType.addTableCatalogSheet });
|
||||
}}
|
||||
onQueryChange={({ target }: { target: HTMLInputElement }) =>
|
||||
onChange(ActionType.queryChange, {
|
||||
name: target.name,
|
||||
value: target.value,
|
||||
})
|
||||
}
|
||||
onRemoveTableCatalog={(idx: number) => {
|
||||
setDB({
|
||||
type: ActionType.removeTableCatalogSheet,
|
||||
|
||||
@@ -45,15 +45,12 @@ export type DatabaseObject = {
|
||||
password?: string;
|
||||
encryption?: boolean;
|
||||
credentials_info?: string;
|
||||
query?: string | object;
|
||||
catalog?: {};
|
||||
query?: Record<string, string>;
|
||||
catalog?: Record<string, string>;
|
||||
};
|
||||
configuration_method: CONFIGURATION_METHOD;
|
||||
engine?: string;
|
||||
|
||||
// Gsheets temporary storage
|
||||
catalog?: Array<CatalogObject>;
|
||||
|
||||
// Performance
|
||||
cache_timeout?: string;
|
||||
allow_run_async?: boolean;
|
||||
@@ -85,11 +82,14 @@ export type DatabaseObject = {
|
||||
allows_virtual_table_explore?: boolean; // in SQL Lab
|
||||
schemas_allowed_for_csv_upload?: string[]; // in Security
|
||||
cancel_query_on_windows_unload?: boolean; // in Performance
|
||||
version?: string;
|
||||
|
||||
// todo: ask beto where this should live
|
||||
cost_query_enabled?: boolean; // in SQL Lab
|
||||
version?: string;
|
||||
cost_estimate_enabled?: boolean; // in SQL Lab
|
||||
};
|
||||
|
||||
// Temporary storage
|
||||
catalog?: Array<CatalogObject>;
|
||||
query_input?: string;
|
||||
extra?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// storage keys for welcome page sticky tabs..
|
||||
// storage keys for welcome page sticky tabs and tables
|
||||
export const HOMEPAGE_CHART_FILTER = 'homepage_chart_filter';
|
||||
export const HOMEPAGE_ACTIVITY_FILTER = 'homepage_activity_filter';
|
||||
export const HOMEPAGE_DASHBOARD_FILTER = 'homepage_dashboard_filter';
|
||||
export const HOMEPAGE_COLLAPSE_STATE = 'homepage_collapse_state';
|
||||
|
||||
@@ -32,7 +32,7 @@ export enum TableTabTypes {
|
||||
export type Filters = {
|
||||
col: string;
|
||||
opr: string;
|
||||
value: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
export interface DashboardTableProps {
|
||||
|
||||
@@ -132,10 +132,19 @@ export const getRecentAcitivtyObjs = (
|
||||
) =>
|
||||
SupersetClient.get({ endpoint: recent }).then(recentsRes => {
|
||||
const res: any = {};
|
||||
const filters = [
|
||||
{
|
||||
col: 'created_by',
|
||||
opr: 'rel_o_m',
|
||||
value: 0,
|
||||
},
|
||||
];
|
||||
const newBatch = [
|
||||
SupersetClient.get({ endpoint: `/api/v1/chart/?q=${getParams()}` }),
|
||||
SupersetClient.get({
|
||||
endpoint: `/api/v1/dashboard/?q=${getParams()}`,
|
||||
endpoint: `/api/v1/chart/?q=${getParams(filters)}`,
|
||||
}),
|
||||
SupersetClient.get({
|
||||
endpoint: `/api/v1/dashboard/?q=${getParams(filters)}`,
|
||||
}),
|
||||
];
|
||||
return Promise.all(newBatch)
|
||||
@@ -269,15 +278,12 @@ export function shortenSQL(sql: string, maxLines: number) {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// loading card count for homepage
|
||||
export const loadingCardCount = 5;
|
||||
|
||||
const breakpoints = [576, 768, 992, 1200];
|
||||
export const mq = breakpoints.map(bp => `@media (max-width: ${bp}px)`);
|
||||
|
||||
export const CardStylesOverrides = styled.div`
|
||||
.ant-card-cover > div {
|
||||
height: 264px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CardContainer = styled.div<{
|
||||
showThumbnails?: boolean | undefined;
|
||||
}>`
|
||||
@@ -286,7 +292,7 @@ export const CardContainer = styled.div<{
|
||||
display: grid;
|
||||
grid-gap: ${theme.gridUnit * 12}px ${theme.gridUnit * 4}px;
|
||||
grid-template-columns: repeat(auto-fit, 300px);
|
||||
max-height: ${showThumbnails ? '314' : '140'}px;
|
||||
max-height: ${showThumbnails ? '314' : '148'}px;
|
||||
margin-top: ${theme.gridUnit * -6}px;
|
||||
padding: ${
|
||||
showThumbnails
|
||||
|
||||
@@ -21,9 +21,9 @@ import moment from 'moment';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { setInLocalStorage } from 'src/utils/localStorageHelpers';
|
||||
|
||||
import Loading from 'src/components/Loading';
|
||||
import ListViewCard from 'src/components/ListViewCard';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
import { LoadingCards, ActivityData } from 'src/views/CRUD/welcome/Welcome';
|
||||
import {
|
||||
CardStyles,
|
||||
getEditedObjects,
|
||||
@@ -34,7 +34,7 @@ import { Chart } from 'src/types/Chart';
|
||||
import { Dashboard, SavedQueryObject } from 'src/views/CRUD/types';
|
||||
|
||||
import Icons from 'src/components/Icons';
|
||||
import { ActivityData } from './Welcome';
|
||||
|
||||
import EmptyState from './EmptyState';
|
||||
|
||||
/**
|
||||
@@ -230,7 +230,7 @@ export default function ActivityTable({
|
||||
const doneFetching = loadedCount < 3;
|
||||
|
||||
if ((loadingState && !editedObjs) || doneFetching) {
|
||||
return <Loading position="inline" />;
|
||||
return <LoadingCards />;
|
||||
}
|
||||
return (
|
||||
<Styles>
|
||||
|
||||
@@ -35,6 +35,7 @@ import PropertiesModal from 'src/explore/components/PropertiesModal';
|
||||
import { User } from 'src/types/bootstrapTypes';
|
||||
import { CardContainer, PAGE_SIZE } from 'src/views/CRUD/utils';
|
||||
import { HOMEPAGE_CHART_FILTER } from 'src/views/CRUD/storageKeys';
|
||||
import { LoadingCards } from 'src/views/CRUD/welcome/Welcome';
|
||||
import ChartCard from 'src/views/CRUD/chart/ChartCard';
|
||||
import Chart from 'src/types/Chart';
|
||||
import handleResourceExport from 'src/utils/export';
|
||||
@@ -131,6 +132,12 @@ function ChartTable({
|
||||
operator: 'chart_is_favorite',
|
||||
value: true,
|
||||
});
|
||||
} else if (filterName === 'Examples') {
|
||||
filters.push({
|
||||
id: 'created_by',
|
||||
operator: 'rel_o_m',
|
||||
value: 0,
|
||||
});
|
||||
}
|
||||
return filters;
|
||||
};
|
||||
@@ -177,7 +184,7 @@ function ChartTable({
|
||||
});
|
||||
}
|
||||
|
||||
if (loading) return <Loading position="inline" />;
|
||||
if (loading) return <LoadingCards cover={showThumbnails} />;
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
{sliceCurrentlyEditing && (
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
setInLocalStorage,
|
||||
getFromLocalStorage,
|
||||
} from 'src/utils/localStorageHelpers';
|
||||
import { LoadingCards } from 'src/views/CRUD/welcome/Welcome';
|
||||
import {
|
||||
createErrorHandler,
|
||||
CardContainer,
|
||||
@@ -142,6 +143,12 @@ function DashboardTable({
|
||||
operator: 'dashboard_is_favorite',
|
||||
value: true,
|
||||
});
|
||||
} else if (filterName === 'Examples') {
|
||||
filters.push({
|
||||
id: 'created_by',
|
||||
operator: 'rel_o_m',
|
||||
value: 0,
|
||||
});
|
||||
}
|
||||
return filters;
|
||||
};
|
||||
@@ -189,7 +196,7 @@ function DashboardTable({
|
||||
filters: getFilters(filter),
|
||||
});
|
||||
|
||||
if (loading) return <Loading position="inline" />;
|
||||
if (loading) return <LoadingCards cover={showThumbnails} />;
|
||||
return (
|
||||
<>
|
||||
<SubMenu
|
||||
|
||||
@@ -22,7 +22,7 @@ import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light';
|
||||
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql';
|
||||
import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github';
|
||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||
import Loading from 'src/components/Loading';
|
||||
import { LoadingCards } from 'src/views/CRUD/welcome/Welcome';
|
||||
import { Dropdown, Menu } from 'src/common/components';
|
||||
import { useListViewResource, copyQueryLink } from 'src/views/CRUD/hooks';
|
||||
import ListViewCard from 'src/components/ListViewCard';
|
||||
@@ -240,7 +240,7 @@ const SavedQueries = ({
|
||||
</Menu>
|
||||
);
|
||||
|
||||
if (loading) return <Loading position="inline" />;
|
||||
if (loading) return <LoadingCards cover={showThumbnails} />;
|
||||
return (
|
||||
<>
|
||||
{queryDeleteModal && (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user