View/Table/Row Import/Export events

This commit is contained in:
Rory Powell 2022-04-11 22:32:12 +01:00
parent 2c682d3507
commit 06d70266cd
12 changed files with 180 additions and 62 deletions

View File

@ -103,6 +103,7 @@ exports.Events = {
// ROW // ROW
// ROW_CREATED: "row:created", // ROW_CREATED: "row:created",
ROW_IMPORT: "row:import",
// COMPONENT // COMPONENT
COMPONENT_CREATED: "component:created", COMPONENT_CREATED: "component:created",

View File

@ -9,7 +9,8 @@ const layout = require("./layout")
const org = require("./org") const org = require("./org")
const query = require("./query") const query = require("./query")
const role = require("./role") const role = require("./role")
const row = require("./screen") const screen = require("./screen")
const row = require("./row")
const table = require("./table") const table = require("./table")
const serve = require("./serve") const serve = require("./serve")
const user = require("./user") const user = require("./user")
@ -27,6 +28,7 @@ module.exports = {
org, org,
query, query,
role, role,
screen,
row, row,
table, table,
serve, serve,

View File

@ -1,7 +1,14 @@
// const events = require("../events") const events = require("../events")
// const { Events } = require("../constants") const { Events } = require("../constants")
/* eslint-disable */
// exports.created = () => { // exports.created = () => {
// const properties = {} // const properties = {}
// events.processEvent(Events.ROW_CREATED, properties) // events.processEvent(Events.ROW_CREATED, properties)
// } // }
exports.import = (table, format, count) => {
const properties = {}
events.processEvent(Events.ROW_IMPORT, properties)
}

View File

@ -1,29 +1,29 @@
const events = require("../events") const events = require("../events")
const { Events } = require("../constants") const { Events } = require("../constants")
exports.created = () => { /* eslint-disable */
exports.created = table => {
const properties = {} const properties = {}
events.processEvent(Events.TABLE_CREATED, properties) events.processEvent(Events.TABLE_CREATED, properties)
} }
exports.updated = () => { exports.updated = table => {
const properties = {} const properties = {}
events.processEvent(Events.TABLE_UPDATED, properties) events.processEvent(Events.TABLE_UPDATED, properties)
} }
exports.deleted = () => { exports.deleted = table => {
const properties = {} const properties = {}
events.processEvent(Events.TABLE_DELETED, properties) events.processEvent(Events.TABLE_DELETED, properties)
} }
// TODO exports.exported = (table, format) => {
exports.exported = () => {
const properties = {} const properties = {}
events.processEvent(Events.TABLE_EXPORTED, properties) events.processEvent(Events.TABLE_EXPORTED, properties)
} }
// TODO exports.imported = (table, format) => {
exports.imported = () => {
const properties = {} const properties = {}
events.processEvent(Events.TABLE_IMPORTED, properties) events.processEvent(Events.TABLE_IMPORTED, properties)
} }

View File

@ -1,6 +1,8 @@
const events = require("../events") const events = require("../events")
const { Events } = require("../constants") const { Events } = require("../constants")
/* eslint-disable */
exports.created = () => { exports.created = () => {
const properties = {} const properties = {}
events.processEvent(Events.VIEW_CREATED, properties) events.processEvent(Events.VIEW_CREATED, properties)
@ -18,8 +20,7 @@ exports.deleted = () => {
events.processEvent(Events.VIEW_DELETED, properties) events.processEvent(Events.VIEW_DELETED, properties)
} }
// TODO exports.exported = (table, format) => {
exports.exported = () => {
const properties = {} const properties = {}
events.processEvent(Events.VIEW_EXPORTED, properties) events.processEvent(Events.VIEW_EXPORTED, properties)
} }

View File

@ -69,7 +69,9 @@ jest.mock("../../../events", () => {
assigned: jest.fn(), assigned: jest.fn(),
unassigned: jest.fn(), unassigned: jest.fn(),
}, },
row: {}, row: {
import: jest.fn(),
},
screen: { screen: {
created: jest.fn(), created: jest.fn(),
deleted: jest.fn(), deleted: jest.fn(),
@ -103,7 +105,9 @@ jest.mock("../../../events", () => {
imported: jest.fn(), imported: jest.fn(),
permissionUpdated: jest.fn(), permissionUpdated: jest.fn(),
}, },
view: {}, view: {
exported: jest.fn(),
},
} }
}) })

View File

@ -19,6 +19,7 @@ const { cloneDeep } = require("lodash/fp")
const csvParser = require("../../../utilities/csvParser") const csvParser = require("../../../utilities/csvParser")
const { handleRequest } = require("../row/external") const { handleRequest } = require("../row/external")
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core")
async function makeTableRequest( async function makeTableRequest(
datasource, datasource,
@ -314,5 +315,6 @@ exports.bulkImport = async function (ctx) {
await handleRequest(DataSourceOperation.BULK_CREATE, table._id, { await handleRequest(DataSourceOperation.BULK_CREATE, table._id, {
rows, rows,
}) })
events.row.import(table, "csv", rows.length)
return table return table
} }

View File

@ -56,12 +56,17 @@ exports.find = async function (ctx) {
exports.save = async function (ctx) { exports.save = async function (ctx) {
const appId = ctx.appId const appId = ctx.appId
const table = ctx.request.body const table = ctx.request.body
const importFormat =
table.dataImport && table.dataImport.csvString ? "csv" : undefined
const savedTable = await pickApi({ table }).save(ctx) const savedTable = await pickApi({ table }).save(ctx)
if (!table._id) { if (!table._id) {
events.table.created(savedTable) events.table.created(savedTable)
} else { } else {
events.table.updated(savedTable) events.table.updated(savedTable)
} }
if (importFormat) {
events.table.imported(savedTable, importFormat)
}
ctx.status = 200 ctx.status = 200
ctx.message = `Table ${table.name} saved successfully.` ctx.message = `Table ${table.name} saved successfully.`
ctx.eventEmitter && ctx.eventEmitter &&

View File

@ -26,7 +26,8 @@ import { getViews, saveView } from "../view/utils"
import viewTemplate from "../view/viewBuilder" import viewTemplate from "../view/viewBuilder"
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro" import { quotas } from "@budibase/pro"
import { events } from "@budibase/backend-core"
export async function clearColumns(table: any, columnNames: any) { export async function clearColumns(table: any, columnNames: any) {
const db = getAppDB() const db = getAppDB()
@ -148,8 +149,7 @@ export async function handleDataImport(user: any, table: any, dataImport: any) {
} }
await quotas.addRows(finalData.length, () => db.bulkDocs(finalData)) await quotas.addRows(finalData.length, () => db.bulkDocs(finalData))
let response = await db.put(table) events.row.import(table, "csv", finalData.length)
table._rev = response._rev
return table return table
} }
@ -245,7 +245,7 @@ class TableSaveFunctions {
// after saving // after saving
async after(table: any) { async after(table: any) {
table = await handleSearchIndexes(table) table = await handleSearchIndexes(table)
table = await handleDataImport(this.user, table, this.dataImport) await handleDataImport(this.user, table, this.dataImport)
return table return table
} }

View File

@ -6,6 +6,8 @@ const { fetchView } = require("../row")
const { getTable } = require("../table/utils") const { getTable } = require("../table/utils")
const { FieldTypes } = require("../../../constants") const { FieldTypes } = require("../../../constants")
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core")
const { DocumentTypes } = require("../../../db/utils")
exports.fetch = async ctx => { exports.fetch = async ctx => {
ctx.body = await getViews() ctx.body = await getViews()
@ -79,9 +81,9 @@ exports.exportView = async ctx => {
let rows = ctx.body let rows = ctx.body
let schema = view && view.meta && view.meta.schema let schema = view && view.meta && view.meta.schema
const tableId = ctx.params.tableId || view.meta.tableId
const table = await getTable(tableId)
if (!schema) { if (!schema) {
const tableId = ctx.params.tableId || view.meta.tableId
const table = await getTable(tableId)
schema = table.schema schema = table.schema
} }
@ -116,4 +118,10 @@ exports.exportView = async ctx => {
// send down the file // send down the file
ctx.attachment(filename) ctx.attachment(filename)
ctx.body = apiFileReturn(exporter(headers, rows)) ctx.body = apiFileReturn(exporter(headers, rows))
if (viewName.startsWith(DocumentTypes.TABLE)) {
events.table.exported(table, format)
} else {
events.view.exported(table, format)
}
} }

View File

@ -14,37 +14,56 @@ describe("/tables", () => {
}) })
describe("create", () => { describe("create", () => {
it("returns a success message when the table is successfully created", async () => {
const res = await request beforeEach(() => {
jest.clearAllMocks()
})
const createTable = (table) => {
if (!table) {
table = basicTable()
}
return request
.post(`/api/tables`) .post(`/api/tables`)
.send({ .send(table)
name: "TestTable",
key: "name",
schema: {
name: {type: "string"}
}
})
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200) .expect(200)
}
it("returns a success message when the table is successfully created", async () => {
const res = await createTable()
expect(res.res.statusMessage).toEqual("Table TestTable saved successfully.") expect(res.res.statusMessage).toEqual("Table TestTable saved successfully.")
expect(res.body.name).toEqual("TestTable") expect(res.body.name).toEqual("TestTable")
expect(events.table.created).toBeCalledTimes(1) expect(events.table.created).toBeCalledTimes(1)
expect(events.table.created).toBeCalledWith(res.body) expect(events.table.created).toBeCalledWith(res.body)
}) })
it("creates a table via data import CSV", async () => {
const table = basicTable()
table.dataImport = {
csvString: "\"name\",\"description\"\n\"test-name\",\"test-desc\"",
}
table.dataImport.schema = table.schema
const res = await createTable(table)
expect(events.table.created).toBeCalledTimes(1)
expect(events.table.created).toBeCalledWith(res.body)
expect(events.table.imported).toBeCalledTimes(1)
expect(events.table.imported).toBeCalledWith(res.body, "csv")
expect(events.row.import).toBeCalledTimes(1)
expect(events.row.import).toBeCalledWith(res.body, "csv", 1)
})
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
await checkBuilderEndpoint({ await checkBuilderEndpoint({
config, config,
method: "POST", method: "POST",
url: `/api/tables`, url: `/api/tables`,
body: { body: basicTable()
name: "TestTable",
key: "name",
schema: {
name: {type: "string"}
}
}
}) })
}) })
}) })
@ -124,6 +143,30 @@ describe("/tables", () => {
}) })
}) })
describe("import", () => {
it("imports rows successfully", async () => {
const table = await config.createTable()
const importRequest = {
dataImport: {
csvString: "\"name\",\"description\"\n\"test-name\",\"test-desc\"",
schema: table.schema
}
}
jest.clearAllMocks()
await request
.post(`/api/tables/${table._id}/import`)
.send(importRequest)
.set(config.defaultHeaders())
.expect('Content-Type', /json/)
.expect(200)
expect(events.table.created).not.toHaveBeenCalled()
expect(events.row.import).toBeCalledTimes(1)
expect(events.row.import).toBeCalledWith(table, "csv", 1)
})
})
describe("fetch", () => { describe("fetch", () => {
let testTable let testTable

View File

@ -1,4 +1,5 @@
const setup = require("./utilities") const setup = require("./utilities")
const { events } = require("@budibase/backend-core")
function priceTable() { function priceTable() {
return { return {
@ -205,33 +206,77 @@ describe("/views", () => {
}) })
describe("exportView", () => { describe("exportView", () => {
it("should be able to export a view", async () => {
await config.createTable(priceTable()) beforeEach(() => {
await config.createRow() jest.clearAllMocks()
})
const setupExport = async () => {
const table = await config.createTable()
await config.createRow({ name: "test-name", description: "test-desc" })
return table
}
const exportView = async (viewName, format) => {
return request
.get(`/api/views/export?view=${viewName}&format=${format}`)
.set(config.defaultHeaders())
.expect(200)
}
const assertJsonExport = (res) => {
const rows = JSON.parse(res.text)
expect(rows.length).toBe(1)
expect(rows[0].name).toBe("test-name")
expect(rows[0].description).toBe("test-desc")
}
const assertCSVExport = (res) => {
expect(res.text).toBe("\"name\",\"description\"\n\"test-name\",\"test-desc\"")
}
it("should be able to export a table as JSON", async () => {
const table = await setupExport()
const res = await exportView(table._id, "json")
assertJsonExport(res)
expect(events.table.exported).toBeCalledTimes(1)
expect(events.table.exported).toBeCalledWith(table, "json")
})
it("should be able to export a table as CSV", async () => {
const table = await setupExport()
const res = await exportView(table._id, "csv")
assertCSVExport(res)
expect(events.table.exported).toBeCalledTimes(1)
expect(events.table.exported).toBeCalledWith(table, "csv")
})
it("should be able to export a view as JSON", async () => {
let table = await setupExport()
const view = await config.createView() const view = await config.createView()
let res = await request table = await config.getTable(table._id)
.get(`/api/views/export?view=${view.name}&format=json`)
.set(config.defaultHeaders()) let res = await exportView(view.name, "json")
.expect(200)
let error assertJsonExport(res)
try { expect(events.view.exported).toBeCalledTimes(1)
const obj = JSON.parse(res.text) expect(events.view.exported).toBeCalledWith(table, "json")
expect(obj.length).toBe(1) })
} catch (err) {
error = err it("should be able to export a view as CSV", async () => {
} let table = await setupExport()
expect(error).toBeUndefined() const view = await config.createView()
res = await request table = await config.getTable(table._id)
.get(`/api/views/export?view=${view.name}&format=csv`)
.set(config.defaultHeaders()) let res = await exportView(view.name, "csv")
.expect(200)
// this shouldn't be JSON assertCSVExport(res)
try { expect(events.view.exported).toBeCalledTimes(1)
JSON.parse(res.text) expect(events.view.exported).toBeCalledWith(table, "csv")
} catch (err) {
error = err
}
expect(error).toBeDefined()
}) })
}) })
}) })