mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) updates from grist-core
This commit is contained in:
commit
b1921209df
@ -830,6 +830,16 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getCsvLink() {
|
public getCsvLink() {
|
||||||
|
const params = this._getDocApiDownloadParams();
|
||||||
|
return this.docPageModel.appModel.api.getDocAPI(this.docId()).getDownloadCsvUrl(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getXlsxActiveViewLink() {
|
||||||
|
const params = this._getDocApiDownloadParams();
|
||||||
|
return this.docPageModel.appModel.api.getDocAPI(this.docId()).getDownloadXlsxUrl(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDocApiDownloadParams() {
|
||||||
const filters = this.viewModel.activeSection.peek().activeFilters.get().map(filterInfo => ({
|
const filters = this.viewModel.activeSection.peek().activeFilters.get().map(filterInfo => ({
|
||||||
colRef : filterInfo.fieldOrColumn.origCol().origColRef(),
|
colRef : filterInfo.fieldOrColumn.origCol().origColRef(),
|
||||||
filter : filterInfo.filter()
|
filter : filterInfo.filter()
|
||||||
@ -841,8 +851,7 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
activeSortSpec: JSON.stringify(this.viewModel.activeSection().activeSortSpec()),
|
activeSortSpec: JSON.stringify(this.viewModel.activeSection().activeSortSpec()),
|
||||||
filters : JSON.stringify(filters),
|
filters : JSON.stringify(filters),
|
||||||
};
|
};
|
||||||
|
return params;
|
||||||
return this.docPageModel.appModel.api.getDocAPI(this.docId()).getDownloadCsvUrl(params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasGranularAccessRules(): boolean {
|
public hasGranularAccessRules(): boolean {
|
||||||
|
@ -50,7 +50,7 @@ export function buildNameConfig(owner: MultiHolder, origColumn: ColumnRec, curso
|
|||||||
),
|
),
|
||||||
cssInput(editableColId,
|
cssInput(editableColId,
|
||||||
saveColId,
|
saveColId,
|
||||||
dom.boolAttr('disabled', use => use(origColumn.disableModify) || !use(origColumn.untieColIdFromLabel)),
|
dom.boolAttr(`readonly`, use => use(origColumn.disableModify) || !use(origColumn.untieColIdFromLabel)),
|
||||||
cssCodeBlock.cls(''),
|
cssCodeBlock.cls(''),
|
||||||
{style: 'margin-top: 8px'},
|
{style: 'margin-top: 8px'},
|
||||||
testId('field-col-id'),
|
testId('field-col-id'),
|
||||||
@ -396,7 +396,7 @@ const cssInput = styled(textInput, `
|
|||||||
color: ${theme.inputPlaceholderFg};
|
color: ${theme.inputPlaceholderFg};
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&[readonly] {
|
||||||
background-color: ${theme.inputDisabledBg};
|
background-color: ${theme.inputDisabledBg};
|
||||||
color: ${theme.inputDisabledFg};
|
color: ${theme.inputDisabledFg};
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,8 @@ export function makeViewLayoutMenu(viewSection: ViewSectionRec, isReadonly: bool
|
|||||||
menuItemCmd(allCommands.printSection, 'Print widget', testId('print-section')),
|
menuItemCmd(allCommands.printSection, 'Print widget', testId('print-section')),
|
||||||
menuItemLink({ href: gristDoc.getCsvLink(), target: '_blank', download: ''},
|
menuItemLink({ href: gristDoc.getCsvLink(), target: '_blank', download: ''},
|
||||||
'Download as CSV', testId('download-section')),
|
'Download as CSV', testId('download-section')),
|
||||||
|
menuItemLink({ href: gristDoc.getXlsxActiveViewLink(), target: '_blank', download: ''},
|
||||||
|
'Download as XLSX', testId('download-section')),
|
||||||
dom.maybe((use) => ['detail', 'single'].includes(use(viewSection.parentKey)), () =>
|
dom.maybe((use) => ['detail', 'single'].includes(use(viewSection.parentKey)), () =>
|
||||||
menuItemCmd(allCommands.editLayout, 'Edit Card Layout',
|
menuItemCmd(allCommands.editLayout, 'Edit Card Layout',
|
||||||
dom.cls('disabled', isReadonly))),
|
dom.cls('disabled', isReadonly))),
|
||||||
|
@ -346,9 +346,9 @@ export interface UserAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameters for the download CSV endpoint (/download/csv).
|
* Parameters for the download CSV and XLSX endpoint (/download/csv & /download/csv).
|
||||||
*/
|
*/
|
||||||
export interface DownloadCsvParams {
|
export interface DownloadDocParams {
|
||||||
tableId: string;
|
tableId: string;
|
||||||
viewSection?: number;
|
viewSection?: number;
|
||||||
activeSortSpec?: string;
|
activeSortSpec?: string;
|
||||||
@ -391,8 +391,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;
|
||||||
getDownloadXlsxUrl(): string;
|
getDownloadXlsxUrl(params?: DownloadDocParams): string;
|
||||||
getDownloadCsvUrl(params: DownloadCsvParams): string;
|
getDownloadCsvUrl(params: DownloadDocParams): 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).
|
||||||
@ -866,11 +866,11 @@ export class DocAPIImpl extends BaseAPI implements DocAPI {
|
|||||||
return this._url + `/download?template=${Number(template)}`;
|
return this._url + `/download?template=${Number(template)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDownloadXlsxUrl() {
|
public getDownloadXlsxUrl(params: DownloadDocParams) {
|
||||||
return this._url + '/download/xlsx';
|
return this._url + '/download/xlsx?' + encodeQueryParams({...params});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDownloadCsvUrl(params: DownloadCsvParams) {
|
public getDownloadCsvUrl(params: DownloadDocParams) {
|
||||||
// We spread `params` to work around TypeScript being overly cautious.
|
// We spread `params` to work around TypeScript being overly cautious.
|
||||||
return this._url + '/download/csv?' + encodeQueryParams({...params});
|
return this._url + '/download/csv?' + encodeQueryParams({...params});
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,9 @@ import {DocManager} from "app/server/lib/DocManager";
|
|||||||
import {docSessionFromRequest, makeExceptionalDocSession, OptDocSession} from "app/server/lib/DocSession";
|
import {docSessionFromRequest, makeExceptionalDocSession, OptDocSession} from "app/server/lib/DocSession";
|
||||||
import {DocWorker} from "app/server/lib/DocWorker";
|
import {DocWorker} from "app/server/lib/DocWorker";
|
||||||
import {IDocWorkerMap} from "app/server/lib/DocWorkerMap";
|
import {IDocWorkerMap} from "app/server/lib/DocWorkerMap";
|
||||||
import {parseExportParameters} from "app/server/lib/Export";
|
import {parseExportParameters, DownloadOptions} from "app/server/lib/Export";
|
||||||
import {downloadCSV, DownloadCSVOptions} from "app/server/lib/ExportCSV";
|
import {downloadCSV} from "app/server/lib/ExportCSV";
|
||||||
import {downloadXLSX, DownloadXLSXOptions} from "app/server/lib/ExportXLSX";
|
import {downloadXLSX} from "app/server/lib/ExportXLSX";
|
||||||
import {expressWrap} from 'app/server/lib/expressWrap';
|
import {expressWrap} from 'app/server/lib/expressWrap';
|
||||||
import {filterDocumentInPlace} from "app/server/lib/filterUtils";
|
import {filterDocumentInPlace} from "app/server/lib/filterUtils";
|
||||||
import {googleAuthTokenMiddleware} from "app/server/lib/GoogleAuth";
|
import {googleAuthTokenMiddleware} from "app/server/lib/GoogleAuth";
|
||||||
@ -736,24 +736,21 @@ export class DocWorkerApi {
|
|||||||
this._app.get('/api/docs/:docId/download/csv', canView, withDoc(async (activeDoc, req, res) => {
|
this._app.get('/api/docs/:docId/download/csv', canView, withDoc(async (activeDoc, req, res) => {
|
||||||
// Query DB for doc metadata to get the doc title.
|
// Query DB for doc metadata to get the doc title.
|
||||||
const {name: docTitle} = await this._dbManager.getDoc(req);
|
const {name: docTitle} = await this._dbManager.getDoc(req);
|
||||||
|
const options = this._getDownloadOptions(req, docTitle);
|
||||||
const params = parseExportParameters(req);
|
|
||||||
const filename = docTitle + (params.tableId === docTitle ? '' : '-' + params.tableId);
|
|
||||||
|
|
||||||
const options: DownloadCSVOptions = {
|
|
||||||
...params,
|
|
||||||
filename,
|
|
||||||
};
|
|
||||||
|
|
||||||
await downloadCSV(activeDoc, req, res, options);
|
await downloadCSV(activeDoc, req, res, options);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._app.get('/api/docs/:docId/download/xlsx', canView, withDoc(async (activeDoc, req, res) => {
|
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).
|
// Query DB for doc metadata to get the doc title (to use as the filename).
|
||||||
const {name: filename} = await this._dbManager.getDoc(req);
|
const {name: docTitle} = await this._dbManager.getDoc(req);
|
||||||
|
const options = !_.isEmpty(req.query) ? this._getDownloadOptions(req, docTitle) : {
|
||||||
const options: DownloadXLSXOptions = {filename};
|
filename: docTitle,
|
||||||
|
tableId: '',
|
||||||
|
viewSectionId: undefined,
|
||||||
|
filters: [],
|
||||||
|
sortOrder: [],
|
||||||
|
};
|
||||||
await downloadXLSX(activeDoc, req, res, options);
|
await downloadXLSX(activeDoc, req, res, options);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -815,6 +812,15 @@ export class DocWorkerApi {
|
|||||||
return docAuth.docId!;
|
return docAuth.docId!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getDownloadOptions(req: Request, name: string): DownloadOptions {
|
||||||
|
const params = parseExportParameters(req);
|
||||||
|
const options: DownloadOptions = {
|
||||||
|
...params,
|
||||||
|
filename: name + (params.tableId === name ? '' : '-' + params.tableId),
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
private _getActiveDoc(req: RequestWithLogin): Promise<ActiveDoc> {
|
private _getActiveDoc(req: RequestWithLogin): Promise<ActiveDoc> {
|
||||||
return this._docManager.fetchDoc(docSessionFromRequest(req), getDocId(req));
|
return this._docManager.fetchDoc(docSessionFromRequest(req), getDocId(req));
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,17 @@ export interface ExportParameters {
|
|||||||
filters: Filter[];
|
filters: Filter[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options parameters for CSV and XLSX export functions.
|
||||||
|
*/
|
||||||
|
export interface DownloadOptions {
|
||||||
|
filename: string;
|
||||||
|
tableId: string;
|
||||||
|
viewSectionId: number | undefined;
|
||||||
|
filters: Filter[];
|
||||||
|
sortOrder: number[];
|
||||||
|
}
|
||||||
|
|
||||||
interface FilteredMetaTables {
|
interface FilteredMetaTables {
|
||||||
[tableId: string]: TableDataAction;
|
[tableId: string]: TableDataAction;
|
||||||
}
|
}
|
||||||
@ -97,7 +108,7 @@ export function parseExportParameters(req: express.Request): ExportParameters {
|
|||||||
tableId,
|
tableId,
|
||||||
viewSectionId,
|
viewSectionId,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
filters
|
filters,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,13 @@
|
|||||||
import {ApiError} from 'app/common/ApiError';
|
import {ApiError} from 'app/common/ApiError';
|
||||||
import {createFormatter} from 'app/common/ValueFormatter';
|
import {createFormatter} from 'app/common/ValueFormatter';
|
||||||
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
||||||
import {ExportData, exportSection, exportTable, Filter} from 'app/server/lib/Export';
|
import {ExportData, exportSection, exportTable, Filter, DownloadOptions} from 'app/server/lib/Export';
|
||||||
import log from 'app/server/lib/log';
|
import log from 'app/server/lib/log';
|
||||||
import * as bluebird from 'bluebird';
|
import * as bluebird from 'bluebird';
|
||||||
import contentDisposition from 'content-disposition';
|
import contentDisposition from 'content-disposition';
|
||||||
import csv from 'csv';
|
import csv from 'csv';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
|
|
||||||
export interface DownloadCSVOptions {
|
|
||||||
filename: string;
|
|
||||||
tableId: string;
|
|
||||||
viewSectionId: number | undefined;
|
|
||||||
filters: Filter[];
|
|
||||||
sortOrder: number[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// promisify csv
|
// promisify csv
|
||||||
bluebird.promisifyAll(csv);
|
bluebird.promisifyAll(csv);
|
||||||
|
|
||||||
@ -23,7 +15,7 @@ bluebird.promisifyAll(csv);
|
|||||||
* Converts `activeDoc` to a CSV and sends the converted data through `res`.
|
* Converts `activeDoc` to a CSV and sends the converted data through `res`.
|
||||||
*/
|
*/
|
||||||
export async function downloadCSV(activeDoc: ActiveDoc, req: express.Request,
|
export async function downloadCSV(activeDoc: ActiveDoc, req: express.Request,
|
||||||
res: express.Response, options: DownloadCSVOptions) {
|
res: express.Response, options: DownloadOptions) {
|
||||||
log.info('Generating .csv file...');
|
log.info('Generating .csv file...');
|
||||||
const {filename, tableId, viewSectionId, filters, sortOrder} = options;
|
const {filename, tableId, viewSectionId, filters, sortOrder} = options;
|
||||||
const data = viewSectionId ?
|
const data = viewSectionId ?
|
||||||
|
@ -1,34 +1,86 @@
|
|||||||
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
||||||
import {createExcelFormatter} from 'app/server/lib/ExcelFormatter';
|
import {createExcelFormatter} from 'app/server/lib/ExcelFormatter';
|
||||||
import {ExportData, exportDoc} from 'app/server/lib/Export';
|
import {ExportData, exportDoc, DownloadOptions, exportSection, exportTable, Filter} from 'app/server/lib/Export';
|
||||||
import {Alignment, Border, Fill, Workbook} from 'exceljs';
|
import {Alignment, Border, Fill, Workbook} from 'exceljs';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import log from 'app/server/lib/log';
|
import log from 'app/server/lib/log';
|
||||||
import contentDisposition from 'content-disposition';
|
import contentDisposition from 'content-disposition';
|
||||||
|
import { ApiError } from 'app/common/ApiError';
|
||||||
export interface DownloadXLSXOptions {
|
|
||||||
filename: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts `activeDoc` to CSV and sends the converted data through `res`.
|
* Converts `activeDoc` to XLSX and sends the converted data through `res`.
|
||||||
*/
|
*/
|
||||||
export async function downloadXLSX(activeDoc: ActiveDoc, req: express.Request,
|
export async function downloadXLSX(activeDoc: ActiveDoc, req: express.Request,
|
||||||
res: express.Response, {filename}: DownloadXLSXOptions) {
|
res: express.Response, options: DownloadOptions) {
|
||||||
log.debug(`Generating .xlsx file`);
|
log.debug(`Generating .xlsx file`);
|
||||||
const data = await makeXLSX(activeDoc, req);
|
const {filename, tableId, viewSectionId, filters, sortOrder} = options;
|
||||||
|
// hanlding 3 cases : full XLSX export (full file), view xlsx export, table xlsx export
|
||||||
|
const data = viewSectionId ? await makeXLSXFromViewSection(activeDoc, viewSectionId, sortOrder, filters, req)
|
||||||
|
: tableId ? await makeXLSXFromTable(activeDoc, tableId, req)
|
||||||
|
: await makeXLSX(activeDoc, req);
|
||||||
|
|
||||||
res.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
res.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||||
res.setHeader('Content-Disposition', contentDisposition(filename + '.xlsx'));
|
res.setHeader('Content-Disposition', contentDisposition(filename + '.xlsx'));
|
||||||
res.send(data);
|
res.send(data);
|
||||||
log.debug('XLSX file generated');
|
log.debug('XLSX file generated');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a XLSX stream of a view section that can be transformed or parsed.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
export async function makeXLSXFromViewSection(
|
||||||
|
activeDoc: ActiveDoc,
|
||||||
|
viewSectionId: number,
|
||||||
|
sortOrder: number[],
|
||||||
|
filters: Filter[],
|
||||||
|
req: express.Request,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const data = await exportSection(activeDoc, viewSectionId, sortOrder, filters, req);
|
||||||
|
const xlsx = await convertToExcel([data], req.hostname === 'localhost');
|
||||||
|
return xlsx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a XLSX 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.
|
||||||
|
*/
|
||||||
|
export async function makeXLSXFromTable(
|
||||||
|
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 XLSX 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 xlsx = await convertToExcel([data], req.hostname === 'localhost');
|
||||||
|
return xlsx;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates excel document with all tables from an active Grist document.
|
* Creates excel document with all tables from an active Grist document.
|
||||||
*/
|
*/
|
||||||
export async function makeXLSX(
|
export async function makeXLSX(
|
||||||
activeDoc: ActiveDoc,
|
activeDoc: ActiveDoc,
|
||||||
req: express.Request): Promise<ArrayBuffer> {
|
req: express.Request,
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
const content = await exportDoc(activeDoc, req);
|
const content = await exportDoc(activeDoc, req);
|
||||||
const data = await convertToExcel(content, req.hostname === 'localhost');
|
const data = await convertToExcel(content, req.hostname === 'localhost');
|
||||||
return data;
|
return data;
|
||||||
|
@ -1804,6 +1804,39 @@ function testDocApi() {
|
|||||||
assert.deepEqual(resp.data, { error: 'tableId parameter should be a string: undefined' });
|
assert.deepEqual(resp.data, { error: 'tableId parameter should be a string: undefined' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("GET /docs/{did}/download/xlsx serves XLSX-encoded document", async function() {
|
||||||
|
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/download/xlsx?tableId=Table1`, chimpy);
|
||||||
|
assert.equal(resp.status, 200);
|
||||||
|
assert.notEqual(resp.data, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("GET /docs/{did}/download/xlsx respects permissions", async function() {
|
||||||
|
// kiwi has no access to TestDoc
|
||||||
|
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.TestDoc}/download/xlsx?tableId=Table1`, kiwi);
|
||||||
|
assert.equal(resp.status, 403);
|
||||||
|
assert.deepEqual(resp.data, { error: 'No view access' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("GET /docs/{did}/download/xlsx returns 404 if tableId is invalid", async function() {
|
||||||
|
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.TestDoc}/download/xlsx?tableId=MissingTableId`, chimpy);
|
||||||
|
assert.equal(resp.status, 404);
|
||||||
|
assert.deepEqual(resp.data, { error: 'Table MissingTableId not found.' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("GET /docs/{did}/download/xlsx returns 404 if viewSectionId is invalid", async function() {
|
||||||
|
const resp = await axios.get(
|
||||||
|
`${serverUrl}/api/docs/${docIds.TestDoc}/download/xlsx?tableId=Table1&viewSection=9999`, chimpy);
|
||||||
|
assert.equal(resp.status, 404);
|
||||||
|
assert.deepEqual(resp.data, { error: 'No record 9999 in table _grist_Views_section' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("GET /docs/{did}/download/xlsx returns 200 if tableId is missing", async function() {
|
||||||
|
const resp = await axios.get(
|
||||||
|
`${serverUrl}/api/docs/${docIds.TestDoc}/download/xlsx`, chimpy);
|
||||||
|
assert.equal(resp.status, 200);
|
||||||
|
assert.notEqual(resp.data, null);
|
||||||
|
});
|
||||||
|
|
||||||
it('POST /workspaces/{wid}/import handles empty filenames', async function() {
|
it('POST /workspaces/{wid}/import handles empty filenames', async function() {
|
||||||
if (!process.env.TEST_REDIS_URL) { this.skip(); }
|
if (!process.env.TEST_REDIS_URL) { this.skip(); }
|
||||||
const worker1 = await userApi.getWorkerAPI('import');
|
const worker1 = await userApi.getWorkerAPI('import');
|
||||||
|
Loading…
Reference in New Issue
Block a user