Merge master.

This commit is contained in:
Sam Rose 2024-12-09 17:18:38 +00:00
commit 76b5190454
No known key found for this signature in database
30 changed files with 322 additions and 221 deletions

View File

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

View File

@ -23,3 +23,4 @@ export { default as plugin } from "./plugin"
export { default as backup } from "./backup"
export { default as environmentVariable } from "./environmentVariable"
export { default as auditLog } from "./auditLog"
export { default as rowAction } from "./rowAction"

View File

@ -0,0 +1,13 @@
import { publishEvent } from "../events"
import { Event, RowActionCreatedEvent } from "@budibase/types"
async function created(
rowAction: RowActionCreatedEvent,
timestamp?: string | number
) {
await publishEvent(Event.ROW_ACTION_CREATED, rowAction, timestamp)
}
export default {
created,
}

View File

@ -1,13 +1,14 @@
import { publishEvent } from "../events"
import {
Event,
TableExportFormat,
FieldType,
Table,
TableCreatedEvent,
TableUpdatedEvent,
TableDeletedEvent,
TableExportedEvent,
TableExportFormat,
TableImportedEvent,
TableUpdatedEvent,
} from "@budibase/types"
async function created(table: Table, timestamp?: string | number) {
@ -20,15 +21,35 @@ async function created(table: Table, timestamp?: string | number) {
await publishEvent(Event.TABLE_CREATED, properties, timestamp)
}
async function updated(table: Table) {
async function updated(oldTable: Table, newTable: Table) {
// only publish the event if it has fields we are interested in
let defaultValues, aiColumn
// check that new fields have been added
for (const key in newTable.schema) {
if (!oldTable.schema[key]) {
const newColumn = newTable.schema[key]
if ("default" in newColumn && newColumn.default != null) {
defaultValues = true
}
if (newColumn.type === FieldType.AI) {
aiColumn = newColumn.operation
}
}
}
const properties: TableUpdatedEvent = {
tableId: table._id as string,
tableId: newTable._id as string,
defaultValues,
aiColumn,
audited: {
name: table.name,
name: newTable.name,
},
}
if (defaultValues || aiColumn) {
await publishEvent(Event.TABLE_UPDATED, properties)
}
}
async function deleted(table: Table) {
const properties: TableDeletedEvent = {

View File

@ -1,6 +1,11 @@
import { publishEvent } from "../events"
import {
CalculationType,
Event,
Table,
TableExportFormat,
View,
ViewCalculation,
ViewCalculationCreatedEvent,
ViewCalculationDeletedEvent,
ViewCalculationUpdatedEvent,
@ -11,20 +16,20 @@ import {
ViewFilterDeletedEvent,
ViewFilterUpdatedEvent,
ViewUpdatedEvent,
View,
ViewCalculation,
Table,
TableExportFormat,
ViewV2,
ViewJoinCreatedEvent,
} from "@budibase/types"
async function created(view: View, timestamp?: string | number) {
async function created(view: ViewV2, timestamp?: string | number) {
const properties: ViewCreatedEvent = {
name: view.name,
type: view.type,
tableId: view.tableId,
}
await publishEvent(Event.VIEW_CREATED, properties, timestamp)
}
async function updated(view: View) {
async function updated(view: ViewV2) {
const properties: ViewUpdatedEvent = {
tableId: view.tableId,
}
@ -46,16 +51,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)
}
@ -67,10 +83,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)
}
@ -91,6 +113,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,
@ -102,4 +131,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

@ -46,7 +46,7 @@
}
} else {
// Leave the core data as it is
return testData
return cloneDeep(testData)
}
}
@ -63,7 +63,10 @@
return true
}
$: testData = testData || parseTestData($selectedAutomation.data.testData)
$: currentTestData = $selectedAutomation.data.testData
// Can be updated locally to avoid race condition when testing
$: testData = parseTestData(currentTestData)
$: {
// clone the trigger so we're not mutating the reference
@ -85,7 +88,7 @@
required => testData?.[required] || required !== "row"
)
function parseTestJSON(e) {
async function parseTestJSON(e) {
let jsonUpdate
try {
@ -105,7 +108,9 @@
}
}
const updatedAuto =
automationStore.actions.addTestDataToAutomation(jsonUpdate)
await automationStore.actions.save(updatedAuto)
}
const testAutomation = async () => {
@ -150,10 +155,14 @@
{#if selectedValues}
<div class="tab-content-padding">
<AutomationBlockSetup
bind:testData
{schemaProperties}
isTestModal
{testData}
block={trigger}
on:update={e => {
const { testData: updatedTestData } = e.detail
testData = updatedTestData
}}
/>
</div>
{/if}
@ -162,7 +171,7 @@
<TextArea
value={JSON.stringify($selectedAutomation.data.testData, null, 2)}
error={failedParse}
on:change={e => parseTestJSON(e)}
on:change={async e => await parseTestJSON(e)}
/>
</div>
{/if}

View File

@ -48,7 +48,7 @@
import { QueryUtils, Utils, search, memo } from "@budibase/frontend-core"
import { getSchemaForDatasourcePlus } from "dataBinding"
import { TriggerStepID, ActionStepID } from "constants/backend/automations"
import { onMount } from "svelte"
import { onMount, createEventDispatcher } from "svelte"
import { writable } from "svelte/store"
import { cloneDeep } from "lodash/fp"
import {
@ -67,6 +67,8 @@
export let isTestModal = false
export let bindings = []
const dispatch = createEventDispatcher()
// Stop unnecessary rendering
const memoBlock = memo(block)
@ -503,15 +505,7 @@
row: { "Active": true, "Order Id" : 14, ... }
})
*/
const onChange = async update => {
if (isTestModal) {
testData = update
}
updateAutomation(update)
}
const updateAutomation = Utils.sequential(async update => {
const onChange = Utils.sequential(async update => {
const request = cloneDeep(update)
// Process app trigger updates
if (isTrigger && !isTestModal) {
@ -540,7 +534,9 @@
}
try {
if (isTestModal) {
let newTestData = { schema }
// Be sure to merge in the testData prop data, as it can contain custom
// default data
let newTestData = { schema, ...testData }
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
if (stepId === TriggerStepID.WEBHOOK) {
@ -557,7 +553,13 @@
...request,
}
await automationStore.actions.addTestDataToAutomation(newTestData)
const updatedAuto =
automationStore.actions.addTestDataToAutomation(newTestData)
// Ensure the test request has the latest info.
dispatch("update", updatedAuto)
await automationStore.actions.save(updatedAuto)
} else {
const data = { schema, ...request }
await automationStore.actions.updateBlockInputs(block, data)

View File

@ -880,13 +880,13 @@ const automationActions = store => ({
appId,
})
},
addTestDataToAutomation: async data => {
addTestDataToAutomation: data => {
let newAutomation = cloneDeep(get(selectedAutomation).data)
newAutomation.testData = {
...newAutomation.testData,
...data,
}
await store.actions.save(newAutomation)
return newAutomation
},
constructBlock(type, stepId, blockDefinition) {
let newName

View File

@ -6,6 +6,7 @@ import {
RowActionResponse,
RowActionsResponse,
} from "@budibase/types"
import { events } from "@budibase/backend-core"
import sdk from "../../../sdk"
async function getTable(ctx: Ctx) {
@ -59,6 +60,8 @@ export async function create(
name: ctx.request.body.name,
})
await events.rowAction.created(createdAction)
ctx.body = {
tableId,
id: createdAction.id,

View File

@ -45,13 +45,13 @@ export async function updateTable(
inputs.created = true
}
try {
const { datasource, table } = await sdk.tables.external.save(
const { datasource, oldTable, table } = await sdk.tables.external.save(
datasourceId!,
inputs,
{ tableId, renaming }
)
builderSocket?.emitDatasourceUpdate(ctx, datasource)
return table
return { table, oldTable }
} catch (err: any) {
if (err instanceof Error) {
ctx.throw(400, err.message)

View File

@ -120,8 +120,15 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
await events.table.created(savedTable)
} else {
const api = pickApi({ table })
savedTable = await api.updateTable(ctx, renaming)
await events.table.updated(savedTable)
const { table: updatedTable, oldTable } = await api.updateTable(
ctx,
renaming
)
savedTable = updatedTable
if (oldTable) {
await events.table.updated(oldTable, savedTable)
}
}
if (renaming) {
await sdk.views.renameLinkedViews(savedTable, renaming)

View File

@ -30,14 +30,14 @@ export async function updateTable(
}
try {
const { table } = await sdk.tables.internal.save(tableToSave, {
const { table, oldTable } = await sdk.tables.internal.save(tableToSave, {
userId: ctx.user._id,
rowsToImport: rows,
tableId: ctx.request.body._id,
renaming,
})
return table
return { table, oldTable }
} catch (err: any) {
if (err instanceof Error) {
ctx.throw(400, err.message)

View File

@ -19,8 +19,6 @@ import { builderSocket } from "../../../websockets"
const cloneDeep = require("lodash/cloneDeep")
import isEqual from "lodash/isEqual"
export async function fetch(ctx: Ctx) {
ctx.body = await getViews()
}
@ -60,71 +58,11 @@ export async function save(ctx: Ctx) {
existingTable.views[viewName] = existingTable.views[originalName]
}
await db.put(table)
await handleViewEvents(
existingTable.views[viewName] as View,
table.views[viewName]
)
ctx.body = table.views[viewName]
builderSocket?.emitTableUpdate(ctx, table)
}
export async function calculationEvents(existingView: View, newView: View) {
const existingCalculation = existingView && existingView.calculation
const newCalculation = newView && newView.calculation
if (existingCalculation && !newCalculation) {
await events.view.calculationDeleted(existingView)
}
if (!existingCalculation && newCalculation) {
await events.view.calculationCreated(newView)
}
if (
existingCalculation &&
newCalculation &&
existingCalculation !== newCalculation
) {
await events.view.calculationUpdated(newView)
}
}
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)
}
}
async function handleViewEvents(existingView: View, newView: View) {
if (!existingView) {
await events.view.created(newView)
} else {
await events.view.updated(newView)
}
await calculationEvents(existingView, newView)
await filterEvents(existingView, newView)
}
export async function destroy(ctx: Ctx) {
const db = context.getAppDB()
const viewName = decodeURIComponent(ctx.params.viewName)

View File

@ -17,6 +17,7 @@ import {
CreateViewResponse,
UpdateViewResponse,
} from "@budibase/types"
import { events } from "@budibase/backend-core"
import { builderSocket, gridSocket } from "../../../websockets"
import { helpers } from "@budibase/shared-core"
@ -150,6 +151,9 @@ export async function create(ctx: Ctx<CreateViewRequest, CreateViewResponse>) {
primaryDisplay: view.primaryDisplay,
}
const result = await sdk.views.create(tableId, parsedView)
await events.view.created(result)
ctx.status = 201
ctx.body = {
data: result,
@ -160,6 +164,46 @@ export async function create(ctx: Ctx<CreateViewRequest, CreateViewResponse>) {
gridSocket?.emitViewUpdate(ctx, result)
}
async function handleViewFilterEvents(existingView: ViewV2, view: ViewV2) {
const filterGroups = view.queryUI?.groups?.length || 0
const properties = { filterGroups, tableId: view.tableId }
if (
filterGroups >= 2 &&
filterGroups > (existingView?.queryUI?.groups?.length || 0)
) {
await events.view.filterUpdated(properties)
}
}
async function handleViewEvents(existingView: ViewV2, view: ViewV2) {
// Grouped filters
if (view.queryUI?.groups) {
await handleViewFilterEvents(existingView, view)
}
// if new columns in the view
for (const key in view.schema) {
if ("calculationType" in view.schema[key] && !existingView?.schema?.[key]) {
await events.view.calculationCreated({
calculationType: view.schema[key].calculationType,
tableId: view.tableId,
})
}
// view joins
for (const column in view.schema[key]?.columns ?? []) {
// if the new column is visible and it wasn't before
if (
!existingView?.schema?.[key].columns?.[column].visible &&
view.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
@ -187,10 +231,15 @@ export async function update(ctx: Ctx<UpdateViewRequest, UpdateViewResponse>) {
primaryDisplay: view.primaryDisplay,
}
const result = await sdk.views.update(tableId, parsedView)
ctx.body = {
data: result,
}
const { view: result, existingView } = await sdk.views.update(
tableId,
parsedView
)
await handleViewEvents(existingView, result)
await events.view.updated(result)
ctx.body = { data: result }
const table = await sdk.tables.getTable(tableId)
builderSocket?.emitTableUpdate(ctx, table)

View File

@ -247,6 +247,9 @@ if (descriptions.length) {
},
},
},
primary: ["_id"],
views: {},
sql: true,
})
)
@ -254,9 +257,8 @@ if (descriptions.length) {
...table,
name: generator.guid(),
})
expect(events.table.updated).toHaveBeenCalledTimes(1)
expect(events.table.updated).toHaveBeenCalledWith(updatedTable)
expect(events.table.updated).toHaveBeenCalledWith(table, updatedTable)
})
it("updates all the row fields for a table when a schema key is renamed", async () => {

View File

@ -73,25 +73,12 @@ describe("/views", () => {
}
describe("create", () => {
it("returns a success message when the view is successfully created", async () => {
await saveView()
expect(events.view.created).toHaveBeenCalledTimes(1)
})
it("creates a view with a calculation", async () => {
jest.clearAllMocks()
const view = await saveView({ calculation: ViewCalculation.COUNT })
expect(view.tableId).toBe(table._id)
expect(events.view.created).toHaveBeenCalledTimes(1)
expect(events.view.updated).not.toHaveBeenCalled()
expect(events.view.calculationCreated).toHaveBeenCalledTimes(1)
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
expect(events.view.filterCreated).not.toHaveBeenCalled()
expect(events.view.filterUpdated).not.toHaveBeenCalled()
expect(events.view.filterDeleted).not.toHaveBeenCalled()
})
it("creates a view with a filter", async () => {
@ -109,14 +96,6 @@ describe("/views", () => {
})
expect(view.tableId).toBe(table._id)
expect(events.view.created).toHaveBeenCalledTimes(1)
expect(events.view.updated).not.toHaveBeenCalled()
expect(events.view.calculationCreated).not.toHaveBeenCalled()
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
expect(events.view.filterCreated).toHaveBeenCalledTimes(1)
expect(events.view.filterUpdated).not.toHaveBeenCalled()
expect(events.view.filterDeleted).not.toHaveBeenCalled()
})
it("updates the table row with the new view metadata", async () => {
@ -166,13 +145,6 @@ describe("/views", () => {
await saveView()
expect(events.view.created).not.toHaveBeenCalled()
expect(events.view.updated).toHaveBeenCalledTimes(1)
expect(events.view.calculationCreated).not.toHaveBeenCalled()
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
expect(events.view.filterCreated).not.toHaveBeenCalled()
expect(events.view.filterUpdated).not.toHaveBeenCalled()
expect(events.view.filterDeleted).not.toHaveBeenCalled()
})
it("updates a view calculation", async () => {
@ -182,13 +154,6 @@ describe("/views", () => {
await saveView({ calculation: ViewCalculation.COUNT })
expect(events.view.created).not.toHaveBeenCalled()
expect(events.view.updated).toHaveBeenCalledTimes(1)
expect(events.view.calculationCreated).not.toHaveBeenCalled()
expect(events.view.calculationUpdated).toHaveBeenCalledTimes(1)
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
expect(events.view.filterCreated).not.toHaveBeenCalled()
expect(events.view.filterUpdated).not.toHaveBeenCalled()
expect(events.view.filterDeleted).not.toHaveBeenCalled()
})
it("deletes a view calculation", async () => {
@ -198,13 +163,6 @@ describe("/views", () => {
await saveView({ calculation: undefined })
expect(events.view.created).not.toHaveBeenCalled()
expect(events.view.updated).toHaveBeenCalledTimes(1)
expect(events.view.calculationCreated).not.toHaveBeenCalled()
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
expect(events.view.calculationDeleted).toHaveBeenCalledTimes(1)
expect(events.view.filterCreated).not.toHaveBeenCalled()
expect(events.view.filterUpdated).not.toHaveBeenCalled()
expect(events.view.filterDeleted).not.toHaveBeenCalled()
})
it("updates a view filter", async () => {
@ -230,13 +188,6 @@ describe("/views", () => {
})
expect(events.view.created).not.toHaveBeenCalled()
expect(events.view.updated).toHaveBeenCalledTimes(1)
expect(events.view.calculationCreated).not.toHaveBeenCalled()
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
expect(events.view.filterCreated).not.toHaveBeenCalled()
expect(events.view.filterUpdated).toHaveBeenCalledTimes(1)
expect(events.view.filterDeleted).not.toHaveBeenCalled()
})
it("deletes a view filter", async () => {
@ -254,13 +205,6 @@ describe("/views", () => {
await saveView({ filters: [] })
expect(events.view.created).not.toHaveBeenCalled()
expect(events.view.updated).toHaveBeenCalledTimes(1)
expect(events.view.calculationCreated).not.toHaveBeenCalled()
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
expect(events.view.filterCreated).not.toHaveBeenCalled()
expect(events.view.filterUpdated).not.toHaveBeenCalled()
expect(events.view.filterDeleted).toHaveBeenCalledTimes(1)
})
})

View File

@ -1,39 +1,39 @@
import {
ArrayOperator,
BasicOperator,
BBReferenceFieldSubType,
CalculationType,
CreateViewRequest,
Datasource,
EmptyFilterOption,
FieldSchema,
FieldType,
INTERNAL_TABLE_SOURCE_ID,
JsonFieldSubType,
JsonTypes,
LegacyFilter,
NumericCalculationFieldMetadata,
PermissionLevel,
QuotaUsageType,
RelationshipType,
RenameColumn,
Row,
SaveTableRequest,
SearchFilters,
SearchResponse,
SearchViewRowRequest,
SortOrder,
SortType,
StaticQuotaName,
Table,
TableSchema,
TableSourceType,
UILogicalOperator,
UISearchFilter,
UpdateViewRequest,
ViewV2,
SearchResponse,
BasicOperator,
CalculationType,
RelationshipType,
TableSchema,
RenameColumn,
BBReferenceFieldSubType,
NumericCalculationFieldMetadata,
ViewV2Schema,
ViewV2Type,
JsonTypes,
EmptyFilterOption,
JsonFieldSubType,
UISearchFilter,
LegacyFilter,
SearchViewRowRequest,
ArrayOperator,
UILogicalOperator,
SearchFilters,
} from "@budibase/types"
import { generator, mocks } from "@budibase/backend-core/tests"
import {
@ -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 { context, db, events, roles } 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,46 @@ if (descriptions.length) {
expect((await config.api.table.get(tableId)).views).toEqual({
[view.name]: expected,
})
expect(events.view.updated).toHaveBeenCalledTimes(1)
})
it("handles view grouped filter events", async () => {
view.queryUI = {
logicalOperator: UILogicalOperator.ALL,
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
groups: [
{
logicalOperator: UILogicalOperator.ALL,
filters: [
{
operator: BasicOperator.EQUAL,
field: "newField",
value: "newValue",
},
],
},
],
}
await config.api.viewV2.update(view)
expect(events.view.filterUpdated).not.toHaveBeenCalled()
// @ts-ignore
view.queryUI.groups.push({
logicalOperator: UILogicalOperator.ALL,
filters: [
{
operator: BasicOperator.EQUAL,
field: "otherField",
value: "otherValue",
},
],
})
await config.api.viewV2.update(view)
expect(events.view.filterUpdated).toHaveBeenCalledWith({
filterGroups: 2,
tableId: view.tableId,
})
})
it("can update all fields", async () => {
@ -1621,6 +1665,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 +2199,7 @@ if (descriptions.length) {
}),
})
)
expect(events.view.viewJoinCreated).not.toHaveBeenCalled()
})
it("does not rename columns with the same name but from other tables", async () => {
@ -2226,6 +2272,36 @@ if (descriptions.length) {
)
})
it("handles events for changing column visibility from default false", async () => {
let auxTable = await createAuxTable()
let aux2Table = await createAuxTable()
const table = await createMainTable([
{ name: "aux", tableId: auxTable._id!, fk: "fk_aux" },
{ name: "aux2", tableId: aux2Table._id!, fk: "fk_aux2" },
])
const view = await createView(table._id!, {
aux: {
visible: true,
columns: {
name: { visible: false, readonly: true },
},
},
aux2: {
visible: true,
columns: {
name: { visible: false, readonly: true },
},
},
})
// @ts-expect-error column exists above
view.schema.aux2.columns.name.visible = true
await config.api.viewV2.update(view)
expect(events.view.viewJoinCreated).toHaveBeenCalledTimes(1)
})
it("updates all views references", async () => {
let auxTable = await createAuxTable()

View File

@ -7,24 +7,6 @@ export const backfill = async (appDb: Database, timestamp: string | number) => {
for (const table of tables) {
await events.table.created(table, timestamp)
if (table.views) {
for (const view of Object.values(table.views)) {
if (sdk.views.isV2(view)) {
continue
}
await events.view.created(view, timestamp)
if (view.calculation) {
await events.view.calculationCreated(view, timestamp)
}
if (view.filters?.length) {
await events.view.filterCreated(view, timestamp)
}
}
}
}
return tables.length

View File

@ -73,16 +73,12 @@ describe("migrations", () => {
expect(events.query.created).toHaveBeenCalledTimes(2)
expect(events.role.created).toHaveBeenCalledTimes(3) // created roles + admin (created on table creation)
expect(events.table.created).toHaveBeenCalledTimes(3)
expect(events.view.created).toHaveBeenCalledTimes(2)
expect(events.view.calculationCreated).toHaveBeenCalledTimes(1)
expect(events.view.filterCreated).toHaveBeenCalledTimes(1)
expect(events.screen.created).toHaveBeenCalledTimes(2)
expect(events.backfill.appSucceeded).toHaveBeenCalledTimes(2)
// to make sure caching is working as expected
expect(
events.processors.analyticsProcessor.processEvent
).toHaveBeenCalledTimes(24) // Addtion of of the events above
).toHaveBeenCalledTimes(20) // Addition of of the events above
})
})
})

View File

@ -281,7 +281,7 @@ export async function save(
tableToSave.sql = true
}
return { datasource: updatedDatasource, table: tableToSave }
return { datasource: updatedDatasource, table: tableToSave, oldTable }
}
export async function destroy(datasourceId: string, table: Table) {

View File

@ -171,7 +171,7 @@ export async function save(
}
// has to run after, make sure it has _id
await runStaticFormulaChecks(table, { oldTable, deletion: false })
return { table }
return { table, oldTable }
}
export async function destroy(table: Table) {

View File

@ -63,7 +63,7 @@ export async function create(
export async function update(
tableId: string,
view: Readonly<ViewV2>
): Promise<ViewV2> {
): Promise<{ view: Readonly<ViewV2>; existingView: ViewV2 }> {
const db = context.getAppDB()
const { datasourceId, tableName } = breakExternalTableId(tableId)
@ -87,7 +87,7 @@ export async function update(
delete views[existingView.name]
views[view.name] = view
await db.put(ds)
return view
return { view, existingView } as { view: ViewV2; existingView: ViewV2 }
}
export async function remove(viewId: string): Promise<ViewV2> {

View File

@ -315,7 +315,10 @@ export async function create(
return view
}
export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
export async function update(
tableId: string,
view: ViewV2
): Promise<{ view: ViewV2; existingView: ViewV2 }> {
await guardViewSchema(tableId, view)
return pickApi(tableId).update(tableId, view)

View File

@ -54,7 +54,7 @@ export async function create(
export async function update(
tableId: string,
view: Readonly<ViewV2>
): Promise<ViewV2> {
): Promise<{ view: ViewV2; existingView: ViewV2 }> {
const db = context.getAppDB()
const table = await sdk.tables.getTable(tableId)
table.views ??= {}
@ -76,7 +76,7 @@ export async function update(
delete table.views[existingView.name]
table.views[view.name] = view
await db.put(table)
return view
return { view, existingView } as { view: ViewV2; existingView: ViewV2 }
}
export async function remove(viewId: string): Promise<ViewV2> {

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

@ -24,3 +24,4 @@ export * from "./plugin"
export * from "./backup"
export * from "./environmentVariable"
export * from "./auditLog"
export * from "./rowAction"

View File

@ -0,0 +1,6 @@
import { BaseEvent } from "./event"
export interface RowActionCreatedEvent extends BaseEvent {
name: string
automationId: string
}

View File

@ -1,4 +1,5 @@
import { BaseEvent, TableExportFormat } from "./event"
import { AIOperationEnum } from "../ai"
export interface TableCreatedEvent extends BaseEvent {
tableId: string
@ -9,6 +10,8 @@ export interface TableCreatedEvent extends BaseEvent {
export interface TableUpdatedEvent extends BaseEvent {
tableId: string
defaultValues: boolean | undefined
aiColumn: AIOperationEnum | undefined
audited: {
name: string
}

View File

@ -1,7 +1,9 @@
import { ViewCalculation } from "../../documents"
import { CalculationType, ViewCalculation, ViewV2Type } from "../../documents"
import { BaseEvent, TableExportFormat } from "./event"
export interface ViewCreatedEvent extends BaseEvent {
name: string
type?: ViewV2Type
tableId: string
}
@ -20,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 {
@ -32,7 +36,7 @@ export interface ViewFilterDeletedEvent extends BaseEvent {
export interface ViewCalculationCreatedEvent extends BaseEvent {
tableId: string
calculation: ViewCalculation
calculation: CalculationType
}
export interface ViewCalculationUpdatedEvent extends BaseEvent {
@ -44,3 +48,7 @@ export interface ViewCalculationDeletedEvent extends BaseEvent {
tableId: string
calculation: ViewCalculation
}
export interface ViewJoinCreatedEvent extends BaseEvent {
tableId: string
}