Merge branch 'develop' of github.com:Budibase/budibase into feature/test-image
This commit is contained in:
commit
84403253b1
|
@ -38,17 +38,6 @@ jobs:
|
||||||
fi
|
fi
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Tag and release Proxy service docker image
|
|
||||||
run: |
|
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
|
||||||
yarn build:docker:proxy:prod
|
|
||||||
docker tag proxy-service budibase/proxy:$PROD_TAG
|
|
||||||
docker push budibase/proxy:$PROD_TAG
|
|
||||||
env:
|
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
|
||||||
PROD_TAG: k8s
|
|
||||||
|
|
||||||
- name: Configure AWS Credentials
|
- name: Configure AWS Credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v1
|
uses: aws-actions/configure-aws-credentials@v1
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -28,17 +28,6 @@ jobs:
|
||||||
release_version=$(cat lerna.json | jq -r '.version')
|
release_version=$(cat lerna.json | jq -r '.version')
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Tag and release Proxy service docker image
|
|
||||||
run: |
|
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
|
||||||
yarn build:docker:proxy:preprod
|
|
||||||
docker tag proxy-service budibase/proxy:$PREPROD_TAG
|
|
||||||
docker push budibase/proxy:$PREPROD_TAG
|
|
||||||
env:
|
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
|
||||||
PREPROD_TAG: k8s-preprod
|
|
||||||
|
|
||||||
- name: Pull values.yaml from budibase-infra
|
- name: Pull values.yaml from budibase-infra
|
||||||
run: |
|
run: |
|
||||||
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
|
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
|
||||||
|
|
|
@ -29,17 +29,6 @@ jobs:
|
||||||
release_version=$(cat lerna.json | jq -r '.version')
|
release_version=$(cat lerna.json | jq -r '.version')
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Tag and release Proxy service docker image
|
|
||||||
run: |
|
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
|
||||||
yarn build:docker:proxy:release
|
|
||||||
docker tag proxy-service budibase/proxy:$RELEASE_TAG
|
|
||||||
docker push budibase/proxy:$RELEASE_TAG
|
|
||||||
env:
|
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
|
||||||
RELEASE_TAG: k8s-release
|
|
||||||
|
|
||||||
- name: Pull values.yaml from budibase-infra
|
- name: Pull values.yaml from budibase-infra
|
||||||
run: |
|
run: |
|
||||||
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
|
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
|
||||||
|
|
|
@ -26,7 +26,7 @@ env:
|
||||||
FEATURE_PREVIEW_URL: https://budirelease.live
|
FEATURE_PREVIEW_URL: https://budirelease.live
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release-images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -44,19 +44,12 @@ jobs:
|
||||||
run: yarn install:pro develop
|
run: yarn install:pro develop
|
||||||
|
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: yarn build:sdk
|
- run: yarn build:sdk
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
|
|
||||||
- name: Configure AWS Credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v1
|
|
||||||
with:
|
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
aws-region: eu-west-1
|
|
||||||
|
|
||||||
- name: Publish budibase packages to NPM
|
- name: Publish budibase packages to NPM
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
@ -76,22 +69,25 @@ jobs:
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
||||||
|
|
||||||
- name: Get the latest budibase release version
|
deploy-to-release-env:
|
||||||
|
needs: [release-images]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get the current budibase release version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
release_version=$(cat lerna.json | jq -r '.version')
|
release_version=$(cat lerna.json | jq -r '.version')
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Tag and release Proxy service docker image
|
- name: Configure AWS Credentials
|
||||||
run: |
|
uses: aws-actions/configure-aws-credentials@v1
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
with:
|
||||||
yarn build:docker:proxy:release
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
docker tag proxy-service budibase/proxy:$RELEASE_TAG
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
docker push budibase/proxy:$RELEASE_TAG
|
aws-region: eu-west-1
|
||||||
env:
|
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
|
||||||
RELEASE_TAG: k8s-release
|
|
||||||
|
|
||||||
- name: Pull values.yaml from budibase-infra
|
- name: Pull values.yaml from budibase-infra
|
||||||
run: |
|
run: |
|
||||||
|
@ -149,3 +145,54 @@ jobs:
|
||||||
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
|
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
|
||||||
content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env."
|
content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env."
|
||||||
embed-title: ${{ env.RELEASE_VERSION }}
|
embed-title: ${{ env.RELEASE_VERSION }}
|
||||||
|
|
||||||
|
release-helm-chart:
|
||||||
|
needs: [release-images]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup Helm
|
||||||
|
uses: azure/setup-helm@v1
|
||||||
|
id: helm-install
|
||||||
|
|
||||||
|
# due to helm repo index issue: https://github.com/helm/helm/issues/7363
|
||||||
|
# we need to create new package in a different dir, merge the index and move the package back
|
||||||
|
- name: Build and release helm chart
|
||||||
|
run: |
|
||||||
|
git config user.name "Budibase Helm Bot"
|
||||||
|
git config user.email "<>"
|
||||||
|
git reset --hard
|
||||||
|
git pull
|
||||||
|
mkdir sync
|
||||||
|
echo "Packaging chart to sync dir"
|
||||||
|
helm package charts/budibase --version 0.0.0-develop --app-version develop --destination sync
|
||||||
|
echo "Packaging successful"
|
||||||
|
git checkout gh-pages
|
||||||
|
echo "Indexing helm repo"
|
||||||
|
helm repo index --merge docs/index.yaml sync
|
||||||
|
mv -f sync/* docs
|
||||||
|
rm -rf sync
|
||||||
|
echo "Pushing new helm release"
|
||||||
|
git add -A
|
||||||
|
git commit -m "Helm Release: develop"
|
||||||
|
git push
|
||||||
|
|
||||||
|
trigger-deploy-to-qa-env:
|
||||||
|
needs: [release-helm-chart]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Get the current budibase release version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
release_version=$(cat lerna.json | jq -r '.version')
|
||||||
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: passeidireto/trigger-external-workflow-action@main
|
||||||
|
env:
|
||||||
|
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
|
||||||
|
with:
|
||||||
|
repository: budibase/budibase-deploys
|
||||||
|
event: deploy-develop-to-qa
|
||||||
|
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}
|
|
@ -73,7 +73,7 @@ jobs:
|
||||||
git config user.email "<>"
|
git config user.email "<>"
|
||||||
git reset --hard
|
git reset --hard
|
||||||
git pull
|
git pull
|
||||||
helm package charts/budibase
|
helm package charts/budibase --version "$RELEASE_VERSION" --app-version "$RELEASE_VERSION"
|
||||||
git checkout gh-pages
|
git checkout gh-pages
|
||||||
mv *.tgz docs
|
mv *.tgz docs
|
||||||
helm repo index docs
|
helm repo index docs
|
||||||
|
|
|
@ -98,17 +98,6 @@ jobs:
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
aws-region: eu-west-1
|
aws-region: eu-west-1
|
||||||
|
|
||||||
- name: Tag and release Proxy service docker image
|
|
||||||
run: |
|
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
|
||||||
yarn build:docker:proxy:preprod
|
|
||||||
docker tag proxy-service budibase/proxy:$PREPROD_TAG
|
|
||||||
docker push budibase/proxy:$PREPROD_TAG
|
|
||||||
env:
|
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
|
||||||
PREPROD_TAG: k8s-preprod
|
|
||||||
|
|
||||||
- name: Pull values.yaml from budibase-infra
|
- name: Pull values.yaml from budibase-infra
|
||||||
run: |
|
run: |
|
||||||
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
|
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
|
||||||
|
|
|
@ -4,6 +4,7 @@ builder/*
|
||||||
packages/server/runtime_apps/
|
packages/server/runtime_apps/
|
||||||
.idea/
|
.idea/
|
||||||
bb-airgapped.tar.gz
|
bb-airgapped.tar.gz
|
||||||
|
*.iml
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
@ -65,8 +66,6 @@ typings/
|
||||||
.env
|
.env
|
||||||
!qa-core/.env
|
!qa-core/.env
|
||||||
!hosting/.env
|
!hosting/.env
|
||||||
hosting/.generated-nginx.dev.conf
|
|
||||||
hosting/proxy/.generated-nginx.prod.conf
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
.cache
|
.cache
|
||||||
|
@ -104,7 +103,9 @@ stats.html
|
||||||
|
|
||||||
# TypeScript cache
|
# TypeScript cache
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# plugins
|
||||||
budibase-component
|
budibase-component
|
||||||
budibase-datasource
|
budibase-datasource
|
||||||
|
|
||||||
*.iml
|
*.iml
|
||||||
|
|
|
@ -11,8 +11,10 @@ sources:
|
||||||
- https://github.com/Budibase/budibase
|
- https://github.com/Budibase/budibase
|
||||||
- https://budibase.com
|
- https://budibase.com
|
||||||
type: application
|
type: application
|
||||||
version: 0.2.11
|
# populates on packaging
|
||||||
appVersion: 1.0.214
|
version: 0.0.0
|
||||||
|
# populates on packaging
|
||||||
|
appVersion: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: couchdb
|
- name: couchdb
|
||||||
version: 3.6.1
|
version: 3.6.1
|
||||||
|
|
|
@ -28,11 +28,26 @@ spec:
|
||||||
app.kubernetes.io/name: budibase-proxy
|
app.kubernetes.io/name: budibase-proxy
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: budibase/proxy:{{ .Values.services.proxy.tag | default "k8s" }}
|
- image: budibase/proxy:{{ .Values.globals.appVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: proxy-service
|
name: proxy-service
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.services.proxy.port }}
|
- containerPort: {{ .Values.services.proxy.port }}
|
||||||
|
env:
|
||||||
|
- name: APPS_UPSTREAM_URL
|
||||||
|
value: {{ tpl .Values.services.proxy.upstreams.apps . | quote }}
|
||||||
|
- name: WORKER_UPSTREAM_URL
|
||||||
|
value: {{ tpl .Values.services.proxy.upstreams.worker . | quote }}
|
||||||
|
- name: MINIO_UPSTREAM_URL
|
||||||
|
value: {{ tpl .Values.services.proxy.upstreams.minio . | quote }}
|
||||||
|
- name: COUCHDB_UPSTREAM_URL
|
||||||
|
value: {{ .Values.services.couchdb.url | default (tpl .Values.services.proxy.upstreams.couchdb .) | quote }}
|
||||||
|
- name: RESOLVER
|
||||||
|
{{ if .Values.services.proxy.resolver }}
|
||||||
|
value: {{ .Values.services.proxy.resolver }}
|
||||||
|
{{ else }}
|
||||||
|
value: kube-dns.kube-system.svc.{{ .Values.services.dns }}
|
||||||
|
{{ end }}
|
||||||
{{ with .Values.services.proxy.resources }}
|
{{ with .Values.services.proxy.resources }}
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml . | nindent 10 }}
|
{{- toYaml . | nindent 10 }}
|
||||||
|
|
|
@ -124,6 +124,11 @@ services:
|
||||||
proxy:
|
proxy:
|
||||||
port: 10000
|
port: 10000
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
upstreams:
|
||||||
|
apps: 'http://app-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.apps.port }}'
|
||||||
|
worker: 'http://worker-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.worker.port }}'
|
||||||
|
minio: 'http://minio-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.objectStore.port }}'
|
||||||
|
couchdb: 'http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}'
|
||||||
resources: {}
|
resources: {}
|
||||||
|
|
||||||
apps:
|
apps:
|
||||||
|
|
|
@ -24,9 +24,9 @@ services:
|
||||||
proxy-service:
|
proxy-service:
|
||||||
container_name: budi-nginx-dev
|
container_name: budi-nginx-dev
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
image: nginx:latest
|
image: budibase/proxy:latest
|
||||||
volumes:
|
volumes:
|
||||||
- ./.generated-nginx.dev.conf:/etc/nginx/nginx.conf
|
- ./nginx.dev.conf:/etc/nginx/templates/nginx.conf.template
|
||||||
- ./proxy/error.html:/usr/share/nginx/html/error.html
|
- ./proxy/error.html:/usr/share/nginx/html/error.html
|
||||||
ports:
|
ports:
|
||||||
- "${MAIN_PORT}:10000"
|
- "${MAIN_PORT}:10000"
|
||||||
|
@ -34,6 +34,8 @@ services:
|
||||||
- dev-service
|
- dev-service
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
|
environment:
|
||||||
|
- PROXY_ADDRESS=host.docker.internal
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
dev_data:
|
dev_data:
|
||||||
|
|
|
@ -82,6 +82,12 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10
|
- PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10
|
||||||
- PROXY_RATE_LIMIT_API_PER_SECOND=20
|
- PROXY_RATE_LIMIT_API_PER_SECOND=20
|
||||||
|
- APPS_UPSTREAM_URL=http://app-service:4002
|
||||||
|
- WORKER_UPSTREAM_URL=http://worker-service:4003
|
||||||
|
- MINIO_UPSTREAM_URL=http://minio-service:9000
|
||||||
|
- COUCHDB_UPSTREAM_URL=http://couchdb-service:5984
|
||||||
|
- WATCHTOWER_UPSTREAM_URL=http://watchtower-service:8080
|
||||||
|
- RESOLVER=127.0.0.11
|
||||||
depends_on:
|
depends_on:
|
||||||
- minio-service
|
- minio-service
|
||||||
- worker-service
|
- worker-service
|
||||||
|
|
|
@ -25,17 +25,17 @@ http {
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream app-service {
|
upstream app-service {
|
||||||
server {{address}}:4001;
|
server ${PROXY_ADDRESS}:4001;
|
||||||
keepalive 32;
|
keepalive 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream worker-service {
|
upstream worker-service {
|
||||||
server {{address}}:4002;
|
server ${PROXY_ADDRESS}:4002;
|
||||||
keepalive 32;
|
keepalive 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream builder {
|
upstream builder {
|
||||||
server {{address}}:3000;
|
server ${PROXY_ADDRESS}:3000;
|
||||||
keepalive 32;
|
keepalive 32;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ FROM nginx:latest
|
||||||
# use the default nginx behaviour for *.template files which are processed with envsubst
|
# use the default nginx behaviour for *.template files which are processed with envsubst
|
||||||
# override the output dir to output directly to /etc/nginx instead of /etc/nginx/conf.d
|
# override the output dir to output directly to /etc/nginx instead of /etc/nginx/conf.d
|
||||||
ENV NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx
|
ENV NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx
|
||||||
COPY .generated-nginx.prod.conf /etc/nginx/templates/nginx.conf.template
|
COPY nginx.prod.conf /etc/nginx/templates/nginx.conf.template
|
||||||
|
|
||||||
# IPv6 removal needs to happen after envsubst
|
# IPv6 removal needs to happen after envsubst
|
||||||
RUN rm -rf /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
|
RUN rm -rf /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
|
||||||
|
@ -16,4 +16,11 @@ COPY error.html /usr/share/nginx/html/error.html
|
||||||
|
|
||||||
# Default environment
|
# Default environment
|
||||||
ENV PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10
|
ENV PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10
|
||||||
ENV PROXY_RATE_LIMIT_API_PER_SECOND=20
|
ENV PROXY_RATE_LIMIT_API_PER_SECOND=20
|
||||||
|
# Use docker-compose values as defaults for backwards compatibility
|
||||||
|
ENV APPS_UPSTREAM_URL=http://app-service:4002
|
||||||
|
ENV WORKER_UPSTREAM_URL=http://worker-service:4003
|
||||||
|
ENV MINIO_UPSTREAM_URL=http://minio-service:9000
|
||||||
|
ENV COUCHDB_UPSTREAM_URL=http://couchdb-service:5984
|
||||||
|
ENV WATCHTOWER_UPSTREAM_URL=http://watchtower-service:8080
|
||||||
|
ENV RESOLVER=127.0.0.11
|
||||||
|
|
|
@ -23,7 +23,7 @@ http {
|
||||||
tcp_nodelay on;
|
tcp_nodelay on;
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
types_hash_max_size 2048;
|
types_hash_max_size 2048;
|
||||||
resolver {{ resolver }} valid=10s ipv6=off;
|
resolver ${RESOLVER} valid=10s ipv6=off;
|
||||||
|
|
||||||
# buffering
|
# buffering
|
||||||
client_header_buffer_size 1k;
|
client_header_buffer_size 1k;
|
||||||
|
@ -76,27 +76,23 @@ http {
|
||||||
add_header Content-Security-Policy "${csp_default}; ${csp_script}; ${csp_style}; ${csp_object}; ${csp_base_uri}; ${csp_connect}; ${csp_font}; ${csp_frame}; ${csp_img}; ${csp_manifest}; ${csp_media}; ${csp_worker};" always;
|
add_header Content-Security-Policy "${csp_default}; ${csp_script}; ${csp_style}; ${csp_object}; ${csp_base_uri}; ${csp_connect}; ${csp_font}; ${csp_frame}; ${csp_img}; ${csp_manifest}; ${csp_media}; ${csp_worker};" always;
|
||||||
|
|
||||||
# upstreams
|
# upstreams
|
||||||
set $apps {{ apps }};
|
set $apps ${APPS_UPSTREAM_URL};
|
||||||
set $worker {{ worker }};
|
set $worker ${WORKER_UPSTREAM_URL};
|
||||||
set $minio {{ minio }};
|
set $minio ${MINIO_UPSTREAM_URL};
|
||||||
set $couchdb {{ couchdb }};
|
set $couchdb ${COUCHDB_UPSTREAM_URL};
|
||||||
{{#if watchtower}}
|
set $watchtower ${WATCHTOWER_UPSTREAM_URL};
|
||||||
set $watchtower {{ watchtower }};
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
location /app {
|
location /app {
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass $apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = / {
|
location = / {
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass $apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
{{#if watchtower}}
|
|
||||||
location = /v1/update {
|
location = /v1/update {
|
||||||
proxy_pass http://$watchtower:8080;
|
proxy_pass $watchtower;
|
||||||
}
|
}
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
location ~ ^/(builder|app_) {
|
location ~ ^/(builder|app_) {
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
@ -107,19 +103,17 @@ http {
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass $apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/api/(system|admin|global)/ {
|
location ~ ^/api/(system|admin|global)/ {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
proxy_pass $worker;
|
||||||
proxy_pass http://$worker:4003;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location /worker/ {
|
location /worker/ {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
proxy_pass $worker;
|
||||||
proxy_pass http://$worker:4003;
|
|
||||||
rewrite ^/worker/(.*)$ /$1 break;
|
rewrite ^/worker/(.*)$ /$1 break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +132,7 @@ http {
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass $apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
|
@ -157,7 +151,7 @@ http {
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass $apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/webhooks/ {
|
location /api/webhooks/ {
|
||||||
|
@ -177,11 +171,11 @@ http {
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass $apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /db/ {
|
location /db/ {
|
||||||
proxy_pass http://$couchdb:5984;
|
proxy_pass $couchdb;
|
||||||
rewrite ^/db/(.*)$ /$1 break;
|
rewrite ^/db/(.*)$ /$1 break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +185,7 @@ http {
|
||||||
proxy_set_header Connection 'upgrade';
|
proxy_set_header Connection 'upgrade';
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache_bypass $http_upgrade;
|
proxy_cache_bypass $http_upgrade;
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass $apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
@ -205,7 +199,7 @@ http {
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
chunked_transfer_encoding off;
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
proxy_pass http://$minio:9000;
|
proxy_pass $minio;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /files/signed/ {
|
location /files/signed/ {
|
||||||
|
@ -224,7 +218,7 @@ http {
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
chunked_transfer_encoding off;
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
proxy_pass http://$minio:9000;
|
proxy_pass $minio;
|
||||||
rewrite ^/files/signed/(.*)$ /$1 break;
|
rewrite ^/files/signed/(.*)$ /$1 break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.2.4-alpha.4",
|
"version": "2.2.10-alpha.11",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
10
package.json
10
package.json
|
@ -56,15 +56,11 @@
|
||||||
"test:e2e:ci:record": "lerna run cy:ci:record --stream",
|
"test:e2e:ci:record": "lerna run cy:ci:record --stream",
|
||||||
"test:e2e:ci:notify": "lerna run cy:ci:notify",
|
"test:e2e:ci:notify": "lerna run cy:ci:notify",
|
||||||
"build:specs": "lerna run specs",
|
"build:specs": "lerna run specs",
|
||||||
"build:docker": "lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
|
"build:docker": "lerna run build:docker && npm run build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
|
||||||
"build:docker:pre": "lerna run build && lerna run predocker",
|
"build:docker:pre": "lerna run build && lerna run predocker",
|
||||||
"build:docker:proxy": "docker build hosting/proxy -t proxy-service",
|
"build:docker:proxy": "docker build hosting/proxy -t proxy-service",
|
||||||
"build:docker:proxy:compose": "node scripts/proxy/generateProxyConfig compose && npm run build:docker:proxy",
|
|
||||||
"build:docker:proxy:preprod": "node scripts/proxy/generateProxyConfig preprod && npm run build:docker:proxy",
|
|
||||||
"build:docker:proxy:release": "node scripts/proxy/generateProxyConfig release && npm run build:docker:proxy",
|
|
||||||
"build:docker:proxy:prod": "node scripts/proxy/generateProxyConfig prod && npm run build:docker:proxy",
|
|
||||||
"build:docker:selfhost": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -",
|
"build:docker:selfhost": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -",
|
||||||
"build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy:compose && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -",
|
"build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -",
|
||||||
"build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild",
|
"build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild",
|
||||||
"build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -",
|
"build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -",
|
||||||
"build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .",
|
"build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .",
|
||||||
|
@ -90,4 +86,4 @@
|
||||||
"install:pro": "bash scripts/pro/install.sh",
|
"install:pro": "bash scripts/pro/install.sh",
|
||||||
"dep:clean": "yarn clean && yarn bootstrap"
|
"dep:clean": "yarn clean && yarn bootstrap"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.2.4-alpha.4",
|
"version": "2.2.10-alpha.11",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -15,13 +15,15 @@
|
||||||
"prebuild": "rimraf dist/",
|
"prebuild": "rimraf dist/",
|
||||||
"prepack": "cp package.json dist",
|
"prepack": "cp package.json dist",
|
||||||
"build": "tsc -p tsconfig.build.json",
|
"build": "tsc -p tsconfig.build.json",
|
||||||
|
"build:pro": "../../scripts/pro/build.sh",
|
||||||
|
"postbuild": "yarn run build:pro",
|
||||||
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
||||||
"test": "jest --coverage --maxWorkers=2",
|
"test": "jest --coverage --maxWorkers=2",
|
||||||
"test:watch": "jest --watchAll"
|
"test:watch": "jest --watchAll"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/nano": "10.1.1",
|
"@budibase/nano": "10.1.1",
|
||||||
"@budibase/types": "2.2.4-alpha.4",
|
"@budibase/types": "2.2.10-alpha.11",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
"@shopify/jest-koa-mocks": "5.0.1",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-cloudfront-sign": "2.2.0",
|
"aws-cloudfront-sign": "2.2.0",
|
||||||
|
|
|
@ -13,6 +13,18 @@ const getClient = async (type: LockType): Promise<Redlock> => {
|
||||||
}
|
}
|
||||||
return noRetryRedlock
|
return noRetryRedlock
|
||||||
}
|
}
|
||||||
|
case LockType.DEFAULT: {
|
||||||
|
if (!noRetryRedlock) {
|
||||||
|
noRetryRedlock = await newRedlock(OPTIONS.DEFAULT)
|
||||||
|
}
|
||||||
|
return noRetryRedlock
|
||||||
|
}
|
||||||
|
case LockType.DELAY_500: {
|
||||||
|
if (!noRetryRedlock) {
|
||||||
|
noRetryRedlock = await newRedlock(OPTIONS.DELAY_500)
|
||||||
|
}
|
||||||
|
return noRetryRedlock
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error(`Could not get redlock client: ${type}`)
|
throw new Error(`Could not get redlock client: ${type}`)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +53,9 @@ export const OPTIONS = {
|
||||||
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
|
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||||
retryJitter: 100, // time in ms
|
retryJitter: 100, // time in ms
|
||||||
},
|
},
|
||||||
|
DELAY_500: {
|
||||||
|
retryDelay: 500,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const newRedlock = async (opts: Options = {}) => {
|
export const newRedlock = async (opts: Options = {}) => {
|
||||||
|
@ -55,19 +70,17 @@ export const doWithLock = async (opts: LockOptions, task: any) => {
|
||||||
let lock
|
let lock
|
||||||
try {
|
try {
|
||||||
// aquire lock
|
// aquire lock
|
||||||
let name: string
|
let name: string = `lock:${tenancy.getTenantId()}_${opts.name}`
|
||||||
if (opts.systemLock) {
|
|
||||||
name = opts.name
|
|
||||||
} else {
|
|
||||||
name = `${tenancy.getTenantId()}_${opts.name}`
|
|
||||||
}
|
|
||||||
if (opts.nameSuffix) {
|
if (opts.nameSuffix) {
|
||||||
name = name + `_${opts.nameSuffix}`
|
name = name + `_${opts.nameSuffix}`
|
||||||
}
|
}
|
||||||
lock = await redlock.lock(name, opts.ttl)
|
lock = await redlock.lock(name, opts.ttl)
|
||||||
// perform locked task
|
// perform locked task
|
||||||
return task()
|
// need to await to ensure completion before unlocking
|
||||||
|
const result = await task()
|
||||||
|
return result
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
console.log("lock error")
|
||||||
// lock limit exceeded
|
// lock limit exceeded
|
||||||
if (e.name === "LockError") {
|
if (e.name === "LockError") {
|
||||||
if (opts.type === LockType.TRY_ONCE) {
|
if (opts.type === LockType.TRY_ONCE) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "2.2.4-alpha.4",
|
"version": "2.2.10-alpha.11",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
||||||
"@budibase/string-templates": "2.2.4-alpha.4",
|
"@budibase/string-templates": "2.2.10-alpha.11",
|
||||||
"@spectrum-css/actionbutton": "1.0.1",
|
"@spectrum-css/actionbutton": "1.0.1",
|
||||||
"@spectrum-css/actiongroup": "1.0.1",
|
"@spectrum-css/actiongroup": "1.0.1",
|
||||||
"@spectrum-css/avatar": "3.0.2",
|
"@spectrum-css/avatar": "3.0.2",
|
||||||
|
|
|
@ -10,10 +10,13 @@
|
||||||
export let green = false
|
export let green = false
|
||||||
export let active = false
|
export let active = false
|
||||||
export let inactive = false
|
export let inactive = false
|
||||||
|
export let hoverable = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
|
on:click
|
||||||
class="spectrum-Label"
|
class="spectrum-Label"
|
||||||
|
class:hoverable
|
||||||
class:spectrum-Label--small={size === "S"}
|
class:spectrum-Label--small={size === "S"}
|
||||||
class:spectrum-Label--large={size === "L"}
|
class:spectrum-Label--large={size === "L"}
|
||||||
class:spectrum-Label--grey={grey}
|
class:spectrum-Label--grey={grey}
|
||||||
|
@ -27,3 +30,13 @@
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.spectrum-Label--grey {
|
||||||
|
background-color: var(--spectrum-global-color-gray-500);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.hoverable:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/label/dist/index-vars.css"
|
import "@spectrum-css/label/dist/index-vars.css"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import Badge from "../Badge/Badge.svelte"
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
export let value
|
export let value
|
||||||
|
@ -24,17 +25,11 @@
|
||||||
|
|
||||||
{#each relationships as relationship}
|
{#each relationships as relationship}
|
||||||
{#if relationship?.primaryDisplay}
|
{#if relationship?.primaryDisplay}
|
||||||
<span class="spectrum-Label spectrum-Label--grey" on:click={onClick}>
|
<Badge hoverable grey on:click={onClick}>
|
||||||
{relationship.primaryDisplay}
|
{relationship.primaryDisplay}
|
||||||
</span>
|
</Badge>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{#if leftover}
|
{#if leftover}
|
||||||
<div>+{leftover} more</div>
|
<div>+{leftover} more</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
|
||||||
span:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -3,17 +3,17 @@ const interact = require('../../support/interact')
|
||||||
|
|
||||||
filterTests(["smoke", "all"], () => {
|
filterTests(["smoke", "all"], () => {
|
||||||
context("Account Portals", () => {
|
context("Account Portals", () => {
|
||||||
|
|
||||||
const bbUserEmail = "bbuser@test.com"
|
const bbUserEmail = "bbuser@test.com"
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
cy.deleteApp("Cypress Tests")
|
cy.deleteApp("Cypress Tests")
|
||||||
cy.createApp("Cypress Tests", false)
|
cy.createApp("Cypress Tests", false)
|
||||||
|
|
||||||
// Create new user
|
// Create new user
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
||||||
cy.createUser(bbUserEmail)
|
cy.createUser(bbUserEmail)
|
||||||
cy.contains("bbuser").click()
|
cy.contains("bbuser").click()
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
|
@ -25,18 +25,18 @@ filterTests(["smoke", "all"], () => {
|
||||||
cy.get(interact.SPECTRUM_MENU).within(() => {
|
cy.get(interact.SPECTRUM_MENU).within(() => {
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
|
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
||||||
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
|
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
|
||||||
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
|
||||||
|
|
||||||
// Login as new user and set password
|
// Login as new user and set password
|
||||||
cy.logOut()
|
cy.logOut()
|
||||||
cy.get('@pwd').then((pwd) => {
|
cy.get('@pwd').then((pwd) => {
|
||||||
cy.login(bbUserEmail, pwd)
|
cy.login(bbUserEmail, pwd)
|
||||||
})
|
})
|
||||||
|
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
||||||
}
|
}
|
||||||
|
@ -58,15 +58,15 @@ filterTests(["smoke", "all"], () => {
|
||||||
|
|
||||||
cy.logoutNoAppGrid()
|
cy.logoutNoAppGrid()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should verify Admin Portal", () => {
|
|
||||||
cy.login()
|
|
||||||
// Configure user role
|
|
||||||
cy.setUserRole("bbuser", "Admin")
|
|
||||||
bbUserLogin()
|
|
||||||
|
|
||||||
// Verify available options for Admin portal
|
xit("should verify Admin Portal", () => {
|
||||||
cy.get(interact.SPECTRUM_SIDENAV)
|
cy.login()
|
||||||
|
// Configure user role
|
||||||
|
cy.setUserRole("bbuser", "Admin")
|
||||||
|
bbUserLogin()
|
||||||
|
|
||||||
|
// Verify available options for Admin portal
|
||||||
|
cy.get(interact.SPECTRUM_SIDENAV)
|
||||||
.should('contain', 'Apps')
|
.should('contain', 'Apps')
|
||||||
//.and('contain', 'Usage')
|
//.and('contain', 'Usage')
|
||||||
.and('contain', 'Users')
|
.and('contain', 'Users')
|
||||||
|
@ -75,12 +75,12 @@ filterTests(["smoke", "all"], () => {
|
||||||
.and('contain', 'Organisation')
|
.and('contain', 'Organisation')
|
||||||
.and('contain', 'Theming')
|
.and('contain', 'Theming')
|
||||||
.and('contain', 'Update')
|
.and('contain', 'Update')
|
||||||
//.and('contain', 'Upgrade')
|
//.and('contain', 'Upgrade')
|
||||||
|
|
||||||
cy.logOut()
|
cy.logOut()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should verify Development Portal", () => {
|
xit("should verify Development Portal", () => {
|
||||||
// Only Development access should be enabled
|
// Only Development access should be enabled
|
||||||
cy.login()
|
cy.login()
|
||||||
cy.setUserRole("bbuser", "Developer")
|
cy.setUserRole("bbuser", "Developer")
|
||||||
|
@ -98,7 +98,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
.and('not.contain', 'Update')
|
.and('not.contain', 'Update')
|
||||||
.and('not.contain', 'Upgrade')
|
.and('not.contain', 'Upgrade')
|
||||||
|
|
||||||
cy.logOut()
|
cy.logOut()
|
||||||
})
|
})
|
||||||
|
|
||||||
const bbUserLogin = () => {
|
const bbUserLogin = () => {
|
||||||
|
|
|
@ -9,7 +9,7 @@ filterTests(["all"], () => {
|
||||||
cy.createApp("Cypress Tests")
|
cy.createApp("Cypress Tests")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should be accessible from the applications list", () => {
|
xit("Should be accessible from the applications list", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.get(".appTable .title")
|
cy.get(".appTable .title")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
|
@ -27,7 +27,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Find a more suitable place for this.
|
// Find a more suitable place for this.
|
||||||
it("Should allow unlocking in the app list", () => {
|
xit("Should allow unlocking in the app list", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
cy.get(".appTable .lock-status").eq(0).contains("Locked by you").click()
|
cy.get(".appTable .lock-status").eq(0).contains("Locked by you").click()
|
||||||
|
@ -38,7 +38,7 @@ filterTests(["all"], () => {
|
||||||
cy.get(".lock-status").should("not.be.visible")
|
cy.get(".lock-status").should("not.be.visible")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should allow unlocking in the app overview screen", () => {
|
xit("Should allow unlocking in the app overview screen", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
|
@ -58,7 +58,7 @@ filterTests(["all"], () => {
|
||||||
cy.get(".lock-status").should("not.be.visible")
|
cy.get(".lock-status").should("not.be.visible")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should reflect the deploy state of an app that hasn't been published.", () => {
|
xit("Should reflect the deploy state of an app that hasn't been published.", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
|
@ -81,7 +81,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should reflect the app deployment state", () => {
|
xit("Should reflect the app deployment state", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
.contains("Edit")
|
.contains("Edit")
|
||||||
|
@ -117,7 +117,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should reflect an application that has been unpublished", () => {
|
xit("Should reflect an application that has been unpublished", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
.contains("Edit")
|
.contains("Edit")
|
||||||
|
@ -154,7 +154,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should allow the editing of the application icon and colour", () => {
|
xit("Should allow the editing of the application icon and colour", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
|
@ -196,7 +196,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should reflect the last time the application was edited", () => {
|
xit("Should reflect the last time the application was edited", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
|
@ -221,7 +221,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should reflect application version is up-to-date", () => {
|
xit("Should reflect application version is up-to-date", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
|
@ -302,7 +302,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should allow editing of the app details.", () => {
|
xit("Should allow editing of the app details.", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
|
@ -373,13 +373,13 @@ filterTests(["all"], () => {
|
||||||
.contains("Copy App ID")
|
.contains("Copy App ID")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get(".spectrum-Toast-content")
|
cy.get(".spectrum-Toast-content")
|
||||||
.contains("App ID copied to clipboard.")
|
.contains("App ID copied to clipboard.")
|
||||||
.should("be.visible")
|
.should("be.visible")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should allow unpublishing of the application via the Unpublish link", () => {
|
xit("Should allow unpublishing of the application via the Unpublish link", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
|
@ -388,7 +388,7 @@ filterTests(["all"], () => {
|
||||||
|
|
||||||
cy.get(`[data-cy="app-status"]`).within(() => {
|
cy.get(`[data-cy="app-status"]`).within(() => {
|
||||||
cy.contains("Unpublish").click({ force: true })
|
cy.contains("Unpublish").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get("[data-cy='unpublish-modal']")
|
cy.get("[data-cy='unpublish-modal']")
|
||||||
.should("be.visible")
|
.should("be.visible")
|
||||||
|
@ -399,11 +399,11 @@ filterTests(["all"], () => {
|
||||||
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
||||||
cy.get(".status-display").contains("Unpublished")
|
cy.get(".status-display").contains("Unpublished")
|
||||||
cy.get(".status-display .icon svg[aria-label='GlobeStrike']")
|
cy.get(".status-display .icon svg[aria-label='GlobeStrike']")
|
||||||
.should("exist")
|
.should("exist")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should allow deleting of the application", () => {
|
xit("Should allow deleting of the application", () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.get(".appTable .app-row-actions button")
|
cy.get(".appTable .app-row-actions button")
|
||||||
.contains("Manage")
|
.contains("Manage")
|
||||||
|
|
|
@ -2,13 +2,13 @@ import filterTests from "../support/filterTests"
|
||||||
const interact = require('../support/interact')
|
const interact = require('../support/interact')
|
||||||
|
|
||||||
filterTests(['smoke', 'all'], () => {
|
filterTests(['smoke', 'all'], () => {
|
||||||
context("Create a automation", () => {
|
xcontext("Create a automation", () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
cy.createTestApp()
|
cy.createTestApp()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create a automation", () => {
|
xit("should create a automation", () => {
|
||||||
cy.createTestTableWithData()
|
cy.createTestTableWithData()
|
||||||
cy.wait(2000)
|
cy.wait(2000)
|
||||||
cy.contains("Automate").click()
|
cy.contains("Automate").click()
|
||||||
|
|
|
@ -26,13 +26,15 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.get("input").type("Test View")
|
cy.get("input").type("Test View")
|
||||||
cy.get("button").contains("Create View").click({ force: true })
|
cy.get("button").contains("Create View").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.get(interact.TABLE_TITLE_H1).contains("Test View")
|
cy.contains(interact.TABLE_TITLE_H1, "Test View", { timeout: 10000 })
|
||||||
cy.get(interact.TITLE).then($headers => {
|
cy.get(".table-wrapper").within(() => {
|
||||||
expect($headers).to.have.length(3)
|
cy.get(interact.TITLE).then($headers => {
|
||||||
const headers = Array.from($headers).map(header =>
|
expect($headers).to.have.length(3)
|
||||||
header.textContent.trim()
|
const headers = Array.from($headers).map(header =>
|
||||||
)
|
header.textContent.trim()
|
||||||
expect(removeSpacing(headers)).to.deep.eq(["group", "age", "rating"])
|
)
|
||||||
|
expect(removeSpacing(headers)).to.deep.eq(["group", "age", "rating"])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -70,20 +72,22 @@ filterTests(['smoke', 'all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.get(interact.TITLE).then($headers => {
|
cy.get(".table-wrapper").within(() => {
|
||||||
expect($headers).to.have.length(7)
|
cy.get(interact.TITLE).then($headers => {
|
||||||
const headers = Array.from($headers).map(header =>
|
expect($headers).to.have.length(7)
|
||||||
header.textContent.trim()
|
const headers = Array.from($headers).map(header =>
|
||||||
)
|
header.textContent.trim()
|
||||||
expect(removeSpacing(headers)).to.deep.eq([
|
)
|
||||||
"field",
|
expect(removeSpacing(headers)).to.deep.eq([
|
||||||
"sum",
|
"field",
|
||||||
"min",
|
"sum",
|
||||||
"max",
|
"min",
|
||||||
"count",
|
"max",
|
||||||
"sumsqr",
|
"count",
|
||||||
"avg",
|
"sumsqr",
|
||||||
])
|
"avg",
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
cy.get(interact.SPECTRUM_TABLE_CELL).then($values => {
|
cy.get(interact.SPECTRUM_TABLE_CELL).then($values => {
|
||||||
let values = Array.from($values).map(header => header.textContent.trim())
|
let values = Array.from($values).map(header => header.textContent.trim())
|
||||||
|
|
|
@ -14,11 +14,13 @@ filterTests(["smoke", "all"], () => {
|
||||||
const restUrl = "https://api.openbrewerydb.org/breweries"
|
const restUrl = "https://api.openbrewerydb.org/breweries"
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
cy.createRestQuery("GET", restUrl, "/breweries")
|
cy.createRestQuery("GET", restUrl, "/breweries")
|
||||||
cy.get(interact.SPECTRUM_TABS_ITEM).contains("Transformer").click()
|
cy.reload()
|
||||||
|
cy.contains(".nav-item-content", "/breweries", { timeout: 20000 }).click()
|
||||||
|
cy.contains(interact.SPECTRUM_TABS_ITEM, "Transformer", { timeout: 5000 }).click({ force: true })
|
||||||
// Get Transformer Function from file
|
// Get Transformer Function from file
|
||||||
cy.readFile("cypress/support/queryLevelTransformerFunction.js").then(
|
cy.readFile("cypress/support/queryLevelTransformerFunction.js").then(
|
||||||
transformerFunction => {
|
transformerFunction => {
|
||||||
cy.get(interact.CODEMIRROR_TEXTAREA)
|
cy.get(interact.CODEMIRROR_TEXTAREA, { timeout: 5000 })
|
||||||
// Highlight current text and overwrite with file contents
|
// Highlight current text and overwrite with file contents
|
||||||
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
|
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
|
||||||
force: true,
|
force: true,
|
||||||
|
@ -28,6 +30,7 @@ filterTests(["smoke", "all"], () => {
|
||||||
)
|
)
|
||||||
// Send Query
|
// Send Query
|
||||||
cy.intercept("**/queries/preview").as("query")
|
cy.intercept("**/queries/preview").as("query")
|
||||||
|
cy.get(interact.SPECTRUM_BUTTON).contains("Save").click({ force: true })
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Send").click({ force: true })
|
cy.get(interact.SPECTRUM_BUTTON).contains("Send").click({ force: true })
|
||||||
cy.wait("@query")
|
cy.wait("@query")
|
||||||
// Assert against Status Code, body, & body rows
|
// Assert against Status Code, body, & body rows
|
||||||
|
@ -42,7 +45,9 @@ filterTests(["smoke", "all"], () => {
|
||||||
const restUrl = "https://api.openbrewerydb.org/breweries"
|
const restUrl = "https://api.openbrewerydb.org/breweries"
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
cy.createRestQuery("GET", restUrl, "/breweries")
|
cy.createRestQuery("GET", restUrl, "/breweries")
|
||||||
cy.get(interact.SPECTRUM_TABS_ITEM).contains("Transformer").click()
|
cy.reload()
|
||||||
|
cy.contains(".nav-item-content", "/breweries", { timeout: 2000 }).click()
|
||||||
|
cy.contains(interact.SPECTRUM_TABS_ITEM, "Transformer", { timeout: 5000 }).click({ force: true })
|
||||||
// Get Transformer Function with Data from file
|
// Get Transformer Function with Data from file
|
||||||
cy.readFile(
|
cy.readFile(
|
||||||
"cypress/support/queryLevelTransformerFunctionWithData.js"
|
"cypress/support/queryLevelTransformerFunctionWithData.js"
|
||||||
|
@ -71,7 +76,9 @@ filterTests(["smoke", "all"], () => {
|
||||||
const restUrl = "https://api.openbrewerydb.org/breweries"
|
const restUrl = "https://api.openbrewerydb.org/breweries"
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
cy.createRestQuery("GET", restUrl, "/breweries")
|
cy.createRestQuery("GET", restUrl, "/breweries")
|
||||||
cy.get(interact.SPECTRUM_TABS_ITEM).contains("Transformer").click()
|
cy.reload()
|
||||||
|
cy.contains(".nav-item-content", "/breweries", { timeout: 2000 }).click()
|
||||||
|
cy.contains(interact.SPECTRUM_TABS_ITEM, "Transformer", { timeout: 5000 }).click({ force: true })
|
||||||
// Clear the code box and add "test"
|
// Clear the code box and add "test"
|
||||||
cy.get(interact.CODEMIRROR_TEXTAREA)
|
cy.get(interact.CODEMIRROR_TEXTAREA)
|
||||||
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
|
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
|
||||||
|
|
|
@ -413,7 +413,7 @@ Cypress.Commands.add("searchForApplication", appName => {
|
||||||
// Assumes there are no others
|
// Assumes there are no others
|
||||||
Cypress.Commands.add("applicationInAppTable", appName => {
|
Cypress.Commands.add("applicationInAppTable", appName => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 30000 })
|
||||||
cy.get(".appTable", { timeout: 5000 }).within(() => {
|
cy.get(".appTable", { timeout: 30000 }).within(() => {
|
||||||
cy.get(".title").contains(appName).should("exist")
|
cy.get(".title").contains(appName).should("exist")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -441,7 +441,7 @@ Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
||||||
if (!initialTable) {
|
if (!initialTable) {
|
||||||
cy.navigateToDataSection()
|
cy.navigateToDataSection()
|
||||||
}
|
}
|
||||||
cy.get(`[data-cy="new-datasource"]`, { timeout: 2000 }).click()
|
cy.get(`[data-cy="new-datasource"]`, { timeout: 20000 }).click()
|
||||||
cy.wait(2000)
|
cy.wait(2000)
|
||||||
cy.get(".item", { timeout: 2000 })
|
cy.get(".item", { timeout: 2000 })
|
||||||
.contains("Budibase DB")
|
.contains("Budibase DB")
|
||||||
|
@ -461,10 +461,7 @@ Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
||||||
cy.get(".nav-item", { timeout: 2000 })
|
cy.get(".nav-item", { timeout: 2000 })
|
||||||
.contains("Budibase DB")
|
.contains("Budibase DB")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.get(".spectrum-Tabs-content", { timeout: 2000 }).should(
|
cy.get(".nav-item-content", { timeout: 2000 }).should("contain", tableName)
|
||||||
"contain",
|
|
||||||
tableName
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createTestTableWithData", () => {
|
Cypress.Commands.add("createTestTableWithData", () => {
|
||||||
|
@ -483,7 +480,7 @@ Cypress.Commands.add(
|
||||||
|
|
||||||
// Configure column
|
// Configure column
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get("input").first().type(columnName).blur()
|
cy.get("input").first().type(columnName)
|
||||||
|
|
||||||
// Unset table display column
|
// Unset table display column
|
||||||
cy.contains("display column").click({ force: true })
|
cy.contains("display column").click({ force: true })
|
||||||
|
@ -795,7 +792,7 @@ Cypress.Commands.add("selectExternalDatasource", datasourceName => {
|
||||||
// Navigates to Data Section
|
// Navigates to Data Section
|
||||||
cy.navigateToDataSection()
|
cy.navigateToDataSection()
|
||||||
// Open Datasource modal
|
// Open Datasource modal
|
||||||
cy.get(".nav").within(() => {
|
cy.get(".container").within(() => {
|
||||||
cy.get("[data-cy='new-datasource']").click()
|
cy.get("[data-cy='new-datasource']").click()
|
||||||
})
|
})
|
||||||
// Clicks specified datasource & continue
|
// Clicks specified datasource & continue
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "2.2.4-alpha.4",
|
"version": "2.2.10-alpha.11",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -71,10 +71,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.2.4-alpha.4",
|
"@budibase/bbui": "2.2.10-alpha.11",
|
||||||
"@budibase/client": "2.2.4-alpha.4",
|
"@budibase/client": "2.2.10-alpha.11",
|
||||||
"@budibase/frontend-core": "2.2.4-alpha.4",
|
"@budibase/frontend-core": "2.2.10-alpha.11",
|
||||||
"@budibase/string-templates": "2.2.4-alpha.4",
|
"@budibase/string-templates": "2.2.10-alpha.11",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -180,7 +180,7 @@
|
||||||
onSelect(block)
|
onSelect(block)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name={showLooping ? "ChevronDown" : "ChevronUp"} />
|
<Icon name={showLooping ? "ChevronUp" : "ChevronDown"} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -216,7 +216,6 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: var(--spacing-m);
|
|
||||||
}
|
}
|
||||||
.table-title > div {
|
.table-title > div {
|
||||||
margin-left: var(--spacing-xs);
|
margin-left: var(--spacing-xs);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { ActionButton, Modal, notifications } from "@budibase/bbui"
|
import { ActionButton, Modal, notifications } from "@budibase/bbui"
|
||||||
import CreateEditRelationship from "../../Datasources/CreateEditRelationship.svelte"
|
import CreateEditRelationship from "../../Datasources/CreateEditRelationship.svelte"
|
||||||
import { datasources, tables } from "../../../../stores/backend"
|
import { datasources } from "../../../../stores/backend"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let table
|
export let table
|
||||||
|
@ -21,8 +21,6 @@
|
||||||
// Create datasource
|
// Create datasource
|
||||||
await datasources.save(datasource)
|
await datasources.save(datasource)
|
||||||
notifications.success(`Relationship information saved.`)
|
notifications.success(`Relationship information saved.`)
|
||||||
const tableList = await tables.fetch()
|
|
||||||
await tables.select(tableList.find(tbl => tbl._id === table._id))
|
|
||||||
dispatch("updatecolumns")
|
dispatch("updatecolumns")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error saving relationship info: ${err}`)
|
notifications.error(`Error saving relationship info: ${err}`)
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
// in the case of internal tables the sourceId will just be undefined
|
// in the case of internal tables the sourceId will just be undefined
|
||||||
$: tableOptions = $tables.list.filter(
|
$: tableOptions = $tables.list.filter(
|
||||||
opt =>
|
opt =>
|
||||||
opt._id !== $tables.draft._id &&
|
opt._id !== $tables.selected._id &&
|
||||||
opt.type === table.type &&
|
opt.type === table.type &&
|
||||||
table.sourceId === opt.sourceId
|
table.sourceId === opt.sourceId
|
||||||
)
|
)
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
|
|
||||||
async function saveColumn() {
|
async function saveColumn() {
|
||||||
if (field.type === AUTO_TYPE) {
|
if (field.type === AUTO_TYPE) {
|
||||||
field = buildAutoColumn($tables.draft.name, field.name, field.subtype)
|
field = buildAutoColumn($tables.selected.name, field.name, field.subtype)
|
||||||
}
|
}
|
||||||
if (field.type !== LINK_TYPE) {
|
if (field.type !== LINK_TYPE) {
|
||||||
delete field.fieldName
|
delete field.fieldName
|
||||||
|
@ -310,7 +310,7 @@
|
||||||
newError.name = `${PROHIBITED_COLUMN_NAMES.join(
|
newError.name = `${PROHIBITED_COLUMN_NAMES.join(
|
||||||
", "
|
", "
|
||||||
)} are not allowed as column names`
|
)} are not allowed as column names`
|
||||||
} else if (inUse($tables.draft, fieldInfo.name, originalName)) {
|
} else if (inUse($tables.selected, fieldInfo.name, originalName)) {
|
||||||
newError.name = `Column name already in use.`
|
newError.name = `Column name already in use.`
|
||||||
}
|
}
|
||||||
if (fieldInfo.fieldName && fieldInfo.tableId) {
|
if (fieldInfo.fieldName && fieldInfo.tableId) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { tables, rows } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import RowFieldControl from "../RowFieldControl.svelte"
|
import RowFieldControl from "../RowFieldControl.svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
@ -25,7 +25,6 @@
|
||||||
try {
|
try {
|
||||||
await API.saveRow({ ...row, tableId: table._id })
|
await API.saveRow({ ...row, tableId: table._id })
|
||||||
notifications.success("Row saved successfully")
|
notifications.success("Row saved successfully")
|
||||||
rows.save()
|
|
||||||
dispatch("updaterows")
|
dispatch("updaterows")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.handled) {
|
if (error.handled) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { tables, rows } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import RowFieldControl from "../RowFieldControl.svelte"
|
import RowFieldControl from "../RowFieldControl.svelte"
|
||||||
|
@ -57,7 +57,6 @@
|
||||||
try {
|
try {
|
||||||
await API.saveRow({ ...row, tableId: table._id })
|
await API.saveRow({ ...row, tableId: table._id })
|
||||||
notifications.success("User saved successfully")
|
notifications.success("User saved successfully")
|
||||||
rows.save()
|
|
||||||
dispatch("updaterows")
|
dispatch("updaterows")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.handled) {
|
if (error.handled) {
|
||||||
|
|
|
@ -9,19 +9,20 @@
|
||||||
|
|
||||||
$: views = $tables.list.flatMap(table => Object.keys(table.views || {}))
|
$: views = $tables.list.flatMap(table => Object.keys(table.views || {}))
|
||||||
|
|
||||||
function saveView() {
|
const saveView = async () => {
|
||||||
|
name = name?.trim()
|
||||||
if (views.includes(name)) {
|
if (views.includes(name)) {
|
||||||
notifications.error(`View exists with name ${name}`)
|
notifications.error(`View exists with name ${name}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
viewsStore.save({
|
await viewsStore.save({
|
||||||
name,
|
name,
|
||||||
tableId: $tables.selected._id,
|
tableId: $tables.selected._id,
|
||||||
field,
|
field,
|
||||||
})
|
})
|
||||||
notifications.success(`View ${name} created`)
|
notifications.success(`View ${name} created`)
|
||||||
$goto(`../../view/${name}`)
|
$goto(`../../view/${encodeURIComponent(name)}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error creating view")
|
notifications.error("Error creating view")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { goto, isActive, params } from "@roxi/routify"
|
||||||
import { get } from "svelte/store"
|
|
||||||
import { goto, params } from "@roxi/routify"
|
|
||||||
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
||||||
import { database, datasources, queries, tables, views } from "stores/backend"
|
import { database, datasources, queries, tables, views } from "stores/backend"
|
||||||
import EditDatasourcePopover from "./popovers/EditDatasourcePopover.svelte"
|
import EditDatasourcePopover from "./popovers/EditDatasourcePopover.svelte"
|
||||||
|
@ -14,40 +12,61 @@
|
||||||
customQueryText,
|
customQueryText,
|
||||||
} from "helpers/data/utils"
|
} from "helpers/data/utils"
|
||||||
import IntegrationIcon from "./IntegrationIcon.svelte"
|
import IntegrationIcon from "./IntegrationIcon.svelte"
|
||||||
import { notifications } from "@budibase/bbui"
|
|
||||||
|
|
||||||
let openDataSources = []
|
let openDataSources = []
|
||||||
$: enrichedDataSources = Array.isArray($datasources.list)
|
$: enrichedDataSources = enrichDatasources(
|
||||||
? $datasources.list.map(datasource => {
|
$datasources,
|
||||||
const selected = $datasources.selected === datasource._id
|
$params,
|
||||||
const open = openDataSources.includes(datasource._id)
|
$isActive,
|
||||||
const containsSelected = containsActiveEntity(datasource)
|
$tables,
|
||||||
const onlySource = $datasources.list.length === 1
|
$queries,
|
||||||
return {
|
$views
|
||||||
...datasource,
|
)
|
||||||
selected,
|
|
||||||
open: selected || open || containsSelected || onlySource,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: []
|
|
||||||
$: openDataSource = enrichedDataSources.find(x => x.open)
|
$: openDataSource = enrichedDataSources.find(x => x.open)
|
||||||
$: {
|
$: {
|
||||||
// Ensure the open datasource is always included in the list of open
|
// Ensure the open datasource is always actually open
|
||||||
// datasources
|
|
||||||
if (openDataSource) {
|
if (openDataSource) {
|
||||||
openNode(openDataSource)
|
openNode(openDataSource)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectDatasource(datasource) {
|
const enrichDatasources = (
|
||||||
openNode(datasource)
|
datasources,
|
||||||
datasources.select(datasource._id)
|
params,
|
||||||
$goto(`./datasource/${datasource._id}`)
|
isActive,
|
||||||
|
tables,
|
||||||
|
queries,
|
||||||
|
views
|
||||||
|
) => {
|
||||||
|
if (!datasources?.list?.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return datasources.list.map(datasource => {
|
||||||
|
const selected =
|
||||||
|
isActive("./datasource") &&
|
||||||
|
datasources.selectedDatasourceId === datasource._id
|
||||||
|
const open = openDataSources.includes(datasource._id)
|
||||||
|
const containsSelected = containsActiveEntity(
|
||||||
|
datasource,
|
||||||
|
params,
|
||||||
|
isActive,
|
||||||
|
tables,
|
||||||
|
queries,
|
||||||
|
views
|
||||||
|
)
|
||||||
|
const onlySource = datasources.list.length === 1
|
||||||
|
return {
|
||||||
|
...datasource,
|
||||||
|
selected,
|
||||||
|
containsSelected,
|
||||||
|
open: selected || open || containsSelected || onlySource,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickQuery(query) {
|
function selectDatasource(datasource) {
|
||||||
queries.select(query)
|
openNode(datasource)
|
||||||
$goto(`./datasource/${query.datasourceId}/${query._id}`)
|
$goto(`./datasource/${datasource._id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeNode(datasource) {
|
function closeNode(datasource) {
|
||||||
|
@ -69,21 +88,39 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
const containsActiveEntity = (
|
||||||
try {
|
datasource,
|
||||||
await datasources.fetch()
|
params,
|
||||||
await queries.fetch()
|
isActive,
|
||||||
} catch (error) {
|
tables,
|
||||||
notifications.error("Error fetching datasources and queries")
|
queries,
|
||||||
}
|
views
|
||||||
})
|
) => {
|
||||||
|
// Check for being on a datasource page
|
||||||
const containsActiveEntity = datasource => {
|
if (params.datasourceId === datasource._id) {
|
||||||
// If we're view a query then the datasource ID is in the URL
|
|
||||||
if ($params.selectedDatasource === datasource._id) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for hardcoded datasource edge cases
|
||||||
|
if (
|
||||||
|
isActive("./datasource/bb_internal") &&
|
||||||
|
datasource._id === "bb_internal"
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isActive("./datasource/datasource_internal_bb_default") &&
|
||||||
|
datasource._id === "datasource_internal_bb_default"
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a matching query
|
||||||
|
if (params.queryId) {
|
||||||
|
const query = queries.list?.find(q => q._id === params.queryId)
|
||||||
|
return datasource._id === query?.datasourceId
|
||||||
|
}
|
||||||
|
|
||||||
// If there are no entities it can't contain anything
|
// If there are no entities it can't contain anything
|
||||||
if (!datasource.entities) {
|
if (!datasource.entities) {
|
||||||
return false
|
return false
|
||||||
|
@ -96,13 +133,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a matching table
|
// Check for a matching table
|
||||||
if ($params.selectedTable) {
|
if (params.tableId) {
|
||||||
const selectedTable = get(tables).selected?._id
|
const selectedTable = tables.selected?._id
|
||||||
return options.find(x => x._id === selectedTable) != null
|
return options.find(x => x._id === selectedTable) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a matching view
|
// Check for a matching view
|
||||||
const selectedView = get(views).selected?.name
|
const selectedView = views.selected?.name
|
||||||
const table = options.find(table => {
|
const table = options.find(table => {
|
||||||
return table.views?.[selectedView] != null
|
return table.views?.[selectedView] != null
|
||||||
})
|
})
|
||||||
|
@ -117,7 +154,7 @@
|
||||||
border={idx > 0}
|
border={idx > 0}
|
||||||
text={datasource.name}
|
text={datasource.name}
|
||||||
opened={datasource.open}
|
opened={datasource.open}
|
||||||
selected={datasource.selected}
|
selected={$isActive("./datasource") && datasource.selected}
|
||||||
withArrow={true}
|
withArrow={true}
|
||||||
on:click={() => selectDatasource(datasource)}
|
on:click={() => selectDatasource(datasource)}
|
||||||
on:iconClick={() => toggleNode(datasource)}
|
on:iconClick={() => toggleNode(datasource)}
|
||||||
|
@ -143,11 +180,11 @@
|
||||||
iconText={customQueryIconText(datasource, query)}
|
iconText={customQueryIconText(datasource, query)}
|
||||||
iconColor={customQueryIconColor(datasource, query)}
|
iconColor={customQueryIconColor(datasource, query)}
|
||||||
text={customQueryText(datasource, query)}
|
text={customQueryText(datasource, query)}
|
||||||
opened={$queries.selected === query._id}
|
selected={$isActive("./query/:queryId") &&
|
||||||
selected={$queries.selected === query._id}
|
$queries.selectedQueryId === query._id}
|
||||||
on:click={() => onClickQuery(query)}
|
on:click={() => $goto(`./query/${query._id}`)}
|
||||||
>
|
>
|
||||||
<EditQueryPopover {query} {onClickQuery} />
|
<EditQueryPopover {query} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -156,6 +193,9 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.hierarchy-items-container {
|
||||||
|
margin: 0 calc(-1 * var(--spacing-xl));
|
||||||
|
}
|
||||||
.datasource-icon {
|
.datasource-icon {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
|
|
|
@ -104,7 +104,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickTable(table) {
|
function onClickTable(table) {
|
||||||
tables.select(table)
|
|
||||||
$goto(`../../table/${table._id}`)
|
$goto(`../../table/${table._id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import ICONS from "../icons"
|
import ICONS from "../icons"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { IntegrationTypes } from "constants/backend"
|
import { IntegrationTypes, DatasourceTypes } from "constants/backend"
|
||||||
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
|
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
|
||||||
import DatasourceConfigModal from "components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte"
|
import DatasourceConfigModal from "components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte"
|
||||||
import GoogleDatasourceConfigModal from "components/backend/DatasourceNavigator/modals/GoogleDatasourceConfigModal.svelte"
|
import GoogleDatasourceConfigModal from "components/backend/DatasourceNavigator/modals/GoogleDatasourceConfigModal.svelte"
|
||||||
|
@ -31,6 +31,7 @@
|
||||||
$: customIntegrations = Object.entries(integrations).filter(
|
$: customIntegrations = Object.entries(integrations).filter(
|
||||||
entry => entry[1].custom
|
entry => entry[1].custom
|
||||||
)
|
)
|
||||||
|
$: sortedIntegrations = sortIntegrations(integrations)
|
||||||
|
|
||||||
checkShowImport()
|
checkShowImport()
|
||||||
|
|
||||||
|
@ -99,6 +100,29 @@
|
||||||
}
|
}
|
||||||
integrations = newIntegrations
|
integrations = newIntegrations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortIntegrations(integrations) {
|
||||||
|
let integrationsArray = Object.entries(integrations)
|
||||||
|
function getTypeOrder(schema) {
|
||||||
|
if (schema.type === DatasourceTypes.API) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (schema.type === DatasourceTypes.RELATIONAL) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return schema.type?.charCodeAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
integrationsArray.sort((a, b) => {
|
||||||
|
let typeOrderA = getTypeOrder(a[1])
|
||||||
|
let typeOrderB = getTypeOrder(b[1])
|
||||||
|
if (typeOrderA === typeOrderB) {
|
||||||
|
return a[1].friendlyName?.localeCompare(b[1].friendlyName)
|
||||||
|
}
|
||||||
|
return typeOrderA < typeOrderB ? -1 : 1
|
||||||
|
})
|
||||||
|
return integrationsArray
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={internalTableModal}>
|
<Modal bind:this={internalTableModal}>
|
||||||
|
@ -157,7 +181,7 @@
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
<Body size="S">Connect to an external datasource</Body>
|
<Body size="S">Connect to an external datasource</Body>
|
||||||
<div class="item-list">
|
<div class="item-list">
|
||||||
{#each Object.entries(integrations).filter(([key, val]) => key !== IntegrationTypes.INTERNAL && !val.custom) as [integrationType, schema]}
|
{#each sortedIntegrations.filter(([key, val]) => key !== IntegrationTypes.INTERNAL && !val.custom) as [integrationType, schema]}
|
||||||
<DatasourceCard
|
<DatasourceCard
|
||||||
on:selected={evt => selectIntegration(evt.detail)}
|
on:selected={evt => selectIntegration(evt.detail)}
|
||||||
{schema}
|
{schema}
|
||||||
|
|
|
@ -64,7 +64,6 @@
|
||||||
// reload
|
// reload
|
||||||
await datasources.fetch()
|
await datasources.fetch()
|
||||||
await queries.fetch()
|
await queries.fetch()
|
||||||
await datasources.select(datasourceId)
|
|
||||||
|
|
||||||
if (navigateDatasource) {
|
if (navigateDatasource) {
|
||||||
$goto(`./datasource/${datasourceId}`)
|
$goto(`./datasource/${datasourceId}`)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { datasources, queries, tables } from "stores/backend"
|
import { datasources } from "stores/backend"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import { ActionMenu, MenuItem, Icon } from "@budibase/bbui"
|
import { ActionMenu, MenuItem, Icon } from "@budibase/bbui"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
@ -14,23 +14,10 @@
|
||||||
|
|
||||||
async function deleteDatasource() {
|
async function deleteDatasource() {
|
||||||
try {
|
try {
|
||||||
let wasSelectedSource = $datasources.selected
|
const isSelected = datasource.selected || datasource.containsSelected
|
||||||
if (!wasSelectedSource && $queries.selected) {
|
|
||||||
const queryId = $queries.selected
|
|
||||||
wasSelectedSource = $datasources.list.find(ds =>
|
|
||||||
queryId.includes(ds._id)
|
|
||||||
)?._id
|
|
||||||
}
|
|
||||||
const wasSelectedTable = $tables.selected
|
|
||||||
await datasources.delete(datasource)
|
await datasources.delete(datasource)
|
||||||
notifications.success("Datasource deleted")
|
notifications.success("Datasource deleted")
|
||||||
// Navigate to first index page if the source you are deleting is selected
|
if (isSelected) {
|
||||||
const entities = Object.values(datasource?.entities || {})
|
|
||||||
if (
|
|
||||||
wasSelectedSource === datasource._id ||
|
|
||||||
(entities &&
|
|
||||||
entities.find(entity => entity._id === wasSelectedTable?._id))
|
|
||||||
) {
|
|
||||||
$goto("./datasource")
|
$goto("./datasource")
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -5,23 +5,17 @@
|
||||||
import { datasources, queries } from "stores/backend"
|
import { datasources, queries } from "stores/backend"
|
||||||
|
|
||||||
export let query
|
export let query
|
||||||
export let onClickQuery
|
|
||||||
|
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
|
|
||||||
async function deleteQuery() {
|
async function deleteQuery() {
|
||||||
try {
|
try {
|
||||||
const wasSelectedQuery = $queries.selected
|
// Go back to the datasource if we are deleting the active query
|
||||||
// need to calculate this before the query is deleted
|
if ($queries.selectedQueryId === query._id) {
|
||||||
const navigateToDatasource = wasSelectedQuery === query._id
|
|
||||||
|
|
||||||
await queries.delete(query)
|
|
||||||
await datasources.fetch()
|
|
||||||
|
|
||||||
if (navigateToDatasource) {
|
|
||||||
await datasources.select(query.datasourceId)
|
|
||||||
$goto(`./datasource/${query.datasourceId}`)
|
$goto(`./datasource/${query.datasourceId}`)
|
||||||
}
|
}
|
||||||
|
await queries.delete(query)
|
||||||
|
await datasources.fetch()
|
||||||
notifications.success("Query deleted")
|
notifications.success("Query deleted")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error deleting query")
|
notifications.error("Error deleting query")
|
||||||
|
@ -31,7 +25,7 @@
|
||||||
async function duplicateQuery() {
|
async function duplicateQuery() {
|
||||||
try {
|
try {
|
||||||
const newQuery = await queries.duplicate(query)
|
const newQuery = await queries.duplicate(query)
|
||||||
onClickQuery(newQuery)
|
$goto(`./query/${newQuery._id}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error duplicating query")
|
notifications.error("Error duplicating query")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
|
||||||
import { tables, views, database } from "stores/backend"
|
import { tables, views, database } from "stores/backend"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
||||||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
|
import { goto, isActive } from "@roxi/routify"
|
||||||
|
|
||||||
const alphabetical = (a, b) => a.name?.toLowerCase() > b.name?.toLowerCase()
|
const alphabetical = (a, b) => a.name?.toLowerCase() > b.name?.toLowerCase()
|
||||||
|
|
||||||
export let sourceId
|
export let sourceId
|
||||||
|
|
||||||
$: selectedView = $views.selected && $views.selected.name
|
|
||||||
$: sortedTables = $tables.list
|
$: sortedTables = $tables.list
|
||||||
.filter(table => table.sourceId === sourceId)
|
.filter(table => table.sourceId === sourceId)
|
||||||
.sort(alphabetical)
|
.sort(alphabetical)
|
||||||
|
|
||||||
function selectTable(table) {
|
|
||||||
tables.select(table)
|
|
||||||
$goto(`./table/${table._id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectView(view) {
|
|
||||||
views.select(view)
|
|
||||||
$goto(`./view/${view.name}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClickView(table, viewName) {
|
|
||||||
if (selectedView === viewName) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
selectView({
|
|
||||||
name: viewName,
|
|
||||||
...table.views[viewName],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $database?._id}
|
{#if $database?._id}
|
||||||
|
@ -44,8 +23,9 @@
|
||||||
border={idx > 0}
|
border={idx > 0}
|
||||||
icon={table._id === TableNames.USERS ? "UserGroup" : "Table"}
|
icon={table._id === TableNames.USERS ? "UserGroup" : "Table"}
|
||||||
text={table.name}
|
text={table.name}
|
||||||
selected={$tables.selected?._id === table._id}
|
selected={$isActive("./table/:tableId") &&
|
||||||
on:click={() => selectTable(table)}
|
$tables.selected?._id === table._id}
|
||||||
|
on:click={() => $goto(`./table/${table._id}`)}
|
||||||
>
|
>
|
||||||
{#if table._id !== TableNames.USERS}
|
{#if table._id !== TableNames.USERS}
|
||||||
<EditTablePopover {table} />
|
<EditTablePopover {table} />
|
||||||
|
@ -56,8 +36,8 @@
|
||||||
indentLevel={2}
|
indentLevel={2}
|
||||||
icon="Remove"
|
icon="Remove"
|
||||||
text={viewName}
|
text={viewName}
|
||||||
selected={selectedView === viewName}
|
selected={$isActive("./view") && $views.selected?.name === viewName}
|
||||||
on:click={() => onClickView(table, viewName)}
|
on:click={() => $goto(`./view/${encodeURIComponent(viewName)}`)}
|
||||||
>
|
>
|
||||||
<EditViewPopover
|
<EditViewPopover
|
||||||
view={{ name: viewName, ...table.views[viewName] }}
|
view={{ name: viewName, ...table.views[viewName] }}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { tables, datasources } from "stores/backend"
|
import { tables, datasources } from "stores/backend"
|
||||||
|
@ -41,17 +41,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteTable() {
|
async function deleteTable() {
|
||||||
const wasSelectedTable = $tables.selected
|
const isSelected = $params.tableId === table._id
|
||||||
try {
|
try {
|
||||||
await tables.delete(table)
|
await tables.delete(table)
|
||||||
await store.actions.screens.delete(templateScreens)
|
await store.actions.screens.delete(templateScreens)
|
||||||
await tables.fetch()
|
|
||||||
if (table.type === "external") {
|
if (table.type === "external") {
|
||||||
await datasources.fetch()
|
await datasources.fetch()
|
||||||
}
|
}
|
||||||
notifications.success("Table deleted")
|
notifications.success("Table deleted")
|
||||||
if (wasSelectedTable && wasSelectedTable._id === table._id) {
|
if (isSelected) {
|
||||||
$goto("./table")
|
$goto(`./datasource/${table.datasourceId}`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error deleting table")
|
notifications.error("Error deleting table")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { views } from "stores/backend"
|
import { views } from "stores/backend"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
@ -33,11 +33,15 @@
|
||||||
|
|
||||||
async function deleteView() {
|
async function deleteView() {
|
||||||
try {
|
try {
|
||||||
|
const isSelected =
|
||||||
|
decodeURIComponent($params.viewName) === $views.selectedViewName
|
||||||
const name = view.name
|
const name = view.name
|
||||||
const id = view.tableId
|
const id = view.tableId
|
||||||
await views.delete(name)
|
await views.delete(name)
|
||||||
notifications.success("View deleted")
|
notifications.success("View deleted")
|
||||||
$goto(`./table/${id}`)
|
if (isSelected) {
|
||||||
|
$goto(`./table/${id}`)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error deleting view")
|
notifications.error("Error deleting view")
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
import { ProgressCircle } from "@budibase/bbui"
|
import { ProgressCircle } from "@budibase/bbui"
|
||||||
import CopyInput from "components/common/inputs/CopyInput.svelte"
|
import CopyInput from "components/common/inputs/CopyInput.svelte"
|
||||||
|
|
||||||
let feedbackModal
|
|
||||||
let publishModal
|
let publishModal
|
||||||
let asyncModal
|
let asyncModal
|
||||||
let publishCompleteModal
|
let publishCompleteModal
|
||||||
|
@ -23,13 +22,13 @@
|
||||||
|
|
||||||
export let onOk
|
export let onOk
|
||||||
|
|
||||||
async function deployApp() {
|
async function publishApp() {
|
||||||
try {
|
try {
|
||||||
//In Progress
|
//In Progress
|
||||||
asyncModal.show()
|
asyncModal.show()
|
||||||
publishModal.hide()
|
publishModal.hide()
|
||||||
|
|
||||||
published = await API.deployAppChanges()
|
published = await API.publishAppChanges($store.appId)
|
||||||
|
|
||||||
if (typeof onOk === "function") {
|
if (typeof onOk === "function") {
|
||||||
await onOk()
|
await onOk()
|
||||||
|
@ -56,20 +55,11 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button cta on:click={publishModal.show}>Publish</Button>
|
<Button cta on:click={publishModal.show}>Publish</Button>
|
||||||
<Modal bind:this={feedbackModal}>
|
|
||||||
<ModalContent
|
|
||||||
title="Enjoying Budibase?"
|
|
||||||
size="L"
|
|
||||||
showConfirmButton={false}
|
|
||||||
showCancelButton={false}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal bind:this={publishModal}>
|
<Modal bind:this={publishModal}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Publish to Production"
|
title="Publish to Production"
|
||||||
confirmText="Publish"
|
confirmText="Publish"
|
||||||
onConfirm={deployApp}
|
onConfirm={publishApp}
|
||||||
dataCy={"deploy-app-modal"}
|
dataCy={"deploy-app-modal"}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
<style>
|
<style>
|
||||||
.panel {
|
.panel {
|
||||||
width: 260px;
|
width: 260px;
|
||||||
|
flex: 0 0 260px;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
}
|
}
|
||||||
.panel.wide {
|
.panel.wide {
|
||||||
width: 420px;
|
width: 420px;
|
||||||
|
flex: 0 0 420px;
|
||||||
}
|
}
|
||||||
.header {
|
.header {
|
||||||
flex: 0 0 48px;
|
flex: 0 0 48px;
|
||||||
|
|
|
@ -29,11 +29,12 @@
|
||||||
|
|
||||||
export let query
|
export let query
|
||||||
|
|
||||||
|
const transformerDocs = "https://docs.budibase.com/docs/transformers"
|
||||||
|
|
||||||
let fields = query?.schema ? schemaToFields(query.schema) : []
|
let fields = query?.schema ? schemaToFields(query.schema) : []
|
||||||
let parameters
|
let parameters
|
||||||
let data = []
|
let data = []
|
||||||
let saveId
|
let saveId
|
||||||
const transformerDocs = "https://docs.budibase.com/docs/transformers"
|
|
||||||
|
|
||||||
$: datasource = $datasources.list.find(ds => ds._id === query.datasourceId)
|
$: datasource = $datasources.list.find(ds => ds._id === query.datasourceId)
|
||||||
$: query.schema = fieldsToSchema(fields)
|
$: query.schema = fieldsToSchema(fields)
|
||||||
|
@ -94,132 +95,144 @@
|
||||||
try {
|
try {
|
||||||
const { _id } = await queries.save(query.datasourceId, query)
|
const { _id } = await queries.save(query.datasourceId, query)
|
||||||
saveId = _id
|
saveId = _id
|
||||||
notifications.success(`Query saved successfully.`)
|
notifications.success(`Query saved successfully`)
|
||||||
$goto(`../${_id}`)
|
|
||||||
|
// Go to the correct URL if we just created a new query
|
||||||
|
if (!query._rev) {
|
||||||
|
$goto(`../../${_id}`)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error creating query")
|
notifications.error("Error saving query")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout gap="S" noPadding>
|
<div class="wrapper">
|
||||||
<Heading size="M">Query {integrationInfo?.friendlyName}</Heading>
|
<Layout gap="S" noPadding>
|
||||||
<Divider />
|
<Heading size="M">Query {integrationInfo?.friendlyName}</Heading>
|
||||||
<Heading size="S">Config</Heading>
|
|
||||||
<div class="config">
|
|
||||||
<div class="config-field">
|
|
||||||
<Label>Query Name</Label>
|
|
||||||
<Input bind:value={query.name} />
|
|
||||||
</div>
|
|
||||||
{#if queryConfig}
|
|
||||||
<div class="config-field">
|
|
||||||
<Label>Function</Label>
|
|
||||||
<Select
|
|
||||||
bind:value={query.queryVerb}
|
|
||||||
on:change={resetDependentFields}
|
|
||||||
options={Object.keys(queryConfig)}
|
|
||||||
getOptionLabel={verb =>
|
|
||||||
queryConfig[verb]?.displayName || capitalise(verb)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="config-field">
|
|
||||||
<AccessLevelSelect {saveId} {query} label="Access Level" />
|
|
||||||
</div>
|
|
||||||
{#if integrationInfo?.extra && query.queryVerb}
|
|
||||||
<ExtraQueryConfig
|
|
||||||
{query}
|
|
||||||
{populateExtraQuery}
|
|
||||||
config={integrationInfo.extra}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#key query.parameters}
|
|
||||||
<BindingBuilder
|
|
||||||
queryBindings={query.parameters}
|
|
||||||
bindable={false}
|
|
||||||
on:change={e => {
|
|
||||||
query.parameters = e.detail.map(binding => {
|
|
||||||
return {
|
|
||||||
name: binding.name,
|
|
||||||
default: binding.value,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if shouldShowQueryConfig}
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Heading size="S">Config</Heading>
|
||||||
<div class="config">
|
<div class="config">
|
||||||
<Heading size="S">Fields</Heading>
|
<div class="config-field">
|
||||||
<Body size="S">Fill in the fields specific to this query.</Body>
|
<Label>Query Name</Label>
|
||||||
<IntegrationQueryEditor
|
<Input bind:value={query.name} />
|
||||||
{datasource}
|
|
||||||
{query}
|
|
||||||
height={200}
|
|
||||||
schema={queryConfig[query.queryVerb]}
|
|
||||||
bind:parameters
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
</div>
|
|
||||||
<div class="config">
|
|
||||||
<div class="help-heading">
|
|
||||||
<Heading size="S">Transformer</Heading>
|
|
||||||
<Icon
|
|
||||||
on:click={() => window.open(transformerDocs)}
|
|
||||||
hoverable
|
|
||||||
name="Help"
|
|
||||||
size="L"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Body size="S"
|
{#if queryConfig}
|
||||||
>Add a JavaScript function to transform the query result.</Body
|
<div class="config-field">
|
||||||
>
|
<Label>Function</Label>
|
||||||
<CodeMirrorEditor
|
<Select
|
||||||
height={200}
|
bind:value={query.queryVerb}
|
||||||
label="Transformer"
|
on:change={resetDependentFields}
|
||||||
value={query.transformer}
|
options={Object.keys(queryConfig)}
|
||||||
resize="vertical"
|
getOptionLabel={verb =>
|
||||||
on:change={e => (query.transformer = e.detail)}
|
queryConfig[verb]?.displayName || capitalise(verb)}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
</div>
|
||||||
</div>
|
<div class="config-field">
|
||||||
<div class="viewer-controls">
|
<AccessLevelSelect {saveId} {query} label="Access Level" />
|
||||||
<Heading size="S">Results</Heading>
|
</div>
|
||||||
<ButtonGroup gap="M">
|
{#if integrationInfo?.extra && query.queryVerb}
|
||||||
<Button cta disabled={queryInvalid} on:click={saveQuery}>
|
<ExtraQueryConfig
|
||||||
Save Query
|
{query}
|
||||||
</Button>
|
{populateExtraQuery}
|
||||||
<Button secondary on:click={previewQuery}>Run Query</Button>
|
config={integrationInfo.extra}
|
||||||
</ButtonGroup>
|
/>
|
||||||
</div>
|
{/if}
|
||||||
<Body size="S">
|
{#key query.parameters}
|
||||||
Below, you can preview the results from your query and change the schema.
|
<BindingBuilder
|
||||||
</Body>
|
queryBindings={query.parameters}
|
||||||
<section class="viewer">
|
bindable={false}
|
||||||
{#if data}
|
on:change={e => {
|
||||||
<Tabs selected="JSON">
|
query.parameters = e.detail.map(binding => {
|
||||||
<Tab title="JSON">
|
return {
|
||||||
<JSONPreview data={data[0]} minHeight="120" />
|
name: binding.name,
|
||||||
</Tab>
|
default: binding.value,
|
||||||
<Tab title="Schema">
|
}
|
||||||
<KeyValueBuilder
|
})
|
||||||
bind:object={fields}
|
}}
|
||||||
name="field"
|
/>
|
||||||
headings
|
{/key}
|
||||||
options={SchemaTypeOptions}
|
|
||||||
/>
|
|
||||||
</Tab>
|
|
||||||
<Tab title="Preview">
|
|
||||||
<ExternalDataSourceTable {query} {data} />
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</div>
|
||||||
{/if}
|
{#if shouldShowQueryConfig}
|
||||||
</Layout>
|
<Divider />
|
||||||
|
<div class="config">
|
||||||
|
<Heading size="S">Fields</Heading>
|
||||||
|
<Body size="S">Fill in the fields specific to this query.</Body>
|
||||||
|
<IntegrationQueryEditor
|
||||||
|
{datasource}
|
||||||
|
{query}
|
||||||
|
height={200}
|
||||||
|
schema={queryConfig[query.queryVerb]}
|
||||||
|
bind:parameters
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
</div>
|
||||||
|
<div class="config">
|
||||||
|
<div class="help-heading">
|
||||||
|
<Heading size="S">Transformer</Heading>
|
||||||
|
<Icon
|
||||||
|
on:click={() => window.open(transformerDocs)}
|
||||||
|
hoverable
|
||||||
|
name="Help"
|
||||||
|
size="L"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Body size="S"
|
||||||
|
>Add a JavaScript function to transform the query result.</Body
|
||||||
|
>
|
||||||
|
<CodeMirrorEditor
|
||||||
|
height={200}
|
||||||
|
label="Transformer"
|
||||||
|
value={query.transformer}
|
||||||
|
resize="vertical"
|
||||||
|
on:change={e => (query.transformer = e.detail)}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
</div>
|
||||||
|
<div class="viewer-controls">
|
||||||
|
<Heading size="S">Results</Heading>
|
||||||
|
<ButtonGroup gap="XS">
|
||||||
|
<Button cta disabled={queryInvalid} on:click={saveQuery}>
|
||||||
|
Save Query
|
||||||
|
</Button>
|
||||||
|
<Button secondary on:click={previewQuery}>Run Query</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
<Body size="S">
|
||||||
|
Below, you can preview the results from your query and change the
|
||||||
|
schema.
|
||||||
|
</Body>
|
||||||
|
<section class="viewer">
|
||||||
|
{#if data}
|
||||||
|
<Tabs selected="JSON">
|
||||||
|
<Tab title="JSON">
|
||||||
|
<JSONPreview data={data[0]} minHeight="120" />
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Schema">
|
||||||
|
<KeyValueBuilder
|
||||||
|
bind:object={fields}
|
||||||
|
name="field"
|
||||||
|
headings
|
||||||
|
options={SchemaTypeOptions}
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Preview">
|
||||||
|
<ExternalDataSourceTable {query} {data} />
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
width: 640px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.config {
|
.config {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: var(--spacing-s);
|
grid-gap: var(--spacing-s);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { datasources, flags, integrations, queries } from "stores/backend"
|
import { datasources, flags, integrations, queries } from "stores/backend"
|
||||||
import {
|
import {
|
||||||
Banner,
|
Banner,
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
import CodeMirrorEditor, {
|
import CodeMirrorEditor, {
|
||||||
EditorModes,
|
EditorModes,
|
||||||
} from "components/common/CodeMirrorEditor.svelte"
|
} from "components/common/CodeMirrorEditor.svelte"
|
||||||
import RestBodyInput from "../../_components/RestBodyInput.svelte"
|
import RestBodyInput from "./RestBodyInput.svelte"
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import restUtils from "helpers/data/utils"
|
import restUtils from "helpers/data/utils"
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import JSONPreview from "components/integration/JSONPreview.svelte"
|
import JSONPreview from "components/integration/JSONPreview.svelte"
|
||||||
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
|
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
|
||||||
import DynamicVariableModal from "../../_components/DynamicVariableModal.svelte"
|
import DynamicVariableModal from "./DynamicVariableModal.svelte"
|
||||||
import Placeholder from "assets/bb-spaceship.svg"
|
import Placeholder from "assets/bb-spaceship.svg"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@
|
||||||
toBindingsArray,
|
toBindingsArray,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
|
|
||||||
|
export let queryId
|
||||||
|
|
||||||
let query, datasource
|
let query, datasource
|
||||||
let breakQs = {},
|
let breakQs = {},
|
||||||
requestBindings = {}
|
requestBindings = {}
|
||||||
|
@ -102,8 +104,8 @@
|
||||||
|
|
||||||
function getSelectedQuery() {
|
function getSelectedQuery() {
|
||||||
return cloneDeep(
|
return cloneDeep(
|
||||||
$queries.list.find(q => q._id === $queries.selected) || {
|
$queries.list.find(q => q._id === queryId) || {
|
||||||
datasourceId: $params.selectedDatasource,
|
datasourceId: $params.datasourceId,
|
||||||
parameters: [],
|
parameters: [],
|
||||||
fields: {
|
fields: {
|
||||||
// only init the objects, everything else is optional strings
|
// only init the objects, everything else is optional strings
|
||||||
|
@ -159,6 +161,7 @@
|
||||||
async function saveQuery() {
|
async function saveQuery() {
|
||||||
const toSave = buildQuery()
|
const toSave = buildQuery()
|
||||||
try {
|
try {
|
||||||
|
const isNew = !query._rev
|
||||||
const { _id } = await queries.save(toSave.datasourceId, toSave)
|
const { _id } = await queries.save(toSave.datasourceId, toSave)
|
||||||
saveId = _id
|
saveId = _id
|
||||||
query = getSelectedQuery()
|
query = getSelectedQuery()
|
||||||
|
@ -174,6 +177,9 @@
|
||||||
staticVariables,
|
staticVariables,
|
||||||
restBindings
|
restBindings
|
||||||
)
|
)
|
||||||
|
if (isNew) {
|
||||||
|
$goto(`../../${_id}`)
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error saving query`)
|
notifications.error(`Error saving query`)
|
||||||
}
|
}
|
||||||
|
@ -464,8 +470,9 @@
|
||||||
on:click={saveQuery}
|
on:click={saveQuery}
|
||||||
tooltip={!hasSchema
|
tooltip={!hasSchema
|
||||||
? "Saving a query before sending will mean no schema is generated"
|
? "Saving a query before sending will mean no schema is generated"
|
||||||
: null}>Save</Button
|
: null}
|
||||||
>
|
>Save
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Tabs selected="Bindings" quiet noPadding noHorizPadding onTop>
|
<Tabs selected="Bindings" quiet noPadding noHorizPadding onTop>
|
||||||
<Tab title="Bindings">
|
<Tab title="Bindings">
|
||||||
|
@ -708,26 +715,33 @@
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
width: 960px;
|
width: 960px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.url-block {
|
.url-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.verb {
|
.verb {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.url {
|
.url {
|
||||||
flex: 4;
|
flex: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top {
|
.top {
|
||||||
min-height: 50%;
|
min-height: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
padding-bottom: 50px;
|
padding-bottom: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats {
|
.stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-xl);
|
gap: var(--spacing-xl);
|
||||||
|
@ -735,40 +749,49 @@
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.green {
|
.green {
|
||||||
color: #53a761;
|
color: #53a761;
|
||||||
}
|
}
|
||||||
|
|
||||||
.red {
|
.red {
|
||||||
color: #ea7d82;
|
color: #ea7d82;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-bar {
|
.top-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.access {
|
.access {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-internal {
|
.placeholder-internal {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
gap: var(--spacing-l);
|
gap: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder {
|
.placeholder {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: var(--spacing-xl);
|
margin-top: var(--spacing-xl);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-container {
|
.auth-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-select {
|
.auth-select {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
|
@ -261,3 +261,12 @@ export const BannedSearchTypes = [
|
||||||
"json",
|
"json",
|
||||||
"jsonarray",
|
"jsonarray",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const DatasourceTypes = {
|
||||||
|
RELATIONAL: "Relational",
|
||||||
|
NON_RELATIONAL: "Non-relational",
|
||||||
|
SPREADSHEET: "Spreadsheet",
|
||||||
|
OBJECT_STORE: "Object store",
|
||||||
|
GRAPH: "Graph",
|
||||||
|
API: "API",
|
||||||
|
}
|
||||||
|
|
|
@ -6,11 +6,13 @@ export const syncURLToState = options => {
|
||||||
urlParam,
|
urlParam,
|
||||||
stateKey,
|
stateKey,
|
||||||
validate,
|
validate,
|
||||||
|
update,
|
||||||
baseUrl = "..",
|
baseUrl = "..",
|
||||||
fallbackUrl,
|
fallbackUrl,
|
||||||
store,
|
store,
|
||||||
routify,
|
routify,
|
||||||
beforeNavigate,
|
beforeNavigate,
|
||||||
|
decode,
|
||||||
} = options || {}
|
} = options || {}
|
||||||
if (
|
if (
|
||||||
!urlParam ||
|
!urlParam ||
|
||||||
|
@ -28,17 +30,29 @@ export const syncURLToState = options => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decodes encoded URL params if required
|
||||||
|
const decodeParams = urlParams => {
|
||||||
|
if (!decode) {
|
||||||
|
return urlParams
|
||||||
|
}
|
||||||
|
let decoded = {}
|
||||||
|
Object.keys(urlParams || {}).forEach(key => {
|
||||||
|
decoded[key] = decode(urlParams[key])
|
||||||
|
})
|
||||||
|
return decoded
|
||||||
|
}
|
||||||
|
|
||||||
// We can't dynamically fetch the value of stateful routify stores so we need
|
// We can't dynamically fetch the value of stateful routify stores so we need
|
||||||
// to just subscribe and cache the latest versions.
|
// to just subscribe and cache the latest versions.
|
||||||
// We can grab their initial values as this is during component
|
// We can grab their initial values as this is during component
|
||||||
// initialisation.
|
// initialisation.
|
||||||
let cachedParams = get(routify.params)
|
let cachedParams = decodeParams(get(routify.params))
|
||||||
let cachedGoto = get(routify.goto)
|
let cachedGoto = get(routify.goto)
|
||||||
let cachedRedirect = get(routify.redirect)
|
let cachedRedirect = get(routify.redirect)
|
||||||
let cachedPage = get(routify.page)
|
let cachedPage = get(routify.page)
|
||||||
let previousParamsHash = null
|
let previousParamsHash = null
|
||||||
let debug = false
|
let debug = false
|
||||||
const log = (...params) => debug && console.log(...params)
|
const log = (...params) => debug && console.log(`[${urlParam}]`, ...params)
|
||||||
|
|
||||||
// Navigate to a certain URL
|
// Navigate to a certain URL
|
||||||
const gotoUrl = (url, params) => {
|
const gotoUrl = (url, params) => {
|
||||||
|
@ -76,7 +90,7 @@ export const syncURLToState = options => {
|
||||||
// Check if new value is valid
|
// Check if new value is valid
|
||||||
if (validate && fallbackUrl) {
|
if (validate && fallbackUrl) {
|
||||||
if (!validate(urlValue)) {
|
if (!validate(urlValue)) {
|
||||||
log("Invalid URL param!")
|
log("Invalid URL param!", urlValue)
|
||||||
redirectUrl(fallbackUrl)
|
redirectUrl(fallbackUrl)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -85,10 +99,16 @@ export const syncURLToState = options => {
|
||||||
// Only update state if we have a new value
|
// Only update state if we have a new value
|
||||||
if (urlValue !== stateValue) {
|
if (urlValue !== stateValue) {
|
||||||
log(`state.${stateKey} (${stateValue}) <= url.${urlParam} (${urlValue})`)
|
log(`state.${stateKey} (${stateValue}) <= url.${urlParam} (${urlValue})`)
|
||||||
store.update(state => {
|
if (update) {
|
||||||
state[stateKey] = urlValue
|
// Use custom update function if provided
|
||||||
return state
|
update(urlValue)
|
||||||
})
|
} else {
|
||||||
|
// Otherwise manually update the store
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
[stateKey]: urlValue,
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +122,7 @@ export const syncURLToState = options => {
|
||||||
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
|
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
|
||||||
if (validate && fallbackUrl) {
|
if (validate && fallbackUrl) {
|
||||||
if (!validate(stateValue)) {
|
if (!validate(stateValue)) {
|
||||||
log("Invalid state param!")
|
log("Invalid state param!", stateValue)
|
||||||
redirectUrl(fallbackUrl)
|
redirectUrl(fallbackUrl)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -130,6 +150,7 @@ export const syncURLToState = options => {
|
||||||
|
|
||||||
// Subscribe to URL changes and cache them
|
// Subscribe to URL changes and cache them
|
||||||
const unsubscribeParams = routify.params.subscribe($urlParams => {
|
const unsubscribeParams = routify.params.subscribe($urlParams => {
|
||||||
|
$urlParams = decodeParams($urlParams)
|
||||||
cachedParams = $urlParams
|
cachedParams = $urlParams
|
||||||
mapUrlToState($urlParams)
|
mapUrlToState($urlParams)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,75 +1,46 @@
|
||||||
<script>
|
<script>
|
||||||
import { redirect } from "@roxi/routify"
|
import { Button, Layout } from "@budibase/bbui"
|
||||||
import { Button, Tabs, Tab, Layout } from "@budibase/bbui"
|
|
||||||
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
|
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
|
||||||
import CreateDatasourceModal from "components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte"
|
import CreateDatasourceModal from "components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte"
|
||||||
|
import Panel from "components/design/Panel.svelte"
|
||||||
let selected = "Sources"
|
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
function selectFirstDatasource() {
|
|
||||||
$redirect("./table")
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=1 -->
|
<!-- routify:options index=1 -->
|
||||||
<div class="root">
|
<div class="data">
|
||||||
<div class="nav">
|
<Panel title="Sources" borderRight>
|
||||||
<Tabs {selected} on:select={selectFirstDatasource}>
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
<Tab title="Sources">
|
<Button dataCy={`new-datasource`} cta on:click={modal.show}>
|
||||||
<Layout paddingX="L" paddingY="L" gap="S">
|
Add source
|
||||||
<Button dataCy={`new-datasource`} cta wide on:click={modal.show}
|
</Button>
|
||||||
>Add source</Button
|
<CreateDatasourceModal bind:modal />
|
||||||
>
|
<DatasourceNavigator />
|
||||||
</Layout>
|
</Layout>
|
||||||
<CreateDatasourceModal bind:modal />
|
</Panel>
|
||||||
<DatasourceNavigator />
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.data {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
height: 0;
|
height: 0;
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 260px minmax(0, 1fr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
flex: 1 1 auto;
|
padding: 28px 40px 40px 40px;
|
||||||
padding: var(--spacing-l) 40px 40px 40px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: var(--spacing-l);
|
gap: var(--spacing-l);
|
||||||
}
|
|
||||||
.content :global(> span) {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav {
|
|
||||||
overflow-y: auto;
|
|
||||||
background: var(--background);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
position: relative;
|
flex: 1 1 auto;
|
||||||
border-right: var(--border-light);
|
|
||||||
padding-bottom: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-button {
|
|
||||||
position: absolute;
|
|
||||||
top: var(--spacing-l);
|
|
||||||
right: var(--spacing-xl);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script>
|
||||||
|
import { params } from "@roxi/routify"
|
||||||
|
import { datasources } from "stores/backend"
|
||||||
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
|
import * as routify from "@roxi/routify"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
const stopSyncing = syncURLToState({
|
||||||
|
urlParam: "datasourceId",
|
||||||
|
stateKey: "selectedDatasourceId",
|
||||||
|
validate: id => $datasources.list?.some(ds => ds._id === id),
|
||||||
|
update: datasources.select,
|
||||||
|
fallbackUrl: "../",
|
||||||
|
store: datasources,
|
||||||
|
routify,
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(stopSyncing)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#key $params.datasourceId}
|
||||||
|
<slot />
|
||||||
|
{/key}
|
|
@ -21,34 +21,31 @@
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import ImportRestQueriesModal from "components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte"
|
import ImportRestQueriesModal from "components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte"
|
||||||
|
|
||||||
let importQueriesModal
|
|
||||||
|
|
||||||
let changed,
|
|
||||||
isValid = true
|
|
||||||
let integration, baseDatasource, datasource
|
|
||||||
let queryList
|
|
||||||
const querySchema = {
|
const querySchema = {
|
||||||
name: {},
|
name: {},
|
||||||
queryVerb: { displayName: "Method" },
|
queryVerb: { displayName: "Method" },
|
||||||
}
|
}
|
||||||
|
|
||||||
$: baseDatasource = $datasources.list.find(
|
let importQueriesModal
|
||||||
ds => ds._id === $datasources.selected
|
let changed = false
|
||||||
)
|
let isValid = true
|
||||||
|
let integration, baseDatasource, datasource
|
||||||
|
let queryList
|
||||||
|
|
||||||
|
$: baseDatasource = $datasources.selected
|
||||||
$: queryList = $queries.list.filter(
|
$: queryList = $queries.list.filter(
|
||||||
query => query.datasourceId === datasource?._id
|
query => query.datasourceId === datasource?._id
|
||||||
)
|
)
|
||||||
$: hasChanged(baseDatasource, datasource)
|
$: hasChanged(baseDatasource, datasource)
|
||||||
$: updateDatasource(baseDatasource)
|
$: updateDatasource(baseDatasource)
|
||||||
|
|
||||||
function hasChanged(base, ds) {
|
const hasChanged = (base, ds) => {
|
||||||
if (base && ds) {
|
if (base && ds) {
|
||||||
changed = !isEqual(base, ds)
|
changed = !isEqual(base, ds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveDatasource() {
|
const saveDatasource = async () => {
|
||||||
try {
|
try {
|
||||||
// Create datasource
|
// Create datasource
|
||||||
await datasources.save(datasource)
|
await datasources.save(datasource)
|
||||||
|
@ -63,12 +60,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickQuery(query) {
|
const updateDatasource = base => {
|
||||||
queries.select(query)
|
|
||||||
$goto(`./${query._id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDatasource(base) {
|
|
||||||
if (base) {
|
if (base) {
|
||||||
datasource = cloneDeep(base)
|
datasource = cloneDeep(base)
|
||||||
integration = $integrations[datasource.source]
|
integration = $integrations[datasource.source]
|
||||||
|
@ -87,7 +79,7 @@
|
||||||
|
|
||||||
{#if datasource && integration}
|
{#if datasource && integration}
|
||||||
<section>
|
<section>
|
||||||
<Layout>
|
<Layout noPadding>
|
||||||
<Layout gap="XS" noPadding>
|
<Layout gap="XS" noPadding>
|
||||||
<header>
|
<header>
|
||||||
<svelte:component
|
<svelte:component
|
||||||
|
@ -95,16 +87,16 @@
|
||||||
height="26"
|
height="26"
|
||||||
width="26"
|
width="26"
|
||||||
/>
|
/>
|
||||||
<Heading size="M">{datasource.name}</Heading>
|
<Heading size="M">{$datasources.selected?.name}</Heading>
|
||||||
</header>
|
</header>
|
||||||
<Body size="M">{integration.description}</Body>
|
<Body size="M">{integration.description}</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider />
|
<Divider />
|
||||||
<div class="config-header">
|
<div class="config-header">
|
||||||
<Heading size="S">Configuration</Heading>
|
<Heading size="S">Configuration</Heading>
|
||||||
<Button disabled={!changed || !isValid} cta on:click={saveDatasource}
|
<Button disabled={!changed || !isValid} cta on:click={saveDatasource}>
|
||||||
>Save</Button
|
Save
|
||||||
>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<IntegrationConfigForm
|
<IntegrationConfigForm
|
||||||
on:change={hasChanged}
|
on:change={hasChanged}
|
||||||
|
@ -120,12 +112,16 @@
|
||||||
<Heading size="S">Queries</Heading>
|
<Heading size="S">Queries</Heading>
|
||||||
<div class="query-buttons">
|
<div class="query-buttons">
|
||||||
{#if datasource?.source === IntegrationTypes.REST}
|
{#if datasource?.source === IntegrationTypes.REST}
|
||||||
<Button secondary on:click={() => importQueriesModal.show()}
|
<Button secondary on:click={() => importQueriesModal.show()}>
|
||||||
>Import</Button
|
Import
|
||||||
>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
<Button cta icon="Add" on:click={() => $goto("./new")}
|
<Button
|
||||||
>Add query
|
cta
|
||||||
|
icon="Add"
|
||||||
|
on:click={() => $goto(`../../query/new/${datasource._id}`)}
|
||||||
|
>
|
||||||
|
Add query
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -137,7 +133,7 @@
|
||||||
{#if queryList && queryList.length > 0}
|
{#if queryList && queryList.length > 0}
|
||||||
<div class="query-list">
|
<div class="query-list">
|
||||||
<Table
|
<Table
|
||||||
on:click={({ detail }) => onClickQuery(detail)}
|
on:click={({ detail }) => $goto(`../../query/${detail._id}`)}
|
||||||
schema={querySchema}
|
schema={querySchema}
|
||||||
data={queryList}
|
data={queryList}
|
||||||
allowEditColumns={false}
|
allowEditColumns={false}
|
|
@ -1,23 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params } from "@roxi/routify"
|
|
||||||
import { queries, datasources } from "stores/backend"
|
|
||||||
import { IntegrationTypes } from "constants/backend"
|
|
||||||
import { redirect } from "@roxi/routify"
|
|
||||||
|
|
||||||
let datasourceId
|
|
||||||
if ($params.query) {
|
|
||||||
const query = $queries.list.find(q => q._id === $params.query)
|
|
||||||
if (query) {
|
|
||||||
queries.select(query)
|
|
||||||
datasourceId = query.datasourceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const datasource = $datasources.list.find(
|
|
||||||
ds => ds._id === $datasources.selected || ds._id === datasourceId
|
|
||||||
)
|
|
||||||
if (datasource?.source === IntegrationTypes.REST) {
|
|
||||||
$redirect(`../rest/${$params.query}`)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -1,39 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params, redirect } from "@roxi/routify"
|
|
||||||
import { database, datasources, queries } from "stores/backend"
|
|
||||||
import QueryInterface from "components/integration/QueryViewer.svelte"
|
|
||||||
import { IntegrationTypes } from "constants/backend"
|
|
||||||
|
|
||||||
let selectedQuery, datasource
|
|
||||||
$: selectedQuery = $queries.list.find(
|
|
||||||
query => query._id === $queries.selected
|
|
||||||
) || {
|
|
||||||
datasourceId: $params.selectedDatasource,
|
|
||||||
parameters: [],
|
|
||||||
fields: {},
|
|
||||||
queryVerb: "read",
|
|
||||||
}
|
|
||||||
$: datasource = $datasources.list.find(
|
|
||||||
ds => ds._id === $params.selectedDatasource
|
|
||||||
)
|
|
||||||
$: {
|
|
||||||
if (datasource?.source === IntegrationTypes.REST) {
|
|
||||||
$redirect(`../rest/${$params.query}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="inner">
|
|
||||||
{#if $database._id && selectedQuery}
|
|
||||||
<QueryInterface query={selectedQuery} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.inner {
|
|
||||||
width: 640px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,17 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params } from "@roxi/routify"
|
|
||||||
import { datasources } from "stores/backend"
|
|
||||||
|
|
||||||
if ($params.selectedDatasource && !$params.query) {
|
|
||||||
const datasource = $datasources.list.find(
|
|
||||||
m => m._id === $params.selectedDatasource
|
|
||||||
)
|
|
||||||
if (datasource) {
|
|
||||||
datasources.select(datasource._id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#key $params.selectedDatasource}
|
|
||||||
<slot />
|
|
||||||
{/key}
|
|
|
@ -1,13 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params } from "@roxi/routify"
|
|
||||||
import { queries } from "stores/backend"
|
|
||||||
|
|
||||||
if ($params.query) {
|
|
||||||
const query = $queries.list.find(q => q._id === $params.query)
|
|
||||||
if (query) {
|
|
||||||
queries.select(query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -1,7 +0,0 @@
|
||||||
<script>
|
|
||||||
import { datasources } from "stores/backend"
|
|
||||||
|
|
||||||
datasources.select("bb_internal")
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -4,13 +4,19 @@
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons"
|
import ICONS from "components/backend/DatasourceNavigator/icons"
|
||||||
import { tables, datasources } from "stores/backend"
|
import { tables, datasources } from "stores/backend"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
$: internalTablesBySourceId = $tables.list.filter(
|
$: internalTablesBySourceId = $tables.list.filter(
|
||||||
table =>
|
table =>
|
||||||
table.type !== "external" && $datasources.selected === table.sourceId
|
table.type !== "external" && table.sourceId === BUDIBASE_INTERNAL_DB_ID
|
||||||
)
|
)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
datasources.select(BUDIBASE_INTERNAL_DB_ID)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
@ -73,7 +79,7 @@
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
border: var(--border-dark);
|
border: var(--border-dark);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2fr 0.75fr 20px;
|
grid-template-columns: 1fr auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--spacing-m);
|
padding: var(--spacing-m);
|
||||||
gap: var(--layout-xs);
|
gap: var(--layout-xs);
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
<script>
|
|
||||||
import { datasources } from "stores/backend"
|
|
||||||
import { DEFAULT_BB_DATASOURCE_ID } from "constants/backend"
|
|
||||||
|
|
||||||
datasources.select(DEFAULT_BB_DATASOURCE_ID)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -4,12 +4,18 @@
|
||||||
import ICONS from "components/backend/DatasourceNavigator/icons"
|
import ICONS from "components/backend/DatasourceNavigator/icons"
|
||||||
import { tables, datasources } from "stores/backend"
|
import { tables, datasources } from "stores/backend"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
|
import { DEFAULT_BB_DATASOURCE_ID } from "constants/backend"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
$: internalTablesBySourceId = $tables.list.filter(
|
$: internalTablesBySourceId = $tables.list.filter(
|
||||||
table =>
|
table =>
|
||||||
table.type !== "external" && $datasources.selected === table.sourceId
|
table.type !== "external" && table.sourceId === DEFAULT_BB_DATASOURCE_ID
|
||||||
)
|
)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
datasources.select(DEFAULT_BB_DATASOURCE_ID)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
@ -23,10 +29,11 @@
|
||||||
<svelte:component this={ICONS.BUDIBASE} height="26" width="26" />
|
<svelte:component this={ICONS.BUDIBASE} height="26" width="26" />
|
||||||
<Heading size="M">Sample Data</Heading>
|
<Heading size="M">Sample Data</Heading>
|
||||||
</header>
|
</header>
|
||||||
<Body size="M">A little something to get you up and running!</Body>
|
<Body size="M">
|
||||||
<Body size="M"
|
A little something to get you up and running!
|
||||||
>If you have no need for this datasource, feel free to delete it.</Body
|
<br />
|
||||||
>
|
If you have no need for this datasource, feel free to delete it.
|
||||||
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Heading size="S">Tables</Heading>
|
<Heading size="S">Tables</Heading>
|
||||||
|
@ -73,7 +80,7 @@
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
border: var(--border-dark);
|
border: var(--border-dark);
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2fr 0.75fr 20px;
|
grid-template-columns: 1fr auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--spacing-m);
|
padding: var(--spacing-m);
|
||||||
gap: var(--layout-xs);
|
gap: var(--layout-xs);
|
||||||
|
|
|
@ -4,12 +4,16 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// navigate to first table in list, if not already selected
|
const { list, selected } = $datasources
|
||||||
$datasources.list.length > 0 && $redirect(`./${$datasources.list[0]._id}`)
|
if (selected) {
|
||||||
|
$redirect(`./${selected?._id}`)
|
||||||
|
} else {
|
||||||
|
$redirect(`./${list[0]._id}`)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $datasources.list.length === 0}
|
{#if !$datasources.list?.length}
|
||||||
<i>Connect your first datasource to start building.</i>
|
<i>Connect your first datasource to start building.</i>
|
||||||
{:else}<i>Select a datasource to edit</i>{/if}
|
{:else}<i>Select a datasource to edit</i>{/if}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script>
|
||||||
|
import { queries } from "stores/backend"
|
||||||
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
|
import * as routify from "@roxi/routify"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
const stopSyncing = syncURLToState({
|
||||||
|
urlParam: "queryId",
|
||||||
|
stateKey: "selectedQueryId",
|
||||||
|
validate: id => id === "new" || $queries.list?.some(q => q._id === id),
|
||||||
|
update: queries.select,
|
||||||
|
fallbackUrl: "../",
|
||||||
|
store: queries,
|
||||||
|
routify,
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(stopSyncing)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#key $queries.selectedQueryId}
|
||||||
|
<slot />
|
||||||
|
{/key}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script>
|
||||||
|
import { database, queries, datasources } from "stores/backend"
|
||||||
|
import QueryViewer from "components/integration/QueryViewer.svelte"
|
||||||
|
import RestQueryViewer from "components/integration/RestQueryViewer.svelte"
|
||||||
|
import { IntegrationTypes } from "constants/backend"
|
||||||
|
|
||||||
|
$: query = $queries.selected
|
||||||
|
$: datasource = $datasources.list.find(ds => ds._id === query?.datasourceId)
|
||||||
|
$: isRestQuery = datasource?.source === IntegrationTypes.REST
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $database._id && query}
|
||||||
|
{#if isRestQuery}
|
||||||
|
<RestQueryViewer queryId={$queries.selectedQueryId} />
|
||||||
|
{:else}
|
||||||
|
<QueryViewer {query} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { queries } from "stores/backend"
|
||||||
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const { list, selected } = $queries
|
||||||
|
if (selected) {
|
||||||
|
$redirect(`./${selected?._id}`)
|
||||||
|
} else if (list?.length) {
|
||||||
|
$redirect(`./${list[0]._id}`)
|
||||||
|
} else {
|
||||||
|
$redirect("../")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
import { params, redirect } from "@roxi/routify"
|
||||||
|
import { database, datasources } from "stores/backend"
|
||||||
|
import QueryViewer from "components/integration/QueryViewer.svelte"
|
||||||
|
import RestQueryViewer from "components/integration/RestQueryViewer.svelte"
|
||||||
|
import { IntegrationTypes } from "constants/backend"
|
||||||
|
|
||||||
|
$: datasource = $datasources.list.find(ds => ds._id === $params.datasourceId)
|
||||||
|
$: {
|
||||||
|
if (!datasource) {
|
||||||
|
$redirect("../../../")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$: isRestQuery = datasource?.source === IntegrationTypes.REST
|
||||||
|
$: query = buildNewQuery(isRestQuery)
|
||||||
|
|
||||||
|
const buildNewQuery = isRestQuery => {
|
||||||
|
let query = {
|
||||||
|
datasourceId: $params.datasourceId,
|
||||||
|
parameters: [],
|
||||||
|
fields: {},
|
||||||
|
queryVerb: "read",
|
||||||
|
}
|
||||||
|
if (isRestQuery) {
|
||||||
|
query.flags = {}
|
||||||
|
query.fields = { disabledHeaders: {}, headers: {} }
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $database._id && datasource && query}
|
||||||
|
{#if isRestQuery}
|
||||||
|
<RestQueryViewer />
|
||||||
|
{:else}
|
||||||
|
<QueryViewer {query} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
|
$redirect("../")
|
||||||
|
</script>
|
|
@ -1,13 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params } from "@roxi/routify"
|
|
||||||
import { tables } from "stores/backend"
|
|
||||||
|
|
||||||
if ($params.selectedTable) {
|
|
||||||
const table = $tables.list.find(m => m._id === $params.selectedTable)
|
|
||||||
if (table) {
|
|
||||||
tables.select(table)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script>
|
||||||
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
|
import { tables } from "stores/backend"
|
||||||
|
import * as routify from "@roxi/routify"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
const stopSyncing = syncURLToState({
|
||||||
|
urlParam: "tableId",
|
||||||
|
stateKey: "selectedTableId",
|
||||||
|
validate: id => $tables.list?.some(table => table._id === id),
|
||||||
|
update: tables.select,
|
||||||
|
fallbackUrl: "../",
|
||||||
|
store: tables,
|
||||||
|
routify,
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(stopSyncing)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
|
@ -3,9 +3,11 @@
|
||||||
import { tables, database } from "stores/backend"
|
import { tables, database } from "stores/backend"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $database?._id && $tables?.selected?.name}
|
{#if $database?._id && $tables?.selected}
|
||||||
<TableDataTable />
|
<TableDataTable />
|
||||||
{:else}<i>Create your first table to start building</i>{/if}
|
{:else}
|
||||||
|
<i>Create your first table to start building</i>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
i {
|
i {
|
|
@ -4,7 +4,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<RelationshipDataTable
|
<RelationshipDataTable
|
||||||
tableId={$params.selectedTable}
|
tableId={$params.tableId}
|
||||||
rowId={$params.selectedRow}
|
rowId={$params.rowId}
|
||||||
fieldName={decodeURI($params.selectedField)}
|
fieldName={decodeURI($params.field)}
|
||||||
/>
|
/>
|
|
@ -1,19 +0,0 @@
|
||||||
<script>
|
|
||||||
import { tables } from "stores/backend"
|
|
||||||
import { redirect, leftover } from "@roxi/routify"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
// navigate to first table in list, if not already selected
|
|
||||||
// and this is the final url (i.e. no selectedTable)
|
|
||||||
if (
|
|
||||||
!$leftover &&
|
|
||||||
$tables.list.length > 0 &&
|
|
||||||
(!$tables.selected || !$tables.selected._id)
|
|
||||||
) {
|
|
||||||
$redirect(`./${$tables.list[0]._id}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -1,14 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
import { redirect } from "@roxi/routify"
|
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
$tables.list.length > 0 && $redirect(`./${$tables.list[0]._id}`)
|
const { list, selected } = $tables
|
||||||
|
if (selected) {
|
||||||
|
$redirect(`./${selected?._id}`)
|
||||||
|
} else if (list?.length) {
|
||||||
|
$redirect(`./${list[0]._id}`)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $tables.list.length === 0}
|
{#if !$tables.list?.length}
|
||||||
<i>Create your first table to start building</i>
|
<i>Create your first table to start building</i>
|
||||||
{:else}<i>Select a table to edit</i>{/if}
|
{:else}<i>Select a table to edit</i>{/if}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params } from "@roxi/routify"
|
|
||||||
import { tables, views } from "stores/backend"
|
|
||||||
|
|
||||||
if ($params.selectedView) {
|
|
||||||
let view
|
|
||||||
const viewName = decodeURI($params.selectedView)
|
|
||||||
for (let table of $tables.list) {
|
|
||||||
if (table.views && table.views[viewName]) {
|
|
||||||
view = table.views[viewName]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (view) {
|
|
||||||
views.select({
|
|
||||||
name: viewName,
|
|
||||||
...view,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script>
|
||||||
|
import { views } from "stores/backend"
|
||||||
|
import { syncURLToState } from "helpers/urlStateSync"
|
||||||
|
import * as routify from "@roxi/routify"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
|
const stopSyncing = syncURLToState({
|
||||||
|
urlParam: "viewName",
|
||||||
|
stateKey: "selectedViewName",
|
||||||
|
validate: name => $views.list?.some(view => view.name === name),
|
||||||
|
update: views.select,
|
||||||
|
fallbackUrl: "../",
|
||||||
|
store: views,
|
||||||
|
routify,
|
||||||
|
decode: decodeURIComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(stopSyncing)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import { views } from "stores/backend"
|
||||||
|
import { redirect } from "@roxi/routify"
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const { list, selected } = $views
|
||||||
|
if (selected) {
|
||||||
|
$redirect(`./${encodeURIComponent(selected?.name)}`)
|
||||||
|
} else if (list?.length) {
|
||||||
|
$redirect(`./${encodeURIComponent(list[0].name)}`)
|
||||||
|
} else {
|
||||||
|
$redirect("../")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -186,7 +186,9 @@
|
||||||
<span>{$organisation?.company || "Budibase"}</span>
|
<span>{$organisation?.company || "Budibase"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="onboarding">
|
<div class="onboarding">
|
||||||
<ConfigChecklist />
|
{#if $auth.user?.admin?.global}
|
||||||
|
<ConfigChecklist />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
|
|
@ -1,20 +1,36 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, derived } from "svelte/store"
|
||||||
import { queries, tables, views } from "./"
|
import { queries, tables } from "./"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
|
||||||
export const INITIAL_DATASOURCE_VALUES = {
|
|
||||||
list: [],
|
|
||||||
selected: null,
|
|
||||||
schemaError: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDatasourcesStore() {
|
export function createDatasourcesStore() {
|
||||||
const store = writable(INITIAL_DATASOURCE_VALUES)
|
const store = writable({
|
||||||
const { subscribe, update, set } = store
|
list: [],
|
||||||
|
selectedDatasourceId: null,
|
||||||
|
schemaError: null,
|
||||||
|
})
|
||||||
|
const derivedStore = derived(store, $store => ({
|
||||||
|
...$store,
|
||||||
|
selected: $store.list?.find(ds => ds._id === $store.selectedDatasourceId),
|
||||||
|
}))
|
||||||
|
|
||||||
async function updateDatasource(response) {
|
const fetch = async () => {
|
||||||
|
const datasources = await API.getDatasources()
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
list: datasources,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const select = id => {
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
selectedDatasourceId: id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateDatasource = async response => {
|
||||||
const { datasource, error } = response
|
const { datasource, error } = response
|
||||||
update(state => {
|
store.update(state => {
|
||||||
const currentIdx = state.list.findIndex(ds => ds._id === datasource._id)
|
const currentIdx = state.list.findIndex(ds => ds._id === datasource._id)
|
||||||
const sources = state.list
|
const sources = state.list
|
||||||
if (currentIdx >= 0) {
|
if (currentIdx >= 0) {
|
||||||
|
@ -24,82 +40,64 @@ export function createDatasourcesStore() {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
list: sources,
|
list: sources,
|
||||||
selected: datasource._id,
|
selectedDatasourceId: datasource._id,
|
||||||
schemaError: error,
|
schemaError: error,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return datasource
|
return datasource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateSchema = async (datasource, tablesFilter) => {
|
||||||
|
const response = await API.buildDatasourceSchema({
|
||||||
|
datasourceId: datasource?._id,
|
||||||
|
tablesFilter,
|
||||||
|
})
|
||||||
|
return await updateDatasource(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async (body, fetchSchema = false) => {
|
||||||
|
let response
|
||||||
|
if (body._id) {
|
||||||
|
response = await API.updateDatasource(body)
|
||||||
|
} else {
|
||||||
|
response = await API.createDatasource({
|
||||||
|
datasource: body,
|
||||||
|
fetchSchema,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return updateDatasource(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteDatasource = async datasource => {
|
||||||
|
await API.deleteDatasource({
|
||||||
|
datasourceId: datasource?._id,
|
||||||
|
datasourceRev: datasource?._rev,
|
||||||
|
})
|
||||||
|
store.update(state => {
|
||||||
|
const sources = state.list.filter(
|
||||||
|
existing => existing._id !== datasource._id
|
||||||
|
)
|
||||||
|
return { list: sources, selected: null }
|
||||||
|
})
|
||||||
|
await queries.fetch()
|
||||||
|
await tables.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeSchemaError = () => {
|
||||||
|
store.update(state => {
|
||||||
|
return { ...state, schemaError: null }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe: derivedStore.subscribe,
|
||||||
update,
|
fetch,
|
||||||
init: async () => {
|
init: fetch,
|
||||||
const datasources = await API.getDatasources()
|
select,
|
||||||
set({
|
updateSchema,
|
||||||
list: datasources,
|
save,
|
||||||
selected: null,
|
delete: deleteDatasource,
|
||||||
})
|
removeSchemaError,
|
||||||
},
|
|
||||||
fetch: async () => {
|
|
||||||
const datasources = await API.getDatasources()
|
|
||||||
|
|
||||||
// Clear selected if it no longer exists, otherwise keep it
|
|
||||||
const selected = get(store).selected
|
|
||||||
let nextSelected = null
|
|
||||||
if (selected && datasources.find(source => source._id === selected)) {
|
|
||||||
nextSelected = selected
|
|
||||||
}
|
|
||||||
|
|
||||||
update(state => ({ ...state, list: datasources, selected: nextSelected }))
|
|
||||||
},
|
|
||||||
select: datasourceId => {
|
|
||||||
update(state => ({ ...state, selected: datasourceId }))
|
|
||||||
queries.unselect()
|
|
||||||
tables.unselect()
|
|
||||||
views.unselect()
|
|
||||||
},
|
|
||||||
unselect: () => {
|
|
||||||
update(state => ({ ...state, selected: null }))
|
|
||||||
},
|
|
||||||
updateSchema: async (datasource, tablesFilter) => {
|
|
||||||
const response = await API.buildDatasourceSchema({
|
|
||||||
datasourceId: datasource?._id,
|
|
||||||
tablesFilter,
|
|
||||||
})
|
|
||||||
return await updateDatasource(response)
|
|
||||||
},
|
|
||||||
save: async (body, fetchSchema = false) => {
|
|
||||||
let response
|
|
||||||
if (body._id) {
|
|
||||||
response = await API.updateDatasource(body)
|
|
||||||
} else {
|
|
||||||
response = await API.createDatasource({
|
|
||||||
datasource: body,
|
|
||||||
fetchSchema,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return updateDatasource(response)
|
|
||||||
},
|
|
||||||
delete: async datasource => {
|
|
||||||
await API.deleteDatasource({
|
|
||||||
datasourceId: datasource?._id,
|
|
||||||
datasourceRev: datasource?._rev,
|
|
||||||
})
|
|
||||||
update(state => {
|
|
||||||
const sources = state.list.filter(
|
|
||||||
existing => existing._id !== datasource._id
|
|
||||||
)
|
|
||||||
return { list: sources, selected: null }
|
|
||||||
})
|
|
||||||
await queries.fetch()
|
|
||||||
await tables.fetch()
|
|
||||||
},
|
|
||||||
removeSchemaError: () => {
|
|
||||||
update(state => {
|
|
||||||
return { ...state, schemaError: null }
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
export { database } from "./database"
|
export { database } from "./database"
|
||||||
export { tables } from "./tables"
|
export { tables } from "./tables"
|
||||||
export { views } from "./views"
|
export { views } from "./views"
|
||||||
export { rows } from "./rows"
|
|
||||||
export { permissions } from "./permissions"
|
export { permissions } from "./permissions"
|
||||||
export { roles } from "./roles"
|
export { roles } from "./roles"
|
||||||
export { datasources } from "./datasources"
|
export { datasources } from "./datasources"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get, derived } from "svelte/store"
|
||||||
import { datasources, integrations, tables, views } from "./"
|
import { datasources, integrations } from "./"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { duplicateName } from "helpers/duplicate"
|
import { duplicateName } from "helpers/duplicate"
|
||||||
|
|
||||||
|
@ -10,125 +10,127 @@ const sortQueries = queryList => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createQueriesStore() {
|
export function createQueriesStore() {
|
||||||
const store = writable({ list: [], selected: null })
|
const store = writable({
|
||||||
const { subscribe, set, update } = store
|
list: [],
|
||||||
|
selectedQueryId: null,
|
||||||
|
})
|
||||||
|
const derivedStore = derived(store, $store => ({
|
||||||
|
...$store,
|
||||||
|
selected: $store.list?.find(q => q._id === $store.selectedQueryId),
|
||||||
|
}))
|
||||||
|
|
||||||
const actions = {
|
const fetch = async () => {
|
||||||
init: async () => {
|
const queries = await API.getQueries()
|
||||||
const queries = await API.getQueries()
|
sortQueries(queries)
|
||||||
set({
|
store.update(state => ({
|
||||||
list: queries,
|
...state,
|
||||||
selected: null,
|
list: queries,
|
||||||
})
|
}))
|
||||||
},
|
}
|
||||||
fetch: async () => {
|
|
||||||
const queries = await API.getQueries()
|
const save = async (datasourceId, query) => {
|
||||||
|
const _integrations = get(integrations)
|
||||||
|
const dataSource = get(datasources).list.filter(
|
||||||
|
ds => ds._id === datasourceId
|
||||||
|
)
|
||||||
|
// Check if readable attribute is found
|
||||||
|
if (dataSource.length !== 0) {
|
||||||
|
const integration = _integrations[dataSource[0].source]
|
||||||
|
const readable = integration.query[query.queryVerb].readable
|
||||||
|
if (readable) {
|
||||||
|
query.readable = readable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
query.datasourceId = datasourceId
|
||||||
|
const savedQuery = await API.saveQuery(query)
|
||||||
|
store.update(state => {
|
||||||
|
const idx = state.list.findIndex(query => query._id === savedQuery._id)
|
||||||
|
const queries = state.list
|
||||||
|
if (idx >= 0) {
|
||||||
|
queries.splice(idx, 1, savedQuery)
|
||||||
|
} else {
|
||||||
|
queries.push(savedQuery)
|
||||||
|
}
|
||||||
sortQueries(queries)
|
sortQueries(queries)
|
||||||
update(state => ({
|
return {
|
||||||
...state,
|
|
||||||
list: queries,
|
list: queries,
|
||||||
}))
|
selectedQueryId: savedQuery._id,
|
||||||
},
|
|
||||||
save: async (datasourceId, query) => {
|
|
||||||
const _integrations = get(integrations)
|
|
||||||
const dataSource = get(datasources).list.filter(
|
|
||||||
ds => ds._id === datasourceId
|
|
||||||
)
|
|
||||||
// Check if readable attribute is found
|
|
||||||
if (dataSource.length !== 0) {
|
|
||||||
const integration = _integrations[dataSource[0].source]
|
|
||||||
const readable = integration.query[query.queryVerb].readable
|
|
||||||
if (readable) {
|
|
||||||
query.readable = readable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
query.datasourceId = datasourceId
|
})
|
||||||
const savedQuery = await API.saveQuery(query)
|
return savedQuery
|
||||||
update(state => {
|
}
|
||||||
const idx = state.list.findIndex(query => query._id === savedQuery._id)
|
|
||||||
const queries = state.list
|
|
||||||
if (idx >= 0) {
|
|
||||||
queries.splice(idx, 1, savedQuery)
|
|
||||||
} else {
|
|
||||||
queries.push(savedQuery)
|
|
||||||
}
|
|
||||||
sortQueries(queries)
|
|
||||||
return {
|
|
||||||
list: queries,
|
|
||||||
selected: savedQuery._id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return savedQuery
|
|
||||||
},
|
|
||||||
import: async ({ data, datasourceId }) => {
|
|
||||||
return await API.importQueries({
|
|
||||||
datasourceId,
|
|
||||||
data,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
select: query => {
|
|
||||||
update(state => ({ ...state, selected: query._id }))
|
|
||||||
views.unselect()
|
|
||||||
tables.unselect()
|
|
||||||
datasources.unselect()
|
|
||||||
},
|
|
||||||
unselect: () => {
|
|
||||||
update(state => ({ ...state, selected: null }))
|
|
||||||
},
|
|
||||||
preview: async query => {
|
|
||||||
const parameters = query.parameters.reduce(
|
|
||||||
(acc, next) => ({
|
|
||||||
...acc,
|
|
||||||
[next.name]: next.default,
|
|
||||||
}),
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
const result = await API.previewQuery({
|
|
||||||
...query,
|
|
||||||
parameters,
|
|
||||||
})
|
|
||||||
// Assume all the fields are strings and create a basic schema from the
|
|
||||||
// unique fields returned by the server
|
|
||||||
const schema = {}
|
|
||||||
for (let [field, type] of Object.entries(result.schemaFields)) {
|
|
||||||
schema[field] = type || "string"
|
|
||||||
}
|
|
||||||
return { ...result, schema, rows: result.rows || [] }
|
|
||||||
},
|
|
||||||
delete: async query => {
|
|
||||||
await API.deleteQuery({
|
|
||||||
queryId: query?._id,
|
|
||||||
queryRev: query?._rev,
|
|
||||||
})
|
|
||||||
update(state => {
|
|
||||||
state.list = state.list.filter(existing => existing._id !== query._id)
|
|
||||||
if (state.selected === query._id) {
|
|
||||||
state.selected = null
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
duplicate: async query => {
|
|
||||||
let list = get(store).list
|
|
||||||
const newQuery = { ...query }
|
|
||||||
const datasourceId = query.datasourceId
|
|
||||||
|
|
||||||
delete newQuery._id
|
const importQueries = async ({ data, datasourceId }) => {
|
||||||
delete newQuery._rev
|
return await API.importQueries({
|
||||||
newQuery.name = duplicateName(
|
datasourceId,
|
||||||
query.name,
|
data,
|
||||||
list.map(q => q.name)
|
})
|
||||||
)
|
}
|
||||||
|
|
||||||
return actions.save(datasourceId, newQuery)
|
const select = id => {
|
||||||
},
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
selectedQueryId: id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const preview = async query => {
|
||||||
|
const parameters = query.parameters.reduce(
|
||||||
|
(acc, next) => ({
|
||||||
|
...acc,
|
||||||
|
[next.name]: next.default,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
const result = await API.previewQuery({
|
||||||
|
...query,
|
||||||
|
parameters,
|
||||||
|
})
|
||||||
|
// Assume all the fields are strings and create a basic schema from the
|
||||||
|
// unique fields returned by the server
|
||||||
|
const schema = {}
|
||||||
|
for (let [field, type] of Object.entries(result.schemaFields)) {
|
||||||
|
schema[field] = type || "string"
|
||||||
|
}
|
||||||
|
return { ...result, schema, rows: result.rows || [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteQuery = async query => {
|
||||||
|
await API.deleteQuery({
|
||||||
|
queryId: query?._id,
|
||||||
|
queryRev: query?._rev,
|
||||||
|
})
|
||||||
|
store.update(state => {
|
||||||
|
state.list = state.list.filter(existing => existing._id !== query._id)
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicate = async query => {
|
||||||
|
let list = get(store).list
|
||||||
|
const newQuery = { ...query }
|
||||||
|
const datasourceId = query.datasourceId
|
||||||
|
|
||||||
|
delete newQuery._id
|
||||||
|
delete newQuery._rev
|
||||||
|
newQuery.name = duplicateName(
|
||||||
|
query.name,
|
||||||
|
list.map(q => q.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
return await save(datasourceId, newQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe: derivedStore.subscribe,
|
||||||
set,
|
fetch,
|
||||||
update,
|
init: fetch,
|
||||||
...actions,
|
select,
|
||||||
|
save,
|
||||||
|
import: importQueries,
|
||||||
|
delete: deleteQuery,
|
||||||
|
preview,
|
||||||
|
duplicate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { writable, get } from "svelte/store"
|
|
||||||
import { tables } from "./"
|
|
||||||
|
|
||||||
export function createRowsStore() {
|
|
||||||
const { subscribe } = writable([])
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe,
|
|
||||||
save: () => tables.select(get(tables).selected),
|
|
||||||
delete: () => tables.select(get(tables).selected),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const rows = createRowsStore()
|
|
|
@ -1,41 +1,35 @@
|
||||||
import { get, writable } from "svelte/store"
|
import { get, writable, derived } from "svelte/store"
|
||||||
import { datasources, queries, views } from "./"
|
import { datasources } from "./"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { SWITCHABLE_TYPES } from "constants/backend"
|
import { SWITCHABLE_TYPES } from "constants/backend"
|
||||||
|
|
||||||
export function createTablesStore() {
|
export function createTablesStore() {
|
||||||
const store = writable({})
|
const store = writable({
|
||||||
const { subscribe, update, set } = store
|
list: [],
|
||||||
|
selectedTableId: null,
|
||||||
|
})
|
||||||
|
const derivedStore = derived(store, $store => ({
|
||||||
|
...$store,
|
||||||
|
selected: $store.list?.find(table => table._id === $store.selectedTableId),
|
||||||
|
}))
|
||||||
|
|
||||||
async function fetch() {
|
const fetch = async () => {
|
||||||
const tables = await API.getTables()
|
const tables = await API.getTables()
|
||||||
update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
list: tables,
|
list: tables,
|
||||||
}))
|
}))
|
||||||
return tables
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function select(table) {
|
const select = tableId => {
|
||||||
if (!table) {
|
store.update(state => ({
|
||||||
update(state => ({
|
...state,
|
||||||
...state,
|
selectedTableId: tableId,
|
||||||
selected: {},
|
}))
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
selected: table,
|
|
||||||
draft: cloneDeep(table),
|
|
||||||
}))
|
|
||||||
views.unselect()
|
|
||||||
queries.unselect()
|
|
||||||
datasources.unselect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(table) {
|
const save = async table => {
|
||||||
const updatedTable = cloneDeep(table)
|
const updatedTable = cloneDeep(table)
|
||||||
const oldTable = get(store).list.filter(t => t._id === table._id)[0]
|
const oldTable = get(store).list.filter(t => t._id === table._id)[0]
|
||||||
|
|
||||||
|
@ -72,96 +66,72 @@ export function createTablesStore() {
|
||||||
if (table.type === "external") {
|
if (table.type === "external") {
|
||||||
await datasources.fetch()
|
await datasources.fetch()
|
||||||
}
|
}
|
||||||
await select(savedTable)
|
await select(savedTable._id)
|
||||||
return savedTable
|
return savedTable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteTable = async table => {
|
||||||
|
await API.deleteTable({
|
||||||
|
tableId: table?._id,
|
||||||
|
tableRev: table?._rev,
|
||||||
|
})
|
||||||
|
await fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveField = async ({
|
||||||
|
originalName,
|
||||||
|
field,
|
||||||
|
primaryDisplay = false,
|
||||||
|
indexes,
|
||||||
|
}) => {
|
||||||
|
let draft = cloneDeep(get(derivedStore).selected)
|
||||||
|
|
||||||
|
// delete the original if renaming
|
||||||
|
// need to handle if the column had no name, empty string
|
||||||
|
if (originalName != null && originalName !== field.name) {
|
||||||
|
delete draft.schema[originalName]
|
||||||
|
draft._rename = {
|
||||||
|
old: originalName,
|
||||||
|
updated: field.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally set display column
|
||||||
|
if (primaryDisplay) {
|
||||||
|
draft.primaryDisplay = field.name
|
||||||
|
} else if (draft.primaryDisplay === originalName) {
|
||||||
|
const fields = Object.keys(draft.schema)
|
||||||
|
// pick another display column randomly if unselecting
|
||||||
|
draft.primaryDisplay = fields.filter(
|
||||||
|
name => name !== originalName || name !== field
|
||||||
|
)[0]
|
||||||
|
}
|
||||||
|
if (indexes) {
|
||||||
|
draft.indexes = indexes
|
||||||
|
}
|
||||||
|
draft.schema = {
|
||||||
|
...draft.schema,
|
||||||
|
[field.name]: cloneDeep(field),
|
||||||
|
}
|
||||||
|
|
||||||
|
await save(draft)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteField = async field => {
|
||||||
|
let draft = cloneDeep(get(derivedStore).selected)
|
||||||
|
delete draft.schema[field.name]
|
||||||
|
await save(draft)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe: derivedStore.subscribe,
|
||||||
update,
|
|
||||||
fetch,
|
fetch,
|
||||||
|
init: fetch,
|
||||||
select,
|
select,
|
||||||
unselect: () => {
|
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
selected: null,
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
save,
|
save,
|
||||||
init: async () => {
|
delete: deleteTable,
|
||||||
const tables = await API.getTables()
|
saveField,
|
||||||
set({
|
deleteField,
|
||||||
list: tables,
|
|
||||||
selected: {},
|
|
||||||
draft: {},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
delete: async table => {
|
|
||||||
await API.deleteTable({
|
|
||||||
tableId: table?._id,
|
|
||||||
tableRev: table?._rev,
|
|
||||||
})
|
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
list: state.list.filter(existing => existing._id !== table._id),
|
|
||||||
selected: {},
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
saveField: async ({
|
|
||||||
originalName,
|
|
||||||
field,
|
|
||||||
primaryDisplay = false,
|
|
||||||
indexes,
|
|
||||||
}) => {
|
|
||||||
let promise
|
|
||||||
update(state => {
|
|
||||||
// delete the original if renaming
|
|
||||||
// need to handle if the column had no name, empty string
|
|
||||||
if (originalName != null && originalName !== field.name) {
|
|
||||||
delete state.draft.schema[originalName]
|
|
||||||
state.draft._rename = {
|
|
||||||
old: originalName,
|
|
||||||
updated: field.name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally set display column
|
|
||||||
if (primaryDisplay) {
|
|
||||||
state.draft.primaryDisplay = field.name
|
|
||||||
} else if (state.draft.primaryDisplay === originalName) {
|
|
||||||
const fields = Object.keys(state.draft.schema)
|
|
||||||
// pick another display column randomly if unselecting
|
|
||||||
state.draft.primaryDisplay = fields.filter(
|
|
||||||
name => name !== originalName || name !== field
|
|
||||||
)[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexes) {
|
|
||||||
state.draft.indexes = indexes
|
|
||||||
}
|
|
||||||
|
|
||||||
state.draft.schema = {
|
|
||||||
...state.draft.schema,
|
|
||||||
[field.name]: cloneDeep(field),
|
|
||||||
}
|
|
||||||
promise = save(state.draft)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
if (promise) {
|
|
||||||
await promise
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deleteField: async field => {
|
|
||||||
let promise
|
|
||||||
update(state => {
|
|
||||||
delete state.draft.schema[field.name]
|
|
||||||
promise = save(state.draft)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
if (promise) {
|
|
||||||
await promise
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,52 +1,54 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get, derived } from "svelte/store"
|
||||||
import { tables, datasources, queries } from "./"
|
import { tables } from "./"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
|
||||||
export function createViewsStore() {
|
export function createViewsStore() {
|
||||||
const { subscribe, update } = writable({
|
const store = writable({
|
||||||
list: [],
|
selectedViewName: null,
|
||||||
selected: null,
|
})
|
||||||
|
const derivedStore = derived([store, tables], ([$store, $tables]) => {
|
||||||
|
let list = []
|
||||||
|
$tables.list?.forEach(table => {
|
||||||
|
list = list.concat(Object.values(table?.views || {}))
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
...$store,
|
||||||
|
list,
|
||||||
|
selected: list.find(view => view.name === $store.selectedViewName),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const select = name => {
|
||||||
|
store.update(state => ({
|
||||||
|
...state,
|
||||||
|
selectedViewName: name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteView = async view => {
|
||||||
|
await API.deleteView(view)
|
||||||
|
await tables.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async view => {
|
||||||
|
const savedView = await API.saveView(view)
|
||||||
|
const viewMeta = {
|
||||||
|
name: view.name,
|
||||||
|
...savedView,
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewTable = get(tables).list.find(table => table._id === view.tableId)
|
||||||
|
|
||||||
|
if (view.originalName) delete viewTable.views[view.originalName]
|
||||||
|
viewTable.views[view.name] = viewMeta
|
||||||
|
await tables.save(viewTable)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe: derivedStore.subscribe,
|
||||||
update,
|
select,
|
||||||
select: view => {
|
delete: deleteView,
|
||||||
update(state => ({
|
save,
|
||||||
...state,
|
|
||||||
selected: view,
|
|
||||||
}))
|
|
||||||
tables.unselect()
|
|
||||||
queries.unselect()
|
|
||||||
datasources.unselect()
|
|
||||||
},
|
|
||||||
unselect: () => {
|
|
||||||
update(state => ({
|
|
||||||
...state,
|
|
||||||
selected: null,
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
delete: async view => {
|
|
||||||
await API.deleteView(view)
|
|
||||||
await tables.fetch()
|
|
||||||
},
|
|
||||||
save: async view => {
|
|
||||||
const savedView = await API.saveView(view)
|
|
||||||
const viewMeta = {
|
|
||||||
name: view.name,
|
|
||||||
...savedView,
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewTable = get(tables).list.find(
|
|
||||||
table => table._id === view.tableId
|
|
||||||
)
|
|
||||||
|
|
||||||
if (view.originalName) delete viewTable.views[view.originalName]
|
|
||||||
viewTable.views[view.name] = viewMeta
|
|
||||||
await tables.save(viewTable)
|
|
||||||
|
|
||||||
update(state => ({ ...state, selected: viewMeta }))
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "2.2.4-alpha.4",
|
"version": "2.2.10-alpha.11",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
"outputPath": "build"
|
"outputPath": "build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.2.4-alpha.4",
|
"@budibase/backend-core": "2.2.10-alpha.11",
|
||||||
"@budibase/string-templates": "2.2.4-alpha.4",
|
"@budibase/string-templates": "2.2.10-alpha.11",
|
||||||
"@budibase/types": "2.2.4-alpha.4",
|
"@budibase/types": "2.2.10-alpha.11",
|
||||||
"axios": "0.21.2",
|
"axios": "0.21.2",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"cli-progress": "3.11.2",
|
"cli-progress": "3.11.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "2.2.4-alpha.4",
|
"version": "2.2.10-alpha.11",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.2.4-alpha.4",
|
"@budibase/bbui": "2.2.10-alpha.11",
|
||||||
"@budibase/frontend-core": "2.2.4-alpha.4",
|
"@budibase/frontend-core": "2.2.10-alpha.11",
|
||||||
"@budibase/string-templates": "2.2.4-alpha.4",
|
"@budibase/string-templates": "2.2.10-alpha.11",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "2.2.4-alpha.4",
|
"version": "2.2.10-alpha.11",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.2.4-alpha.4",
|
"@budibase/bbui": "2.2.10-alpha.11",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,11 @@ export const buildAppEndpoints = API => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deploys the current app.
|
* Publishes the current app.
|
||||||
*/
|
*/
|
||||||
deployAppChanges: async () => {
|
publishAppChanges: async appId => {
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: "/api/deploy",
|
url: `/api/applications/${appId}/publish`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -98,8 +98,8 @@ export const buildAppEndpoints = API => ({
|
||||||
* @param appId the production ID of the app to unpublish
|
* @param appId the production ID of the app to unpublish
|
||||||
*/
|
*/
|
||||||
unpublishApp: async appId => {
|
unpublishApp: async appId => {
|
||||||
return await API.delete({
|
return await API.post({
|
||||||
url: `/api/applications/${appId}?unpublish=1`,
|
url: `/api/applications/${appId}/unpublish`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ export const buildViewEndpoints = API => ({
|
||||||
params.set("group", groupBy)
|
params.set("group", groupBy)
|
||||||
}
|
}
|
||||||
const QUERY_VIEW_URL = field
|
const QUERY_VIEW_URL = field
|
||||||
? `/api/views/${name}?${params}`
|
? `/api/views/${encodeURIComponent(name)}?${params}`
|
||||||
: `/api/views/${name}`
|
: `/api/views/${encodeURIComponent(name)}`
|
||||||
return await API.get({ url: QUERY_VIEW_URL })
|
return await API.get({ url: QUERY_VIEW_URL })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ export const buildViewEndpoints = API => ({
|
||||||
*/
|
*/
|
||||||
deleteView: async viewName => {
|
deleteView: async viewName => {
|
||||||
return await API.delete({
|
return await API.delete({
|
||||||
url: `/api/views/${viewName}`,
|
url: `/api/views/${encodeURIComponent(viewName)}`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "2.2.4-alpha.4",
|
"version": "2.2.10-alpha.11",
|
||||||
"description": "Budibase Public API SDK",
|
"description": "Budibase Public API SDK",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.2.4-alpha.4",
|
"version": "2.2.10-alpha.11",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -43,11 +43,11 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.0.3",
|
"@apidevtools/swagger-parser": "10.0.3",
|
||||||
"@budibase/backend-core": "2.2.4-alpha.4",
|
"@budibase/backend-core": "2.2.10-alpha.11",
|
||||||
"@budibase/client": "2.2.4-alpha.4",
|
"@budibase/client": "2.2.10-alpha.11",
|
||||||
"@budibase/pro": "2.2.4-alpha.4",
|
"@budibase/pro": "2.2.10-alpha.11",
|
||||||
"@budibase/string-templates": "2.2.4-alpha.4",
|
"@budibase/string-templates": "2.2.10-alpha.11",
|
||||||
"@budibase/types": "2.2.4-alpha.4",
|
"@budibase/types": "2.2.10-alpha.11",
|
||||||
"@bull-board/api": "3.7.0",
|
"@bull-board/api": "3.7.0",
|
||||||
"@bull-board/koa": "3.9.4",
|
"@bull-board/koa": "3.9.4",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -2,12 +2,6 @@
|
||||||
const compose = require("docker-compose")
|
const compose = require("docker-compose")
|
||||||
const path = require("path")
|
const path = require("path")
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const isWsl = require("is-wsl")
|
|
||||||
const { processStringSync } = require("@budibase/string-templates")
|
|
||||||
|
|
||||||
function isLinux() {
|
|
||||||
return !isWsl && process.platform !== "darwin" && process.platform !== "win32"
|
|
||||||
}
|
|
||||||
|
|
||||||
// This script wraps docker-compose allowing you to manage your dev infrastructure with simple commands.
|
// This script wraps docker-compose allowing you to manage your dev infrastructure with simple commands.
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
|
@ -23,16 +17,6 @@ const Commands = {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
// generate nginx file, always do this incase it has changed
|
|
||||||
const hostingPath = path.join(process.cwd(), "..", "..", "hosting")
|
|
||||||
const nginxHbsPath = path.join(hostingPath, "nginx.dev.conf.hbs")
|
|
||||||
const nginxOutputPath = path.join(hostingPath, ".generated-nginx.dev.conf")
|
|
||||||
const contents = fs.readFileSync(nginxHbsPath, "utf8")
|
|
||||||
const config = {
|
|
||||||
address: isLinux() ? "172.17.0.1" : "host.docker.internal",
|
|
||||||
}
|
|
||||||
fs.writeFileSync(nginxOutputPath, processStringSync(contents, config))
|
|
||||||
|
|
||||||
const envFilePath = path.join(process.cwd(), ".env")
|
const envFilePath = path.join(process.cwd(), ".env")
|
||||||
if (!fs.existsSync(envFilePath)) {
|
if (!fs.existsSync(envFilePath)) {
|
||||||
const envFileJson = {
|
const envFileJson = {
|
||||||
|
|
|
@ -567,6 +567,40 @@
|
||||||
"data"
|
"data"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"deploymentOutput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"_id": {
|
||||||
|
"description": "The ID of the app.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"description": "Status of the deployment, whether it succeeded or failed",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"SUCCESS",
|
||||||
|
"FAILURE"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"appUrl": {
|
||||||
|
"description": "The URL of the published app",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"_id",
|
||||||
|
"status",
|
||||||
|
"appUrl"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"data"
|
||||||
|
]
|
||||||
|
},
|
||||||
"row": {
|
"row": {
|
||||||
"description": "The row to be created/updated, based on the table schema.",
|
"description": "The row to be created/updated, based on the table schema.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -1933,6 +1967,56 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/applications/{appId}/unpublish": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "unpublish",
|
||||||
|
"summary": "Unpublish an application",
|
||||||
|
"tags": [
|
||||||
|
"applications"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/appIdUrl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "The app was published successfully."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/applications/{appId}/publish": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "publish",
|
||||||
|
"summary": "Unpublish an application",
|
||||||
|
"tags": [
|
||||||
|
"applications"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/appIdUrl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Returns the deployment object.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/deploymentOutput"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"deployment": {
|
||||||
|
"$ref": "#/components/examples/deploymentOutput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/applications/search": {
|
"/applications/search": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "search",
|
"operationId": "search",
|
||||||
|
|
|
@ -411,6 +411,30 @@ components:
|
||||||
- version
|
- version
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
|
deploymentOutput:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
_id:
|
||||||
|
description: The ID of the app.
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
description: Status of the deployment, whether it succeeded or failed
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- SUCCESS
|
||||||
|
- FAILURE
|
||||||
|
appUrl:
|
||||||
|
description: The URL of the published app
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- _id
|
||||||
|
- status
|
||||||
|
- appUrl
|
||||||
|
required:
|
||||||
|
- data
|
||||||
row:
|
row:
|
||||||
description: The row to be created/updated, based on the table schema.
|
description: The row to be created/updated, based on the table schema.
|
||||||
type: object
|
type: object
|
||||||
|
@ -1453,6 +1477,35 @@ paths:
|
||||||
examples:
|
examples:
|
||||||
application:
|
application:
|
||||||
$ref: "#/components/examples/application"
|
$ref: "#/components/examples/application"
|
||||||
|
"/applications/{appId}/unpublish":
|
||||||
|
post:
|
||||||
|
operationId: unpublish
|
||||||
|
summary: Unpublish an application
|
||||||
|
tags:
|
||||||
|
- applications
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/appIdUrl"
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: The app was published successfully.
|
||||||
|
"/applications/{appId}/publish":
|
||||||
|
post:
|
||||||
|
operationId: publish
|
||||||
|
summary: Unpublish an application
|
||||||
|
tags:
|
||||||
|
- applications
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/appIdUrl"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Returns the deployment object.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/deploymentOutput"
|
||||||
|
examples:
|
||||||
|
deployment:
|
||||||
|
$ref: "#/components/examples/deploymentOutput"
|
||||||
/applications/search:
|
/applications/search:
|
||||||
post:
|
post:
|
||||||
operationId: search
|
operationId: search
|
||||||
|
|
|
@ -80,6 +80,22 @@ const applicationOutputSchema = object(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const deploymentOutputSchema = object({
|
||||||
|
_id: {
|
||||||
|
description: "The ID of the app.",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
description: "Status of the deployment, whether it succeeded or failed",
|
||||||
|
type: "string",
|
||||||
|
enum: ["SUCCESS", "FAILURE"],
|
||||||
|
},
|
||||||
|
appUrl: {
|
||||||
|
description: "The URL of the published app",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
module.exports = new Resource()
|
module.exports = new Resource()
|
||||||
.setExamples({
|
.setExamples({
|
||||||
application: {
|
application: {
|
||||||
|
@ -104,4 +120,7 @@ module.exports = new Resource()
|
||||||
items: applicationOutputSchema,
|
items: applicationOutputSchema,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
deploymentOutput: object({
|
||||||
|
data: deploymentOutputSchema,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue