From 794c2f828ed3a20b6d6e4394b51f8b4327fc9df4 Mon Sep 17 00:00:00 2001 From: Danila Gulderov Date: Thu, 11 Jan 2024 11:27:54 +0300 Subject: [PATCH] Add xlsx support (#275) Signed-off-by: Danila Gulderov --- package.json | 3 +- .../components/context_menu/context_menu.js | 8 ++ .../context_menu/context_menu_ui.js | 20 ++++- public/components/main/main_utils.tsx | 32 ++++++-- .../report_settings/report_settings.tsx | 43 +++++++--- .../report_settings_constants.tsx | 6 +- server/model/backendModel.ts | 2 + server/model/index.ts | 3 +- server/routes/utils/constants.ts | 11 +++ server/routes/utils/converters/backendToUi.ts | 2 +- server/routes/utils/dataReportHelpers.ts | 36 ++++++++- .../routes/utils/savedSearchReportHelper.ts | 18 ++++- server/routes/utils/types.ts | 2 +- yarn.lock | 81 +++++++++---------- 14 files changed, 190 insertions(+), 77 deletions(-) diff --git a/package.json b/package.json index 679ee0ec..afb2ecb3 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "react-toast-notifications": "^2.4.0", "set-interval-async": "1.0.33", "showdown": "^1.9.1", - "tesseract.js": "^4.0.2" + "tesseract.js": "^4.0.2", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz" }, "devDependencies": { "@elastic/eslint-import-resolver-kibana": "link:../../packages/osd-eslint-import-resolver-opensearch-dashboards", diff --git a/public/components/context_menu/context_menu.js b/public/components/context_menu/context_menu.js index 99b9a35f..fa2bb930 100644 --- a/public/components/context_menu/context_menu.js +++ b/public/components/context_menu/context_menu.js @@ -179,6 +179,14 @@ $(function () { generateInContextReport(timeRanges, queryUrl, 'csv', { saved_search_id }); }); + // generate XLSX onclick + $(document).on('click', '#generateXLSX', function () { + const timeRanges = getTimeFieldsFromUrl(); + const queryUrl = replaceQueryURL(location.href); + const saved_search_id = getUuidFromUrl()[1]; + generateInContextReport(timeRanges, queryUrl, 'xlsx', { saved_search_id }); + }); + // navigate to Create report definition page with report source and pre-set time range $(document).on('click', '#createReportDefinition', function () { contextMenuCreateReportDefinition(this.baseURI); diff --git a/public/components/context_menu/context_menu_ui.js b/public/components/context_menu/context_menu_ui.js index 2dfd798f..49eaf36b 100644 --- a/public/components/context_menu/context_menu_ui.js +++ b/public/components/context_menu/context_menu_ui.js @@ -18,7 +18,7 @@ export const popoverMenu = (savedObjectAvailable) => { ? 'euiContextMenuItem' : 'euiContextMenuItem euiContextMenuItem-isDisabled'; const button = savedObjectAvailable ? 'button' : 'button disabled'; - const popoverHeight = savedObjectAvailable ? '395px' : '380px'; + const popoverHeight = getPopoverHeight(false, savedObjectAvailable); const message = savedObjectAvailable ? i18n.translate('opensearch.reports.menu.visual.waitPrompt', { defaultMessage: @@ -138,14 +138,14 @@ export const popoverMenuDiscover = (savedObjectAvailable) => { ? 'euiContextMenuItem' : 'euiContextMenuItem euiContextMenuItem-isDisabled'; const button = savedObjectAvailable ? 'button' : 'button disabled'; - const popoverHeight = savedObjectAvailable ? '354px' : '322px'; + const popoverHeight = getPopoverHeight(true, savedObjectAvailable); const message = savedObjectAvailable ? i18n.translate('opensearch.reports.menu.csv.waitPrompt', { defaultMessage: 'Files can take a minute or two to generate depending on the size of your source data.', }) : i18n.translate('opensearch.reports.menu.csv.savePrompt', { - defaultMessage: 'Save this search to enable CSV reports.', + defaultMessage: 'Save this search to enable CSV/XLSX reports.', }); const arrowRight = '60px'; const popoverRight = '77px'; @@ -189,6 +189,12 @@ export const popoverMenuDiscover = (savedObjectAvailable) => { + <${button} class="${buttonClass}" type="button" data-test-subj="downloadPanel-GeneratePDF" id="generateXLSX"> + + + ${i18n.translate('opensearch.reports.menu.csv.generateXLSX', { defaultMessage: 'Generate XLSX' })} + +
@@ -394,3 +400,11 @@ export const reportGenerationInProgressModal = () => {
`; }; + +export function getPopoverHeight(isDiscover, isSavedObjectAvailable) { + if (isDiscover && !isSavedObjectAvailable) { + return '372px'; + } + + return '388px'; +} diff --git a/public/components/main/main_utils.tsx b/public/components/main/main_utils.tsx index f4b32bbf..0bb75b18 100644 --- a/public/components/main/main_utils.tsx +++ b/public/components/main/main_utils.tsx @@ -28,6 +28,7 @@ type fileFormatsOptions = { export const fileFormatsUpper: fileFormatsOptions = { csv: 'CSV', + xlsx: 'XLSX', pdf: 'PDF', png: 'PNG', }; @@ -40,12 +41,17 @@ export const humanReadableDate = (date: string | number | Date) => { }; export const extractFilename = (filename: string) => { - return filename.substring(0, filename.length - 4); + const index = filename.lastIndexOf('.'); + if (index == -1) { + return filename; + } + + return filename.slice(0, index); }; export const extractFileFormat = (filename: string) => { - const fileFormat = filename; - return fileFormat.substring(filename.length - 3, filename.length); + const index = filename.lastIndexOf('.'); + return filename.slice(index+1); }; export const getFileFormatPrefix = (fileFormat: string) => { @@ -112,13 +118,23 @@ export const removeDuplicatePdfFileFormat = (filename: string) => { return filename.substring(0, filename.length - 4); }; +async function getDataReportURL(stream: string, fileFormat: string): Promise { + if (fileFormat == 'xlsx') { + const response = await fetch(stream); + const blob = await response.blob(); + return URL.createObjectURL(blob); + } + + const blob = new Blob([stream]); + return URL.createObjectURL(blob); +} + export const readDataReportToFile = async ( stream: string, fileFormat: string, fileName: string ) => { - const blob = new Blob([stream]); - const url = URL.createObjectURL(blob); + const url = await getDataReportURL(stream, fileFormat); let link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', fileName); @@ -133,7 +149,7 @@ export const readStreamToFile = async ( fileName: string ) => { let link = document.createElement('a'); - if (fileName.includes('csv')) { + if (fileName.includes('csv') || fileName.includes('xlsx')) { readDataReportToFile(stream, fileFormat, fileName); return; } @@ -168,7 +184,7 @@ export const generateReportFromDefinitionId = async ( if (!response) return; const fileFormat = extractFileFormat(response['filename']); const fileName = response['filename']; - if (fileFormat === 'csv') { + if (fileFormat === 'csv' || fileFormat === 'xlsx') { await readStreamToFile(await response['data'], fileFormat, fileName); status = true; return; @@ -212,7 +228,7 @@ 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']; - if (fileFormat === 'csv') { + if (fileFormat === 'csv' || fileFormat === 'xlsx') { await readStreamToFile(await response['data'], fileFormat, fileName); handleSuccessToast(); return response; diff --git a/public/components/report_definitions/report_settings/report_settings.tsx b/public/components/report_definitions/report_settings/report_settings.tsx index 0db86cfd..98b97d9f 100644 --- a/public/components/report_definitions/report_settings/report_settings.tsx +++ b/public/components/report_definitions/report_settings/report_settings.tsx @@ -28,6 +28,7 @@ import { PDF_PNG_FILE_FORMAT_OPTIONS, HEADER_FOOTER_CHECKBOX, REPORT_SOURCE_TYPES, + SAVED_SEARCH_FORMAT_OPTIONS, } from './report_settings_constants'; import Showdown from 'showdown'; import ReactMde from 'react-mde'; @@ -275,6 +276,27 @@ export function ReportSettings(props: ReportSettingProps) { ); }; + const CSVandXLSXFileFormats = () => { + return ( +
+ + + +
+ ); + }; + const SettingsMarkdown = () => { const [ checkboxIdSelectHeaderFooter, @@ -437,6 +459,14 @@ export function ReportSettings(props: ReportSettingProps) { ); }; + const DataReportFormatAndMarkdown = () => { + return ( +
+ +
+ ); + }; + const setReportSourceDropdownOption = (options, reportSource, url) => { let index = 0; if (reportSource === REPORT_SOURCE_TYPES.dashboard) { @@ -762,18 +792,7 @@ export function ReportSettings(props: ReportSettingProps) { ) : ( -
- - -

CSV

-
-
-
+ ); const displayNotebooksSelect = diff --git a/public/components/report_definitions/report_settings/report_settings_constants.tsx b/public/components/report_definitions/report_settings/report_settings_constants.tsx index 824892f1..b74ba9e2 100644 --- a/public/components/report_definitions/report_settings/report_settings_constants.tsx +++ b/public/components/report_definitions/report_settings/report_settings_constants.tsx @@ -46,12 +46,12 @@ export const PDF_PNG_FILE_FORMAT_OPTIONS = [ export const SAVED_SEARCH_FORMAT_OPTIONS = [ { - id: 'csvFormat', + id: 'csv', label: 'CSV', }, { - id: 'xlsFormat', - label: 'XLS', + id: 'xlsx', + label: 'XLSX', }, ]; diff --git a/server/model/backendModel.ts b/server/model/backendModel.ts index 2967f546..d266297c 100644 --- a/server/model/backendModel.ts +++ b/server/model/backendModel.ts @@ -101,6 +101,7 @@ export enum BACKEND_REPORT_FORMAT { pdf = 'Pdf', png = 'Png', csv = 'Csv', + xlsx = 'Xlsx', } export enum BACKEND_TRIGGER_TYPE { @@ -126,6 +127,7 @@ export const REPORT_SOURCE_DICT = { export const REPORT_FORMAT_DICT = { [FORMAT.csv]: BACKEND_REPORT_FORMAT.csv, + [FORMAT.xlsx]: BACKEND_REPORT_FORMAT.xlsx, [FORMAT.pdf]: BACKEND_REPORT_FORMAT.pdf, [FORMAT.png]: BACKEND_REPORT_FORMAT.png, }; diff --git a/server/model/index.ts b/server/model/index.ts index 73ffae07..743b981c 100644 --- a/server/model/index.ts +++ b/server/model/index.ts @@ -42,8 +42,7 @@ export const dataReportSchema = schema.object({ } }, }), - //TODO: future support schema.literal('xlsx') - report_format: schema.oneOf([schema.literal(FORMAT.csv)]), + report_format: schema.oneOf([schema.literal(FORMAT.csv), schema.literal(FORMAT.xlsx)]), limit: schema.number({ defaultValue: DEFAULT_MAX_SIZE, min: 0 }), excel: schema.boolean({ defaultValue: true }), }); diff --git a/server/routes/utils/constants.ts b/server/routes/utils/constants.ts index b8b970d6..aab040a3 100644 --- a/server/routes/utils/constants.ts +++ b/server/routes/utils/constants.ts @@ -10,6 +10,7 @@ export enum FORMAT { pdf = 'pdf', png = 'png', csv = 'csv', + xlsx = 'xlsx', } export enum REPORT_STATE { @@ -181,6 +182,11 @@ export const GLOBAL_BASIC_COUNTER: CountersType = { total: 0, }, }, + xlsx: { + download: { + total: 0, + }, + }, }, }; @@ -288,5 +294,10 @@ export const DEFAULT_ROLLING_COUNTER: CountersType = { count: 0, }, }, + xlsx: { + download: { + count: 0, + }, + }, }, }; diff --git a/server/routes/utils/converters/backendToUi.ts b/server/routes/utils/converters/backendToUi.ts index 0d9b9816..290bb7b5 100644 --- a/server/routes/utils/converters/backendToUi.ts +++ b/server/routes/utils/converters/backendToUi.ts @@ -238,7 +238,7 @@ const getDataReportCoreParams = ( ): DataReportSchemaType => { let res: DataReportSchemaType = { base_url: baseUrl, - report_format: getUiReportFormat(fileFormat), + report_format: getUiReportFormat(fileFormat), limit: limit, time_duration: duration, saved_search_id: sourceId, diff --git a/server/routes/utils/dataReportHelpers.ts b/server/routes/utils/dataReportHelpers.ts index 466a4b15..3fcbfdb9 100644 --- a/server/routes/utils/dataReportHelpers.ts +++ b/server/routes/utils/dataReportHelpers.ts @@ -15,6 +15,7 @@ import { OpenSearchQueryConfig, } from '../../../../../src/plugins/data/common'; import { string } from 'joi'; +import XLSX from 'xlsx'; export var metaData = { saved_search_id: null, @@ -233,6 +234,36 @@ function flattenHits(hits, result = {}, prefix = '') { return result; } +function flattenObject(obj = {}, parentKey = '', result: any = {}) { + for (const [key, value] of Object.entries(obj)) { + const newKey = parentKey ? `${parentKey}.${key}` : key; + + if (typeof value == 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length > 0) { + flattenObject(value, newKey, result) + } else if (Array.isArray(value)) { + result[newKey] = JSON.stringify(value); + } else { + result[newKey] = value; + } + } + + return result; +} + +function flattenArray(array = []) { + return array.map(item => flattenObject(item)); +} + +export const convertToExcel = async (dataset: any) => { + const flatDataset = flattenArray(dataset[0]); + const worksheet = XLSX.utils.json_to_sheet(flatDataset, { sheetStubs: true }); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); + const base64 = XLSX.write(workbook, { type: 'base64', bookType: 'xlsx' }); + + return 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,' + base64; +} + //Return only the selected fields function traverse(data, keys, result = {}) { data = flattenHits(data); @@ -259,8 +290,7 @@ function sanitize(doc: any) { if (doc[field] == null) continue; if ( doc[field].toString().startsWith('+') || - (doc[field].toString().startsWith('-') && - typeof doc[field] !== 'number') || + (doc[field].toString().startsWith('-') && typeof doc[field] !== 'number') || doc[field].toString().startsWith('=') || doc[field].toString().startsWith('@') ) { @@ -311,4 +341,4 @@ const addDocValueFields = (report: any, requestBody: any) => { docvalue_fields: docValues, }; return requestBody; -}; \ No newline at end of file +}; diff --git a/server/routes/utils/savedSearchReportHelper.ts b/server/routes/utils/savedSearchReportHelper.ts index fa4bb633..456273c8 100644 --- a/server/routes/utils/savedSearchReportHelper.ts +++ b/server/routes/utils/savedSearchReportHelper.ts @@ -6,6 +6,7 @@ import { buildRequestBody, convertToCSV, + convertToExcel, getOpenSearchData, getSelectedFields, metaData, @@ -19,6 +20,7 @@ import { getFileName, callCluster } from './helpers'; import { CreateReportResultType } from './types'; import { RequestParams } from '@elastic/elasticsearch'; import esb from 'elastic-builder'; +import { FORMAT } from './constants'; /** * Specify how long scroll context should be maintained for scrolled search @@ -125,7 +127,7 @@ async function populateMetaData( } /** - * Generate CSV data by query and convert OpenSearch data set. + * Generate CSV and XLSX data by query and convert OpenSearch data set. * @param client OpenSearch client * @param limit limit size of result data set */ @@ -153,13 +155,18 @@ async function generateReportData( const reqBody = buildRequestBody(report, allowLeadingWildcards, 0); logger.info( - `[Reporting csv module] DSL request body: ${JSON.stringify(reqBody)}` + `[Reporting ${params.report_format} module] DSL request body: ${JSON.stringify(reqBody)}` ); if (total > maxResultSize) { await getOpenSearchDataByScroll(); } else { await getOpenSearchDataBySearch(); } + + if (params.report_format == FORMAT.xlsx) { + return convertOpenSearchDataToExcel(); + } + return convertOpenSearchDataToCsv(); // Fetch OpenSearch query max size windows to decide search or scroll @@ -267,4 +274,11 @@ async function generateReportData( dataset.push(getOpenSearchData(arrayHits, report, params, dateFormat, timezone)); return await convertToCSV(dataset, csvSeparator); } + + async function convertOpenSearchDataToExcel() { + const dataset = []; + dataset.push(getOpenSearchData(arrayHits, report, params, dateFormat, timezone)); + + return await convertToExcel(dataset); + } } diff --git a/server/routes/utils/types.ts b/server/routes/utils/types.ts index 3f0f21b9..967e40b3 100644 --- a/server/routes/utils/types.ts +++ b/server/routes/utils/types.ts @@ -12,7 +12,7 @@ export interface CreateReportResultType { } type ReportSourceType = 'dashboard' | 'visualization' | 'saved_search' | 'notebook'; -type ReportFormatType = 'pdf' | 'png' | 'csv'; +type ReportFormatType = 'pdf' | 'png' | 'csv' | 'xlsx'; type UsageActionType = 'download'; export type EntityType = 'report' | 'report_definition' | 'report_source'; diff --git a/yarn.lock b/yarn.lock index 03e14cfb..772addc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1209,12 +1209,17 @@ array-buffer-byte-length@^1.0.0: call-bind "^1.0.2" is-array-buffer "^3.0.1" +array-find@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-find/-/array-find-1.0.0.tgz#6c8e286d11ed768327f8e62ecee87353ca3e78b8" + integrity sha512-kO/vVCacW9mnpn3WPWbTVlEnOabK2L7LWi2HViURtCM46y1zb6I8UMjx4LgbiqadTgHnLInUronwn3ampNTJtQ== + array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== -array.prototype.find@^2.1.1, array.prototype.find@^2.2.2: +array.prototype.find@^2.1.1: version "2.2.2" resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.2.2.tgz#e862cf891e725d8f2a10e5e42d750629faaabd32" integrity sha512-DRumkfW97iZGOfn+lIXbkVrXL04sfYKX+EfOodo8XboR5sxPDVvOjZTF/rysusa9lmhmSOeD6Vp6RKQP+eP4Tg== @@ -2264,7 +2269,7 @@ dayjs@^1.10.4: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== -debug@4, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9, debug@^3.1.0, debug@^3.2.7, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: +debug@4, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9, debug@^3.1.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, 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== @@ -2485,15 +2490,6 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e" - integrity sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.2.0" - tapable "^0.1.8" - enhanced-resolve@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" @@ -2503,6 +2499,15 @@ enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" +enhanced-resolve@~0.9.0: + version "0.9.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e" + integrity sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.2.0" + tapable "^0.1.8" + enquirer@^2.3.6: version "2.4.1" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" @@ -2665,22 +2670,21 @@ eslint-import-resolver-node@0.3.2: debug "^2.6.9" resolve "^1.5.0" -eslint-import-resolver-webpack@0.13.8: - version "0.13.8" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.8.tgz#5f64d1d653eefa19cdfd0f0165c996b6be7012f9" - integrity sha512-Y7WIaXWV+Q21Rz/PJgUxiW/FTBOWmU8NTLdz+nz9mMoiz5vAev/fOaQxwD7qRzTfE3HSm1qsxZ5uRd7eX+VEtA== +eslint-import-resolver-webpack@0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.11.1.tgz#fcf1fd57a775f51e18f442915f85dd6ba45d2f26" + integrity sha512-eK3zR7xVQR/MaoBWwGuD+CULYVuqe5QFlDukman71aI6IboCGzggDUohHNfu1ZeBnbHcUHJc0ywWoXUBNB6qdg== dependencies: - array.prototype.find "^2.2.2" - debug "^3.2.7" - enhanced-resolve "^0.9.1" + array-find "^1.0.0" + debug "^2.6.8" + enhanced-resolve "~0.9.0" find-root "^1.1.0" - hasown "^2.0.0" - interpret "^1.4.0" - is-core-module "^2.13.1" - is-regex "^1.1.4" - lodash "^4.17.21" - resolve "^2.0.0-next.5" - semver "^5.7.2" + has "^1.0.1" + interpret "^1.0.0" + lodash "^4.17.4" + node-libs-browser "^1.0.0 || ^2.0.0" + resolve "^1.10.0" + semver "^5.3.0" eslint-plugin-babel@^5.3.1: version "5.3.1" @@ -3257,7 +3261,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.3: +has@^1.0.1, has@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== @@ -3478,7 +3482,7 @@ internal-slot@^1.0.5: hasown "^2.0.0" side-channel "^1.0.4" -interpret@^1.4.0: +interpret@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== @@ -3563,7 +3567,7 @@ is-ci@^3.0.0: dependencies: ci-info "^3.2.0" -is-core-module@^2.13.0, is-core-module@^2.13.1: +is-core-module@^2.13.0: version "2.13.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== @@ -4178,7 +4182,7 @@ lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== -lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4489,7 +4493,7 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-libs-browser@^2.2.1: +"node-libs-browser@^1.0.0 || ^2.0.0", node-libs-browser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== @@ -5311,7 +5315,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== -resolve@^1.12.0, resolve@^1.5.0, resolve@^1.7.1: +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.5.0, resolve@^1.7.1: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -5320,15 +5324,6 @@ resolve@^1.12.0, resolve@^1.5.0, resolve@^1.7.1: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^2.0.0-next.5: - version "2.0.0-next.5" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" - integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -5453,7 +5448,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -semver@^5.6.0, semver@^5.7.0, semver@^5.7.1, semver@^5.7.2, semver@^6.3.0, semver@^6.3.1, semver@^7.5.2, semver@^7.5.3: +semver@^5.3.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1, semver@^6.3.0, semver@^6.3.1, semver@^7.5.2, semver@^7.5.3: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -6604,6 +6599,10 @@ x-is-string@^0.1.0: resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" integrity sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w== +"xlsx@https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz": + version "0.20.1" + resolved "https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz#8980af12ddad223649a6d99ccb712443d3008ca4" + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"