UNPKG

@adaptabletools/adaptable

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

779 lines (778 loc) 37.7 kB
import { ExportModuleId } from '../Utilities/Constants/ModuleConstants'; import { waitForTimeout } from '../Utilities/waitForTimeout'; import { convertCSSAbsoluteFontSizeToPt, getVariableColor, sanitizeStyle, } from '../Utilities/Helpers/StyleHelper'; import { DateFormatter } from '../Utilities/Helpers/FormatHelper'; import tinycolor from 'tinycolor2'; import StringExtensions from '../Utilities/Extensions/StringExtensions'; import { createUuid } from '../AdaptableState/Uuid'; import { inferSchema, initParser } from 'udsv'; import { AG_GRID_GROUPED_COLUMN, AG_GRID_SELECTION_COLUMN, } from '../Utilities/Constants/GeneralConstants'; export class AgGridExportAdapter { constructor(_adaptableInstance) { this._adaptableInstance = _adaptableInstance; /** * !!! * do NOT mutate this array reference, this is passed only initially to AG Grid and we can only change it's internal state */ this.DANGER_excelStyles = []; this.originalExcelStyles = []; this.excelStylesCache = {}; this.cellClassKey2excelStyleIdMap = {}; this.excelStylesWithFormattedDate = {}; } get agGridApi() { return this._adaptableInstance.agGridAdapter.getAgGridApi(); } get adaptableApi() { return this._adaptableInstance.api; } get exportOptions() { return this._adaptableInstance.api.optionsApi.getExportOptions(); } get logger() { return this._adaptableInstance.logger; } static getExcelClassNameForCell(colId, primaryKeyValue, userDefinedCellClass) { let excelClassName = `--excel-cell-${colId}-${primaryKeyValue}`; if (excelClassName.indexOf(' ') > 0) { excelClassName = excelClassName.replace(/\s/g, '_'); } return userDefinedCellClass != null ? `${excelClassName}-${Array.isArray(userDefinedCellClass) ? userDefinedCellClass.join('-') : userDefinedCellClass}` : excelClassName; } destroy() { this.excelStylesCache = null; this.cellClassKey2excelStyleIdMap = null; this.excelStylesWithFormattedDate = null; } async exportData(config) { const { report, format, showProgressIndicator } = config; try { if (showProgressIndicator) { this.adaptableApi.userInterfaceApi.showProgressIndicator({ text: `${report.Name} Export in progress...`, }); // waitForTimeout required to give the ProgressIndicator rendering a head-start (see rAF in ProgressIndicator implementation) // see #raf_progress_indicator await waitForTimeout(16); } this.adaptableApi.exportApi.internalApi.setExportInProgress(config.report.Name, config.format, config.destination); const { exportContext, exportParams } = this.buildExportProcessData(config); if (exportContext.isVisualExcelReport) { // FIXME AFL patch styles only for exported columns! // or even better, only cells this.patchExcelStyles(); } // 1. easiest case, we download the file using AG Grid // these methods will automatically handle the file download if (exportContext.destination === 'Download' && exportContext.isExcelReport) { this.agGridApi.exportDataAsExcel(exportParams); return; } if (exportContext.destination === 'Download' && format === 'CSV') { this.agGridApi.exportDataAsCsv(exportParams); return; } // 2. we have to handle the export ourselves // we get the report data and return it, so that the caller can handle it if (exportContext.isExcelReport) { return { type: 'excel', data: this.agGridApi.getDataAsExcel(exportParams), }; } const csvReportData = this.agGridApi.getDataAsCsv(exportParams); if (exportContext.format === 'CSV') { return { type: 'csv', data: csvReportData, }; } // convert the CSV data to JSON const jsonReportData = this.mapCsvToJson(csvReportData, exportContext); return { type: 'json', data: jsonReportData, }; } catch (error) { this.logger.consoleError(`Error exporting ${report.Name} in ${format} format to ${config.destination}`, error); } finally { /** * Cleanup export process */ this.adaptableApi.exportApi.internalApi.setExportComplete(); if (format === 'VisualExcel') { this.resetExcelStyles(); } if (showProgressIndicator) { this.adaptableApi.userInterfaceApi.hideProgressIndicator(); } } } /** * Creates export context and parameters for a given export configuration */ buildExportProcessData(config) { const exportContext = this.buildExportProcessContext(config); if (exportContext.isVisualExcelReport) { this.patchExcelStyles(); } const exportParams = this.buildExportParams(exportContext); exportContext.exportedColumnIds = exportParams.columnKeys; return { exportContext, exportParams, }; } buildExportParams(exportContext) { const baseExportParams = this.buildBaseExportParams(exportContext); if (exportContext.format === 'Excel' || exportContext.format === 'VisualExcel') { const excelExportParams = baseExportParams; excelExportParams.sheetName = 'Sheet 1'; return excelExportParams; } // for everything else we use the CSV export const csvExportParams = baseExportParams; csvExportParams.columnSeparator = this.getCsvSeparator(exportContext); return csvExportParams; } buildBaseExportParams(exportContext) { // TODO AFL: get custom info from user/ExportOptions for ExcelExportParams? const params = {}; const { columnKeys, exportedRows, shouldRowBeSkipped, skipRowGroups, rowGroupExpandState } = this.getReportColsAndRows(exportContext); params.columnKeys = columnKeys; params.exportedRows = exportedRows; params.shouldRowBeSkipped = shouldRowBeSkipped; params.skipRowGroups = skipRowGroups; // only Excel supports expanded state configuration, but setting it for all formats won't hurt, it will just be ignored params.rowGroupExpandState = rowGroupExpandState; params.fileName = this.adaptableApi.exportApi.internalApi.getReportFileName(exportContext.report.Name, exportContext.format, exportContext.destination); params.skipColumnHeaders = this.computeSkipColumnHeaders(exportContext); if (!exportContext.isExcelReport && exportContext.format !== 'CSV') { // if we map CSV to JSON, we don't want to skip the column headers params.skipColumnHeaders = false; } params.getCustomContentBelowRow = this.computeGetCustomContentBelowRow(exportContext); params.processCellCallback = this.computeProcessCellCallback(exportContext); params.processRowGroupCallback = this.computeProcessRowGroupCallback(exportContext); return params; } computeProcessRowGroupCallback(exportContext) { return (params) => { // recreating the standard AG Grid styling for row groups: 'Parent -> Child' // additionally the values are formatted let rowGroupNode = params.node; const isFooterRow = rowGroupNode.footer; const rowGroupSummary = [ this.processRowGroupForExcelExport(rowGroupNode, exportContext) ?? '', ]; while (rowGroupNode.parent) { rowGroupNode = rowGroupNode.parent; const formattedParentNode = this.processRowGroupForExcelExport(rowGroupNode, exportContext); if (formattedParentNode) { rowGroupSummary.push(formattedParentNode); } } let summary = rowGroupSummary.reverse().join(' -> '); if (isFooterRow) { summary = `Total: ${summary}`; } return summary; }; } computeProcessCellCallback(exportContext) { return (params) => { const { node, column, value } = params; const colId = column.getColId(); if (exportContext.report.ReportRowScope === 'SelectedRows') { // handle case when this cell(column) is not selected if ( // see #isCellPartOfSelection !exportContext.isCellPartOfSelection(params.node.id, colId)) return ''; } const columnId = column.getColId(); if (node?.group && (this.adaptableApi.columnApi.isAutoRowGroupColumn(columnId) || // we would still need to process the cell if this is a group row with an aggregated value node?.aggData?.[columnId] == undefined)) { // skip processing of row groups, this was already handled in processRowGroupCallback() return value; } return this.processCellForExcelExport(node, columnId, exportContext); }; } getReportColsAndRows(exportContext) { const { report, exportedColumnIds, isExportingVisibleColumnToJSON } = exportContext; /** * 1. columnKeys */ const columnKeys = exportedColumnIds; /** * 2. exportedRows */ let exportedRows = 'all'; // 'AllRows' or 'ExpressionRows' if (report.ReportRowScope === 'VisibleRows' || report.ReportRowScope === 'SelectedRows') { exportedRows = 'filteredAndSorted'; } /** * 3. shouldRowBeSkipped */ let shouldRowBeSkipped; // 'AllRows' or 'VisibleRows': no need to skip rows as we are exporting all rows if (report.ReportRowScope === 'SelectedRows') { // mapping of row ids to column ids which contain selected cells // or true if the whole row is selected const selectedRowColumnMapping = new Map(); // ids of selected rows this.adaptableApi.gridApi.getSelectedRowInfo().gridRows?.forEach((row) => { if (row?.rowNode?.id) { selectedRowColumnMapping.set(row.rowNode.id, true); } }); // ids of rows which contain selected cells this.adaptableApi.gridApi.getSelectedCellInfo().gridCells?.map((gridCell) => { if (gridCell.rowNode?.id) { const selectedColumns = selectedRowColumnMapping.get(gridCell.rowNode.id); if (selectedColumns == null) { selectedRowColumnMapping.set(gridCell.rowNode.id, new Set([gridCell.column.columnId])); } else if (selectedColumns instanceof Set) { selectedColumns.add(gridCell.column.columnId); } // else no nothing, the entire row is selected } }); shouldRowBeSkipped = (params) => { return !selectedRowColumnMapping.has(params.node.id); }; // #isCellPartOfSelection exportContext.isCellPartOfSelection = (rowId, columnId) => { const selectedRow = selectedRowColumnMapping.get(rowId); if (selectedRow === true) { return true; } if (selectedRow instanceof Set) { return selectedRow.has(columnId); } return false; }; } if (report.ReportRowScope === 'ExpressionRows') { shouldRowBeSkipped = (params) => { const rowNodeMatchesExpression = this.adaptableApi.internalApi .getQueryLanguageService() .evaluateBooleanExpression(report?.Query?.BooleanExpression, ExportModuleId, params.node); return !rowNodeMatchesExpression; }; } /** * 4. skipRowGroups */ let skipRowGroups; if (report.ReportRowScope === 'AllRows' || isExportingVisibleColumnToJSON) { // we want to return the entire data, without the groups skipRowGroups = true; } if (this.isTreeDataGrid()) { // we need the group rows for TreeData skipRowGroups = false; } /** * 5. rowGroupExpandState */ let rowGroupExpandState = 'expanded'; if (report.ReportRowScope === 'VisibleRows') { rowGroupExpandState = 'match'; } return { columnKeys, exportedRows, shouldRowBeSkipped, skipRowGroups, rowGroupExpandState }; } isTreeDataGrid() { return this.adaptableApi.gridApi.isTreeDataGrid(); } buildExportProcessContext(config) { const { report } = config; const currentLayout = this.adaptableApi.layoutApi.getCurrentLayout(); const groupColumnIds = this.isTreeDataGrid() ? [AG_GRID_GROUPED_COLUMN] : currentLayout.RowGroupedColumns ?? currentLayout.PivotGroupedColumns; const pivotColumnIds = currentLayout.PivotColumns; const isExportingVisibleColumnToJSON = config.format === 'JSON' && report.ReportColumnScope === 'VisibleColumns'; const exportableColumnIds = this.adaptableApi.columnApi .getExportableColumns() .map((col) => col.columnId); const exportableColumnIdsSet = new Set(exportableColumnIds); // we compute the exported column keys early, as we need them for other parts of the export process const onlyExportableColumnIds = (columnId) => exportableColumnIdsSet.has(columnId); const getVisibleColumnIds = () => { return this.adaptableApi.layoutApi .getCurrentVisibleColumnIdsForTableLayout() .filter(onlyExportableColumnIds); }; let exportedColumnIds = []; switch (report.ReportColumnScope) { case 'AllColumns': exportedColumnIds = exportableColumnIds.filter((colId) => this.isTreeDataGrid() || !this.adaptableApi.columnApi.isAutoRowGroupColumn(colId)); break; case 'VisibleColumns': exportedColumnIds = getVisibleColumnIds(); if (isExportingVisibleColumnToJSON) { // for JSON we will export CSV and then convert to JSON // in this case we have to map the auto-group columns to the real keys exportedColumnIds = [ ...groupColumnIds, ...exportedColumnIds.filter((columnId) => this.isTreeDataGrid() || !this.adaptableApi.columnApi.isAutoRowGroupColumn(columnId)), ]; } break; case 'SelectedColumns': const selectedRowInfo = this.adaptableApi.gridApi.getSelectedRowInfo(); if (selectedRowInfo?.gridRows?.length) { // if there is at least one selected row, we have to export all visible columns exportedColumnIds = getVisibleColumnIds(); } else { // otherwise, we can export only columns which contain selected cells const selectedColumnIds = this.adaptableApi.gridApi .getSelectedCellInfo() .columns?.map((column) => column.columnId); const selectedColumnIdsSet = new Set(selectedColumnIds.filter(onlyExportableColumnIds)); // ensure that column order is preserved exportedColumnIds = getVisibleColumnIds().filter((colId) => selectedColumnIdsSet.has(colId)); } break; case 'ScopeColumns': if ('ColumnIds' in report.Scope) { exportedColumnIds = report.Scope.ColumnIds?.filter(onlyExportableColumnIds); } else { exportedColumnIds = this.adaptableApi.columnScopeApi .getColumnsInScope(report.Scope) .map((col) => col.columnId) .filter(onlyExportableColumnIds); } break; default: this.adaptableApi.logError('Invalid ReportColumnScope: ', report.ReportColumnScope); // fallback to all columns exportedColumnIds = exportableColumnIds; } return { ...config, exportedColumnIds, isExcelReport: config.format === 'Excel' || config.format === 'VisualExcel', isVisualExcelReport: config.format === 'VisualExcel', isExportingVisibleColumnToJSON, // will be overriden later, see #isCellPartOfSelection isCellPartOfSelection: () => false, getCurrent: () => { return { layout: currentLayout, groupColumnIds, pivotColumnIds, }; }, }; } processCellForExcelExport(rowNode, columnId, exportContext) { if (this.adaptableApi.exportApi.internalApi.isVisualDataExportInProgress()) { const cellKey = AgGridExportAdapter.getExcelClassNameForCell(columnId, this.adaptableApi.gridApi.getPrimaryKeyValueForRowNode(rowNode)); const isoFormattedDate = this.getExcelStyleWithFormattedDate(cellKey); if (isoFormattedDate) { // this is a Date cell which will be formatted by Excel return isoFormattedDate; } } return this.adaptableApi.exportApi.internalApi.getCellExportValueFromRowNode(rowNode, columnId, exportContext.isVisualExcelReport); } processRowGroupForExcelExport(rowNode, exportContext) { if (this.isTreeDataGrid()) { return rowNode.key; } const columnId = rowNode.field ?? rowNode.rowGroupColumn?.getColId() ?? rowNode.rowGroupColumn?.getColDef()?.field; if (!columnId || !rowNode.key) { return; } let rawValue = rowNode.key; const columnDataType = this.adaptableApi.columnApi.getColumnDataTypeForColumnId(columnId); if ((columnDataType === 'date' || columnDataType === 'dateString') && typeof rawValue === 'string' && // rawValue is composed only of digits /^\d+$/.test(rawValue)) { // AG-Grid converts the value to string, we have to reconvert it back const dateRawValue = parseInt(rawValue); if (dateRawValue != undefined) { // @ts-ignore rawValue = dateRawValue; } } return this.adaptableApi.exportApi.internalApi.getCellExportValueFromRawValue(rowNode, rawValue, columnId, exportContext.isVisualExcelReport); } patchExcelStyles() { const exportExcelStyles = this.buildExcelStyles(); // set DANGER_excelStyles without changing the array reference this.DANGER_excelStyles.splice(0, this.DANGER_excelStyles.length, ...exportExcelStyles); } resetExcelStyles() { this.DANGER_excelStyles.splice(0, this.DANGER_excelStyles.length, ...this.originalExcelStyles); this.excelStylesCache = {}; this.cellClassKey2excelStyleIdMap = {}; this.excelStylesWithFormattedDate = {}; } buildExcelStyles() { // we make sure that we start with a clean slate // theoretically this should have happened at the end of the export process, but just in case this.resetExcelStyles(); // we memoize as much as possible, as this is called quite A LOT const adaptableColumnMemo = {}; const getAdaptableColumnWithColumnId = (columnId) => { const memoizedColumn = adaptableColumnMemo[columnId]; if (memoizedColumn) { return memoizedColumn; } const abColumn = this.adaptableApi.columnApi.getColumnWithColumnId(columnId); adaptableColumnMemo[columnId] = abColumn; return abColumn; }; const formatColumnsWithDisplayFormatMemo = {}; const getFormatColumnsWithDisplayFormatForColumn = (columnId) => { const memoizedFormatColumns = formatColumnsWithDisplayFormatMemo[columnId]; if (memoizedFormatColumns) { return memoizedFormatColumns; } const abColumn = getAdaptableColumnWithColumnId(columnId); const formatColumns = this.adaptableApi.formatColumnApi.internalApi.getFormatColumnsWithDisplayFormatForColumn(abColumn); formatColumnsWithDisplayFormatMemo[columnId] = formatColumns; return formatColumns; }; const agGridDisplayedColumns = this.agGridApi .getAllDisplayedColumns() .filter((agCol) => agCol.getId() !== AG_GRID_SELECTION_COLUMN); const colDefs = agGridDisplayedColumns.map((column) => { return column.getColDef(); }); const forAllVisibleRowNodesDoConfig = { includeGroupRows: true }; const userExcelStyles = this.DANGER_excelStyles; this.adaptableApi.internalApi.forAllVisibleRowNodesDo((node, rowIndex) => { const rowParams = { node, data: node.data, rowIndex, api: this.agGridApi, context: this.agGridApi.getGridOption('context') || {}, }; const getRowStyleFn = this.agGridApi.getGridOption('getRowStyle'); const rowStyle = getRowStyleFn ? getRowStyleFn(rowParams) : {}; agGridDisplayedColumns.forEach((column, columnIndex) => { const colDef = colDefs[columnIndex]; const columnId = column.getId(); const adaptableColumn = getAdaptableColumnWithColumnId(columnId); if (!adaptableColumn) { this.logger.warn(`Export Styling: Column with id ${columnId} not found in Adaptable`); return; } const isDateCellExportedAsFormattedValue = this.isDateCellExportedAsFormattedValue(adaptableColumn); let cellClassParams; const getLazyCellClassParams = () => { if (!cellClassParams) { cellClassParams = { colDef, node, column, data: node.data, value: this.adaptableApi.gridApi.getRawValueFromRowNode(node, columnId), rowIndex, api: this.agGridApi, context: {}, }; } return cellClassParams; }; const cellStyle = typeof colDef.cellStyle === 'function' ? colDef.cellStyle(getLazyCellClassParams()) : {}; const excelStyles = []; // add user defined excel styles let userColDefCellClass = this._adaptableInstance.agGridColumnAdapter.getUserColDefProperty(column.getColId(), 'cellClass'); const userDefinedCellClass = typeof userColDefCellClass === 'function' ? userColDefCellClass(getLazyCellClassParams()) : userColDefCellClass; const userDefinedExcelStyle = userDefinedCellClass && userExcelStyles.find((excelStyle) => { return typeof userDefinedCellClass === 'string' ? userDefinedCellClass === excelStyle.id : userDefinedCellClass?.includes?.(excelStyle.id); }); if (userDefinedExcelStyle) { excelStyles.push(userDefinedExcelStyle); } // add adaptable derived styles (format column etc.) const adaptableStyle = { ...rowStyle, ...Object.keys(cellStyle).reduce((result, key) => { if (cellStyle[key] !== null) { result[key] = cellStyle[key]; } return result; }, {}), }; const sanitizedAdaptableStyle = sanitizeStyle(adaptableStyle); if (Object.values(sanitizedAdaptableStyle).some((style) => style != null)) { excelStyles.push(this.convertCSSToExcelStyle(sanitizedAdaptableStyle)); } const excelDataType = this.getExcelDataType(adaptableColumn.dataType); const rawValue = this.adaptableApi.gridApi.getRawValueFromRowNode(node, column.getId()); // don't add the cell style if it has no adaptable custom styles if (!excelStyles.length && // if this is a formatted Date value, we still need to add the AG GRID specific type & numberFormat below !(excelDataType === 'DateTime' && isDateCellExportedAsFormattedValue)) { return; } const cellClassId = AgGridExportAdapter.getExcelClassNameForCell(column.getId(), this.adaptableApi.gridApi.getPrimaryKeyValueForRowNode(node), userDefinedCellClass); const finalCellExcelStyle = Object.assign({}, ...excelStyles); if (excelDataType === 'DateTime' && isDateCellExportedAsFormattedValue) { let dateFormatPattern = this.exportOptions.exportDateFormat; const abColumn = getAdaptableColumnWithColumnId(column.getColId()); if (!dateFormatPattern) { const mostRelevantFormatColumn = this.adaptableApi.formatColumnApi.internalApi.getMostRelevantFormatColumnForColumn(getFormatColumnsWithDisplayFormatForColumn(column.getColId()), abColumn, { node, value: rawValue }); dateFormatPattern = mostRelevantFormatColumn?.DisplayFormat?.Formatter === 'DateFormatter' && mostRelevantFormatColumn?.DisplayFormat?.Options?.Pattern; } if (dateFormatPattern) { const normalisedValue = this._adaptableInstance.getNormalisedValueFromRawValue(rawValue, abColumn); if (normalisedValue) { // we have to pass the date in the ISO format to Excel // see https://www.ag-grid.com/javascript-data-grid/excel-export-data-types/#dates // we can NOT use Date.toISOString() because we don't want the timezone corrections to kick in const isoFormattedValue = DateFormatter(normalisedValue, { Pattern: `yyyy-MM-dd'T'HH:mm:ss.SSS`, }); if (isoFormattedValue) { finalCellExcelStyle.dataType = 'DateTime'; finalCellExcelStyle.numberFormat = { format: dateFormatPattern }; // create a new cell key to ensure any user provided className does not interfere const cellKey = AgGridExportAdapter.getExcelClassNameForCell(column.getColId(), this.adaptableApi.gridApi.getPrimaryKeyValueForRowNode(node)); // we need to register so that later the cellProcessor will put the isoFormattedValue through (thus giving the formatting responsability to Excel) this.registerExcelStyleWithFormattedDate(cellKey, isoFormattedValue); } } } } this.registerExcelStyle(finalCellExcelStyle, cellClassId); }); }, forAllVisibleRowNodesDoConfig); return Object.values(this.excelStylesCache); } registerExcelStyle(excelStyle, cellClassKey) { const excelStyleKey = JSON.stringify(excelStyle); if (!this.excelStylesCache[excelStyleKey]) { const excelStyleId = createUuid(); const excelStyleWithId = { ...excelStyle, id: excelStyleId }; this.excelStylesCache[excelStyleKey] = excelStyleWithId; } this.cellClassKey2excelStyleIdMap[cellClassKey] = this.excelStylesCache[excelStyleKey].id; } registerExcelStyleWithFormattedDate(cellClassId, isoFormattedValue) { this.excelStylesWithFormattedDate[cellClassId] = isoFormattedValue; } isDateCellExportedAsFormattedValue(abColumn) { return (!!this.exportOptions.exportDateFormat || // FIXME AFL move this method here this.adaptableApi.exportApi.internalApi.getCellExportFormatType(abColumn, 'date') === 'formattedValue'); } convertCSSToExcelStyle(style) { const getHexColor = (color) => { const preparedColor = getVariableColor(color); const t = tinycolor(preparedColor); const a = t.getAlpha(); return tinycolor.mix(tinycolor('white'), t, a * 100).toHexString(); }; let result = {}; if (style.backgroundColor != null) { result.interior = { color: getHexColor(style.backgroundColor), pattern: 'Solid', }; } if (style.borderColor != null) { const excelBorder = { color: style.borderColor, lineStyle: 'Continuous', weight: 1, }; result.borders = { borderBottom: excelBorder, borderLeft: excelBorder, borderRight: excelBorder, borderTop: excelBorder, }; } if (style.textAlign) { result.alignment = { horizontal: StringExtensions.CapitaliseFirstLetter(style.textAlign), }; } if (style.color != null) { if (!result.font) { result.font = {}; } result.font = { color: getHexColor(style.color), }; } if (style.fontStyle === 'italic') { if (!result.font) { result.font = {}; } result.font.italic = true; } if (style.fontWeight != null && (style.fontWeight === 'bold' || Number(style.fontWeight) >= 600)) { if (!result.font) { result.font = {}; } result.font.bold = true; } if (style.fontSize != null) { if (!result.font) { result.font = {}; } result.font.size = convertCSSAbsoluteFontSizeToPt(style.fontSize); } return result; } getExcelDataType(adaptableColumnDataType) { switch (adaptableColumnDataType) { case 'number': return 'Number'; case 'boolean': return 'Boolean'; case 'date': case 'dateString': return 'DateTime'; case 'text': default: return 'String'; } } getExcelStyleIdForCellClassKey(cellClassKey) { return this.cellClassKey2excelStyleIdMap[cellClassKey]; } getExcelStyleWithFormattedDate(cellClassId) { return this.excelStylesWithFormattedDate[cellClassId]; } mapCsvToJson(csvReportData, exportContext) { const columns = exportContext.exportedColumnIds .map((columnId) => this.adaptableApi.columnApi.getColumnWithColumnId(columnId)) .map((column) => ({ columnId: column.columnId, field: column.field ?? column.columnId, friendlyName: column.friendlyName, dataType: column.dataType, })); const reportData = { columns, rows: [], }; if (exportContext.report.ReportColumnScope === 'VisibleColumns') { if (exportContext.getCurrent().groupColumnIds?.length) { reportData.groupColumnIds = exportContext.getCurrent().groupColumnIds; } if (exportContext.getCurrent().pivotColumnIds?.length) { reportData.pivotColumnIds = exportContext.getCurrent().pivotColumnIds; } } // map Row Data const schema = inferSchema(csvReportData, { col: this.getCsvSeparator(exportContext), header: (rows) => { const headerRow = rows[0]; // replace headerNames with columnIds headerRow.forEach((headerName, index) => { const column = columns.find((col) => col.friendlyName === headerName); if (column) { headerRow[index] = column.columnId; } }); return [headerRow]; }, }); const parser = initParser(schema); const jsonData = parser.typedObjs(csvReportData); reportData.rows = jsonData; return reportData; } getCsvSeparator(exportProcessContext) { if (typeof this.exportOptions.csvSeparator === 'function') { return this.exportOptions.csvSeparator(this.adaptableApi.exportApi.internalApi.buildBaseExportContext(exportProcessContext.report.Name, exportProcessContext.format, exportProcessContext.destination)); } else { return this.exportOptions.csvSeparator; } } computeSkipColumnHeaders(exportProcessContext) { if (typeof this.exportOptions.skipColumnHeaders === 'function') { return this.exportOptions.skipColumnHeaders(this.adaptableApi.exportApi.internalApi.buildBaseExportContext(exportProcessContext.report.Name, exportProcessContext.format, exportProcessContext.destination)); } else { return this.exportOptions.skipColumnHeaders; } } computeGetCustomContentBelowRow(exportContext) { if (!this.adaptableApi.gridApi.isMasterDetailGrid()) { return; } const getDetailsRowsFn = this.exportOptions.getDetailRows; if (typeof getDetailsRowsFn !== 'function') { this.adaptableApi.logInfo('ExportOptions.getDetailRows() is not defined, skipping detail rows export'); return; } return (params) => { const { node } = params; const shouldExportDetailRows = exportContext.report.ReportRowScope === 'AllRows' || (exportContext.report.ReportRowScope === 'VisibleRows' && node.expanded); if (!shouldExportDetailRows) { return; } const context = { ...this.adaptableApi.exportApi.internalApi.buildBaseExportContext(exportContext.report.Name, exportContext.format, exportContext.destination), adaptableColumn: this.adaptableApi.columnApi.getColumnWithColumnId(params.column?.getId()), masterRowNode: node, masterRowData: node?.data, isExpanded: node.expanded, createCellCsv: (cellContent) => this.adaptableApi.exportApi.internalApi.createCellCsv(cellContent), createCellExcel: (cellContent, cellType) => this.adaptableApi.exportApi.internalApi.createCellExcel(cellContent, cellType), createCellHeader: (cellContent) => { return { ...this.adaptableApi.exportApi.internalApi.createCellHeader(cellContent), // see #masterDetailHeader styleId: '_masterDetailHeader', }; }, }; const detailRows = getDetailsRowsFn(context); if (exportContext.isExcelReport) { const anyExcelRow = detailRows[0]; if (anyExcelRow.cells == undefined) { this.adaptableApi.logWarn(`Export ${exportContext.report.Name}: ExportOptions.getDetailRows() should return an array of ExcelRow objects`); return; } } else { const anyCsvCell = detailRows[0]?.[0]; if (anyCsvCell.data == undefined) { this.adaptableApi.logWarn(`Export ${exportContext.report.Name}: ExportOptions.getDetailRows() should return an array of CsvRow objects`); return; } } return detailRows; }; } }