mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Relocate export urls to /download/
Summary: Moves CSV and XLSX export urls under /download/, and removes the document title query parameter which is now retrieved from the backend. Test Plan: No new tests. Existing tests that verify endpoints still function. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3010
This commit is contained in:
@@ -33,7 +33,9 @@ import { googleAuthTokenMiddleware } from "app/server/lib/GoogleAuth";
|
||||
import * as _ from "lodash";
|
||||
import {isRaisedException} from "app/common/gristTypes";
|
||||
import {localeFromRequest} from "app/server/lib/ServerLocale";
|
||||
import { generateCSV, generateXLSX } from "app/server/serverMethods";
|
||||
import { downloadCSV, DownloadCSVOptions } from "app/server/lib/ExportCSV";
|
||||
import { downloadXLSX, DownloadXLSXOptions } from "app/server/lib/ExportXLSX";
|
||||
import { parseExportParameters } from "app/server/lib/Export";
|
||||
|
||||
// Cap on the number of requests that can be outstanding on a single document via the
|
||||
// rest doc api. When this limit is exceeded, incoming requests receive an immediate
|
||||
@@ -566,9 +568,31 @@ export class DocWorkerApi {
|
||||
res.json(result);
|
||||
}));
|
||||
|
||||
this._app.get('/api/docs/:docId/gen-csv', canView, withDoc(generateCSV));
|
||||
this._app.get('/api/docs/:docId/download/csv', canView, withDoc(async (activeDoc, req, res) => {
|
||||
// Query DB for doc metadata to get the doc title.
|
||||
const {name: docTitle} =
|
||||
await this._dbManager.getDoc({userId: getUserId(req), org: req.org, urlId: getDocId(req)});
|
||||
|
||||
this._app.get('/api/docs/:docId/gen-xlsx', canView, withDoc(generateXLSX));
|
||||
const params = parseExportParameters(req);
|
||||
const filename = docTitle + (params.tableId === docTitle ? '' : '-' + params.tableId);
|
||||
|
||||
const options: DownloadCSVOptions = {
|
||||
...params,
|
||||
filename,
|
||||
};
|
||||
|
||||
await downloadCSV(activeDoc, req, res, options);
|
||||
}));
|
||||
|
||||
this._app.get('/api/docs/:docId/download/xlsx', canView, withDoc(async (activeDoc, req, res) => {
|
||||
// Query DB for doc metadata to get the doc title (to use as the filename).
|
||||
const {name: filename} =
|
||||
await this._dbManager.getDoc({userId: getUserId(req), org: req.org, urlId: getDocId(req)});
|
||||
|
||||
const options: DownloadXLSXOptions = {filename};
|
||||
|
||||
await downloadXLSX(activeDoc, req, res, options);
|
||||
}));
|
||||
|
||||
this._app.get('/api/docs/:docId/send-to-drive', canView, decodeGoogleToken, withDoc(exportToDrive));
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {DocumentSettings} from 'app/common/DocumentSettings';
|
||||
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
||||
import {RequestWithLogin} from 'app/server/lib/Authorizer';
|
||||
import {docSessionFromRequest} from 'app/server/lib/DocSession';
|
||||
import { integerParam, optJsonParam, stringParam} from 'app/server/lib/requestUtils';
|
||||
import {optIntegerParam, optJsonParam, stringParam} from 'app/server/lib/requestUtils';
|
||||
import {ServerColumnGetters} from 'app/server/lib/ServerColumnGetters';
|
||||
import * as express from 'express';
|
||||
import * as _ from 'underscore';
|
||||
@@ -72,7 +72,7 @@ export interface ExportData {
|
||||
*/
|
||||
export interface ExportParameters {
|
||||
tableId: string;
|
||||
viewSectionId: number;
|
||||
viewSectionId: number | undefined;
|
||||
sortOrder: number[];
|
||||
filters: Filter[];
|
||||
}
|
||||
@@ -82,7 +82,7 @@ export interface ExportParameters {
|
||||
*/
|
||||
export function parseExportParameters(req: express.Request): ExportParameters {
|
||||
const tableId = stringParam(req.query.tableId);
|
||||
const viewSectionId = integerParam(req.query.viewSection);
|
||||
const viewSectionId = optIntegerParam(req.query.viewSection);
|
||||
const sortOrder = optJsonParam(req.query.activeSortSpec, []) as number[];
|
||||
const filters: Filter[] = optJsonParam(req.query.filters, []);
|
||||
|
||||
@@ -94,20 +94,6 @@ export function parseExportParameters(req: express.Request): ExportParameters {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the file name (without an extension) for exported table.
|
||||
* @param activeDoc ActiveDoc
|
||||
* @param req Request (with export params)
|
||||
*/
|
||||
export function parseExportFileName(activeDoc: ActiveDoc, req: express.Request) {
|
||||
const title = req.query.title;
|
||||
const tableId = req.query.tableId;
|
||||
const docName = title || activeDoc.docName;
|
||||
const name = docName +
|
||||
(tableId === docName ? '' : '-' + tableId);
|
||||
return name;
|
||||
}
|
||||
|
||||
// Makes assertion that value does exists or throws an error
|
||||
function safe<T>(value: T, msg: string) {
|
||||
if (!value) { throw new Error(msg); }
|
||||
|
||||
@@ -1,16 +1,54 @@
|
||||
import {createFormatter} from 'app/common/ValueFormatter';
|
||||
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
||||
import {ExportData, exportSection, Filter} from 'app/server/lib/Export';
|
||||
import {ExportData, exportSection, exportTable, Filter} from 'app/server/lib/Export';
|
||||
import * as bluebird from 'bluebird';
|
||||
import * as csv from 'csv';
|
||||
import * as express from 'express';
|
||||
import * as log from 'app/server/lib/log';
|
||||
import * as contentDisposition from 'content-disposition';
|
||||
|
||||
export interface DownloadCSVOptions {
|
||||
filename: string;
|
||||
tableId: string;
|
||||
viewSectionId: number | undefined;
|
||||
filters: Filter[];
|
||||
sortOrder: number[];
|
||||
}
|
||||
|
||||
// promisify csv
|
||||
bluebird.promisifyAll(csv);
|
||||
|
||||
/**
|
||||
* Returns a csv stream that can be transformed or parsed. See https://github.com/wdavidw/node-csv
|
||||
* for API details.
|
||||
* 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: DownloadCSVOptions) {
|
||||
log.info('Generating .csv file...');
|
||||
const {filename, tableId, viewSectionId, filters, sortOrder} = options;
|
||||
|
||||
try {
|
||||
const data = viewSectionId ?
|
||||
await makeCSVFromViewSection(activeDoc, viewSectionId, sortOrder, filters, req) :
|
||||
await makeCSVFromTable(activeDoc, tableId, req);
|
||||
res.set('Content-Type', 'text/csv');
|
||||
res.setHeader('Content-Disposition', contentDisposition(filename + '.csv'));
|
||||
res.send(data);
|
||||
} catch (err) {
|
||||
log.error("Exporting to CSV has failed. Request url: %s", req.url, err);
|
||||
const errHtml =
|
||||
`<!doctype html>
|
||||
<html>
|
||||
<body>There was an unexpected error while generating a csv file.</body>
|
||||
</html>
|
||||
`;
|
||||
res.status(400).send(errHtml);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -18,7 +56,7 @@ bluebird.promisifyAll(csv);
|
||||
* @param {Filter[]} filters (optional) - filters defined from ui.
|
||||
* @return {Promise<string>} Promise for the resulting CSV.
|
||||
*/
|
||||
export async function makeCSV(
|
||||
export async function makeCSVFromViewSection(
|
||||
activeDoc: ActiveDoc,
|
||||
viewSectionId: number,
|
||||
sortOrder: number[],
|
||||
@@ -30,6 +68,31 @@ export async function makeCSV(
|
||||
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.getTable('_grist_Tables')!;
|
||||
const tableRef = tables.findRow('tableId', tableId);
|
||||
|
||||
const data = await exportTable(activeDoc, tableRef, req);
|
||||
const file = convertToCsv(data);
|
||||
return file;
|
||||
}
|
||||
|
||||
function convertToCsv({
|
||||
rowIds,
|
||||
access,
|
||||
|
||||
@@ -3,6 +3,37 @@ import {createExcelFormatter} from 'app/server/lib/ExcelFormatter';
|
||||
import {ExportData, exportDoc} from 'app/server/lib/Export';
|
||||
import {Alignment, Border, Fill, Workbook} from 'exceljs';
|
||||
import * as express from 'express';
|
||||
import * as log from 'app/server/lib/log';
|
||||
import * as contentDisposition from 'content-disposition';
|
||||
|
||||
export interface DownloadXLSXOptions {
|
||||
filename: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `activeDoc` to CSV and sends the converted data through `res`.
|
||||
*/
|
||||
export async function downloadXLSX(activeDoc: ActiveDoc, req: express.Request,
|
||||
res: express.Response, {filename}: DownloadXLSXOptions) {
|
||||
log.debug(`Generating .xlsx file`);
|
||||
try {
|
||||
const data = await makeXLSX(activeDoc, req);
|
||||
res.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', contentDisposition(filename + '.xlsx'));
|
||||
res.send(data);
|
||||
log.debug('XLSX file generated');
|
||||
} catch (err) {
|
||||
log.error("Exporting to XLSX has failed. Request url: %s", req.url, err);
|
||||
// send a generic information to client
|
||||
const errHtml =
|
||||
`<!doctype html>
|
||||
<html>
|
||||
<body>There was an unexpected error while generating a xlsx file.</body>
|
||||
</html>
|
||||
`;
|
||||
res.status(400).send(errHtml);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates excel document with all tables from an active Grist document.
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import {parseExportFileName, parseExportParameters} from 'app/server/lib/Export';
|
||||
import {makeCSV} from 'app/server/lib/ExportCSV';
|
||||
import {makeXLSX} from 'app/server/lib/ExportXLSX';
|
||||
import * as log from 'app/server/lib/log';
|
||||
import * as contentDisposition from 'content-disposition';
|
||||
import * as express from 'express';
|
||||
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
||||
|
||||
export async function generateCSV(activeDoc: ActiveDoc, req: express.Request, res: express.Response) {
|
||||
log.info('Generating .csv file...');
|
||||
const {
|
||||
viewSectionId,
|
||||
filters,
|
||||
sortOrder
|
||||
} = parseExportParameters(req);
|
||||
|
||||
// Generate a decent name for the exported file.
|
||||
const name = parseExportFileName(activeDoc, req);
|
||||
try {
|
||||
const data = await makeCSV(activeDoc, viewSectionId, sortOrder, filters, req);
|
||||
res.set('Content-Type', 'text/csv');
|
||||
res.setHeader('Content-Disposition', contentDisposition(name + '.csv'));
|
||||
res.send(data);
|
||||
} catch (err) {
|
||||
log.error("Exporting to CSV has failed. Request url: %s", req.url, err);
|
||||
const errHtml =
|
||||
`<!doctype html>
|
||||
<html>
|
||||
<body>There was an unexpected error while generating a csv file.</body>
|
||||
</html>
|
||||
`;
|
||||
res.status(400).send(errHtml);
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateXLSX(activeDoc: ActiveDoc, req: express.Request, res: express.Response) {
|
||||
log.debug(`Generating .xlsx file`);
|
||||
try {
|
||||
const data = await makeXLSX(activeDoc, req);
|
||||
res.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
res.setHeader('Content-Disposition', contentDisposition((req.query.title || activeDoc.docName) + '.xlsx'));
|
||||
res.send(data);
|
||||
log.debug('XLSX file generated');
|
||||
} catch (err) {
|
||||
log.error("Exporting to XLSX has failed. Request url: %s", req.url, err);
|
||||
// send a generic information to client
|
||||
const errHtml =
|
||||
`<!doctype html>
|
||||
<html>
|
||||
<body>There was an unexpected error while generating a xlsx file.</body>
|
||||
</html>
|
||||
`;
|
||||
res.status(400).send(errHtml);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user