(core) Update export CSV and Excel endpoints

Summary:
The endpoints for exporting CSV and Excel are now under
/api/docs/:docId/ and are forwarded to a doc worker for export.

The Share Menu has been updated to use the new endpoints.

Test Plan: No new tests. Existing tests that verify endpoints work correctly.

Reviewers: paulfitz

Reviewed By: paulfitz

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D3007
This commit is contained in:
George Gevoian 2021-08-30 13:06:40 -07:00
parent 5258fa649d
commit ef5da42378
7 changed files with 25 additions and 36 deletions

View File

@ -626,11 +626,11 @@ export class GristDoc extends DisposableWithEvents {
} }
public getXlsxLink() { public getXlsxLink() {
const baseUrl = this.docPageModel.appModel.api.getDocAPI(this.docId()).getGenerateXlsxUrl();
const params = { const params = {
...this.docComm.getUrlParams(),
title: this.docPageModel.currentDocTitle.get(), title: this.docPageModel.currentDocTitle.get(),
}; };
return this.docComm.docUrl(`gen_xlsx`) + '?' + encodeQueryParams(params); return baseUrl + '?' + encodeQueryParams(params);
} }
public getCsvLink() { public getCsvLink() {
@ -638,15 +638,16 @@ export class GristDoc extends DisposableWithEvents {
colRef : field.colRef.peek(), colRef : field.colRef.peek(),
filter : field.activeFilter.peek() filter : field.activeFilter.peek()
})); }));
const baseUrl = this.docPageModel.appModel.api.getDocAPI(this.docId()).getGenerateCsvUrl();
const params = { const params = {
...this.docComm.getUrlParams(),
title: this.docPageModel.currentDocTitle.get(), title: this.docPageModel.currentDocTitle.get(),
viewSection: this.viewModel.activeSectionId(), viewSection: this.viewModel.activeSectionId(),
tableId: this.viewModel.activeSection().table().tableId(), tableId: this.viewModel.activeSection().table().tableId(),
activeSortSpec: JSON.stringify(this.viewModel.activeSection().activeSortSpec()), activeSortSpec: JSON.stringify(this.viewModel.activeSection().activeSortSpec()),
filters : JSON.stringify(filters), filters : JSON.stringify(filters),
}; };
return this.docComm.docUrl(`gen_csv`) + '?' + encodeQueryParams(params); return baseUrl + '?' + encodeQueryParams(params);
} }
public hasGranularAccessRules(): boolean { public hasGranularAccessRules(): boolean {

View File

@ -347,6 +347,8 @@ export interface DocAPI {
// is HEAD, the result will contain a copy of any rows added or updated. // is HEAD, the result will contain a copy of any rows added or updated.
compareVersion(leftHash: string, rightHash: string): Promise<DocStateComparison>; compareVersion(leftHash: string, rightHash: string): Promise<DocStateComparison>;
getDownloadUrl(template?: boolean): string; getDownloadUrl(template?: boolean): string;
getGenerateXlsxUrl(): string;
getGenerateCsvUrl(): string;
/** /**
* Exports current document to the Google Drive as a spreadsheet file. To invoke this method, first * Exports current document to the Google Drive as a spreadsheet file. To invoke this method, first
* acquire "code" via Google Auth Endpoint (see ShareMenu.ts for an example). * acquire "code" via Google Auth Endpoint (see ShareMenu.ts for an example).
@ -790,6 +792,14 @@ export class DocAPIImpl extends BaseAPI implements DocAPI {
return this._url + `/download?template=${Number(template)}`; return this._url + `/download?template=${Number(template)}`;
} }
public getGenerateXlsxUrl() {
return this._url + '/gen-xlsx';
}
public getGenerateCsvUrl() {
return this._url + '/gen-csv';
}
public async sendToDrive(code: string, title: string): Promise<{url: string}> { public async sendToDrive(code: string, title: string): Promise<{url: string}> {
const url = new URL(`${this._url}/send-to-drive`); const url = new URL(`${this._url}/send-to-drive`);
url.searchParams.append('title', title); url.searchParams.append('title', title);

View File

@ -42,6 +42,8 @@ export class DocApiForwarder {
app.use('/api/docs/:docId/remove', withDoc); app.use('/api/docs/:docId/remove', withDoc);
app.delete('/api/docs/:docId', withDoc); app.delete('/api/docs/:docId', withDoc);
app.use('/api/docs/:docId/download', withDoc); app.use('/api/docs/:docId/download', withDoc);
app.use('/api/docs/:docId/gen-csv', withDoc);
app.use('/api/docs/:docId/gen-xlsx', withDoc);
app.use('/api/docs/:docId/send-to-drive', withDoc); app.use('/api/docs/:docId/send-to-drive', withDoc);
app.use('/api/docs/:docId/fork', withDoc); app.use('/api/docs/:docId/fork', withDoc);
app.use('/api/docs/:docId/create-fork', withDoc); app.use('/api/docs/:docId/create-fork', withDoc);

View File

@ -33,6 +33,7 @@ import { googleAuthTokenMiddleware } from "app/server/lib/GoogleAuth";
import * as _ from "lodash"; import * as _ from "lodash";
import {isRaisedException} from "app/common/gristTypes"; import {isRaisedException} from "app/common/gristTypes";
import {localeFromRequest} from "app/server/lib/ServerLocale"; import {localeFromRequest} from "app/server/lib/ServerLocale";
import { generateCSV, generateXLSX } from "app/server/serverMethods";
// Cap on the number of requests that can be outstanding on a single document via the // 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 // rest doc api. When this limit is exceeded, incoming requests receive an immediate
@ -565,6 +566,10 @@ export class DocWorkerApi {
res.json(result); res.json(result);
})); }));
this._app.get('/api/docs/:docId/gen-csv', canView, withDoc(generateCSV));
this._app.get('/api/docs/:docId/gen-xlsx', canView, withDoc(generateXLSX));
this._app.get('/api/docs/:docId/send-to-drive', canView, decodeGoogleToken, withDoc(exportToDrive)); this._app.get('/api/docs/:docId/send-to-drive', canView, decodeGoogleToken, withDoc(exportToDrive));
// Create a document. When an upload is included, it is imported as the initial // Create a document. When an upload is included, it is imported as the initial

View File

@ -13,7 +13,6 @@ import {IDocStorageManager} from 'app/server/lib/IDocStorageManager';
import * as log from 'app/server/lib/log'; import * as log from 'app/server/lib/log';
import {integerParam, optStringParam, stringParam} from 'app/server/lib/requestUtils'; import {integerParam, optStringParam, stringParam} from 'app/server/lib/requestUtils';
import {OpenMode, quoteIdent, SQLiteDB} from 'app/server/lib/SQLiteDB'; import {OpenMode, quoteIdent, SQLiteDB} from 'app/server/lib/SQLiteDB';
import {generateCSV, generateXLSX} from 'app/server/serverMethods';
import * as contentDisposition from 'content-disposition'; import * as contentDisposition from 'content-disposition';
import * as express from 'express'; import * as express from 'express';
import * as fse from 'fs-extra'; import * as fse from 'fs-extra';
@ -30,14 +29,6 @@ export class DocWorker {
this._comm = comm; this._comm = comm;
} }
public async getCSV(req: express.Request, res: express.Response): Promise<void> {
await generateCSV(req, res, this._comm);
}
public async getXLSX(req: express.Request, res: express.Response): Promise<void> {
await generateXLSX(req, res, this._comm);
}
public async getAttachment(req: express.Request, res: express.Response): Promise<void> { public async getAttachment(req: express.Request, res: express.Response): Promise<void> {
try { try {
const docSession = this._getDocSession(stringParam(req.query.clientId), const docSession = this._getDocSession(stringParam(req.query.clientId),

View File

@ -1266,14 +1266,6 @@ export class FlexServer implements GristServer {
// This doesn't check for doc access permissions because the request isn't tied to a document. // This doesn't check for doc access permissions because the request isn't tied to a document.
addUploadRoute(this, this.app, this._trustOriginsMiddleware, ...basicMiddleware); addUploadRoute(this, this.app, this._trustOriginsMiddleware, ...basicMiddleware);
this.app.get('/gen_csv', ...docAccessMiddleware, expressWrap(async (req, res) => {
return this._docWorker.getCSV(req, res);
}));
this.app.get('/gen_xlsx', ...docAccessMiddleware, expressWrap(async (req, res) => {
return this._docWorker.getXLSX(req, res);
}));
this.app.get('/attachment', ...docAccessMiddleware, this.app.get('/attachment', ...docAccessMiddleware,
expressWrap(async (req, res) => this._docWorker.getAttachment(req, res))); expressWrap(async (req, res) => this._docWorker.getAttachment(req, res)));
} }

View File

@ -1,13 +1,12 @@
import * as Comm from 'app/server/lib/Comm';
import {parseExportFileName, parseExportParameters} from 'app/server/lib/Export'; import {parseExportFileName, parseExportParameters} from 'app/server/lib/Export';
import {makeCSV} from 'app/server/lib/ExportCSV'; import {makeCSV} from 'app/server/lib/ExportCSV';
import {makeXLSX} from 'app/server/lib/ExportXLSX'; import {makeXLSX} from 'app/server/lib/ExportXLSX';
import * as log from 'app/server/lib/log'; import * as log from 'app/server/lib/log';
import {integerParam, stringParam} from 'app/server/lib/requestUtils';
import * as contentDisposition from 'content-disposition'; import * as contentDisposition from 'content-disposition';
import * as express from 'express'; import * as express from 'express';
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
export async function generateCSV(req: express.Request, res: express.Response, comm: Comm) { export async function generateCSV(activeDoc: ActiveDoc, req: express.Request, res: express.Response) {
log.info('Generating .csv file...'); log.info('Generating .csv file...');
const { const {
viewSectionId, viewSectionId,
@ -15,12 +14,6 @@ export async function generateCSV(req: express.Request, res: express.Response, c
sortOrder sortOrder
} = parseExportParameters(req); } = parseExportParameters(req);
const clientId = stringParam(req.query.clientId);
const docFD = integerParam(req.query.docFD);
const client = comm.getClient(clientId);
const docSession = client.getDocSession(docFD);
const activeDoc = docSession.activeDoc;
// Generate a decent name for the exported file. // Generate a decent name for the exported file.
const name = parseExportFileName(activeDoc, req); const name = parseExportFileName(activeDoc, req);
try { try {
@ -40,13 +33,8 @@ export async function generateCSV(req: express.Request, res: express.Response, c
} }
} }
export async function generateXLSX(req: express.Request, res: express.Response, comm: Comm) { export async function generateXLSX(activeDoc: ActiveDoc, req: express.Request, res: express.Response) {
log.debug(`Generating .xlsx file`); log.debug(`Generating .xlsx file`);
const clientId = stringParam(req.query.clientId);
const docFD = integerParam(req.query.docFD);
const client = comm.getClient(clientId);
const docSession = client.getDocSession(docFD);
const activeDoc = docSession.activeDoc;
try { try {
const data = await makeXLSX(activeDoc, req); const data = await makeXLSX(activeDoc, req);
res.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); res.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');