1
0
Fork 0
mirror of synced 2024-06-23 08:30:31 +12:00
budibase/packages/server/src/api/controllers/view/index.js

190 lines
5.1 KiB
JavaScript

const viewTemplate = require("./viewBuilder")
const { apiFileReturn } = require("../../../utilities/fileSystem")
const exporters = require("./exporters")
const { saveView, getView, getViews, deleteView } = require("./utils")
const { fetchView } = require("../row")
const { FieldTypes } = require("../../../constants")
const { getAppDB } = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core")
const { DocumentType } = require("../../../db/utils")
const { cloneDeep, isEqual } = require("lodash")
const sdk = require("../../../sdk")
exports.fetch = async ctx => {
ctx.body = await getViews()
}
exports.save = async ctx => {
const db = getAppDB()
const { originalName, ...viewToSave } = ctx.request.body
const view = viewTemplate(viewToSave)
const viewName = viewToSave.name
if (!viewName) {
ctx.throw(400, "Cannot create view without a name")
}
await saveView(originalName, viewName, view)
// add views to table document
const existingTable = await db.get(ctx.request.body.tableId)
const table = cloneDeep(existingTable)
if (!table.views) table.views = {}
if (!view.meta.schema) {
view.meta.schema = table.schema
}
table.views[viewName] = view.meta
if (originalName) {
delete table.views[originalName]
existingTable.views[viewName] = existingTable.views[originalName]
}
await db.put(table)
await handleViewEvents(existingTable.views[viewName], table.views[viewName])
ctx.body = {
...table.views[viewToSave.name],
name: viewToSave.name,
}
}
const calculationEvents = async (existingView, newView) => {
const existingCalculation = existingView && existingView.calculation
const newCalculation = newView && newView.calculation
if (existingCalculation && !newCalculation) {
await events.view.calculationDeleted(existingView)
}
if (!existingCalculation && newCalculation) {
await events.view.calculationCreated(newView)
}
if (
existingCalculation &&
newCalculation &&
existingCalculation !== newCalculation
) {
await events.view.calculationUpdated(newView)
}
}
const filterEvents = async (existingView, newView) => {
const hasExistingFilters = !!(
existingView &&
existingView.filters &&
existingView.filters.length
)
const hasNewFilters = !!(newView && newView.filters && newView.filters.length)
if (hasExistingFilters && !hasNewFilters) {
await events.view.filterDeleted(newView)
}
if (!hasExistingFilters && hasNewFilters) {
await events.view.filterCreated(newView)
}
if (
hasExistingFilters &&
hasNewFilters &&
!isEqual(existingView.filters, newView.filters)
) {
await events.view.filterUpdated(newView)
}
}
const handleViewEvents = async (existingView, newView) => {
if (!existingView) {
await events.view.created(newView)
} else {
await events.view.updated(newView)
}
await calculationEvents(existingView, newView)
await filterEvents(existingView, newView)
}
exports.destroy = async ctx => {
const db = getAppDB()
const viewName = decodeURI(ctx.params.viewName)
const view = await deleteView(viewName)
const table = await db.get(view.meta.tableId)
delete table.views[viewName]
await db.put(table)
await events.view.deleted(view)
ctx.body = view
}
exports.exportView = async ctx => {
const viewName = decodeURI(ctx.query.view)
const view = await getView(viewName)
const format = ctx.query.format
if (!format || !Object.values(exporters.ExportFormats).includes(format)) {
ctx.throw(400, "Format must be specified, either csv or json")
}
if (view) {
ctx.params.viewName = viewName
// Fetch view rows
ctx.query = {
group: view.meta.groupBy,
calculation: view.meta.calculation,
stats: !!view.meta.field,
field: view.meta.field,
}
} else {
// table all_ view
/* istanbul ignore next */
ctx.params.viewName = viewName
}
await fetchView(ctx)
let rows = ctx.body
let schema = view && view.meta && view.meta.schema
const tableId = ctx.params.tableId || view.meta.tableId
const table = await sdk.tables.getTable(tableId)
if (!schema) {
schema = table.schema
}
// remove any relationships
const relationships = Object.entries(schema)
.filter(entry => entry[1].type === FieldTypes.LINK)
.map(entry => entry[0])
// iterate relationship columns and remove from and row and schema
relationships.forEach(column => {
rows.forEach(row => {
delete row[column]
})
delete schema[column]
})
// make sure no "undefined" entries appear in the CSV
if (format === exporters.ExportFormats.CSV) {
const schemaKeys = Object.keys(schema)
for (let key of schemaKeys) {
for (let row of rows) {
if (row[key] == null) {
row[key] = ""
}
}
}
}
// Export part
let headers = Object.keys(schema)
const exporter = exporters[format]
const filename = `${viewName}.${format}`
// send down the file
ctx.attachment(filename)
ctx.body = apiFileReturn(exporter(headers, rows))
if (viewName.startsWith(DocumentType.TABLE)) {
await events.table.exported(table, format)
} else {
await events.view.exported(table, format)
}
}