Merge branch 'master' of github.com:Budibase/budibase into template-export
This commit is contained in:
commit
8d01cc8d8b
|
@ -12,6 +12,7 @@
|
||||||
import RowPopover from "./popovers/Row.svelte"
|
import RowPopover from "./popovers/Row.svelte"
|
||||||
import ColumnPopover from "./popovers/Column.svelte"
|
import ColumnPopover from "./popovers/Column.svelte"
|
||||||
import ViewPopover from "./popovers/View.svelte"
|
import ViewPopover from "./popovers/View.svelte"
|
||||||
|
import ExportPopover from "./popovers/Export.svelte"
|
||||||
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
|
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
|
||||||
import EditRowPopover from "./popovers/EditRow.svelte"
|
import EditRowPopover from "./popovers/EditRow.svelte"
|
||||||
import * as api from "./api"
|
import * as api from "./api"
|
||||||
|
@ -51,6 +52,10 @@
|
||||||
.filter(id => !INTERNAL_HEADERS.includes(id))
|
.filter(id => !INTERNAL_HEADERS.includes(id))
|
||||||
|
|
||||||
$: schema = $backendUiStore.selectedModel.schema
|
$: schema = $backendUiStore.selectedModel.schema
|
||||||
|
$: modelView = {
|
||||||
|
schema: $backendUiStore.selectedModel.schema,
|
||||||
|
name: $backendUiStore.selectedView.name,
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
@ -61,6 +66,7 @@
|
||||||
{#if Object.keys($backendUiStore.selectedModel.schema).length > 0}
|
{#if Object.keys($backendUiStore.selectedModel.schema).length > 0}
|
||||||
<RowPopover />
|
<RowPopover />
|
||||||
<ViewPopover />
|
<ViewPopover />
|
||||||
|
<ExportPopover view={modelView} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
import CalculationPopover from "./popovers/Calculate.svelte"
|
import CalculationPopover from "./popovers/Calculate.svelte"
|
||||||
import GroupByPopover from "./popovers/GroupBy.svelte"
|
import GroupByPopover from "./popovers/GroupBy.svelte"
|
||||||
import FilterPopover from "./popovers/Filter.svelte"
|
import FilterPopover from "./popovers/Filter.svelte"
|
||||||
|
import ExportPopover from "./popovers/Export.svelte"
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
|
||||||
|
@ -51,4 +52,5 @@
|
||||||
{#if view.calculation}
|
{#if view.calculation}
|
||||||
<GroupByPopover {view} />
|
<GroupByPopover {view} />
|
||||||
{/if}
|
{/if}
|
||||||
|
<ExportPopover {view} />
|
||||||
</Table>
|
</Table>
|
||||||
|
|
|
@ -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>
|
|
@ -92,9 +92,6 @@
|
||||||
"server-destroy": "^1.0.1",
|
"server-destroy": "^1.0.1",
|
||||||
"supertest": "^4.0.2"
|
"supertest": "^4.0.2"
|
||||||
},
|
},
|
||||||
"nodemonConfig": {
|
|
||||||
"delay": "1000"
|
|
||||||
},
|
|
||||||
"jest": {
|
"jest": {
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
"setupFiles": [
|
"setupFiles": [
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
const CouchDB = require("../../../db")
|
const CouchDB = require("../../../db")
|
||||||
const viewTemplate = require("./viewBuilder")
|
const viewTemplate = require("./viewBuilder")
|
||||||
|
const fs = require("fs")
|
||||||
|
const path = require("path")
|
||||||
|
const os = require("os")
|
||||||
|
const exporters = require("./exporters")
|
||||||
|
|
||||||
const controller = {
|
const controller = {
|
||||||
fetch: async ctx => {
|
fetch: async ctx => {
|
||||||
|
@ -79,6 +83,48 @@ const controller = {
|
||||||
ctx.body = view
|
ctx.body = view
|
||||||
ctx.message = `View ${ctx.params.viewName} saved successfully.`
|
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
|
module.exports = controller
|
||||||
|
|
|
@ -15,5 +15,11 @@ router
|
||||||
.get("/api/views", authorized(BUILDER), viewController.fetch)
|
.get("/api/views", authorized(BUILDER), viewController.fetch)
|
||||||
.delete("/api/views/:viewName", authorized(BUILDER), viewController.destroy)
|
.delete("/api/views/:viewName", authorized(BUILDER), viewController.destroy)
|
||||||
.post("/api/views", authorized(BUILDER), viewController.save)
|
.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
|
module.exports = router
|
||||||
|
|
Loading…
Reference in New Issue