diff --git a/packages/builder/src/components/database/DataTable/ModelDataTable.svelte b/packages/builder/src/components/database/DataTable/ModelDataTable.svelte index 20f7a61c6b..c0d5ad6959 100644 --- a/packages/builder/src/components/database/DataTable/ModelDataTable.svelte +++ b/packages/builder/src/components/database/DataTable/ModelDataTable.svelte @@ -12,6 +12,7 @@ import RowPopover from "./popovers/Row.svelte" import ColumnPopover from "./popovers/Column.svelte" import ViewPopover from "./popovers/View.svelte" + import ExportPopover from "./popovers/Export.svelte" import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte" import EditRowPopover from "./popovers/EditRow.svelte" import * as api from "./api" @@ -51,6 +52,10 @@ .filter(id => !INTERNAL_HEADERS.includes(id)) $: schema = $backendUiStore.selectedModel.schema + $: modelView = { + schema: $backendUiStore.selectedModel.schema, + name: $backendUiStore.selectedView.name, + }
@@ -61,6 +66,7 @@ {#if Object.keys($backendUiStore.selectedModel.schema).length > 0} + {/if} diff --git a/packages/builder/src/components/database/DataTable/ViewDataTable.svelte b/packages/builder/src/components/database/DataTable/ViewDataTable.svelte index 7ffa2a1963..0958737b59 100644 --- a/packages/builder/src/components/database/DataTable/ViewDataTable.svelte +++ b/packages/builder/src/components/database/DataTable/ViewDataTable.svelte @@ -18,6 +18,7 @@ import CalculationPopover from "./popovers/Calculate.svelte" import GroupByPopover from "./popovers/GroupBy.svelte" import FilterPopover from "./popovers/Filter.svelte" + import ExportPopover from "./popovers/Export.svelte" export let view = {} @@ -51,4 +52,5 @@ {#if view.calculation} {/if} + diff --git a/packages/builder/src/components/database/DataTable/popovers/Export.svelte b/packages/builder/src/components/database/DataTable/popovers/Export.svelte new file mode 100644 index 0000000000..b8f8847a0a --- /dev/null +++ b/packages/builder/src/components/database/DataTable/popovers/Export.svelte @@ -0,0 +1,71 @@ + + +
+ + + Export + +
+ +
Export Format
+ +
+ + +
+
+ + diff --git a/packages/server/package.json b/packages/server/package.json index 2d2deca1a9..9d808e0d83 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -92,9 +92,6 @@ "server-destroy": "^1.0.1", "supertest": "^4.0.2" }, - "nodemonConfig": { - "delay": "1000" - }, "jest": { "testEnvironment": "node", "setupFiles": [ diff --git a/packages/server/src/api/controllers/view/exporters.js b/packages/server/src/api/controllers/view/exporters.js new file mode 100644 index 0000000000..c1e5679d2a --- /dev/null +++ b/packages/server/src/api/controllers/view/exporters.js @@ -0,0 +1,14 @@ +exports.csv = function(headers, rows) { + let csv = headers.map(key => `"${key}"`).join(",") + + for (let row of rows) { + csv = `${csv}\n${headers + .map(header => `"${row[header]}"`.trim()) + .join(",")}` + } + return csv +} + +exports.json = function(headers, rows) { + return JSON.stringify(rows, undefined, 2) +} diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js index b04b59e32b..260197aae7 100644 --- a/packages/server/src/api/controllers/view/index.js +++ b/packages/server/src/api/controllers/view/index.js @@ -1,5 +1,9 @@ const CouchDB = require("../../../db") const viewTemplate = require("./viewBuilder") +const fs = require("fs") +const path = require("path") +const os = require("os") +const exporters = require("./exporters") const controller = { fetch: async ctx => { @@ -79,6 +83,48 @@ const controller = { ctx.body = view ctx.message = `View ${ctx.params.viewName} saved successfully.` }, + exportView: async ctx => { + const db = new CouchDB(ctx.user.instanceId) + const view = ctx.request.body + const format = ctx.query.format + + // fetch records for the view + const response = await db.query(`database/${view.name}`, { + include_docs: !view.calculation, + group: view.groupBy, + }) + + if (view.calculation === "stats") { + response.rows = response.rows.map(row => ({ + group: row.key, + field: view.field, + ...row.value, + avg: row.value.sum / row.value.count, + })) + } else { + response.rows = response.rows.map(row => row.doc) + } + + let headers = Object.keys(view.schema) + + const exporter = exporters[format] + const exportedFile = exporter(headers, response.rows) + + const filename = `${view.name}.${format}` + + fs.writeFileSync(path.join(os.tmpdir(), filename), exportedFile) + + ctx.body = { + url: `/api/views/export/download/${filename}`, + name: view.name, + } + }, + downloadExport: async ctx => { + const filename = ctx.params.fileName + + ctx.attachment(filename) + ctx.body = fs.createReadStream(path.join(os.tmpdir(), filename)) + }, } module.exports = controller diff --git a/packages/server/src/api/routes/view.js b/packages/server/src/api/routes/view.js index 53d27b9268..2c88f6d19a 100644 --- a/packages/server/src/api/routes/view.js +++ b/packages/server/src/api/routes/view.js @@ -15,5 +15,11 @@ router .get("/api/views", authorized(BUILDER), viewController.fetch) .delete("/api/views/:viewName", authorized(BUILDER), viewController.destroy) .post("/api/views", authorized(BUILDER), viewController.save) + .post("/api/views/export", authorized(BUILDER), viewController.exportView) + .get( + "/api/views/export/download/:fileName", + authorized(BUILDER), + viewController.downloadExport + ) module.exports = router