This is an automated email from the ASF dual-hosted git repository.

erikrit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new b8eaa11  feat(frontend): add ability to download dashboard and chart 
as image (#9819)
b8eaa11 is described below

commit b8eaa114eddc6a42e2b69b7ab88479c3128a52be
Author: Christian Murphy <[email protected]>
AuthorDate: Sun May 17 14:50:27 2020 -0700

    feat(frontend): add ability to download dashboard and chart as image (#9819)
    
    * feat(frontend): add ability to export dashboard and chart as image (#6973)
    
    * refactor: migrate download image as to typescript
    
    * feature(frontend): download as image updates
    
    add toast message if error occurs.
    make generate file stem an internal method.
    
    * refactor(frontend): move default background color to a const
    
    * feat(frontend): wrap download image toast in translate function
---
 superset-frontend/package-lock.json                | 156 +++++++++++----------
 superset-frontend/package.json                     |   2 +
 .../components/HeaderActionsDropdown_spec.jsx      |   8 +-
 .../dashboard/components/HeaderActionsDropdown.jsx |   7 +
 .../dashboard/components/SliceHeaderControls.jsx   |  10 ++
 .../src/dashboard/util/downloadAsImage.ts          |  74 ++++++++++
 6 files changed, 182 insertions(+), 75 deletions(-)

diff --git a/superset-frontend/package-lock.json 
b/superset-frontend/package-lock.json
index 84999ca..d9c5e79 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -7339,6 +7339,15 @@
         "@types/jquery": "*"
       }
     },
+    "@types/dom-to-image": {
+      "version": "2.6.0",
+      "resolved": 
"https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.0.tgz";,
+      "integrity": 
"sha512-X7qEh5AI1s/fb/Ijb1WU/tl7nZjD/A3b0PZiq3QjD31EZkgPooPdpte9MCJWQgqjxA0F8AJFuPd53YDrFJFE7w==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/eslint-visitor-keys": {
       "version": "1.0.0",
       "resolved": 
"https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz";,
@@ -10406,7 +10415,7 @@
             },
             "debug": {
               "version": "4.1.1",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
               "dev": true,
               "optional": true,
@@ -10461,7 +10470,7 @@
             },
             "glob": {
               "version": "7.1.3",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
               "dev": true,
               "optional": true,
@@ -10493,7 +10502,7 @@
             },
             "ignore-walk": {
               "version": "3.0.1",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
               "dev": true,
               "optional": true,
@@ -10514,7 +10523,7 @@
             },
             "inherits": {
               "version": "2.0.3",
-              "resolved": false,
+              "resolved": "",
               "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
               "dev": true,
               "optional": true
@@ -10562,14 +10571,14 @@
             },
             "ms": {
               "version": "2.1.1",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
               "dev": true,
               "optional": true
             },
             "needle": {
               "version": "2.3.0",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==",
               "dev": true,
               "optional": true,
@@ -10581,7 +10590,7 @@
             },
             "node-pre-gyp": {
               "version": "0.12.0",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==",
               "dev": true,
               "optional": true,
@@ -10600,7 +10609,7 @@
             },
             "nopt": {
               "version": "4.0.1",
-              "resolved": false,
+              "resolved": "",
               "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
               "dev": true,
               "optional": true,
@@ -10611,14 +10620,14 @@
             },
             "npm-bundled": {
               "version": "1.0.6",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==",
               "dev": true,
               "optional": true
             },
             "npm-packlist": {
               "version": "1.4.1",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==",
               "dev": true,
               "optional": true,
@@ -10698,7 +10707,7 @@
             },
             "process-nextick-args": {
               "version": "2.0.0",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
               "dev": true,
               "optional": true
@@ -10718,7 +10727,7 @@
             },
             "readable-stream": {
               "version": "2.3.6",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
               "dev": true,
               "optional": true,
@@ -10734,7 +10743,7 @@
             },
             "rimraf": {
               "version": "2.6.3",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
               "dev": true,
               "optional": true,
@@ -10765,7 +10774,7 @@
             },
             "semver": {
               "version": "5.7.0",
-              "resolved": false,
+              "resolved": "",
               "integrity": 
"sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
               "dev": true,
               "optional": true
@@ -12933,6 +12942,11 @@
         }
       }
     },
+    "dom-to-image": {
+      "version": "2.6.0",
+      "resolved": 
"https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz";,
+      "integrity": "sha1-ilA2CAiMh7HCL5A0rgMuGJiVWGc="
+    },
     "dom-walk": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz";,
@@ -14651,28 +14665,28 @@
               "dependencies": {
                 "abbrev": {
                   "version": "1.1.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
                   "dev": true,
                   "optional": true
                 },
                 "ansi-regex": {
                   "version": "2.1.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
                   "dev": true,
                   "optional": true
                 },
                 "aproba": {
                   "version": "1.2.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
                   "dev": true,
                   "optional": true
                 },
                 "are-we-there-yet": {
                   "version": "1.1.5",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
                   "dev": true,
                   "optional": true,
@@ -14683,14 +14697,14 @@
                 },
                 "balanced-match": {
                   "version": "1.0.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
                   "dev": true,
                   "optional": true
                 },
                 "brace-expansion": {
                   "version": "1.1.11",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
                   "dev": true,
                   "optional": true,
@@ -14701,35 +14715,35 @@
                 },
                 "code-point-at": {
                   "version": "1.1.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
                   "dev": true,
                   "optional": true
                 },
                 "concat-map": {
                   "version": "0.0.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
                   "dev": true,
                   "optional": true
                 },
                 "console-control-strings": {
                   "version": "1.1.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
                   "dev": true,
                   "optional": true
                 },
                 "core-util-is": {
                   "version": "1.0.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
                   "dev": true,
                   "optional": true
                 },
                 "debug": {
                   "version": "4.1.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
                   "dev": true,
                   "optional": true,
@@ -14739,35 +14753,35 @@
                 },
                 "deep-extend": {
                   "version": "0.6.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
                   "dev": true,
                   "optional": true
                 },
                 "delegates": {
                   "version": "1.0.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
                   "dev": true,
                   "optional": true
                 },
                 "detect-libc": {
                   "version": "1.0.3",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
                   "dev": true,
                   "optional": true
                 },
                 "fs.realpath": {
                   "version": "1.0.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
                   "dev": true,
                   "optional": true
                 },
                 "gauge": {
                   "version": "2.7.4",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
                   "dev": true,
                   "optional": true,
@@ -14784,7 +14798,7 @@
                 },
                 "glob": {
                   "version": "7.1.3",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
                   "dev": true,
                   "optional": true,
@@ -14799,14 +14813,14 @@
                 },
                 "has-unicode": {
                   "version": "2.0.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
                   "dev": true,
                   "optional": true
                 },
                 "iconv-lite": {
                   "version": "0.4.24",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
                   "dev": true,
                   "optional": true,
@@ -14816,7 +14830,7 @@
                 },
                 "ignore-walk": {
                   "version": "3.0.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
                   "dev": true,
                   "optional": true,
@@ -14826,7 +14840,7 @@
                 },
                 "inflight": {
                   "version": "1.0.6",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
                   "dev": true,
                   "optional": true,
@@ -14837,21 +14851,21 @@
                 },
                 "inherits": {
                   "version": "2.0.3",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
                   "dev": true,
                   "optional": true
                 },
                 "ini": {
                   "version": "1.3.5",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
                   "dev": true,
                   "optional": true
                 },
                 "is-fullwidth-code-point": {
                   "version": "1.0.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
                   "dev": true,
                   "optional": true,
@@ -14861,14 +14875,14 @@
                 },
                 "isarray": {
                   "version": "1.0.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
                   "dev": true,
                   "optional": true
                 },
                 "minimatch": {
                   "version": "3.0.4",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
                   "dev": true,
                   "optional": true,
@@ -14878,14 +14892,14 @@
                 },
                 "ms": {
                   "version": "2.1.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
                   "dev": true,
                   "optional": true
                 },
                 "needle": {
                   "version": "2.3.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==",
                   "dev": true,
                   "optional": true,
@@ -14897,7 +14911,7 @@
                 },
                 "node-pre-gyp": {
                   "version": "0.12.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==",
                   "dev": true,
                   "optional": true,
@@ -14916,7 +14930,7 @@
                 },
                 "nopt": {
                   "version": "4.0.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
                   "dev": true,
                   "optional": true,
@@ -14927,14 +14941,14 @@
                 },
                 "npm-bundled": {
                   "version": "1.0.6",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==",
                   "dev": true,
                   "optional": true
                 },
                 "npm-packlist": {
                   "version": "1.4.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==",
                   "dev": true,
                   "optional": true,
@@ -14945,7 +14959,7 @@
                 },
                 "npmlog": {
                   "version": "4.1.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
                   "dev": true,
                   "optional": true,
@@ -14958,21 +14972,21 @@
                 },
                 "number-is-nan": {
                   "version": "1.0.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
                   "dev": true,
                   "optional": true
                 },
                 "object-assign": {
                   "version": "4.1.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
                   "dev": true,
                   "optional": true
                 },
                 "once": {
                   "version": "1.4.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
                   "dev": true,
                   "optional": true,
@@ -14982,21 +14996,21 @@
                 },
                 "os-homedir": {
                   "version": "1.0.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
                   "dev": true,
                   "optional": true
                 },
                 "os-tmpdir": {
                   "version": "1.0.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
                   "dev": true,
                   "optional": true
                 },
                 "osenv": {
                   "version": "0.1.5",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
                   "dev": true,
                   "optional": true,
@@ -15007,21 +15021,21 @@
                 },
                 "path-is-absolute": {
                   "version": "1.0.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
                   "dev": true,
                   "optional": true
                 },
                 "process-nextick-args": {
                   "version": "2.0.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
                   "dev": true,
                   "optional": true
                 },
                 "rc": {
                   "version": "1.2.8",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
                   "dev": true,
                   "optional": true,
@@ -15034,7 +15048,7 @@
                 },
                 "readable-stream": {
                   "version": "2.3.6",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                   "dev": true,
                   "optional": true,
@@ -15050,7 +15064,7 @@
                 },
                 "rimraf": {
                   "version": "2.6.3",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
                   "dev": true,
                   "optional": true,
@@ -15060,49 +15074,49 @@
                 },
                 "safe-buffer": {
                   "version": "5.1.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
                   "dev": true,
                   "optional": true
                 },
                 "safer-buffer": {
                   "version": "2.1.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
                   "dev": true,
                   "optional": true
                 },
                 "sax": {
                   "version": "1.2.4",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
                   "dev": true,
                   "optional": true
                 },
                 "semver": {
                   "version": "5.7.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
                   "dev": true,
                   "optional": true
                 },
                 "set-blocking": {
                   "version": "2.0.0",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
                   "dev": true,
                   "optional": true
                 },
                 "signal-exit": {
                   "version": "3.0.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
                   "dev": true,
                   "optional": true
                 },
                 "string-width": {
                   "version": "1.0.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
                   "dev": true,
                   "optional": true,
@@ -15114,7 +15128,7 @@
                 },
                 "string_decoder": {
                   "version": "1.1.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                   "dev": true,
                   "optional": true,
@@ -15124,7 +15138,7 @@
                 },
                 "strip-ansi": {
                   "version": "3.0.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
                   "dev": true,
                   "optional": true,
@@ -15134,21 +15148,21 @@
                 },
                 "strip-json-comments": {
                   "version": "2.0.1",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
                   "dev": true,
                   "optional": true
                 },
                 "util-deprecate": {
                   "version": "1.0.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
                   "dev": true,
                   "optional": true
                 },
                 "wide-align": {
                   "version": "1.1.3",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": 
"sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
                   "dev": true,
                   "optional": true,
@@ -15158,7 +15172,7 @@
                 },
                 "wrappy": {
                   "version": "1.0.2",
-                  "resolved": false,
+                  "resolved": "",
                   "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
                   "dev": true,
                   "optional": true
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index 7fcbae1..2f52c70 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -115,6 +115,7 @@
     "d3-color": "^1.2.0",
     "d3-scale": "^2.1.2",
     "dnd-core": "^2.6.0",
+    "dom-to-image": "^2.6.0",
     "dompurify": "^2.0.7",
     "emotion-theming": "^10.0.27",
     "geolib": "^2.0.24",
@@ -188,6 +189,7 @@
     "@hot-loader/react-dom": "^16.13.0",
     "@istanbuljs/nyc-config-typescript": "^1.0.1",
     "@types/classnames": "^2.2.9",
+    "@types/dom-to-image": "^2.6.0",
     "@types/jest": "^25.1.4",
     "@types/jquery": "^3.3.32",
     "@types/react": "^16.9.23",
diff --git 
a/superset-frontend/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
 
b/superset-frontend/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
index 02a5844..b5b18e4 100644
--- 
a/superset-frontend/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
+++ 
b/superset-frontend/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
@@ -66,9 +66,9 @@ describe('HeaderActionsDropdown', () => {
       expect(wrapper.find(SaveModal)).toHaveLength(0);
     });
 
-    it('should render one MenuItem', () => {
+    it('should render two MenuItems', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(MenuItem)).toHaveLength(1);
+      expect(wrapper.find(MenuItem)).toHaveLength(2);
     });
 
     it('should render the RefreshIntervalModal', () => {
@@ -100,9 +100,9 @@ describe('HeaderActionsDropdown', () => {
       expect(wrapper.find(SaveModal)).toHaveLength(1);
     });
 
-    it('should render two MenuItems', () => {
+    it('should render three MenuItems', () => {
       const wrapper = setup(overrideProps);
-      expect(wrapper.find(MenuItem)).toHaveLength(2);
+      expect(wrapper.find(MenuItem)).toHaveLength(3);
     });
 
     it('should render the RefreshIntervalModal', () => {
diff --git 
a/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx 
b/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx
index 357c858..0097c0c 100644
--- a/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx
+++ b/superset-frontend/src/dashboard/components/HeaderActionsDropdown.jsx
@@ -28,6 +28,7 @@ import SaveModal from './SaveModal';
 import injectCustomCss from '../util/injectCustomCss';
 import { SAVE_TYPE_NEWDASHBOARD } from '../util/constants';
 import URLShortLinkModal from '../../components/URLShortLinkModal';
+import downloadAsImage from '../util/downloadAsImage';
 import getDashboardUrl from '../util/getDashboardUrl';
 import { getActiveFilters } from '../util/activeDashboardFilters';
 
@@ -217,6 +218,12 @@ class HeaderActionsDropdown extends React.PureComponent {
             onChange={this.changeCss}
           />
         )}
+
+        {!editMode && (
+          <MenuItem onClick={downloadAsImage('.dashboard', dashboardTitle)}>
+            {t('Download as image')}
+          </MenuItem>
+        )}
       </DropdownButton>
     );
   }
diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls.jsx 
b/superset-frontend/src/dashboard/components/SliceHeaderControls.jsx
index 80cdd99..1322207 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls.jsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls.jsx
@@ -22,6 +22,7 @@ import moment from 'moment';
 import { Dropdown, MenuItem } from 'react-bootstrap';
 import { t } from '@superset-ui/translation';
 import URLShortLinkModal from '../../components/URLShortLinkModal';
+import downloadAsImage from '../util/downloadAsImage';
 import getDashboardUrl from '../util/getDashboardUrl';
 import { getActiveFilters } from '../util/activeDashboardFilters';
 
@@ -176,6 +177,15 @@ class SliceHeaderControls extends React.PureComponent {
             title={t('Share chart')}
             triggerNode={<span>{t('Share chart')}</span>}
           />
+
+          <MenuItem
+            onClick={downloadAsImage(
+              '.dashboard-component-chart-holder',
+              slice.slice_name,
+            )}
+          >
+            {t('Download as image')}
+          </MenuItem>
         </Dropdown.Menu>
       </Dropdown>
     );
diff --git a/superset-frontend/src/dashboard/util/downloadAsImage.ts 
b/superset-frontend/src/dashboard/util/downloadAsImage.ts
new file mode 100644
index 0000000..2be12cc
--- /dev/null
+++ b/superset-frontend/src/dashboard/util/downloadAsImage.ts
@@ -0,0 +1,74 @@
+/**
+ * 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 { SyntheticEvent } from 'react';
+import domToImage from 'dom-to-image';
+import kebabCase from 'lodash/kebabCase';
+import { t } from '@superset-ui/translation';
+import { addWarningToast } from 'src/messageToasts/actions';
+
+/**
+ * @remark
+ * same as 
https://github.com/apache/incubator-superset/blob/c53bc4ddf9808a8bb6916bbe3cb31935d33a2420/superset-frontend/stylesheets/less/variables.less#L34
+ */
+const GRAY_BACKGROUND_COLOR = '#F5F5F5';
+
+/**
+ * generate a consistent file stem from a description and date
+ *
+ * @param description title or description of content of file
+ * @param date date when file was generated
+ */
+const generateFileStem = (description: string, date = new Date()) => {
+  return `${kebabCase(description)}-${date
+    .toISOString()
+    .replace(/[: ]/g, '-')}`;
+};
+
+/**
+ * Create an event handler for turning an element into an image
+ *
+ * @param selector css selector of the parent element which should be turned 
into image
+ * @param description name or a short description of what is being printed.
+ *   Value will be normalized, and a date as well as a file extension will be 
added.
+ * @param backgroundColor background color to apply to screenshot document
+ * @returns event handler
+ */
+export default function downloadAsImage(
+  selector: string,
+  description: string,
+  backgroundColor = GRAY_BACKGROUND_COLOR,
+) {
+  return (event: SyntheticEvent) => {
+    const elementToPrint = event.currentTarget.closest(selector);
+
+    if (!elementToPrint)
+      return addWarningToast(
+        t('Image download failed, please refresh and try again.'),
+      );
+
+    return domToImage
+      .toJpeg(elementToPrint, { quality: 0.95, bgcolor: backgroundColor })
+      .then(dataUrl => {
+        const link = document.createElement('a');
+        link.download = `${generateFileStem(description)}.jpg`;
+        link.href = dataUrl;
+        link.click();
+      });
+  };
+}

Reply via email to