mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
bc54a6646e
Summary: Fixes a problem reported here: https://community.getgrist.com/t/exporting-the-records-in-a-linked-view/2556/4 The download CSV/Excel link now contains an additional `linkingFilter` URL parameter containing JSON-encoded `filters` and `operations`. This object is originally created in the frontend in `LinkingState`, and previously it was only used internally in the frontend. It would make its way via `QuerySetManager` to `QuerySet.getFilterFunc` where the actual filtering logic happened. Now most of that logic has been moved to a similar function in `common`. The new function works with a new interface `ColumnGettersByColId` which abstract over the different ways data is accessed in the client and server in this context. There's no significant new logic in the diff, just refactoring and wiring. Test Plan: Expanded two `nbrowser/SelectBy*.ts` test suites to also check the contents of a downloaded CSV in different linking scenarios. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3961
100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
import {ApiError} from 'app/common/ApiError';
|
|
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
|
import {FilterColValues} from "app/common/ActiveDocAPI";
|
|
import {DownloadOptions, ExportData, exportSection, exportTable, Filter} from 'app/server/lib/Export';
|
|
import log from 'app/server/lib/log';
|
|
import * as bluebird from 'bluebird';
|
|
import contentDisposition from 'content-disposition';
|
|
import csv from 'csv';
|
|
import * as express from 'express';
|
|
|
|
// promisify csv
|
|
bluebird.promisifyAll(csv);
|
|
|
|
/**
|
|
* Converts `activeDoc` to a CSV and sends the converted data through `res`.
|
|
*/
|
|
export async function downloadCSV(activeDoc: ActiveDoc, req: express.Request,
|
|
res: express.Response, options: DownloadOptions) {
|
|
log.info('Generating .csv file...');
|
|
const {filename, tableId, viewSectionId, filters, sortOrder, linkingFilter} = options;
|
|
const data = viewSectionId ?
|
|
await makeCSVFromViewSection(
|
|
activeDoc, viewSectionId, sortOrder || null, filters || null, linkingFilter || null, req) :
|
|
await makeCSVFromTable(activeDoc, tableId, req);
|
|
res.set('Content-Type', 'text/csv');
|
|
res.setHeader('Content-Disposition', contentDisposition(filename + '.csv'));
|
|
res.send(data);
|
|
}
|
|
|
|
/**
|
|
* Returns a csv stream of a view section 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.
|
|
* @param {Filter[]} filters (optional) - filters defined from ui.
|
|
* @return {Promise<string>} Promise for the resulting CSV.
|
|
*/
|
|
export async function makeCSVFromViewSection(
|
|
activeDoc: ActiveDoc,
|
|
viewSectionId: number,
|
|
sortOrder: number[] | null,
|
|
filters: Filter[] | null,
|
|
linkingFilter: FilterColValues | null,
|
|
req: express.Request) {
|
|
|
|
const data = await exportSection(activeDoc, viewSectionId, sortOrder, filters, linkingFilter, req);
|
|
const file = convertToCsv(data);
|
|
return file;
|
|
}
|
|
|
|
/**
|
|
* Returns a csv stream of a table that can be transformed or parsed.
|
|
*
|
|
* @param {Object} activeDoc - the activeDoc that the table being converted belongs to.
|
|
* @param {Integer} tableId - id of the table to export.
|
|
* @return {Promise<string>} Promise for the resulting CSV.
|
|
*/
|
|
export async function makeCSVFromTable(
|
|
activeDoc: ActiveDoc,
|
|
tableId: string,
|
|
req: express.Request) {
|
|
|
|
if (!activeDoc.docData) {
|
|
throw new Error('No docData in active document');
|
|
}
|
|
|
|
// Look up the table to make a CSV from.
|
|
const tables = activeDoc.docData.getMetaTable('_grist_Tables');
|
|
const tableRef = tables.findRow('tableId', tableId);
|
|
|
|
if (tableRef === 0) {
|
|
throw new ApiError(`Table ${tableId} not found.`, 404);
|
|
}
|
|
|
|
const data = await exportTable(activeDoc, tableRef, req);
|
|
const file = convertToCsv(data);
|
|
return file;
|
|
}
|
|
|
|
function convertToCsv({
|
|
rowIds,
|
|
access,
|
|
columns: viewColumns,
|
|
docSettings
|
|
}: ExportData) {
|
|
|
|
// create formatters for columns
|
|
const formatters = viewColumns.map(col => col.formatter);
|
|
// Arrange the data into a row-indexed matrix, starting with column headers.
|
|
const csvMatrix = [viewColumns.map(col => col.label)];
|
|
// populate all the rows with values as strings
|
|
rowIds.forEach(row => {
|
|
csvMatrix.push(access.map((getter, c) => formatters[c].formatAny(getter(row))));
|
|
});
|
|
return csv.stringifyAsync(csvMatrix);
|
|
}
|