Merge remote-tracking branch 'origin/develop' into feature/portal-pending-users-section
This commit is contained in:
commit
6fa59da628
137 changed files with 1749 additions and 798 deletions
48
.github/workflows/budibase_ci.yml
vendored
48
.github/workflows/budibase_ci.yml
vendored
|
@ -16,7 +16,7 @@ on:
|
||||||
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:
|
||||||
|
@ -34,12 +34,13 @@ 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
|
||||||
|
@ -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
|
||||||
|
|
53
.github/workflows/release-develop.yml
vendored
53
.github/workflows/release-develop.yml
vendored
|
@ -3,18 +3,8 @@ concurrency: release-prerelease
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
tags:
|
||||||
- develop
|
- v*-alpha.*
|
||||||
paths:
|
|
||||||
- '.aws/**'
|
|
||||||
- '.github/**'
|
|
||||||
- 'charts/**'
|
|
||||||
- 'packages/**'
|
|
||||||
- 'scripts/**'
|
|
||||||
- 'package.json'
|
|
||||||
- 'yarn.lock'
|
|
||||||
- 'package.json'
|
|
||||||
- 'yarn.lock'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
@ -30,24 +20,39 @@ jobs:
|
||||||
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 install:pro develop
|
|
||||||
|
|
||||||
- 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 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:
|
||||||
|
@ -56,6 +61,8 @@ jobs:
|
||||||
# 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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
63
.github/workflows/release-master.yml
vendored
63
.github/workflows/release-master.yml
vendored
|
@ -3,56 +3,59 @@ 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/**'
|
|
||||||
- 'charts/**'
|
|
||||||
- 'packages/**'
|
|
||||||
- 'scripts/**'
|
|
||||||
- 'package.json'
|
|
||||||
- 'yarn.lock'
|
|
||||||
- 'package.json'
|
|
||||||
- 'yarn.lock'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
versioning:
|
tags:
|
||||||
type: choice
|
description: "Release tag"
|
||||||
description: "Versioning type: patch, minor, major"
|
|
||||||
default: patch
|
|
||||||
options:
|
|
||||||
- patch
|
|
||||||
- minor
|
|
||||||
- major
|
|
||||||
required: true
|
required: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
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:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Fail if branch is not master
|
- name: Fail if branch is not master
|
||||||
if: github.ref != 'refs/heads/master'
|
if: github.ref != 'refs/heads/master'
|
||||||
run: |
|
run: |
|
||||||
echo "Ref is not master, you must run this job from master."
|
echo "Ref is not master, you must run this job from master."
|
||||||
exit 1
|
// Change to "exit 1" when merged. Left to 0 to not fail all the pipelines and not to cause noise
|
||||||
- uses: actions/checkout@v2
|
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,10 +68,12 @@ 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"
|
||||||
|
|
||||||
|
@ -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
39
.github/workflows/tag-prerelease.yml
vendored
Normal 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
49
.github/workflows/tag-release.yml
vendored
Normal 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
3
.gitmodules
vendored
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "packages/pro"]
|
||||||
|
path = packages/pro
|
||||||
|
url = git@github.com:Budibase/budibase-pro.git
|
4
.husky/post-checkout
Executable file
4
.husky/post-checkout
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
# .husky/post-checkout
|
||||||
|
# ...
|
||||||
|
|
||||||
|
git config submodule.recurse true
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ Install instructions [here](https://brew.sh/)
|
||||||
`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.
|
||||||
|
|
|
@ -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,6 +84,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
|
||||||
|
|
||||||
### 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -222,9 +222,9 @@ http {
|
||||||
rewrite ^/files/signed/(.*)$ /$1 break;
|
rewrite ^/files/signed/(.*)$ /$1 break;
|
||||||
}
|
}
|
||||||
|
|
||||||
client_header_timeout 60;
|
client_header_timeout 120;
|
||||||
client_body_timeout 60;
|
client_body_timeout 120;
|
||||||
keepalive_timeout 60;
|
keepalive_timeout 120;
|
||||||
|
|
||||||
# gzip
|
# gzip
|
||||||
gzip on;
|
gzip on;
|
||||||
|
|
|
@ -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
|
||||||
|
|
18
lerna.json
18
lerna.json
|
@ -1,8 +1,22 @@
|
||||||
{
|
{
|
||||||
"version": "2.5.6-alpha.44",
|
"version": "2.6.8-alpha.10",
|
||||||
"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": [
|
||||||
|
|
36
package.json
36
package.json
|
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.5.6-alpha.44",
|
"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.6-alpha.44",
|
"@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",
|
||||||
|
|
|
@ -47,7 +47,7 @@ async function put(
|
||||||
type: LockType.TRY_ONCE,
|
type: LockType.TRY_ONCE,
|
||||||
name: LockName.PERSIST_WRITETHROUGH,
|
name: LockName.PERSIST_WRITETHROUGH,
|
||||||
resource: key,
|
resource: key,
|
||||||
ttl: 1000,
|
ttl: 15000,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const writeDb = async (toWrite: any) => {
|
const writeDb = async (toWrite: any) => {
|
||||||
|
@ -71,6 +71,7 @@ async function put(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!lockResponse.executed) {
|
if (!lockResponse.executed) {
|
||||||
logWarn(`Ignoring redlock conflict in write-through cache`)
|
logWarn(`Ignoring redlock conflict in write-through cache`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -434,7 +434,7 @@ export class QueryBuilder<T> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.#query.empty) {
|
if (this.#query.empty) {
|
||||||
build(this.#query.empty, (key: string) => `!${key}:["" TO *]`)
|
build(this.#query.empty, (key: string) => `(*:* -${key}:["" TO *])`)
|
||||||
}
|
}
|
||||||
if (this.#query.notEmpty) {
|
if (this.#query.notEmpty) {
|
||||||
build(this.#query.notEmpty, (key: string) => `${key}:["" TO *]`)
|
build(this.#query.notEmpty, (key: string) => `${key}:["" TO *]`)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -15,17 +15,18 @@ import {
|
||||||
|
|
||||||
async function planChanged(
|
async function planChanged(
|
||||||
account: Account,
|
account: Account,
|
||||||
from: PlanType,
|
opts: {
|
||||||
to: PlanType,
|
from: PlanType
|
||||||
quantity: number | undefined,
|
to: PlanType
|
||||||
duration: PriceDuration | undefined
|
fromQuantity: number | undefined
|
||||||
|
toQuantity: number | undefined
|
||||||
|
fromDuration: PriceDuration | undefined
|
||||||
|
toDuration: PriceDuration | undefined
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
const properties: LicensePlanChangedEvent = {
|
const properties: LicensePlanChangedEvent = {
|
||||||
accountId: account.accountId,
|
accountId: account.accountId,
|
||||||
to,
|
...opts,
|
||||||
from,
|
|
||||||
quantity,
|
|
||||||
duration,
|
|
||||||
}
|
}
|
||||||
await publishEvent(Event.LICENSE_PLAN_CHANGED, properties)
|
await publishEvent(Event.LICENSE_PLAN_CHANGED, properties)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -5,6 +5,7 @@ import * as db from "../../db"
|
||||||
import { Header } from "../../constants"
|
import { Header } from "../../constants"
|
||||||
import { newid } from "../../utils"
|
import { newid } from "../../utils"
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
|
import { BBContext } from "@budibase/types"
|
||||||
|
|
||||||
describe("utils", () => {
|
describe("utils", () => {
|
||||||
const config = new DBTestConfiguration()
|
const config = new DBTestConfiguration()
|
||||||
|
@ -106,4 +107,85 @@ describe("utils", () => {
|
||||||
expect(actual).toBe(undefined)
|
expect(actual).toBe(undefined)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("isServingBuilder", () => {
|
||||||
|
let ctx: BBContext
|
||||||
|
|
||||||
|
const expectResult = (result: boolean) =>
|
||||||
|
expect(utils.isServingBuilder(ctx)).toBe(result)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
ctx = structures.koa.newContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true if current path is in builder", async () => {
|
||||||
|
ctx.path = "/builder/app/app_"
|
||||||
|
expectResult(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false if current path doesn't have '/' suffix", async () => {
|
||||||
|
ctx.path = "/builder/app"
|
||||||
|
expectResult(false)
|
||||||
|
|
||||||
|
ctx.path = "/xx"
|
||||||
|
expectResult(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("isServingBuilderPreview", () => {
|
||||||
|
let ctx: BBContext
|
||||||
|
|
||||||
|
const expectResult = (result: boolean) =>
|
||||||
|
expect(utils.isServingBuilderPreview(ctx)).toBe(result)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
ctx = structures.koa.newContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true if current path is in builder preview", async () => {
|
||||||
|
ctx.path = "/app/preview/xx"
|
||||||
|
expectResult(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false if current path is not in builder preview", async () => {
|
||||||
|
ctx.path = "/builder"
|
||||||
|
expectResult(false)
|
||||||
|
|
||||||
|
ctx.path = "/xx"
|
||||||
|
expectResult(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("isPublicAPIRequest", () => {
|
||||||
|
let ctx: BBContext
|
||||||
|
|
||||||
|
const expectResult = (result: boolean) =>
|
||||||
|
expect(utils.isPublicApiRequest(ctx)).toBe(result)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
ctx = structures.koa.newContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true if current path remains to public API", async () => {
|
||||||
|
ctx.path = "/api/public/v1/invoices"
|
||||||
|
expectResult(true)
|
||||||
|
|
||||||
|
ctx.path = "/api/public/v1"
|
||||||
|
expectResult(true)
|
||||||
|
|
||||||
|
ctx.path = "/api/public/v2"
|
||||||
|
expectResult(true)
|
||||||
|
|
||||||
|
ctx.path = "/api/public/v21"
|
||||||
|
expectResult(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false if current path doesn't remain to public API", async () => {
|
||||||
|
ctx.path = "/api/public"
|
||||||
|
expectResult(false)
|
||||||
|
|
||||||
|
ctx.path = "/xx"
|
||||||
|
expectResult(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
import { getAllApps, queryGlobalView } from "../db"
|
import { getAllApps } from "../db"
|
||||||
import {
|
import { Header, MAX_VALID_DATE, DocumentType, SEPARATOR } from "../constants"
|
||||||
Header,
|
|
||||||
MAX_VALID_DATE,
|
|
||||||
DocumentType,
|
|
||||||
SEPARATOR,
|
|
||||||
ViewName,
|
|
||||||
} from "../constants"
|
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import * as tenancy from "../tenancy"
|
import * as tenancy from "../tenancy"
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
|
@ -23,7 +17,9 @@ const APP_PREFIX = DocumentType.APP + SEPARATOR
|
||||||
const PROD_APP_PREFIX = "/app/"
|
const PROD_APP_PREFIX = "/app/"
|
||||||
|
|
||||||
const BUILDER_PREVIEW_PATH = "/app/preview"
|
const BUILDER_PREVIEW_PATH = "/app/preview"
|
||||||
const BUILDER_REFERER_PREFIX = "/builder/app/"
|
const BUILDER_PREFIX = "/builder"
|
||||||
|
const BUILDER_APP_PREFIX = `${BUILDER_PREFIX}/app/`
|
||||||
|
const PUBLIC_API_PREFIX = "/api/public/v"
|
||||||
|
|
||||||
function confirmAppId(possibleAppId: string | undefined) {
|
function confirmAppId(possibleAppId: string | undefined) {
|
||||||
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
||||||
|
@ -69,6 +65,18 @@ export function isServingApp(ctx: Ctx) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isServingBuilder(ctx: Ctx): boolean {
|
||||||
|
return ctx.path.startsWith(BUILDER_APP_PREFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isServingBuilderPreview(ctx: Ctx): boolean {
|
||||||
|
return ctx.path.startsWith(BUILDER_PREVIEW_PATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPublicApiRequest(ctx: Ctx): boolean {
|
||||||
|
return ctx.path.startsWith(PUBLIC_API_PREFIX)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a request tries to find the appId, which can be located in various places
|
* Given a request tries to find the appId, which can be located in various places
|
||||||
* @param {object} ctx The main request body to look through.
|
* @param {object} ctx The main request body to look through.
|
||||||
|
@ -110,7 +118,7 @@ export async function getAppIdFromCtx(ctx: Ctx) {
|
||||||
// make sure this is performed after prod app url resolution, in case the
|
// make sure this is performed after prod app url resolution, in case the
|
||||||
// referer header is present from a builder redirect
|
// referer header is present from a builder redirect
|
||||||
const referer = ctx.request.headers.referer
|
const referer = ctx.request.headers.referer
|
||||||
if (!appId && referer?.includes(BUILDER_REFERER_PREFIX)) {
|
if (!appId && referer?.includes(BUILDER_APP_PREFIX)) {
|
||||||
const refererId = parseAppIdFromUrl(ctx.request.headers.referer)
|
const refererId = parseAppIdFromUrl(ctx.request.headers.referer)
|
||||||
appId = confirmAppId(refererId)
|
appId = confirmAppId(refererId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ export const plan = (type: PlanType = PlanType.FREE): PurchasedPlan => {
|
||||||
type,
|
type,
|
||||||
usesInvoicing: false,
|
usesInvoicing: false,
|
||||||
model: PlanModel.PER_USER,
|
model: PlanModel.PER_USER,
|
||||||
price: price(),
|
price: type !== PlanType.FREE ? price() : undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,6 @@
|
||||||
"@budibase/types": ["../types/src"]
|
"@budibase/types": ["../types/src"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"references": [
|
|
||||||
{ "path": "../types" }
|
"exclude": ["node_modules", "dist"]
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"dist",
|
|
||||||
]
|
|
||||||
}
|
}
|
|
@ -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.6-alpha.44",
|
"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.6-alpha.44",
|
"@budibase/shared-core": "0.0.1",
|
||||||
"@budibase/string-templates": "2.5.6-alpha.44",
|
"@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",
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,14 @@
|
||||||
export let ignoreTimezones = false
|
export let ignoreTimezones = false
|
||||||
export let time24hr = false
|
export let time24hr = false
|
||||||
export let range = false
|
export let range = false
|
||||||
|
export let flatpickr
|
||||||
|
export let useKeyboardShortcuts = true
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const flatpickrId = `${uuid()}-wrapper`
|
const flatpickrId = `${uuid()}-wrapper`
|
||||||
|
|
||||||
let open = false
|
let open = false
|
||||||
let flatpickr, flatpickrOptions
|
let flatpickrOptions
|
||||||
|
|
||||||
// Another classic flatpickr issue. Errors were randomly being thrown due to
|
// Another classic flatpickr issue. Errors were randomly being thrown due to
|
||||||
// flatpickr internal code. Making sure that "destroy" is a valid function
|
// flatpickr internal code. Making sure that "destroy" is a valid function
|
||||||
|
@ -59,6 +63,8 @@
|
||||||
dispatch("change", timestamp.toISOString())
|
dispatch("change", timestamp.toISOString())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onOpen: () => dispatch("open"),
|
||||||
|
onClose: () => dispatch("close"),
|
||||||
}
|
}
|
||||||
|
|
||||||
$: redrawOptions = {
|
$: redrawOptions = {
|
||||||
|
@ -113,12 +119,16 @@
|
||||||
|
|
||||||
const onOpen = () => {
|
const onOpen = () => {
|
||||||
open = true
|
open = true
|
||||||
|
if (useKeyboardShortcuts) {
|
||||||
document.addEventListener("keyup", clearDateOnBackspace)
|
document.addEventListener("keyup", clearDateOnBackspace)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
open = false
|
open = false
|
||||||
|
if (useKeyboardShortcuts) {
|
||||||
document.removeEventListener("keyup", clearDateOnBackspace)
|
document.removeEventListener("keyup", clearDateOnBackspace)
|
||||||
|
}
|
||||||
|
|
||||||
// Manually blur all input fields since flatpickr creates a second
|
// Manually blur all input fields since flatpickr creates a second
|
||||||
// duplicate input field.
|
// duplicate input field.
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
1
packages/builder/assets/make.svg
Normal file
1
packages/builder/assets/make.svg
Normal 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 |
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "2.5.6-alpha.44",
|
"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.6-alpha.44",
|
"@budibase/bbui": "0.0.1",
|
||||||
"@budibase/frontend-core": "2.5.6-alpha.44",
|
"@budibase/frontend-core": "0.0.1",
|
||||||
"@budibase/shared-core": "2.5.6-alpha.44",
|
"@budibase/shared-core": "0.0.1",
|
||||||
"@budibase/string-templates": "2.5.6-alpha.44",
|
"@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",
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 },
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -61,11 +61,63 @@
|
||||||
$: isTrigger = block?.type === "TRIGGER"
|
$: isTrigger = block?.type === "TRIGGER"
|
||||||
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
|
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO - Remove after November 2023
|
||||||
|
* *******************************
|
||||||
|
* Code added to provide backwards compatibility between Values 1,2,3,4,5
|
||||||
|
* and the new JSON body.
|
||||||
|
*/
|
||||||
|
let deprecatedSchemaProperties
|
||||||
|
$: {
|
||||||
|
if (block?.stepId === "integromat" || block?.stepId === "zapier") {
|
||||||
|
deprecatedSchemaProperties = schemaProperties.filter(
|
||||||
|
prop => !prop[0].startsWith("value")
|
||||||
|
)
|
||||||
|
if (!deprecatedSchemaProperties.map(entry => entry[0]).includes("body")) {
|
||||||
|
deprecatedSchemaProperties.push([
|
||||||
|
"body",
|
||||||
|
{
|
||||||
|
title: "Payload",
|
||||||
|
type: "json",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deprecatedSchemaProperties = schemaProperties
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/****************************************************/
|
||||||
|
|
||||||
const getInputData = (testData, blockInputs) => {
|
const getInputData = (testData, blockInputs) => {
|
||||||
let newInputData = testData || blockInputs
|
let newInputData = testData || blockInputs
|
||||||
if (block.event === "app:trigger" && !newInputData?.fields) {
|
if (block.event === "app:trigger" && !newInputData?.fields) {
|
||||||
newInputData = cloneDeep(blockInputs)
|
newInputData = cloneDeep(blockInputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO - Remove after November 2023
|
||||||
|
* *******************************
|
||||||
|
* Code added to provide backwards compatibility between Values 1,2,3,4,5
|
||||||
|
* and the new JSON body.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
(block?.stepId === "integromat" || block?.stepId === "zapier") &&
|
||||||
|
!newInputData?.body?.value
|
||||||
|
) {
|
||||||
|
let deprecatedValues = {
|
||||||
|
...newInputData,
|
||||||
|
}
|
||||||
|
delete deprecatedValues.url
|
||||||
|
delete deprecatedValues.body
|
||||||
|
newInputData = {
|
||||||
|
url: newInputData.url,
|
||||||
|
body: {
|
||||||
|
value: JSON.stringify(deprecatedValues),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**********************************/
|
||||||
|
|
||||||
inputData = newInputData
|
inputData = newInputData
|
||||||
setDefaultEnumValues()
|
setDefaultEnumValues()
|
||||||
}
|
}
|
||||||
|
@ -239,7 +291,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
{#each schemaProperties as [key, value]}
|
{#each deprecatedSchemaProperties as [key, value]}
|
||||||
<div class="block-field">
|
<div class="block-field">
|
||||||
{#if key !== "fields"}
|
{#if key !== "fields"}
|
||||||
<Label
|
<Label
|
||||||
|
@ -256,6 +308,28 @@
|
||||||
options={value.enum}
|
options={value.enum}
|
||||||
getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)}
|
getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)}
|
||||||
/>
|
/>
|
||||||
|
{:else if value.type === "json"}
|
||||||
|
<Editor
|
||||||
|
editorHeight="250"
|
||||||
|
editorWidth="448"
|
||||||
|
mode="json"
|
||||||
|
value={inputData[key]?.value}
|
||||||
|
on:change={e => {
|
||||||
|
/**
|
||||||
|
* TODO - Remove after November 2023
|
||||||
|
* *******************************
|
||||||
|
* Code added to provide backwards compatibility between Values 1,2,3,4,5
|
||||||
|
* and the new JSON body.
|
||||||
|
*/
|
||||||
|
delete inputData.value1
|
||||||
|
delete inputData.value2
|
||||||
|
delete inputData.value3
|
||||||
|
delete inputData.value4
|
||||||
|
delete inputData.value5
|
||||||
|
/***********************/
|
||||||
|
onChange(e, key)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{:else if value.customType === "column"}
|
{:else if value.customType === "column"}
|
||||||
<Select
|
<Select
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
$: 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">
|
||||||
|
<ExportButton
|
||||||
{disabled}
|
{disabled}
|
||||||
view={$tableId}
|
view={$tableId}
|
||||||
filters={$filter}
|
filters={$filter}
|
||||||
|
@ -18,4 +19,11 @@
|
||||||
sortOrder: $sort.order,
|
sortOrder: $sort.order,
|
||||||
}}
|
}}
|
||||||
selectedRows={selectedRowArray}
|
selectedRows={selectedRowArray}
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
span {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -42,16 +42,7 @@ export const parseFile = e => {
|
||||||
|
|
||||||
reader.addEventListener("load", function (e) {
|
reader.addEventListener("load", function (e) {
|
||||||
const fileData = e.target.result
|
const fileData = e.target.result
|
||||||
|
if (file.type?.includes("json")) {
|
||||||
if (file.type === "text/csv") {
|
|
||||||
API.csvToJson(fileData)
|
|
||||||
.then(rows => {
|
|
||||||
resolveRows(rows)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
reject("can't convert csv to json")
|
|
||||||
})
|
|
||||||
} else if (file.type === "application/json") {
|
|
||||||
const parsedFileData = JSON.parse(fileData)
|
const parsedFileData = JSON.parse(fileData)
|
||||||
|
|
||||||
if (Array.isArray(parsedFileData)) {
|
if (Array.isArray(parsedFileData)) {
|
||||||
|
@ -62,7 +53,13 @@ export const parseFile = e => {
|
||||||
reject("invalid json format")
|
reject("invalid json format")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reject("invalid file type")
|
API.csvToJson(fileData)
|
||||||
|
.then(rows => {
|
||||||
|
resolveRows(rows)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
reject("cannot parse csv")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
export let tab = true
|
export let tab = true
|
||||||
export let mode
|
export let mode
|
||||||
export let editorHeight = 500
|
export let editorHeight = 500
|
||||||
|
export let editorWidth = 640
|
||||||
// export let parameters = []
|
// export let parameters = []
|
||||||
|
|
||||||
let width
|
let width
|
||||||
|
@ -169,7 +170,9 @@
|
||||||
{#if label}
|
{#if label}
|
||||||
<Label small>{label}</Label>
|
<Label small>{label}</Label>
|
||||||
{/if}
|
{/if}
|
||||||
<div style={`--code-mirror-height: ${editorHeight}px`}>
|
<div
|
||||||
|
style={`--code-mirror-height: ${editorHeight}px; --code-mirror-width: ${editorWidth}px;`}
|
||||||
|
>
|
||||||
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -183,6 +186,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
div :global(.CodeMirror) {
|
div :global(.CodeMirror) {
|
||||||
|
width: var(--code-mirror-width) !important;
|
||||||
height: var(--code-mirror-height) !important;
|
height: var(--code-mirror-height) !important;
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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) {
|
||||||
|
if (error.message) {
|
||||||
|
notifications.error(error.message)
|
||||||
|
} else {
|
||||||
notifications.error(`Failed to save user group`)
|
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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,7 +293,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.
|
||||||
`}
|
`}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ export function createUsersStore() {
|
||||||
inviteCode,
|
inviteCode,
|
||||||
password,
|
password,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName: !lastName?.trim() ? undefined : lastName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,5 @@
|
||||||
},
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"require": ["tsconfig-paths/register"]
|
"require": ["tsconfig-paths/register"]
|
||||||
},
|
}
|
||||||
"references": [
|
|
||||||
{ "path": "../types" },
|
|
||||||
{ "path": "../backend-core" },
|
|
||||||
]
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "2.5.6-alpha.44",
|
"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.6-alpha.44",
|
"@budibase/backend-core": "0.0.1",
|
||||||
"@budibase/string-templates": "2.5.6-alpha.44",
|
"@budibase/string-templates": "0.0.1",
|
||||||
"@budibase/types": "2.5.6-alpha.44",
|
"@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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "2.5.6-alpha.44",
|
"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.6-alpha.44",
|
"@budibase/bbui": "0.0.1",
|
||||||
"@budibase/frontend-core": "2.5.6-alpha.44",
|
"@budibase/frontend-core": "0.0.1",
|
||||||
"@budibase/shared-core": "2.5.6-alpha.44",
|
"@budibase/shared-core": "0.0.1",
|
||||||
"@budibase/string-templates": "2.5.6-alpha.44",
|
"@budibase/string-templates": "0.0.1",
|
||||||
"@budibase/types": "2.5.6-alpha.44",
|
"@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",
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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?",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "2.5.6-alpha.44",
|
"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.6-alpha.44",
|
"@budibase/bbui": "0.0.1",
|
||||||
"@budibase/shared-core": "2.5.6-alpha.44",
|
"@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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
$: readonly =
|
$: readonly =
|
||||||
column.schema.autocolumn ||
|
column.schema.autocolumn ||
|
||||||
column.schema.disabled ||
|
column.schema.disabled ||
|
||||||
|
column.schema.type === "formula" ||
|
||||||
(!$config.allowEditRows && row._id)
|
(!$config.allowEditRows && row._id)
|
||||||
|
|
||||||
// Register this cell API if the row is focused
|
// Register this cell API if the row is focused
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import { CoreDatePicker, Icon } from "@budibase/bbui"
|
import { CoreDatePicker, Icon } from "@budibase/bbui"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let schema
|
export let schema
|
||||||
export let onChange
|
export let onChange
|
||||||
export let focused = false
|
export let focused = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
|
export let api
|
||||||
|
|
||||||
|
let flatpickr
|
||||||
|
let isOpen
|
||||||
|
|
||||||
// adding the 0- will turn a string like 00:00:00 into a valid ISO
|
// adding the 0- will turn a string like 00:00:00 into a valid ISO
|
||||||
// date, but will make actual ISO dates invalid
|
// date, but will make actual ISO dates invalid
|
||||||
|
@ -19,6 +24,26 @@
|
||||||
? "MMM D YYYY"
|
? "MMM D YYYY"
|
||||||
: "MMM D YYYY, HH:mm"
|
: "MMM D YYYY, HH:mm"
|
||||||
$: editable = focused && !readonly
|
$: editable = focused && !readonly
|
||||||
|
|
||||||
|
// Ensure we close flatpickr when unselected
|
||||||
|
$: {
|
||||||
|
if (!focused) {
|
||||||
|
flatpickr?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onKeyDown = () => {
|
||||||
|
return isOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
api = {
|
||||||
|
onKeyDown,
|
||||||
|
focus: () => flatpickr?.open(),
|
||||||
|
blur: () => flatpickr?.close(),
|
||||||
|
isActive: () => isOpen,
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -42,6 +67,10 @@
|
||||||
{timeOnly}
|
{timeOnly}
|
||||||
time24hr
|
time24hr
|
||||||
ignoreTimezones={schema.ignoreTimezones}
|
ignoreTimezones={schema.ignoreTimezones}
|
||||||
|
bind:flatpickr
|
||||||
|
on:open={() => (isOpen = true)}
|
||||||
|
on:close={() => (isOpen = false)}
|
||||||
|
useKeyboardShortcuts={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 />
|
||||||
|
{#if allowAddRows}
|
||||||
<NewRow />
|
<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: "";
|
||||||
|
|
|
@ -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,6 +29,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</GridScrollWrapper>
|
</GridScrollWrapper>
|
||||||
|
{#if $config.allowAddColumns}
|
||||||
<div
|
<div
|
||||||
class="add"
|
class="add"
|
||||||
style="left:{left}px"
|
style="left:{left}px"
|
||||||
|
@ -30,6 +37,7 @@
|
||||||
>
|
>
|
||||||
<Icon name="Add" />
|
<Icon name="Add" />
|
||||||
</div>
|
</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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)) {
|
||||||
|
if ($config.allowAddRows) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
dispatch("add-row-inline")
|
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,8 +91,10 @@
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "Enter":
|
case "Enter":
|
||||||
|
if ($config.allowAddRows) {
|
||||||
dispatch("add-row-inline")
|
dispatch("add-row-inline")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case "ArrowLeft":
|
case "ArrowLeft":
|
||||||
|
@ -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":
|
||||||
|
if ($config.allowDeleteRows) {
|
||||||
toggleSelectRow()
|
toggleSelectRow()
|
||||||
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
startEnteringValue(e.key, e.which)
|
startEnteringValue(e.key, e.which)
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { clickOutside, Menu, MenuItem, notifications } from "@budibase/bbui"
|
import {
|
||||||
|
clickOutside,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Helpers,
|
||||||
|
notifications,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
import { NewRowID } from "../lib/constants"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
focusedRow,
|
focusedRow,
|
||||||
|
@ -14,9 +21,11 @@
|
||||||
clipboard,
|
clipboard,
|
||||||
dispatch,
|
dispatch,
|
||||||
focusedCellAPI,
|
focusedCellAPI,
|
||||||
|
focusedRowId,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
$: style = makeStyle($menu)
|
$: style = makeStyle($menu)
|
||||||
|
$: isNewRow = $focusedRowId === NewRowID
|
||||||
|
|
||||||
const makeStyle = menu => {
|
const makeStyle = menu => {
|
||||||
return `left:${menu.left}px; top:${menu.top}px;`
|
return `left:${menu.left}px; top:${menu.top}px;`
|
||||||
|
@ -36,6 +45,11 @@
|
||||||
$focusedCellId = `${newRow._id}-${column}`
|
$focusedCellId = `${newRow._id}-${column}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const copyToClipboard = async value => {
|
||||||
|
await Helpers.copyToClipboard(value)
|
||||||
|
notifications.success("Copied to clipboard")
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $menu.visible}
|
{#if $menu.visible}
|
||||||
|
@ -58,22 +72,38 @@
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="Maximize"
|
icon="Maximize"
|
||||||
disabled={!$config.allowEditRows}
|
disabled={isNewRow || !$config.allowEditRows}
|
||||||
on:click={() => dispatch("edit-row", $focusedRow)}
|
on:click={() => dispatch("edit-row", $focusedRow)}
|
||||||
on:click={menu.actions.close}
|
on:click={menu.actions.close}
|
||||||
>
|
>
|
||||||
Edit row in modal
|
Edit row in modal
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon="Copy"
|
||||||
|
disabled={isNewRow || !$focusedRow?._id}
|
||||||
|
on:click={() => copyToClipboard($focusedRow?._id)}
|
||||||
|
on:click={menu.actions.close}
|
||||||
|
>
|
||||||
|
Copy row _id
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon="Copy"
|
||||||
|
disabled={isNewRow || !$focusedRow?._rev}
|
||||||
|
on:click={() => copyToClipboard($focusedRow?._rev)}
|
||||||
|
on:click={menu.actions.close}
|
||||||
|
>
|
||||||
|
Copy row _rev
|
||||||
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="Duplicate"
|
icon="Duplicate"
|
||||||
disabled={!$config.allowAddRows}
|
disabled={isNewRow || !$config.allowAddRows}
|
||||||
on:click={duplicate}
|
on:click={duplicate}
|
||||||
>
|
>
|
||||||
Duplicate row
|
Duplicate row
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="Delete"
|
icon="Delete"
|
||||||
disabled={!$config.allowDeleteRows}
|
disabled={isNewRow || !$config.allowDeleteRows}
|
||||||
on:click={deleteRow}
|
on:click={deleteRow}
|
||||||
>
|
>
|
||||||
Delete row
|
Delete row
|
||||||
|
|
|
@ -338,15 +338,11 @@ export const deriveStores = context => {
|
||||||
...state,
|
...state,
|
||||||
[rowId]: true,
|
[rowId]: true,
|
||||||
}))
|
}))
|
||||||
const newRow = { ...row, ...get(rowChangeCache)[rowId] }
|
const saved = await API.saveRow({ ...row, ...get(rowChangeCache)[rowId] })
|
||||||
const saved = await API.saveRow(newRow)
|
|
||||||
|
|
||||||
// Update state after a successful change
|
// Update state after a successful change
|
||||||
rows.update(state => {
|
rows.update(state => {
|
||||||
state[index] = {
|
state[index] = saved
|
||||||
...newRow,
|
|
||||||
_rev: saved._rev,
|
|
||||||
}
|
|
||||||
return state.slice()
|
return state.slice()
|
||||||
})
|
})
|
||||||
rowChangeCache.update(state => {
|
rowChangeCache.update(state => {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
50
packages/frontend-core/src/fetch/GroupUserFetch.js
Normal file
50
packages/frontend-core/src/fetch/GroupUserFetch.js
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
1
packages/pro
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 14345384f7a6755d1e2de327104741e0f208f55d
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "2.5.6-alpha.44",
|
"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",
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.5.6-alpha.44",
|
"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.6-alpha.44",
|
"@budibase/backend-core": "0.0.1",
|
||||||
"@budibase/client": "2.5.6-alpha.44",
|
"@budibase/client": "0.0.1",
|
||||||
"@budibase/pro": "2.5.6-alpha.44",
|
"@budibase/pro": "0.0.1",
|
||||||
"@budibase/shared-core": "2.5.6-alpha.44",
|
"@budibase/shared-core": "0.0.1",
|
||||||
"@budibase/string-templates": "2.5.6-alpha.44",
|
"@budibase/string-templates": "0.0.1",
|
||||||
"@budibase/types": "2.5.6-alpha.44",
|
"@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",
|
||||||
|
|
|
@ -37,7 +37,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
const { cleanExportRows } = require("./utils")
|
import { cleanExportRows } from "./utils"
|
||||||
|
|
||||||
const CALCULATION_TYPES = {
|
const CALCULATION_TYPES = {
|
||||||
SUM: "sum",
|
SUM: "sum",
|
||||||
|
@ -391,6 +391,9 @@ export async function exportRows(ctx: UserCtx) {
|
||||||
const table = await db.get(ctx.params.tableId)
|
const table = await db.get(ctx.params.tableId)
|
||||||
const rowIds = ctx.request.body.rows
|
const rowIds = ctx.request.body.rows
|
||||||
let format = ctx.query.format
|
let format = ctx.query.format
|
||||||
|
if (typeof format !== "string") {
|
||||||
|
ctx.throw(400, "Format parameter is not valid")
|
||||||
|
}
|
||||||
const { columns, query } = ctx.request.body
|
const { columns, query } = ctx.request.body
|
||||||
|
|
||||||
let result
|
let result
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -137,8 +137,8 @@ export function cleanExportRows(
|
||||||
delete schema[column]
|
delete schema[column]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Intended to avoid 'undefined' in export
|
|
||||||
if (format === Format.CSV) {
|
if (format === Format.CSV) {
|
||||||
|
// Intended to append empty values in export
|
||||||
const schemaKeys = Object.keys(schema)
|
const schemaKeys = Object.keys(schema)
|
||||||
for (let key of schemaKeys) {
|
for (let key of schemaKeys) {
|
||||||
if (columns?.length && columns.indexOf(key) > 0) {
|
if (columns?.length && columns.indexOf(key) > 0) {
|
||||||
|
@ -146,7 +146,7 @@ export function cleanExportRows(
|
||||||
}
|
}
|
||||||
for (let row of cleanRows) {
|
for (let row of cleanRows) {
|
||||||
if (row[key] == null) {
|
if (row[key] == null) {
|
||||||
row[key] = ""
|
row[key] = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { getDatasourceParams } from "../../../db/utils"
|
||||||
import { context, events } from "@budibase/backend-core"
|
import { context, events } from "@budibase/backend-core"
|
||||||
import { Table, UserCtx } from "@budibase/types"
|
import { Table, UserCtx } from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import csv from "csvtojson"
|
import { jsonFromCsvString } from "../../../utilities/csv"
|
||||||
|
|
||||||
function pickApi({ tableId, table }: { tableId?: string; table?: Table }) {
|
function pickApi({ tableId, table }: { tableId?: string; table?: Table }) {
|
||||||
if (table && !tableId) {
|
if (table && !tableId) {
|
||||||
|
@ -104,7 +104,7 @@ export async function bulkImport(ctx: UserCtx) {
|
||||||
export async function csvToJson(ctx: UserCtx) {
|
export async function csvToJson(ctx: UserCtx) {
|
||||||
const { csvString } = ctx.request.body
|
const { csvString } = ctx.request.body
|
||||||
|
|
||||||
const result = await csv().fromString(csvString)
|
const result = await jsonFromCsvString(csvString)
|
||||||
|
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = result
|
ctx.body = result
|
||||||
|
|
|
@ -10,7 +10,9 @@ export function csv(headers: string[], rows: Row[]) {
|
||||||
val =
|
val =
|
||||||
typeof val === "object" && !(val instanceof Date)
|
typeof val === "object" && !(val instanceof Date)
|
||||||
? `"${JSON.stringify(val).replace(/"/g, "'")}"`
|
? `"${JSON.stringify(val).replace(/"/g, "'")}"`
|
||||||
: `"${val}"`
|
: val !== undefined
|
||||||
|
? `"${val}"`
|
||||||
|
: ""
|
||||||
return val.trim()
|
return val.trim()
|
||||||
})
|
})
|
||||||
.join(",")}`
|
.join(",")}`
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -105,7 +105,7 @@ describe("internal search", () => {
|
||||||
"column": "",
|
"column": "",
|
||||||
},
|
},
|
||||||
}, PARAMS)
|
}, PARAMS)
|
||||||
checkLucene(response, `*:* AND !column:["" TO *]`, PARAMS)
|
checkLucene(response, `*:* AND (*:* -column:["" TO *])`, PARAMS)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("test notEmpty query", async () => {
|
it("test notEmpty query", async () => {
|
||||||
|
|
|
@ -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([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -25,6 +26,10 @@ export const definition: AutomationStepSchema = {
|
||||||
type: AutomationIOType.STRING,
|
type: AutomationIOType.STRING,
|
||||||
title: "Webhook URL",
|
title: "Webhook URL",
|
||||||
},
|
},
|
||||||
|
body: {
|
||||||
|
type: AutomationIOType.JSON,
|
||||||
|
title: "Payload",
|
||||||
|
},
|
||||||
value1: {
|
value1: {
|
||||||
type: AutomationIOType.STRING,
|
type: AutomationIOType.STRING,
|
||||||
title: "Input Value 1",
|
title: "Input Value 1",
|
||||||
|
@ -69,7 +74,19 @@ export const definition: AutomationStepSchema = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function run({ inputs }: AutomationStepInput) {
|
export async function run({ inputs }: AutomationStepInput) {
|
||||||
const { url, value1, value2, value3, value4, value5 } = inputs
|
//TODO - Remove deprecated values 1,2,3,4,5 after November 2023
|
||||||
|
const { url, value1, value2, value3, value4, value5, body } = inputs
|
||||||
|
|
||||||
|
let payload = {}
|
||||||
|
try {
|
||||||
|
payload = body?.value ? JSON.parse(body?.value) : {}
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
httpStatus: 400,
|
||||||
|
response: "Invalid payload JSON",
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!url?.trim()?.length) {
|
if (!url?.trim()?.length) {
|
||||||
return {
|
return {
|
||||||
|
@ -88,6 +105,7 @@ export async function run({ inputs }: AutomationStepInput) {
|
||||||
value3,
|
value3,
|
||||||
value4,
|
value4,
|
||||||
value5,
|
value5,
|
||||||
|
...payload,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
|
@ -24,6 +24,10 @@ export const definition: AutomationStepSchema = {
|
||||||
type: AutomationIOType.STRING,
|
type: AutomationIOType.STRING,
|
||||||
title: "Webhook URL",
|
title: "Webhook URL",
|
||||||
},
|
},
|
||||||
|
body: {
|
||||||
|
type: AutomationIOType.JSON,
|
||||||
|
title: "Payload",
|
||||||
|
},
|
||||||
value1: {
|
value1: {
|
||||||
type: AutomationIOType.STRING,
|
type: AutomationIOType.STRING,
|
||||||
title: "Payload Value 1",
|
title: "Payload Value 1",
|
||||||
|
@ -63,7 +67,19 @@ export const definition: AutomationStepSchema = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function run({ inputs }: AutomationStepInput) {
|
export async function run({ inputs }: AutomationStepInput) {
|
||||||
const { url, value1, value2, value3, value4, value5 } = inputs
|
//TODO - Remove deprecated values 1,2,3,4,5 after November 2023
|
||||||
|
const { url, value1, value2, value3, value4, value5, body } = inputs
|
||||||
|
|
||||||
|
let payload = {}
|
||||||
|
try {
|
||||||
|
payload = body?.value ? JSON.parse(body?.value) : {}
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
httpStatus: 400,
|
||||||
|
response: "Invalid payload JSON",
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!url?.trim()?.length) {
|
if (!url?.trim()?.length) {
|
||||||
return {
|
return {
|
||||||
|
@ -85,6 +101,7 @@ export async function run({ inputs }: AutomationStepInput) {
|
||||||
value3,
|
value3,
|
||||||
value4,
|
value4,
|
||||||
value5,
|
value5,
|
||||||
|
...payload,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
54
packages/server/src/automations/tests/make.spec.ts
Normal file
54
packages/server/src/automations/tests/make.spec.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { getConfig, afterAll, runStep, actions } from "./utilities"
|
||||||
|
|
||||||
|
describe("test the outgoing webhook action", () => {
|
||||||
|
let config = getConfig()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll()
|
||||||
|
|
||||||
|
it("should be able to run the action", async () => {
|
||||||
|
const res = await runStep(actions.integromat.stepId, {
|
||||||
|
value1: "test",
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.response.url).toEqual("http://www.test.com")
|
||||||
|
expect(res.response.method).toEqual("post")
|
||||||
|
expect(res.success).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add the payload props when a JSON string is provided", async () => {
|
||||||
|
const payload = `{"value1":1,"value2":2,"value3":3,"value4":4,"value5":5,"name":"Adam","age":9}`
|
||||||
|
const res = await runStep(actions.integromat.stepId, {
|
||||||
|
value1: "ONE",
|
||||||
|
value2: "TWO",
|
||||||
|
value3: "THREE",
|
||||||
|
value4: "FOUR",
|
||||||
|
value5: "FIVE",
|
||||||
|
body: {
|
||||||
|
value: payload,
|
||||||
|
},
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.response.url).toEqual("http://www.test.com")
|
||||||
|
expect(res.response.method).toEqual("post")
|
||||||
|
expect(res.response.body).toEqual(payload)
|
||||||
|
expect(res.success).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return a 400 if the JSON payload string is malformed", async () => {
|
||||||
|
const payload = `{ value1 1 }`
|
||||||
|
const res = await runStep(actions.integromat.stepId, {
|
||||||
|
value1: "ONE",
|
||||||
|
body: {
|
||||||
|
value: payload,
|
||||||
|
},
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.httpStatus).toEqual(400)
|
||||||
|
expect(res.response).toEqual("Invalid payload JSON")
|
||||||
|
expect(res.success).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,27 +0,0 @@
|
||||||
const setup = require("./utilities")
|
|
||||||
const fetch = require("node-fetch")
|
|
||||||
|
|
||||||
jest.mock("node-fetch")
|
|
||||||
|
|
||||||
describe("test the outgoing webhook action", () => {
|
|
||||||
let inputs
|
|
||||||
let config = setup.getConfig()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.init()
|
|
||||||
inputs = {
|
|
||||||
value1: "test",
|
|
||||||
url: "http://www.test.com",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
|
||||||
|
|
||||||
it("should be able to run the action", async () => {
|
|
||||||
const res = await setup.runStep(setup.actions.zapier.stepId, inputs)
|
|
||||||
expect(res.response.url).toEqual("http://www.test.com")
|
|
||||||
expect(res.response.method).toEqual("post")
|
|
||||||
expect(res.success).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
56
packages/server/src/automations/tests/zapier.spec.ts
Normal file
56
packages/server/src/automations/tests/zapier.spec.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { getConfig, afterAll, runStep, actions } from "./utilities"
|
||||||
|
|
||||||
|
describe("test the outgoing webhook action", () => {
|
||||||
|
let config = getConfig()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll()
|
||||||
|
|
||||||
|
it("should be able to run the action", async () => {
|
||||||
|
const res = await runStep(actions.zapier.stepId, {
|
||||||
|
value1: "test",
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.response.url).toEqual("http://www.test.com")
|
||||||
|
expect(res.response.method).toEqual("post")
|
||||||
|
expect(res.success).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add the payload props when a JSON string is provided", async () => {
|
||||||
|
const payload = `{ "value1": 1, "value2": 2, "value3": 3, "value4": 4, "value5": 5, "name": "Adam", "age": 9 }`
|
||||||
|
const res = await runStep(actions.zapier.stepId, {
|
||||||
|
value1: "ONE",
|
||||||
|
value2: "TWO",
|
||||||
|
value3: "THREE",
|
||||||
|
value4: "FOUR",
|
||||||
|
value5: "FIVE",
|
||||||
|
body: {
|
||||||
|
value: payload,
|
||||||
|
},
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.response.url).toEqual("http://www.test.com")
|
||||||
|
expect(res.response.method).toEqual("post")
|
||||||
|
expect(res.response.body).toEqual(
|
||||||
|
`{"platform":"budibase","value1":1,"value2":2,"value3":3,"value4":4,"value5":5,"name":"Adam","age":9}`
|
||||||
|
)
|
||||||
|
expect(res.success).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return a 400 if the JSON payload string is malformed", async () => {
|
||||||
|
const payload = `{ value1 1 }`
|
||||||
|
const res = await runStep(actions.zapier.stepId, {
|
||||||
|
value1: "ONE",
|
||||||
|
body: {
|
||||||
|
value: payload,
|
||||||
|
},
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.httpStatus).toEqual(400)
|
||||||
|
expect(res.response).toEqual("Invalid payload JSON")
|
||||||
|
expect(res.success).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
|
@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue