diff --git a/lerna.json b/lerna.json index 5b551ea7cf..030dde848c 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.7.2", + "version": "3.8.1", "npmClient": "yarn", "concurrency": 20, "command": { diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 7510655ed7..1e23672ad7 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -69,7 +69,7 @@ "rotating-file-stream": "3.1.0", "sanitize-s3-objectkey": "0.0.1", "semver": "^7.5.4", - "tar-fs": "2.1.1", + "tar-fs": "2.1.2", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/packages/backend-core/src/cache/writethrough.ts b/packages/backend-core/src/cache/writethrough.ts index cd7409ca15..5a1d9f6a14 100644 --- a/packages/backend-core/src/cache/writethrough.ts +++ b/packages/backend-core/src/cache/writethrough.ts @@ -96,6 +96,24 @@ async function get(db: Database, id: string): Promise { return cacheItem.doc } +async function tryGet( + db: Database, + id: string +): Promise { + const cache = await getCache() + const cacheKey = makeCacheKey(db, id) + let cacheItem: CacheItem | null = await cache.get(cacheKey) + if (!cacheItem) { + const doc = await db.tryGet(id) + if (!doc) { + return null + } + cacheItem = makeCacheItem(doc) + await cache.store(cacheKey, cacheItem) + } + return cacheItem.doc +} + async function remove(db: Database, docOrId: any, rev?: any): Promise { const cache = await getCache() if (!docOrId) { @@ -123,10 +141,17 @@ export class Writethrough { return put(this.db, doc, writeRateMs) } + /** + * @deprecated use `tryGet` instead + */ async get(id: string) { return get(this.db, id) } + async tryGet(id: string) { + return tryGet(this.db, id) + } + async remove(docOrId: any, rev?: any) { return remove(this.db, docOrId, rev) } diff --git a/packages/backend-core/src/configs/configs.ts b/packages/backend-core/src/configs/configs.ts index f184bf87df..3747fff82e 100644 --- a/packages/backend-core/src/configs/configs.ts +++ b/packages/backend-core/src/configs/configs.ts @@ -47,6 +47,9 @@ export async function getConfig( export async function save( config: Config ): Promise<{ id: string; rev: string }> { + if (!config._id) { + config._id = generateConfigID(config.type) + } const db = context.getGlobalDB() return db.put(config) } diff --git a/packages/backend-core/src/configs/tests/configs.spec.ts b/packages/backend-core/src/configs/tests/configs.spec.ts index 2c6a1948ec..5b5186109c 100644 --- a/packages/backend-core/src/configs/tests/configs.spec.ts +++ b/packages/backend-core/src/configs/tests/configs.spec.ts @@ -12,7 +12,6 @@ describe("configs", () => { const setDbPlatformUrl = async (dbUrl: string) => { const settingsConfig = { - _id: configs.generateConfigID(ConfigType.SETTINGS), type: ConfigType.SETTINGS, config: { platformUrl: dbUrl, diff --git a/packages/backend-core/src/constants/db.ts b/packages/backend-core/src/constants/db.ts index 3085b91ef1..28d389e6ba 100644 --- a/packages/backend-core/src/constants/db.ts +++ b/packages/backend-core/src/constants/db.ts @@ -60,6 +60,11 @@ export const StaticDatabases = { SCIM_LOGS: { name: "scim-logs", }, + // Used by self-host users making use of Budicloud resources. Introduced when + // we started letting self-host users use Budibase AI in the cloud. + SELF_HOST_CLOUD: { + name: "self-host-cloud", + }, } export const APP_PREFIX = prefixed(DocumentType.APP) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index 8e0c71ff18..e701f111aa 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -157,6 +157,33 @@ export async function doInTenant( return newContext(updates, task) } +// We allow self-host licensed users to make use of some Budicloud services +// (e.g. Budibase AI). When they do this, they use their license key as an API +// key. We use that license key to identify the tenant ID, and we set the +// context to be self-host using cloud. This affects things like where their +// quota documents get stored (because we want to avoid creating a new global +// DB for each self-host tenant). +export async function doInSelfHostTenantUsingCloud( + tenantId: string, + task: () => T +): Promise { + const updates = { tenantId, isSelfHostUsingCloud: true } + return newContext(updates, task) +} + +export function isSelfHostUsingCloud() { + const context = Context.get() + return !!context?.isSelfHostUsingCloud +} + +export function getSelfHostCloudDB() { + const context = Context.get() + if (!context || !context.isSelfHostUsingCloud) { + throw new Error("Self-host cloud DB not found") + } + return getDB(StaticDatabases.SELF_HOST_CLOUD.name) +} + export async function doInAppContext( appId: string, task: () => T @@ -325,6 +352,11 @@ export function getGlobalDB(): Database { if (!context || (env.MULTI_TENANCY && !context.tenantId)) { throw new Error("Global DB not found") } + if (context.isSelfHostUsingCloud) { + throw new Error( + "Global DB not found - self-host users using cloud don't have a global DB" + ) + } return getDB(baseGlobalDBName(context?.tenantId)) } @@ -344,6 +376,11 @@ export function getAppDB(opts?: any): Database { if (!appId) { throw new Error("Unable to retrieve app DB - no app ID.") } + if (isSelfHostUsingCloud()) { + throw new Error( + "App DB not found - self-host users using cloud don't have app DBs" + ) + } return getDB(appId, opts) } diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index 23598b951e..adee495e60 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -5,6 +5,7 @@ import { GoogleSpreadsheet } from "google-spreadsheet" // keep this out of Budibase types, don't want to expose context info export type ContextMap = { tenantId?: string + isSelfHostUsingCloud?: boolean appId?: string identity?: IdentityContext environmentVariables?: Record diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index 937f88c846..fa4742f23f 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -6,6 +6,7 @@ import { isInvalidISODateString, isValidFilter, isValidISODateString, + isValidTime, sqlLog, validateManyToMany, } from "./utils" @@ -417,11 +418,17 @@ class InternalBuilder { } if (typeof input === "string" && schema.type === FieldType.DATETIME) { - if (isInvalidISODateString(input)) { - return null - } - if (isValidISODateString(input)) { - return new Date(input.trim()) + if (schema.timeOnly) { + if (!isValidTime(input)) { + return null + } + } else { + if (isInvalidISODateString(input)) { + return null + } + if (isValidISODateString(input)) { + return new Date(input.trim()) + } } } return input diff --git a/packages/backend-core/src/sql/tests/utils.spec.ts b/packages/backend-core/src/sql/tests/utils.spec.ts new file mode 100644 index 0000000000..819a0aa581 --- /dev/null +++ b/packages/backend-core/src/sql/tests/utils.spec.ts @@ -0,0 +1,35 @@ +import { isValidISODateString, isInvalidISODateString } from "../utils" + +describe("ISO date string validity checks", () => { + it("accepts a valid ISO date string without a time", () => { + const str = "2013-02-01" + const valid = isValidISODateString(str) + const invalid = isInvalidISODateString(str) + expect(valid).toEqual(true) + expect(invalid).toEqual(false) + }) + + it("accepts a valid ISO date string with a time", () => { + const str = "2013-02-01T01:23:45Z" + const valid = isValidISODateString(str) + const invalid = isInvalidISODateString(str) + expect(valid).toEqual(true) + expect(invalid).toEqual(false) + }) + + it("accepts a valid ISO date string with a time and millis", () => { + const str = "2013-02-01T01:23:45.678Z" + const valid = isValidISODateString(str) + const invalid = isInvalidISODateString(str) + expect(valid).toEqual(true) + expect(invalid).toEqual(false) + }) + + it("rejects an invalid ISO date string", () => { + const str = "2013-523-814T444:22:11Z" + const valid = isValidISODateString(str) + const invalid = isInvalidISODateString(str) + expect(valid).toEqual(false) + expect(invalid).toEqual(true) + }) +}) diff --git a/packages/backend-core/src/sql/utils.ts b/packages/backend-core/src/sql/utils.ts index 33f51b23ac..144a2f39ab 100644 --- a/packages/backend-core/src/sql/utils.ts +++ b/packages/backend-core/src/sql/utils.ts @@ -14,7 +14,7 @@ import environment from "../environment" const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` const ROW_ID_REGEX = /^\[.*]$/g const ENCODED_SPACE = encodeURIComponent(" ") -const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}.\d{3}Z)?$/ +const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:.\d{3})?Z)?$/ const TIME_REGEX = /^(?:\d{2}:)?(?:\d{2}:)(?:\d{2})$/ export function isExternalTableID(tableId: string) { @@ -142,17 +142,17 @@ export function breakRowIdField(_id: string | { _id: string }): any[] { } } -export function isInvalidISODateString(str: string) { +export function isValidISODateString(str: string) { const trimmedValue = str.trim() if (!ISO_DATE_REGEX.test(trimmedValue)) { return false } - let d = new Date(trimmedValue) - return isNaN(d.getTime()) + const d = new Date(trimmedValue) + return !isNaN(d.getTime()) } -export function isValidISODateString(str: string) { - return ISO_DATE_REGEX.test(str.trim()) +export function isInvalidISODateString(str: string) { + return !isValidISODateString(str) } export function isValidFilter(value: any) { diff --git a/packages/bbui/src/Form/Core/Select.svelte b/packages/bbui/src/Form/Core/Select.svelte index 4a921f163c..115209054c 100644 --- a/packages/bbui/src/Form/Core/Select.svelte +++ b/packages/bbui/src/Form/Core/Select.svelte @@ -35,7 +35,7 @@ export let footer: string | undefined = undefined export let open: boolean = false export let searchTerm: string | undefined = undefined - export let loading: boolean | undefined = undefined + export let loading: boolean | undefined = false export let onOptionMouseenter = () => {} export let onOptionMouseleave = () => {} export let customPopoverHeight: string | undefined = undefined diff --git a/packages/bbui/src/Form/Core/TextArea.svelte b/packages/bbui/src/Form/Core/TextArea.svelte index 30c1993f9b..15cc13a7fa 100644 --- a/packages/bbui/src/Form/Core/TextArea.svelte +++ b/packages/bbui/src/Form/Core/TextArea.svelte @@ -75,6 +75,7 @@ class:is-disabled={disabled} class:is-focused={isFocused} > + + diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ExternalActions.js b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ExternalActions.js deleted file mode 100644 index d5d382485c..0000000000 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ExternalActions.js +++ /dev/null @@ -1,13 +0,0 @@ -import DiscordLogo from "assets/discord.svg" -import ZapierLogo from "assets/zapier.png" -import n8nLogo from "assets/n8n_square.png" -import MakeLogo from "assets/make.svg" -import SlackLogo from "assets/slack.svg" - -export const externalActions = { - zapier: { name: "zapier", icon: ZapierLogo }, - discord: { name: "discord", icon: DiscordLogo }, - slack: { name: "slack", icon: SlackLogo }, - integromat: { name: "integromat", icon: MakeLogo }, - n8n: { name: "n8n", icon: n8nLogo }, -} diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ExternalActions.ts b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ExternalActions.ts new file mode 100644 index 0000000000..cb9fa407d5 --- /dev/null +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ExternalActions.ts @@ -0,0 +1,20 @@ +import DiscordLogo from "assets/discord.svg" +import ZapierLogo from "assets/zapier.png" +import n8nLogo from "assets/n8n_square.png" +import MakeLogo from "assets/make.svg" +import SlackLogo from "assets/slack.svg" +import { AutomationActionStepId } from "@budibase/types" + +export type ExternalAction = { + name: string + icon: string +} +export const externalActions: Partial< + Record +> = { + [AutomationActionStepId.zapier]: { name: "zapier", icon: ZapierLogo }, + [AutomationActionStepId.discord]: { name: "discord", icon: DiscordLogo }, + [AutomationActionStepId.slack]: { name: "slack", icon: SlackLogo }, + [AutomationActionStepId.integromat]: { name: "integromat", icon: MakeLogo }, + [AutomationActionStepId.n8n]: { name: "n8n", icon: n8nLogo }, +} diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte index 01f0032e58..f705edcda6 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte @@ -3,6 +3,7 @@ automationStore, automationHistoryStore, selectedAutomation, + appStore, } from "@/stores/builder" import ConfirmDialog from "@/components/common/ConfirmDialog.svelte" import TestDataModal from "./TestDataModal.svelte" @@ -19,6 +20,9 @@ import { memo } from "@budibase/frontend-core" import { sdk } from "@budibase/shared-core" import DraggableCanvas from "../DraggableCanvas.svelte" + import { onMount } from "svelte" + import { environment } from "@/stores/portal" + import Count from "../../SetupPanel/Count.svelte" export let automation @@ -31,6 +35,10 @@ let treeEle let draggable + let prodErrors + + $: $automationStore.showTestModal === true && testDataModal.show() + // Memo auto - selectedAutomation $: memoAutomation.set(automation) @@ -63,53 +71,65 @@ notifications.error("Error deleting automation") } } + + onMount(async () => { + try { + await automationStore.actions.initAppSelf() + await environment.loadVariables() + const response = await automationStore.actions.getLogs({ + automationId: automation._id, + status: "error", + }) + prodErrors = response?.data?.length || 0 + } catch (error) { + console.error(error) + } + }) -
-
- - -
-
- - -
+
+
+
+ {automation.name}
- -
-
- -
- {#if !$automationStore.showTestPanel && $automationStore.testResults} - - {/if} -
+ + + { + const params = new URLSearchParams({ + ...(prodErrors ? { open: "error" } : {}), + automationId: automation._id, + }) + window.open( + `/builder/app/${ + $appStore.appId + }/settings/automations?${params.toString()}`, + "_blank" + ) + }} + > + Logs + + + {#if !isRowAction}
-
- - - {#if Object.keys(blockRefs).length} - {#each blocks as block, idx (block.id)} - - {/each} - {/if} - - +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + + {#if Object.keys(blockRefs).length} + {#each blocks as block, idx (block.id)} + + {/each} + {/if} + + +
- + { + automationStore.update(state => ({ ...state, showTestModal: false })) + }} +> diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index 95df59b249..af6849b14d 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -1,38 +1,21 @@ + +
+ {#if blockRef} + {#if isTriggerBlock} + + + Trigger + + + {:else if blockRef.looped} + Looping + {:else} + + {/if} + {#if blockResult && flowStatus && !hideStatus} + + { + if (branch || !block) { + return + } + await automationStore.actions.selectNode( + block?.id, + flowStatus.type == FlowStatusType.SUCCESS + ? DataMode.OUTPUT + : DataMode.ERRORS + ) + }} + > + {flowStatus.message} + + + {/if} + {/if} +
+ + diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/StepNode.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/StepNode.svelte index fbd49723be..cccc44f4e9 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/StepNode.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/StepNode.svelte @@ -46,16 +46,26 @@ ...userBindings, ...settingBindings, ] + onMount(() => { // Register the trigger as the focus element for the automation // Onload, the canvas will use the dimensions to center the step if (stepEle && step.type === "TRIGGER" && !$view.focusEle) { const { width, height, left, right, top, bottom, x, y } = stepEle.getBoundingClientRect() - view.update(state => ({ ...state, - focusEle: { width, height, left, right, top, bottom, x, y }, + focusEle: { + width, + height, + left, + right, + top, + bottom, + x, + y, + ...(step.type === "TRIGGER" ? { targetY: 100 } : {}), + }, })) } }) diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte index c962d4fd39..2538cb2853 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte @@ -122,7 +122,6 @@ await tick() try { await automationStore.actions.test($selectedAutomation.data, testData) - $automationStore.showTestPanel = true } catch (error) { notifications.error(error) } diff --git a/packages/builder/src/components/automation/AutomationBuilder/StepPanel.svelte b/packages/builder/src/components/automation/AutomationBuilder/StepPanel.svelte new file mode 100644 index 0000000000..e11598ae78 --- /dev/null +++ b/packages/builder/src/components/automation/AutomationBuilder/StepPanel.svelte @@ -0,0 +1,270 @@ + + + + + + +
+
+ { + if ($memoBlock && !isTrigger($memoBlock)) { + automationStore.actions.updateBlockTitle($memoBlock, e.detail) + } + }} + /> + { + automationStore.actions.selectNode() + }} + /> +
+ {#if isStep} +
+ {#if $memoBlock && !isBranchStep($memoBlock) && ($memoBlock.features?.[AutomationFeature.LOOPING] || !$memoBlock.features)} + { + if (loopBlock) { + await automationStore.actions.removeLooping(blockRef) + } else { + await automationStore.actions.addLooping(blockRef) + } + }} + > + {loopBlock ? `Stop Looping` : `Loop`} + + {/if} + { + if (!blockRef) { + return + } + await automationStore.actions.deleteAutomationBlock(blockRef.pathTo) + }} + > + Delete + +
+ {/if} +
+ +
+
+ {#if loopBlock} + + + + + {:else if isAppAction} + + + + {/if} + + + + + {#if block?.stepId === AutomationTriggerStepId.WEBHOOK} + + {/if} +
+ + +
+ { + automationStore.update(state => ({ + ...state, + showTestModal: true, + })) + }} + /> +
+ + diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationCustomLayout.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationCustomLayout.svelte new file mode 100644 index 0000000000..a4aac22977 --- /dev/null +++ b/packages/builder/src/components/automation/SetupPanel/AutomationCustomLayout.svelte @@ -0,0 +1,44 @@ + + +{#if layout} + {#each layout as config} + {#if config.wrapped === false} + + {:else} + + + + {/if} + {/each} +{/if} diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationSchemaLayout.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationSchemaLayout.svelte new file mode 100644 index 0000000000..6011e2753d --- /dev/null +++ b/packages/builder/src/components/automation/SetupPanel/AutomationSchemaLayout.svelte @@ -0,0 +1,389 @@ + + +{#each schemaProperties as [key, field]} + {#if canShowField(field, inputData)} + {@const { config, props, value, title } = schemaToUI(key, field, block)} + {#if config} + {#if config.wrapped === false} + { + defaultChange({ [key]: e.detail }, block) + })} + /> + {:else} + + { + defaultChange({ [key]: e.detail }, block) + })} + /> + + {/if} + {/if} + {/if} +{/each} diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationSelector.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationSelector.svelte index a9cb3cc64f..d9d37edda2 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationSelector.svelte @@ -1,15 +1,18 @@ -
- -
+
+ { + e.stopPropagation() + startEditing() + }} + on:keydown={async e => { + if (e.key === "Enter") { + stopEditing() + } + }} + on:blur={stopEditing} + /> +
+ {/if} + {#if blockNameError && editing} +
+
+ +
+
+ {/if} +
+ + diff --git a/packages/builder/src/components/automation/SetupPanel/BlockProperties.svelte b/packages/builder/src/components/automation/SetupPanel/BlockProperties.svelte new file mode 100644 index 0000000000..6458c7aee8 --- /dev/null +++ b/packages/builder/src/components/automation/SetupPanel/BlockProperties.svelte @@ -0,0 +1,65 @@ + + +{#if customLayout} + + +{:else} + + +{/if} diff --git a/packages/builder/src/components/automation/SetupPanel/Count.svelte b/packages/builder/src/components/automation/SetupPanel/Count.svelte new file mode 100644 index 0000000000..4f49dc468f --- /dev/null +++ b/packages/builder/src/components/automation/SetupPanel/Count.svelte @@ -0,0 +1,51 @@ + + +
+ + {#if count} + { + dispatch("hover") + }} + > + {count} + + {/if} + + +
+ + diff --git a/packages/builder/src/components/automation/SetupPanel/DateSelector.svelte b/packages/builder/src/components/automation/SetupPanel/DateSelector.svelte new file mode 100644 index 0000000000..f5dff35822 --- /dev/null +++ b/packages/builder/src/components/automation/SetupPanel/DateSelector.svelte @@ -0,0 +1,37 @@ + + + dispatch("change", e.detail)} + {bindings} + allowJS={true} + updateOnChange={false} + {disabled} + {context} +> + { + dispatch("change", e.detail) + }} + /> + diff --git a/packages/builder/src/components/automation/SetupPanel/ExecuteScript.svelte b/packages/builder/src/components/automation/SetupPanel/ExecuteScript.svelte new file mode 100644 index 0000000000..26047893c8 --- /dev/null +++ b/packages/builder/src/components/automation/SetupPanel/ExecuteScript.svelte @@ -0,0 +1,111 @@ + + + + { + // Push any pending changes when the window closes + dispatch("change", value) + }} +> +
+
+ { + // need to pass without the value inside + value = e.detail + }} + completions={stepCompletions} + mode={codeMode} + autocompleteEnabled={codeMode !== EditorModes.JS} + bind:getCaretPosition + bind:insertAtPos + placeholder={codeMode === EditorModes.Handlebars + ? "Add bindings by typing {{" + : null} + /> +
+ {#if editingJs} +
+ +
+ {/if} +
+
+ + diff --git a/packages/builder/src/components/automation/SetupPanel/ExecuteScriptV2.svelte b/packages/builder/src/components/automation/SetupPanel/ExecuteScriptV2.svelte new file mode 100644 index 0000000000..a5ba264f60 --- /dev/null +++ b/packages/builder/src/components/automation/SetupPanel/ExecuteScriptV2.svelte @@ -0,0 +1,67 @@ + + +
+ dispatch("change", e.detail)} + {value} + {bindings} + allowJS={true} + allowHBS={false} + updateOnChange={false} + {context} + > +
+ dispatch("change", e.detail)} + dropdown={DropdownPosition.Relative} + /> +
+
+
+ + diff --git a/packages/builder/src/components/automation/SetupPanel/FileSelector.svelte b/packages/builder/src/components/automation/SetupPanel/FileSelector.svelte new file mode 100644 index 0000000000..2ca1b02191 --- /dev/null +++ b/packages/builder/src/components/automation/SetupPanel/FileSelector.svelte @@ -0,0 +1,89 @@ + + +
+
+ { + dispatch("change", { + [key]: null, + meta: { + useAttachmentBinding: e.detail, + }, + }) + }} + /> +
+ +
+ {#if !useAttachmentBinding} + {#key value} + + {/key} + {:else} + dispatch("change", { [key]: e.detail })} + {bindings} + updateOnChange={false} + {context} + /> + {/if} +
+
diff --git a/packages/builder/src/components/automation/SetupPanel/FilterSelector.svelte b/packages/builder/src/components/automation/SetupPanel/FilterSelector.svelte new file mode 100644 index 0000000000..6de5e54398 --- /dev/null +++ b/packages/builder/src/components/automation/SetupPanel/FilterSelector.svelte @@ -0,0 +1,122 @@ + + + + {filterCount > 0 ? "Update Filter" : "No Filter set"} + + + { + tempFilters = filters + }} +> + + + + (tempFilters = e.detail)} + evaluationContext={context} + /> + + diff --git a/packages/builder/src/components/automation/SetupPanel/PropField.svelte b/packages/builder/src/components/automation/SetupPanel/PropField.svelte index ba3c0ff081..6f202719bd 100644 --- a/packages/builder/src/components/automation/SetupPanel/PropField.svelte +++ b/packages/builder/src/components/automation/SetupPanel/PropField.svelte @@ -1,10 +1,10 @@ -
+ import { API } from "@/api" + import { tables } from "@/stores/builder" + import { type TableDatasource, SortOrder } from "@budibase/types" + import { fetchData } from "@budibase/frontend-core" + import type TableFetch from "@budibase/frontend-core/src/fetch/TableFetch" + import { Select } from "@budibase/bbui" + + export let tableId: string | undefined + + let datasource: TableDatasource + let fetch: TableFetch | undefined + let rowSearchTerm = "" + let selectedRow + + $: primaryDisplay = table?.primaryDisplay + + $: table = tableId + ? $tables.list.find(table => table._id === tableId) + : undefined + + $: if (table && tableId) { + datasource = { type: "table", tableId } + fetch = createFetch(datasource) + } + + $: if (rowSearchTerm && primaryDisplay) { + fetch?.update({ + query: { + fuzzy: { + [primaryDisplay]: rowSearchTerm || "", + }, + }, + }) + } + + const createFetch = (datasource: TableDatasource) => { + if (!datasource || !primaryDisplay) { + return + } + + return fetchData({ + API, + datasource, + options: { + sortColumn: primaryDisplay, + sortOrder: SortOrder.ASCENDING, + query: { + fuzzy: { + [primaryDisplay]: rowSearchTerm || "", + }, + }, + limit: 20, + }, + }) + } + + $: fetchedRows = fetch ? $fetch?.rows : [] + $: fetchLoading = fetch ? $fetch?.loading : false + + const compare = (a: any, b: any) => { + primaryDisplay && a?.[primaryDisplay] === b?.[primaryDisplay] + } + + +