diff --git a/.github/workflows/deploy-qa.yml b/.github/workflows/deploy-qa.yml index d850d289ff..1339ad2eb9 100644 --- a/.github/workflows/deploy-qa.yml +++ b/.github/workflows/deploy-qa.yml @@ -11,10 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: peter-evans/repository-dispatch@v2 - env: - PAYLOAD_VERSION: ${{ github.sha }} - REF_NAME: ${{ github.ref_name}} with: repository: budibase/budibase-deploys event-type: budicloud-qa-deploy token: ${{ secrets.GH_ACCESS_TOKEN }} + client-payload: |- + { + "VERSION": "${{ github.sha }}", + "REF_NAME": "${{ github.ref_name}}" + } diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml index df25182cd6..2edb470405 100644 --- a/.github/workflows/release-master.yml +++ b/.github/workflows/release-master.yml @@ -165,17 +165,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Get the current budibase release version - id: version - run: | - release_version=$(cat lerna.json | jq -r '.version') - echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV - - uses: passeidireto/trigger-external-workflow-action@main - env: - PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }} - REF_NAME: ${{ github.ref_name}} + - uses: peter-evans/repository-dispatch@v2 with: repository: budibase/budibase-deploys - event: budicloud-qa-deploy - github_pat: ${{ secrets.GH_ACCESS_TOKEN }} + event-type: budicloud-qa-deploy + token: ${{ secrets.GH_ACCESS_TOKEN }} + client-payload: |- + { + "VERSION": "${{ github.ref_name }}", + "REF_NAME": "${{ github.ref_name}}" + } diff --git a/.github/workflows/release-singleimage.yml b/.github/workflows/release-singleimage.yml index a3444d5e7a..16b1da186a 100644 --- a/.github/workflows/release-singleimage.yml +++ b/.github/workflows/release-singleimage.yml @@ -66,7 +66,7 @@ jobs: context: . push: true platforms: linux/amd64,linux/arm64 - build-args: BUDIBASE_VERSION=$BUDIBASE_VERSION + build-args: BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }} tags: budibase/budibase,budibase/budibase:${{ env.RELEASE_VERSION }} file: ./hosting/single/Dockerfile.v2 env: @@ -79,7 +79,7 @@ jobs: platforms: linux/amd64 build-args: | TARGETBUILD=aas - BUDIBASE_VERSION=$BUDIBASE_VERSION + BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }} tags: budibase/budibase-aas,budibase/budibase-aas:${{ env.RELEASE_VERSION }} file: ./hosting/single/Dockerfile.v2 env: diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index eaf71ae61a..78c07a037c 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -1,4 +1,4 @@ -name: Tag release +name: Release concurrency: group: tag-release cancel-in-progress: false @@ -19,6 +19,8 @@ on: jobs: tag-release: runs-on: ubuntu-latest + outputs: + version: ${{ steps.tag-release.outputs.version }} steps: - name: Fail if branch is not master @@ -33,6 +35,7 @@ jobs: - run: cd scripts && yarn - name: Tag release + id: tag-release run: | cd scripts # setup the username and email. @@ -41,3 +44,23 @@ jobs: BUMP_TYPE_INPUT=${{ github.event.inputs.versioning }} BUMP_TYPE=${BUMP_TYPE_INPUT:-"patch"} ./versionCommit.sh $BUMP_TYPE + + + new_version=$(./getCurrentVersion.sh) + echo "version=$new_version" >> $GITHUB_OUTPUT + + trigger-release: + needs: [tag-release] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: peter-evans/repository-dispatch@v2 + with: + repository: budibase/budibase-deploys + event-type: release-prod + token: ${{ secrets.GH_ACCESS_TOKEN }} + client-payload: |- + { + "TAG": "${{ needs.tag-release.outputs.version }}" + } diff --git a/README.md b/README.md index 7827d4e48a..35b84a8816 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Budibase + Budibase

diff --git a/hosting/proxy/error.html b/hosting/proxy/error.html index 023c1ebaff..545d6c7f6d 100644 --- a/hosting/proxy/error.html +++ b/hosting/proxy/error.html @@ -57,8 +57,8 @@ --spectrum-global-color-gray-600: rgb(144,144,144); --spectrum-global-color-gray-900: rgb(255,255,255); --spectrum-global-color-gray-800: rgb(227,227,227); - --spectrum-global-color-static-blue-600: rgb(20,115,230); - --spectrum-global-color-static-blue-hover: rgb( 18, 103, 207); + --bb-indigo: #6E56FF; + --bb-indigo-light: #9F8FFF; } html, body { @@ -90,15 +90,8 @@ .info { display: flex; flex-direction: column; - align-items: left; + align-items: flex-start; } - - @media only screen and (max-width: 600px) { - .info { - align-items: center; - } - } - .status { color: var(--spectrum-global-color-gray-600) } @@ -113,13 +106,14 @@ .buttons { display: flex; flex-direction: row; + justify-content: flex-start; margin-top: 15px; } .homeButton { - background-color: var(--spectrum-global-color-static-blue-600); + background-color: var(--bb-indigo); } .homeButton:hover { - background-color: var(--spectrum-global-color-static-blue-hover); + background-color: var(--bb-indigo-light); } .statusButton { background-color: transparent; @@ -127,20 +121,30 @@ border: none; } .hero { - height: 160px; - width: 160px; - margin-right: 80px; + height: 60px; + margin: 10px 40px 10px 0; + } + .hero img { + height: 100%; } .content { display: flex; flex-direction: row; - align-items: flex-end; + align-items: center; justify-content: center; + padding: 0 40px; + } + h1 { + margin-bottom: 10px; + } + h3 { + margin-top: 0; } @media only screen and (max-width: 600px) { .content { flex-direction: column; + align-items: flex-start; } } @@ -152,16 +156,15 @@
- Budibase Logo + Budibase Logo
-

+

 

Houston we have a problem!

-

-

+

 

diff --git a/hosting/single/runner.sh b/hosting/single/runner.sh index 770b23eec1..9dc7aa25d8 100644 --- a/hosting/single/runner.sh +++ b/hosting/single/runner.sh @@ -77,7 +77,7 @@ mkdir -p ${DATA_DIR}/minio chown -R couchdb:couchdb ${DATA_DIR}/couch redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 & /bbcouch-runner.sh & -minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 & +/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 & /etc/init.d/nginx restart if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then # Add monthly cron job to renew certbot certificate diff --git a/i18n/README.de.md b/i18n/README.de.md index a2f4c3afb9..17d3d1ebbe 100644 --- a/i18n/README.de.md +++ b/i18n/README.de.md @@ -1,6 +1,6 @@

- Budibase + Budibase

diff --git a/i18n/README.es.md b/i18n/README.es.md index 21eb8caef7..227d5d5d5f 100644 --- a/i18n/README.es.md +++ b/i18n/README.es.md @@ -1,6 +1,6 @@

- Budibase + Budibase

diff --git a/i18n/README.fr.md b/i18n/README.fr.md index 12abd4d073..f5f9fbb25e 100644 --- a/i18n/README.fr.md +++ b/i18n/README.fr.md @@ -1,6 +1,6 @@

- Budibase + Budibase

diff --git a/i18n/README.id.md b/i18n/README.id.md index d4a25f569c..c2077f3922 100644 --- a/i18n/README.id.md +++ b/i18n/README.id.md @@ -1,6 +1,6 @@

- Budibase + Budibase

diff --git a/i18n/README.jp.md b/i18n/README.jp.md index 6fea497d53..62d0b1d3aa 100644 --- a/i18n/README.jp.md +++ b/i18n/README.jp.md @@ -1,6 +1,6 @@

- Budibase + Budibase

diff --git a/i18n/README.zh.md b/i18n/README.zh.md index 7e4dffd387..a6a9575029 100644 --- a/i18n/README.zh.md +++ b/i18n/README.zh.md @@ -1,6 +1,6 @@

- Budibase + Budibase

diff --git a/lerna.json b/lerna.json index f0f51242d1..611cf7d32b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.12.4", + "version": "2.13.0", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 417fb31e0e..8a27cde104 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "scripts": { "preinstall": "node scripts/syncProPackage.js", "setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev", - "build": "lerna run build --stream", + "build": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream", "build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput", "check:types": "lerna run check:types", "build:sdk": "lerna run --stream build:sdk", diff --git a/packages/backend-core/src/index.ts b/packages/backend-core/src/index.ts index c7cf9f56cc..ffffd8240a 100644 --- a/packages/backend-core/src/index.ts +++ b/packages/backend-core/src/index.ts @@ -30,7 +30,6 @@ export * as timers from "./timers" export { default as env } from "./environment" export * as blacklist from "./blacklist" export * as docUpdates from "./docUpdates" -export * from "./utils/Duration" export { SearchParams } from "./db" // Add context to tenancy for backwards compatibility // only do this for external usages to prevent internal diff --git a/packages/backend-core/src/objectStore/utils.ts b/packages/backend-core/src/objectStore/utils.ts index dba5f3d1c2..4c3a84ba91 100644 --- a/packages/backend-core/src/objectStore/utils.ts +++ b/packages/backend-core/src/objectStore/utils.ts @@ -18,8 +18,12 @@ export const ObjectStoreBuckets = { } const bbTmp = join(tmpdir(), ".budibase") -if (!fs.existsSync(bbTmp)) { +try { fs.mkdirSync(bbTmp) +} catch (e: any) { + if (e.code !== "EEXIST") { + throw e + } } export function budibaseTempDir() { diff --git a/packages/backend-core/src/queue/inMemoryQueue.ts b/packages/backend-core/src/queue/inMemoryQueue.ts index a8add7ecb6..af2ec6dbaa 100644 --- a/packages/backend-core/src/queue/inMemoryQueue.ts +++ b/packages/backend-core/src/queue/inMemoryQueue.ts @@ -36,7 +36,7 @@ class InMemoryQueue { * @param opts This is not used by the in memory queue as there is no real use * case when in memory, but is the same API as Bull */ - constructor(name: string, opts?: any) { + constructor(name: string, opts = null) { this._name = name this._opts = opts this._messages = [] diff --git a/packages/backend-core/src/queue/queue.ts b/packages/backend-core/src/queue/queue.ts index c0d1861de3..0658147709 100644 --- a/packages/backend-core/src/queue/queue.ts +++ b/packages/backend-core/src/queue/queue.ts @@ -2,18 +2,11 @@ import env from "../environment" import { getRedisOptions } from "../redis/utils" import { JobQueue } from "./constants" import InMemoryQueue from "./inMemoryQueue" -import BullQueue, { QueueOptions } from "bull" +import BullQueue from "bull" import { addListeners, StalledFn } from "./listeners" -import { Duration } from "../utils" import * as timers from "../timers" -import * as Redis from "ioredis" -// the queue lock is held for 5 minutes -const QUEUE_LOCK_MS = Duration.fromMinutes(5).toMs() -// queue lock is refreshed every 30 seconds -const QUEUE_LOCK_RENEW_INTERNAL_MS = Duration.fromSeconds(30).toMs() -// cleanup the queue every 60 seconds -const CLEANUP_PERIOD_MS = Duration.fromSeconds(60).toMs() +const CLEANUP_PERIOD_MS = 60 * 1000 let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = [] let cleanupInterval: NodeJS.Timeout @@ -28,14 +21,7 @@ export function createQueue( opts: { removeStalledCb?: StalledFn } = {} ): BullQueue.Queue { const { opts: redisOpts, redisProtocolUrl } = getRedisOptions() - const queueConfig: QueueOptions = { - redis: redisProtocolUrl! || (redisOpts as Redis.RedisOptions), - settings: { - maxStalledCount: 0, - lockDuration: QUEUE_LOCK_MS, - lockRenewTime: QUEUE_LOCK_RENEW_INTERNAL_MS, - }, - } + const queueConfig: any = redisProtocolUrl || { redis: redisOpts } let queue: any if (!env.isTest()) { queue = new BullQueue(jobQueue, queueConfig) diff --git a/packages/backend-core/src/utils/Duration.ts b/packages/backend-core/src/utils/Duration.ts deleted file mode 100644 index f376c2f7c7..0000000000 --- a/packages/backend-core/src/utils/Duration.ts +++ /dev/null @@ -1,49 +0,0 @@ -export enum DurationType { - MILLISECONDS = "milliseconds", - SECONDS = "seconds", - MINUTES = "minutes", - HOURS = "hours", - DAYS = "days", -} - -const conversion: Record = { - milliseconds: 1, - seconds: 1000, - minutes: 60 * 1000, - hours: 60 * 60 * 1000, - days: 24 * 60 * 60 * 1000, -} - -export class Duration { - static convert(from: DurationType, to: DurationType, duration: number) { - const milliseconds = duration * conversion[from] - return milliseconds / conversion[to] - } - - static from(from: DurationType, duration: number) { - return { - to: (to: DurationType) => { - return Duration.convert(from, to, duration) - }, - toMs: () => { - return Duration.convert(from, DurationType.MILLISECONDS, duration) - }, - } - } - - static fromSeconds(duration: number) { - return Duration.from(DurationType.SECONDS, duration) - } - - static fromMinutes(duration: number) { - return Duration.from(DurationType.MINUTES, duration) - } - - static fromHours(duration: number) { - return Duration.from(DurationType.HOURS, duration) - } - - static fromDays(duration: number) { - return Duration.from(DurationType.DAYS, duration) - } -} diff --git a/packages/backend-core/src/utils/index.ts b/packages/backend-core/src/utils/index.ts index ac17227459..318a7f13ba 100644 --- a/packages/backend-core/src/utils/index.ts +++ b/packages/backend-core/src/utils/index.ts @@ -1,4 +1,3 @@ export * from "./hashing" export * from "./utils" export * from "./stringUtils" -export * from "./Duration" diff --git a/packages/backend-core/src/utils/tests/Duration.spec.ts b/packages/backend-core/src/utils/tests/Duration.spec.ts deleted file mode 100644 index 46b996f788..0000000000 --- a/packages/backend-core/src/utils/tests/Duration.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Duration, DurationType } from "../Duration" - -describe("duration", () => { - it("should convert minutes to milliseconds", () => { - expect(Duration.fromMinutes(5).toMs()).toBe(300000) - }) - - it("should convert seconds to milliseconds", () => { - expect(Duration.fromSeconds(30).toMs()).toBe(30000) - }) - - it("should convert days to milliseconds", () => { - expect(Duration.fromDays(1).toMs()).toBe(86400000) - }) - - it("should convert minutes to days", () => { - expect(Duration.fromMinutes(1440).to(DurationType.DAYS)).toBe(1) - }) -}) diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte index 0b6a9bb94f..bfa0285250 100644 --- a/packages/bbui/src/Form/Core/Dropzone.svelte +++ b/packages/bbui/src/Form/Core/Dropzone.svelte @@ -386,7 +386,7 @@ } .compact .placeholder, .compact img { - margin: 10px 16px; + margin: 8px 16px; } .compact img { height: 90px; @@ -456,6 +456,12 @@ color: var(--red); } + .spectrum-Dropzone { + height: 220px; + } + .compact .spectrum-Dropzone { + height: 40px; + } .spectrum-Dropzone.disabled { pointer-events: none; background-color: var(--spectrum-global-color-gray-200); @@ -463,10 +469,6 @@ .disabled .spectrum-Heading--sizeL { color: var(--spectrum-alias-text-color-disabled); } - .compact .spectrum-Dropzone { - padding-top: 8px; - padding-bottom: 8px; - } .compact .spectrum-IllustratedMessage-description { margin: 0; } @@ -477,7 +479,6 @@ flex-wrap: wrap; justify-content: center; } - .tag { margin-top: 8px; } diff --git a/packages/bbui/src/bbui.css b/packages/bbui/src/bbui.css index 343aa77b27..9b5d89f61c 100644 --- a/packages/bbui/src/bbui.css +++ b/packages/bbui/src/bbui.css @@ -2,6 +2,15 @@ --background: #ffffff; --ink: #000000; + /* Brand colours */ + --bb-coral: #FF4E4E; + --bb-coral-light: #F97777; + --bb-indigo: #6E56FF; + --bb-indigo-light: #9F8FFF; + --bb-lime: #ECFFB5; + --bb-forest-green: #053835; + --bb-beige: #F6EFEA; + --grey-1: #fafafa; --grey-2: #f5f5f5; --grey-3: #eeeeee; diff --git a/packages/builder/.gitignore b/packages/builder/.gitignore index acd1a70579..abc5671984 100644 --- a/packages/builder/.gitignore +++ b/packages/builder/.gitignore @@ -6,3 +6,4 @@ release/ dist/ routify .routify/ +svelte.config.js \ No newline at end of file diff --git a/packages/builder/assets/bb-emblem.svg b/packages/builder/assets/bb-emblem.svg index 7d499e4862..26d09cc97f 100644 --- a/packages/builder/assets/bb-emblem.svg +++ b/packages/builder/assets/bb-emblem.svg @@ -1,80 +1,13 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/packages/builder/assets/bb-space-black.svg b/packages/builder/assets/bb-space-black.svg deleted file mode 100644 index fa1743f90c..0000000000 --- a/packages/builder/assets/bb-space-black.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/builder/assets/bb-space-purple.svg b/packages/builder/assets/bb-space-purple.svg deleted file mode 100644 index ccfb8b220d..0000000000 --- a/packages/builder/assets/bb-space-purple.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/builder/public/bblogo.png b/packages/builder/public/bblogo.png index 8c89c12f19..aa5ee4466e 100644 Binary files a/packages/builder/public/bblogo.png and b/packages/builder/public/bblogo.png differ diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js index c22240370b..ba2458f414 100644 --- a/packages/builder/src/builderStore/store/automation/index.js +++ b/packages/builder/src/builderStore/store/automation/index.js @@ -3,6 +3,7 @@ import { API } from "api" import { cloneDeep } from "lodash/fp" import { generate } from "shortid" import { selectedAutomation } from "builderStore" +import { notifications } from "@budibase/bbui" const initialAutomationState = { automations: [], @@ -21,6 +22,37 @@ export const getAutomationStore = () => { return store } +const updateReferencesInObject = (obj, modifiedIndex, action) => { + const regex = /{{\s*steps\.(\d+)\./g + for (const key in obj) { + if (typeof obj[key] === "string") { + let matches + while ((matches = regex.exec(obj[key])) !== null) { + const referencedStep = parseInt(matches[1]) + if (action === "add" && referencedStep >= modifiedIndex) { + obj[key] = obj[key].replace( + `{{ steps.${referencedStep}.`, + `{{ steps.${referencedStep + 1}.` + ) + } else if (action === "delete" && referencedStep > modifiedIndex) { + obj[key] = obj[key].replace( + `{{ steps.${referencedStep}.`, + `{{ steps.${referencedStep - 1}.` + ) + } + } + } else if (typeof obj[key] === "object" && obj[key] !== null) { + updateReferencesInObject(obj[key], modifiedIndex, action) + } + } +} + +const updateStepReferences = (steps, modifiedIndex, action) => { + steps.forEach(step => { + updateReferencesInObject(step.inputs, modifiedIndex, action) + }) +} + const automationActions = store => ({ definitions: async () => { const response = await API.getAutomationDefinitions() @@ -218,10 +250,40 @@ const automationActions = store => ({ if (!automation) { return } + + try { + updateStepReferences(newAutomation.definition.steps, blockIdx, "add") + } catch (e) { + notifications.error("Error adding automation block") + } newAutomation.definition.steps.splice(blockIdx, 0, block) await store.actions.save(newAutomation) }, - deleteAutomationBlock: async block => { + saveAutomationName: async (blockId, name) => { + const automation = get(selectedAutomation) + let newAutomation = cloneDeep(automation) + if (!automation) { + return + } + newAutomation.definition.stepNames = { + ...newAutomation.definition.stepNames, + [blockId]: name.trim(), + } + + await store.actions.save(newAutomation) + }, + deleteAutomationName: async blockId => { + const automation = get(selectedAutomation) + let newAutomation = cloneDeep(automation) + if (!automation) { + return + } + delete newAutomation.definition.stepNames[blockId] + + await store.actions.save(newAutomation) + }, + + deleteAutomationBlock: async (block, blockIdx) => { const automation = get(selectedAutomation) let newAutomation = cloneDeep(automation) @@ -233,7 +295,14 @@ const automationActions = store => ({ newAutomation.definition.steps = newAutomation.definition.steps.filter( step => step.id !== block.id ) + delete newAutomation.definition.stepNames?.[block.id] } + try { + updateStepReferences(newAutomation.definition.steps, blockIdx, "delete") + } catch (e) { + notifications.error("Error deleting automation block") + } + await store.actions.save(newAutomation) }, replace: async (automationId, automation) => { diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte index 63a3478ef3..a3c6a06690 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte @@ -5,13 +5,7 @@ import TestDataModal from "./TestDataModal.svelte" import { flip } from "svelte/animate" import { fly } from "svelte/transition" - import { - Heading, - Icon, - ActionButton, - notifications, - Modal, - } from "@budibase/bbui" + import { Icon, notifications, Modal } from "@budibase/bbui" import { ActionStepID } from "constants/backend/automations" import UndoRedoControl from "components/common/UndoRedoControl.svelte" import { automationHistoryStore } from "builderStore" @@ -20,9 +14,8 @@ let testDataModal let confirmDeleteDialog - - $: blocks = getBlocks(automation) - + let scrolling = false + $: blocks = getBlocks(automation).filter(x => x.stepId !== ActionStepID.LOOP) const getBlocks = automation => { let blocks = [] if (automation.definition.trigger) { @@ -32,58 +25,72 @@ return blocks } - async function deleteAutomation() { + const deleteAutomation = async () => { try { await automationStore.actions.delete($selectedAutomation) } catch (error) { notifications.error("Error deleting automation") } } + + const handleScroll = e => { + if (e.target.scrollTop >= 30) { + scrolling = true + } else if (e.target.scrollTop) { + // Set scrolling back to false if scrolled back to less than 100px + scrolling = false + } + } -
-
- {automation.name} -
- +
+
+ +
+
+
+ +
{ + testDataModal.show() + }} + > + Run test +
+
+
-
- { - testDataModal.show() - }} - icon="MultipleCheck" - size="M">Run test - { - $automationStore.showTestPanel = true - }} - size="M">Test Details +
{ + $automationStore.showTestPanel = true + }} + > + Test details
-
- {#each blocks as block, idx (block.id)} -
- {#if block.stepId !== ActionStepID.LOOP} - - {/if} -
- {/each} +
+
+ {#each blocks as block, idx (block.id)} +
+ {#if block.stepId !== ActionStepID.LOOP} + + {/if} +
+ {/each} +
.canvas { padding: var(--spacing-l) var(--spacing-xl); + overflow-y: auto; + max-height: 100%; + } + + .header-left :global(div) { + border-right: none; } /* Fix for firefox not respecting bottom padding in scrolling containers */ .canvas > *:last-child { @@ -117,23 +130,45 @@ } .content { - display: inline-block; - text-align: left; + flex-grow: 1; + padding: 23px 23px 80px; + box-sizing: border-box; + } + + .header.scrolling { + background: var(--background); + border-bottom: var(--border-light); + border-left: var(--border-light); + z-index: 1; } .header { + z-index: 1; display: flex; justify-content: space-between; align-items: center; + padding-left: var(--spacing-l); + transition: background 130ms ease-out; + flex: 0 0 48px; + padding-right: var(--spacing-xl); + } + .controls { + display: flex; + gap: var(--spacing-xl); } - .controls, .buttons { display: flex; justify-content: flex-end; align-items: center; - gap: var(--spacing-xl); - } - .buttons { gap: var(--spacing-s); } + + .buttons:hover { + cursor: pointer; + } + + .disabled { + pointer-events: none; + color: var(--spectrum-global-color-gray-500) !important; + } diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index 6c964c84a9..c6d38a4d2e 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -7,20 +7,16 @@ Detail, Modal, Button, - ActionButton, notifications, Label, + AbsTooltip, } from "@budibase/bbui" import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte" import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte" import ActionModal from "./ActionModal.svelte" import FlowItemHeader from "./FlowItemHeader.svelte" import RoleSelect from "components/design/settings/controls/RoleSelect.svelte" - import { - ActionStepID, - TriggerStepID, - Features, - } from "constants/backend/automations" + import { ActionStepID, TriggerStepID } from "constants/backend/automations" import { permissions } from "stores/backend" export let block @@ -86,7 +82,7 @@ if (loopBlock) { await automationStore.actions.deleteAutomationBlock(loopBlock) } - await automationStore.actions.deleteAutomationBlock(block) + await automationStore.actions.deleteAutomationBlock(block, blockIdx) } catch (error) { notifications.error("Error saving automation") } @@ -129,6 +125,10 @@
+ + + +
{}}>
@@ -139,9 +139,6 @@ {#if !showLooping}
-
- removeLooping()} icon="DeleteOutline" /> -
(open = !open)} /> {#if open}
- {#if !isTrigger} -
-
- {#if !loopBlock && (block?.features?.[Features.LOOPING] || !block.features)} - addLooping()} icon="Reuse"> - Add Looping - - {/if} - deleteStep()} - icon="DeleteOutline" - /> -
-
- {/if} - {#if isAppAction} - - +
+ + +
{/if} diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemHeader.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemHeader.svelte index c09474a370..0c7ac3d27d 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemHeader.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemHeader.svelte @@ -1,8 +1,9 @@ -
-
dispatch("toggle")} class="splitHeader"> +
+
{#if externalActions[block.stepId]} {/if}
- {#if isTrigger} + {#if isHeaderTrigger} Trigger - When this happens: {:else} - Step {idx} - Do this: +
+ Step {idx} +
{/if} - {block?.name?.toUpperCase() || ""} + { + automationName = e.target.value.trim() + }} + on:click={startTyping} + on:blur={async () => { + typing = false + if (automationNameError) { + automationName = stepNames[block.id] || block?.name + } else { + await saveName() + } + }} + />
{#if showTestStatus && testResult} -
- {status?.message} +
+
+ + {status?.message} + +
+ dispatch("toggle")} + hoverable + name={open ? "ChevronUp" : "ChevronDown"} + />
{/if}
{ onSelect(block) }} > - + {#if !showTestStatus} + {#if !isHeaderTrigger && !loopBlock && (block?.features?.[Features.LOOPING] || !block.features)} + + + + {/if} + + + + {/if} + {#if !showTestStatus} + dispatch("toggle")} + hoverable + name={open ? "ChevronUp" : "ChevronDown"} + /> + {/if}
+ {#if automationNameError} +
+ +
+ +
+
+
+ {/if}
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte index 17d5b35575..5c97d77ae8 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte @@ -60,6 +60,7 @@ step.id === block.id) - // Extract all outputs from all previous steps as available bindins + // Extract all outputs from all previous steps as available bindingsx§x let bindings = [] let loopBlockCount = 0 for (let idx = 0; idx < blockIdx; idx++) { @@ -183,20 +182,19 @@ } } const outputs = Object.entries(schema) - let bindingIcon = "" - let bindindingRank = 0 - + let bindingRank = 0 if (idx === 0) { bindingIcon = automation.trigger.icon } else if (isLoopBlock) { bindingIcon = "Reuse" - bindindingRank = idx + 1 + bindingRank = idx + 1 } else { bindingIcon = allSteps[idx].icon - bindindingRank = idx - loopBlockCount + bindingRank = idx - loopBlockCount } - + let bindingName = + automation.stepNames?.[allSteps[idx - loopBlockCount].id] bindings = bindings.concat( outputs.map(([name, value]) => { let runtimeName = isLoopBlock @@ -205,14 +203,20 @@ ? `steps[${idx - loopBlockCount}].${name}` : `steps.${idx - loopBlockCount}.${name}` const runtime = idx === 0 ? `trigger.${name}` : runtimeName - const categoryName = - idx === 0 - ? "Trigger outputs" - : isLoopBlock - ? "Loop Outputs" - : `Step ${idx - loopBlockCount} outputs` + + let categoryName + if (idx === 0) { + categoryName = "Trigger outputs" + } else if (isLoopBlock) { + categoryName = "Loop Outputs" + } else if (bindingName) { + categoryName = `${bindingName} outputs` + } else { + categoryName = `Step ${idx - loopBlockCount} outputs` + } + return { - readableBinding: runtime, + readableBinding: bindingName ? `${bindingName}.${name}` : runtime, runtimeBinding: runtime, type: value.type, description: value.description, @@ -221,7 +225,7 @@ display: { type: value.type, name: name, - rank: bindindingRank, + rank: bindingRank, }, } }) @@ -277,6 +281,16 @@ return !dependsOn || !!inputData[dependsOn] } + function shouldRenderField(value) { + return ( + value.customType !== "row" && + value.customType !== "code" && + value.customType !== "queryParams" && + value.customType !== "cron" && + value.customType !== "triggerSchema" + ) + } + onMount(async () => { try { await environment.loadVariables() @@ -289,245 +303,248 @@
{#each schemaProperties as [key, value]} {#if canShowField(key, value)} -
- {#if key !== "fields" && value.type !== "boolean"} +
+ {#if key !== "fields" && value.type !== "boolean" && shouldRenderField(value)} {/if} - {#if value.type === "string" && value.enum && canShowField(key, value)} - onChange(e, key)} - /> -
- {:else if value.type === "date"} - onChange(e, key)} - {bindings} - allowJS={true} - updateOnChange={false} - drawerLeft="260px" - > - onChange(e, key)} + placeholder={false} + options={value.enum} + getOptionLabel={(x, idx) => + value.pretty ? value.pretty[idx] : x} /> - - {:else if value.customType === "column"} - onChange(e, key)} - value={inputData[key]} - /> - {:else if value.customType === "email"} - {#if isTestModal} - onChange(e, key)} - {bindings} - fillWidth - updateOnChange={false} - /> - {:else} - + onChange(e, key)} + /> +
+ {:else if value.type === "date"} + onChange(e, key)} {bindings} - allowJS={false} + allowJS={true} updateOnChange={false} drawerLeft="260px" - /> - {/if} - {:else if value.customType === "query"} - onChange(e, key)} - value={inputData[key]} - /> - {:else if value.customType === "cron"} - onChange(e, key)} - value={inputData[key]} - /> - {:else if value.customType === "queryParams"} - onChange(e, key)} - value={inputData[key]} - {bindings} - /> - {:else if value.customType === "table"} - onChange(e, key)} - /> - {:else if value.customType === "row"} - { - if (e.detail?.key) { - onChange(e, e.detail.key) - } else { - onChange(e, key) - } - }} - {bindings} - {isTestModal} - {isUpdateRow} - /> - {:else if value.customType === "webhookUrl"} - onChange(e, key)} - value={inputData[key]} - /> - {:else if value.customType === "fields"} - onChange(e, key)} - {bindings} - {isTestModal} - /> - {:else if value.customType === "triggerSchema"} - onChange(e, key)} - value={inputData[key]} - /> - {:else if value.customType === "code"} - - {#if codeMode == EditorModes.JS} - (codeBindingOpen = !codeBindingOpen)} - quiet - icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"} - > - Bindings - - {#if codeBindingOpen} -
{JSON.stringify(bindings, null, 2)}
- {/if} - {/if} - { - // need to pass without the value inside - onChange({ detail: e.detail }, key) - inputData[key] = e.detail - }} - completions={stepCompletions} - mode={codeMode} - autocompleteEnabled={codeMode != EditorModes.JS} - height={500} - /> -
- {#if codeMode == EditorModes.Handlebars} - -
-
- Add available bindings by typing - }} - -
-
- {/if} -
-
- {:else if value.customType === "loopOption"} - onChange(e, key)} - {bindings} - updateOnChange={false} + value={inputData[key]} + options={Object.keys(table?.schema || {})} /> - {:else} -
+ {:else if value.customType === "filters"} + Define filters + + + (tempFilters = e.detail)} + /> + + {:else if value.customType === "password"} + onChange(e, key)} + value={inputData[key]} + /> + {:else if value.customType === "email"} + {#if isTestModal} + onChange(e, key)} + {bindings} + fillWidth + updateOnChange={false} + /> + {:else} onChange(e, key)} {bindings} + allowJS={false} updateOnChange={false} - placeholder={value.customType === "queryLimit" - ? queryLimit - : ""} drawerLeft="260px" /> -
+ {/if} + {:else if value.customType === "query"} + onChange(e, key)} + value={inputData[key]} + /> + {:else if value.customType === "cron"} + onChange(e, key)} + value={inputData[key]} + /> + {:else if value.customType === "queryParams"} + onChange(e, key)} + value={inputData[key]} + {bindings} + /> + {:else if value.customType === "table"} + onChange(e, key)} + /> + {:else if value.customType === "row"} + { + if (e.detail?.key) { + onChange(e, e.detail.key) + } else { + onChange(e, key) + } + }} + {bindings} + {isTestModal} + {isUpdateRow} + /> + {:else if value.customType === "webhookUrl"} + onChange(e, key)} + value={inputData[key]} + /> + {:else if value.customType === "fields"} + onChange(e, key)} + {bindings} + {isTestModal} + /> + {:else if value.customType === "triggerSchema"} + onChange(e, key)} + value={inputData[key]} + /> + {:else if value.customType === "code"} + + {#if codeMode == EditorModes.JS} + (codeBindingOpen = !codeBindingOpen)} + quiet + icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"} + > + Bindings + + {#if codeBindingOpen} +
{JSON.stringify(bindings, null, 2)}
+ {/if} + {/if} + { + // need to pass without the value inside + onChange({ detail: e.detail }, key) + inputData[key] = e.detail + }} + completions={stepCompletions} + mode={codeMode} + autocompleteEnabled={codeMode != EditorModes.JS} + height={500} + /> +
+ {#if codeMode == EditorModes.Handlebars} + +
+
+ Add available bindings by typing + }} + +
+
+ {/if} +
+
+ {:else if value.customType === "loopOption"} + query._id} - getOptionLabel={query => query.name} - /> +
+ +
+ table.name} - getOptionValue={table => table._id} -/> +
+ +
+