Merge remote-tracking branch 'origin/master' into feature/automation-sidebar
This commit is contained in:
commit
507018aa0b
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue