Merge remote-tracking branch 'origin/develop' into feature/user-side-panel-ux-updates

This commit is contained in:
Dean 2023-05-11 12:17:45 +01:00
commit 4b45f1a9f0
107 changed files with 1232 additions and 702 deletions

View File

@ -1,36 +1,25 @@
name: Budibase CI name: Budibase CI
on: on:
# Trigger the workflow on push or pull request, # Trigger the workflow on push or pull request,
# but only for the master branch # but only for the master branch
push: push:
branches:
- master
- develop
pull_request:
branches: branches:
- master - master
- develop - develop
workflow_dispatch: pull_request:
branches:
- master
- develop
workflow_dispatch:
env: env:
BRANCH: ${{ github.event.pull_request.head.ref }} BRANCH: ${{ github.event.pull_request.head.ref }}
BASE_BRANCH: ${{ github.event.pull_request.base.ref}} BASE_BRANCH: ${{ github.event.pull_request.base.ref}}
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }} PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: yarn
- run: yarn lint
build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -38,8 +27,20 @@ jobs:
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 14.x node-version: 14.x
- name: Install Pro - run: yarn
run: yarn install:pro $BRANCH $BASE_BRANCH - run: yarn lint
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: yarn - run: yarn
- run: yarn bootstrap - run: yarn bootstrap
- run: yarn build - run: yarn build
@ -48,16 +49,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Use Node.js 14.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 14.x node-version: 14.x
- name: Install Pro
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn - run: yarn
- run: yarn bootstrap - run: yarn bootstrap
- run: yarn build - run: yarn build
- run: yarn test - run: yarn test --ignore=@budibase/pro
- uses: codecov/codecov-action@v3 - uses: codecov/codecov-action@v3
with: with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
@ -68,26 +70,29 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Use Node.js 14.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 14.x node-version: 14.x
- name: Install Pro
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn - run: yarn
- run: yarn bootstrap - run: yarn bootstrap
- run: yarn test:pro - run: yarn build --scope=@budibase/types --scope=@budibase/shared-core
- run: yarn test --scope=@budibase/pro
integration-test: integration-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Use Node.js 14.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 14.x node-version: 14.x
- name: Install Pro
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn && yarn bootstrap && yarn build - run: yarn && yarn bootstrap && yarn build
- run: | - run: |
cd qa-core cd qa-core
@ -96,3 +101,24 @@ jobs:
env: env:
BB_ADMIN_USER_EMAIL: admin BB_ADMIN_USER_EMAIL: admin
BB_ADMIN_USER_PASSWORD: admin BB_ADMIN_USER_PASSWORD: admin
check-pro-submodule:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
fetch-depth: 0
- name: Check submodule
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
else
echo "All good, the submodule had been merged!"
fi

View File

@ -1,21 +1,11 @@
name: Budibase Prerelease name: Budibase Prerelease
concurrency: release-prerelease concurrency: release-prerelease
on: on:
push: push:
branches: tags:
- develop - v*-alpha.*
paths: workflow_dispatch:
- '.aws/**'
- '.github/**'
- 'charts/**'
- 'packages/**'
- 'scripts/**'
- 'package.json'
- 'yarn.lock'
- 'package.json'
- 'yarn.lock'
workflow_dispatch:
env: env:
# Posthog token used by ui at build time # Posthog token used by ui at build time
@ -24,43 +14,60 @@ env:
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
FEATURE_PREVIEW_URL: https://budirelease.live FEATURE_PREVIEW_URL: https://budirelease.live
jobs: jobs:
release-images: release-images:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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 - 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 - uses: actions/setup-node@v1
with: with:
node-version: 14.x node-version: 14.x
- name: Install Pro - run: yarn
run: yarn install:pro develop - name: Update versions
run: |
- run: yarn version=$(cat lerna.json \
- run: yarn bootstrap | grep version \
| head -1 \
| awk -F: '{gsub(/"/,"",$2);gsub(/[[:space:]]*/,"",$2); print $2}' \
| sed 's/[",]//g')
echo "Setting version $version"
yarn lerna exec "yarn version --no-git-tag-version --new-version=$version"
echo "Updating dependencies"
node scripts/syncLocalDependencies.js $version
echo "Syncing yarn workspace"
yarn
- run: yarn build - run: yarn build
- run: yarn build:sdk - run: yarn build:sdk
# - run: yarn test
- name: Publish budibase packages to NPM - name: Publish budibase packages to NPM
env: env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: | run: |
# setup the username and email. # setup the username and email.
git config --global user.name "Budibase Staging Release Bot" git config --global user.name "Budibase Staging Release Bot"
git config --global user.email "<>" 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 echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
yarn release:develop yarn release:develop
- name: Build/release Docker images - name: Build/release Docker images
run: | run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
yarn build:docker:develop yarn build:docker:develop
env: env:
@ -84,7 +91,7 @@ jobs:
git config user.name "Budibase Helm Bot" git config user.name "Budibase Helm Bot"
git config user.email "<>" git config user.email "<>"
git reset --hard git reset --hard
git pull git fetch
mkdir sync mkdir sync
echo "Packaging chart to sync dir" echo "Packaging chart to sync dir"
helm package charts/budibase --version 0.0.0-develop --app-version develop --destination sync helm package charts/budibase --version 0.0.0-develop --app-version develop --destination sync

View File

@ -2,57 +2,60 @@ name: Budibase Release
concurrency: release concurrency: release
on: on:
push: push:
branches: tags:
- master - "v[0-9]+.[0-9]+.[0-9]+"
paths: # Exclude all pre-releases
- '.aws/**' - "!v*[0-9]+.[0-9]+.[0-9]+-*"
- '.github/**' workflow_dispatch:
- 'charts/**' inputs:
- 'packages/**' tags:
- 'scripts/**' description: "Release tag"
- 'package.json' required: true
- 'yarn.lock' type: boolean
- 'package.json'
- 'yarn.lock'
workflow_dispatch:
inputs:
versioning:
type: choice
description: "Versioning type: patch, minor, major"
default: patch
options:
- patch
- minor
- major
required: true
env: env:
# Posthog token used by ui at build time # Posthog token used by ui at build time
POSTHOG_TOKEN: phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU POSTHOG_TOKEN: phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }} INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }} PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
jobs: jobs:
release-images: release-images:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Fail if branch is not master
if: github.ref != 'refs/heads/master'
run: |
echo "Ref is not master, you must run this job from master."
exit 1
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
fetch-depth: 0
- name: Fail if branch is not master
if: github.ref != 'refs/heads/master'
run: |
echo "Ref is not master, you must run this job from master."
// Change to "exit 1" when merged. Left to 0 to not fail all the pipelines and not to cause noise
exit 0
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 14.x node-version: 14.x
- name: Install Pro
run: yarn install:pro master
- run: yarn - run: yarn
- run: yarn bootstrap - name: Update versions
run: |
version=$(cat lerna.json \
| grep version \
| head -1 \
| awk -F: '{gsub(/"/,"",$2);gsub(/[[:space:]]*/,"",$2); print $2}' \
| sed 's/[",]//g')
echo "Setting version $version"
yarn lerna exec "yarn version --no-git-tag-version --new-version=$version"
echo "Updating dependencies"
node scripts/syncLocalDependencies.js $version
echo "Syncing yarn workspace"
yarn
- run: yarn lint - run: yarn lint
- run: yarn build - run: yarn build
- run: yarn build:sdk - run: yarn build:sdk
@ -65,15 +68,17 @@ jobs:
# setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default # setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default
git config --global user.name "Budibase Release Bot" git config --global user.name "Budibase Release Bot"
git config --global user.email "<>" 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 echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
yarn release yarn release
- name: 'Get Previous tag' - name: "Get Previous tag"
id: previoustag id: previoustag
uses: "WyriHaximus/github-action-get-previous-tag@v1" uses: "WyriHaximus/github-action-get-previous-tag@v1"
- name: Build/release Docker images - name: Build/release Docker images
run: | run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
yarn build:docker yarn build:docker
env: env:
@ -103,7 +108,7 @@ jobs:
git config user.name "Budibase Helm Bot" git config user.name "Budibase Helm Bot"
git config user.email "<>" git config user.email "<>"
git reset --hard git reset --hard
git pull git fetch
mkdir sync mkdir sync
echo "Packaging chart to sync dir" echo "Packaging chart to sync dir"
helm package charts/budibase --version 0.0.0-master --app-version v"$RELEASE_VERSION" --destination sync helm package charts/budibase --version 0.0.0-master --app-version v"$RELEASE_VERSION" --destination sync

39
.github/workflows/tag-prerelease.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: Tag prerelease
concurrency: release-prerelease
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: yarn
- name: Tag prerelease
run: |
# setup the username and email.
git config --global user.name "Budibase Staging Release Bot"
git config --global user.email "<>"
./scripts/versionCommit.sh prerelease

49
.github/workflows/tag-release.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Tag release
concurrency: release-prerelease
on:
push:
branches:
- master
paths:
- ".aws/**"
- ".github/**"
- "charts/**"
- "packages/**"
- "scripts/**"
- "package.json"
- "yarn.lock"
workflow_dispatch:
inputs:
versioning:
type: choice
description: "Versioning type: patch, minor, major"
default: patch
options:
- patch
- minor
- major
required: true
jobs:
tag-prerelease:
runs-on: ubuntu-latest
steps:
- name: Fail if branch is not master
if: github.ref != 'refs/heads/master'
run: |
echo "Ref is not master, you must run this job from master."
exit 1
- uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- run: yarn
- name: Tag prerelease
run: |
# setup the username and email.
git config --global user.name "Budibase Staging Release Bot"
git config --global user.email "<>"
./scripts/versionCommit.sh ${{ github.event.inputs.versioning }}

3
.gitmodules vendored
View File

@ -0,0 +1,3 @@
[submodule "packages/pro"]
path = packages/pro
url = git@github.com:Budibase/budibase-pro.git

4
.husky/post-checkout Executable file
View File

@ -0,0 +1,4 @@
# .husky/post-checkout
# ...
git config submodule.recurse true

View File

@ -1,13 +1,17 @@
## Dev Environment on Debian 11 ## Dev Environment on Debian 11
### Install NVM & Node 14 ### Install NVM & Node 14
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
Install NVM Install NVM
``` ```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
``` ```
Install Node 14 Install Node 14
``` ```
nvm install 14 nvm install 14
``` ```
@ -17,13 +21,16 @@ nvm install 14
``` ```
npm install -g yarn jest lerna npm install -g yarn jest lerna
``` ```
### Install Docker and Docker Compose ### Install Docker and Docker Compose
``` ```
apt install docker.io apt install docker.io
pip3 install docker-compose pip3 install docker-compose
``` ```
### Clone the repo ### Clone the repo
``` ```
git clone https://github.com/Budibase/budibase.git git clone https://github.com/Budibase/budibase.git
``` ```
@ -44,10 +51,13 @@ This setup process was tested on Debian 11 (bullseye) with version numbers show
cd budibase cd budibase
yarn setup yarn setup
``` ```
The yarn setup command runs several build steps i.e. The yarn setup command runs several build steps i.e.
``` ```
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
``` ```
So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose.
The dev version will be available on port 10000 i.e. The dev version will be available on port 10000 i.e.
@ -55,6 +65,7 @@ The dev version will be available on port 10000 i.e.
http://127.0.0.1:10000/builder/admin http://127.0.0.1:10000/builder/admin
### File descriptor issues with Vite and Chrome in Linux ### File descriptor issues with Vite and Chrome in Linux
If your dev environment stalls forever, with some network requests stuck in flight, it's likely that Chrome is trying to open more file descriptors than your system allows. If your dev environment stalls forever, with some network requests stuck in flight, it's likely that Chrome is trying to open more file descriptors than your system allows.
To fix this, apply the following tweaks. To fix this, apply the following tweaks.
@ -62,4 +73,4 @@ Debian based distros:
Add `* - nofile 65536` to `/etc/security/limits.conf`. Add `* - nofile 65536` to `/etc/security/limits.conf`.
Arch: Arch:
Add `DefaultLimitNOFILE=65536` to `/etc/systemd/system.conf`. Add `DefaultLimitNOFILE=65536` to `/etc/systemd/system.conf`.

View File

@ -4,14 +4,14 @@
Install instructions [here](https://brew.sh/) Install instructions [here](https://brew.sh/)
| **NOTE**: If you are working on a M1 Apple Silicon which is running Z shell, you could need to add | **NOTE**: If you are working on a M1 Apple Silicon which is running Z shell, you could need to add
`eval $(/opt/homebrew/bin/brew shellenv)` line to your `.zshrc`. This will make your zsh to find the apps you install `eval $(/opt/homebrew/bin/brew shellenv)` line to your `.zshrc`. This will make your zsh to find the apps you install
through brew. through brew.
### Install Node ### Install Node
Budibase requires a recent version of node 14: Budibase requires a recent version of node 14:
``` ```
brew install node npm brew install node npm
node -v node -v
@ -22,12 +22,15 @@ node -v
``` ```
npm install -g yarn jest lerna npm install -g yarn jest lerna
``` ```
### Install Docker and Docker Compose ### Install Docker and Docker Compose
``` ```
brew install docker docker-compose brew install docker docker-compose
``` ```
### Clone the repo ### Clone the repo
``` ```
git clone https://github.com/Budibase/budibase.git git clone https://github.com/Budibase/budibase.git
``` ```
@ -48,10 +51,13 @@ This setup process was tested on Mac OSX 12 (Monterey) with version numbers show
cd budibase cd budibase
yarn setup yarn setup
``` ```
The yarn setup command runs several build steps i.e. The yarn setup command runs several build steps i.e.
``` ```
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
``` ```
So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose.
The dev version will be available on port 10000 i.e. The dev version will be available on port 10000 i.e.

View File

@ -1,13 +1,15 @@
## Dev Environment on Windows 10/11 (WSL2) ## Dev Environment on Windows 10/11 (WSL2)
### Install WSL with Ubuntu LTS ### Install WSL with Ubuntu LTS
Enable WSL 2 on Windows 10/11 for docker support. Enable WSL 2 on Windows 10/11 for docker support.
``` ```
wsl --set-default-version 2 wsl --set-default-version 2
``` ```
Install Ubuntu LTS. Install Ubuntu LTS.
``` ```
wsl --install Ubuntu wsl --install Ubuntu
``` ```
@ -16,6 +18,7 @@ Or follow the instruction here:
https://learn.microsoft.com/en-us/windows/wsl/install https://learn.microsoft.com/en-us/windows/wsl/install
### Install Docker in windows ### Install Docker in windows
Download the installer from docker and install it. Download the installer from docker and install it.
Check this url for more detailed instructions: Check this url for more detailed instructions:
@ -24,18 +27,21 @@ https://docs.docker.com/desktop/install/windows-install/
You should follow the next steps from within the Ubuntu terminal. You should follow the next steps from within the Ubuntu terminal.
### Install NVM & Node 14 ### Install NVM & Node 14
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
Install NVM Install NVM
``` ```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
``` ```
Install Node 14 Install Node 14
``` ```
nvm install 14 nvm install 14
``` ```
### Install npm requirements ### Install npm requirements
``` ```
@ -43,6 +49,7 @@ npm install -g yarn jest lerna
``` ```
### Clone the repo ### Clone the repo
``` ```
git clone https://github.com/Budibase/budibase.git git clone https://github.com/Budibase/budibase.git
``` ```
@ -63,10 +70,13 @@ This setup process was tested on Windows 11 with version numbers show below. You
cd budibase cd budibase
yarn setup yarn setup
``` ```
The yarn setup command runs several build steps i.e. The yarn setup command runs several build steps i.e.
``` ```
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
``` ```
So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose.
The dev version will be available on port 10000 i.e. The dev version will be available on port 10000 i.e.
@ -74,8 +84,9 @@ The dev version will be available on port 10000 i.e.
http://127.0.0.1:10000/builder/admin http://127.0.0.1:10000/builder/admin
### Working with the code ### Working with the code
Here are the instructions to work on the application from within Visual Studio Code (in Windows) through the WSL. All the commands and files are within the Ubuntu system and it should run as if you were working on a Linux machine. Here are the instructions to work on the application from within Visual Studio Code (in Windows) through the WSL. All the commands and files are within the Ubuntu system and it should run as if you were working on a Linux machine.
https://code.visualstudio.com/docs/remote/wsl https://code.visualstudio.com/docs/remote/wsl
Note you will be able to run the application from within the WSL terminal and you will be able to access the application from the a browser in Windows. Note you will be able to run the application from within the WSL terminal and you will be able to access the application from the a browser in Windows.

View File

@ -5,8 +5,11 @@ ENV COUCHDB_PASSWORD admin
EXPOSE 5984 EXPOSE 5984
RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common wget unzip curl && \ RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common wget unzip curl && \
apt-add-repository 'deb http://security.debian.org/debian-security stretch/updates main' && \ wget -qO - https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | apt-key add - && \
apt-get update && apt-get install -y --no-install-recommends openjdk-8-jre && \ apt-add-repository 'deb http://security.debian.org/debian-security bullseye-security/updates main' && \
apt-add-repository 'deb http://archive.debian.org/debian stretch-backports main' && \
apt-add-repository --yes https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/ && \
apt-get update && apt-get install -y --no-install-recommends adoptopenjdk-8-hotspot && \
rm -rf /var/lib/apt/lists/ rm -rf /var/lib/apt/lists/
# setup clouseau # setup clouseau

View File

@ -22,7 +22,7 @@ FROM budibase/couchdb
ARG TARGETARCH ARG TARGETARCH
ENV TARGETARCH $TARGETARCH ENV TARGETARCH $TARGETARCH
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service) #TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
# e.g. docker build --build-arg TARGETBUILD=aas .... # e.g. docker build --build-arg TARGETBUILD=aas ....
ARG TARGETBUILD=single ARG TARGETBUILD=single
ENV TARGETBUILD $TARGETBUILD ENV TARGETBUILD $TARGETBUILD
@ -32,7 +32,7 @@ COPY --from=build /worker /worker
# install base dependencies # install base dependencies
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server && \ apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server && \
apt-add-repository 'deb http://security.debian.org/debian-security stretch/updates main' && \ apt-add-repository 'deb http://security.debian.org/debian-security bullseye-security/updates main' && \
apt-get update apt-get update
# install other dependencies, nodejs, oracle requirements, jdk8, redis, nginx # install other dependencies, nodejs, oracle requirements, jdk8, redis, nginx

View File

@ -1,8 +1,22 @@
{ {
"version": "2.5.10-alpha.1", "version": "2.6.8-alpha.7",
"npmClient": "yarn", "npmClient": "yarn",
"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"
],
"useWorkspaces": true, "useWorkspaces": true,
"packages": ["packages/*"],
"command": { "command": {
"publish": { "publish": {
"ignoreChanges": [ "ignoreChanges": [
@ -17,4 +31,4 @@
"loadEnvFiles": false "loadEnvFiles": false
} }
} }
} }

View File

@ -8,7 +8,7 @@
"eslint": "^7.28.0", "eslint": "^7.28.0",
"eslint-plugin-cypress": "^2.11.3", "eslint-plugin-cypress": "^2.11.3",
"eslint-plugin-svelte3": "^3.2.0", "eslint-plugin-svelte3": "^3.2.0",
"husky": "^7.0.1", "husky": "^8.0.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"kill-port": "^1.6.1", "kill-port": "^1.6.1",
"lerna": "^6.6.1", "lerna": "^6.6.1",
@ -17,22 +17,22 @@
"prettier-plugin-svelte": "^2.3.0", "prettier-plugin-svelte": "^2.3.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup-plugin-replace": "^2.2.0", "rollup-plugin-replace": "^2.2.0",
"semver": "^7.5.0",
"svelte": "^3.38.2", "svelte": "^3.38.2",
"typescript": "4.7.3" "typescript": "4.7.3"
}, },
"scripts": { "scripts": {
"setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev", "preinstall": "node scripts/syncProPackage.js",
"bootstrap": "lerna link && ./scripts/link-dependencies.sh", "setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
"bootstrap": "./scripts/bootstrap.sh && lerna link && ./scripts/link-dependencies.sh",
"build": "lerna run --stream build", "build": "lerna run --stream build",
"build:dev": "lerna run --stream prebuild && tsc --build --watch --preserveWatchOutput", "build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
"backend:bootstrap": "./scripts/scopeBackend.sh && yarn run bootstrap", "backend:bootstrap": "./scripts/scopeBackend.sh && yarn run bootstrap",
"backend:build": "./scripts/scopeBackend.sh 'lerna run --stream build'", "backend:build": "./scripts/scopeBackend.sh 'lerna run --stream build'",
"build:sdk": "lerna run --stream build:sdk", "build:sdk": "lerna run --stream build:sdk",
"deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular", "deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular",
"release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro", "release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish --no-git-tag-version --no-push --no-git-reset",
"release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop --exact && yarn release:pro:develop", "release:develop": "lerna publish from-package --yes --force-publish --dist-tag develop --exact --no-git-tag-version --no-push --no-git-reset",
"release:pro": "bash scripts/pro/release.sh",
"release:pro:develop": "bash scripts/pro/release.sh develop",
"restore": "yarn run clean && yarn run bootstrap && yarn run build", "restore": "yarn run clean && yarn run bootstrap && yarn run build",
"nuke": "yarn run nuke:packages && yarn run nuke:docker", "nuke": "yarn run nuke:packages && yarn run nuke:docker",
"nuke:packages": "yarn run restore", "nuke:packages": "yarn run restore",
@ -46,7 +46,6 @@
"dev:server": "yarn run kill-server && lerna run --stream --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server", "dev:server": "yarn run kill-server && lerna run --stream --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server",
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built", "dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built",
"test": "lerna run --stream test --stream", "test": "lerna run --stream test --stream",
"test:pro": "bash scripts/pro/test.sh",
"lint:eslint": "eslint packages && eslint qa-core", "lint:eslint": "eslint packages && eslint qa-core",
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"", "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",
"lint": "yarn run lint:eslint && yarn run lint:prettier", "lint": "yarn run lint:eslint && yarn run lint:prettier",
@ -82,12 +81,25 @@
"mode:account": "yarn mode:cloud && yarn env:account:enable", "mode:account": "yarn mode:cloud && yarn env:account:enable",
"security:audit": "node scripts/audit.js", "security:audit": "node scripts/audit.js",
"postinstall": "husky install", "postinstall": "husky install",
"install:pro": "bash scripts/pro/install.sh", "dep:clean": "yarn clean -y && yarn bootstrap",
"dep:clean": "yarn clean && yarn bootstrap" "submodules:load": "git submodule init && git submodule update && yarn && yarn bootstrap",
"submodules:unload": "git submodule deinit --all && yarn && yarn bootstrap"
}, },
"workspaces": { "workspaces": {
"packages": [ "packages": [
"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"
] ]
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -15,8 +15,6 @@
"prebuild": "rimraf dist/", "prebuild": "rimraf dist/",
"prepack": "cp package.json dist", "prepack": "cp package.json dist",
"build": "tsc -p tsconfig.build.json", "build": "tsc -p tsconfig.build.json",
"build:pro": "../../scripts/pro/build.sh",
"postbuild": "yarn run build:pro",
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput", "build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
"test": "bash scripts/test.sh", "test": "bash scripts/test.sh",
"test:watch": "jest --watchAll" "test:watch": "jest --watchAll"
@ -24,7 +22,7 @@
"dependencies": { "dependencies": {
"@budibase/nano": "10.1.2", "@budibase/nano": "10.1.2",
"@budibase/pouchdb-replication-stream": "1.2.10", "@budibase/pouchdb-replication-stream": "1.2.10",
"@budibase/types": "2.5.10-alpha.1", "@budibase/types": "0.0.1",
"@shopify/jest-koa-mocks": "5.0.1", "@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0", "aws-cloudfront-sign": "2.2.0",

View File

@ -96,6 +96,7 @@ const environment = {
SALT_ROUNDS: process.env.SALT_ROUNDS, SALT_ROUNDS: process.env.SALT_ROUNDS,
REDIS_URL: process.env.REDIS_URL || "localhost:6379", REDIS_URL: process.env.REDIS_URL || "localhost:6379",
REDIS_PASSWORD: process.env.REDIS_PASSWORD || "budibase", REDIS_PASSWORD: process.env.REDIS_PASSWORD || "budibase",
REDIS_CLUSTERED: process.env.REDIS_CLUSTERED,
MOCK_REDIS: process.env.MOCK_REDIS, MOCK_REDIS: process.env.MOCK_REDIS,
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,

View File

@ -40,6 +40,12 @@ function logging(queue: Queue, jobQueue: JobQueue) {
case JobQueue.APP_BACKUP: case JobQueue.APP_BACKUP:
eventType = "app-backup-event" eventType = "app-backup-event"
break break
case JobQueue.AUDIT_LOG:
eventType = "audit-log-event"
break
case JobQueue.SYSTEM_EVENT_QUEUE:
eventType = "system-event"
break
} }
if (process.env.NODE_DEBUG?.includes("bull")) { if (process.env.NODE_DEBUG?.includes("bull")) {
queue queue

View File

@ -12,7 +12,7 @@ import * as timers from "../timers"
const RETRY_PERIOD_MS = 2000 const RETRY_PERIOD_MS = 2000
const STARTUP_TIMEOUT_MS = 5000 const STARTUP_TIMEOUT_MS = 5000
const CLUSTERED = false const CLUSTERED = env.REDIS_CLUSTERED
const DEFAULT_SELECT_DB = SelectableDatabase.DEFAULT const DEFAULT_SELECT_DB = SelectableDatabase.DEFAULT
// for testing just generate the client once // for testing just generate the client once
@ -81,7 +81,7 @@ function init(selectDb = DEFAULT_SELECT_DB) {
if (client) { if (client) {
client.disconnect() client.disconnect()
} }
const { redisProtocolUrl, opts, host, port } = getRedisOptions(CLUSTERED) const { redisProtocolUrl, opts, host, port } = getRedisOptions()
if (CLUSTERED) { if (CLUSTERED) {
client = new Redis.Cluster([{ host, port }], opts) client = new Redis.Cluster([{ host, port }], opts)

View File

@ -85,7 +85,7 @@ export const doWithLock = async <T>(
opts: LockOptions, opts: LockOptions,
task: () => Promise<T> task: () => Promise<T>
): Promise<RedlockExecution<T>> => { ): Promise<RedlockExecution<T>> => {
const redlock = await getClient(opts.type) const redlock = await getClient(opts.type, opts.customOptions)
let lock let lock
try { try {
// determine lock name // determine lock name

View File

@ -57,7 +57,7 @@ export enum SelectableDatabase {
UNUSED_14 = 15, UNUSED_14 = 15,
} }
export function getRedisOptions(clustered = false) { export function getRedisOptions() {
let password = env.REDIS_PASSWORD let password = env.REDIS_PASSWORD
let url: string[] | string = env.REDIS_URL.split("//") let url: string[] | string = env.REDIS_URL.split("//")
// get rid of the protocol // get rid of the protocol
@ -83,7 +83,7 @@ export function getRedisOptions(clustered = false) {
const opts: any = { const opts: any = {
connectTimeout: CONNECT_TIMEOUT_MS, connectTimeout: CONNECT_TIMEOUT_MS,
} }
if (clustered) { if (env.REDIS_CLUSTERED) {
opts.redisOptions = {} opts.redisOptions = {}
opts.redisOptions.tls = {} opts.redisOptions.tls = {}
opts.redisOptions.password = password opts.redisOptions.password = password

View File

@ -7,11 +7,6 @@
"@budibase/types": ["../types/src"] "@budibase/types": ["../types/src"]
} }
}, },
"references": [
{ "path": "../types" } "exclude": ["node_modules", "dist"]
], }
"exclude": [
"node_modules",
"dist",
]
}

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,8 +38,8 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1", "@adobe/spectrum-css-workflow-icons": "1.2.1",
"@budibase/shared-core": "2.5.10-alpha.1", "@budibase/shared-core": "0.0.1",
"@budibase/string-templates": "2.5.10-alpha.1", "@budibase/string-templates": "0.0.1",
"@spectrum-css/accordion": "3.0.24", "@spectrum-css/accordion": "3.0.24",
"@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "1.0.1", "@spectrum-css/actiongroup": "1.0.1",

View File

@ -1,4 +1,8 @@
const ignoredClasses = [".flatpickr-calendar", ".spectrum-Popover"] const ignoredClasses = [
".flatpickr-calendar",
".spectrum-Popover",
".download-js-link",
]
let clickHandlers = [] let clickHandlers = []
/** /**
@ -22,8 +26,8 @@ const handleClick = event => {
} }
// Ignore clicks for modals, unless the handler is registered from a modal // Ignore clicks for modals, unless the handler is registered from a modal
const sourceInModal = handler.anchor.closest(".spectrum-Modal") != null const sourceInModal = handler.anchor.closest(".spectrum-Underlay") != null
const clickInModal = event.target.closest(".spectrum-Modal") != null const clickInModal = event.target.closest(".spectrum-Underlay") != null
if (clickInModal && !sourceInModal) { if (clickInModal && !sourceInModal) {
return return
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"> <g id="Make-App-Icon-Circle" transform="translate(3757 -1767)"> <circle id="Ellipse_10" data-name="Ellipse 10" cx="256" cy="256" r="256" transform="translate(-3757 1767)" fill="#6d00cc"/> <path id="Path_141560" data-name="Path 141560" d="M244.78,14.544a7.187,7.187,0,0,0-7.186,7.192V213.927a7.19,7.19,0,0,0,7.186,7.192h52.063a7.187,7.187,0,0,0,7.186-7.192V21.736a7.183,7.183,0,0,0-7.186-7.192ZM92.066,17.083,5.77,188.795a7.191,7.191,0,0,0,3.192,9.654l46.514,23.379a7.184,7.184,0,0,0,9.654-3.2l86.3-171.711a7.184,7.184,0,0,0-3.2-9.654L101.719,13.886a7.2,7.2,0,0,0-9.654,3.2m72.592.614L127.731,204.876a7.189,7.189,0,0,0,5.632,8.442l51.028,10.306a7.2,7.2,0,0,0,8.481-5.665L229.8,30.786a7.19,7.19,0,0,0-5.637-8.442L173.133,12.038a7.391,7.391,0,0,0-1.427-.144,7.194,7.194,0,0,0-7.048,5.8" transform="translate(-3676.356 1905.425)" fill="#fff"/> </g> </svg>

After

Width:  |  Height:  |  Size: 951 B

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -58,10 +58,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "2.5.10-alpha.1", "@budibase/bbui": "0.0.1",
"@budibase/frontend-core": "2.5.10-alpha.1", "@budibase/frontend-core": "0.0.1",
"@budibase/shared-core": "2.5.10-alpha.1", "@budibase/shared-core": "0.0.1",
"@budibase/string-templates": "2.5.10-alpha.1", "@budibase/string-templates": "0.0.1",
"@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1", "@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1",

View File

@ -147,6 +147,9 @@ const automationActions = store => ({
testData, testData,
}) })
if (!result?.trigger && !result?.steps?.length) { if (!result?.trigger && !result?.steps?.length) {
if (result?.err?.code === "usage_limit_exceeded") {
throw "You have exceeded your automation quota"
}
throw "Something went wrong testing your automation" throw "Something went wrong testing your automation"
} }
store.update(state => { store.update(state => {

View File

@ -94,7 +94,7 @@
/> />
<span class="icon-spacing"> <span class="icon-spacing">
<Body size="XS"> <Body size="XS">
{idx.charAt(0).toUpperCase() + idx.slice(1)} {action.stepTitle || idx.charAt(0).toUpperCase() + idx.slice(1)}
</Body> </Body>
</span> </span>
</div> </div>

View File

@ -1,11 +1,11 @@
import DiscordLogo from "assets/discord.svg" import DiscordLogo from "assets/discord.svg"
import ZapierLogo from "assets/zapier.png" import ZapierLogo from "assets/zapier.png"
import IntegromatLogo from "assets/integromat.png" import MakeLogo from "assets/make.svg"
import SlackLogo from "assets/slack.svg" import SlackLogo from "assets/slack.svg"
export const externalActions = { export const externalActions = {
zapier: { name: "zapier", icon: ZapierLogo }, zapier: { name: "zapier", icon: ZapierLogo },
discord: { name: "discord", icon: DiscordLogo }, discord: { name: "discord", icon: DiscordLogo },
slack: { name: "slack", icon: SlackLogo }, slack: { name: "slack", icon: SlackLogo },
integromat: { name: "integromat", icon: IntegromatLogo }, integromat: { name: "integromat", icon: MakeLogo },
} }

View File

@ -52,7 +52,7 @@
await automationStore.actions.test($selectedAutomation, testData) await automationStore.actions.test($selectedAutomation, testData)
$automationStore.showTestPanel = true $automationStore.showTestPanel = true
} catch (error) { } catch (error) {
notifications.error("Error testing automation") notifications.error(error)
} }
} }
</script> </script>

View File

@ -39,7 +39,7 @@
{#if datasource} {#if datasource}
<div> <div>
<ActionButton icon="DataCorrelated" primary quiet on:click={modal.show}> <ActionButton icon="DataCorrelated" primary quiet on:click={modal.show}>
Define existing relationship Define relationship
</ActionButton> </ActionButton>
</div> </div>
<Modal bind:this={modal}> <Modal bind:this={modal}>

View File

@ -9,13 +9,21 @@
$: selectedRowArray = Object.keys($selectedRows).map(id => ({ _id: id })) $: selectedRowArray = Object.keys($selectedRows).map(id => ({ _id: id }))
</script> </script>
<ExportButton <span data-ignore-click-outside="true">
{disabled} <ExportButton
view={$tableId} {disabled}
filters={$filter} view={$tableId}
sorting={{ filters={$filter}
sortColumn: $sort.column, sorting={{
sortOrder: $sort.order, sortColumn: $sort.column,
}} sortOrder: $sort.order,
selectedRows={selectedRowArray} }}
/> selectedRows={selectedRowArray}
/>
</span>
<style>
span {
display: contents;
}
</style>

View File

@ -2,19 +2,19 @@
import TableFilterButton from "../TableFilterButton.svelte" import TableFilterButton from "../TableFilterButton.svelte"
import { getContext } from "svelte" import { getContext } from "svelte"
const { columns, config, filter, table } = getContext("grid") const { columns, tableId, filter, table } = getContext("grid")
const onFilter = e => { const onFilter = e => {
filter.set(e.detail || []) filter.set(e.detail || [])
} }
</script> </script>
{#key $config.tableId} {#key $tableId}
<TableFilterButton <TableFilterButton
schema={$table?.schema} schema={$table?.schema}
filters={$filter} filters={$filter}
on:change={onFilter} on:change={onFilter}
disabled={!$columns.length} disabled={!$columns.length}
tableId={$config.tableId} tableId={$tableId}
/> />
{/key} {/key}

View File

@ -2,7 +2,7 @@
import ManageAccessButton from "../ManageAccessButton.svelte" import ManageAccessButton from "../ManageAccessButton.svelte"
import { getContext } from "svelte" import { getContext } from "svelte"
const { config } = getContext("grid") const { tableId } = getContext("grid")
</script> </script>
<ManageAccessButton resourceId={$config.tableId} /> <ManageAccessButton resourceId={$tableId} />

View File

@ -147,6 +147,9 @@ const buildUsersAboveLimitBanner = EXPIRY_KEY => {
return { return {
key: EXPIRY_KEY, key: EXPIRY_KEY,
type: BANNER_TYPES.WARNING, type: BANNER_TYPES.WARNING,
onChange: () => {
defaultCacheFn(EXPIRY_KEY)
},
criteria: () => { criteria: () => {
return userLicensing.warnUserLimit return userLicensing.warnUserLimit
}, },

View File

@ -1,47 +1,28 @@
<script> <script>
import { url, goto } from "@roxi/routify"
import { import {
Button, ActionMenu,
Layout,
Heading, Heading,
Icon, Icon,
Popover, Layout,
notifications,
Table,
ActionMenu,
MenuItem, MenuItem,
Modal, Modal,
Table,
notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import UserGroupPicker from "components/settings/UserGroupPicker.svelte" import { goto, url } from "@roxi/routify"
import { createPaginationStore } from "helpers/pagination"
import { users, apps, groups, auth, features } from "stores/portal"
import { onMount, setContext } from "svelte"
import { roles } from "stores/backend"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { Breadcrumb, Breadcrumbs } from "components/portal/page"
import { roles } from "stores/backend"
import { apps, auth, features, groups } from "stores/portal"
import { onMount, setContext } from "svelte"
import AppNameTableRenderer from "../users/_components/AppNameTableRenderer.svelte"
import AppRoleTableRenderer from "../users/_components/AppRoleTableRenderer.svelte"
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte" import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
import GroupIcon from "./_components/GroupIcon.svelte" import GroupIcon from "./_components/GroupIcon.svelte"
import { Breadcrumbs, Breadcrumb } from "components/portal/page" import GroupUsers from "./_components/GroupUsers.svelte"
import AppNameTableRenderer from "../users/_components/AppNameTableRenderer.svelte"
import RemoveUserTableRenderer from "./_components/RemoveUserTableRenderer.svelte"
import AppRoleTableRenderer from "../users/_components/AppRoleTableRenderer.svelte"
import ScimBanner from "../_components/SCIMBanner.svelte"
export let groupId export let groupId
$: userSchema = {
email: {
width: "1fr",
},
...(readonly
? {}
: {
_id: {
displayName: "",
width: "auto",
borderLeft: true,
},
}),
}
const appSchema = { const appSchema = {
name: { name: {
width: "2fr", width: "2fr",
@ -50,12 +31,6 @@
width: "1fr", width: "1fr",
}, },
} }
const customUserTableRenderers = [
{
column: "_id",
component: RemoveUserTableRenderer,
},
]
const customAppTableRenderers = [ const customAppTableRenderers = [
{ {
column: "name", column: "name",
@ -67,20 +42,12 @@
}, },
] ]
let popoverAnchor
let popover
let searchTerm = ""
let prevSearch = undefined
let pageInfo = createPaginationStore()
let loaded = false let loaded = false
let editModal, deleteModal let editModal, deleteModal
$: scimEnabled = $features.isScimEnabled $: scimEnabled = $features.isScimEnabled
$: readonly = !$auth.isAdmin || scimEnabled $: readonly = !$auth.isAdmin || scimEnabled
$: page = $pageInfo.page
$: fetchUsers(page, searchTerm)
$: group = $groups.find(x => x._id === groupId) $: group = $groups.find(x => x._id === groupId)
$: filtered = $users.data
$: groupApps = $apps $: groupApps = $apps
.filter(app => .filter(app =>
groups.actions groups.actions
@ -97,25 +64,6 @@
} }
} }
async function fetchUsers(page, search) {
if ($pageInfo.loading) {
return
}
// need to remove the page if they've started searching
if (search && !prevSearch) {
pageInfo.reset()
page = undefined
}
prevSearch = search
try {
pageInfo.loading()
await users.search({ page, email: search })
pageInfo.fetched($users.hasNextPage, $users.nextPage)
} catch (error) {
notifications.error("Error getting user list")
}
}
async function deleteGroup() { async function deleteGroup() {
try { try {
await groups.actions.delete(group) await groups.actions.delete(group)
@ -130,21 +78,17 @@
try { try {
await groups.actions.save(group) await groups.actions.save(group)
} catch (error) { } catch (error) {
notifications.error(`Failed to save user group`) if (error.message) {
notifications.error(error.message)
} else {
notifications.error(`Failed to save user group`)
}
} }
} }
const removeUser = async id => {
await groups.actions.removeUser(groupId, id)
}
const removeApp = async app => { const removeApp = async app => {
await groups.actions.removeApp(groupId, apps.getProdAppID(app.devId)) await groups.actions.removeApp(groupId, apps.getProdAppID(app.devId))
} }
setContext("users", {
removeUser,
})
setContext("roles", { setContext("roles", {
updateRole: () => {}, updateRole: () => {},
removeRole: removeApp, removeRole: removeApp,
@ -186,41 +130,7 @@
</div> </div>
<Layout noPadding gap="S"> <Layout noPadding gap="S">
<div class="header"> <GroupUsers {groupId} />
<Heading size="S">Users</Heading>
{#if !scimEnabled}
<div bind:this={popoverAnchor}>
<Button disabled={readonly} on:click={popover.show()} cta
>Add user</Button
>
</div>
{:else}
<ScimBanner />
{/if}
<Popover align="right" bind:this={popover} anchor={popoverAnchor}>
<UserGroupPicker
bind:searchTerm
labelKey="email"
selected={group.users?.map(user => user._id)}
list={$users.data}
on:select={e => groups.actions.addUser(groupId, e.detail)}
on:deselect={e => groups.actions.removeUser(groupId, e.detail)}
/>
</Popover>
</div>
<Table
schema={userSchema}
data={group?.users}
allowEditRows={false}
customPlaceholder
customRenderers={customUserTableRenderers}
on:click={e => $goto(`../users/${e.detail._id}`)}
>
<div class="placeholder" slot="placeholder">
<Heading size="S">This user group doesn't have any users</Heading>
</div>
</Table>
</Layout> </Layout>
<Layout noPadding gap="S"> <Layout noPadding gap="S">

View File

@ -9,15 +9,23 @@
export let group export let group
export let saveGroup export let saveGroup
let nameError
</script> </script>
<ModalContent <ModalContent
onConfirm={() => saveGroup(group)} onConfirm={() => {
if (!group.name?.trim()) {
nameError = "Group name cannot be empty"
return false
}
saveGroup(group)
}}
size="M" size="M"
title={group?._rev ? "Edit group" : "Create group"} title={group?._rev ? "Edit group" : "Create group"}
confirmText="Save" confirmText="Save"
> >
<Input bind:value={group.name} label="Name" /> <Input bind:value={group.name} label="Name" error={nameError} />
<div class="modal-format"> <div class="modal-format">
<div class="modal-inner"> <div class="modal-inner">
<Body size="XS">Icon</Body> <Body size="XS">Icon</Body>

View File

@ -0,0 +1,59 @@
<script>
import { Button, Popover, notifications } from "@budibase/bbui"
import UserGroupPicker from "components/settings/UserGroupPicker.svelte"
import { createPaginationStore } from "helpers/pagination"
import { auth, groups, users } from "stores/portal"
export let groupId
export let onUsersUpdated
let popoverAnchor
let popover
let searchTerm = ""
let prevSearch = undefined
let pageInfo = createPaginationStore()
$: readonly = !$auth.isAdmin
$: page = $pageInfo.page
$: searchUsers(page, searchTerm)
$: group = $groups.find(x => x._id === groupId)
async function searchUsers(page, search) {
if ($pageInfo.loading) {
return
}
// need to remove the page if they've started searching
if (search && !prevSearch) {
pageInfo.reset()
page = undefined
}
prevSearch = search
try {
pageInfo.loading()
await users.search({ page, email: search })
pageInfo.fetched($users.hasNextPage, $users.nextPage)
} catch (error) {
notifications.error("Error getting user list")
}
}
</script>
<div bind:this={popoverAnchor}>
<Button disabled={readonly} on:click={popover.show()} cta>Add user</Button>
</div>
<Popover align="right" bind:this={popover} anchor={popoverAnchor}>
<UserGroupPicker
bind:searchTerm
labelKey="email"
selected={group.users?.map(user => user._id)}
list={$users.data}
on:select={async e => {
await groups.actions.addUser(groupId, e.detail)
onUsersUpdated()
}}
on:deselect={async e => {
await groups.actions.removeUser(groupId, e.detail)
onUsersUpdated()
}}
/>
</Popover>

View File

@ -0,0 +1,112 @@
<script>
import EditUserPicker from "./EditUserPicker.svelte"
import { Heading, Pagination, Table } from "@budibase/bbui"
import { fetchData } from "@budibase/frontend-core"
import { goto } from "@roxi/routify"
import { API } from "api"
import { auth, features, groups } from "stores/portal"
import { setContext } from "svelte"
import ScimBanner from "../../_components/SCIMBanner.svelte"
import RemoveUserTableRenderer from "../_components/RemoveUserTableRenderer.svelte"
export let groupId
const fetchGroupUsers = fetchData({
API,
datasource: {
type: "groupUser",
},
options: {
query: {
groupId,
},
},
})
$: userSchema = {
email: {
width: "1fr",
},
...(readonly
? {}
: {
_id: {
displayName: "",
width: "auto",
borderLeft: true,
},
}),
}
const customUserTableRenderers = [
{
column: "_id",
component: RemoveUserTableRenderer,
},
]
$: scimEnabled = $features.isScimEnabled
$: readonly = !$auth.isAdmin || scimEnabled
const removeUser = async id => {
await groups.actions.removeUser(groupId, id)
fetchGroupUsers.refresh()
}
setContext("users", {
removeUser,
})
</script>
<div class="header">
<Heading size="S">Users</Heading>
{#if !scimEnabled}
<EditUserPicker {groupId} onUsersUpdated={fetchGroupUsers.getInitialData} />
{:else}
<ScimBanner />
{/if}
</div>
<Table
schema={userSchema}
data={$fetchGroupUsers?.rows}
allowEditRows={false}
customPlaceholder
customRenderers={customUserTableRenderers}
on:click={e => $goto(`../users/${e.detail._id}`)}
>
<div class="placeholder" slot="placeholder">
<Heading size="S">This user group doesn't have any users</Heading>
</div>
</Table>
<div class="pagination">
<Pagination
page={$fetchGroupUsers.pageNumber + 1}
hasPrevPage={$fetchGroupUsers.loading
? false
: $fetchGroupUsers.hasPrevPage}
hasNextPage={$fetchGroupUsers.loading
? false
: $fetchGroupUsers.hasNextPage}
goToPrevPage={$fetchGroupUsers.loading ? null : fetchGroupUsers.prevPage}
goToNextPage={$fetchGroupUsers.loading ? null : fetchGroupUsers.nextPage}
/>
</div>
<style>
.header {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-l);
}
.header :global(.spectrum-Heading) {
flex: 1 1 auto;
}
.placeholder {
width: 100%;
text-align: center;
}
</style>

View File

@ -66,6 +66,8 @@
} catch (error) { } catch (error) {
if (error.status === 400) { if (error.status === 400) {
notifications.error(error.message) notifications.error(error.message)
} else if (error.message) {
notifications.error(error.message)
} else { } else {
notifications.error(`Failed to save group`) notifications.error(`Failed to save group`)
} }

View File

@ -261,7 +261,7 @@
header={`Users will soon be limited to ${staticUserLimit}`} header={`Users will soon be limited to ${staticUserLimit}`}
message={`Our free plan is going to be limited to ${staticUserLimit} users in ${$licensing.userLimitDays}. message={`Our free plan is going to be limited to ${staticUserLimit} users in ${$licensing.userLimitDays}.
This means any users exceeding the limit have been de-activated. This means any users exceeding the limit will be de-activated.
De-activated users will not able to access the builder or any published apps until you upgrade to one of our paid plans. De-activated users will not able to access the builder or any published apps until you upgrade to one of our paid plans.
`} `}

View File

@ -28,7 +28,7 @@ export function createGroupsStore() {
// on the backend anyway // on the backend anyway
if (get(licensing).groupsEnabled) { if (get(licensing).groupsEnabled) {
const groups = await API.getGroups() const groups = await API.getGroups()
store.set(groups) store.set(groups.data)
} }
}, },

View File

@ -13,9 +13,5 @@
}, },
"ts-node": { "ts-node": {
"require": ["tsconfig-paths/register"] "require": ["tsconfig-paths/register"]
}, }
"references": [ }
{ "path": "../types" },
{ "path": "../backend-core" },
]
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "dist/index.js", "main": "dist/index.js",
"bin": { "bin": {
@ -29,9 +29,9 @@
"outputPath": "build" "outputPath": "build"
}, },
"dependencies": { "dependencies": {
"@budibase/backend-core": "2.5.10-alpha.1", "@budibase/backend-core": "0.0.1",
"@budibase/string-templates": "2.5.10-alpha.1", "@budibase/string-templates": "0.0.1",
"@budibase/types": "2.5.10-alpha.1", "@budibase/types": "0.0.1",
"axios": "0.21.2", "axios": "0.21.2",
"chalk": "4.1.0", "chalk": "4.1.0",
"cli-progress": "3.11.2", "cli-progress": "3.11.2",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,11 +19,11 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "2.5.10-alpha.1", "@budibase/bbui": "0.0.1",
"@budibase/frontend-core": "2.5.10-alpha.1", "@budibase/frontend-core": "0.0.1",
"@budibase/shared-core": "2.5.10-alpha.1", "@budibase/shared-core": "0.0.1",
"@budibase/string-templates": "2.5.10-alpha.1", "@budibase/string-templates": "0.0.1",
"@budibase/types": "2.5.10-alpha.1", "@budibase/types": "0.0.1",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

View File

@ -5,6 +5,8 @@
const { styleable, builderStore } = getContext("sdk") const { styleable, builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
let handlingOnClick = false
export let disabled = false export let disabled = false
export let text = "" export let text = ""
export let onClick export let onClick
@ -16,6 +18,16 @@
export let icon = null export let icon = null
export let active = false export let active = false
const handleOnClick = async () => {
handlingOnClick = true
if (onClick) {
await onClick()
}
handlingOnClick = false
}
let node let node
$: $component.editing && node?.focus() $: $component.editing && node?.focus()
@ -37,9 +49,9 @@
<button <button
class={`spectrum-Button spectrum-Button--size${size} spectrum-Button--${type}`} class={`spectrum-Button spectrum-Button--size${size} spectrum-Button--${type}`}
class:spectrum-Button--quiet={quiet} class:spectrum-Button--quiet={quiet}
{disabled} disabled={disabled || handlingOnClick}
use:styleable={$component.styles} use:styleable={$component.styles}
on:click={onClick} on:click={handleOnClick}
contenteditable={$component.editing && !icon} contenteditable={$component.editing && !icon}
on:blur={$component.editing ? updateText : null} on:blur={$component.editing ? updateText : null}
bind:this={node} bind:this={node}

View File

@ -384,7 +384,7 @@ const confirmTextMap = {
["Save Row"]: "Are you sure you want to save this row?", ["Save Row"]: "Are you sure you want to save this row?",
["Execute Query"]: "Are you sure you want to execute this query?", ["Execute Query"]: "Are you sure you want to execute this query?",
["Trigger Automation"]: "Are you sure you want to trigger this automation?", ["Trigger Automation"]: "Are you sure you want to trigger this automation?",
["Prompt User"]: "Are you sure you want to contiune?", ["Prompt User"]: "Are you sure you want to continue?",
} }
/** /**

View File

@ -1,13 +1,13 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "2.5.10-alpha.1", "@budibase/bbui": "0.0.1",
"@budibase/shared-core": "2.5.10-alpha.1", "@budibase/shared-core": "0.0.1",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"socket.io-client": "^4.6.1", "socket.io-client": "^4.6.1",

View File

@ -52,6 +52,20 @@ export const buildGroupsEndpoints = API => {
}) })
}, },
/**
* Gets a group users by the group id
*/
getGroupUsers: async ({ id, bookmark }) => {
let url = `/api/global/groups/${id}/users?`
if (bookmark) {
url += `bookmark=${bookmark}`
}
return await API.get({
url,
})
},
/** /**
* Adds users to a group * Adds users to a group
* @param groupId The group to update * @param groupId The group to update

View File

@ -70,7 +70,15 @@
</div> </div>
{/if} {/if}
{/if} {/if}
{#if $config.allowExpandRows} {#if rowSelected && $config.allowDeleteRows}
<div class="delete" on:click={() => dispatch("request-bulk-delete")}>
<Icon
name="Delete"
size="S"
color="var(--spectrum-global-color-red-400)"
/>
</div>
{:else if $config.allowExpandRows}
<div <div
class="expand" class="expand"
class:visible={!disableExpand && (rowFocused || rowHovered)} class:visible={!disableExpand && (rowFocused || rowHovered)}
@ -111,9 +119,12 @@
.number.visible { .number.visible {
display: flex; display: flex;
} }
.delete,
.expand {
margin-right: 4px;
}
.expand { .expand {
opacity: 0; opacity: 0;
margin-right: 4px;
} }
.expand :global(.spectrum-Icon) { .expand :global(.spectrum-Icon) {
pointer-events: none; pointer-events: none;
@ -124,4 +135,11 @@
.expand.visible :global(.spectrum-Icon) { .expand.visible :global(.spectrum-Icon) {
pointer-events: all; pointer-events: all;
} }
.delete:hover {
cursor: pointer;
}
.delete:hover :global(.spectrum-Icon) {
color: var(--spectrum-global-color-red-600) !important;
}
</style> </style>

View File

@ -1,8 +1,8 @@
<script> <script>
import { Modal, ModalContent, Button, notifications } from "@budibase/bbui" import { Modal, ModalContent, notifications } from "@budibase/bbui"
import { getContext, onMount } from "svelte" import { getContext, onMount } from "svelte"
const { selectedRows, rows, config, subscribe } = getContext("grid") const { selectedRows, rows, subscribe } = getContext("grid")
let modal let modal
@ -27,20 +27,6 @@
onMount(() => subscribe("request-bulk-delete", () => modal?.show())) onMount(() => subscribe("request-bulk-delete", () => modal?.show()))
</script> </script>
{#if selectedRowCount}
<div class="delete-button" data-ignore-click-outside="true">
<Button
icon="Delete"
size="M"
on:click={modal.show}
disabled={!$config.allowEditRows}
cta
>
Delete {selectedRowCount} row{selectedRowCount === 1 ? "" : "s"}
</Button>
</div>
{/if}
<Modal bind:this={modal}> <Modal bind:this={modal}>
<ModalContent <ModalContent
title="Delete rows" title="Delete rows"
@ -53,14 +39,3 @@
row{selectedRowCount === 1 ? "" : "s"}? row{selectedRowCount === 1 ? "" : "s"}?
</ModalContent> </ModalContent>
</Modal> </Modal>
<style>
.delete-button :global(.spectrum-Button:not(:disabled)) {
background-color: var(--spectrum-global-color-red-400);
border-color: var(--spectrum-global-color-red-400);
}
.delete-button :global(.spectrum-Button:not(:disabled):hover) {
background-color: var(--spectrum-global-color-red-500);
border-color: var(--spectrum-global-color-red-500);
}
</style>

View File

@ -3,7 +3,7 @@
import { ActionButton, Popover } from "@budibase/bbui" import { ActionButton, Popover } from "@budibase/bbui"
import { DefaultColumnWidth } from "../lib/constants" import { DefaultColumnWidth } from "../lib/constants"
const { stickyColumn, columns } = getContext("grid") const { stickyColumn, columns, compact } = getContext("grid")
const smallSize = 120 const smallSize = 120
const mediumSize = DefaultColumnWidth const mediumSize = DefaultColumnWidth
const largeSize = DefaultColumnWidth * 1.5 const largeSize = DefaultColumnWidth * 1.5
@ -59,12 +59,13 @@
on:click={() => (open = !open)} on:click={() => (open = !open)}
selected={open} selected={open}
disabled={!allCols.length} disabled={!allCols.length}
tooltip={$compact ? "Width" : null}
> >
Width {$compact ? "" : "Width"}
</ActionButton> </ActionButton>
</div> </div>
<Popover bind:open {anchor} align="left"> <Popover bind:open {anchor} align={$compact ? "right" : "left"}>
<div class="content"> <div class="content">
{#each sizeOptions as option} {#each sizeOptions as option}
<ActionButton <ActionButton

View File

@ -1,8 +1,9 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { ActionButton, Popover, Toggle } from "@budibase/bbui" import { ActionButton, Popover, Toggle, Icon } from "@budibase/bbui"
import { getColumnIcon } from "../lib/utils"
const { columns, stickyColumn } = getContext("grid") const { columns, stickyColumn, compact } = getContext("grid")
let open = false let open = false
let anchor let anchor
@ -47,25 +48,32 @@
on:click={() => (open = !open)} on:click={() => (open = !open)}
selected={open || anyHidden} selected={open || anyHidden}
disabled={!$columns.length} disabled={!$columns.length}
tooltip={$compact ? "Columns" : ""}
> >
Columns {$compact ? "" : "Columns"}
</ActionButton> </ActionButton>
</div> </div>
<Popover bind:open {anchor} align="left"> <Popover bind:open {anchor} align={$compact ? "right" : "left"}>
<div class="content"> <div class="content">
<div class="columns"> <div class="columns">
{#if $stickyColumn} {#if $stickyColumn}
<div class="column">
<Icon size="S" name={getColumnIcon($stickyColumn)} />
{$stickyColumn.label}
</div>
<Toggle disabled size="S" value={true} /> <Toggle disabled size="S" value={true} />
<span>{$stickyColumn.label}</span>
{/if} {/if}
{#each $columns as column} {#each $columns as column}
<div class="column">
<Icon size="S" name={getColumnIcon(column)} />
{column.label}
</div>
<Toggle <Toggle
size="S" size="S"
value={column.visible} value={column.visible}
on:change={e => toggleVisibility(column, e.detail)} on:change={e => toggleVisibility(column, e.detail)}
/> />
<span>{column.label}</span>
{/each} {/each}
</div> </div>
<div class="buttons"> <div class="buttons">
@ -90,6 +98,13 @@
.columns { .columns {
display: grid; display: grid;
align-items: center; align-items: center;
grid-template-columns: auto 1fr; grid-template-columns: 1fr auto;
}
.columns :global(.spectrum-Switch) {
margin-right: 0;
}
.column {
display: flex;
gap: 8px;
} }
</style> </style>

View File

@ -7,7 +7,7 @@
SmallRowHeight, SmallRowHeight,
} from "../lib/constants" } from "../lib/constants"
const { rowHeight, columns, table } = getContext("grid") const { rowHeight, columns, table, compact } = getContext("grid")
const sizeOptions = [ const sizeOptions = [
{ {
label: "Small", label: "Small",
@ -41,12 +41,13 @@
size="M" size="M"
on:click={() => (open = !open)} on:click={() => (open = !open)}
selected={open} selected={open}
tooltip={$compact ? "Height" : null}
> >
Height {$compact ? "" : "Height"}
</ActionButton> </ActionButton>
</div> </div>
<Popover bind:open {anchor} align="left"> <Popover bind:open {anchor} align={$compact ? "right" : "left"}>
<div class="content"> <div class="content">
{#each sizeOptions as option} {#each sizeOptions as option}
<ActionButton <ActionButton

View File

@ -2,7 +2,7 @@
import { getContext } from "svelte" import { getContext } from "svelte"
import { ActionButton, Popover, Select } from "@budibase/bbui" import { ActionButton, Popover, Select } from "@budibase/bbui"
const { sort, columns, stickyColumn } = getContext("grid") const { sort, columns, stickyColumn, compact } = getContext("grid")
let open = false let open = false
let anchor let anchor
@ -90,12 +90,13 @@
on:click={() => (open = !open)} on:click={() => (open = !open)}
selected={open} selected={open}
disabled={!columnOptions.length} disabled={!columnOptions.length}
tooltip={$compact ? "Sort" : ""}
> >
Sort {$compact ? "" : "Sort"}
</ActionButton> </ActionButton>
</div> </div>
<Popover bind:open {anchor} align="left"> <Popover bind:open {anchor} align={$compact ? "right" : "left"}>
<div class="content"> <div class="content">
<Select <Select
placeholder={null} placeholder={null}

View File

@ -6,7 +6,7 @@
import { createEventManagers } from "../lib/events" import { createEventManagers } from "../lib/events"
import { createAPIClient } from "../../../api" import { createAPIClient } from "../../../api"
import { attachStores } from "../stores" import { attachStores } from "../stores"
import DeleteButton from "../controls/DeleteButton.svelte" import BulkDeleteHandler from "../controls/BulkDeleteHandler.svelte"
import BetaButton from "../controls/BetaButton.svelte" import BetaButton from "../controls/BetaButton.svelte"
import GridBody from "./GridBody.svelte" import GridBody from "./GridBody.svelte"
import ResizeOverlay from "../overlays/ResizeOverlay.svelte" import ResizeOverlay from "../overlays/ResizeOverlay.svelte"
@ -112,13 +112,12 @@
<AddRowButton /> <AddRowButton />
<AddColumnButton /> <AddColumnButton />
<slot name="controls" /> <slot name="controls" />
<SortButton />
<HideColumnsButton />
<ColumnWidthButton /> <ColumnWidthButton />
<RowHeightButton /> <RowHeightButton />
<HideColumnsButton />
<SortButton />
</div> </div>
<div class="controls-right"> <div class="controls-right">
<DeleteButton />
<UserAvatars /> <UserAvatars />
</div> </div>
</div> </div>
@ -131,7 +130,9 @@
<GridBody /> <GridBody />
</div> </div>
<BetaButton /> <BetaButton />
<NewRow /> {#if allowAddRows}
<NewRow />
{/if}
<div class="overlays"> <div class="overlays">
<ResizeOverlay /> <ResizeOverlay />
<ReorderOverlay /> <ReorderOverlay />
@ -146,6 +147,9 @@
<ProgressCircle /> <ProgressCircle />
</div> </div>
{/if} {/if}
{#if allowDeleteRows}
<BulkDeleteHandler />
{/if}
<KeyboardManager /> <KeyboardManager />
</div> </div>
@ -214,6 +218,7 @@
padding: var(--cell-padding); padding: var(--cell-padding);
gap: var(--cell-spacing); gap: var(--cell-spacing);
background: var(--background); background: var(--background);
z-index: 2;
} }
.controls-left, .controls-left,
.controls-right { .controls-right {
@ -239,7 +244,7 @@
height: 100%; height: 100%;
display: grid; display: grid;
place-items: center; place-items: center;
z-index: 10; z-index: 100;
} }
.grid-loading:before { .grid-loading:before {
content: ""; content: "";

View File

@ -4,8 +4,14 @@
import HeaderCell from "../cells/HeaderCell.svelte" import HeaderCell from "../cells/HeaderCell.svelte"
import { Icon } from "@budibase/bbui" import { Icon } from "@budibase/bbui"
const { renderedColumns, dispatch, scroll, hiddenColumnsWidth, width } = const {
getContext("grid") renderedColumns,
dispatch,
scroll,
hiddenColumnsWidth,
width,
config,
} = getContext("grid")
$: columnsWidth = $renderedColumns.reduce( $: columnsWidth = $renderedColumns.reduce(
(total, col) => (total += col.width), (total, col) => (total += col.width),
@ -23,13 +29,15 @@
{/each} {/each}
</div> </div>
</GridScrollWrapper> </GridScrollWrapper>
<div {#if $config.allowAddColumns}
class="add" <div
style="left:{left}px" class="add"
on:click={() => dispatch("add-column")} style="left:{left}px"
> on:click={() => dispatch("add-column")}
<Icon name="Add" /> >
</div> <Icon name="Add" />
</div>
{/if}
</div> </div>
<style> <style>
@ -38,7 +46,6 @@
border-bottom: var(--cell-border); border-bottom: var(--cell-border);
position: relative; position: relative;
height: var(--default-row-height); height: var(--default-row-height);
z-index: 1;
} }
.row { .row {
display: flex; display: flex;
@ -54,6 +61,7 @@
border-right: var(--cell-border); border-right: var(--cell-border);
border-bottom: var(--cell-border); border-bottom: var(--cell-border);
background: var(--spectrum-global-color-gray-100); background: var(--spectrum-global-color-gray-100);
z-index: 20;
} }
.add:hover { .add:hover {
background: var(--spectrum-global-color-gray-200); background: var(--spectrum-global-color-gray-200);

View File

@ -270,7 +270,7 @@
z-index: 3; z-index: 3;
position: absolute; position: absolute;
top: calc(var(--row-height) + var(--offset) + 24px); top: calc(var(--row-height) + var(--offset) + 24px);
left: var(--gutter-width); left: 18px;
} }
.button-with-keys { .button-with-keys {
display: flex; display: flex;

View File

@ -21,6 +21,9 @@ const TypeIconMap = {
} }
export const getColumnIcon = column => { export const getColumnIcon = column => {
if (column.schema.autocolumn) {
return "MagicWand"
}
const type = column.schema.type const type = column.schema.type
return TypeIconMap[type] || "Text" return TypeIconMap[type] || "Text"
} }

View File

@ -13,6 +13,7 @@
clipboard, clipboard,
dispatch, dispatch,
selectedRows, selectedRows,
config,
} = getContext("grid") } = getContext("grid")
const ignoredOriginSelectors = [ const ignoredOriginSelectors = [
@ -37,10 +38,12 @@
e.preventDefault() e.preventDefault()
focusFirstCell() focusFirstCell()
} else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
e.preventDefault() if ($config.allowAddRows) {
dispatch("add-row-inline") e.preventDefault()
dispatch("add-row-inline")
}
} else if (e.key === "Delete" || e.key === "Backspace") { } else if (e.key === "Delete" || e.key === "Backspace") {
if (Object.keys($selectedRows).length) { if (Object.keys($selectedRows).length && $config.allowDeleteRows) {
dispatch("request-bulk-delete") dispatch("request-bulk-delete")
} }
} }
@ -88,7 +91,9 @@
} }
break break
case "Enter": case "Enter":
dispatch("add-row-inline") if ($config.allowAddRows) {
dispatch("add-row-inline")
}
} }
} else { } else {
switch (e.key) { switch (e.key) {
@ -106,7 +111,7 @@
break break
case "Delete": case "Delete":
case "Backspace": case "Backspace":
if (Object.keys($selectedRows).length) { if (Object.keys($selectedRows).length && $config.allowDeleteRows) {
dispatch("request-bulk-delete") dispatch("request-bulk-delete")
} else { } else {
deleteSelectedCell() deleteSelectedCell()
@ -117,7 +122,9 @@
break break
case " ": case " ":
case "Space": case "Space":
toggleSelectRow() if ($config.allowDeleteRows) {
toggleSelectRow()
}
break break
default: default:
startEnteringValue(e.key, e.which) startEnteringValue(e.key, e.which)

View File

@ -2,6 +2,7 @@ import { writable, get, derived } from "svelte/store"
import { tick } from "svelte" import { tick } from "svelte"
import { import {
DefaultRowHeight, DefaultRowHeight,
GutterWidth,
LargeRowHeight, LargeRowHeight,
MediumRowHeight, MediumRowHeight,
NewRowID, NewRowID,
@ -43,6 +44,8 @@ export const deriveStores = context => {
enrichedRows, enrichedRows,
rowLookupMap, rowLookupMap,
rowHeight, rowHeight,
stickyColumn,
width,
} = context } = context
// Derive the row that contains the selected cell // Derive the row that contains the selected cell
@ -70,6 +73,7 @@ export const deriveStores = context => {
hoveredRowId.set(null) hoveredRowId.set(null)
} }
// Derive the amount of content lines to show in cells depending on row height
const contentLines = derived(rowHeight, $rowHeight => { const contentLines = derived(rowHeight, $rowHeight => {
if ($rowHeight === LargeRowHeight) { if ($rowHeight === LargeRowHeight) {
return 3 return 3
@ -79,9 +83,15 @@ export const deriveStores = context => {
return 1 return 1
}) })
// Derive whether we should use the compact UI, depending on width
const compact = derived([stickyColumn, width], ([$stickyColumn, $width]) => {
return ($stickyColumn?.width || 0) + $width + GutterWidth < 1100
})
return { return {
focusedRow, focusedRow,
contentLines, contentLines,
compact,
ui: { ui: {
actions: { actions: {
blur, blur,

View File

@ -362,13 +362,35 @@ export default class DataFetch {
return return
} }
this.store.update($store => ({ ...$store, loading: true })) this.store.update($store => ({ ...$store, loading: true }))
const { rows, info, error } = await this.getPage() const { rows, info, error, cursor } = await this.getPage()
let { cursors } = get(this.store)
const { pageNumber } = get(this.store)
if (!rows.length && pageNumber > 0) {
// If the full page is gone but we have previous pages, navigate to the previous page
this.store.update($store => ({
...$store,
loading: false,
cursors: cursors.slice(0, pageNumber),
}))
return await this.prevPage()
}
const currentNextCursor = cursors[pageNumber + 1]
if (currentNextCursor != cursor) {
// If the current cursor changed, all the next pages need to be updated, so we mark them as stale
cursors = cursors.slice(0, pageNumber + 1)
cursors[pageNumber + 1] = cursor
}
this.store.update($store => ({ this.store.update($store => ({
...$store, ...$store,
rows, rows,
info, info,
loading: false, loading: false,
error, error,
cursors,
})) }))
} }

View File

@ -0,0 +1,50 @@
import { get } from "svelte/store"
import DataFetch from "./DataFetch.js"
import { TableNames } from "../constants"
export default class GroupUserFetch extends DataFetch {
constructor(opts) {
super({
...opts,
datasource: {
tableId: TableNames.USERS,
},
})
}
determineFeatureFlags() {
return {
supportsSearch: true,
supportsSort: false,
supportsPagination: true,
}
}
async getDefinition() {
return {
schema: {},
}
}
async getData() {
const { query, cursor } = get(this.store)
try {
const res = await this.API.getGroupUsers({
id: query.groupId,
bookmark: cursor,
})
return {
rows: res?.users || [],
hasNextPage: res?.hasNextPage || false,
cursor: res?.bookmark || null,
}
} catch (error) {
return {
rows: [],
hasNextPage: false,
error,
}
}
}
}

View File

@ -6,6 +6,7 @@ import NestedProviderFetch from "./NestedProviderFetch.js"
import FieldFetch from "./FieldFetch.js" import FieldFetch from "./FieldFetch.js"
import JSONArrayFetch from "./JSONArrayFetch.js" import JSONArrayFetch from "./JSONArrayFetch.js"
import UserFetch from "./UserFetch.js" import UserFetch from "./UserFetch.js"
import GroupUserFetch from "./GroupUserFetch.js"
const DataFetchMap = { const DataFetchMap = {
table: TableFetch, table: TableFetch,
@ -13,6 +14,7 @@ const DataFetchMap = {
query: QueryFetch, query: QueryFetch,
link: RelationshipFetch, link: RelationshipFetch,
user: UserFetch, user: UserFetch,
groupUser: GroupUserFetch,
// Client specific datasource types // Client specific datasource types
provider: NestedProviderFetch, provider: NestedProviderFetch,

1
packages/pro Submodule

@ -0,0 +1 @@
Subproject commit c57a98d246a50a43905d8572a88c901ec598390c

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/sdk", "name": "@budibase/sdk",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"description": "Budibase Public API SDK", "description": "Budibase Public API SDK",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",

View File

@ -20,9 +20,9 @@ const baseConfig: Config.InitialProjectOptions = {
} }
// add pro sources if they exist // add pro sources if they exist
if (fs.existsSync("../../../budibase-pro")) { if (fs.existsSync("../pro/packages")) {
baseConfig.moduleNameMapper["@budibase/pro"] = baseConfig.moduleNameMapper!["@budibase/pro"] =
"<rootDir>/../../../budibase-pro/packages/pro/src" "<rootDir>/../pro/packages/pro/src"
} }
const config: Config.InitialOptions = { const config: Config.InitialOptions = {

View File

@ -1,6 +1,10 @@
{ {
"watch": ["src", "../backend-core", "../../../budibase-pro/packages/pro"], "watch": ["src", "../backend-core", "../pro/packages/pro"],
"ext": "js,ts,json", "ext": "js,ts,json",
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../backend-core/dist/**/*"], "ignore": [
"src/**/*.spec.ts",
"src/**/*.spec.js",
"../backend-core/dist/**/*"
],
"exec": "ts-node src/index.ts" "exec": "ts-node src/index.ts"
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -45,12 +45,12 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "10.0.3", "@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "2.5.10-alpha.1", "@budibase/backend-core": "0.0.1",
"@budibase/client": "2.5.10-alpha.1", "@budibase/client": "0.0.1",
"@budibase/pro": "2.5.10-alpha.1", "@budibase/pro": "0.0.1",
"@budibase/shared-core": "2.5.10-alpha.1", "@budibase/shared-core": "0.0.1",
"@budibase/string-templates": "2.5.10-alpha.1", "@budibase/string-templates": "0.0.1",
"@budibase/types": "2.5.10-alpha.1", "@budibase/types": "0.0.1",
"@bull-board/api": "3.7.0", "@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4", "@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View File

@ -69,9 +69,9 @@ export async function validate({
if (type === FieldTypes.FORMULA || column.autocolumn) { if (type === FieldTypes.FORMULA || column.autocolumn) {
continue continue
} }
// special case for options, need to always allow unselected (null) // special case for options, need to always allow unselected (empty)
if (type === FieldTypes.OPTIONS && constraints.inclusion) { if (type === FieldTypes.OPTIONS && constraints.inclusion) {
constraints.inclusion.push(null) constraints.inclusion.push(null, "")
} }
let res let res

View File

@ -42,8 +42,14 @@ if (!env.isTest()) {
host: REDIS_OPTS.host, host: REDIS_OPTS.host,
port: REDIS_OPTS.port, port: REDIS_OPTS.port,
}, },
password: REDIS_OPTS.opts.password, password:
database: 1, REDIS_OPTS.opts.password || REDIS_OPTS.opts.redisOptions.password,
}
if (!env.REDIS_CLUSTERED) {
// Can't set direct redis db in clustered env
// @ts-ignore
options.database = 1
} }
} }
rateLimitStore = new Stores.Redis(options) rateLimitStore = new Stores.Redis(options)

View File

@ -212,6 +212,7 @@ describe("/rows", () => {
attachmentNull: attachment, attachmentNull: attachment,
attachmentUndefined: attachment, attachmentUndefined: attachment,
attachmentEmpty: attachment, attachmentEmpty: attachment,
attachmentEmptyArrayStr: attachment
}, },
}) })
@ -239,6 +240,7 @@ describe("/rows", () => {
attachmentNull: null, attachmentNull: null,
attachmentUndefined: undefined, attachmentUndefined: undefined,
attachmentEmpty: "", attachmentEmpty: "",
attachmentEmptyArrayStr: "[]",
} }
const id = (await config.createRow(row))._id const id = (await config.createRow(row))._id
@ -268,6 +270,7 @@ describe("/rows", () => {
expect(saved.attachmentNull).toEqual([]) expect(saved.attachmentNull).toEqual([])
expect(saved.attachmentUndefined).toBe(undefined) expect(saved.attachmentUndefined).toBe(undefined)
expect(saved.attachmentEmpty).toEqual([]) expect(saved.attachmentEmpty).toEqual([])
expect(saved.attachmentEmptyArrayStr).toEqual([])
}) })
}) })

View File

@ -9,7 +9,7 @@ import * as serverLog from "./steps/serverLog"
import * as discord from "./steps/discord" import * as discord from "./steps/discord"
import * as slack from "./steps/slack" import * as slack from "./steps/slack"
import * as zapier from "./steps/zapier" import * as zapier from "./steps/zapier"
import * as integromat from "./steps/integromat" import * as make from "./steps/make"
import * as filter from "./steps/filter" import * as filter from "./steps/filter"
import * as delay from "./steps/delay" import * as delay from "./steps/delay"
import * as queryRow from "./steps/queryRows" import * as queryRow from "./steps/queryRows"
@ -43,7 +43,7 @@ const ACTION_IMPLS: Record<
discord: discord.run, discord: discord.run,
slack: slack.run, slack: slack.run,
zapier: zapier.run, zapier: zapier.run,
integromat: integromat.run, integromat: make.run,
} }
export const BUILTIN_ACTION_DEFINITIONS: Record<string, AutomationStepSchema> = export const BUILTIN_ACTION_DEFINITIONS: Record<string, AutomationStepSchema> =
{ {
@ -63,7 +63,7 @@ export const BUILTIN_ACTION_DEFINITIONS: Record<string, AutomationStepSchema> =
discord: discord.definition, discord: discord.definition,
slack: slack.definition, slack: slack.definition,
zapier: zapier.definition, zapier: zapier.definition,
integromat: integromat.definition, integromat: make.definition,
} }
// don't add the bash script/definitions unless in self host // don't add the bash script/definitions unless in self host

View File

@ -9,10 +9,11 @@ import {
} from "@budibase/types" } from "@budibase/types"
export const definition: AutomationStepSchema = { export const definition: AutomationStepSchema = {
name: "Integromat Integration", name: "Make Integration",
tagline: "Trigger an Integromat scenario", stepTitle: "Make",
tagline: "Trigger a Make scenario",
description: description:
"Performs a webhook call to Integromat and gets the response (if configured)", "Performs a webhook call to Make and gets the response (if configured)",
icon: "ri-shut-down-line", icon: "ri-shut-down-line",
stepId: AutomationActionStepId.integromat, stepId: AutomationActionStepId.integromat,
type: AutomationStepType.ACTION, type: AutomationStepType.ACTION,

View File

@ -34,8 +34,6 @@ function parseIntSafe(number?: string) {
} }
} }
let inThread = false
const environment = { const environment = {
// important - prefer app port to generic port // important - prefer app port to generic port
PORT: process.env.APP_PORT || process.env.PORT, PORT: process.env.APP_PORT || process.env.PORT,
@ -47,6 +45,7 @@ const environment = {
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
REDIS_URL: process.env.REDIS_URL, REDIS_URL: process.env.REDIS_URL,
REDIS_PASSWORD: process.env.REDIS_PASSWORD, REDIS_PASSWORD: process.env.REDIS_PASSWORD,
REDIS_CLUSTERED: process.env.REDIS_CLUSTERED,
HTTP_MIGRATIONS: process.env.HTTP_MIGRATIONS, HTTP_MIGRATIONS: process.env.HTTP_MIGRATIONS,
API_REQ_LIMIT_PER_SEC: process.env.API_REQ_LIMIT_PER_SEC, API_REQ_LIMIT_PER_SEC: process.env.API_REQ_LIMIT_PER_SEC,
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
@ -94,12 +93,8 @@ const environment = {
isProd: () => { isProd: () => {
return !isDev() return !isDev()
}, },
// used to check if already in a thread, don't thread further
setInThread: () => {
inThread = true
},
isInThread: () => { isInThread: () => {
return inThread return process.env.FORKED_PROCESS
}, },
} }

View File

@ -15,7 +15,7 @@ import {
} from "@budibase/types" } from "@budibase/types"
import { OAuth2Client } from "google-auth-library" import { OAuth2Client } from "google-auth-library"
import { buildExternalTableId, finaliseExternalTables } from "./utils" import { buildExternalTableId, finaliseExternalTables } from "./utils"
import { GoogleSpreadsheet } from "google-spreadsheet" import { GoogleSpreadsheet, GoogleSpreadsheetRow } from "google-spreadsheet"
import fetch from "node-fetch" import fetch from "node-fetch"
import { configs, HTTPError } from "@budibase/backend-core" import { configs, HTTPError } from "@budibase/backend-core"
import { dataFilters } from "@budibase/shared-core" import { dataFilters } from "@budibase/shared-core"
@ -434,7 +434,20 @@ class GoogleSheetsIntegration implements DatasourcePlus {
try { try {
await this.connect() await this.connect()
const sheet = this.client.sheetsByTitle[query.sheet] const sheet = this.client.sheetsByTitle[query.sheet]
const rows = await sheet.getRows() let rows: GoogleSpreadsheetRow[] = []
if (query.paginate) {
const limit = query.paginate.limit || 100
let page: number =
typeof query.paginate.page === "number"
? query.paginate.page
: parseInt(query.paginate.page || "1")
rows = await sheet.getRows({
limit,
offset: (page - 1) * limit,
})
} else {
rows = await sheet.getRows()
}
const filtered = dataFilters.runLuceneQuery(rows, query.filters) const filtered = dataFilters.runLuceneQuery(rows, query.filters)
const headerValues = sheet.headerValues const headerValues = sheet.headerValues
let response = [] let response = []

View File

@ -39,6 +39,12 @@ export class Thread {
const workerOpts: any = { const workerOpts: any = {
autoStart: true, autoStart: true,
maxConcurrentWorkers: this.count, maxConcurrentWorkers: this.count,
workerOptions: {
env: {
...process.env,
FORKED_PROCESS: "1",
},
},
} }
if (opts.timeoutMs) { if (opts.timeoutMs) {
this.timeoutMs = opts.timeoutMs this.timeoutMs = opts.timeoutMs

View File

@ -25,11 +25,9 @@ function makeVariableKey(queryId: string, variable: string) {
export function threadSetup() { export function threadSetup() {
// don't run this if not threading // don't run this if not threading
if (env.isTest() || env.DISABLE_THREADING) { if (env.isTest() || env.DISABLE_THREADING || !env.isInThread()) {
return return
} }
// when thread starts, make sure it is recorded
env.setInThread()
db.init() db.init()
} }

View File

@ -1,5 +1,6 @@
// @ts-nocheck // @ts-nocheck
import { FieldTypes } from "../../constants" import { FieldTypes } from "../../constants"
import { logging } from "@budibase/backend-core"
/** /**
* A map of how we convert various properties in rows to each other based on the row type. * A map of how we convert various properties in rows to each other based on the row type.
@ -67,9 +68,23 @@ export const TYPE_TRANSFORM_MAP: any = {
}, },
}, },
[FieldTypes.ATTACHMENT]: { [FieldTypes.ATTACHMENT]: {
"": [],
[null]: [], [null]: [],
[undefined]: undefined, [undefined]: undefined,
parse: attachments => {
if (typeof attachments === "string") {
if (attachments === "") {
return []
}
let result
try {
result = JSON.parse(attachments)
} catch (e) {
logging.logAlert("Could not parse attachments", e)
}
return result
}
return attachments
},
}, },
[FieldTypes.BOOLEAN]: { [FieldTypes.BOOLEAN]: {
"": null, "": null,

View File

@ -11,7 +11,7 @@
"@budibase/backend-core": ["../backend-core/src"], "@budibase/backend-core": ["../backend-core/src"],
"@budibase/backend-core/*": ["../backend-core/*"], "@budibase/backend-core/*": ["../backend-core/*"],
"@budibase/shared-core": ["../shared-core/src"], "@budibase/shared-core": ["../shared-core/src"],
"@budibase/pro": ["../../../budibase-pro/packages/pro/src"] "@budibase/pro": ["../pro/packages/pro/src"]
} }
}, },
"ts-node": { "ts-node": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/shared-core", "name": "@budibase/shared-core",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"description": "Shared data utils", "description": "Shared data utils",
"main": "dist/cjs/src/index.js", "main": "dist/cjs/src/index.js",
"types": "dist/mjs/src/index.d.ts", "types": "dist/mjs/src/index.d.ts",
@ -20,7 +20,7 @@
"dev:builder": "yarn prebuild && concurrently \"tsc -p tsconfig.build.json --watch\" \"tsc -p tsconfig-cjs.build.json --watch\"" "dev:builder": "yarn prebuild && concurrently \"tsc -p tsconfig.build.json --watch\" \"tsc -p tsconfig-cjs.build.json --watch\""
}, },
"dependencies": { "dependencies": {
"@budibase/types": "2.5.10-alpha.1" "@budibase/types": "0.0.1"
}, },
"devDependencies": { "devDependencies": {
"concurrently": "^7.6.0", "concurrently": "^7.6.0",

View File

@ -6,6 +6,5 @@
"paths": { "paths": {
"@budibase/types": ["../types/src"] "@budibase/types": ["../types/src"]
} }
}, }
"references": [{ "path": "../types" }]
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/types", "name": "@budibase/types",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"description": "Budibase types", "description": "Budibase types",
"main": "dist/cjs/index.js", "main": "dist/cjs/index.js",
"types": "dist/mjs/index.d.ts", "types": "dist/mjs/index.d.ts",

View File

@ -104,6 +104,7 @@ interface InputOutputBlock {
export interface AutomationStepSchema { export interface AutomationStepSchema {
name: string name: string
stepTitle?: string
tagline: string tagline: string
icon: string icon: string
description: string description: string

View File

@ -1,3 +1,4 @@
import { PaginationResponse } from "../../api"
import { Document } from "../document" import { Document } from "../document"
export interface UserGroup extends Document { export interface UserGroup extends Document {
@ -21,3 +22,15 @@ export interface GroupUser {
export interface UserGroupRoles { export interface UserGroupRoles {
[key: string]: string [key: string]: string
} }
export interface SearchGroupRequest {}
export interface SearchGroupResponse {
data: UserGroup[]
}
export interface SearchUserGroupResponse extends PaginationResponse {
users: {
_id: any
email: any
}[]
}

View File

@ -65,6 +65,7 @@ export type DatabaseQueryOpts = {
key?: string key?: string
keys?: string[] keys?: string[]
group?: boolean group?: boolean
startkey_docid?: string
} }
export const isDocument = (doc: any): doc is Document => { export const isDocument = (doc: any): doc is Document => {

View File

@ -20,11 +20,11 @@ const config: Config.InitialOptions = {
} }
// add pro sources if they exist // add pro sources if they exist
if (fs.existsSync("../../../budibase-pro")) { if (fs.existsSync("../pro/packages")) {
config.moduleNameMapper["@budibase/pro/(.*)"] = config.moduleNameMapper!["@budibase/pro/(.*)"] =
"<rootDir>/../../../budibase-pro/packages/pro/$1" "<rootDir>/../pro/packages/pro/$1"
config.moduleNameMapper["@budibase/pro"] = config.moduleNameMapper!["@budibase/pro"] =
"<rootDir>/../../../budibase-pro/packages/pro/src" "<rootDir>/../pro/packages/pro/src"
} }
export default config export default config

View File

@ -1,6 +1,10 @@
{ {
"watch": ["src", "../backend-core", "../../../budibase-pro/packages/pro"], "watch": ["src", "../backend-core", "../pro/packages/pro"],
"ext": "js,ts,json", "ext": "js,ts,json",
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../backend-core/dist/**/*"], "ignore": [
"src/**/*.spec.ts",
"src/**/*.spec.js",
"../backend-core/dist/**/*"
],
"exec": "ts-node src/index.ts" "exec": "ts-node src/index.ts"
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "2.5.10-alpha.1", "version": "0.0.1",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -37,10 +37,10 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "2.5.10-alpha.1", "@budibase/backend-core": "0.0.1",
"@budibase/pro": "2.5.10-alpha.1", "@budibase/pro": "0.0.1",
"@budibase/string-templates": "2.5.10-alpha.1", "@budibase/string-templates": "0.0.1",
"@budibase/types": "2.5.10-alpha.1", "@budibase/types": "0.0.1",
"@koa/router": "8.0.8", "@koa/router": "8.0.8",
"@sentry/node": "6.17.7", "@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",

View File

@ -69,9 +69,11 @@ const bulkCreate = async (users: User[], groupIds: string[]) => {
return await userSdk.bulkCreate(users, groupIds) return await userSdk.bulkCreate(users, groupIds)
} }
export const bulkUpdate = async (ctx: any) => { export const bulkUpdate = async (
ctx: Ctx<BulkUserRequest, BulkUserResponse>
) => {
const currentUserId = ctx.user._id const currentUserId = ctx.user._id
const input = ctx.request.body as BulkUserRequest const input = ctx.request.body
let created, deleted let created, deleted
try { try {
if (input.create) { if (input.create) {
@ -83,7 +85,7 @@ export const bulkUpdate = async (ctx: any) => {
} catch (err: any) { } catch (err: any) {
ctx.throw(err.status || 400, err?.message || err) ctx.throw(err.status || 400, err?.message || err)
} }
ctx.body = { created, deleted } as BulkUserResponse ctx.body = { created, deleted }
} }
const parseBooleanParam = (param: any) => { const parseBooleanParam = (param: any) => {
@ -184,15 +186,15 @@ export const destroy = async (ctx: any) => {
} }
} }
export const getAppUsers = async (ctx: any) => { export const getAppUsers = async (ctx: Ctx<SearchUsersRequest>) => {
const body = ctx.request.body as SearchUsersRequest const body = ctx.request.body
const users = await userSdk.getUsersByAppAccess(body?.appId) const users = await userSdk.getUsersByAppAccess(body?.appId)
ctx.body = { data: users } ctx.body = { data: users }
} }
export const search = async (ctx: any) => { export const search = async (ctx: Ctx<SearchUsersRequest>) => {
const body = ctx.request.body as SearchUsersRequest const body = ctx.request.body
if (body.paginated === false) { if (body.paginated === false) {
await getAppUsers(ctx) await getAppUsers(ctx)
@ -238,8 +240,8 @@ export const tenantUserLookup = async (ctx: any) => {
/* /*
Encapsulate the app user onboarding flows here. Encapsulate the app user onboarding flows here.
*/ */
export const onboardUsers = async (ctx: any) => { export const onboardUsers = async (ctx: Ctx<InviteUsersRequest>) => {
const request = ctx.request.body as InviteUsersRequest | BulkUserRequest const request = ctx.request.body
const isBulkCreate = "create" in request const isBulkCreate = "create" in request
const emailConfigured = await isEmailConfigured() const emailConfigured = await isEmailConfigured()
@ -255,7 +257,7 @@ export const onboardUsers = async (ctx: any) => {
} else if (emailConfigured) { } else if (emailConfigured) {
onboardingResponse = await inviteMultiple(ctx) onboardingResponse = await inviteMultiple(ctx)
} else if (!emailConfigured) { } else if (!emailConfigured) {
const inviteRequest = ctx.request.body as InviteUsersRequest const inviteRequest = ctx.request.body
let createdPasswords: any = {} let createdPasswords: any = {}
@ -295,10 +297,10 @@ export const onboardUsers = async (ctx: any) => {
} }
} }
export const invite = async (ctx: any) => { export const invite = async (ctx: Ctx<InviteUserRequest>) => {
const request = ctx.request.body as InviteUserRequest const request = ctx.request.body
let multiRequest = [request] as InviteUsersRequest let multiRequest = [request]
const response = await userSdk.invite(multiRequest) const response = await userSdk.invite(multiRequest)
// explicitly throw for single user invite // explicitly throw for single user invite
@ -318,8 +320,8 @@ export const invite = async (ctx: any) => {
} }
} }
export const inviteMultiple = async (ctx: any) => { export const inviteMultiple = async (ctx: Ctx<InviteUsersRequest>) => {
const request = ctx.request.body as InviteUsersRequest const request = ctx.request.body
ctx.body = await userSdk.invite(request) ctx.body = await userSdk.invite(request)
} }
@ -424,7 +426,6 @@ export const inviteAccept = async (
if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) { if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
// explicitly re-throw limit exceeded errors // explicitly re-throw limit exceeded errors
ctx.throw(400, err) ctx.throw(400, err)
return
} }
console.warn("Error inviting user", err) console.warn("Error inviting user", err)
ctx.throw(400, "Unable to create new user, invitation invalid.") ctx.throw(400, "Unable to create new user, invitation invalid.")

View File

@ -13,6 +13,7 @@ describe("/api/global/groups", () => {
}) })
beforeEach(async () => { beforeEach(async () => {
jest.resetAllMocks()
mocks.licenses.useGroups() mocks.licenses.useGroups()
}) })
@ -24,6 +25,63 @@ describe("/api/global/groups", () => {
expect(events.group.updated).not.toBeCalled() expect(events.group.updated).not.toBeCalled()
expect(events.group.permissionsEdited).not.toBeCalled() expect(events.group.permissionsEdited).not.toBeCalled()
}) })
it("should not allow undefined names", async () => {
const group = { ...structures.groups.UserGroup(), name: undefined } as any
const response = await config.api.groups.saveGroup(group, { expect: 400 })
expect(JSON.parse(response.text).message).toEqual(
'Invalid body - "name" is required'
)
})
it("should not allow empty names", async () => {
const group = { ...structures.groups.UserGroup(), name: "" }
const response = await config.api.groups.saveGroup(group, { expect: 400 })
expect(JSON.parse(response.text).message).toEqual(
'Invalid body - "name" is not allowed to be empty'
)
})
it("should not allow whitespace names", async () => {
const group = { ...structures.groups.UserGroup(), name: " " }
const response = await config.api.groups.saveGroup(group, { expect: 400 })
expect(JSON.parse(response.text).message).toEqual(
'Invalid body - "name" is not allowed to be empty'
)
})
it("should trim names", async () => {
const group = { ...structures.groups.UserGroup(), name: " group name " }
await config.api.groups.saveGroup(group)
expect(events.group.created).toBeCalledWith(
expect.objectContaining({ name: "group name" })
)
})
describe("name max length", () => {
const maxLength = 50
it(`should allow names shorter than ${maxLength} characters`, async () => {
const group = {
...structures.groups.UserGroup(),
name: structures.generator.word({ length: maxLength }),
}
await config.api.groups.saveGroup(group, { expect: 200 })
})
it(`should not allow names longer than ${maxLength} characters`, async () => {
const group = {
...structures.groups.UserGroup(),
name: structures.generator.word({ length: maxLength + 1 }),
}
const response = await config.api.groups.saveGroup(group, {
expect: 400,
})
expect(JSON.parse(response.text).message).toEqual(
'Invalid body - "name" length must be less than or equal to 50 characters long'
)
})
})
}) })
describe("update", () => { describe("update", () => {

View File

@ -7,13 +7,13 @@ export class GroupsAPI extends TestAPI {
super(config) super(config)
} }
saveGroup = (group: UserGroup) => { saveGroup = (group: UserGroup, { expect } = { expect: 200 }) => {
return this.request return this.request
.post(`/api/global/groups`) .post(`/api/global/groups`)
.send(group) .send(group)
.set(this.config.defaultHeaders()) .set(this.config.defaultHeaders())
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200) .expect(expect)
} }
deleteGroup = (id: string, rev: string) => { deleteGroup = (id: string, rev: string) => {

View File

@ -1,10 +1,25 @@
import { generator } from "@budibase/backend-core/tests"
import { db } from "@budibase/backend-core"
import { UserGroupRoles } from "@budibase/types"
export const UserGroup = () => { export const UserGroup = () => {
const appsCount = generator.integer({ min: 0, max: 3 })
const roles = Array.from({ length: appsCount }).reduce(
(p: UserGroupRoles, v) => {
return {
...p,
[db.generateAppID()]: generator.pickone(["ADMIN", "POWER", "BASIC"]),
}
},
{}
)
let group = { let group = {
apps: [], apps: [],
color: "var(--spectrum-global-color-blue-600)", color: generator.color(),
icon: "UserGroup", icon: generator.word(),
name: "New group", name: generator.word({ length: 2 }),
roles: { app_uuid1: "ADMIN", app_uuid2: "POWER" }, roles: roles,
users: [], users: [],
} }
return group return group

View File

@ -9,7 +9,7 @@
"@budibase/types": ["../types/src"], "@budibase/types": ["../types/src"],
"@budibase/backend-core": ["../backend-core/src"], "@budibase/backend-core": ["../backend-core/src"],
"@budibase/backend-core/*": ["../backend-core/*"], "@budibase/backend-core/*": ["../backend-core/*"],
"@budibase/pro": ["../../../budibase-pro/packages/pro/src"] "@budibase/pro": ["../pro/packages/pro/src"]
} }
}, },
"ts-node": { "ts-node": {

7
scripts/bootstrap.sh Executable file
View File

@ -0,0 +1,7 @@
if [ -d "packages/pro/packages" ]; then
cd packages/pro
yarn
lerna bootstrap
yarn setup
fi

20
scripts/bumpVersion.js Normal file
View File

@ -0,0 +1,20 @@
const fs = require("fs")
const semver = require("semver")
const filePath = "lerna.json"
const versionBump = process.argv[2] || "patch"
// Read and parse lerna.json file
const fileData = fs.readFileSync(filePath)
const lernaData = JSON.parse(fileData)
const currentVersion = lernaData.version
const newVersion = semver.inc(currentVersion, versionBump, "alpha")
// Update lerna.json file with new version
lernaData.version = newVersion
const updatedData = JSON.stringify(lernaData, null, 2)
fs.writeFileSync(filePath, updatedData)
console.log(`Updated version from ${currentVersion} to ${newVersion}`)

View File

@ -34,33 +34,17 @@ yarn unlink
yarn link yarn link
cd - cd -
if [ -d "../budibase-pro" ]; then if [ -d packages/pro/packages ]; then
cd ../budibase-pro pro_loaded_locally=true
echo "Bootstrapping budibase-pro" else
yarn bootstrap pro_loaded_locally=false
fi
cd packages/pro if [ $pro_loaded_locally = true ]; then
echo "Linking pro" echo "Linking pro"
cd packages/pro/packages/pro
yarn unlink yarn unlink
yarn link yarn link
echo "Linking backend-core to pro"
yarn link '@budibase/backend-core'
echo "Linking types to pro"
yarn link '@budibase/types'
echo "Linking string-templates to pro"
yarn link '@budibase/string-templates'
cd ../../../budibase
echo "Linking pro to worker"
cd packages/worker && yarn link '@budibase/pro'
cd -
echo "Linking pro to server"
cd packages/server && yarn link '@budibase/pro'
cd - cd -
fi fi
@ -79,7 +63,7 @@ if [ -d "../account-portal" ]; then
echo "Linking types to account-portal" echo "Linking types to account-portal"
yarn link "@budibase/types" yarn link "@budibase/types"
if [ -d "../../../budibase-pro" ]; then if [ $pro_loaded_locally = true ]; then
echo "Linking pro to account-portal" echo "Linking pro to account-portal"
yarn link "@budibase/pro" yarn link "@budibase/pro"
fi fi

View File

@ -1,15 +0,0 @@
#!/bin/bash
# This script is designed for building the pro repo after the backend-core build has completed.
# This ensures that any changes in core that are required by pro are done in the correct order.
set -e
# Go to parent of budibase
cd ../../../
if [[ -d "budibase-pro" ]]; then
cd budibase-pro
echo "Building pro"
yarn build
fi

View File

@ -1,39 +0,0 @@
#!/bin/bash
if [[ -z "${CI}" ]]; then
echo 'Cannot run install.sh unless in CI'
exit 0
fi
BRANCH=$1
BASE_BRANCH=$2
cd ../
echo "Cloning pro repo..."
git clone https://$PERSONAL_ACCESS_TOKEN@github.com/Budibase/budibase-pro.git
# Community forks won't be able to clone the pro repo as they can't access secrets
# Skip the rest of the installation and rely on npm version instead
# This should be ok as forks will not rely on pro changes
if [[ -d "budibase-pro" ]]; then
cd budibase-pro
if [[ -z "${BRANCH}" ]]; then
echo Using GITHUB_REF_NAME: $GITHUB_REF_NAME
export BRANCH=$GITHUB_REF_NAME
fi
# Try to checkout the matching pro branch
git checkout $BRANCH
if [[ $? == "1" ]] && [[ $BASE_BRANCH ]]; then
# There is no matching branch, try to match the base branch
git checkout $BASE_BRANCH
fi
# If neither branch exists continue with default branch 'develop'
git pull
echo "Initializing pro repo..."
yarn
fi

Some files were not shown because too many files have changed in this diff Show More