Merge branch 'master' into eslint-recommended-1
This commit is contained in:
commit
93b086bb4e
|
@ -13,9 +13,7 @@ const EXCLUDED_EVENTS: Event[] = [
|
||||||
Event.ROLE_UPDATED,
|
Event.ROLE_UPDATED,
|
||||||
Event.DATASOURCE_UPDATED,
|
Event.DATASOURCE_UPDATED,
|
||||||
Event.QUERY_UPDATED,
|
Event.QUERY_UPDATED,
|
||||||
Event.TABLE_UPDATED,
|
|
||||||
Event.VIEW_UPDATED,
|
Event.VIEW_UPDATED,
|
||||||
Event.VIEW_FILTER_UPDATED,
|
|
||||||
Event.VIEW_CALCULATION_UPDATED,
|
Event.VIEW_CALCULATION_UPDATED,
|
||||||
Event.AUTOMATION_TRIGGER_UPDATED,
|
Event.AUTOMATION_TRIGGER_UPDATED,
|
||||||
Event.USER_GROUP_UPDATED,
|
Event.USER_GROUP_UPDATED,
|
||||||
|
|
|
@ -23,3 +23,4 @@ export { default as plugin } from "./plugin"
|
||||||
export { default as backup } from "./backup"
|
export { default as backup } from "./backup"
|
||||||
export { default as environmentVariable } from "./environmentVariable"
|
export { default as environmentVariable } from "./environmentVariable"
|
||||||
export { default as auditLog } from "./auditLog"
|
export { default as auditLog } from "./auditLog"
|
||||||
|
export { default as rowAction } from "./rowAction"
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
|
@ -1,13 +1,14 @@
|
||||||
import { publishEvent } from "../events"
|
import { publishEvent } from "../events"
|
||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
TableExportFormat,
|
FieldType,
|
||||||
Table,
|
Table,
|
||||||
TableCreatedEvent,
|
TableCreatedEvent,
|
||||||
TableUpdatedEvent,
|
|
||||||
TableDeletedEvent,
|
TableDeletedEvent,
|
||||||
TableExportedEvent,
|
TableExportedEvent,
|
||||||
|
TableExportFormat,
|
||||||
TableImportedEvent,
|
TableImportedEvent,
|
||||||
|
TableUpdatedEvent,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
async function created(table: Table, timestamp?: string | number) {
|
async function created(table: Table, timestamp?: string | number) {
|
||||||
|
@ -20,14 +21,34 @@ async function created(table: Table, timestamp?: string | number) {
|
||||||
await publishEvent(Event.TABLE_CREATED, properties, timestamp)
|
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 = {
|
const properties: TableUpdatedEvent = {
|
||||||
tableId: table._id as string,
|
tableId: newTable._id as string,
|
||||||
|
defaultValues,
|
||||||
|
aiColumn,
|
||||||
audited: {
|
audited: {
|
||||||
name: table.name,
|
name: newTable.name,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if (defaultValues || aiColumn) {
|
||||||
await publishEvent(Event.TABLE_UPDATED, properties)
|
await publishEvent(Event.TABLE_UPDATED, properties)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleted(table: Table) {
|
async function deleted(table: Table) {
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { publishEvent } from "../events"
|
import { publishEvent } from "../events"
|
||||||
import {
|
import {
|
||||||
|
CalculationType,
|
||||||
Event,
|
Event,
|
||||||
|
Table,
|
||||||
|
TableExportFormat,
|
||||||
|
View,
|
||||||
|
ViewCalculation,
|
||||||
ViewCalculationCreatedEvent,
|
ViewCalculationCreatedEvent,
|
||||||
ViewCalculationDeletedEvent,
|
ViewCalculationDeletedEvent,
|
||||||
ViewCalculationUpdatedEvent,
|
ViewCalculationUpdatedEvent,
|
||||||
|
@ -11,20 +16,20 @@ import {
|
||||||
ViewFilterDeletedEvent,
|
ViewFilterDeletedEvent,
|
||||||
ViewFilterUpdatedEvent,
|
ViewFilterUpdatedEvent,
|
||||||
ViewUpdatedEvent,
|
ViewUpdatedEvent,
|
||||||
View,
|
ViewV2,
|
||||||
ViewCalculation,
|
ViewJoinCreatedEvent,
|
||||||
Table,
|
|
||||||
TableExportFormat,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
async function created(view: View, timestamp?: string | number) {
|
async function created(view: ViewV2, timestamp?: string | number) {
|
||||||
const properties: ViewCreatedEvent = {
|
const properties: ViewCreatedEvent = {
|
||||||
|
name: view.name,
|
||||||
|
type: view.type,
|
||||||
tableId: view.tableId,
|
tableId: view.tableId,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.VIEW_CREATED, properties, timestamp)
|
await publishEvent(Event.VIEW_CREATED, properties, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updated(view: View) {
|
async function updated(view: ViewV2) {
|
||||||
const properties: ViewUpdatedEvent = {
|
const properties: ViewUpdatedEvent = {
|
||||||
tableId: view.tableId,
|
tableId: view.tableId,
|
||||||
}
|
}
|
||||||
|
@ -46,16 +51,27 @@ async function exported(table: Table, format: TableExportFormat) {
|
||||||
await publishEvent(Event.VIEW_EXPORTED, properties)
|
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 = {
|
const properties: ViewFilterCreatedEvent = {
|
||||||
tableId: view.tableId,
|
tableId,
|
||||||
|
filterGroups,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.VIEW_FILTER_CREATED, properties, timestamp)
|
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 = {
|
const properties: ViewFilterUpdatedEvent = {
|
||||||
tableId: view.tableId,
|
tableId: tableId,
|
||||||
|
filterGroups,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.VIEW_FILTER_UPDATED, properties)
|
await publishEvent(Event.VIEW_FILTER_UPDATED, properties)
|
||||||
}
|
}
|
||||||
|
@ -67,10 +83,16 @@ async function filterDeleted(view: View) {
|
||||||
await publishEvent(Event.VIEW_FILTER_DELETED, properties)
|
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 = {
|
const properties: ViewCalculationCreatedEvent = {
|
||||||
tableId: view.tableId,
|
tableId,
|
||||||
calculation: view.calculation as ViewCalculation,
|
calculation: calculationType,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp)
|
await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp)
|
||||||
}
|
}
|
||||||
|
@ -91,6 +113,13 @@ async function calculationDeleted(existingView: View) {
|
||||||
await publishEvent(Event.VIEW_CALCULATION_DELETED, properties)
|
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 {
|
export default {
|
||||||
created,
|
created,
|
||||||
updated,
|
updated,
|
||||||
|
@ -102,4 +131,5 @@ export default {
|
||||||
calculationCreated,
|
calculationCreated,
|
||||||
calculationUpdated,
|
calculationUpdated,
|
||||||
calculationDeleted,
|
calculationDeleted,
|
||||||
|
viewJoinCreated,
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,7 @@ beforeAll(async () => {
|
||||||
jest.spyOn(events.view, "calculationCreated")
|
jest.spyOn(events.view, "calculationCreated")
|
||||||
jest.spyOn(events.view, "calculationUpdated")
|
jest.spyOn(events.view, "calculationUpdated")
|
||||||
jest.spyOn(events.view, "calculationDeleted")
|
jest.spyOn(events.view, "calculationDeleted")
|
||||||
|
jest.spyOn(events.view, "viewJoinCreated")
|
||||||
|
|
||||||
jest.spyOn(events.plugin, "init")
|
jest.spyOn(events.plugin, "init")
|
||||||
jest.spyOn(events.plugin, "imported")
|
jest.spyOn(events.plugin, "imported")
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Leave the core data as it is
|
// Leave the core data as it is
|
||||||
return testData
|
return cloneDeep(testData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,10 @@
|
||||||
return true
|
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
|
// clone the trigger so we're not mutating the reference
|
||||||
|
@ -85,7 +88,7 @@
|
||||||
required => testData?.[required] || required !== "row"
|
required => testData?.[required] || required !== "row"
|
||||||
)
|
)
|
||||||
|
|
||||||
function parseTestJSON(e) {
|
async function parseTestJSON(e) {
|
||||||
let jsonUpdate
|
let jsonUpdate
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -105,7 +108,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatedAuto =
|
||||||
automationStore.actions.addTestDataToAutomation(jsonUpdate)
|
automationStore.actions.addTestDataToAutomation(jsonUpdate)
|
||||||
|
await automationStore.actions.save(updatedAuto)
|
||||||
}
|
}
|
||||||
|
|
||||||
const testAutomation = async () => {
|
const testAutomation = async () => {
|
||||||
|
@ -150,10 +155,14 @@
|
||||||
{#if selectedValues}
|
{#if selectedValues}
|
||||||
<div class="tab-content-padding">
|
<div class="tab-content-padding">
|
||||||
<AutomationBlockSetup
|
<AutomationBlockSetup
|
||||||
bind:testData
|
|
||||||
{schemaProperties}
|
{schemaProperties}
|
||||||
isTestModal
|
isTestModal
|
||||||
|
{testData}
|
||||||
block={trigger}
|
block={trigger}
|
||||||
|
on:update={e => {
|
||||||
|
const { testData: updatedTestData } = e.detail
|
||||||
|
testData = updatedTestData
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -162,7 +171,7 @@
|
||||||
<TextArea
|
<TextArea
|
||||||
value={JSON.stringify($selectedAutomation.data.testData, null, 2)}
|
value={JSON.stringify($selectedAutomation.data.testData, null, 2)}
|
||||||
error={failedParse}
|
error={failedParse}
|
||||||
on:change={e => parseTestJSON(e)}
|
on:change={async e => await parseTestJSON(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
import { QueryUtils, Utils, search, memo } from "@budibase/frontend-core"
|
import { QueryUtils, Utils, search, memo } from "@budibase/frontend-core"
|
||||||
import { getSchemaForDatasourcePlus } from "dataBinding"
|
import { getSchemaForDatasourcePlus } from "dataBinding"
|
||||||
import { TriggerStepID, ActionStepID } from "constants/backend/automations"
|
import { TriggerStepID, ActionStepID } from "constants/backend/automations"
|
||||||
import { onMount } from "svelte"
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import {
|
import {
|
||||||
|
@ -67,6 +67,8 @@
|
||||||
export let isTestModal = false
|
export let isTestModal = false
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
// Stop unnecessary rendering
|
// Stop unnecessary rendering
|
||||||
const memoBlock = memo(block)
|
const memoBlock = memo(block)
|
||||||
|
|
||||||
|
@ -503,15 +505,7 @@
|
||||||
row: { "Active": true, "Order Id" : 14, ... }
|
row: { "Active": true, "Order Id" : 14, ... }
|
||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
const onChange = async update => {
|
const onChange = Utils.sequential(async update => {
|
||||||
if (isTestModal) {
|
|
||||||
testData = update
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAutomation(update)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateAutomation = Utils.sequential(async update => {
|
|
||||||
const request = cloneDeep(update)
|
const request = cloneDeep(update)
|
||||||
// Process app trigger updates
|
// Process app trigger updates
|
||||||
if (isTrigger && !isTestModal) {
|
if (isTrigger && !isTestModal) {
|
||||||
|
@ -540,7 +534,9 @@
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (isTestModal) {
|
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
|
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
|
||||||
if (stepId === TriggerStepID.WEBHOOK) {
|
if (stepId === TriggerStepID.WEBHOOK) {
|
||||||
|
@ -557,7 +553,13 @@
|
||||||
...request,
|
...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 {
|
} else {
|
||||||
const data = { schema, ...request }
|
const data = { schema, ...request }
|
||||||
await automationStore.actions.updateBlockInputs(block, data)
|
await automationStore.actions.updateBlockInputs(block, data)
|
||||||
|
|
|
@ -880,13 +880,13 @@ const automationActions = store => ({
|
||||||
appId,
|
appId,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
addTestDataToAutomation: async data => {
|
addTestDataToAutomation: data => {
|
||||||
let newAutomation = cloneDeep(get(selectedAutomation).data)
|
let newAutomation = cloneDeep(get(selectedAutomation).data)
|
||||||
newAutomation.testData = {
|
newAutomation.testData = {
|
||||||
...newAutomation.testData,
|
...newAutomation.testData,
|
||||||
...data,
|
...data,
|
||||||
}
|
}
|
||||||
await store.actions.save(newAutomation)
|
return newAutomation
|
||||||
},
|
},
|
||||||
constructBlock(type, stepId, blockDefinition) {
|
constructBlock(type, stepId, blockDefinition) {
|
||||||
let newName
|
let newName
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit b2f2e2b9d45906744196875b87a121948e8e4c09
|
Subproject commit 9d88d38d82b928f43005197e1a9ec9856572946b
|
|
@ -6,6 +6,7 @@ import {
|
||||||
RowActionResponse,
|
RowActionResponse,
|
||||||
RowActionsResponse,
|
RowActionsResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { events } from "@budibase/backend-core"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
|
||||||
async function getTable(ctx: Ctx) {
|
async function getTable(ctx: Ctx) {
|
||||||
|
@ -59,6 +60,8 @@ export async function create(
|
||||||
name: ctx.request.body.name,
|
name: ctx.request.body.name,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await events.rowAction.created(createdAction)
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
tableId,
|
tableId,
|
||||||
id: createdAction.id,
|
id: createdAction.id,
|
||||||
|
|
|
@ -45,13 +45,13 @@ export async function updateTable(
|
||||||
inputs.created = true
|
inputs.created = true
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { datasource, table } = await sdk.tables.external.save(
|
const { datasource, oldTable, table } = await sdk.tables.external.save(
|
||||||
datasourceId!,
|
datasourceId!,
|
||||||
inputs,
|
inputs,
|
||||||
{ tableId, renaming }
|
{ tableId, renaming }
|
||||||
)
|
)
|
||||||
builderSocket?.emitDatasourceUpdate(ctx, datasource)
|
builderSocket?.emitDatasourceUpdate(ctx, datasource)
|
||||||
return table
|
return { table, oldTable }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
ctx.throw(400, err.message)
|
ctx.throw(400, err.message)
|
||||||
|
|
|
@ -120,8 +120,15 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
||||||
await events.table.created(savedTable)
|
await events.table.created(savedTable)
|
||||||
} else {
|
} else {
|
||||||
const api = pickApi({ table })
|
const api = pickApi({ table })
|
||||||
savedTable = await api.updateTable(ctx, renaming)
|
const { table: updatedTable, oldTable } = await api.updateTable(
|
||||||
await events.table.updated(savedTable)
|
ctx,
|
||||||
|
renaming
|
||||||
|
)
|
||||||
|
savedTable = updatedTable
|
||||||
|
|
||||||
|
if (oldTable) {
|
||||||
|
await events.table.updated(oldTable, savedTable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (renaming) {
|
if (renaming) {
|
||||||
await sdk.views.renameLinkedViews(savedTable, renaming)
|
await sdk.views.renameLinkedViews(savedTable, renaming)
|
||||||
|
|
|
@ -30,14 +30,14 @@ export async function updateTable(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { table } = await sdk.tables.internal.save(tableToSave, {
|
const { table, oldTable } = await sdk.tables.internal.save(tableToSave, {
|
||||||
userId: ctx.user._id,
|
userId: ctx.user._id,
|
||||||
rowsToImport: rows,
|
rowsToImport: rows,
|
||||||
tableId: ctx.request.body._id,
|
tableId: ctx.request.body._id,
|
||||||
renaming,
|
renaming,
|
||||||
})
|
})
|
||||||
|
|
||||||
return table
|
return { table, oldTable }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
ctx.throw(400, err.message)
|
ctx.throw(400, err.message)
|
||||||
|
|
|
@ -19,8 +19,6 @@ import { builderSocket } from "../../../websockets"
|
||||||
|
|
||||||
const cloneDeep = require("lodash/cloneDeep")
|
const cloneDeep = require("lodash/cloneDeep")
|
||||||
|
|
||||||
import isEqual from "lodash/isEqual"
|
|
||||||
|
|
||||||
export async function fetch(ctx: Ctx) {
|
export async function fetch(ctx: Ctx) {
|
||||||
ctx.body = await getViews()
|
ctx.body = await getViews()
|
||||||
}
|
}
|
||||||
|
@ -60,71 +58,11 @@ export async function save(ctx: Ctx) {
|
||||||
existingTable.views[viewName] = existingTable.views[originalName]
|
existingTable.views[viewName] = existingTable.views[originalName]
|
||||||
}
|
}
|
||||||
await db.put(table)
|
await db.put(table)
|
||||||
await handleViewEvents(
|
|
||||||
existingTable.views[viewName] as View,
|
|
||||||
table.views[viewName]
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx.body = table.views[viewName]
|
ctx.body = table.views[viewName]
|
||||||
builderSocket?.emitTableUpdate(ctx, table)
|
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) {
|
export async function destroy(ctx: Ctx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const viewName = decodeURIComponent(ctx.params.viewName)
|
const viewName = decodeURIComponent(ctx.params.viewName)
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
CreateViewResponse,
|
CreateViewResponse,
|
||||||
UpdateViewResponse,
|
UpdateViewResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { events } from "@budibase/backend-core"
|
||||||
import { builderSocket, gridSocket } from "../../../websockets"
|
import { builderSocket, gridSocket } from "../../../websockets"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
|
||||||
|
@ -150,6 +151,9 @@ export async function create(ctx: Ctx<CreateViewRequest, CreateViewResponse>) {
|
||||||
primaryDisplay: view.primaryDisplay,
|
primaryDisplay: view.primaryDisplay,
|
||||||
}
|
}
|
||||||
const result = await sdk.views.create(tableId, parsedView)
|
const result = await sdk.views.create(tableId, parsedView)
|
||||||
|
|
||||||
|
await events.view.created(result)
|
||||||
|
|
||||||
ctx.status = 201
|
ctx.status = 201
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
data: result,
|
data: result,
|
||||||
|
@ -160,6 +164,46 @@ export async function create(ctx: Ctx<CreateViewRequest, CreateViewResponse>) {
|
||||||
gridSocket?.emitViewUpdate(ctx, result)
|
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>) {
|
export async function update(ctx: Ctx<UpdateViewRequest, UpdateViewResponse>) {
|
||||||
const view = ctx.request.body
|
const view = ctx.request.body
|
||||||
|
|
||||||
|
@ -187,10 +231,15 @@ export async function update(ctx: Ctx<UpdateViewRequest, UpdateViewResponse>) {
|
||||||
primaryDisplay: view.primaryDisplay,
|
primaryDisplay: view.primaryDisplay,
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await sdk.views.update(tableId, parsedView)
|
const { view: result, existingView } = await sdk.views.update(
|
||||||
ctx.body = {
|
tableId,
|
||||||
data: result,
|
parsedView
|
||||||
}
|
)
|
||||||
|
|
||||||
|
await handleViewEvents(existingView, result)
|
||||||
|
await events.view.updated(result)
|
||||||
|
|
||||||
|
ctx.body = { data: result }
|
||||||
|
|
||||||
const table = await sdk.tables.getTable(tableId)
|
const table = await sdk.tables.getTable(tableId)
|
||||||
builderSocket?.emitTableUpdate(ctx, table)
|
builderSocket?.emitTableUpdate(ctx, table)
|
||||||
|
|
|
@ -247,6 +247,9 @@ if (descriptions.length) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
primary: ["_id"],
|
||||||
|
views: {},
|
||||||
|
sql: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -254,9 +257,8 @@ if (descriptions.length) {
|
||||||
...table,
|
...table,
|
||||||
name: generator.guid(),
|
name: generator.guid(),
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(events.table.updated).toHaveBeenCalledTimes(1)
|
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 () => {
|
it("updates all the row fields for a table when a schema key is renamed", async () => {
|
||||||
|
|
|
@ -73,25 +73,12 @@ describe("/views", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("create", () => {
|
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 () => {
|
it("creates a view with a calculation", async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|
||||||
const view = await saveView({ calculation: ViewCalculation.COUNT })
|
const view = await saveView({ calculation: ViewCalculation.COUNT })
|
||||||
|
|
||||||
expect(view.tableId).toBe(table._id)
|
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 () => {
|
it("creates a view with a filter", async () => {
|
||||||
|
@ -109,14 +96,6 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(view.tableId).toBe(table._id)
|
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 () => {
|
it("updates the table row with the new view metadata", async () => {
|
||||||
|
@ -166,13 +145,6 @@ describe("/views", () => {
|
||||||
await saveView()
|
await saveView()
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
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 () => {
|
it("updates a view calculation", async () => {
|
||||||
|
@ -182,13 +154,6 @@ describe("/views", () => {
|
||||||
await saveView({ calculation: ViewCalculation.COUNT })
|
await saveView({ calculation: ViewCalculation.COUNT })
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
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 () => {
|
it("deletes a view calculation", async () => {
|
||||||
|
@ -198,13 +163,6 @@ describe("/views", () => {
|
||||||
await saveView({ calculation: undefined })
|
await saveView({ calculation: undefined })
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
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 () => {
|
it("updates a view filter", async () => {
|
||||||
|
@ -230,13 +188,6 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
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 () => {
|
it("deletes a view filter", async () => {
|
||||||
|
@ -254,13 +205,6 @@ describe("/views", () => {
|
||||||
await saveView({ filters: [] })
|
await saveView({ filters: [] })
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
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)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
import {
|
import {
|
||||||
|
ArrayOperator,
|
||||||
|
BasicOperator,
|
||||||
|
BBReferenceFieldSubType,
|
||||||
|
CalculationType,
|
||||||
CreateViewRequest,
|
CreateViewRequest,
|
||||||
Datasource,
|
Datasource,
|
||||||
|
EmptyFilterOption,
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
FieldType,
|
FieldType,
|
||||||
INTERNAL_TABLE_SOURCE_ID,
|
INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
JsonFieldSubType,
|
||||||
|
JsonTypes,
|
||||||
|
LegacyFilter,
|
||||||
|
NumericCalculationFieldMetadata,
|
||||||
PermissionLevel,
|
PermissionLevel,
|
||||||
QuotaUsageType,
|
QuotaUsageType,
|
||||||
|
RelationshipType,
|
||||||
|
RenameColumn,
|
||||||
Row,
|
Row,
|
||||||
SaveTableRequest,
|
SaveTableRequest,
|
||||||
|
SearchFilters,
|
||||||
|
SearchResponse,
|
||||||
|
SearchViewRowRequest,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
SortType,
|
SortType,
|
||||||
StaticQuotaName,
|
StaticQuotaName,
|
||||||
Table,
|
Table,
|
||||||
|
TableSchema,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
UILogicalOperator,
|
||||||
|
UISearchFilter,
|
||||||
UpdateViewRequest,
|
UpdateViewRequest,
|
||||||
ViewV2,
|
ViewV2,
|
||||||
SearchResponse,
|
|
||||||
BasicOperator,
|
|
||||||
CalculationType,
|
|
||||||
RelationshipType,
|
|
||||||
TableSchema,
|
|
||||||
RenameColumn,
|
|
||||||
BBReferenceFieldSubType,
|
|
||||||
NumericCalculationFieldMetadata,
|
|
||||||
ViewV2Schema,
|
ViewV2Schema,
|
||||||
ViewV2Type,
|
ViewV2Type,
|
||||||
JsonTypes,
|
|
||||||
EmptyFilterOption,
|
|
||||||
JsonFieldSubType,
|
|
||||||
UISearchFilter,
|
|
||||||
LegacyFilter,
|
|
||||||
SearchViewRowRequest,
|
|
||||||
ArrayOperator,
|
|
||||||
UILogicalOperator,
|
|
||||||
SearchFilters,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { generator, mocks } from "@budibase/backend-core/tests"
|
import { generator, mocks } from "@budibase/backend-core/tests"
|
||||||
import {
|
import {
|
||||||
|
@ -42,7 +42,7 @@ import {
|
||||||
} from "../../../integrations/tests/utils"
|
} from "../../../integrations/tests/utils"
|
||||||
import merge from "lodash/merge"
|
import merge from "lodash/merge"
|
||||||
import { quotas } from "@budibase/pro"
|
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] })
|
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
||||||
|
|
||||||
|
@ -129,6 +129,7 @@ if (descriptions.length) {
|
||||||
id: expect.stringMatching(new RegExp(`${table._id!}_`)),
|
id: expect.stringMatching(new RegExp(`${table._id!}_`)),
|
||||||
version: 2,
|
version: 2,
|
||||||
})
|
})
|
||||||
|
expect(events.view.created).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can persist views with all fields", async () => {
|
it("can persist views with all fields", async () => {
|
||||||
|
@ -195,6 +196,7 @@ if (descriptions.length) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(res).toEqual(expected)
|
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 () => {
|
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)
|
const res = await config.api.viewV2.create(newView)
|
||||||
|
expect(events.view.created).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
const expected: ViewV2 = {
|
const expected: ViewV2 = {
|
||||||
...newView,
|
...newView,
|
||||||
|
@ -283,6 +286,7 @@ if (descriptions.length) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdView = await config.api.viewV2.create(newView)
|
const createdView = await config.api.viewV2.create(newView)
|
||||||
|
expect(events.view.created).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
expect(createdView).toEqual({
|
expect(createdView).toEqual({
|
||||||
...newView,
|
...newView,
|
||||||
|
@ -990,6 +994,46 @@ if (descriptions.length) {
|
||||||
expect((await config.api.table.get(tableId)).views).toEqual({
|
expect((await config.api.table.get(tableId)).views).toEqual({
|
||||||
[view.name]: expected,
|
[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 () => {
|
it("can update all fields", async () => {
|
||||||
|
@ -1621,6 +1665,7 @@ if (descriptions.length) {
|
||||||
field: "age",
|
field: "age",
|
||||||
}
|
}
|
||||||
await config.api.viewV2.update(view)
|
await config.api.viewV2.update(view)
|
||||||
|
expect(events.view.calculationCreated).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
const { rows } = await config.api.row.search(view.id)
|
const { rows } = await config.api.row.search(view.id)
|
||||||
expect(rows).toHaveLength(2)
|
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 () => {
|
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 () => {
|
it("updates all views references", async () => {
|
||||||
let auxTable = await createAuxTable()
|
let auxTable = await createAuxTable()
|
||||||
|
|
||||||
|
|
|
@ -7,24 +7,6 @@ export const backfill = async (appDb: Database, timestamp: string | number) => {
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
await events.table.created(table, timestamp)
|
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
|
return tables.length
|
||||||
|
|
|
@ -73,16 +73,12 @@ describe("migrations", () => {
|
||||||
expect(events.query.created).toHaveBeenCalledTimes(2)
|
expect(events.query.created).toHaveBeenCalledTimes(2)
|
||||||
expect(events.role.created).toHaveBeenCalledTimes(3) // created roles + admin (created on table creation)
|
expect(events.role.created).toHaveBeenCalledTimes(3) // created roles + admin (created on table creation)
|
||||||
expect(events.table.created).toHaveBeenCalledTimes(3)
|
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)
|
expect(events.backfill.appSucceeded).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
// to make sure caching is working as expected
|
// to make sure caching is working as expected
|
||||||
expect(
|
expect(
|
||||||
events.processors.analyticsProcessor.processEvent
|
events.processors.analyticsProcessor.processEvent
|
||||||
).toHaveBeenCalledTimes(24) // Addtion of of the events above
|
).toHaveBeenCalledTimes(20) // Addition of of the events above
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -281,7 +281,7 @@ export async function save(
|
||||||
tableToSave.sql = true
|
tableToSave.sql = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return { datasource: updatedDatasource, table: tableToSave }
|
return { datasource: updatedDatasource, table: tableToSave, oldTable }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(datasourceId: string, table: Table) {
|
export async function destroy(datasourceId: string, table: Table) {
|
||||||
|
|
|
@ -171,7 +171,7 @@ export async function save(
|
||||||
}
|
}
|
||||||
// has to run after, make sure it has _id
|
// has to run after, make sure it has _id
|
||||||
await runStaticFormulaChecks(table, { oldTable, deletion: false })
|
await runStaticFormulaChecks(table, { oldTable, deletion: false })
|
||||||
return { table }
|
return { table, oldTable }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(table: Table) {
|
export async function destroy(table: Table) {
|
||||||
|
|
|
@ -63,7 +63,7 @@ export async function create(
|
||||||
export async function update(
|
export async function update(
|
||||||
tableId: string,
|
tableId: string,
|
||||||
view: Readonly<ViewV2>
|
view: Readonly<ViewV2>
|
||||||
): Promise<ViewV2> {
|
): Promise<{ view: Readonly<ViewV2>; existingView: ViewV2 }> {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
|
@ -87,7 +87,7 @@ export async function update(
|
||||||
delete views[existingView.name]
|
delete views[existingView.name]
|
||||||
views[view.name] = view
|
views[view.name] = view
|
||||||
await db.put(ds)
|
await db.put(ds)
|
||||||
return view
|
return { view, existingView } as { view: ViewV2; existingView: ViewV2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(viewId: string): Promise<ViewV2> {
|
export async function remove(viewId: string): Promise<ViewV2> {
|
||||||
|
|
|
@ -315,7 +315,10 @@ export async function create(
|
||||||
return view
|
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)
|
await guardViewSchema(tableId, view)
|
||||||
|
|
||||||
return pickApi(tableId).update(tableId, view)
|
return pickApi(tableId).update(tableId, view)
|
||||||
|
|
|
@ -54,7 +54,7 @@ export async function create(
|
||||||
export async function update(
|
export async function update(
|
||||||
tableId: string,
|
tableId: string,
|
||||||
view: Readonly<ViewV2>
|
view: Readonly<ViewV2>
|
||||||
): Promise<ViewV2> {
|
): Promise<{ view: ViewV2; existingView: ViewV2 }> {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const table = await sdk.tables.getTable(tableId)
|
const table = await sdk.tables.getTable(tableId)
|
||||||
table.views ??= {}
|
table.views ??= {}
|
||||||
|
@ -76,7 +76,7 @@ export async function update(
|
||||||
delete table.views[existingView.name]
|
delete table.views[existingView.name]
|
||||||
table.views[view.name] = view
|
table.views[view.name] = view
|
||||||
await db.put(table)
|
await db.put(table)
|
||||||
return view
|
return { view, existingView } as { view: ViewV2; existingView: ViewV2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(viewId: string): Promise<ViewV2> {
|
export async function remove(viewId: string): Promise<ViewV2> {
|
||||||
|
|
|
@ -118,6 +118,7 @@ export enum Event {
|
||||||
VIEW_CALCULATION_CREATED = "view:calculation:created",
|
VIEW_CALCULATION_CREATED = "view:calculation:created",
|
||||||
VIEW_CALCULATION_UPDATED = "view:calculation:updated",
|
VIEW_CALCULATION_UPDATED = "view:calculation:updated",
|
||||||
VIEW_CALCULATION_DELETED = "view:calculation:deleted",
|
VIEW_CALCULATION_DELETED = "view:calculation:deleted",
|
||||||
|
VIEW_JOIN_CREATED = "view:join:created",
|
||||||
|
|
||||||
// ROWS
|
// ROWS
|
||||||
ROWS_CREATED = "rows:created",
|
ROWS_CREATED = "rows:created",
|
||||||
|
@ -192,6 +193,9 @@ export enum Event {
|
||||||
// AUDIT LOG
|
// AUDIT LOG
|
||||||
AUDIT_LOGS_FILTERED = "audit_log:filtered",
|
AUDIT_LOGS_FILTERED = "audit_log:filtered",
|
||||||
AUDIT_LOGS_DOWNLOADED = "audit_log:downloaded",
|
AUDIT_LOGS_DOWNLOADED = "audit_log:downloaded",
|
||||||
|
|
||||||
|
// ROW ACTION
|
||||||
|
ROW_ACTION_CREATED = "row_action:created",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserGroupSyncEvents: Event[] = [
|
export const UserGroupSyncEvents: Event[] = [
|
||||||
|
@ -376,6 +380,7 @@ export const AuditedEventFriendlyName: Record<Event, string | undefined> = {
|
||||||
[Event.VIEW_CALCULATION_CREATED]: undefined,
|
[Event.VIEW_CALCULATION_CREATED]: undefined,
|
||||||
[Event.VIEW_CALCULATION_UPDATED]: undefined,
|
[Event.VIEW_CALCULATION_UPDATED]: undefined,
|
||||||
[Event.VIEW_CALCULATION_DELETED]: undefined,
|
[Event.VIEW_CALCULATION_DELETED]: undefined,
|
||||||
|
[Event.VIEW_JOIN_CREATED]: undefined,
|
||||||
|
|
||||||
// SERVED - NOT AUDITED
|
// SERVED - NOT AUDITED
|
||||||
[Event.SERVED_BUILDER]: undefined,
|
[Event.SERVED_BUILDER]: undefined,
|
||||||
|
@ -395,6 +400,9 @@ export const AuditedEventFriendlyName: Record<Event, string | undefined> = {
|
||||||
// AUDIT LOG - NOT AUDITED
|
// AUDIT LOG - NOT AUDITED
|
||||||
[Event.AUDIT_LOGS_FILTERED]: undefined,
|
[Event.AUDIT_LOGS_FILTERED]: undefined,
|
||||||
[Event.AUDIT_LOGS_DOWNLOADED]: 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
|
// properties added at the final stage of the event pipeline
|
||||||
|
|
|
@ -24,3 +24,4 @@ export * from "./plugin"
|
||||||
export * from "./backup"
|
export * from "./backup"
|
||||||
export * from "./environmentVariable"
|
export * from "./environmentVariable"
|
||||||
export * from "./auditLog"
|
export * from "./auditLog"
|
||||||
|
export * from "./rowAction"
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { BaseEvent } from "./event"
|
||||||
|
|
||||||
|
export interface RowActionCreatedEvent extends BaseEvent {
|
||||||
|
name: string
|
||||||
|
automationId: string
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { BaseEvent, TableExportFormat } from "./event"
|
import { BaseEvent, TableExportFormat } from "./event"
|
||||||
|
import { AIOperationEnum } from "../ai"
|
||||||
|
|
||||||
export interface TableCreatedEvent extends BaseEvent {
|
export interface TableCreatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
|
@ -9,6 +10,8 @@ export interface TableCreatedEvent extends BaseEvent {
|
||||||
|
|
||||||
export interface TableUpdatedEvent extends BaseEvent {
|
export interface TableUpdatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
|
defaultValues: boolean | undefined
|
||||||
|
aiColumn: AIOperationEnum | undefined
|
||||||
audited: {
|
audited: {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { ViewCalculation } from "../../documents"
|
import { CalculationType, ViewCalculation, ViewV2Type } from "../../documents"
|
||||||
import { BaseEvent, TableExportFormat } from "./event"
|
import { BaseEvent, TableExportFormat } from "./event"
|
||||||
|
|
||||||
export interface ViewCreatedEvent extends BaseEvent {
|
export interface ViewCreatedEvent extends BaseEvent {
|
||||||
|
name: string
|
||||||
|
type?: ViewV2Type
|
||||||
tableId: string
|
tableId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,10 +22,12 @@ export interface ViewExportedEvent extends BaseEvent {
|
||||||
|
|
||||||
export interface ViewFilterCreatedEvent extends BaseEvent {
|
export interface ViewFilterCreatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
|
filterGroups: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewFilterUpdatedEvent extends BaseEvent {
|
export interface ViewFilterUpdatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
|
filterGroups: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewFilterDeletedEvent extends BaseEvent {
|
export interface ViewFilterDeletedEvent extends BaseEvent {
|
||||||
|
@ -32,7 +36,7 @@ export interface ViewFilterDeletedEvent extends BaseEvent {
|
||||||
|
|
||||||
export interface ViewCalculationCreatedEvent extends BaseEvent {
|
export interface ViewCalculationCreatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
calculation: ViewCalculation
|
calculation: CalculationType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewCalculationUpdatedEvent extends BaseEvent {
|
export interface ViewCalculationUpdatedEvent extends BaseEvent {
|
||||||
|
@ -44,3 +48,7 @@ export interface ViewCalculationDeletedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
calculation: ViewCalculation
|
calculation: ViewCalculation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ViewJoinCreatedEvent extends BaseEvent {
|
||||||
|
tableId: string
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue