fix(Dashboard): Sync/Async Dashboard Screenshot Generation and Default Cache (#30755)
Co-authored-by: Michael S. Molina <michael.s.molina@gmail.com> Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
This commit is contained in:
parent
2518190b2d
commit
3e29777526
|
|
@ -32,4 +32,4 @@ jobs:
|
|||
# license: https://applitools.com/legal/open-source-terms-of-use/
|
||||
# pkg:npm/node-forge@1.3.1
|
||||
# selecting BSD-3-Clause licensing terms for node-forge to ensure compatibility with Apache
|
||||
allow-dependencies-licenses: pkg:npm/store2@2.14.2, pkg:npm/applitools/core, pkg:npm/applitools/core-base, pkg:npm/applitools/css-tree, pkg:npm/applitools/ec-client, pkg:npm/applitools/eg-socks5-proxy-server, pkg:npm/applitools/eyes, pkg:npm/applitools/eyes-cypress, pkg:npm/applitools/nml-client, pkg:npm/applitools/tunnel-client, pkg:npm/applitools/utils, pkg:npm/node-forge@1.3.1
|
||||
allow-dependencies-licenses: pkg:npm/store2@2.14.2, pkg:npm/applitools/core, pkg:npm/applitools/core-base, pkg:npm/applitools/css-tree, pkg:npm/applitools/ec-client, pkg:npm/applitools/eg-socks5-proxy-server, pkg:npm/applitools/eyes, pkg:npm/applitools/eyes-cypress, pkg:npm/applitools/nml-client, pkg:npm/applitools/tunnel-client, pkg:npm/applitools/utils, pkg:npm/node-forge@1.3.1, pkg:npm/rgbcolor
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@
|
|||
"d3-scale": "^2.1.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"dom-to-image-more": "^3.2.0",
|
||||
"dom-to-pdf": "^0.3.2",
|
||||
"emotion-rgba": "0.0.12",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
|
|
@ -13879,6 +13880,13 @@
|
|||
"version": "6.9.7",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/raf": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
"version": "1.2.4",
|
||||
"license": "MIT"
|
||||
|
|
@ -17358,7 +17366,6 @@
|
|||
},
|
||||
"node_modules/atob": {
|
||||
"version": "2.1.2",
|
||||
"dev": true,
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"bin": {
|
||||
"atob": "bin/atob.js"
|
||||
|
|
@ -18076,6 +18083,16 @@
|
|||
"version": "1.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"funding": [
|
||||
|
|
@ -18543,6 +18560,18 @@
|
|||
"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==",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"bin": {
|
||||
"btoa": "bin/btoa.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buf-compare": {
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
|
|
@ -19036,6 +19065,33 @@
|
|||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/canvg": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
|
||||
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
|
||||
"license": "MIT",
|
||||
"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/canvg/node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/capture-exit": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
|
|
@ -20619,6 +20675,16 @@
|
|||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-loader": {
|
||||
"version": "6.8.1",
|
||||
"dev": true,
|
||||
|
|
@ -22609,10 +22675,25 @@
|
|||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"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": "3.2.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"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"
|
||||
},
|
||||
|
|
@ -22640,6 +22721,13 @@
|
|||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "2.5.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.7.tgz",
|
||||
"integrity": "sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"dev": true,
|
||||
|
|
@ -25929,6 +26017,12 @@
|
|||
"version": "6.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "3.2.0",
|
||||
"dev": true,
|
||||
|
|
@ -28659,6 +28753,20 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"dev": true,
|
||||
|
|
@ -33251,6 +33359,24 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jspdf": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
|
||||
"integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"atob": "^2.1.2",
|
||||
"btoa": "^1.2.1",
|
||||
"fflate": "^0.8.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"canvg": "^3.0.6",
|
||||
"core-js": "^3.6.0",
|
||||
"dompurify": "^2.5.4",
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/jsprim": {
|
||||
"version": "1.4.2",
|
||||
"dev": true,
|
||||
|
|
@ -43191,7 +43317,7 @@
|
|||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/periscopic": {
|
||||
|
|
@ -44746,7 +44872,7 @@
|
|||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"performance-now": "^2.1.0"
|
||||
|
|
@ -48352,6 +48478,16 @@
|
|||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.15"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "6.0.1",
|
||||
"license": "ISC",
|
||||
|
|
@ -50008,6 +50144,16 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/stackblur-canvas": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
|
||||
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.14"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval": {
|
||||
"version": "2.1.0",
|
||||
"license": "MIT",
|
||||
|
|
@ -50563,6 +50709,16 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svgo": {
|
||||
"version": "3.2.0",
|
||||
"dev": true,
|
||||
|
|
@ -51024,6 +51180,16 @@
|
|||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/text-table": {
|
||||
"version": "0.2.0",
|
||||
"license": "MIT"
|
||||
|
|
@ -52416,6 +52582,16 @@
|
|||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "3.4.0",
|
||||
"dev": true,
|
||||
|
|
@ -58112,7 +58288,9 @@
|
|||
}
|
||||
},
|
||||
"plugins/legacy-preset-chart-nvd3/node_modules/dompurify": {
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz",
|
||||
"integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)"
|
||||
},
|
||||
"plugins/plugin-chart-echarts": {
|
||||
|
|
@ -68699,7 +68877,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"dompurify": {
|
||||
"version": "3.1.0"
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz",
|
||||
"integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -69831,6 +70011,12 @@
|
|||
"@types/qs": {
|
||||
"version": "6.9.7"
|
||||
},
|
||||
"@types/raf": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||
"optional": true
|
||||
},
|
||||
"@types/range-parser": {
|
||||
"version": "1.2.4"
|
||||
},
|
||||
|
|
@ -72271,8 +72457,7 @@
|
|||
"peer": true
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"dev": true
|
||||
"version": "2.1.2"
|
||||
},
|
||||
"atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
|
|
@ -72766,6 +72951,12 @@
|
|||
"base16": {
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
|
|
@ -73091,6 +73282,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"
|
||||
},
|
||||
|
|
@ -73396,6 +73592,30 @@
|
|||
"caniuse-lite": {
|
||||
"version": "1.0.30001639"
|
||||
},
|
||||
"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.38.1",
|
||||
"raf": "^3.4.1",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"rgbcolor": "^1.0.1",
|
||||
"stackblur-canvas": "^2.0.0",
|
||||
"svg-pathdata": "^6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"capture-exit": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
|
|
@ -74471,6 +74691,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",
|
||||
"dev": true,
|
||||
|
|
@ -75800,9 +76029,22 @@
|
|||
"entities": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"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": "3.2.0"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
|
|
@ -75816,6 +76058,12 @@
|
|||
"domelementtype": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "2.5.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.7.tgz",
|
||||
"integrity": "sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q==",
|
||||
"optional": true
|
||||
},
|
||||
"domutils": {
|
||||
"version": "3.1.0",
|
||||
"dev": true,
|
||||
|
|
@ -77982,6 +78230,11 @@
|
|||
"fetch-retry": {
|
||||
"version": "6.0.0"
|
||||
},
|
||||
"fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
|
||||
},
|
||||
"figures": {
|
||||
"version": "3.2.0",
|
||||
"dev": true,
|
||||
|
|
@ -79740,6 +79993,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": "8.0.2",
|
||||
"dev": true,
|
||||
|
|
@ -82719,6 +82982,21 @@
|
|||
"through": ">=2.2.7 <3"
|
||||
}
|
||||
},
|
||||
"jspdf": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
|
||||
"integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"atob": "^2.1.2",
|
||||
"btoa": "^1.2.1",
|
||||
"canvg": "^3.0.6",
|
||||
"core-js": "^3.38.1",
|
||||
"dompurify": "^2.5.4",
|
||||
"fflate": "^0.8.1",
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.2",
|
||||
"dev": true,
|
||||
|
|
@ -88598,7 +88876,7 @@
|
|||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"periscopic": {
|
||||
"version": "3.1.0",
|
||||
|
|
@ -89546,7 +89824,7 @@
|
|||
},
|
||||
"raf": {
|
||||
"version": "3.4.1",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
|
|
@ -91831,6 +92109,12 @@
|
|||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"dev": true
|
||||
},
|
||||
"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": "6.0.1",
|
||||
"requires": {
|
||||
|
|
@ -92964,6 +93248,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"stackblur-canvas": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
|
||||
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
|
||||
"optional": true
|
||||
},
|
||||
"static-eval": {
|
||||
"version": "2.1.0",
|
||||
"requires": {
|
||||
|
|
@ -93308,6 +93598,12 @@
|
|||
"version": "2.0.4",
|
||||
"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.2.0",
|
||||
"dev": true,
|
||||
|
|
@ -93610,6 +93906,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"
|
||||
},
|
||||
|
|
@ -94483,6 +94788,15 @@
|
|||
"version": "1.0.1",
|
||||
"dev": true
|
||||
},
|
||||
"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",
|
||||
"dev": true
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@
|
|||
"d3-scale": "^2.1.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"dom-to-image-more": "^3.2.0",
|
||||
"dom-to-pdf": "^0.3.2",
|
||||
"emotion-rgba": "0.0.12",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ export enum FeatureFlag {
|
|||
UseAnalagousColors = 'USE_ANALAGOUS_COLORS',
|
||||
ForceSqlLabRunAsync = 'SQLLAB_FORCE_RUN_ASYNC',
|
||||
SlackEnableAvatars = 'SLACK_ENABLE_AVATARS',
|
||||
EnableDashboardScreenshotEndpoints = 'ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS',
|
||||
EnableDashboardDownloadWebDriverScreenshot = 'ENABLE_DASHBOARD_DOWNLOAD_WEBDRIVER_SCREENSHOT',
|
||||
}
|
||||
|
||||
export type ScheduleQueriesProps = {
|
||||
|
|
|
|||
|
|
@ -23,13 +23,20 @@ import { Menu } from 'src/components/Menu';
|
|||
import downloadAsImage from 'src/utils/downloadAsImage';
|
||||
import DownloadAsImage from './DownloadAsImage';
|
||||
|
||||
const mockAddDangerToast = jest.fn();
|
||||
|
||||
jest.mock('src/utils/downloadAsImage', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => (_e: SyntheticEvent) => {}),
|
||||
}));
|
||||
|
||||
jest.mock('src/components/MessageToasts/withToasts', () => ({
|
||||
useToasts: () => ({
|
||||
addDangerToast: mockAddDangerToast,
|
||||
}),
|
||||
}));
|
||||
|
||||
const createProps = () => ({
|
||||
addDangerToast: jest.fn(),
|
||||
text: 'Download as Image',
|
||||
dashboardTitle: 'Test Dashboard',
|
||||
logEvent: jest.fn(),
|
||||
|
|
@ -40,22 +47,24 @@ const renderComponent = () => {
|
|||
<Menu>
|
||||
<DownloadAsImage {...createProps()} />
|
||||
</Menu>,
|
||||
{
|
||||
useRedux: true,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
test('Should call download image on click', async () => {
|
||||
const props = createProps();
|
||||
renderComponent();
|
||||
await waitFor(() => {
|
||||
expect(downloadAsImage).toHaveBeenCalledTimes(0);
|
||||
expect(props.addDangerToast).toHaveBeenCalledTimes(0);
|
||||
expect(mockAddDangerToast).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: 'Download as Image' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(downloadAsImage).toHaveBeenCalledTimes(1);
|
||||
expect(props.addDangerToast).toHaveBeenCalledTimes(0);
|
||||
expect(mockAddDangerToast).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,20 +21,20 @@ 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';
|
||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
|
||||
export default function DownloadAsImage({
|
||||
text,
|
||||
logEvent,
|
||||
dashboardTitle,
|
||||
addDangerToast,
|
||||
...rest
|
||||
}: {
|
||||
text: string;
|
||||
addDangerToast: Function;
|
||||
dashboardTitle: string;
|
||||
logEvent?: Function;
|
||||
}) {
|
||||
const SCREENSHOT_NODE_SELECTOR = '.dashboard';
|
||||
const { addDangerToast } = useToasts();
|
||||
const onDownloadImage = async (e: SyntheticEvent) => {
|
||||
try {
|
||||
downloadAsImage(SCREENSHOT_NODE_SELECTOR, dashboardTitle, true)(e);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { 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';
|
||||
|
||||
const mockAddDangerToast = jest.fn();
|
||||
|
||||
jest.mock('src/utils/downloadAsPdf', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => (_e: SyntheticEvent) => {}),
|
||||
}));
|
||||
|
||||
jest.mock('src/components/MessageToasts/withToasts', () => ({
|
||||
useToasts: () => ({
|
||||
addDangerToast: mockAddDangerToast,
|
||||
}),
|
||||
}));
|
||||
|
||||
const createProps = () => ({
|
||||
text: 'Export as PDF',
|
||||
dashboardTitle: 'Test Dashboard',
|
||||
logEvent: jest.fn(),
|
||||
});
|
||||
|
||||
const renderComponent = () => {
|
||||
render(
|
||||
<Menu>
|
||||
<DownloadAsPdf {...createProps()} />
|
||||
</Menu>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
};
|
||||
|
||||
test('Should call download pdf on click', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() => {
|
||||
expect(downloadAsPdf).toHaveBeenCalledTimes(0);
|
||||
expect(mockAddDangerToast).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByRole('button', { name: 'Export as PDF' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(downloadAsPdf).toHaveBeenCalledTimes(1);
|
||||
expect(mockAddDangerToast).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('Component is rendered with role="button"', async () => {
|
||||
renderComponent();
|
||||
const button = screen.getByRole('button', { name: 'Export as PDF' });
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 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 { 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';
|
||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
|
||||
export default function DownloadAsPdf({
|
||||
text,
|
||||
logEvent,
|
||||
dashboardTitle,
|
||||
...rest
|
||||
}: {
|
||||
text: string;
|
||||
dashboardTitle: string;
|
||||
logEvent?: Function;
|
||||
}) {
|
||||
const SCREENSHOT_NODE_SELECTOR = '.dashboard';
|
||||
const { addDangerToast } = useToasts();
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
@ -130,6 +130,9 @@ describe('DownloadScreenshot component', () => {
|
|||
await waitFor(() => {
|
||||
expect(mockAddInfoToast).toHaveBeenCalledWith(
|
||||
'The screenshot is being generated. Please, do not leave the page.',
|
||||
{
|
||||
noDuplicate: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -202,7 +205,7 @@ describe('DownloadScreenshot component', () => {
|
|||
// Wait for the successful image retrieval message
|
||||
await waitFor(() => {
|
||||
expect(mockAddSuccessToast).toHaveBeenCalledWith(
|
||||
'The screenshot is now being downloaded.',
|
||||
'The screenshot has been downloaded.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { useSelector } from 'react-redux';
|
|||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
import { last } from 'lodash';
|
||||
import { getDashboardUrlParams } from 'src/utils/urlUtils';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { DownloadScreenshotFormat } from './types';
|
||||
|
||||
const RETRY_INTERVAL = 3000;
|
||||
|
|
@ -53,21 +54,66 @@ export default function DownloadScreenshot({
|
|||
const activeTabs = useSelector(
|
||||
(state: RootState) => state.dashboardState.activeTabs || undefined,
|
||||
);
|
||||
|
||||
const anchor = useSelector(
|
||||
(state: RootState) =>
|
||||
last(state.dashboardState.directPathToChild) || undefined,
|
||||
);
|
||||
|
||||
const dataMask = useSelector(
|
||||
(state: RootState) => state.dataMask || undefined,
|
||||
);
|
||||
|
||||
const { addDangerToast, addSuccessToast, addInfoToast } = useToasts();
|
||||
const currentIntervalIds = useRef<NodeJS.Timeout[]>([]);
|
||||
|
||||
const printLoadingToast = () =>
|
||||
addInfoToast(
|
||||
t('The screenshot is being generated. Please, do not leave the page.'),
|
||||
{
|
||||
noDuplicate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const printFailureToast = useCallback(
|
||||
() =>
|
||||
addDangerToast(
|
||||
t('The screenshot could not be downloaded. Please, try again later.'),
|
||||
),
|
||||
[addDangerToast],
|
||||
);
|
||||
|
||||
const printSuccessToast = useCallback(
|
||||
() => addSuccessToast(t('The screenshot has been downloaded.')),
|
||||
[addSuccessToast],
|
||||
);
|
||||
|
||||
const stopIntervals = useCallback(
|
||||
(message?: 'success' | 'failure') => {
|
||||
currentIntervalIds.current.forEach(clearInterval);
|
||||
|
||||
if (message === 'failure') {
|
||||
printFailureToast();
|
||||
}
|
||||
if (message === 'success') {
|
||||
printSuccessToast();
|
||||
}
|
||||
},
|
||||
[printFailureToast, printSuccessToast],
|
||||
);
|
||||
|
||||
const onDownloadScreenshot = () => {
|
||||
let retries = 0;
|
||||
|
||||
const toastIntervalId = setInterval(
|
||||
() => printLoadingToast(),
|
||||
RETRY_INTERVAL,
|
||||
);
|
||||
|
||||
currentIntervalIds.current = [
|
||||
...(currentIntervalIds.current || []),
|
||||
toastIntervalId,
|
||||
];
|
||||
|
||||
printLoadingToast();
|
||||
|
||||
// this function checks if the image is ready
|
||||
const checkImageReady = (cacheKey: string) =>
|
||||
SupersetClient.get({
|
||||
|
|
@ -85,6 +131,7 @@ export default function DownloadScreenshot({
|
|||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
stopIntervals('success');
|
||||
})
|
||||
.catch(err => {
|
||||
if ((err as SupersetApiError).status === 404) {
|
||||
|
|
@ -92,33 +139,14 @@ export default function DownloadScreenshot({
|
|||
}
|
||||
});
|
||||
|
||||
// this is the functions that handles the retries
|
||||
const fetchImageWithRetry = (cacheKey: string) => {
|
||||
checkImageReady(cacheKey)
|
||||
.then(() => {
|
||||
addSuccessToast(t('The screenshot is now being downloaded.'));
|
||||
})
|
||||
.catch(error => {
|
||||
// we check how many retries have been made
|
||||
if (retries < MAX_RETRIES) {
|
||||
retries += 1;
|
||||
addInfoToast(
|
||||
t(
|
||||
'The screenshot is being generated. Please, do not leave the page.',
|
||||
),
|
||||
{
|
||||
noDuplicate: true,
|
||||
},
|
||||
);
|
||||
setTimeout(() => fetchImageWithRetry(cacheKey), RETRY_INTERVAL);
|
||||
} else {
|
||||
addDangerToast(
|
||||
t(
|
||||
'The screenshot could not be downloaded. Please, try again later.',
|
||||
),
|
||||
);
|
||||
logging.error(error);
|
||||
if (retries >= MAX_RETRIES) {
|
||||
stopIntervals('failure');
|
||||
logging.error('Max retries reached');
|
||||
return;
|
||||
}
|
||||
checkImageReady(cacheKey).catch(() => {
|
||||
retries += 1;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -136,18 +164,15 @@ export default function DownloadScreenshot({
|
|||
if (!cacheKey) {
|
||||
throw new Error('No image URL in response');
|
||||
}
|
||||
addInfoToast(
|
||||
t(
|
||||
'The screenshot is being generated. Please, do not leave the page.',
|
||||
),
|
||||
);
|
||||
const retryIntervalId = setInterval(() => {
|
||||
fetchImageWithRetry(cacheKey);
|
||||
}, RETRY_INTERVAL);
|
||||
currentIntervalIds.current.push(retryIntervalId);
|
||||
fetchImageWithRetry(cacheKey);
|
||||
})
|
||||
.catch(error => {
|
||||
logging.error(error);
|
||||
addDangerToast(
|
||||
t('The screenshot could not be downloaded. Please, try again later.'),
|
||||
);
|
||||
stopIntervals('failure');
|
||||
})
|
||||
.finally(() => {
|
||||
logEvent?.(
|
||||
|
|
@ -158,6 +183,16 @@ export default function DownloadScreenshot({
|
|||
});
|
||||
};
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (currentIntervalIds.current.length > 0) {
|
||||
stopIntervals();
|
||||
}
|
||||
currentIntervalIds.current = [];
|
||||
},
|
||||
[stopIntervals],
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu.Item key={format} {...rest}>
|
||||
<div onClick={onDownloadScreenshot} role="button" tabIndex={0}>
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { Menu } from 'src/components/Menu';
|
||||
import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core';
|
||||
import DownloadScreenshot from './DownloadScreenshot';
|
||||
import { DownloadScreenshotFormat } from './types';
|
||||
import DownloadAsPdf from './DownloadAsPdf';
|
||||
import DownloadAsImage from './DownloadAsImage';
|
||||
|
||||
export interface DownloadMenuItemProps {
|
||||
pdfMenuItemTitle: string;
|
||||
|
|
@ -34,11 +37,17 @@ const DownloadMenuItems = (props: DownloadMenuItemProps) => {
|
|||
imageMenuItemTitle,
|
||||
logEvent,
|
||||
dashboardId,
|
||||
dashboardTitle,
|
||||
...rest
|
||||
} = props;
|
||||
const isWebDriverScreenshotEnabled =
|
||||
isFeatureEnabled(FeatureFlag.EnableDashboardScreenshotEndpoints) &&
|
||||
isFeatureEnabled(FeatureFlag.EnableDashboardDownloadWebDriverScreenshot);
|
||||
|
||||
return (
|
||||
<Menu selectable={false}>
|
||||
{isWebDriverScreenshotEnabled ? (
|
||||
<>
|
||||
<DownloadScreenshot
|
||||
text={pdfMenuItemTitle}
|
||||
dashboardId={dashboardId}
|
||||
|
|
@ -53,6 +62,23 @@ const DownloadMenuItems = (props: DownloadMenuItemProps) => {
|
|||
format={DownloadScreenshotFormat.PNG}
|
||||
{...rest}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DownloadAsPdf
|
||||
text={pdfMenuItemTitle}
|
||||
dashboardTitle={dashboardTitle}
|
||||
logEvent={logEvent}
|
||||
{...rest}
|
||||
/>
|
||||
<DownloadAsImage
|
||||
text={imageMenuItemTitle}
|
||||
dashboardTitle={dashboardTitle}
|
||||
logEvent={logEvent}
|
||||
{...rest}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
declare module 'dom-to-pdf' {
|
||||
interface Image {
|
||||
type: string;
|
||||
quality: number;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
margin: number;
|
||||
filename: string;
|
||||
image: Image;
|
||||
html2canvas: object;
|
||||
excludeClassNames?: string[];
|
||||
}
|
||||
|
||||
function domToPdf(elementToPrint: Element, options?: Options): Promise<any>;
|
||||
|
||||
export default domToPdf;
|
||||
}
|
||||
|
|
@ -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 domToPdf from 'dom-to-pdf';
|
||||
import { kebabCase } from 'lodash';
|
||||
import { logging, t } from '@superset-ui/core';
|
||||
import { addWarningToast } from 'src/components/MessageToasts/actions';
|
||||
|
||||
/**
|
||||
* 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()) =>
|
||||
`${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 isExactSelector if false, searches for the closest ancestor that matches selector.
|
||||
* @returns event handler
|
||||
*/
|
||||
export default function downloadAsPdf(
|
||||
selector: string,
|
||||
description: string,
|
||||
isExactSelector = false,
|
||||
) {
|
||||
return (event: SyntheticEvent) => {
|
||||
const elementToPrint = isExactSelector
|
||||
? document.querySelector(selector)
|
||||
: event.currentTarget.closest(selector);
|
||||
|
||||
if (!elementToPrint) {
|
||||
return addWarningToast(
|
||||
t('PDF download failed, please refresh and try again.'),
|
||||
);
|
||||
}
|
||||
|
||||
const options = {
|
||||
margin: 10,
|
||||
filename: `${generateFileStem(description)}.pdf`,
|
||||
image: { type: 'jpeg', quality: 1 },
|
||||
html2canvas: { scale: 2 },
|
||||
excludeClassNames: ['header-controls'],
|
||||
};
|
||||
return domToPdf(elementToPrint, options)
|
||||
.then(() => {
|
||||
// nothing to be done
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
logging.error('PDF generation failed', e);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -478,6 +478,14 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = {
|
|||
"PRESTO_EXPAND_DATA": False,
|
||||
# Exposes API endpoint to compute thumbnails
|
||||
"THUMBNAILS": False,
|
||||
# Enable the endpoints to cache and retrieve dashboard screenshots via webdriver.
|
||||
# Requires configuring Celery and a cache using THUMBNAIL_CACHE_CONFIG.
|
||||
"ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS": False,
|
||||
# Generate screenshots (PDF or JPG) of dashboards using the web driver.
|
||||
# When disabled, screenshots are generated on the fly by the browser.
|
||||
# This feature flag is used by the download feature in the dashboard view.
|
||||
# It is dependent on ENABLE_DASHBOARD_SCREENSHOT_ENDPOINT being enabled.
|
||||
"ENABLE_DASHBOARD_DOWNLOAD_WEBDRIVER_SCREENSHOT": False,
|
||||
"SHARE_QUERIES_VIA_KV_STORE": False,
|
||||
"TAGGING_SYSTEM": False,
|
||||
"SQLLAB_BACKEND_PERSISTENCE": True,
|
||||
|
|
|
|||
|
|
@ -156,12 +156,18 @@ def with_dashboard(
|
|||
class DashboardRestApi(BaseSupersetModelRestApi):
|
||||
datamodel = SQLAInterface(Dashboard)
|
||||
|
||||
@before_request(only=["thumbnail"])
|
||||
@before_request(only=["thumbnail", "cache_dashboard_screenshot", "screenshot"])
|
||||
def ensure_thumbnails_enabled(self) -> Optional[Response]:
|
||||
if not is_feature_enabled("THUMBNAILS"):
|
||||
return self.response_404()
|
||||
return None
|
||||
|
||||
@before_request(only=["cache_dashboard_screenshot", "screenshot"])
|
||||
def ensure_screenshots_enabled(self) -> Optional[Response]:
|
||||
if not is_feature_enabled("ENABLE_DASHBOARD_SCREENSHOT_ENDPOINTS"):
|
||||
return self.response_404()
|
||||
return None
|
||||
|
||||
include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET | {
|
||||
RouteMethod.EXPORT,
|
||||
RouteMethod.IMPORT,
|
||||
|
|
@ -1133,7 +1139,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
|||
dashboard_id=dashboard.id,
|
||||
dashboard_url=dashboard_url,
|
||||
cache_key=cache_key,
|
||||
force=True,
|
||||
force=False,
|
||||
thumb_size=thumb_size,
|
||||
window_size=window_size,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3025,7 +3025,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
return self.client.get(uri)
|
||||
|
||||
@pytest.mark.usefixtures("create_dashboard_with_tag")
|
||||
def test_cache_dashboard_screenshot_success(self):
|
||||
@patch("superset.dashboards.api.is_feature_enabled")
|
||||
def test_cache_dashboard_screenshot_success(self, is_feature_enabled):
|
||||
is_feature_enabled.return_value = True
|
||||
self.login(ADMIN_USERNAME)
|
||||
dashboard = (
|
||||
db.session.query(Dashboard)
|
||||
|
|
@ -3036,7 +3038,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
assert response.status_code == 202
|
||||
|
||||
@pytest.mark.usefixtures("create_dashboard_with_tag")
|
||||
def test_cache_dashboard_screenshot_dashboard_validation(self):
|
||||
@patch("superset.dashboards.api.is_feature_enabled")
|
||||
def test_cache_dashboard_screenshot_dashboard_validation(self, is_feature_enabled):
|
||||
is_feature_enabled.return_value = True
|
||||
self.login(ADMIN_USERNAME)
|
||||
dashboard = (
|
||||
db.session.query(Dashboard)
|
||||
|
|
@ -3052,7 +3056,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
response = self._cache_screenshot(dashboard.id, invalid_payload)
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_cache_dashboard_screenshot_dashboard_not_found(self):
|
||||
@patch("superset.dashboards.api.is_feature_enabled")
|
||||
def test_cache_dashboard_screenshot_dashboard_not_found(self, is_feature_enabled):
|
||||
is_feature_enabled.return_value = True
|
||||
self.login(ADMIN_USERNAME)
|
||||
non_existent_id = 999
|
||||
response = self._cache_screenshot(non_existent_id)
|
||||
|
|
@ -3061,10 +3067,14 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
@pytest.mark.usefixtures("create_dashboard_with_tag")
|
||||
@patch("superset.dashboards.api.cache_dashboard_screenshot")
|
||||
@patch("superset.dashboards.api.DashboardScreenshot.get_from_cache_key")
|
||||
def test_screenshot_success_png(self, mock_get_cache, mock_cache_task):
|
||||
@patch("superset.dashboards.api.is_feature_enabled")
|
||||
def test_screenshot_success_png(
|
||||
self, is_feature_enabled, mock_get_cache, mock_cache_task
|
||||
):
|
||||
"""
|
||||
Validate screenshot returns png
|
||||
"""
|
||||
is_feature_enabled.return_value = True
|
||||
self.login(ADMIN_USERNAME)
|
||||
mock_cache_task.return_value = None
|
||||
mock_get_cache.return_value = BytesIO(b"fake image data")
|
||||
|
|
@ -3087,12 +3097,14 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
@patch("superset.dashboards.api.cache_dashboard_screenshot")
|
||||
@patch("superset.dashboards.api.build_pdf_from_screenshots")
|
||||
@patch("superset.dashboards.api.DashboardScreenshot.get_from_cache_key")
|
||||
@patch("superset.dashboards.api.is_feature_enabled")
|
||||
def test_screenshot_success_pdf(
|
||||
self, mock_get_from_cache, mock_build_pdf, mock_cache_task
|
||||
self, is_feature_enabled, mock_get_from_cache, mock_build_pdf, mock_cache_task
|
||||
):
|
||||
"""
|
||||
Validate screenshot can return pdf.
|
||||
"""
|
||||
is_feature_enabled.return_value = True
|
||||
self.login(ADMIN_USERNAME)
|
||||
mock_cache_task.return_value = None
|
||||
mock_get_from_cache.return_value = BytesIO(b"fake image data")
|
||||
|
|
@ -3115,7 +3127,11 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
@pytest.mark.usefixtures("create_dashboard_with_tag")
|
||||
@patch("superset.dashboards.api.cache_dashboard_screenshot")
|
||||
@patch("superset.dashboards.api.DashboardScreenshot.get_from_cache_key")
|
||||
def test_screenshot_not_in_cache(self, mock_get_cache, mock_cache_task):
|
||||
@patch("superset.dashboards.api.is_feature_enabled")
|
||||
def test_screenshot_not_in_cache(
|
||||
self, is_feature_enabled, mock_get_cache, mock_cache_task
|
||||
):
|
||||
is_feature_enabled.return_value = True
|
||||
self.login(ADMIN_USERNAME)
|
||||
mock_cache_task.return_value = None
|
||||
mock_get_cache.return_value = None
|
||||
|
|
@ -3132,7 +3148,9 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
response = self._get_screenshot(dashboard.id, cache_key, "pdf")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_screenshot_dashboard_not_found(self):
|
||||
@patch("superset.dashboards.api.is_feature_enabled")
|
||||
def test_screenshot_dashboard_not_found(self, is_feature_enabled):
|
||||
is_feature_enabled.return_value = True
|
||||
self.login(ADMIN_USERNAME)
|
||||
non_existent_id = 999
|
||||
response = self._get_screenshot(non_existent_id, "some_cache_key", "png")
|
||||
|
|
@ -3141,7 +3159,11 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
@pytest.mark.usefixtures("create_dashboard_with_tag")
|
||||
@patch("superset.dashboards.api.cache_dashboard_screenshot")
|
||||
@patch("superset.dashboards.api.DashboardScreenshot.get_from_cache_key")
|
||||
def test_screenshot_invalid_download_format(self, mock_get_cache, mock_cache_task):
|
||||
@patch("superset.dashboards.api.is_feature_enabled")
|
||||
def test_screenshot_invalid_download_format(
|
||||
self, is_feature_enabled, mock_get_cache, mock_cache_task
|
||||
):
|
||||
is_feature_enabled.return_value = True
|
||||
self.login(ADMIN_USERNAME)
|
||||
mock_cache_task.return_value = None
|
||||
mock_get_cache.return_value = BytesIO(b"fake png data")
|
||||
|
|
@ -3158,3 +3180,20 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
|
||||
response = self._get_screenshot(dashboard.id, cache_key, "invalid")
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.usefixtures("create_dashboard_with_tag")
|
||||
@patch("superset.dashboards.api.is_feature_enabled")
|
||||
def test_cache_dashboard_screenshot_feature_disabled(self, is_feature_enabled):
|
||||
is_feature_enabled.return_value = False
|
||||
self.login(ADMIN_USERNAME)
|
||||
|
||||
dashboard = (
|
||||
db.session.query(Dashboard)
|
||||
.filter(Dashboard.dashboard_title == "dash with tag")
|
||||
.first()
|
||||
)
|
||||
|
||||
assert dashboard is not None
|
||||
|
||||
response = self._cache_screenshot(dashboard.id)
|
||||
assert response.status_code == 404
|
||||
|
|
|
|||
Loading…
Reference in New Issue