Merge remote-tracking branch 'origin/develop' into fix/automation-performance-ux
This commit is contained in:
commit
302ff6b270
|
@ -93,6 +93,8 @@ then `cd ` into your local copy.
|
||||||
|
|
||||||
#### 3. Install and Build
|
#### 3. Install and Build
|
||||||
|
|
||||||
|
| **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash)
|
||||||
|
|
||||||
To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed.
|
To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed.
|
||||||
|
|
||||||
##### Quick method
|
##### Quick method
|
||||||
|
|
|
@ -7,6 +7,15 @@ assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Hosting**
|
||||||
|
<!-- Delete as appropriate -->
|
||||||
|
- Self
|
||||||
|
- Method: <method> <!-- One of: k8s, docker single image, docker compose, digital ocean: -->
|
||||||
|
- Budibase Version: <version> <!-- e.g. 1.0.105 -->
|
||||||
|
- App Version: <version> <!-- Indicate app version if bug is related to an application -->
|
||||||
|
- Cloud
|
||||||
|
- Tenant ID: <tenantId> <!-- shown in URL as <tenantID>.budibase.app -->
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ staleLabel: stale
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
markComment: >
|
markComment: >
|
||||||
This issue has been automatically marked as stale because it has not had
|
This issue has been automatically marked as stale because it has not had
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
recent activity.
|
||||||
for your contributions.
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
closeComment: false
|
closeComment: false
|
||||||
|
|
|
@ -12,6 +12,11 @@ on:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
|
|
||||||
|
env:
|
||||||
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
BASE_BRANCH: ${{ github.event.pull_request.base.ref}}
|
||||||
|
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -27,6 +32,10 @@ jobs:
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Install Pro
|
||||||
|
run: yarn install:pro $BRANCH $BASE_BRANCH
|
||||||
|
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
|
|
@ -19,6 +19,7 @@ env:
|
||||||
POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }}
|
POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }}
|
||||||
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
||||||
POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
|
POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
|
||||||
|
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
@ -29,6 +30,10 @@ jobs:
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
|
- name: Install Pro
|
||||||
|
run: yarn install:pro develop
|
||||||
|
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
@ -46,9 +51,9 @@ jobs:
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
# setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default
|
# setup the username and email.
|
||||||
git config user.name "Budibase Staging Release Bot"
|
git config --global user.name "Budibase Staging Release Bot"
|
||||||
git config user.email "<>"
|
git config --global user.email "<>"
|
||||||
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
||||||
yarn release:develop
|
yarn release:develop
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ env:
|
||||||
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
||||||
POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
|
POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
|
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
@ -30,6 +31,10 @@ jobs:
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
|
- name: Install Pro
|
||||||
|
run: yarn install:pro master
|
||||||
|
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
|
|
@ -11,7 +11,7 @@ sources:
|
||||||
- https://github.com/Budibase/budibase
|
- https://github.com/Budibase/budibase
|
||||||
- https://budibase.com
|
- https://budibase.com
|
||||||
type: application
|
type: application
|
||||||
version: 0.2.8
|
version: 0.2.9
|
||||||
appVersion: 1.0.48
|
appVersion: 1.0.48
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: couchdb
|
- name: couchdb
|
||||||
|
|
|
@ -98,10 +98,6 @@ spec:
|
||||||
value: http://worker-service:{{ .Values.services.worker.port }}
|
value: http://worker-service:{{ .Values.services.worker.port }}
|
||||||
- name: PLATFORM_URL
|
- name: PLATFORM_URL
|
||||||
value: {{ .Values.globals.platformUrl | quote }}
|
value: {{ .Values.globals.platformUrl | quote }}
|
||||||
- name: USE_QUOTAS
|
|
||||||
value: {{ .Values.globals.useQuotas | quote }}
|
|
||||||
- name: EXCLUDE_QUOTAS_TENANTS
|
|
||||||
value: {{ .Values.globals.excludeQuotasTenants | quote }}
|
|
||||||
- name: ACCOUNT_PORTAL_URL
|
- name: ACCOUNT_PORTAL_URL
|
||||||
value: {{ .Values.globals.accountPortalUrl | quote }}
|
value: {{ .Values.globals.accountPortalUrl | quote }}
|
||||||
- name: ACCOUNT_PORTAL_API_KEY
|
- name: ACCOUNT_PORTAL_API_KEY
|
||||||
|
@ -114,12 +110,23 @@ spec:
|
||||||
value: {{ .Values.globals.google.clientId | quote }}
|
value: {{ .Values.globals.google.clientId | quote }}
|
||||||
- name: GOOGLE_CLIENT_SECRET
|
- name: GOOGLE_CLIENT_SECRET
|
||||||
value: {{ .Values.globals.google.secret | quote }}
|
value: {{ .Values.globals.google.secret | quote }}
|
||||||
|
- name: AUTOMATION_MAX_ITERATIONS
|
||||||
|
value: {{ .Values.globals.automationMaxIterations | quote }}
|
||||||
|
|
||||||
image: budibase/apps:{{ .Values.globals.appVersion }}
|
image: budibase/apps:{{ .Values.globals.appVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: bbapps
|
name: bbapps
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.services.apps.port }}
|
- containerPort: {{ .Values.services.apps.port }}
|
||||||
resources: {}
|
resources: {}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
status: {}
|
status: {}
|
||||||
|
|
|
@ -39,5 +39,13 @@ spec:
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: couchdb-backup
|
name: couchdb-backup
|
||||||
resources: {}
|
resources: {}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
status: {}
|
status: {}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
|
@ -60,6 +60,14 @@ spec:
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /data
|
- mountPath: /data
|
||||||
name: minio-data
|
name: minio-data
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -32,6 +32,14 @@ spec:
|
||||||
- containerPort: {{ .Values.services.proxy.port }}
|
- containerPort: {{ .Values.services.proxy.port }}
|
||||||
resources: {}
|
resources: {}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -39,6 +39,14 @@ spec:
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /data
|
- mountPath: /data
|
||||||
name: redis-data
|
name: redis-data
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -121,6 +121,14 @@ spec:
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.services.worker.port }}
|
- containerPort: {{ .Values.services.worker.port }}
|
||||||
resources: {}
|
resources: {}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
status: {}
|
status: {}
|
||||||
|
|
|
@ -93,8 +93,6 @@ globals:
|
||||||
logLevel: info
|
logLevel: info
|
||||||
selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup
|
selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup
|
||||||
multiTenancy: "0" # set to 0 to disable multiple orgs, set to 1 to enable multiple orgs
|
multiTenancy: "0" # set to 0 to disable multiple orgs, set to 1 to enable multiple orgs
|
||||||
useQuotas: "0"
|
|
||||||
excludeQuotasTenants: "" # comma seperated list of tenants to exclude from quotas
|
|
||||||
accountPortalUrl: ""
|
accountPortalUrl: ""
|
||||||
accountPortalApiKey: ""
|
accountPortalApiKey: ""
|
||||||
cookieDomain: ""
|
cookieDomain: ""
|
||||||
|
@ -103,6 +101,7 @@ globals:
|
||||||
google:
|
google:
|
||||||
clientId: ""
|
clientId: ""
|
||||||
secret: ""
|
secret: ""
|
||||||
|
automationMaxIterations: "500"
|
||||||
|
|
||||||
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
||||||
|
|
||||||
|
@ -230,6 +229,8 @@ couchdb:
|
||||||
## Optional tolerations
|
## Optional tolerations
|
||||||
tolerations: []
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
service:
|
service:
|
||||||
# annotations:
|
# annotations:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.105-alpha.10",
|
"version": "1.0.105-alpha.39",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
14
package.json
14
package.json
|
@ -21,18 +21,17 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
|
"setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
|
||||||
"bootstrap": "lerna link && lerna bootstrap",
|
"bootstrap": "lerna link && lerna bootstrap && ./scripts/link-dependencies.sh",
|
||||||
"build": "lerna run build",
|
"build": "lerna run build",
|
||||||
"publishdev": "lerna run publishdev",
|
"release": "lerna publish patch --yes --force-publish && yarn release:pro",
|
||||||
"publishnpm": "yarn build && lerna publish --force-publish",
|
"release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop && yarn release:pro:develop",
|
||||||
"release": "lerna publish patch --yes --force-publish",
|
"release:pro": "bash scripts/pro/release.sh",
|
||||||
"release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop",
|
"release:pro:develop": "bash scripts/pro/release.sh develop",
|
||||||
"restore": "yarn run clean && yarn run bootstrap && yarn run build",
|
"restore": "yarn run clean && yarn run bootstrap && yarn run build",
|
||||||
"nuke": "yarn run nuke:packages && yarn run nuke:docker",
|
"nuke": "yarn run nuke:packages && yarn run nuke:docker",
|
||||||
"nuke:packages": "yarn run restore",
|
"nuke:packages": "yarn run restore",
|
||||||
"nuke:docker": "lerna run --parallel dev:stack:nuke",
|
"nuke:docker": "lerna run --parallel dev:stack:nuke",
|
||||||
"clean": "lerna clean",
|
"clean": "lerna clean",
|
||||||
"kill-port": "kill-port 4001",
|
|
||||||
"kill-builder": "kill-port 3000",
|
"kill-builder": "kill-port 3000",
|
||||||
"kill-server": "kill-port 4001 4002",
|
"kill-server": "kill-port 4001 4002",
|
||||||
"kill-all": "yarn run kill-builder && yarn run kill-server",
|
"kill-all": "yarn run kill-builder && yarn run kill-server",
|
||||||
|
@ -74,6 +73,7 @@
|
||||||
"mode:cloud": "yarn env:selfhost:disable && yarn env:multi:enable && yarn env:account:disable",
|
"mode:cloud": "yarn env:selfhost:disable && yarn env:multi:enable && yarn env:account:disable",
|
||||||
"mode:account": "yarn mode:cloud && yarn env:account:enable",
|
"mode:account": "yarn mode:cloud && yarn env:account:enable",
|
||||||
"security:audit": "node scripts/audit.js",
|
"security:audit": "node scripts/audit.js",
|
||||||
"postinstall": "husky install"
|
"postinstall": "husky install",
|
||||||
|
"install:pro": "bash scripts/pro/install.sh"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.0.105-alpha.10",
|
"version": "1.0.105-alpha.39",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -13,9 +13,11 @@ exports.Cookies = {
|
||||||
|
|
||||||
exports.Headers = {
|
exports.Headers = {
|
||||||
API_KEY: "x-budibase-api-key",
|
API_KEY: "x-budibase-api-key",
|
||||||
|
LICENSE_KEY: "x-budibase-license-key",
|
||||||
API_VER: "x-budibase-api-version",
|
API_VER: "x-budibase-api-version",
|
||||||
APP_ID: "x-budibase-app-id",
|
APP_ID: "x-budibase-app-id",
|
||||||
TYPE: "x-budibase-type",
|
TYPE: "x-budibase-type",
|
||||||
|
PREVIEW_ROLE: "x-budibase-role",
|
||||||
TENANT_ID: "x-budibase-tenant-id",
|
TENANT_ID: "x-budibase-tenant-id",
|
||||||
TOKEN: "x-budibase-token",
|
TOKEN: "x-budibase-token",
|
||||||
CSRF_TOKEN: "x-csrf-token",
|
CSRF_TOKEN: "x-csrf-token",
|
||||||
|
|
|
@ -23,6 +23,7 @@ exports.StaticDatabases = {
|
||||||
docs: {
|
docs: {
|
||||||
apiKeys: "apikeys",
|
apiKeys: "apikeys",
|
||||||
usageQuota: "usage_quota",
|
usageQuota: "usage_quota",
|
||||||
|
licenseInfo: "license_info",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// contains information about tenancy and so on
|
// contains information about tenancy and so on
|
||||||
|
|
|
@ -27,6 +27,7 @@ const UNICODE_MAX = "\ufff0"
|
||||||
exports.ViewNames = {
|
exports.ViewNames = {
|
||||||
USER_BY_EMAIL: "by_email",
|
USER_BY_EMAIL: "by_email",
|
||||||
BY_API_KEY: "by_api_key",
|
BY_API_KEY: "by_api_key",
|
||||||
|
USER_BY_BUILDERS: "by_builders",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.StaticDatabases = StaticDatabases
|
exports.StaticDatabases = StaticDatabases
|
||||||
|
@ -429,34 +430,9 @@ async function getScopedConfig(db, params) {
|
||||||
return configDoc && configDoc.config ? configDoc.config : configDoc
|
return configDoc && configDoc.config ? configDoc.config : configDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateNewUsageQuotaDoc() {
|
|
||||||
return {
|
|
||||||
_id: StaticDatabases.GLOBAL.docs.usageQuota,
|
|
||||||
quotaReset: Date.now() + 2592000000,
|
|
||||||
usageQuota: {
|
|
||||||
automationRuns: 0,
|
|
||||||
rows: 0,
|
|
||||||
storage: 0,
|
|
||||||
apps: 0,
|
|
||||||
users: 0,
|
|
||||||
views: 0,
|
|
||||||
emails: 0,
|
|
||||||
},
|
|
||||||
usageLimits: {
|
|
||||||
automationRuns: 1000,
|
|
||||||
rows: 4000,
|
|
||||||
apps: 4,
|
|
||||||
storage: 1000,
|
|
||||||
users: 10,
|
|
||||||
emails: 50,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.Replication = Replication
|
exports.Replication = Replication
|
||||||
exports.getScopedConfig = getScopedConfig
|
exports.getScopedConfig = getScopedConfig
|
||||||
exports.generateConfigID = generateConfigID
|
exports.generateConfigID = generateConfigID
|
||||||
exports.getConfigParams = getConfigParams
|
exports.getConfigParams = getConfigParams
|
||||||
exports.getScopedFullConfig = getScopedFullConfig
|
exports.getScopedFullConfig = getScopedFullConfig
|
||||||
exports.generateNewUsageQuotaDoc = generateNewUsageQuotaDoc
|
|
||||||
exports.generateDevInfoID = generateDevInfoID
|
exports.generateDevInfoID = generateDevInfoID
|
||||||
|
|
|
@ -56,10 +56,34 @@ exports.createApiKeyView = async () => {
|
||||||
await db.put(designDoc)
|
await db.put(designDoc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.createUserBuildersView = async () => {
|
||||||
|
const db = getGlobalDB()
|
||||||
|
let designDoc
|
||||||
|
try {
|
||||||
|
designDoc = await db.get("_design/database")
|
||||||
|
} catch (err) {
|
||||||
|
// no design doc, make one
|
||||||
|
designDoc = DesignDoc()
|
||||||
|
}
|
||||||
|
const view = {
|
||||||
|
map: `function(doc) {
|
||||||
|
if (doc.builder && doc.builder.global === true) {
|
||||||
|
emit(doc._id, doc._id)
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
}
|
||||||
|
designDoc.views = {
|
||||||
|
...designDoc.views,
|
||||||
|
[ViewNames.USER_BY_BUILDERS]: view,
|
||||||
|
}
|
||||||
|
await db.put(designDoc)
|
||||||
|
}
|
||||||
|
|
||||||
exports.queryGlobalView = async (viewName, params, db = null) => {
|
exports.queryGlobalView = async (viewName, params, db = null) => {
|
||||||
const CreateFuncByName = {
|
const CreateFuncByName = {
|
||||||
[ViewNames.USER_BY_EMAIL]: exports.createUserEmailView,
|
[ViewNames.USER_BY_EMAIL]: exports.createUserEmailView,
|
||||||
[ViewNames.BY_API_KEY]: exports.createApiKeyView,
|
[ViewNames.BY_API_KEY]: exports.createApiKeyView,
|
||||||
|
[ViewNames.USER_BY_BUILDERS]: exports.createUserBuildersView,
|
||||||
}
|
}
|
||||||
// can pass DB in if working with something specific
|
// can pass DB in if working with something specific
|
||||||
if (!db) {
|
if (!db) {
|
||||||
|
|
|
@ -22,12 +22,14 @@ module.exports = {
|
||||||
MINIO_URL: process.env.MINIO_URL,
|
MINIO_URL: process.env.MINIO_URL,
|
||||||
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
|
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
|
||||||
MULTI_TENANCY: process.env.MULTI_TENANCY,
|
MULTI_TENANCY: process.env.MULTI_TENANCY,
|
||||||
ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL,
|
ACCOUNT_PORTAL_URL:
|
||||||
|
process.env.ACCOUNT_PORTAL_URL || "https://account.budibase.app",
|
||||||
ACCOUNT_PORTAL_API_KEY: process.env.ACCOUNT_PORTAL_API_KEY,
|
ACCOUNT_PORTAL_API_KEY: process.env.ACCOUNT_PORTAL_API_KEY,
|
||||||
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
||||||
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
||||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||||
PLATFORM_URL: process.env.PLATFORM_URL,
|
PLATFORM_URL: process.env.PLATFORM_URL,
|
||||||
|
TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS,
|
||||||
isTest,
|
isTest,
|
||||||
_set(key, value) {
|
_set(key, value) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
class BudibaseError extends Error {
|
||||||
|
constructor(message, type, code) {
|
||||||
|
super(message)
|
||||||
|
this.type = type
|
||||||
|
this.code = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
BudibaseError,
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
const licensing = require("./licensing")
|
||||||
|
|
||||||
|
const codes = {
|
||||||
|
...licensing.codes,
|
||||||
|
}
|
||||||
|
|
||||||
|
const types = {
|
||||||
|
...licensing.types,
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
...licensing.context,
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPublicError = err => {
|
||||||
|
let error
|
||||||
|
if (err.code || err.type) {
|
||||||
|
// add generic error information
|
||||||
|
error = {
|
||||||
|
code: err.code,
|
||||||
|
type: err.type,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code && context[err.code]) {
|
||||||
|
error = {
|
||||||
|
...error,
|
||||||
|
// get any additional context from this error
|
||||||
|
...context[err.code](err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
codes,
|
||||||
|
types,
|
||||||
|
UsageLimitError: licensing.UsageLimitError,
|
||||||
|
getPublicError,
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
const { BudibaseError } = require("./base")
|
||||||
|
|
||||||
|
const types = {
|
||||||
|
LICENSE_ERROR: "license_error",
|
||||||
|
}
|
||||||
|
|
||||||
|
const codes = {
|
||||||
|
USAGE_LIMIT_EXCEEDED: "usage_limit_exceeded",
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
[codes.USAGE_LIMIT_EXCEEDED]: err => {
|
||||||
|
return {
|
||||||
|
limitName: err.limitName,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsageLimitError extends BudibaseError {
|
||||||
|
constructor(message, limitName) {
|
||||||
|
super(message, types.LICENSE_ERROR, codes.USAGE_LIMIT_EXCEEDED)
|
||||||
|
this.limitName = limitName
|
||||||
|
this.status = 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
types,
|
||||||
|
codes,
|
||||||
|
context,
|
||||||
|
UsageLimitError,
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
const env = require("../environment")
|
||||||
|
const tenancy = require("../tenancy")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the TENANT_FEATURE_FLAGS env var and return an array of features flags for each tenant.
|
||||||
|
* The env var is formatted as:
|
||||||
|
* tenant1:feature1:feature2,tenant2:feature1
|
||||||
|
*/
|
||||||
|
const getFeatureFlags = () => {
|
||||||
|
if (!env.TENANT_FEATURE_FLAGS) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenantFeatureFlags = {}
|
||||||
|
|
||||||
|
env.TENANT_FEATURE_FLAGS.split(",").forEach(tenantToFeatures => {
|
||||||
|
const [tenantId, ...features] = tenantToFeatures.split(":")
|
||||||
|
|
||||||
|
features.forEach(feature => {
|
||||||
|
if (!tenantFeatureFlags[tenantId]) {
|
||||||
|
tenantFeatureFlags[tenantId] = []
|
||||||
|
}
|
||||||
|
tenantFeatureFlags[tenantId].push(feature)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return tenantFeatureFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
const TENANT_FEATURE_FLAGS = getFeatureFlags()
|
||||||
|
|
||||||
|
exports.isEnabled = featureFlag => {
|
||||||
|
const tenantId = tenancy.getTenantId()
|
||||||
|
|
||||||
|
return (
|
||||||
|
TENANT_FEATURE_FLAGS &&
|
||||||
|
TENANT_FEATURE_FLAGS[tenantId] &&
|
||||||
|
TENANT_FEATURE_FLAGS[tenantId].includes(featureFlag)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getTenantFeatureFlags = tenantId => {
|
||||||
|
if (TENANT_FEATURE_FLAGS && TENANT_FEATURE_FLAGS[tenantId]) {
|
||||||
|
return TENANT_FEATURE_FLAGS[tenantId]
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.FeatureFlag = {
|
||||||
|
LICENSING: "LICENSING",
|
||||||
|
}
|
|
@ -15,4 +15,9 @@ module.exports = {
|
||||||
auth: require("../auth"),
|
auth: require("../auth"),
|
||||||
constants: require("../constants"),
|
constants: require("../constants"),
|
||||||
migrations: require("../migrations"),
|
migrations: require("../migrations"),
|
||||||
|
errors: require("./errors"),
|
||||||
|
env: require("./environment"),
|
||||||
|
accounts: require("./cloud/accounts"),
|
||||||
|
tenancy: require("./tenancy"),
|
||||||
|
featureFlags: require("./featureFlags"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@ const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||||
|
|
||||||
const { authenticateThirdParty } = require("./third-party-common")
|
const { authenticateThirdParty } = require("./third-party-common")
|
||||||
|
|
||||||
async function authenticate(accessToken, refreshToken, profile, done) {
|
const buildVerifyFn = async saveUserFn => {
|
||||||
|
return (accessToken, refreshToken, profile, done) => {
|
||||||
const thirdPartyUser = {
|
const thirdPartyUser = {
|
||||||
provider: profile.provider, // should always be 'google'
|
provider: profile.provider, // should always be 'google'
|
||||||
providerType: "google",
|
providerType: "google",
|
||||||
|
@ -18,20 +19,18 @@ async function authenticate(accessToken, refreshToken, profile, done) {
|
||||||
return authenticateThirdParty(
|
return authenticateThirdParty(
|
||||||
thirdPartyUser,
|
thirdPartyUser,
|
||||||
true, // require local accounts to exist
|
true, // require local accounts to exist
|
||||||
done
|
done,
|
||||||
|
saveUserFn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance of the google passport strategy. This wrapper fetches the configuration
|
* Create an instance of the google passport strategy. This wrapper fetches the configuration
|
||||||
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
|
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
|
||||||
* @returns Dynamically configured Passport Google Strategy
|
* @returns Dynamically configured Passport Google Strategy
|
||||||
*/
|
*/
|
||||||
exports.strategyFactory = async function (
|
exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
|
||||||
config,
|
|
||||||
callbackUrl,
|
|
||||||
verify = authenticate
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const { clientID, clientSecret } = config
|
const { clientID, clientSecret } = config
|
||||||
|
|
||||||
|
@ -41,6 +40,7 @@ exports.strategyFactory = async function (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const verify = buildVerifyFn(saveUserFn)
|
||||||
return new GoogleStrategy(
|
return new GoogleStrategy(
|
||||||
{
|
{
|
||||||
clientID: config.clientID,
|
clientID: config.clientID,
|
||||||
|
@ -58,4 +58,4 @@ exports.strategyFactory = async function (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// expose for testing
|
// expose for testing
|
||||||
exports.authenticate = authenticate
|
exports.buildVerifyFn = buildVerifyFn
|
||||||
|
|
|
@ -2,6 +2,7 @@ const fetch = require("node-fetch")
|
||||||
const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
|
const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
|
||||||
const { authenticateThirdParty } = require("./third-party-common")
|
const { authenticateThirdParty } = require("./third-party-common")
|
||||||
|
|
||||||
|
const buildVerifyFn = saveUserFn => {
|
||||||
/**
|
/**
|
||||||
* @param {*} issuer The identity provider base URL
|
* @param {*} issuer The identity provider base URL
|
||||||
* @param {*} sub The user ID
|
* @param {*} sub The user ID
|
||||||
|
@ -13,7 +14,7 @@ const { authenticateThirdParty } = require("./third-party-common")
|
||||||
* @param {*} params The response body from requesting an access_token
|
* @param {*} params The response body from requesting an access_token
|
||||||
* @param {*} done The passport callback: err, user, info
|
* @param {*} done The passport callback: err, user, info
|
||||||
*/
|
*/
|
||||||
async function authenticate(
|
return async (
|
||||||
issuer,
|
issuer,
|
||||||
sub,
|
sub,
|
||||||
profile,
|
profile,
|
||||||
|
@ -23,7 +24,7 @@ async function authenticate(
|
||||||
idToken,
|
idToken,
|
||||||
params,
|
params,
|
||||||
done
|
done
|
||||||
) {
|
) => {
|
||||||
const thirdPartyUser = {
|
const thirdPartyUser = {
|
||||||
// store the issuer info to enable sync in future
|
// store the issuer info to enable sync in future
|
||||||
provider: issuer,
|
provider: issuer,
|
||||||
|
@ -40,9 +41,11 @@ async function authenticate(
|
||||||
return authenticateThirdParty(
|
return authenticateThirdParty(
|
||||||
thirdPartyUser,
|
thirdPartyUser,
|
||||||
false, // don't require local accounts to exist
|
false, // don't require local accounts to exist
|
||||||
done
|
done,
|
||||||
|
saveUserFn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {*} profile The structured profile created by passport using the user info endpoint
|
* @param {*} profile The structured profile created by passport using the user info endpoint
|
||||||
|
@ -86,7 +89,7 @@ function validEmail(value) {
|
||||||
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
|
* from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
|
||||||
* @returns Dynamically configured Passport OIDC Strategy
|
* @returns Dynamically configured Passport OIDC Strategy
|
||||||
*/
|
*/
|
||||||
exports.strategyFactory = async function (config, callbackUrl) {
|
exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
|
||||||
try {
|
try {
|
||||||
const { clientID, clientSecret, configUrl } = config
|
const { clientID, clientSecret, configUrl } = config
|
||||||
|
|
||||||
|
@ -106,6 +109,7 @@ exports.strategyFactory = async function (config, callbackUrl) {
|
||||||
|
|
||||||
const body = await response.json()
|
const body = await response.json()
|
||||||
|
|
||||||
|
const verify = buildVerifyFn(saveUserFn)
|
||||||
return new OIDCStrategy(
|
return new OIDCStrategy(
|
||||||
{
|
{
|
||||||
issuer: body.issuer,
|
issuer: body.issuer,
|
||||||
|
@ -116,7 +120,7 @@ exports.strategyFactory = async function (config, callbackUrl) {
|
||||||
clientSecret: clientSecret,
|
clientSecret: clientSecret,
|
||||||
callbackURL: callbackUrl,
|
callbackURL: callbackUrl,
|
||||||
},
|
},
|
||||||
authenticate
|
verify
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
@ -125,4 +129,4 @@ exports.strategyFactory = async function (config, callbackUrl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// expose for testing
|
// expose for testing
|
||||||
exports.authenticate = authenticate
|
exports.buildVerifyFn = buildVerifyFn
|
||||||
|
|
|
@ -58,8 +58,10 @@ describe("google", () => {
|
||||||
|
|
||||||
it("delegates authentication to third party common", async () => {
|
it("delegates authentication to third party common", async () => {
|
||||||
const google = require("../google")
|
const google = require("../google")
|
||||||
|
const mockSaveUserFn = jest.fn()
|
||||||
|
const authenticate = await google.buildVerifyFn(mockSaveUserFn)
|
||||||
|
|
||||||
await google.authenticate(
|
await authenticate(
|
||||||
data.accessToken,
|
data.accessToken,
|
||||||
data.refreshToken,
|
data.refreshToken,
|
||||||
profile,
|
profile,
|
||||||
|
@ -69,7 +71,8 @@ describe("google", () => {
|
||||||
expect(authenticateThirdParty).toHaveBeenCalledWith(
|
expect(authenticateThirdParty).toHaveBeenCalledWith(
|
||||||
user,
|
user,
|
||||||
true,
|
true,
|
||||||
mockDone)
|
mockDone,
|
||||||
|
mockSaveUserFn)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -83,8 +83,10 @@ describe("oidc", () => {
|
||||||
|
|
||||||
async function doAuthenticate() {
|
async function doAuthenticate() {
|
||||||
const oidc = require("../oidc")
|
const oidc = require("../oidc")
|
||||||
|
const mockSaveUserFn = jest.fn()
|
||||||
|
const authenticate = await oidc.buildVerifyFn(mockSaveUserFn)
|
||||||
|
|
||||||
await oidc.authenticate(
|
await authenticate(
|
||||||
issuer,
|
issuer,
|
||||||
sub,
|
sub,
|
||||||
profile,
|
profile,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
const { generateGlobalUserID } = require("../../db/utils")
|
const { generateGlobalUserID } = require("../../db/utils")
|
||||||
const { saveUser } = require("../../utils")
|
|
||||||
const { authError } = require("./utils")
|
const { authError } = require("./utils")
|
||||||
const { newid } = require("../../hashing")
|
const { newid } = require("../../hashing")
|
||||||
const { createASession } = require("../../security/sessions")
|
const { createASession } = require("../../security/sessions")
|
||||||
|
@ -16,8 +15,11 @@ exports.authenticateThirdParty = async function (
|
||||||
thirdPartyUser,
|
thirdPartyUser,
|
||||||
requireLocalAccount = true,
|
requireLocalAccount = true,
|
||||||
done,
|
done,
|
||||||
saveUserFn = saveUser
|
saveUserFn
|
||||||
) {
|
) {
|
||||||
|
if (!saveUserFn) {
|
||||||
|
throw new Error("Save user function must be provided")
|
||||||
|
}
|
||||||
if (!thirdPartyUser.provider) {
|
if (!thirdPartyUser.provider) {
|
||||||
return authError(done, "third party user provider required")
|
return authError(done, "third party user provider required")
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ exports.Databases = {
|
||||||
FLAGS: "flags",
|
FLAGS: "flags",
|
||||||
APP_METADATA: "appMetadata",
|
APP_METADATA: "appMetadata",
|
||||||
QUERY_VARS: "queryVars",
|
QUERY_VARS: "queryVars",
|
||||||
|
LICENSES: "license",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.SEPARATOR = SEPARATOR
|
exports.SEPARATOR = SEPARATOR
|
||||||
|
|
|
@ -176,6 +176,13 @@ exports.getGlobalUserByEmail = async email => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.getBuildersCount = async () => {
|
||||||
|
const builders = await queryGlobalView(ViewNames.USER_BY_BUILDERS, {
|
||||||
|
include_docs: false,
|
||||||
|
})
|
||||||
|
return builders ? builders.length : 0
|
||||||
|
}
|
||||||
|
|
||||||
exports.saveUser = async (
|
exports.saveUser = async (
|
||||||
user,
|
user,
|
||||||
tenantId,
|
tenantId,
|
||||||
|
@ -289,4 +296,5 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => {
|
||||||
userId,
|
userId,
|
||||||
sessions.map(({ sessionId }) => sessionId)
|
sessions.map(({ sessionId }) => sessionId)
|
||||||
)
|
)
|
||||||
|
await userCache.invalidateUser(userId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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": "1.0.105-alpha.10",
|
"version": "1.0.105-alpha.39",
|
||||||
"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": "^1.0.105-alpha.10",
|
"@budibase/string-templates": "^1.0.105-alpha.39",
|
||||||
"@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",
|
||||||
|
|
|
@ -80,8 +80,4 @@
|
||||||
.active svg {
|
.active svg {
|
||||||
color: var(--spectrum-global-color-blue-600);
|
color: var(--spectrum-global-color-blue-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.spectrum-ActionButton-label {
|
|
||||||
padding-bottom: 2px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -36,6 +36,10 @@
|
||||||
padding-left: var(--spacing-l);
|
padding-left: var(--spacing-l);
|
||||||
padding-right: var(--spacing-l);
|
padding-right: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
.paddingX-XL {
|
||||||
|
padding-left: var(--spacing-xl);
|
||||||
|
padding-right: var(--spacing-xl);
|
||||||
|
}
|
||||||
.paddingY-S {
|
.paddingY-S {
|
||||||
padding-top: var(--spacing-s);
|
padding-top: var(--spacing-s);
|
||||||
padding-bottom: var(--spacing-s);
|
padding-bottom: var(--spacing-s);
|
||||||
|
@ -48,6 +52,10 @@
|
||||||
padding-top: var(--spacing-l);
|
padding-top: var(--spacing-l);
|
||||||
padding-bottom: var(--spacing-l);
|
padding-bottom: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
.paddingY-XL {
|
||||||
|
padding-top: var(--spacing-xl);
|
||||||
|
padding-bottom: var(--spacing-xl);
|
||||||
|
}
|
||||||
.gap-XXS {
|
.gap-XXS {
|
||||||
grid-gap: var(--spacing-xs);
|
grid-gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
export let wide = false
|
export let wide = false
|
||||||
|
export let maxWidth = "80ch"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class:wide>
|
<div style="--max-width: {maxWidth}" class:wide>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
max-width: 80ch;
|
max-width: var(--max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: calc(var(--spacing-xl) * 2);
|
padding: calc(var(--spacing-xl) * 2);
|
||||||
min-height: calc(100% - var(--spacing-xl) * 4);
|
min-height: calc(100% - var(--spacing-xl) * 4);
|
||||||
|
|
|
@ -16,11 +16,11 @@
|
||||||
easing: easing,
|
easing: easing,
|
||||||
})
|
})
|
||||||
|
|
||||||
$: if (value) $progress = value
|
$: if (value || value === 0) $progress = value
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class:spectrum-ProgressBar--indeterminate={!value}
|
class:spectrum-ProgressBar--indeterminate={!value && value !== 0}
|
||||||
class:spectrum-ProgressBar--sideLabel={sideLabel}
|
class:spectrum-ProgressBar--sideLabel={sideLabel}
|
||||||
class="spectrum-ProgressBar spectrum-ProgressBar--size{size}"
|
class="spectrum-ProgressBar spectrum-ProgressBar--size{size}"
|
||||||
value={$progress}
|
value={$progress}
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
aria-valuenow={$progress}
|
aria-valuenow={$progress}
|
||||||
aria-valuemin="0"
|
aria-valuemin="0"
|
||||||
aria-valuemax="100"
|
aria-valuemax="100"
|
||||||
style={width ? `width: ${width}px;` : ""}
|
style={width ? `width: ${width};` : ""}
|
||||||
>
|
>
|
||||||
{#if $$slots}
|
{#if $$slots}
|
||||||
<div
|
<div
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if value}
|
{#if value || value === 0}
|
||||||
<div
|
<div
|
||||||
class="spectrum-FieldLabel spectrum-ProgressBar-percentage spectrum-FieldLabel--size{size}"
|
class="spectrum-FieldLabel spectrum-ProgressBar-percentage spectrum-FieldLabel--size{size}"
|
||||||
>
|
>
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
<div class="spectrum-ProgressBar-track">
|
<div class="spectrum-ProgressBar-track">
|
||||||
<div
|
<div
|
||||||
class="spectrum-ProgressBar-fill"
|
class="spectrum-ProgressBar-fill"
|
||||||
style={value ? `width: ${$progress}%` : ""}
|
style={value || value === 0 ? `width: ${$progress}%` : ""}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="spectrum-ProgressBar-label" hidden="" />
|
<div class="spectrum-ProgressBar-label" hidden="" />
|
||||||
|
|
|
@ -1,42 +1,21 @@
|
||||||
<script>
|
<script>
|
||||||
import Icon from "../Icon/Icon.svelte"
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
import { copyToClipboard } from "../helpers"
|
||||||
import { notifications } from "../Stores/notifications"
|
import { notifications } from "../Stores/notifications"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
const onClick = e => {
|
const onClick = async e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
copyToClipboard(value)
|
try {
|
||||||
}
|
await copyToClipboard(value)
|
||||||
|
|
||||||
const copyToClipboard = value => {
|
|
||||||
return new Promise(res => {
|
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
|
||||||
// Try using the clipboard API first
|
|
||||||
navigator.clipboard.writeText(value).then(res)
|
|
||||||
} else {
|
|
||||||
// Fall back to the textarea hack
|
|
||||||
let textArea = document.createElement("textarea")
|
|
||||||
textArea.value = value
|
|
||||||
textArea.style.position = "fixed"
|
|
||||||
textArea.style.left = "-9999px"
|
|
||||||
textArea.style.top = "-9999px"
|
|
||||||
document.body.appendChild(textArea)
|
|
||||||
textArea.focus()
|
|
||||||
textArea.select()
|
|
||||||
document.execCommand("copy")
|
|
||||||
textArea.remove()
|
|
||||||
res()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
notifications.success("Copied to clipboard")
|
notifications.success("Copied to clipboard")
|
||||||
})
|
} catch (error) {
|
||||||
.catch(() => {
|
|
||||||
notifications.error(
|
notifications.error(
|
||||||
"Failed to copy to clipboard. Check the dev console for the value."
|
"Failed to copy to clipboard. Check the dev console for the value."
|
||||||
)
|
)
|
||||||
console.warn("Failed to copy the value", value)
|
console.warn("Failed to copy the value", value)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
export let disableSorting = false
|
export let disableSorting = false
|
||||||
export let autoSortColumns = true
|
export let autoSortColumns = true
|
||||||
export let compact = false
|
export let compact = false
|
||||||
|
export let customPlaceholder = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -387,13 +388,24 @@
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="placeholder" class:placeholder--no-fields={!fields?.length}>
|
<div
|
||||||
|
class="placeholder"
|
||||||
|
class:placeholder--custom={customPlaceholder}
|
||||||
|
class:placeholder--no-fields={!fields?.length}
|
||||||
|
>
|
||||||
|
{#if customPlaceholder}
|
||||||
|
<slot name="placeholder" />
|
||||||
|
{:else}
|
||||||
<div class="placeholder-content">
|
<div class="placeholder-content">
|
||||||
<svg class="spectrum-Icon spectrum-Icon--sizeXXL" focusable="false">
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-Icon--sizeXXL"
|
||||||
|
focusable="false"
|
||||||
|
>
|
||||||
<use xlink:href="#spectrum-icon-18-Table" />
|
<use xlink:href="#spectrum-icon-18-Table" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>No rows found</div>
|
<div>No rows found</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -458,6 +470,13 @@
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
border-top: var(--table-border);
|
||||||
|
}
|
||||||
|
.spectrum-Table-headCell:first-of-type {
|
||||||
|
border-left: var(--table-border);
|
||||||
|
}
|
||||||
|
.spectrum-Table-headCell:last-of-type {
|
||||||
|
border-right: var(--table-border);
|
||||||
}
|
}
|
||||||
.spectrum-Table-headCell--alignCenter {
|
.spectrum-Table-headCell--alignCenter {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -576,16 +595,19 @@
|
||||||
border-top: none;
|
border-top: none;
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
background-color: var(--table-bg);
|
background-color: var(--table-bg);
|
||||||
|
padding: 40px;
|
||||||
}
|
}
|
||||||
.placeholder--no-fields {
|
.placeholder--no-fields {
|
||||||
border-top: var(--table-border);
|
border-top: var(--table-border);
|
||||||
}
|
}
|
||||||
|
.placeholder--custom {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
.wrapper--quiet .placeholder {
|
.wrapper--quiet .placeholder {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
.placeholder-content {
|
.placeholder-content {
|
||||||
padding: 40px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
padding-left: var(--spacing-xl);
|
padding-left: var(--spacing-xl);
|
||||||
padding-right: var(--spacing-xl);
|
padding-right: var(--spacing-xl);
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom: var(--border-light);
|
border-bottom: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
}
|
}
|
||||||
.spectrum-Tabs-content {
|
.spectrum-Tabs-content {
|
||||||
margin-top: var(--spectrum-global-dimension-static-size-150);
|
margin-top: var(--spectrum-global-dimension-static-size-150);
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
export let serif = false
|
export let serif = false
|
||||||
export let weight = null
|
export let weight = null
|
||||||
export let textAlign = null
|
export let textAlign = null
|
||||||
|
export let color = null
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
style={`
|
style={`
|
||||||
${weight ? `font-weight:${weight};` : ""}
|
${weight ? `font-weight:${weight};` : ""}
|
||||||
${textAlign ? `text-align:${textAlign};` : ""}
|
${textAlign ? `text-align:${textAlign};` : ""}
|
||||||
|
${color ? `color:${color};` : ""}
|
||||||
`}
|
`}
|
||||||
class="spectrum-Body spectrum-Body--size{size}"
|
class="spectrum-Body spectrum-Body--size{size}"
|
||||||
class:spectrum-Body--serif={serif}
|
class:spectrum-Body--serif={serif}
|
||||||
|
|
|
@ -5,12 +5,13 @@
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let textAlign
|
export let textAlign
|
||||||
export let noPadding = false
|
export let noPadding = false
|
||||||
|
export let weight = "default" // light, heavy, default
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1
|
<h1
|
||||||
style={textAlign ? `text-align:${textAlign}` : ``}
|
style={textAlign ? `text-align:${textAlign}` : ``}
|
||||||
class:noPadding
|
class:noPadding
|
||||||
class="spectrum-Heading spectrum-Heading--size{size}"
|
class="spectrum-Heading spectrum-Heading--size{size} spectrum-Heading--{weight}"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
@ -106,3 +106,29 @@ export const deepSet = (obj, key, value) => {
|
||||||
export const cloneDeep = obj => {
|
export const cloneDeep = obj => {
|
||||||
return JSON.parse(JSON.stringify(obj))
|
return JSON.parse(JSON.stringify(obj))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies a value to the clipboard
|
||||||
|
* @param value the value to copy
|
||||||
|
*/
|
||||||
|
export const copyToClipboard = value => {
|
||||||
|
return new Promise(res => {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
// Try using the clipboard API first
|
||||||
|
navigator.clipboard.writeText(value).then(res)
|
||||||
|
} else {
|
||||||
|
// Fall back to the textarea hack
|
||||||
|
let textArea = document.createElement("textarea")
|
||||||
|
textArea.value = value
|
||||||
|
textArea.style.position = "fixed"
|
||||||
|
textArea.style.left = "-9999px"
|
||||||
|
textArea.style.top = "-9999px"
|
||||||
|
document.body.appendChild(textArea)
|
||||||
|
textArea.focus()
|
||||||
|
textArea.select()
|
||||||
|
document.execCommand("copy")
|
||||||
|
textArea.remove()
|
||||||
|
res()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,12 +4,44 @@ filterTests(['smoke', 'all'], () => {
|
||||||
context("Auto Screens UI", () => {
|
context("Auto Screens UI", () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
cy.createTestApp()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should disable the autogenerated screen options if no sources are available", () => {
|
||||||
|
cy.createApp("First Test App", false)
|
||||||
|
|
||||||
|
cy.closeModal();
|
||||||
|
|
||||||
|
cy.contains("Design").click()
|
||||||
|
cy.get("[aria-label=AddCircle]").click()
|
||||||
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get(".item.disabled").contains("Autogenerated screens")
|
||||||
|
cy.get(".confirm-wrap .spectrum-Button").should('be.disabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.deleteAllApps()
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not display incompatible sources", () => {
|
||||||
|
cy.createApp("Test App")
|
||||||
|
|
||||||
|
cy.selectExternalDatasource("REST")
|
||||||
|
cy.selectExternalDatasource("S3")
|
||||||
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get(".spectrum-Button").contains("Save and continue to query").click({ force : true })
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.navigateToAutogeneratedModal()
|
||||||
|
|
||||||
|
cy.get('.data-source-entry').should('have.length', 1)
|
||||||
|
cy.get('.data-source-entry')
|
||||||
|
|
||||||
|
cy.deleteAllApps()
|
||||||
|
});
|
||||||
|
|
||||||
it("should generate internal table screens", () => {
|
it("should generate internal table screens", () => {
|
||||||
// Create autogenerated screens from the internal table
|
cy.createTestApp()
|
||||||
cy.createAutogeneratedScreens(["Cypress Tests"])
|
// Create Autogenerated screens from the internal table
|
||||||
|
cy.createDatasourceScreen(["Cypress Tests"])
|
||||||
// Confirm screens have been auto generated
|
// Confirm screens have been auto generated
|
||||||
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true })
|
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true })
|
||||||
cy.get(".nav-items-container").should('contain', 'cypress-tests/:id')
|
cy.get(".nav-items-container").should('contain', 'cypress-tests/:id')
|
||||||
|
@ -21,8 +53,8 @@ filterTests(['smoke', 'all'], () => {
|
||||||
const initialTable = "Cypress Tests"
|
const initialTable = "Cypress Tests"
|
||||||
const secondTable = "Table Two"
|
const secondTable = "Table Two"
|
||||||
cy.createTable(secondTable)
|
cy.createTable(secondTable)
|
||||||
// Create autogenerated screens from the internal tables
|
// Create Autogenerated screens from the internal tables
|
||||||
cy.createAutogeneratedScreens([initialTable, secondTable])
|
cy.createDatasourceScreen([initialTable, secondTable])
|
||||||
// Confirm screens have been auto generated
|
// Confirm screens have been auto generated
|
||||||
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true })
|
cy.get(".nav-items-container").contains("cypress-tests").click({ force: true })
|
||||||
// Previously generated tables are suffixed with numbers - as expected
|
// Previously generated tables are suffixed with numbers - as expected
|
||||||
|
@ -33,6 +65,25 @@ filterTests(['smoke', 'all'], () => {
|
||||||
.and('contain', 'table-two/new/row')
|
.and('contain', 'table-two/new/row')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should generate multiple internal table screens with the same screen access level", () => {
|
||||||
|
//The tables created in the previous step still exist
|
||||||
|
cy.createTable("Table Three")
|
||||||
|
cy.createTable("Table Four")
|
||||||
|
cy.createDatasourceScreen(["Table Three", "Table Four"], "Admin")
|
||||||
|
|
||||||
|
cy.get(".nav-items-container").contains("table-three").click()
|
||||||
|
cy.get(".nav-items-container").should('contain', 'table-three/:id')
|
||||||
|
.and('contain', 'table-three/new/row')
|
||||||
|
|
||||||
|
cy.get(".nav-items-container").contains("table-four").click()
|
||||||
|
cy.get(".nav-items-container").should('contain', 'table-four/:id')
|
||||||
|
.and('contain', 'table-four/new/row')
|
||||||
|
|
||||||
|
//The access level should now be set to admin. Previous screens should be filtered.
|
||||||
|
cy.get(".nav-items-container").contains("table-two").should('not.exist')
|
||||||
|
cy.get(".nav-items-container").contains("cypress-tests").should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
if (Cypress.env("TEST_ENV")) {
|
if (Cypress.env("TEST_ENV")) {
|
||||||
it("should generate data source screens", () => {
|
it("should generate data source screens", () => {
|
||||||
// Using MySQL data source for testing this
|
// Using MySQL data source for testing this
|
||||||
|
@ -40,8 +91,9 @@ filterTests(['smoke', 'all'], () => {
|
||||||
// Select & configure MySQL data source
|
// Select & configure MySQL data source
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
cy.addDatasourceConfig(datasource)
|
cy.addDatasourceConfig(datasource)
|
||||||
// Create autogenerated screens from a MySQL table - MySQL contains books table
|
// Create Autogenerated screens from a MySQL table - MySQL contains books table
|
||||||
cy.createAutogeneratedScreens(["books"])
|
cy.createDatasourceScreen(["books"])
|
||||||
|
|
||||||
cy.get(".nav-items-container").contains("books").click()
|
cy.get(".nav-items-container").contains("books").click()
|
||||||
cy.get(".nav-items-container").should('contain', 'books/:id')
|
cy.get(".nav-items-container").should('contain', 'books/:id')
|
||||||
.and('contain', 'books/new/row')
|
.and('contain', 'books/new/row')
|
||||||
|
|
|
@ -25,9 +25,13 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
|
|
||||||
if (Cypress.env("TEST_ENV")) {
|
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
||||||
|
.its("body")
|
||||||
|
.then(val => {
|
||||||
|
if (val.length > 0) {
|
||||||
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
cy.get(".spectrum-Button").contains("Templates").click({force: true})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
cy.get(".template-category-filters").should("exist")
|
cy.get(".template-category-filters").should("exist")
|
||||||
cy.get(".template-categories").should("exist")
|
cy.get(".template-categories").should("exist")
|
||||||
|
|
|
@ -20,7 +20,6 @@ filterTests(['smoke', 'all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Setup trigger
|
// Setup trigger
|
||||||
cy.contains("Setup").click()
|
|
||||||
cy.get(".spectrum-Picker-label").click()
|
cy.get(".spectrum-Picker-label").click()
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.contains("dog").click()
|
cy.contains("dog").click()
|
||||||
|
@ -32,7 +31,6 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.contains("Create Row").trigger('mouseover').click().click()
|
cy.contains("Create Row").trigger('mouseover').click().click()
|
||||||
cy.get(".spectrum-Button--cta").click()
|
cy.get(".spectrum-Button--cta").click()
|
||||||
})
|
})
|
||||||
cy.contains("Setup").click()
|
|
||||||
cy.get(".spectrum-Picker-label").eq(1).click()
|
cy.get(".spectrum-Picker-label").eq(1).click()
|
||||||
cy.contains("dog").click()
|
cy.contains("dog").click()
|
||||||
cy.get(".spectrum-Textfield-input")
|
cy.get(".spectrum-Textfield-input")
|
||||||
|
|
|
@ -26,7 +26,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
|
|
||||||
it("should add a URL param binding", () => {
|
it("should add a URL param binding", () => {
|
||||||
const paramName = "foo"
|
const paramName = "foo"
|
||||||
cy.createScreen("Test Param", `/test/:${paramName}`)
|
cy.createScreen(`/test/:${paramName}`)
|
||||||
cy.addComponent("Elements", "Paragraph").then(componentId => {
|
cy.addComponent("Elements", "Paragraph").then(componentId => {
|
||||||
addSettingBinding("text", `URL.${paramName}`)
|
addSettingBinding("text", `URL.${paramName}`)
|
||||||
// The builder preview pages don't have a real URL, so all we can do
|
// The builder preview pages don't have a real URL, so all we can do
|
||||||
|
|
|
@ -9,17 +9,33 @@ filterTests(["smoke", "all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should successfully create a screen", () => {
|
it("Should successfully create a screen", () => {
|
||||||
cy.createScreen("Test Screen", "/test")
|
cy.createScreen("/test")
|
||||||
cy.get(".nav-items-container").within(() => {
|
cy.get(".nav-items-container").within(() => {
|
||||||
cy.contains("/test").should("exist")
|
cy.contains("/test").should("exist")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Should update the url", () => {
|
it("Should update the url", () => {
|
||||||
cy.createScreen("Test Screen", "test with spaces")
|
cy.createScreen("test with spaces")
|
||||||
cy.get(".nav-items-container").within(() => {
|
cy.get(".nav-items-container").within(() => {
|
||||||
cy.contains("/test-with-spaces").should("exist")
|
cy.contains("/test-with-spaces").should("exist")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("Should create a blank screen with the selected access level", () => {
|
||||||
|
cy.createScreen("admin only", "Admin")
|
||||||
|
|
||||||
|
cy.get(".nav-items-container").within(() => {
|
||||||
|
cy.contains("/admin-only").should("exist")
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.createScreen("open to all", "Public")
|
||||||
|
|
||||||
|
cy.get(".nav-items-container").within(() => {
|
||||||
|
cy.contains("/open-to-all").should("exist")
|
||||||
|
//The access level should now be set to admin. Previous screens should be filtered.
|
||||||
|
cy.get(".nav-item").contains("/test-screen").should("not.exist")
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -55,13 +55,14 @@ filterTests(["smoke", "all"], () => {
|
||||||
|
|
||||||
if (Cypress.env("TEST_ENV")) {
|
if (Cypress.env("TEST_ENV")) {
|
||||||
// No Pagination in CI - Test env only for the next two tests
|
// No Pagination in CI - Test env only for the next two tests
|
||||||
it("Adds 15 rows and checks pagination", () => {
|
xit("Adds 15 rows and checks pagination", () => {
|
||||||
// 10 rows per page, 15 rows should create 2 pages within table
|
// 10 rows per page, 15 rows should create 2 pages within table
|
||||||
const totalRows = 16
|
const totalRows = 16
|
||||||
for (let i = 1; i < totalRows; i++) {
|
for (let i = 1; i < totalRows; i++) {
|
||||||
cy.addRow([i])
|
cy.addRow([i])
|
||||||
}
|
}
|
||||||
cy.wait(1000)
|
cy.reload()
|
||||||
|
cy.wait(2000)
|
||||||
cy.get(".spectrum-Pagination").within(() => {
|
cy.get(".spectrum-Pagination").within(() => {
|
||||||
cy.get(".spectrum-ActionButton").eq(1).click()
|
cy.get(".spectrum-ActionButton").eq(1).click()
|
||||||
})
|
})
|
||||||
|
@ -70,13 +71,13 @@ filterTests(["smoke", "all"], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Deletes rows and checks pagination", () => {
|
xit("Deletes rows and checks pagination", () => {
|
||||||
// Delete rows, removing second page of rows from table
|
// Delete rows, removing second page from table
|
||||||
const deleteRows = 5
|
|
||||||
cy.get(".spectrum-Checkbox-input").check({ force: true })
|
cy.get(".spectrum-Checkbox-input").check({ force: true })
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".popovers").within(() => {
|
||||||
cy.contains("Delete 5 row(s)").click()
|
cy.get(".spectrum-Button").click({ force: true })
|
||||||
cy.get(".spectrum-Modal").contains("Delete").click()
|
})
|
||||||
|
cy.get(".spectrum-Dialog-grid").contains("Delete").click({ force: true })
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
|
|
||||||
// Confirm table only has one page
|
// Confirm table only has one page
|
||||||
|
|
|
@ -19,6 +19,7 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Save and fetch tables")
|
.contains("Save and fetch tables")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
|
cy.wait(500)
|
||||||
// Intercept Request after button click & apply assertions
|
// Intercept Request after button click & apply assertions
|
||||||
cy.wait("@datasource")
|
cy.wait("@datasource")
|
||||||
cy.get("@datasource")
|
cy.get("@datasource")
|
||||||
|
@ -31,6 +32,7 @@ filterTests(["all"], () => {
|
||||||
cy.get("@datasource")
|
cy.get("@datasource")
|
||||||
.its("response.body")
|
.its("response.body")
|
||||||
.should("have.property", "status", 500)
|
.should("have.property", "status", 500)
|
||||||
|
cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should add MySQL data source and fetch tables", () => {
|
it("should add MySQL data source and fetch tables", () => {
|
||||||
|
@ -72,10 +74,13 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Popover").contains("COUNTRIES").click()
|
cy.get(".spectrum-Popover").contains("COUNTRIES").click()
|
||||||
cy.get(".spectrum-Picker").eq(4).click()
|
cy.get(".spectrum-Picker").eq(4).click()
|
||||||
cy.get(".spectrum-Popover").contains("REGION_ID").click()
|
cy.get(".spectrum-Popover").contains("REGION_ID").click()
|
||||||
// Save relationship & reload page
|
|
||||||
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
|
||||||
cy.reload()
|
|
||||||
})
|
})
|
||||||
|
// Save relationship & reload page
|
||||||
|
cy.get(".spectrum-ButtonGroup").within(() => {
|
||||||
|
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
||||||
|
})
|
||||||
|
cy.reload()
|
||||||
|
|
||||||
// Confirm table length & column name
|
// Confirm table length & column name
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(1)
|
.eq(1)
|
||||||
|
@ -131,7 +136,7 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Table-row").eq(0).click()
|
cy.get(".spectrum-Table-row").eq(0).click({ force: true })
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
|
@ -175,11 +180,12 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should duplicate a query", () => {
|
it("should duplicate a query", () => {
|
||||||
// Get last nav item - The query
|
/// Get query nav item - QueryName
|
||||||
cy.get(".nav-item")
|
cy.get(".nav-item")
|
||||||
.last()
|
.contains(queryName)
|
||||||
|
.parent()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".icon").eq(1).click({ force: true })
|
cy.get(".spectrum-Icon").eq(1).click({ force: true })
|
||||||
})
|
})
|
||||||
// Select and confirm duplication
|
// Select and confirm duplication
|
||||||
cy.get(".spectrum-Menu").contains("Duplicate").click()
|
cy.get(".spectrum-Menu").contains("Duplicate").click()
|
||||||
|
@ -199,12 +205,12 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should delete a query", () => {
|
it("should delete a query", () => {
|
||||||
// Get last nav item - The query
|
// Get query nav item - QueryName
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
cy.get(".nav-item")
|
cy.get(".nav-item")
|
||||||
.last()
|
.contains(queryName)
|
||||||
|
.parent()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".icon").eq(1).click({ force: true })
|
cy.get(".spectrum-Icon").eq(1).click({ force: true })
|
||||||
})
|
})
|
||||||
// Select Delete
|
// Select Delete
|
||||||
cy.get(".spectrum-Menu").contains("Delete").click()
|
cy.get(".spectrum-Menu").contains("Delete").click()
|
||||||
|
@ -212,10 +218,8 @@ filterTests(["all"], () => {
|
||||||
.contains("Delete Query")
|
.contains("Delete Query")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
}
|
|
||||||
// Confirm deletion
|
// Confirm deletion
|
||||||
cy.get(".nav-item").should("not.contain", queryName)
|
cy.get(".nav-item").should("not.contain", queryName)
|
||||||
cy.get(".nav-item").should("not.contain", queryRename)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -46,9 +46,10 @@ filterTests(["all"], () => {
|
||||||
cy.get("@datasource")
|
cy.get("@datasource")
|
||||||
.its("response.body")
|
.its("response.body")
|
||||||
.should("have.property", "status", 500)
|
.should("have.property", "status", 500)
|
||||||
|
cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should add Oracle data source and fetch tables", () => {
|
xit("should add Oracle data source and fetch tables", () => {
|
||||||
// Add & configure Oracle data source
|
// Add & configure Oracle data source
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
cy.intercept("**/datasources").as("datasource")
|
cy.intercept("**/datasources").as("datasource")
|
||||||
|
@ -64,7 +65,7 @@ filterTests(["all"], () => {
|
||||||
.should("be.gt", 0)
|
.should("be.gt", 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should define a One relationship type", () => {
|
xit("should define a One relationship type", () => {
|
||||||
// Select relationship type & configure
|
// Select relationship type & configure
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Define relationship")
|
.contains("Define relationship")
|
||||||
|
@ -93,7 +94,7 @@ filterTests(["all"], () => {
|
||||||
cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS")
|
cy.get(".spectrum-Table-cell").should("contain", "COUNTRIES to REGIONS")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should define a Many relationship type", () => {
|
xit("should define a Many relationship type", () => {
|
||||||
// Select relationship type & configure
|
// Select relationship type & configure
|
||||||
cy.get(".spectrum-Button")
|
cy.get(".spectrum-Button")
|
||||||
.contains("Define relationship")
|
.contains("Define relationship")
|
||||||
|
@ -127,7 +128,7 @@ filterTests(["all"], () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should delete relationships", () => {
|
xit("should delete relationships", () => {
|
||||||
// Delete both relationships
|
// Delete both relationships
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(1)
|
.eq(1)
|
||||||
|
@ -156,7 +157,7 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should add a query", () => {
|
xit("should add a query", () => {
|
||||||
// Add query
|
// Add query
|
||||||
cy.get(".spectrum-Button").contains("Add query").click({ force: true })
|
cy.get(".spectrum-Button").contains("Add query").click({ force: true })
|
||||||
cy.get(".spectrum-Form-item")
|
cy.get(".spectrum-Form-item")
|
||||||
|
@ -181,7 +182,7 @@ filterTests(["all"], () => {
|
||||||
cy.get(".nav-item").should("contain", queryName)
|
cy.get(".nav-item").should("contain", queryName)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should duplicate a query", () => {
|
xit("should duplicate a query", () => {
|
||||||
// Get query nav item
|
// Get query nav item
|
||||||
cy.get(".nav-item")
|
cy.get(".nav-item")
|
||||||
.contains(queryName)
|
.contains(queryName)
|
||||||
|
@ -194,7 +195,7 @@ filterTests(["all"], () => {
|
||||||
cy.get(".nav-item").should("contain", queryName + " (1)")
|
cy.get(".nav-item").should("contain", queryName + " (1)")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should edit a query name", () => {
|
xit("should edit a query name", () => {
|
||||||
// Rename query
|
// Rename query
|
||||||
cy.get(".spectrum-Form-item")
|
cy.get(".spectrum-Form-item")
|
||||||
.eq(0)
|
.eq(0)
|
||||||
|
@ -206,7 +207,7 @@ filterTests(["all"], () => {
|
||||||
cy.get(".nav-item").should("contain", queryRename)
|
cy.get(".nav-item").should("contain", queryRename)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should delete a query", () => {
|
xit("should delete a query", () => {
|
||||||
// Get query nav item - QueryName
|
// Get query nav item - QueryName
|
||||||
cy.get(".nav-item")
|
cy.get(".nav-item")
|
||||||
.contains(queryName)
|
.contains(queryName)
|
||||||
|
|
|
@ -21,16 +21,10 @@ filterTests(["all"], () => {
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
// Intercept Request after button click & apply assertions
|
// Intercept Request after button click & apply assertions
|
||||||
cy.wait("@datasource")
|
cy.wait("@datasource")
|
||||||
cy.get("@datasource")
|
|
||||||
.its("response.body")
|
|
||||||
.should(
|
|
||||||
"have.property",
|
|
||||||
"message",
|
|
||||||
"connect ECONNREFUSED 127.0.0.1:5432"
|
|
||||||
)
|
|
||||||
cy.get("@datasource")
|
cy.get("@datasource")
|
||||||
.its("response.body")
|
.its("response.body")
|
||||||
.should("have.property", "status", 500)
|
.should("have.property", "status", 500)
|
||||||
|
cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should add PostgreSQL data source and fetch tables", () => {
|
it("should add PostgreSQL data source and fetch tables", () => {
|
||||||
|
@ -113,13 +107,13 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should delete a relationship", () => {
|
it("should delete a relationship", () => {
|
||||||
cy.get(".hierarchy-items-container").contains(datasource).click()
|
cy.get(".hierarchy-items-container").contains("PostgreSQL-2").click()
|
||||||
cy.reload()
|
cy.reload()
|
||||||
// Delete one relationship
|
// Delete one relationship
|
||||||
cy.get(".spectrum-Table")
|
cy.get(".spectrum-Table")
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".spectrum-Table-row").eq(0).click()
|
cy.get(".spectrum-Table-row").eq(0).click({ force: true })
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
|
@ -161,7 +155,7 @@ filterTests(["all"], () => {
|
||||||
|
|
||||||
it("should switch to schema with no tables", () => {
|
it("should switch to schema with no tables", () => {
|
||||||
// Switch Schema - To one without any tables
|
// Switch Schema - To one without any tables
|
||||||
cy.get(".hierarchy-items-container").contains(datasource).click()
|
cy.get(".hierarchy-items-container").contains("PostgreSQL-2").click()
|
||||||
switchSchema("randomText")
|
switchSchema("randomText")
|
||||||
|
|
||||||
// No tables displayed
|
// No tables displayed
|
||||||
|
@ -208,11 +202,12 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should duplicate a query", () => {
|
it("should duplicate a query", () => {
|
||||||
// Get last nav item - The query
|
// Locate previously created query
|
||||||
cy.get(".nav-item")
|
cy.get(".nav-item")
|
||||||
.last()
|
.contains(queryName)
|
||||||
|
.siblings(".actions")
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".icon").eq(1).click({ force: true })
|
cy.get(".icon").click({ force: true })
|
||||||
})
|
})
|
||||||
// Select and confirm duplication
|
// Select and confirm duplication
|
||||||
cy.get(".spectrum-Menu").contains("Duplicate").click()
|
cy.get(".spectrum-Menu").contains("Duplicate").click()
|
||||||
|
@ -240,12 +235,12 @@ filterTests(["all"], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should delete a query", () => {
|
it("should delete a query", () => {
|
||||||
// Get last nav item - The query
|
// Get query nav item - QueryName
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
cy.get(".nav-item")
|
cy.get(".nav-item")
|
||||||
.last()
|
.contains(queryName)
|
||||||
|
.parent()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get(".icon").eq(1).click({ force: true })
|
cy.get(".spectrum-Icon").eq(1).click({ force: true })
|
||||||
})
|
})
|
||||||
// Select Delete
|
// Select Delete
|
||||||
cy.get(".spectrum-Menu").contains("Delete").click()
|
cy.get(".spectrum-Menu").contains("Delete").click()
|
||||||
|
@ -253,10 +248,8 @@ filterTests(["all"], () => {
|
||||||
.contains("Delete Query")
|
.contains("Delete Query")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
}
|
|
||||||
// Confirm deletion
|
// Confirm deletion
|
||||||
cy.get(".nav-item").should("not.contain", queryName)
|
cy.get(".nav-item").should("not.contain", queryName)
|
||||||
cy.get(".nav-item").should("not.contain", queryRename)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const switchSchema = schema => {
|
const switchSchema = schema => {
|
||||||
|
|
|
@ -32,7 +32,17 @@ Cypress.Commands.add("login", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createApp", name => {
|
Cypress.Commands.add("closeModal", () => {
|
||||||
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get(".close-icon").click()
|
||||||
|
cy.wait(500)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("createApp", (name, addDefaultTable) => {
|
||||||
|
const shouldCreateDefaultTable =
|
||||||
|
typeof addDefaultTable != "boolean" ? true : addDefaultTable
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
cy.get(`[data-cy="create-app-btn"]`).click({ force: true })
|
||||||
|
@ -51,7 +61,9 @@ Cypress.Commands.add("createApp", name => {
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
|
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
|
||||||
cy.wait(10000)
|
cy.wait(10000)
|
||||||
})
|
})
|
||||||
|
if (shouldCreateDefaultTable) {
|
||||||
cy.createTable("Cypress Tests", true)
|
cy.createTable("Cypress Tests", true)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("deleteApp", name => {
|
Cypress.Commands.add("deleteApp", name => {
|
||||||
|
@ -60,6 +72,8 @@ Cypress.Commands.add("deleteApp", name => {
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
||||||
.its("body")
|
.its("body")
|
||||||
.then(val => {
|
.then(val => {
|
||||||
|
const findAppName = val.some(val => val.name == name)
|
||||||
|
if (findAppName) {
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
if (Cypress.env("TEST_ENV")) {
|
if (Cypress.env("TEST_ENV")) {
|
||||||
cy.searchForApplication(name)
|
cy.searchForApplication(name)
|
||||||
|
@ -100,6 +114,9 @@ Cypress.Commands.add("deleteApp", name => {
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -130,7 +147,7 @@ Cypress.Commands.add("createTestApp", () => {
|
||||||
const appName = "Cypress Tests"
|
const appName = "Cypress Tests"
|
||||||
cy.deleteApp(appName)
|
cy.deleteApp(appName)
|
||||||
cy.createApp(appName, "This app is used for Cypress testing.")
|
cy.createApp(appName, "This app is used for Cypress testing.")
|
||||||
cy.createScreen("home", "home")
|
cy.createScreen("home")
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createTestTableWithData", () => {
|
Cypress.Commands.add("createTestTableWithData", () => {
|
||||||
|
@ -270,32 +287,98 @@ Cypress.Commands.add("navigateToDataSection", () => {
|
||||||
cy.contains("Data").click()
|
cy.contains("Data").click()
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createScreen", (screenName, route) => {
|
//Blank
|
||||||
|
Cypress.Commands.add("createScreen", (route, accessLevelLabel) => {
|
||||||
cy.contains("Design").click()
|
cy.contains("Design").click()
|
||||||
cy.get("[aria-label=AddCircle]").click()
|
cy.get("[aria-label=AddCircle]").click()
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get(".item").contains("Blank").click()
|
cy.get(".item").contains("Blank screen").click()
|
||||||
cy.get(".spectrum-Button").contains("Add screens").click({ force: true })
|
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
cy.get(".spectrum-Dialog-grid").within(() => {
|
||||||
cy.get(".spectrum-Form-itemField").eq(0).type(screenName)
|
cy.get(".spectrum-Form-itemField").eq(0).type(route)
|
||||||
cy.get(".spectrum-Form-itemField").eq(1).type(route)
|
|
||||||
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
if (accessLevelLabel) {
|
||||||
|
cy.get(".spectrum-Picker-label").click()
|
||||||
|
cy.wait(500)
|
||||||
|
cy.contains(accessLevelLabel).click()
|
||||||
|
}
|
||||||
|
cy.get(".spectrum-Button").contains("Done").click({ force: true })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createAutogeneratedScreens", screenNames => {
|
Cypress.Commands.add(
|
||||||
|
"createDatasourceScreen",
|
||||||
|
(datasourceNames, accessLevelLabel) => {
|
||||||
|
cy.contains("Design").click()
|
||||||
|
cy.get("[aria-label=AddCircle]").click()
|
||||||
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get(".item").contains("Autogenerated screens").click()
|
||||||
|
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||||
|
cy.wait(500)
|
||||||
|
})
|
||||||
|
cy.get(".spectrum-Modal [data-cy='data-source-modal']").within(() => {
|
||||||
|
for (let i = 0; i < datasourceNames.length; i++) {
|
||||||
|
cy.get(".data-source-entry").contains(datasourceNames[i]).click()
|
||||||
|
//Ensure the check mark is visible
|
||||||
|
cy.get(".data-source-entry")
|
||||||
|
.contains(datasourceNames[i])
|
||||||
|
.get(".data-source-check")
|
||||||
|
.should("exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
cy.get(".spectrum-Button").contains("Confirm").click({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
if (accessLevelLabel) {
|
||||||
|
cy.get(".spectrum-Picker-label").click()
|
||||||
|
cy.wait(500)
|
||||||
|
cy.contains(accessLevelLabel).click()
|
||||||
|
}
|
||||||
|
cy.get(".spectrum-Button").contains("Done").click({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.contains("Design").click()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Cypress.Commands.add("navigateToAutogeneratedModal", () => {
|
||||||
// Screen name must already exist within data source
|
// Screen name must already exist within data source
|
||||||
cy.contains("Design").click()
|
cy.contains("Design").click()
|
||||||
cy.get("[aria-label=AddCircle]").click()
|
cy.get("[aria-label=AddCircle]").click()
|
||||||
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get(".item").contains("Autogenerated screens").click()
|
||||||
|
cy.get(".spectrum-Button").contains("Continue").click({ force: true })
|
||||||
|
cy.wait(500)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add(
|
||||||
|
"createAutogeneratedScreens",
|
||||||
|
(screenNames, accessLevelLabel) => {
|
||||||
|
cy.navigateToAutogeneratedModal()
|
||||||
|
|
||||||
for (let i = 0; i < screenNames.length; i++) {
|
for (let i = 0; i < screenNames.length; i++) {
|
||||||
cy.get(".item").contains(screenNames[i]).click()
|
cy.get(".data-source-entry").contains(screenNames[i]).click()
|
||||||
}
|
}
|
||||||
cy.get(".spectrum-Button").contains("Add screens").click({ force: true })
|
|
||||||
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
if (accessLevelLabel) {
|
||||||
|
cy.get(".spectrum-Picker-label").click()
|
||||||
|
cy.wait(500)
|
||||||
|
cy.contains(accessLevelLabel).click()
|
||||||
|
}
|
||||||
|
cy.get(".spectrum-Button").contains("Confirm").click({ force: true })
|
||||||
cy.wait(4000)
|
cy.wait(4000)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Cypress.Commands.add("addRow", values => {
|
Cypress.Commands.add("addRow", values => {
|
||||||
cy.contains("Create row").click()
|
cy.contains("Create row").click()
|
||||||
|
@ -410,7 +493,9 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => {
|
||||||
if (datasource == "Oracle") {
|
if (datasource == "Oracle") {
|
||||||
cy.get("input").clear().type(Cypress.env("oracle").HOST)
|
cy.get("input").clear().type(Cypress.env("oracle").HOST)
|
||||||
} else {
|
} else {
|
||||||
cy.get("input").clear().type(Cypress.env("HOST_IP"))
|
cy.get("input")
|
||||||
|
.clear({ force: true })
|
||||||
|
.type(Cypress.env("mysql").HOST, { force: true })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.105-alpha.10",
|
"version": "1.0.105-alpha.39",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.105-alpha.10",
|
"@budibase/bbui": "^1.0.105-alpha.39",
|
||||||
"@budibase/client": "^1.0.105-alpha.10",
|
"@budibase/client": "^1.0.105-alpha.39",
|
||||||
"@budibase/frontend-core": "^1.0.105-alpha.10",
|
"@budibase/frontend-core": "^1.0.105-alpha.39",
|
||||||
"@budibase/string-templates": "^1.0.105-alpha.10",
|
"@budibase/string-templates": "^1.0.105-alpha.39",
|
||||||
"@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",
|
||||||
|
|
|
@ -22,6 +22,7 @@ export const Events = {
|
||||||
},
|
},
|
||||||
SCREEN: {
|
SCREEN: {
|
||||||
CREATED: "Screen Created",
|
CREATED: "Screen Created",
|
||||||
|
CREATE_ROLE_UPDATED: "Changed Role On Screen Creation",
|
||||||
},
|
},
|
||||||
AUTOMATION: {
|
AUTOMATION: {
|
||||||
CREATED: "Automation Created",
|
CREATED: "Automation Created",
|
||||||
|
|
|
@ -654,7 +654,7 @@ export const getSchemaForDatasource = (asset, datasource, options) => {
|
||||||
* Builds a form schema given a form component.
|
* Builds a form schema given a form component.
|
||||||
* A form schema is a schema of all the fields nested anywhere within a form.
|
* A form schema is a schema of all the fields nested anywhere within a form.
|
||||||
*/
|
*/
|
||||||
const buildFormSchema = component => {
|
export const buildFormSchema = component => {
|
||||||
let schema = {}
|
let schema = {}
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return schema
|
return schema
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
if (v.internal) {
|
if (v.internal) {
|
||||||
acc[k] = v
|
acc[k] = v
|
||||||
}
|
}
|
||||||
|
delete acc.LOOP
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,9 @@
|
||||||
animate:flip={{ duration: 500 }}
|
animate:flip={{ duration: 500 }}
|
||||||
in:fly|local={{ x: 500, duration: 1500 }}
|
in:fly|local={{ x: 500, duration: 1500 }}
|
||||||
>
|
>
|
||||||
|
{#if block.stepId !== "LOOP"}
|
||||||
<FlowItem {testDataModal} {block} />
|
<FlowItem {testDataModal} {block} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
Modal,
|
Modal,
|
||||||
Button,
|
Button,
|
||||||
StatusLight,
|
StatusLight,
|
||||||
ActionButton,
|
|
||||||
Select,
|
Select,
|
||||||
|
ActionButton,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
||||||
|
@ -25,8 +25,8 @@
|
||||||
let webhookModal
|
let webhookModal
|
||||||
let actionModal
|
let actionModal
|
||||||
let resultsModal
|
let resultsModal
|
||||||
let setupToggled
|
|
||||||
let blockComplete
|
let blockComplete
|
||||||
|
let showLooping = false
|
||||||
|
|
||||||
$: rowControl = $automationStore.selectedAutomation.automation.rowControl
|
$: rowControl = $automationStore.selectedAutomation.automation.rowControl
|
||||||
$: showBindingPicker =
|
$: showBindingPicker =
|
||||||
|
@ -48,8 +48,21 @@
|
||||||
$automationStore.selectedAutomation?.automation?.definition?.steps.length +
|
$automationStore.selectedAutomation?.automation?.definition?.steps.length +
|
||||||
1
|
1
|
||||||
|
|
||||||
|
$: loopingSelected =
|
||||||
|
$automationStore.selectedAutomation?.automation.definition.steps.find(
|
||||||
|
x => x.blockToLoop === block.id
|
||||||
|
)
|
||||||
|
|
||||||
async function deleteStep() {
|
async function deleteStep() {
|
||||||
|
let loopBlock =
|
||||||
|
$automationStore.selectedAutomation?.automation.definition.steps.find(
|
||||||
|
x => x.blockToLoop === block.id
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (loopBlock) {
|
||||||
|
automationStore.actions.deleteAutomationBlock(loopBlock)
|
||||||
|
}
|
||||||
automationStore.actions.deleteAutomationBlock(block)
|
automationStore.actions.deleteAutomationBlock(block)
|
||||||
await automationStore.actions.save(
|
await automationStore.actions.save(
|
||||||
$automationStore.selectedAutomation?.automation
|
$automationStore.selectedAutomation?.automation
|
||||||
|
@ -72,6 +85,23 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addLooping() {
|
||||||
|
loopingSelected = true
|
||||||
|
const loopDefinition = $automationStore.blockDefinitions.ACTION.LOOP
|
||||||
|
|
||||||
|
const loopBlock = $automationStore.selectedAutomation.constructBlock(
|
||||||
|
"ACTION",
|
||||||
|
"LOOP",
|
||||||
|
loopDefinition
|
||||||
|
)
|
||||||
|
loopBlock.blockToLoop = block.id
|
||||||
|
block.loopBlock = loopBlock.id
|
||||||
|
automationStore.actions.addBlockToAutomation(loopBlock, blockIdx)
|
||||||
|
await automationStore.actions.save(
|
||||||
|
$automationStore.selectedAutomation?.automation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async function onSelect(block) {
|
async function onSelect(block) {
|
||||||
await automationStore.update(state => {
|
await automationStore.update(state => {
|
||||||
state.selectedBlock = block
|
state.selectedBlock = block
|
||||||
|
@ -80,13 +110,68 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class={`block ${block.type} hoverable`} class:selected on:click={() => {}}>
|
||||||
|
{#if loopingSelected}
|
||||||
|
<div class="blockSection">
|
||||||
<div
|
<div
|
||||||
class={`block ${block.type} hoverable`}
|
on:click={() => {
|
||||||
class:selected
|
showLooping = !showLooping
|
||||||
|
}}
|
||||||
|
class="splitHeader"
|
||||||
|
>
|
||||||
|
<div class="center-items">
|
||||||
|
<svg
|
||||||
|
width="28px"
|
||||||
|
height="28px"
|
||||||
|
class="spectrum-Icon"
|
||||||
|
style="color:grey;"
|
||||||
|
focusable="false"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-icon-18-Reuse" />
|
||||||
|
</svg>
|
||||||
|
<div class="iconAlign">
|
||||||
|
<Detail size="S">Looping</Detail>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blockTitle">
|
||||||
|
<div
|
||||||
|
style="margin-left: 10px;"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
onSelect(block)
|
onSelect(block)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Icon name={showLooping ? "ChevronDown" : "ChevronUp"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider noMargin />
|
||||||
|
{#if !showLooping}
|
||||||
|
<div class="blockSection">
|
||||||
|
<div class="block-options">
|
||||||
|
<div class="delete-padding" on:click={() => deleteStep()}>
|
||||||
|
<Icon name="DeleteOutline" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Layout noPadding gap="S">
|
||||||
|
<AutomationBlockSetup
|
||||||
|
schemaProperties={Object.entries(
|
||||||
|
$automationStore.blockDefinitions.ACTION.LOOP.schema.inputs
|
||||||
|
.properties
|
||||||
|
)}
|
||||||
|
block={$automationStore.selectedAutomation?.automation.definition.steps.find(
|
||||||
|
x => x.blockToLoop === block.id
|
||||||
|
)}
|
||||||
|
{webhookModal}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
<Divider noMargin />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="blockSection">
|
<div class="blockSection">
|
||||||
<div
|
<div
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
@ -123,55 +208,57 @@
|
||||||
<Detail size="S">{block?.name?.toUpperCase() || ""}</Detail>
|
<Detail size="S">{block?.name?.toUpperCase() || ""}</Detail>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="blockTitle">
|
||||||
{#if testResult && testResult[0]}
|
{#if testResult && testResult[0]}
|
||||||
<span on:click={() => resultsModal.show()}>
|
<div style="float: right;" on:click={() => resultsModal.show()}>
|
||||||
<StatusLight
|
<StatusLight
|
||||||
positive={isTrigger || testResult[0].outputs?.success}
|
positive={isTrigger || testResult[0].outputs?.success}
|
||||||
negative={!testResult[0].outputs?.success}
|
negative={!testResult[0].outputs?.success}
|
||||||
><Body size="XS">View response</Body></StatusLight
|
><Body size="XS">View response</Body></StatusLight
|
||||||
>
|
>
|
||||||
</span>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div
|
||||||
|
style="margin-left: 10px;"
|
||||||
|
on:click={() => {
|
||||||
|
onSelect(block)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name={blockComplete ? "ChevronDown" : "ChevronUp"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if !blockComplete}
|
{#if !blockComplete}
|
||||||
<Divider noMargin />
|
<Divider noMargin />
|
||||||
<div class="blockSection">
|
<div class="blockSection">
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<div class="splitHeader">
|
|
||||||
<ActionButton
|
|
||||||
on:click={() => {
|
|
||||||
onSelect(block)
|
|
||||||
setupToggled = !setupToggled
|
|
||||||
}}
|
|
||||||
quiet
|
|
||||||
icon={setupToggled ? "ChevronDown" : "ChevronRight"}
|
|
||||||
>
|
|
||||||
<Detail size="S">Setup</Detail>
|
|
||||||
</ActionButton>
|
|
||||||
{#if !isTrigger}
|
{#if !isTrigger}
|
||||||
<div class="block-options">
|
|
||||||
{#if showBindingPicker}
|
|
||||||
<div>
|
<div>
|
||||||
|
<div class="block-options">
|
||||||
|
{#if !loopingSelected}
|
||||||
|
<ActionButton on:click={() => addLooping()} icon="Reuse"
|
||||||
|
>Add Looping</ActionButton
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{#if showBindingPicker}
|
||||||
<Select
|
<Select
|
||||||
on:change={toggleFieldControl}
|
on:change={toggleFieldControl}
|
||||||
quiet
|
|
||||||
defaultValue="Use values"
|
defaultValue="Use values"
|
||||||
autoWidth
|
autoWidth
|
||||||
value={rowControl ? "Use bindings" : "Use values"}
|
value={rowControl ? "Use bindings" : "Use values"}
|
||||||
options={["Use values", "Use bindings"]}
|
options={["Use values", "Use bindings"]}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
<div class="delete-padding" on:click={() => deleteStep()}>
|
<ActionButton
|
||||||
<Icon name="DeleteOutline" />
|
on:click={() => deleteStep()}
|
||||||
|
icon="DeleteOutline"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if setupToggled}
|
|
||||||
<AutomationBlockSetup
|
<AutomationBlockSetup
|
||||||
schemaProperties={Object.entries(block.schema.inputs.properties)}
|
schemaProperties={Object.entries(block.schema.inputs.properties)}
|
||||||
{block}
|
{block}
|
||||||
|
@ -182,7 +269,6 @@
|
||||||
>Finish and test automation</Button
|
>Finish and test automation</Button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -210,8 +296,10 @@
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
}
|
}
|
||||||
.block-options {
|
.block-options {
|
||||||
display: flex;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
.center-items {
|
.center-items {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -246,4 +334,9 @@
|
||||||
/* center horizontally */
|
/* center horizontally */
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blockTitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { ModalContent, Icon, Detail, TextArea } from "@budibase/bbui"
|
import { ModalContent, Icon, Detail, TextArea, Label } from "@budibase/bbui"
|
||||||
|
|
||||||
export let testResult
|
export let testResult
|
||||||
export let isTrigger
|
export let isTrigger
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
<ModalContent
|
<ModalContent
|
||||||
showCloseIcon={false}
|
showCloseIcon={false}
|
||||||
showConfirmButton={false}
|
showConfirmButton={false}
|
||||||
title="Test Automation"
|
title="Test Results"
|
||||||
cancelText="Close"
|
cancelText="Close"
|
||||||
>
|
>
|
||||||
<div slot="header">
|
<div slot="header">
|
||||||
|
@ -26,7 +26,18 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span>
|
||||||
|
{#if testResult[0].outputs.iterations}
|
||||||
|
<div style="display: flex;">
|
||||||
|
<Icon name="Reuse" />
|
||||||
|
<div style="margin-left: 10px;">
|
||||||
|
<Label>
|
||||||
|
This loop ran {testResult[0].outputs.iterations} times.</Label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
<div
|
<div
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
inputToggled = !inputToggled
|
inputToggled = !inputToggled
|
||||||
|
|
|
@ -87,33 +87,65 @@
|
||||||
if (!block || !automation) {
|
if (!block || !automation) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find previous steps to the selected one
|
// Find previous steps to the selected one
|
||||||
let allSteps = [...automation.steps]
|
let allSteps = [...automation.steps]
|
||||||
|
|
||||||
if (automation.trigger) {
|
if (automation.trigger) {
|
||||||
allSteps = [automation.trigger, ...allSteps]
|
allSteps = [automation.trigger, ...allSteps]
|
||||||
}
|
}
|
||||||
const blockIdx = allSteps.findIndex(step => step.id === block.id)
|
let blockIdx = allSteps.findIndex(step => step.id === block.id)
|
||||||
|
|
||||||
// Extract all outputs from all previous steps as available bindings
|
// Extract all outputs from all previous steps as available bindins
|
||||||
let bindings = []
|
let bindings = []
|
||||||
for (let idx = 0; idx < blockIdx; idx++) {
|
for (let idx = 0; idx < blockIdx; idx++) {
|
||||||
const outputs = Object.entries(
|
let wasLoopBlock = allSteps[idx]?.stepId === "LOOP"
|
||||||
allSteps[idx].schema?.outputs?.properties ?? {}
|
let isLoopBlock =
|
||||||
)
|
allSteps[idx]?.stepId === "LOOP" &&
|
||||||
|
allSteps.find(x => x.blockToLoop === block.id)
|
||||||
|
|
||||||
|
// If the previous block was a loop block, decerement the index so the following
|
||||||
|
// steps are in the correct order
|
||||||
|
if (wasLoopBlock) {
|
||||||
|
blockIdx--
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = allSteps[idx]?.schema?.outputs?.properties ?? {}
|
||||||
|
|
||||||
|
// If its a Loop Block, we need to add this custom schema
|
||||||
|
if (isLoopBlock) {
|
||||||
|
schema = {
|
||||||
|
currentItem: {
|
||||||
|
type: "string",
|
||||||
|
description: "the item currently being executed",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const outputs = Object.entries(schema)
|
||||||
|
|
||||||
bindings = bindings.concat(
|
bindings = bindings.concat(
|
||||||
outputs.map(([name, value]) => {
|
outputs.map(([name, value]) => {
|
||||||
const runtime = idx === 0 ? `trigger.${name}` : `steps.${idx}.${name}`
|
let runtimeName = isLoopBlock
|
||||||
|
? `loop.${name}`
|
||||||
|
: block.name.startsWith("JS")
|
||||||
|
? `steps[${idx}].${name}`
|
||||||
|
: `steps.${idx}.${name}`
|
||||||
|
const runtime = idx === 0 ? `trigger.${name}` : runtimeName
|
||||||
return {
|
return {
|
||||||
label: runtime,
|
label: runtime,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
category: idx === 0 ? "Trigger outputs" : `Step ${idx} outputs`,
|
category:
|
||||||
|
idx === 0
|
||||||
|
? "Trigger outputs"
|
||||||
|
: isLoopBlock
|
||||||
|
? "Loop Outputs"
|
||||||
|
: `Step ${idx} outputs`,
|
||||||
path: runtime,
|
path: runtime,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,6 +294,14 @@
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
/>
|
/>
|
||||||
</CodeEditorModal>
|
</CodeEditorModal>
|
||||||
|
{:else if value.customType === "loopOption"}
|
||||||
|
<Select
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
autoWidth
|
||||||
|
value={inputData[key]}
|
||||||
|
options={["Array", "String"]}
|
||||||
|
defaultValue={"Array"}
|
||||||
|
/>
|
||||||
{:else if value.type === "string" || value.type === "number" || value.type === "integer"}
|
{:else if value.type === "string" || value.type === "number" || value.type === "integer"}
|
||||||
{#if isTestModal}
|
{#if isTestModal}
|
||||||
<ModalBindableInput
|
<ModalBindableInput
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
import Table from "./Table.svelte"
|
import Table from "./Table.svelte"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||||
import { Pagination } from "@budibase/bbui"
|
import { Pagination, Heading, Body, Layout } from "@budibase/bbui"
|
||||||
import { fetchData } from "@budibase/frontend-core"
|
import { fetchData } from "@budibase/frontend-core"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
|
||||||
|
@ -27,6 +27,8 @@
|
||||||
$: enrichedSchema = enrichSchema($tables.selected?.schema)
|
$: enrichedSchema = enrichSchema($tables.selected?.schema)
|
||||||
$: id = $tables.selected?._id
|
$: id = $tables.selected?._id
|
||||||
$: fetch = createFetch(id)
|
$: fetch = createFetch(id)
|
||||||
|
$: hasCols = checkHasCols(schema)
|
||||||
|
$: hasRows = !!$fetch.rows?.length
|
||||||
|
|
||||||
const enrichSchema = schema => {
|
const enrichSchema = schema => {
|
||||||
let tempSchema = { ...schema }
|
let tempSchema = { ...schema }
|
||||||
|
@ -47,6 +49,20 @@
|
||||||
|
|
||||||
return tempSchema
|
return tempSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkHasCols = schema => {
|
||||||
|
if (!schema || Object.keys(schema).length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let fields = Object.values(schema)
|
||||||
|
for (let field of fields) {
|
||||||
|
if (!field.autocolumn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Fetches new data whenever the table changes
|
// Fetches new data whenever the table changes
|
||||||
const createFetch = tableId => {
|
const createFetch = tableId => {
|
||||||
return fetchData({
|
return fetchData({
|
||||||
|
@ -104,19 +120,28 @@
|
||||||
disableSorting
|
disableSorting
|
||||||
on:updatecolumns={onUpdateColumns}
|
on:updatecolumns={onUpdateColumns}
|
||||||
on:updaterows={onUpdateRows}
|
on:updaterows={onUpdateRows}
|
||||||
|
customPlaceholder
|
||||||
>
|
>
|
||||||
<CreateColumnButton on:updatecolumns={onUpdateColumns} />
|
<div class="buttons">
|
||||||
{#if schema && Object.keys(schema).length > 0}
|
<div class="left-buttons">
|
||||||
|
<CreateColumnButton
|
||||||
|
highlighted={$fetch.loaded && (!hasCols || !hasRows)}
|
||||||
|
on:updatecolumns={onUpdateColumns}
|
||||||
|
/>
|
||||||
{#if !isUsersTable}
|
{#if !isUsersTable}
|
||||||
<CreateRowButton
|
<CreateRowButton
|
||||||
on:updaterows={onUpdateRows}
|
on:updaterows={onUpdateRows}
|
||||||
title={"Create row"}
|
title={"Create row"}
|
||||||
modalContentComponent={CreateEditRow}
|
modalContentComponent={CreateEditRow}
|
||||||
|
disabled={!hasCols}
|
||||||
|
highlighted={$fetch.loaded && hasCols && !hasRows}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if isInternal}
|
{#if isInternal}
|
||||||
<CreateViewButton />
|
<CreateViewButton disabled={!hasCols || !hasRows} />
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="right-buttons">
|
||||||
<ManageAccessButton resourceId={$tables.selected?._id} />
|
<ManageAccessButton resourceId={$tables.selected?._id} />
|
||||||
{#if isUsersTable}
|
{#if isUsersTable}
|
||||||
<EditRolesButton />
|
<EditRolesButton />
|
||||||
|
@ -128,16 +153,40 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<HideAutocolumnButton bind:hideAutocolumns />
|
<HideAutocolumnButton bind:hideAutocolumns />
|
||||||
<!-- always have the export last -->
|
|
||||||
<ExportButton view={$tables.selected?._id} />
|
|
||||||
<ImportButton
|
<ImportButton
|
||||||
tableId={$tables.selected?._id}
|
tableId={$tables.selected?._id}
|
||||||
on:updaterows={onUpdateRows}
|
on:updaterows={onUpdateRows}
|
||||||
/>
|
/>
|
||||||
|
<ExportButton
|
||||||
|
disabled={!hasRows || !hasCols}
|
||||||
|
view={$tables.selected?._id}
|
||||||
|
/>
|
||||||
{#key id}
|
{#key id}
|
||||||
<TableFilterButton {schema} on:change={onFilter} />
|
<TableFilterButton
|
||||||
|
{schema}
|
||||||
|
on:change={onFilter}
|
||||||
|
disabled={!hasCols || !hasRows}
|
||||||
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div slot="placeholder">
|
||||||
|
<Layout gap="S">
|
||||||
|
{#if !hasCols}
|
||||||
|
<Heading>Let's create some columns</Heading>
|
||||||
|
<Body>
|
||||||
|
Start building out your table structure<br />
|
||||||
|
by adding some columns
|
||||||
|
</Body>
|
||||||
|
{:else}
|
||||||
|
<Heading>Now let's add a row</Heading>
|
||||||
|
<Body>
|
||||||
|
Add some data to your table<br />
|
||||||
|
by adding some rows
|
||||||
|
</Body>
|
||||||
{/if}
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
</Table>
|
</Table>
|
||||||
{#key id}
|
{#key id}
|
||||||
<div in:fade={{ delay: 200, duration: 100 }}>
|
<div in:fade={{ delay: 200, duration: 100 }}>
|
||||||
|
@ -162,4 +211,20 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: var(--spacing-xl);
|
margin-top: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
.buttons {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.left-buttons,
|
||||||
|
.right-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -34,10 +34,10 @@
|
||||||
$: label = meta.name ? capitalise(meta.name) : ""
|
$: label = meta.name ? capitalise(meta.name) : ""
|
||||||
|
|
||||||
const timeStamp = resolveTimeStamp(value)
|
const timeStamp = resolveTimeStamp(value)
|
||||||
const isTimeStamp = timeStamp ? true : false
|
const isTimeStamp = !!timeStamp
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if type === "options"}
|
{#if type === "options" && meta.constraints.inclusion.length !== 0}
|
||||||
<Select
|
<Select
|
||||||
{label}
|
{label}
|
||||||
data-cy="{meta.name}-select"
|
data-cy="{meta.name}-select"
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
<Dropzone {label} bind:value />
|
<Dropzone {label} bind:value />
|
||||||
{:else if type === "boolean"}
|
{:else if type === "boolean"}
|
||||||
<Toggle text={label} bind:value data-cy="{meta.name}-input" />
|
<Toggle text={label} bind:value data-cy="{meta.name}-input" />
|
||||||
{:else if type === "array"}
|
{:else if type === "array" && meta.constraints.inclusion.length !== 0}
|
||||||
<Multiselect bind:value {label} options={meta.constraints.inclusion} />
|
<Multiselect bind:value {label} options={meta.constraints.inclusion} />
|
||||||
{:else if type === "link"}
|
{:else if type === "link"}
|
||||||
<LinkedRowSelector bind:linkedRows={value} schema={meta} />
|
<LinkedRowSelector bind:linkedRows={value} schema={meta} />
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
export let rowCount
|
export let rowCount
|
||||||
export let type
|
export let type
|
||||||
export let disableSorting = false
|
export let disableSorting = false
|
||||||
|
export let customPlaceholder = false
|
||||||
|
|
||||||
let selectedRows = []
|
let selectedRows = []
|
||||||
let editableColumn
|
let editableColumn
|
||||||
|
@ -117,10 +118,10 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<div>
|
<Layout noPadding gap="XS">
|
||||||
{#if title}
|
{#if title}
|
||||||
<div class="table-title">
|
<div class="table-title">
|
||||||
<Heading size="S">{title}</Heading>
|
<Heading size="M">{title}</Heading>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div transition:fade|local>
|
<div transition:fade|local>
|
||||||
<Spinner size="10" />
|
<Spinner size="10" />
|
||||||
|
@ -134,7 +135,7 @@
|
||||||
<DeleteRowsButton on:updaterows {selectedRows} {deleteRows} />
|
<DeleteRowsButton on:updaterows {selectedRows} {deleteRows} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Layout>
|
||||||
{#key tableId}
|
{#key tableId}
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<Table
|
<Table
|
||||||
|
@ -144,6 +145,7 @@
|
||||||
{customRenderers}
|
{customRenderers}
|
||||||
{rowCount}
|
{rowCount}
|
||||||
{disableSorting}
|
{disableSorting}
|
||||||
|
{customPlaceholder}
|
||||||
bind:selectedRows
|
bind:selectedRows
|
||||||
allowSelectRows={allowEditing && !isUsersTable}
|
allowSelectRows={allowEditing && !isUsersTable}
|
||||||
allowEditRows={allowEditing}
|
allowEditRows={allowEditing}
|
||||||
|
@ -153,7 +155,9 @@
|
||||||
on:editrow={e => editRow(e.detail)}
|
on:editrow={e => editRow(e.detail)}
|
||||||
on:clickrelationship={e => selectRelationship(e.detail)}
|
on:clickrelationship={e => selectRelationship(e.detail)}
|
||||||
on:sort
|
on:sort
|
||||||
/>
|
>
|
||||||
|
<slot slot="placeholder" name="placeholder" />
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -176,6 +180,7 @@
|
||||||
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);
|
||||||
|
|
|
@ -2,10 +2,21 @@
|
||||||
import { ActionButton, Modal } from "@budibase/bbui"
|
import { ActionButton, Modal } from "@budibase/bbui"
|
||||||
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
|
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
|
||||||
|
|
||||||
|
export let highlighted = false
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="TableColumnAddRight" quiet size="S" on:click={modal.show}>
|
<ActionButton
|
||||||
|
{disabled}
|
||||||
|
selected={highlighted}
|
||||||
|
emphasized={highlighted}
|
||||||
|
icon="TableColumnAddRight"
|
||||||
|
quiet
|
||||||
|
size="S"
|
||||||
|
on:click={modal.show}
|
||||||
|
>
|
||||||
Create column
|
Create column
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -4,11 +4,21 @@
|
||||||
|
|
||||||
export let modalContentComponent = CreateEditRow
|
export let modalContentComponent = CreateEditRow
|
||||||
export let title = "Create row"
|
export let title = "Create row"
|
||||||
|
export let disabled = false
|
||||||
|
export let highlighted = false
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="TableRowAddBottom" size="S" quiet on:click={modal.show}>
|
<ActionButton
|
||||||
|
{disabled}
|
||||||
|
emphasized={highlighted}
|
||||||
|
selected={highlighted}
|
||||||
|
icon="TableRowAddBottom"
|
||||||
|
size="S"
|
||||||
|
quiet
|
||||||
|
on:click={modal.show}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -2,10 +2,18 @@
|
||||||
import { Modal, ActionButton } from "@budibase/bbui"
|
import { Modal, ActionButton } from "@budibase/bbui"
|
||||||
import CreateViewModal from "../modals/CreateViewModal.svelte"
|
import CreateViewModal from "../modals/CreateViewModal.svelte"
|
||||||
|
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="CollectionAdd" size="S" quiet on:click={modal.show}>
|
<ActionButton
|
||||||
|
{disabled}
|
||||||
|
icon="CollectionAdd"
|
||||||
|
size="S"
|
||||||
|
quiet
|
||||||
|
on:click={modal.show}
|
||||||
|
>
|
||||||
Create view
|
Create view
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -3,11 +3,18 @@
|
||||||
import ExportModal from "../modals/ExportModal.svelte"
|
import ExportModal from "../modals/ExportModal.svelte"
|
||||||
|
|
||||||
export let view
|
export let view
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="DataDownload" size="S" quiet on:click={modal.show}>
|
<ActionButton
|
||||||
|
{disabled}
|
||||||
|
icon="DataDownload"
|
||||||
|
size="S"
|
||||||
|
quiet
|
||||||
|
on:click={modal.show}
|
||||||
|
>
|
||||||
Export
|
Export
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="MagicWand" primary size="S" quiet on:click={hideOrUnhide}>
|
<ActionButton
|
||||||
{#if hideAutocolumns}Show auto columns{:else}Hide auto columns{/if}
|
icon={hideAutocolumns ? "VisibilityOff" : "Visibility"}
|
||||||
|
primary
|
||||||
|
size="S"
|
||||||
|
quiet
|
||||||
|
on:click={hideOrUnhide}
|
||||||
|
>
|
||||||
|
Auto columns
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
export let schema
|
export let schema
|
||||||
export let filters
|
export let filters
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let modal
|
let modal
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
icon="Filter"
|
icon="Filter"
|
||||||
size="S"
|
size="S"
|
||||||
quiet
|
quiet
|
||||||
|
{disabled}
|
||||||
on:click={modal.show}
|
on:click={modal.show}
|
||||||
active={tempValue?.length > 0}
|
active={tempValue?.length > 0}
|
||||||
>
|
>
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
<script>
|
||||||
|
export let width = "100"
|
||||||
|
export let height = "100"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
{width}
|
||||||
|
{height}
|
||||||
|
viewBox="0 0 256 220"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M245.97 168.943C232.308 176.064 161.536 205.163 146.469 213.018C131.402 220.874 123.032 220.798 111.129 215.108C99.227 209.418 23.913 178.996 10.346 172.511C3.566 169.271 0 166.535 0 163.951V138.075C0 138.075 98.05 116.73 113.879 111.051C129.707 105.372 135.199 105.167 148.669 110.101C162.141 115.037 242.687 129.569 256 134.445L255.994 159.955C255.996 162.513 252.924 165.319 245.97 168.943"
|
||||||
|
fill="#912626"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M245.965 143.22C232.304 150.338 161.534 179.438 146.467 187.292C131.401 195.149 123.031 195.072 111.129 189.382C99.226 183.696 23.915 153.269 10.349 146.788C-3.21698 140.303 -3.50098 135.84 9.82502 130.622C23.151 125.402 98.049 96.017 113.88 90.338C129.708 84.661 135.199 84.454 148.669 89.39C162.14 94.324 232.488 122.325 245.799 127.2C259.115 132.081 259.626 136.1 245.965 143.22"
|
||||||
|
fill="#C6302B"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M245.97 127.074C232.308 134.196 161.536 163.294 146.469 171.152C131.402 179.005 123.032 178.929 111.129 173.239C99.226 167.552 23.913 137.127 10.346 130.642C3.566 127.402 0 124.67 0 122.085V96.206C0 96.206 98.05 74.862 113.879 69.183C129.707 63.504 135.199 63.298 148.669 68.233C162.142 73.168 242.688 87.697 256 92.574L255.994 118.087C255.996 120.644 252.924 123.45 245.97 127.074Z"
|
||||||
|
fill="#912626"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M245.965 101.351C232.304 108.471 161.534 137.569 146.467 145.426C131.401 153.28 123.031 153.203 111.129 147.513C99.226 141.827 23.915 111.401 10.349 104.919C-3.21698 98.436 -3.50098 93.972 9.82502 88.752C23.151 83.535 98.05 54.148 113.88 48.47C129.708 42.792 135.199 42.586 148.669 47.521C162.14 52.455 232.488 80.454 245.799 85.331C259.115 90.211 259.626 94.231 245.965 101.351"
|
||||||
|
fill="#C6302B"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M245.97 83.653C232.308 90.773 161.536 119.873 146.469 127.731C131.402 135.585 123.032 135.508 111.129 129.818C99.226 124.131 23.913 93.705 10.346 87.223C3.566 83.98 0 81.247 0 78.665V52.785C0 52.785 98.05 31.442 113.879 25.764C129.707 20.084 135.199 19.88 148.669 24.814C162.142 29.749 242.688 44.278 256 49.155L255.994 74.667C255.996 77.222 252.924 80.028 245.97 83.653Z"
|
||||||
|
fill="#912626"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M245.965 57.93C232.304 65.05 161.534 94.15 146.467 102.004C131.401 109.858 123.031 109.781 111.129 104.094C99.227 98.404 23.915 67.98 10.35 61.497C-3.21699 55.015 -3.49999 50.55 9.82501 45.331C23.151 40.113 98.05 10.73 113.88 5.04999C129.708 -0.629006 135.199 -0.833006 148.669 4.10199C162.14 9.03699 232.488 37.036 245.799 41.913C259.115 46.789 259.626 50.81 245.965 57.93"
|
||||||
|
fill="#C6302B"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M159.283 32.757L137.273 35.042L132.346 46.898L124.388 33.668L98.9729 31.384L117.937 24.545L112.247 14.047L130.002 20.991L146.74 15.511L142.216 26.366L159.283 32.757V32.757ZM131.032 90.275L89.9549 73.238L148.815 64.203L131.032 90.275V90.275ZM74.0819 39.347C91.4569 39.347 105.542 44.807 105.542 51.541C105.542 58.277 91.4569 63.736 74.0819 63.736C56.7069 63.736 42.6219 58.276 42.6219 51.541C42.6219 44.807 56.7069 39.347 74.0819 39.347"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M185.295 35.998L220.131 49.764L185.325 63.517L185.295 35.997"
|
||||||
|
fill="#621B1C"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M146.755 51.243L185.295 35.998L185.325 63.517L181.546 64.995L146.755 51.243Z"
|
||||||
|
fill="#9A2928"
|
||||||
|
/>
|
||||||
|
</svg>
|
|
@ -13,6 +13,7 @@ import Budibase from "./Budibase.svelte"
|
||||||
import Oracle from "./Oracle.svelte"
|
import Oracle from "./Oracle.svelte"
|
||||||
import GoogleSheets from "./GoogleSheets.svelte"
|
import GoogleSheets from "./GoogleSheets.svelte"
|
||||||
import Firebase from "./Firebase.svelte"
|
import Firebase from "./Firebase.svelte"
|
||||||
|
import Redis from "./Redis.svelte"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
BUDIBASE: Budibase,
|
BUDIBASE: Budibase,
|
||||||
|
@ -30,4 +31,5 @@ export default {
|
||||||
ORACLE: Oracle,
|
ORACLE: Oracle,
|
||||||
GOOGLE_SHEETS: GoogleSheets,
|
GOOGLE_SHEETS: GoogleSheets,
|
||||||
FIREBASE: Firebase,
|
FIREBASE: Firebase,
|
||||||
|
REDIS: Redis,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
<script>
|
||||||
|
import { store } from "builderStore"
|
||||||
|
import { ModalContent, Layout, notifications, Icon } from "@budibase/bbui"
|
||||||
|
import { tables, datasources } from "stores/backend"
|
||||||
|
import getTemplates from "builderStore/store/screenTemplates"
|
||||||
|
import ICONS from "../../backend/DatasourceNavigator/icons"
|
||||||
|
import { IntegrationNames } from "constants"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
export let onCancel
|
||||||
|
export let onConfirm
|
||||||
|
export let initalScreens = []
|
||||||
|
|
||||||
|
let selectedScreens = [...initalScreens]
|
||||||
|
|
||||||
|
const toggleScreenSelection = (table, datasource) => {
|
||||||
|
if (selectedScreens.find(s => s.table === table.name)) {
|
||||||
|
selectedScreens = selectedScreens.filter(
|
||||||
|
screen => screen.table !== table.name
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let partialTemplates = getTemplates($store, $tables.list).reduce(
|
||||||
|
(acc, template) => {
|
||||||
|
if (template.table === table.name) {
|
||||||
|
template.datasource = datasource.name
|
||||||
|
acc.push(template)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
selectedScreens = [...partialTemplates, ...selectedScreens]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmDatasourceSelection = async () => {
|
||||||
|
await onConfirm({
|
||||||
|
templates: selectedScreens,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$: filteredSources = Array.isArray($datasources.list)
|
||||||
|
? $datasources.list.reduce((acc, datasource) => {
|
||||||
|
if (
|
||||||
|
datasource.source !== IntegrationNames.REST &&
|
||||||
|
datasource["entities"]
|
||||||
|
) {
|
||||||
|
acc.push(datasource)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
: []
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
await datasources.fetch()
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Error fetching datasources")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span data-cy="data-source-modal">
|
||||||
|
<ModalContent
|
||||||
|
title="Create CRUD Screens"
|
||||||
|
confirmText="Confirm"
|
||||||
|
cancelText="Back"
|
||||||
|
onConfirm={confirmDatasourceSelection}
|
||||||
|
{onCancel}
|
||||||
|
disabled={!selectedScreens.length}
|
||||||
|
size="L"
|
||||||
|
>
|
||||||
|
<Layout noPadding gap="S">
|
||||||
|
{#each filteredSources as datasource}
|
||||||
|
<div class="data-source-wrap">
|
||||||
|
<div class="data-source-header">
|
||||||
|
<div class="datasource-icon">
|
||||||
|
<svelte:component
|
||||||
|
this={ICONS[datasource.source]}
|
||||||
|
height="24"
|
||||||
|
width="24"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="data-source-name">{datasource.name}</div>
|
||||||
|
</div>
|
||||||
|
{#if Array.isArray(datasource.entities)}
|
||||||
|
{#each datasource.entities.filter(table => table._id !== "ta_users") as table}
|
||||||
|
<div
|
||||||
|
class="data-source-entry"
|
||||||
|
class:selected={selectedScreens.find(
|
||||||
|
x => x.table === table.name
|
||||||
|
)}
|
||||||
|
on:click={() => toggleScreenSelection(table, datasource)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16px"
|
||||||
|
height="16px"
|
||||||
|
class="spectrum-Icon"
|
||||||
|
style="color: white"
|
||||||
|
focusable="false"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-icon-18-Table" />
|
||||||
|
</svg>
|
||||||
|
{table.name}
|
||||||
|
|
||||||
|
{#if selectedScreens.find(x => x.table === table.name)}
|
||||||
|
<span class="data-source-check">
|
||||||
|
<Icon size="S" name="CheckmarkCircle" />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{#if datasource["entities"] && !Array.isArray(datasource.entities)}
|
||||||
|
{#each Object.keys(datasource.entities).filter(table => table._id !== "ta_users") as table_key}
|
||||||
|
<div
|
||||||
|
class="data-source-entry"
|
||||||
|
class:selected={selectedScreens.find(
|
||||||
|
x => x.table === datasource.entities[table_key].name
|
||||||
|
)}
|
||||||
|
on:click={() =>
|
||||||
|
toggleScreenSelection(
|
||||||
|
datasource.entities[table_key],
|
||||||
|
datasource
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16px"
|
||||||
|
height="16px"
|
||||||
|
class="spectrum-Icon"
|
||||||
|
style="color: white"
|
||||||
|
focusable="false"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-icon-18-Table" />
|
||||||
|
</svg>
|
||||||
|
{datasource.entities[table_key].name}
|
||||||
|
|
||||||
|
{#if selectedScreens.find(x => x.table === datasource.entities[table_key].name)}
|
||||||
|
<span class="data-source-check">
|
||||||
|
<Icon size="S" name="CheckmarkCircle" />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</Layout>
|
||||||
|
</ModalContent>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.data-source-wrap {
|
||||||
|
padding-bottom: var(--spectrum-alias-item-padding-s);
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-source-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-source-entry {
|
||||||
|
cursor: pointer;
|
||||||
|
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
||||||
|
padding: var(--spectrum-alias-item-padding-s);
|
||||||
|
background: var(--spectrum-alias-background-color-primary);
|
||||||
|
transition: 0.3s all;
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: 4px;
|
||||||
|
border-width: 1px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-source-entry:hover,
|
||||||
|
.selected {
|
||||||
|
background: var(--spectrum-alias-background-color-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-source-name {
|
||||||
|
padding: var(--spectrum-alias-item-padding-s);
|
||||||
|
min-height: var(--spectrum-icon-size-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-source-entry .data-source-check {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-source-entry :global(.spectrum-Icon) {
|
||||||
|
min-width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-source-entry .data-source-check :global(.spectrum-Icon) {
|
||||||
|
color: var(--spectrum-global-color-green-600);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,117 +1,98 @@
|
||||||
<script>
|
<script>
|
||||||
import { store } from "builderStore"
|
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import {
|
import { ModalContent, Body, Layout, Icon, Heading } from "@budibase/bbui"
|
||||||
ModalContent,
|
|
||||||
Body,
|
|
||||||
Detail,
|
|
||||||
Layout,
|
|
||||||
Icon,
|
|
||||||
ProgressCircle,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import getTemplates from "builderStore/store/screenTemplates"
|
|
||||||
|
|
||||||
export let onConfirm
|
export let onConfirm
|
||||||
export let onCancel
|
export let onCancel
|
||||||
export let showProgressCircle = false
|
|
||||||
|
|
||||||
const blankScreen = "createFromScratch"
|
let autoCreateModeKey = "autoCreate"
|
||||||
|
let blankScreenModeKey = "blankScreen"
|
||||||
|
|
||||||
let selectedScreens = []
|
let selectedScreenMode
|
||||||
let templates = getTemplates($store, $tables.list)
|
|
||||||
|
|
||||||
$: blankSelected = selectedScreens?.length === 1
|
|
||||||
$: autoSelected = selectedScreens?.length > 0 && !blankSelected
|
|
||||||
|
|
||||||
const toggleScreenSelection = table => {
|
|
||||||
if (selectedScreens.find(s => s.table === table.name)) {
|
|
||||||
selectedScreens = selectedScreens.filter(
|
|
||||||
screen => screen.table !== table.name
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let partialTemplates = getTemplates($store, $tables.list).filter(
|
|
||||||
template => template.table === table.name
|
|
||||||
)
|
|
||||||
selectedScreens = [...partialTemplates, ...selectedScreens]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmScreenSelection = async () => {
|
const confirmScreenSelection = async () => {
|
||||||
await onConfirm(selectedScreens)
|
await onConfirm(selectedScreenMode)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Add screens"
|
title="Add screens"
|
||||||
confirmText="Add screens"
|
confirmText="Continue"
|
||||||
cancelText="Cancel"
|
cancelText="Cancel"
|
||||||
onConfirm={confirmScreenSelection}
|
onConfirm={confirmScreenSelection}
|
||||||
{onCancel}
|
{onCancel}
|
||||||
disabled={!selectedScreens.length}
|
disabled={!selectedScreenMode}
|
||||||
size="L"
|
size="L"
|
||||||
>
|
>
|
||||||
<Body size="S">
|
|
||||||
Please select the screens you would like to add to your application.
|
|
||||||
Autogenerated screens come with CRUD functionality.
|
|
||||||
</Body>
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<Detail size="S">Blank screen</Detail>
|
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="screen-type item"
|
||||||
class:selected={selectedScreens.find(x => x.id.includes(blankScreen))}
|
class:selected={selectedScreenMode == blankScreenModeKey}
|
||||||
on:click={() =>
|
on:click={() => {
|
||||||
toggleScreenSelection(templates.find(t => t.id === blankScreen))}
|
selectedScreenMode = blankScreenModeKey
|
||||||
class:disabled={autoSelected}
|
}}
|
||||||
>
|
>
|
||||||
<div data-cy="blank-screen" class="content">
|
<div data-cy="blank-screen" class="content screen-type-wrap">
|
||||||
<div class="text">Blank</div>
|
<Icon name="WebPage" />
|
||||||
|
<div class="screen-type-text">
|
||||||
|
<Heading size="XS">Blank screen</Heading>
|
||||||
|
<Body size="S">Add a blank screen</Body>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
style="color: var(--spectrum-global-color-green-600); float: right"
|
||||||
>
|
>
|
||||||
{#if selectedScreens.find(x => x.id === blankScreen)}
|
{#if selectedScreenMode == blankScreenModeKey}
|
||||||
<div class="checkmark-spacing">
|
<div class="checkmark-spacing">
|
||||||
<Icon size="S" name="CheckmarkCircleOutline" />
|
<Icon size="S" name="CheckmarkCircle" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if $tables.list.filter(table => table._id !== "ta_users").length > 0}
|
|
||||||
<Detail size="S">Autogenerated Screens</Detail>
|
|
||||||
|
|
||||||
{#each $tables.list.filter(table => table._id !== "ta_users") as table}
|
|
||||||
<div
|
<div
|
||||||
class:disabled={blankSelected}
|
class="screen-type item"
|
||||||
class:selected={selectedScreens.find(x => x.table === table.name)}
|
class:selected={selectedScreenMode == autoCreateModeKey}
|
||||||
on:click={() => toggleScreenSelection(table)}
|
on:click={() => {
|
||||||
class="item"
|
selectedScreenMode = autoCreateModeKey
|
||||||
|
}}
|
||||||
|
class:disabled={!$tables.list.filter(table => table._id !== "ta_users")
|
||||||
|
.length}
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div data-cy="autogenerated-screens" class="content screen-type-wrap">
|
||||||
<div class="text">{table.name}</div>
|
<Icon name="WebPages" />
|
||||||
|
<div class="screen-type-text">
|
||||||
|
<Heading size="XS">Autogenerated screens</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
Add autogenerated screens with CRUD functionality to get a working
|
||||||
|
app quickly! (Requires a data source)
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="color: var(--spectrum-global-color-green-600); float: right"
|
style="color: var(--spectrum-global-color-green-600); float: right"
|
||||||
>
|
>
|
||||||
{#if selectedScreens.find(x => x.table === table.name)}
|
{#if selectedScreenMode == autoCreateModeKey}
|
||||||
<div class="checkmark-spacing">
|
<div class="checkmark-spacing">
|
||||||
<Icon size="S" name="CheckmarkCircleOutline" />
|
<Icon size="S" name="CheckmarkCircle" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</Layout>
|
</Layout>
|
||||||
<div slot="footer">
|
|
||||||
{#if showProgressCircle}
|
|
||||||
<div class="footer-progress"><ProgressCircle size="S" /></div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.screen-type.item {
|
||||||
|
padding: var(--spectrum-alias-item-padding-xl);
|
||||||
|
}
|
||||||
|
.screen-type-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
.disabled {
|
.disabled {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -119,22 +100,9 @@
|
||||||
.checkmark-spacing {
|
.checkmark-spacing {
|
||||||
margin-right: var(--spacing-m);
|
margin-right: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
letter-spacing: 0px;
|
letter-spacing: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-progress {
|
|
||||||
margin-top: var(--spacing-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-left: var(--spacing-m);
|
|
||||||
font-size: 14px;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
grid-gap: var(--spectrum-alias-grid-margin-xsmall);
|
||||||
|
@ -143,16 +111,22 @@
|
||||||
transition: 0.3s all;
|
transition: 0.3s all;
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-sizing: border-box;
|
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item:hover,
|
.item:hover,
|
||||||
.selected {
|
.selected {
|
||||||
background: var(--spectrum-alias-background-color-tertiary);
|
background: var(--spectrum-alias-background-color-tertiary);
|
||||||
}
|
}
|
||||||
|
.screen-type-wrap .screen-type-text {
|
||||||
|
padding-left: var(--spectrum-alias-item-padding-xl);
|
||||||
|
}
|
||||||
|
.screen-type-wrap :global(.spectrum-Icon) {
|
||||||
|
min-width: var(--spectrum-icon-size-m);
|
||||||
|
}
|
||||||
|
.screen-type-wrap :global(.spectrum-Heading) {
|
||||||
|
padding-bottom: var(--spectrum-alias-item-padding-s);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
<script>
|
<script>
|
||||||
import { ModalContent, Input, ProgressCircle } from "@budibase/bbui"
|
import { ModalContent, Input } from "@budibase/bbui"
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
import { selectedAccessRole, allScreens } from "builderStore"
|
import { selectedAccessRole, allScreens } from "builderStore"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
export let onConfirm
|
export let onConfirm
|
||||||
export let onCancel
|
export let onCancel
|
||||||
export let showProgressCircle = false
|
|
||||||
export let screenName
|
|
||||||
export let screenUrl
|
export let screenUrl
|
||||||
export let confirmText = "Continue"
|
export let confirmText = "Continue"
|
||||||
|
|
||||||
let routeError
|
let routeError
|
||||||
let touched = false
|
let touched = false
|
||||||
|
let screenAccessRole = $selectedAccessRole + ""
|
||||||
|
|
||||||
|
const appPrefix = "/app"
|
||||||
|
|
||||||
|
$: appUrl = screenUrl
|
||||||
|
? `${window.location.origin}${appPrefix}${screenUrl}`
|
||||||
|
: `${window.location.origin}${appPrefix}`
|
||||||
|
|
||||||
const routeChanged = event => {
|
const routeChanged = event => {
|
||||||
if (!event.detail.startsWith("/")) {
|
if (!event.detail.startsWith("/")) {
|
||||||
|
@ -38,7 +43,6 @@
|
||||||
|
|
||||||
const confirmScreenDetails = async () => {
|
const confirmScreenDetails = async () => {
|
||||||
await onConfirm({
|
await onConfirm({
|
||||||
screenName,
|
|
||||||
screenUrl,
|
screenUrl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -51,24 +55,25 @@
|
||||||
onConfirm={confirmScreenDetails}
|
onConfirm={confirmScreenDetails}
|
||||||
{onCancel}
|
{onCancel}
|
||||||
cancelText={"Back"}
|
cancelText={"Back"}
|
||||||
disabled={!screenName || !screenUrl || routeError || !touched}
|
disabled={!screenAccessRole || !screenUrl || routeError || !touched}
|
||||||
>
|
>
|
||||||
<Input label="Name" bind:value={screenName} />
|
|
||||||
<Input
|
<Input
|
||||||
label="URL"
|
label="Enter a URL for the new screen"
|
||||||
error={routeError}
|
error={routeError}
|
||||||
bind:value={screenUrl}
|
bind:value={screenUrl}
|
||||||
on:change={routeChanged}
|
on:change={routeChanged}
|
||||||
/>
|
/>
|
||||||
<div slot="footer">
|
<div class="app-server" title={appUrl}>
|
||||||
{#if showProgressCircle}
|
{appUrl}
|
||||||
<div class="footer-progress"><ProgressCircle size="S" /></div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.footer-progress {
|
.app-server {
|
||||||
margin-top: var(--spacing-s);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
width: 320px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,34 +1,46 @@
|
||||||
<script>
|
<script>
|
||||||
import ScreenDetailsModal from "components/design/NavigationPanel/ScreenDetailsModal.svelte"
|
import ScreenDetailsModal from "components/design/NavigationPanel/ScreenDetailsModal.svelte"
|
||||||
import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte"
|
import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte"
|
||||||
|
import DatasourceModal from "components/design/NavigationPanel/DatasourceModal.svelte"
|
||||||
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl"
|
||||||
import { Modal, notifications } from "@budibase/bbui"
|
import { Modal, ModalContent, Select, notifications } from "@budibase/bbui"
|
||||||
import { store, selectedAccessRole } from "builderStore"
|
import { store, selectedAccessRole } from "builderStore"
|
||||||
import analytics, { Events } from "analytics"
|
import analytics, { Events } from "analytics"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
import getTemplates from "builderStore/store/screenTemplates"
|
||||||
|
import { tables, roles } from "stores/backend"
|
||||||
|
|
||||||
let pendingScreen
|
let pendingScreen
|
||||||
let showProgressCircle = false
|
|
||||||
|
|
||||||
// Modal refs
|
// Modal refs
|
||||||
let newScreenModal
|
let newScreenModal
|
||||||
let screenDetailsModal
|
let screenDetailsModal
|
||||||
|
let datasourceModal
|
||||||
|
let screenAccessRoleModal
|
||||||
|
|
||||||
|
// Cache variables for workflow
|
||||||
|
let screenAccessRole = $selectedAccessRole + ""
|
||||||
|
let selectedTemplates = null
|
||||||
|
let blankScreenUrl = null
|
||||||
|
|
||||||
|
let screenMode = null
|
||||||
|
|
||||||
// External handler to show the screen wizard
|
// External handler to show the screen wizard
|
||||||
export const showModal = () => {
|
export const showModal = () => {
|
||||||
newScreenModal.show()
|
selectedTemplates = null
|
||||||
|
blankScreenUrl = null
|
||||||
|
screenMode = null
|
||||||
|
|
||||||
|
newScreenModal.show()
|
||||||
// Reset state when showing modal again
|
// Reset state when showing modal again
|
||||||
pendingScreen = null
|
pendingScreen = null
|
||||||
showProgressCircle = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates an array of screens, checking and sanitising their URLs
|
// Creates an array of screens, checking and sanitising their URLs
|
||||||
const createScreens = async screens => {
|
const createScreens = async ({ screens, screenAccessRole }) => {
|
||||||
if (!screens?.length) {
|
if (!screens?.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
showProgressCircle = true
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (let screen of screens) {
|
for (let screen of screens) {
|
||||||
|
@ -46,7 +58,9 @@
|
||||||
screen.routing.route = sanitizeUrl(screen.routing.route)
|
screen.routing.route = sanitizeUrl(screen.routing.route)
|
||||||
|
|
||||||
// Use the currently selected role
|
// Use the currently selected role
|
||||||
screen.routing.roleId = get(selectedAccessRole) || "BASIC"
|
screen.routing.roleId = screenAccessRole
|
||||||
|
? screenAccessRole
|
||||||
|
: get(selectedAccessRole) || "BASIC"
|
||||||
|
|
||||||
// Create the screen
|
// Create the screen
|
||||||
await store.actions.screens.save(screen)
|
await store.actions.screens.save(screen)
|
||||||
|
@ -55,6 +69,8 @@
|
||||||
if (screen.template) {
|
if (screen.template) {
|
||||||
analytics.captureEvent(Events.SCREEN.CREATED, {
|
analytics.captureEvent(Events.SCREEN.CREATED, {
|
||||||
template: screen.template,
|
template: screen.template,
|
||||||
|
datasource: screen.datasource,
|
||||||
|
screenAccessRole,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,8 +85,6 @@
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error creating screens")
|
notifications.error("Error creating screens")
|
||||||
}
|
}
|
||||||
|
|
||||||
showProgressCircle = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if any screens exist in the store with the given route and
|
// Checks if any screens exist in the store with the given route and
|
||||||
|
@ -98,38 +112,120 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler for NewScreenModal
|
// Handler for NewScreenModal
|
||||||
const confirmScreenSelection = async templates => {
|
const confirmScreenSelection = async mode => {
|
||||||
// Handle template selection
|
screenMode = mode
|
||||||
if (templates?.length > 1) {
|
|
||||||
// Autoscreens, so create immediately
|
if (mode == "autoCreate") {
|
||||||
const screens = templates.map(template => template.create())
|
datasourceModal.show()
|
||||||
await createScreens(screens)
|
|
||||||
} else {
|
} else {
|
||||||
// Empty screen, so proceed to the next modal
|
let templates = getTemplates($store, $tables.list)
|
||||||
pendingScreen = templates[0].create()
|
const blankScreenTemplate = templates.find(
|
||||||
|
t => t.id === "createFromScratch"
|
||||||
|
)
|
||||||
|
pendingScreen = blankScreenTemplate.create()
|
||||||
screenDetailsModal.show()
|
screenDetailsModal.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler for ScreenDetailsModal
|
// Handler for DatasourceModal confirmation, move to screen access select
|
||||||
const confirmScreenDetails = async ({ screenName, screenUrl }) => {
|
const confirmScreenDatasources = async ({ templates }) => {
|
||||||
|
selectedTemplates = templates
|
||||||
|
screenAccessRoleModal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for Datasource Screen Creation
|
||||||
|
const completeDatasourceScreenCreation = async () => {
|
||||||
|
// // Handle template selection
|
||||||
|
if (selectedTemplates?.length > 1) {
|
||||||
|
// Autoscreens, so create immediately
|
||||||
|
const screens = selectedTemplates.map(template => {
|
||||||
|
let screenTemplate = template.create()
|
||||||
|
screenTemplate.datasource = template.datasource
|
||||||
|
return screenTemplate
|
||||||
|
})
|
||||||
|
await createScreens({ screens, screenAccessRole })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmScreenBlank = async ({ screenUrl }) => {
|
||||||
|
blankScreenUrl = screenUrl
|
||||||
|
screenAccessRoleModal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit request for a blank screen
|
||||||
|
const confirmBlankScreenCreation = async ({
|
||||||
|
screenUrl,
|
||||||
|
screenAccessRole,
|
||||||
|
}) => {
|
||||||
if (!pendingScreen) {
|
if (!pendingScreen) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pendingScreen.props._instanceName = screenName
|
|
||||||
pendingScreen.routing.route = screenUrl
|
pendingScreen.routing.route = screenUrl
|
||||||
await createScreens([pendingScreen])
|
await createScreens({ screens: [pendingScreen], screenAccessRole })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit screen config for creation.
|
||||||
|
const confirmScreenCreation = async () => {
|
||||||
|
if (screenMode === "blankScreen") {
|
||||||
|
confirmBlankScreenCreation({
|
||||||
|
screenUrl: blankScreenUrl,
|
||||||
|
screenAccessRole,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
completeDatasourceScreenCreation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleSelectBack = () => {
|
||||||
|
if (screenMode === "blankScreen") {
|
||||||
|
screenDetailsModal.show()
|
||||||
|
} else {
|
||||||
|
datasourceModal.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={newScreenModal}>
|
<Modal bind:this={newScreenModal}>
|
||||||
<NewScreenModal onConfirm={confirmScreenSelection} {showProgressCircle} />
|
<NewScreenModal onConfirm={confirmScreenSelection} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal bind:this={datasourceModal}>
|
||||||
|
<DatasourceModal
|
||||||
|
onConfirm={confirmScreenDatasources}
|
||||||
|
onCancel={() => newScreenModal.show()}
|
||||||
|
initalScreens={!selectedTemplates ? [] : [...selectedTemplates]}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal bind:this={screenAccessRoleModal}>
|
||||||
|
<ModalContent
|
||||||
|
title={"Create CRUD Screens"}
|
||||||
|
confirmText={"Done"}
|
||||||
|
cancelText={"Back"}
|
||||||
|
onConfirm={confirmScreenCreation}
|
||||||
|
onCancel={roleSelectBack}
|
||||||
|
>
|
||||||
|
Select which level of access you want your screens to have
|
||||||
|
<Select
|
||||||
|
bind:value={screenAccessRole}
|
||||||
|
on:change={() => {
|
||||||
|
analytics.captureEvent(Events.SCREEN.CREATE_ROLE_UPDATED, {
|
||||||
|
screenAccessRole,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
label="Access"
|
||||||
|
getOptionLabel={role => role.name}
|
||||||
|
getOptionValue={role => role._id}
|
||||||
|
getOptionColor={role => role.color}
|
||||||
|
options={$roles}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal bind:this={screenDetailsModal}>
|
<Modal bind:this={screenDetailsModal}>
|
||||||
<ScreenDetailsModal
|
<ScreenDetailsModal
|
||||||
{showProgressCircle}
|
onConfirm={confirmScreenBlank}
|
||||||
onConfirm={confirmScreenDetails}
|
|
||||||
onCancel={() => newScreenModal.show()}
|
onCancel={() => newScreenModal.show()}
|
||||||
|
initialUrl={blankScreenUrl}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -26,14 +26,6 @@
|
||||||
on:change={value => (parameters.rowId = value.detail)}
|
on:change={value => (parameters.rowId = value.detail)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Label small>Row Rev</Label>
|
|
||||||
<DrawerBindableInput
|
|
||||||
{bindings}
|
|
||||||
title="Row rev to delete"
|
|
||||||
value={parameters.revId}
|
|
||||||
on:change={value => (parameters.revId = value.detail)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Label small />
|
<Label small />
|
||||||
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script>
|
||||||
|
import { Select, Label, Combobox } from "@budibase/bbui"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
import { currentAsset, store } from "builderStore"
|
||||||
|
import {
|
||||||
|
getActionProviderComponents,
|
||||||
|
buildFormSchema,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
|
import { findComponent } from "builderStore/componentUtils"
|
||||||
|
|
||||||
|
export let parameters
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
|
const typeOptions = [
|
||||||
|
{
|
||||||
|
label: "Set value",
|
||||||
|
value: "set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Reset to default value",
|
||||||
|
value: "reset",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
$: formComponent = findComponent($currentAsset.props, parameters.componentId)
|
||||||
|
$: formSchema = buildFormSchema(formComponent)
|
||||||
|
$: fieldOptions = Object.keys(formSchema || {})
|
||||||
|
$: actionProviders = getActionProviderComponents(
|
||||||
|
$currentAsset,
|
||||||
|
$store.selectedComponentId,
|
||||||
|
"ValidateForm"
|
||||||
|
)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!parameters.type) {
|
||||||
|
parameters.type = "set"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<Label small>Form</Label>
|
||||||
|
<Select
|
||||||
|
bind:value={parameters.componentId}
|
||||||
|
options={actionProviders}
|
||||||
|
getOptionLabel={x => x._instanceName}
|
||||||
|
getOptionValue={x => x._id}
|
||||||
|
/>
|
||||||
|
<Label small>Type</Label>
|
||||||
|
<Select
|
||||||
|
placeholder={null}
|
||||||
|
bind:value={parameters.type}
|
||||||
|
options={typeOptions}
|
||||||
|
/>
|
||||||
|
<Label small>Field</Label>
|
||||||
|
<Combobox bind:value={parameters.field} options={fieldOptions} />
|
||||||
|
{#if parameters.type === "set"}
|
||||||
|
<Label small>Value</Label>
|
||||||
|
<DrawerBindableInput
|
||||||
|
{bindings}
|
||||||
|
value={parameters.value}
|
||||||
|
on:change={e => (parameters.value = e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
display: grid;
|
||||||
|
column-gap: var(--spacing-l);
|
||||||
|
row-gap: var(--spacing-s);
|
||||||
|
grid-template-columns: 60px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -14,3 +14,4 @@ export { default as DuplicateRow } from "./DuplicateRow.svelte"
|
||||||
export { default as S3Upload } from "./S3Upload.svelte"
|
export { default as S3Upload } from "./S3Upload.svelte"
|
||||||
export { default as ExportData } from "./ExportData.svelte"
|
export { default as ExportData } from "./ExportData.svelte"
|
||||||
export { default as ContinueIf } from "./ContinueIf.svelte"
|
export { default as ContinueIf } from "./ContinueIf.svelte"
|
||||||
|
export { default as UpdateFieldValue } from "./UpdateFieldValue.svelte"
|
||||||
|
|
|
@ -42,25 +42,29 @@
|
||||||
"name": "Trigger Automation",
|
"name": "Trigger Automation",
|
||||||
"component": "TriggerAutomation"
|
"component": "TriggerAutomation"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Update Field Value",
|
||||||
|
"component": "UpdateFieldValue"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Validate Form",
|
"name": "Validate Form",
|
||||||
"component": "ValidateForm"
|
"component": "ValidateForm"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Log Out",
|
"name": "Change Form Step",
|
||||||
"component": "LogOut"
|
"component": "ChangeFormStep"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Clear Form",
|
"name": "Clear Form",
|
||||||
"component": "ClearForm"
|
"component": "ClearForm"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Close Screen Modal",
|
"name": "Log Out",
|
||||||
"component": "CloseScreenModal"
|
"component": "LogOut"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Change Form Step",
|
"name": "Close Screen Modal",
|
||||||
"component": "ChangeFormStep"
|
"component": "CloseScreenModal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Refresh Data Provider",
|
"name": "Refresh Data Provider",
|
||||||
|
|
|
@ -52,7 +52,6 @@
|
||||||
.map(query => ({
|
.map(query => ({
|
||||||
label: query.name,
|
label: query.name,
|
||||||
name: query.name,
|
name: query.name,
|
||||||
tableId: query._id,
|
|
||||||
...query,
|
...query,
|
||||||
type: "query",
|
type: "query",
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -10,9 +10,15 @@
|
||||||
let drawer
|
let drawer
|
||||||
let tempValue = value || []
|
let tempValue = value || []
|
||||||
|
|
||||||
const saveFilter = async () => {
|
const saveOptions = async () => {
|
||||||
// Filter out incomplete options
|
// Filter out incomplete options, default if needed
|
||||||
tempValue = tempValue.filter(option => option.value && option.label)
|
tempValue = tempValue.filter(option => option.value || option.label)
|
||||||
|
for (let i = 0; i < tempValue.length; i++) {
|
||||||
|
let option = tempValue[i]
|
||||||
|
option.label = option.label ? option.label : option.value
|
||||||
|
option.value = option.value ? option.value : option.label
|
||||||
|
tempValue[i] = option
|
||||||
|
}
|
||||||
dispatch("change", tempValue)
|
dispatch("change", tempValue)
|
||||||
drawer.hide()
|
drawer.hide()
|
||||||
}
|
}
|
||||||
|
@ -23,6 +29,6 @@
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
Define the options for this picker.
|
Define the options for this picker.
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
|
<Button cta slot="buttons" on:click={saveOptions}>Save</Button>
|
||||||
<OptionsDrawer bind:options={tempValue} slot="body" />
|
<OptionsDrawer bind:options={tempValue} slot="body" />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
@ -15,16 +15,14 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchemaForDatasource($currentAsset, datasource, {
|
$: schema = getSchemaForDatasource($currentAsset, datasource).schema
|
||||||
searchableSchema: true,
|
|
||||||
}).schema
|
|
||||||
$: options = getOptions(datasource, schema || {})
|
$: options = getOptions(datasource, schema || {})
|
||||||
$: boundValue = getSelectedOption(value, options)
|
$: boundValue = getSelectedOption(value, options)
|
||||||
|
|
||||||
function getOptions(ds, dsSchema) {
|
function getOptions(ds, dsSchema) {
|
||||||
let base = Object.values(dsSchema)
|
let base = Object.values(dsSchema)
|
||||||
if (!ds?.tableId) {
|
if (!ds?.tableId) {
|
||||||
return base
|
return base.map(field => field.name)
|
||||||
}
|
}
|
||||||
const currentTable = $tables.list.find(table => table._id === ds.tableId)
|
const currentTable = $tables.list.find(table => table._id === ds.tableId)
|
||||||
return getFields(base, { allowLinks: currentTable?.sql }).map(
|
return getFields(base, { allowLinks: currentTable?.sql }).map(
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
fields = response.schema
|
fields = response.schema
|
||||||
notifications.success("Query executed successfully")
|
notifications.success("Query executed successfully")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error previewing query")
|
notifications.error(`Query Error: ${error.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,7 @@ export const IntegrationTypes = {
|
||||||
INTERNAL: "INTERNAL",
|
INTERNAL: "INTERNAL",
|
||||||
GOOGLE_SHEETS: "GOOGLE_SHEETS",
|
GOOGLE_SHEETS: "GOOGLE_SHEETS",
|
||||||
FIREBASE: "FIREBASE",
|
FIREBASE: "FIREBASE",
|
||||||
|
REDIS: "REDIS",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IntegrationNames = {
|
export const IntegrationNames = {
|
||||||
|
@ -197,6 +198,7 @@ export const IntegrationNames = {
|
||||||
[IntegrationTypes.INTERNAL]: "Internal",
|
[IntegrationTypes.INTERNAL]: "Internal",
|
||||||
[IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets",
|
[IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets",
|
||||||
[IntegrationTypes.FIREBASE]: "Firebase",
|
[IntegrationTypes.FIREBASE]: "Firebase",
|
||||||
|
[IntegrationTypes.REDIS]: "Redis",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SchemaTypeOptions = [
|
export const SchemaTypeOptions = [
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { auth } from "../stores/portal"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
|
export const FEATURE_FLAGS = {
|
||||||
|
LICENSING: "LICENSING",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isEnabled = featureFlag => {
|
||||||
|
const user = get(auth).user
|
||||||
|
if (user?.featureFlags?.includes(featureFlag)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
notifications.success("Invitation accepted successfully")
|
notifications.success("Invitation accepted successfully")
|
||||||
$goto("../auth/login")
|
$goto("../auth/login")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error accepting invitation")
|
notifications.error(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
|
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
|
||||||
import UpdateAPIKeyModal from "components/settings/UpdateAPIKeyModal.svelte"
|
import UpdateAPIKeyModal from "components/settings/UpdateAPIKeyModal.svelte"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
import { isEnabled, FEATURE_FLAGS } from "../../../helpers/featureFlags"
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let userInfoModal
|
let userInfoModal
|
||||||
|
@ -54,10 +55,17 @@
|
||||||
if (!$adminStore.cloud) {
|
if (!$adminStore.cloud) {
|
||||||
menu = menu.concat([
|
menu = menu.concat([
|
||||||
{
|
{
|
||||||
title: "Updates",
|
title: "Update",
|
||||||
href: "/builder/portal/settings/update",
|
href: "/builder/portal/settings/update",
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
if (isEnabled(FEATURE_FLAGS.LICENSING)) {
|
||||||
|
menu = menu.concat({
|
||||||
|
title: "Upgrade",
|
||||||
|
href: "/builder/portal/settings/upgrade",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
menu = menu.concat([
|
menu = menu.concat([
|
||||||
|
|
|
@ -75,8 +75,8 @@
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
<Heading size="M">{createAppTitle}</Heading>
|
<Heading size="L">{createAppTitle}</Heading>
|
||||||
<Body size="S">
|
<Body size="M">
|
||||||
{welcomeBody}
|
{welcomeBody}
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<Button
|
<Button
|
||||||
dataCy="create-app-btn"
|
dataCy="create-app-btn"
|
||||||
size="L"
|
size="M"
|
||||||
icon="Add"
|
icon="Add"
|
||||||
cta
|
cta
|
||||||
on:click={initiateAppCreation}
|
on:click={initiateAppCreation}
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
<Button
|
<Button
|
||||||
dataCy="import-app-btn"
|
dataCy="import-app-btn"
|
||||||
icon="Import"
|
icon="Import"
|
||||||
size="L"
|
size="M"
|
||||||
quiet
|
quiet
|
||||||
secondary
|
secondary
|
||||||
on:click={initiateAppImport}
|
on:click={initiateAppImport}
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
})
|
})
|
||||||
notifications.success("Successfully created user")
|
notifications.success("Successfully created user")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error creating user")
|
notifications.error(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Layout,
|
||||||
|
Heading,
|
||||||
|
Body,
|
||||||
|
Divider,
|
||||||
|
Link,
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Label,
|
||||||
|
notifications,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { auth, admin } from "stores/portal"
|
||||||
|
import { redirect } from "@roxi/routify"
|
||||||
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
import { API } from "api"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
$: license = $auth.user.license
|
||||||
|
$: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
|
||||||
|
|
||||||
|
$: activateDisabled = !licenseKey || licenseKeyDisabled
|
||||||
|
|
||||||
|
let licenseInfo
|
||||||
|
|
||||||
|
let licenseKeyDisabled = false
|
||||||
|
let licenseKeyType = "text"
|
||||||
|
let licenseKey = ""
|
||||||
|
|
||||||
|
// Make sure page can't be visited directly in cloud
|
||||||
|
$: {
|
||||||
|
if ($admin.cloud) {
|
||||||
|
$redirect("../../portal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activate = async () => {
|
||||||
|
await API.activateLicenseKey({ licenseKey })
|
||||||
|
await auth.getSelf()
|
||||||
|
await setLicenseInfo()
|
||||||
|
notifications.success("Successfully activated")
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
try {
|
||||||
|
await API.refreshLicense()
|
||||||
|
await auth.getSelf()
|
||||||
|
notifications.success("Refreshed license")
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
notifications.error("Error refreshing license")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deactivate the license key field if there is a license key set
|
||||||
|
$: {
|
||||||
|
if (licenseInfo?.licenseKey) {
|
||||||
|
licenseKey = "**********************************************"
|
||||||
|
licenseKeyType = "password"
|
||||||
|
licenseKeyDisabled = true
|
||||||
|
activateDisabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setLicenseInfo = async () => {
|
||||||
|
licenseInfo = await API.getLicenseInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await setLicenseInfo()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $auth.isAdmin}
|
||||||
|
<Layout noPadding>
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading size="M">Upgrade</Heading>
|
||||||
|
<Body size="M">
|
||||||
|
{#if license.plan.type === "free"}
|
||||||
|
Upgrade your budibase installation to unlock additional features. To
|
||||||
|
subscribe to a plan visit your <Link size="L" href={upgradeUrl}
|
||||||
|
>Account</Link
|
||||||
|
>.
|
||||||
|
{:else}
|
||||||
|
To manage your plan visit your <Link size="L" href={upgradeUrl}
|
||||||
|
>Account</Link
|
||||||
|
>.
|
||||||
|
{/if}
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
<Divider size="S" />
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading size="S">Activate</Heading>
|
||||||
|
<Body size="S">Enter your license key below to activate your plan</Body>
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding>
|
||||||
|
<div class="fields">
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">License Key</Label>
|
||||||
|
<Input
|
||||||
|
thin
|
||||||
|
bind:value={licenseKey}
|
||||||
|
type={licenseKeyType}
|
||||||
|
disabled={licenseKeyDisabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button cta on:click={activate} disabled={activateDisabled}
|
||||||
|
>Activate</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
<Divider size="S" />
|
||||||
|
<Layout gap="L" noPadding>
|
||||||
|
<Layout gap="S" noPadding>
|
||||||
|
<Heading size="S">Plan</Heading>
|
||||||
|
<Layout noPadding gap="XXS">
|
||||||
|
<Body size="S">You are currently on the {license.plan.type} plan</Body
|
||||||
|
>
|
||||||
|
<Body size="XS">
|
||||||
|
{processStringSync(
|
||||||
|
"Updated {{ duration time 'millisecond' }} ago",
|
||||||
|
{
|
||||||
|
time:
|
||||||
|
new Date().getTime() -
|
||||||
|
new Date(license.refreshedAt).getTime(),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Body>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
<div>
|
||||||
|
<Button secondary on:click={refresh}>Refresh</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fields {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100px 1fr;
|
||||||
|
grid-gap: var(--spacing-l);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.105-alpha.10",
|
"version": "1.0.105-alpha.39",
|
||||||
"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": {
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Publish Dev",
|
|
||||||
"program": "${workspaceFolder}/scripts/publishDev.js"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1834,7 +1834,12 @@
|
||||||
"icon": "Form",
|
"icon": "Form",
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"illegalChildren": ["section", "form"],
|
"illegalChildren": ["section", "form"],
|
||||||
"actions": ["ValidateForm", "ClearForm", "ChangeFormStep"],
|
"actions": [
|
||||||
|
"ValidateForm",
|
||||||
|
"ClearForm",
|
||||||
|
"ChangeFormStep",
|
||||||
|
"UpdateFieldValue"
|
||||||
|
],
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
|
@ -1975,6 +1980,17 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
|
@ -2049,6 +2065,17 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
|
@ -2089,6 +2116,17 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
|
@ -2125,6 +2163,17 @@
|
||||||
"key": "placeholder",
|
"key": "placeholder",
|
||||||
"placeholder": "Choose an option"
|
"placeholder": "Choose an option"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Type",
|
"label": "Type",
|
||||||
|
@ -2274,6 +2323,17 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Autocomplete",
|
"label": "Autocomplete",
|
||||||
|
@ -2399,6 +2459,17 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
|
@ -2439,6 +2510,17 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"label": "Formatting",
|
"label": "Formatting",
|
||||||
|
@ -2512,6 +2594,17 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
|
@ -2657,6 +2750,17 @@
|
||||||
"label": "Extensions",
|
"label": "Extensions",
|
||||||
"key": "extensions"
|
"key": "extensions"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
|
@ -2697,6 +2801,17 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Autocomplete",
|
"label": "Autocomplete",
|
||||||
|
@ -2742,6 +2857,17 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
|
@ -2750,6 +2876,62 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"s3upload": {
|
||||||
|
"name": "S3 File Upload",
|
||||||
|
"info": "This component can't be used with S3 datasources that use custom endpoints.",
|
||||||
|
"icon": "UploadToCloud",
|
||||||
|
"styles": ["size"],
|
||||||
|
"editable": true,
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field/attachment",
|
||||||
|
"label": "Field",
|
||||||
|
"key": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Label",
|
||||||
|
"key": "label"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dataSource/s3",
|
||||||
|
"label": "S3 Datasource",
|
||||||
|
"key": "datasourceId"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Bucket",
|
||||||
|
"key": "bucket"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "File Name",
|
||||||
|
"key": "key"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Disabled",
|
||||||
|
"key": "disabled",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/attachment",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"dataprovider": {
|
"dataprovider": {
|
||||||
"name": "Data Provider",
|
"name": "Data Provider",
|
||||||
"info": "Pagination is only available for data stored in tables.",
|
"info": "Pagination is only available for data stored in tables.",
|
||||||
|
@ -3581,51 +3763,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"s3upload": {
|
|
||||||
"name": "S3 File Upload",
|
|
||||||
"info": "This component can't be used with S3 datasources that use custom endpoints.",
|
|
||||||
"icon": "UploadToCloud",
|
|
||||||
"styles": ["size"],
|
|
||||||
"editable": true,
|
|
||||||
"settings": [
|
|
||||||
{
|
|
||||||
"type": "field/attachment",
|
|
||||||
"label": "Field",
|
|
||||||
"key": "field"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"label": "Label",
|
|
||||||
"key": "label"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "dataSource/s3",
|
|
||||||
"label": "S3 Datasource",
|
|
||||||
"key": "datasourceId"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"label": "Bucket",
|
|
||||||
"key": "bucket"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"label": "File Name",
|
|
||||||
"key": "key"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Disabled",
|
|
||||||
"key": "disabled",
|
|
||||||
"defaultValue": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "validation/attachment",
|
|
||||||
"label": "Validation",
|
|
||||||
"key": "validation"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"markdownviewer": {
|
"markdownviewer": {
|
||||||
"name": "Markdown Viewer",
|
"name": "Markdown Viewer",
|
||||||
"icon": "TaskList",
|
"icon": "TaskList",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.0.105-alpha.10",
|
"version": "1.0.105-alpha.39",
|
||||||
"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": "^1.0.105-alpha.10",
|
"@budibase/bbui": "^1.0.105-alpha.39",
|
||||||
"@budibase/frontend-core": "^1.0.105-alpha.10",
|
"@budibase/frontend-core": "^1.0.105-alpha.39",
|
||||||
"@budibase/string-templates": "^1.0.105-alpha.10",
|
"@budibase/string-templates": "^1.0.105-alpha.39",
|
||||||
"@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,5 +1,5 @@
|
||||||
import { createAPIClient } from "@budibase/frontend-core"
|
import { createAPIClient } from "@budibase/frontend-core"
|
||||||
import { notificationStore, authStore } from "../stores"
|
import { notificationStore, authStore, devToolsStore } from "../stores"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
export const API = createAPIClient({
|
export const API = createAPIClient({
|
||||||
|
@ -21,6 +21,12 @@ export const API = createAPIClient({
|
||||||
if (auth?.csrfToken) {
|
if (auth?.csrfToken) {
|
||||||
headers["x-csrf-token"] = auth.csrfToken
|
headers["x-csrf-token"] = auth.csrfToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add role header
|
||||||
|
const role = get(devToolsStore).role
|
||||||
|
if (role) {
|
||||||
|
headers["x-budibase-role"] = role
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Show an error notification for all API failures.
|
// Show an error notification for all API failures.
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue