From ff33a59b1fc820e4667ed1fa418a0f23820afcf2 Mon Sep 17 00:00:00 2001 From: Rupal Mahajan Date: Tue, 3 Jan 2023 17:26:04 +0000 Subject: [PATCH] [Backport 2.3.0] Use front-end report generation instead of chromium (#612) * Bump version 2.3.1 (#500) Signed-off-by: Rupal Mahajan Signed-off-by: Rupal Mahajan * Use front-end report generation instead of chromium (#586) Signed-off-by: Rupal Mahajan * Increment version to 2.4.1-SNAPSHOT (#540) Signed-off-by: opensearch-ci-bot Signed-off-by: opensearch-ci-bot Co-authored-by: opensearch-ci-bot * --wip-- Signed-off-by: Joshua Li * Add initial implementation of client reporting generation Signed-off-by: Joshua Li * Fix url with basepath Signed-off-by: Joshua Li * Update header footer height Signed-off-by: Joshua Li * Update dialog text to not close dialog Signed-off-by: Joshua Li * Remove console.log Signed-off-by: Joshua Li * Remove unused components Signed-off-by: Joshua Li * Remove chromium references Signed-off-by: Joshua Li * Add report generation error handling Signed-off-by: Joshua Li * Minor refactors Signed-off-by: Joshua Li * Add postinstall patch to support safari for html2canvas Signed-off-by: Joshua Li * Add dompurify Signed-off-by: Joshua Li * Fix build error Signed-off-by: Joshua Li * Remove chromium from CI Signed-off-by: Joshua Li * Update CI artifact name Signed-off-by: Joshua Li Signed-off-by: opensearch-ci-bot Signed-off-by: Joshua Li Co-authored-by: opensearch-trigger-bot[bot] <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Co-authored-by: opensearch-ci-bot Signed-off-by: Rupal Mahajan * Fix workflow Signed-off-by: Rupal Mahajan * Fix dompurify version for build Signed-off-by: Rupal Mahajan * Revert "Bump version 2.3.1 (#500)" This reverts commit 9ca29ff1527d3a50b7861510989dd7bba94861cc. Signed-off-by: Rupal Mahajan Signed-off-by: Rupal Mahajan Signed-off-by: opensearch-ci-bot Signed-off-by: Joshua Li Co-authored-by: Joshua Li Co-authored-by: opensearch-trigger-bot[bot] <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Co-authored-by: opensearch-ci-bot --- ...boards-reports-test-and-build-workflow.yml | 51 +-- README.md | 35 -- dashboards-reports/.gitignore | 1 - ....opensearch_dashboards-plugin-helpers.json | 1 + dashboards-reports/package.json | 11 +- .../components/context_menu/context_menu.js | 72 +++- .../context_menu/context_menu_ui.js | 13 +- .../main/loading_modal/loading_modal.tsx | 11 +- .../public/components/main/main_utils.tsx | 50 ++- .../components/utils/settings_service.ts | 9 +- .../visual_report/assets/report_styles.ts} | 7 + .../components/visual_report/constants.ts | 34 ++ .../visual_report/generate_report.ts | 207 ++++++++++ dashboards-reports/public/plugin.ts | 2 +- .../headless-chrome/README.md | 56 --- .../headless-chrome/build_headless_chrome.sh | 176 -------- .../scripts/patch-html2canvas.js | 33 ++ dashboards-reports/server/plugin.ts | 6 - .../server/routes/lib/createReport.ts | 37 +- dashboards-reports/server/routes/report.ts | 6 + .../__tests__/visualReportHelper.test.ts | 84 ---- .../server/routes/utils/constants.ts | 18 - .../server/routes/utils/types.ts | 2 + .../utils/visual_report/footer_template.html | 5 - .../utils/visual_report/header_template.html | 5 - .../utils/visual_report/visualReportHelper.ts | 332 --------------- dashboards-reports/translations/pl.json | 4 +- dashboards-reports/yarn.lock | 389 +++++++++--------- 28 files changed, 614 insertions(+), 1043 deletions(-) rename dashboards-reports/{server/routes/utils/visual_report/style.css => public/components/visual_report/assets/report_styles.ts} (97%) create mode 100644 dashboards-reports/public/components/visual_report/constants.ts create mode 100644 dashboards-reports/public/components/visual_report/generate_report.ts delete mode 100644 dashboards-reports/rendering-engine/headless-chrome/README.md delete mode 100644 dashboards-reports/rendering-engine/headless-chrome/build_headless_chrome.sh create mode 100644 dashboards-reports/scripts/patch-html2canvas.js delete mode 100644 dashboards-reports/server/routes/utils/__tests__/visualReportHelper.test.ts delete mode 100644 dashboards-reports/server/routes/utils/visual_report/footer_template.html delete mode 100644 dashboards-reports/server/routes/utils/visual_report/header_template.html delete mode 100644 dashboards-reports/server/routes/utils/visual_report/visualReportHelper.ts diff --git a/.github/workflows/dashboards-reports-test-and-build-workflow.yml b/.github/workflows/dashboards-reports-test-and-build-workflow.yml index 71b8543d..37bffb52 100644 --- a/.github/workflows/dashboards-reports-test-and-build-workflow.yml +++ b/.github/workflows/dashboards-reports-test-and-build-workflow.yml @@ -37,15 +37,6 @@ jobs: - name: Move Dashboards Reports to Plugins Dir run: mv dashboards-reports ../OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }} - - name: Add Chromium Binary to Reporting for Testing - run: | - sudo apt update - sudo apt install -y libnss3-dev fonts-liberation libfontconfig1 - cd ../OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }} - wget https://github.com/opendistro-for-elasticsearch/kibana-reports/releases/download/chromium-1.12.0.0/chromium-linux-x64.zip - unzip chromium-linux-x64.zip - rm chromium-linux-x64.zip - - name: OpenSearch Dashboards Plugin Bootstrap uses: nick-invision/retry@v1 with: @@ -71,48 +62,10 @@ jobs: run: | cd ../OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }} yarn build - - cd build - mkdir -p ./{linux-x64,linux-arm64,windows-x64}/opensearch-dashboards/${{ env.PLUGIN_NAME }} - cp ./${{ env.PLUGIN_NAME }}-*.zip ./linux-x64/${{ env.ARTIFACT_NAME }}-${{ env.OPENSEARCH_PLUGIN_VERSION }}-linux-x64.zip - cp ./${{ env.PLUGIN_NAME }}-*.zip ./linux-arm64/${{ env.ARTIFACT_NAME }}-${{ env.OPENSEARCH_PLUGIN_VERSION }}-linux-arm64.zip - mv ./${{ env.PLUGIN_NAME }}-*.zip ./windows-x64/${{ env.ARTIFACT_NAME }}-${{ env.OPENSEARCH_PLUGIN_VERSION }}-windows-x64.zip - - cd linux-x64 - wget https://github.com/opensearch-project/dashboards-reports/releases/download/chromium-1.12.0.0/chromium-linux-x64.zip - unzip chromium-linux-x64.zip -d ./opensearch-dashboards/${{ env.PLUGIN_NAME }} - zip -ur ./${{ env.ARTIFACT_NAME }}-*.zip ./opensearch-dashboards - mv ./${{ env.ARTIFACT_NAME }}-*.zip .. - cd .. - - cd linux-arm64 - wget https://github.com/opensearch-project/dashboards-reports/releases/download/chromium-1.12.0.0/chromium-linux-arm64.zip - unzip chromium-linux-arm64.zip -d ./opensearch-dashboards/${{ env.PLUGIN_NAME }} - zip -ur ./${{ env.ARTIFACT_NAME }}-*.zip ./opensearch-dashboards - mv ./${{ env.ARTIFACT_NAME }}-*.zip .. - cd .. - - cd windows-x64 - wget https://github.com/opensearch-project/dashboards-reports/releases/download/chromium-1.12.0.0/chromium-windows-x64.zip - unzip chromium-windows-x64.zip -d ./opensearch-dashboards/${{ env.PLUGIN_NAME }} - zip -ur ./${{ env.ARTIFACT_NAME }}-*.zip ./opensearch-dashboards - mv ./${{ env.ARTIFACT_NAME }}-*.zip .. - cd .. + mv ./build/*.zip ./build/${{ env.ARTIFACT_NAME }}-${{ env.OPENSEARCH_PLUGIN_VERSION }}.zip - name: Upload Artifact For Linux x64 uses: actions/upload-artifact@v1 with: name: dashboards-reports-linux-x64 - path: ../OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }}/build/${{ env.ARTIFACT_NAME }}-${{ env.OPENSEARCH_PLUGIN_VERSION }}-linux-x64.zip - - - name: Upload Artifact For Linux arm64 - uses: actions/upload-artifact@v1 - with: - name: dashboards-reports-linux-arm64 - path: ../OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }}/build/${{ env.ARTIFACT_NAME }}-${{ env.OPENSEARCH_PLUGIN_VERSION }}-linux-arm64.zip - - - name: Upload Artifact For Windows - uses: actions/upload-artifact@v1 - with: - name: dashboards-reports-windows-x64 - path: ../OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }}/build/${{ env.ARTIFACT_NAME }}-${{ env.OPENSEARCH_PLUGIN_VERSION }}-windows-x64.zip + path: ../OpenSearch-Dashboards/plugins/${{ env.PLUGIN_NAME }}/build/${{ env.ARTIFACT_NAME }}-${{ env.OPENSEARCH_PLUGIN_VERSION }}.zip diff --git a/README.md b/README.md index 9c1cca06..b79cff8c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ - [Contributing](#contributing) - [Setup](#setup-&-build) - [Notifications Integration](#notifications-integration) -- [Troubleshooting](#troubleshooting) - [Code of Conduct](#code-of-conduct) - [Security](#security) - [License](#license) @@ -106,39 +105,6 @@ Complete OpenSearch Dashboards Report feature is composed of 2 plugins. OpenSearch Dashboards Reports integration with [Notifications](https://github.com/opensearch-project/notifications) is currently in progress. Tracking [here](https://github.com/opensearch-project/dashboards-reports/issues/72) -## Troubleshooting - -### Fail to launch Chromium - -There could be two reasons for this problem - -1. You are not having the correct version of headless-chrome matching to the OS that your OpenSearch Dashboards is running. Different versions of headless-chrome can be found [here](https://github.com/opensearch-project/dashboards-reports/releases/tag/chromium-1.12.0.0) - -2. Missing additional dependencies. Please refer to [additional dependencies section](./dashboards-reports/rendering-engine/headless-chrome/README.md#additional-libaries) to install required dependencies according to your operating system. - -### Missing Font Dependencies - -Chromium may not have all of the dependencies you may require to be able to view all of the content of your reports. - -If you are using a CentOS/RHEL system, install the following packages: - -- [`ipa-gothic-fonts`](https://centos.pkgs.org/7/centos-x86_64/ipa-gothic-fonts-003.03-5.el7.noarch.rpm.html) -- [`xorg-x11-fonts-100dpi`](https://centos.pkgs.org/7/centos-x86_64/xorg-x11-fonts-100dpi-7.5-9.el7.noarch.rpm.html) -- [`xorg-x11-fonts-75dpi`](https://centos.pkgs.org/7/centos-x86_64/xorg-x11-fonts-75dpi-7.5-9.el7.noarch.rpm.html) -- [`xorg-x11-utils`](https://centos.pkgs.org/7/centos-x86_64/xorg-x11-utils-7.5-23.el7.x86_64.rpm.html) -- [`xorg-x11-fonts-cyrillic`](https://centos.pkgs.org/7/centos-x86_64/xorg-x11-fonts-cyrillic-7.5-9.el7.noarch.rpm.html) -- [`xorg-x11-fonts-Type1`](https://centos.pkgs.org/7/centos-x86_64/xorg-x11-fonts-Type1-7.5-9.el7.noarch.rpm.html) -- [`xorg-x11-fonts-misc`](https://centos.pkgs.org/7/centos-x86_64/xorg-x11-fonts-misc-7.5-9.el7.noarch.rpm.html) -- [`fontconfig`](https://www.freedesktop.org/wiki/Software/fontconfig/) -- [`freetype`](https://freetype.org/) - -If you are using a Ubuntu/Debian system, install the following packages: - -- [`fonts-liberation`](https://packages.debian.org/search?keywords=fonts-liberation) -- [`libfontconfig1`](https://packages.debian.org/sid/libfontconfig1) - -The installation command for both systems can be found [here](./dashboards-reports/rendering-engine/headless-chrome/README.md). - ## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](CODE_OF_CONDUCT.md). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq), or contact [opensource-codeofconduct@amazon.com](mailto:opensource-codeofconduct@amazon.com) with any additional questions or comments. @@ -153,4 +119,3 @@ See the [LICENSE](./LICENSE) file for our project's licensing. We will ask you t ## Copyright -Copyright OpenSearch Contributors. See [NOTICE](NOTICE.txt) for details. diff --git a/dashboards-reports/.gitignore b/dashboards-reports/.gitignore index 9e1fedcc..16b7cd30 100644 --- a/dashboards-reports/.gitignore +++ b/dashboards-reports/.gitignore @@ -10,4 +10,3 @@ yarn-error.log .eslintcache package-lock.json /target/ -.chromium/ \ No newline at end of file diff --git a/dashboards-reports/.opensearch_dashboards-plugin-helpers.json b/dashboards-reports/.opensearch_dashboards-plugin-helpers.json index 05b7d7e4..eee5a7ea 100644 --- a/dashboards-reports/.opensearch_dashboards-plugin-helpers.json +++ b/dashboards-reports/.opensearch_dashboards-plugin-helpers.json @@ -5,6 +5,7 @@ "yarn.lock", ".i18nrc.json", "common/**/*", + "scripts/**/*", "public/**/*", "server/**/*", "translations/**/*" diff --git a/dashboards-reports/package.json b/dashboards-reports/package.json index 47181e18..523b2c97 100644 --- a/dashboards-reports/package.json +++ b/dashboards-reports/package.json @@ -13,20 +13,21 @@ "test": "../../node_modules/.bin/jest --config ./test/jest.config.js", "cypress:run": "cypress run", "cypress:open": "cypress open", - "plugin_helpers": "node ../../scripts/plugin_helpers" + "plugin_helpers": "node ../../scripts/plugin_helpers", + "postinstall": "node ./scripts/patch-html2canvas.js" }, "dependencies": { - "async-mutex": "^0.2.6", "babel-polyfill": "^6.26.0", "cron-validator": "^1.1.1", - "dompurify": "^2.3.8", + "dompurify": "^2.4.1", "elastic-builder": "^2.7.1", "enzyme-adapter-react-16": "^1.15.5", + "html2canvas": "1.4.1", "jest-fetch-mock": "^3.0.3", "jquery": "^3.5.0", "jsdom": "13.1.0", "json-2-csv": "^3.7.6", - "puppeteer-core": "^13.7.0", + "jspdf": "^2.5.1", "react-addons-test-utils": "^15.6.2", "react-id-generator": "^3.0.1", "react-markdown": "^4.3.1", @@ -44,7 +45,6 @@ "@types/dompurify": "^2.3.3", "@types/enzyme-adapter-react-16": "^1.0.6", "@types/jsdom": "^16.2.3", - "@types/puppeteer-core": "^5.4.0", "@types/react": "^16.14.23", "@types/react-addons-test-utils": "^0.14.25", "@types/react-dom": "^16.9.8", @@ -60,6 +60,7 @@ "identity-obj-proxy": "^3.0.0", "jest-dom": "^4.0.0", "react-test-renderer": "^16.12.0", + "replace-in-file": "^6.3.5", "ts-jest": "^26.1.0" }, "resolutions": { diff --git a/dashboards-reports/public/components/context_menu/context_menu.js b/dashboards-reports/public/components/context_menu/context_menu.js index f30e336d..0b7485ab 100644 --- a/dashboards-reports/public/components/context_menu/context_menu.js +++ b/dashboards-reports/public/components/context_menu/context_menu.js @@ -4,24 +4,30 @@ */ /* eslint-disable no-restricted-globals */ -import $ from 'jquery'; +//@ts-check import { i18n } from '@osd/i18n'; +import $ from 'jquery'; +import { parse } from 'url'; import { readStreamToFile } from '../main/main_utils'; +import { uiSettingsService } from '../utils/settings_service'; +import { + GENERATE_REPORT_PARAM, + GENERATE_REPORT_PARAM_REGEX, +} from '../visual_report/constants'; +import { generateReport } from '../visual_report/generate_report'; import { - contextMenuCreateReportDefinition, - getTimeFieldsFromUrl, - displayLoadingModal, addSuccessOrFailureToast, + contextMenuCreateReportDefinition, contextMenuViewReports, + displayLoadingModal, + getTimeFieldsFromUrl, replaceQueryURL, } from './context_menu_helpers'; import { + getMenuItem, popoverMenu, popoverMenuDiscover, - getMenuItem, } from './context_menu_ui'; -import { parse } from 'url'; -import { uiSettingsService } from '../utils/settings_service'; const generateInContextReport = async ( timeRanges, @@ -102,23 +108,28 @@ const generateInContextReport = async ( credentials: 'include', } ) - .then((response) => { - if (response.status === 200) { - $('#reportGenerationProgressModal').remove(); - addSuccessOrFailureToast('success'); - } else { - if (response.status === 403) { + .then(async (response) => [response.status, await response.json()]) + .then(async ([status, data]) => { + if (status !== 200) { + if (status === 403) { addSuccessOrFailureToast('permissionsFailure'); - } else if (response.status === 503) { + } else if (status === 503) { addSuccessOrFailureToast('timeoutFailure', reportSource); } else { addSuccessOrFailureToast('failure'); } + } else if (fileFormat === 'pdf' || fileFormat === 'png') { + try { + await generateReport(data.reportId); + addSuccessOrFailureToast('success'); + } catch (error) { + console.error(error); + addSuccessOrFailureToast('failure'); + } + } else if (data.data) { + await readStreamToFile(data.data, fileFormat, data.filename); } - return response.json(); - }) - .then(async (data) => { - await readStreamToFile(data.data, fileFormat, data.filename); + $('#reportGenerationProgressModal').remove(); }); }; @@ -213,9 +224,34 @@ $(function () { }); }); + checkURLParams(); locationHashChanged(); }); +/* generate a report if flagged in URL params */ +const checkURLParams = async () => { + const [hash, query] = location.href.split('#')[1].split('?'); + const params = new URLSearchParams(query); + const id = params.get(GENERATE_REPORT_PARAM); + if (!id) return; + await new Promise((resolve) => setTimeout(resolve, 1000)); + displayLoadingModal(); + try { + await generateReport(id, 30000); + window.history.replaceState( + {}, + '', + `#${hash}?${query.replace(GENERATE_REPORT_PARAM_REGEX, '')}` + ); + addSuccessOrFailureToast('success'); + } catch (error) { + console.error(error); + addSuccessOrFailureToast('failure'); + } finally { + $('#reportGenerationProgressModal').remove(); + } +}; + const isDiscoverNavMenu = (navMenu) => { return ( navMenu[0].children.length === 5 && diff --git a/dashboards-reports/public/components/context_menu/context_menu_ui.js b/dashboards-reports/public/components/context_menu/context_menu_ui.js index 0c99641f..119b7bae 100644 --- a/dashboards-reports/public/components/context_menu/context_menu_ui.js +++ b/dashboards-reports/public/components/context_menu/context_menu_ui.js @@ -246,7 +246,7 @@ export const popoverMenuDiscover = (savedObjectAvailable) => { export const permissionsMissingOnGeneration = () => { return ` -
+

${i18n.translate( 'opensearch.reports.menu.newNotificationAppears', { defaultMessage: 'A new notification appears' } @@ -277,7 +277,7 @@ export const permissionsMissingOnGeneration = () => { export const reportGenerationSuccess = () => { return ` -

+

A new notification appears

@@ -319,7 +319,7 @@ export const reportGenerationFailure = ( }) ) => { return ` -
+

A new notification appears

@@ -346,7 +346,7 @@ export const reportGenerationFailure = ( export const reportGenerationInProgressModal = () => { return ` -
+
@@ -377,7 +377,7 @@ export const reportGenerationInProgressModal = () => { 'opensearch.reports.menu.progress.youCanClose', { defaultMessage: - 'You can close this dialog while we continue in the background.', + 'Please keep this dialog open while report is being generated.', } )}
@@ -385,9 +385,6 @@ export const reportGenerationInProgressModal = () => {
-
-
-
diff --git a/dashboards-reports/public/components/main/loading_modal/loading_modal.tsx b/dashboards-reports/public/components/main/loading_modal/loading_modal.tsx index ff3f70c2..61c53968 100644 --- a/dashboards-reports/public/components/main/loading_modal/loading_modal.tsx +++ b/dashboards-reports/public/components/main/loading_modal/loading_modal.tsx @@ -59,7 +59,7 @@ export function GenerateReportLoadingModal(props: { setShowLoading: any }) { {i18n.translate('opensearch.reports.loading.youCanClose', { defaultMessage: - 'You can close this dialog while we continue in the background.', + 'Please keep this dialog open while report is being generated.', })} @@ -72,15 +72,6 @@ export function GenerateReportLoadingModal(props: { setShowLoading: any }) { - - - - {i18n.translate('opensearch.reports.loading.close', { - defaultMessage: 'Close', - })} - - - diff --git a/dashboards-reports/public/components/main/main_utils.tsx b/dashboards-reports/public/components/main/main_utils.tsx index 44066c02..f4b32bbf 100644 --- a/dashboards-reports/public/components/main/main_utils.tsx +++ b/dashboards-reports/public/components/main/main_utils.tsx @@ -3,10 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import 'babel-polyfill'; import { i18n } from '@osd/i18n'; -import { HttpFetchOptions, HttpSetup } from '../../../../../src/core/public'; +import 'babel-polyfill'; +import { HttpSetup } from '../../../../../src/core/public'; import { uiSettingsService } from '../utils/settings_service'; +import { GENERATE_REPORT_PARAM } from '../visual_report/constants'; export const getAvailableNotificationsChannels = (configList: any) => { let availableChannels = []; @@ -14,16 +15,16 @@ export const getAvailableNotificationsChannels = (configList: any) => { let channelEntry = {}; channelEntry = { label: configList[i].config.name, - id: configList[i].config_id - } + id: configList[i].config_id, + }; availableChannels.push(channelEntry); } return availableChannels; -} +}; type fileFormatsOptions = { - [key: string]: string -} + [key: string]: string; +}; export const fileFormatsUpper: fileFormatsOptions = { csv: 'CSV', @@ -164,11 +165,23 @@ export const generateReportFromDefinitionId = async ( }) .then(async (response: any) => { // for emailing a report, this API response doesn't have response body - if (response) { - const fileFormat = extractFileFormat(response['filename']); - const fileName = response['filename']; + if (!response) return; + const fileFormat = extractFileFormat(response['filename']); + const fileName = response['filename']; + if (fileFormat === 'csv') { await readStreamToFile(await response['data'], fileFormat, fileName); + status = true; + return; } + + // generate reports in browser is memory intensive, do it in a new process by removing referrer + const a = document.createElement('a'); + a.href = + window.location.origin + + `${response.queryUrl}&${GENERATE_REPORT_PARAM}=${response.reportId}`; + a.target = '_blank'; + a.rel = 'noreferrer'; + a.click(); status = true; }) .catch((error) => { @@ -199,9 +212,20 @@ export const generateReportById = async ( //TODO: duplicate code, extract to be a function that can reuse. e.g. handleResponse(response) const fileFormat = extractFileFormat(response['filename']); const fileName = response['filename']; - await readStreamToFile(await response['data'], fileFormat, fileName); - handleSuccessToast(); - return response; + if (fileFormat === 'csv') { + await readStreamToFile(await response['data'], fileFormat, fileName); + handleSuccessToast(); + return response; + } + + // generate reports in browser is memory intensive, do it in a new process by removing referrer + const a = document.createElement('a'); + a.href = + window.location.origin + + `${response.queryUrl}&${GENERATE_REPORT_PARAM}=${reportId}`; + a.target = '_blank'; + a.rel = 'noreferrer'; + a.click(); }) .catch((error) => { console.log('error on generating report by id:', error); diff --git a/dashboards-reports/public/components/utils/settings_service.ts b/dashboards-reports/public/components/utils/settings_service.ts index 197e5e4e..a95b3303 100644 --- a/dashboards-reports/public/components/utils/settings_service.ts +++ b/dashboards-reports/public/components/utils/settings_service.ts @@ -3,13 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { IUiSettingsClient } from '../../../../../src/core/public'; +import { HttpStart, IUiSettingsClient } from '../../../../../src/core/public'; let uiSettings: IUiSettingsClient; +let http: HttpStart; export const uiSettingsService = { - init: (client: IUiSettingsClient) => { - uiSettings = client; + init: (uiSettingsClient: IUiSettingsClient, httpClient: HttpStart) => { + uiSettings = uiSettingsClient; + http = httpClient; }, get: (key: string, defaultOverride?: any) => { return uiSettings?.get(key, defaultOverride) || ''; @@ -28,4 +30,5 @@ export const uiSettingsService = { csvSeparator, }; }, + getHttpClient: () => http, }; diff --git a/dashboards-reports/server/routes/utils/visual_report/style.css b/dashboards-reports/public/components/visual_report/assets/report_styles.ts similarity index 97% rename from dashboards-reports/server/routes/utils/visual_report/style.css rename to dashboards-reports/public/components/visual_report/assets/report_styles.ts index c329e281..a78ee8a8 100644 --- a/dashboards-reports/server/routes/utils/visual_report/style.css +++ b/dashboards-reports/public/components/visual_report/assets/report_styles.ts @@ -1,3 +1,9 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const reportingStyle = ` html, body { margin: 0; @@ -213,3 +219,4 @@ iframe, embed, object { padding: 6px 13px; border: 1px solid #c8ccd0; } +`; diff --git a/dashboards-reports/public/components/visual_report/constants.ts b/dashboards-reports/public/components/visual_report/constants.ts new file mode 100644 index 00000000..c0e6a6d0 --- /dev/null +++ b/dashboards-reports/public/components/visual_report/constants.ts @@ -0,0 +1,34 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import Showdown from 'showdown'; + +// search param key name to trigger report generation, value is a report ID +export const GENERATE_REPORT_PARAM = 'visualReportId'; +export const GENERATE_REPORT_PARAM_REGEX = new RegExp( + '[&?]' + GENERATE_REPORT_PARAM + '=[^&]+', + '' +); + +export enum VISUAL_REPORT_TYPE { + dashboard = 'Dashboard', + visualization = 'Visualization', + notebook = 'Notebook', +} +export enum SELECTOR { + dashboard = '#dashboardViewport', + visualization = '.visEditor__content', + notebook = '.euiPageBody', +} + +export const DEFAULT_REPORT_HEADER = '

OpenSearch Dashboards Reports

'; + +export const converter = new Showdown.Converter({ + tables: true, + simplifiedAutoLink: true, + strikethrough: true, + tasklists: true, + noHeaderId: true, +}); diff --git a/dashboards-reports/public/components/visual_report/generate_report.ts b/dashboards-reports/public/components/visual_report/generate_report.ts new file mode 100644 index 00000000..52e82121 --- /dev/null +++ b/dashboards-reports/public/components/visual_report/generate_report.ts @@ -0,0 +1,207 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import createDOMPurify from 'dompurify'; +import html2canvas from 'html2canvas'; +import jsPDF from 'jspdf'; +import { v1 as uuidv1 } from 'uuid'; +import { ReportSchemaType } from '../../../server/model'; +import { uiSettingsService } from '../utils/settings_service'; +import { reportingStyle } from './assets/report_styles'; +import { + converter, + DEFAULT_REPORT_HEADER, + SELECTOR, + VISUAL_REPORT_TYPE, +} from './constants'; + +const waitForSelector = (selector: string, timeout = 30000) => { + return Promise.race([ + new Promise((resolve) => { + if (document.querySelector(selector)) { + return resolve(document.querySelector(selector)); + } + const observer = new MutationObserver((mutations) => { + if (document.querySelector(selector)) { + resolve(document.querySelector(selector)); + observer.disconnect(); + } + }); + observer.observe(document.body, { + childList: true, + subtree: true, + }); + }), + new Promise((resolve, reject) => + setTimeout( + () => + reject( + 'Timed out waiting for selector ' + + selector + + ' while generating report.' + ), + timeout + ) + ), + ]); +}; + +const timeout = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +const removeNonReportElements = ( + doc: Document, + reportSource: VISUAL_REPORT_TYPE +) => { + // remove buttons + doc.querySelectorAll("[class^='euiButton']").forEach((e) => e.remove()); + // remove top navBar + doc.querySelectorAll("[class^='euiHeader']").forEach((e) => e.remove()); + // remove visualization editor + if (reportSource === VISUAL_REPORT_TYPE.visualization) { + doc.querySelector('[data-test-subj="splitPanelResizer"]')?.remove(); + doc.querySelector('.visEditor__collapsibleSidebar')?.remove(); + } + doc.body.style.paddingTop = '0px'; +}; + +const addReportHeader = (doc: Document, header: string) => { + const headerHtml = `
+
+
${header}
+
+
`; + const headerContainer = document.createElement('div'); + headerContainer.className = 'reportWrapper'; + headerContainer.innerHTML = headerHtml; + const body = doc.getElementsByTagName('body')[0]; + body.insertBefore(headerContainer, body.children[0]); +}; + +const addReportFooter = (doc: Document, footer: string) => { + const footerHtml = `
+
+
${footer}
+
+
`; + const footerContainer = document.createElement('div'); + footerContainer.className = 'reportWrapper'; + footerContainer.innerHTML = footerHtml; + const body = doc.getElementsByTagName('body')[0]; + body.appendChild(footerContainer); +}; + +const addReportStyle = (doc: Document, style: string) => { + const styleElement = document.createElement('style'); + styleElement.innerHTML = style; + doc.getElementsByTagName('head')[0].appendChild(styleElement); +}; + +const computeHeight = (height: number, header: string, footer: string) => { + let computedHeight = height; + const headerLines = header.split('\n').length; + const footerLines = footer.split('\n').length; + if (headerLines) { + computedHeight += 24 * headerLines; + } + if (footerLines) { + computedHeight += 50 + 24 * footerLines; + } + return computedHeight; +}; + +export const generateReport = async (id: string, forceDelay = 15000) => { + const http = uiSettingsService.getHttpClient(); + const DOMPurify = createDOMPurify(window); + + const report = await http.get( + '../api/reporting/reports/' + id + ); + const format = + report.report_definition.report_params.core_params.report_format; + const reportSource = report.report_definition.report_params + .report_source as unknown as VISUAL_REPORT_TYPE; + const headerInput = report.report_definition.report_params.core_params.header; + const footerInput = report.report_definition.report_params.core_params.footer; + const header = headerInput + ? DOMPurify.sanitize(converter.makeHtml(headerInput)) + : DEFAULT_REPORT_HEADER; + const footer = footerInput + ? DOMPurify.sanitize(converter.makeHtml(footerInput)) + : ''; + const fileName = + report.report_definition.report_params.report_name + + `_${new Date().toISOString()}_${uuidv1()}.${format}`; + + await timeout(1000); + switch (reportSource) { + case VISUAL_REPORT_TYPE.dashboard: + await waitForSelector(SELECTOR.dashboard); + break; + case VISUAL_REPORT_TYPE.visualization: + await waitForSelector(SELECTOR.visualization); + break; + case VISUAL_REPORT_TYPE.notebook: + await waitForSelector(SELECTOR.notebook); + break; + default: + throw Error( + `report source can only be one of [Dashboard, Visualization, Notebook]` + ); + } + await timeout(forceDelay); + + const width = document.documentElement.scrollWidth; + const height = computeHeight( + document.documentElement.scrollHeight, + header, + footer + ); + return html2canvas(document.body, { + windowWidth: width, + windowHeight: height, + width, + height, + imageTimeout: 30000, + useCORS: true, + removeContainer: false, + onclone: function (documentClone) { + removeNonReportElements(documentClone, reportSource); + addReportHeader(documentClone, header); + addReportFooter(documentClone, footer); + addReportStyle(documentClone, reportingStyle); + }, + }).then(function (canvas) { + // TODO remove this and 'removeContainer: false' when https://github.com/niklasvh/html2canvas/pull/2949 is merged + document + .querySelectorAll('.html2canvas-container') + .forEach((e) => { + const iframe = e.contentWindow; + if (e) { + e.src = 'about:blank'; + if (iframe) { + iframe.document.write(''); + iframe.document.clear(); + iframe.close(); + } + e.remove(); + } + }); + + if (format === 'png') { + const link = document.createElement('a'); + link.download = fileName; + link.href = canvas.toDataURL(); + link.click(); + } else { + const orient = canvas.width > canvas.height ? 'landscape' : 'portrait'; + const pdf = new jsPDF(orient, 'px', [canvas.width, canvas.height]); + pdf.addImage(canvas, 'JPEG', 0, 0, canvas.width, canvas.height); + pdf.save(fileName); + } + return true; + }); +}; diff --git a/dashboards-reports/public/plugin.ts b/dashboards-reports/public/plugin.ts index 2c26eb6c..b91b6be1 100644 --- a/dashboards-reports/public/plugin.ts +++ b/dashboards-reports/public/plugin.ts @@ -23,7 +23,7 @@ export class ReportsDashboardsPlugin implements Plugin { public setup(core: CoreSetup): ReportsDashboardsPluginSetup { - uiSettingsService.init(core.uiSettings); + uiSettingsService.init(core.uiSettings, core.http); // Register an application into the side navigation menu core.application.register({ id: PLUGIN_ID, diff --git a/dashboards-reports/rendering-engine/headless-chrome/README.md b/dashboards-reports/rendering-engine/headless-chrome/README.md deleted file mode 100644 index 21cf34a5..00000000 --- a/dashboards-reports/rendering-engine/headless-chrome/README.md +++ /dev/null @@ -1,56 +0,0 @@ -## Chrome Binaries for OpenSearch Dashboards Reports used by Puppeteer -Headless Chrome for Linux and Mac are chrome binaries which are significantly smaller than the standard binaries shipped by Google and Puppeteer. -Chrome binary can be built from shell script build_headless_chrome.sh for Mac, Linux x64 and Linux arm64, -output of script is called headless_shell. - -## Puppeteer's Chrome version - -Find the puppeteer version used in OpenSearch Dashboards node_modules.json and get the associated chrome SHA to build from crrev.com and puppeteer repositories. Puppeteer 1.9 uses rev 674921 with commit sha as 312d84c8ce62810976feda0d3457108a6dfff9e6) - -## headless Chrome folder structure --chromium - |-chromium - |-chromium - |-src - |-out - |-headless - |-headless_shell # output of scripts - -## How to generate the headless_chrome -This is a shell script to set environment variable, download the source code and build the executable. - -## Commands to create headless_chrome -Run below command to create headless_shell for each platform - -headless-chrome.sh chrome-version-SHA (arch_name (arm64)) -. Mac x64: ./build_headless_chrome.sh -. Linux x64: ./build_headless_chrome.sh -. Linux arm64: ./build_headless_chrome.sh arm64 - -# How to call in Command line: -. PNG report: ./headless_shell --headless --disable-gpu --screenshot=test.png https://opensearch.org/docs/ -. PDF report: ./headless_shell --headless --disable-gpu --print-to-pdf=test.pdf https://opensearch.org/docs/ - -## Headless Chromium for MAC -# Files: - headless_shell - libswiftshader_libGLESv2.dylib - -## Headless Chromium for Linux (arm64 and x64) -# Files: - headless_shell - swiftshader - |-libEGL.so - |-libEGL.so.TOC - |-libGLESv2.so - |-libGLESv2.so.TOC -# Additional libaries: -- Ubuntu needs additional dependencies to run chromium -``` -sudo apt install -y libnss3-dev fonts-liberation libfontconfig1 -``` -- RedHat/CentOS/Amazon Linux 2 needs additional dependencies to run chromium -``` -sudo yum install -y libnss3.so xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc fontconfig freetype ipa-gothic-fonts -``` - diff --git a/dashboards-reports/rendering-engine/headless-chrome/build_headless_chrome.sh b/dashboards-reports/rendering-engine/headless-chrome/build_headless_chrome.sh deleted file mode 100644 index 219ba8e8..00000000 --- a/dashboards-reports/rendering-engine/headless-chrome/build_headless_chrome.sh +++ /dev/null @@ -1,176 +0,0 @@ -#!/bin/bash - -# Initializes a Linux environment. This need only be done once per -# machine. The OS needs to be a flavor that supports apt get, such as Ubuntu. - -function generateArgs { -if [ $1 == 'linux' ]; then - echo 'import("//build/args/headless.gn") -is_component_build = false -remove_webcore_debug_symbols = true -enable_nacl = false -is_debug = false -symbol_level = 0 -use_kerberos = false' > args.gn -elif [ $1 == 'darwin' ]; then - echo '#args configuration - -icu_use_data_file = false -v8_use_external_startup_data = false -remove_webcore_debug_symbols = true -use_kerberos = false -use_libpci = false -use_pulseaudio = false -use_udev = false -is_debug = false -symbol_level = 0 -is_component_build = false -enable_nacl = false -enable_print_preview = false -enable_basic_printing = false -enable_remoting = false -use_alsa = false -use_cups = false -use_dbus = false -use_gio = false -' > args.gn -fi -} - -ARGC=("$#") - -if [ $ARGC -lt 1 ]; -then - echo "format: build_headless_chrome.sh {chrome_source_version} (arch_name)" - echo "Mac x64: ./build_headless_chrome.sh 312d84c8ce62810976feda0d3457108a6dfff9e6" - echo "Linux x64: ./build_headless_chrome.sh 312d84c8ce62810976feda0d3457108a6dfff9e6" - echo "Linux arm64: ./build_headless_chrome.sh 312d84c8ce62810976feda0d3457108a6dfff9e6 arm64" - exit -fi - -source_version=$1 - -if [ $ARGC -lt 2 ]; -then - arch_name="x64" -else - arch_name=$2 -fi - -if ! [ -x "$(command -v python)" ]; then - echo "Python is not found, please install python or setup python environment properly" - exit -fi - -# Launch the cross-platform init script using a relative path -# from this script's location. -mkdir -p ~/chromium - -if [ "$#" -eq 2 ]; then - arch_name=$2 -fi - -current_folder=$(pwd) - -# find the current platform -platform_name='unknown' -if [[ "$OSTYPE" == "linux-gnu"* ]]; then - platform_name='linux' -elif [[ "$OSTYPE" == "darwin"* ]]; then - platform_name='darwin' -elif [[ "$OSTYPE" == "win32" ]]; then - platform_name='windows' -fi - -if [[ "$platform_name" == "unknown" ]]; then - echo "platform is" $platform_name - exit -fi - -echo "source_version = " $source_version -echo "platform_name = " $platform_name -echo "arch_name = " $arch_name -generateArgs $platform_name - -# Configure git -git config --global core.autocrlf false -git config --global core.filemode false -git config --global branch.autosetuprebase always -cd chromium - -# Grab Chromium's custom build tools, if they aren't already installed -# (On Windows, they are installed before this Python script is run) -if ! [ -d "depot_tools" ] -then - git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git -fi - -# Put depot_tools on the path so we can properly run the fetch command -export PATH="$PATH:${HOME}/chromium/depot_tools" -echo ${HOME}/chromium/depot_tools - -# Fetch the Chromium source code - -if [ -d 'chromium' ]; then - echo "chromium src aready exists, please delete it and retry..." - exit -fi - -mkdir -p chromium -cd chromium -pwd - -# Build Linux deps -echo "fetching chromium..." -fetch chromium - - -# Build Linux deps - -cd src -if [[ arch_name -eq "arm64" ]]; then - ./build/linux/sysroot_scripts/install-sysroot.py --arch=$arch_name -fi - -if [[ platform_name -eq "linux" ]]; then - ./build/install-build-deps.sh -fi - - -# Set to "arm" to build for ARM on Linux -echo 'Building Chromium ' $source_version ' for ' $arch_name - -# Sync the codebase to the correct version, syncing master first -# to ensure that we actually have all the versions we may refer to -echo 'Syncing source code' - - -git checkout -f master -git fetch -f origin -gclient sync --with_branch_heads --with_tags --jobs 16 -git checkout $source_version -gclient sync --with_branch_heads --with_tags --jobs 16 -gclient runhooks -echo "current_folder :" $current_folder - -platform_build_args=$current_folder'/args.gn' -#platform_build_args=$current_folder/chromium/build_chromium/$platform_name/args.gn - -outputDir='headless' -mkdir -p 'out/headless' - -echo "platform_build_args :" $platform_build_args - -cp $platform_build_args 'out/headless/args.gn' -echo "platform_build_args :" $platform_build_args -echo 'target_cpu = '\"$arch_name\" >> 'out/headless/args.gn' - -gn gen out/headless - -autoninja -C out/headless headless_shell - -if [[ ($platform_name != "Windows" && $arch_name != 'arm64') ]]; then - echo 'Optimizing headless_shell' - mv out/headless/headless_shell out/headless/headless_shell_raw - strip -o out/headless/headless_shell out/headless/headless_shell_raw -fi diff --git a/dashboards-reports/scripts/patch-html2canvas.js b/dashboards-reports/scripts/patch-html2canvas.js new file mode 100644 index 00000000..c01f5bf4 --- /dev/null +++ b/dashboards-reports/scripts/patch-html2canvas.js @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// @ts-check +// workaround for Safari support before https://github.com/niklasvh/html2canvas/pull/2911 is merged +const replace = require('replace-in-file'); + +const options = { + files: [ + __dirname + '/../node_modules/html2canvas/**/*.js', + __dirname + '/../node_modules/html2canvas/**/*.js.map', + ], + from: 'if (image.width === width && image.height === height) {', + to: 'if (false && image.width === width && image.height === height) {', +}; + +try { + const changedFiles = replace.sync(options); + console.log( + 'Modified files for html2canvas Safari support:\n', + changedFiles + .filter((file) => file.hasChanged) + .map((file) => file.file) + .join('\n') + ); +} catch (error) { + console.error( + 'Error occurred when modifiying files for html2canvas Safari support:', + error + ); +} diff --git a/dashboards-reports/server/plugin.ts b/dashboards-reports/server/plugin.ts index dd461296..d7e43ede 100644 --- a/dashboards-reports/server/plugin.ts +++ b/dashboards-reports/server/plugin.ts @@ -11,7 +11,6 @@ import { Logger, ILegacyClusterClient, } from '../../../src/core/server'; -import { Semaphore, SemaphoreInterface, withTimeout } from 'async-mutex'; import opensearchReportsPlugin from './backend/opensearch-reports-plugin'; import { ReportsDashboardsPluginSetup, @@ -37,7 +36,6 @@ export class ReportsDashboardsPlugin implements Plugin { private readonly logger: Logger; - private readonly semaphore: SemaphoreInterface; private readonly initializerContext: PluginInitializerContext< ReportingConfigType >; @@ -46,9 +44,6 @@ export class ReportsDashboardsPlugin constructor(context: PluginInitializerContext) { this.logger = context.logger.get(); this.initializerContext = context; - const timeoutError = new Error('Server busy'); - timeoutError.statusCode = 503; - this.semaphore = withTimeout(new Semaphore(1), 300000, timeoutError); } public async setup(core: CoreSetup) { @@ -99,7 +94,6 @@ export class ReportsDashboardsPlugin (context, request) => { return { logger: this.logger, - semaphore: this.semaphore, opensearchReportsClient, notificationsClient, }; diff --git a/dashboards-reports/server/routes/lib/createReport.ts b/dashboards-reports/server/routes/lib/createReport.ts index 57f2c5cd..9f894586 100644 --- a/dashboards-reports/server/routes/lib/createReport.ts +++ b/dashboards-reports/server/routes/lib/createReport.ts @@ -16,13 +16,12 @@ import { RequestHandlerContext, } from '../../../../../src/core/server'; import { createSavedSearchReport } from '../utils/savedSearchReportHelper'; -import { ReportSchemaType } from '../../model'; +import { ReportSchemaType, VisualReportSchemaType } from '../../model'; import { CreateReportResultType } from '../utils/types'; -import { createVisualReport } from '../utils/visual_report/visualReportHelper'; import { saveReport } from './saveReport'; -import { SemaphoreInterface } from 'async-mutex'; import { ReportingConfig } from 'server'; import _ from 'lodash'; +import { getFileName } from '../utils/helpers'; export const createReport = async ( request: OpenSearchDashboardsRequest, @@ -34,8 +33,6 @@ export const createReport = async ( const isScheduledTask = false; //@ts-ignore const logger: Logger = context.reporting_plugin.logger; - //@ts-ignore - const semaphore: SemaphoreInterface = context.reporting_plugin.semaphore; // @ts-ignore const opensearchReportsClient: ILegacyScopedClusterClient = context.reporting_plugin.opensearchReportsClient.asScoped( request @@ -88,18 +85,24 @@ export const createReport = async ( const completeQueryUrl = `${protocol}://${hostname}:${port}${relativeUrl}`; const extraHeaders = _.pick(request.headers, EXTRA_HEADERS); - const [value, release] = await semaphore.acquire(); - try { - createReportResult = await createVisualReport( - reportParams, - completeQueryUrl, - logger, - extraHeaders, - timezone - ); - } finally { - release(); - } + const { + core_params, + report_name: reportName, + report_source: reportSource, + } = reportParams; + const coreParams = core_params as VisualReportSchemaType; + const { + header, + footer, + window_height: windowHeight, + window_width: windowWidth, + report_format: reportFormat, + } = coreParams; + const curTime = new Date(); + const timeCreated = curTime.valueOf(); + const fileName = `${getFileName(reportName, curTime)}.${reportFormat}`; + + return { timeCreated, dataUrl: '', fileName, reportId, queryUrl: relativeUrl }; } // update report state to "created" // TODO: temporarily remove the following diff --git a/dashboards-reports/server/routes/report.ts b/dashboards-reports/server/routes/report.ts index 4c443a57..8bf7614b 100644 --- a/dashboards-reports/server/routes/report.ts +++ b/dashboards-reports/server/routes/report.ts @@ -74,6 +74,8 @@ export default function (router: IRouter, config: ReportingConfig) { body: { data: reportData.dataUrl, filename: reportData.fileName, + reportId: reportData.reportId, + queryUrl: reportData.queryUrl, }, }); } catch (error) { @@ -141,6 +143,8 @@ export default function (router: IRouter, config: ReportingConfig) { body: { data: reportData.dataUrl, filename: reportData.fileName, + reportId: reportData.reportId, + queryUrl: reportData.queryUrl, }, }); } catch (error) { @@ -212,6 +216,8 @@ export default function (router: IRouter, config: ReportingConfig) { body: { data: reportData.dataUrl, filename: reportData.fileName, + reportId: reportData.reportId, + queryUrl: reportData.queryUrl, }, }); } catch (error) { diff --git a/dashboards-reports/server/routes/utils/__tests__/visualReportHelper.test.ts b/dashboards-reports/server/routes/utils/__tests__/visualReportHelper.test.ts deleted file mode 100644 index 4bf3cd0b..00000000 --- a/dashboards-reports/server/routes/utils/__tests__/visualReportHelper.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import 'regenerator-runtime/runtime'; -import { createVisualReport } from '../visual_report/visualReportHelper'; -import { Logger } from '../../../../../../src/core/server'; -import { ReportParamsSchemaType, reportSchema } from '../../../model'; -import { mockLogger } from '../../../../test/__mocks__/loggerMock'; - -const mockHeader = { mockKey: 'mockValue' }; -const input = { - query_url: '/app/dashboards#/view/7adfa750-4c81-11e8-b3d7-01146121b73d', - time_from: 1343576635300, - time_to: 1596037435301, - report_definition: { - report_params: { - report_name: 'test visual report', - report_source: 'Dashboard', - description: 'Hi this is your Dashboard on demand', - core_params: { - base_url: '/app/dashboards#/view/7adfa750-4c81-11e8-b3d7-01146121b73d', - window_width: 1300, - window_height: 900, - report_format: 'png', - time_duration: 'PT5M', - origin: 'http://localhost:5601', - }, - }, - delivery: { - configIds: [], - title: 'title', - textDescription: 'text description', - htmlDescription: 'html description', - }, - trigger: { - trigger_type: 'On demand', - }, - }, -}; - -const mockHtmlPath = `file://${__dirname}/demo_dashboard.html`; - -describe('test create visual report', () => { - test('create report with valid input', async () => { - // Check if the assumption of input is up-to-date - reportSchema.validate(input); - }, 20000); - - test('create png report', async () => { - expect.assertions(3); - const reportParams = input.report_definition.report_params; - const { dataUrl, fileName } = await createVisualReport( - reportParams as ReportParamsSchemaType, - mockHtmlPath, - mockLogger, - mockHeader, - undefined, - /^(data:image|file:\/\/)/ - ); - expect(fileName).toContain(`${reportParams.report_name}`); - expect(fileName).toContain('.png'); - expect(dataUrl).toBeDefined(); - }, 60000); - - test('create pdf report', async () => { - expect.assertions(3); - const reportParams = input.report_definition.report_params; - reportParams.core_params.report_format = 'pdf'; - - const { dataUrl, fileName } = await createVisualReport( - reportParams as ReportParamsSchemaType, - mockHtmlPath, - mockLogger, - mockHeader, - undefined, - /^(data:image|file:\/\/)/ - ); - expect(fileName).toContain(`${reportParams.report_name}`); - expect(fileName).toContain('.pdf'); - expect(dataUrl).toBeDefined(); - }, 60000); -}); diff --git a/dashboards-reports/server/routes/utils/constants.ts b/dashboards-reports/server/routes/utils/constants.ts index 6af81fd2..b8b970d6 100644 --- a/dashboards-reports/server/routes/utils/constants.ts +++ b/dashboards-reports/server/routes/utils/constants.ts @@ -57,17 +57,9 @@ export enum DELIVERY_TYPE { channel = 'Channel', } -export enum SELECTOR { - dashboard = '#dashboardViewport', - visualization = '.visEditor__content', - notebook = '.euiPageBody', -} - // https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-from-size.html export const DEFAULT_MAX_SIZE = 10000; -export const DEFAULT_REPORT_HEADER = '

OpenSearch Dashboards Reports

'; - export const SECURITY_CONSTANTS = { TENANT_LOCAL_STORAGE_KEY: 'opendistro::security::tenant::show_popup', }; @@ -79,14 +71,6 @@ export const EXTRA_HEADERS = [ 'x-forwarded-for', ]; -export const converter = new Showdown.Converter({ - tables: true, - simplifiedAutoLink: true, - strikethrough: true, - tasklists: true, - noHeaderId: true, -}); - const BLOCKED_KEYWORD = 'BLOCKED_KEYWORD'; const ipv4Regex = /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])/g const ipv6Regex = /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/g; @@ -107,8 +91,6 @@ export const replaceBlockedKeywords = (htmlString: string) => { return htmlString; } -export const CHROMIUM_PATH = `${__dirname}/../../../.chromium/headless_shell`; - /** * Metric constants diff --git a/dashboards-reports/server/routes/utils/types.ts b/dashboards-reports/server/routes/utils/types.ts index 3c589466..3f0f21b9 100644 --- a/dashboards-reports/server/routes/utils/types.ts +++ b/dashboards-reports/server/routes/utils/types.ts @@ -7,6 +7,8 @@ export interface CreateReportResultType { timeCreated: number; dataUrl: string; fileName: string; + reportId: string; + queryUrl: string; } type ReportSourceType = 'dashboard' | 'visualization' | 'saved_search' | 'notebook'; diff --git a/dashboards-reports/server/routes/utils/visual_report/footer_template.html b/dashboards-reports/server/routes/utils/visual_report/footer_template.html deleted file mode 100644 index 6fc56f8c..00000000 --- a/dashboards-reports/server/routes/utils/visual_report/footer_template.html +++ /dev/null @@ -1,5 +0,0 @@ -
-
-
-
-
diff --git a/dashboards-reports/server/routes/utils/visual_report/header_template.html b/dashboards-reports/server/routes/utils/visual_report/header_template.html deleted file mode 100644 index 9796c499..00000000 --- a/dashboards-reports/server/routes/utils/visual_report/header_template.html +++ /dev/null @@ -1,5 +0,0 @@ -
-
-
-
-
diff --git a/dashboards-reports/server/routes/utils/visual_report/visualReportHelper.ts b/dashboards-reports/server/routes/utils/visual_report/visualReportHelper.ts deleted file mode 100644 index ad836b8f..00000000 --- a/dashboards-reports/server/routes/utils/visual_report/visualReportHelper.ts +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import puppeteer, { Headers } from 'puppeteer-core'; -import createDOMPurify from 'dompurify'; -import { JSDOM } from 'jsdom'; -import { Logger } from '../../../../../../src/core/server'; -import { - DEFAULT_REPORT_HEADER, - REPORT_TYPE, - FORMAT, - SELECTOR, - CHROMIUM_PATH, - SECURITY_CONSTANTS, - ALLOWED_HOSTS, -} from '../constants'; -import { getFileName } from '../helpers'; -import { CreateReportResultType } from '../types'; -import { ReportParamsSchemaType, VisualReportSchemaType } from 'server/model'; -import { converter, replaceBlockedKeywords } from '../constants'; -import fs from 'fs'; -import _ from 'lodash'; - -export const createVisualReport = async ( - reportParams: ReportParamsSchemaType, - queryUrl: string, - logger: Logger, - extraHeaders: Headers, - timezone?: string, - validRequestProtocol = /^(data:image)/, -): Promise => { - const { - core_params, - report_name: reportName, - report_source: reportSource, - } = reportParams; - const coreParams = core_params as VisualReportSchemaType; - const { - header, - footer, - window_height: windowHeight, - window_width: windowWidth, - report_format: reportFormat, - } = coreParams; - - const window = new JSDOM('').window; - const DOMPurify = createDOMPurify(window); - - let keywordFilteredHeader = header - ? converter.makeHtml(header) - : DEFAULT_REPORT_HEADER; - let keywordFilteredFooter = footer ? converter.makeHtml(footer) : ''; - - keywordFilteredHeader = DOMPurify.sanitize(keywordFilteredHeader); - keywordFilteredFooter = DOMPurify.sanitize(keywordFilteredFooter); - - // filter blocked keywords in header and footer - if (keywordFilteredHeader !== '') { - keywordFilteredHeader = replaceBlockedKeywords(keywordFilteredHeader); - } - if (keywordFilteredFooter !== '') { - keywordFilteredFooter = replaceBlockedKeywords(keywordFilteredFooter); - } - - // set up puppeteer - const browser = await puppeteer.launch({ - headless: true, - /** - * TODO: temp fix to disable sandbox when launching chromium on Linux instance - * https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#setting-up-chrome-linux-sandbox - */ - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-gpu', - '--no-zygote', - '--font-render-hinting=none', - '--js-flags="--jitless --no-opt"', - '--disable-features=V8OptimizeJavascript', - ], - executablePath: CHROMIUM_PATH, - ignoreHTTPSErrors: true, - env: { - TZ: timezone || 'UTC', - }, - pipe: true, - }); - const page = await browser.newPage(); - - await page.setRequestInterception(true); - let localStorageAvailable = true; - page.on('request', (req) => { - // disallow non-allowlisted connections. urls with valid protocols do not need ALLOWED_HOSTS check - if ( - !validRequestProtocol.test(req.url()) && - !ALLOWED_HOSTS.test(new URL(req.url()).hostname) - ) { - if (req.isNavigationRequest() && req.redirectChain().length > 0) { - localStorageAvailable = false; - logger.error( - 'Reporting does not allow redirections to outside of localhost, aborting. URL received: ' + - req.url() - ); - } else { - logger.warn( - 'Disabled connection to non-allowlist domains: ' + req.url() - ); - } - req.abort(); - } else { - req.continue(); - } - }); - - page.setDefaultNavigationTimeout(0); - page.setDefaultTimeout(300000); // use 300s timeout instead of default 30s - // Set extra headers that are needed - if (!_.isEmpty(extraHeaders)) { - await page.setExtraHTTPHeaders(extraHeaders); - } - logger.info(`original queryUrl ${queryUrl}`); - await page.goto(queryUrl, { waitUntil: 'networkidle0' }); - // should add to local storage after page.goto, then access the page again - browser must have an url to register local storage item on it - try { - await page.evaluate( - /* istanbul ignore next */ - (key) => { - try { - if ( - localStorageAvailable && - typeof localStorage !== 'undefined' && - localStorage !== null - ) { - localStorage.setItem(key, 'false'); - } - } catch (err) {} - }, - SECURITY_CONSTANTS.TENANT_LOCAL_STORAGE_KEY - ); - } catch (err) { - logger.error(err); - } - await page.goto(queryUrl, { waitUntil: 'networkidle0' }); - logger.info(`page url ${page.url()}`); - - await page.setViewport({ - width: windowWidth, - height: windowHeight, - }); - - let buffer: Buffer; - // remove unwanted elements - await page.evaluate( - /* istanbul ignore next */ - (reportSource, REPORT_TYPE) => { - // remove buttons - document - .querySelectorAll("[class^='euiButton']") - .forEach((e) => e.remove()); - // remove top navBar - document - .querySelectorAll("[class^='euiHeader']") - .forEach((e) => e.remove()); - // remove visualization editor - if (reportSource === REPORT_TYPE.visualization) { - document - .querySelector('[data-test-subj="splitPanelResizer"]') - ?.remove(); - document.querySelector('.visEditor__collapsibleSidebar')?.remove(); - } - document.body.style.paddingTop = '0px'; - }, - reportSource, - REPORT_TYPE - ); - - // force wait for any resize to load after the above DOM modification - await new Promise(resolve => setTimeout(resolve, 1000)); - // crop content - switch (reportSource) { - case REPORT_TYPE.dashboard: - await page.waitForSelector(SELECTOR.dashboard, { - visible: true, - }); - break; - case REPORT_TYPE.visualization: - await page.waitForSelector(SELECTOR.visualization, { - visible: true, - }); - break; - case REPORT_TYPE.notebook: - await page.waitForSelector(SELECTOR.notebook, { - visible: true, - }); - break; - default: - throw Error( - `report source can only be one of [Dashboard, Visualization]` - ); - } - - // wait for dynamic page content to render - await waitForDynamicContent(page); - - await addReportHeader(page, keywordFilteredHeader); - await addReportFooter(page, keywordFilteredFooter); - await addReportStyle(page); - - // this causes UT to fail in github CI but works locally - try { - const numDisallowedTags = await page.evaluate( - () => - document.getElementsByTagName('iframe').length + - document.getElementsByTagName('embed').length + - document.getElementsByTagName('object').length - ); - if (numDisallowedTags > 0) { - throw Error('Reporting does not support "iframe", "embed", or "object" tags, aborting'); - } - } catch (error) { - logger.error(error); - } - - // create pdf or png accordingly - if (reportFormat === FORMAT.pdf) { - const scrollHeight = await page.evaluate( - /* istanbul ignore next */ - () => document.documentElement.scrollHeight - ); - - buffer = await page.pdf({ - margin: undefined, - width: windowWidth, - height: scrollHeight + 'px', - printBackground: true, - pageRanges: '1', - }); - } else if (reportFormat === FORMAT.png) { - buffer = await page.screenshot({ - fullPage: true, - }); - } - - const curTime = new Date(); - const timeCreated = curTime.valueOf(); - const fileName = `${getFileName(reportName, curTime)}.${reportFormat}`; - await browser.close(); - - return { timeCreated, dataUrl: buffer.toString('base64'), fileName }; -}; - -const addReportStyle = async (page: puppeteer.Page) => { - const css = fs.readFileSync(`${__dirname}/style.css`).toString(); - - await page.evaluate( - /* istanbul ignore next */ - (style: string) => { - const styleElement = document.createElement('style'); - styleElement.innerHTML = style; - document.getElementsByTagName('head')[0].appendChild(styleElement); - }, - css - ); -}; - -const addReportHeader = async (page: puppeteer.Page, header: string) => { - const headerHtml = fs - .readFileSync(`${__dirname}/header_template.html`) - .toString() - .replace('', header); - - await page.evaluate( - /* istanbul ignore next */ - (headerHtml: string) => { - const content = document.body.firstChild; - const headerContainer = document.createElement('div'); - headerContainer.className = 'reportWrapper'; - headerContainer.innerHTML = headerHtml; - content?.parentNode?.insertBefore(headerContainer, content); - }, - headerHtml - ); -}; - -const addReportFooter = async (page: puppeteer.Page, footer: string) => { - const headerHtml = fs - .readFileSync(`${__dirname}/footer_template.html`) - .toString() - .replace('', footer); - - await page.evaluate( - /* istanbul ignore next */ - (headerHtml: string) => { - const content = document.body.firstChild; - const headerContainer = document.createElement('div'); - headerContainer.className = 'reportWrapper'; - headerContainer.innerHTML = headerHtml; - content?.parentNode?.insertBefore(headerContainer, null); - }, - headerHtml - ); -}; - -// add waitForDynamicContent function -const waitForDynamicContent = async ( - page, - timeout = 30000, - interval = 1000, - checks = 5 -) => { - const maxChecks = timeout / interval; - let passedChecks = 0; - let previousLength = 0; - - let i = 0; - while (i++ <= maxChecks) { - let pageContent = await page.content(); - let currentLength = pageContent.length; - - previousLength === 0 || previousLength != currentLength - ? (passedChecks = 0) - : passedChecks++; - if (passedChecks >= checks) { - break; - } - - previousLength = currentLength; - await new Promise(resolve => setTimeout(resolve, interval)); - } -}; diff --git a/dashboards-reports/translations/pl.json b/dashboards-reports/translations/pl.json index 6f377e5a..f2d770f0 100644 --- a/dashboards-reports/translations/pl.json +++ b/dashboards-reports/translations/pl.json @@ -124,7 +124,7 @@ "opensearch.reports.loading.close": "Zamknij", "opensearch.reports.loading.generatingReport": "Generowanie raportu.", "opensearch.reports.loading.preparingYourFile": "Przygotowanie pliku do pobrania.", - "opensearch.reports.loading.youCanClose": "Możesz zamknąć to okno dialogowe, raport jest generowany w tle.", + "opensearch.reports.loading.youCanClose": "Nie zamykaj tego okna dialogowego podczas generowania raportu.", "opensearch.reports.main.errorDownloadingReport": "Błąd przy pobieraniu raportu", "opensearch.reports.main.errorGeneratingReportDefinitionsTable.": "Błąd generowania listy definicji raportów.", "opensearch.reports.main.errorGeneratingReportsTable.": "Błąd generowania listy raportów.", @@ -154,7 +154,7 @@ "opensearch.reports.menu.newNotificationAppears": "Pojawiło się nowe powiadomienie", "opensearch.reports.menu.progress.generatingReport": "Generowanie raportu", "opensearch.reports.menu.progress.preparingYourFile": "Przygotowanie pliku do pobrania", - "opensearch.reports.menu.progress.youCanClose": "Możesz zamknąć to okno dialogowe, raport jest generowany w tle.", + "opensearch.reports.menu.progress.youCanClose": "Nie zamykaj tego okna dialogowego podczas generowania raportu.", "opensearch.reports.menu.scheduleAndShare": "Wygeneruj i udostępnij", "opensearch.reports.menu.successfullyGenerated": "Pomyślnie wygenerowano raport.", "opensearch.reports.menu.visual.createReportDefinition": "Utwórz definicję raportu.", diff --git a/dashboards-reports/yarn.lock b/dashboards-reports/yarn.lock index 0de638cd..1302300c 100644 --- a/dashboards-reports/yarn.lock +++ b/dashboards-reports/yarn.lock @@ -418,6 +418,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3" + integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.10.4", "@babel/template@^7.3.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -879,19 +886,10 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== -"@types/puppeteer-core@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@types/puppeteer-core/-/puppeteer-core-5.4.0.tgz#880a7917b4ede95cbfe2d5e81a558cfcb072c0fb" - integrity sha512-yqRPuv4EFcSkTyin6Yy17pN6Qz2vwVwTCJIDYMXbE3j8vTPhv0nCQlZOl5xfi0WHUkqvQsjAR8hAfjeMCoetwg== - dependencies: - "@types/puppeteer" "*" - -"@types/puppeteer@*": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.0.tgz#1ef860bd7a9dcf0c4633aac8c0ec21f75b431868" - integrity sha512-zTYDLjnHjgzokrwKt7N0rgn7oZPYo1J0m8Ghu+gXqzLCEn8RWbELa2uprE2UFJ0jU/Sk0x9jXXdOH/5QQLFHhQ== - dependencies: - "@types/node" "*" +"@types/raf@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2" + integrity sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw== "@types/react-addons-test-utils@^0.14.25": version "0.14.25" @@ -985,13 +983,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yauzl@^2.9.1": - version "2.10.0" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" - integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== - dependencies: - "@types/node" "*" - "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -1170,13 +1161,6 @@ acorn@^6.0.1, acorn@^6.0.4, acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - airbnb-prop-types@^2.16.0: version "2.16.0" resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2" @@ -1366,13 +1350,6 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-mutex@^0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.2.6.tgz#0d7a3deb978bc2b984d5908a2038e1ae2e54ff40" - integrity sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw== - dependencies: - tslib "^2.0.0" - async@^3.2.0: version "3.2.3" resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" @@ -1521,7 +1498,12 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base64-js@^1.0.2, base64-js@^1.3.1: +base64-arraybuffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + +base64-js@^1.0.2: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -1568,15 +1550,6 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - blob-util@2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" @@ -1724,6 +1697,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +btoa@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" + integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g== + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1748,14 +1726,6 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.2.1, buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -1833,6 +1803,20 @@ caniuse-lite@^1.0.30001317: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001322.tgz#2e4c09d11e1e8f852767dab287069a8d0c29d623" integrity sha512-neRmrmIrCGuMnxGSoh+x7zYtQFFgnSY2jaomjU56sCkTA6JINqQrxutF459JpWcWRajvoyn95sOXq4Pqrnyjew== +canvg@^3.0.6: + version "3.0.10" + resolved "https://registry.yarnpkg.com/canvg/-/canvg-3.0.10.tgz#8e52a2d088b6ffa23ac78970b2a9eebfae0ef4b3" + integrity sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q== + 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" + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1874,6 +1858,14 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + character-entities-legacy@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" @@ -2016,6 +2008,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -2149,6 +2150,11 @@ core-js@^2.4.0, core-js@^2.5.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== +core-js@^3.6.0, core-js@^3.8.3: + version "3.26.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.26.1.tgz#7a9816dabd9ee846c1c0fe0e8fcad68f3709134e" + integrity sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2201,7 +2207,7 @@ cron-validator@^1.1.1: resolved "https://registry.yarnpkg.com/cron-validator/-/cron-validator-1.1.1.tgz#0a27bb75508c7bc03c8b840d2d9f170eeacb5615" integrity sha512-vfZb05w/wezuwPZBDvdIBmJp2BvuJExHeyKRa5oBqD2ZDXR61hb3QgPc/3ZhBEQJlAy8Jlnn5XC/JCT3IDqxwg== -cross-fetch@3.1.5, cross-fetch@^3.0.4: +cross-fetch@^3.0.4: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== @@ -2234,6 +2240,13 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +css-line-break@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0" + integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w== + dependencies: + utrie "^1.0.2" + css-what@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad" @@ -2331,13 +2344,6 @@ date-fns@^1.27.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== -debug@4, debug@4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2428,11 +2434,6 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -devtools-protocol@0.0.981744: - version "0.0.981744" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.981744.tgz#9960da0370284577d46c28979a0b32651022bacf" - integrity sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg== - diff-sequences@^25.2.6: version "25.2.6" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" @@ -2493,10 +2494,10 @@ domhandler@^3.0, domhandler@^3.0.0: dependencies: domelementtype "^2.0.1" -dompurify@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.8.tgz#224fe9ae57d7ebd9a1ae1ac18c1c1ca3f532226f" - integrity sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw== +dompurify@^2.2.0, dompurify@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631" + integrity sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA== domutils@^2.0.0: version "2.2.0" @@ -2578,7 +2579,7 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -2940,17 +2941,6 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-zip@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - extract-zip@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" @@ -3000,6 +2990,11 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fflate@^0.4.8: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -3063,7 +3058,7 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -3113,11 +3108,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -3195,7 +3185,7 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -3223,7 +3213,7 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^5.0.0, get-stream@^5.1.0: +get-stream@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== @@ -3272,7 +3262,7 @@ glob-parent@^3.1.0, glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@^7.1.2: +glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3284,18 +3274,6 @@ glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3, glob@^7.1.4: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - global-dirs@^2.0.1: version "2.1.0" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" @@ -3490,6 +3468,14 @@ html-to-react@^1.3.4: lodash.camelcase "^4.3.0" ramda "^0.27" +html2canvas@1.4.1, html2canvas@^1.0.0-rc.5: + version "1.4.1" + resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543" + integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA== + dependencies: + css-line-break "^2.1.0" + text-segmentation "^1.0.3" + htmlparser2@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" @@ -3514,14 +3500,6 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== -https-proxy-agent@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -3546,7 +3524,7 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13, ieee754@^1.1.4: +ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -4227,6 +4205,21 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jspdf@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/jspdf/-/jspdf-2.5.1.tgz#00c85250abf5447a05f3b32ab9935ab4a56592cc" + 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" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -4667,11 +4660,6 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp-classic@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - mkdirp@1.x: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -5159,13 +5147,6 @@ pirates@^4.0.4: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== -pkg-dir@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" @@ -5213,11 +5194,6 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -progress@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -5246,11 +5222,6 @@ prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -proxy-from-env@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -5318,24 +5289,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -puppeteer-core@^13.7.0: - version "13.7.0" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-13.7.0.tgz#3344bee3994163f49120a55ddcd144a40575ba5b" - integrity sha512-rXja4vcnAzFAP1OVLq/5dWNfwBGuzcOARJ6qGV7oAZhnLmVRU8G5MsdeQEAOy332ZhkIOnn9jp15R89LKHyp2Q== - dependencies: - cross-fetch "3.1.5" - debug "4.3.4" - devtools-protocol "0.0.981744" - extract-zip "2.0.1" - https-proxy-agent "5.0.1" - pkg-dir "4.2.0" - progress "2.0.3" - proxy-from-env "1.1.0" - rimraf "3.0.2" - tar-fs "2.1.1" - unbzip2-stream "1.4.3" - ws "8.5.0" - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -5360,6 +5313,13 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + ramda@^0.27: version "0.27.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" @@ -5516,7 +5476,7 @@ react-transition-group@^4.3.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -5556,6 +5516,11 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== +regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.7: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" @@ -5610,6 +5575,15 @@ replace-ext@1.0.0: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= +replace-in-file@^6.3.5: + version "6.3.5" + resolved "https://registry.yarnpkg.com/replace-in-file/-/replace-in-file-6.3.5.tgz#ff956b0ab5bc96613207d603d197cd209400a654" + integrity sha512-arB9d3ENdKva2fxRnSjwBEXfK1npgyci7ZZuwysgAp7ORjHSyxz6oqIjTEv8R0Ydl4Ll7uOAZXL4vbkhGIizCg== + dependencies: + chalk "^4.1.2" + glob "^7.2.0" + yargs "^17.2.1" + request-progress@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" @@ -5734,12 +5708,10 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rimraf@3.0.2, rimraf@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" +rgbcolor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d" + integrity sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw== rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" @@ -5748,6 +5720,13 @@ rimraf@^2.5.4, rimraf@^2.6.3: dependencies: glob "^7.1.3" +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -6024,6 +6003,11 @@ ssri@^6.0.1: dependencies: figgy-pudding "^3.5.1" +stackblur-canvas@^2.0.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz#aa87bbed1560fdcd3138fff344fc6a1c413ebac4" + integrity sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ== + state-toggle@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" @@ -6105,7 +6089,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0: +string-width@^4.1.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6240,6 +6224,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +svg-pathdata@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz#80b0e0283b652ccbafb69ad4f8f73e8d3fbf2cac" + integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw== + symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -6260,27 +6249,6 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar-fs@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - terser-webpack-plugin@^1.4.3: version "1.4.5" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" @@ -6314,6 +6282,13 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-segmentation@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943" + integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw== + dependencies: + utrie "^1.0.2" + throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" @@ -6327,11 +6302,6 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - timers-browserify@^2.0.4: version "2.0.12" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" @@ -6460,11 +6430,6 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" - integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== - tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -6511,14 +6476,6 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -unbzip2-stream@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" - integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - unherit@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" @@ -6666,6 +6623,13 @@ util@^0.11.0: dependencies: inherits "2.0.3" +utrie@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645" + integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw== + dependencies: + base64-arraybuffer "^1.0.2" + uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -6890,6 +6854,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -6905,7 +6878,7 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@8.5.0, ws@^6.1.2, ws@^7.4.6: +ws@^6.1.2, ws@^7.4.6: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== @@ -6966,6 +6939,11 @@ yargs-parser@^15.0.1: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs@^14.2: version "14.2.3" resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414" @@ -7000,6 +6978,19 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^17.2.1: + version "17.6.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" + integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"