Fix : export CSV, TSV and XLS translation

Feature : add export CSV with semicolon separator
This commit is contained in:
Ben0it-T 2021-10-03 09:18:02 +02:00
parent bc9c7e5aa4
commit 11bf4c7c07
6 changed files with 115 additions and 112 deletions

View file

@ -400,7 +400,11 @@ template(name="exportBoard")
li li
a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}") a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
i.fa.fa-share-alt i.fa.fa-share-alt
| {{_ 'export-board-csv'}} | {{_ 'export-board-csv'}} ' , '
li
a(href="{{exportScsvUrl}}", download="{{exportCsvFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board-csv'}} ' ; '
li li
a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}") a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
i.fa.fa-share-alt i.fa.fa-share-alt

View file

@ -512,6 +512,21 @@ BlazeComponent.extendComponent({
}; };
const queryParams = { const queryParams = {
authToken: Accounts._storedLoginToken(), authToken: Accounts._storedLoginToken(),
delimiter: ',',
};
return FlowRouter.path(
'/api/boards/:boardId/export/csv',
params,
queryParams,
);
},
exportScsvUrl() {
const params = {
boardId: Session.get('currentBoard'),
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
delimiter: ';',
}; };
return FlowRouter.path( return FlowRouter.path(
'/api/boards/:boardId/export/csv', '/api/boards/:boardId/export/csv',

View file

@ -167,25 +167,38 @@ if (Meteor.isServer) {
const exporter = new Exporter(boardId); const exporter = new Exporter(boardId);
if (exporter.canExport(user) || impersonateDone) { if (exporter.canExport(user) || impersonateDone) {
if (impersonateDone) { if (impersonateDone) {
// TODO: Checking for CSV or TSV export type does not work:
// let exportType = 'export' + params.query.delimiter ? 'CSV' : 'TSV';
// So logging export to CSV:
let exportType = 'exportCSV'; let exportType = 'exportCSV';
if( params.query.delimiter == "\t" ) {
exportType = 'exportTSV';
}
ImpersonatedUsers.insert({ ImpersonatedUsers.insert({
adminId: adminId, adminId: adminId,
boardId: boardId, boardId: boardId,
reason: exportType, reason: exportType,
}); });
} }
body = params.query.delimiter let userLanguage = 'en';
? exporter.buildCsv(params.query.delimiter) if (user && user.profile) {
: exporter.buildCsv(); userLanguage = user.profile.language
//'Content-Length': body.length, }
res.writeHead(200, {
'Content-Type': params.query.delimiter ? 'text/csv' : 'text/tsv', if( params.query.delimiter == "\t" ) {
}); // TSV file
res.write(body); res.writeHead(200, {
'Content-Type': 'text/tsv',
});
}
else {
// CSV file (comma or semicolon)
res.writeHead(200, {
'Content-Type': 'text/csv; charset=utf-8',
});
// Adding UTF8 BOM to quick fix MS Excel issue
// use Uint8Array to prevent from converting bytes to string
res.write(new Uint8Array([0xEF, 0xBB, 0xBF]));
}
res.write(exporter.buildCsv(params.query.delimiter, userLanguage));
res.end(); res.end();
} else { } else {
res.writeHead(403); res.writeHead(403);

View file

@ -49,8 +49,13 @@ runOnServer(function() {
isAdmin: true, isAdmin: true,
}); });
} }
const exporterExcel = new ExporterExcel(boardId); let userLanguage = 'en';
if(user && user.profile){
userLanguage = user.profile.language
}
const exporterExcel = new ExporterExcel(boardId, userLanguage);
if (exporterExcel.canExport(user) || impersonateDone) { if (exporterExcel.canExport(user) || impersonateDone) {
if (impersonateDone) { if (impersonateDone) {
ImpersonatedUsers.insert({ ImpersonatedUsers.insert({

View file

@ -197,65 +197,43 @@ export class Exporter {
return result; return result;
} }
buildCsv(delimiter = ',') { buildCsv(userDelimiter = ',', userLanguage='en') {
const result = this.build(); const result = this.build();
const columnHeaders = []; const columnHeaders = [];
const cardRows = []; const cardRows = [];
const papaconfig = { const papaconfig = {
delimiter, // get parameter (was: auto-detect) quotes: true,
worker: true,
};
/*
newline: "", // auto-detect
quoteChar: '"', quoteChar: '"',
escapeChar: '"', escapeChar: '"',
delimiter: userDelimiter,
header: true, header: true,
transformHeader: undefined, newline: "\r\n",
dynamicTyping: false, skipEmptyLines: false,
preview: 0, escapeFormulae: true,
encoding: "",
comments: false,
step: undefined,
complete: undefined,
error: undefined,
download: false,
downloadRequestHeaders: undefined,
downloadRequestBody: undefined,
skipEmptyLines: false,
chunk: undefined,
chunkSize: undefined,
fastMode: undefined,
beforeFirstChunk: undefined,
withCredentials: undefined,
transform: undefined
}; };
*/
//delimitersToGuess: [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP]
columnHeaders.push( columnHeaders.push(
'Title', TAPi18n.__('title','',userLanguage),
'Description', TAPi18n.__('description','',userLanguage),
'Status', TAPi18n.__('list','',userLanguage),
'Swimlane', TAPi18n.__('swimlane','',userLanguage),
'Owner', TAPi18n.__('owner','',userLanguage),
'Requested by', TAPi18n.__('requested-by','',userLanguage),
'Assigned by', TAPi18n.__('assigned-by','',userLanguage),
'Members', TAPi18n.__('members','',userLanguage),
'Assignees', TAPi18n.__('assignee','',userLanguage),
'Labels', TAPi18n.__('labels','',userLanguage),
'Start at', TAPi18n.__('card-start','',userLanguage),
'Due at', TAPi18n.__('card-due','',userLanguage),
'End at', TAPi18n.__('card-end','',userLanguage),
'Over time', TAPi18n.__('overtime-hours','',userLanguage),
'Spent time (hours)', TAPi18n.__('spent-time-hours','',userLanguage),
'Created at', TAPi18n.__('createdAt','',userLanguage),
'Last modified at', TAPi18n.__('last-modified-at','',userLanguage),
'Last activity', TAPi18n.__('last-activity','',userLanguage),
'Vote', TAPi18n.__('voting','',userLanguage),
'Archived', TAPi18n.__('archived','',userLanguage),
); );
const customFieldMap = {}; const customFieldMap = {};
let i = 0; let i = 0;
@ -283,30 +261,8 @@ export class Exporter {
} }
i++; i++;
}); });
cardRows.push([[columnHeaders]]); //cardRows.push([[columnHeaders]]);
/* TODO: Try to get translations working. cardRows.push(columnHeaders);
These currently only bring English translations.
TAPi18n.__('title'),
TAPi18n.__('description'),
TAPi18n.__('status'),
TAPi18n.__('swimlane'),
TAPi18n.__('owner'),
TAPi18n.__('requested-by'),
TAPi18n.__('assigned-by'),
TAPi18n.__('members'),
TAPi18n.__('assignee'),
TAPi18n.__('labels'),
TAPi18n.__('card-start'),
TAPi18n.__('card-due'),
TAPi18n.__('card-end'),
TAPi18n.__('overtime-hours'),
TAPi18n.__('spent-time-hours'),
TAPi18n.__('createdAt'),
TAPi18n.__('last-modified-at'),
TAPi18n.__('last-activity'),
TAPi18n.__('voting'),
TAPi18n.__('archived'),
*/
result.cards.forEach((card) => { result.cards.forEach((card) => {
const currentRow = []; const currentRow = [];
@ -409,7 +365,8 @@ export class Exporter {
currentRow.push(customFieldValuesToPush[valueIndex]); currentRow.push(customFieldValuesToPush[valueIndex]);
} }
} }
cardRows.push([[currentRow]]); //cardRows.push([[currentRow]]);
cardRows.push(currentRow);
}); });
return Papa.unparse(cardRows, papaconfig); return Papa.unparse(cardRows, papaconfig);

View file

@ -3,8 +3,9 @@ import { createWorkbook } from './createWorkbook';
// exporter maybe is broken since Gridfs introduced, add fs and path // exporter maybe is broken since Gridfs introduced, add fs and path
class ExporterExcel { class ExporterExcel {
constructor(boardId) { constructor(boardId, userLanguage) {
this._boardId = boardId; this._boardId = boardId;
this.userLanguage = userLanguage;
} }
build(res) { build(res) {
@ -157,8 +158,8 @@ class ExporterExcel {
//init exceljs workbook //init exceljs workbook
const workbook = createWorkbook(); const workbook = createWorkbook();
workbook.creator = TAPi18n.__('export-board'); workbook.creator = TAPi18n.__('export-board','',this.userLanguage);
workbook.lastModifiedBy = TAPi18n.__('export-board'); workbook.lastModifiedBy = TAPi18n.__('export-board','',this.userLanguage);
workbook.created = new Date(); workbook.created = new Date();
workbook.modified = new Date(); workbook.modified = new Date();
workbook.lastPrinted = new Date(); workbook.lastPrinted = new Date();
@ -367,11 +368,11 @@ class ExporterExcel {
ws.addRow().values = ['', '', '', '', '', '']; ws.addRow().values = ['', '', '', '', '', ''];
//add kanban info //add kanban info
ws.addRow().values = [ ws.addRow().values = [
TAPi18n.__('createdAt'), TAPi18n.__('createdAt','',this.userLanguage),
addTZhours(result.createdAt), addTZhours(result.createdAt),
TAPi18n.__('modifiedAt'), TAPi18n.__('modifiedAt','',this.userLanguage),
addTZhours(result.modifiedAt), addTZhours(result.modifiedAt),
TAPi18n.__('members'), TAPi18n.__('members','',this.userLanguage),
jmem, jmem,
]; ];
ws.getRow(3).font = { ws.getRow(3).font = {
@ -388,6 +389,14 @@ class ExporterExcel {
}, },
numFmt: 'yyyy/mm/dd hh:mm:ss', numFmt: 'yyyy/mm/dd hh:mm:ss',
}; };
ws.getCell('D3').style = {
font: {
name: TAPi18n.__('excel-font'),
size: '10',
bold: true,
},
numFmt: 'yyyy/mm/dd hh:mm:ss',
};
//cell center //cell center
function cellCenter(cellno) { function cellCenter(cellno) {
ws.getCell(cellno).alignment = { ws.getCell(cellno).alignment = {
@ -455,24 +464,24 @@ class ExporterExcel {
//ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签']; //ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签'];
//this is where order in which the excel file generates //this is where order in which the excel file generates
ws.addRow().values = [ ws.addRow().values = [
TAPi18n.__('number'), TAPi18n.__('number','',this.userLanguage),
TAPi18n.__('title'), TAPi18n.__('title','',this.userLanguage),
TAPi18n.__('description'), TAPi18n.__('description','',this.userLanguage),
TAPi18n.__('parent-card'), TAPi18n.__('parent-card','',this.userLanguage),
TAPi18n.__('owner'), TAPi18n.__('owner','',this.userLanguage),
TAPi18n.__('createdAt'), TAPi18n.__('createdAt','',this.userLanguage),
TAPi18n.__('last-modified-at'), TAPi18n.__('last-modified-at','',this.userLanguage),
TAPi18n.__('card-received'), TAPi18n.__('card-received','',this.userLanguage),
TAPi18n.__('card-start'), TAPi18n.__('card-start','',this.userLanguage),
TAPi18n.__('card-due'), TAPi18n.__('card-due','',this.userLanguage),
TAPi18n.__('card-end'), TAPi18n.__('card-end','',this.userLanguage),
TAPi18n.__('list'), TAPi18n.__('list','',this.userLanguage),
TAPi18n.__('swimlane'), TAPi18n.__('swimlane','',this.userLanguage),
TAPi18n.__('assignee'), TAPi18n.__('assignee','',this.userLanguage),
TAPi18n.__('members'), TAPi18n.__('members','',this.userLanguage),
TAPi18n.__('labels'), TAPi18n.__('labels','',this.userLanguage),
TAPi18n.__('overtime-hours'), TAPi18n.__('overtime-hours','',this.userLanguage),
TAPi18n.__('spent-time-hours'), TAPi18n.__('spent-time-hours','',this.userLanguage),
]; ];
ws.getRow(5).height = 20; ws.getRow(5).height = 20;
allBorder('A5'); allBorder('A5');