From f7f60cc75d4166510727903813499370a0277d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CA=88=E1=B5=83=E1=B5=A2?= Date: Wed, 29 Apr 2020 21:54:07 -0700 Subject: [PATCH] [Chart & Dashboard] improve listview filter ui and add expandable list support (#9651) * better styling for select filters and search input * add emotion babel plugin * add spec for search input * fix async select, add beeter error messages * add error boundary around views * cleanup and lint * add expandable list to dashboards * fix emotion babel config * fix specs * remove commented out code * add selected style * DRY up styles * add icons * fix type * add apache license * fix svgs * fix ExpandableList type * reduce custom css * lint --- superset-frontend/images/icons/cancel-x.svg | 27 +++ superset-frontend/images/icons/search.svg | 29 +++ superset-frontend/package-lock.json | 212 +++++++++++------- superset-frontend/package.json | 1 + .../components/ListView/ListView_spec.jsx | 2 +- .../components/SearchInput_spec.jsx | 92 ++++++++ .../dashboardList/DashboardList_spec.jsx | 2 + superset-frontend/src/components/Button.jsx | 1 + .../src/components/ExpandableList.tsx | 62 +++++ .../src/components/ListView/Filters.tsx | 14 +- .../components/ListView/ListViewStyles.less | 7 + .../components/ListView/TableCollection.tsx | 6 +- .../src/components/ListView/types.ts | 8 +- .../src/components/SearchInput.tsx | 87 ++++++- .../src/components/StyledSelect.tsx | 65 +++--- .../src/views/chartList/ChartList.tsx | 41 ++-- .../src/views/dashboardList/DashboardList.tsx | 41 +++- superset-frontend/src/welcome/App.jsx | 17 +- superset-frontend/webpack.config.js | 9 + superset/dashboards/api.py | 4 + 20 files changed, 558 insertions(+), 169 deletions(-) create mode 100644 superset-frontend/images/icons/cancel-x.svg create mode 100644 superset-frontend/images/icons/search.svg create mode 100644 superset-frontend/spec/javascripts/components/SearchInput_spec.jsx create mode 100644 superset-frontend/src/components/ExpandableList.tsx diff --git a/superset-frontend/images/icons/cancel-x.svg b/superset-frontend/images/icons/cancel-x.svg new file mode 100644 index 0000000000000..be42ff5a7102b --- /dev/null +++ b/superset-frontend/images/icons/cancel-x.svg @@ -0,0 +1,27 @@ + + + + Icon / X-Small@1.5x + Created with Sketch. + + + + + \ No newline at end of file diff --git a/superset-frontend/images/icons/search.svg b/superset-frontend/images/icons/search.svg new file mode 100644 index 0000000000000..29e04e35f3050 --- /dev/null +++ b/superset-frontend/images/icons/search.svg @@ -0,0 +1,29 @@ + + + + Icon / Search@1.5x + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index afffab3adecdc..bb729c97a47ff 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -5360,6 +5360,44 @@ "prop-types": "^15.6.0" } }, + "@emotion/babel-plugin-jsx-pragmatic": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin-jsx-pragmatic/-/babel-plugin-jsx-pragmatic-0.1.5.tgz", + "integrity": "sha512-y+3AJ0SItMDaAgGPVkQBC/S/BaqaPACkQ6MyCI2CUlrjTxKttTVfD3TMtcs7vLEcLxqzZ1xiG0vzwCXjhopawQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-jsx": "^7.2.0" + } + }, + "@emotion/babel-preset-css-prop": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/babel-preset-css-prop/-/babel-preset-css-prop-10.0.27.tgz", + "integrity": "sha512-rducrjTpLGDholp0l2l4pXqpzAqYYGMg/x4IteO0db2smf6zegn6RRZdDnbaoMSs63tfPWgo2WukT1/F1gX/AA==", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.3.0", + "@babel/runtime": "^7.5.5", + "@emotion/babel-plugin-jsx-pragmatic": "^0.1.5", + "babel-plugin-emotion": "^10.0.27" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + } + } + }, "@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -11831,7 +11869,7 @@ }, "chownr": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true @@ -11866,7 +11904,7 @@ }, "debug": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "optional": true, @@ -11897,7 +11935,7 @@ }, "fs-minipass": { "version": "1.2.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, @@ -11931,7 +11969,7 @@ }, "glob": { "version": "7.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, @@ -11963,7 +12001,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, @@ -11984,7 +12022,7 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true @@ -12025,14 +12063,14 @@ }, "minimist": { "version": "0.0.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true }, "minipass": { "version": "2.3.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, @@ -12043,7 +12081,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, @@ -12053,7 +12091,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -12063,14 +12101,14 @@ }, "ms": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, "optional": true }, "needle": { "version": "2.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, "optional": true, @@ -12082,7 +12120,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -12101,7 +12139,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -12112,14 +12150,14 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, "optional": true, @@ -12199,7 +12237,7 @@ }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true @@ -12228,7 +12266,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -12244,7 +12282,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, @@ -12275,7 +12313,7 @@ }, "semver": { "version": "5.7.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, "optional": true @@ -12335,7 +12373,7 @@ }, "tar": { "version": "4.4.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, @@ -12375,7 +12413,7 @@ }, "yallist": { "version": "3.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true, "optional": true @@ -16792,28 +16830,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, @@ -16824,14 +16862,14 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": false, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, @@ -16842,42 +16880,42 @@ }, "chownr": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "optional": true, @@ -16887,28 +16925,28 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, @@ -16918,14 +16956,14 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, @@ -16942,7 +16980,7 @@ }, "glob": { "version": "7.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, @@ -16957,14 +16995,14 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": false, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, @@ -16974,7 +17012,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, @@ -16984,7 +17022,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, @@ -16995,21 +17033,21 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true }, "ini": { "version": "1.3.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, @@ -17019,14 +17057,14 @@ }, "isarray": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, @@ -17036,14 +17074,14 @@ }, "minimist": { "version": "0.0.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true }, "minipass": { "version": "2.3.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, @@ -17054,7 +17092,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, @@ -17064,7 +17102,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -17074,14 +17112,14 @@ }, "ms": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, "optional": true }, "needle": { "version": "2.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.0.tgz", "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, "optional": true, @@ -17093,7 +17131,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -17112,7 +17150,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -17123,14 +17161,14 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, "optional": true, @@ -17141,7 +17179,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, @@ -17154,21 +17192,21 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, @@ -17178,21 +17216,21 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, @@ -17203,21 +17241,21 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, @@ -17239,7 +17277,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -17255,7 +17293,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, @@ -17265,49 +17303,49 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.7.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, @@ -17319,7 +17357,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -17329,7 +17367,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, @@ -17339,14 +17377,14 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, @@ -17362,14 +17400,14 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, @@ -17379,14 +17417,14 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true }, "yallist": { "version": "3.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true, "optional": true diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 66a433bfbaabc..e591a8bd8542b 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -181,6 +181,7 @@ "@babel/preset-env": "^7.8.7", "@babel/preset-react": "^7.8.3", "@babel/register": "^7.8.6", + "@emotion/babel-preset-css-prop": "^10.0.27", "@hot-loader/react-dom": "^16.13.0", "@types/classnames": "^2.2.9", "@istanbuljs/nyc-config-typescript": "^1.0.1", diff --git a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx index 637667b513857..54b29c0e625e9 100644 --- a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx +++ b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx @@ -384,7 +384,7 @@ describe('ListView with new UI filters', () => { act(() => { wrapper - .find('[data-test="filters-search"]') + .find('[data-test="search-input"]') .last() .props() .onBlur(); diff --git a/superset-frontend/spec/javascripts/components/SearchInput_spec.jsx b/superset-frontend/spec/javascripts/components/SearchInput_spec.jsx new file mode 100644 index 0000000000000..da31a325f6b99 --- /dev/null +++ b/superset-frontend/spec/javascripts/components/SearchInput_spec.jsx @@ -0,0 +1,92 @@ +/** + * 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 SearchInput from 'src/components/SearchInput'; + +describe('SearchInput', () => { + const defaultProps = { + onSubmit: jest.fn(), + onClear: jest.fn(), + onChange: jest.fn(), + value: '', + }; + + const factory = overrideProps => { + const props = { ...defaultProps, ...(overrideProps || {}) }; + return shallow(); + }; + + let wrapper; + + beforeAll(() => { + wrapper = factory(); + }); + + afterEach(() => { + defaultProps.onSubmit.mockClear(); + defaultProps.onClear.mockClear(); + defaultProps.onChange.mockClear(); + }); + + it('renders', () => { + expect(React.isValidElement()).toBe(true); + }); + + const typeSearchInput = value => { + wrapper + .find('[data-test="search-input"]') + .props() + .onChange({ currentTarget: { value } }); + }; + + it('submits on enter', () => { + typeSearchInput('foo'); + + wrapper + .find('[data-test="search-input"]') + .props() + .onKeyDown({ key: 'Enter' }); + + expect(defaultProps.onChange).toHaveBeenCalled(); + expect(defaultProps.onSubmit).toHaveBeenCalled(); + }); + + it('submits on search icon click', () => { + typeSearchInput('bar'); + + wrapper + .find('[data-test="search-submit"]') + .props() + .onClick(); + + expect(defaultProps.onSubmit).toHaveBeenCalled(); + }); + + it('clears on clear icon click', () => { + const wrapper2 = factory({ value: 'fizz' }); + wrapper2 + .find('[data-test="search-clear"]') + .props() + .onClick(); + + expect(defaultProps.onClear).toHaveBeenCalled(); + }); +}); diff --git a/superset-frontend/spec/javascripts/views/dashboardList/DashboardList_spec.jsx b/superset-frontend/spec/javascripts/views/dashboardList/DashboardList_spec.jsx index 830c9b9c9dcd3..25e597a77188b 100644 --- a/superset-frontend/spec/javascripts/views/dashboardList/DashboardList_spec.jsx +++ b/superset-frontend/spec/javascripts/views/dashboardList/DashboardList_spec.jsx @@ -43,6 +43,7 @@ const mockDashboards = [...new Array(3)].map((_, i) => ({ changed_by_fk: 1, published: true, changed_on: new Date().toISOString(), + owners: [{ first_name: 'admin', last_name: 'admin_user' }], })); fetchMock.get(dashboardsInfoEndpoint, { @@ -96,6 +97,7 @@ describe('DashboardList', () => { }); it('edits', () => { expect(wrapper.find(PropertiesModal)).toHaveLength(0); + wrapper .find('.fa-pencil') .first() diff --git a/superset-frontend/src/components/Button.jsx b/superset-frontend/src/components/Button.jsx index 80be8498c6e20..8cd7105d5519c 100644 --- a/superset-frontend/src/components/Button.jsx +++ b/superset-frontend/src/components/Button.jsx @@ -32,6 +32,7 @@ const propTypes = { onClick: PropTypes.func, disabled: PropTypes.bool, bsSize: PropTypes.string, + bsStyle: PropTypes.string, btnStyles: PropTypes.string, }; const defaultProps = { diff --git a/superset-frontend/src/components/ExpandableList.tsx b/superset-frontend/src/components/ExpandableList.tsx new file mode 100644 index 0000000000000..89332d23cb624 --- /dev/null +++ b/superset-frontend/src/components/ExpandableList.tsx @@ -0,0 +1,62 @@ +/** + * 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, { ReactNode, useState } from 'react'; +// @ts-ignore +import { css } from '@emotion/core'; +import Button from 'src/components/Button'; + +interface Props { + items: string[] | ReactNode[]; + display?: number; +} + +function intersperse(arr: any[], sep: string | ReactNode) { + if (arr.length === 0) { + return []; + } + + return arr.slice(1).reduce((xs, x) => xs.concat([sep, x]), [arr[0]]); +} + +export default function ExpandableList({ items, display = 3 }: Props) { + const [showingAll, setShowingAll] = useState(false); + const toggleShowingAll = () => setShowingAll(!showingAll); + const itemsToDisplay = items.slice(0, display); + const showMoreAction = items.length > display; + + const lessAction = ( + + ); + const moreAction = ( + + ); + return ( + + {showingAll + ? intersperse(items, ', ') + : intersperse(itemsToDisplay, ', ')} + {showMoreAction && ','} + {showMoreAction && (showingAll ? lessAction : moreAction)} + + ); +} diff --git a/superset-frontend/src/components/ListView/Filters.tsx b/superset-frontend/src/components/ListView/Filters.tsx index 6d9ba5921d233..13edfd8294c21 100644 --- a/superset-frontend/src/components/ListView/Filters.tsx +++ b/superset-frontend/src/components/ListView/Filters.tsx @@ -36,7 +36,7 @@ interface SelectFilterProps extends BaseFilter { } const FilterContainer = styled.div` - display: inline; + display: inline-flex; margin-right: 8px; `; @@ -116,6 +116,10 @@ interface SearchHeaderProps extends BaseFilter { function SearchFilter({ Header, initialValue, onSubmit }: SearchHeaderProps) { const [value, setValue] = useState(initialValue || ''); const handleSubmit = () => onSubmit(value); + const onClear = () => { + setValue(''); + onSubmit(''); + }; return ( @@ -126,12 +130,8 @@ function SearchFilter({ Header, initialValue, onSubmit }: SearchHeaderProps) { onChange={e => { setValue(e.currentTarget.value); }} - onKeyDown={e => { - if (e.key === 'Enter') { - handleSubmit(); - } - }} - onBlur={handleSubmit} + onSubmit={handleSubmit} + onClear={onClear} /> ); diff --git a/superset-frontend/src/components/ListView/ListViewStyles.less b/superset-frontend/src/components/ListView/ListViewStyles.less index 2a510c6c6da4b..bbe3d1944fd51 100644 --- a/superset-frontend/src/components/ListView/ListViewStyles.less +++ b/superset-frontend/src/components/ListView/ListViewStyles.less @@ -16,6 +16,9 @@ * specific language governing permissions and limitations * under the License. */ + +@import '~stylesheets/less/variables.less'; + .superset-list-view { .filter-dropdown { margin-top: 20px; @@ -61,6 +64,10 @@ margin: 0 8px; } + .table-row-selected { + background-color: @gray-bg; + } + .table-cell { word-break: break-all; } diff --git a/superset-frontend/src/components/ListView/TableCollection.tsx b/superset-frontend/src/components/ListView/TableCollection.tsx index 863e655ce6df3..52b561b721b80 100644 --- a/superset-frontend/src/components/ListView/TableCollection.tsx +++ b/superset-frontend/src/components/ListView/TableCollection.tsx @@ -69,11 +69,13 @@ export default function TableCollection({ {rows.map(row => { prepareRow(row); - const loadingProps = loading ? { className: 'table-row-loader' } : {}; return ( row.setState && row.setState({ hover: true })} onMouseLeave={() => row.setState && row.setState({ hover: false }) diff --git a/superset-frontend/src/components/ListView/types.ts b/superset-frontend/src/components/ListView/types.ts index 76acae3b7a3e4..f8e08a930e303 100644 --- a/superset-frontend/src/components/ListView/types.ts +++ b/superset-frontend/src/components/ListView/types.ts @@ -45,13 +45,7 @@ export type Filters = Filter[]; export interface FilterValue { id: string; operator?: string; - value: - | string - | boolean - | number - | null - | undefined - | { datasource_id: number; datasource_type: string }; + value: string | boolean | number | null | undefined; } export interface FetchDataConfig { diff --git a/superset-frontend/src/components/SearchInput.tsx b/superset-frontend/src/components/SearchInput.tsx index 2a4b8f1b3f234..11fba470cb4ee 100644 --- a/superset-frontend/src/components/SearchInput.tsx +++ b/superset-frontend/src/components/SearchInput.tsx @@ -17,13 +17,94 @@ * under the License. */ import styled from '@superset-ui/style'; +import React from 'react'; -export default styled.input` - background-color: ${({ theme }) => theme.colors.secondary.light5}; +interface Props { + onSubmit: () => void; + onClear: () => void; + value: string; + onChange: React.EventHandler>; + placeholder?: string; +} + +const SearchInputWrapper = styled.div` + position: relative; +`; + +const StyledInput = styled.input` + width: 200px; background-image: none; border: 1px solid ${({ theme }) => theme.colors.secondary.light2}; border-radius: 4px; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - padding: 4px 8px; + padding: 4px 28px; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + &:focus { + outline: none; + } +`; + +const SearchIcon = styled.div` + position: absolute; + z-index: 2; + display: block; + width: 28px; + height: 28px; + text-align: center; + cursor: pointer; + background-position: 2px 2px; + background-image: url('/static/assets/images/icons/search.svg'); + background-repeat: no-repeat; +`; + +const ClearIcon = styled.div` + position: absolute; + z-index: 2; + display: block; + width: 28px; + height: 28px; + text-align: center; + cursor: pointer; + right: 2px; + top: 1px; + background-position: 2px 2px; + background-image: url('/static/assets/images/icons/cancel-x.svg'); + background-repeat: no-repeat; `; + +export default function SearchInput({ + onChange, + onClear, + onSubmit, + placeholder = 'Search', + value, +}: Props) { + return ( + + onSubmit()} + /> + { + if (e.key === 'Enter') { + onSubmit(); + } + }} + onBlur={() => onSubmit()} + placeholder={placeholder} + onChange={onChange} + value={value} + /> + {value && ( + onClear()} + /> + )} + + ); +} diff --git a/superset-frontend/src/components/StyledSelect.tsx b/superset-frontend/src/components/StyledSelect.tsx index 0573fd6a10971..939bc43b79f4b 100644 --- a/superset-frontend/src/components/StyledSelect.tsx +++ b/superset-frontend/src/components/StyledSelect.tsx @@ -17,54 +17,51 @@ * under the License. */ import styled from '@superset-ui/style'; +import { css } from '@emotion/core'; // @ts-ignore import Select, { Async } from 'react-select'; -export default styled(Select)` - display: inline; +const styles = css` + display: block; &.is-focused:not(.is-open) > .Select-control { border: none; box-shadow: none; } - .Select-control { - display: inline-table; - border: none; - width: 100px; - &:focus, - &:hover { - border: none; - box-shadow: none; - } - .Select-arrow-zone { - padding-left: 10px; - } - } - .Select-menu-outer { - margin-top: 0; - border-bottom-left-radius: 0; - border-bottom-left-radius: 0; + &.is-open > .Select-control .Select-arrow { + top: 50%; } -`; -export const AsyncStyledSelect = styled(Async)` - display: inline; - &.is-focused:not(.is-open) > .Select-control { - border: none; - box-shadow: none; - } .Select-control { - display: inline-table; + display: inline-flex; border: none; - width: 100px; + width: 128px; + top: -5px; &:focus, &:hover { border: none; box-shadow: none; } - + .Select-multi-value-wrapper { + display: flex; + } + .Select-value { + position: relative; + padding-right: 2px; + max-width: 104px; + } + .Select-input { + padding-left: 0; + padding-right: 8px; + } .Select-arrow-zone { - padding-left: 10px; + width: auto; + padding: 0; + .Select-arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + } } } .Select-menu-outer { @@ -73,3 +70,11 @@ export const AsyncStyledSelect = styled(Async)` border-bottom-left-radius: 0; } `; + +export default styled(Select)` + ${styles} +`; + +export const AsyncStyledSelect = styled(Async)` + ${styles} +`; diff --git a/superset-frontend/src/views/chartList/ChartList.tsx b/superset-frontend/src/views/chartList/ChartList.tsx index 5b1c5b42c305c..37b5157256f60 100644 --- a/superset-frontend/src/views/chartList/ChartList.tsx +++ b/superset-frontend/src/views/chartList/ChartList.tsx @@ -85,20 +85,11 @@ class ChartList extends React.PureComponent { this.updateFilters, ); }, - ([e1, e2]) => { + e => { this.props.addDangerToast( - t( - 'An error occurred while fetching charts: %s, %s', - e1.message, - e2.message, - ), + t('An error occurred while fetching charts: %s', e.statusText), ); - if (e1) { - console.error(e1); - } - if (e2) { - console.error(e2); - } + console.error(e); }, ); } @@ -302,7 +293,10 @@ class ChartList extends React.PureComponent { (err: any) => { console.error(err); this.props.addDangerToast( - t('There was an issue deleting the selected charts'), + t( + 'There was an issue deleting the selected charts: %s', + err.statusText, + ), ); }, ); @@ -319,6 +313,7 @@ class ChartList extends React.PureComponent { }, loading: true, }); + const filterExps = filters .map(({ id: col, operator: opr, value }) => ({ col, @@ -329,9 +324,11 @@ class ChartList extends React.PureComponent { if ( fltr.col === 'datasource' && fltr.value && - typeof fltr.value === 'object' + typeof fltr.value === 'string' ) { - const { datasource_id: dsId, datasource_type: dsType } = fltr.value; + const { datasource_id: dsId, datasource_type: dsType } = JSON.parse( + fltr.value, + ); return [ ...acc, { ...fltr, col: 'datasource_id', value: dsId }, @@ -356,8 +353,9 @@ class ChartList extends React.PureComponent { this.setState({ charts: json.result, chartCount: json.count }); }) .catch(e => { + console.log(e.body); this.props.addDangerToast( - t('An error occurred while fetching charts: %s', e.message), + t('An error occurred while fetching charts: %s', e.statusText), ); }) .finally(() => { @@ -376,7 +374,7 @@ class ChartList extends React.PureComponent { return postProcess ? postProcess(json?.result) : json?.result; } catch (e) { this.props.addDangerToast( - t('An error occurred while fetching chart filters: %s', e.message), + t('An error occurred while fetching chart filters: %s', e.statusText), ); } return []; @@ -385,6 +383,10 @@ class ChartList extends React.PureComponent { convertOwners = (owners: any[]) => owners.map(({ text: label, value }) => ({ label, value })); + stringifyValues = (datasources: any[]) => { + return datasources.map(ds => ({ ...ds, value: JSON.stringify(ds.value) })); + }; + updateFilters = async () => { const { filterOperators } = this.state; const fetchOwners = this.createFetchResource( @@ -419,7 +421,10 @@ class ChartList extends React.PureComponent { input: 'select', operator: 'eq', unfilteredLabel: 'All', - fetchSelects: this.createFetchResource('/api/v1/chart/datasources'), + fetchSelects: this.createFetchResource( + '/api/v1/chart/datasources', + this.stringifyValues, + ), }, { Header: 'Search', diff --git a/superset-frontend/src/views/dashboardList/DashboardList.tsx b/superset-frontend/src/views/dashboardList/DashboardList.tsx index 0137c9ef023c8..25569afa82592 100644 --- a/superset-frontend/src/views/dashboardList/DashboardList.tsx +++ b/superset-frontend/src/views/dashboardList/DashboardList.tsx @@ -25,6 +25,7 @@ import React from 'react'; import { Panel } from 'react-bootstrap'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; import ListView from 'src/components/ListView/ListView'; +import ExpandableList from 'src/components/ExpandableList'; import { FetchDataConfig, FilterOperatorMap, @@ -102,7 +103,11 @@ class DashboardList extends React.PureComponent { }, ([e1, e2]) => { this.props.addDangerToast( - t('An error occurred while fetching Dashboards'), + t( + 'An error occurred while fetching Dashboards: %s, %s', + e1.statusText, + e1.statusText, + ), ); if (e1) { console.error(e1); @@ -143,6 +148,23 @@ class DashboardList extends React.PureComponent { accessor: 'dashboard_title', sortable: true, }, + { + Cell: ({ + row: { + original: { owners }, + }, + }: any) => ( + + `${firstName} ${lastName}`, + )} + display={2} + /> + ), + Header: t('Owners'), + accessor: 'owners', + }, { Cell: ({ row: { @@ -184,10 +206,6 @@ class DashboardList extends React.PureComponent { accessor: 'slug', hidden: true, }, - { - accessor: 'owners', - hidden: true, - }, { Cell: ({ row: { state, original } }: any) => { const handleDelete = () => this.handleDashboardDelete(original); @@ -281,9 +299,9 @@ class DashboardList extends React.PureComponent { loading: false, }); }) - .catch(() => { + .catch(e => { this.props.addDangerToast( - t('An error occurred while fetching Dashboards'), + t('An error occurred while fetching dashboards: %s', e.statusText), ); }); }; @@ -326,7 +344,10 @@ class DashboardList extends React.PureComponent { (err: any) => { console.error(err); this.props.addDangerToast( - t('There was an issue deleting the selected dashboards'), + t( + 'There was an issue deleting the selected dashboards: ', + err.statusText, + ), ); }, ); @@ -371,9 +392,9 @@ class DashboardList extends React.PureComponent { .then(({ json = {} }) => { this.setState({ dashboards: json.result, dashboardCount: json.count }); }) - .catch(() => { + .catch(e => { this.props.addDangerToast( - t('An error occurred while fetching Dashboards'), + t('An error occurred while fetching dashboards: %s', e.statusText), ); }) .finally(() => { diff --git a/superset-frontend/src/welcome/App.jsx b/superset-frontend/src/welcome/App.jsx index c97dea1275588..9cef3206b6268 100644 --- a/superset-frontend/src/welcome/App.jsx +++ b/superset-frontend/src/welcome/App.jsx @@ -27,6 +27,7 @@ import { ThemeProvider } from 'emotion-theming'; import { initFeatureFlags } from 'src/featureFlags'; import { supersetTheme } from '@superset-ui/style'; +import ErrorBoundary from 'src/components/ErrorBoundary'; import Menu from 'src/components/Menu/Menu'; import DashboardList from 'src/views/dashboardList/DashboardList'; import ChartList from 'src/views/chartList/ChartList'; @@ -65,16 +66,24 @@ const App = () => ( - + + + - + + + - + + + - + + + diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js index 50233b9874659..ceb514ee537d8 100644 --- a/superset-frontend/webpack.config.js +++ b/superset-frontend/webpack.config.js @@ -155,6 +155,15 @@ const babelLoader = { // faster when there are millions of small files cacheCompression: false, plugins: ['emotion'], + presets: [ + [ + '@emotion/babel-preset-css-prop', + { + autoLabel: true, + labelFormat: '[local]', + }, + ], + ], }, }; diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index c1d6590591ffc..4ff945be9e599 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -100,6 +100,10 @@ class DashboardRestApi(BaseSupersetModelRestApi): "changed_by.username", "changed_on", "dashboard_title", + "owners.id", + "owners.username", + "owners.first_name", + "owners.last_name", "id", "published", "slug",