Merge remote-tracking branch 'origin/master' into feature/automation-sidebar

This commit is contained in:
Dean 2025-03-07 09:14:29 +00:00
commit 507018aa0b
8 changed files with 229 additions and 11 deletions

View File

@ -102,6 +102,10 @@
if (rowTriggers.includes(trigger?.event)) {
const tableId = trigger?.inputs?.tableId
if (!jsonUpdate.row) {
jsonUpdate.row = {}
}
// Reset the tableId as it must match the trigger
if (jsonUpdate?.row?.tableId !== tableId) {
jsonUpdate.row.tableId = tableId
@ -161,7 +165,7 @@
block={trigger}
on:update={e => {
const { testData: updatedTestData } = e.detail
testData = updatedTestData
testData = parseTestData(updatedTestData)
}}
/>
</div>

View File

@ -18,6 +18,7 @@
Toggle,
Divider,
Icon,
CoreSelect,
} from "@budibase/bbui"
import CreateWebhookModal from "@/components/automation/Shared/CreateWebhookModal.svelte"
@ -48,7 +49,13 @@
EditorModes,
} from "@/components/common/CodeEditor"
import FilterBuilder from "@/components/design/settings/controls/FilterEditor/FilterBuilder.svelte"
import { QueryUtils, Utils, search, memo } from "@budibase/frontend-core"
import {
QueryUtils,
Utils,
search,
memo,
fetchData,
} from "@budibase/frontend-core"
import { getSchemaForDatasourcePlus } from "@/dataBinding"
import { TriggerStepID, ActionStepID } from "@/constants/backend/automations"
import { onMount, createEventDispatcher } from "svelte"
@ -59,10 +66,13 @@
AutomationStepType,
AutomationActionStepId,
AutomationCustomIOType,
SortOrder,
} from "@budibase/types"
import PropField from "./PropField.svelte"
import { utils } from "@budibase/shared-core"
import DrawerBindableCodeEditorField from "@/components/common/bindings/DrawerBindableCodeEditorField.svelte"
import { API } from "@/api"
import InfoDisplay from "@/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte"
export let automation
export let block
@ -97,6 +107,8 @@
let inputData
let insertAtPos, getCaretPosition
let stepLayouts = {}
let rowSearchTerm = ""
let selectedRow
$: memoBlock.set(block)
$: memoContext.set($evaluationContext)
@ -112,9 +124,13 @@
$: stepId = $memoBlock.stepId
$: getInputData(testData, $memoBlock.inputs)
$: tableId = inputData ? inputData.tableId : null
$: tableId =
inputData?.row?.tableId ||
testData?.row?.tableId ||
inputData?.tableId ||
null
$: table = tableId
? $tables.list.find(table => table._id === inputData.tableId)
? $tables.list.find(table => table._id === tableId)
: { schema: {} }
$: schema = getSchemaForDatasourcePlus(tableId, {
searchableSchema: true,
@ -143,6 +159,40 @@
? [hbAutocomplete([...bindingsToCompletions(bindings, codeMode)])]
: []
$: fetch = createFetch({ type: "table", tableId })
$: fetchedRows = $fetch?.rows
$: fetch?.update({
query: {
fuzzy: {
[primaryDisplay]: rowSearchTerm || "",
},
},
})
$: fetchLoading = $fetch?.loading
$: primaryDisplay = table?.primaryDisplay
const createFetch = datasource => {
if (!datasource) {
return
}
return fetchData({
API,
datasource,
options: {
sortColumn: primaryDisplay,
sortOrder: SortOrder.ASCENDING,
query: {
fuzzy: {
[primaryDisplay]: rowSearchTerm || "",
},
},
limit: 20,
},
})
}
const getInputData = (testData, blockInputs) => {
// Test data is not cloned for reactivity
let newInputData = testData || cloneDeep(blockInputs)
@ -170,7 +220,7 @@
const stepStore = writable({})
$: stepState = $stepStore?.[block.id]
$: customStepLayouts($memoBlock, schemaProperties, stepState)
$: customStepLayouts($memoBlock, schemaProperties, stepState, fetchedRows)
const customStepLayouts = block => {
if (
@ -363,6 +413,49 @@
disabled: isTestModal,
},
},
{
type: CoreSelect,
title: "Row",
props: {
disabled: !table,
placeholder: "Select a row",
options: fetchedRows,
loading: fetchLoading,
value: selectedRow,
autocomplete: true,
filter: false,
getOptionLabel: row => row?.[primaryDisplay] || "",
compare: (a, b) => a?.[primaryDisplay] === b?.[primaryDisplay],
onChange: e => {
if (isTestModal) {
onChange({
id: e.detail?._id,
revision: e.detail?._rev,
row: e.detail,
oldRow: e.detail,
meta: {
fields: inputData["meta"]?.fields || {},
oldFields: e.detail?.meta?.fields || {},
},
})
}
},
},
},
{
type: InfoDisplay,
props: {
warning: true,
icon: "AlertCircleFilled",
body: `Be careful when testing this automation because your data may be modified or deleted.`,
},
},
{
type: Divider,
props: {
noMargin: true,
},
},
...getIdConfig(),
...getRevConfig(),
...getRowTypeConfig(),
@ -561,6 +654,15 @@
...request,
}
if (
newTestData?.row == null ||
Object.keys(newTestData?.row).length === 0
) {
selectedRow = null
} else {
selectedRow = newTestData.row
}
const updatedAuto =
automationStore.actions.addTestDataToAutomation(newTestData)
@ -674,6 +776,7 @@
{bindings}
on:change={config.props.onChange}
context={$memoContext}
bind:searchTerm={rowSearchTerm}
/>
</PropField>
{:else}

View File

@ -268,13 +268,22 @@
>
<ActionButton
icon="Add"
fullWidth
on:click={() => {
customPopover.show()
}}
disabled={!schemaFields}
>Add fields
</ActionButton>
<ActionButton
icon="Remove"
on:click={() => {
dispatch("change", {
meta: { fields: {} },
row: {},
})
}}
>Clear
</ActionButton>
</div>
{/if}
@ -340,4 +349,11 @@
.prop-control-wrap :global(.icon.json-slot-icon) {
right: 1px !important;
}
.add-fields-btn {
display: flex;
flex-direction: row;
justify-content: center;
gap: var(--spacing-s);
}
</style>

View File

@ -44,6 +44,19 @@ const ADDED_HELPERS = {
description:
"Produce a humanized duration left/until given an amount of time and the type of time measurement.",
},
difference: {
args: ["from", "to", "[unitType=ms]"],
example:
'{{ difference "2025-09-30" "2025-06-17" "seconds" }} -> 9072000',
description:
"Gets the difference between two dates, in milliseconds. Pass a third parameter to adjust the unit measurement.",
},
durationFromNow: {
args: ["time"],
example: '{{durationFromNow "2021-09-30"}} -> 8 months',
description:
"Produce a humanized duration left/until given an amount of time and the type of time measurement.",
},
},
}

View File

@ -1,4 +1,4 @@
import dayjs from "dayjs"
import dayjs, { UnitType } from "dayjs"
import dayjsDurationPlugin from "dayjs/plugin/duration"
import dayjsAdvancedFormatPlugin from "dayjs/plugin/advancedFormat"
@ -121,7 +121,7 @@ export const date = (str: any, pattern: any, options: any) => {
return date.format(config.pattern)
}
export const duration = (str: any, pattern: any, format: any) => {
export const duration = (str: any, pattern: any, format?: any) => {
const config = initialConfig(str, pattern)
setLocale(config.str, config.pattern)
@ -133,3 +133,13 @@ export const duration = (str: any, pattern: any, format: any) => {
return duration.humanize()
}
}
export const difference = (from: string, to: string, units?: UnitType) => {
const result = dayjs(new Date(from)).diff(dayjs(new Date(to)), units)
return result
}
export const durationFromNow = (from: string) => {
const diff = difference(from, new Date().toISOString(), "ms")
return duration(diff, "ms")
}

View File

@ -1,7 +1,7 @@
// @ts-ignore we don't have types for it
import helpers from "@budibase/handlebars-helpers"
import { date, duration } from "./date"
import { date, difference, duration, durationFromNow } from "./date"
import {
HelperFunctionBuiltin,
EXTERNAL_FUNCTION_COLLECTIONS,
@ -9,8 +9,10 @@ import {
import Handlebars from "handlebars"
const ADDED_HELPERS = {
date: date,
duration: duration,
date,
duration,
difference,
durationFromNow,
}
export const externalCollections = EXTERNAL_FUNCTION_COLLECTIONS

View File

@ -1222,6 +1222,22 @@
],
"example": "{{duration 8 \"seconds\"}} -> a few seconds",
"description": "<p>Produce a humanized duration left/until given an amount of time and the type of time measurement.</p>\n"
},
"difference": {
"args": [
"from",
"to",
"[unitType=ms]"
],
"example": "{{ difference \"2025-09-30\" \"2025-06-17\" \"seconds\" }} -> 9072000",
"description": "<p>Gets the difference between two dates, in milliseconds. Pass a third parameter to adjust the unit measurement.</p>\n"
},
"durationFromNow": {
"args": [
"time"
],
"example": "{{durationFromNow \"2021-09-30\"}} -> 8 months",
"description": "<p>Produce a humanized duration left/until given an amount of time and the type of time measurement.</p>\n"
}
}
}

View File

@ -0,0 +1,54 @@
import tk from "timekeeper"
import * as date from "../../src/helpers/date"
const frozenDate = new Date("2025-03-06T11:38:41.000Z")
tk.freeze(frozenDate)
describe("date helper", () => {
describe("difference", () => {
it("should return the difference between two dates", () => {
const result = date.difference(
"2021-01-02T12:34:56.789Z",
"2021-01-01T01:00:00.000Z"
)
const expected =
1 * 24 * 60 * 60 * 1000 + // 1 day
11 * 60 * 60 * 1000 + // 11 hours
34 * 60 * 1000 + // 34 minutes
56 * 1000 + // seconds
789 // milliseconds
expect(result).toEqual(expected)
})
it("should be able to set the time unit", () => {
const result = date.difference(
"2021-01-02T12:34:56",
"2021-01-01T01:00:00",
"days"
)
expect(result).toEqual(1)
})
})
describe("durationFromNow", () => {
it("should return the difference between two close dates", () => {
const result = date.durationFromNow("2025-03-06T11:38:43.000Z")
expect(result).toEqual("a few seconds")
})
it("should return the difference between two days hours apart", () => {
const result = date.durationFromNow("2025-03-06T01:00:00.000Z")
expect(result).toEqual("11 hours")
})
it("accepts days in the past", () => {
const result = date.durationFromNow("2025-03-01")
expect(result).toEqual("5 days")
})
it("accepts days in the future", () => {
const result = date.durationFromNow("2025-03-08")
expect(result).toEqual("2 days")
})
})
})