diff --git a/.github/workflows/dashboards-reports-test-and-build-workflow.yml b/.github/workflows/dashboards-reports-test-and-build-workflow.yml
index 87f6244a..4d1eab02 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 ec1067ce..c8a947c1 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 = ``;
+ 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 = ``;
+ 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"