2020-07-21 13:20:51 +00:00
|
|
|
const gutil = require('app/common/gutil');
|
|
|
|
const {SortFunc} = require('app/common/SortFunc');
|
|
|
|
const ValueFormatter = require('app/common/ValueFormatter');
|
2020-09-11 20:27:09 +00:00
|
|
|
const {docSessionFromRequest} = require('app/server/lib/DocSession');
|
2020-07-21 13:20:51 +00:00
|
|
|
const Promise = require('bluebird');
|
|
|
|
const contentDisposition = require('content-disposition');
|
|
|
|
const csv = require('csv');
|
|
|
|
const fs = require('fs-extra');
|
|
|
|
const log = require('./lib/log');
|
|
|
|
const {ServerColumnGetters} = require('./lib/ServerColumnGetters');
|
|
|
|
const multiparty = require('multiparty');
|
|
|
|
const tmp = require('tmp');
|
|
|
|
const _ = require('underscore');
|
|
|
|
|
|
|
|
Promise.promisifyAll(csv);
|
|
|
|
Promise.promisifyAll(multiparty, {filter: name => (name === 'parse'), multiArgs: true});
|
|
|
|
Promise.promisifyAll(fs);
|
|
|
|
Promise.promisifyAll(tmp);
|
|
|
|
|
|
|
|
function generateCSV(req, res, comm) {
|
|
|
|
log.info('Generating .csv file...');
|
|
|
|
// Get the current table id
|
|
|
|
var tableId = req.param('tableId');
|
|
|
|
var viewSectionId = parseInt(req.param('viewSection'), 10);
|
|
|
|
var activeSortOrder = gutil.safeJsonParse(req.param('activeSortSpec'), null);
|
|
|
|
|
|
|
|
// Get the active doc
|
|
|
|
var clientId = req.param('clientId');
|
|
|
|
var docFD = parseInt(req.param('docFD'), 10);
|
|
|
|
var client = comm.getClient(clientId);
|
|
|
|
var docSession = client.getDocSession(docFD);
|
|
|
|
var activeDoc = docSession.activeDoc;
|
|
|
|
|
|
|
|
// Generate a decent name for the exported file.
|
|
|
|
var docName = req.query.title || activeDoc.docName;
|
|
|
|
var name = docName +
|
|
|
|
(tableId === docName ? '' : '-' + tableId) + '.csv';
|
|
|
|
|
|
|
|
res.set('Content-Type', 'text/csv');
|
|
|
|
res.setHeader('Content-Disposition', contentDisposition(name));
|
2020-09-02 18:17:17 +00:00
|
|
|
return makeCSV(activeDoc, viewSectionId, activeSortOrder, req)
|
2020-07-21 13:20:51 +00:00
|
|
|
.then(data => res.send(data));
|
|
|
|
}
|
|
|
|
exports.generateCSV = generateCSV;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a csv stream that can be transformed or parsed. See https://github.com/wdavidw/node-csv
|
|
|
|
* for API details.
|
|
|
|
*
|
|
|
|
* @param {Object} activeDoc - the activeDoc that the table being converted belongs to.
|
|
|
|
* @param {Integer} viewSectionId - id of the viewsection to export.
|
|
|
|
* @param {Integer[]} activeSortOrder (optional) - overriding sort order.
|
|
|
|
* @return {Promise<string>} Promise for the resulting CSV.
|
|
|
|
*/
|
2020-09-02 18:17:17 +00:00
|
|
|
function makeCSV(activeDoc, viewSectionId, sortOrder, req) {
|
2020-07-21 13:20:51 +00:00
|
|
|
return Promise.try(() => {
|
|
|
|
const tables = activeDoc.docData.getTables();
|
|
|
|
const viewSection = tables.get('_grist_Views_section').getRecord(viewSectionId);
|
|
|
|
const table = tables.get('_grist_Tables').getRecord(viewSection.tableRef);
|
|
|
|
const fields = tables.get('_grist_Views_section_field').filterRecords({ parentId: viewSection.id});
|
|
|
|
const tableColumns = tables.get('_grist_Tables_column').filterRecords({parentId: table.id});
|
|
|
|
const tableColsById = _.indexBy(tableColumns, 'id');
|
|
|
|
|
|
|
|
// Produce a column description matching what user will see / expect to export
|
|
|
|
const viewify = (col, field) => {
|
|
|
|
field = field || {};
|
|
|
|
const displayCol = tableColsById[field.displayCol || col.displayCol || col.id];
|
|
|
|
const colWidgetOptions = gutil.safeJsonParse(col.widgetOptions, {});
|
|
|
|
const fieldWidgetOptions = gutil.safeJsonParse(field.widgetOptions, {});
|
|
|
|
return {
|
|
|
|
id: displayCol.id,
|
|
|
|
colId: displayCol.colId,
|
|
|
|
label: col.label,
|
|
|
|
colType: col.type,
|
|
|
|
widgetOptions: Object.assign(colWidgetOptions, fieldWidgetOptions)
|
|
|
|
};
|
|
|
|
};
|
|
|
|
const viewColumns = _.sortBy(fields, 'parentPos').map(
|
|
|
|
(field) => viewify(tableColsById[field.colRef], field));
|
|
|
|
|
|
|
|
// The columns named in sort order need to now become display columns
|
|
|
|
sortOrder = sortOrder || gutil.safeJsonParse(viewSection.sortColRefs, []);
|
|
|
|
const fieldsByColRef = _.indexBy(fields, 'colRef');
|
|
|
|
sortOrder = sortOrder.map((directionalColRef) => {
|
|
|
|
const colRef = Math.abs(directionalColRef);
|
|
|
|
const col = tableColsById[colRef];
|
|
|
|
if (!col) return 0;
|
|
|
|
const effectiveColRef = viewify(col, fieldsByColRef[colRef]).id;
|
|
|
|
return directionalColRef > 0 ? effectiveColRef : -effectiveColRef;
|
|
|
|
});
|
|
|
|
|
2020-09-11 20:27:09 +00:00
|
|
|
return [activeDoc.fetchTable(docSessionFromRequest(req), table.tableId, true), tableColumns, viewColumns];
|
2020-07-21 13:20:51 +00:00
|
|
|
}).spread((data, tableColumns, viewColumns) => {
|
|
|
|
const rowIds = data[2];
|
|
|
|
const dataByColId = data[3];
|
|
|
|
const getters = new ServerColumnGetters(rowIds, dataByColId, tableColumns);
|
|
|
|
const sorter = new SortFunc(getters);
|
|
|
|
sorter.updateSpec(sortOrder);
|
|
|
|
rowIds.sort((a, b) => sorter.compare(a, b));
|
|
|
|
const formatters = viewColumns.map(col =>
|
|
|
|
ValueFormatter.createFormatter(col.colType, col.widgetOptions));
|
|
|
|
// Arrange the data into a row-indexed matrix, starting with column headers.
|
|
|
|
const csvMatrix = [viewColumns.map(col => col.label)];
|
|
|
|
const access = viewColumns.map(col => getters.getColGetter(col.id));
|
|
|
|
rowIds.forEach(row => {
|
|
|
|
csvMatrix.push(access.map((getter, c) => formatters[c].formatAny(getter(row))));
|
|
|
|
});
|
2020-11-12 20:01:11 +00:00
|
|
|
return csv.stringifyAsync(csvMatrix);
|
2020-07-21 13:20:51 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
exports.makeCSV = makeCSV;
|