mirror of https://github.com/nocodb/nocodb
Pranav C
3 years ago
8 changed files with 707 additions and 401 deletions
@ -0,0 +1,44 @@ |
|||||||
|
import { Request, Response, Router } from 'express'; |
||||||
|
import ncMetaAclMw from '../../helpers/ncMetaAclMw'; |
||||||
|
import { |
||||||
|
extractCsvData, |
||||||
|
getViewAndModelFromRequestByAliasOrId |
||||||
|
} from './helpers'; |
||||||
|
import papaparse from 'papaparse'; |
||||||
|
|
||||||
|
async function csvDataExport(req: Request, res: Response) { |
||||||
|
const { model, view } = await getViewAndModelFromRequestByAliasOrId(req); |
||||||
|
|
||||||
|
const { offset, csvRows, elapsed } = await extractCsvData(model, view, req); |
||||||
|
|
||||||
|
const data = papaparse.unparse( |
||||||
|
{ |
||||||
|
fields: model.columns.map(c => c.title), |
||||||
|
data: csvRows |
||||||
|
}, |
||||||
|
{ |
||||||
|
escapeFormulae: true |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
res.set({ |
||||||
|
'Access-Control-Expose-Headers': 'nc-export-offset', |
||||||
|
'nc-export-offset': offset, |
||||||
|
'nc-export-elapsed-time': elapsed, |
||||||
|
'Content-Disposition': `attachment; filename="${view.title}-export.csv"` |
||||||
|
}); |
||||||
|
res.send(data); |
||||||
|
} |
||||||
|
|
||||||
|
const router = Router({ mergeParams: true }); |
||||||
|
|
||||||
|
router.get( |
||||||
|
'/api/v1/db/data/:orgs/:projectName/:tableName/export/csv', |
||||||
|
ncMetaAclMw(csvDataExport, 'csvDataExport') |
||||||
|
); |
||||||
|
router.get( |
||||||
|
'/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/export/csv', |
||||||
|
ncMetaAclMw(csvDataExport, 'csvDataExport') |
||||||
|
); |
||||||
|
|
||||||
|
export default router; |
@ -0,0 +1,155 @@ |
|||||||
|
import Project from '../../../../noco-models/Project'; |
||||||
|
import Model from '../../../../noco-models/Model'; |
||||||
|
import View from '../../../../noco-models/View'; |
||||||
|
import { NcError } from '../../helpers/catchError'; |
||||||
|
import { Request } from 'express'; |
||||||
|
import Base from '../../../../noco-models/Base'; |
||||||
|
import NcConnectionMgrv2 from '../../../common/NcConnectionMgrv2'; |
||||||
|
import { isSystemColumn, UITypes } from 'nocodb-sdk'; |
||||||
|
|
||||||
|
import { nocoExecute } from 'nc-help'; |
||||||
|
import Column from '../../../../noco-models/Column'; |
||||||
|
import LookupColumn from '../../../../noco-models/LookupColumn'; |
||||||
|
import LinkToAnotherRecordColumn from '../../../../noco-models/LinkToAnotherRecordColumn'; |
||||||
|
|
||||||
|
export async function getViewAndModelFromRequestByAliasOrId( |
||||||
|
req: |
||||||
|
| Request<{ projectName: string; tableName: string; viewName?: string }> |
||||||
|
| Request |
||||||
|
) { |
||||||
|
let project = await Project.getWithInfoByTitle(req.params.projectName); |
||||||
|
|
||||||
|
if (!project) { |
||||||
|
project = await Project.getWithInfo(req.params.projectName); |
||||||
|
} |
||||||
|
|
||||||
|
const model = await Model.getByAliasOrId({ |
||||||
|
project_id: project.id, |
||||||
|
base_id: project.bases?.[0]?.id, |
||||||
|
aliasOrId: req.params.tableName |
||||||
|
}); |
||||||
|
const view = |
||||||
|
req.params.viewName && |
||||||
|
(await View.getByTitleOrId({ |
||||||
|
titleOrId: req.params.viewName, |
||||||
|
fk_model_id: model.id |
||||||
|
})); |
||||||
|
if (!model) NcError.notFound('Table not found'); |
||||||
|
return { model, view }; |
||||||
|
} |
||||||
|
|
||||||
|
export async function extractCsvData(model: Model, view: View, req: Request) { |
||||||
|
const base = await Base.get(model.base_id); |
||||||
|
const baseModel = await Model.getBaseModelSQL({ |
||||||
|
id: model.id, |
||||||
|
viewId: view?.id, |
||||||
|
dbDriver: NcConnectionMgrv2.get(base) |
||||||
|
}); |
||||||
|
|
||||||
|
let offset = +req.query.offset || 0; |
||||||
|
const limit = 100; |
||||||
|
// const size = +process.env.NC_EXPORT_MAX_SIZE || 1024;
|
||||||
|
const timeout = +process.env.NC_EXPORT_MAX_TIMEOUT || 5000; |
||||||
|
const csvRows = []; |
||||||
|
const startTime = process.hrtime(); |
||||||
|
let elapsed, temp; |
||||||
|
|
||||||
|
for ( |
||||||
|
elapsed = 0; |
||||||
|
elapsed < timeout; |
||||||
|
offset += limit, |
||||||
|
temp = process.hrtime(startTime), |
||||||
|
elapsed = temp[0] * 1000 + temp[1] / 1000000 |
||||||
|
) { |
||||||
|
const rows = await nocoExecute( |
||||||
|
await baseModel.defaultResolverReq(req.query, false, false), |
||||||
|
await baseModel.list({ ...req.query, offset, limit }), |
||||||
|
{}, |
||||||
|
req.query |
||||||
|
); |
||||||
|
|
||||||
|
if (!rows?.length) { |
||||||
|
offset = -1; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
for (const row of rows) { |
||||||
|
const csvRow = { ...row }; |
||||||
|
|
||||||
|
for (const column of view.model.columns) { |
||||||
|
if (isSystemColumn(column) && !view.show_system_fields) continue; |
||||||
|
csvRow[column.title] = await serializeCellValue({ |
||||||
|
value: row[column.title], |
||||||
|
column |
||||||
|
}); |
||||||
|
} |
||||||
|
csvRows.push(csvRow); |
||||||
|
} |
||||||
|
} |
||||||
|
return { offset, csvRows, elapsed }; |
||||||
|
} |
||||||
|
|
||||||
|
export async function serializeCellValue({ |
||||||
|
value, |
||||||
|
column |
||||||
|
}: { |
||||||
|
column?: Column; |
||||||
|
value: any; |
||||||
|
}) { |
||||||
|
if (!column) { |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
if (!value) return value; |
||||||
|
|
||||||
|
switch (column?.uidt) { |
||||||
|
case UITypes.Attachment: { |
||||||
|
let data = value; |
||||||
|
try { |
||||||
|
if (typeof value === 'string') { |
||||||
|
data = JSON.parse(value); |
||||||
|
} |
||||||
|
} catch {} |
||||||
|
|
||||||
|
return (data || []).map( |
||||||
|
attachment => |
||||||
|
`${encodeURI(attachment.title)}(${encodeURI(attachment.url)})` |
||||||
|
); |
||||||
|
} |
||||||
|
case UITypes.Lookup: |
||||||
|
{ |
||||||
|
const colOptions = await column.getColOptions<LookupColumn>(); |
||||||
|
const lookupColumn = await colOptions.getLookupColumn(); |
||||||
|
return ( |
||||||
|
await Promise.all( |
||||||
|
[...(Array.isArray(value) ? value : [value])].map(async v => |
||||||
|
serializeCellValue({ |
||||||
|
value: v, |
||||||
|
column: lookupColumn |
||||||
|
}) |
||||||
|
) |
||||||
|
) |
||||||
|
).join(', '); |
||||||
|
} |
||||||
|
break; |
||||||
|
case UITypes.LinkToAnotherRecord: |
||||||
|
{ |
||||||
|
const colOptions = await column.getColOptions< |
||||||
|
LinkToAnotherRecordColumn |
||||||
|
>(); |
||||||
|
const relatedModel = await colOptions.getRelatedTable(); |
||||||
|
await relatedModel.getColumns(); |
||||||
|
return [...(Array.isArray(value) ? value : [value])] |
||||||
|
.map(v => { |
||||||
|
return v[relatedModel.primaryValue?.title]; |
||||||
|
}) |
||||||
|
.join(', '); |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
if (value && typeof value === 'object') { |
||||||
|
return JSON.stringify(value); |
||||||
|
} |
||||||
|
return value; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue