Integrate actions into new global context and greatly improve performance by only enriching when required

This commit is contained in:
Andrew Kingston 2023-09-15 11:52:35 +01:00
parent 3839e26758
commit f78cc26a68
2 changed files with 89 additions and 86 deletions

View File

@ -35,6 +35,10 @@
findHBSBlocks,
isJSBinding,
} from "@budibase/string-templates"
import {
getActionContextKey,
getActionDependentContextKeys,
} from "../utils/buttonActions.js"
export let instance = {}
export let isLayout = false
@ -165,9 +169,6 @@
hasMissingRequiredSettings)
$: emptyState = empty && showEmptyState
// Enrich component settings
// $: enrichComponentSettings($context, settingsDefinitionMap)
// Evaluate conditional UI settings and store any component setting changes
// which need to be made
$: evaluateConditions(conditions)
@ -296,18 +297,42 @@
}
})
// The known context key map is built up at runtime, as changes to keys are
// encountered. We manually seed this to the required action keys as these
// are not encountered at runtime and so need computed in advance.
knownContextKeyMap = generateActionKeyMap(instance, settingsDefinition)
bindingString = bindings.join(" ")
// Run any migrations
runMigrations(instance, settingsDefinition)
// Force an initial enrichment of the new settings
enrichComponentSettings(get(context), settingsDefinitionMap, {
force: true,
})
bindingString = bindings.join(" ")
knownContextKeyMap = {}
enrichComponentSettings(get(context), settingsDefinitionMap)
}
// Force an initial enrichment of the new settings
enrichComponentSettings($context, settingsDefinitionMap)
// Extracts a map of all context keys which are required by action settings
// to provide the functions to evaluate at runtime. This needs done manually
// as the action definitions themselves do not specify bindings for action
// keys, meaning we cannot do this while doing the other normal bindings.
const generateActionKeyMap = (instance, settingsDefinition) => {
let map = {}
settingsDefinition.forEach(setting => {
if (setting.type === "event") {
instance[setting.key]?.forEach(action => {
// We depend on the actual action key
const actionKey = getActionContextKey(action)
if (actionKey) {
map[actionKey] = true
}
// We also depend on any manually declared context keys
getActionDependentContextKeys(action)?.forEach(key => {
map[key] = true
})
})
}
})
return map
}
const runMigrations = (instance, settingsDefinition) => {

View File

@ -17,6 +17,54 @@ import { ActionTypes } from "constants"
import { enrichDataBindings } from "./enrichDataBinding"
import { Helpers } from "@budibase/bbui"
// Default action handler, which extracts an action from context that was
// provided by another component and executes it with all action parameters
const contextActionHandler = async (action, context) => {
const key = getActionContextKey(action)
const fn = context[key]
if (fn) {
return await fn(action.parameters)
}
}
// Generates the context key, which is the key that this action depends on in
// context to provide the function it will run. This is broken out as a util
// because we reuse this inside the core Component.svelte file to determine
// what the required action context keys are for all action settings.
export const getActionContextKey = action => {
const type = action?.["##eventHandlerType"]
const key = (componentId, type) => `${componentId}_${type}`
switch (type) {
case "Scroll To Field":
return key(action.parameters.componentId, ActionTypes.ScrollTo)
case "Update Field Value":
return key(action.parameters.componentId, ActionTypes.UpdateFieldValue)
case "Validate Form":
return key(action.parameters.componentId, ActionTypes.ValidateForm)
case "Refresh Data Provider":
return key(action.parameters.componentId, ActionTypes.RefreshDatasource)
case "Clear Form":
return key(action.parameters.componentId, ActionTypes.ClearForm)
case "Change Form Step":
return key(action.parameters.componentId, ActionTypes.ChangeFormStep)
default:
return null
}
}
// If button actions depend on context, they must declare which keys they need
export const getActionDependentContextKeys = action => {
const type = action?.["##eventHandlerType"]
switch (type) {
case "Save Row":
case "Duplicate Row":
if (action.parameters?.providerId) {
return [action.parameters.providerId]
}
}
return []
}
const saveRowHandler = async (action, context) => {
const { fields, providerId, tableId, notificationOverride } =
action.parameters
@ -189,17 +237,6 @@ const navigationHandler = action => {
routeStore.actions.navigate(url, peek, externalNewTab)
}
const scrollHandler = async (action, context) => {
return await executeActionHandler(
context,
action.parameters.componentId,
ActionTypes.ScrollTo,
{
field: action.parameters.field,
}
)
}
const queryExecutionHandler = async action => {
const { datasourceId, queryId, queryParams, notificationOverride } =
action.parameters
@ -235,47 +272,6 @@ const queryExecutionHandler = async action => {
}
}
const executeActionHandler = async (
context,
componentId,
actionType,
params
) => {
const fn = context[`${componentId}_${actionType}`]
if (fn) {
return await fn(params)
}
}
const updateFieldValueHandler = async (action, context) => {
return await executeActionHandler(
context,
action.parameters.componentId,
ActionTypes.UpdateFieldValue,
{
type: action.parameters.type,
field: action.parameters.field,
value: action.parameters.value,
}
)
}
const validateFormHandler = async (action, context) => {
return await executeActionHandler(
context,
action.parameters.componentId,
ActionTypes.ValidateForm
)
}
const refreshDataProviderHandler = async (action, context) => {
return await executeActionHandler(
context,
action.parameters.componentId,
ActionTypes.RefreshDatasource
)
}
const logoutHandler = async action => {
await authStore.actions.logOut()
let redirectUrl = "/builder/auth/login"
@ -292,23 +288,6 @@ const logoutHandler = async action => {
}
}
const clearFormHandler = async (action, context) => {
return await executeActionHandler(
context,
action.parameters.componentId,
ActionTypes.ClearForm
)
}
const changeFormStepHandler = async (action, context) => {
return await executeActionHandler(
context,
action.parameters.componentId,
ActionTypes.ChangeFormStep,
action.parameters
)
}
const closeScreenModalHandler = action => {
let url
if (action?.parameters) {
@ -416,16 +395,10 @@ const handlerMap = {
["Duplicate Row"]: duplicateRowHandler,
["Delete Row"]: deleteRowHandler,
["Navigate To"]: navigationHandler,
["Scroll To Field"]: scrollHandler,
["Execute Query"]: queryExecutionHandler,
["Trigger Automation"]: triggerAutomationHandler,
["Validate Form"]: validateFormHandler,
["Update Field Value"]: updateFieldValueHandler,
["Refresh Data Provider"]: refreshDataProviderHandler,
["Log Out"]: logoutHandler,
["Clear Form"]: clearFormHandler,
["Close Screen Modal"]: closeScreenModalHandler,
["Change Form Step"]: changeFormStepHandler,
["Update State"]: updateStateHandler,
["Upload File to S3"]: s3UploadHandler,
["Export Data"]: exportDataHandler,
@ -460,7 +433,12 @@ export const enrichButtonActions = (actions, context) => {
return actions
}
const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
// Get handlers for each action. If no bespoke handler is configured, fall
// back to simply executing this action from context.
const handlers = actions.map(def => {
return handlerMap[def["##eventHandlerType"]] || contextActionHandler
})
return async eventContext => {
// Button context is built up as actions are executed.
// Inherit any previous button context which may have come from actions