diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index c8bc31f41d..840d580892 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -10,7 +10,6 @@ on: push: branches: - master - - develop pull_request: workflow_dispatch: @@ -262,11 +261,7 @@ jobs: 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) - elif [[ $branch == "develop" ]]; then - base_commit=$(git rev-parse origin/develop) - fi + base_commit=$(git rev-parse origin/master) if [[ ! -z $base_commit ]]; then echo "target_branch=$branch" diff --git a/.github/workflows/close-featurebranch.yml b/.github/workflows/close-featurebranch.yml index 0ec3b43598..46cb781730 100644 --- a/.github/workflows/close-featurebranch.yml +++ b/.github/workflows/close-featurebranch.yml @@ -4,7 +4,13 @@ on: pull_request: types: [closed] branches: - - develop + - master + workflow_dispatch: + inputs: + BRANCH: + type: string + description: Which featurebranch branch to destroy? + required: true jobs: release: @@ -13,8 +19,8 @@ jobs: - uses: actions/checkout@v3 - uses: passeidireto/trigger-external-workflow-action@main env: - PAYLOAD_BRANCH: ${{ github.head_ref }} - PAYLOAD_PR_NUMBER: ${{ github.ref }} + PAYLOAD_BRANCH: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.BRANCH || github.head_ref }} + PAYLOAD_PR_NUMBER: ${{ github.event.pull_request.number }} with: repository: budibase/budibase-deploys event: featurebranch-qa-close diff --git a/.github/workflows/deploy-featurebranch.yml b/.github/workflows/deploy-featurebranch.yml index 2c6302b56a..f1fb12c087 100644 --- a/.github/workflows/deploy-featurebranch.yml +++ b/.github/workflows/deploy-featurebranch.yml @@ -3,7 +3,6 @@ name: deploy-featurebranch on: pull_request: branches: - - develop - master jobs: diff --git a/.github/workflows/deploy-preprod.yml b/.github/workflows/deploy-preprod.yml deleted file mode 100644 index 9b7bca4770..0000000000 --- a/.github/workflows/deploy-preprod.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: "deploy-preprod" -on: - workflow_dispatch: - workflow_call: - -jobs: - deploy-to-legacy-preprod-env: - runs-on: ubuntu-latest - steps: - - name: Fail if not a tag - run: | - if [[ $GITHUB_REF != refs/tags/* ]]; then - echo "Workflow Dispatch can only be run on tags" - exit 1 - fi - - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Fail if tag is not in master - run: | - if ! git merge-base --is-ancestor ${{ github.sha }} origin/master; then - echo "Tag is not in master. This pipeline can only execute tags that are present on the master branch" - exit 1 - fi - - - name: Get the latest 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 }} - with: - repository: budibase/budibase-deploys - event: budicloud-preprod-deploy - github_pat: ${{ secrets.GH_ACCESS_TOKEN }} - diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml deleted file mode 100644 index bd727b7865..0000000000 --- a/.github/workflows/release-develop.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: Budibase Prerelease -concurrency: - group: release-prerelease - cancel-in-progress: false - -on: - push: - tags: - - "*-alpha.*" - workflow_dispatch: - -env: - # Posthog token used by ui at build time - # disable unless needed for testing - # POSTHOG_TOKEN: phc_uDYOfnFt6wAbBAXkC6STjcrTpAFiWIhqgFcsC1UVO5F - INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} - PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - FEATURE_PREVIEW_URL: https://budirelease.live - -jobs: - release-images: - runs-on: ubuntu-latest - - steps: - - name: Fail if not a tag - run: | - if [[ $GITHUB_REF != refs/tags/* ]]; then - echo "Workflow Dispatch can only be run on tags" - exit 1 - fi - - - uses: actions/checkout@v2 - with: - submodules: true - token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - fetch-depth: 0 - - - name: Fail if tag is not develop - run: | - if ! git merge-base --is-ancestor ${{ github.sha }} origin/develop; then - echo "Tag is not in develop" - exit 1 - fi - - - uses: actions/setup-node@v1 - with: - node-version: 18.x - - - run: yarn install --frozen-lockfile - - name: Update versions - run: ./scripts/updateVersions.sh - - run: yarn build - - run: yarn build:sdk - - - name: Publish budibase packages to NPM - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - # setup the username and email. - git config --global user.name "Budibase Staging Release Bot" - git config --global user.email "<>" - git submodule foreach git commit -a -m 'Release process' - git commit -a -m 'Release process' - echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc - yarn release:develop - - - name: Build/release Docker images - run: | - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - yarn build:docker:develop - env: - DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} - - release-helm-chart: - needs: [release-images] - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup Helm - uses: azure/setup-helm@v1 - id: helm-install - - # due to helm repo index issue: https://github.com/helm/helm/issues/7363 - # we need to create new package in a different dir, merge the index and move the package back - - name: Build and release helm chart - run: | - git config user.name "Budibase Helm Bot" - git config user.email "<>" - git reset --hard - git fetch - mkdir sync - echo "Packaging chart to sync dir" - helm package charts/budibase --version 0.0.0-develop --app-version develop --destination sync - echo "Packaging successful" - git checkout gh-pages - echo "Indexing helm repo" - helm repo index --merge docs/index.yaml sync - mv -f sync/* docs - rm -rf sync - echo "Pushing new helm release" - git add -A - git commit -m "Helm Release: develop" - git push - - trigger-deploy-to-qa-env: - needs: [release-helm-chart] - 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 }} - with: - repository: budibase/budibase-deploys - event: budicloud-qa-deploy - github_pat: ${{ secrets.GH_ACCESS_TOKEN }} diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml index b4991cbfbe..3af3a751ad 100644 --- a/.github/workflows/release-master.yml +++ b/.github/workflows/release-master.yml @@ -110,19 +110,13 @@ jobs: git commit -m "Helm Release: ${{ env.RELEASE_VERSION }}" git push - deploy-to-legacy-preprod-env: - needs: [release-images] - uses: ./.github/workflows/deploy-preprod.yml - secrets: inherit - # Trigger deploy to new EKS preprod environment - trigger-deploy-to-preprod-env: + trigger-deploy-to-qa-env: needs: [release-helm-chart] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - - name: Get the latest budibase release version + - name: Get the current budibase release version id: version run: | release_version=$(cat lerna.json | jq -r '.version') @@ -133,5 +127,5 @@ jobs: PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }} with: repository: budibase/budibase-deploys - event: budicloud-preprod-deploy + event: budicloud-qa-deploy github_pat: ${{ secrets.GH_ACCESS_TOKEN }} diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml index f87d561db9..49e3473e63 100644 --- a/.github/workflows/stale_bot.yml +++ b/.github/workflows/stale_bot.yml @@ -2,7 +2,7 @@ name: Close stale issues and PRs # https://github.com/actions/stale on: workflow_dispatch: schedule: - - cron: '*/30 * * * *' # Every 30 mins + - cron: "*/30 * * * *" # Every 30 mins jobs: stale: @@ -10,20 +10,37 @@ jobs: steps: - uses: actions/stale@v8 with: - # stale rules - days-before-stale: 60 + operations-per-run: 1 + # stale rules for PRs days-before-pr-stale: 7 stale-issue-label: stale - stale-issue-message: "This issue has been automatically marked as stale because it has not had any activity for 60 days." - - # close rules - # days after being marked as stale to close - days-before-close: 30 - close-issue-label: closed-stale - close-issue-message: This issue has been automatically closed it has not had any activity in 90 days." - days-before-pr-close: 7 - - # exemptions exempt-pr-labels: pinned,security,roadmap + days-before-pr-close: 7 + - uses: actions/stale@v8 + with: + operations-per-run: 3 + # stale rules for high priority bugs + days-before-stale: 30 + only-issue-labels: bug,High priority + stale-issue-label: warn + + - uses: actions/stale@v8 + with: + operations-per-run: 3 + # stale rules for medium priority bugs + days-before-stale: 90 + only-issue-labels: bug,Medium priority + stale-issue-label: warn + + - uses: actions/stale@v8 + with: + operations-per-run: 3 + # stale rules for all bugs + days-before-stale: 180 + stale-issue-label: stale + only-issue-labels: bug + stale-issue-message: "This issue has been automatically marked as stale because it has not had any activity for six months." + + days-before-close: 30 diff --git a/.github/workflows/tag-prerelease.yml b/.github/workflows/tag-prerelease.yml deleted file mode 100644 index f6446c55f5..0000000000 --- a/.github/workflows/tag-prerelease.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Tag prerelease -concurrency: - group: tag-prerelease - cancel-in-progress: false - -on: - push: - branches: - - develop - paths: - - ".aws/**" - - ".github/**" - - "charts/**" - - "packages/**" - - "scripts/**" - - "package.json" - - "yarn.lock" - workflow_dispatch: - -jobs: - tag-prerelease: - runs-on: ubuntu-latest - - steps: - - name: Fail if branch is not develop - if: github.ref != 'refs/heads/develop' - run: | - echo "Ref is not develop, you must run this job from develop." - exit 1 - - uses: actions/checkout@v2 - with: - submodules: true - token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - - 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 "<>" - ./versionCommit.sh prerelease diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index 191c3ad9ef..eaf71ae61a 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -4,17 +4,6 @@ concurrency: cancel-in-progress: false on: - push: - branches: - - master - paths: - - ".aws/**" - - ".github/**" - - "charts/**" - - "packages/**" - - "scripts/**" - - "package.json" - - "yarn.lock" workflow_dispatch: inputs: versioning: diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 3a32075a33..77afd9453b 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -138,6 +138,8 @@ To develop the Budibase platform you'll need [Docker](https://www.docker.com/) a `yarn setup` will check that all necessary components are installed and setup the repo for usage. +If you have access to the `@budibase/pro` submodule then please follow the Pro section of this guide before running the above command. + ##### Manual method The following commands can be executed to manually get Budibase up and running (assuming Docker/Docker Compose has been installed). @@ -146,6 +148,8 @@ The following commands can be executed to manually get Budibase up and running ( `yarn build` will build all budibase packages. +If you have access to the `@budibase/pro` submodule then please follow the Pro section of this guide before running the above commands. + #### 4. Running To run the budibase server and builder in dev mode (i.e. with live reloading): diff --git a/lerna.json b/lerna.json index 8764e90ee7..0dd6abdc46 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.11.32", + "version": "2.11.34", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/src/plugin/utils.ts b/packages/backend-core/src/plugin/utils.ts index f73ded0659..8974a9f5a2 100644 --- a/packages/backend-core/src/plugin/utils.ts +++ b/packages/backend-core/src/plugin/utils.ts @@ -6,6 +6,7 @@ import { AutomationStepIdArray, AutomationIOType, AutomationCustomIOType, + DatasourceFeature, } from "@budibase/types" import joi from "joi" @@ -67,9 +68,27 @@ function validateDatasource(schema: any) { version: joi.string().optional(), schema: joi.object({ docs: joi.string(), + plus: joi.boolean().optional(), + isSQL: joi.boolean().optional(), + auth: joi + .object({ + type: joi.string().required(), + }) + .optional(), + features: joi + .object( + Object.fromEntries( + Object.values(DatasourceFeature).map(key => [ + key, + joi.boolean().optional(), + ]) + ) + ) + .optional(), + relationships: joi.boolean().optional(), + description: joi.string().required(), friendlyName: joi.string().required(), type: joi.string().allow(...DATASOURCE_TYPES), - description: joi.string().required(), datasource: joi.object().pattern(joi.string(), fieldValidator).required(), query: joi .object() diff --git a/packages/backend-core/src/users/users.ts b/packages/backend-core/src/users/users.ts index a7e1389920..b087a6b538 100644 --- a/packages/backend-core/src/users/users.ts +++ b/packages/backend-core/src/users/users.ts @@ -14,13 +14,14 @@ import { } from "../db" import { BulkDocsResponse, + ContextUser, + SearchQuery, + SearchQueryOperators, SearchUsersRequest, User, - ContextUser, } from "@budibase/types" -import { getGlobalDB } from "../context" import * as context from "../context" -import { user as userCache } from "../cache" +import { getGlobalDB } from "../context" type GetOpts = { cleanup?: boolean } @@ -39,6 +40,31 @@ function removeUserPassword(users: User | User[]) { return users } +export const isSupportedUserSearch = (query: SearchQuery) => { + const allowed = [ + { op: SearchQueryOperators.STRING, key: "email" }, + { op: SearchQueryOperators.EQUAL, key: "_id" }, + ] + for (let [key, operation] of Object.entries(query)) { + if (typeof operation !== "object") { + return false + } + const fields = Object.keys(operation || {}) + // this filter doesn't contain options - ignore + if (fields.length === 0) { + continue + } + const allowedOperation = allowed.find( + allow => + allow.op === key && fields.length === 1 && fields[0] === allow.key + ) + if (!allowedOperation) { + return false + } + } + return true +} + export const bulkGetGlobalUsersById = async ( userIds: string[], opts?: GetOpts @@ -211,8 +237,8 @@ export const searchGlobalUsersByEmail = async ( const PAGE_LIMIT = 8 export const paginatedUsers = async ({ - page, - email, + bookmark, + query, appId, }: SearchUsersRequest = {}) => { const db = getGlobalDB() @@ -222,18 +248,20 @@ export const paginatedUsers = async ({ limit: PAGE_LIMIT + 1, } // add a startkey if the page was specified (anchor) - if (page) { - opts.startkey = page + if (bookmark) { + opts.startkey = bookmark } // property specifies what to use for the page/anchor let userList: User[], property = "_id", getKey - if (appId) { + if (query?.equal?._id) { + userList = [await getById(query.equal._id)] + } else if (appId) { userList = await searchGlobalUsersByApp(appId, opts) getKey = (doc: any) => getGlobalUserByAppPage(appId, doc) - } else if (email) { - userList = await searchGlobalUsersByEmail(email, opts) + } else if (query?.string?.email) { + userList = await searchGlobalUsersByEmail(query?.string?.email, opts) property = "email" } else { // no search, query allDocs diff --git a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte index 9e42dfecd9..abec380b46 100644 --- a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte +++ b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte @@ -13,6 +13,8 @@ import { Helpers } from "@budibase/bbui" import { RelationshipErrorChecker } from "./relationshipErrors" import { onMount } from "svelte" + import RelationshipSelector from "components/common/RelationshipSelector.svelte" + import { PrettyRelationshipDefinitions } from "constants/backend" export let save export let datasource @@ -22,16 +24,21 @@ export let selectedFromTable export let close - const relationshipTypes = [ - { - label: "One to Many", - value: RelationshipType.MANY_TO_ONE, + let relationshipMap = { + [RelationshipType.MANY_TO_MANY]: { + part1: PrettyRelationshipDefinitions.MANY, + part2: PrettyRelationshipDefinitions.MANY, }, - { - label: "Many to Many", - value: RelationshipType.MANY_TO_MANY, + [RelationshipType.MANY_TO_ONE]: { + part1: PrettyRelationshipDefinitions.ONE, + part2: PrettyRelationshipDefinitions.MANY, }, - ] + } + let relationshipOpts1 = Object.values(PrettyRelationshipDefinitions) + let relationshipOpts2 = Object.values(PrettyRelationshipDefinitions) + + let relationshipPart1 = PrettyRelationshipDefinitions.MANY + let relationshipPart2 = PrettyRelationshipDefinitions.ONE let originalFromColumnName = toRelationship.name, originalToColumnName = fromRelationship.name @@ -49,14 +56,32 @@ ) let errors = {} let fromPrimary, fromForeign, fromColumn, toColumn - let fromId, toId, throughId, throughToKey, throughFromKey + + let throughId, throughToKey, throughFromKey let isManyToMany, isManyToOne, relationshipType let hasValidated = false + $: fromId = null + $: toId = null + $: tableOptions = plusTables.map(table => ({ label: table.name, value: table._id, + name: table.name, + _id: table._id, })) + + $: { + // Determine the relationship type based on the selected values of both parts + relationshipType = Object.entries(relationshipMap).find( + ([_, parts]) => + parts.part1 === relationshipPart1 && parts.part2 === relationshipPart2 + )?.[0] + + changed(() => { + hasValidated = false + }) + } $: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet(relationshipType) $: isManyToMany = relationshipType === RelationshipType.MANY_TO_MANY @@ -338,33 +363,34 @@ onConfirm={saveRelationship} disabled={!valid} > - - changed(() => { - const table = plusTables.find(tbl => tbl._id === e.detail) - fromColumn = table?.name || "" - fromPrimary = table?.primary?.[0] - })} - /> - {/if} + + + changed(() => { + const table = plusTables.find(tbl => tbl._id === e.detail) + fromColumn = table?.name || "" + fromPrimary = table?.primary?.[0] + })} + secondaryTableChanged={e => + changed(() => { + const table = plusTables.find(tbl => tbl._id === e.detail) + toColumn = table.name || "" + fromForeign = null + })} + /> + {#if isManyToOne && fromId} - changed(() => { - const table = plusTables.find(tbl => tbl._id === e.detail) - toColumn = table.name || "" - fromForeign = null - })} - /> {#if isManyToMany} table.name} getOptionValue={table => table._id} bind:value={relationshipTableIdPrimary} + on:change={primaryTableChanged} + bind:error={errors.fromTable} /> @@ -46,20 +52,24 @@ +{#if editableColumn} + +{/if}