Add new export experience (#12201)

* Use script setup

* Start on export dialog

* Use new system field interface, replace limit with numeric input

* Set placeholder

* Add sort config

* Use folder picker, correct layoutQuery use

* Add local download button

* Allow writing exports to file

* Add notification after export

* Fix sort config, use new export endpoint

* Setup notification hints

* Add information notice

* Fix local limit, cancel button

* Add (basic) docs for export functionality

* Fix json export file format

* Implement xml batch stitching

* Resolve review points
This commit is contained in:
Rijk van Zanten
2022-03-17 15:43:45 -04:00
committed by GitHub
parent aca9ff9709
commit 1c3e94d830
25 changed files with 1095 additions and 416 deletions

View File

@@ -1,14 +1,13 @@
import { RequestHandler } from 'express';
import { Transform, transforms } from 'json2csv';
import ms from 'ms';
import { PassThrough } from 'stream';
import { getCache } from '../cache';
import env from '../env';
import asyncHandler from '../utils/async-handler';
import { getCacheKey } from '../utils/get-cache-key';
import { parse as toXML } from 'js2xmlparser';
import { getCacheControlHeader } from '../utils/get-cache-headers';
import logger from '../logger';
import { ExportService } from '../services';
import { getDateFormatted } from '../utils/get-date-formatted';
export const respond: RequestHandler = asyncHandler(async (req, res) => {
const { cache } = getCache();
@@ -38,6 +37,8 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => {
}
if (req.sanitizedQuery.export) {
const exportService = new ExportService({ accountability: req.accountability, schema: req.schema });
let filename = '';
if (req.collection) {
@@ -51,30 +52,19 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => {
if (req.sanitizedQuery.export === 'json') {
res.attachment(`${filename}.json`);
res.set('Content-Type', 'application/json');
return res.status(200).send(JSON.stringify(res.locals.payload?.data || null, null, '\t'));
return res.status(200).send(exportService.transform(res.locals.payload?.data, 'json'));
}
if (req.sanitizedQuery.export === 'xml') {
res.attachment(`${filename}.xml`);
res.set('Content-Type', 'text/xml');
return res.status(200).send(toXML('data', res.locals.payload?.data));
return res.status(200).send(exportService.transform(res.locals.payload?.data, 'xml'));
}
if (req.sanitizedQuery.export === 'csv') {
res.attachment(`${filename}.csv`);
res.set('Content-Type', 'text/csv');
const stream = new PassThrough();
if (!res.locals.payload?.data || res.locals.payload.data.length === 0) {
stream.end(Buffer.from(''));
return stream.pipe(res);
} else {
stream.end(Buffer.from(JSON.stringify(res.locals.payload.data), 'utf-8'));
const json2csv = new Transform({
transforms: [transforms.flatten({ separator: '.' })],
});
return stream.pipe(json2csv).pipe(res);
}
return res.status(200).send(exportService.transform(res.locals.payload?.data, 'csv'));
}
}
@@ -86,15 +76,3 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => {
return res.status(204).end();
}
});
function getDateFormatted() {
const date = new Date();
let month = String(date.getMonth() + 1);
if (month.length === 1) month = '0' + month;
let day = String(date.getDate());
if (day.length === 1) day = '0' + day;
return `${date.getFullYear()}-${month}-${day} at ${date.getHours()}.${date.getMinutes()}.${date.getSeconds()}`;
}