send individual events for view calcs and joins

This commit is contained in:
Martin McKeaveney 2024-12-08 23:18:07 +00:00
parent 41c1632d60
commit 917c235295
9 changed files with 124 additions and 53 deletions

View File

@ -13,6 +13,7 @@ const EXCLUDED_EVENTS: Event[] = [
Event.ROLE_UPDATED,
Event.DATASOURCE_UPDATED,
Event.QUERY_UPDATED,
Event.VIEW_UPDATED,
Event.VIEW_FILTER_UPDATED,
Event.VIEW_CALCULATION_UPDATED,
Event.AUTOMATION_TRIGGER_UPDATED,

View File

@ -5,7 +5,7 @@ async function created(
rowAction: RowActionCreatedEvent,
timestamp?: string | number
) {
await publishEvent(Event.TABLE_CREATED, rowAction, timestamp)
await publishEvent(Event.ROW_ACTION_CREATED, rowAction, timestamp)
}
export default {

View File

@ -1,6 +1,11 @@
import { publishEvent } from "../events"
import {
CalculationType,
Event,
Table,
TableExportFormat,
View,
ViewCalculation,
ViewCalculationCreatedEvent,
ViewCalculationDeletedEvent,
ViewCalculationUpdatedEvent,
@ -11,11 +16,8 @@ import {
ViewFilterDeletedEvent,
ViewFilterUpdatedEvent,
ViewUpdatedEvent,
View,
ViewV2,
ViewCalculation,
Table,
TableExportFormat,
ViewJoinCreatedEvent,
} from "@budibase/types"
/* eslint-disable */
@ -29,17 +31,9 @@ async function created(view: ViewV2, timestamp?: string | number) {
await publishEvent(Event.VIEW_CREATED, properties, timestamp)
}
async function updated(newView: ViewV2) {
let viewJoins = 0
for (const key in newView.schema) {
if (newView.schema[key]?.columns) {
viewJoins += Object.keys(newView.schema[key]?.columns).length
}
}
async function updated(view: ViewV2) {
const properties: ViewUpdatedEvent = {
tableId: newView.tableId,
groupedFilters: newView.queryUI?.groups?.length || 0,
viewJoins,
tableId: view.tableId,
}
await publishEvent(Event.VIEW_UPDATED, properties)
}
@ -59,16 +53,27 @@ async function exported(table: Table, format: TableExportFormat) {
await publishEvent(Event.VIEW_EXPORTED, properties)
}
async function filterCreated(view: View, timestamp?: string | number) {
async function filterCreated(
{ tableId, filterGroups }: { tableId: string; filterGroups: number },
timestamp?: string | number
) {
const properties: ViewFilterCreatedEvent = {
tableId: view.tableId,
tableId,
filterGroups,
}
await publishEvent(Event.VIEW_FILTER_CREATED, properties, timestamp)
}
async function filterUpdated(view: View) {
async function filterUpdated({
tableId,
filterGroups,
}: {
tableId: string
filterGroups: number
}) {
const properties: ViewFilterUpdatedEvent = {
tableId: view.tableId,
tableId: tableId,
filterGroups,
}
await publishEvent(Event.VIEW_FILTER_UPDATED, properties)
}
@ -80,10 +85,16 @@ async function filterDeleted(view: View) {
await publishEvent(Event.VIEW_FILTER_DELETED, properties)
}
async function calculationCreated(view: View, timestamp?: string | number) {
async function calculationCreated(
{
tableId,
calculationType,
}: { tableId: string; calculationType: CalculationType },
timestamp?: string | number
) {
const properties: ViewCalculationCreatedEvent = {
tableId: view.tableId,
calculation: view.calculation as ViewCalculation,
tableId,
calculation: calculationType,
}
await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp)
}
@ -104,6 +115,13 @@ async function calculationDeleted(existingView: View) {
await publishEvent(Event.VIEW_CALCULATION_DELETED, properties)
}
async function viewJoinCreated(tableId: any, timestamp?: string | number) {
const properties: ViewJoinCreatedEvent = {
tableId,
}
await publishEvent(Event.VIEW_JOIN_CREATED, properties, timestamp)
}
export default {
created,
updated,
@ -115,4 +133,5 @@ export default {
calculationCreated,
calculationUpdated,
calculationDeleted,
viewJoinCreated,
}

View File

@ -117,6 +117,7 @@ beforeAll(async () => {
jest.spyOn(events.view, "calculationCreated")
jest.spyOn(events.view, "calculationUpdated")
jest.spyOn(events.view, "calculationDeleted")
jest.spyOn(events.view, "viewJoinCreated")
jest.spyOn(events.plugin, "init")
jest.spyOn(events.plugin, "imported")

View File

@ -65,31 +65,6 @@ export async function save(ctx: Ctx) {
builderSocket?.emitTableUpdate(ctx, table)
}
export async function filterEvents(existingView: View, newView: View) {
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)
}
}
export async function destroy(ctx: Ctx) {
const db = context.getAppDB()
const viewName = decodeURIComponent(ctx.params.viewName)

View File

@ -16,10 +16,14 @@ import {
CountCalculationFieldMetadata,
CreateViewResponse,
UpdateViewResponse,
View,
Event,
} from "@budibase/types"
import { events } from "@budibase/backend-core"
import { builderSocket, gridSocket } from "../../../websockets"
import { helpers } from "@budibase/shared-core"
import isEqual from "lodash/isEqual"
import { publishEvent } from "@budibase/backend-core/src/events"
function stripUnknownFields(
field: ViewFieldMetadata
@ -164,6 +168,54 @@ export async function create(ctx: Ctx<CreateViewRequest, CreateViewResponse>) {
gridSocket?.emitViewUpdate(ctx, result)
}
async function handleViewEvents(existingView: ViewV2, view: ViewV2) {
// Grouped filters
if (view.queryUI?.groups) {
const filterGroups = view.queryUI?.groups?.length || 0
const properties = { filterGroups, tableId: view.tableId }
if (!existingView?.queryUI) {
await publishEvent(Event.VIEW_FILTER_CREATED, properties)
await events.view.filterCreated(properties)
} else {
if (
filterGroups >
((existingView && existingView?.queryUI?.groups?.length) || 0)
) {
await events.view.filterUpdated(properties)
}
}
}
// if new columns in the view
for (const key in view.schema) {
if (!existingView?.schema?.[key]) {
const newColumn = view.schema[key]
// view calculations
// @ts-expect-error non calculation types just won't have the calculationType field
const calculationType = newColumn.calculationType
if (calculationType) {
// Send the event
await events.view.calculationCreated({
calculationType,
tableId: view.tableId,
})
}
// view joins
if (newColumn.columns) {
for (const column in newColumn?.columns) {
// if the new column is visible and it wasn't before
if (!existingView?.schema?.[key].columns?.[column].visible) {
// new view join exposing a column
await events.view.viewJoinCreated({ tableId: view.tableId })
}
}
}
}
}
}
export async function update(ctx: Ctx<UpdateViewRequest, UpdateViewResponse>) {
const view = ctx.request.body
@ -191,8 +243,12 @@ export async function update(ctx: Ctx<UpdateViewRequest, UpdateViewResponse>) {
primaryDisplay: view.primaryDisplay,
}
const { view: result } = await sdk.views.update(tableId, parsedView)
const { view: result, existingView } = await sdk.views.update(
tableId,
parsedView
)
await handleViewEvents(existingView, result)
await events.view.updated(result)
ctx.body = { data: result }

View File

@ -42,7 +42,7 @@ import {
} from "../../../integrations/tests/utils"
import merge from "lodash/merge"
import { quotas } from "@budibase/pro"
import { db, roles, context } from "@budibase/backend-core"
import { db, roles, context, events } from "@budibase/backend-core"
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
@ -129,6 +129,7 @@ if (descriptions.length) {
id: expect.stringMatching(new RegExp(`${table._id!}_`)),
version: 2,
})
expect(events.view.created).toHaveBeenCalledTimes(1)
})
it("can persist views with all fields", async () => {
@ -195,6 +196,7 @@ if (descriptions.length) {
}
expect(res).toEqual(expected)
expect(events.view.created).toHaveBeenCalledTimes(1)
})
it("can create a view with just a query field, no queryUI, for backwards compatibility", async () => {
@ -224,6 +226,7 @@ if (descriptions.length) {
},
}
const res = await config.api.viewV2.create(newView)
expect(events.view.created).toHaveBeenCalledTimes(1)
const expected: ViewV2 = {
...newView,
@ -283,6 +286,7 @@ if (descriptions.length) {
}
const createdView = await config.api.viewV2.create(newView)
expect(events.view.created).toHaveBeenCalledTimes(1)
expect(createdView).toEqual({
...newView,
@ -990,6 +994,7 @@ if (descriptions.length) {
expect((await config.api.table.get(tableId)).views).toEqual({
[view.name]: expected,
})
expect(events.view.updated).toHaveBeenCalledTimes(1)
})
it("can update all fields", async () => {
@ -1621,6 +1626,7 @@ if (descriptions.length) {
field: "age",
}
await config.api.viewV2.update(view)
expect(events.view.calculationCreated).toHaveBeenCalledTimes(1)
const { rows } = await config.api.row.search(view.id)
expect(rows).toHaveLength(2)
@ -2154,6 +2160,7 @@ if (descriptions.length) {
}),
})
)
expect(events.view.viewJoinCreated).not.toBeCalled()
})
it("does not rename columns with the same name but from other tables", async () => {

View File

@ -118,6 +118,7 @@ export enum Event {
VIEW_CALCULATION_CREATED = "view:calculation:created",
VIEW_CALCULATION_UPDATED = "view:calculation:updated",
VIEW_CALCULATION_DELETED = "view:calculation:deleted",
VIEW_JOIN_CREATED = "view:join:created",
// ROWS
ROWS_CREATED = "rows:created",
@ -192,6 +193,9 @@ export enum Event {
// AUDIT LOG
AUDIT_LOGS_FILTERED = "audit_log:filtered",
AUDIT_LOGS_DOWNLOADED = "audit_log:downloaded",
// ROW ACTION
ROW_ACTION_CREATED = "row_action:created",
}
export const UserGroupSyncEvents: Event[] = [
@ -376,6 +380,7 @@ export const AuditedEventFriendlyName: Record<Event, string | undefined> = {
[Event.VIEW_CALCULATION_CREATED]: undefined,
[Event.VIEW_CALCULATION_UPDATED]: undefined,
[Event.VIEW_CALCULATION_DELETED]: undefined,
[Event.VIEW_JOIN_CREATED]: undefined,
// SERVED - NOT AUDITED
[Event.SERVED_BUILDER]: undefined,
@ -395,6 +400,9 @@ export const AuditedEventFriendlyName: Record<Event, string | undefined> = {
// AUDIT LOG - NOT AUDITED
[Event.AUDIT_LOGS_FILTERED]: undefined,
[Event.AUDIT_LOGS_DOWNLOADED]: undefined,
// ROW ACTIONS - NOT AUDITED
[Event.ROW_ACTION_CREATED]: undefined,
}
// properties added at the final stage of the event pipeline

View File

@ -1,4 +1,4 @@
import { ViewCalculation, ViewV2Type } from "../../documents"
import { CalculationType, ViewCalculation, ViewV2Type } from "../../documents"
import { BaseEvent, TableExportFormat } from "./event"
export interface ViewCreatedEvent extends BaseEvent {
@ -9,8 +9,6 @@ export interface ViewCreatedEvent extends BaseEvent {
export interface ViewUpdatedEvent extends BaseEvent {
tableId: string
groupedFilters: number
viewJoins: number
}
export interface ViewDeletedEvent extends BaseEvent {
@ -24,10 +22,12 @@ export interface ViewExportedEvent extends BaseEvent {
export interface ViewFilterCreatedEvent extends BaseEvent {
tableId: string
filterGroups: number
}
export interface ViewFilterUpdatedEvent extends BaseEvent {
tableId: string
filterGroups: number
}
export interface ViewFilterDeletedEvent extends BaseEvent {
@ -36,7 +36,7 @@ export interface ViewFilterDeletedEvent extends BaseEvent {
export interface ViewCalculationCreatedEvent extends BaseEvent {
tableId: string
calculation: ViewCalculation
calculation: CalculationType
}
export interface ViewCalculationUpdatedEvent extends BaseEvent {
@ -48,3 +48,7 @@ export interface ViewCalculationDeletedEvent extends BaseEvent {
tableId: string
calculation: ViewCalculation
}
export interface ViewJoinCreatedEvent extends BaseEvent {
tableId: string
}