View/Filter/Calculation events

This commit is contained in:
Rory Powell 2022-04-12 00:19:52 +01:00
parent 6db5c62e48
commit 957e90fe86
5 changed files with 241 additions and 30 deletions

View File

@ -97,9 +97,11 @@ exports.Events = {
VIEW_DELETED: "view:deleted",
VIEW_EXPORTED: "view:exported",
VIEW_FILTER_CREATED: "view:filter:created",
VIEW_FILTER_DELETED: "view:filter:created",
VIEW_FILTER_UPDATED: "view:filter:updated",
VIEW_FILTER_DELETED: "view:filter:deleted",
VIEW_CALCULATION_CREATED: "view:calculation:created",
VIEW_CALCULATION_DELETED: "view:calculation:created",
VIEW_CALCULATION_UPDATED: "view:calculation:updated",
VIEW_CALCULATION_DELETED: "view:calculation:deleted",
// ROW
// ROW_CREATED: "row:created",

View File

@ -8,13 +8,11 @@ exports.created = () => {
events.processEvent(Events.VIEW_CREATED, properties)
}
// TODO
exports.updated = () => {
const properties = {}
events.processEvent(Events.VIEW_UPDATED, properties)
}
// TODO
exports.deleted = () => {
const properties = {}
events.processEvent(Events.VIEW_DELETED, properties)
@ -25,25 +23,31 @@ exports.exported = (table, format) => {
events.processEvent(Events.VIEW_EXPORTED, properties)
}
// TODO
exports.filterCreated = () => {
const properties = {}
events.processEvent(Events.VIEW_FILTER_CREATED, properties)
}
// TODO
exports.filterUpdated = () => {
const properties = {}
events.processEvent(Events.VIEW_FILTER_UPDATED, properties)
}
exports.filterDeleted = () => {
const properties = {}
events.processEvent(Events.VIEW_FILTER_DELETED, properties)
}
// TODO
exports.calculationCreated = () => {
const properties = {}
events.processEvent(Events.VIEW_CALCULATION_CREATED, properties)
}
// TODO
exports.calculationUpdated = () => {
const properties = {}
events.processEvent(Events.VIEW_CALCULATION_UPDATED, properties)
}
exports.calculationDeleted = () => {
const properties = {}
events.processEvent(Events.VIEW_CALCULATION_DELETED, properties)

View File

@ -106,7 +106,16 @@ jest.mock("../../../events", () => {
permissionUpdated: jest.fn(),
},
view: {
created: jest.fn(),
updated: jest.fn(),
deleted: jest.fn(),
exported: jest.fn(),
filterCreated: jest.fn(),
filterUpdated: jest.fn(),
filterDeleted: jest.fn(),
calculationCreated: jest.fn(),
calculationUpdated: jest.fn(),
calculationDeleted: jest.fn(),
},
}
})

View File

@ -8,6 +8,7 @@ const { FieldTypes } = require("../../../constants")
const { getAppDB } = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core")
const { DocumentTypes } = require("../../../db/utils")
const { cloneDeep, isEqual } = require("lodash")
exports.fetch = async ctx => {
ctx.body = await getViews()
@ -17,24 +18,28 @@ exports.save = async ctx => {
const db = getAppDB()
const { originalName, ...viewToSave } = ctx.request.body
const view = viewTemplate(viewToSave)
const viewName = viewToSave.name
if (!viewToSave.name) {
if (!viewName) {
ctx.throw(400, "Cannot create view without a name")
}
await saveView(originalName, viewToSave.name, view)
await saveView(originalName, viewName, view)
// add views to table document
const table = await db.get(ctx.request.body.tableId)
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[viewToSave.name] = view.meta
table.views[viewName] = view.meta
if (originalName) {
delete table.views[originalName]
existingTable.views[viewName] = existingTable.views[originalName]
}
await db.put(table)
handleViewEvents(existingTable.views[viewName], table.views[viewName])
ctx.body = {
...table.views[viewToSave.name],
@ -42,6 +47,62 @@ exports.save = async ctx => {
}
}
const calculationEvents = (existingView, newView) => {
const existingCalculation = existingView && existingView.calculation
const newCalculation = newView && newView.calculation
if (existingCalculation && !newCalculation) {
events.view.calculationDeleted()
}
if (!existingCalculation && newCalculation) {
events.view.calculationCreated()
}
if (
existingCalculation &&
newCalculation &&
existingCalculation !== newCalculation
) {
events.view.calculationUpdated()
}
}
const filterEvents = (existingView, newView) => {
const hasExistingFilters = !!(
existingView &&
existingView.filters &&
existingView.filters.length
)
const hasNewFilters = !!(newView && newView.filters && newView.filters.length)
if (hasExistingFilters && !hasNewFilters) {
events.view.filterDeleted()
}
if (!hasExistingFilters && hasNewFilters) {
events.view.filterCreated()
}
if (
hasExistingFilters &&
hasNewFilters &&
!isEqual(existingView.filters, newView.filters)
) {
events.view.filterUpdated()
}
}
const handleViewEvents = (existingView, newView) => {
if (!existingView) {
events.view.created()
} else {
events.view.updated()
}
calculationEvents(existingView, newView)
filterEvents(existingView, newView)
}
exports.destroy = async ctx => {
const db = getAppDB()
const viewName = decodeURI(ctx.params.viewName)
@ -49,6 +110,7 @@ exports.destroy = async ctx => {
const table = await db.get(view.meta.tableId)
delete table.views[viewName]
await db.put(table)
events.view.deleted()
ctx.body = view
}

View File

@ -30,27 +30,70 @@ describe("/views", () => {
beforeEach(async () => {
await config.init()
table = await config.createTable(priceTable())
})
const saveView = async (view) => {
const viewToSave = {
name: "TestView",
field: "Price",
calculation: "stats",
tableId: table._id,
...view
}
return request
.post(`/api/views`)
.send(viewToSave)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
}
describe("create", () => {
beforeEach(async () => {
table = await config.createTable(priceTable())
})
it("returns a success message when the view is successfully created", async () => {
const res = await request
.post(`/api/views`)
.send({
name: "TestView",
field: "Price",
calculation: "stats",
tableId: table._id,
})
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
const res = await saveView()
expect(res.body.tableId).toBe(table._id)
expect(events.view.created).toBeCalledTimes(1)
})
it("creates a view with a calculation", async () => {
jest.clearAllMocks()
const res = await saveView({ calculation: "count" })
expect(res.body.tableId).toBe(table._id)
expect(events.view.created).toBeCalledTimes(1)
expect(events.view.updated).not.toBeCalled()
expect(events.view.calculationCreated).toBeCalledTimes(1)
expect(events.view.calculationUpdated).not.toBeCalled()
expect(events.view.calculationDeleted).not.toBeCalled()
expect(events.view.filterCreated).not.toBeCalled()
expect(events.view.filterUpdated).not.toBeCalled()
expect(events.view.filterDeleted).not.toBeCalled()
})
it("creates a view with a filter", async () => {
jest.clearAllMocks()
const res = await saveView({
calculation: null,
filters: [{
value: "1",
condition: "EQUALS",
key: "price"
}],
})
expect(res.body.tableId).toBe(table._id)
expect(events.view.created).toBeCalledTimes(1)
expect(events.view.updated).not.toBeCalled()
expect(events.view.calculationCreated).not.toBeCalled()
expect(events.view.calculationUpdated).not.toBeCalled()
expect(events.view.calculationDeleted).not.toBeCalled()
expect(events.view.filterCreated).toBeCalledTimes(1)
expect(events.view.filterUpdated).not.toBeCalled()
expect(events.view.filterDeleted).not.toBeCalled()
})
it("updates the table row with the new view metadata", async () => {
@ -102,6 +145,100 @@ describe("/views", () => {
})
})
describe("update", () => {
it("updates a view with no calculation or filter changed", async () => {
await saveView()
jest.clearAllMocks()
await saveView()
expect(events.view.created).not.toBeCalled()
expect(events.view.updated).toBeCalledTimes(1)
expect(events.view.calculationCreated).not.toBeCalled()
expect(events.view.calculationUpdated).not.toBeCalled()
expect(events.view.calculationDeleted).not.toBeCalled()
expect(events.view.filterCreated).not.toBeCalled()
expect(events.view.filterUpdated).not.toBeCalled()
expect(events.view.filterDeleted).not.toBeCalled()
})
it("updates a view calculation", async () => {
await saveView({ calculation: "sum" })
jest.clearAllMocks()
await saveView({ calculation: "count" })
expect(events.view.created).not.toBeCalled()
expect(events.view.updated).toBeCalledTimes(1)
expect(events.view.calculationCreated).not.toBeCalled()
expect(events.view.calculationUpdated).toBeCalledTimes(1)
expect(events.view.calculationDeleted).not.toBeCalled()
expect(events.view.filterCreated).not.toBeCalled()
expect(events.view.filterUpdated).not.toBeCalled()
expect(events.view.filterDeleted).not.toBeCalled()
})
it("deletes a view calculation", async () => {
await saveView({ calculation: "sum" })
jest.clearAllMocks()
await saveView({ calculation: null })
expect(events.view.created).not.toBeCalled()
expect(events.view.updated).toBeCalledTimes(1)
expect(events.view.calculationCreated).not.toBeCalled()
expect(events.view.calculationUpdated).not.toBeCalled()
expect(events.view.calculationDeleted).toBeCalledTimes(1)
expect(events.view.filterCreated).not.toBeCalled()
expect(events.view.filterUpdated).not.toBeCalled()
expect(events.view.filterDeleted).not.toBeCalled()
})
it("updates a view filter", async () => {
await saveView({ filters: [{
value: "1",
condition: "EQUALS",
key: "price"
}] })
jest.clearAllMocks()
await saveView({ filters: [{
value: "2",
condition: "EQUALS",
key: "price"
}] })
expect(events.view.created).not.toBeCalled()
expect(events.view.updated).toBeCalledTimes(1)
expect(events.view.calculationCreated).not.toBeCalled()
expect(events.view.calculationUpdated).not.toBeCalled()
expect(events.view.calculationDeleted).not.toBeCalled()
expect(events.view.filterCreated).not.toBeCalled()
expect(events.view.filterUpdated).toBeCalledTimes(1)
expect(events.view.filterDeleted).not.toBeCalled()
})
it("deletes a view filter", async () => {
await saveView({ filters: [{
value: "1",
condition: "EQUALS",
key: "price"
}] })
jest.clearAllMocks()
await saveView({ filters: [] })
expect(events.view.created).not.toBeCalled()
expect(events.view.updated).toBeCalledTimes(1)
expect(events.view.calculationCreated).not.toBeCalled()
expect(events.view.calculationUpdated).not.toBeCalled()
expect(events.view.calculationDeleted).not.toBeCalled()
expect(events.view.filterCreated).not.toBeCalled()
expect(events.view.filterUpdated).not.toBeCalled()
expect(events.view.filterDeleted).toBeCalledTimes(1)
})
})
describe("fetch", () => {
beforeEach(async () => {
table = await config.createTable(priceTable())
@ -125,10 +262,6 @@ describe("/views", () => {
})
describe("query", () => {
beforeEach(async () => {
table = await config.createTable(priceTable())
})
it("returns data for the created view", async () => {
await config.createView({
name: "TestView",
@ -202,6 +335,7 @@ describe("/views", () => {
.expect(200)
expect(res.body.map).toBeDefined()
expect(res.body.meta.tableId).toEqual(table._id)
expect(events.view.deleted).toBeCalledTimes(1)
})
})