View/Filter/Calculation events
This commit is contained in:
parent
6db5c62e48
commit
957e90fe86
|
@ -97,9 +97,11 @@ exports.Events = {
|
||||||
VIEW_DELETED: "view:deleted",
|
VIEW_DELETED: "view:deleted",
|
||||||
VIEW_EXPORTED: "view:exported",
|
VIEW_EXPORTED: "view:exported",
|
||||||
VIEW_FILTER_CREATED: "view:filter:created",
|
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_CREATED: "view:calculation:created",
|
||||||
VIEW_CALCULATION_DELETED: "view:calculation:created",
|
VIEW_CALCULATION_UPDATED: "view:calculation:updated",
|
||||||
|
VIEW_CALCULATION_DELETED: "view:calculation:deleted",
|
||||||
|
|
||||||
// ROW
|
// ROW
|
||||||
// ROW_CREATED: "row:created",
|
// ROW_CREATED: "row:created",
|
||||||
|
|
|
@ -8,13 +8,11 @@ exports.created = () => {
|
||||||
events.processEvent(Events.VIEW_CREATED, properties)
|
events.processEvent(Events.VIEW_CREATED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
exports.updated = () => {
|
exports.updated = () => {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.VIEW_UPDATED, properties)
|
events.processEvent(Events.VIEW_UPDATED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
exports.deleted = () => {
|
exports.deleted = () => {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.VIEW_DELETED, properties)
|
events.processEvent(Events.VIEW_DELETED, properties)
|
||||||
|
@ -25,25 +23,31 @@ exports.exported = (table, format) => {
|
||||||
events.processEvent(Events.VIEW_EXPORTED, properties)
|
events.processEvent(Events.VIEW_EXPORTED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
exports.filterCreated = () => {
|
exports.filterCreated = () => {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.VIEW_FILTER_CREATED, properties)
|
events.processEvent(Events.VIEW_FILTER_CREATED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
exports.filterUpdated = () => {
|
||||||
|
const properties = {}
|
||||||
|
events.processEvent(Events.VIEW_FILTER_UPDATED, properties)
|
||||||
|
}
|
||||||
|
|
||||||
exports.filterDeleted = () => {
|
exports.filterDeleted = () => {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.VIEW_FILTER_DELETED, properties)
|
events.processEvent(Events.VIEW_FILTER_DELETED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
exports.calculationCreated = () => {
|
exports.calculationCreated = () => {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.VIEW_CALCULATION_CREATED, properties)
|
events.processEvent(Events.VIEW_CALCULATION_CREATED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
exports.calculationUpdated = () => {
|
||||||
|
const properties = {}
|
||||||
|
events.processEvent(Events.VIEW_CALCULATION_UPDATED, properties)
|
||||||
|
}
|
||||||
|
|
||||||
exports.calculationDeleted = () => {
|
exports.calculationDeleted = () => {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.VIEW_CALCULATION_DELETED, properties)
|
events.processEvent(Events.VIEW_CALCULATION_DELETED, properties)
|
||||||
|
|
|
@ -106,7 +106,16 @@ jest.mock("../../../events", () => {
|
||||||
permissionUpdated: jest.fn(),
|
permissionUpdated: jest.fn(),
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
|
created: jest.fn(),
|
||||||
|
updated: jest.fn(),
|
||||||
|
deleted: jest.fn(),
|
||||||
exported: jest.fn(),
|
exported: jest.fn(),
|
||||||
|
filterCreated: jest.fn(),
|
||||||
|
filterUpdated: jest.fn(),
|
||||||
|
filterDeleted: jest.fn(),
|
||||||
|
calculationCreated: jest.fn(),
|
||||||
|
calculationUpdated: jest.fn(),
|
||||||
|
calculationDeleted: jest.fn(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,6 +8,7 @@ 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 { events } = require("@budibase/backend-core")
|
||||||
const { DocumentTypes } = require("../../../db/utils")
|
const { DocumentTypes } = require("../../../db/utils")
|
||||||
|
const { cloneDeep, isEqual } = require("lodash")
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
ctx.body = await getViews()
|
ctx.body = await getViews()
|
||||||
|
@ -17,24 +18,28 @@ exports.save = async ctx => {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const { originalName, ...viewToSave } = ctx.request.body
|
const { originalName, ...viewToSave } = ctx.request.body
|
||||||
const view = viewTemplate(viewToSave)
|
const view = viewTemplate(viewToSave)
|
||||||
|
const viewName = viewToSave.name
|
||||||
|
|
||||||
if (!viewToSave.name) {
|
if (!viewName) {
|
||||||
ctx.throw(400, "Cannot create view without a name")
|
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
|
// 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 (!table.views) table.views = {}
|
||||||
if (!view.meta.schema) {
|
if (!view.meta.schema) {
|
||||||
view.meta.schema = table.schema
|
view.meta.schema = table.schema
|
||||||
}
|
}
|
||||||
table.views[viewToSave.name] = view.meta
|
table.views[viewName] = view.meta
|
||||||
if (originalName) {
|
if (originalName) {
|
||||||
delete table.views[originalName]
|
delete table.views[originalName]
|
||||||
|
existingTable.views[viewName] = existingTable.views[originalName]
|
||||||
}
|
}
|
||||||
await db.put(table)
|
await db.put(table)
|
||||||
|
handleViewEvents(existingTable.views[viewName], table.views[viewName])
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
...table.views[viewToSave.name],
|
...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 => {
|
exports.destroy = async ctx => {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const viewName = decodeURI(ctx.params.viewName)
|
const viewName = decodeURI(ctx.params.viewName)
|
||||||
|
@ -49,6 +110,7 @@ exports.destroy = async ctx => {
|
||||||
const table = await db.get(view.meta.tableId)
|
const table = await db.get(view.meta.tableId)
|
||||||
delete table.views[viewName]
|
delete table.views[viewName]
|
||||||
await db.put(table)
|
await db.put(table)
|
||||||
|
events.view.deleted()
|
||||||
|
|
||||||
ctx.body = view
|
ctx.body = view
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,27 +30,70 @@ describe("/views", () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await config.init()
|
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", () => {
|
describe("create", () => {
|
||||||
beforeEach(async () => {
|
|
||||||
table = await config.createTable(priceTable())
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns a success message when the view is successfully created", async () => {
|
it("returns a success message when the view is successfully created", async () => {
|
||||||
const res = await request
|
const res = await saveView()
|
||||||
.post(`/api/views`)
|
expect(res.body.tableId).toBe(table._id)
|
||||||
.send({
|
expect(events.view.created).toBeCalledTimes(1)
|
||||||
name: "TestView",
|
})
|
||||||
field: "Price",
|
|
||||||
calculation: "stats",
|
it("creates a view with a calculation", async () => {
|
||||||
tableId: table._id,
|
jest.clearAllMocks()
|
||||||
})
|
|
||||||
.set(config.defaultHeaders())
|
const res = await saveView({ calculation: "count" })
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect(res.body.tableId).toBe(table._id)
|
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 () => {
|
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", () => {
|
describe("fetch", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
table = await config.createTable(priceTable())
|
table = await config.createTable(priceTable())
|
||||||
|
@ -125,10 +262,6 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("query", () => {
|
describe("query", () => {
|
||||||
beforeEach(async () => {
|
|
||||||
table = await config.createTable(priceTable())
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns data for the created view", async () => {
|
it("returns data for the created view", async () => {
|
||||||
await config.createView({
|
await config.createView({
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
|
@ -202,6 +335,7 @@ describe("/views", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body.map).toBeDefined()
|
expect(res.body.map).toBeDefined()
|
||||||
expect(res.body.meta.tableId).toEqual(table._id)
|
expect(res.body.meta.tableId).toEqual(table._id)
|
||||||
|
expect(events.view.deleted).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue