diff --git a/.eslintignore b/.eslintignore index 1dac74b117..0d81de0ef9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,4 +10,4 @@ packages/builder/.routify packages/builder/cypress/support/queryLevelTransformerFunction.js packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js packages/builder/cypress/reports -packages/sdk/sdk \ No newline at end of file +packages/sdk/sdk diff --git a/.eslintrc.json b/.eslintrc.json index 79e0c00abd..d94c749042 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,8 +16,7 @@ "dist", "public", "*.spec.js", - "bundle.js", - "packages/pro" + "bundle.js" ], "plugins": ["svelte3"], "extends": ["eslint:recommended"], @@ -30,9 +29,7 @@ "files": ["**/*.ts"], "parser": "@typescript-eslint/parser", "plugins": [], - "extends": [ - "eslint:recommended" - ], + "extends": ["eslint:recommended"], "rules": { "no-unused-vars": "off", "no-inner-declarations": "off", diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 6c875f2dfe..c04031cc9d 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -1,5 +1,9 @@ name: Budibase CI +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + on: # Trigger the workflow on push or pull request, # but only for the master branch @@ -23,6 +27,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + submodules: true + token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - name: Use Node.js 14.x uses: actions/setup-node@v3 with: @@ -135,15 +142,39 @@ jobs: with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - fetch-depth: 0 - - name: Check submodule + + - name: Check pro commit + id: get_pro_commits run: | cd packages/pro - git fetch - if ! git merge-base --is-ancestor $(git log -n 1 --pretty=format:%H) origin/develop; then - echo "Current commit has not been merged to develop" - echo "Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/develop/docs/getting_started.md" - exit 1 + pro_commit=$(git rev-parse HEAD) + + branch=${{ github.base_ref || github.ref_name }} + echo "Running on branch `$branch` (base_ref=${{ github.base_ref }}, ref_name=${{ github.head_ref }})" + + if [[ "$branch" == "master" ]]; then + base_commit=$(git rev-parse origin/master) else - echo "All good, the submodule had been merged!" + base_commit=$(git rev-parse origin/develop) fi + + echo "pro_commit=$pro_commit" + echo "pro_commit=$pro_commit" >> "$GITHUB_OUTPUT" + echo "base_commit=$base_commit" + echo "base_commit=$base_commit" >> "$GITHUB_OUTPUT" + + - name: Check submodule merged to develop + uses: actions/github-script@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const submoduleCommit = '${{ steps.get_pro_commits.outputs.pro_commit }}'; + const baseCommit = '${{ steps.get_pro_commits.outputs.base_commit }}'; + + if (submoduleCommit !== baseCommit) { + console.error('Submodule commit does not match the latest commit on the develop branch.'); + console.error('Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/develop/docs/getting_started.md') + process.exit(1); + } else { + console.log('All good, the submodule had been merged and setup correctly!') + } diff --git a/.github/workflows/tag-prerelease.yml b/.github/workflows/tag-prerelease.yml index 83660e409d..f6446c55f5 100644 --- a/.github/workflows/tag-prerelease.yml +++ b/.github/workflows/tag-prerelease.yml @@ -32,10 +32,11 @@ jobs: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - run: yarn + - run: cd scripts && yarn - name: Tag prerelease run: | + cd scripts # setup the username and email. git config --global user.name "Budibase Staging Release Bot" git config --global user.email "<>" - ./scripts/versionCommit.sh prerelease + ./versionCommit.sh prerelease diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index f361c200a0..191c3ad9ef 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -42,12 +42,13 @@ jobs: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - run: yarn + - run: cd scripts && yarn - name: Tag release run: | + cd scripts # setup the username and email. git config --global user.name "Budibase Staging Release Bot" git config --global user.email "<>" BUMP_TYPE_INPUT=${{ github.event.inputs.versioning }} BUMP_TYPE=${BUMP_TYPE_INPUT:-"patch"} - ./scripts/versionCommit.sh $BUMP_TYPE + ./versionCommit.sh $BUMP_TYPE diff --git a/hosting/nginx.dev.conf b/hosting/nginx.dev.conf index 1ecee422cd..915125cbce 100644 --- a/hosting/nginx.dev.conf +++ b/hosting/nginx.dev.conf @@ -126,6 +126,16 @@ http { proxy_pass http://app-service; } + location /embed { + rewrite /embed/(.*) /app/$1 break; + proxy_pass http://app-service; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header x-budibase-embed "true"; + add_header x-budibase-embed "true"; + add_header Content-Security-Policy "frame-ancestors *"; + } + location /builder { proxy_read_timeout 120s; proxy_connect_timeout 120s; diff --git a/hosting/proxy/nginx.prod.conf b/hosting/proxy/nginx.prod.conf index 001a08a9a6..9ce6b54053 100644 --- a/hosting/proxy/nginx.prod.conf +++ b/hosting/proxy/nginx.prod.conf @@ -92,6 +92,16 @@ http { proxy_pass $apps; } + location /embed { + rewrite /embed/(.*) /app/$1 break; + proxy_pass $apps; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header x-budibase-embed "true"; + add_header x-budibase-embed "true"; + add_header Content-Security-Policy "frame-ancestors *"; + } + location = / { proxy_pass $apps; } diff --git a/hosting/scripts/airgapped/airgappedDockerBuild.js b/hosting/scripts/airgapped/airgappedDockerBuild.js index cc0ea48eb3..58bc7c09a9 100755 --- a/hosting/scripts/airgapped/airgappedDockerBuild.js +++ b/hosting/scripts/airgapped/airgappedDockerBuild.js @@ -2,7 +2,9 @@ const fs = require("fs") const { execSync } = require("child_process") const path = require("path") -const IMAGES = { +const IS_SINGLE_IMAGE = process.env.SINGLE_IMAGE + +let IMAGES = { worker: "budibase/worker", apps: "budibase/apps", proxy: "budibase/proxy", @@ -10,7 +12,13 @@ const IMAGES = { couch: "ibmcom/couchdb3", curl: "curlimages/curl", redis: "redis", - watchtower: "containrrr/watchtower" + watchtower: "containrrr/watchtower", +} + +if (IS_SINGLE_IMAGE) { + IMAGES = { + budibase: "budibase/budibase" + } } const FILES = { @@ -39,11 +47,10 @@ for (let image in IMAGES) { } // copy config files -copyFile(FILES.COMPOSE) +if (!IS_SINGLE_IMAGE) { + copyFile(FILES.COMPOSE) +} copyFile(FILES.ENV) // compress -execSync(`tar -czf bb-airgapped.tar.gz hosting/scripts/bb-airgapped`) - -// clean up -fs.rmdirSync(OUTPUT_DIR, { recursive: true }) \ No newline at end of file +execSync(`tar -czf bb-airgapped.tar.gz hosting/scripts/bb-airgapped`) \ No newline at end of file diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index 56df8185a9..e43e5ad10c 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -37,6 +37,14 @@ COPY --from=build /worker /worker RUN apt-get update && \ apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server +# Install postgres client for pg_dump utils +RUN apt install software-properties-common apt-transport-https gpg -y \ + && curl -fsSl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null \ + && echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main | tee /etc/apt/sources.list.d/postgresql.list \ + && apt update -y \ + && apt install postgresql-client-15 -y \ + && apt remove software-properties-common apt-transport-https gpg -y + # install other dependencies, nodejs, oracle requirements, jdk8, redis, nginx WORKDIR /nodejs RUN curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh && \ diff --git a/lerna.json b/lerna.json index c228cfe62e..eb08127241 100644 --- a/lerna.json +++ b/lerna.json @@ -1,22 +1,10 @@ { - "version": "2.7.25-alpha.7", + "version": "2.7.36-alpha.4", "npmClient": "yarn", - "useNx": true, "packages": [ - "packages/backend-core", - "packages/bbui", - "packages/builder", - "packages/cli", - "packages/client", - "packages/frontend-core", - "packages/sdk", - "packages/server", - "packages/shared-core", - "packages/string-templates", - "packages/types", - "packages/worker", - "packages/pro/packages/pro" + "packages/*" ], + "useNx": true, "command": { "publish": { "ignoreChanges": [ diff --git a/package.json b/package.json index 03addbdab9..8b715d8747 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "prettier-plugin-svelte": "^2.3.0", "rimraf": "^3.0.2", "rollup-plugin-replace": "^2.2.0", - "semver": "^7.5.0", "svelte": "^3.38.2", "typescript": "4.7.3" }, @@ -66,6 +65,7 @@ "build:docker:selfhost": "lerna run --stream build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -", "build:docker:develop": "node scripts/pinVersions && lerna run --stream build:docker && yarn build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", "build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild", + "build:docker:airgap:single": "SINGLE_IMAGE=1 node hosting/scripts/airgapped/airgappedDockerBuild", "build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -", "build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .", "build:docker:single:image": "docker build -f hosting/single/Dockerfile -t budibase:latest .", @@ -94,19 +94,7 @@ }, "workspaces": { "packages": [ - "packages/backend-core", - "packages/bbui", - "packages/builder", - "packages/cli", - "packages/client", - "packages/frontend-core", - "packages/sdk", - "packages/server", - "packages/shared-core", - "packages/string-templates", - "packages/types", - "packages/worker", - "packages/pro/packages/pro" + "packages/*" ] }, "resolutions": { diff --git a/packages/backend-core/src/configs/configs.ts b/packages/backend-core/src/configs/configs.ts index 701b5bb329..49ace84d52 100644 --- a/packages/backend-core/src/configs/configs.ts +++ b/packages/backend-core/src/configs/configs.ts @@ -5,6 +5,7 @@ import { GoogleInnerConfig, OIDCConfig, OIDCInnerConfig, + OIDCLogosConfig, SCIMConfig, SCIMInnerConfig, SettingsConfig, @@ -191,6 +192,10 @@ export function getDefaultGoogleConfig(): GoogleInnerConfig | undefined { // OIDC +export async function getOIDCLogosDoc(): Promise { + return getConfig(ConfigType.OIDC_LOGOS) +} + async function getOIDCConfigDoc(): Promise { return getConfig(ConfigType.OIDC) } diff --git a/packages/backend-core/src/db/Replication.ts b/packages/backend-core/src/db/Replication.ts index eb9d613a58..e813722d98 100644 --- a/packages/backend-core/src/db/Replication.ts +++ b/packages/backend-core/src/db/Replication.ts @@ -57,6 +57,9 @@ class Replication { appReplicateOpts() { return { filter: (doc: any) => { + if (doc._id && doc._id.startsWith(DocumentType.AUTOMATION_LOG)) { + return false + } return doc._id !== DocumentType.APP_METADATA }, } diff --git a/packages/backend-core/src/docIds/ids.ts b/packages/backend-core/src/docIds/ids.ts index 152977b3af..e0ac85b3df 100644 --- a/packages/backend-core/src/docIds/ids.ts +++ b/packages/backend-core/src/docIds/ids.ts @@ -81,8 +81,19 @@ export function generateAppUserID(prodAppId: string, userId: string) { * Generates a new role ID. * @returns {string} The new role ID which the role doc can be stored under. */ -export function generateRoleID(id?: any) { - return `${DocumentType.ROLE}${SEPARATOR}${id || newid()}` +export function generateRoleID(name: string) { + const prefix = `${DocumentType.ROLE}${SEPARATOR}` + if (name.startsWith(prefix)) { + return name + } + return `${prefix}${name}` +} + +/** + * Utility function to be more verbose. + */ +export function prefixRoleID(name: string) { + return generateRoleID(name) } /** diff --git a/packages/backend-core/src/events/publishers/serve.ts b/packages/backend-core/src/events/publishers/serve.ts index 64e24e20a7..ac6a23dfdb 100644 --- a/packages/backend-core/src/events/publishers/serve.ts +++ b/packages/backend-core/src/events/publishers/serve.ts @@ -14,10 +14,15 @@ async function servedBuilder(timezone: string) { await publishEvent(Event.SERVED_BUILDER, properties) } -async function servedApp(app: App, timezone: string) { +async function servedApp( + app: App, + timezone: string, + embed?: boolean | undefined +) { const properties: AppServedEvent = { appVersion: app.version, timezone, + embed: embed === true, } await publishEvent(Event.SERVED_APP, properties) } diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index e8a3c76c0a..cf5c6bc406 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -1,5 +1,5 @@ import { BuiltinPermissionID, PermissionLevel } from "./permissions" -import { generateRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db" +import { prefixRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db" import { getAppDB } from "../context" import { doWithDB } from "../db" import { Screen, Role as RoleDoc } from "@budibase/types" @@ -25,18 +25,28 @@ const EXTERNAL_BUILTIN_ROLE_IDS = [ BUILTIN_IDS.PUBLIC, ] +export const RoleIDVersion = { + // original version, with a UUID based ID + UUID: undefined, + // new version - with name based ID + NAME: "name", +} + export class Role implements RoleDoc { _id: string _rev?: string name: string permissionId: string inherits?: string + version?: string permissions = {} constructor(id: string, name: string, permissionId: string) { this._id = id this.name = name this.permissionId = permissionId + // version for managing the ID - removing the role_ when responding + this.version = RoleIDVersion.NAME } addInheritance(inherits: string) { @@ -157,13 +167,16 @@ export async function getRole( role = cloneDeep( Object.values(BUILTIN_ROLES).find(role => role._id === roleId) ) + } else { + // make sure has the prefix (if it has it then it won't be added) + roleId = prefixRoleID(roleId) } try { const db = getAppDB() const dbRole = await db.get(getDBRoleID(roleId)) role = Object.assign(role, dbRole) // finalise the ID - role._id = getExternalRoleID(role._id) + role._id = getExternalRoleID(role._id, role.version) } catch (err) { if (!isBuiltin(roleId) && opts?.defaultPublic) { return cloneDeep(BUILTIN_ROLES.PUBLIC) @@ -261,6 +274,9 @@ export async function getAllRoles(appId?: string) { }) ) roles = body.rows.map((row: any) => row.doc) + roles.forEach( + role => (role._id = getExternalRoleID(role._id!, role.version)) + ) } const builtinRoles = getBuiltinRoles() @@ -268,14 +284,15 @@ export async function getAllRoles(appId?: string) { for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) { const builtinRole = builtinRoles[builtinRoleId] const dbBuiltin = roles.filter( - dbRole => getExternalRoleID(dbRole._id) === builtinRoleId + dbRole => + getExternalRoleID(dbRole._id!, dbRole.version) === builtinRoleId )[0] if (dbBuiltin == null) { roles.push(builtinRole || builtinRoles.BASIC) } else { // remove role and all back after combining with the builtin roles = roles.filter(role => role._id !== dbBuiltin._id) - dbBuiltin._id = getExternalRoleID(dbBuiltin._id) + dbBuiltin._id = getExternalRoleID(dbBuiltin._id!, dbBuiltin.version) roles.push(Object.assign(builtinRole, dbBuiltin)) } } @@ -381,19 +398,22 @@ export class AccessController { /** * Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions). */ -export function getDBRoleID(roleId?: string) { - if (roleId?.startsWith(DocumentType.ROLE)) { - return roleId +export function getDBRoleID(roleName: string) { + if (roleName?.startsWith(DocumentType.ROLE)) { + return roleName } - return generateRoleID(roleId) + return prefixRoleID(roleName) } /** * Remove the "role_" from builtin role IDs that have been written to the DB (for permissions). */ -export function getExternalRoleID(roleId?: string) { +export function getExternalRoleID(roleId: string, version?: string) { // for built-in roles we want to remove the DB role ID element (role_) - if (roleId?.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) { + if ( + (roleId.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) || + version === RoleIDVersion.NAME + ) { return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1] } return roleId diff --git a/packages/bbui/src/FancyForm/FancyCheckboxGroup.svelte b/packages/bbui/src/FancyForm/FancyCheckboxGroup.svelte index aaea388c36..ca3a6d937a 100644 --- a/packages/bbui/src/FancyForm/FancyCheckboxGroup.svelte +++ b/packages/bbui/src/FancyForm/FancyCheckboxGroup.svelte @@ -12,7 +12,6 @@ const dispatch = createEventDispatcher() $: updateSelected(selectedBooleans) - $: dispatch("change", selected) $: allSelected = selected?.length === options.length $: noneSelected = !selected?.length @@ -28,6 +27,7 @@ } } selected = array + dispatch("change", selected) } function toggleSelectAll() { @@ -36,6 +36,7 @@ } else { selectedBooleans = reset() } + dispatch("change", selected) } diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index bd575600b1..aada17b318 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -99,9 +99,15 @@ bind:this={button} > {#if fieldIcon} - - - + {#if !useOptionIconImage} + + + + {:else} + + icon + + {/if} {/if} {#if fieldColour} @@ -311,4 +317,8 @@ max-width: 170px; font-size: 12px; } + + .option-extra.icon.field-icon { + display: flex; + } diff --git a/packages/bbui/src/Markdown/SpectrumMDE.svelte b/packages/bbui/src/Markdown/SpectrumMDE.svelte index 9b0832c91f..8e7b1bbdfd 100644 --- a/packages/bbui/src/Markdown/SpectrumMDE.svelte +++ b/packages/bbui/src/Markdown/SpectrumMDE.svelte @@ -87,7 +87,7 @@ border-color: var(--spectrum-global-color-gray-400); } /* Toolbar button color */ - :global(.EasyMDEContainer .editor-toolbar button i) { + :global(.EasyMDEContainer .editor-toolbar button) { color: var(--spectrum-global-color-gray-800); } /* Separator between toolbar buttons*/ diff --git a/packages/builder/package.json b/packages/builder/package.json index a2567dc638..646bb144df 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -9,7 +9,8 @@ "dev:builder": "routify -c dev:vite", "dev:vite": "vite --host 0.0.0.0", "rollup": "rollup -c -w", - "test": "vitest run" + "test": "vitest run", + "test:watch": "vitest" }, "jest": { "globals": { diff --git a/packages/builder/src/builderStore/datasource.js b/packages/builder/src/builderStore/datasource.js deleted file mode 100644 index 219ff7eb8f..0000000000 --- a/packages/builder/src/builderStore/datasource.js +++ /dev/null @@ -1,54 +0,0 @@ -import { datasources, tables } from "../stores/backend" -import { IntegrationNames } from "../constants/backend" -import { get } from "svelte/store" -import cloneDeep from "lodash/cloneDeepWith" -import { API } from "api" - -function prepareData(config) { - let datasource = {} - let existingTypeCount = get(datasources).list.filter( - ds => ds.source === config.type - ).length - - let baseName = IntegrationNames[config.type] || config.name - let name = - existingTypeCount === 0 ? baseName : `${baseName}-${existingTypeCount + 1}` - - datasource.type = "datasource" - datasource.source = config.type - datasource.config = config.config - datasource.name = name - datasource.plus = config.plus - - return datasource -} - -export async function saveDatasource(config, { skipFetch, tablesFilter } = {}) { - const datasource = prepareData(config) - // Create datasource - const fetchSchema = !skipFetch && datasource.plus - const resp = await datasources.save(datasource, { fetchSchema, tablesFilter }) - - // update the tables incase datasource plus - await tables.fetch() - await datasources.select(resp._id) - return resp -} - -export async function createRestDatasource(integration) { - const config = cloneDeep(integration) - return saveDatasource(config) -} - -export async function validateDatasourceConfig(config) { - const datasource = prepareData(config) - return await API.validateDatasource(datasource) -} - -export async function getDatasourceInfo(config) { - let datasource = config - if (!config._id) { - datasource = prepareData(config) - } - return await API.fetchInfoForDatasource(datasource) -} diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index d0414b5733..9dfd11a537 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -61,6 +61,9 @@ const INITIAL_FRONTEND_STATE = { showNotificationAction: false, sidePanel: false, }, + features: { + componentValidation: false, + }, errors: [], hasAppPackage: false, libraries: null, @@ -117,10 +120,13 @@ export const getFrontendStore = () => { reset: () => { store.set({ ...INITIAL_FRONTEND_STATE }) websocket?.disconnect() + websocket = null }, initialise: async pkg => { const { layouts, screens, application, clientLibPath, hasLock } = pkg - websocket = createBuilderWebsocket(application.appId) + if (!websocket) { + websocket = createBuilderWebsocket(application.appId) + } await store.actions.components.refreshDefinitions(application.appId) // Reset store state @@ -145,6 +151,10 @@ export const getFrontendStore = () => { navigation: application.navigation || {}, usedPlugins: application.usedPlugins || [], hasLock, + features: { + ...INITIAL_FRONTEND_STATE.features, + ...application.features, + }, initialised: true, })) screenHistoryStore.reset() @@ -280,9 +290,12 @@ export const getFrontendStore = () => { } }, save: async screen => { - // Validate screen structure - // Temporarily disabled to accommodate migration issues - // store.actions.screens.validate(screen) + const state = get(store) + + // Validate screen structure if the app supports it + if (state.features?.componentValidation) { + store.actions.screens.validate(screen) + } // Check screen definition for any component settings which need updated store.actions.screens.enrichEmptySettings(screen) @@ -293,7 +306,6 @@ export const getFrontendStore = () => { const routesResponse = await API.fetchAppRoutes() // If plugins changed we need to fetch the latest app metadata - const state = get(store) let usedPlugins = state.usedPlugins if (savedScreen.pluginAdded) { const { application } = await API.fetchAppPackage(state.appId) diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index 092138170f..7a02433411 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -78,9 +78,6 @@ } async function removeLooping() { - let loopBlock = $selectedAutomation?.definition.steps.find( - x => x.blockToLoop === block.id - ) try { await automationStore.actions.deleteAutomationBlock(loopBlock) } catch (error) { @@ -89,10 +86,6 @@ } async function deleteStep() { - let loopBlock = $selectedAutomation?.definition.steps.find( - x => x.blockToLoop === block.id - ) - try { if (loopBlock) { await automationStore.actions.deleteAutomationBlock(loopBlock) @@ -168,8 +161,8 @@ $automationStore.blockDefinitions.ACTION.LOOP.schema.inputs .properties )} - block={loopBlock} {webhookModal} + block={loopBlock} /> @@ -191,7 +184,7 @@ {#if !isTrigger}
- {#if block?.features?.[Features.LOOPING] || !block.features} + {#if !loopBlock && (block?.features?.[Features.LOOPING] || !block.features)} addLooping()} icon="Reuse"> Add Looping diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index a8fa700b90..823dcc432b 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -309,7 +309,7 @@ } function canShowField(key, value) { - const dependsOn = value.dependsOn + const dependsOn = value?.dependsOn return !dependsOn || !!inputData[dependsOn] } @@ -333,7 +333,7 @@ : null}>{value.title || (key === "row" ? "Table" : key)} {/if} - {#if value.type === "string" && value.enum && canShowField(key)} + {#if value.type === "string" && value.enum && canShowField(key, value)} { + tables.select(tableId) + if (!$isActive("./table/:tableId")) { + $goto(`./table/${tableId}`) + } + } + function closeNode(datasource) { openDataSources = openDataSources.filter(id => datasource._id !== id) } @@ -151,9 +160,16 @@ {#if $database?._id}
+ selectTable(TableNames.USERS)} + /> {#each enrichedDataSources as datasource, idx} 0} + border text={datasource.name} opened={datasource.open} selected={$isActive("./datasource") && datasource.selected} @@ -174,7 +190,7 @@ {#if datasource.open} - + {#each $queries.list.filter(query => query.datasourceId === datasource._id) as query} - import { - Label, - Input, - Layout, - Toggle, - Button, - TextArea, - Modal, - EnvDropdown, - Accordion, - notifications, - } from "@budibase/bbui" - import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" - import { capitalise } from "helpers" - import { IntegrationTypes } from "constants/backend" - import { createValidationStore } from "helpers/validation/yup" - import { createEventDispatcher, onMount } from "svelte" - import { environment, licensing, auth } from "stores/portal" - import CreateEditVariableModal from "components/portal/environment/CreateEditVariableModal.svelte" - - export let datasource - export let schema - export let creating - - let createVariableModal - let selectedKey - - const validation = createValidationStore() - const dispatch = createEventDispatcher() - - function filter([key, value]) { - if (!value) { - return false - } - return !( - (datasource.source === IntegrationTypes.REST && - key === "defaultHeaders") || - value.deprecated - ) - } - - $: config = datasource?.config - $: configKeys = Object.entries(schema || {}) - .filter(el => filter(el)) - .map(([key]) => key) - - // setup the validation for each required field - $: configKeys.forEach(key => { - if (schema[key].required) { - validation.addValidatorType(key, schema[key].type, schema[key].required) - } - }) - // run the validation whenever the config changes - $: validation.check(config) - // dispatch the validation result - $: dispatch( - "valid", - Object.values($validation.errors).filter(val => val != null).length === 0 - ) - - let addButton - - function getDisplayName(key, fieldKey) { - let name - if (fieldKey && schema[key]["fields"][fieldKey]?.display) { - name = schema[key]["fields"][fieldKey].display - } else if (fieldKey) { - name = fieldKey - } else if (schema[key]?.display) { - name = schema[key].display - } else { - name = key - } - return capitalise(name) - } - - function getDisplayError(error, configKey) { - return error?.replace( - new RegExp(`${configKey}`, "i"), - getDisplayName(configKey) - ) - } - - function getFieldGroupKeys(fieldGroup) { - return Object.entries(schema[fieldGroup].fields || {}) - .filter(el => filter(el)) - .map(([key]) => key) - } - - async function save(data) { - try { - await environment.createVariable(data) - config[selectedKey] = `{{ env.${data.name} }}` - createVariableModal.hide() - } catch (err) { - notifications.error(`Failed to create variable: ${err.message}`) - } - } - - function showModal(configKey) { - selectedKey = configKey - createVariableModal.show() - } - - async function handleUpgradePanel() { - await environment.upgradePanelOpened() - $licensing.goToUpgradePage() - } - - onMount(async () => { - try { - await environment.loadVariables() - if ($auth.user) { - await licensing.init() - } - } catch (err) { - console.error(err) - } - }) - - -
- - {#if !creating} -
- - -
- {/if} - {#each configKeys as configKey} - {#if schema[configKey].type === "object"} -
- - -
- - {:else if schema[configKey].type === "boolean"} -
- - -
- {:else if schema[configKey].type === "longForm"} -
- -