Merge branch 'master' into master
This commit is contained in:
commit
c4cbebca79
95 changed files with 2036 additions and 1224 deletions
8
.github/workflows/deploy-qa.yml
vendored
8
.github/workflows/deploy-qa.yml
vendored
|
@ -11,10 +11,12 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: peter-evans/repository-dispatch@v2
|
- uses: peter-evans/repository-dispatch@v2
|
||||||
env:
|
|
||||||
PAYLOAD_VERSION: ${{ github.sha }}
|
|
||||||
REF_NAME: ${{ github.ref_name}}
|
|
||||||
with:
|
with:
|
||||||
repository: budibase/budibase-deploys
|
repository: budibase/budibase-deploys
|
||||||
event-type: budicloud-qa-deploy
|
event-type: budicloud-qa-deploy
|
||||||
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
client-payload: |-
|
||||||
|
{
|
||||||
|
"VERSION": "${{ github.sha }}",
|
||||||
|
"REF_NAME": "${{ github.ref_name}}"
|
||||||
|
}
|
||||||
|
|
19
.github/workflows/release-master.yml
vendored
19
.github/workflows/release-master.yml
vendored
|
@ -165,17 +165,14 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Get the current budibase release version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
release_version=$(cat lerna.json | jq -r '.version')
|
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: passeidireto/trigger-external-workflow-action@main
|
- uses: peter-evans/repository-dispatch@v2
|
||||||
env:
|
|
||||||
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
|
|
||||||
REF_NAME: ${{ github.ref_name}}
|
|
||||||
with:
|
with:
|
||||||
repository: budibase/budibase-deploys
|
repository: budibase/budibase-deploys
|
||||||
event: budicloud-qa-deploy
|
event-type: budicloud-qa-deploy
|
||||||
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}
|
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
client-payload: |-
|
||||||
|
{
|
||||||
|
"VERSION": "${{ github.ref_name }}",
|
||||||
|
"REF_NAME": "${{ github.ref_name}}"
|
||||||
|
}
|
||||||
|
|
4
.github/workflows/release-singleimage.yml
vendored
4
.github/workflows/release-singleimage.yml
vendored
|
@ -66,7 +66,7 @@ jobs:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
build-args: BUDIBASE_VERSION=$BUDIBASE_VERSION
|
build-args: BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
|
||||||
tags: budibase/budibase,budibase/budibase:${{ env.RELEASE_VERSION }}
|
tags: budibase/budibase,budibase/budibase:${{ env.RELEASE_VERSION }}
|
||||||
file: ./hosting/single/Dockerfile.v2
|
file: ./hosting/single/Dockerfile.v2
|
||||||
env:
|
env:
|
||||||
|
@ -79,7 +79,7 @@ jobs:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
build-args: |
|
build-args: |
|
||||||
TARGETBUILD=aas
|
TARGETBUILD=aas
|
||||||
BUDIBASE_VERSION=$BUDIBASE_VERSION
|
BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
|
||||||
tags: budibase/budibase-aas,budibase/budibase-aas:${{ env.RELEASE_VERSION }}
|
tags: budibase/budibase-aas,budibase/budibase-aas:${{ env.RELEASE_VERSION }}
|
||||||
file: ./hosting/single/Dockerfile.v2
|
file: ./hosting/single/Dockerfile.v2
|
||||||
env:
|
env:
|
||||||
|
|
25
.github/workflows/tag-release.yml
vendored
25
.github/workflows/tag-release.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
name: Tag release
|
name: Release
|
||||||
concurrency:
|
concurrency:
|
||||||
group: tag-release
|
group: tag-release
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
@ -19,6 +19,8 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
tag-release:
|
tag-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.tag-release.outputs.version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Fail if branch is not master
|
- name: Fail if branch is not master
|
||||||
|
@ -33,6 +35,7 @@ jobs:
|
||||||
|
|
||||||
- run: cd scripts && yarn
|
- run: cd scripts && yarn
|
||||||
- name: Tag release
|
- name: Tag release
|
||||||
|
id: tag-release
|
||||||
run: |
|
run: |
|
||||||
cd scripts
|
cd scripts
|
||||||
# setup the username and email.
|
# setup the username and email.
|
||||||
|
@ -41,3 +44,23 @@ jobs:
|
||||||
BUMP_TYPE_INPUT=${{ github.event.inputs.versioning }}
|
BUMP_TYPE_INPUT=${{ github.event.inputs.versioning }}
|
||||||
BUMP_TYPE=${BUMP_TYPE_INPUT:-"patch"}
|
BUMP_TYPE=${BUMP_TYPE_INPUT:-"patch"}
|
||||||
./versionCommit.sh $BUMP_TYPE
|
./versionCommit.sh $BUMP_TYPE
|
||||||
|
|
||||||
|
|
||||||
|
new_version=$(./getCurrentVersion.sh)
|
||||||
|
echo "version=$new_version" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
trigger-release:
|
||||||
|
needs: [tag-release]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: peter-evans/repository-dispatch@v2
|
||||||
|
with:
|
||||||
|
repository: budibase/budibase-deploys
|
||||||
|
event-type: release-prod
|
||||||
|
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
client-payload: |-
|
||||||
|
{
|
||||||
|
"TAG": "${{ needs.tag-release.outputs.version }}"
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.budibase.com">
|
<a href="https://www.budibase.com">
|
||||||
<img alt="Budibase" src="https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" width="60" />
|
<img alt="Budibase" src="https://res.cloudinary.com/daog6scxm/image/upload/v1696515725/Branding/Assets/Symbol/RGB/Full%20Colour/Budibase_Symbol_RGB_FullColour_cbqvha_1_z5cwq2.svg" width="60" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
|
|
|
@ -57,8 +57,8 @@
|
||||||
--spectrum-global-color-gray-600: rgb(144,144,144);
|
--spectrum-global-color-gray-600: rgb(144,144,144);
|
||||||
--spectrum-global-color-gray-900: rgb(255,255,255);
|
--spectrum-global-color-gray-900: rgb(255,255,255);
|
||||||
--spectrum-global-color-gray-800: rgb(227,227,227);
|
--spectrum-global-color-gray-800: rgb(227,227,227);
|
||||||
--spectrum-global-color-static-blue-600: rgb(20,115,230);
|
--bb-indigo: #6E56FF;
|
||||||
--spectrum-global-color-static-blue-hover: rgb( 18, 103, 207);
|
--bb-indigo-light: #9F8FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
|
@ -90,15 +90,8 @@
|
||||||
.info {
|
.info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: left;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
|
||||||
.info {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
color: var(--spectrum-global-color-gray-600)
|
color: var(--spectrum-global-color-gray-600)
|
||||||
}
|
}
|
||||||
|
@ -113,13 +106,14 @@
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
.homeButton {
|
.homeButton {
|
||||||
background-color: var(--spectrum-global-color-static-blue-600);
|
background-color: var(--bb-indigo);
|
||||||
}
|
}
|
||||||
.homeButton:hover {
|
.homeButton:hover {
|
||||||
background-color: var(--spectrum-global-color-static-blue-hover);
|
background-color: var(--bb-indigo-light);
|
||||||
}
|
}
|
||||||
.statusButton {
|
.statusButton {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -127,20 +121,30 @@
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.hero {
|
.hero {
|
||||||
height: 160px;
|
height: 60px;
|
||||||
width: 160px;
|
margin: 10px 40px 10px 0;
|
||||||
margin-right: 80px;
|
}
|
||||||
|
.hero img {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-end;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
padding: 0 40px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
.content {
|
.content {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -152,16 +156,15 @@
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
<img src="https://raw.githubusercontent.com/Budibase/budibase/master/packages/builder/assets/bb-space-man.svg" alt="Budibase Logo">
|
<img src="https://res.cloudinary.com/daog6scxm/image/upload/v1696515725/Branding/Assets/Symbol/RGB/Full%20Colour/Budibase_Symbol_RGB_FullColour_cbqvha_1_z5cwq2.svg" alt="Budibase Logo">
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div>
|
<div>
|
||||||
<h4 id="status" class="status"></h4>
|
<h4 id="status" class="status"> </h4>
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
Houston we have a problem!
|
Houston we have a problem!
|
||||||
</h1>
|
</h1>
|
||||||
<h3 id="message" class="message">
|
<h3 id="message" class="message"> </h3>
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button class="homeButton" onclick=goHome()>Return home</button>
|
<button class="homeButton" onclick=goHome()>Return home</button>
|
||||||
|
|
|
@ -77,7 +77,7 @@ mkdir -p ${DATA_DIR}/minio
|
||||||
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
||||||
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
||||||
/bbcouch-runner.sh &
|
/bbcouch-runner.sh &
|
||||||
minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 &
|
/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 &
|
||||||
/etc/init.d/nginx restart
|
/etc/init.d/nginx restart
|
||||||
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
||||||
# Add monthly cron job to renew certbot certificate
|
# Add monthly cron job to renew certbot certificate
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.budibase.com">
|
<a href="https://www.budibase.com">
|
||||||
<img alt="Budibase" src="https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" width="60" />
|
<img alt="Budibase" src="https://res.cloudinary.com/daog6scxm/image/upload/v1696515725/Branding/Assets/Symbol/RGB/Full%20Colour/Budibase_Symbol_RGB_FullColour_cbqvha_1_z5cwq2.svg" width="60" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.budibase.com">
|
<a href="https://www.budibase.com">
|
||||||
<img alt="Budibase" src="https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" width="60" />
|
<img alt="Budibase" src="https://res.cloudinary.com/daog6scxm/image/upload/v1696515725/Branding/Assets/Symbol/RGB/Full%20Colour/Budibase_Symbol_RGB_FullColour_cbqvha_1_z5cwq2.svg" width="60" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.budibase.com">
|
<a href="https://www.budibase.com">
|
||||||
<img alt="Budibase" src="https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" width="60" />
|
<img alt="Budibase" src="https://res.cloudinary.com/daog6scxm/image/upload/v1696515725/Branding/Assets/Symbol/RGB/Full%20Colour/Budibase_Symbol_RGB_FullColour_cbqvha_1_z5cwq2.svg" width="60" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.budibase.com">
|
<a href="https://www.budibase.com">
|
||||||
<img alt="Budibase" src="https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" width="60" />
|
<img alt="Budibase" src="https://res.cloudinary.com/daog6scxm/image/upload/v1696515725/Branding/Assets/Symbol/RGB/Full%20Colour/Budibase_Symbol_RGB_FullColour_cbqvha_1_z5cwq2.svg" width="60" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.budibase.com">
|
<a href="https://www.budibase.com">
|
||||||
<img alt="Budibase" src="https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" width="60" />
|
<img alt="Budibase" src="https://res.cloudinary.com/daog6scxm/image/upload/v1696515725/Branding/Assets/Symbol/RGB/Full%20Colour/Budibase_Symbol_RGB_FullColour_cbqvha_1_z5cwq2.svg" width="60" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.budibase.com">
|
<a href="https://www.budibase.com">
|
||||||
<img alt="Budibase" src="https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" width="60" />
|
<img alt="Budibase" src="https://res.cloudinary.com/daog6scxm/image/upload/v1696515725/Branding/Assets/Symbol/RGB/Full%20Colour/Budibase_Symbol_RGB_FullColour_cbqvha_1_z5cwq2.svg" width="60" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.12.4",
|
"version": "2.13.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "node scripts/syncProPackage.js",
|
"preinstall": "node scripts/syncProPackage.js",
|
||||||
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev",
|
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev",
|
||||||
"build": "lerna run build --stream",
|
"build": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream",
|
||||||
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
|
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
|
||||||
"check:types": "lerna run check:types",
|
"check:types": "lerna run check:types",
|
||||||
"build:sdk": "lerna run --stream build:sdk",
|
"build:sdk": "lerna run --stream build:sdk",
|
||||||
|
|
|
@ -30,7 +30,6 @@ export * as timers from "./timers"
|
||||||
export { default as env } from "./environment"
|
export { default as env } from "./environment"
|
||||||
export * as blacklist from "./blacklist"
|
export * as blacklist from "./blacklist"
|
||||||
export * as docUpdates from "./docUpdates"
|
export * as docUpdates from "./docUpdates"
|
||||||
export * from "./utils/Duration"
|
|
||||||
export { SearchParams } from "./db"
|
export { SearchParams } from "./db"
|
||||||
// Add context to tenancy for backwards compatibility
|
// Add context to tenancy for backwards compatibility
|
||||||
// only do this for external usages to prevent internal
|
// only do this for external usages to prevent internal
|
||||||
|
|
|
@ -18,8 +18,12 @@ export const ObjectStoreBuckets = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const bbTmp = join(tmpdir(), ".budibase")
|
const bbTmp = join(tmpdir(), ".budibase")
|
||||||
if (!fs.existsSync(bbTmp)) {
|
try {
|
||||||
fs.mkdirSync(bbTmp)
|
fs.mkdirSync(bbTmp)
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.code !== "EEXIST") {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function budibaseTempDir() {
|
export function budibaseTempDir() {
|
||||||
|
|
|
@ -36,7 +36,7 @@ class InMemoryQueue {
|
||||||
* @param opts This is not used by the in memory queue as there is no real use
|
* @param opts This is not used by the in memory queue as there is no real use
|
||||||
* case when in memory, but is the same API as Bull
|
* case when in memory, but is the same API as Bull
|
||||||
*/
|
*/
|
||||||
constructor(name: string, opts?: any) {
|
constructor(name: string, opts = null) {
|
||||||
this._name = name
|
this._name = name
|
||||||
this._opts = opts
|
this._opts = opts
|
||||||
this._messages = []
|
this._messages = []
|
||||||
|
|
|
@ -2,18 +2,11 @@ import env from "../environment"
|
||||||
import { getRedisOptions } from "../redis/utils"
|
import { getRedisOptions } from "../redis/utils"
|
||||||
import { JobQueue } from "./constants"
|
import { JobQueue } from "./constants"
|
||||||
import InMemoryQueue from "./inMemoryQueue"
|
import InMemoryQueue from "./inMemoryQueue"
|
||||||
import BullQueue, { QueueOptions } from "bull"
|
import BullQueue from "bull"
|
||||||
import { addListeners, StalledFn } from "./listeners"
|
import { addListeners, StalledFn } from "./listeners"
|
||||||
import { Duration } from "../utils"
|
|
||||||
import * as timers from "../timers"
|
import * as timers from "../timers"
|
||||||
import * as Redis from "ioredis"
|
|
||||||
|
|
||||||
// the queue lock is held for 5 minutes
|
const CLEANUP_PERIOD_MS = 60 * 1000
|
||||||
const QUEUE_LOCK_MS = Duration.fromMinutes(5).toMs()
|
|
||||||
// queue lock is refreshed every 30 seconds
|
|
||||||
const QUEUE_LOCK_RENEW_INTERNAL_MS = Duration.fromSeconds(30).toMs()
|
|
||||||
// cleanup the queue every 60 seconds
|
|
||||||
const CLEANUP_PERIOD_MS = Duration.fromSeconds(60).toMs()
|
|
||||||
let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = []
|
let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = []
|
||||||
let cleanupInterval: NodeJS.Timeout
|
let cleanupInterval: NodeJS.Timeout
|
||||||
|
|
||||||
|
@ -28,14 +21,7 @@ export function createQueue<T>(
|
||||||
opts: { removeStalledCb?: StalledFn } = {}
|
opts: { removeStalledCb?: StalledFn } = {}
|
||||||
): BullQueue.Queue<T> {
|
): BullQueue.Queue<T> {
|
||||||
const { opts: redisOpts, redisProtocolUrl } = getRedisOptions()
|
const { opts: redisOpts, redisProtocolUrl } = getRedisOptions()
|
||||||
const queueConfig: QueueOptions = {
|
const queueConfig: any = redisProtocolUrl || { redis: redisOpts }
|
||||||
redis: redisProtocolUrl! || (redisOpts as Redis.RedisOptions),
|
|
||||||
settings: {
|
|
||||||
maxStalledCount: 0,
|
|
||||||
lockDuration: QUEUE_LOCK_MS,
|
|
||||||
lockRenewTime: QUEUE_LOCK_RENEW_INTERNAL_MS,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
let queue: any
|
let queue: any
|
||||||
if (!env.isTest()) {
|
if (!env.isTest()) {
|
||||||
queue = new BullQueue(jobQueue, queueConfig)
|
queue = new BullQueue(jobQueue, queueConfig)
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
export enum DurationType {
|
|
||||||
MILLISECONDS = "milliseconds",
|
|
||||||
SECONDS = "seconds",
|
|
||||||
MINUTES = "minutes",
|
|
||||||
HOURS = "hours",
|
|
||||||
DAYS = "days",
|
|
||||||
}
|
|
||||||
|
|
||||||
const conversion: Record<DurationType, number> = {
|
|
||||||
milliseconds: 1,
|
|
||||||
seconds: 1000,
|
|
||||||
minutes: 60 * 1000,
|
|
||||||
hours: 60 * 60 * 1000,
|
|
||||||
days: 24 * 60 * 60 * 1000,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Duration {
|
|
||||||
static convert(from: DurationType, to: DurationType, duration: number) {
|
|
||||||
const milliseconds = duration * conversion[from]
|
|
||||||
return milliseconds / conversion[to]
|
|
||||||
}
|
|
||||||
|
|
||||||
static from(from: DurationType, duration: number) {
|
|
||||||
return {
|
|
||||||
to: (to: DurationType) => {
|
|
||||||
return Duration.convert(from, to, duration)
|
|
||||||
},
|
|
||||||
toMs: () => {
|
|
||||||
return Duration.convert(from, DurationType.MILLISECONDS, duration)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromSeconds(duration: number) {
|
|
||||||
return Duration.from(DurationType.SECONDS, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromMinutes(duration: number) {
|
|
||||||
return Duration.from(DurationType.MINUTES, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromHours(duration: number) {
|
|
||||||
return Duration.from(DurationType.HOURS, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromDays(duration: number) {
|
|
||||||
return Duration.from(DurationType.DAYS, duration)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * from "./hashing"
|
export * from "./hashing"
|
||||||
export * from "./utils"
|
export * from "./utils"
|
||||||
export * from "./stringUtils"
|
export * from "./stringUtils"
|
||||||
export * from "./Duration"
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { Duration, DurationType } from "../Duration"
|
|
||||||
|
|
||||||
describe("duration", () => {
|
|
||||||
it("should convert minutes to milliseconds", () => {
|
|
||||||
expect(Duration.fromMinutes(5).toMs()).toBe(300000)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should convert seconds to milliseconds", () => {
|
|
||||||
expect(Duration.fromSeconds(30).toMs()).toBe(30000)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should convert days to milliseconds", () => {
|
|
||||||
expect(Duration.fromDays(1).toMs()).toBe(86400000)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should convert minutes to days", () => {
|
|
||||||
expect(Duration.fromMinutes(1440).to(DurationType.DAYS)).toBe(1)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -386,7 +386,7 @@
|
||||||
}
|
}
|
||||||
.compact .placeholder,
|
.compact .placeholder,
|
||||||
.compact img {
|
.compact img {
|
||||||
margin: 10px 16px;
|
margin: 8px 16px;
|
||||||
}
|
}
|
||||||
.compact img {
|
.compact img {
|
||||||
height: 90px;
|
height: 90px;
|
||||||
|
@ -456,6 +456,12 @@
|
||||||
color: var(--red);
|
color: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spectrum-Dropzone {
|
||||||
|
height: 220px;
|
||||||
|
}
|
||||||
|
.compact .spectrum-Dropzone {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
.spectrum-Dropzone.disabled {
|
.spectrum-Dropzone.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: var(--spectrum-global-color-gray-200);
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
|
@ -463,10 +469,6 @@
|
||||||
.disabled .spectrum-Heading--sizeL {
|
.disabled .spectrum-Heading--sizeL {
|
||||||
color: var(--spectrum-alias-text-color-disabled);
|
color: var(--spectrum-alias-text-color-disabled);
|
||||||
}
|
}
|
||||||
.compact .spectrum-Dropzone {
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
.compact .spectrum-IllustratedMessage-description {
|
.compact .spectrum-IllustratedMessage-description {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -477,7 +479,6 @@
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,15 @@
|
||||||
--background: #ffffff;
|
--background: #ffffff;
|
||||||
--ink: #000000;
|
--ink: #000000;
|
||||||
|
|
||||||
|
/* Brand colours */
|
||||||
|
--bb-coral: #FF4E4E;
|
||||||
|
--bb-coral-light: #F97777;
|
||||||
|
--bb-indigo: #6E56FF;
|
||||||
|
--bb-indigo-light: #9F8FFF;
|
||||||
|
--bb-lime: #ECFFB5;
|
||||||
|
--bb-forest-green: #053835;
|
||||||
|
--bb-beige: #F6EFEA;
|
||||||
|
|
||||||
--grey-1: #fafafa;
|
--grey-1: #fafafa;
|
||||||
--grey-2: #f5f5f5;
|
--grey-2: #f5f5f5;
|
||||||
--grey-3: #eeeeee;
|
--grey-3: #eeeeee;
|
||||||
|
|
1
packages/builder/.gitignore
vendored
1
packages/builder/.gitignore
vendored
|
@ -6,3 +6,4 @@ release/
|
||||||
dist/
|
dist/
|
||||||
routify
|
routify
|
||||||
.routify/
|
.routify/
|
||||||
|
svelte.config.js
|
|
@ -1,80 +1,13 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<svg width="265" height="265" viewBox="0 0 265 265" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<g clip-path="url(#clip0_1_1799)">
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<path d="M158.2 8.6V116.6C158.2 121.3 162 125.2 166.8 125.2H213.8C218 125.2 222 123.2 224.6 119.8L262.9 68.9C265.7 65.2 265.7 60.1 262.9 56.4L224.6 5.4C222 2 218 0 213.8 0H166.8C162 0 158.2 3.8 158.2 8.6Z" fill="#FF4E4E"/>
|
||||||
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
<path d="M158.2 148.4V256.4C158.2 261.1 162 265 166.8 265H213.8C218 265 222 263 224.6 259.6L262.9 208.7C265.7 205 265.7 199.9 262.9 196.2L224.6 145.3C222.1 141.9 218.1 139.9 213.8 139.9H166.8C162 139.8 158.2 143.7 158.2 148.4Z" fill="#6E56FF"/>
|
||||||
<style type="text/css">
|
<path d="M0 8.6V116.6C0 121.3 3.8 125.2 8.6 125.2H109.6C113.8 125.2 117.8 123.2 120.4 119.8L155.9 72.5C160.3 66.6 160.3 58.5 155.9 52.6L120.3 5.4C117.8 2 113.8 0 109.5 0H8.6C3.8 0 0 3.8 0 8.6Z" fill="#F97777"/>
|
||||||
.st0{fill:#000000;}
|
<path d="M0 148.4V256.4C0 261.1 3.8 265 8.6 265H109.6C113.8 265 117.8 263 120.4 259.6L155.9 212.3C160.3 206.4 160.3 198.3 155.9 192.4L120.4 145.1C117.9 141.7 113.9 139.7 109.6 139.7H8.6C3.8 139.8 0 143.7 0 148.4Z" fill="#9F8FFF"/>
|
||||||
.st1{fill:#FFFFFF;}
|
|
||||||
.st2{fill:#4285F4;}
|
|
||||||
</style>
|
|
||||||
<rect x="-152.17" y="-24.17" class="st0" width="96.17" height="96.17"/>
|
|
||||||
<path class="st1" d="M-83.19,48h-41.79c-1.76,0-3.19-1.43-3.19-3.19V3.02c0-1.76,1.43-3.19,3.19-3.19h41.79
|
|
||||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C-80,46.57-81.43,48-83.19,48z"/>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st0" d="M-99.62,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
|
||||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
|
||||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35h-4.89V12.57H-99.62z
|
|
||||||
M-93.46,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
|
||||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
|
||||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
|
||||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
|
||||||
C-93.55,28.92-93.46,28.52-93.46,28.11z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st0" d="M-114.76,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58
|
|
||||||
c0.86,0.39,1.59,0.91,2.19,1.57c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89
|
|
||||||
c-0.35,0.9-0.84,1.68-1.47,2.35c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35
|
|
||||||
h-4.89V12.57H-114.76z M-108.6,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
|
||||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
|
||||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
|
||||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
|
||||||
C-108.68,28.92-108.6,28.52-108.6,28.11z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<path class="st2" d="M44.81,159H3.02c-1.76,0-3.19-1.43-3.19-3.19v-41.79c0-1.76,1.43-3.19,3.19-3.19h41.79
|
|
||||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C48,157.57,46.57,159,44.81,159z"/>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M28.38,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
|
||||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
|
||||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146h-4.89v-22.43H28.38z
|
|
||||||
M34.54,139.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
|
||||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
|
||||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
|
||||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
|
||||||
C34.45,139.92,34.54,139.52,34.54,139.11z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M13.24,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
|
||||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
|
||||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146H8.35v-22.43H13.24z M19.4,139.11
|
|
||||||
c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69c-0.38-0.17-0.79-0.26-1.24-0.26
|
|
||||||
c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01c-0.17,0.39-0.26,0.8-0.26,1.23
|
|
||||||
c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68c0.39,0.17,0.8,0.26,1.23,0.26
|
|
||||||
c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1C19.32,139.92,19.4,139.52,19.4,139.11z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st0" d="M44,48H4c-2.21,0-4-1.79-4-4V4c0-2.21,1.79-4,4-4h40c2.21,0,4,1.79,4,4v40C48,46.21,46.21,48,44,48z"/>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M28.48,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
|
||||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
|
||||||
c-0.64,0.7-1.4,1.25-2.28,1.66C34.8,35.8,33.86,36,32.84,36c-1.84,0-3.3-0.69-4.37-2.07v1.62h-5V12H28.48z M34.78,28.31
|
|
||||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
|
||||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
|
||||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
|
||||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C34.69,29.16,34.78,28.75,34.78,28.31z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M13,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
|
||||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
|
||||||
c-0.64,0.7-1.4,1.25-2.28,1.66C19.32,35.8,18.38,36,17.37,36c-1.84,0-3.3-0.69-4.37-2.07v1.62H8V12H13z M19.3,28.31
|
|
||||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
|
||||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
|
||||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
|
||||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C19.21,29.16,19.3,28.75,19.3,28.31z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1_1799">
|
||||||
|
<rect width="265" height="265" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
viewBox="0 0 46.39 46.39" style="enable-background:new 0 0 46.39 46.39;" xml:space="preserve">
|
|
||||||
<style type="text/css">
|
|
||||||
.st0{fill:#FFFFFF;}
|
|
||||||
</style>
|
|
||||||
<g>
|
|
||||||
<path class="st0" d="M45.28,22.9c-0.49,28.44-42.79,28.43-43.27,0C2.5-5.54,44.8-5.54,45.28,22.9z"/>
|
|
||||||
<path d="M23.65,45.45c-29.64-0.48-29.63-44.63,0-45.1C53.28,0.82,53.28,44.98,23.65,45.45z M23.65,2.18
|
|
||||||
c-27.09,0.09-27.09,41.36,0,41.44C50.74,43.53,50.74,2.26,23.65,2.18z"/>
|
|
||||||
<path d="M41.94,21.07C38.86,8.69,3.47,8.77,5.01,24.45C5.74,49.51,46.24,46.16,41.94,21.07z"/>
|
|
||||||
<path class="st0" d="M14.69,22.35c0.06,4.27-6.65,4.27-6.58,0C8.05,18.08,14.76,18.08,14.69,22.35z"/>
|
|
||||||
<path class="st0" d="M11.07,28.39c0.02,1.7-2.65,1.7-2.62,0C8.42,26.68,11.09,26.68,11.07,28.39z"/>
|
|
||||||
<path class="st0" d="M30.56,16.2c0.28-1.27,6.76,0.79,8.45,5.64c1.69,4.84-2.11,12.22-3.52,11.43
|
|
||||||
C36.02,25.33,37.44,22.84,30.56,16.2z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.2 KiB |
|
@ -1,25 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
viewBox="0 0 34.41 34.41" style="enable-background:new 0 0 34.41 34.41;" xml:space="preserve">
|
|
||||||
<style type="text/css">
|
|
||||||
.st0{fill:#FFFFFF;stroke:url(#SVGID_1_);stroke-miterlimit:10;}
|
|
||||||
.st1{fill:url(#SVGID_2_);}
|
|
||||||
.st2{fill:#FFFFFF;}
|
|
||||||
</style>
|
|
||||||
<g>
|
|
||||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0.9311" y1="17.2025" x2="33.4739" y2="17.2025">
|
|
||||||
<stop offset="0" style="stop-color:#9E99FF"/>
|
|
||||||
<stop offset="1" style="stop-color:#5C45FF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<path class="st0" d="M17.2,33.2c-21.03-0.34-21.03-31.67,0-32C38.23,1.54,38.23,32.87,17.2,33.2z"/>
|
|
||||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="3.9435" y1="20.058" x2="30.407" y2="20.058">
|
|
||||||
<stop offset="0" style="stop-color:#9E99FF"/>
|
|
||||||
<stop offset="1" style="stop-color:#5C45FF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<path class="st1" d="M30.18,15.91C27.99,7.12,2.89,7.18,3.98,18.3C4.5,36.09,33.23,33.71,30.18,15.91z"/>
|
|
||||||
<path class="st2" d="M6.42,21.1c-0.02-1.21,1.88-1.21,1.86,0C8.29,22.3,6.4,22.3,6.42,21.1z"/>
|
|
||||||
<path class="st2" d="M6.18,16.81c-0.04-3.03,4.72-3.03,4.67,0C10.89,19.84,6.13,19.84,6.18,16.81z"/>
|
|
||||||
<path class="st2" d="M25.61,24.56c0.38-5.63,1.38-7.4-3.5-12.11c0.2-0.9,4.8,0.56,6,4C29.3,19.88,26.61,25.12,25.61,24.56z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 787 B After Width: | Height: | Size: 3.9 KiB |
|
@ -3,6 +3,7 @@ import { API } from "api"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import { selectedAutomation } from "builderStore"
|
import { selectedAutomation } from "builderStore"
|
||||||
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
const initialAutomationState = {
|
const initialAutomationState = {
|
||||||
automations: [],
|
automations: [],
|
||||||
|
@ -21,6 +22,37 @@ export const getAutomationStore = () => {
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateReferencesInObject = (obj, modifiedIndex, action) => {
|
||||||
|
const regex = /{{\s*steps\.(\d+)\./g
|
||||||
|
for (const key in obj) {
|
||||||
|
if (typeof obj[key] === "string") {
|
||||||
|
let matches
|
||||||
|
while ((matches = regex.exec(obj[key])) !== null) {
|
||||||
|
const referencedStep = parseInt(matches[1])
|
||||||
|
if (action === "add" && referencedStep >= modifiedIndex) {
|
||||||
|
obj[key] = obj[key].replace(
|
||||||
|
`{{ steps.${referencedStep}.`,
|
||||||
|
`{{ steps.${referencedStep + 1}.`
|
||||||
|
)
|
||||||
|
} else if (action === "delete" && referencedStep > modifiedIndex) {
|
||||||
|
obj[key] = obj[key].replace(
|
||||||
|
`{{ steps.${referencedStep}.`,
|
||||||
|
`{{ steps.${referencedStep - 1}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||||
|
updateReferencesInObject(obj[key], modifiedIndex, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStepReferences = (steps, modifiedIndex, action) => {
|
||||||
|
steps.forEach(step => {
|
||||||
|
updateReferencesInObject(step.inputs, modifiedIndex, action)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const automationActions = store => ({
|
const automationActions = store => ({
|
||||||
definitions: async () => {
|
definitions: async () => {
|
||||||
const response = await API.getAutomationDefinitions()
|
const response = await API.getAutomationDefinitions()
|
||||||
|
@ -218,10 +250,40 @@ const automationActions = store => ({
|
||||||
if (!automation) {
|
if (!automation) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateStepReferences(newAutomation.definition.steps, blockIdx, "add")
|
||||||
|
} catch (e) {
|
||||||
|
notifications.error("Error adding automation block")
|
||||||
|
}
|
||||||
newAutomation.definition.steps.splice(blockIdx, 0, block)
|
newAutomation.definition.steps.splice(blockIdx, 0, block)
|
||||||
await store.actions.save(newAutomation)
|
await store.actions.save(newAutomation)
|
||||||
},
|
},
|
||||||
deleteAutomationBlock: async block => {
|
saveAutomationName: async (blockId, name) => {
|
||||||
|
const automation = get(selectedAutomation)
|
||||||
|
let newAutomation = cloneDeep(automation)
|
||||||
|
if (!automation) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newAutomation.definition.stepNames = {
|
||||||
|
...newAutomation.definition.stepNames,
|
||||||
|
[blockId]: name.trim(),
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.actions.save(newAutomation)
|
||||||
|
},
|
||||||
|
deleteAutomationName: async blockId => {
|
||||||
|
const automation = get(selectedAutomation)
|
||||||
|
let newAutomation = cloneDeep(automation)
|
||||||
|
if (!automation) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete newAutomation.definition.stepNames[blockId]
|
||||||
|
|
||||||
|
await store.actions.save(newAutomation)
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteAutomationBlock: async (block, blockIdx) => {
|
||||||
const automation = get(selectedAutomation)
|
const automation = get(selectedAutomation)
|
||||||
let newAutomation = cloneDeep(automation)
|
let newAutomation = cloneDeep(automation)
|
||||||
|
|
||||||
|
@ -233,7 +295,14 @@ const automationActions = store => ({
|
||||||
newAutomation.definition.steps = newAutomation.definition.steps.filter(
|
newAutomation.definition.steps = newAutomation.definition.steps.filter(
|
||||||
step => step.id !== block.id
|
step => step.id !== block.id
|
||||||
)
|
)
|
||||||
|
delete newAutomation.definition.stepNames?.[block.id]
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
updateStepReferences(newAutomation.definition.steps, blockIdx, "delete")
|
||||||
|
} catch (e) {
|
||||||
|
notifications.error("Error deleting automation block")
|
||||||
|
}
|
||||||
|
|
||||||
await store.actions.save(newAutomation)
|
await store.actions.save(newAutomation)
|
||||||
},
|
},
|
||||||
replace: async (automationId, automation) => {
|
replace: async (automationId, automation) => {
|
||||||
|
|
|
@ -5,13 +5,7 @@
|
||||||
import TestDataModal from "./TestDataModal.svelte"
|
import TestDataModal from "./TestDataModal.svelte"
|
||||||
import { flip } from "svelte/animate"
|
import { flip } from "svelte/animate"
|
||||||
import { fly } from "svelte/transition"
|
import { fly } from "svelte/transition"
|
||||||
import {
|
import { Icon, notifications, Modal } from "@budibase/bbui"
|
||||||
Heading,
|
|
||||||
Icon,
|
|
||||||
ActionButton,
|
|
||||||
notifications,
|
|
||||||
Modal,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { ActionStepID } from "constants/backend/automations"
|
import { ActionStepID } from "constants/backend/automations"
|
||||||
import UndoRedoControl from "components/common/UndoRedoControl.svelte"
|
import UndoRedoControl from "components/common/UndoRedoControl.svelte"
|
||||||
import { automationHistoryStore } from "builderStore"
|
import { automationHistoryStore } from "builderStore"
|
||||||
|
@ -20,9 +14,8 @@
|
||||||
|
|
||||||
let testDataModal
|
let testDataModal
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
|
let scrolling = false
|
||||||
$: blocks = getBlocks(automation)
|
$: blocks = getBlocks(automation).filter(x => x.stepId !== ActionStepID.LOOP)
|
||||||
|
|
||||||
const getBlocks = automation => {
|
const getBlocks = automation => {
|
||||||
let blocks = []
|
let blocks = []
|
||||||
if (automation.definition.trigger) {
|
if (automation.definition.trigger) {
|
||||||
|
@ -32,58 +25,72 @@
|
||||||
return blocks
|
return blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteAutomation() {
|
const deleteAutomation = async () => {
|
||||||
try {
|
try {
|
||||||
await automationStore.actions.delete($selectedAutomation)
|
await automationStore.actions.delete($selectedAutomation)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error deleting automation")
|
notifications.error("Error deleting automation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleScroll = e => {
|
||||||
|
if (e.target.scrollTop >= 30) {
|
||||||
|
scrolling = true
|
||||||
|
} else if (e.target.scrollTop) {
|
||||||
|
// Set scrolling back to false if scrolled back to less than 100px
|
||||||
|
scrolling = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="canvas">
|
<div class="header" class:scrolling>
|
||||||
<div class="header">
|
<div class="header-left">
|
||||||
<Heading size="S">{automation.name}</Heading>
|
<UndoRedoControl store={automationHistoryStore} />
|
||||||
<div class="controls">
|
</div>
|
||||||
<UndoRedoControl store={automationHistoryStore} />
|
<div class="controls">
|
||||||
|
<div class="buttons">
|
||||||
|
<Icon hoverable size="M" name="Play" />
|
||||||
|
<div
|
||||||
|
on:click={() => {
|
||||||
|
testDataModal.show()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Run test
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
<Icon
|
<Icon
|
||||||
on:click={confirmDeleteDialog.show}
|
disabled={!$automationStore.testResults}
|
||||||
hoverable
|
hoverable
|
||||||
size="M"
|
size="M"
|
||||||
name="DeleteOutline"
|
name="Multiple"
|
||||||
/>
|
/>
|
||||||
<div class="buttons">
|
<div
|
||||||
<ActionButton
|
class:disabled={!$automationStore.testResults}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
testDataModal.show()
|
$automationStore.showTestPanel = true
|
||||||
}}
|
}}
|
||||||
icon="MultipleCheck"
|
>
|
||||||
size="M">Run test</ActionButton
|
Test details
|
||||||
>
|
|
||||||
<ActionButton
|
|
||||||
disabled={!$automationStore.testResults}
|
|
||||||
on:click={() => {
|
|
||||||
$automationStore.showTestPanel = true
|
|
||||||
}}
|
|
||||||
size="M">Test Details</ActionButton
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="canvas" on:scroll={handleScroll}>
|
||||||
{#each blocks as block, idx (block.id)}
|
<div class="content">
|
||||||
<div
|
{#each blocks as block, idx (block.id)}
|
||||||
class="block"
|
<div
|
||||||
animate:flip={{ duration: 500 }}
|
class="block"
|
||||||
in:fly={{ x: 500, duration: 500 }}
|
animate:flip={{ duration: 500 }}
|
||||||
out:fly|local={{ x: 500, duration: 500 }}
|
in:fly={{ x: 500, duration: 500 }}
|
||||||
>
|
out:fly|local={{ x: 500, duration: 500 }}
|
||||||
{#if block.stepId !== ActionStepID.LOOP}
|
>
|
||||||
<FlowItem {testDataModal} {block} {idx} />
|
{#if block.stepId !== ActionStepID.LOOP}
|
||||||
{/if}
|
<FlowItem {testDataModal} {block} {idx} />
|
||||||
</div>
|
{/if}
|
||||||
{/each}
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={confirmDeleteDialog}
|
bind:this={confirmDeleteDialog}
|
||||||
|
@ -103,6 +110,12 @@
|
||||||
<style>
|
<style>
|
||||||
.canvas {
|
.canvas {
|
||||||
padding: var(--spacing-l) var(--spacing-xl);
|
padding: var(--spacing-l) var(--spacing-xl);
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left :global(div) {
|
||||||
|
border-right: none;
|
||||||
}
|
}
|
||||||
/* Fix for firefox not respecting bottom padding in scrolling containers */
|
/* Fix for firefox not respecting bottom padding in scrolling containers */
|
||||||
.canvas > *:last-child {
|
.canvas > *:last-child {
|
||||||
|
@ -117,23 +130,45 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: inline-block;
|
flex-grow: 1;
|
||||||
text-align: left;
|
padding: 23px 23px 80px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header.scrolling {
|
||||||
|
background: var(--background);
|
||||||
|
border-bottom: var(--border-light);
|
||||||
|
border-left: var(--border-light);
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding-left: var(--spacing-l);
|
||||||
|
transition: background 130ms ease-out;
|
||||||
|
flex: 0 0 48px;
|
||||||
|
padding-right: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
.controls,
|
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.buttons {
|
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttons:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
color: var(--spectrum-global-color-gray-500) !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,20 +7,16 @@
|
||||||
Detail,
|
Detail,
|
||||||
Modal,
|
Modal,
|
||||||
Button,
|
Button,
|
||||||
ActionButton,
|
|
||||||
notifications,
|
notifications,
|
||||||
Label,
|
Label,
|
||||||
|
AbsTooltip,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
||||||
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
||||||
import ActionModal from "./ActionModal.svelte"
|
import ActionModal from "./ActionModal.svelte"
|
||||||
import FlowItemHeader from "./FlowItemHeader.svelte"
|
import FlowItemHeader from "./FlowItemHeader.svelte"
|
||||||
import RoleSelect from "components/design/settings/controls/RoleSelect.svelte"
|
import RoleSelect from "components/design/settings/controls/RoleSelect.svelte"
|
||||||
import {
|
import { ActionStepID, TriggerStepID } from "constants/backend/automations"
|
||||||
ActionStepID,
|
|
||||||
TriggerStepID,
|
|
||||||
Features,
|
|
||||||
} from "constants/backend/automations"
|
|
||||||
import { permissions } from "stores/backend"
|
import { permissions } from "stores/backend"
|
||||||
|
|
||||||
export let block
|
export let block
|
||||||
|
@ -86,7 +82,7 @@
|
||||||
if (loopBlock) {
|
if (loopBlock) {
|
||||||
await automationStore.actions.deleteAutomationBlock(loopBlock)
|
await automationStore.actions.deleteAutomationBlock(loopBlock)
|
||||||
}
|
}
|
||||||
await automationStore.actions.deleteAutomationBlock(block)
|
await automationStore.actions.deleteAutomationBlock(block, blockIdx)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error saving automation")
|
notifications.error("Error saving automation")
|
||||||
}
|
}
|
||||||
|
@ -129,6 +125,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="blockTitle">
|
<div class="blockTitle">
|
||||||
|
<AbsTooltip type="negative" text="Remove looping">
|
||||||
|
<Icon on:click={removeLooping} hoverable name="DeleteOutline" />
|
||||||
|
</AbsTooltip>
|
||||||
|
|
||||||
<div style="margin-left: 10px;" on:click={() => {}}>
|
<div style="margin-left: 10px;" on:click={() => {}}>
|
||||||
<Icon hoverable name={showLooping ? "ChevronDown" : "ChevronUp"} />
|
<Icon hoverable name={showLooping ? "ChevronDown" : "ChevronUp"} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -139,9 +139,6 @@
|
||||||
<Divider noMargin />
|
<Divider noMargin />
|
||||||
{#if !showLooping}
|
{#if !showLooping}
|
||||||
<div class="blockSection">
|
<div class="blockSection">
|
||||||
<div class="block-options">
|
|
||||||
<ActionButton on:click={() => removeLooping()} icon="DeleteOutline" />
|
|
||||||
</div>
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<AutomationBlockSetup
|
<AutomationBlockSetup
|
||||||
schemaProperties={Object.entries(
|
schemaProperties={Object.entries(
|
||||||
|
@ -162,31 +159,19 @@
|
||||||
{block}
|
{block}
|
||||||
{testDataModal}
|
{testDataModal}
|
||||||
{idx}
|
{idx}
|
||||||
|
{addLooping}
|
||||||
|
{deleteStep}
|
||||||
on:toggle={() => (open = !open)}
|
on:toggle={() => (open = !open)}
|
||||||
/>
|
/>
|
||||||
{#if open}
|
{#if open}
|
||||||
<Divider noMargin />
|
<Divider noMargin />
|
||||||
<div class="blockSection">
|
<div class="blockSection">
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
{#if !isTrigger}
|
|
||||||
<div>
|
|
||||||
<div class="block-options">
|
|
||||||
{#if !loopBlock && (block?.features?.[Features.LOOPING] || !block.features)}
|
|
||||||
<ActionButton on:click={() => addLooping()} icon="Reuse">
|
|
||||||
Add Looping
|
|
||||||
</ActionButton>
|
|
||||||
{/if}
|
|
||||||
<ActionButton
|
|
||||||
on:click={() => deleteStep()}
|
|
||||||
icon="DeleteOutline"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if isAppAction}
|
{#if isAppAction}
|
||||||
<Label>Role</Label>
|
<div>
|
||||||
<RoleSelect bind:value={role} />
|
<Label>Role</Label>
|
||||||
|
<RoleSelect bind:value={role} />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<AutomationBlockSetup
|
<AutomationBlockSetup
|
||||||
schemaProperties={Object.entries(block.schema.inputs.properties)}
|
schemaProperties={Object.entries(block.schema.inputs.properties)}
|
||||||
|
@ -270,5 +255,6 @@
|
||||||
.blockTitle {
|
.blockTitle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: var(--spacing-s);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore, selectedAutomation } from "builderStore"
|
||||||
import { Icon, Body, Detail, StatusLight } from "@budibase/bbui"
|
import { Icon, Body, StatusLight, AbsTooltip } from "@budibase/bbui"
|
||||||
import { externalActions } from "./ExternalActions"
|
import { externalActions } from "./ExternalActions"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { Features } from "constants/backend/automations"
|
||||||
|
|
||||||
export let block
|
export let block
|
||||||
export let open
|
export let open
|
||||||
|
@ -10,9 +11,20 @@
|
||||||
export let testResult
|
export let testResult
|
||||||
export let isTrigger
|
export let isTrigger
|
||||||
export let idx
|
export let idx
|
||||||
|
export let addLooping
|
||||||
|
export let deleteStep
|
||||||
|
|
||||||
|
let validRegex = /^[A-Za-z0-9_\s]+$/
|
||||||
|
let typing = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: stepNames = $selectedAutomation.definition.stepNames
|
||||||
|
$: automationName = stepNames?.[block.id] || block?.name || ""
|
||||||
|
$: automationNameError = getAutomationNameError(automationName)
|
||||||
|
$: status = updateStatus(testResult, isTrigger)
|
||||||
|
$: isHeaderTrigger = isTrigger || block.type === "TRIGGER"
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (!testResult) {
|
if (!testResult) {
|
||||||
testResult = $automationStore.testResults?.steps?.filter(step =>
|
testResult = $automationStore.testResults?.steps?.filter(step =>
|
||||||
|
@ -20,8 +32,9 @@
|
||||||
)?.[0]
|
)?.[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$: isTrigger = isTrigger || block.type === "TRIGGER"
|
$: loopBlock = $selectedAutomation.definition.steps.find(
|
||||||
$: status = updateStatus(testResult, isTrigger)
|
x => x.blockToLoop === block?.id
|
||||||
|
)
|
||||||
|
|
||||||
async function onSelect(block) {
|
async function onSelect(block) {
|
||||||
await automationStore.update(state => {
|
await automationStore.update(state => {
|
||||||
|
@ -43,10 +56,49 @@
|
||||||
return { negative: true, message: "Error" }
|
return { negative: true, message: "Error" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAutomationNameError = name => {
|
||||||
|
if (stepNames) {
|
||||||
|
for (const [key, value] of Object.entries(stepNames)) {
|
||||||
|
if (name === value && key !== block.id) {
|
||||||
|
return "This name already exists, please enter a unique name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name !== block.name && name?.length > 0) {
|
||||||
|
let invalidRoleName = !validRegex.test(name)
|
||||||
|
if (invalidRoleName) {
|
||||||
|
return "Please enter a role name consisting of only alphanumeric symbols and underscores"
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTyping = async () => {
|
||||||
|
typing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveName = async () => {
|
||||||
|
if (automationNameError || block.name === automationName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (automationName.length === 0) {
|
||||||
|
await automationStore.actions.deleteAutomationName(block.id)
|
||||||
|
} else {
|
||||||
|
await automationStore.actions.saveAutomationName(block.id, automationName)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="blockSection">
|
<div
|
||||||
<div on:click={() => dispatch("toggle")} class="splitHeader">
|
class:typing={typing && !automationNameError}
|
||||||
|
class:typing-error={automationNameError}
|
||||||
|
class="blockSection"
|
||||||
|
>
|
||||||
|
<div class="splitHeader">
|
||||||
<div class="center-items">
|
<div class="center-items">
|
||||||
{#if externalActions[block.stepId]}
|
{#if externalActions[block.stepId]}
|
||||||
<img
|
<img
|
||||||
|
@ -67,40 +119,104 @@
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="iconAlign">
|
<div class="iconAlign">
|
||||||
{#if isTrigger}
|
{#if isHeaderTrigger}
|
||||||
<Body size="XS"><b>Trigger</b></Body>
|
<Body size="XS"><b>Trigger</b></Body>
|
||||||
<Body size="XS">When this happens:</Body>
|
|
||||||
{:else}
|
{:else}
|
||||||
<Body size="XS"><b>Step {idx}</b></Body>
|
<div style="margin-left: 2px;">
|
||||||
<Body size="XS">Do this:</Body>
|
<Body size="XS"><b>Step {idx}</b></Body>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<Detail size="S">{block?.name?.toUpperCase() || ""}</Detail>
|
<input
|
||||||
|
placeholder="Enter some text"
|
||||||
|
name="name"
|
||||||
|
autocomplete="off"
|
||||||
|
value={automationName}
|
||||||
|
on:input={e => {
|
||||||
|
automationName = e.target.value.trim()
|
||||||
|
}}
|
||||||
|
on:click={startTyping}
|
||||||
|
on:blur={async () => {
|
||||||
|
typing = false
|
||||||
|
if (automationNameError) {
|
||||||
|
automationName = stepNames[block.id] || block?.name
|
||||||
|
} else {
|
||||||
|
await saveName()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="blockTitle">
|
<div class="blockTitle">
|
||||||
{#if showTestStatus && testResult}
|
{#if showTestStatus && testResult}
|
||||||
<div style="float: right;">
|
<div class="status-container">
|
||||||
<StatusLight
|
<div style="float:right;">
|
||||||
positive={status?.positive}
|
<StatusLight
|
||||||
yellow={status?.yellow}
|
positive={status?.positive}
|
||||||
negative={status?.negative}
|
yellow={status?.yellow}
|
||||||
><Body size="XS">{status?.message}</Body></StatusLight
|
negative={status?.negative}
|
||||||
>
|
>
|
||||||
|
<Body size="XS">{status?.message}</Body>
|
||||||
|
</StatusLight>
|
||||||
|
</div>
|
||||||
|
<Icon
|
||||||
|
on:click={() => dispatch("toggle")}
|
||||||
|
hoverable
|
||||||
|
name={open ? "ChevronUp" : "ChevronDown"}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
<div
|
||||||
style="margin-left: 10px; margin-bottom: var(--spacing-xs);"
|
class="context-actions"
|
||||||
|
class:hide-context-actions={typing}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
onSelect(block)
|
onSelect(block)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon hoverable name={open ? "ChevronUp" : "ChevronDown"} />
|
{#if !showTestStatus}
|
||||||
|
{#if !isHeaderTrigger && !loopBlock && (block?.features?.[Features.LOOPING] || !block.features)}
|
||||||
|
<AbsTooltip type="info" text="Add looping">
|
||||||
|
<Icon on:click={addLooping} hoverable name="RotateCW" />
|
||||||
|
</AbsTooltip>
|
||||||
|
{/if}
|
||||||
|
<AbsTooltip type="negative" text="Delete step">
|
||||||
|
<Icon on:click={deleteStep} hoverable name="DeleteOutline" />
|
||||||
|
</AbsTooltip>
|
||||||
|
{/if}
|
||||||
|
{#if !showTestStatus}
|
||||||
|
<Icon
|
||||||
|
on:click={() => dispatch("toggle")}
|
||||||
|
hoverable
|
||||||
|
name={open ? "ChevronUp" : "ChevronDown"}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if automationNameError}
|
||||||
|
<div class="error-container">
|
||||||
|
<AbsTooltip type="negative" text={automationNameError}>
|
||||||
|
<div class="error-icon">
|
||||||
|
<Icon size="S" name="Alert" />
|
||||||
|
</div>
|
||||||
|
</AbsTooltip>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.status-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
/* You can also add padding or margin to adjust the spacing between the text and the chevron if needed. */
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
.center-items {
|
.center-items {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -117,10 +233,55 @@
|
||||||
|
|
||||||
.blockSection {
|
.blockSection {
|
||||||
padding: var(--spacing-xl);
|
padding: var(--spacing-xl);
|
||||||
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blockTitle {
|
.blockTitle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
}
|
||||||
|
|
||||||
|
.hide-context-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
color: var(--ink);
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
font-size: var(--spectrum-alias-font-size-default);
|
||||||
|
width: 230px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide arrows for number fields */
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing {
|
||||||
|
border: 1px solid var(--spectrum-global-color-static-blue-500);
|
||||||
|
border-radius: 4px 4px 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-error {
|
||||||
|
border: 1px solid var(--spectrum-global-color-static-red-500);
|
||||||
|
border-radius: 4px 4px 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon :global(.spectrum-Icon) {
|
||||||
|
fill: var(--spectrum-global-color-red-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
padding-top: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Add test data"
|
title="Add test data"
|
||||||
confirmText="Test"
|
confirmText="Test"
|
||||||
|
size="M"
|
||||||
showConfirmButton={true}
|
showConfirmButton={true}
|
||||||
disabled={isError}
|
disabled={isError}
|
||||||
onConfirm={testAutomation}
|
onConfirm={testAutomation}
|
||||||
|
|
|
@ -58,7 +58,6 @@
|
||||||
let fillWidth = true
|
let fillWidth = true
|
||||||
let inputData
|
let inputData
|
||||||
let codeBindingOpen = false
|
let codeBindingOpen = false
|
||||||
|
|
||||||
$: filters = lookForFilters(schemaProperties) || []
|
$: filters = lookForFilters(schemaProperties) || []
|
||||||
$: tempFilters = filters
|
$: tempFilters = filters
|
||||||
$: stepId = block.stepId
|
$: stepId = block.stepId
|
||||||
|
@ -155,7 +154,7 @@
|
||||||
}
|
}
|
||||||
let blockIdx = allSteps.findIndex(step => step.id === block.id)
|
let blockIdx = allSteps.findIndex(step => step.id === block.id)
|
||||||
|
|
||||||
// Extract all outputs from all previous steps as available bindins
|
// Extract all outputs from all previous steps as available bindingsx§x
|
||||||
let bindings = []
|
let bindings = []
|
||||||
let loopBlockCount = 0
|
let loopBlockCount = 0
|
||||||
for (let idx = 0; idx < blockIdx; idx++) {
|
for (let idx = 0; idx < blockIdx; idx++) {
|
||||||
|
@ -183,20 +182,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const outputs = Object.entries(schema)
|
const outputs = Object.entries(schema)
|
||||||
|
|
||||||
let bindingIcon = ""
|
let bindingIcon = ""
|
||||||
let bindindingRank = 0
|
let bindingRank = 0
|
||||||
|
|
||||||
if (idx === 0) {
|
if (idx === 0) {
|
||||||
bindingIcon = automation.trigger.icon
|
bindingIcon = automation.trigger.icon
|
||||||
} else if (isLoopBlock) {
|
} else if (isLoopBlock) {
|
||||||
bindingIcon = "Reuse"
|
bindingIcon = "Reuse"
|
||||||
bindindingRank = idx + 1
|
bindingRank = idx + 1
|
||||||
} else {
|
} else {
|
||||||
bindingIcon = allSteps[idx].icon
|
bindingIcon = allSteps[idx].icon
|
||||||
bindindingRank = idx - loopBlockCount
|
bindingRank = idx - loopBlockCount
|
||||||
}
|
}
|
||||||
|
let bindingName =
|
||||||
|
automation.stepNames?.[allSteps[idx - loopBlockCount].id]
|
||||||
bindings = bindings.concat(
|
bindings = bindings.concat(
|
||||||
outputs.map(([name, value]) => {
|
outputs.map(([name, value]) => {
|
||||||
let runtimeName = isLoopBlock
|
let runtimeName = isLoopBlock
|
||||||
|
@ -205,14 +203,20 @@
|
||||||
? `steps[${idx - loopBlockCount}].${name}`
|
? `steps[${idx - loopBlockCount}].${name}`
|
||||||
: `steps.${idx - loopBlockCount}.${name}`
|
: `steps.${idx - loopBlockCount}.${name}`
|
||||||
const runtime = idx === 0 ? `trigger.${name}` : runtimeName
|
const runtime = idx === 0 ? `trigger.${name}` : runtimeName
|
||||||
const categoryName =
|
|
||||||
idx === 0
|
let categoryName
|
||||||
? "Trigger outputs"
|
if (idx === 0) {
|
||||||
: isLoopBlock
|
categoryName = "Trigger outputs"
|
||||||
? "Loop Outputs"
|
} else if (isLoopBlock) {
|
||||||
: `Step ${idx - loopBlockCount} outputs`
|
categoryName = "Loop Outputs"
|
||||||
|
} else if (bindingName) {
|
||||||
|
categoryName = `${bindingName} outputs`
|
||||||
|
} else {
|
||||||
|
categoryName = `Step ${idx - loopBlockCount} outputs`
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
readableBinding: runtime,
|
readableBinding: bindingName ? `${bindingName}.${name}` : runtime,
|
||||||
runtimeBinding: runtime,
|
runtimeBinding: runtime,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
|
@ -221,7 +225,7 @@
|
||||||
display: {
|
display: {
|
||||||
type: value.type,
|
type: value.type,
|
||||||
name: name,
|
name: name,
|
||||||
rank: bindindingRank,
|
rank: bindingRank,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -277,6 +281,16 @@
|
||||||
return !dependsOn || !!inputData[dependsOn]
|
return !dependsOn || !!inputData[dependsOn]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldRenderField(value) {
|
||||||
|
return (
|
||||||
|
value.customType !== "row" &&
|
||||||
|
value.customType !== "code" &&
|
||||||
|
value.customType !== "queryParams" &&
|
||||||
|
value.customType !== "cron" &&
|
||||||
|
value.customType !== "triggerSchema"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
await environment.loadVariables()
|
await environment.loadVariables()
|
||||||
|
@ -289,245 +303,248 @@
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
{#each schemaProperties as [key, value]}
|
{#each schemaProperties as [key, value]}
|
||||||
{#if canShowField(key, value)}
|
{#if canShowField(key, value)}
|
||||||
<div class="block-field">
|
<div class:block-field={shouldRenderField(value)}>
|
||||||
{#if key !== "fields" && value.type !== "boolean"}
|
{#if key !== "fields" && value.type !== "boolean" && shouldRenderField(value)}
|
||||||
<Label
|
<Label
|
||||||
tooltip={value.title === "Binding / Value"
|
tooltip={value.title === "Binding / Value"
|
||||||
? "If using the String input type, please use a comma or newline separated string"
|
? "If using the String input type, please use a comma or newline separated string"
|
||||||
: null}>{value.title || (key === "row" ? "Table" : key)}</Label
|
: null}>{value.title || (key === "row" ? "Table" : key)}</Label
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if value.type === "string" && value.enum && canShowField(key, value)}
|
<div class:field-width={shouldRenderField(value)}>
|
||||||
<Select
|
{#if value.type === "string" && value.enum && canShowField(key, value)}
|
||||||
on:change={e => onChange(e, key)}
|
<Select
|
||||||
value={inputData[key]}
|
|
||||||
placeholder={false}
|
|
||||||
options={value.enum}
|
|
||||||
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 => {
|
|
||||||
onChange(e, key)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{:else if value.type === "boolean"}
|
|
||||||
<div style="margin-top: 10px">
|
|
||||||
<Checkbox
|
|
||||||
text={value.title}
|
|
||||||
value={inputData[key]}
|
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{:else if value.type === "date"}
|
|
||||||
<DrawerBindableSlot
|
|
||||||
fillWidth
|
|
||||||
title={value.title}
|
|
||||||
panel={AutomationBindingPanel}
|
|
||||||
type={"date"}
|
|
||||||
value={inputData[key]}
|
|
||||||
on:change={e => onChange(e, key)}
|
|
||||||
{bindings}
|
|
||||||
allowJS={true}
|
|
||||||
updateOnChange={false}
|
|
||||||
drawerLeft="260px"
|
|
||||||
>
|
|
||||||
<DatePicker
|
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
on:change={e => onChange(e, key)}
|
placeholder={false}
|
||||||
|
options={value.enum}
|
||||||
|
getOptionLabel={(x, idx) =>
|
||||||
|
value.pretty ? value.pretty[idx] : x}
|
||||||
/>
|
/>
|
||||||
</DrawerBindableSlot>
|
{:else if value.type === "json"}
|
||||||
{:else if value.customType === "column"}
|
<Editor
|
||||||
<Select
|
editorHeight="250"
|
||||||
on:change={e => onChange(e, key)}
|
editorWidth="448"
|
||||||
value={inputData[key]}
|
mode="json"
|
||||||
options={Object.keys(table?.schema || {})}
|
value={inputData[key]?.value}
|
||||||
/>
|
on:change={e => {
|
||||||
{:else if value.customType === "filters"}
|
onChange(e, key)
|
||||||
<ActionButton on:click={drawer.show}>Define filters</ActionButton>
|
}}
|
||||||
<Drawer bind:this={drawer} {fillWidth} title="Filtering">
|
|
||||||
<Button cta slot="buttons" on:click={() => saveFilters(key)}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<FilterDrawer
|
|
||||||
slot="body"
|
|
||||||
{filters}
|
|
||||||
{bindings}
|
|
||||||
{schemaFields}
|
|
||||||
datasource={{ type: "table", tableId }}
|
|
||||||
panel={AutomationBindingPanel}
|
|
||||||
fillWidth
|
|
||||||
on:change={e => (tempFilters = e.detail)}
|
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
{:else if value.type === "boolean"}
|
||||||
{:else if value.customType === "password"}
|
<div style="margin-top: 10px">
|
||||||
<Input
|
<Checkbox
|
||||||
type="password"
|
text={value.title}
|
||||||
on:change={e => onChange(e, key)}
|
value={inputData[key]}
|
||||||
value={inputData[key]}
|
on:change={e => onChange(e, key)}
|
||||||
/>
|
/>
|
||||||
{:else if value.customType === "email"}
|
</div>
|
||||||
{#if isTestModal}
|
{:else if value.type === "date"}
|
||||||
<ModalBindableInput
|
<DrawerBindableSlot
|
||||||
title={value.title}
|
|
||||||
value={inputData[key]}
|
|
||||||
panel={AutomationBindingPanel}
|
|
||||||
type="email"
|
|
||||||
on:change={e => onChange(e, key)}
|
|
||||||
{bindings}
|
|
||||||
fillWidth
|
|
||||||
updateOnChange={false}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<DrawerBindableInput
|
|
||||||
fillWidth
|
fillWidth
|
||||||
title={value.title}
|
title={value.title}
|
||||||
panel={AutomationBindingPanel}
|
panel={AutomationBindingPanel}
|
||||||
type="email"
|
type={"date"}
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
{bindings}
|
{bindings}
|
||||||
allowJS={false}
|
allowJS={true}
|
||||||
updateOnChange={false}
|
updateOnChange={false}
|
||||||
drawerLeft="260px"
|
drawerLeft="260px"
|
||||||
/>
|
>
|
||||||
{/if}
|
<DatePicker
|
||||||
{:else if value.customType === "query"}
|
value={inputData[key]}
|
||||||
<QuerySelector
|
on:change={e => onChange(e, key)}
|
||||||
on:change={e => onChange(e, key)}
|
/>
|
||||||
value={inputData[key]}
|
</DrawerBindableSlot>
|
||||||
/>
|
{:else if value.customType === "column"}
|
||||||
{:else if value.customType === "cron"}
|
<Select
|
||||||
<CronBuilder
|
|
||||||
on:change={e => onChange(e, key)}
|
|
||||||
value={inputData[key]}
|
|
||||||
/>
|
|
||||||
{:else if value.customType === "queryParams"}
|
|
||||||
<QueryParamSelector
|
|
||||||
on:change={e => onChange(e, key)}
|
|
||||||
value={inputData[key]}
|
|
||||||
{bindings}
|
|
||||||
/>
|
|
||||||
{:else if value.customType === "table"}
|
|
||||||
<TableSelector
|
|
||||||
{isTrigger}
|
|
||||||
value={inputData[key]}
|
|
||||||
on:change={e => onChange(e, key)}
|
|
||||||
/>
|
|
||||||
{:else if value.customType === "row"}
|
|
||||||
<RowSelector
|
|
||||||
value={inputData[key]}
|
|
||||||
meta={inputData["meta"] || {}}
|
|
||||||
on:change={e => {
|
|
||||||
if (e.detail?.key) {
|
|
||||||
onChange(e, e.detail.key)
|
|
||||||
} else {
|
|
||||||
onChange(e, key)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
{bindings}
|
|
||||||
{isTestModal}
|
|
||||||
{isUpdateRow}
|
|
||||||
/>
|
|
||||||
{:else if value.customType === "webhookUrl"}
|
|
||||||
<WebhookDisplay
|
|
||||||
on:change={e => onChange(e, key)}
|
|
||||||
value={inputData[key]}
|
|
||||||
/>
|
|
||||||
{:else if value.customType === "fields"}
|
|
||||||
<FieldSelector
|
|
||||||
{block}
|
|
||||||
value={inputData[key]}
|
|
||||||
on:change={e => onChange(e, key)}
|
|
||||||
{bindings}
|
|
||||||
{isTestModal}
|
|
||||||
/>
|
|
||||||
{:else if value.customType === "triggerSchema"}
|
|
||||||
<SchemaSetup
|
|
||||||
on:change={e => onChange(e, key)}
|
|
||||||
value={inputData[key]}
|
|
||||||
/>
|
|
||||||
{:else if value.customType === "code"}
|
|
||||||
<CodeEditorModal>
|
|
||||||
{#if codeMode == EditorModes.JS}
|
|
||||||
<ActionButton
|
|
||||||
on:click={() => (codeBindingOpen = !codeBindingOpen)}
|
|
||||||
quiet
|
|
||||||
icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"}
|
|
||||||
>
|
|
||||||
<Detail size="S">Bindings</Detail>
|
|
||||||
</ActionButton>
|
|
||||||
{#if codeBindingOpen}
|
|
||||||
<pre>{JSON.stringify(bindings, null, 2)}</pre>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
<CodeEditor
|
|
||||||
value={inputData[key]}
|
|
||||||
on:change={e => {
|
|
||||||
// need to pass without the value inside
|
|
||||||
onChange({ detail: e.detail }, key)
|
|
||||||
inputData[key] = e.detail
|
|
||||||
}}
|
|
||||||
completions={stepCompletions}
|
|
||||||
mode={codeMode}
|
|
||||||
autocompleteEnabled={codeMode != EditorModes.JS}
|
|
||||||
height={500}
|
|
||||||
/>
|
|
||||||
<div class="messaging">
|
|
||||||
{#if codeMode == EditorModes.Handlebars}
|
|
||||||
<Icon name="FlashOn" />
|
|
||||||
<div class="messaging-wrap">
|
|
||||||
<div>
|
|
||||||
Add available bindings by typing <strong>
|
|
||||||
}}
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</CodeEditorModal>
|
|
||||||
{:else if value.customType === "loopOption"}
|
|
||||||
<Select
|
|
||||||
on:change={e => onChange(e, key)}
|
|
||||||
autoWidth
|
|
||||||
value={inputData[key]}
|
|
||||||
options={["Array", "String"]}
|
|
||||||
defaultValue={"Array"}
|
|
||||||
/>
|
|
||||||
{:else if value.type === "string" || value.type === "number" || value.type === "integer"}
|
|
||||||
{#if isTestModal}
|
|
||||||
<ModalBindableInput
|
|
||||||
title={value.title}
|
|
||||||
value={inputData[key]}
|
|
||||||
panel={AutomationBindingPanel}
|
|
||||||
type={value.customType}
|
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
{bindings}
|
value={inputData[key]}
|
||||||
updateOnChange={false}
|
options={Object.keys(table?.schema || {})}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else if value.customType === "filters"}
|
||||||
<div class="test">
|
<ActionButton on:click={drawer.show}>Define filters</ActionButton>
|
||||||
|
<Drawer bind:this={drawer} {fillWidth} title="Filtering">
|
||||||
|
<Button cta slot="buttons" on:click={() => saveFilters(key)}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<FilterDrawer
|
||||||
|
slot="body"
|
||||||
|
{filters}
|
||||||
|
{bindings}
|
||||||
|
{schemaFields}
|
||||||
|
datasource={{ type: "table", tableId }}
|
||||||
|
panel={AutomationBindingPanel}
|
||||||
|
fillWidth
|
||||||
|
on:change={e => (tempFilters = e.detail)}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
{:else if value.customType === "password"}
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
value={inputData[key]}
|
||||||
|
/>
|
||||||
|
{:else if value.customType === "email"}
|
||||||
|
{#if isTestModal}
|
||||||
|
<ModalBindableInput
|
||||||
|
title={value.title}
|
||||||
|
value={inputData[key]}
|
||||||
|
panel={AutomationBindingPanel}
|
||||||
|
type="email"
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
{bindings}
|
||||||
|
fillWidth
|
||||||
|
updateOnChange={false}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
fillWidth={true}
|
fillWidth
|
||||||
title={value.title}
|
title={value.title}
|
||||||
panel={AutomationBindingPanel}
|
panel={AutomationBindingPanel}
|
||||||
type={value.customType}
|
type="email"
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
allowJS={false}
|
||||||
updateOnChange={false}
|
updateOnChange={false}
|
||||||
placeholder={value.customType === "queryLimit"
|
|
||||||
? queryLimit
|
|
||||||
: ""}
|
|
||||||
drawerLeft="260px"
|
drawerLeft="260px"
|
||||||
/>
|
/>
|
||||||
</div>
|
{/if}
|
||||||
|
{:else if value.customType === "query"}
|
||||||
|
<QuerySelector
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
value={inputData[key]}
|
||||||
|
/>
|
||||||
|
{:else if value.customType === "cron"}
|
||||||
|
<CronBuilder
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
value={inputData[key]}
|
||||||
|
/>
|
||||||
|
{:else if value.customType === "queryParams"}
|
||||||
|
<QueryParamSelector
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
value={inputData[key]}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
|
{:else if value.customType === "table"}
|
||||||
|
<TableSelector
|
||||||
|
{isTrigger}
|
||||||
|
value={inputData[key]}
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
/>
|
||||||
|
{:else if value.customType === "row"}
|
||||||
|
<RowSelector
|
||||||
|
value={inputData[key]}
|
||||||
|
meta={inputData["meta"] || {}}
|
||||||
|
on:change={e => {
|
||||||
|
if (e.detail?.key) {
|
||||||
|
onChange(e, e.detail.key)
|
||||||
|
} else {
|
||||||
|
onChange(e, key)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{bindings}
|
||||||
|
{isTestModal}
|
||||||
|
{isUpdateRow}
|
||||||
|
/>
|
||||||
|
{:else if value.customType === "webhookUrl"}
|
||||||
|
<WebhookDisplay
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
value={inputData[key]}
|
||||||
|
/>
|
||||||
|
{:else if value.customType === "fields"}
|
||||||
|
<FieldSelector
|
||||||
|
{block}
|
||||||
|
value={inputData[key]}
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
{bindings}
|
||||||
|
{isTestModal}
|
||||||
|
/>
|
||||||
|
{:else if value.customType === "triggerSchema"}
|
||||||
|
<SchemaSetup
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
value={inputData[key]}
|
||||||
|
/>
|
||||||
|
{:else if value.customType === "code"}
|
||||||
|
<CodeEditorModal>
|
||||||
|
{#if codeMode == EditorModes.JS}
|
||||||
|
<ActionButton
|
||||||
|
on:click={() => (codeBindingOpen = !codeBindingOpen)}
|
||||||
|
quiet
|
||||||
|
icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"}
|
||||||
|
>
|
||||||
|
<Detail size="S">Bindings</Detail>
|
||||||
|
</ActionButton>
|
||||||
|
{#if codeBindingOpen}
|
||||||
|
<pre>{JSON.stringify(bindings, null, 2)}</pre>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
<CodeEditor
|
||||||
|
value={inputData[key]}
|
||||||
|
on:change={e => {
|
||||||
|
// need to pass without the value inside
|
||||||
|
onChange({ detail: e.detail }, key)
|
||||||
|
inputData[key] = e.detail
|
||||||
|
}}
|
||||||
|
completions={stepCompletions}
|
||||||
|
mode={codeMode}
|
||||||
|
autocompleteEnabled={codeMode != EditorModes.JS}
|
||||||
|
height={500}
|
||||||
|
/>
|
||||||
|
<div class="messaging">
|
||||||
|
{#if codeMode == EditorModes.Handlebars}
|
||||||
|
<Icon name="FlashOn" />
|
||||||
|
<div class="messaging-wrap">
|
||||||
|
<div>
|
||||||
|
Add available bindings by typing <strong>
|
||||||
|
}}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</CodeEditorModal>
|
||||||
|
{:else if value.customType === "loopOption"}
|
||||||
|
<Select
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
autoWidth
|
||||||
|
value={inputData[key]}
|
||||||
|
options={["Array", "String"]}
|
||||||
|
defaultValue={"Array"}
|
||||||
|
/>
|
||||||
|
{:else if value.type === "string" || value.type === "number" || value.type === "integer"}
|
||||||
|
{#if isTestModal}
|
||||||
|
<ModalBindableInput
|
||||||
|
title={value.title}
|
||||||
|
value={inputData[key]}
|
||||||
|
panel={AutomationBindingPanel}
|
||||||
|
type={value.customType}
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
{bindings}
|
||||||
|
updateOnChange={false}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<div class="test">
|
||||||
|
<DrawerBindableInput
|
||||||
|
fillWidth={true}
|
||||||
|
title={value.title}
|
||||||
|
panel={AutomationBindingPanel}
|
||||||
|
type={value.customType}
|
||||||
|
value={inputData[key]}
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
{bindings}
|
||||||
|
updateOnChange={false}
|
||||||
|
placeholder={value.customType === "queryLimit"
|
||||||
|
? queryLimit
|
||||||
|
: ""}
|
||||||
|
drawerLeft="260px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -541,6 +558,10 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.field-width {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
.messaging {
|
.messaging {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -555,8 +576,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.block-field {
|
.block-field {
|
||||||
display: grid;
|
display: flex; /* Use Flexbox */
|
||||||
grid-gap: 5px;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row; /* Arrange label and field side by side */
|
||||||
|
align-items: center; /* Align vertically in the center */
|
||||||
|
gap: 10px; /* Add some space between label and field */
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test :global(.drawer) {
|
.test :global(.drawer) {
|
||||||
|
|
|
@ -23,7 +23,9 @@
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Button primary on:click={show}>Edit Code</Button>
|
<div class="center">
|
||||||
|
<Button primary on:click={show}>Edit Code</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container :global(section > header) {
|
.container :global(section > header) {
|
||||||
|
@ -33,4 +35,9 @@
|
||||||
.container :global(textarea) {
|
.container :global(textarea) {
|
||||||
min-height: 60px;
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { queries } from "stores/backend"
|
import { queries } from "stores/backend"
|
||||||
import { Select } from "@budibase/bbui"
|
import { Select, Label } from "@budibase/bbui"
|
||||||
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
||||||
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||||
|
|
||||||
|
@ -27,41 +27,55 @@
|
||||||
$: if (value?.queryId == null) value = { queryId: "" }
|
$: if (value?.queryId == null) value = { queryId: "" }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="block-field">
|
<div class="schema-fields">
|
||||||
<Select
|
<Label>Query</Label>
|
||||||
label="Query"
|
<div class="field-width">
|
||||||
on:change={onChangeQuery}
|
<Select
|
||||||
value={value.queryId}
|
on:change={onChangeQuery}
|
||||||
options={$queries.list}
|
value={value.queryId}
|
||||||
getOptionValue={query => query._id}
|
options={$queries.list}
|
||||||
getOptionLabel={query => query.name}
|
getOptionValue={query => query._id}
|
||||||
/>
|
getOptionLabel={query => query.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if parameters.length}
|
{#if parameters.length}
|
||||||
<div class="schema-fields">
|
<div class="schema-fields">
|
||||||
{#each parameters as field}
|
{#each parameters as field}
|
||||||
<DrawerBindableInput
|
<Label>{field.name}</Label>
|
||||||
panel={AutomationBindingPanel}
|
<div class="field-width">
|
||||||
extraThin
|
<DrawerBindableInput
|
||||||
value={value[field.name]}
|
panel={AutomationBindingPanel}
|
||||||
on:change={e => onChange(e, field)}
|
extraThin
|
||||||
label={field.name}
|
value={value[field.name]}
|
||||||
type="string"
|
on:change={e => onChange(e, field)}
|
||||||
{bindings}
|
type="string"
|
||||||
fillWidth={true}
|
{bindings}
|
||||||
updateOnChange={false}
|
fillWidth={true}
|
||||||
/>
|
updateOnChange={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.schema-fields {
|
.field-width {
|
||||||
display: grid;
|
width: 320px;
|
||||||
grid-gap: var(--spacing-xl);
|
|
||||||
margin-top: var(--spacing-xl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.schema-fields {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.schema-fields :global(label) {
|
.schema-fields :global(label) {
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { Select, Checkbox } from "@budibase/bbui"
|
import { Select, Checkbox, Label } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import RowSelectorTypes from "./RowSelectorTypes.svelte"
|
import RowSelectorTypes from "./RowSelectorTypes.svelte"
|
||||||
import DrawerBindableSlot from "../../common/bindings/DrawerBindableSlot.svelte"
|
import DrawerBindableSlot from "../../common/bindings/DrawerBindableSlot.svelte"
|
||||||
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||||
|
import { TableNames } from "constants"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -99,41 +100,25 @@
|
||||||
$: if (value?.tableId == null) value = { tableId: "" }
|
$: if (value?.tableId == null) value = { tableId: "" }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Select
|
<div class="schema-fields">
|
||||||
on:change={onChangeTable}
|
<Label>Table</Label>
|
||||||
value={value.tableId}
|
<div class="field-width">
|
||||||
options={$tables.list}
|
<Select
|
||||||
getOptionLabel={table => table.name}
|
on:change={onChangeTable}
|
||||||
getOptionValue={table => table._id}
|
value={value.tableId}
|
||||||
/>
|
options={$tables.list.filter(table => table._id !== TableNames.USERS)}
|
||||||
|
getOptionLabel={table => table.name}
|
||||||
|
getOptionValue={table => table._id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{#if schemaFields.length}
|
{#if schemaFields.length}
|
||||||
<div class="schema-fields">
|
{#each schemaFields as [field, schema]}
|
||||||
{#each schemaFields as [field, schema]}
|
<div class="schema-fields">
|
||||||
{#if !schema.autocolumn && schema.type !== "attachment"}
|
<Label>{field}</Label>
|
||||||
{#if isTestModal}
|
<div class="field-width">
|
||||||
<RowSelectorTypes
|
{#if !schema.autocolumn && schema.type !== "attachment"}
|
||||||
{isTestModal}
|
{#if isTestModal}
|
||||||
{field}
|
|
||||||
{schema}
|
|
||||||
bindings={parsedBindings}
|
|
||||||
{value}
|
|
||||||
{onChange}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<DrawerBindableSlot
|
|
||||||
fillWidth
|
|
||||||
title={value.title}
|
|
||||||
label={field}
|
|
||||||
panel={AutomationBindingPanel}
|
|
||||||
type={schema.type}
|
|
||||||
{schema}
|
|
||||||
value={value[field]}
|
|
||||||
on:change={e => onChange(e, field)}
|
|
||||||
{bindings}
|
|
||||||
allowJS={true}
|
|
||||||
updateOnChange={false}
|
|
||||||
drawerLeft="260px"
|
|
||||||
>
|
|
||||||
<RowSelectorTypes
|
<RowSelectorTypes
|
||||||
{isTestModal}
|
{isTestModal}
|
||||||
{field}
|
{field}
|
||||||
|
@ -142,28 +127,61 @@
|
||||||
{value}
|
{value}
|
||||||
{onChange}
|
{onChange}
|
||||||
/>
|
/>
|
||||||
</DrawerBindableSlot>
|
{:else}
|
||||||
|
<DrawerBindableSlot
|
||||||
|
fillWidth
|
||||||
|
title={value.title}
|
||||||
|
panel={AutomationBindingPanel}
|
||||||
|
type={schema.type}
|
||||||
|
{schema}
|
||||||
|
value={value[field]}
|
||||||
|
on:change={e => onChange(e, field)}
|
||||||
|
{bindings}
|
||||||
|
allowJS={true}
|
||||||
|
updateOnChange={false}
|
||||||
|
drawerLeft="260px"
|
||||||
|
>
|
||||||
|
<RowSelectorTypes
|
||||||
|
{isTestModal}
|
||||||
|
{field}
|
||||||
|
{schema}
|
||||||
|
bindings={parsedBindings}
|
||||||
|
{value}
|
||||||
|
{onChange}
|
||||||
|
/>
|
||||||
|
</DrawerBindableSlot>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
{#if isUpdateRow && schema.type === "link"}
|
{#if isUpdateRow && schema.type === "link"}
|
||||||
<div class="checkbox-field">
|
<div class="checkbox-field">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value={meta.fields?.[field]?.clearRelationships}
|
value={meta.fields?.[field]?.clearRelationships}
|
||||||
text={"Clear relationships if empty?"}
|
text={"Clear relationships if empty?"}
|
||||||
size={"S"}
|
size={"S"}
|
||||||
on:change={e => onChangeSetting(e, field)}
|
on:change={e => onChangeSetting(e, field)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.field-width {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
.schema-fields {
|
.schema-fields {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-gap: var(--spacing-s);
|
justify-content: space-between;
|
||||||
margin-top: var(--spacing-s);
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.schema-fields :global(label) {
|
.schema-fields :global(label) {
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { Select, DatePicker, Multiselect, TextArea } from "@budibase/bbui"
|
||||||
Select,
|
|
||||||
DatePicker,
|
|
||||||
Multiselect,
|
|
||||||
TextArea,
|
|
||||||
Label,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||||
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
||||||
import ModalBindableInput from "../../common/bindings/ModalBindableInput.svelte"
|
import ModalBindableInput from "../../common/bindings/ModalBindableInput.svelte"
|
||||||
|
@ -33,20 +27,14 @@
|
||||||
{#if schemaHasOptions(schema) && schema.type !== "array"}
|
{#if schemaHasOptions(schema) && schema.type !== "array"}
|
||||||
<Select
|
<Select
|
||||||
on:change={e => onChange(e, field)}
|
on:change={e => onChange(e, field)}
|
||||||
label={field}
|
|
||||||
value={value[field]}
|
value={value[field]}
|
||||||
options={schema.constraints.inclusion}
|
options={schema.constraints.inclusion}
|
||||||
/>
|
/>
|
||||||
{:else if schema.type === "datetime"}
|
{:else if schema.type === "datetime"}
|
||||||
<DatePicker
|
<DatePicker value={value[field]} on:change={e => onChange(e, field)} />
|
||||||
label={field}
|
|
||||||
value={value[field]}
|
|
||||||
on:change={e => onChange(e, field)}
|
|
||||||
/>
|
|
||||||
{:else if schema.type === "boolean"}
|
{:else if schema.type === "boolean"}
|
||||||
<Select
|
<Select
|
||||||
on:change={e => onChange(e, field)}
|
on:change={e => onChange(e, field)}
|
||||||
label={field}
|
|
||||||
value={value[field]}
|
value={value[field]}
|
||||||
options={[
|
options={[
|
||||||
{ label: "True", value: "true" },
|
{ label: "True", value: "true" },
|
||||||
|
@ -56,19 +44,13 @@
|
||||||
{:else if schema.type === "array"}
|
{:else if schema.type === "array"}
|
||||||
<Multiselect
|
<Multiselect
|
||||||
bind:value={value[field]}
|
bind:value={value[field]}
|
||||||
label={field}
|
|
||||||
options={schema.constraints.inclusion}
|
options={schema.constraints.inclusion}
|
||||||
on:change={e => onChange(e, field)}
|
on:change={e => onChange(e, field)}
|
||||||
/>
|
/>
|
||||||
{:else if schema.type === "longform"}
|
{:else if schema.type === "longform"}
|
||||||
<TextArea
|
<TextArea bind:value={value[field]} on:change={e => onChange(e, field)} />
|
||||||
label={field}
|
|
||||||
bind:value={value[field]}
|
|
||||||
on:change={e => onChange(e, field)}
|
|
||||||
/>
|
|
||||||
{:else if schema.type === "json"}
|
{:else if schema.type === "json"}
|
||||||
<span>
|
<span>
|
||||||
<Label>{field}</Label>
|
|
||||||
<Editor
|
<Editor
|
||||||
editorHeight="150"
|
editorHeight="150"
|
||||||
mode="json"
|
mode="json"
|
||||||
|
@ -92,7 +74,6 @@
|
||||||
panel={AutomationBindingPanel}
|
panel={AutomationBindingPanel}
|
||||||
value={value[field]}
|
value={value[field]}
|
||||||
on:change={e => onChange(e, field)}
|
on:change={e => onChange(e, field)}
|
||||||
label={field}
|
|
||||||
type="string"
|
type="string"
|
||||||
bindings={parsedBindings}
|
bindings={parsedBindings}
|
||||||
fillWidth={true}
|
fillWidth={true}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<Select
|
<Select
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
bind:value
|
bind:value
|
||||||
options={filteredTables}
|
options={filteredTables.filter(table => table._id !== TableNames.USERS)}
|
||||||
getOptionLabel={table => table.name}
|
getOptionLabel={table => table.name}
|
||||||
getOptionValue={table => table._id}
|
getOptionValue={table => table._id}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,123 +4,33 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
version="1.1"
|
viewBox="0 0 265 265"
|
||||||
id="Layer_1"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
viewBox="0 0 48 48"
|
|
||||||
style="enable-background:new 0 0 48 48;"
|
|
||||||
xml:space="preserve"
|
|
||||||
{height}
|
{height}
|
||||||
{width}
|
{width}
|
||||||
>
|
>
|
||||||
<style type="text/css">
|
<g clip-path="url(#clip0_1_1799)">
|
||||||
.st0 {
|
|
||||||
fill: #393c44;
|
|
||||||
}
|
|
||||||
.st1 {
|
|
||||||
fill: #ffffff;
|
|
||||||
}
|
|
||||||
.st2 {
|
|
||||||
fill: #4285f4;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<rect x="-152.17" y="-24.17" class="st0" width="96.17" height="96.17" />
|
|
||||||
<path
|
|
||||||
class="st1"
|
|
||||||
d="M-83.19,48h-41.79c-1.76,0-3.19-1.43-3.19-3.19V3.02c0-1.76,1.43-3.19,3.19-3.19h41.79
|
|
||||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C-80,46.57-81.43,48-83.19,48z"
|
|
||||||
/>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
class="st0"
|
|
||||||
d="M-99.62,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
|
||||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
|
||||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35h-4.89V12.57H-99.62z
|
|
||||||
M-93.46,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
|
||||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
|
||||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
|
||||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
|
||||||
C-93.55,28.92-93.46,28.52-93.46,28.11z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
class="st0"
|
|
||||||
d="M-114.76,12.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58
|
|
||||||
c0.86,0.39,1.59,0.91,2.19,1.57c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89
|
|
||||||
c-0.35,0.9-0.84,1.68-1.47,2.35c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V35
|
|
||||||
h-4.89V12.57H-114.76z M-108.6,28.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
|
||||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
|
||||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
|
||||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
|
||||||
C-108.68,28.92-108.6,28.52-108.6,28.11z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
class="st2"
|
|
||||||
d="M44.81,159H3.02c-1.76,0-3.19-1.43-3.19-3.19v-41.79c0-1.76,1.43-3.19,3.19-3.19h41.79
|
|
||||||
c1.76,0,3.19,1.43,3.19,3.19v41.79C48,157.57,46.57,159,44.81,159z"
|
|
||||||
/>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
class="st1"
|
|
||||||
d="M28.38,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
|
||||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
|
||||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146h-4.89v-22.43H28.38z
|
|
||||||
M34.54,139.11c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69
|
|
||||||
c-0.38-0.17-0.79-0.26-1.24-0.26c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01
|
|
||||||
c-0.17,0.39-0.26,0.8-0.26,1.23c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68
|
|
||||||
c0.39,0.17,0.8,0.26,1.23,0.26c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1
|
|
||||||
C34.45,139.92,34.54,139.52,34.54,139.11z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
class="st1"
|
|
||||||
d="M13.24,123.57v9.94c1.15-1.21,2.59-1.81,4.32-1.81c1.03,0,1.97,0.19,2.82,0.58c0.86,0.39,1.59,0.91,2.19,1.57
|
|
||||||
c0.6,0.66,1.08,1.43,1.42,2.32c0.34,0.89,0.51,1.84,0.51,2.85c0,1.03-0.18,1.99-0.53,2.89c-0.35,0.9-0.84,1.68-1.47,2.35
|
|
||||||
c-0.63,0.67-1.37,1.19-2.23,1.58c-0.86,0.39-1.78,0.58-2.77,0.58c-1.8,0-3.22-0.66-4.27-1.97V146H8.35v-22.43H13.24z M19.4,139.11
|
|
||||||
c0-0.43-0.08-0.84-0.24-1.23c-0.16-0.39-0.39-0.72-0.68-1.01c-0.29-0.29-0.62-0.52-1-0.69c-0.38-0.17-0.79-0.26-1.24-0.26
|
|
||||||
c-0.43,0-0.84,0.08-1.22,0.24c-0.38,0.16-0.71,0.39-0.99,0.68c-0.28,0.29-0.5,0.63-0.68,1.01c-0.17,0.39-0.26,0.8-0.26,1.23
|
|
||||||
c0,0.43,0.08,0.84,0.24,1.22c0.16,0.38,0.39,0.71,0.68,0.99c0.29,0.28,0.63,0.5,1.01,0.68c0.39,0.17,0.8,0.26,1.23,0.26
|
|
||||||
c0.43,0,0.84-0.08,1.22-0.24c0.38-0.16,0.71-0.39,0.99-0.68c0.28-0.29,0.5-0.62,0.68-1C19.32,139.92,19.4,139.52,19.4,139.11z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path
|
<path
|
||||||
class="st0"
|
d="M158.2 8.6V116.6C158.2 121.3 162 125.2 166.8 125.2H213.8C218 125.2 222 123.2 224.6 119.8L262.9 68.9C265.7 65.2 265.7 60.1 262.9 56.4L224.6 5.4C222 2 218 0 213.8 0H166.8C162 0 158.2 3.8 158.2 8.6Z"
|
||||||
d="M44,48H4c-2.21,0-4-1.79-4-4V4c0-2.21,1.79-4,4-4h40c2.21,0,4,1.79,4,4v40C48,46.21,46.21,48,44,48z"
|
fill="#FF4E4E"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M158.2 148.4V256.4C158.2 261.1 162 265 166.8 265H213.8C218 265 222 263 224.6 259.6L262.9 208.7C265.7 205 265.7 199.9 262.9 196.2L224.6 145.3C222.1 141.9 218.1 139.9 213.8 139.9H166.8C162 139.8 158.2 143.7 158.2 148.4Z"
|
||||||
|
fill="#6E56FF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0 8.6V116.6C0 121.3 3.8 125.2 8.6 125.2H109.6C113.8 125.2 117.8 123.2 120.4 119.8L155.9 72.5C160.3 66.6 160.3 58.5 155.9 52.6L120.3 5.4C117.8 2 113.8 0 109.5 0H8.6C3.8 0 0 3.8 0 8.6Z"
|
||||||
|
fill="#F97777"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0 148.4V256.4C0 261.1 3.8 265 8.6 265H109.6C113.8 265 117.8 263 120.4 259.6L155.9 212.3C160.3 206.4 160.3 198.3 155.9 192.4L120.4 145.1C117.9 141.7 113.9 139.7 109.6 139.7H8.6C3.8 139.8 0 143.7 0 148.4Z"
|
||||||
|
fill="#9F8FFF"
|
||||||
/>
|
/>
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
class="st1"
|
|
||||||
d="M28.48,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
|
||||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
|
||||||
c-0.64,0.7-1.4,1.25-2.28,1.66C34.8,35.8,33.86,36,32.84,36c-1.84,0-3.3-0.69-4.37-2.07v1.62h-5V12H28.48z M34.78,28.31
|
|
||||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
|
||||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
|
||||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
|
||||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C34.69,29.16,34.78,28.75,34.78,28.31z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
class="st1"
|
|
||||||
d="M13,12v10.44c1.18-1.27,2.65-1.9,4.42-1.9c1.05,0,2.01,0.2,2.89,0.61c0.87,0.41,1.62,0.96,2.24,1.65
|
|
||||||
c0.62,0.69,1.1,1.5,1.45,2.44c0.35,0.94,0.52,1.93,0.52,2.99c0,1.08-0.18,2.09-0.54,3.04c-0.36,0.95-0.86,1.77-1.51,2.47
|
|
||||||
c-0.64,0.7-1.4,1.25-2.28,1.66C19.32,35.8,18.38,36,17.37,36c-1.84,0-3.3-0.69-4.37-2.07v1.62H8V12H13z M19.3,28.31
|
|
||||||
c0-0.45-0.08-0.88-0.25-1.29c-0.17-0.41-0.4-0.76-0.69-1.06c-0.3-0.3-0.64-0.54-1.02-0.72c-0.39-0.18-0.81-0.27-1.27-0.27
|
|
||||||
c-0.44,0-0.86,0.09-1.24,0.26c-0.39,0.17-0.72,0.41-1.01,0.71c-0.29,0.3-0.52,0.66-0.69,1.06c-0.18,0.41-0.26,0.84-0.26,1.29
|
|
||||||
s0.08,0.88,0.25,1.28c0.17,0.4,0.4,0.74,0.69,1.04c0.29,0.29,0.64,0.53,1.04,0.71c0.4,0.18,0.82,0.27,1.26,0.27
|
|
||||||
c0.44,0,0.86-0.09,1.24-0.26c0.39-0.17,0.72-0.41,1.01-0.71c0.29-0.3,0.52-0.65,0.69-1.05C19.21,29.16,19.3,28.75,19.3,28.31z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1_1799">
|
||||||
|
<rect width="265" height="265" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
@ -30,15 +30,15 @@
|
||||||
part2: PrettyRelationshipDefinitions.MANY,
|
part2: PrettyRelationshipDefinitions.MANY,
|
||||||
},
|
},
|
||||||
[RelationshipType.MANY_TO_ONE]: {
|
[RelationshipType.MANY_TO_ONE]: {
|
||||||
part1: PrettyRelationshipDefinitions.ONE,
|
part1: PrettyRelationshipDefinitions.MANY,
|
||||||
part2: PrettyRelationshipDefinitions.MANY,
|
part2: PrettyRelationshipDefinitions.ONE,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
let relationshipOpts1 = Object.values(PrettyRelationshipDefinitions)
|
let relationshipOpts1 = Object.values(PrettyRelationshipDefinitions)
|
||||||
let relationshipOpts2 = Object.values(PrettyRelationshipDefinitions)
|
let relationshipOpts2 = Object.values(PrettyRelationshipDefinitions)
|
||||||
|
|
||||||
let relationshipPart1 = PrettyRelationshipDefinitions.MANY
|
let relationshipPart1 = PrettyRelationshipDefinitions.ONE
|
||||||
let relationshipPart2 = PrettyRelationshipDefinitions.ONE
|
let relationshipPart2 = PrettyRelationshipDefinitions.MANY
|
||||||
|
|
||||||
let originalFromColumnName = toRelationship.name,
|
let originalFromColumnName = toRelationship.name,
|
||||||
originalToColumnName = fromRelationship.name
|
originalToColumnName = fromRelationship.name
|
||||||
|
|
|
@ -90,13 +90,17 @@
|
||||||
|
|
||||||
.openMenu {
|
.openMenu {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #6a1dc8;
|
background-color: var(--bb-indigo);
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 10px 18px;
|
padding: 10px 18px;
|
||||||
|
transition: background-color 130ms ease-out;
|
||||||
|
}
|
||||||
|
.openMenu:hover {
|
||||||
|
background-color: var(--bb-indigo-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.helpMenu {
|
.helpMenu {
|
||||||
|
|
|
@ -206,12 +206,12 @@
|
||||||
.text-area-slot-icon {
|
.text-area-slot-icon {
|
||||||
border-bottom: 1px solid var(--spectrum-alias-border-color);
|
border-bottom: 1px solid var(--spectrum-alias-border-color);
|
||||||
border-bottom-right-radius: 0px !important;
|
border-bottom-right-radius: 0px !important;
|
||||||
top: 26px !important;
|
top: 1px !important;
|
||||||
}
|
}
|
||||||
.json-slot-icon {
|
.json-slot-icon {
|
||||||
border-bottom: 1px solid var(--spectrum-alias-border-color);
|
border-bottom: 1px solid var(--spectrum-alias-border-color);
|
||||||
border-bottom-right-radius: 0px !important;
|
border-bottom-right-radius: 0px !important;
|
||||||
top: 23px !important;
|
top: 1px !important;
|
||||||
right: 0px !important;
|
right: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Checkbox, Select, RadioGroup, Stepper, Input } from "@budibase/bbui"
|
import { Checkbox, Select, RadioGroup, Stepper, Input } from "@budibase/bbui"
|
||||||
import DataSourceSelect from "./controls/DataSourceSelect.svelte"
|
import DataSourceSelect from "./controls/DataSourceSelect/DataSourceSelect.svelte"
|
||||||
import S3DataSourceSelect from "./controls/S3DataSourceSelect.svelte"
|
import S3DataSourceSelect from "./controls/S3DataSourceSelect.svelte"
|
||||||
import DataProviderSelect from "./controls/DataProviderSelect.svelte"
|
import DataProviderSelect from "./controls/DataProviderSelect.svelte"
|
||||||
import ButtonActionEditor from "./controls/ButtonActionEditor/ButtonActionEditor.svelte"
|
import ButtonActionEditor from "./controls/ButtonActionEditor/ButtonActionEditor.svelte"
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<script>
|
||||||
|
import { Divider, Heading } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let dividerState
|
||||||
|
export let heading
|
||||||
|
export let dataSet
|
||||||
|
export let value
|
||||||
|
export let onSelect
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if dividerState}
|
||||||
|
<Divider />
|
||||||
|
{/if}
|
||||||
|
{#if heading}
|
||||||
|
<div class="title">
|
||||||
|
<Heading size="XS">{heading}</Heading>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<ul class="spectrum-Menu" role="listbox">
|
||||||
|
{#each dataSet as data}
|
||||||
|
<li
|
||||||
|
class="spectrum-Menu-item"
|
||||||
|
class:is-selected={value?.label === data.label &&
|
||||||
|
value?.type === data.type}
|
||||||
|
role="option"
|
||||||
|
aria-selected="true"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => onSelect(data)}
|
||||||
|
>
|
||||||
|
<span class="spectrum-Menu-itemLabel">
|
||||||
|
{data.label}
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.title {
|
||||||
|
padding: 0 var(--spacing-m) var(--spacing-s) var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -7,10 +7,8 @@
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
Popover,
|
||||||
Divider,
|
|
||||||
Select,
|
Select,
|
||||||
Layout,
|
Layout,
|
||||||
Heading,
|
|
||||||
Drawer,
|
Drawer,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
Icon,
|
Icon,
|
||||||
|
@ -32,6 +30,7 @@
|
||||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
|
||||||
|
import DataSourceCategory from "components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
|
||||||
export let value = {}
|
export let value = {}
|
||||||
|
@ -279,102 +278,81 @@
|
||||||
</div>
|
</div>
|
||||||
<Popover bind:this={dropdownRight} anchor={anchorRight}>
|
<Popover bind:this={dropdownRight} anchor={anchorRight}>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<div class="title">
|
<DataSourceCategory
|
||||||
<Heading size="XS">Tables</Heading>
|
heading="Tables"
|
||||||
</div>
|
dataSet={tables}
|
||||||
<ul>
|
{value}
|
||||||
{#each tables as table}
|
onSelect={handleSelected}
|
||||||
<li on:click={() => handleSelected(table)}>{table.label}</li>
|
/>
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{#if views?.length}
|
{#if views?.length}
|
||||||
<Divider />
|
<DataSourceCategory
|
||||||
<div class="title">
|
dividerState={true}
|
||||||
<Heading size="XS">Views</Heading>
|
heading="Views"
|
||||||
</div>
|
dataSet={views}
|
||||||
<ul>
|
{value}
|
||||||
{#each views as view}
|
onSelect={handleSelected}
|
||||||
<li on:click={() => handleSelected(view)}>{view.label}</li>
|
/>
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if queries?.length}
|
{#if queries?.length}
|
||||||
<Divider />
|
<DataSourceCategory
|
||||||
<div class="title">
|
dividerState={true}
|
||||||
<Heading size="XS">Queries</Heading>
|
heading="Queries"
|
||||||
</div>
|
dataSet={queries}
|
||||||
<ul>
|
{value}
|
||||||
{#each queries as query}
|
onSelect={handleSelected}
|
||||||
<li
|
/>
|
||||||
class:selected={value === query}
|
|
||||||
on:click={() => handleSelected(query)}
|
|
||||||
>
|
|
||||||
{query.label}
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if links?.length}
|
{#if links?.length}
|
||||||
<Divider />
|
<DataSourceCategory
|
||||||
<div class="title">
|
dividerState={true}
|
||||||
<Heading size="XS">Relationships</Heading>
|
heading="Links"
|
||||||
</div>
|
dataSet={links}
|
||||||
<ul>
|
{value}
|
||||||
{#each links as link}
|
onSelect={handleSelected}
|
||||||
<li on:click={() => handleSelected(link)}>{link.label}</li>
|
/>
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if fields?.length}
|
{#if fields?.length}
|
||||||
<Divider />
|
<DataSourceCategory
|
||||||
<div class="title">
|
dividerState={true}
|
||||||
<Heading size="XS">Fields</Heading>
|
heading="Fields"
|
||||||
</div>
|
dataSet={fields}
|
||||||
<ul>
|
{value}
|
||||||
{#each fields as field}
|
onSelect={handleSelected}
|
||||||
<li on:click={() => handleSelected(field)}>{field.label}</li>
|
/>
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if jsonArrays?.length}
|
{#if jsonArrays?.length}
|
||||||
<Divider />
|
<DataSourceCategory
|
||||||
<div class="title">
|
dividerState={true}
|
||||||
<Heading size="XS">JSON Arrays</Heading>
|
heading="JSON Arrays"
|
||||||
</div>
|
dataSet={jsonArrays}
|
||||||
<ul>
|
{value}
|
||||||
{#each jsonArrays as field}
|
onSelect={handleSelected}
|
||||||
<li on:click={() => handleSelected(field)}>{field.label}</li>
|
/>
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if showDataProviders && dataProviders?.length}
|
{#if showDataProviders && dataProviders?.length}
|
||||||
<Divider />
|
<DataSourceCategory
|
||||||
<div class="title">
|
dividerState={true}
|
||||||
<Heading size="XS">Data Providers</Heading>
|
heading="Data Providers"
|
||||||
</div>
|
dataSet={dataProviders}
|
||||||
<ul>
|
{value}
|
||||||
{#each dataProviders as provider}
|
onSelect={handleSelected}
|
||||||
<li
|
/>
|
||||||
class:selected={value === provider}
|
{/if}
|
||||||
on:click={() => handleSelected(provider)}
|
<DataSourceCategory
|
||||||
>
|
dividerState={true}
|
||||||
{provider.label}
|
heading="Other"
|
||||||
</li>
|
dataSet={[custom]}
|
||||||
{/each}
|
{value}
|
||||||
</ul>
|
onSelect={handleSelected}
|
||||||
|
/>
|
||||||
|
{#if otherSources?.length}
|
||||||
|
<DataSourceCategory
|
||||||
|
dividerState={false}
|
||||||
|
dataSet={otherSources}
|
||||||
|
{value}
|
||||||
|
onSelect={handleSelected}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<Divider />
|
|
||||||
<div class="title">
|
|
||||||
<Heading size="XS">Other</Heading>
|
|
||||||
</div>
|
|
||||||
<ul>
|
|
||||||
<li on:click={() => handleSelected(custom)}>{custom.label}</li>
|
|
||||||
{#if otherSources?.length}
|
|
||||||
{#each otherSources as source}
|
|
||||||
<li on:click={() => handleSelected(source)}>{source.label}</li>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
|
@ -398,31 +376,6 @@
|
||||||
.dropdown {
|
.dropdown {
|
||||||
padding: var(--spacing-m) 0;
|
padding: var(--spacing-m) 0;
|
||||||
z-index: 99999999;
|
z-index: 99999999;
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
padding: 0 var(--spacing-m) var(--spacing-s) var(--spacing-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 0px;
|
|
||||||
padding: var(--spacing-s) var(--spacing-m);
|
|
||||||
font-size: var(--font-size-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
color: var(--spectrum-global-color-blue-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
li:hover {
|
|
||||||
background-color: var(--spectrum-global-color-gray-200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
|
@ -20,7 +20,7 @@
|
||||||
let open = false
|
let open = false
|
||||||
|
|
||||||
// Auto hide the component when another item is selected
|
// Auto hide the component when another item is selected
|
||||||
$: if (open && $draggable.selected != componentInstance._id) {
|
$: if (open && $draggable.selected !== componentInstance._id) {
|
||||||
popover.hide()
|
popover.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,13 +100,13 @@
|
||||||
}}
|
}}
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
open = false
|
open = false
|
||||||
if ($draggable.selected == componentInstance._id) {
|
if ($draggable.selected === componentInstance._id) {
|
||||||
$draggable.actions.select()
|
$draggable.actions.select()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
{anchor}
|
{anchor}
|
||||||
align="left-outside"
|
align="left-outside"
|
||||||
showPopover={drawers.length == 0}
|
showPopover={drawers.length === 0}
|
||||||
clickOutsideOverride={drawers.length > 0}
|
clickOutsideOverride={drawers.length > 0}
|
||||||
maxHeight={600}
|
maxHeight={600}
|
||||||
handlePostionUpdate={customPositionHandler}
|
handlePostionUpdate={customPositionHandler}
|
||||||
|
@ -115,6 +115,7 @@
|
||||||
<Layout noPadding noGap>
|
<Layout noPadding noGap>
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
<ComponentSettingsSection
|
<ComponentSettingsSection
|
||||||
|
includeHidden
|
||||||
{componentInstance}
|
{componentInstance}
|
||||||
componentDefinition={parsedComponentDef}
|
componentDefinition={parsedComponentDef}
|
||||||
isScreen={false}
|
isScreen={false}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import DataSourceSelect from "./DataSourceSelect.svelte"
|
import DataSourceSelect from "./DataSourceSelect/DataSourceSelect.svelte"
|
||||||
|
|
||||||
const otherSources = [{ name: "Custom", label: "Custom" }]
|
const otherSources = [{ name: "Custom", label: "Custom" }]
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -91,7 +91,6 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: var(--spacing-l);
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.centered {
|
.centered {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
|
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
|
||||||
import Panel from "components/design/Panel.svelte"
|
import Panel from "components/design/Panel.svelte"
|
||||||
import { isActive, redirect, goto, params } from "@roxi/routify"
|
import { isActive, redirect, goto, params } from "@roxi/routify"
|
||||||
import BetaButton from "./_components/BetaButton.svelte"
|
|
||||||
import { datasources } from "stores/backend"
|
import { datasources } from "stores/backend"
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
@ -30,7 +29,6 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<BetaButton />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -16,16 +16,18 @@
|
||||||
export let isScreen = false
|
export let isScreen = false
|
||||||
export let onUpdateSetting
|
export let onUpdateSetting
|
||||||
export let showSectionTitle = true
|
export let showSectionTitle = true
|
||||||
|
export let includeHidden = false
|
||||||
export let tag
|
export let tag
|
||||||
|
|
||||||
$: sections = getSections(
|
$: sections = getSections(
|
||||||
componentInstance,
|
componentInstance,
|
||||||
componentDefinition,
|
componentDefinition,
|
||||||
isScreen,
|
isScreen,
|
||||||
tag
|
tag,
|
||||||
|
includeHidden
|
||||||
)
|
)
|
||||||
|
|
||||||
const getSections = (instance, definition, isScreen, tag) => {
|
const getSections = (instance, definition, isScreen, tag, includeHidden) => {
|
||||||
const settings = definition?.settings ?? []
|
const settings = definition?.settings ?? []
|
||||||
const generalSettings = settings.filter(
|
const generalSettings = settings.filter(
|
||||||
setting => !setting.section && setting.tag === tag
|
setting => !setting.section && setting.tag === tag
|
||||||
|
@ -52,7 +54,12 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
section.settings.forEach(setting => {
|
section.settings.forEach(setting => {
|
||||||
setting.visible = canRenderControl(instance, setting, isScreen)
|
setting.visible = canRenderControl(
|
||||||
|
instance,
|
||||||
|
setting,
|
||||||
|
isScreen,
|
||||||
|
includeHidden
|
||||||
|
)
|
||||||
})
|
})
|
||||||
section.visible =
|
section.visible =
|
||||||
section.name === "General" ||
|
section.name === "General" ||
|
||||||
|
@ -122,16 +129,20 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const canRenderControl = (instance, setting, isScreen) => {
|
const canRenderControl = (instance, setting, isScreen, includeHidden) => {
|
||||||
// Prevent rendering on click setting for screens
|
// Prevent rendering on click setting for screens
|
||||||
if (setting?.type === "event" && isScreen) {
|
if (setting?.type === "event" && isScreen) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// Check we have a component to render for this setting
|
||||||
const control = getComponentForSetting(setting)
|
const control = getComponentForSetting(setting)
|
||||||
if (!control) {
|
if (!control) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// Check if setting is hidden
|
||||||
|
if (setting.hidden && !includeHidden) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return shouldDisplay(instance, setting)
|
return shouldDisplay(instance, setting)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,12 +9,18 @@
|
||||||
let searchString
|
let searchString
|
||||||
let searching = false
|
let searching = false
|
||||||
|
|
||||||
$: filteredApps = $apps.filter(app => {
|
$: filteredApps = $apps
|
||||||
return (
|
.filter(app => {
|
||||||
!searchString ||
|
return (
|
||||||
app.name.toLowerCase().includes(searchString.toLowerCase())
|
!searchString ||
|
||||||
)
|
app.name.toLowerCase().includes(searchString.toLowerCase())
|
||||||
})
|
)
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
const lowerA = a.name.toLowerCase()
|
||||||
|
const lowerB = b.name.toLowerCase()
|
||||||
|
return lowerA > lowerB ? 1 : -1
|
||||||
|
})
|
||||||
|
|
||||||
const startSearching = async () => {
|
const startSearching = async () => {
|
||||||
searching = true
|
searching = true
|
||||||
|
|
|
@ -8,11 +8,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<img
|
<img alt="Budibase Logo" class="budibaseLogo" src="/builder/bblogo.png" />
|
||||||
alt="Budibase Logo"
|
|
||||||
class="budibaseLogo"
|
|
||||||
src={"https://i.imgur.com/Xhdt1YP.png"}
|
|
||||||
/>
|
|
||||||
<div class="headingAndBack">
|
<div class="headingAndBack">
|
||||||
{#if onBack}
|
{#if onBack}
|
||||||
<button on:click={onBack}>
|
<button on:click={onBack}>
|
||||||
|
|
|
@ -2776,6 +2776,35 @@
|
||||||
"barTitle": "Justify text"
|
"barTitle": "Justify text"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2833,6 +2862,35 @@
|
||||||
"type": "validation/number",
|
"type": "validation/number",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2885,6 +2943,35 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2942,6 +3029,35 @@
|
||||||
"type": "validation/string",
|
"type": "validation/string",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3110,6 +3226,35 @@
|
||||||
"type": "validation/string",
|
"type": "validation/string",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3272,6 +3417,35 @@
|
||||||
"type": "validation/array",
|
"type": "validation/array",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3352,6 +3526,35 @@
|
||||||
"type": "validation/boolean",
|
"type": "validation/boolean",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3431,6 +3634,35 @@
|
||||||
"type": "validation/string",
|
"type": "validation/string",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3512,6 +3744,35 @@
|
||||||
"type": "validation/datetime",
|
"type": "validation/datetime",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3598,6 +3859,22 @@
|
||||||
"value": "custom"
|
"value": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Preferred camera",
|
||||||
|
"key": "preferredCamera",
|
||||||
|
"defaultValue": "environment",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Front",
|
||||||
|
"value": "user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Back",
|
||||||
|
"value": "environment"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -3613,6 +3890,35 @@
|
||||||
"type": "validation/string",
|
"type": "validation/string",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3789,6 +4095,35 @@
|
||||||
"type": "validation/attachment",
|
"type": "validation/attachment",
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3857,6 +4192,35 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3909,6 +4273,35 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -5590,23 +5983,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"tag": "style",
|
|
||||||
"type": "select",
|
|
||||||
"label": "Align labels",
|
|
||||||
"key": "labelPosition",
|
|
||||||
"defaultValue": "left",
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"label": "Left",
|
|
||||||
"value": "left"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Above",
|
|
||||||
"value": "above"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"tag": "style",
|
"tag": "style",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -5921,6 +6297,35 @@
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,15 +26,15 @@
|
||||||
$: parentId = $component?.id
|
$: parentId = $component?.id
|
||||||
$: inBuilder = $builderStore.inBuilder
|
$: inBuilder = $builderStore.inBuilder
|
||||||
$: instance = {
|
$: instance = {
|
||||||
|
...props,
|
||||||
_component: getComponent(type),
|
_component: getComponent(type),
|
||||||
_id: id,
|
_id: id,
|
||||||
_instanceName: getInstanceName(name, type),
|
_instanceName: getInstanceName(name, type),
|
||||||
|
_containsSlot: containsSlot,
|
||||||
_styles: {
|
_styles: {
|
||||||
...styles,
|
...styles,
|
||||||
normal: styles?.normal || {},
|
normal: styles?.normal || {},
|
||||||
},
|
},
|
||||||
_containsSlot: containsSlot,
|
|
||||||
...props,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register this block component if we're inside the builder so it can be
|
// Register this block component if we're inside the builder so it can be
|
||||||
|
|
|
@ -140,6 +140,7 @@
|
||||||
interactive &&
|
interactive &&
|
||||||
!isLayout &&
|
!isLayout &&
|
||||||
!isRoot &&
|
!isRoot &&
|
||||||
|
!isBlock &&
|
||||||
definition?.draggable !== false
|
definition?.draggable !== false
|
||||||
$: droppable = interactive
|
$: droppable = interactive
|
||||||
$: builderHidden =
|
$: builderHidden =
|
||||||
|
@ -194,6 +195,7 @@
|
||||||
interactive,
|
interactive,
|
||||||
draggable,
|
draggable,
|
||||||
editable,
|
editable,
|
||||||
|
isBlock,
|
||||||
},
|
},
|
||||||
empty: emptyState,
|
empty: emptyState,
|
||||||
selected,
|
selected,
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
export let size
|
export let size
|
||||||
export let disabled
|
export let disabled
|
||||||
export let fields
|
export let fields
|
||||||
export let labelPosition
|
|
||||||
export let title
|
export let title
|
||||||
export let description
|
export let description
|
||||||
export let showDeleteButton
|
export let showDeleteButton
|
||||||
|
@ -97,7 +96,6 @@
|
||||||
size,
|
size,
|
||||||
disabled,
|
disabled,
|
||||||
fields: fieldsOrDefault,
|
fields: fieldsOrDefault,
|
||||||
labelPosition,
|
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
saveButtonLabel: saveLabel,
|
saveButtonLabel: saveLabel,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import BlockComponent from "components/BlockComponent.svelte"
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import Placeholder from "components/app/Placeholder.svelte"
|
import Placeholder from "components/app/Placeholder.svelte"
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
export let dataSource
|
export let dataSource
|
||||||
export let actionUrl
|
export let actionUrl
|
||||||
|
@ -9,7 +10,6 @@
|
||||||
export let size
|
export let size
|
||||||
export let disabled
|
export let disabled
|
||||||
export let fields
|
export let fields
|
||||||
export let labelPosition
|
|
||||||
export let title
|
export let title
|
||||||
export let description
|
export let description
|
||||||
export let saveButtonLabel
|
export let saveButtonLabel
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
barcodeqr: "codescanner",
|
barcodeqr: "codescanner",
|
||||||
bb_reference: "bbreferencefield",
|
bb_reference: "bbreferencefield",
|
||||||
}
|
}
|
||||||
|
const context = getContext("context")
|
||||||
|
|
||||||
let formId
|
let formId
|
||||||
|
|
||||||
|
@ -226,16 +227,20 @@
|
||||||
<BlockComponent type="text" props={{ text: description }} order={1} />
|
<BlockComponent type="text" props={{ text: description }} order={1} />
|
||||||
{/if}
|
{/if}
|
||||||
{#key fields}
|
{#key fields}
|
||||||
<BlockComponent type="fieldgroup" props={{ labelPosition }} order={1}>
|
<BlockComponent type="container">
|
||||||
{#each fields as field, idx}
|
<div class="form-block fields" class:mobile={$context.device.mobile}>
|
||||||
{#if getComponentForField(field) && field.active}
|
{#each fields as field, idx}
|
||||||
<BlockComponent
|
{#if getComponentForField(field) && field.active}
|
||||||
type={getComponentForField(field)}
|
<BlockComponent
|
||||||
props={getPropsForField(field)}
|
type={getComponentForField(field)}
|
||||||
order={idx}
|
props={getPropsForField(field)}
|
||||||
/>
|
order={idx}
|
||||||
{/if}
|
interactive
|
||||||
{/each}
|
name={field?.field}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
{/key}
|
{/key}
|
||||||
</BlockComponent>
|
</BlockComponent>
|
||||||
|
@ -245,3 +250,14 @@
|
||||||
text="Choose your table and add some fields to your form to get started"
|
text="Choose your table and add some fields to your form to get started"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fields {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
gap: 8px 16px;
|
||||||
|
}
|
||||||
|
.fields.mobile :global(.spectrum-Form-item) {
|
||||||
|
grid-column: span 6 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -4,9 +4,6 @@
|
||||||
const { linkable, styleable } = getContext("sdk")
|
const { linkable, styleable } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
// BB emblem: https://i.imgur.com/Xhdt1YP.png
|
|
||||||
// Space logo: https://i.imgur.com/Dn7Xt1G.png
|
|
||||||
|
|
||||||
export let logoUrl
|
export let logoUrl
|
||||||
export let hideLogo
|
export let hideLogo
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let extensions
|
export let extensions
|
||||||
export let onChange
|
export let onChange
|
||||||
export let maximum = undefined
|
export let maximum = undefined
|
||||||
|
export let span
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -72,32 +73,25 @@
|
||||||
{field}
|
{field}
|
||||||
{disabled}
|
{disabled}
|
||||||
{validation}
|
{validation}
|
||||||
|
{span}
|
||||||
type="attachment"
|
type="attachment"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
defaultValue={[]}
|
defaultValue={[]}
|
||||||
>
|
>
|
||||||
<div class="minHeightWrapper">
|
{#if fieldState}
|
||||||
{#if fieldState}
|
<CoreDropzone
|
||||||
<CoreDropzone
|
value={fieldState.value}
|
||||||
value={fieldState.value}
|
disabled={fieldState.disabled}
|
||||||
disabled={fieldState.disabled}
|
error={fieldState.error}
|
||||||
error={fieldState.error}
|
on:change={handleChange}
|
||||||
on:change={handleChange}
|
{processFiles}
|
||||||
{processFiles}
|
{deleteAttachments}
|
||||||
{deleteAttachments}
|
{handleFileTooLarge}
|
||||||
{handleFileTooLarge}
|
{handleTooManyFiles}
|
||||||
{handleTooManyFiles}
|
{maximum}
|
||||||
{maximum}
|
{extensions}
|
||||||
{extensions}
|
{compact}
|
||||||
{compact}
|
/>
|
||||||
/>
|
{/if}
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<style>
|
|
||||||
.minHeightWrapper {
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let beepOnScan = false
|
export let beepOnScan = false
|
||||||
export let beepFrequency = 2637
|
export let beepFrequency = 2637
|
||||||
export let customFrequency = 1046
|
export let customFrequency = 1046
|
||||||
|
export let preferredCamera = "environment"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
let cameraEnabled
|
let cameraEnabled
|
||||||
let cameraStarted = false
|
let cameraStarted = false
|
||||||
let html5QrCode
|
let html5QrCode
|
||||||
let cameraSetting = { facingMode: "environment" }
|
let cameraSetting = { facingMode: preferredCamera }
|
||||||
let cameraConfig = {
|
let cameraConfig = {
|
||||||
fps: 25,
|
fps: 25,
|
||||||
qrbox: { width: 250, height: 250 },
|
qrbox: { width: 250, height: 250 },
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
export let beepOnScan
|
export let beepOnScan
|
||||||
export let beepFrequency
|
export let beepFrequency
|
||||||
export let customFrequency
|
export let customFrequency
|
||||||
|
export let preferredCamera
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
{beepOnScan}
|
{beepOnScan}
|
||||||
{beepFrequency}
|
{beepFrequency}
|
||||||
{customFrequency}
|
{customFrequency}
|
||||||
|
{preferredCamera}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let validation
|
export let validation
|
||||||
export let defaultValue
|
export let defaultValue
|
||||||
export let onChange
|
export let onChange
|
||||||
|
export let span
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
{validation}
|
{validation}
|
||||||
{defaultValue}
|
{defaultValue}
|
||||||
|
{span}
|
||||||
type="datetime"
|
type="datetime"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import Placeholder from "../Placeholder.svelte"
|
import Placeholder from "../Placeholder.svelte"
|
||||||
import FieldGroupFallback from "./FieldGroupFallback.svelte"
|
|
||||||
import { getContext, onDestroy } from "svelte"
|
import { getContext, onDestroy } from "svelte"
|
||||||
|
|
||||||
export let label
|
export let label
|
||||||
|
@ -12,6 +11,7 @@
|
||||||
export let type
|
export let type
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let validation
|
export let validation
|
||||||
|
export let span = 6
|
||||||
|
|
||||||
// Get contexts
|
// Get contexts
|
||||||
const formContext = getContext("form")
|
const formContext = getContext("form")
|
||||||
|
@ -62,40 +62,58 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FieldGroupFallback>
|
<div
|
||||||
<div class="spectrum-Form-item" use:styleable={$component.styles}>
|
class="spectrum-Form-item"
|
||||||
{#key $component.editing}
|
class:span-2={span === 2}
|
||||||
<label
|
class:span-3={span === 3}
|
||||||
bind:this={labelNode}
|
class:span-6={span === 6 || !span}
|
||||||
contenteditable={$component.editing}
|
use:styleable={$component.styles}
|
||||||
on:blur={$component.editing ? updateLabel : null}
|
class:above={labelPos === "above"}
|
||||||
class:hidden={!label}
|
>
|
||||||
for={fieldState?.fieldId}
|
{#key $component.editing}
|
||||||
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`}
|
<label
|
||||||
>
|
bind:this={labelNode}
|
||||||
{label || " "}
|
contenteditable={$component.editing}
|
||||||
</label>
|
on:blur={$component.editing ? updateLabel : null}
|
||||||
{/key}
|
class:hidden={!label}
|
||||||
<div class="spectrum-Form-itemField">
|
for={fieldState?.fieldId}
|
||||||
{#if !formContext}
|
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`}
|
||||||
<Placeholder text="Form components need to be wrapped in a form" />
|
>
|
||||||
{:else if !fieldState}
|
{label || " "}
|
||||||
<Placeholder />
|
</label>
|
||||||
{:else if schemaType && schemaType !== type && !["options", "longform"].includes(type)}
|
{/key}
|
||||||
<Placeholder
|
<div class="spectrum-Form-itemField">
|
||||||
text="This Field setting is the wrong data type for this component"
|
{#if !formContext}
|
||||||
/>
|
<Placeholder text="Form components need to be wrapped in a form" />
|
||||||
{:else}
|
{:else if !fieldState}
|
||||||
<slot />
|
<Placeholder />
|
||||||
{#if fieldState.error}
|
{:else if schemaType && schemaType !== type && !["options", "longform"].includes(type)}
|
||||||
<div class="error">{fieldState.error}</div>
|
<Placeholder
|
||||||
{/if}
|
text="This Field setting is the wrong data type for this component"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<slot />
|
||||||
|
{#if fieldState.error}
|
||||||
|
<div class="error">{fieldState.error}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</FieldGroupFallback>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
:global(.form-block .spectrum-Form-item.span-2) {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
:global(.form-block .spectrum-Form-item.span-3) {
|
||||||
|
grid-column: span 3;
|
||||||
|
}
|
||||||
|
:global(.form-block .spectrum-Form-item.span-6) {
|
||||||
|
grid-column: span 6;
|
||||||
|
}
|
||||||
|
.spectrum-Form-item.above {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
label {
|
label {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
export let onChange
|
export let onChange
|
||||||
export let optionsType = "select"
|
export let optionsType = "select"
|
||||||
export let direction = "vertical"
|
export let direction = "vertical"
|
||||||
|
export let span
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
{label}
|
{label}
|
||||||
{disabled}
|
{disabled}
|
||||||
{validation}
|
{validation}
|
||||||
|
{span}
|
||||||
defaultValue={expandedDefaultValue}
|
defaultValue={expandedDefaultValue}
|
||||||
type="array"
|
type="array"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
export let direction = "vertical"
|
export let direction = "vertical"
|
||||||
export let onChange
|
export let onChange
|
||||||
export let sort = true
|
export let sort = true
|
||||||
|
export let span
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
{validation}
|
{validation}
|
||||||
{defaultValue}
|
{defaultValue}
|
||||||
|
{span}
|
||||||
type="options"
|
type="options"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
export let filter
|
export let filter
|
||||||
export let datasourceType = "table"
|
export let datasourceType = "table"
|
||||||
export let primaryDisplay
|
export let primaryDisplay
|
||||||
|
export let span
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -137,7 +138,9 @@
|
||||||
typeof value === "object" ? value._id : value
|
typeof value === "object" ? value._id : value
|
||||||
)
|
)
|
||||||
// Make sure field state is valid
|
// Make sure field state is valid
|
||||||
fieldApi.setValue(values)
|
if (values?.length > 0) {
|
||||||
|
fieldApi.setValue(values)
|
||||||
|
}
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +189,7 @@
|
||||||
{validation}
|
{validation}
|
||||||
defaultValue={expandedDefaultValue}
|
defaultValue={expandedDefaultValue}
|
||||||
{type}
|
{type}
|
||||||
|
{span}
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
bind:fieldSchema
|
bind:fieldSchema
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let defaultValue = ""
|
export let defaultValue = ""
|
||||||
export let align
|
export let align
|
||||||
export let onChange
|
export let onChange
|
||||||
|
export let span
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
{validation}
|
{validation}
|
||||||
{defaultValue}
|
{defaultValue}
|
||||||
|
{span}
|
||||||
type={type === "number" ? "number" : "string"}
|
type={type === "number" ? "number" : "string"}
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
|
|
|
@ -40,6 +40,7 @@ export const styleable = (node, styles = {}) => {
|
||||||
|
|
||||||
const componentId = newStyles.id
|
const componentId = newStyles.id
|
||||||
const customStyles = newStyles.custom || ""
|
const customStyles = newStyles.custom || ""
|
||||||
|
const { isBlock } = newStyles
|
||||||
const normalStyles = { ...baseStyles, ...newStyles.normal }
|
const normalStyles = { ...baseStyles, ...newStyles.normal }
|
||||||
const hoverStyles = {
|
const hoverStyles = {
|
||||||
...normalStyles,
|
...normalStyles,
|
||||||
|
@ -76,6 +77,9 @@ export const styleable = (node, styles = {}) => {
|
||||||
// Handler to start editing a component (if applicable) when double
|
// Handler to start editing a component (if applicable) when double
|
||||||
// clicking in the builder preview
|
// clicking in the builder preview
|
||||||
editComponent = event => {
|
editComponent = event => {
|
||||||
|
if (isBlock) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (newStyles.interactive && newStyles.editable) {
|
if (newStyles.interactive && newStyles.editable) {
|
||||||
builderStore.actions.setEditMode(true)
|
builderStore.actions.setEditMode(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,10 @@
|
||||||
<script context="module">
|
|
||||||
// We can create a module level cache for all relationship cells to avoid
|
|
||||||
// having to fetch the table definition one time for each cell
|
|
||||||
let primaryDisplayCache = {}
|
|
||||||
|
|
||||||
const getPrimaryDisplayForTableId = async (API, tableId) => {
|
|
||||||
if (primaryDisplayCache[tableId]) {
|
|
||||||
return primaryDisplayCache[tableId]
|
|
||||||
}
|
|
||||||
const definition = await API.fetchTableDefinition(tableId)
|
|
||||||
const primaryDisplay =
|
|
||||||
definition?.primaryDisplay || definition?.schema?.[0]?.name
|
|
||||||
primaryDisplayCache[tableId] = primaryDisplay
|
|
||||||
return primaryDisplay
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getColor } from "../lib/utils"
|
import { getColor } from "../lib/utils"
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { Icon, Input, ProgressCircle, clickOutside } from "@budibase/bbui"
|
import { Icon, Input, ProgressCircle, clickOutside } from "@budibase/bbui"
|
||||||
import { debounce } from "../../../utils/utils"
|
import { debounce } from "../../../utils/utils"
|
||||||
|
|
||||||
const { API, dispatch } = getContext("grid")
|
const { API, dispatch, cache } = getContext("grid")
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let api
|
export let api
|
||||||
|
@ -147,7 +130,9 @@
|
||||||
// Find the primary display for the related table
|
// Find the primary display for the related table
|
||||||
if (!primaryDisplay) {
|
if (!primaryDisplay) {
|
||||||
searching = true
|
searching = true
|
||||||
primaryDisplay = await getPrimaryDisplayForTableId(API, schema.tableId)
|
primaryDisplay = await cache.actions.getPrimaryDisplayForTableId(
|
||||||
|
schema.tableId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show initial list of results
|
// Show initial list of results
|
||||||
|
@ -195,7 +180,7 @@
|
||||||
const toggleRow = async row => {
|
const toggleRow = async row => {
|
||||||
if (value?.some(x => x._id === row._id)) {
|
if (value?.some(x => x._id === row._id)) {
|
||||||
// If the row is already included, remove it and update the candidate
|
// If the row is already included, remove it and update the candidate
|
||||||
// row to be the the same position if possible
|
// row to be the same position if possible
|
||||||
if (oneRowOnly) {
|
if (oneRowOnly) {
|
||||||
await onChange([])
|
await onChange([])
|
||||||
} else {
|
} else {
|
||||||
|
@ -260,31 +245,29 @@
|
||||||
class:wrap={editable || contentLines > 1}
|
class:wrap={editable || contentLines > 1}
|
||||||
on:wheel={e => (focused ? e.stopPropagation() : null)}
|
on:wheel={e => (focused ? e.stopPropagation() : null)}
|
||||||
>
|
>
|
||||||
{#if Array.isArray(value) && value.length}
|
{#each value || [] as relationship}
|
||||||
{#each value as relationship}
|
{#if relationship[primaryDisplay] || relationship.primaryDisplay}
|
||||||
{#if relationship[primaryDisplay] || relationship.primaryDisplay}
|
<div class="badge">
|
||||||
<div class="badge">
|
<span
|
||||||
<span
|
on:click={editable
|
||||||
on:click={editable
|
? () => showRelationship(relationship._id)
|
||||||
? () => showRelationship(relationship._id)
|
: null}
|
||||||
: null}
|
>
|
||||||
>
|
{readable(
|
||||||
{readable(
|
relationship[primaryDisplay] || relationship.primaryDisplay
|
||||||
relationship[primaryDisplay] || relationship.primaryDisplay
|
)}
|
||||||
)}
|
</span>
|
||||||
</span>
|
{#if editable}
|
||||||
{#if editable}
|
<Icon
|
||||||
<Icon
|
name="Close"
|
||||||
name="Close"
|
size="XS"
|
||||||
size="XS"
|
hoverable
|
||||||
hoverable
|
on:click={() => toggleRow(relationship)}
|
||||||
on:click={() => toggleRow(relationship)}
|
/>
|
||||||
/>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
{/each}
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
{#if editable}
|
{#if editable}
|
||||||
<div class="add" on:click={open}>
|
<div class="add" on:click={open}>
|
||||||
<Icon name="Add" size="S" />
|
<Icon name="Add" size="S" />
|
||||||
|
@ -320,7 +303,7 @@
|
||||||
<div class="searching">
|
<div class="searching">
|
||||||
<ProgressCircle size="S" />
|
<ProgressCircle size="S" />
|
||||||
</div>
|
</div>
|
||||||
{:else if Array.isArray(searchResults) && searchResults.length}
|
{:else if searchResults?.length}
|
||||||
<div class="results">
|
<div class="results">
|
||||||
{#each searchResults as row, idx}
|
{#each searchResults as row, idx}
|
||||||
<div
|
<div
|
||||||
|
|
47
packages/frontend-core/src/components/grid/stores/cache.js
Normal file
47
packages/frontend-core/src/components/grid/stores/cache.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
export const createActions = context => {
|
||||||
|
const { API } = context
|
||||||
|
|
||||||
|
// Cache for the primary display columns of different tables.
|
||||||
|
// If we ever need to cache table definitions for other purposes then we can
|
||||||
|
// expand this to be a more generic cache.
|
||||||
|
let primaryDisplayCache = {}
|
||||||
|
|
||||||
|
const resetPrimaryDisplayCache = () => {
|
||||||
|
primaryDisplayCache = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPrimaryDisplayForTableId = async tableId => {
|
||||||
|
// If we've never encountered this tableId before then store a promise that
|
||||||
|
// resolves to the primary display so that subsequent invocations before the
|
||||||
|
// promise completes can reuse this promise
|
||||||
|
if (!primaryDisplayCache[tableId]) {
|
||||||
|
primaryDisplayCache[tableId] = new Promise(resolve => {
|
||||||
|
API.fetchTableDefinition(tableId).then(def => {
|
||||||
|
const display = def?.primaryDisplay || def?.schema?.[0]?.name
|
||||||
|
primaryDisplayCache[tableId] = display
|
||||||
|
resolve(display)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// We await the result so that we account for both promises and primitives
|
||||||
|
return await primaryDisplayCache[tableId]
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cache: {
|
||||||
|
actions: {
|
||||||
|
getPrimaryDisplayForTableId,
|
||||||
|
resetPrimaryDisplayCache,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialise = context => {
|
||||||
|
const { datasource, cache } = context
|
||||||
|
|
||||||
|
// Wipe the caches whenever the datasource changes to ensure we aren't
|
||||||
|
// storing any stale information
|
||||||
|
datasource.subscribe(cache.actions.resetPrimaryDisplayCache)
|
||||||
|
}
|
|
@ -160,11 +160,6 @@ export const createActions = context => {
|
||||||
return getAPI()?.actions.canUseColumn(name)
|
return getAPI()?.actions.canUseColumn(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the default number of rows for a single page
|
|
||||||
const getFeatures = () => {
|
|
||||||
return getAPI()?.actions.getFeatures()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
datasource: {
|
datasource: {
|
||||||
...datasource,
|
...datasource,
|
||||||
|
@ -177,7 +172,6 @@ export const createActions = context => {
|
||||||
getRow,
|
getRow,
|
||||||
isDatasourceValid,
|
isDatasourceValid,
|
||||||
canUseColumn,
|
canUseColumn,
|
||||||
getFeatures,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,11 +35,6 @@ export const createActions = context => {
|
||||||
return $columns.some(col => col.name === name) || $sticky?.name === name
|
return $columns.some(col => col.name === name) || $sticky?.name === name
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFeatures = () => {
|
|
||||||
// We don't support any features
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nonPlus: {
|
nonPlus: {
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -50,7 +45,6 @@ export const createActions = context => {
|
||||||
getRow,
|
getRow,
|
||||||
isDatasourceValid,
|
isDatasourceValid,
|
||||||
canUseColumn,
|
canUseColumn,
|
||||||
getFeatures,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import TableFetch from "../../../../fetch/TableFetch"
|
|
||||||
|
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
|
||||||
|
@ -46,10 +45,6 @@ export const createActions = context => {
|
||||||
return $columns.some(col => col.name === name) || $sticky?.name === name
|
return $columns.some(col => col.name === name) || $sticky?.name === name
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFeatures = () => {
|
|
||||||
return new TableFetch({ API }).determineFeatureFlags()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
table: {
|
table: {
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -60,7 +55,6 @@ export const createActions = context => {
|
||||||
getRow,
|
getRow,
|
||||||
isDatasourceValid,
|
isDatasourceValid,
|
||||||
canUseColumn,
|
canUseColumn,
|
||||||
getFeatures,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import ViewV2Fetch from "../../../../fetch/ViewV2Fetch"
|
|
||||||
|
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
|
||||||
|
@ -46,10 +45,6 @@ export const createActions = context => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFeatures = () => {
|
|
||||||
return new ViewV2Fetch({ API }).determineFeatureFlags()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
viewV2: {
|
viewV2: {
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -60,7 +55,6 @@ export const createActions = context => {
|
||||||
getRow,
|
getRow,
|
||||||
isDatasourceValid,
|
isDatasourceValid,
|
||||||
canUseColumn,
|
canUseColumn,
|
||||||
getFeatures,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import * as Datasource from "./datasource"
|
||||||
import * as Table from "./datasources/table"
|
import * as Table from "./datasources/table"
|
||||||
import * as ViewV2 from "./datasources/viewV2"
|
import * as ViewV2 from "./datasources/viewV2"
|
||||||
import * as NonPlus from "./datasources/nonPlus"
|
import * as NonPlus from "./datasources/nonPlus"
|
||||||
|
import * as Cache from "./cache"
|
||||||
|
|
||||||
const DependencyOrderedStores = [
|
const DependencyOrderedStores = [
|
||||||
Sort,
|
Sort,
|
||||||
|
@ -42,6 +43,7 @@ const DependencyOrderedStores = [
|
||||||
Clipboard,
|
Clipboard,
|
||||||
Config,
|
Config,
|
||||||
Notifications,
|
Notifications,
|
||||||
|
Cache,
|
||||||
]
|
]
|
||||||
|
|
||||||
export const attachStores = context => {
|
export const attachStores = context => {
|
||||||
|
|
|
@ -114,10 +114,6 @@ export const createActions = context => {
|
||||||
const $allFilters = get(allFilters)
|
const $allFilters = get(allFilters)
|
||||||
const $sort = get(sort)
|
const $sort = get(sort)
|
||||||
|
|
||||||
// Determine how many rows to fetch per page
|
|
||||||
const features = datasource.actions.getFeatures()
|
|
||||||
const limit = features?.supportsPagination ? RowPageSize : null
|
|
||||||
|
|
||||||
// Create new fetch model
|
// Create new fetch model
|
||||||
const newFetch = fetchData({
|
const newFetch = fetchData({
|
||||||
API,
|
API,
|
||||||
|
@ -126,8 +122,12 @@ export const createActions = context => {
|
||||||
filter: $allFilters,
|
filter: $allFilters,
|
||||||
sortColumn: $sort.column,
|
sortColumn: $sort.column,
|
||||||
sortOrder: $sort.order,
|
sortOrder: $sort.order,
|
||||||
limit,
|
limit: RowPageSize,
|
||||||
paginate: true,
|
paginate: true,
|
||||||
|
|
||||||
|
// Disable client side limiting, so that for queries and custom data
|
||||||
|
// sources we don't impose fake row limits. We want all the data.
|
||||||
|
clientSideLimiting: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,11 @@ export default class DataFetch {
|
||||||
|
|
||||||
// Pagination config
|
// Pagination config
|
||||||
paginate: true,
|
paginate: true,
|
||||||
|
|
||||||
|
// Client side feature customisation
|
||||||
|
clientSideSearching: true,
|
||||||
|
clientSideSorting: true,
|
||||||
|
clientSideLimiting: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// State of the fetch
|
// State of the fetch
|
||||||
|
@ -208,24 +213,32 @@ export default class DataFetch {
|
||||||
* Fetches some filtered, sorted and paginated data
|
* Fetches some filtered, sorted and paginated data
|
||||||
*/
|
*/
|
||||||
async getPage() {
|
async getPage() {
|
||||||
const { sortColumn, sortOrder, sortType, limit } = this.options
|
const {
|
||||||
|
sortColumn,
|
||||||
|
sortOrder,
|
||||||
|
sortType,
|
||||||
|
limit,
|
||||||
|
clientSideSearching,
|
||||||
|
clientSideSorting,
|
||||||
|
clientSideLimiting,
|
||||||
|
} = this.options
|
||||||
const { query } = get(this.store)
|
const { query } = get(this.store)
|
||||||
|
|
||||||
// Get the actual data
|
// Get the actual data
|
||||||
let { rows, info, hasNextPage, cursor, error } = await this.getData()
|
let { rows, info, hasNextPage, cursor, error } = await this.getData()
|
||||||
|
|
||||||
// If we don't support searching, do a client search
|
// If we don't support searching, do a client search
|
||||||
if (!this.features.supportsSearch) {
|
if (!this.features.supportsSearch && clientSideSearching) {
|
||||||
rows = runLuceneQuery(rows, query)
|
rows = runLuceneQuery(rows, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't support sorting, do a client-side sort
|
// If we don't support sorting, do a client-side sort
|
||||||
if (!this.features.supportsSort) {
|
if (!this.features.supportsSort && clientSideSorting) {
|
||||||
rows = luceneSort(rows, sortColumn, sortOrder, sortType)
|
rows = luceneSort(rows, sortColumn, sortOrder, sortType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't support pagination, do a client-side limit
|
// If we don't support pagination, do a client-side limit
|
||||||
if (!this.features.supportsPagination) {
|
if (!this.features.supportsPagination && clientSideLimiting) {
|
||||||
rows = luceneLimit(rows, limit)
|
rows = luceneLimit(rows, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ export const serveApp = async function (ctx: Ctx) {
|
||||||
const { head, html, css } = App.render({
|
const { head, html, css } = App.render({
|
||||||
metaImage:
|
metaImage:
|
||||||
branding?.metaImageUrl ||
|
branding?.metaImageUrl ||
|
||||||
"https://res.cloudinary.com/daog6scxm/image/upload/v1666109324/meta-images/budibase-meta-image_uukc1m.png",
|
"https://res.cloudinary.com/daog6scxm/image/upload/v1698759482/meta-images/plain-branded-meta-image-coral_ocxmgu.png",
|
||||||
metaDescription: branding?.metaDescription || "",
|
metaDescription: branding?.metaDescription || "",
|
||||||
metaTitle:
|
metaTitle:
|
||||||
branding?.metaTitle || `${appInfo.name} - built with Budibase`,
|
branding?.metaTitle || `${appInfo.name} - built with Budibase`,
|
||||||
|
@ -185,7 +185,7 @@ export const serveApp = async function (ctx: Ctx) {
|
||||||
metaTitle: branding?.metaTitle,
|
metaTitle: branding?.metaTitle,
|
||||||
metaImage:
|
metaImage:
|
||||||
branding?.metaImageUrl ||
|
branding?.metaImageUrl ||
|
||||||
"https://res.cloudinary.com/daog6scxm/image/upload/v1666109324/meta-images/budibase-meta-image_uukc1m.png",
|
"https://res.cloudinary.com/daog6scxm/image/upload/v1698759482/meta-images/plain-branded-meta-image-coral_ocxmgu.png",
|
||||||
metaDescription: branding?.metaDescription || "",
|
metaDescription: branding?.metaDescription || "",
|
||||||
favicon:
|
favicon:
|
||||||
branding.faviconUrl !== ""
|
branding.faviconUrl !== ""
|
||||||
|
|
|
@ -49,7 +49,12 @@ describe.each([
|
||||||
let table: Table
|
let table: Table
|
||||||
let tableId: string
|
let tableId: string
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
afterAll(async () => {
|
||||||
|
if (dsProvider) {
|
||||||
|
await dsProvider.stopContainer()
|
||||||
|
}
|
||||||
|
setup.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.init()
|
await config.init()
|
||||||
|
@ -521,20 +526,17 @@ describe.each([
|
||||||
const rowUsage = await getRowUsage()
|
const rowUsage = await getRowUsage()
|
||||||
const queryUsage = await getQueryUsage()
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await config.api.row.patch(table._id!, {
|
const row = await config.api.row.patch(table._id!, {
|
||||||
_id: existing._id!,
|
_id: existing._id!,
|
||||||
_rev: existing._rev!,
|
_rev: existing._rev!,
|
||||||
tableId: table._id!,
|
tableId: table._id!,
|
||||||
name: "Updated Name",
|
name: "Updated Name",
|
||||||
})
|
})
|
||||||
|
|
||||||
expect((res as any).res.statusMessage).toEqual(
|
expect(row.name).toEqual("Updated Name")
|
||||||
`${table.name} updated successfully.`
|
expect(row.description).toEqual(existing.description)
|
||||||
)
|
|
||||||
expect(res.body.name).toEqual("Updated Name")
|
|
||||||
expect(res.body.description).toEqual(existing.description)
|
|
||||||
|
|
||||||
const savedRow = await loadRow(res.body._id, table._id!)
|
const savedRow = await loadRow(row._id!, table._id!)
|
||||||
|
|
||||||
expect(savedRow.body.description).toEqual(existing.description)
|
expect(savedRow.body.description).toEqual(existing.description)
|
||||||
expect(savedRow.body.name).toEqual("Updated Name")
|
expect(savedRow.body.name).toEqual("Updated Name")
|
||||||
|
@ -561,6 +563,56 @@ describe.each([
|
||||||
await assertRowUsage(rowUsage)
|
await assertRowUsage(rowUsage)
|
||||||
await assertQueryUsage(queryUsage)
|
await assertQueryUsage(queryUsage)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should not overwrite links if those links are not set", async () => {
|
||||||
|
let linkField: FieldSchema = {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
name: "",
|
||||||
|
fieldName: "",
|
||||||
|
constraints: {
|
||||||
|
type: "array",
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
relationshipType: RelationshipType.ONE_TO_MANY,
|
||||||
|
tableId: InternalTable.USER_METADATA,
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = await config.api.table.create({
|
||||||
|
name: "TestTable",
|
||||||
|
type: "table",
|
||||||
|
sourceType: TableSourceType.INTERNAL,
|
||||||
|
sourceId: INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
schema: {
|
||||||
|
user1: { ...linkField, name: "user1", fieldName: "user1" },
|
||||||
|
user2: { ...linkField, name: "user2", fieldName: "user2" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let user1 = await config.createUser()
|
||||||
|
let user2 = await config.createUser()
|
||||||
|
|
||||||
|
let row = await config.api.row.save(table._id!, {
|
||||||
|
user1: [{ _id: user1._id }],
|
||||||
|
user2: [{ _id: user2._id }],
|
||||||
|
})
|
||||||
|
|
||||||
|
let getResp = await config.api.row.get(table._id!, row._id!)
|
||||||
|
expect(getResp.body.user1[0]._id).toEqual(user1._id)
|
||||||
|
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||||
|
|
||||||
|
let patchResp = await config.api.row.patch(table._id!, {
|
||||||
|
_id: row._id!,
|
||||||
|
_rev: row._rev!,
|
||||||
|
tableId: table._id!,
|
||||||
|
user1: [{ _id: user2._id }],
|
||||||
|
})
|
||||||
|
expect(patchResp.user1[0]._id).toEqual(user2._id)
|
||||||
|
expect(patchResp.user2[0]._id).toEqual(user2._id)
|
||||||
|
|
||||||
|
getResp = await config.api.row.get(table._id!, row._id!)
|
||||||
|
expect(getResp.body.user1[0]._id).toEqual(user2._id)
|
||||||
|
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("destroy", () => {
|
describe("destroy", () => {
|
||||||
|
|
|
@ -492,6 +492,67 @@ describe("/tables", () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should succeed when the row is created from the other side of the relationship", async () => {
|
||||||
|
// We found a bug just after releasing this feature where if the row was created from the
|
||||||
|
// users table, not the table linking to it, the migration would succeed but lose the data.
|
||||||
|
// This happened because the order of the documents in the link was reversed.
|
||||||
|
const table = await config.api.table.create({
|
||||||
|
name: "table",
|
||||||
|
type: "table",
|
||||||
|
sourceId: INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
sourceType: TableSourceType.INTERNAL,
|
||||||
|
schema: {
|
||||||
|
"user relationship": {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
fieldName: "test",
|
||||||
|
name: "user relationship",
|
||||||
|
constraints: {
|
||||||
|
type: "array",
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
relationshipType: RelationshipType.MANY_TO_ONE,
|
||||||
|
tableId: InternalTable.USER_METADATA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let testRow = await config.api.row.save(table._id!, {})
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
users.map(u =>
|
||||||
|
config.api.row.patch(InternalTable.USER_METADATA, {
|
||||||
|
tableId: InternalTable.USER_METADATA,
|
||||||
|
_rev: u._rev!,
|
||||||
|
_id: u._id!,
|
||||||
|
test: [testRow],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await config.api.table.migrate(table._id!, {
|
||||||
|
oldColumn: table.schema["user relationship"],
|
||||||
|
newColumn: {
|
||||||
|
name: "user column",
|
||||||
|
type: FieldType.BB_REFERENCE,
|
||||||
|
subtype: FieldSubtype.USERS,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const migratedTable = await config.api.table.get(table._id!)
|
||||||
|
expect(migratedTable.schema["user column"]).toBeDefined()
|
||||||
|
expect(migratedTable.schema["user relationship"]).not.toBeDefined()
|
||||||
|
|
||||||
|
const resp = await config.api.row.get(table._id!, testRow._id!)
|
||||||
|
const migratedRow = resp.body as Row
|
||||||
|
|
||||||
|
expect(migratedRow["user column"]).toBeDefined()
|
||||||
|
expect(migratedRow["user relationship"]).not.toBeDefined()
|
||||||
|
expect(migratedRow["user column"]).toHaveLength(3)
|
||||||
|
expect(migratedRow["user column"].map((u: Row) => u._id)).toEqual(
|
||||||
|
expect.arrayContaining(users.map(u => u._id))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("should successfully migrate a many-to-many user relationship to a users column", async () => {
|
it("should successfully migrate a many-to-many user relationship to a users column", async () => {
|
||||||
const table = await config.api.table.create({
|
const table = await config.api.table.create({
|
||||||
name: "table",
|
name: "table",
|
||||||
|
|
|
@ -36,7 +36,7 @@ describe("Run through some parts of the automations system", () => {
|
||||||
it("should be able to init in builder", async () => {
|
it("should be able to init in builder", async () => {
|
||||||
const automation: Automation = {
|
const automation: Automation = {
|
||||||
...basicAutomation(),
|
...basicAutomation(),
|
||||||
appId: config.appId,
|
appId: config.appId!,
|
||||||
}
|
}
|
||||||
const fields: any = { a: 1, appId: config.appId }
|
const fields: any = { a: 1, appId: config.appId }
|
||||||
await triggers.externalTrigger(automation, fields)
|
await triggers.externalTrigger(automation, fields)
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
const setup = require("./utilities")
|
|
||||||
|
|
||||||
describe("test the update row action", () => {
|
|
||||||
let table, row, inputs
|
|
||||||
let config = setup.getConfig()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.init()
|
|
||||||
table = await config.createTable()
|
|
||||||
row = await config.createRow()
|
|
||||||
inputs = {
|
|
||||||
rowId: row._id,
|
|
||||||
row: {
|
|
||||||
...row,
|
|
||||||
name: "Updated name",
|
|
||||||
// put a falsy option in to be removed
|
|
||||||
description: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
|
||||||
|
|
||||||
it("should be able to run the action", async () => {
|
|
||||||
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs)
|
|
||||||
expect(res.success).toEqual(true)
|
|
||||||
const updatedRow = await config.getRow(table._id, res.id)
|
|
||||||
expect(updatedRow.name).toEqual("Updated name")
|
|
||||||
expect(updatedRow.description).not.toEqual("")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should check invalid inputs return an error", async () => {
|
|
||||||
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {})
|
|
||||||
expect(res.success).toEqual(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should return an error when table doesn't exist", async () => {
|
|
||||||
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
|
|
||||||
row: { _id: "invalid" },
|
|
||||||
rowId: "invalid",
|
|
||||||
})
|
|
||||||
expect(res.success).toEqual(false)
|
|
||||||
})
|
|
||||||
})
|
|
169
packages/server/src/automations/tests/updateRow.spec.ts
Normal file
169
packages/server/src/automations/tests/updateRow.spec.ts
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
import {
|
||||||
|
FieldSchema,
|
||||||
|
FieldType,
|
||||||
|
INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
InternalTable,
|
||||||
|
RelationshipType,
|
||||||
|
Row,
|
||||||
|
Table,
|
||||||
|
TableSourceType,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
import * as setup from "./utilities"
|
||||||
|
import * as uuid from "uuid"
|
||||||
|
|
||||||
|
describe("test the update row action", () => {
|
||||||
|
let table: Table, row: Row, inputs: any
|
||||||
|
let config = setup.getConfig()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.init()
|
||||||
|
table = await config.createTable()
|
||||||
|
row = await config.createRow()
|
||||||
|
inputs = {
|
||||||
|
rowId: row._id,
|
||||||
|
row: {
|
||||||
|
...row,
|
||||||
|
name: "Updated name",
|
||||||
|
// put a falsy option in to be removed
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
|
it("should be able to run the action", async () => {
|
||||||
|
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs)
|
||||||
|
expect(res.success).toEqual(true)
|
||||||
|
const updatedRow = await config.getRow(table._id!, res.id)
|
||||||
|
expect(updatedRow.name).toEqual("Updated name")
|
||||||
|
expect(updatedRow.description).not.toEqual("")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should check invalid inputs return an error", async () => {
|
||||||
|
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {})
|
||||||
|
expect(res.success).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return an error when table doesn't exist", async () => {
|
||||||
|
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
|
||||||
|
row: { _id: "invalid" },
|
||||||
|
rowId: "invalid",
|
||||||
|
})
|
||||||
|
expect(res.success).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not overwrite links if those links are not set", async () => {
|
||||||
|
let linkField: FieldSchema = {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
name: "",
|
||||||
|
fieldName: "",
|
||||||
|
constraints: {
|
||||||
|
type: "array",
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
relationshipType: RelationshipType.ONE_TO_MANY,
|
||||||
|
tableId: InternalTable.USER_METADATA,
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = await config.api.table.create({
|
||||||
|
name: uuid.v4(),
|
||||||
|
type: "table",
|
||||||
|
sourceType: TableSourceType.INTERNAL,
|
||||||
|
sourceId: INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
schema: {
|
||||||
|
user1: { ...linkField, name: "user1", fieldName: uuid.v4() },
|
||||||
|
user2: { ...linkField, name: "user2", fieldName: uuid.v4() },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let user1 = await config.createUser()
|
||||||
|
let user2 = await config.createUser()
|
||||||
|
|
||||||
|
let row = await config.api.row.save(table._id!, {
|
||||||
|
user1: [{ _id: user1._id }],
|
||||||
|
user2: [{ _id: user2._id }],
|
||||||
|
})
|
||||||
|
|
||||||
|
let getResp = await config.api.row.get(table._id!, row._id!)
|
||||||
|
expect(getResp.body.user1[0]._id).toEqual(user1._id)
|
||||||
|
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||||
|
|
||||||
|
let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
|
||||||
|
rowId: row._id,
|
||||||
|
row: {
|
||||||
|
_id: row._id,
|
||||||
|
_rev: row._rev,
|
||||||
|
tableId: row.tableId,
|
||||||
|
user1: [user2._id],
|
||||||
|
user2: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(stepResp.success).toEqual(true)
|
||||||
|
|
||||||
|
getResp = await config.api.row.get(table._id!, row._id!)
|
||||||
|
expect(getResp.body.user1[0]._id).toEqual(user2._id)
|
||||||
|
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should overwrite links if those links are not set and we ask it do", async () => {
|
||||||
|
let linkField: FieldSchema = {
|
||||||
|
type: FieldType.LINK,
|
||||||
|
name: "",
|
||||||
|
fieldName: "",
|
||||||
|
constraints: {
|
||||||
|
type: "array",
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
relationshipType: RelationshipType.ONE_TO_MANY,
|
||||||
|
tableId: InternalTable.USER_METADATA,
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = await config.api.table.create({
|
||||||
|
name: uuid.v4(),
|
||||||
|
type: "table",
|
||||||
|
sourceType: TableSourceType.INTERNAL,
|
||||||
|
sourceId: INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
schema: {
|
||||||
|
user1: { ...linkField, name: "user1", fieldName: uuid.v4() },
|
||||||
|
user2: { ...linkField, name: "user2", fieldName: uuid.v4() },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let user1 = await config.createUser()
|
||||||
|
let user2 = await config.createUser()
|
||||||
|
|
||||||
|
let row = await config.api.row.save(table._id!, {
|
||||||
|
user1: [{ _id: user1._id }],
|
||||||
|
user2: [{ _id: user2._id }],
|
||||||
|
})
|
||||||
|
|
||||||
|
let getResp = await config.api.row.get(table._id!, row._id!)
|
||||||
|
expect(getResp.body.user1[0]._id).toEqual(user1._id)
|
||||||
|
expect(getResp.body.user2[0]._id).toEqual(user2._id)
|
||||||
|
|
||||||
|
let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
|
||||||
|
rowId: row._id,
|
||||||
|
row: {
|
||||||
|
_id: row._id,
|
||||||
|
_rev: row._rev,
|
||||||
|
tableId: row.tableId,
|
||||||
|
user1: [user2._id],
|
||||||
|
user2: "",
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
fields: {
|
||||||
|
user2: {
|
||||||
|
clearRelationships: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(stepResp.success).toEqual(true)
|
||||||
|
|
||||||
|
getResp = await config.api.row.get(table._id!, row._id!)
|
||||||
|
expect(getResp.body.user1[0]._id).toEqual(user2._id)
|
||||||
|
expect(getResp.body.user2).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
|
@ -4,11 +4,11 @@ import { BUILTIN_ACTION_DEFINITIONS, getAction } from "../../actions"
|
||||||
import emitter from "../../../events/index"
|
import emitter from "../../../events/index"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
|
|
||||||
let config: any
|
let config: TestConfig
|
||||||
|
|
||||||
export function getConfig() {
|
export function getConfig(): TestConfig {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
config = new TestConfig(false)
|
config = new TestConfig(true)
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
isBBReferenceField,
|
isBBReferenceField,
|
||||||
isRelationshipField,
|
isRelationshipField,
|
||||||
LinkDocument,
|
LinkDocument,
|
||||||
|
LinkInfo,
|
||||||
RelationshipFieldMetadata,
|
RelationshipFieldMetadata,
|
||||||
RelationshipType,
|
RelationshipType,
|
||||||
Row,
|
Row,
|
||||||
|
@ -125,7 +126,23 @@ abstract class UserColumnMigrator implements ColumnMigrator {
|
||||||
protected newColumn: BBReferenceFieldMetadata
|
protected newColumn: BBReferenceFieldMetadata
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
abstract updateRow(row: Row, link: LinkDocument): void
|
abstract updateRow(row: Row, linkInfo: LinkInfo): void
|
||||||
|
|
||||||
|
pickUserTableLinkSide(link: LinkDocument): LinkInfo {
|
||||||
|
if (link.doc1.tableId === InternalTable.USER_METADATA) {
|
||||||
|
return link.doc1
|
||||||
|
} else {
|
||||||
|
return link.doc2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pickOtherTableLinkSide(link: LinkDocument): LinkInfo {
|
||||||
|
if (link.doc1.tableId === InternalTable.USER_METADATA) {
|
||||||
|
return link.doc2
|
||||||
|
} else {
|
||||||
|
return link.doc1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async doMigration(): Promise<MigrationResult> {
|
async doMigration(): Promise<MigrationResult> {
|
||||||
let oldTable = cloneDeep(this.table)
|
let oldTable = cloneDeep(this.table)
|
||||||
|
@ -137,15 +154,17 @@ abstract class UserColumnMigrator implements ColumnMigrator {
|
||||||
|
|
||||||
let links = await sdk.links.fetchWithDocument(this.table._id!)
|
let links = await sdk.links.fetchWithDocument(this.table._id!)
|
||||||
for (let link of links) {
|
for (let link of links) {
|
||||||
|
const userSide = this.pickUserTableLinkSide(link)
|
||||||
|
const otherSide = this.pickOtherTableLinkSide(link)
|
||||||
if (
|
if (
|
||||||
link.doc1.tableId !== this.table._id ||
|
otherSide.tableId !== this.table._id ||
|
||||||
link.doc1.fieldName !== this.oldColumn.name ||
|
otherSide.fieldName !== this.oldColumn.name ||
|
||||||
link.doc2.tableId !== InternalTable.USER_METADATA
|
userSide.tableId !== InternalTable.USER_METADATA
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = rowsById[link.doc1.rowId]
|
let row = rowsById[otherSide.rowId]
|
||||||
if (!row) {
|
if (!row) {
|
||||||
// This can happen if the row has been deleted but the link hasn't,
|
// This can happen if the row has been deleted but the link hasn't,
|
||||||
// which was a state that was found during the initial testing of this
|
// which was a state that was found during the initial testing of this
|
||||||
|
@ -153,7 +172,7 @@ abstract class UserColumnMigrator implements ColumnMigrator {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateRow(row, link)
|
this.updateRow(row, userSide)
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = context.getAppDB()
|
let db = context.getAppDB()
|
||||||
|
@ -175,20 +194,20 @@ abstract class UserColumnMigrator implements ColumnMigrator {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SingleUserColumnMigrator extends UserColumnMigrator {
|
class SingleUserColumnMigrator extends UserColumnMigrator {
|
||||||
updateRow(row: Row, link: LinkDocument): void {
|
updateRow(row: Row, linkInfo: LinkInfo): void {
|
||||||
row[this.newColumn.name] = dbCore.getGlobalIDFromUserMetadataID(
|
row[this.newColumn.name] = dbCore.getGlobalIDFromUserMetadataID(
|
||||||
link.doc2.rowId
|
linkInfo.rowId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MultiUserColumnMigrator extends UserColumnMigrator {
|
class MultiUserColumnMigrator extends UserColumnMigrator {
|
||||||
updateRow(row: Row, link: LinkDocument): void {
|
updateRow(row: Row, linkInfo: LinkInfo): void {
|
||||||
if (!row[this.newColumn.name]) {
|
if (!row[this.newColumn.name]) {
|
||||||
row[this.newColumn.name] = []
|
row[this.newColumn.name] = []
|
||||||
}
|
}
|
||||||
row[this.newColumn.name].push(
|
row[this.newColumn.name].push(
|
||||||
dbCore.getGlobalIDFromUserMetadataID(link.doc2.rowId)
|
dbCore.getGlobalIDFromUserMetadataID(linkInfo.rowId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,13 @@ export class RowAPI extends TestAPI {
|
||||||
.send(row)
|
.send(row)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(expectStatus)
|
if (resp.status !== expectStatus) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected status ${expectStatus} but got ${
|
||||||
|
resp.status
|
||||||
|
}, body: ${JSON.stringify(resp.body)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
return resp.body as Row
|
return resp.body as Row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,13 +83,20 @@ export class RowAPI extends TestAPI {
|
||||||
sourceId: string,
|
sourceId: string,
|
||||||
row: PatchRowRequest,
|
row: PatchRowRequest,
|
||||||
{ expectStatus } = { expectStatus: 200 }
|
{ expectStatus } = { expectStatus: 200 }
|
||||||
) => {
|
): Promise<Row> => {
|
||||||
return this.request
|
let resp = await this.request
|
||||||
.patch(`/api/${sourceId}/rows`)
|
.patch(`/api/${sourceId}/rows`)
|
||||||
.send(row)
|
.send(row)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(expectStatus)
|
if (resp.status !== expectStatus) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected status ${expectStatus} but got ${
|
||||||
|
resp.status
|
||||||
|
}, body: ${JSON.stringify(resp.body)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return resp.body as Row
|
||||||
}
|
}
|
||||||
|
|
||||||
delete = async (
|
delete = async (
|
||||||
|
|
|
@ -22,7 +22,15 @@ export class TableAPI extends TestAPI {
|
||||||
.send(data)
|
.send(data)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(expectStatus)
|
|
||||||
|
if (res.status !== expectStatus) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected status ${expectStatus} but got ${
|
||||||
|
res.status
|
||||||
|
} with body ${JSON.stringify(res.body)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import { Document } from "../document"
|
import { Document } from "../document"
|
||||||
|
|
||||||
|
export interface LinkInfo {
|
||||||
|
rowId: string
|
||||||
|
fieldName: string
|
||||||
|
tableId: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface LinkDocument extends Document {
|
export interface LinkDocument extends Document {
|
||||||
type: string
|
type: string
|
||||||
doc1: {
|
doc1: LinkInfo
|
||||||
rowId: string
|
doc2: LinkInfo
|
||||||
fieldName: string
|
|
||||||
tableId: string
|
|
||||||
}
|
|
||||||
doc2: {
|
|
||||||
rowId: string
|
|
||||||
fieldName: string
|
|
||||||
tableId: string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LinkDocumentValue {
|
export interface LinkDocumentValue {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
export enum FeatureFlag {
|
export enum FeatureFlag {
|
||||||
LICENSING = "LICENSING",
|
LICENSING = "LICENSING",
|
||||||
// Feature IDs in Posthog
|
PER_CREATOR_PER_USER_PRICE = "PER_CREATOR_PER_USER_PRICE",
|
||||||
PER_CREATOR_PER_USER_PRICE = "18873",
|
PER_CREATOR_PER_USER_PRICE_ALERT = "PER_CREATOR_PER_USER_PRICE_ALERT",
|
||||||
PER_CREATOR_PER_USER_PRICE_ALERT = "18530",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TenantFeatureFlags {
|
export interface TenantFeatureFlags {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #3869D4 !important;
|
color: #6E56FF !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
a img {
|
a img {
|
||||||
|
@ -109,11 +109,11 @@
|
||||||
/* Buttons ------------------------------ */
|
/* Buttons ------------------------------ */
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background-color: #3869D4;
|
background-color: #6E56FF;
|
||||||
border-top: 10px solid #3869D4;
|
border-top: 10px solid #6E56FF;
|
||||||
border-right: 18px solid #3869D4;
|
border-right: 18px solid #6E56FF;
|
||||||
border-bottom: 10px solid #3869D4;
|
border-bottom: 10px solid #6E56FF;
|
||||||
border-left: 18px solid #3869D4;
|
border-left: 18px solid #6E56FF;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: #FFF !important;
|
color: #FFF !important;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
|
|
|
@ -16,15 +16,11 @@
|
||||||
cellspacing="0"
|
cellspacing="0"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
width="32"
|
|
||||||
height="32"
|
height="32"
|
||||||
style="margin-right:16px; vertical-align: middle;"
|
style="margin-right:16px; vertical-align: middle;"
|
||||||
alt="Budibase Logo"
|
alt="Budibase Logo"
|
||||||
src="https://i.imgur.com/Xhdt1YP.png"
|
src="https://res.cloudinary.com/daog6scxm/image/upload/v1696521007/Branding/Assets/Logo/RGB/Full%20Colour/Budibase_Logo_RGB_FullColour_Negative_e9yziz_1_u6oxzg.png"
|
||||||
/>
|
/>
|
||||||
<strong style="vertical-align: middle; font-size: 1.1em">
|
|
||||||
Budibase
|
|
||||||
</strong>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
## Description
|
## Description
|
||||||
_Describe the problem or feature in addition to a link to the relevant github issues._
|
_Describe the problem or feature in addition to a link to the relevant github issues._
|
||||||
|
|
||||||
Addresses:
|
### Addresses:
|
||||||
- `<Enter the Link to the issue(s) this PR addresses>`
|
- `<Enter the Link to the issue(s) this PR addresses>`
|
||||||
- ...more if required
|
- ...more if required
|
||||||
|
|
||||||
|
@ -10,9 +10,3 @@ Addresses:
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
_If a UI facing feature, a short video of the happy path, and some screenshots of the new functionality._
|
_If a UI facing feature, a short video of the happy path, and some screenshots of the new functionality._
|
||||||
|
|
||||||
## Documentation
|
|
||||||
- [ ] I have reviewed the budibase documentatation to verify if this feature requires any changes. If changes or new docs are required I have written them.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
if [[ $TARGETARCH == arm* ]] ;
|
if [[ $TARGETARCH == arm* ]] ;
|
||||||
then
|
then
|
||||||
echo "INSTALLING ARM64 MINIO"
|
echo "INSTALLING ARM64 MINIO"
|
||||||
wget wget https://dl.min.io/server/minio/release/linux-arm64/archive/minio.deb -O minio.deb
|
wget https://dl.min.io/server/minio/release/linux-arm64/minio
|
||||||
else
|
else
|
||||||
echo "INSTALLING AMD64 MINIO"
|
echo "INSTALLING AMD64 MINIO"
|
||||||
wget wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio.deb -O minio.deb
|
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
||||||
fi
|
fi
|
||||||
dpkg -i minio.deb
|
chmod +x minio
|
Loading…
Reference in a new issue