Skip to content

Commit

Permalink
Automation trigger filtering (#14123)
Browse files Browse the repository at this point in the history
* backend for triggering automation based on filters

* frontend for handling triggering automations on filter / old row

* lint and bug fix

* fix issue with test header

* make test data optional

* improve safety on trigger gate

* add support for running trigger with filter if no change happened but filter matches

* update var naming to actually make sense

* tests

* fix lint

* improve gating for shouldTrigger check

* remove unecessary cast

* unecessary tableId check

* frontend text updates

* resolving comments

* pro

* Update packages/types/src/documents/app/automation.ts

Co-authored-by: Sam Rose <hello@samwho.dev>

* link out to docs for trigger filtering

* fix pro

* more pr comments

* use getAppId

---------

Co-authored-by: Sam Rose <hello@samwho.dev>
  • Loading branch information
PClmnt and samwho authored Jul 18, 2024
1 parent 5e3bec8 commit 7fd55fe
Show file tree
Hide file tree
Showing 13 changed files with 366 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
export let enableNaming = true
let validRegex = /^[A-Za-z0-9_\s]+$/
let typing = false
const dispatch = createEventDispatcher()
$: stepNames = $selectedAutomation?.definition.stepNames
$: automationName = stepNames?.[block.id] || block?.name || ""
$: automationNameError = getAutomationNameError(automationName)
$: status = updateStatus(testResult, isTrigger)
$: status = updateStatus(testResult)
$: isHeaderTrigger = isTrigger || block.type === "TRIGGER"
$: {
Expand All @@ -43,7 +42,7 @@
})
}
function updateStatus(results, isTrigger) {
function updateStatus(results) {
if (!results) {
return {}
}
Expand All @@ -56,7 +55,6 @@
return { negative: true, message: "Error" }
}
}
const getAutomationNameError = name => {
if (stepNames) {
for (const [key, value] of Object.entries(stepNames)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,31 @@
let blocks
function prepTestResults(results) {
return results?.steps.filter(x => x.stepId !== ActionStepID.LOOP || [])
if (results.message) {
return [
{
inputs: {},
outputs: {
success: results.outputs?.success || false,
status: results.outputs?.status || "unknown",
message: results.message,
},
},
]
} else {
return results?.steps?.filter(x => x.stepId !== ActionStepID.LOOP) || []
}
}
$: filteredResults = prepTestResults(testResults)
$: {
blocks = []
if (automation) {
if (testResults.message) {
blocks = automation?.definition?.trigger
? [automation.definition.trigger]
: []
} else if (automation) {
blocks = []
if (automation.definition.trigger) {
blocks.push(automation.definition.trigger)
}
Expand All @@ -46,7 +63,9 @@
open={!!openBlocks[block.id]}
on:toggle={() => (openBlocks[block.id] = !openBlocks[block.id])}
isTrigger={idx === 0}
testResult={filteredResults?.[idx]}
testResult={testResults.message
? testResults
: filteredResults?.[idx]}
showTestStatus
{block}
{idx}
Expand All @@ -68,7 +87,9 @@
<Tabs quiet noHorizPadding selected="Input">
<Tab title="Input">
<div class="wrap">
{#if filteredResults?.[idx]?.inputs}
{#if testResults.message}
No input
{:else if filteredResults?.[idx]?.inputs}
<JsonView depth={2} json={filteredResults?.[idx]?.inputs} />
{:else}
No input
Expand All @@ -77,13 +98,22 @@
</Tab>
<Tab title="Output">
<div class="wrap">
{#if filteredResults?.[idx]?.outputs}
{#if testResults.message}
<JsonView
depth={2}
json={{
success: testResults.outputs?.success || false,
status: testResults.outputs?.status || "unknown",
message: testResults.message,
}}
/>
{:else if filteredResults?.[idx]?.outputs}
<JsonView
depth={2}
json={filteredResults?.[idx]?.outputs}
/>
{:else}
No input
No output
{/if}
</div>
</Tab>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
Helpers,
Toggle,
Divider,
Icon,
} from "@budibase/bbui"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import { automationStore, selectedAutomation, tables } from "stores/builder"
import { environment, licensing } from "stores/portal"
Expand Down Expand Up @@ -365,41 +367,74 @@
/**
* Handler for row trigger automation updates.
@param {object} update - An automation block.inputs update object
@example
onRowTriggerUpdate({
"tableId" : "ta_bb_employee"
})
* @param {object} update - An automation block.inputs update object
* @param {string} [update.tableId] - The ID of the table
* @param {object} [update.filters] - Filter configuration for the row trigger
* @param {object} [update.filters-def] - Filter definitions for the row trigger
* @example
* // Example with tableId
* onRowTriggerUpdate({
* "tableId" : "ta_bb_employee"
* })
* @example
* // Example with filters
* onRowTriggerUpdate({
* filters: {
* equal: { "1:Approved": "true" }
* },
* "filters-def": [{
* id: "oH1T4S49n",
* field: "1:Approved",
* operator: "equal",
* value: "true",
* valueType: "Value",
* type: "string"
* }]
* })
*/
const onRowTriggerUpdate = async update => {
if (
Object.hasOwn(update, "tableId") &&
$selectedAutomation.testData?.row?.tableId !== update.tableId
["tableId", "filters", "meta"].some(key => Object.hasOwn(update, key))
) {
try {
const reqSchema = getSchemaForDatasourcePlus(update.tableId, {
searchableSchema: true,
}).schema
// Parse the block inputs as usual
const updatedAutomation =
await automationStore.actions.processBlockInputs(block, {
schema: reqSchema,
...update,
})
// Save the entire automation and reset the testData
await automationStore.actions.save({
...updatedAutomation,
testData: {
// Reset Core fields
row: { tableId: update.tableId },
oldRow: { tableId: update.tableId },
meta: {},
id: "",
revision: "",
},
})
let updatedAutomation
if (
Object.hasOwn(update, "tableId") &&
$selectedAutomation.testData?.row?.tableId !== update.tableId
) {
const reqSchema = getSchemaForDatasourcePlus(update.tableId, {
searchableSchema: true,
}).schema
updatedAutomation = await automationStore.actions.processBlockInputs(
block,
{
schema: reqSchema,
...update,
}
)
// Reset testData when tableId changes
updatedAutomation = {
...updatedAutomation,
testData: {
row: { tableId: update.tableId },
oldRow: { tableId: update.tableId },
meta: {},
id: "",
revision: "",
},
}
} else {
// For filters update, just process block inputs without resetting testData
updatedAutomation = await automationStore.actions.processBlockInputs(
block,
update
)
}
await automationStore.actions.save(updatedAutomation)
return
} catch (e) {
Expand All @@ -408,7 +443,6 @@
}
}
}
/**
* Handler for App trigger automation updates.
* Ensure updates to the field list are reflected in testData
Expand Down Expand Up @@ -743,6 +777,7 @@
value.customType !== "triggerSchema" &&
value.customType !== "automationFields" &&
value.customType !== "fields" &&
value.customType !== "trigger_filter_setting" &&
value.type !== "signature_single" &&
value.type !== "attachment" &&
value.type !== "attachment_single"
Expand Down Expand Up @@ -807,13 +842,23 @@
{@const label = getFieldLabel(key, value)}
<div class:block-field={shouldRenderField(value)}>
{#if key !== "fields" && value.type !== "boolean" && shouldRenderField(value)}
<Label
tooltip={value.title === "Binding / Value"
? "If using the String input type, please use a comma or newline separated string"
: null}
>
{label}
</Label>
<div class="label-container">
<Label>
{label}
</Label>
{#if value.customType === "trigger_filter"}
<Icon
hoverable
on:click={() =>
window.open(
"https://docs.budibase.com/docs/row-trigger-filters",
"_blank"
)}
size="XS"
name="InfoOutline"
/>
{/if}
</div>
{/if}
<div class:field-width={shouldRenderField(value)}>
{#if value.type === "string" && value.enum && canShowField(key, value)}
Expand Down Expand Up @@ -932,8 +977,12 @@
{/if}
</div>
</div>
{:else if value.customType === "filters"}
<ActionButton on:click={drawer.show}>Define filters</ActionButton>
{:else if value.customType === "filters" || value.customType === "trigger_filter"}
<ActionButton fullWidth on:click={drawer.show}
>{filters.length > 0
? "Update Filter"
: "No Filter set"}</ActionButton
>
<Drawer bind:this={drawer} title="Filtering">
<Button cta slot="buttons" on:click={() => saveFilters(key)}>
Save
Expand All @@ -945,6 +994,7 @@
{schemaFields}
datasource={{ type: "table", tableId }}
panel={AutomationBindingPanel}
showFilterEmptyDropdown={!rowTriggers.includes(stepId)}
on:change={e => (tempFilters = e.detail)}
/>
</DrawerContent>
Expand Down Expand Up @@ -1085,6 +1135,11 @@
{/if}
<style>
.label-container {
display: flex;
align-items: center;
gap: var(--spacing-s);
}
.field-width {
width: 320px;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
export let panel = ClientBindingPanel
export let allowBindings = true
export let datasource
export let showFilterEmptyDropdown
const dispatch = createEventDispatcher()
let rawFilters
Expand Down Expand Up @@ -63,6 +63,7 @@
{schemaFields}
{datasource}
{allowBindings}
{showFilterEmptyDropdown}
>
<div slot="filtering-hero-content" />
Expand Down
2 changes: 1 addition & 1 deletion packages/builder/src/stores/builder/automations.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ const automationActions = store => ({
const message = err.message || err.status || JSON.stringify(err)
throw `Automation test failed - ${message}`
}
if (!result?.trigger && !result?.steps?.length) {
if (!result?.trigger && !result?.steps?.length && !result?.message) {
if (result?.err?.code === "usage_limit_exceeded") {
throw "You have exceeded your automation quota"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend-core/src/components/FilterBuilder.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
export let behaviourFilters = false
export let allowBindings = false
export let filtersLabel = "Filters"
export let showFilterEmptyDropdown = true
$: {
if (
tables.find(
Expand Down Expand Up @@ -218,7 +218,7 @@
on:change={e => handleAllOr(e.detail)}
placeholder={null}
/>
{#if datasource?.type === "table"}
{#if datasource?.type === "table" && showFilterEmptyDropdown}
<Select
label="When filter empty"
value={onEmptyFilter}
Expand Down
Loading

0 comments on commit 7fd55fe

Please sign in to comment.