You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by hu...@apache.org on 2023/10/31 01:54:39 UTC
(superset) branch master updated: feat(Export as PDF - rasterized): Adding rasterized pdf functionality to dashboard (#25696)
This is an automated email from the ASF dual-hosted git repository.
hugh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 74dbada473 feat(Export as PDF - rasterized): Adding rasterized pdf functionality to dashboard (#25696)
74dbada473 is described below
commit 74dbada473e150203986f22c5e38ac314c551f9c
Author: Jack <41...@users.noreply.github.com>
AuthorDate: Mon Oct 30 20:54:33 2023 -0500
feat(Export as PDF - rasterized): Adding rasterized pdf functionality to dashboard (#25696)
---
superset-frontend/package-lock.json | 270 +++++++++++++++++++++
superset-frontend/package.json | 1 +
.../HeaderActionsDropdown.test.tsx | 9 +-
.../Header/HeaderActionsDropdown/index.jsx | 47 ++--
.../DownloadMenuItems/DownloadAsImage.test.tsx | 42 ++++
.../menu/DownloadMenuItems/DownloadAsImage.tsx | 37 +++
.../menu/DownloadMenuItems/DownloadAsPdf.test.tsx | 42 ++++
.../menu/DownloadMenuItems/DownloadAsPdf.tsx | 37 +++
.../DownloadMenuItems/DownloadMenuItems.test.tsx | 25 ++
.../components/menu/DownloadMenuItems/index.tsx | 62 +++++
superset-frontend/src/logger/LogUtils.ts | 3 +
superset-frontend/src/types/dom-to-pdf.d.ts | 19 ++
superset-frontend/src/utils/downloadAsImage.ts | 2 +-
.../utils/{downloadAsImage.ts => downloadAsPdf.ts} | 42 ++--
14 files changed, 574 insertions(+), 64 deletions(-)
diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index 370cd3f1f2..b622368280 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -72,6 +72,7 @@
"d3-color": "^3.1.0",
"d3-scale": "^2.1.2",
"dom-to-image-more": "^2.10.1",
+ "dom-to-pdf": "^0.3.2",
"emotion-rgba": "0.0.9",
"fast-glob": "^3.2.7",
"fontsource-fira-code": "^4.0.0",
@@ -19604,6 +19605,12 @@
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw=="
},
+ "node_modules/@types/raf": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.2.tgz",
+ "integrity": "sha512-sM4HyDVlDFl4goOXPF+g9nNHJFZQGot+HgySjM4cRjqXzjdatcEvYrtG4Ia8XumR9T6k8G2tW9B7hnUj51Uf0A==",
+ "optional": true
+ },
"node_modules/@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
@@ -23875,6 +23882,15 @@
"resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
"integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ=="
},
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -24508,6 +24524,17 @@
"node-int64": "^0.4.0"
}
},
+ "node_modules/btoa": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+ "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
+ "bin": {
+ "btoa": "bin/btoa.js"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/buf-compare": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz",
@@ -24998,6 +25025,25 @@
}
]
},
+ "node_modules/canvg": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
+ "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/capture-exit": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
@@ -26971,6 +27017,15 @@
"isobject": "^3.0.1"
}
},
+ "node_modules/css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "optional": true,
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/css-loader": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz",
@@ -29446,11 +29501,25 @@
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
},
+ "node_modules/dom-to-image": {
+ "version": "2.6.0",
+ "resolved": "git+ssh://git@github.com/dmapper/dom-to-image.git#a7c386a8ea813930f05449ac71ab4be0c262dff3",
+ "license": "MIT"
+ },
"node_modules/dom-to-image-more": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.10.1.tgz",
"integrity": "sha512-gMG28V47WGj5/xvrsbSPJAWSaV7CBh4teLErn1iGD1sa29HsFsHxvnoLj8VxVvfqnjPgsiUGs2IV2VAxLJGb+A=="
},
+ "node_modules/dom-to-pdf": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/dom-to-pdf/-/dom-to-pdf-0.3.2.tgz",
+ "integrity": "sha512-eHLQ/IK+2PQlRjybQ9UHYwpiTd/YZFKqGFyRCjVvi6CPlH58drWQnxf7HBCVRUyAjOtI3RG0kvLidPhC7dOhcQ==",
+ "dependencies": {
+ "dom-to-image": "git+https://github.com/dmapper/dom-to-image.git",
+ "jspdf": "^2.5.1"
+ }
+ },
"node_modules/dom-walk": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
@@ -32262,6 +32331,11 @@
"resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz",
"integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA=="
},
+ "node_modules/fflate": {
+ "version": "0.4.8",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
+ "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
+ },
"node_modules/figgy-pudding": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@@ -35152,6 +35226,19 @@
"node": ">=6"
}
},
+ "node_modules/html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "optional": true,
+ "dependencies": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
@@ -40207,6 +40294,23 @@
"node": "*"
}
},
+ "node_modules/jspdf": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz",
+ "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==",
+ "dependencies": {
+ "@babel/runtime": "^7.14.0",
+ "atob": "^2.1.2",
+ "btoa": "^1.2.1",
+ "fflate": "^0.4.8"
+ },
+ "optionalDependencies": {
+ "canvg": "^3.0.6",
+ "core-js": "^3.6.0",
+ "dompurify": "^2.2.0",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
"node_modules/jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@@ -54038,6 +54142,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.8.15"
+ }
+ },
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -55431,6 +55544,15 @@
"node": ">=8"
}
},
+ "node_modules/stackblur-canvas": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz",
+ "integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.14"
+ }
+ },
"node_modules/stackframe": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz",
@@ -56146,6 +56268,15 @@
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
"dev": true
},
+ "node_modules/svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "optional": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/svgo": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz",
@@ -56978,6 +57109,15 @@
"node": ">=0.10"
}
},
+ "node_modules/text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "optional": true,
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -58498,6 +58638,15 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "optional": true,
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
@@ -79412,6 +79561,12 @@
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw=="
},
+ "@types/raf": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.2.tgz",
+ "integrity": "sha512-sM4HyDVlDFl4goOXPF+g9nNHJFZQGot+HgySjM4cRjqXzjdatcEvYrtG4Ia8XumR9T6k8G2tW9B7hnUj51Uf0A==",
+ "optional": true
+ },
"@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
@@ -82819,6 +82974,12 @@
"resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
"integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ=="
},
+ "base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "optional": true
+ },
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -83305,6 +83466,11 @@
"node-int64": "^0.4.0"
}
},
+ "btoa": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+ "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
+ },
"buf-compare": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz",
@@ -83669,6 +83835,22 @@
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz",
"integrity": "sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ=="
},
+ "canvg": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
+ "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
+ "optional": true,
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ }
+ },
"capture-exit": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
@@ -85266,6 +85448,15 @@
"isobject": "^3.0.1"
}
},
+ "css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "optional": true,
+ "requires": {
+ "utrie": "^1.0.2"
+ }
+ },
"css-loader": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz",
@@ -87124,11 +87315,24 @@
}
}
},
+ "dom-to-image": {
+ "version": "git+ssh://git@github.com/dmapper/dom-to-image.git#a7c386a8ea813930f05449ac71ab4be0c262dff3",
+ "from": "dom-to-image@git+https://github.com/dmapper/dom-to-image.git"
+ },
"dom-to-image-more": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.10.1.tgz",
"integrity": "sha512-gMG28V47WGj5/xvrsbSPJAWSaV7CBh4teLErn1iGD1sa29HsFsHxvnoLj8VxVvfqnjPgsiUGs2IV2VAxLJGb+A=="
},
+ "dom-to-pdf": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/dom-to-pdf/-/dom-to-pdf-0.3.2.tgz",
+ "integrity": "sha512-eHLQ/IK+2PQlRjybQ9UHYwpiTd/YZFKqGFyRCjVvi6CPlH58drWQnxf7HBCVRUyAjOtI3RG0kvLidPhC7dOhcQ==",
+ "requires": {
+ "dom-to-image": "git+https://github.com/dmapper/dom-to-image.git",
+ "jspdf": "^2.5.1"
+ }
+ },
"dom-walk": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
@@ -89312,6 +89516,11 @@
"resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz",
"integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA=="
},
+ "fflate": {
+ "version": "0.4.8",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
+ "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
+ },
"figgy-pudding": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@@ -91480,6 +91689,16 @@
}
}
},
+ "html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "optional": true,
+ "requires": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ }
+ },
"htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
@@ -95329,6 +95548,21 @@
"through": ">=2.2.7 <3"
}
},
+ "jspdf": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz",
+ "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==",
+ "requires": {
+ "@babel/runtime": "^7.14.0",
+ "atob": "^2.1.2",
+ "btoa": "^1.2.1",
+ "canvg": "^3.0.6",
+ "core-js": "^3.6.0",
+ "dompurify": "^2.2.0",
+ "fflate": "^0.4.8",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
"jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@@ -105883,6 +106117,12 @@
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
},
+ "rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "optional": true
+ },
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -107035,6 +107275,12 @@
}
}
},
+ "stackblur-canvas": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz",
+ "integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==",
+ "optional": true
+ },
"stackframe": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz",
@@ -107577,6 +107823,12 @@
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
"dev": true
},
+ "svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "optional": true
+ },
"svgo": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz",
@@ -108182,6 +108434,15 @@
"integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==",
"dev": true
},
+ "text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "optional": true,
+ "requires": {
+ "utrie": "^1.0.2"
+ }
+ },
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -109275,6 +109536,15 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
+ "utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "optional": true,
+ "requires": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index fec56fdb45..a0f5bdd8c0 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -137,6 +137,7 @@
"d3-color": "^3.1.0",
"d3-scale": "^2.1.2",
"dom-to-image-more": "^2.10.1",
+ "dom-to-pdf": "^0.3.2",
"emotion-rgba": "0.0.9",
"fast-glob": "^3.2.7",
"fontsource-fira-code": "^4.0.0",
diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
index 218e2e4546..e112e7e531 100644
--- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
+++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/HeaderActionsDropdown.test.tsx
@@ -109,10 +109,10 @@ test('should render', () => {
expect(container).toBeInTheDocument();
});
-test('should render the dropdown button', () => {
+test('should render the Download dropdown button when not in edit mode', () => {
const mockedProps = createProps();
setup(mockedProps);
- expect(screen.getByRole('button')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument();
});
test('should render the menu items', async () => {
@@ -121,16 +121,17 @@ test('should render the menu items', async () => {
expect(screen.getAllByRole('menuitem')).toHaveLength(4);
expect(screen.getByText('Refresh dashboard')).toBeInTheDocument();
expect(screen.getByText('Set auto-refresh interval')).toBeInTheDocument();
- expect(screen.getByText('Download as image')).toBeInTheDocument();
expect(screen.getByText('Enter fullscreen')).toBeInTheDocument();
+ expect(screen.getByText('Download')).toBeInTheDocument();
});
test('should render the menu items in edit mode', async () => {
setup(editModeOnProps);
- expect(screen.getAllByRole('menuitem')).toHaveLength(4);
+ expect(screen.getAllByRole('menuitem')).toHaveLength(5);
expect(screen.getByText('Set auto-refresh interval')).toBeInTheDocument();
expect(screen.getByText('Edit properties')).toBeInTheDocument();
expect(screen.getByText('Edit CSS')).toBeInTheDocument();
+ expect(screen.getByText('Download')).toBeInTheDocument();
});
describe('with native filters feature flag disabled', () => {
diff --git a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
index 1073d73ab0..4d8bcf4ee0 100644
--- a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
+++ b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
@@ -28,6 +28,7 @@ import {
import { Menu } from 'src/components/Menu';
import { URL_PARAMS } from 'src/constants';
import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
+import DownloadMenuItems from 'src/dashboard/components/menu/DownloadMenuItems';
import CssEditor from 'src/dashboard/components/CssEditor';
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
import SaveModal from 'src/dashboard/components/SaveModal';
@@ -35,11 +36,9 @@ import HeaderReportDropdown from 'src/features/reports/ReportModal/HeaderReportD
import injectCustomCss from 'src/dashboard/util/injectCustomCss';
import { SAVE_TYPE_NEWDASHBOARD } from 'src/dashboard/util/constants';
import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeModal';
-import downloadAsImage from 'src/utils/downloadAsImage';
import getDashboardUrl from 'src/dashboard/util/getDashboardUrl';
import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
import { getUrlParam } from 'src/utils/urlUtils';
-import { LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE } from 'src/logger/LogUtils';
const propTypes = {
addSuccessToast: PropTypes.func.isRequired,
@@ -90,14 +89,12 @@ const MENU_KEYS = {
SET_FILTER_MAPPING: 'set-filter-mapping',
EDIT_PROPERTIES: 'edit-properties',
EDIT_CSS: 'edit-css',
- DOWNLOAD_AS_IMAGE: 'download-as-image',
+ DOWNLOAD_DASHBOARD: 'download-dashboard',
TOGGLE_FULLSCREEN: 'toggle-fullscreen',
MANAGE_EMBEDDED: 'manage-embedded',
MANAGE_EMAIL_REPORT: 'manage-email-report',
};
-const SCREENSHOT_NODE_SELECTOR = '.dashboard';
-
class HeaderActionsDropdown extends React.PureComponent {
static discardChanges() {
window.location.reload();
@@ -158,7 +155,7 @@ class HeaderActionsDropdown extends React.PureComponent {
this.props.startPeriodicRender(refreshInterval * 1000);
}
- handleMenuClick({ key, domEvent }) {
+ handleMenuClick({ key }) {
switch (key) {
case MENU_KEYS.REFRESH_DASHBOARD:
this.props.forceRefreshAllCharts();
@@ -167,23 +164,6 @@ class HeaderActionsDropdown extends React.PureComponent {
case MENU_KEYS.EDIT_PROPERTIES:
this.props.showPropertiesModal();
break;
- case MENU_KEYS.DOWNLOAD_AS_IMAGE: {
- // menu closes with a delay, we need to hide it manually,
- // so that we don't capture it on the screenshot
- const menu = document.querySelector(
- '.ant-dropdown:not(.ant-dropdown-hidden)',
- );
- menu.style.visibility = 'hidden';
- downloadAsImage(
- SCREENSHOT_NODE_SELECTOR,
- this.props.dashboardTitle,
- true,
- )(domEvent).then(() => {
- menu.style.visibility = 'visible';
- });
- this.props.logEvent?.(LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE);
- break;
- }
case MENU_KEYS.TOGGLE_FULLSCREEN: {
const url = getDashboardUrl({
pathname: window.location.pathname,
@@ -311,14 +291,19 @@ class HeaderActionsDropdown extends React.PureComponent {
/>
</Menu.Item>
)}
- {!editMode && (
- <Menu.Item
- key={MENU_KEYS.DOWNLOAD_AS_IMAGE}
- onClick={this.handleMenuClick}
- >
- {t('Download as image')}
- </Menu.Item>
- )}
+ <Menu.SubMenu
+ key={MENU_KEYS.DOWNLOAD_DASHBOARD}
+ disabled={isLoading}
+ title={t('Download')}
+ logEvent={this.props.logEvent}
+ >
+ <DownloadMenuItems
+ pdfMenuItemTitle={t('Export to PDF')}
+ imageMenuItemTitle={t('Download as Image')}
+ dashboardTitle={dashboardTitle}
+ addDangerToast={addDangerToast}
+ />
+ </Menu.SubMenu>
{userCanShare && (
<Menu.SubMenu
key={MENU_KEYS.SHARE_DASHBOARD}
diff --git a/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsImage.test.tsx b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsImage.test.tsx
new file mode 100644
index 0000000000..7881e2a76b
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsImage.test.tsx
@@ -0,0 +1,42 @@
+import React, { SyntheticEvent } from 'react';
+import { render, screen, waitFor } from 'spec/helpers/testing-library';
+import userEvent from '@testing-library/user-event';
+import { Menu } from 'src/components/Menu';
+import downloadAsImage from 'src/utils/downloadAsImage';
+import DownloadAsImage from './DownloadAsImage';
+
+jest.mock('src/utils/downloadAsImage', () => ({
+ __esModule: true,
+ default: jest.fn(() => (_e: SyntheticEvent) => {}),
+}));
+
+const createProps = () => ({
+ addDangerToast: jest.fn(),
+ text: 'Download as Image',
+ dashboardTitle: 'Test Dashboard',
+ logEvent: jest.fn(),
+});
+
+const renderComponent = () => {
+ render(
+ <Menu>
+ <DownloadAsImage {...createProps()} />
+ </Menu>,
+ );
+};
+
+test('Should call download image on click', async () => {
+ const props = createProps();
+ renderComponent();
+ await waitFor(() => {
+ expect(downloadAsImage).toBeCalledTimes(0);
+ expect(props.addDangerToast).toBeCalledTimes(0);
+ });
+
+ userEvent.click(screen.getByRole('button', { name: 'Download as Image' }));
+
+ await waitFor(() => {
+ expect(downloadAsImage).toBeCalledTimes(1);
+ expect(props.addDangerToast).toBeCalledTimes(0);
+ });
+});
diff --git a/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsImage.tsx b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsImage.tsx
new file mode 100644
index 0000000000..4d7d466348
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsImage.tsx
@@ -0,0 +1,37 @@
+import React, { SyntheticEvent } from 'react';
+import { logging, t } from '@superset-ui/core';
+import { Menu } from 'src/components/Menu';
+import { LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE } from 'src/logger/LogUtils';
+import downloadAsImage from 'src/utils/downloadAsImage';
+
+export default function DownloadAsImage({
+ text,
+ logEvent,
+ dashboardTitle,
+ addDangerToast,
+ ...rest
+}: {
+ text: string;
+ addDangerToast: Function;
+ dashboardTitle: string;
+ logEvent?: Function;
+}) {
+ const SCREENSHOT_NODE_SELECTOR = '.dashboard';
+ const onDownloadImage = async (e: SyntheticEvent) => {
+ try {
+ downloadAsImage(SCREENSHOT_NODE_SELECTOR, dashboardTitle, true)(e);
+ } catch (error) {
+ logging.error(error);
+ addDangerToast(t('Sorry, something went wrong. Try again later.'));
+ }
+ logEvent?.(LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE);
+ };
+
+ return (
+ <Menu.Item key="download-image" {...rest}>
+ <div onClick={onDownloadImage} role="button" tabIndex={0}>
+ {text}
+ </div>
+ </Menu.Item>
+ );
+}
diff --git a/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsPdf.test.tsx b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsPdf.test.tsx
new file mode 100644
index 0000000000..371026e7aa
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsPdf.test.tsx
@@ -0,0 +1,42 @@
+import React, { SyntheticEvent } from 'react';
+import { render, screen, waitFor } from 'spec/helpers/testing-library';
+import userEvent from '@testing-library/user-event';
+import { Menu } from 'src/components/Menu';
+import downloadAsPdf from 'src/utils/downloadAsPdf';
+import DownloadAsPdf from './DownloadAsPdf';
+
+jest.mock('src/utils/downloadAsPdf', () => ({
+ __esModule: true,
+ default: jest.fn(() => (_e: SyntheticEvent) => {}),
+}));
+
+const createProps = () => ({
+ addDangerToast: jest.fn(),
+ text: 'Export as PDF',
+ dashboardTitle: 'Test Dashboard',
+ logEvent: jest.fn(),
+});
+
+const renderComponent = () => {
+ render(
+ <Menu>
+ <DownloadAsPdf {...createProps()} />
+ </Menu>,
+ );
+};
+
+test('Should call download pdf on click', async () => {
+ const props = createProps();
+ renderComponent();
+ await waitFor(() => {
+ expect(downloadAsPdf).toBeCalledTimes(0);
+ expect(props.addDangerToast).toBeCalledTimes(0);
+ });
+
+ userEvent.click(screen.getByRole('button', { name: 'Export as PDF' }));
+
+ await waitFor(() => {
+ expect(downloadAsPdf).toBeCalledTimes(1);
+ expect(props.addDangerToast).toBeCalledTimes(0);
+ });
+});
diff --git a/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsPdf.tsx b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsPdf.tsx
new file mode 100644
index 0000000000..eb3616b731
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadAsPdf.tsx
@@ -0,0 +1,37 @@
+import React, { SyntheticEvent } from 'react';
+import { logging, t } from '@superset-ui/core';
+import { Menu } from 'src/components/Menu';
+import downloadAsPdf from 'src/utils/downloadAsPdf';
+import { LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_PDF } from 'src/logger/LogUtils';
+
+export default function DownloadAsPdf({
+ text,
+ logEvent,
+ dashboardTitle,
+ addDangerToast,
+ ...rest
+}: {
+ text: string;
+ addDangerToast: Function;
+ dashboardTitle: string;
+ logEvent?: Function;
+}) {
+ const SCREENSHOT_NODE_SELECTOR = '.dashboard';
+ const onDownloadPdf = async (e: SyntheticEvent) => {
+ try {
+ downloadAsPdf(SCREENSHOT_NODE_SELECTOR, dashboardTitle, true)(e);
+ } catch (error) {
+ logging.error(error);
+ addDangerToast(t('Sorry, something went wrong. Try again later.'));
+ }
+ logEvent?.(LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_PDF);
+ };
+
+ return (
+ <Menu.Item key="download-pdf" {...rest}>
+ <div onClick={onDownloadPdf} role="button" tabIndex={0}>
+ {text}
+ </div>
+ </Menu.Item>
+ );
+}
diff --git a/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadMenuItems.test.tsx b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadMenuItems.test.tsx
new file mode 100644
index 0000000000..e93d7bd3ed
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/DownloadMenuItems.test.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import DownloadMenuItems from '.';
+
+const createProps = () => ({
+ addDangerToast: jest.fn(),
+ pdfMenuItemTitle: 'Export to PDF',
+ imageMenuItemTitle: 'Download as Image',
+ dashboardTitle: 'Test Dashboard',
+ logEvent: jest.fn(),
+});
+
+const renderComponent = () => {
+ render(<DownloadMenuItems {...createProps()} />);
+};
+
+test('Should render menu items', () => {
+ renderComponent();
+ expect(
+ screen.getByRole('menuitem', { name: 'Export to PDF' }),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('menuitem', { name: 'Download as Image' }),
+ ).toBeInTheDocument();
+});
diff --git a/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/index.tsx b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/index.tsx
new file mode 100644
index 0000000000..a67140a004
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/menu/DownloadMenuItems/index.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 from 'react';
+import { Menu } from 'src/components/Menu';
+import DownloadAsImage from './DownloadAsImage';
+import DownloadAsPdf from './DownloadAsPdf';
+
+export interface DownloadMenuItemProps {
+ pdfMenuItemTitle: string;
+ imageMenuItemTitle: string;
+ addDangerToast: Function;
+ dashboardTitle: string;
+ logEvent?: Function;
+}
+
+const DownloadMenuItems = (props: DownloadMenuItemProps) => {
+ const {
+ pdfMenuItemTitle,
+ imageMenuItemTitle,
+ addDangerToast,
+ dashboardTitle,
+ logEvent,
+ ...rest
+ } = props;
+
+ return (
+ <Menu selectable={false}>
+ <DownloadAsPdf
+ text={pdfMenuItemTitle}
+ addDangerToast={addDangerToast}
+ dashboardTitle={dashboardTitle}
+ logEvent={logEvent}
+ {...rest}
+ />
+ <DownloadAsImage
+ text={imageMenuItemTitle}
+ addDangerToast={addDangerToast}
+ dashboardTitle={dashboardTitle}
+ logEvent={logEvent}
+ {...rest}
+ />
+ </Menu>
+ );
+};
+
+export default DownloadMenuItems;
diff --git a/superset-frontend/src/logger/LogUtils.ts b/superset-frontend/src/logger/LogUtils.ts
index 31fae5b0c4..2020cb67c0 100644
--- a/superset-frontend/src/logger/LogUtils.ts
+++ b/superset-frontend/src/logger/LogUtils.ts
@@ -51,6 +51,8 @@ export const LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA =
'confirm_overwrite_dashboard_metadata';
export const LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE =
'dashboard_download_as_image';
+export const LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_PDF =
+ 'dashboard_download_as_pdf';
export const LOG_ACTIONS_CHART_DOWNLOAD_AS_IMAGE = 'chart_download_as_image';
export const LOG_ACTIONS_CHART_DOWNLOAD_AS_CSV = 'chart_download_as_csv';
export const LOG_ACTIONS_CHART_DOWNLOAD_AS_CSV_PIVOTED =
@@ -90,6 +92,7 @@ export const LOG_EVENT_TYPE_USER = new Set([
LOG_ACTIONS_MOUNT_EXPLORER,
LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA,
LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE,
+ LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_PDF,
LOG_ACTIONS_CHART_DOWNLOAD_AS_IMAGE,
]);
diff --git a/superset-frontend/src/types/dom-to-pdf.d.ts b/superset-frontend/src/types/dom-to-pdf.d.ts
new file mode 100644
index 0000000000..19ecce85b4
--- /dev/null
+++ b/superset-frontend/src/types/dom-to-pdf.d.ts
@@ -0,0 +1,19 @@
+declare module 'dom-to-pdf' {
+ interface Image {
+ type: string;
+ quality: number;
+ }
+
+ interface Options {
+ margin: number;
+ filename: string;
+ image: Image;
+ html2canvas: object;
+ }
+
+ const domToPdf = (
+ elementToPrint: Element,
+ options?: Options,
+ ): Promise<any> => {};
+ export default domToPdf;
+}
diff --git a/superset-frontend/src/utils/downloadAsImage.ts b/superset-frontend/src/utils/downloadAsImage.ts
index de74543646..79373cc76a 100644
--- a/superset-frontend/src/utils/downloadAsImage.ts
+++ b/superset-frontend/src/utils/downloadAsImage.ts
@@ -70,7 +70,7 @@ export default function downloadAsImage(
return domToImage
.toJpeg(elementToPrint, {
- quality: 0.95,
+ quality: 1,
bgcolor: supersetTheme.colors.grayscale.light4,
filter,
})
diff --git a/superset-frontend/src/utils/downloadAsImage.ts b/superset-frontend/src/utils/downloadAsPdf.ts
similarity index 66%
copy from superset-frontend/src/utils/downloadAsImage.ts
copy to superset-frontend/src/utils/downloadAsPdf.ts
index de74543646..eebca66b8b 100644
--- a/superset-frontend/src/utils/downloadAsImage.ts
+++ b/superset-frontend/src/utils/downloadAsPdf.ts
@@ -17,9 +17,9 @@
* under the License.
*/
import { SyntheticEvent } from 'react';
-import domToImage from 'dom-to-image-more';
+import domToPdf from 'dom-to-pdf';
import kebabCase from 'lodash/kebabCase';
-import { t, supersetTheme } from '@superset-ui/core';
+import { logging, t } from '@superset-ui/core';
import { addWarningToast } from 'src/components/MessageToasts/actions';
/**
@@ -40,7 +40,7 @@ const generateFileStem = (description: string, date = new Date()) =>
* @param isExactSelector if false, searches for the closest ancestor that matches selector.
* @returns event handler
*/
-export default function downloadAsImage(
+export default function downloadAsPdf(
selector: string,
description: string,
isExactSelector = false,
@@ -52,36 +52,22 @@ export default function downloadAsImage(
if (!elementToPrint) {
return addWarningToast(
- t('Image download failed, please refresh and try again.'),
+ t('PDF download failed, please refresh and try again.'),
);
}
- // Mapbox controls are loaded from different origin, causing CORS error
- // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL#exceptions
- const filter = (node: Element) => {
- if (typeof node.className === 'string') {
- return (
- node.className !== 'mapboxgl-control-container' &&
- !node.className.includes('ant-dropdown')
- );
- }
- return true;
+ const options = {
+ margin: 10,
+ filename: `${generateFileStem(description)}.pdf`,
+ image: { type: 'jpeg', quality: 1 },
+ html2canvas: { scale: 2 },
};
-
- return domToImage
- .toJpeg(elementToPrint, {
- quality: 0.95,
- bgcolor: supersetTheme.colors.grayscale.light4,
- filter,
- })
- .then(dataUrl => {
- const link = document.createElement('a');
- link.download = `${generateFileStem(description)}.jpg`;
- link.href = dataUrl;
- link.click();
+ return domToPdf(elementToPrint, options)
+ .then(() => {
+ // nothing to be done
})
- .catch(e => {
- console.error('Creating image failed', e);
+ .catch((e: Error) => {
+ logging.error('PDF generation failed', e);
});
};
}