Browse Source

View/Table/Row Import/Export events

master
Rory Powell 5 years ago
parent
commit
06d70266cd
  1. 1
      packages/backend-core/src/events/constants.js
  2. 4
      packages/backend-core/src/events/handlers/index.js
  3. 11
      packages/backend-core/src/events/handlers/row.js
  4. 14
      packages/backend-core/src/events/handlers/table.js
  5. 5
      packages/backend-core/src/events/handlers/view.js
  6. 8
      packages/backend-core/src/tests/utilities/mocks/events.js
  7. 2
      packages/server/src/api/controllers/table/external.js
  8. 5
      packages/server/src/api/controllers/table/index.js
  9. 8
      packages/server/src/api/controllers/table/utils.ts
  10. 12
      packages/server/src/api/controllers/view/index.js
  11. 75
      packages/server/src/api/routes/tests/table.spec.js
  12. 95
      packages/server/src/api/routes/tests/view.spec.js

1
packages/backend-core/src/events/constants.js

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

4
packages/backend-core/src/events/handlers/index.js

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

11
packages/backend-core/src/events/handlers/row.js

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

14
packages/backend-core/src/events/handlers/table.js

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

5
packages/backend-core/src/events/handlers/view.js

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

8
packages/backend-core/src/tests/utilities/mocks/events.js

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

2
packages/server/src/api/controllers/table/external.js

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

5
packages/server/src/api/controllers/table/index.js

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

8
packages/server/src/api/controllers/table/utils.ts

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

12
packages/server/src/api/controllers/view/index.js

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

75
packages/server/src/api/routes/tests/table.spec.js

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

95
packages/server/src/api/routes/tests/view.spec.js

@ -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 view = await config.createView() })
let res = await request
.get(`/api/views/export?view=${view.name}&format=json`) const setupExport = async () => {
.set(config.defaultHeaders()) const table = await config.createTable()
.expect(200) await config.createRow({ name: "test-name", description: "test-desc" })
let error return table
try { }
const obj = JSON.parse(res.text)
expect(obj.length).toBe(1) const exportView = async (viewName, format) => {
} catch (err) { return request
error = err .get(`/api/views/export?view=${viewName}&format=${format}`)
}
expect(error).toBeUndefined()
res = await request
.get(`/api/views/export?view=${view.name}&format=csv`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect(200) .expect(200)
// this shouldn't be JSON }
try {
JSON.parse(res.text) const assertJsonExport = (res) => {
} catch (err) { const rows = JSON.parse(res.text)
error = err expect(rows.length).toBe(1)
} expect(rows[0].name).toBe("test-name")
expect(error).toBeDefined() 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()
table = await config.getTable(table._id)
let res = await exportView(view.name, "json")
assertJsonExport(res)
expect(events.view.exported).toBeCalledTimes(1)
expect(events.view.exported).toBeCalledWith(table, "json")
})
it("should be able to export a view as CSV", async () => {
let table = await setupExport()
const view = await config.createView()
table = await config.getTable(table._id)
let res = await exportView(view.name, "csv")
assertCSVExport(res)
expect(events.view.exported).toBeCalledTimes(1)
expect(events.view.exported).toBeCalledWith(table, "csv")
}) })
}) })
}) })

Loading…
Cancel
Save