Compare commits

..

217 Commits

Author SHA1 Message Date
Ville Brofeldt
7d35a91642 fix(dnd): make clicked dnd metrics unique (#16632)
(cherry picked from commit 9dfa33fedf)
2021-09-10 16:31:04 -07:00
Elizabeth Thompson
cc821bb747 fix: params in sql lab are jumpy in the ace editor (#16536)
* fix jumpy params

* remove cypress tests

(cherry picked from commit 519baa6f00)
2021-09-10 16:30:33 -07:00
Michael S. Molina
37c2020035 chore: Merges latest Select changes (#16587) 2021-09-07 10:41:55 -03:00
Ville Brofeldt
9de2196b7f fix: TemporalWrapperType string representation (#16614)
* fix: TemporalWrapperType string representation

* fix tests
2021-09-07 13:50:24 +03:00
Kamil Gabryjelski
effcf3b50f perf(dashboard): decrease number of rerenders of FiltersBadge (#16545)
* perf(dashboard): decrease rerenders in FiltersBadge

* implement caching of dashboard filter indicators

* Implement caching for native filter indicators
2021-09-07 12:03:43 +02:00
Kamil Gabryjelski
7faa5c6aff perf(dashboard): reduce rerenders of DragDroppable (#16525)
* perf(dashboard): reduce rerenders of DragDroppable

* lint fix
2021-09-07 11:19:07 +02:00
Duy Nguyen Hoang
3fe2e6ec73 Remove export CSV in old filter box (#16592) 2021-09-06 14:40:24 +03:00
Ville Brofeldt
7cbced8833 chore(deps): bump superset-ui to 0.18.2 (#16601) 2021-09-06 12:20:29 +03:00
simcha90
df5c0fbce9 feat: Experimental cross-filter plugins (#16594)
* fix:fix get permission function

* feat: add cross filter chart in charts gallery under FF
2021-09-06 08:25:14 +03:00
ofekisr
e60b489867 refactor sql_json view endpoint: separate concern into ad hod method (#16595) 2021-09-05 23:18:17 +03:00
ofekisr
be77ad2288 refactor: sql_json view endpoint: encapsulate ctas parameters (#16548)
* refactor sql_json view endpoint: encapsulate ctas parameters

* fix failed tests

* fix failed tests and ci issues
2021-09-05 16:34:26 +03:00
Beto Dealmeida
359383b578 feat: Add Aurora Data API engine spec (#16535)
* feat: Add Aurora Data API engine spec

* Fix lint
2021-09-04 08:40:14 -07:00
joeADSP
5f8dff1341 docs: update for small typos (#16568) 2021-09-03 15:22:29 -03:00
Beto Dealmeida
9bb890ebed fix: impersonate user label/tooltip (#16573) 2021-09-03 09:42:11 -07:00
Rob DiCiuccio
4e380db3fd fix: Support Jinja template functions in global async queries (#16412)
* Support Jinja template functions in async queries

* Pylint

* Add tests for async tasks

* Remove redundant has_request_context check
2021-09-03 14:33:29 +03:00
Elizabeth Thompson
a0db5367d2 bump emotion to help with cache clobbering (#16559) 2021-09-02 13:48:21 -07:00
Ville Brofeldt
070fdbeebc fix(tests): make parquet select deterministic with order by (#16570) 2021-09-02 15:16:33 -03:00
Michael S. Molina
02798a3517 fix: Adds a loading message when needed in the Select component (#16531) 2021-09-02 13:17:47 -03:00
Yongjie Zhao
2e11b05d73 chore: bump superset-ui to 0.18.1 (#16563) 2021-09-02 09:03:43 +03:00
Ville Brofeldt
75a1b19174 chore: bump superset-ui to 0.18.0 (#16544) 2021-09-02 13:16:03 +08:00
ofekisr
e947f8a4c8 refactor sql_json view endpoint: extract to method reuse code (#16546) 2021-09-01 19:24:33 +03:00
Yongjie Zhao
80c39daa85 fix: can't drop column when name overlap (#16482) 2021-09-01 13:52:10 +08:00
Daniel Wood
e024f8c7d6 fix: Set correct comparison operator for snowflake-sqlalchemy pinning (#16526) 2021-08-31 16:18:55 -06:00
ofekisr
68c2a6d43a refactor sql_json view endpoint: separate getting and checking existing query to ad hoc methods (#16449) 2021-08-31 19:55:45 +03:00
Ville Brofeldt
c5a5cf7db9 fix(datasets): add support for removing owners (#16461)
* fix(datasets): add support for removing owners

* default to current user
2021-08-31 22:27:09 +08:00
grumpy-miner
1f1e2dd29a fix: ensure setting operator to None (#16371) (#16372) 2021-08-31 12:53:40 +03:00
Geido
f001c44727 Make Modal draggable and resizable (#16447) 2021-08-31 12:46:44 +03:00
Shiva Raisinghani
d25b0967a1 feat: Add parquet upload (#14449)
* allow csv upload to accept parquet file

* fix mypy

* fix if statement

* add test for specificying columns in CSV upload

* clean up test

* change order in test

* fix failures

* upload parquet to seperate table in test

* fix error message

* fix mypy again

* rename other extensions to columnar

* add new form for columnar upload

* add support for zip files

* undo csv form changes except usecols

* add more tests for zip

* isort & black

* pylint

* fix trailing space

* address more review comments

* pylint

* black

* resolve remaining issues
2021-08-31 10:20:25 +03:00
Daniel Wood
ad8336a5b4 fix: Pin snowflake-sqlalchemy to 1.2.4 (#16515)
* Pin snowflake-sqlalchemy to 1.2.4

* lint files
2021-08-31 07:48:51 +03:00
AAfghahi
e2469162fa fix: select database fix (#16472)
* select database fix

* made a backend change
2021-08-30 17:04:10 -07:00
Phillip Kelley-Dotson
a616b8785e make chart rerender on timeseries change (#16411) 2021-08-30 16:43:14 -07:00
Phillip Kelley-Dotson
960d1e4d5d fix: stop endless loading when dataset no longer exist (#16511)
* initial fix

* add const

* add translate
2021-08-30 16:42:02 -07:00
Grace Guo
2199f65373 fix: sql lab refetch button (#16469)
* fix: SQL Lab show "Refetch Results" button while fetching new query results (#15109)

* fix: SQL Lab show "Refetch Results" button while fetching new query results

* fix comments

(cherry picked from commit 408d58f937)

* handle exception caused by invalid query id
2021-08-30 15:47:00 -07:00
Will Barrett
8aa24e50d9 chore: remove myself from codeowners on Preset integration (#16470) 2021-08-30 15:14:46 -07:00
Ville Brofeldt
90e2f09c67 docs: update entries for v1.2 and v1.3 (#16496) 2021-08-30 13:58:08 +03:00
Srini Kadamati
1ffd73d03b chore: Docs/superset1.3 release notes (#16390)
* docs: superset release notes 1.3

* swapped out image

* Update RELEASING/release-notes-1-3/README.md

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

* Update RELEASING/release-notes-1-3/README.md

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

* Update RELEASING/release-notes-1-3/README.md

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

* Update RELEASING/release-notes-1-3/README.md

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

Co-authored-by: Junlin Chen <junlin@preset.io>
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
2021-08-30 09:06:24 +03:00
Beto Dealmeida
62d8ab7f9c fix: create example DB if needed (#16451)
* fix: create example DB if needed

* fix lint
2021-08-27 07:19:37 -07:00
Ville Brofeldt
147637a02d fix(native-filters): add handle undefined control value gracefully (#16468) 2021-08-27 14:03:05 +03:00
Erik Ritter
8adc31d14c Revert "chore: Changes the DatabaseSelector to use the new Select component (#16334)" (#16478)
This reverts commit c768941f2f.
2021-08-26 22:28:04 -03:00
Jesse Yang
a413f796a6 fix(explore): JS error for creating new metrics from columns (#16477) 2021-08-26 17:04:45 -07:00
AAfghahi
ee2eccdb67 fix: queryEditor bug (#16452)
* queryEditor bug

* update tests

Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
2021-08-26 15:20:02 -07:00
Ke Zhu
fd6456186d docs: make code snippet usable with required imports (#16473) 2021-08-26 16:16:30 -06:00
Kamil Gabryjelski
f422f1ea49 perf(dashboard): decouple redux props from dashboard components (#16421)
* perf(dashboard): decouple redux props from dashboard components

* Lint fix

* Dont make copy of filters object

* Remove unnecessary exports
2021-08-26 21:54:11 +02:00
Kamil Gabryjelski
8ad495a572 perf(dashboard): reduce number of rerenders of Charts (#16444)
* perf(dashboard): reduce number of rerenders of Charts

* Remove static property

* Cleanup
2021-08-26 21:53:44 +02:00
Ville Brofeldt
ac1d779a30 chore(ci): bump pylint to 2.10.2 (#16463) 2021-08-26 20:30:41 +03:00
Yongjie Zhao
577ede4b12 fix: prevent page crash when chart can't render (#16464) 2021-08-26 22:26:01 +08:00
Srini Kadamati
c66d6d83b9 chore: fixed slack invite link (#16466) 2021-08-26 09:04:51 -04:00
Ville Brofeldt
1c71eda70f fix(native-filters): handle null values in value filter (#16460)
* fix(native-filters): handle null values in value filter

* lint
2021-08-26 15:26:53 +03:00
Eugene Klimov
1badcaed45 feat: add function list to auto-complete to Clickhouse datasource (#16234)
* add function list to auto-complete to Clickhouse datasource, fix https://github.com/apache/superset/issues/15477

Signed-off-by: Slach <bloodjazman@gmail.com>

* add function list to auto-complete to Clickhouse datasource, fix https://github.com/apache/superset/issues/15477
fix review comments https://github.com/apache/superset/pull/16234#discussion_r688362980

Signed-off-by: Slach <bloodjazman@gmail.com>

* fix CI/CD https://github.com/apache/superset/pull/16234/checks?check_run_id=3396235776 and https://github.com/apache/superset/pull/16234/checks?check_run_id=3396235707

Signed-off-by: Slach <bloodjazman@gmail.com>

* fix black formatting

Signed-off-by: Slach <bloodjazman@gmail.com>

* fix isort pre-commit hook

Signed-off-by: Slach <bloodjazman@gmail.com>
2021-08-26 18:55:43 +08:00
Jesse Yang
ec087507e5 refactor(explore): improve typing for Dnd controls (#16362) 2021-08-26 01:23:14 -07:00
Ville Brofeldt
18be181946 fix(explore): update overwrite button on perm change (#16437)
* fix(explore): update overwrite on perm change

* remove redundant user_id prop

* fix types

* fix user type

* fix tests

* fix lint
2021-08-26 06:24:33 +03:00
Geido
db11c3e6c8 feat: Draggable and Resizable Modal (#16394)
* Implement resizable prop

* Implement resizableConfig

* Implement fluid Syntax Highlighter

* Implement draggable

* Destroy on close

* Implement draggableConfig

* Enhance with footer calculation

* Add new line

* Make whole header draggable trigger
2021-08-25 16:11:16 +03:00
ofekisr
93c60e4021 refactor: sql_json view endpoint (#16441)
* refactor sql_json view endpoint

* fix pylint

* renaming

* renaming

Co-authored-by: Amit Miran <47772523+amitmiran137@users.noreply.github.com>
2021-08-25 15:51:48 +03:00
Maxhui
6a2cec51c5 fix(dashboard): undo and redo buttons weird alignment (#16417)
* fix(dashboard-ui): undo and redo buttons weird alignment

* fix(Explore control pane): keyboard nav & focus

* lint: lint frontend

* fix: fix redo and undo button style

* lint: lint line

* lint: lint line

Co-authored-by: xuzhebin <maxhui2020@gmail.com>
2021-08-25 08:52:59 -03:00
Grace Guo
08b8aa277f fix: setupPlugin in chart list page (#16413)
* fix: setupPlugin in chart list page

* fix the order of setupPlugins call

* Fix jest test on loading geojson

* add jest changes

* fix unit tests

Co-authored-by: Jesse Yang <jesse.yang@airbnb.com>
2021-08-24 23:10:46 -07:00
Grace Guo
78d8089b18 fix: Disable Slack notification method if no api token (#16367)
* feat: allow company choose alert/report notification methods

* fix: disable Slack as notification method if no api token

* fix unit test

* improve typing
2021-08-24 17:28:50 -07:00
Beto Dealmeida
5e472980a6 feat: add Shillelagh DB engine spec (#16416) 2021-08-24 17:07:29 -07:00
AAfghahi
631ad02a76 fix: copy to Clipboard order (#16299)
* copy to Clipboard order

* centralized copyToClipboard

* fixed table order

* fixed tests

* added colnames to all viz types

* added colnames to all viz types

* added colnames to all viz types
2021-08-24 14:47:09 -07:00
David Aaron Suddjian
e71c6e60e4 docs: make FEATURE_FLAGS.md reference a link (#16415) 2021-08-24 14:08:11 -07:00
Ville Brofeldt
5eded9fe1b chore(viz): bump superset-ui to 0.17.87 (#16420) 2021-08-24 11:10:23 -07:00
Hugh A. Miles II
c0e9006eb7 feat: add activate command (#16404)
* add activate command

* Update Makefile

* fix linting

* fix
2021-08-24 10:45:40 -07:00
Evan Rusackas
81241b6024 Revert "fix(explore): let admin overwrite slice (#16290)" (#16408)
This reverts commit d13b081cfe.
2021-08-24 09:38:41 -06:00
Ville Brofeldt
35864748f2 fix(explore): retain chart ownership on query context update (#16419) 2021-08-24 18:37:34 +03:00
Michael S. Molina
6a5568764e chore: Removes the TODOs and uses the default page size (#16422) 2021-08-24 11:44:27 -03:00
Michael S. Molina
575e7af859 fix: Show cross filter option only when cross filter is enabled (#16391) 2021-08-24 09:13:39 -03:00
Geido
9a37ad1a1e fix: Return original document title when leaving a dashboard (#16323)
* Return original title on unmount

* Name var originalDocumentTitle
2021-08-24 15:08:13 +03:00
Ville Brofeldt
f6637cac7d fix(api): return total count on related endpoint (#16397)
* fix(api): return total count on related endpoint

* update response code from 400 to 422
2021-08-24 15:07:58 +03:00
Michael S. Molina
1fc9318594 chore: Changes the AlertReportModal to use the new Select component (#16144)
* chore: Changes the AlertReportModal to use the new Select component

* Gives time to close the select before changing the type
2021-08-24 08:19:27 -03:00
Geido
c14364c616 chore: Enhance Omnibar (#16273)
* Fix style and implement ESC

* Include ESC test case

* Move pagination outside of table

* Update superset-frontend/src/components/OmniContainer/OmniContainer.test.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Enhance

* Handle close

* Localize placeholder

* Fix tests

* Clear input on close

* Destroy modal on close

* Clean up

* Fix tests

Co-authored-by: Evan Rusackas <evan@preset.io>
2021-08-24 11:52:07 +03:00
David Aaron Suddjian
486ef6b81f feat: config to customize bootstrap data overrides (#16386)
* feat: config to customize bootstrap data overrides

* comments
2021-08-23 14:40:14 -07:00
AAfghahi
a6aad52e38 fix: regex for multi-region IPs (#16410)
* regex for multi-region IPs

* Update index.tsx
2021-08-23 13:54:25 -07:00
Michael S. Molina
c768941f2f chore: Changes the DatabaseSelector to use the new Select component (#16334) 2021-08-23 15:41:03 -03:00
Michael S. Molina
0cdc7675b4 chore: Displays the dataset description in a tooltip in the datasets list (#16392) 2021-08-23 15:38:53 -03:00
John Bodley
bc4b6f0a6c fix(pylint): Fix master (#16405)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
2021-08-24 05:20:52 +12:00
John Bodley
7e4c940314 chore(pylint): Enable useless-suppression check (#16388)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
2021-08-24 03:58:41 +12:00
cccs-tom
970d762779 feat: Add extraVolumes and extraVolumeMounts to all main containers (#16361) 2021-08-21 17:53:13 -07:00
Phillip Kelley-Dotson
3faf653e5f initial commit (#16366) 2021-08-20 17:46:55 -06:00
Erik Ritter
a9f502b67b fix: big number default date format (#16383) 2021-08-20 16:08:54 -07:00
Phillip Kelley-Dotson
c5081991fc initial commit (#16380) 2021-08-20 16:59:53 -06:00
Beto Dealmeida
649e509607 fix: import dashboard w/o metadata (#16360) 2021-08-20 12:03:05 -07:00
Lyndsi Kay Williams
518c3c9ae0 test: Functional RTL for email report modal II (#16148)
* Email Report Modal validation testing

* Starting RTL testing for email report

* Calendar icon now rendering!

* Create report testing in dashboard

* make linter happy

* Fixing weird error

* Removed ExploreChartHeader_spec

* Fixed dashboard header test

* revert changes from merge

* Fix tests

Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
2021-08-20 10:08:36 -07:00
Beto Dealmeida
adebc0997b fix: update table ID in query context on chart import (#16374)
* fix: update table ID in query context on chart import

* Fix test
2021-08-20 10:03:31 -07:00
Ke Zhu
13a2ee373c docs: document FLASK_APP_MUTATOR (#16286) 2021-08-20 10:54:46 -06:00
Hugh A. Miles II
ea803c3d1c feat: Add new dev commands to Makefile (#16327)
* updating makefile

* Update Makefile
2021-08-20 12:31:21 -04:00
Ville Brofeldt
575ee24a99 fix: call external metadata endpoint with correct rison object (#16369) 2021-08-20 11:43:57 +03:00
Hugh A. Miles II
50d896f1b7 fix: Fix parsing onSaving reports toast when user hasn't saved chart (#16330)
* don't maniuplate error message

* remove extra idx reference

* u

* change print

* update with test

* add case for dashboards

* fix test
2021-08-19 14:04:23 -04:00
Beto Dealmeida
37f09bd296 fix: columns/index rebuild (#16355) 2021-08-19 09:38:24 -07:00
Ville Brofeldt
86f4e691d4 chore(viz): bump deckgl plugin to 0.4.11 (#16353) 2021-08-19 19:19:48 +03:00
Geido
c5c28618a5 fix: Blank space in Change dataset modal without warning message (#16324)
* Make scrollTable flex

* Revert unclosable warning
2021-08-19 16:46:25 +02:00
ETselikov
d75da748d5 docs: add VkusVill and TechAudit to users list (#16113)
* Update INTHEWILD.md

* Update INTHEWILD.md
2021-08-19 15:39:00 +03:00
Evan Rusackas
42cd21e383 chore: bump superset-ui to v0.17.85 (#16350) 2021-08-19 09:32:24 +03:00
Beto Dealmeida
ec8d3b03e4 fix: send CSV pivoted in reports (#16347) 2021-08-18 19:36:48 -07:00
Beto Dealmeida
afb8bd5fe6 feat: improve embedded data table in text reports (#16335)
* feat: improve HTML table in text reports

* Remove unused import

* Update tests

* Fix test
2021-08-18 17:00:29 -07:00
Elizabeth Thompson
efe850b731 adjust initial state (#16329) 2021-08-18 13:18:36 -07:00
Kamil Gabryjelski
a547dcb73e fix(explore): reordering columns with dnd sometimes glitching (#16322)
* fix(explore): reordering columns with dnd sometimes glitching

* Fix metrics and filters popover being stale after reordering
2021-08-18 18:39:05 +02:00
Kamil Gabryjelski
2c595b09ea chore(explore): make metric/column search input clearable (#16320)
* chore(explore): make metric/column search input clearable

* Fix typo

* Fix test
2021-08-18 12:23:23 +02:00
Srini Kadamati
5f060a2227 feat: Adding Rockset db engine spec (#16167)
* rockset prototype

* rockset

* t

* removed print / logging

* set a more specific version range for Rockset library
2021-08-18 13:04:59 +03:00
alandao2021
482dffb1db docs: Add Care to users list of Apache Superset (#16308)
* Add Care to users list of Apache Superset

Add Care to users list of Apache Superset

* Update INTHEWILD.md

change to correct order

* Update INTHEWILD.md

Co-authored-by: Erik Ritter <erik.ritter@airbnb.com>
2021-08-18 12:44:08 +03:00
Ville Brofeldt
9075e4206c feat(sqla): apply time grain to all temporal groupbys (#16318) 2021-08-18 11:47:08 +03:00
Elizabeth Thompson
4960b5ee2b pass correct report_format (#16306) 2021-08-17 18:31:38 -07:00
Beto Dealmeida
7a284bb9e8 fix: allow reports to update query_context (#16303) 2021-08-17 16:39:27 -07:00
Erik Ritter
d5f63a74e4 fix: remove mergepoint from past migration (#16301) 2021-08-17 16:27:33 -07:00
Grace Guo
b87e0b32ac refactor: rearrange dashboard page js bundles (#16285) 2021-08-17 16:12:05 -07:00
Beto Dealmeida
ac8e54d909 fix: improve pivot post-processing (#16289)
* fix: improve pivot post-processing

* Add tests

* Trim space from column name
2021-08-17 14:41:22 -07:00
Beto Dealmeida
3c0aefb61a Revert "fix: disable text reports for now (#16257)" (#16296)
This reverts commit ee9a384758.
2021-08-17 14:21:15 -07:00
AAfghahi
4119bb9c1e fix: reverting Dataset names (#16243)
* reverting dataset name changes

* remove extra quotes

Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
2021-08-17 12:16:15 -07:00
Beto Dealmeida
8a36356f49 fix: rename Databricks (#16297) 2021-08-17 11:16:51 -07:00
Phillip Kelley-Dotson
f581e0402b change filter (#16280) 2021-08-17 09:37:39 -07:00
AAfghahi
f5fbfef618 timezone editor (#16281) 2021-08-17 09:32:17 -07:00
Kamil Gabryjelski
203c311ca3 feat(explore): make dnd controls clickable (#16119)
* Make ghost buttons clickable

* Popover for column control

* Make column dnd ghost button clickable

* Prefill operator only if column is defined

* Remove data-tests

* lint fix

* Hide new features behind a feature flag

* Change ghost button texts

* Remove caret for non clickable columns
2021-08-17 14:56:39 +02:00
Ville Brofeldt
5e75baf0cc fix(sqlite): week grain refer to day of week (#16293) 2021-08-17 15:45:08 +03:00
Geido
9876c36f6e Fix table height (#16275) 2021-08-17 09:58:13 +02:00
Ville Brofeldt
d13b081cfe fix(explore): let admin overwrite slice (#16290) 2021-08-17 10:55:33 +03:00
Ville Brofeldt
2be84e78d2 chore(explore): remove unnecessary favstar redirect (#16288) 2021-08-17 09:09:38 +03:00
John Bodley
be7065faf8 chore(pylint): Reenable raise-missing-from check (#16266)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
2021-08-17 10:26:10 +12:00
John Bodley
36bc7b0b80 chore(pylint): Reenable too-few-public-methods check (#16264)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
2021-08-17 05:20:44 +12:00
John Bodley
0df15bf207 chore(pylint): Reenable import-outside-toplevel check (#16263)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
2021-08-17 05:20:13 +12:00
Ville Brofeldt
36abc51f90 fix(dashboard): unset empty time filter indicator (#16272) 2021-08-16 19:32:05 +03:00
John Bodley
b5c7ed9f18 chore(pylint): Reenable too-many-locals check (#16268)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
2021-08-17 04:23:40 +12:00
John Bodley
7b724439b9 chore(pylint): Reenable ungrouped-imports check (#16256)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
2021-08-17 04:01:01 +12:00
John Bodley
8e07dd28bc chore(pylint): Re-enable super-with-arguments check (#16138)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
2021-08-17 04:00:24 +12:00
Beto Dealmeida
ee9a384758 fix: disable text reports for now (#16257)
* fix: disable text reports for now

* Update tests
2021-08-16 08:16:48 -07:00
Beto Dealmeida
542b864e61 fix: pivot col names in post_process (#16262) 2021-08-16 08:16:42 -07:00
Michael S. Molina
0668eaad6a chore: Improves the flow to create a new chart (#16252)
* chore: Improves the flow to create a new chart

* Adds a comment about selector specificity
2021-08-16 11:56:47 -03:00
Geido
22231addec Move pagination outside of table (#16232) 2021-08-16 16:43:49 +02:00
Elizabeth Thompson
3709131089 check roles before fetching reports (#16260) 2021-08-13 20:41:03 -07:00
Ville Brofeldt
d46dc9aa45 chore: upgrade mypy and add type guards (#16227) 2021-08-14 06:31:45 +03:00
Beto Dealmeida
9b2dffeb1d fix: pivot columns with ints for name (#16259) 2021-08-13 18:21:20 -07:00
John Bodley
24b43beff9 chore(pylint): Bump Pylint to 2.9.6 (#16146)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
2021-08-14 10:32:28 +12:00
Phillip Kelley-Dotson
a5dbe6a14d fix examples tab for dashboard (#16253) 2021-08-13 14:56:32 -07:00
Phillip Kelley-Dotson
f94695480a chore: bump superset-ui packages to 0.17.84 (#16251)
* initial bump

* commit pack-lock file
2021-08-13 13:40:26 -06:00
Michael S. Molina
720e5b111a chore: Shows the dataset description in the gallery dropdown (#16200)
* chore: Shows the dataset description in the gallery dropdown

* chore: Adjusts the tooltip positioning, fixes the search and removes unnecessary bootstrap data
2021-08-13 15:08:12 -03:00
Geido
c09f6ed15b fix(Dashboard): Omnibar dropdown visibility and keyboard commands (#16168)
* Fix style and implement ESC

* Include ESC test case

* Update superset-frontend/src/components/OmniContainer/OmniContainer.test.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

Co-authored-by: Evan Rusackas <evan@preset.io>
2021-08-13 18:44:49 +02:00
Hugh A. Miles II
4ae88ce3b4 chore: bump py version for integration test (#16213)
* bump py version for integration test

* bump py

* bump py

* remove files

* lock pylint

* add not-callable
2021-08-13 12:44:32 -04:00
Beto Dealmeida
2611681de9 fix: skip perms on query context update (#16250) 2021-08-13 09:36:30 -07:00
Yongjie Zhao
6cd15d54a0 refactor: external metadata fetch API (#16193)
* refactor: external metadata api

* fix comments

* fix ut

* fix fe lint

* fix UT

* fix UT
2021-08-13 20:56:42 +08:00
Ville Brofeldt
d6f9c48aa1 feat(dao): admin can remove self from object owners (#15149) 2021-08-13 12:42:48 +03:00
Kamil Gabryjelski
517a678cd7 fix(dashboard): cross filter chart highlight when filters badge icon clicked (#16233)
* fix(dashboard): cross filter chart highlight when filters badge icon pressed

* Fix tests

* Fix tests

* break out label logic

Co-authored-by: Ville Brofeldt <ville.v.brofeldt@gmail.com>
2021-08-13 10:02:28 +02:00
Beto Dealmeida
5d3d6b6eae fix: validate_parameters and query (#16241)
* fix: validate_parameters and query

* add onQueryChange
2021-08-12 18:05:27 -07:00
Evan Rusackas
cdcc161846 fix: Remove Advanced Analytics tag for 2 charts (#16240)
* removing AA tag from TimeTableChartPlugin

* package bump for echarts (removes AA tag there)

* package-lock bump for new echarts plugin
2021-08-12 15:32:39 -06:00
AAfghahi
9d0dc561fc Revert "feat: Changing Dataset names (#16199)" (#16235)
This reverts commit adb3ebbba3.
2021-08-12 13:59:44 -07:00
Hugh A. Miles II
171514360e feat: Allow users to connect via legacy SQLA form (#16201)
* hugh/dbui-other

* remove extra condition

* add not callable

* Update .pylintrc

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
2021-08-12 15:10:42 -04:00
Elizabeth Thompson
67c4c0116e fix: remove encryption from db params (#16214)
* remove encryption from db params

* Update superset/db_engine_specs/base.py

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
2021-08-12 12:03:14 -07:00
Geido
a1e18ed110 fix(Explore): Show the tooltip only when label does not fit the container in METRICS/FILTERS/GROUP BY/SORT BY of the DATA panel (#16060)
* Implement dynamic tooltip

* Normalize and consolidate

* Clean up

* Refactor and clean up

* Remove unnecessary var

* Fix type import

* Update superset-frontend/src/explore/components/controls/OptionControls/index.tsx

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

* Remove unnecessary styled span

* Show full tooltip title

* Force show tooltip

* Force show tooltip D&D off

Co-authored-by: Ville Brofeldt <ville.v.brofeldt@gmail.com>
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
2021-08-12 11:48:16 -06:00
Geido
2c5731aea3 Show/hide tooltips (#16192) 2021-08-12 11:42:48 -06:00
Junlin Chen
16a9d219ed chore: Add feature flags to PR template (#16215)
* chore:Add feature flags to bug PR template

* Update .github/PULL_REQUEST_TEMPLATE.md

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

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
2021-08-12 07:38:03 -10:00
Kamil Gabryjelski
a16e290765 fix(explore): conditional formatting value validators (#16230)
* fix(explore): conditional formatting value validators

* Fix typing, make validator more generic

* Remove commented code
2021-08-12 14:31:29 +02:00
Ville Brofeldt
b61c34f7c9 fix(viz): deduce metric name if empty (#16194)
* fix(viz): deduce metric name if empty

* fix unit test
2021-08-12 11:16:05 +03:00
Beto Dealmeida
7de54d016e feat: import configuration from directory (#15686)
* feat: command to import configuration from a directory

This allows us to keep Superset updated from a repo:

```bash
$ superset import-directory /path/to/configs/
```

For example, I created a simple dashboard with a single chart:

PLACEHOLDER

I then exported it to a file `dashboard_export_20210714T104600.zip` and
unzipped it. After deleting the dashboard, chart, dataset, and database
I imported everything back with:

```bash
$ superset import-directory ~/Downloads/dashboard_export_20210714T104600/
```

I then changed the chart title in `~/Downloads/dashboard_export_20210714T104600/charts/Cnt_per_country_1.yaml` and ran the command again. The chart was succesfully updated:

PLACEHOLDER

* Small fixes
2021-08-11 18:42:50 -07:00
Beto Dealmeida
5a8484185b fix: pyinstrument dependency (#16211) 2021-08-11 17:07:16 -07:00
Phillip Kelley-Dotson
c79de7abd7 initial fix (#16212) 2021-08-11 15:50:50 -07:00
Phillip Kelley-Dotson
b4555dfa4f fix: sorting on "Modified By" in chart table (#16208)
* initial fix

* Update ChartList.tsx

change sort to first name
2021-08-11 15:31:10 -07:00
Kamil Gabryjelski
ccfc95fbe6 fix(explore): adhoc metrics popover resets label after hovering outside (#16196)
* fix(explore): adhoc metrics popover resets label after hovering outside

* Remove irrelevant tests and skip rest

* Use ensureIsArray
2021-08-11 10:05:08 -06:00
AAfghahi
6c304b83a9 feat: Changing Dataset names (#16199)
* added google alert

* changing Dataset Names
2021-08-11 08:59:59 -07:00
Kamil Gabryjelski
98fc29cbbb fix(explore): metric label disappearing in some scenarios (#16190) 2021-08-11 14:05:44 +02:00
Ville Brofeldt
4df3672baa chore: bump superset-ui to 0.17.82 (#16186) 2021-08-11 10:08:12 +03:00
Elizabeth Thompson
3aefa6925b update covid dashboard (#16183) 2021-08-10 20:47:13 -07:00
Phillip Kelley-Dotson
a30d884cfc fix: change listivew card layouts to the new homepage card layout (#16171)
* initial commit

* removing CardStylesOverrides (unused)

Co-authored-by: Evan Rusackas <evan@preset.io>
2021-08-10 20:26:24 -07:00
Junlin Chen
9841c78967 chore: switch back tag name to popular from highly-used (#16174)
* chore: switch back tag name to popular from highly-used

* new package lock

* new package lock with npm 7

* fix lint

* remove package changes

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
2021-08-10 20:23:14 -07:00
Phillip Kelley-Dotson
a0c9b9d9c2 fix: ensure created user entities do not show inside examples (#16176)
* initial commit

* fix lint

* Update superset-frontend/src/views/CRUD/utils.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/views/CRUD/utils.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

* Update superset-frontend/src/views/CRUD/utils.tsx

Co-authored-by: Evan Rusackas <evan@preset.io>

Co-authored-by: Evan Rusackas <evan@preset.io>
2021-08-10 19:53:47 -07:00
AAfghahi
6df16c4b1f feat: CLI cleanup (#16178)
* added google alert

* removing datasets from cli
2021-08-10 18:00:34 -07:00
Elizabeth Thompson
628169a171 feat: change query predicate to text (#16160)
* change query predicate to text

* make input multiline

* remove value that is too long for the downgrade

* keep logging lint rule
2021-08-10 17:52:01 -07:00
AAfghahi
2dc0bdda5d feat: Added multi-regional IPs to Database Connections (#16170)
* added google alert

* multi-regional IPs

* beto revisions

* Update superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

* Update superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

* Update superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
2021-08-10 17:25:49 -07:00
Beto Dealmeida
9f52c103ac fix: isDynamic function (#16175)
* fix: isDynamic function

* trigger tests
2021-08-10 16:28:43 -07:00
Elizabeth Thompson
a3102488a1 feat: add chart image info to reports from charts (#16158)
* refetch reports on props update

* add chart types to reports
2021-08-10 15:11:10 -07:00
Geido
5e64d65a8b Hide Safari default tooltip (#16145) 2021-08-10 14:38:13 -06:00
Beto Dealmeida
7b3fce7e81 fix: revert data endpoint name (#16162) 2021-08-10 11:59:27 -07:00
Hugh A. Miles II
3f86a54ac1 fix: turn on SSL in database edit form show 500 error (#16151)
* fix error for query.update

* converrt before making request

* fix query params

* remove unchanged files

* this

* update tsconfig
2021-08-10 13:31:28 -04:00
Hugh A. Miles II
fd80ae34a3 fix: Make sheet_name into a ValidationInputError (#16056)
* setup validates for name

* add error type

* fix linting

* fix test

* remove errors

* fix number

* fix test
2021-08-10 13:07:31 -04:00
Phillip Kelley-Dotson
f0e3b68cc2 fix: ensure that users viewing chart does not automatically save edit data (#16077)
* add last_change_at migration

* add last_saved_by db migration

* finish rest of api migration

* run precommit

* fix name

* run precommitt

* remove unused mods

* merge migrations

* Update superset/migrations/versions/6d20ba9ecb33_add_last_saved_at_to_slice_model.py

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

* Update superset/migrations/versions/6d20ba9ecb33_add_last_saved_at_to_slice_model.py

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

* Update superset/migrations/versions/f6196627326f_update_chart_permissions.py

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>

* fix test

* precommit

* remove print

* fix test

* change test

* test commit

* test 2

* test 3

* third time the charm

* fix put req

Co-authored-by: Beto Dealmeida <roberto@dealmeida.net>
2021-08-10 10:29:49 -06:00
Ville Brofeldt
63ace7b288 feat(cross-filters): add support for temporal filters (#16139)
* feat(cross-filters): add support for temporal filters

* fix test

* make filter optional

* remove mocks

* fix more tests

* remove unnecessary optionality

* fix even more tests

* bump superset-ui

* add isExtra to schema

* address comments

* fix presto test
2021-08-10 17:18:46 +01:00
Elizabeth Thompson
5488a8a948 add config to hide some user menu items (#16156) 2021-08-10 09:03:13 -07:00
Kamil Gabryjelski
6e1d16d956 feat(explore): each control can define its own canDrop for dnd (#16090)
* feat(explore): each control can define its own canDrop for dnd

* Make canDropValue optional

* Add onDropValue
2021-08-10 17:04:42 +02:00
Phillip Kelley-Dotson
a70248736f feat: add sticky state to tables and loadingcards state. (#16102)
* initial feat commit

* fix chart and dash rendering onload

* Update superset-frontend/src/views/CRUD/welcome/Welcome.tsx

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

* fix jumpyness and add const

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
2021-08-10 06:43:59 -07:00
Michael S. Molina
273ab3d257 fix: Safari is not showing scroll bars in Explore (#16089) 2021-08-10 10:41:52 -03:00
Michael S. Molina
07f33998ac fix: Multiple dashboard refresh triggers for the same session (#16094) 2021-08-10 09:12:28 -03:00
Yongjie Zhao
bb1d8fe4ef fix: boolean type into SQL 'in' operator (#16107)
* fix: boolean type into SQL 'in' operator

* fix ut

* fix ut again

* update url

* remove blank line
2021-08-10 19:21:46 +08:00
Maxime Beauchemin
79e8d77acc chore: remove TerserPlugin step for build (#16163) 2021-08-10 17:09:40 +08:00
Lyndsi Kay Williams
3712ee02fa additional params field fixed (#16161) 2021-08-09 19:13:37 -07:00
AAfghahi
606a7bf429 fix: change Alert Permissions (#16118)
* added google alert

* reworked permissions
2021-08-09 13:33:04 -07:00
AAfghahi
5ce38839e7 feat: better errors for report in charts and dashboard (#16131)
* added google alert

* better errors and report actions
2021-08-09 13:32:52 -07:00
AAfghahi
a51851308b feat: added google alert to DB Connection Form (#16095)
* added google alert

* using superset_text

* made google alert public and others private

* Hugh revisions
2021-08-09 14:08:06 -04:00
Kamil Gabryjelski
b7cc89c6d4 fix(explore): dnd error when dragging metric if multi: false (#16088)
* fix(explore): dnd error when dragging metric if multi: false

* Fix error for non-dnd controls
2021-08-09 18:15:50 +02:00
adam-stasiak
578a9e9d53 build: Removed jsx-remove-data-test-id usage from code for multi-build-variant testing (#15386)
* enabled customized production build with testIds

* fix for docker

* changed substitution

* changed substitution
2021-08-09 10:11:26 -06:00
Kamil Gabryjelski
6ac4f4ef2f chore(explore): change dnd placeholders (#16116)
* chore(explore): change dnd placeholders

* Fix tests and lint
2021-08-09 17:50:11 +02:00
Beto Dealmeida
2db1615c83 feat: add profiling to Superset pages (#16136)
* feat: add profiling to Superset pages

* Address comments
2021-08-09 08:39:35 -07:00
Maxime Beauchemin
df50a47777 chore: add stats logging to thumbnail api (#16133) 2021-08-09 08:34:51 -07:00
Geido
b07c80a839 Adjust width (#16092) 2021-08-09 17:25:33 +02:00
Beto Dealmeida
ddb5005900 fix: test (#16137) 2021-08-08 23:13:23 -07:00
Yongjie Zhao
3bbcc30d69 fix: virtual dataset wont work (#16132) 2021-08-07 20:43:12 +08:00
Victor
85ae8e3477 refactor: proper TypeError handling in memoize decorator (#16074)
Co-authored-by: Victor Sadkov <victor.sadkov@cloudkitchens.com>
2021-08-06 20:03:52 -06:00
Grace Guo
85329c374e refactor: remove unnecessary dataset queries from dashboard requests (#16110)
* refactor: remove unnecessary dataset queries from dashboard requests

* fix comments
2021-08-06 19:01:05 -07:00
Josh Berkus
22d8d582f8 docs: update install-from-scratch instructions for CentOS (#16129)
Update install-from-scratch instructions for current CentOS stream, based on doing a test install today.  CentOS stream python packages are now named after the version ("python3") and current Superset code requires updating Pip to a more current version.
2021-08-06 18:52:29 -06:00
Beto Dealmeida
28c383af68 fix: migrate_roles (#16098) 2021-08-06 10:05:19 -07:00
Kamil Gabryjelski
772da8de63 fix(explore): revert dnd column dependency array change to fix infinite rerenders (#16115)
* fix(explore): revert dnd column dependency array change to fix infinite rerenders

* Remove console.log

* Remove comment
2021-08-06 17:59:22 +02:00
ʈᵃᵢ
b80f018691 fix: move watermark to about section (#16097) 2021-08-06 05:48:22 -07:00
Michael S. Molina
6edc1ee3bb chore: Replaces the select for a dropdown button in the CSS editor (#16043) 2021-08-06 07:59:15 -03:00
Michael S. Molina
e59f318ef9 chore: Changes the RefreshIntervalModal component to use the new select component (#16048) 2021-08-06 07:58:53 -03:00
Michael S. Molina
423ff50768 chore: Changes the dashboard properties modal to use the new select component (#16064) 2021-08-05 19:50:16 -07:00
ʈᵃᵢ
2bfc1c29c5 docs: fix link and clarify postgres install instructions (#16101) 2021-08-05 21:53:01 -04:00
David Aaron Suddjian
e6292a89bb fix(explore): drag & drop column select component triggering onChange unnecessarily (#16073)
* fix(explore): avoid sync until after first render

* fix example
2021-08-05 15:38:28 -07:00
David Aaron Suddjian
23072161e2 fix(dashboard): user id can be null when there is an anonymous user (#15592) 2021-08-05 15:35:10 -07:00
Beto Dealmeida
b72fd7b9f4 fix: load tabbed dash only for tests (#16091) 2021-08-05 10:53:22 -07:00
AAfghahi
e6274e0764 change button color (#16093) 2021-08-05 10:38:30 -07:00
Kamil Gabryjelski
af204ff449 chore(explore): bump deckgl to 0.4.9 (#16086) 2021-08-05 18:10:23 +02:00
Beto Dealmeida
1dbd1e9f02 chore: simplify chart permissions (#16078) 2021-08-05 08:18:29 -07:00
Kamil Gabryjelski
a59d458e41 chore(explore): Create new entrypoints for Echarts Timeseries (#15942)
* feat(explore): Create new entrypoints for Echarts Timeseries

* Change order of some charts

* bump superset-ui

* also bump echarts package

* fix UT

Co-authored-by: Ville Brofeldt <ville.v.brofeldt@gmail.com>
2021-08-05 07:28:16 +02:00
Hugh A. Miles II
11a2d4dfdd fix: Remove grey bar for TableElement component when metadata is empty (#16054)
* create serialize json function

* remove grey space with no metadata

* remove console log
2021-08-04 17:07:20 -04:00
Ville Brofeldt
7ef97a54e2 feat(explore): add automatic conditional formatter to pivot table v2 (#16045) 2021-08-04 22:12:20 +03:00
John Bodley
7effa44d54 refactor: adopt --app as celery global option (#16040)
* Update installation.rst

* Update async_queries_celery.mdx
2021-08-05 06:46:03 +12:00
AAfghahi
4359650b7d fix: Adding report bug (#16065)
* report add fix

* added theme
2021-08-04 11:37:46 -07:00
Jon Gillham
7c95595b77 fix(docker-compose): Make db service use correct env file (#15659) 2021-08-04 12:22:46 -06:00
Ville Brofeldt
86cecaeec5 fix(native-filters): add support for boolean cols to select (#16061) 2021-08-04 21:05:55 +03:00
Duy Nguyen Hoang
2c55cc6558 fix: Align alert solid small svg center (#15762) 2021-08-04 12:00:50 -06:00
Michael S. Molina
1917464d2b fix: Fix the Select unselect for object values (#16062) 2021-08-04 11:52:39 -03:00
Ville Brofeldt
7332055ff6 chore: bump superset-ui to 0.17.78 (#16058) 2021-08-04 13:03:34 +03:00
Jesse Yang
490890de23 fix(dashboard): 500 error caused by data_for_slices API (#16053) 2021-08-03 19:01:39 -07:00
399 changed files with 11435 additions and 4763 deletions

6
.github/CODEOWNERS vendored
View File

@@ -3,6 +3,6 @@
/superset/migrations/ @apache/superset-committers
# Notify Preset team when ephemeral env settings are changed
.github/workflows/ecs-task-definition.json @robdiciuccio @craig-rueda @willbarrett @rusackas @eschutho @dpgaspar @nytai @mistercrunch
.github/workflows/docker-ephemeral-env.yml @robdiciuccio @craig-rueda @willbarrett @rusackas @eschutho @dpgaspar @nytai @mistercrunch
.github/workflows/ephemeral*.yml @robdiciuccio @craig-rueda @willbarrett @rusackas @eschutho @dpgaspar @nytai @mistercrunch
.github/workflows/ecs-task-definition.json @robdiciuccio @craig-rueda @rusackas @eschutho @dpgaspar @nytai @mistercrunch
.github/workflows/docker-ephemeral-env.yml @robdiciuccio @craig-rueda @rusackas @eschutho @dpgaspar @nytai @mistercrunch
.github/workflows/ephemeral*.yml @robdiciuccio @craig-rueda @rusackas @eschutho @dpgaspar @nytai @mistercrunch

View File

@@ -11,6 +11,7 @@
<!--- Check any relevant boxes with "x" -->
<!--- HINT: Include "Fixes #nnn" if you are fixing an existing issue -->
- [ ] Has associated issue:
- [ ] Required feature flags:
- [ ] Changes UI
- [ ] Includes DB Migration (follow approval process in [SIP-59](https://github.com/apache/superset/issues/13351))
- [ ] Migration is atomic, supports rollback & is backwards-compatible

View File

@@ -24,9 +24,10 @@ repos:
hooks:
- id: isort
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.790
rev: v0.910
hooks:
- id: mypy
additional_dependencies: [types-all]
- repo: https://github.com/peterdemin/pip-compile-multi
rev: v2.4.1
hooks:

View File

@@ -70,7 +70,8 @@ confidence=
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
#enable=
enable=
useless-suppression,
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
@@ -84,13 +85,10 @@ confidence=
disable=
missing-docstring,
too-many-lines,
ungrouped-imports,
import-outside-toplevel,
raise-missing-from,
super-with-arguments,
too-few-public-methods,
too-many-locals,
duplicate-code,
unspecified-encoding,
# re-enable once this no longer raises false positives
too-many-instance-attributes
[REPORTS]

View File

@@ -17,50 +17,7 @@ specific language governing permissions and limitations
under the License.
-->
## Change Log
### 1.3.1
**Database Migrations**
**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)
### 1.3.0 (2021-08-13)
**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)

View File

@@ -106,7 +106,7 @@ This statement thanks the following, on which it draws for content and inspirati
# Slack Community Guidelines
If you decide to join the [Community Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-l5f5e0av-fyYu8tlfdqbMdz_sPLwUqQ), please adhere to the following rules:
If you decide to join the [Community Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-uxbh5g36-AISUtHbzOXcu0BIj7kgUaw), please adhere to the following rules:
**1. Treat everyone in the community with respect.**

View File

@@ -64,7 +64,7 @@ RUN /frontend-mem-nag.sh \
# Next, copy in the rest and let webpack do its thing
COPY ./superset-frontend /app/superset-frontend
# This is BY FAR the most expensive step (thanks Terser!)
# This seems to be the most expensive step
RUN cd /app/superset-frontend \
&& npm run ${BUILD_CMD} \
&& rm -rf node_modules

View File

@@ -58,7 +58,7 @@ update-py:
update-js:
# Install js packages
cd superset-frontend; npm install
cd superset-frontend; npm ci
venv:
# Create a virtual environment and activate it (recommended)
@@ -66,6 +66,9 @@ venv:
test -d venv || ${PYTHON} -m venv venv # setup a python3 virtualenv
. venv/bin/activate
make activate:
source venv/bin/activate
pre-commit:
# setup pre commit dependencies
pip3 install -r requirements/integration.txt
@@ -81,3 +84,9 @@ py-lint: pre-commit
js-format:
cd superset-frontend; npm run prettier
flask-app:
flask run -p 8088 --with-threads --reload --debugger
node-app:
cd superset-frontend; npm run dev-server

View File

@@ -25,7 +25,7 @@ under the License.
[![PyPI version](https://badge.fury.io/py/apache-superset.svg)](https://badge.fury.io/py/apache-superset)
[![Coverage Status](https://codecov.io/github/apache/superset/coverage.svg?branch=master)](https://codecov.io/github/apache/superset)
[![PyPI](https://img.shields.io/pypi/pyversions/apache-superset.svg?maxAge=2592000)](https://pypi.python.org/pypi/apache-superset)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://join.slack.com/t/apache-superset/shared_invite/zt-l5f5e0av-fyYu8tlfdqbMdz_sPLwUqQ)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://join.slack.com/t/apache-superset/shared_invite/zt-uxbh5g36-AISUtHbzOXcu0BIj7kgUaw)
[![Documentation](https://img.shields.io/badge/docs-apache.org-blue.svg)](https://superset.apache.org)
[![Dependencies Status](https://david-dm.org/apache/superset/status.svg?path=superset-frontend)](https://david-dm.org/apache/superset?path=superset-frontend)
@@ -136,7 +136,7 @@ Want to add support for your datastore or data engine? Read more [here](https://
## Get Involved
- Ask and answer questions on [StackOverflow](https://stackoverflow.com/questions/tagged/apache-superset) using the **apache-superset** tag
- [Join our community's Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-l5f5e0av-fyYu8tlfdqbMdz_sPLwUqQ)
- [Join our community's Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-uxbh5g36-AISUtHbzOXcu0BIj7kgUaw)
and please read our [Slack Community Guidelines](https://github.com/apache/superset/blob/master/CODE_OF_CONDUCT.md#slack-community-guidelines)
- [Join our dev@superset.apache.org Mailing list](https://lists.apache.org/list.html?dev@superset.apache.org)

View File

@@ -29,6 +29,8 @@ on the Superset Slack. People crafting releases and those interested in
partaking in the process should join the channel.
## Release notes for recent releases
- [1.3](release-notes-1-3/README.md)
- [1.2](release-notes-1-2/README.md)
- [1.1](release-notes-1-1/README.md)
- [1.0](release-notes-1-0/README.md)

View File

@@ -384,12 +384,12 @@ def change_log(
with open(csv, "w") as csv_file:
log_items = list(logs)
field_names = log_items[0].keys()
writer = lib_csv.DictWriter(
writer = lib_csv.DictWriter( # type: ignore
csv_file,
delimiter=",",
quotechar='"',
quoting=lib_csv.QUOTE_ALL,
fieldnames=field_names,
fieldnames=field_names, # type: ignore
)
writer.writeheader()
for log in logs:

View File

@@ -0,0 +1,73 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Release Notes for Superset 1.3
Superset 1.3 focuses on hardening and polishing the superset user experience, with tons of UX improvements and bug fixes focused on charts, dashboards, and the new dashboard-native filters.
- [**User Experience**](#user-experience)
- [**PR Highlights**](#pr-highlights)
- [**Breaking Changes and Full Changelog**](#breaking-changes-and-full-changelog)
# User Experience
One major goal of this release is to improve and harden dashboard-native filters. These filters live at the dashboard level instead of within a chart and affect all charts under their scope within a dashboard. Improvements in this release include clearer visual indicators of what charts are within the scope of a selected filter.
![dashboard native filter scoping](media/dashboard_native_filters_1.jpg)
Native-filters can also be set to load collapsed, which also improves connected thumbnail and alerts/reports functionality.
![dashboard native filter collapsed](media/native_filters_collapsed.png)
For charts, we've added a new funnel chart.
![funnel chart](media/funnel_chart.png)
Users can also now use Jinja templating in calculated columns and SQL metrics.
![jinja templating](media/jinja_templating.png)
At the dashboard level, work has been focused on improving available information and UX ergonomics. Users can now download a full .csv of the full dataset behind a table chart from the dashboard.
![export full csv](media/export_full_csv.png)
Continuing on the theme of making more things accessible directly from the dashboard, users can now view the SQL Query behind any chart directly from the dashboard as well.
![view query dashboard](media/view_query_dashboard.png)
# Developer Experience
The API has received a new endpoint to allow the developer to pass DB-specific parameters instead of the full SQLAlchemy URI.
# Database Connectivity
We have improved support for Ascend.io's engine spec and fixed a long list of bugs.
Also in the works is a new database connection UI, which should make connecting to a database easier without having to put together a SQLAlchemy URI. It's behind a feature flag for now, but it can be turned on in config.py with `FORCE_DATABASE_CONNECTIONS_SSL = True`.
# PR Highlights
- [14682](https://github.com/apache/superset/pull/14682) add ascend engine spec (#14682) (@Daniel Wood)
- [14420](https://github.com/apache/superset/pull/14420) feat: API endpoint to validate databases using separate parameters (#14420) (@Beto Dealmeida)
- [14934](https://github.com/apache/superset/pull/14934) feat: Adding FORCE_SSL as feature flag in config.py (#14934) (@AAfghahi)
- [14480](https://github.com/apache/superset/pull/14480) feat(viz): add funnel chart (#14480) (@Ville Brofeldt)
## Breaking Changes and Full Changelog
- To see the complete changelog in this release, head to [CHANGELOG.MD](../../CHANGELOG.md).
- 1.3.0 does not contain any backwards incompatible changes.

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

View File

@@ -61,6 +61,7 @@ Join our growing community!
- [Tails.com](https://tails.com) [@alanmcruickshank]
- [THE ICONIC](http://theiconic.com.au/) [@ksaagariconic]
- [Utair](https://www.utair.ru) [@utair-digital]
- [VkusVill](https://www.vkusvill.ru) [@ETselikov]
- [Zalando](https://www.zalando.com) [@dmigo]
- [Zalora](https://www.zalora.com) [@ksaagariconic]
@@ -96,6 +97,7 @@ Join our growing community!
- [Showmax](https://tech.showmax.com) [@bobek]
- [source{d}](https://www.sourced.tech) [@marnovo]
- [Steamroot](https://streamroot.io/)
- [TechAudit](https://www.techaudit.info) [@ETselikov]
- [Tenable](https://www.tenable.com) [@dflionis]
- [timbr.ai](https://timbr.ai/) [@semantiDan]
- [Tobii](http://www.tobii.com/) [@dwa]
@@ -104,7 +106,6 @@ Join our growing community!
- [Windsor.ai](https://www.windsor.ai/) [@octaviancorlade]
- [Zeta](https://www.zeta.tech/) [@shaikidris]
### Entertainment
- [6play](https://www.6play.fr) [@CoryChaplin]
- [bilibili](https://www.bilibili.com) [@Moinheart]
@@ -129,6 +130,7 @@ Join our growing community!
### Healthcare
- [Amino](https://amino.com) [@shkr]
- [Care](https://www.getcare.io/)[@alandao2021]
- [Living Goods](https://www.livinggoods.org) [@chelule]
- [Maieutical Labs](https://maieuticallabs.it) [@xrmx]
- [QPID Health](http://www.qpidhealth.com/)

View File

@@ -22,11 +22,12 @@ under the License.
This file documents any backwards-incompatible changes in Superset and
assists people when migrating to a new version.
## 1.3.1
## Next
### 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)`.
### Potential Downtime
### Deprecations
### Other
## 1.3.0

View File

@@ -33,7 +33,7 @@ services:
- redis:/data
db:
env_file: docker/.env
env_file: docker/.env-non-dev
image: postgres:10
container_name: superset_db
restart: unless-stopped

View File

@@ -1465,7 +1465,7 @@ install from pip: ::
and run via: ::
celery flower --app=superset.tasks.celery_app:app
celery --app=superset.tasks.celery_app:app flower
Building from source
---------------------

View File

@@ -26,7 +26,7 @@ import { pmc } from '../resources/data';
const links = [
[
'https://join.slack.com/t/apache-superset/shared_invite/zt-l5f5e0av-fyYu8tlfdqbMdz_sPLwUqQ',
'https://join.slack.com/t/apache-superset/shared_invite/zt-uxbh5g36-AISUtHbzOXcu0BIj7kgUaw',
'Slack',
'interact with other Superset users and community members',
],

View File

@@ -47,7 +47,7 @@ A list of some of the recommended packages.
|[IBM Netezza Performance Server](/docs/databases/netezza)|```pip install nzalchemy```|```netezza+nzpy://<UserName>:<DBPassword>@<Database Host>/<Database Name>```|
|[MySQL](/docs/databases/mysql)|```pip install mysqlclient```|```mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>```|
|[Oracle](/docs/databases/oracle)|```pip install cx_Oracle```|```oracle://```|
|[PostgreSQL](/docs/databases/postgresql)|```pip install psycopg2```|```postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>```|
|[PostgreSQL](/docs/databases/postgres)|```pip install psycopg2```|```postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>```|
|[Trino](/docs/databases/trino)|```pip install sqlalchemy-trino```|```trino://{username}:{password}@{hostname}:{port}/{catalog}```|
|[Presto](/docs/databases/presto)|```pip install pyhive```|```presto://```|
|[SAP Hana](/docs/databases/hana)|```pip install hdbcli sqlalchemy-hana or pip install apache-superset[hana]```|```hana://{username}:{password}@{host}:{port}```|
@@ -69,5 +69,5 @@ exists, please file an issue on the
supporting it.
[StackOverflow](https://stackoverflow.com/questions/tagged/apache-superset+superset) and the
[Superset community Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-l5f5e0av-fyYu8tlfdqbMdz_sPLwUqQ)
[Superset community Slack](https://join.slack.com/t/apache-superset/shared_invite/zt-uxbh5g36-AISUtHbzOXcu0BIj7kgUaw)
are great places to get help with connecting to databases in Superset.

View File

@@ -8,8 +8,8 @@ version: 1
## Postgres
Note that the Postgres connector library [psycopg2](https://www.psycopg.org/docs/) comes out of the
box with Superset.
Note that, if you're using docker-compose, the Postgres connector library [psycopg2](https://www.psycopg.org/docs/)
comes out of the box with Superset.
Postgres sample connection parameters:

View File

@@ -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 **Vehicle Sales** table from the **examples** database.
we register the **cleaned_sales_data** table from the **examples** database.
<img src="/images/tutorial_09_add_new_table.png" />

View File

@@ -11,7 +11,7 @@ The core contributors (or committers) to Superset communicate primarily in the f
which you can join):
- [Mailing list](https://lists.apache.org/list.html?dev@superset.apache.org)
- [Apache Superset Slack community](https://join.slack.com/t/apache-superset/shared_invite/zt-l5f5e0av-fyYu8tlfdqbMdz_sPLwUqQ)
- [Apache Superset Slack community](https://join.slack.com/t/apache-superset/shared_invite/zt-uxbh5g36-AISUtHbzOXcu0BIj7kgUaw)
- [Github issues and PR's](https://github.com/apache/superset/issues)
If you're interested in contributing, we recommend reading the Community Contribution Guide

View File

@@ -114,5 +114,5 @@ pip install flower
You can run flower using:
```
celery flower --app=superset.tasks.celery_app:app
celery --app=superset.tasks.celery_app:app flower
```

View File

@@ -201,6 +201,29 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
]
```
### Flask app Configuration Hook
`FLASK_APP_MUTATOR` is a configuration function that can be provided in your environment, receives
the app object and can alter it in any way. For example, add `FLASK_APP_MUTATOR` into your
`superset_config.py` to setup session cookie expiration time to 24 hours:
```python
from flask import session
from flask import Flask
def make_session_permanent():
'''
Enable maxAge for the cookie 'session'
'''
session.permanent = True
# Set up max age of session to 24 hours
PERMANENT_SESSION_LIFETIME = timedelta(hours=24)
def FLASK_APP_MUTATOR(app: Flask) -> None:
app.before_request_funcs.setdefault(None, []).append(make_session_permanent)
```
### Feature Flags
To support a diverse set of users, Superset has some features that are not enabled by default. For
@@ -218,7 +241,7 @@ FEATURE_FLAGS = {
}
```
A current list of feature flags can be found in `RESOURCES/FEATURE_FLAGS.md`
A current list of feature flags can be found in [RESOURCES/FEATURE_FLAGS.md](https://github.com/apache/superset/blob/master/RESOURCES/FEATURE_FLAGS.md).
### SIP 15

View File

@@ -36,6 +36,18 @@ Install the following packages using the `yum` package manager:
sudo yum install gcc gcc-c++ libffi-devel python-devel python-pip python-wheel openssl-devel cyrus-sasl-devel openldap-devel
```
In more recent versions of CentOS and Fedora, you may need to install a slightly different set of packages using `dnf`:
```
sudo dnf install gcc gcc-c++ libffi-devel python3-devel python3-pip python3-wheel openssl-devel cyrus-sasl-devel openldap-devel
```
Also, on CentOS, you may need to upgrade pip for the install to work:
```
pip3 install --upgrade pip
```
**Mac OS X**
If you're not on the latest version of OS X, we recommend upgrading because we've found that many

View File

@@ -7,7 +7,7 @@ route: /docs/security
### Roles
Security in Superset is handled by Flask AppBuilder (FAB), an application development framework
built on top of Flask.”. FAB provides authentication, user management, permissions and roles.
built on top of Flask. FAB provides authentication, user management, permissions and roles.
Please read its [Security documentation](https://flask-appbuilder.readthedocs.io/en/latest/security.html).
### Provided Roles
@@ -15,7 +15,7 @@ Please read its [Security documentation](https://flask-appbuilder.readthedocs.io
Superset ships with a set of roles that are handled by Superset itself. You can assume
that these roles will stay up-to-date as Superset evolves (and as you update Superset versions).
Even though **Admin** users have the ability, we don't recommend that altering the
Even though **Admin** users have the ability, we don't recommend altering the
permissions associated with each role (e.g. by removing or adding permissions to them). The permissions
associated with each role will be re-synchronized to their original values when you run
the **superset init** command (often done between Superset versions).

View File

@@ -22,7 +22,7 @@ maintainers:
- name: craig-rueda
email: craig@craigrueda.com
url: https://github.com/craig-rueda
version: 0.3.5
version: 0.3.6
dependencies:
- name: postgresql
version: 10.2.0

View File

@@ -86,6 +86,9 @@ spec:
- name: superset-config
mountPath: {{ .Values.configMountPath | quote }}
readOnly: true
{{- with .Values.extraVolumeMounts }}
{{- tpl (toYaml .) $ | nindent 12 -}}
{{- end }}
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
@@ -108,4 +111,7 @@ spec:
- name: superset-config
secret:
secretName: {{ tpl .Values.configFromSecret . }}
{{- with .Values.extraVolumes }}
{{- tpl (toYaml .) $ | nindent 8 -}}
{{- end }}
{{- end -}}

View File

@@ -87,6 +87,9 @@ spec:
- name: superset-config
mountPath: {{ .Values.configMountPath | quote }}
readOnly: true
{{- with .Values.extraVolumeMounts }}
{{- tpl (toYaml .) $ | nindent 12 -}}
{{- end }}
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- with .Values.nodeSelector }}
@@ -109,3 +112,6 @@ spec:
- name: superset-config
secret:
secretName: {{ tpl .Values.configFromSecret . }}
{{- with .Values.extraVolumes }}
{{- tpl (toYaml .) $ | nindent 8 -}}
{{- end }}

View File

@@ -95,6 +95,9 @@ spec:
mountPath: {{ .Values.extraConfigMountPath | quote }}
readOnly: true
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- tpl (toYaml .) $ | nindent 12 -}}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
@@ -127,3 +130,6 @@ spec:
configMap:
name: {{ template "superset.fullname" . }}-extra-config
{{- end }}
{{- with .Values.extraVolumes }}
{{- tpl (toYaml .) $ | nindent 8 -}}
{{- end }}

View File

@@ -60,6 +60,9 @@ spec:
mountPath: {{ .Values.extraConfigMountPath | quote }}
readOnly: true
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- tpl (toYaml .) $ | nindent 10 -}}
{{- end }}
command: {{ tpl (toJson .Values.init.command) . }}
resources:
{{ toYaml .Values.init.resources | indent 10 }}
@@ -76,5 +79,8 @@ spec:
configMap:
name: {{ template "superset.fullname" . }}-extra-config
{{- end }}
{{- with .Values.extraVolumes }}
{{- tpl (toYaml .) $ | nindent 8 -}}
{{- end }}
restartPolicy: Never
{{- end }}

View File

@@ -82,6 +82,22 @@ extraConfigs: {}
extraSecrets: {}
extraVolumes: []
# - name: customConfig
# configMap:
# name: '{{ template "superset.fullname" . }}-custom-config'
# - name: additionalSecret
# secret:
# secretName: my-secret
# defaultMode: 0600
extraVolumeMounts: []
# - name: customConfig
# mountPath: /mnt/config
# readOnly: true
# - name: additionalSecret:
# mountPath: /mnt/secret
# A dictionary of overrides to append at the end of superset_config.py - the name does not matter
# WARNING: the order is not guaranteed
configOverrides: {}

View File

@@ -18,3 +18,4 @@
-e file:.
pyrsistent>=0.16.1,<0.17
zipp==3.4.1
sasl==0.2.1

View File

@@ -1,4 +1,4 @@
# SHA1:0862095245a068ae2fc00217da78331e1e7ae505
# SHA1:57a754a4cf09b58d8e02c45bfb1058d2ce4286a6
#
# This file is autogenerated by pip-compile-multi
# To update, run:
@@ -7,9 +7,9 @@
#
-e file:.
# via -r requirements/base.in
aiohttp==3.7.2
aiohttp==3.7.4.post0
# via slackclient
alembic==1.4.3
alembic==1.6.5
# via flask-migrate
amqp==2.6.1
# via kombu
@@ -17,17 +17,17 @@ apispec[yaml]==3.3.2
# via flask-appbuilder
async-timeout==3.0.1
# via aiohttp
attrs==20.2.0
attrs==21.2.0
# via
# aiohttp
# jsonschema
babel==2.8.0
babel==2.9.1
# via flask-babel
backoff==1.10.0
backoff==1.11.1
# via apache-superset
billiard==3.6.3.0
billiard==3.6.4.0
# via celery
bleach==3.3.0
bleach==3.3.1
# via apache-superset
brotli==1.0.9
# via flask-compress
@@ -35,9 +35,9 @@ cachelib==0.1.1
# via apache-superset
celery==4.4.7
# via apache-superset
cffi==1.14.3
cffi==1.14.6
# via cryptography
chardet==3.0.4
chardet==4.0.0
# via aiohttp
click==7.1.2
# via
@@ -48,23 +48,23 @@ colorama==0.4.4
# via
# apache-superset
# flask-appbuilder
convertdate==2.3.0
convertdate==2.3.2
# via holidays
cron-descriptor==1.2.24
# via apache-superset
croniter==0.3.36
croniter==1.0.15
# via apache-superset
cryptography==3.3.2
cryptography==3.4.7
# via apache-superset
defusedxml==0.6.0
defusedxml==0.7.1
# via python3-openid
deprecation==2.1.0
# via apache-superset
dnspython==2.0.0
dnspython==2.1.0
# via email-validator
email-validator==1.1.1
email-validator==1.1.3
# via flask-appbuilder
flask==1.1.2
flask==1.1.4
# via
# apache-superset
# flask-appbuilder
@@ -77,35 +77,35 @@ flask==1.1.2
# flask-openid
# flask-sqlalchemy
# flask-wtf
flask-appbuilder==3.3.0
flask-appbuilder==3.3.2
# via apache-superset
flask-babel==1.0.0
# via flask-appbuilder
flask-caching==1.10.1
# via apache-superset
flask-compress==1.8.0
flask-compress==1.10.1
# via apache-superset
flask-jwt-extended==3.24.1
flask-jwt-extended==3.25.1
# via flask-appbuilder
flask-login==0.4.1
# via flask-appbuilder
flask-migrate==2.5.3
flask-migrate==3.1.0
# via apache-superset
flask-openid==1.3.0
flask-openid==1.2.5
# via flask-appbuilder
flask-sqlalchemy==2.4.4
flask-sqlalchemy==2.5.1
# via
# flask-appbuilder
# flask-migrate
flask-talisman==0.7.0
flask-talisman==0.8.1
# via apache-superset
flask-wtf==0.14.3
# via
# apache-superset
# flask-appbuilder
geographiclib==1.50
geographiclib==1.52
# via geopy
geopy==2.0.0
geopy==2.2.0
# via apache-superset
graphlib-backport==1.0.3
# via apache-superset
@@ -113,9 +113,9 @@ gunicorn==20.0.4
# via apache-superset
holidays==0.10.3
# via apache-superset
humanize==3.1.0
humanize==3.11.0
# via apache-superset
idna==2.10
idna==3.2
# via
# email-validator
# yarl
@@ -136,16 +136,16 @@ kombu==4.6.11
# via celery
korean-lunar-calendar==0.2.1
# via holidays
mako==1.1.3
mako==1.1.4
# via alembic
markdown==3.3.3
markdown==3.3.4
# via apache-superset
markupsafe==1.1.1
markupsafe==2.0.1
# via
# jinja2
# mako
# wtforms
marshmallow==3.9.0
marshmallow==3.13.0
# via
# flask-appbuilder
# marshmallow-enum
@@ -154,23 +154,21 @@ marshmallow-enum==1.5.1
# via flask-appbuilder
marshmallow-sqlalchemy==0.23.1
# via flask-appbuilder
msgpack==1.0.0
msgpack==1.0.2
# via apache-superset
multidict==5.0.0
multidict==5.1.0
# via
# aiohttp
# yarl
natsort==7.0.1
# via croniter
numpy==1.19.4
numpy==1.21.1
# via
# pandas
# pyarrow
packaging==20.4
packaging==21.0
# via
# bleach
# deprecation
pandas==1.2.2
pandas==1.2.5
# via apache-superset
parsedatetime==2.6
# via apache-superset
@@ -189,7 +187,7 @@ pyjwt==1.7.1
# apache-superset
# flask-appbuilder
# flask-jwt-extended
pymeeus==0.3.7
pymeeus==0.5.11
# via convertdate
pyparsing==2.4.7
# via
@@ -199,7 +197,7 @@ pyrsistent==0.16.1
# via
# -r requirements/base.in
# jsonschema
python-dateutil==2.8.1
python-dateutil==2.8.2
# via
# alembic
# apache-superset
@@ -207,7 +205,7 @@ python-dateutil==2.8.1
# flask-appbuilder
# holidays
# pandas
python-dotenv==0.15.0
python-dotenv==0.19.0
# via apache-superset
python-editor==1.0.4
# via alembic
@@ -215,7 +213,7 @@ python-geohash==0.8.5
# via apache-superset
python3-openid==3.2.0
# via flask-openid
pytz==2020.4
pytz==2021.1
# via
# babel
# celery
@@ -228,29 +226,30 @@ pyyaml==5.4.1
# apispec
redis==3.5.3
# via apache-superset
sasl==0.2.1
# via -r requirements/base.in
selenium==3.141.0
# via apache-superset
simplejson==3.17.2
simplejson==3.17.3
# via apache-superset
six==1.15.0
six==1.16.0
# via
# bleach
# cryptography
# flask-jwt-extended
# flask-talisman
# holidays
# isodate
# jsonschema
# packaging
# polyline
# prison
# pyrsistent
# python-dateutil
# sasl
# sqlalchemy-utils
# wtforms-json
slackclient==2.5.0
# via apache-superset
sqlalchemy==1.3.20
sqlalchemy==1.3.24
# via
# alembic
# apache-superset
@@ -266,11 +265,11 @@ sqlparse==0.3.0
# via apache-superset
tabulate==0.8.9
# via apache-superset
typing-extensions==3.7.4.3
typing-extensions==3.10.0.0
# via
# aiohttp
# apache-superset
urllib3==1.25.11
urllib3==1.26.6
# via selenium
vine==1.3.0
# via
@@ -288,7 +287,7 @@ wtforms==2.3.3
# wtforms-json
wtforms-json==0.3.3
# via apache-superset
yarl==1.6.2
yarl==1.6.3
# via aiohttp
zipp==3.4.1
# via -r requirements/base.in

View File

@@ -24,5 +24,5 @@ pyhive[hive]>=0.6.1
psycopg2-binary==2.8.5
tableschema
thrift>=0.11.0,<1.0.0
pygithub>=1.54.1,<2.0.0
progress>=1.5,<2
pyinstrument>=4.0.2,<5

View File

@@ -1,4 +1,4 @@
# SHA1:c470411e2e9cb04b412a94f80a6a9d870bece74d
# SHA1:e4f3ea65026a8aec3735d6d9977f89fef4a1a4f9
#
# This file is autogenerated by pip-compile-multi
# To update, run:
@@ -8,84 +8,77 @@
-r base.txt
-e file:.
# via -r requirements/base.in
boto3==1.16.10
boto3==1.18.19
# via tabulator
botocore==1.19.10
botocore==1.21.19
# via
# boto3
# s3transfer
cached-property==1.5.2
# via tableschema
certifi==2020.6.20
certifi==2021.5.30
# via requests
deprecated==1.2.11
# via pygithub
et-xmlfile==1.0.1
charset-normalizer==2.0.4
# via requests
et-xmlfile==1.1.0
# via openpyxl
flask-cors==3.0.9
flask-cors==3.0.10
# via -r requirements/development.in
future==0.18.2
# via pyhive
ijson==3.1.2.post0
ijson==3.1.4
# via tabulator
jdcal==1.4.1
# via openpyxl
jmespath==0.10.0
# via
# boto3
# botocore
jsonlines==1.2.0
jsonlines==2.0.0
# via tabulator
linear-tsv==1.1.0
# via tabulator
mysqlclient==1.4.2.post1
# via -r requirements/development.in
openpyxl==3.0.5
openpyxl==3.0.7
# via tabulator
pillow==7.2.0
# via -r requirements/development.in
progress==1.5
progress==1.6
# via -r requirements/development.in
psycopg2-binary==2.8.5
# via -r requirements/development.in
pydruid==0.6.1
pure-sasl==0.6.2
# via thrift-sasl
pydruid==0.6.2
# via -r requirements/development.in
pygithub==1.54.1
pyhive[hive]==0.6.4
# via -r requirements/development.in
pyhive[hive]==0.6.3
pyinstrument==4.0.2
# via -r requirements/development.in
requests==2.24.0
requests==2.26.0
# via
# pydruid
# pygithub
# tableschema
# tabulator
rfc3986==1.4.0
rfc3986==1.5.0
# via tableschema
s3transfer==0.3.3
s3transfer==0.5.0
# via boto3
sasl==0.2.1
# via
# pyhive
# thrift-sasl
tableschema==1.20.0
tableschema==1.20.2
# via -r requirements/development.in
tabulator==1.52.5
tabulator==1.53.5
# via tableschema
thrift==0.13.0
# via
# -r requirements/development.in
# pyhive
# thrift-sasl
thrift-sasl==0.4.2
thrift-sasl==0.4.3
# via pyhive
unicodecsv==0.14.1
# via
# tableschema
# tabulator
wrapt==1.12.1
# via deprecated
xlrd==1.2.0
xlrd==2.0.1
# via tabulator
# The following packages are considered to be unsafe in a requirements file:

View File

@@ -8,15 +8,15 @@
-r base.txt
-e file:.
# via -r requirements/base.in
gevent==20.9.0
gevent==21.8.0
# via -r requirements/docker.in
greenlet==0.4.17
greenlet==1.1.1
# via gevent
psycopg2-binary==2.8.6
psycopg2-binary==2.9.1
# via -r requirements/docker.in
zope.event==4.5.0
# via gevent
zope.interface==5.1.2
zope.interface==5.4.0
# via gevent
# The following packages are considered to be unsafe in a requirements file:

View File

@@ -17,3 +17,6 @@
pip-compile-multi!=1.5.9
pre-commit
tox
py>=1.10.0
click==7.1.2
packaging==21.0

View File

@@ -1,62 +1,74 @@
# SHA1:f95c1152ed0bcc554f3668440d63eec2a7d1567c
# SHA1:17ab2346746deadfc557e1df96014e77c8337f4b
#
# This file is autogenerated by pip-compile-multi
# To update, run:
#
# pip-compile-multi
#
appdirs==1.4.4
backports.entry-points-selectable==1.1.0
# via virtualenv
cfgv==3.2.0
cfgv==3.3.0
# via pre-commit
click==7.1.2
# via
# -r requirements/integration.in
# pip-compile-multi
# pip-tools
distlib==0.3.1
distlib==0.3.2
# via virtualenv
filelock==3.0.12
# via
# tox
# virtualenv
identify==1.5.9
identify==2.2.13
# via pre-commit
nodeenv==1.5.0
nodeenv==1.6.0
# via pre-commit
packaging==20.4
# via tox
packaging==21.0
# via
# -r requirements/integration.in
# tox
pep517==0.11.0
# via pip-tools
pip-compile-multi==2.4.1
# via -r requirements/integration.in
pip-tools==5.3.1
pip-tools==6.2.0
# via pip-compile-multi
platformdirs==2.2.0
# via virtualenv
pluggy==0.13.1
# via tox
pre-commit==2.8.2
pre-commit==2.14.0
# via -r requirements/integration.in
py==1.9.0
# via tox
py==1.10.0
# via
# -r requirements/integration.in
# tox
pyparsing==2.4.7
# via packaging
pyyaml==5.4.1
# via pre-commit
six==1.15.0
six==1.16.0
# via
# packaging
# pip-tools
# tox
# virtualenv
toml==0.10.2
# via
# pre-commit
# tox
toposort==1.5
tomli==1.2.1
# via pep517
toposort==1.6
# via pip-compile-multi
tox==3.20.1
tox==3.24.1
# via -r requirements/integration.in
virtualenv==20.1.0
virtualenv==20.7.2
# via
# pre-commit
# tox
wheel==0.37.0
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:
# pip
# setuptools

View File

@@ -22,14 +22,15 @@ freezegun
ipdb
# pinning ipython as pip-compile-multi was bringing higher version
# of the ipython that was not found in CI
ipython==7.16.1
ipython
openapi-spec-validator
openpyxl
parameterized
pyfakefs
pyhive[presto]>=0.6.3
pylint
pylint==2.10.2
pytest
pytest-cov
statsd
pytest-mock
packaging==21.0

View File

@@ -1,4 +1,4 @@
# SHA1:d39180c0eb498d1a7dd73b8428e6ab304b728484
# SHA1:59e47200215ca4695f09e03a773e1a6f310f78da
#
# This file is autogenerated by pip-compile-multi
# To update, run:
@@ -11,79 +11,89 @@
# via -r requirements/base.in
appnope==0.1.2
# via ipython
astroid==2.4.2
astroid==2.7.2
# via pylint
backcall==0.2.0
# via ipython
coverage==5.3
coverage==5.5
# via pytest-cov
decorator==5.0.9
# via ipython
docker==4.3.1
# via
# ipdb
# ipython
docker==5.0.0
# via -r requirements/testing.in
flask-testing==0.8.0
flask-testing==0.8.1
# via -r requirements/testing.in
freezegun==1.0.0
freezegun==1.1.0
# via -r requirements/testing.in
iniconfig==1.1.1
# via pytest
ipdb==0.13.4
ipdb==0.13.9
# via -r requirements/testing.in
ipython==7.16.1
ipython==7.26.0
# via
# -r requirements/testing.in
# ipdb
ipython-genutils==0.2.0
# via traitlets
isort==5.6.4
isort==5.9.3
# via pylint
jedi==0.17.2
jedi==0.18.0
# via ipython
lazy-object-proxy==1.4.3
lazy-object-proxy==1.6.0
# via astroid
matplotlib-inline==0.1.2
# via ipython
mccabe==0.6.1
# via pylint
openapi-spec-validator==0.2.9
openapi-schema-validator==0.1.5
# via openapi-spec-validator
openapi-spec-validator==0.3.1
# via -r requirements/testing.in
parameterized==0.7.4
parameterized==0.8.1
# via -r requirements/testing.in
parso==0.7.1
parso==0.8.2
# via jedi
pexpect==4.8.0
# via ipython
pickleshare==0.7.5
# via ipython
prompt-toolkit==3.0.8
prompt-toolkit==3.0.19
# via ipython
ptyprocess==0.6.0
ptyprocess==0.7.0
# via pexpect
pyfakefs==4.4.0
pyfakefs==4.5.0
# via -r requirements/testing.in
pygments==2.7.2
pygments==2.9.0
# via ipython
pyhive[hive,presto]==0.6.3
pyhive[hive,presto]==0.6.4
# via
# -r requirements/development.in
# -r requirements/testing.in
pylint==2.6.0
pylint==2.10.2
# via -r requirements/testing.in
pytest==6.1.2
pytest==6.2.4
# via
# -r requirements/testing.in
# pytest-cov
# pytest-mock
pytest-cov==2.10.1
pytest-cov==2.12.1
# via -r requirements/testing.in
pytest-mock==3.6.1
# via -r requirements/testing.in
statsd==3.3.0
# via -r requirements/testing.in
traitlets==5.0.5
# via ipython
# via
# ipython
# matplotlib-inline
wcwidth==0.2.5
# via prompt-toolkit
websocket-client==0.57.0
websocket-client==1.2.0
# via docker
wrapt==1.12.1
# via astroid
# The following packages are considered to be unsafe in a requirements file:
# pip

View File

@@ -44,9 +44,13 @@ def import_migration_script(filepath: Path) -> ModuleType:
Import migration script as if it were a module.
"""
spec = importlib.util.spec_from_file_location(filepath.stem, filepath)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore
return module
if spec:
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore
return module
raise Exception(
"No module spec found in location: `{path}`".format(path=str(filepath))
)
def extract_modified_tables(module: ModuleType) -> Set[str]:

View File

@@ -34,7 +34,7 @@ REGEXES=()
for CHECK in "$@"
do
if [[ ${CHECK} == "python" ]]; then
REGEX="(^\.github\/workflows\/.*python|^tests\/|^superset\/|^setup\.py|^requirements\/.+\.txt)"
REGEX="(^\.github\/workflows\/.*python|^tests\/|^superset\/|^setup\.py|^requirements\/.+\.txt|^\.pylintrc)"
echo "Searching for changes in python files"
elif [[ ${CHECK} == "frontend" ]]; then
REGEX="(^\.github\/workflows\/.*(frontend|e2e)|^superset-frontend\/)"

View File

@@ -30,7 +30,7 @@ combine_as_imports = true
include_trailing_comma = true
line_length = 88
known_first_party = superset
known_third_party =alembic,apispec,backoff,bleach,cachelib,celery,click,colorama,cron_descriptor,croniter,cryptography,dateutil,deprecation,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_jwt_extended,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,freezegun,geohash,geopy,graphlib,holidays,humanize,isodate,jinja2,jwt,markdown,markupsafe,marshmallow,marshmallow_enum,msgpack,numpy,pandas,parameterized,parsedatetime,pgsanity,pkg_resources,polyline,prison,progress,pyarrow,pyhive,pyparsing,pytest,pytest_mock,pytz,redis,requests,selenium,setuptools,simplejson,slack,sqlalchemy,sqlalchemy_utils,sqlparse,tabulate,typing_extensions,werkzeug,wtforms,wtforms_json,yaml
known_third_party =alembic,apispec,backoff,bleach,cachelib,celery,click,colorama,cron_descriptor,croniter,cryptography,dateutil,deprecation,flask,flask_appbuilder,flask_babel,flask_caching,flask_compress,flask_jwt_extended,flask_login,flask_migrate,flask_sqlalchemy,flask_talisman,flask_testing,flask_wtf,freezegun,geohash,geopy,graphlib,holidays,humanize,isodate,jinja2,jwt,markdown,markupsafe,marshmallow,marshmallow_enum,msgpack,numpy,pandas,parameterized,parsedatetime,pgsanity,pkg_resources,polyline,prison,progress,pyarrow,pyhive,pyparsing,pytest,pytest_mock,pytz,redis,requests,selenium,setuptools,simplejson,slack,sqlalchemy,sqlalchemy_utils,sqlparse,typing_extensions,urllib3,werkzeug,wtforms,wtforms_json,yaml
multi_line_output = 3
order_by_type = false

View File

@@ -106,14 +106,15 @@ setup(
"simplejson>=3.15.0",
"slackclient==2.5.0", # PINNED! slack changes file upload api in the future versions
"sqlalchemy>=1.3.16, <1.4, !=1.3.21",
"sqlalchemy-utils>=0.36.6,<0.37",
"sqlalchemy-utils>=0.36.6, <0.37",
"sqlparse==0.3.0", # PINNED! see https://github.com/andialbrecht/sqlparse/issues/562
"tabulate==0.8.9",
"typing-extensions>=3.7.4.3,<4", # needed to support typing.Literal on py37
"typing-extensions>=3.10, <4", # needed to support Literal (3.8) and TypeGuard (3.10)
"wtforms-json",
],
extras_require={
"athena": ["pyathena>=1.10.8,<1.11"],
"athena": ["pyathena>=1.10.8, <1.11"],
"aurora-data-api": ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"],
"bigquery": [
"pandas_gbq>=0.10.0",
"pybigquery>=0.4.10",

View File

@@ -82,5 +82,8 @@ module.exports = {
],
],
},
testableProduction: {
plugins: [],
},
},
};

View File

@@ -17,6 +17,6 @@ specific language governing permissions and limitations
under the License.
-->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.335 8.735L7.30998 1.735C7.04507 1.26004 6.54382 0.965683 5.99998 0.965683C5.45614 0.965683 4.9549 1.26004 4.68998 1.735L0.689984 8.735C0.416009 9.19706 0.410037 9.77036 0.674327 10.238C0.938617 10.7057 1.43282 10.9963 1.96998 11H10.03C10.5716 11.0053 11.0741 10.7182 11.3445 10.2489C11.6149 9.77957 11.6113 9.20091 11.335 8.735Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.5 4.5C5.5 4.22386 5.72386 4 6 4C6.27614 4 6.5 4.22386 6.5 4.5V6.5C6.5 6.77614 6.27614 7 6 7C5.72386 7 5.5 6.77614 5.5 6.5V4.5ZM5.5 8.5C5.5 8.22386 5.72386 8 6 8C6.27614 8 6.5 8.22386 6.5 8.5C6.5 8.77614 6.27614 9 6 9C5.72386 9 5.5 8.77614 5.5 8.5Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.345929226875306,14.745929226875305 L13.320909226875305,7.7459292268753055 C13.055999226875306,7.270969226875306 12.554749226875305,6.9766122268753055 12.010909226875306,6.9766122268753055 C11.467069226875305,6.9766122268753055 10.965829226875307,7.270969226875306 10.700909226875304,7.7459292268753055 L6.700913226875306,14.745929226875305 C6.426938226875307,15.207989226875306 6.420966226875306,15.781289226875305 6.685256226875307,16.248929226875305 C6.949546226875305,16.716629226875305 7.443749226875305,17.007229226875303 7.980909226875305,17.010929226875305 H16.040929226875306 C16.582529226875305,17.016229226875307 17.085029226875307,16.729129226875305 17.355429226875305,16.259829226875304 C17.625829226875304,15.790499226875305 17.622229226875305,15.211839226875306 17.345929226875306,14.745929226875305 z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.510929226875305,10.510929226875305 C11.510929226875305,10.234789226875305 11.734789226875307,10.010929226875307 12.010929226875305,10.010929226875307 C12.287069226875303,10.010929226875307 12.510929226875305,10.234789226875305 12.510929226875305,10.510929226875305 V12.510929226875305 C12.510929226875305,12.787069226875305 12.287069226875303,13.010929226875307 12.010929226875305,13.010929226875307 C11.734789226875307,13.010929226875307 11.510929226875305,12.787069226875305 11.510929226875305,12.510929226875305 V10.510929226875305 zM11.510929226875305,14.510929226875305 C11.510929226875305,14.234789226875305 11.734789226875307,14.010929226875307 12.010929226875305,14.010929226875307 C12.287069226875303,14.010929226875307 12.510929226875305,14.234789226875305 12.510929226875305,14.510929226875305 C12.510929226875305,14.787069226875305 12.287069226875303,15.010929226875307 12.010929226875305,15.010929226875307 C11.734789226875307,15.010929226875307 11.510929226875305,14.787069226875305 11.510929226875305,14.510929226875305 z" fill="white" />
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -19,8 +19,8 @@
module.exports = {
testRegex: '(\\/spec|\\/src)\\/.*(_spec|\\.test)\\.(j|t)sx?$',
moduleNameMapper: {
'\\.(css|less)$': '<rootDir>/spec/__mocks__/styleMock.js',
'\\.(gif|ttf|eot|png|jpg)$': '<rootDir>/spec/__mocks__/fileMock.js',
'\\.(css|less|geojson)$': '<rootDir>/spec/__mocks__/mockExportObject.js',
'\\.(gif|ttf|eot|png|jpg)$': '<rootDir>/spec/__mocks__/mockExportString.js',
'\\.svg$': '<rootDir>/spec/__mocks__/svgrMock.tsx',
'^src/(.*)$': '<rootDir>/src/$1',
'^spec/(.*)$': '<rootDir>/spec/$1',

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "superset",
"version": "1.3.1",
"version": "0.0.0dev",
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
"license": "Apache-2.0",
"directories": {
@@ -17,7 +17,7 @@
"prod": "npm run build",
"build-dev": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=development webpack --mode=development --colors",
"build-instrumented": "cross-env NODE_ENV=development BABEL_ENV=instrumented webpack --mode=development --colors",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=production webpack --mode=production --colors",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=\"${BABEL_ENV:=production}\" webpack --mode=production --colors",
"lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx . && npm run type",
"prettier-check": "prettier --check '{src,stylesheets}/**/*.{css,less,sass,scss}'",
"lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx,.ts,tsx . && npm run clean-css && npm run type",
@@ -65,37 +65,38 @@
"@babel/runtime-corejs3": "^7.12.5",
"@data-ui/sparkline": "^0.0.84",
"@emotion/babel-preset-css-prop": "^11.2.0",
"@emotion/cache": "^11.1.3",
"@emotion/react": "^11.1.5",
"@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",
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@superset-ui/chart-controls": "^0.18.2",
"@superset-ui/core": "^0.18.2",
"@superset-ui/legacy-plugin-chart-calendar": "^0.18.2",
"@superset-ui/legacy-plugin-chart-chord": "^0.18.2",
"@superset-ui/legacy-plugin-chart-country-map": "^0.18.2",
"@superset-ui/legacy-plugin-chart-event-flow": "^0.18.2",
"@superset-ui/legacy-plugin-chart-force-directed": "^0.18.2",
"@superset-ui/legacy-plugin-chart-heatmap": "^0.18.2",
"@superset-ui/legacy-plugin-chart-histogram": "^0.18.2",
"@superset-ui/legacy-plugin-chart-horizon": "^0.18.2",
"@superset-ui/legacy-plugin-chart-map-box": "^0.18.2",
"@superset-ui/legacy-plugin-chart-paired-t-test": "^0.18.2",
"@superset-ui/legacy-plugin-chart-parallel-coordinates": "^0.18.2",
"@superset-ui/legacy-plugin-chart-partition": "^0.18.2",
"@superset-ui/legacy-plugin-chart-pivot-table": "^0.18.2",
"@superset-ui/legacy-plugin-chart-rose": "^0.18.2",
"@superset-ui/legacy-plugin-chart-sankey": "^0.18.2",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.18.2",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.2",
"@superset-ui/legacy-plugin-chart-treemap": "^0.18.2",
"@superset-ui/legacy-plugin-chart-world-map": "^0.18.2",
"@superset-ui/legacy-preset-chart-big-number": "^0.18.2",
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.12",
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.2",
"@superset-ui/plugin-chart-echarts": "^0.18.2",
"@superset-ui/plugin-chart-pivot-table": "^0.18.2",
"@superset-ui/plugin-chart-table": "^0.18.2",
"@superset-ui/plugin-chart-word-cloud": "^0.18.2",
"@superset-ui/preset-chart-xy": "^0.18.2",
"@vx/responsive": "^0.0.195",
"abortcontroller-polyfill": "^1.1.9",
"antd": "^4.9.4",
@@ -147,6 +148,7 @@
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^16.13.0",
"react-draggable": "^4.4.3",
"react-gravatar": "^2.6.1",
"react-hot-loader": "^4.12.20",
"react-js-cron": "^1.2.0",
@@ -299,7 +301,6 @@
"storybook-addon-jsx": "^7.3.3",
"storybook-addon-paddings": "^3.2.0",
"style-loader": "^1.0.0",
"terser-webpack-plugin": "^1.1.0",
"thread-loader": "^1.2.0",
"transform-loader": "^0.2.3",
"ts-jest": "^26.4.2",

View File

@@ -0,0 +1,38 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import dashboardInfo from './mockDashboardInfo';
import { user } from '../javascripts/sqllab/fixtures';
export default {
active: true,
creation_method: 'dashboards',
crontab: '0 12 * * 1',
dashboard: dashboardInfo.id,
name: 'Weekly Report',
owners: [user.userId],
recipients: [
{
recipient_config_json: {
target: user.email,
},
type: 'Email',
},
],
type: 'Report',
};

View File

@@ -0,0 +1,46 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import datasources from 'spec/fixtures/mockDatasource';
import messageToasts from 'spec/javascripts/messageToasts/mockMessageToasts';
import {
nativeFiltersInfo,
mockDataMaskInfo,
} from 'spec/javascripts/dashboard/fixtures/mockNativeFilters';
import chartQueries from 'spec/fixtures/mockChartQueries';
import { dashboardLayout } from 'spec/fixtures/mockDashboardLayout';
import dashboardInfo from 'spec/fixtures/mockDashboardInfo';
import { emptyFilters } from 'spec/fixtures/mockDashboardFilters';
import dashboardState from 'spec/fixtures/mockDashboardState';
import { sliceEntitiesForChart } from 'spec/fixtures/mockSliceEntities';
import reports from 'spec/fixtures/mockReportState';
export default {
datasources,
sliceEntities: sliceEntitiesForChart,
charts: chartQueries,
nativeFilters: nativeFiltersInfo,
dataMask: mockDataMaskInfo,
dashboardInfo,
dashboardFilters: emptyFilters,
dashboardState,
dashboardLayout,
messageToasts,
impressionId: 'mock_impression_id',
reports,
};

View File

@@ -30,6 +30,7 @@ import saveModal from 'src/explore/reducers/saveModalReducer';
import explore from 'src/explore/reducers/exploreReducer';
import sqlLab from 'src/SqlLab/reducers/sqlLab';
import localStorageUsageInKilobytes from 'src/SqlLab/reducers/localStorageUsage';
import reports from 'src/reports/reducers/reports';
const impressionId = (state = '') => state;
@@ -53,5 +54,6 @@ export default {
explore,
sqlLab,
localStorageUsageInKilobytes,
reports,
common: () => common,
};

View File

@@ -23,3 +23,5 @@ import { configure as configureTestingLibrary } from '@testing-library/react';
configureTestingLibrary({
testIdAttribute: 'data-test',
});
document.body.innerHTML = '<div id="app" data-bootstrap="{}"></div>';

View File

@@ -54,7 +54,7 @@ describe('RefreshIntervalModal', () => {
});
it('should change refreshFrequency with edit mode', () => {
const wrapper = getMountWrapper(mockedProps);
wrapper.instance().handleFrequencyChange({ value: 30 });
wrapper.instance().handleFrequencyChange(30);
wrapper.instance().onSave();
expect(mockedProps.onChange).toHaveBeenCalled();
expect(mockedProps.onChange).toHaveBeenCalledWith(30, mockedProps.editMode);
@@ -69,11 +69,11 @@ describe('RefreshIntervalModal', () => {
const wrapper = getMountWrapper(props);
wrapper.find('span[role="button"]').simulate('click');
wrapper.instance().handleFrequencyChange({ value: 30 });
wrapper.instance().handleFrequencyChange(30);
wrapper.update();
expect(wrapper.find(ModalTrigger).find(Alert)).toExist();
wrapper.instance().handleFrequencyChange({ value: 3601 });
wrapper.instance().handleFrequencyChange(3601);
wrapper.update();
expect(wrapper.find(ModalTrigger).find(Alert)).not.toExist();
});

View File

@@ -25,7 +25,7 @@ import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import Chart from 'src/dashboard/containers/Chart';
import ChartHolder from 'src/dashboard/components/gridComponents/ChartHolder';
import ChartHolderConnected from 'src/dashboard/components/gridComponents/ChartHolder';
import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton';
import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
import HoverMenu from 'src/dashboard/components/menu/HoverMenu';
@@ -71,7 +71,7 @@ describe('ChartHolder', () => {
const wrapper = mount(
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>
<ChartHolder {...props} {...overrideProps} />
<ChartHolderConnected {...props} {...overrideProps} />
</DndProvider>
</Provider>,
{

View File

@@ -26,7 +26,7 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
import { act } from 'react-dom/test-utils';
import { MarkdownEditor } from 'src/components/AsyncAceEditor';
import Markdown from 'src/dashboard/components/gridComponents/Markdown';
import MarkdownConnected from 'src/dashboard/components/gridComponents/Markdown';
import MarkdownModeDropdown from 'src/dashboard/components/menu/MarkdownModeDropdown';
import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
@@ -66,7 +66,7 @@ describe('Markdown', () => {
const wrapper = mount(
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>
<Markdown {...props} {...overrideProps} />
<MarkdownConnected {...props} {...overrideProps} />
</DndProvider>
</Provider>,
);

View File

@@ -1,87 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { ExploreChartHeader } from 'src/explore/components/ExploreChartHeader';
import ExploreActionButtons from 'src/explore/components/ExploreActionButtons';
import EditableTitle from 'src/components/EditableTitle';
const saveSliceStub = jest.fn();
const updateChartTitleStub = jest.fn();
const fetchUISpecificReportStub = jest.fn();
const mockProps = {
actions: {
saveSlice: saveSliceStub,
updateChartTitle: updateChartTitleStub,
},
can_overwrite: true,
can_download: true,
isStarred: true,
slice: {
form_data: {
viz_type: 'line',
},
},
table_name: 'foo',
form_data: {
viz_type: 'table',
},
user: {
createdOn: '2021-04-27T18:12:38.952304',
email: 'admin',
firstName: 'admin',
isActive: true,
lastName: 'admin',
permissions: {},
roles: { Admin: Array(173) },
userId: 1,
username: 'admin',
},
timeout: 1000,
chart: {
id: 0,
queryResponse: {},
},
fetchUISpecificReport: fetchUISpecificReportStub,
chartHeight: '30px',
};
describe('ExploreChartHeader', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<ExploreChartHeader {...mockProps} />);
});
it('is valid', () => {
expect(React.isValidElement(<ExploreChartHeader {...mockProps} />)).toBe(
true,
);
});
it('renders', () => {
expect(wrapper.find(EditableTitle)).toExist();
expect(wrapper.find(ExploreActionButtons)).toExist();
});
it('should update title but not save', () => {
const editableTitle = wrapper.find(EditableTitle);
expect(editableTitle.props().onSaveTitle).toBe(updateChartTitleStub);
});
});

View File

@@ -42,14 +42,16 @@ describe('SaveModal', () => {
dashboards: [],
},
explore: {
can_overwrite: true,
user_id: '1',
datasource: {},
slice: {
slice_id: 1,
slice_name: 'title',
owners: [1],
},
alert: null,
user: {
userId: 1,
},
},
};
const store = mockStore(initialState);
@@ -104,7 +106,7 @@ describe('SaveModal', () => {
it('disable overwrite option for non-owner', () => {
const wrapperForNonOwner = getWrapper();
wrapperForNonOwner.setProps({ can_overwrite: false });
wrapperForNonOwner.setProps({ userId: 2 });
const overwriteRadio = wrapperForNonOwner.find('#overwrite-radio');
expect(overwriteRadio).toHaveLength(1);
expect(overwriteRadio.prop('disabled')).toBe(true);

View File

@@ -520,7 +520,20 @@ export default function sqlLabReducer(state = {}, action) {
if (changedQuery.changedOn > queriesLastUpdate) {
queriesLastUpdate = changedQuery.changedOn;
}
newQueries[id] = { ...state.queries[id], ...changedQuery };
const prevState = state.queries[id]?.state;
const currentState = changedQuery.state;
newQueries[id] = {
...state.queries[id],
...changedQuery,
// race condition:
// because of async behavior, sql lab may still poll a couple of seconds
// when it started fetching or finished rendering results
state:
currentState === 'success' &&
['fetching', 'success'].includes(prevState)
? prevState
: currentState,
};
change = true;
}
});

View File

@@ -28,11 +28,9 @@ import VizTypeGallery from 'src/explore/components/controls/VizTypeControl/VizTy
import { styledMount as mount } from 'spec/helpers/theming';
import { act } from 'spec/helpers/testing-library';
const defaultProps = {
datasources: [
{ label: 'my first table', value: '1__table' },
{ label: 'another great table', value: '2__table' },
],
const datasource = {
value: '1',
label: 'table',
};
describe('AddSliceContainer', () => {
@@ -43,7 +41,7 @@ describe('AddSliceContainer', () => {
>;
beforeEach(async () => {
wrapper = mount(<AddSliceContainer {...defaultProps} />) as ReactWrapper<
wrapper = mount(<AddSliceContainer />) as ReactWrapper<
AddSliceContainerProps,
AddSliceContainerState,
AddSliceContainer
@@ -68,11 +66,8 @@ describe('AddSliceContainer', () => {
});
it('renders an enabled button if datasource and viz type is selected', () => {
const datasourceValue = defaultProps.datasources[0].value;
wrapper.setState({
datasourceValue,
datasourceId: datasourceValue.split('__')[0],
datasourceType: datasourceValue.split('__')[1],
datasource,
visType: 'table',
});
expect(
@@ -81,15 +76,12 @@ describe('AddSliceContainer', () => {
});
it('formats explore url', () => {
const datasourceValue = defaultProps.datasources[0].value;
wrapper.setState({
datasourceValue,
datasourceId: datasourceValue.split('__')[0],
datasourceType: datasourceValue.split('__')[1],
datasource,
visType: 'table',
});
const formattedUrl =
'/superset/explore/?form_data=%7B%22viz_type%22%3A%22table%22%2C%22datasource%22%3A%221__table%22%7D';
'/superset/explore/?form_data=%7B%22viz_type%22%3A%22table%22%2C%22datasource%22%3A%221%22%7D';
expect(wrapper.instance().exploreUrl()).toBe(formattedUrl);
});
});

View File

@@ -17,32 +17,34 @@
* under the License.
*/
import React from 'react';
import rison from 'rison';
import { styled, t, SupersetClient, JsonResponse } from '@superset-ui/core';
import { Steps } from 'src/common/components';
import Button from 'src/components/Button';
import { Select } from 'src/components';
import { css, styled, t } from '@superset-ui/core';
import { FormLabel } from 'src/components/Form';
import { Tooltip } from 'src/components/Tooltip';
import VizTypeGallery, {
MAX_ADVISABLE_VIZ_GALLERY_WIDTH,
} from 'src/explore/components/controls/VizTypeControl/VizTypeGallery';
interface Datasource {
label: string;
value: string;
}
export type AddSliceContainerProps = {
datasources: Datasource[];
type Dataset = {
id: number;
table_name: string;
description: string;
datasource_type: string;
};
export type AddSliceContainerProps = {};
export type AddSliceContainerState = {
datasourceId?: string;
datasourceType?: string;
datasourceValue?: string;
datasource?: { label: string; value: string };
visType: string | null;
};
const ESTIMATED_NAV_HEIGHT = '56px';
const ESTIMATED_NAV_HEIGHT = 56;
const ELEMENTS_EXCEPT_VIZ_GALLERY = ESTIMATED_NAV_HEIGHT + 250;
const StyledContainer = styled.div`
${({ theme }) => `
@@ -52,7 +54,7 @@ const StyledContainer = styled.div`
justify-content: space-between;
width: 100%;
max-width: ${MAX_ADVISABLE_VIZ_GALLERY_WIDTH}px;
max-height: calc(100vh - ${ESTIMATED_NAV_HEIGHT});
max-height: calc(100vh - ${ESTIMATED_NAV_HEIGHT}px);
border-radius: ${theme.gridUnit}px;
background-color: ${theme.colors.grayscale.light5};
margin-left: auto;
@@ -69,6 +71,7 @@ const StyledContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: ${theme.gridUnit * 2}px;
& > div {
min-width: 200px;
@@ -78,22 +81,100 @@ const StyledContainer = styled.div`
& > span {
color: ${theme.colors.grayscale.light1};
margin-left: ${theme.gridUnit * 4}px;
margin-top: ${theme.gridUnit * 6}px;
}
}
& .viz-gallery {
border: 1px solid ${theme.colors.grayscale.light2};
border-radius: ${theme.gridUnit}px;
margin: ${theme.gridUnit}px 0px;
max-height: calc(100vh - ${ELEMENTS_EXCEPT_VIZ_GALLERY}px);
flex: 1;
}
& .footer {
flex: 1;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
& > span {
color: ${theme.colors.grayscale.light1};
margin-right: ${theme.gridUnit * 4}px;
}
}
/* The following extra ampersands (&&&&) are used to boost selector specificity */
&&&& .ant-steps-item-tail {
display: none;
}
&&&& .ant-steps-item-icon {
margin-right: ${theme.gridUnit * 2}px;
width: ${theme.gridUnit * 5}px;
height: ${theme.gridUnit * 5}px;
line-height: ${theme.gridUnit * 5}px;
}
&&&& .ant-steps-item-title {
line-height: ${theme.gridUnit * 5}px;
}
&&&& .ant-steps-item-content {
overflow: unset;
.ant-steps-item-description {
margin-top: ${theme.gridUnit}px;
}
}
&&&& .ant-tooltip-open {
display: inline;
}
&&&& .ant-select-selector {
padding: 0;
}
&&&& .ant-select-selection-placeholder {
padding-left: ${theme.gridUnit * 3}px;
}
`}
`;
const cssStatic = css`
flex: 0 0 auto;
const TooltipContent = styled.div<{ hasDescription: boolean }>`
${({ theme, hasDescription }) => `
.tooltip-header {
font-size: ${
hasDescription ? theme.typography.sizes.l : theme.typography.sizes.s
}px;
font-weight: ${
hasDescription
? theme.typography.weights.bold
: theme.typography.weights.normal
};
}
.tooltip-description {
margin-top: ${theme.gridUnit * 2}px;
display: -webkit-box;
-webkit-line-clamp: 20;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
`}
`;
const StyledVizTypeGallery = styled(VizTypeGallery)`
const StyledLabel = styled.span`
${({ theme }) => `
border: 1px solid ${theme.colors.grayscale.light2};
border-radius: ${theme.gridUnit}px;
margin: ${theme.gridUnit * 3}px 0px;
flex: 1 1 auto;
position: absolute;
left: ${theme.gridUnit * 3}px;
right: ${theme.gridUnit * 3}px;
overflow: hidden;
text-overflow: ellipsis;
`}
`;
@@ -110,13 +191,16 @@ export default class AddSliceContainer extends React.PureComponent<
this.changeDatasource = this.changeDatasource.bind(this);
this.changeVisType = this.changeVisType.bind(this);
this.gotoSlice = this.gotoSlice.bind(this);
this.newLabel = this.newLabel.bind(this);
this.loadDatasources = this.loadDatasources.bind(this);
this.handleFilterOption = this.handleFilterOption.bind(this);
}
exploreUrl() {
const formData = encodeURIComponent(
JSON.stringify({
viz_type: this.state.visType,
datasource: this.state.datasourceValue,
datasource: this.state.datasource?.value,
}),
);
return `/superset/explore/?form_data=${formData}`;
@@ -126,11 +210,8 @@ export default class AddSliceContainer extends React.PureComponent<
window.location.href = this.exploreUrl();
}
changeDatasource(value: string) {
this.setState({
datasourceValue: value,
datasourceId: value.split('__')[0],
});
changeDatasource(datasource: { label: string; value: string }) {
this.setState({ datasource });
}
changeVisType(visType: string | null) {
@@ -138,55 +219,131 @@ export default class AddSliceContainer extends React.PureComponent<
}
isBtnDisabled() {
return !(this.state.datasourceId && this.state.visType);
return !(this.state.datasource?.value && this.state.visType);
}
newLabel(item: Dataset) {
return (
<Tooltip
mouseEnterDelay={1}
placement="right"
title={
<TooltipContent hasDescription={!!item.description}>
<div className="tooltip-header">{item.table_name}</div>
{item.description && (
<div className="tooltip-description">{item.description}</div>
)}
</TooltipContent>
}
>
<StyledLabel>{item.table_name}</StyledLabel>
</Tooltip>
);
}
loadDatasources(search: string, page: number, pageSize: number) {
const query = rison.encode({
columns: ['id', 'table_name', 'description', 'datasource_type'],
filters: [{ col: 'table_name', opr: 'ct', value: search }],
page,
page_size: pageSize,
order_column: 'table_name',
order_direction: 'asc',
});
return SupersetClient.get({
endpoint: `/api/v1/dataset/?q=${query}`,
}).then((response: JsonResponse) => {
const list: {
label: string;
value: string;
}[] = response.json.result
.map((item: Dataset) => ({
value: `${item.id}__${item.datasource_type}`,
label: this.newLabel(item),
labelText: item.table_name,
}))
.sort((a: { labelText: string }, b: { labelText: string }) =>
a.labelText.localeCompare(b.labelText),
);
return {
data: list,
totalCount: response.json.count,
};
});
}
handleFilterOption(
search: string,
option: { label: string; value: number; labelText: string },
) {
const searchValue = search.trim().toLowerCase();
const { labelText } = option;
return labelText.toLowerCase().includes(searchValue);
}
render() {
const isButtonDisabled = this.isBtnDisabled();
return (
<StyledContainer>
<h3 css={cssStatic}>{t('Create a new chart')}</h3>
<div className="dataset">
<Select
autoFocus
ariaLabel={t('Dataset')}
name="select-datasource"
header={<FormLabel required>{t('Choose a dataset')}</FormLabel>}
onChange={this.changeDatasource}
options={this.props.datasources}
placeholder={t('Choose a dataset')}
showSearch
value={this.state.datasourceValue}
<h3>{t('Create a new chart')}</h3>
<Steps direction="vertical" size="small">
<Steps.Step
title={<FormLabel>{t('Choose a dataset')}</FormLabel>}
status={this.state.datasource?.value ? 'finish' : 'process'}
description={
<div className="dataset">
<Select
autoFocus
ariaLabel={t('Dataset')}
name="select-datasource"
filterOption={this.handleFilterOption}
onChange={this.changeDatasource}
options={this.loadDatasources}
placeholder={t('Choose a dataset')}
showSearch
value={this.state.datasource}
/>
<span>
{t(
'Instructions to add a dataset are available in the Superset tutorial.',
)}{' '}
<a
href="https://superset.apache.org/docs/creating-charts-dashboards/first-dashboard#adding-a-new-table"
rel="noopener noreferrer"
target="_blank"
>
<i className="fa fa-external-link" />
</a>
</span>
</div>
}
/>
<span>
{t(
'Instructions to add a dataset are available in the Superset tutorial.',
)}{' '}
<a
href="https://superset.apache.org/docs/creating-charts-dashboards/first-dashboard#adding-a-new-table"
rel="noopener noreferrer"
target="_blank"
>
<i className="fa fa-external-link" />
</a>
</span>
<Steps.Step
title={<FormLabel>{t('Choose chart type')}</FormLabel>}
status={this.state.visType ? 'finish' : 'process'}
description={
<VizTypeGallery
className="viz-gallery"
onChange={this.changeVisType}
selectedViz={this.state.visType}
/>
}
/>
</Steps>
<div className="footer">
{isButtonDisabled && (
<span>
{t('Please select both a Dataset and a Chart type to proceed')}
</span>
)}
<Button
buttonStyle="primary"
disabled={isButtonDisabled}
onClick={this.gotoSlice}
>
{t('Create new chart')}
</Button>
</div>
<StyledVizTypeGallery
onChange={this.changeVisType}
selectedViz={this.state.visType}
/>
<Button
css={[
cssStatic,
css`
align-self: flex-end;
`,
]}
buttonStyle="primary"
disabled={this.isBtnDisabled()}
onClick={this.gotoSlice}
>
{t('Create new chart')}
</Button>
</StyledContainer>
);
}

View File

@@ -39,7 +39,7 @@ initFeatureFlags(bootstrapData.common.feature_flags);
const App = () => (
<ThemeProvider theme={theme}>
<DynamicPluginProvider>
<AddSliceContainer datasources={bootstrapData.datasources} />
<AddSliceContainer />
</DynamicPluginProvider>
</ThemeProvider>
);

View File

@@ -68,6 +68,9 @@ const propTypes = {
};
const BLANK = {};
const NONEXISTENT_DATASET = t(
'The dataset associated with this chart no longer exists',
);
const defaultProps = {
addFilter: () => BLANK,
@@ -178,7 +181,11 @@ class Chart extends React.PureComponent {
const message = chartAlert || queryResponse?.message;
// if datasource is still loading, don't render JS errors
if (chartAlert && datasource === PLACEHOLDER_DATASOURCE) {
if (
chartAlert !== undefined &&
chartAlert !== NONEXISTENT_DATASET &&
datasource === PLACEHOLDER_DATASOURCE
) {
return (
<Styles
data-ui-anchor="chart"

View File

@@ -49,6 +49,7 @@ export {
Row,
Space,
Skeleton,
Steps,
Switch,
Tag,
Tabs,

View File

@@ -63,6 +63,7 @@ export interface ButtonProps {
htmlType?: 'button' | 'submit' | 'reset';
cta?: boolean;
loading?: boolean | { delay?: number | undefined } | undefined;
showMarginRight?: boolean;
}
export default function Button(props: ButtonProps) {
@@ -76,6 +77,7 @@ export default function Button(props: ButtonProps) {
cta,
children,
href,
showMarginRight = true,
...restProps
} = props;
@@ -154,8 +156,8 @@ export default function Button(props: ButtonProps) {
} else {
renderedChildren = Children.toArray(children);
}
const firstChildMargin = renderedChildren.length > 1 ? theme.gridUnit * 2 : 0;
const firstChildMargin =
showMarginRight && renderedChildren.length > 1 ? theme.gridUnit * 2 : 0;
const button = (
<AntdButton

View File

@@ -52,6 +52,14 @@ const StyledTooltip = styled(Tooltip)`
}
`;
const StyledTooltipTitle = styled.span`
display: -webkit-box;
-webkit-line-clamp: 20;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
`;
const defaultOverlayStyle = {
fontSize: '12px',
lineHeight: '16px',
@@ -69,7 +77,7 @@ export default function InfoTooltip({
}: InfoTooltipProps) {
return (
<StyledTooltip
title={tooltip}
title={<StyledTooltipTitle>{tooltip}</StyledTooltipTitle>}
placement={placement}
trigger={trigger}
overlayStyle={overlayStyle}

View File

@@ -103,7 +103,6 @@ const mockedProps = {
locale: 'en',
version_string: '1.0.0',
version_sha: 'randomSHA',
build_number: 'randomBuildNumber',
},
settings: [
{
@@ -281,10 +280,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, sha or build_number when available', async () => {
test('should render the About section and version_string or sha when available', async () => {
const {
data: {
navbar_right: { version_sha, version_string, build_number },
navbar_right: { version_sha, version_string },
},
} = mockedProps;
@@ -293,11 +292,9 @@ test('should render the About section and version_string, sha or build_number wh
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 () => {

View File

@@ -44,7 +44,6 @@ 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;

View File

@@ -146,9 +146,7 @@ const RightMenu = ({
</Menu.Item>
</Menu.ItemGroup>,
]}
{(navbarRight.version_string ||
navbarRight.version_sha ||
navbarRight.build_number) && [
{(navbarRight.version_string || navbarRight.version_sha) && [
<Menu.Divider key="version-info-divider" />,
<Menu.ItemGroup key="about-section" title={t('About')}>
<div className="about-section">
@@ -167,11 +165,6 @@ const RightMenu = ({
SHA: {navbarRight.version_sha}
</div>
)}
{navbarRight.build_number && (
<div css={versionInfoStyles}>
Build: {navbarRight.build_number}
</div>
)}
</div>
</Menu.ItemGroup>,
]}

View File

@@ -34,6 +34,8 @@ InteractiveModal.args = {
primaryButtonType: 'danger',
show: true,
title: "I'm a modal!",
resizable: false,
draggable: false,
};
InteractiveModal.argTypes = {

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import React, { useRef, useState } from 'react';
import { isNil } from 'lodash';
import { styled, t } from '@superset-ui/core';
import { css } from '@emotion/react';
@@ -25,6 +25,13 @@ import {
ModalProps as AntdModalProps,
} from 'src/common/components';
import Button from 'src/components/Button';
import { Resizable, ResizableProps } from 're-resizable';
import Draggable, {
DraggableBounds,
DraggableData,
DraggableEvent,
DraggableProps,
} from 'react-draggable';
export interface ModalProps {
className?: string;
@@ -46,6 +53,11 @@ export interface ModalProps {
wrapProps?: object;
height?: string;
closable?: boolean;
resizable?: boolean;
resizableConfig?: ResizableProps;
draggable?: boolean;
draggableConfig?: DraggableProps;
destroyOnClose?: boolean;
}
interface StyledModalProps {
@@ -53,8 +65,19 @@ interface StyledModalProps {
responsive?: boolean;
height?: string;
hideFooter?: boolean;
draggable?: boolean;
resizable?: boolean;
}
const MODAL_HEADER_HEIGHT = 55;
const MODAL_MIN_CONTENT_HEIGHT = 54;
const MODAL_FOOTER_HEIGHT = 65;
const RESIZABLE_MIN_HEIGHT = MODAL_HEADER_HEIGHT + MODAL_MIN_CONTENT_HEIGHT;
const RESIZABLE_MIN_WIDTH = '380px';
const RESIZABLE_MAX_HEIGHT = '100vh';
const RESIZABLE_MAX_WIDTH = '100vw';
const BaseModal = (props: AntdModalProps) => (
// Removes mask animation. Fixed in 4.6.0.
// https://github.com/ant-design/ant-design/issues/27192
@@ -100,9 +123,8 @@ export const StyledModal = styled(BaseModal)<StyledModalProps>`
.ant-modal-body {
padding: ${({ theme }) => theme.gridUnit * 4}px;
overflow: auto;
${({ height }) => height && `height: ${height};`}
${({ resizable, height }) => !resizable && height && `height: ${height};`}
}
.ant-modal-footer {
border-top: ${({ theme }) => theme.gridUnit / 4}px solid
${({ theme }) => theme.colors.grayscale.light2};
@@ -128,6 +150,44 @@ export const StyledModal = styled(BaseModal)<StyledModalProps>`
&.no-content-padding .ant-modal-body {
padding: 0;
}
${({ draggable, theme }) =>
draggable &&
`
.ant-modal-header {
padding: 0;
.draggable-trigger {
cursor: move;
padding: ${theme.gridUnit * 4}px;
width: 100%;
}
}
`};
${({ resizable, hideFooter }) =>
resizable &&
`
.resizable {
pointer-events: all;
.resizable-wrapper {
height: 100%;
}
.ant-modal-content {
height: 100%;
.ant-modal-body {
/* 100% - header height - footer height */
height: ${
hideFooter
? `calc(100% - ${MODAL_HEADER_HEIGHT}px);`
: `calc(100% - ${MODAL_HEADER_HEIGHT}px - ${MODAL_FOOTER_HEIGHT}px);`
}
}
}
}
`}
`;
const CustomModal = ({
@@ -147,8 +207,33 @@ const CustomModal = ({
footer,
hideFooter,
wrapProps,
draggable = false,
resizable = false,
resizableConfig = {
maxHeight: RESIZABLE_MAX_HEIGHT,
maxWidth: RESIZABLE_MAX_WIDTH,
minHeight: hideFooter
? RESIZABLE_MIN_HEIGHT
: RESIZABLE_MIN_HEIGHT + MODAL_FOOTER_HEIGHT,
minWidth: RESIZABLE_MIN_WIDTH,
enable: {
bottom: true,
bottomLeft: false,
bottomRight: true,
left: false,
top: false,
topLeft: false,
topRight: false,
right: true,
},
},
draggableConfig,
destroyOnClose,
...rest
}: ModalProps) => {
const draggableRef = useRef<HTMLDivElement>(null);
const [bounds, setBounds] = useState<DraggableBounds>();
const [dragDisabled, setDragDisabled] = useState<boolean>(true);
const modalFooter = isNil(footer)
? [
<Button key="back" onClick={onHide} cta data-test="modal-cancel-button">
@@ -168,6 +253,35 @@ const CustomModal = ({
: footer;
const modalWidth = width || (responsive ? '100vw' : '600px');
const shouldShowMask = !(resizable || draggable);
const onDragStart = (_: DraggableEvent, uiData: DraggableData) => {
const { clientWidth, clientHeight } = window?.document?.documentElement;
const targetRect = draggableRef?.current?.getBoundingClientRect();
if (targetRect) {
setBounds({
left: -targetRect?.left + uiData?.x,
right: clientWidth - (targetRect?.right - uiData?.x),
top: -targetRect?.top + uiData?.y,
bottom: clientHeight - (targetRect?.bottom - uiData?.y),
});
}
};
const ModalTitle = () =>
draggable ? (
<div
className="draggable-trigger"
onMouseOver={() => dragDisabled && setDragDisabled(false)}
onMouseOut={() => !dragDisabled && setDragDisabled(true)}
>
{title}
</div>
) : (
<>{title}</>
);
return (
<StyledModal
centered={!!centered}
@@ -177,14 +291,41 @@ const CustomModal = ({
maxWidth={maxWidth}
responsive={responsive}
visible={show}
title={title}
title={<ModalTitle />}
closeIcon={
<span className="close" aria-hidden="true">
×
</span>
}
footer={!hideFooter ? modalFooter : null}
hideFooter={hideFooter}
wrapProps={{ 'data-test': `${name || title}-modal`, ...wrapProps }}
modalRender={modal =>
resizable || draggable ? (
<Draggable
disabled={!draggable || dragDisabled}
bounds={bounds}
onStart={(event, uiData) => onDragStart(event, uiData)}
{...draggableConfig}
>
{resizable ? (
<Resizable className="resizable" {...resizableConfig}>
<div className="resizable-wrapper" ref={draggableRef}>
{modal}
</div>
</Resizable>
) : (
<div ref={draggableRef}>{modal}</div>
)}
</Draggable>
) : (
modal
)
}
mask={shouldShowMask}
draggable={draggable}
resizable={resizable}
destroyOnClose={destroyOnClose || resizable || draggable}
{...rest}
>
{children}

View File

@@ -33,6 +33,8 @@ interface IModalTriggerProps {
width?: string;
maxWidth?: string;
responsive?: boolean;
draggable?: boolean;
resizable?: boolean;
}
export default {
@@ -53,4 +55,6 @@ InteractiveModalTrigger.args = {
width: '600px',
maxWidth: '1000px',
responsive: true,
draggable: false,
resizable: false,
};

View File

@@ -35,6 +35,10 @@ const propTypes = {
width: PropTypes.string,
maxWidth: PropTypes.string,
responsive: PropTypes.bool,
resizable: PropTypes.bool,
resizableConfig: PropTypes.object,
draggable: PropTypes.bool,
draggableConfig: PropTypes.object,
};
const defaultProps = {
@@ -43,6 +47,8 @@ const defaultProps = {
isButton: false,
className: '',
modalTitle: '',
resizable: false,
draggable: false,
};
export default class ModalTrigger extends React.Component {
@@ -79,6 +85,10 @@ export default class ModalTrigger extends React.Component {
width={this.props.width}
maxWidth={this.props.maxWidth}
responsive={this.props.responsive}
resizable={this.props.resizable}
resizableConfig={this.props.resizableConfig}
draggable={this.props.draggable}
draggableConfig={this.props.draggableConfig}
>
{this.props.modalBody}
</Modal>

View File

@@ -25,16 +25,16 @@ import OmniContainer from './index';
jest.mock('src/featureFlags', () => ({
isFeatureEnabled: jest.fn(),
FeatureFlag: { OMNIBAR: 'OMNIBAR' },
initFeatureFlags: jest.fn(),
}));
test('Do not open Omnibar with the featureflag disabled', () => {
(isFeatureEnabled as jest.Mock).mockImplementation(
(ff: string) => !(ff === 'OMNIBAR'),
);
const logEvent = jest.fn();
render(
<div data-test="test">
<OmniContainer logEvent={logEvent} />
<OmniContainer />
</div>,
);
@@ -54,10 +54,9 @@ test('Open Omnibar with ctrl + k with featureflag enabled', () => {
(isFeatureEnabled as jest.Mock).mockImplementation(
(ff: string) => ff === 'OMNIBAR',
);
const logEvent = jest.fn();
render(
<div data-test="test">
<OmniContainer logEvent={logEvent} />
<OmniContainer />
</div>,
);
@@ -79,53 +78,18 @@ test('Open Omnibar with ctrl + k with featureflag enabled', () => {
ctrlKey: true,
code: 'KeyK',
});
expect(
screen.queryByPlaceholderText('Search all dashboards'),
).not.toBeVisible();
});
test('Open Omnibar with ctrl + s with featureflag enabled', () => {
(isFeatureEnabled as jest.Mock).mockImplementation(
(ff: string) => ff === 'OMNIBAR',
);
const logEvent = jest.fn();
render(
<div data-test="test">
<OmniContainer logEvent={logEvent} />
</div>,
);
expect(
screen.queryByPlaceholderText('Search all dashboards'),
).not.toBeInTheDocument();
// show Omnibar
fireEvent.keyDown(screen.getByTestId('test'), {
ctrlKey: true,
code: 'KeyS',
});
expect(
screen.queryByPlaceholderText('Search all dashboards'),
).toBeInTheDocument();
// hide Omnibar
fireEvent.keyDown(screen.getByTestId('test'), {
ctrlKey: true,
code: 'KeyS',
});
expect(
screen.queryByPlaceholderText('Search all dashboards'),
).not.toBeVisible();
});
test('Open Omnibar with Command + k with featureflag enabled', () => {
(isFeatureEnabled as jest.Mock).mockImplementation(
(ff: string) => ff === 'OMNIBAR',
);
const logEvent = jest.fn();
render(
<div data-test="test">
<OmniContainer logEvent={logEvent} />
<OmniContainer />
</div>,
);
@@ -149,17 +113,16 @@ test('Open Omnibar with Command + k with featureflag enabled', () => {
});
expect(
screen.queryByPlaceholderText('Search all dashboards'),
).not.toBeVisible();
).not.toBeInTheDocument();
});
test('Open Omnibar with Command + s with featureflag enabled', () => {
test('Open Omnibar with Cmd/Ctrl-K and close with ESC', () => {
(isFeatureEnabled as jest.Mock).mockImplementation(
(ff: string) => ff === 'OMNIBAR',
);
const logEvent = jest.fn();
render(
<div data-test="test">
<OmniContainer logEvent={logEvent} />
<OmniContainer />
</div>,
);
@@ -169,19 +132,19 @@ test('Open Omnibar with Command + s with featureflag enabled', () => {
// show Omnibar
fireEvent.keyDown(screen.getByTestId('test'), {
metaKey: true,
code: 'KeyS',
ctrlKey: true,
code: 'KeyK',
});
expect(
screen.queryByPlaceholderText('Search all dashboards'),
).toBeInTheDocument();
// hide Omnibar
// Close Omnibar
fireEvent.keyDown(screen.getByTestId('test'), {
metaKey: true,
code: 'KeyS',
key: 'Escape',
code: 'Escape',
});
expect(
screen.queryByPlaceholderText('Search all dashboards'),
).not.toBeVisible();
).not.toBeInTheDocument();
});

View File

@@ -38,7 +38,8 @@ export function Omnibar({ extensions, placeholder, id }: Props) {
id={id}
placeholder={placeholder}
extensions={extensions}
// autoFocus // I tried to use this prop (autoFocus) but it only works the first time that Omnibar is shown
autoComplete="off"
autoFocus
/>
);
}

View File

@@ -18,10 +18,11 @@
*/
import React, { useRef, useState } from 'react';
import { styled } from '@superset-ui/core';
import { styled, t } from '@superset-ui/core';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import Modal from 'src/components/Modal';
import { useComponentDidMount } from 'src/common/hooks/useComponentDidMount';
import { logEvent } from 'src/logger/actions';
import { Omnibar } from './Omnibar';
import { LOG_ACTIONS_OMNIBAR_TRIGGERED } from '../../logger/LogUtils';
import { getDashboards } from './getDashboards';
@@ -31,37 +32,59 @@ const OmniModal = styled(Modal)`
.ant-modal-body {
padding: 0;
overflow: visible;
}
`;
interface Props {
logEvent: (log: string, object: object) => void;
}
export default function OmniContainer({ logEvent }: Props) {
export default function OmniContainer() {
const showOmni = useRef<boolean>();
const modalRef = useRef<HTMLDivElement>(null);
const [showModal, setShowModal] = useState(false);
const handleLogEvent = (show: boolean) =>
logEvent(LOG_ACTIONS_OMNIBAR_TRIGGERED, {
show_omni: show,
});
const handleClose = () => {
showOmni.current = false;
setShowModal(false);
handleLogEvent(false);
};
useComponentDidMount(() => {
showOmni.current = false;
function handleKeydown(event: KeyboardEvent) {
if (!isFeatureEnabled(FeatureFlag.OMNIBAR)) return;
const controlOrCommand = event.ctrlKey || event.metaKey;
const isOk = ['KeyK', 'KeyS'].includes(event.code); // valid keys "s" or "k"
const isOk = ['KeyK'].includes(event.code);
const isEsc = event.key === 'Escape';
if (isEsc && showOmni.current) {
handleClose();
return;
}
if (controlOrCommand && isOk) {
logEvent(LOG_ACTIONS_OMNIBAR_TRIGGERED, {
show_omni: !!showOmni.current,
});
showOmni.current = !showOmni.current;
setShowModal(showOmni.current);
if (showOmni.current) {
document.getElementById('InputOmnibar')?.focus();
}
handleLogEvent(!!showOmni.current);
}
}
function handleClickOutside(event: MouseEvent) {
if (
modalRef.current &&
!modalRef.current.contains(event.target as Node)
) {
handleClose();
}
}
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleKeydown);
return () => document.removeEventListener('keydown', handleKeydown);
return () => {
document.removeEventListener('keydown', handleKeydown);
document.removeEventListener('mousedown', handleClickOutside);
};
});
return (
@@ -71,12 +94,15 @@ export default function OmniContainer({ logEvent }: Props) {
hideFooter
closable={false}
onHide={() => {}}
destroyOnClose
>
<Omnibar
id="InputOmnibar"
placeholder="Search all dashboards"
extensions={[getDashboards]}
/>
<div ref={modalRef}>
<Omnibar
id="InputOmnibar"
placeholder={t('Search all dashboards')}
extensions={[getDashboards]}
/>
</div>
</OmniModal>
);
}

View File

@@ -55,13 +55,17 @@ describe('Email Report Modal', () => {
(featureFlag: FeatureFlag) => featureFlag === FeatureFlag.ALERT_REPORTS,
);
});
beforeEach(() => {
render(<ReportModal {...defaultProps} />, { useRedux: true });
});
afterAll(() => {
// @ts-ignore
isFeatureEnabledMock.restore();
});
it('inputs respond correctly', () => {
render(<ReportModal {...defaultProps} />, { useRedux: true });
it('inputs respond correctly', () => {
// ----- Report name textbox
// Initial value
const reportNameTextbox = screen.getByTestId('report-name-test');
@@ -86,4 +90,21 @@ describe('Email Report Modal', () => {
const crontabInputs = screen.getAllByRole('combobox');
expect(crontabInputs).toHaveLength(5);
});
it('does not allow user to create a report without a name', () => {
// Grab name textbox and add button
const reportNameTextbox = screen.getByTestId('report-name-test');
const addButton = screen.getByRole('button', { name: /add/i });
// Add button should be enabled while name textbox has text
expect(reportNameTextbox).toHaveDisplayValue('Weekly Report');
expect(addButton).toBeEnabled();
// Clear the text from the name textbox
userEvent.clear(reportNameTextbox);
// Add button should now be disabled, blocking user from creation
expect(reportNameTextbox).toHaveDisplayValue('');
expect(addButton).toBeDisabled();
});
});

View File

@@ -53,7 +53,7 @@ import {
StyledRadioGroup,
} from './styles';
interface ReportObject {
export interface ReportObject {
id?: number;
active: boolean;
crontab: string;
@@ -125,7 +125,6 @@ type ReportActionType =
type: ActionType.reset;
};
const DEFAULT_NOTIFICATION_FORMAT = 'TEXT';
const TEXT_BASED_VISUALIZATION_TYPES = [
'pivot_table',
'pivot_table_v2',
@@ -133,28 +132,34 @@ const TEXT_BASED_VISUALIZATION_TYPES = [
'paired_ttest',
];
const NOTIFICATION_FORMATS = {
TEXT: 'TEXT',
PNG: 'PNG',
CSV: 'CSV',
};
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 || {}),
name: 'Weekly Report',
};
switch (action.type) {
case ActionType.inputChange:
return {
...initialState,
...state,
[action.payload.name]: action.payload.value,
};
case ActionType.fetched:
return {
...initialState,
...action.payload,
};
case ActionType.reset:
return null;
return { ...initialState };
default:
return state;
}
@@ -167,6 +172,11 @@ const ReportModal: FunctionComponent<ReportProps> = ({
...props
}) => {
const vizType = props.props.chart?.sliceFormData?.viz_type;
const isChart = !!props.props.chart;
const defaultNotificationFormat =
isChart && TEXT_BASED_VISUALIZATION_TYPES.includes(vizType)
? NOTIFICATION_FORMATS.TEXT
: NOTIFICATION_FORMATS.PNG;
const [currentReport, setCurrentReport] = useReducer<
Reducer<Partial<ReportObject> | null, ReportActionType>
>(reportReducer, null);
@@ -179,6 +189,7 @@ const ReportModal: FunctionComponent<ReportProps> = ({
// Report fetch logic
const reports = useSelector<any, AlertObject>(state => state.reports);
const isEditMode = reports && Object.keys(reports).length;
useEffect(() => {
if (isEditMode) {
const reportsIds = Object.keys(reports);
@@ -214,7 +225,8 @@ const ReportModal: FunctionComponent<ReportProps> = ({
type: 'Report',
creation_method: props.props.creationMethod,
active: true,
report_format: currentReport?.report_format,
report_format: currentReport?.report_format || defaultNotificationFormat,
timezone: currentReport?.timezone,
};
if (isEditMode) {
@@ -270,17 +282,17 @@ const ReportModal: FunctionComponent<ReportProps> = ({
value: event.target.value,
});
}}
value={currentReport?.report_format || DEFAULT_NOTIFICATION_FORMAT}
value={currentReport?.report_format || defaultNotificationFormat}
>
{TEXT_BASED_VISUALIZATION_TYPES.includes(vizType) && (
<StyledRadio value="TEXT">
<StyledRadio value={NOTIFICATION_FORMATS.TEXT}>
{t('Text embedded in email')}
</StyledRadio>
)}
<StyledRadio value="PNG">
<StyledRadio value={NOTIFICATION_FORMATS.PNG}>
{t('Image (PNG) embedded in email')}
</StyledRadio>
<StyledRadio value="CSV">
<StyledRadio value={NOTIFICATION_FORMATS.CSV}>
{t('Formatted CSV attached in email')}
</StyledRadio>
</StyledRadioGroup>
@@ -374,7 +386,7 @@ const ReportModal: FunctionComponent<ReportProps> = ({
}}
timezone={currentReport?.timezone}
/>
{props.props.chart && renderMessageContentSection}
{isChart && renderMessageContentSection}
</StyledBottomSection>
</StyledModal>
);

View File

@@ -249,15 +249,8 @@ 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 (
menuIsOpen &&
target &&
!target.classList?.contains('Select__menu-list')
);
return target && !target.classList?.contains('Select__menu-list');
},
menuPosition: 'fixed',
});

View File

@@ -300,6 +300,7 @@ const USERS = [
];
export const AsyncSelect = ({
fetchOnlyOnSearch,
withError,
withInitialValue,
responseTime,
@@ -381,7 +382,9 @@ export const AsyncSelect = ({
>
<Select
{...rest}
fetchOnlyOnSearch={fetchOnlyOnSearch}
options={withError ? fetchUserListError : fetchUserListPage}
placeholder={fetchOnlyOnSearch ? 'Type anything' : 'Select...'}
value={
withInitialValue
? { label: 'Valentina', value: 'Valentina' }

View File

@@ -49,8 +49,12 @@ type PickedSelectProps = Pick<
| 'autoFocus'
| 'disabled'
| 'filterOption'
| 'labelInValue'
| 'loading'
| 'notFoundContent'
| 'onChange'
| 'onClear'
| 'onFocus'
| 'placeholder'
| 'showSearch'
| 'value'
@@ -73,6 +77,7 @@ export interface SelectProps extends PickedSelectProps {
allowNewOptions?: boolean;
ariaLabel: string;
header?: ReactNode;
lazyLoading?: boolean;
mode?: 'single' | 'multiple';
name?: string; // discourage usage
options: OptionsType | OptionsPagePromise;
@@ -84,15 +89,11 @@ export interface SelectProps extends PickedSelectProps {
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
const StyledSelect = styled(AntdSelect, {
shouldForwardProp: prop => prop !== 'hasHeader',
})<{ hasHeader: boolean }>`
${({ theme, hasHeader }) => `
width: 100%;
margin-top: ${hasHeader ? theme.gridUnit : 0}px;
const StyledSelect = styled(AntdSelect)`
${({ theme }) => `
&& .ant-select-selector {
border-radius: ${theme.gridUnit}px;
}
@@ -128,10 +129,22 @@ const StyledError = styled.div`
`}
`;
const StyledErrorMessage = styled.div`
overflow: hidden;
text-overflow: ellipsis;
`;
const StyledSpin = styled(Spin)`
margin-top: ${({ theme }) => -theme.gridUnit}px;
`;
const StyledLoadingText = styled.span`
${({ theme }) => `
margin-left: ${theme.gridUnit * 3}px;
color: ${theme.colors.grayscale.light1};
`}
`;
const MAX_TAG_COUNT = 4;
const TOKEN_SEPARATORS = [',', '\n', '\t', ';'];
const DEBOUNCE_TIMEOUT = 500;
@@ -140,7 +153,7 @@ const EMPTY_OPTIONS: OptionsType = [];
const Error = ({ error }: { error: string }) => (
<StyledError>
<Icons.ErrorSolid /> {error}
<Icons.ErrorSolid /> <StyledErrorMessage>{error}</StyledErrorMessage>
</StyledError>
);
@@ -151,8 +164,12 @@ const Select = ({
filterOption = true,
header = null,
invertSelection = false,
labelInValue = false,
lazyLoading = true,
loading = false,
mode = 'single',
name,
onChange,
options,
pageSize = DEFAULT_PAGE_SIZE,
placeholder = t('Select ...'),
@@ -170,12 +187,13 @@ const Select = ({
);
const [selectValue, setSelectValue] = useState(value);
const [searchedValue, setSearchedValue] = useState('');
const [isLoading, setLoading] = useState(false);
const [isLoading, setIsLoading] = useState(loading);
const [isTyping, setIsTyping] = useState(false);
const [error, setError] = useState('');
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const [page, setPage] = useState(0);
const [totalCount, setTotalCount] = useState(0);
const [loadingEnabled, setLoadingEnabled] = useState(false);
const [loadingEnabled, setLoadingEnabled] = useState(!lazyLoading);
const fetchedQueries = useRef(new Map<string, number>());
const mappedMode = isSingleMode
? undefined
@@ -184,16 +202,21 @@ const Select = ({
: 'multiple';
useEffect(() => {
fetchedQueries.current.clear();
setSelectOptions(
options && Array.isArray(options) ? options : EMPTY_OPTIONS,
);
}, [options]);
useEffect(() => {
if (isAsync && value) {
const array: AntdLabeledValue[] = Array.isArray(value)
? (value as AntdLabeledValue[])
: [value as AntdLabeledValue];
setSelectValue(value);
}, [value]);
useEffect(() => {
if (isAsync && selectValue) {
const array: AntdLabeledValue[] = Array.isArray(selectValue)
? (selectValue as AntdLabeledValue[])
: [selectValue as AntdLabeledValue];
const options: AntdLabeledValue[] = [];
array.forEach(element => {
const found = selectOptions.find(
@@ -207,23 +230,20 @@ const Select = ({
setSelectOptions([...selectOptions, ...options]);
}
}
}, [isAsync, selectOptions, value]);
useEffect(() => {
setSelectValue(value);
}, [value]);
}, [isAsync, selectOptions, selectValue]);
const handleTopOptions = useCallback(
(selectedValue: AntdSelectValue | undefined) => {
// bringing selected options to the top of the list
if (selectedValue) {
if (selectedValue !== undefined && selectedValue !== null) {
const isLabeledValue = isAsync || labelInValue;
const topOptions: OptionsType = [];
const otherOptions: OptionsType = [];
selectOptions.forEach(opt => {
let found = false;
if (Array.isArray(selectedValue)) {
if (isAsync) {
if (isLabeledValue) {
found =
(selectedValue as AntdLabeledValue[]).find(
element => element.value === opt.value,
@@ -232,7 +252,7 @@ const Select = ({
found = selectedValue.includes(opt.value);
}
} else {
found = isAsync
found = isLabeledValue
? (selectedValue as AntdLabeledValue).value === opt.value
: selectedValue === opt.value;
}
@@ -252,10 +272,10 @@ const Select = ({
!topOptions.find(
tOpt =>
tOpt.value ===
(isAsync ? (val as AntdLabeledValue)?.value : val),
(isLabeledValue ? (val as AntdLabeledValue)?.value : val),
)
) {
if (isAsync) {
if (isLabeledValue) {
const labelValue = val as AntdLabeledValue;
topOptions.push({
label: labelValue.label,
@@ -275,7 +295,7 @@ const Select = ({
}
}
},
[isAsync, isSingleMode, selectOptions],
[isAsync, isSingleMode, labelInValue, selectOptions],
);
const handleOnSelect = (
@@ -345,9 +365,10 @@ const Select = ({
const cachedCount = fetchedQueries.current.get(key);
if (cachedCount) {
setTotalCount(cachedCount);
setIsTyping(false);
return;
}
setLoading(true);
setIsLoading(true);
const fetchOptions = options as OptionsPagePromise;
fetchOptions(value, page, pageSize)
.then(({ data, totalCount }: OptionsTypePage) => {
@@ -356,39 +377,61 @@ const Select = ({
setTotalCount(totalCount);
})
.catch(onError)
.finally(() => setLoading(false));
.finally(() => {
setIsLoading(false);
setIsTyping(false);
});
},
[options],
);
const handleOnSearch = debounce((search: string) => {
const searchValue = search.trim();
// enables option creation
if (allowNewOptions && isSingleMode) {
const firstOption = selectOptions.length > 0 && selectOptions[0].value;
// replaces the last search value entered with the new one
// only when the value wasn't part of the original options
if (
searchValue &&
firstOption === searchedValue &&
!initialOptions.find(o => o.value === searchedValue)
) {
selectOptions.shift();
setSelectOptions(selectOptions);
}
if (searchValue && !hasOption(searchValue, selectOptions)) {
const newOption = {
label: searchValue,
value: searchValue,
};
// adds a custom option
const newOptions = [...selectOptions, newOption];
setSelectOptions(newOptions);
setSelectValue(searchValue);
}
}
setSearchedValue(searchValue);
}, DEBOUNCE_TIMEOUT);
const handleOnSearch = useMemo(
() =>
debounce((search: string) => {
const searchValue = search.trim();
// enables option creation
if (allowNewOptions && isSingleMode) {
const firstOption =
selectOptions.length > 0 && selectOptions[0].value;
// replaces the last search value entered with the new one
// only when the value wasn't part of the original options
if (
searchValue &&
firstOption === searchedValue &&
!initialOptions.find(o => o.value === searchedValue)
) {
selectOptions.shift();
setSelectOptions(selectOptions);
}
if (searchValue && !hasOption(searchValue, selectOptions)) {
const newOption = {
label: searchValue,
value: searchValue,
};
// adds a custom option
const newOptions = [...selectOptions, newOption];
setSelectOptions(newOptions);
setSelectValue(searchValue);
if (onChange) {
onChange(searchValue, newOptions);
}
}
}
setSearchedValue(searchValue);
if (!searchValue) {
setIsTyping(false);
}
}, DEBOUNCE_TIMEOUT),
[
allowNewOptions,
initialOptions,
isSingleMode,
onChange,
searchedValue,
selectOptions,
],
);
const handlePagination = (e: UIEvent<HTMLElement>) => {
const vScroll = e.currentTarget;
@@ -458,15 +501,30 @@ const Select = ({
}
}, [handleTopOptions, isSingleMode, selectValue]);
useEffect(() => {
if (loading !== undefined && loading !== isLoading) {
setIsLoading(loading);
}
}, [isLoading, loading]);
const dropdownRender = (
originNode: ReactElement & { ref?: RefObject<HTMLElement> },
) => {
if (!isDropdownVisible) {
originNode.ref?.current?.scrollTo({ top: 0 });
}
if ((isLoading && selectOptions.length === 0) || isTyping) {
return <StyledLoadingText>{t('Loading...')}</StyledLoadingText>;
}
return error ? <Error error={error} /> : originNode;
};
const onInputKeyDown = () => {
if (isAsync && !isTyping) {
setIsTyping(true);
}
};
const SuffixIcon = () => {
if (isLoading) {
return <StyledSpin size="small" />;
@@ -481,20 +539,21 @@ const Select = ({
<StyledContainer>
{header}
<StyledSelect
hasHeader={!!header}
aria-label={ariaLabel || name}
dropdownRender={dropdownRender}
filterOption={handleFilterOption}
getPopupContainer={triggerNode => triggerNode.parentNode}
labelInValue={isAsync}
labelInValue={isAsync || labelInValue}
maxTagCount={MAX_TAG_COUNT}
mode={mappedMode}
onDeselect={handleOnDeselect}
onDropdownVisibleChange={handleOnDropdownVisibleChange}
onInputKeyDown={onInputKeyDown}
onPopupScroll={isAsync ? handlePagination : undefined}
onSearch={shouldShowSearch ? handleOnSearch : undefined}
onSelect={handleOnSelect}
onClear={() => setSelectValue(undefined)}
onChange={onChange}
options={selectOptions}
placeholder={placeholder}
showSearch={shouldShowSearch}

View File

@@ -63,7 +63,7 @@ const TableViewStyles = styled.div<{
${({ scrollTable, theme }) =>
scrollTable &&
`
height: 380px;
flex: 1 1 auto;
margin-bottom: ${theme.gridUnit * 4}px;
overflow: auto;
`}
@@ -88,22 +88,24 @@ const TableViewStyles = styled.div<{
`${theme.gridUnit - 2}px solid ${theme.colors.grayscale.light2}`};
${({ small }) => small && `padding-bottom: 0;`}
}
`;
.pagination-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: ${({ theme }) => theme.colors.grayscale.light5};
const PaginationStyles = styled.div<{
isPaginationSticky?: boolean;
}>`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: ${({ theme }) => theme.colors.grayscale.light5};
${({ isPaginationSticky }) =>
isPaginationSticky &&
`
${({ isPaginationSticky }) =>
isPaginationSticky &&
`
position: sticky;
bottom: 0;
left: 0;
`};
}
.row-count-container {
margin-top: ${({ theme }) => theme.gridUnit * 2}px;
@@ -188,32 +190,38 @@ const TableView = ({
}
const isEmpty = !loading && content.length === 0;
const hasPagination = pageCount > 1 && withPagination;
return (
<TableViewStyles {...props}>
<TableCollection
getTableProps={getTableProps}
getTableBodyProps={getTableBodyProps}
prepareRow={prepareRow}
headerGroups={headerGroups}
rows={content}
columns={columns}
loading={loading}
/>
{isEmpty && (
<EmptyWrapperComponent>
{noDataText ? (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={noDataText}
/>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</EmptyWrapperComponent>
)}
{pageCount > 1 && withPagination && (
<div className="pagination-container">
<>
<TableViewStyles {...props}>
<TableCollection
getTableProps={getTableProps}
getTableBodyProps={getTableBodyProps}
prepareRow={prepareRow}
headerGroups={headerGroups}
rows={content}
columns={columns}
loading={loading}
/>
{isEmpty && (
<EmptyWrapperComponent>
{noDataText ? (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={noDataText}
/>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</EmptyWrapperComponent>
)}
</TableViewStyles>
{hasPagination && (
<PaginationStyles
className="pagination-container"
isPaginationSticky={props.isPaginationSticky}
>
<Pagination
totalPages={pageCount || 0}
currentPage={pageCount ? pageIndex + 1 : 0}
@@ -231,9 +239,9 @@ const TableView = ({
)}
</div>
)}
</div>
</PaginationStyles>
)}
</TableViewStyles>
</>
);
};

View File

@@ -19,8 +19,8 @@
import React, { useEffect, useRef } from 'react';
import moment from 'moment-timezone';
import { NativeGraySelect as Select } from 'src/components/Select';
import { t } from '@superset-ui/core';
import { Select } from 'src/components';
const DEFAULT_TIMEZONE = 'GMT Standard Time';
const MIN_SELECT_WIDTH = '400px';
@@ -92,12 +92,6 @@ const TIMEZONE_OPTIONS = TIMEZONES.sort(
offsets: getOffsetKey(zone.name),
}));
const timezoneOptions = TIMEZONE_OPTIONS.map(option => (
<Select.Option key={option.value} value={option.value}>
{option.label}
</Select.Option>
));
const TimezoneSelector = ({ onTimezoneChange, timezone }: TimezoneProps) => {
const prevTimezone = useRef(timezone);
const matchTimezoneToOptions = (timezone: string) =>
@@ -120,12 +114,12 @@ const TimezoneSelector = ({ onTimezoneChange, timezone }: TimezoneProps) => {
return (
<Select
ariaLabel={t('Timezone')}
css={{ minWidth: MIN_SELECT_WIDTH }} // smallest size for current values
onChange={onTimezoneChange}
value={timezone || DEFAULT_TIMEZONE}
>
{timezoneOptions}
</Select>
options={TIMEZONE_OPTIONS}
/>
);
};

View File

@@ -17,17 +17,32 @@
* under the License.
*/
import React from 'react';
import { useTheme } from '@superset-ui/core';
import { useTheme, css } from '@superset-ui/core';
import { Tooltip as AntdTooltip } from 'antd';
import { TooltipProps } from 'antd/lib/tooltip';
import { Global } from '@emotion/react';
export const Tooltip = (props: TooltipProps) => {
const theme = useTheme();
return (
<AntdTooltip
overlayStyle={{ fontSize: theme.typography.sizes.s, lineHeight: '1.6' }}
color={`${theme.colors.grayscale.dark2}e6`}
{...props}
/>
<>
{/* Safari hack to hide browser default tooltips */}
<Global
styles={css`
.ant-tooltip-open {
display: inline-block;
&::after {
content: '';
display: block;
}
}
`}
/>
<AntdTooltip
overlayStyle={{ fontSize: theme.typography.sizes.s, lineHeight: '1.6' }}
color={`${theme.colors.grayscale.dark2}e6`}
{...props}
/>
</>
);
};

View File

@@ -371,8 +371,8 @@ export const hydrateDashboard = (dashboardData, chartData) => (
// only persistent refreshFrequency will be saved to backend
shouldPersistRefreshFrequency: false,
css: dashboardData.css || '',
colorNamespace: metadata?.color_namespace || null,
colorScheme: metadata?.color_scheme || null,
colorNamespace: metadata?.color_namespace,
colorScheme: metadata?.color_scheme,
editMode: canEdit && editMode,
isPublished: dashboardData.published,
hasUnsavedChanges: false,

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import { CssEditor as AceCssEditor } from 'src/components/AsyncAceEditor';
import { AceEditorProps } from 'react-ace';
import userEvent from '@testing-library/user-event';
@@ -32,6 +32,12 @@ jest.mock('src/components/AsyncAceEditor', () => ({
),
}));
const templates = [
{ label: 'Template A', css: 'background-color: red;' },
{ label: 'Template B', css: 'background-color: blue;' },
{ label: 'Template C', css: 'background-color: yellow;' },
];
AceCssEditor.preload = () => new Promise(() => {});
test('renders with default props', () => {
@@ -46,14 +52,15 @@ test('renders with initial CSS', () => {
expect(screen.getByText(initialCss)).toBeInTheDocument();
});
test('renders with templates', () => {
const templates = ['Template A', 'Template B', 'Template C'];
test('renders with templates', async () => {
render(<CssEditor triggerNode={<>Click</>} templates={templates} />);
userEvent.click(screen.getByRole('button', { name: 'Click' }));
userEvent.click(screen.getByText('Load a CSS template'));
templates.forEach(template =>
expect(screen.getByText(template)).toBeInTheDocument(),
);
userEvent.hover(screen.getByText('Load a CSS template'));
await waitFor(() => {
templates.forEach(template =>
expect(screen.getByText(template.label)).toBeInTheDocument(),
);
});
});
test('triggers onChange when using the editor', () => {
@@ -73,9 +80,8 @@ test('triggers onChange when using the editor', () => {
expect(onChange).toHaveBeenLastCalledWith(initialCss.concat(additionalCss));
});
test('triggers onChange when selecting a template', () => {
test('triggers onChange when selecting a template', async () => {
const onChange = jest.fn();
const templates = ['Template A', 'Template B', 'Template C'];
render(
<CssEditor
triggerNode={<>Click</>}
@@ -86,6 +92,6 @@ test('triggers onChange when selecting a template', () => {
userEvent.click(screen.getByRole('button', { name: 'Click' }));
userEvent.click(screen.getByText('Load a CSS template'));
expect(onChange).not.toHaveBeenCalled();
userEvent.click(screen.getByText('Template A'));
userEvent.click(await screen.findByText('Template A'));
expect(onChange).toHaveBeenCalledTimes(1);
});

View File

@@ -18,11 +18,30 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'src/components/Select';
import { t } from '@superset-ui/core';
import { Menu, Dropdown } from 'src/common/components';
import Button from 'src/components/Button';
import { t, styled } from '@superset-ui/core';
import ModalTrigger from 'src/components/ModalTrigger';
import { CssEditor as AceCssEditor } from 'src/components/AsyncAceEditor';
const StyledWrapper = styled.div`
${({ theme }) => `
.css-editor-header {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: ${theme.gridUnit * 2}px;
h5 {
margin-top: ${theme.gridUnit}px;
}
}
.css-editor {
border: 1px solid ${theme.colors.grayscale.light1};
}
`}
`;
const propTypes = {
initialCss: PropTypes.string,
triggerNode: PropTypes.node.isRequired,
@@ -55,21 +74,24 @@ class CssEditor extends React.PureComponent {
});
}
changeCssTemplate(opt) {
this.changeCss(opt.css);
changeCssTemplate({ key }) {
this.changeCss(key);
}
renderTemplateSelector() {
if (this.props.templates) {
const menu = (
<Menu onClick={this.changeCssTemplate}>
{this.props.templates.map(template => (
<Menu.Item key={template.css}>{template.label}</Menu.Item>
))}
</Menu>
);
return (
<div style={{ zIndex: 10 }}>
<h5>{t('Load a template')}</h5>
<Select
options={this.props.templates}
placeholder={t('Load a CSS template')}
onChange={this.changeCssTemplate}
/>
</div>
<Dropdown overlay={menu} placement="bottomRight">
<Button>{t('Load a CSS template')}</Button>
</Dropdown>
);
}
return null;
@@ -81,24 +103,23 @@ class CssEditor extends React.PureComponent {
triggerNode={this.props.triggerNode}
modalTitle={t('CSS')}
modalBody={
<div>
{this.renderTemplateSelector()}
<div style={{ zIndex: 1 }}>
<StyledWrapper>
<div className="css-editor-header">
<h5>{t('Live CSS editor')}</h5>
<div style={{ border: 'solid 1px grey' }}>
<AceCssEditor
minLines={12}
maxLines={30}
onChange={this.changeCss}
height="200px"
width="100%"
editorProps={{ $blockScrolling: true }}
enableLiveAutocompletion
value={this.state.css || ''}
/>
</div>
{this.renderTemplateSelector()}
</div>
</div>
<AceCssEditor
className="css-editor"
minLines={12}
maxLines={30}
onChange={this.changeCss}
height="200px"
width="100%"
editorProps={{ $blockScrolling: true }}
enableLiveAutocompletion
value={this.state.css || ''}
/>
</StyledWrapper>
}
/>
);

View File

@@ -289,7 +289,7 @@ class Dashboard extends React.PureComponent {
}
return (
<>
<OmniContainer logEvent={this.props.actions.logEvent} />
<OmniContainer />
<DashboardBuilder />
</>
);

View File

@@ -18,7 +18,7 @@
*/
/* eslint-env browser */
import cx from 'classnames';
import React, { FC } from 'react';
import React, { FC, useCallback, useMemo } from 'react';
import { JsonObject, styled, css } from '@superset-ui/core';
import ErrorBoundary from 'src/components/ErrorBoundary';
import BuilderComponentPane from 'src/dashboard/components/BuilderComponentPane';
@@ -157,15 +157,14 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
state => state.dashboardState.fullSizeChartId,
);
const handleChangeTab = ({
pathToTabIndex,
}: {
pathToTabIndex: string[];
}) => {
dispatch(setDirectPathToChild(pathToTabIndex));
};
const handleChangeTab = useCallback(
({ pathToTabIndex }: { pathToTabIndex: string[] }) => {
dispatch(setDirectPathToChild(pathToTabIndex));
},
[dispatch],
);
const handleDeleteTopLevelTabs = () => {
const handleDeleteTopLevelTabs = useCallback(() => {
dispatch(deleteTopLevelTabs());
const firstTab = getDirectPathToTabIndex(
@@ -173,7 +172,12 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
0,
);
dispatch(setDirectPathToChild(firstTab));
};
}, [dashboardLayout, dispatch]);
const handleDrop = useCallback(
dropResult => dispatch(handleComponentDrop(dropResult)),
[dispatch],
);
const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
const rootChildId = dashboardRoot.children[0];
@@ -217,6 +221,54 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
const filterBarHeight = `calc(100vh - ${offset}px)`;
const filterBarOffset = dashboardFiltersOpen ? 0 : barTopOffset + 20;
const draggableStyle = useMemo(
() => ({
marginLeft: dashboardFiltersOpen || editMode ? 0 : -32,
}),
[dashboardFiltersOpen, editMode],
);
const renderDraggableContent = useCallback(
({ dropIndicatorProps }: { dropIndicatorProps: JsonObject }) => (
<div>
{!hideDashboardHeader && <DashboardHeader />}
{dropIndicatorProps && <div {...dropIndicatorProps} />}
{!isReport && topLevelTabs && (
<WithPopoverMenu
shouldFocus={shouldFocusTabs}
menuItems={[
<IconButton
icon={<Icons.FallOutlined iconSize="xl" />}
label="Collapse tab content"
onClick={handleDeleteTopLevelTabs}
/>,
]}
editMode={editMode}
>
{/* @ts-ignore */}
<DashboardComponent
id={topLevelTabs?.id}
parentId={DASHBOARD_ROOT_ID}
depth={DASHBOARD_ROOT_DEPTH + 1}
index={0}
renderTabContent={false}
renderHoverMenu={false}
onChangeTab={handleChangeTab}
/>
</WithPopoverMenu>
)}
</div>
),
[
editMode,
handleChangeTab,
handleDeleteTopLevelTabs,
hideDashboardHeader,
isReport,
topLevelTabs,
],
);
return (
<StyledDiv>
{nativeFiltersEnabled && !editMode && (
@@ -244,45 +296,13 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
depth={DASHBOARD_ROOT_DEPTH}
index={0}
orientation="column"
onDrop={dropResult => dispatch(handleComponentDrop(dropResult))}
onDrop={handleDrop}
editMode={editMode}
// you cannot drop on/displace tabs if they already exist
disableDragDrop={!!topLevelTabs}
style={{
marginLeft: dashboardFiltersOpen || editMode ? 0 : -32,
}}
style={draggableStyle}
>
{({ dropIndicatorProps }: { dropIndicatorProps: JsonObject }) => (
<div>
{!hideDashboardHeader && <DashboardHeader />}
{dropIndicatorProps && <div {...dropIndicatorProps} />}
{!isReport && topLevelTabs && (
<WithPopoverMenu
shouldFocus={shouldFocusTabs}
menuItems={[
<IconButton
icon={<Icons.FallOutlined iconSize="xl" />}
label="Collapse tab content"
onClick={handleDeleteTopLevelTabs}
/>,
]}
editMode={editMode}
>
{/*
// @ts-ignore */}
<DashboardComponent
id={topLevelTabs?.id}
parentId={DASHBOARD_ROOT_ID}
depth={DASHBOARD_ROOT_DEPTH + 1}
index={0}
renderTabContent={false}
renderHoverMenu={false}
onChangeTab={handleChangeTab}
/>
</WithPopoverMenu>
)}
</div>
)}
{renderDraggableContent}
</DragDroppable>
</StyledHeader>
<StyledContent fullSizeChartId={fullSizeChartId}>

View File

@@ -38,6 +38,16 @@ const propTypes = {
const defaultProps = {};
const renderDraggableContentBottom = dropProps =>
dropProps.dropIndicatorProps && (
<div className="drop-indicator drop-indicator--bottom" />
);
const renderDraggableContentTop = dropProps =>
dropProps.dropIndicatorProps && (
<div className="drop-indicator drop-indicator--top" />
);
class DashboardGrid extends React.PureComponent {
constructor(props) {
super(props);
@@ -144,11 +154,7 @@ class DashboardGrid extends React.PureComponent {
className="empty-droptarget"
editMode
>
{({ dropIndicatorProps }) =>
dropIndicatorProps && (
<div className="drop-indicator drop-indicator--bottom" />
)
}
{renderDraggableContentBottom}
</DragDroppable>
)}
@@ -181,11 +187,7 @@ class DashboardGrid extends React.PureComponent {
className="empty-droptarget"
editMode
>
{({ dropIndicatorProps }) =>
dropIndicatorProps && (
<div className="drop-indicator drop-indicator--top" />
)
}
{renderDraggableContentTop}
</DragDroppable>
)}

View File

@@ -57,6 +57,8 @@ const sortByStatus = (indicators: Indicator[]): Indicator[] => {
);
};
const indicatorsInitialState: Indicator[] = [];
export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => {
const dispatch = useDispatch();
const datasources = useSelector<RootState, any>(state => state.datasources);
@@ -77,9 +79,11 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => {
state => state.dataMask,
);
const [nativeIndicators, setNativeIndicators] = useState<Indicator[]>([]);
const [nativeIndicators, setNativeIndicators] = useState<Indicator[]>(
indicatorsInitialState,
);
const [dashboardIndicators, setDashboardIndicators] = useState<Indicator[]>(
[],
indicatorsInitialState,
);
const onHighlightFilterSource = useCallback(
@@ -90,46 +94,79 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => {
);
const chart = charts[chartId];
const prevChartStatus = usePrevious(chart?.chartStatus);
const prevChart = usePrevious(chart);
const prevChartStatus = prevChart?.chartStatus;
const prevDashboardFilters = usePrevious(dashboardFilters);
const prevDatasources = usePrevious(datasources);
const showIndicators =
chart?.chartStatus && ['rendered', 'success'].includes(chart.chartStatus);
const showIndicators = useCallback(
() =>
chart?.chartStatus && ['rendered', 'success'].includes(chart.chartStatus),
[chart.chartStatus],
);
useEffect(() => {
if (!showIndicators) {
setDashboardIndicators([]);
}
if (prevChartStatus !== 'success') {
setDashboardIndicators(
selectIndicatorsForChart(chartId, dashboardFilters, datasources, chart),
);
if (!showIndicators && dashboardIndicators.length > 0) {
setDashboardIndicators(indicatorsInitialState);
} else if (prevChartStatus !== 'success') {
if (
chart?.queriesResponse?.[0]?.rejected_filters !==
prevChart?.queriesResponse?.[0]?.rejected_filters ||
chart?.queriesResponse?.[0]?.applied_filters !==
prevChart?.queriesResponse?.[0]?.applied_filters ||
dashboardFilters !== prevDashboardFilters ||
datasources !== prevDatasources
) {
setDashboardIndicators(
selectIndicatorsForChart(
chartId,
dashboardFilters,
datasources,
chart,
),
);
}
}
}, [
chart,
chartId,
dashboardFilters,
dashboardIndicators.length,
datasources,
prevChart?.queriesResponse,
prevChartStatus,
prevDashboardFilters,
prevDatasources,
showIndicators,
]);
const prevNativeFilters = usePrevious(nativeFilters);
const prevDashboardLayout = usePrevious(present);
const prevDataMask = usePrevious(dataMask);
const prevChartConfig = usePrevious(
dashboardInfo.metadata?.chart_configuration,
);
useEffect(() => {
if (!showIndicators) {
setNativeIndicators([]);
}
if (prevChartStatus !== 'success') {
setNativeIndicators(
selectNativeIndicatorsForChart(
nativeFilters,
dataMask,
chartId,
chart,
present,
dashboardInfo.metadata?.chart_configuration,
),
);
if (!showIndicators && nativeIndicators.length > 0) {
setNativeIndicators(indicatorsInitialState);
} else if (prevChartStatus !== 'success') {
if (
chart?.queriesResponse?.[0]?.rejected_filters !==
prevChart?.queriesResponse?.[0]?.rejected_filters ||
chart?.queriesResponse?.[0]?.applied_filters !==
prevChart?.queriesResponse?.[0]?.applied_filters ||
nativeFilters !== prevNativeFilters ||
present !== prevDashboardLayout ||
dataMask !== prevDataMask ||
prevChartConfig !== dashboardInfo.metadata?.chart_configuration
) {
setNativeIndicators(
selectNativeIndicatorsForChart(
nativeFilters,
dataMask,
chartId,
chart,
present,
dashboardInfo.metadata?.chart_configuration,
),
);
}
}
}, [
chart,
@@ -137,8 +174,14 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => {
dashboardInfo.metadata?.chart_configuration,
dataMask,
nativeFilters,
nativeIndicators.length,
present,
prevChart?.queriesResponse,
prevChartConfig,
prevChartStatus,
prevDashboardLayout,
prevDataMask,
prevNativeFilters,
showIndicators,
]);
@@ -155,17 +198,33 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => {
[dashboardIndicators, nativeIndicators],
);
const appliedCrossFilterIndicators = indicators.filter(
indicator => indicator.status === IndicatorStatus.CrossFilterApplied,
const appliedCrossFilterIndicators = useMemo(
() =>
indicators.filter(
indicator => indicator.status === IndicatorStatus.CrossFilterApplied,
),
[indicators],
);
const appliedIndicators = indicators.filter(
indicator => indicator.status === IndicatorStatus.Applied,
const appliedIndicators = useMemo(
() =>
indicators.filter(
indicator => indicator.status === IndicatorStatus.Applied,
),
[indicators],
);
const unsetIndicators = indicators.filter(
indicator => indicator.status === IndicatorStatus.Unset,
const unsetIndicators = useMemo(
() =>
indicators.filter(
indicator => indicator.status === IndicatorStatus.Unset,
),
[indicators],
);
const incompatibleIndicators = indicators.filter(
indicator => indicator.status === IndicatorStatus.Incompatible,
const incompatibleIndicators = useMemo(
() =>
indicators.filter(
indicator => indicator.status === IndicatorStatus.Incompatible,
),
[indicators],
);
if (

View File

@@ -16,16 +16,17 @@
* specific language governing permissions and limitations
* under the License.
*/
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 {
ensureIsArray,
FeatureFlag,
FilterState,
isFeatureEnabled,
} from '@superset-ui/core';
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 { areObjectsEqual } from 'src/reduxUtils';
import { Layout } from '../../types';
import { getTreeCheckedItems } from '../nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils';
@@ -154,6 +155,8 @@ export type Indicator = {
path?: string[];
};
const cachedIndicatorsForChart = {};
const cachedDashboardFilterDataForChart = {};
// inspects redux state to find what the filter indicators should be shown for a given chart
export const selectIndicatorsForChart = (
chartId: number,
@@ -165,37 +168,75 @@ export const selectIndicatorsForChart = (
// so grab the columns from the applied/rejected filters
const appliedColumns = getAppliedColumns(chart);
const rejectedColumns = getRejectedColumns(chart);
const matchingFilters = Object.values(filters).filter(
filter => filter.chartId !== chartId,
);
const matchingDatasources = Object.entries(datasources)
.filter(([key]) =>
matchingFilters.find(filter => filter.datasourceId === key),
)
.map(([, datasource]) => datasource);
const indicators = Object.values(filters)
.filter(filter => filter.chartId !== chartId)
.reduce(
(acc, filter) =>
acc.concat(
selectIndicatorsForChartFromFilter(
chartId,
filter,
datasources[filter.datasourceId] || {},
appliedColumns,
rejectedColumns,
),
const cachedFilterData = cachedDashboardFilterDataForChart[chartId];
if (
cachedIndicatorsForChart[chartId] &&
areObjectsEqual(cachedFilterData?.appliedColumns, appliedColumns) &&
areObjectsEqual(cachedFilterData?.rejectedColumns, rejectedColumns) &&
areObjectsEqual(cachedFilterData?.matchingFilters, matchingFilters) &&
areObjectsEqual(cachedFilterData?.matchingDatasources, matchingDatasources)
) {
return cachedIndicatorsForChart[chartId];
}
const indicators = matchingFilters.reduce(
(acc, filter) =>
acc.concat(
selectIndicatorsForChartFromFilter(
chartId,
filter,
datasources[filter.datasourceId] || {},
appliedColumns,
rejectedColumns,
),
[] as Indicator[],
);
),
[] as Indicator[],
);
indicators.sort((a, b) => a.name.localeCompare(b.name));
cachedIndicatorsForChart[chartId] = indicators;
cachedDashboardFilterDataForChart[chartId] = {
appliedColumns,
rejectedColumns,
matchingFilters,
matchingDatasources,
};
return indicators;
};
const cachedNativeIndicatorsForChart = {};
let cachedNativeFilterDataForChart: any = {};
const defaultChartConfig = {};
export const selectNativeIndicatorsForChart = (
nativeFilters: Filters,
dataMask: DataMaskStateWithId,
chartId: number,
chart: any,
dashboardLayout: Layout,
chartConfiguration: ChartConfiguration = {},
chartConfiguration: ChartConfiguration = defaultChartConfig,
): Indicator[] => {
const appliedColumns = getAppliedColumns(chart);
const rejectedColumns = getRejectedColumns(chart);
const cachedFilterData = cachedNativeFilterDataForChart[chartId];
if (
cachedNativeIndicatorsForChart[chartId] &&
areObjectsEqual(cachedFilterData?.appliedColumns, appliedColumns) &&
areObjectsEqual(cachedFilterData?.rejectedColumns, rejectedColumns) &&
cachedNativeFilterDataForChart?.nativeFilters === nativeFilters &&
cachedNativeFilterDataForChart?.dashboardLayout === dashboardLayout &&
cachedNativeFilterDataForChart?.chartConfiguration === chartConfiguration &&
cachedNativeFilterDataForChart?.dataMask === dataMask
) {
return cachedNativeIndicatorsForChart[chartId];
}
const getStatus = ({
label,
column,
@@ -283,5 +324,18 @@ export const selectNativeIndicatorsForChart = (
})
.filter(filter => filter.status === IndicatorStatus.CrossFilterApplied);
}
return crossFilterIndicators.concat(nativeFilterIndicators);
const indicators = crossFilterIndicators.concat(nativeFilterIndicators);
cachedNativeIndicatorsForChart[chartId] = indicators;
cachedNativeFilterDataForChart = {
...cachedNativeFilterDataForChart,
nativeFilters,
dashboardLayout,
chartConfiguration,
dataMask,
};
cachedNativeFilterDataForChart[chartId] = {
appliedColumns,
rejectedColumns,
};
return indicators;
};

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