Merge branch 'master' of github.com:Budibase/budibase into template-export

This commit is contained in:
Martin McKeaveney 2020-09-25 14:51:22 +01:00
commit 97cf5c7b33
7 changed files with 145 additions and 3 deletions

View File

@ -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,
}
</script>
<section>
@ -61,6 +66,7 @@
{#if Object.keys($backendUiStore.selectedModel.schema).length > 0}
<RowPopover />
<ViewPopover />
<ExportPopover view={modelView} />
{/if}
</div>
</div>

View File

@ -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}
<GroupByPopover {view} />
{/if}
<ExportPopover {view} />
</Table>

View File

@ -0,0 +1,71 @@
<script>
import {
TextButton,
Button,
Icon,
Input,
Select,
Popover,
} from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import api from "builderStore/api"
const FORMATS = [
{
name: "CSV",
key: "csv",
},
{
name: "JSON",
key: "json",
},
]
export let view
let anchor
let dropdown
let exportFormat
async function exportView() {
const response = await api.post(
`/api/views/export?format=${exportFormat}`,
view
)
const downloadInfo = await response.json()
window.location = downloadInfo.url
}
</script>
<div bind:this={anchor}>
<TextButton text small on:click={dropdown.show}>
<Icon name="download" />
Export
</TextButton>
</div>
<Popover bind:this={dropdown} {anchor} align="left">
<h5>Export Format</h5>
<Select secondary thin bind:value={exportFormat}>
{#each FORMATS as format}
<option value={format.key}>{format.name}</option>
{/each}
</Select>
<div class="button-group">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={exportView}>Export</Button>
</div>
</Popover>
<style>
h5 {
margin-top: 0;
}
.button-group {
margin-top: var(--spacing-l);
display: flex;
justify-content: flex-end;
gap: var(--spacing-s);
}
</style>

View File

@ -92,9 +92,6 @@
"server-destroy": "^1.0.1",
"supertest": "^4.0.2"
},
"nodemonConfig": {
"delay": "1000"
},
"jest": {
"testEnvironment": "node",
"setupFiles": [

View File

@ -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)
}

View File

@ -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

View File

@ -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