Merge branch 'develop' into tests/environment-variables
This commit is contained in:
commit
89bad2d45a
|
@ -0,0 +1 @@
|
||||||
|
blank_issues_enabled: false
|
|
@ -1,24 +0,0 @@
|
||||||
---
|
|
||||||
name: Epic
|
|
||||||
about: Plan a new project
|
|
||||||
title: ''
|
|
||||||
labels: epic
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Description
|
|
||||||
Brief summary of what this Epic is, whether it's a larger project, goal, or user story. Describe the job to be done, which persona this Epic is mainly for, or if more multiple, break it down by user and job story.
|
|
||||||
|
|
||||||
## Spec
|
|
||||||
Link to confluence spec
|
|
||||||
|
|
||||||
## Teams and Stakeholders
|
|
||||||
Describe who needs to be kept up-to-date about this Epic, included in discussions, or updated along the way. Stakeholders can be both in Product/Engineering, as well as other teams like Customer Success who might want to keep customers updated on the Epic project.
|
|
||||||
|
|
||||||
|
|
||||||
## Workflow
|
|
||||||
- [ ] Spec Created and pasted above
|
|
||||||
- [ ] Product Review
|
|
||||||
- [ ] Designs created
|
|
||||||
- [ ] Individual Tasks created and assigned to Epic
|
|
|
@ -194,5 +194,5 @@ jobs:
|
||||||
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
|
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
|
||||||
with:
|
with:
|
||||||
repository: budibase/budibase-deploys
|
repository: budibase/budibase-deploys
|
||||||
event: deploy-develop-to-qa
|
event: deploy-budibase-develop-to-qa
|
||||||
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}
|
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}
|
|
@ -18,30 +18,18 @@ jobs:
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- name: Pull cypress.env.yaml from budibase-infra
|
- name: Pull from budibase-infra
|
||||||
run: |
|
run: |
|
||||||
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
|
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \
|
||||||
-H 'Accept: application/vnd.github.v3.raw' \
|
-H 'Accept: application/vnd.github.v3.raw' \
|
||||||
-o packages/builder/cypress.env.json \
|
-o
|
||||||
-L https://api.github.com/repos/budibase/budibase-infra/contents/test/cypress.env.json
|
-L
|
||||||
wc -l packages/builder/cypress.env.json
|
wc -l
|
||||||
|
|
||||||
- name: Cypress run
|
|
||||||
id: cypress
|
|
||||||
continue-on-error: true
|
|
||||||
uses: cypress-io/github-action@v2
|
|
||||||
with:
|
|
||||||
record: true
|
|
||||||
install: false
|
|
||||||
tag: nightly
|
|
||||||
command: yarn test:e2e:ci:record
|
|
||||||
env:
|
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: Test Reports
|
name: Test Reports
|
||||||
path: packages/builder/cypress/reports/testReport.html
|
path:
|
||||||
|
|
||||||
# TODO: enable once running in QA test env
|
# TODO: enable once running in QA test env
|
||||||
# - name: Configure AWS Credentials
|
# - name: Configure AWS Credentials
|
||||||
|
@ -54,11 +42,3 @@ jobs:
|
||||||
# - name: Upload test results HTML
|
# - name: Upload test results HTML
|
||||||
# uses: aws-actions/configure-aws-credentials@v1
|
# uses: aws-actions/configure-aws-credentials@v1
|
||||||
# run: aws s3 cp packages/builder/cypress/reports/testReport.html s3://{{ secrets.BUDI_QA_REPORTS_BUCKET_NAME }}/$GITHUB_RUN_ID/index.html
|
# run: aws s3 cp packages/builder/cypress/reports/testReport.html s3://{{ secrets.BUDI_QA_REPORTS_BUCKET_NAME }}/$GITHUB_RUN_ID/index.html
|
||||||
|
|
||||||
- name: Cypress Discord Notify
|
|
||||||
run: yarn test:e2e:ci:notify
|
|
||||||
env:
|
|
||||||
CYPRESS_WEBHOOK_URL: ${{ secrets.BUDI_QA_WEBHOOK }}
|
|
||||||
CYPRESS_OUTCOME: ${{ steps.cypress.outcome }}
|
|
||||||
CYPRESS_DASHBOARD_URL: ${{ steps.cypress.outputs.dashboardUrl }}
|
|
||||||
GITHUB_RUN_URL: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
3.11.1
|
|
@ -0,0 +1,2 @@
|
||||||
|
nodejs 14.19.3
|
||||||
|
python 3.11.1
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"svelte.svelte-vscode"
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,22 +1,28 @@
|
||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": true
|
"source.fixAll": true
|
||||||
},
|
},
|
||||||
"editor.defaultFormatter": "svelte.svelte-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "vscode.json-language-features"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"debug.javascript.terminalOptions": {
|
"debug.javascript.terminalOptions": {
|
||||||
"skipFiles": [
|
"skipFiles": [
|
||||||
"${workspaceFolder}/packages/backend-core/node_modules/**",
|
"${workspaceFolder}/packages/backend-core/node_modules/**",
|
||||||
"<node_internals>/**"
|
"<node_internals>/**"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
|
"[dockercompose]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[svelte]": {
|
||||||
|
"editor.defaultFormatter": "svelte.svelte-vscode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@ metadata:
|
||||||
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
|
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
|
||||||
alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }}
|
alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if .Values.ingress.securityGroups }}
|
||||||
|
alb.ingress.kubernetes.io/security-groups: {{ .Values.ingress.securityGroups }}
|
||||||
|
{{- end }}
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- http:
|
- http:
|
||||||
|
|
|
@ -139,6 +139,8 @@ spec:
|
||||||
value: {{ .Values.globals.automationMaxIterations | quote }}
|
value: {{ .Values.globals.automationMaxIterations | quote }}
|
||||||
- name: TENANT_FEATURE_FLAGS
|
- name: TENANT_FEATURE_FLAGS
|
||||||
value: {{ .Values.globals.tenantFeatureFlags | quote }}
|
value: {{ .Values.globals.tenantFeatureFlags | quote }}
|
||||||
|
- name: ENCRYPTION_KEY
|
||||||
|
value: {{ .Values.globals.bbEncryptionKey | quote }}
|
||||||
{{ if .Values.globals.bbAdminUserEmail }}
|
{{ if .Values.globals.bbAdminUserEmail }}
|
||||||
- name: BB_ADMIN_USER_EMAIL
|
- name: BB_ADMIN_USER_EMAIL
|
||||||
value: {{ .Values.globals.bbAdminUserEmail | quote }}
|
value: {{ .Values.globals.bbAdminUserEmail | quote }}
|
||||||
|
|
|
@ -146,6 +146,8 @@ spec:
|
||||||
value: {{ .Values.globals.google.secret | quote }}
|
value: {{ .Values.globals.google.secret | quote }}
|
||||||
- name: TENANT_FEATURE_FLAGS
|
- name: TENANT_FEATURE_FLAGS
|
||||||
value: {{ .Values.globals.tenantFeatureFlags | quote }}
|
value: {{ .Values.globals.tenantFeatureFlags | quote }}
|
||||||
|
- name: ENCRYPTION_KEY
|
||||||
|
value: {{ .Values.globals.bbEncryptionKey | quote }}
|
||||||
{{ if .Values.globals.elasticApmEnabled }}
|
{{ if .Values.globals.elasticApmEnabled }}
|
||||||
- name: ELASTIC_APM_ENABLED
|
- name: ELASTIC_APM_ENABLED
|
||||||
value: {{ .Values.globals.elasticApmEnabled | quote }}
|
value: {{ .Values.globals.elasticApmEnabled | quote }}
|
||||||
|
|
|
@ -76,7 +76,7 @@ affinity: {}
|
||||||
globals:
|
globals:
|
||||||
appVersion: "latest"
|
appVersion: "latest"
|
||||||
budibaseEnv: PRODUCTION
|
budibaseEnv: PRODUCTION
|
||||||
tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS"
|
tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
|
||||||
enableAnalytics: "1"
|
enableAnalytics: "1"
|
||||||
sentryDSN: ""
|
sentryDSN: ""
|
||||||
posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU"
|
posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU"
|
||||||
|
|
|
@ -9,7 +9,6 @@ From opening a bug report to creating a pull request: every contribution is appr
|
||||||
- [Glossary of Terms](#glossary-of-terms)
|
- [Glossary of Terms](#glossary-of-terms)
|
||||||
- [Contributing to Budibase](#contributing-to-budibase)
|
- [Contributing to Budibase](#contributing-to-budibase)
|
||||||
|
|
||||||
|
|
||||||
## Not Sure Where to Start?
|
## Not Sure Where to Start?
|
||||||
|
|
||||||
Budibase is a low-code web application builder that creates svelte-based web applications.
|
Budibase is a low-code web application builder that creates svelte-based web applications.
|
||||||
|
@ -22,7 +21,7 @@ Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna
|
||||||
|
|
||||||
- **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
- **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
||||||
|
|
||||||
- **packages/worker** - This [Koa](https://koajs.com/) app is responsible for providing global apis for managing your budibase installation. Authentication, Users, Email, Org and Auth configs are all provided by the worker.
|
- **packages/worker** - This [Koa](https://koajs.com/) app is responsible for providing global apis for managing your budibase installation. Authentication, Users, Email, Org and Auth configs are all provided by the worker.
|
||||||
|
|
||||||
## Contributor License Agreement (CLA)
|
## Contributor License Agreement (CLA)
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ A client represents a single budibase customer. Each budibase client will have 1
|
||||||
|
|
||||||
### App
|
### App
|
||||||
|
|
||||||
A client can have one or more budibase applications. Budibase applications would be things like "Developer Inventory Management" or "Goat Herder CRM". Think of a budibase application as a tree.
|
A client can have one or more budibase applications. Budibase applications would be things like "Developer Inventory Management" or "Goat Herder CRM". Think of a budibase application as a tree.
|
||||||
|
|
||||||
### Database
|
### Database
|
||||||
|
|
||||||
|
@ -73,28 +72,55 @@ A component is the basic frontend building block of a budibase app.
|
||||||
|
|
||||||
### Component Library
|
### Component Library
|
||||||
|
|
||||||
Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`.
|
Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`.
|
||||||
|
|
||||||
## Contributing to Budibase
|
## Contributing to Budibase
|
||||||
|
|
||||||
* Please maintain the existing code style.
|
- Please maintain the existing code style.
|
||||||
|
|
||||||
* Please try to keep your commits small and focused.
|
- Please try to keep your commits small and focused.
|
||||||
|
|
||||||
* Please write tests.
|
- Please write tests.
|
||||||
|
|
||||||
* If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read.
|
- If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read.
|
||||||
|
|
||||||
* Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why.
|
- Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why.
|
||||||
|
|
||||||
### Getting Started For Contributors
|
### Getting Started For Contributors
|
||||||
#### 1. Prerequisites
|
|
||||||
|
|
||||||
NodeJS Version `14.x.x`
|
#### 1. Prerequisites
|
||||||
|
|
||||||
*yarn -* `npm install -g yarn`
|
- NodeJS version `14.x.x`
|
||||||
|
- Python version `3.x`
|
||||||
|
|
||||||
*jest* - `npm install -g jest`
|
### Using asdf (recommended)
|
||||||
|
|
||||||
|
Asdf is a package manager that allows managing multiple dependencies.
|
||||||
|
|
||||||
|
You can install them following any of the steps described below:
|
||||||
|
|
||||||
|
- Install using script (only for mac users):
|
||||||
|
|
||||||
|
`./scripts/install-contributor-dependencies.sh`
|
||||||
|
|
||||||
|
- Or, manually:
|
||||||
|
|
||||||
|
- Installation steps: https://asdf-vm.com/guide/getting-started.html
|
||||||
|
- asdf plugin add nodejs
|
||||||
|
- asdf plugin add python
|
||||||
|
- npm install -g yarn
|
||||||
|
|
||||||
|
### Using NVM and pyenv
|
||||||
|
|
||||||
|
- NVM:
|
||||||
|
- Install: https://github.com/nvm-sh/nvm#installing-and-updating
|
||||||
|
- Setup: `nvm use`
|
||||||
|
- Pyenv:
|
||||||
|
|
||||||
|
- Install: https://github.com/pyenv/pyenv#installation
|
||||||
|
- Setup: `pyenv install -v 3.7.2`
|
||||||
|
|
||||||
|
- _yarn -_ `npm install -g yarn`
|
||||||
|
|
||||||
#### 2. Clone this repository
|
#### 2. Clone this repository
|
||||||
|
|
||||||
|
@ -102,7 +128,7 @@ NodeJS Version `14.x.x`
|
||||||
|
|
||||||
then `cd ` into your local copy.
|
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)
|
| **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash)
|
||||||
|
|
||||||
|
@ -134,9 +160,9 @@ This will enable watch mode for both the builder app, server, client library and
|
||||||
|
|
||||||
#### 5. Debugging using VS Code
|
#### 5. Debugging using VS Code
|
||||||
|
|
||||||
To debug the budibase server and worker a VS Code launch configuration has been provided.
|
To debug the budibase server and worker a VS Code launch configuration has been provided.
|
||||||
|
|
||||||
Visit the debug window and select `Budibase Server` or `Budibase Worker` to debug the respective component.
|
Visit the debug window and select `Budibase Server` or `Budibase Worker` to debug the respective component.
|
||||||
Alternatively to start both components simultaneously select `Start Budibase`.
|
Alternatively to start both components simultaneously select `Start Budibase`.
|
||||||
|
|
||||||
In addition to the above, the remaining budibase components may be run in dev mode using: `yarn dev:noserver`.
|
In addition to the above, the remaining budibase components may be run in dev mode using: `yarn dev:noserver`.
|
||||||
|
@ -156,11 +182,11 @@ For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apa
|
||||||
|
|
||||||
When you are running locally, budibase stores data on disk using docker volumes. The volumes and the types of data associated with each are:
|
When you are running locally, budibase stores data on disk using docker volumes. The volumes and the types of data associated with each are:
|
||||||
|
|
||||||
- `redis_data`
|
- `redis_data`
|
||||||
- Sessions, email tokens
|
- Sessions, email tokens
|
||||||
- `couchdb3_data`
|
- `couchdb3_data`
|
||||||
- Global and app databases
|
- Global and app databases
|
||||||
- `minio_data`
|
- `minio_data`
|
||||||
- App manifest, budibase client, static assets
|
- App manifest, budibase client, static assets
|
||||||
|
|
||||||
### Development Modes
|
### Development Modes
|
||||||
|
@ -172,34 +198,42 @@ A combination of environment variables controls the mode budibase runs in.
|
||||||
Yarn commands can be used to mimic the different modes as described in the sections below:
|
Yarn commands can be used to mimic the different modes as described in the sections below:
|
||||||
|
|
||||||
#### Self Hosted
|
#### Self Hosted
|
||||||
The default mode. A single tenant installation with no usage restrictions.
|
|
||||||
|
The default mode. A single tenant installation with no usage restrictions.
|
||||||
|
|
||||||
To enable this mode, use:
|
To enable this mode, use:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn mode:self
|
yarn mode:self
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Cloud
|
#### Cloud
|
||||||
The cloud mode, with account portal turned off.
|
|
||||||
|
The cloud mode, with account portal turned off.
|
||||||
|
|
||||||
To enable this mode, use:
|
To enable this mode, use:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn mode:cloud
|
yarn mode:cloud
|
||||||
```
|
```
|
||||||
#### Cloud & Account
|
|
||||||
The cloud mode, with account portal turned on. This is a replica of the mode that runs at https://budibase.app
|
|
||||||
|
|
||||||
|
#### Cloud & Account
|
||||||
|
|
||||||
|
The cloud mode, with account portal turned on. This is a replica of the mode that runs at https://budibase.app
|
||||||
|
|
||||||
To enable this mode, use:
|
To enable this mode, use:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn mode:account
|
yarn mode:account
|
||||||
```
|
```
|
||||||
|
|
||||||
### CI
|
### CI
|
||||||
An overview of the CI pipelines can be found [here](../.github/workflows/README.md)
|
|
||||||
|
An overview of the CI pipelines can be found [here](../.github/workflows/README.md)
|
||||||
|
|
||||||
### Pro
|
### Pro
|
||||||
|
|
||||||
@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you require to update code inside the pro package it can be cloned to the same root level as budibase, e.g.
|
@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you require to update code inside the pro package it can be cloned to the same root level as budibase, e.g.
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
|
@ -207,13 +241,14 @@ yarn mode:account
|
||||||
|_ budibase-pro
|
|_ budibase-pro
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that only budibase maintainers will be able to access the pro repo.
|
Note that only budibase maintainers will be able to access the pro repo.
|
||||||
|
|
||||||
The `yarn bootstrap` command can be used to replace the NPM supplied dependency with the local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev.
|
The `yarn bootstrap` command can be used to replace the NPM supplied dependency with the local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev.
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation.
|
Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation.
|
||||||
|
|
||||||
### Running tests
|
### Running tests
|
||||||
|
|
||||||
#### End-to-end Tests
|
#### End-to-end Tests
|
||||||
|
@ -226,12 +261,11 @@ yarn test:e2e
|
||||||
|
|
||||||
Or if you are in the builder you can run `yarn cy:test`.
|
Or if you are in the builder you can run `yarn cy:test`.
|
||||||
|
|
||||||
|
|
||||||
### Other Useful Information
|
### Other Useful Information
|
||||||
|
|
||||||
* The contributors are listed in [AUTHORS.md](https://github.com/Budibase/budibase/blob/master/.github/AUTHORS.md) (add yourself).
|
- The contributors are listed in [AUTHORS.md](https://github.com/Budibase/budibase/blob/master/.github/AUTHORS.md) (add yourself).
|
||||||
|
|
||||||
* This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE).
|
- This project uses a modified version of the MPLv2 license, see [LICENSE](https://github.com/budibase/server/blob/master/LICENSE).
|
||||||
|
|
||||||
* We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions.
|
- We use the [C4 (Collective Code Construction Contract)](https://rfc.zeromq.org/spec:42/C4/) process for contributions.
|
||||||
Please read this if you are unfamiliar with it.
|
Please read this if you are unfamiliar with it.
|
||||||
|
|
|
@ -19,10 +19,11 @@ COUCH_DB_PORT=4005
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
WATCHTOWER_PORT=6161
|
WATCHTOWER_PORT=6161
|
||||||
BUDIBASE_ENVIRONMENT=PRODUCTION
|
BUDIBASE_ENVIRONMENT=PRODUCTION
|
||||||
|
SQL_MAX_ROWS=
|
||||||
|
|
||||||
# An admin user can be automatically created initially if these are set
|
# An admin user can be automatically created initially if these are set
|
||||||
BB_ADMIN_USER_EMAIL=
|
BB_ADMIN_USER_EMAIL=
|
||||||
BB_ADMIN_USER_PASSWORD=
|
BB_ADMIN_USER_PASSWORD=
|
||||||
|
|
||||||
# A path that is watched for plugin bundles. Any bundles found are imported automatically/
|
# A path that is watched for plugin bundles. Any bundles found are imported automatically/
|
||||||
PLUGINS_DIR=
|
PLUGINS_DIR=
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
FROM couchdb:3.2.1
|
||||||
|
|
||||||
|
ENV COUCHDB_USER admin
|
||||||
|
ENV COUCHDB_PASSWORD admin
|
||||||
|
EXPOSE 5984
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common wget unzip curl && \
|
||||||
|
apt-add-repository 'deb http://security.debian.org/debian-security stretch/updates main' && \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends openjdk-8-jre && \
|
||||||
|
rm -rf /var/lib/apt/lists/
|
||||||
|
|
||||||
|
# setup clouseau
|
||||||
|
WORKDIR /
|
||||||
|
RUN wget https://github.com/cloudant-labs/clouseau/releases/download/2.21.0/clouseau-2.21.0-dist.zip && \
|
||||||
|
unzip clouseau-2.21.0-dist.zip && \
|
||||||
|
mv clouseau-2.21.0 /opt/clouseau && \
|
||||||
|
rm clouseau-2.21.0-dist.zip
|
||||||
|
|
||||||
|
WORKDIR /opt/clouseau
|
||||||
|
RUN mkdir ./bin
|
||||||
|
ADD clouseau/clouseau ./bin/
|
||||||
|
ADD clouseau/log4j.properties clouseau/clouseau.ini ./
|
||||||
|
|
||||||
|
# setup CouchDB
|
||||||
|
WORKDIR /opt/couchdb
|
||||||
|
ADD couch/vm.args couch/local.ini ./etc/
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
ADD build-target-paths.sh .
|
||||||
|
ADD runner.sh ./bbcouch-runner.sh
|
||||||
|
RUN chmod +x ./bbcouch-runner.sh /opt/clouseau/bin/clouseau ./build-target-paths.sh
|
||||||
|
CMD ["./bbcouch-runner.sh"]
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo ${TARGETBUILD} > /buildtarget.txt
|
||||||
|
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
||||||
|
# Azure AppService uses /home for persisent data & SSH on port 2222
|
||||||
|
DATA_DIR=/home
|
||||||
|
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
|
||||||
|
mkdir -p $DATA_DIR/{search,minio,couch}
|
||||||
|
mkdir -p $DATA_DIR/couch/{dbs,views}
|
||||||
|
chown -R couchdb:couchdb $DATA_DIR/couch/
|
||||||
|
apt update
|
||||||
|
apt-get install -y openssh-server
|
||||||
|
echo "root:Docker!" | chpasswd
|
||||||
|
mkdir -p /tmp
|
||||||
|
chmod +x /tmp/ssh_setup.sh \
|
||||||
|
&& (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null)
|
||||||
|
cp /etc/sshd_config /etc/ssh/sshd_config
|
||||||
|
/etc/init.d/ssh restart
|
||||||
|
sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini
|
||||||
|
sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini
|
||||||
|
else
|
||||||
|
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
|
||||||
|
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
|
||||||
|
fi
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DATA_DIR=${DATA_DIR:-/data}
|
||||||
|
mkdir -p ${DATA_DIR}
|
||||||
|
mkdir -p ${DATA_DIR}/couch/{dbs,views}
|
||||||
|
mkdir -p ${DATA_DIR}/search
|
||||||
|
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
||||||
|
/build-target-paths.sh
|
||||||
|
/opt/clouseau/bin/clouseau > /dev/stdout 2>&1 &
|
||||||
|
/docker-entrypoint.sh /opt/couchdb/bin/couchdb &
|
||||||
|
sleep 10
|
||||||
|
curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_users
|
||||||
|
curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_replicator
|
||||||
|
sleep infinity
|
|
@ -0,0 +1,23 @@
|
||||||
|
FROM budibase/couchdb
|
||||||
|
|
||||||
|
ENV DATA_DIR /data
|
||||||
|
RUN mkdir /data
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends redis-server
|
||||||
|
|
||||||
|
WORKDIR /minio
|
||||||
|
ADD scripts/install-minio.sh ./install.sh
|
||||||
|
RUN chmod +x install.sh && ./install.sh
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
ADD dependencies/runner.sh .
|
||||||
|
RUN chmod +x ./runner.sh
|
||||||
|
|
||||||
|
EXPOSE 5984
|
||||||
|
EXPOSE 9000
|
||||||
|
EXPOSE 9001
|
||||||
|
EXPOSE 6379
|
||||||
|
|
||||||
|
CMD ["./runner.sh"]
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Docker Image for Running Budibase Tests
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This image contains the basic setup for running
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- Build the Image
|
||||||
|
- Run the Container
|
||||||
|
|
||||||
|
|
||||||
|
### Build the Image
|
||||||
|
The guidance below is based on building the Budibase single image on Debian 11 and AlmaLinux 8. If you use another distro or OS you will need to amend the commands to suit.
|
||||||
|
#### Install Node
|
||||||
|
Budibase requires a more recent version of node (14+) than is available in the base Debian repos so:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -sL https://deb.nodesource.com/setup_16.x | sudo bash -
|
||||||
|
apt install -y nodejs
|
||||||
|
node -v
|
||||||
|
```
|
||||||
|
Install yarn and lerna:
|
||||||
|
```
|
||||||
|
npm install -g yarn jest lerna
|
||||||
|
```
|
||||||
|
#### Install Docker
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install -y docker.io
|
||||||
|
```
|
||||||
|
|
||||||
|
Check the versions of each installed version. This process was tested with the version numbers below so YMMV using anything else:
|
||||||
|
|
||||||
|
- Docker: 20.10.5
|
||||||
|
- node: 16.15.1
|
||||||
|
- yarn: 1.22.19
|
||||||
|
- lerna: 5.1.4
|
||||||
|
|
||||||
|
#### Get the Code
|
||||||
|
Clone the Budibase repo
|
||||||
|
```
|
||||||
|
git clone https://github.com/Budibase/budibase.git
|
||||||
|
cd budibase
|
||||||
|
```
|
||||||
|
#### Setup Node
|
||||||
|
Node setup:
|
||||||
|
```
|
||||||
|
node ./hosting/scripts/setup.js
|
||||||
|
yarn
|
||||||
|
yarn bootstrap
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
#### Build Image
|
||||||
|
The following yarn command does some prep and then runs the docker build command:
|
||||||
|
```
|
||||||
|
yarn build:docker:dependencies
|
||||||
|
```
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
||||||
|
/bbcouch-runner.sh &
|
||||||
|
/minio/minio server ${DATA_DIR}/minio --console-address ":9001" > /dev/stdout 2>&1 &
|
||||||
|
|
||||||
|
echo "Budibase dependencies started..."
|
||||||
|
sleep infinity
|
|
@ -42,25 +42,16 @@ services:
|
||||||
|
|
||||||
couchdb-service:
|
couchdb-service:
|
||||||
# platform: linux/amd64
|
# platform: linux/amd64
|
||||||
container_name: budi-couchdb-dev
|
container_name: budi-couchdb3-dev
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
image: ibmcom/couchdb3
|
image: budibase/couchdb
|
||||||
environment:
|
environment:
|
||||||
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
||||||
- COUCHDB_USER=${COUCH_DB_USER}
|
- COUCHDB_USER=${COUCH_DB_USER}
|
||||||
ports:
|
ports:
|
||||||
- "${COUCH_DB_PORT}:5984"
|
- "${COUCH_DB_PORT}:5984"
|
||||||
volumes:
|
volumes:
|
||||||
- couchdb3_data:/opt/couchdb/data
|
- couchdb_data:/data
|
||||||
|
|
||||||
couch-init:
|
|
||||||
container_name: budi-couchdb-init-dev
|
|
||||||
image: curlimages/curl
|
|
||||||
environment:
|
|
||||||
PUT_CALL: "curl -u ${COUCH_DB_USER}:${COUCH_DB_PASSWORD} -X PUT couchdb-service:5984"
|
|
||||||
depends_on:
|
|
||||||
- couchdb-service
|
|
||||||
command: ["sh","-c","sleep 10 && $${PUT_CALL}/_users && $${PUT_CALL}/_replicator; fg;"]
|
|
||||||
|
|
||||||
redis-service:
|
redis-service:
|
||||||
container_name: budi-redis-dev
|
container_name: budi-redis-dev
|
||||||
|
@ -73,7 +64,7 @@ services:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
couchdb3_data:
|
couchdb_data:
|
||||||
driver: local
|
driver: local
|
||||||
minio_data:
|
minio_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
# optional ports are specified throughout for more advanced use cases.
|
||||||
|
|
||||||
|
services:
|
||||||
|
minio-service:
|
||||||
|
restart: on-failure
|
||||||
|
# Last version that supports the "fs" backend
|
||||||
|
image: minio/minio:RELEASE.2022-10-24T18-35-07Z
|
||||||
|
ports:
|
||||||
|
- 9000
|
||||||
|
- 9001
|
||||||
|
environment:
|
||||||
|
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
||||||
|
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
couchdb-service:
|
||||||
|
# platform: linux/amd64
|
||||||
|
restart: on-failure
|
||||||
|
image: budibase/couchdb
|
||||||
|
environment:
|
||||||
|
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
||||||
|
- COUCHDB_USER=${COUCH_DB_USER}
|
||||||
|
ports:
|
||||||
|
- 5984
|
||||||
|
- 4369
|
||||||
|
- 9100
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:5984/_up"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
redis-service:
|
||||||
|
restart: on-failure
|
||||||
|
image: redis
|
||||||
|
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- 6379
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
if [[ $TARGETARCH == arm* ]] ;
|
||||||
|
then
|
||||||
|
echo "INSTALLING ARM64 MINIO"
|
||||||
|
wget https://dl.min.io/server/minio/release/linux-arm64/minio
|
||||||
|
else
|
||||||
|
echo "INSTALLING AMD64 MINIO"
|
||||||
|
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
||||||
|
fi
|
||||||
|
chmod +x minio
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
tag=$1
|
||||||
|
|
||||||
|
if [[ ! "$tag" ]]; then
|
||||||
|
echo "No tag present. You must pass a tag to this script"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Tagging images with tag: $tag"
|
||||||
|
|
||||||
|
docker tag budibase-couchdb budibase/couchdb:$tag
|
||||||
|
|
||||||
|
docker push --all-tags budibase/couchdb
|
||||||
|
|
|
@ -18,7 +18,7 @@ WORKDIR /worker
|
||||||
ADD packages/worker .
|
ADD packages/worker .
|
||||||
RUN node /pinVersions.js && yarn && yarn build && /cleanup.sh
|
RUN node /pinVersions.js && yarn && yarn build && /cleanup.sh
|
||||||
|
|
||||||
FROM couchdb:3.2.1
|
FROM budibase/couchdb
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV TARGETARCH $TARGETARCH
|
ENV TARGETARCH $TARGETARCH
|
||||||
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
||||||
|
@ -29,23 +29,9 @@ ENV TARGETBUILD $TARGETBUILD
|
||||||
COPY --from=build /app /app
|
COPY --from=build /app /app
|
||||||
COPY --from=build /worker /worker
|
COPY --from=build /worker /worker
|
||||||
|
|
||||||
# ENV CUSTOM_DOMAIN=budi001.custom.com \
|
|
||||||
# See runner.sh for Env Vars
|
|
||||||
# These secret env variables are generated by the runner at startup
|
|
||||||
# their values can be overriden by the user, they will be written
|
|
||||||
# to the .env file in the /data directory for use later on
|
|
||||||
# REDIS_PASSWORD=budibase \
|
|
||||||
# COUCHDB_PASSWORD=budibase \
|
|
||||||
# COUCHDB_USER=budibase \
|
|
||||||
# COUCH_DB_URL=http://budibase:budibase@localhost:5984 \
|
|
||||||
# INTERNAL_API_KEY=budibase \
|
|
||||||
# JWT_SECRET=testsecret \
|
|
||||||
# MINIO_ACCESS_KEY=budibase \
|
|
||||||
# MINIO_SECRET_KEY=budibase \
|
|
||||||
|
|
||||||
# install base dependencies
|
# install base dependencies
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y software-properties-common wget nginx uuid-runtime && \
|
apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server && \
|
||||||
apt-add-repository 'deb http://security.debian.org/debian-security stretch/updates main' && \
|
apt-add-repository 'deb http://security.debian.org/debian-security stretch/updates main' && \
|
||||||
apt-get update
|
apt-get update
|
||||||
|
|
||||||
|
@ -53,7 +39,7 @@ RUN apt-get update && \
|
||||||
WORKDIR /nodejs
|
WORKDIR /nodejs
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh && \
|
RUN curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh && \
|
||||||
bash /tmp/nodesource_setup.sh && \
|
bash /tmp/nodesource_setup.sh && \
|
||||||
apt-get install -y libaio1 nodejs nginx openjdk-8-jdk redis-server unzip && \
|
apt-get install -y --no-install-recommends libaio1 nodejs && \
|
||||||
npm install --global yarn pm2
|
npm install --global yarn pm2
|
||||||
|
|
||||||
# setup nginx
|
# setup nginx
|
||||||
|
@ -69,23 +55,6 @@ RUN mkdir -p scripts/integrations/oracle
|
||||||
ADD packages/server/scripts/integrations/oracle scripts/integrations/oracle
|
ADD packages/server/scripts/integrations/oracle scripts/integrations/oracle
|
||||||
RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/install.sh
|
RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/install.sh
|
||||||
|
|
||||||
# setup clouseau
|
|
||||||
WORKDIR /
|
|
||||||
RUN wget https://github.com/cloudant-labs/clouseau/releases/download/2.21.0/clouseau-2.21.0-dist.zip && \
|
|
||||||
unzip clouseau-2.21.0-dist.zip && \
|
|
||||||
mv clouseau-2.21.0 /opt/clouseau && \
|
|
||||||
rm clouseau-2.21.0-dist.zip
|
|
||||||
|
|
||||||
WORKDIR /opt/clouseau
|
|
||||||
RUN mkdir ./bin
|
|
||||||
ADD hosting/single/clouseau/clouseau ./bin/
|
|
||||||
ADD hosting/single/clouseau/log4j.properties hosting/single/clouseau/clouseau.ini ./
|
|
||||||
RUN chmod +x ./bin/clouseau
|
|
||||||
|
|
||||||
# setup CouchDB
|
|
||||||
WORKDIR /opt/couchdb
|
|
||||||
ADD hosting/single/couch/vm.args hosting/single/couch/local.ini ./etc/
|
|
||||||
|
|
||||||
# setup minio
|
# setup minio
|
||||||
WORKDIR /minio
|
WORKDIR /minio
|
||||||
ADD scripts/install-minio.sh ./install.sh
|
ADD scripts/install-minio.sh ./install.sh
|
||||||
|
@ -98,9 +67,6 @@ RUN chmod +x ./runner.sh
|
||||||
ADD hosting/single/healthcheck.sh .
|
ADD hosting/single/healthcheck.sh .
|
||||||
RUN chmod +x ./healthcheck.sh
|
RUN chmod +x ./healthcheck.sh
|
||||||
|
|
||||||
ADD hosting/scripts/build-target-paths.sh .
|
|
||||||
RUN chmod +x ./build-target-paths.sh
|
|
||||||
|
|
||||||
# Script below sets the path for storing data based on $DATA_DIR
|
# Script below sets the path for storing data based on $DATA_DIR
|
||||||
# For Azure App Service install SSH & point data locations to /home
|
# For Azure App Service install SSH & point data locations to /home
|
||||||
ADD hosting/single/ssh/sshd_config /etc/
|
ADD hosting/single/ssh/sshd_config /etc/
|
||||||
|
|
|
@ -10,7 +10,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME
|
||||||
[[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000
|
[[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000
|
||||||
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
|
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
|
||||||
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
||||||
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS"
|
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
|
||||||
[[ -z "${ACCOUNT_PORTAL_URL}" ]] && export ACCOUNT_PORTAL_URL=https://account.budibase.app
|
[[ -z "${ACCOUNT_PORTAL_URL}" ]] && export ACCOUNT_PORTAL_URL=https://account.budibase.app
|
||||||
[[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379
|
[[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379
|
||||||
[[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1
|
[[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1
|
||||||
|
@ -72,14 +72,11 @@ for LINE in $(cat ${DATA_DIR}/.env); do export $LINE; done
|
||||||
ln -s ${DATA_DIR}/.env /app/.env
|
ln -s ${DATA_DIR}/.env /app/.env
|
||||||
ln -s ${DATA_DIR}/.env /worker/.env
|
ln -s ${DATA_DIR}/.env /worker/.env
|
||||||
# make these directories in runner, incase of mount
|
# make these directories in runner, incase of mount
|
||||||
mkdir -p ${DATA_DIR}/couch/{dbs,views}
|
|
||||||
mkdir -p ${DATA_DIR}/minio
|
mkdir -p ${DATA_DIR}/minio
|
||||||
mkdir -p ${DATA_DIR}/search
|
|
||||||
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
||||||
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
||||||
/opt/clouseau/bin/clouseau > /dev/stdout 2>&1 &
|
/bbcouch-runner.sh &
|
||||||
/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 &
|
/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 &
|
||||||
/docker-entrypoint.sh /opt/couchdb/bin/couchdb &
|
|
||||||
/etc/init.d/nginx restart
|
/etc/init.d/nginx restart
|
||||||
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
||||||
# Add monthly cron job to renew certbot certificate
|
# Add monthly cron job to renew certbot certificate
|
||||||
|
@ -90,15 +87,14 @@ if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
||||||
/etc/init.d/nginx restart
|
/etc/init.d/nginx restart
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# wait for backend services to start
|
||||||
|
sleep 10
|
||||||
|
|
||||||
pushd app
|
pushd app
|
||||||
pm2 start -l /dev/stdout --name app "yarn run:docker"
|
pm2 start -l /dev/stdout --name app "yarn run:docker"
|
||||||
popd
|
popd
|
||||||
pushd worker
|
pushd worker
|
||||||
pm2 start -l /dev/stdout --name worker "yarn run:docker"
|
pm2 start -l /dev/stdout --name worker "yarn run:docker"
|
||||||
popd
|
popd
|
||||||
sleep 10
|
|
||||||
echo "curl to couchdb endpoints"
|
|
||||||
curl -X PUT ${COUCH_DB_URL}/_users
|
|
||||||
curl -X PUT ${COUCH_DB_URL}/_replicator
|
|
||||||
echo "end of runner.sh, sleeping ..."
|
echo "end of runner.sh, sleeping ..."
|
||||||
sleep infinity
|
sleep infinity
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = () => {
|
||||||
|
return {
|
||||||
|
dockerCompose: {
|
||||||
|
composeFilePath: "../../hosting",
|
||||||
|
composeFile: "docker-compose.test.yaml",
|
||||||
|
startupTimeout: 10000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.2.12-alpha.48",
|
"version": "2.3.2-alpha.2",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
10
package.json
10
package.json
|
@ -3,6 +3,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-json": "^4.0.2",
|
"@rollup/plugin-json": "^4.0.2",
|
||||||
|
"@types/supertest": "^2.0.12",
|
||||||
"@typescript-eslint/parser": "5.45.0",
|
"@typescript-eslint/parser": "5.45.0",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"eslint": "^7.28.0",
|
"eslint": "^7.28.0",
|
||||||
|
@ -51,10 +52,6 @@
|
||||||
"lint:fix:eslint": "eslint --fix packages qa-core",
|
"lint:fix:eslint": "eslint --fix packages qa-core",
|
||||||
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"",
|
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"",
|
||||||
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
||||||
"test:e2e": "lerna run cy:test --stream",
|
|
||||||
"test:e2e:ci": "lerna run cy:ci --stream",
|
|
||||||
"test:e2e:ci:record": "lerna run cy:ci:record --stream",
|
|
||||||
"test:e2e:ci:notify": "lerna run cy:ci:notify",
|
|
||||||
"build:specs": "lerna run specs",
|
"build:specs": "lerna run specs",
|
||||||
"build:docker": "lerna run build:docker && npm run build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
|
"build:docker": "lerna run build:docker && npm run build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
|
||||||
"build:docker:pre": "lerna run build && lerna run predocker",
|
"build:docker:pre": "lerna run build && lerna run predocker",
|
||||||
|
@ -66,6 +63,9 @@
|
||||||
"build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .",
|
"build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .",
|
||||||
"build:docker:single:image": "docker build -f hosting/single/Dockerfile -t budibase:latest .",
|
"build:docker:single:image": "docker build -f hosting/single/Dockerfile -t budibase:latest .",
|
||||||
"build:docker:single": "npm run build:docker:pre && npm run build:docker:single:image",
|
"build:docker:single": "npm run build:docker:pre && npm run build:docker:single:image",
|
||||||
|
"build:docker:dependencies": "docker build -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest ./hosting",
|
||||||
|
"publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.2.1 --push ./hosting/couchdb",
|
||||||
|
"publish:docker:dependencies": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest -t budibase/dependencies:v3.2.1 --push ./hosting",
|
||||||
"build:docs": "lerna run build:docs",
|
"build:docs": "lerna run build:docs",
|
||||||
"release:helm": "node scripts/releaseHelmChart",
|
"release:helm": "node scripts/releaseHelmChart",
|
||||||
"env:multi:enable": "lerna run env:multi:enable",
|
"env:multi:enable": "lerna run env:multi:enable",
|
||||||
|
@ -84,4 +84,4 @@
|
||||||
"install:pro": "bash scripts/pro/install.sh",
|
"install:pro": "bash scripts/pro/install.sh",
|
||||||
"dep:clean": "yarn clean && yarn bootstrap"
|
"dep:clean": "yarn clean && yarn bootstrap"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
const { join } = require("path")
|
||||||
|
require("dotenv").config({
|
||||||
|
path: join(__dirname, "..", "..", "hosting", ".env"),
|
||||||
|
})
|
||||||
|
|
||||||
|
const jestTestcontainersConfigGenerator = require("../../jestTestcontainersConfigGenerator")
|
||||||
|
|
||||||
|
module.exports = jestTestcontainersConfigGenerator()
|
|
@ -1,11 +1,11 @@
|
||||||
import { Config } from "@jest/types"
|
import { Config } from "@jest/types"
|
||||||
|
const preset = require("ts-jest/jest-preset")
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const baseConfig: Config.InitialProjectOptions = {
|
||||||
preset: "ts-jest",
|
...preset,
|
||||||
testEnvironment: "node",
|
preset: "@trendyol/jest-testcontainers",
|
||||||
setupFiles: ["./tests/jestSetup.ts"],
|
setupFiles: ["./tests/jestEnv.ts"],
|
||||||
collectCoverageFrom: ["src/**/*.{js,ts}"],
|
setupFilesAfterEnv: ["./tests/jestSetup.ts"],
|
||||||
coverageReporters: ["lcov", "json", "clover"],
|
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.ts?$": "@swc/jest",
|
"^.+\\.ts?$": "@swc/jest",
|
||||||
},
|
},
|
||||||
|
@ -13,12 +13,28 @@ const config: Config.InitialOptions = {
|
||||||
|
|
||||||
if (!process.env.CI) {
|
if (!process.env.CI) {
|
||||||
// use sources when not in CI
|
// use sources when not in CI
|
||||||
config.moduleNameMapper = {
|
baseConfig.moduleNameMapper = {
|
||||||
"@budibase/types": "<rootDir>/../types/src",
|
"@budibase/types": "<rootDir>/../types/src",
|
||||||
"^axios.*$": "<rootDir>/node_modules/axios/lib/axios.js",
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Running tests with compiled dependency sources")
|
console.log("Running tests with compiled dependency sources")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const config: Config.InitialOptions = {
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
...baseConfig,
|
||||||
|
displayName: "sequential test",
|
||||||
|
testMatch: ["<rootDir>/**/*.seq.spec.[jt]s"],
|
||||||
|
runner: "jest-serial-runner",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...baseConfig,
|
||||||
|
testMatch: ["<rootDir>/**/!(*.seq).spec.[jt]s"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
collectCoverageFrom: ["src/**/*.{js,ts}"],
|
||||||
|
coverageReporters: ["lcov", "json", "clover"],
|
||||||
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.2.12-alpha.48",
|
"version": "2.3.2-alpha.2",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -23,7 +23,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/nano": "10.1.1",
|
"@budibase/nano": "10.1.1",
|
||||||
"@budibase/types": "2.2.12-alpha.48",
|
"@budibase/pouchdb-replication-stream": "1.2.10",
|
||||||
|
"@budibase/types": "2.3.2-alpha.2",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
"@shopify/jest-koa-mocks": "5.0.1",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-cloudfront-sign": "2.2.0",
|
"aws-cloudfront-sign": "2.2.0",
|
||||||
|
@ -48,7 +49,6 @@
|
||||||
"posthog-node": "1.3.0",
|
"posthog-node": "1.3.0",
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "7.3.0",
|
||||||
"pouchdb-find": "7.2.2",
|
"pouchdb-find": "7.2.2",
|
||||||
"pouchdb-replication-stream": "1.2.9",
|
|
||||||
"redlock": "4.2.0",
|
"redlock": "4.2.0",
|
||||||
"sanitize-s3-objectkey": "0.0.1",
|
"sanitize-s3-objectkey": "0.0.1",
|
||||||
"semver": "7.3.7",
|
"semver": "7.3.7",
|
||||||
|
@ -59,6 +59,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/core": "^1.3.25",
|
"@swc/core": "^1.3.25",
|
||||||
"@swc/jest": "^0.2.24",
|
"@swc/jest": "^0.2.24",
|
||||||
|
"@trendyol/jest-testcontainers": "^2.1.1",
|
||||||
"@types/chance": "1.1.3",
|
"@types/chance": "1.1.3",
|
||||||
"@types/ioredis": "4.28.0",
|
"@types/ioredis": "4.28.0",
|
||||||
"@types/jest": "27.5.1",
|
"@types/jest": "27.5.1",
|
||||||
|
@ -76,6 +77,7 @@
|
||||||
"chance": "1.1.8",
|
"chance": "1.1.8",
|
||||||
"ioredis-mock": "5.8.0",
|
"ioredis-mock": "5.8.0",
|
||||||
"jest": "28.1.1",
|
"jest": "28.1.1",
|
||||||
|
"jest-serial-runner": "^1.2.1",
|
||||||
"koa": "2.13.4",
|
"koa": "2.13.4",
|
||||||
"nodemon": "2.0.16",
|
"nodemon": "2.0.16",
|
||||||
"pouchdb-adapter-memory": "7.2.2",
|
"pouchdb-adapter-memory": "7.2.2",
|
||||||
|
|
|
@ -9,16 +9,8 @@ import {
|
||||||
jwt as jwtPassport,
|
jwt as jwtPassport,
|
||||||
local,
|
local,
|
||||||
authenticated,
|
authenticated,
|
||||||
auditLog,
|
|
||||||
tenancy,
|
tenancy,
|
||||||
authError,
|
|
||||||
ssoCallbackUrl,
|
|
||||||
csrf,
|
csrf,
|
||||||
internalApi,
|
|
||||||
adminOnly,
|
|
||||||
builderOnly,
|
|
||||||
builderOrAdmin,
|
|
||||||
joiValidator,
|
|
||||||
oidc,
|
oidc,
|
||||||
google,
|
google,
|
||||||
} from "../middleware"
|
} from "../middleware"
|
||||||
|
|
|
@ -2,14 +2,16 @@ require("../../../tests")
|
||||||
const { Writethrough } = require("../writethrough")
|
const { Writethrough } = require("../writethrough")
|
||||||
const { getDB } = require("../../db")
|
const { getDB } = require("../../db")
|
||||||
const tk = require("timekeeper")
|
const tk = require("timekeeper")
|
||||||
|
const { structures } = require("../../../tests")
|
||||||
|
|
||||||
const START_DATE = Date.now()
|
const START_DATE = Date.now()
|
||||||
tk.freeze(START_DATE)
|
tk.freeze(START_DATE)
|
||||||
|
|
||||||
|
|
||||||
const DELAY = 5000
|
const DELAY = 5000
|
||||||
|
|
||||||
const db = getDB("test")
|
const db = getDB(structures.db.id())
|
||||||
const db2 = getDB("test2")
|
const db2 = getDB(structures.db.id())
|
||||||
const writethrough = new Writethrough(db, DELAY), writethrough2 = new Writethrough(db2, DELAY)
|
const writethrough = new Writethrough(db, DELAY), writethrough2 = new Writethrough(db2, DELAY)
|
||||||
|
|
||||||
describe("writethrough", () => {
|
describe("writethrough", () => {
|
||||||
|
|
|
@ -15,18 +15,47 @@ import { getCouchInfo } from "./connections"
|
||||||
import { directCouchCall } from "./utils"
|
import { directCouchCall } from "./utils"
|
||||||
import { getPouchDB } from "./pouchDB"
|
import { getPouchDB } from "./pouchDB"
|
||||||
import { WriteStream, ReadStream } from "fs"
|
import { WriteStream, ReadStream } from "fs"
|
||||||
|
import { newid } from "../../newid"
|
||||||
|
|
||||||
|
function buildNano(couchInfo: { url: string; cookie: string }) {
|
||||||
|
return Nano({
|
||||||
|
url: couchInfo.url,
|
||||||
|
requestDefaults: {
|
||||||
|
headers: {
|
||||||
|
Authorization: couchInfo.cookie,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parseUrl: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DatabaseWithConnection(
|
||||||
|
dbName: string,
|
||||||
|
connection: string,
|
||||||
|
opts?: DatabaseOpts
|
||||||
|
) {
|
||||||
|
if (!connection) {
|
||||||
|
throw new Error("Must provide connection details")
|
||||||
|
}
|
||||||
|
return new DatabaseImpl(dbName, opts, connection)
|
||||||
|
}
|
||||||
|
|
||||||
export class DatabaseImpl implements Database {
|
export class DatabaseImpl implements Database {
|
||||||
public readonly name: string
|
public readonly name: string
|
||||||
private static nano: Nano.ServerScope
|
private static nano: Nano.ServerScope
|
||||||
|
private readonly instanceNano?: Nano.ServerScope
|
||||||
private readonly pouchOpts: DatabaseOpts
|
private readonly pouchOpts: DatabaseOpts
|
||||||
|
|
||||||
constructor(dbName?: string, opts?: DatabaseOpts) {
|
constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
|
||||||
if (dbName == null) {
|
if (dbName == null) {
|
||||||
throw new Error("Database name cannot be undefined.")
|
throw new Error("Database name cannot be undefined.")
|
||||||
}
|
}
|
||||||
this.name = dbName
|
this.name = dbName
|
||||||
this.pouchOpts = opts || {}
|
this.pouchOpts = opts || {}
|
||||||
|
if (connection) {
|
||||||
|
const couchInfo = getCouchInfo(connection)
|
||||||
|
this.instanceNano = buildNano(couchInfo)
|
||||||
|
}
|
||||||
if (!DatabaseImpl.nano) {
|
if (!DatabaseImpl.nano) {
|
||||||
DatabaseImpl.init()
|
DatabaseImpl.init()
|
||||||
}
|
}
|
||||||
|
@ -34,15 +63,7 @@ export class DatabaseImpl implements Database {
|
||||||
|
|
||||||
static init() {
|
static init() {
|
||||||
const couchInfo = getCouchInfo()
|
const couchInfo = getCouchInfo()
|
||||||
DatabaseImpl.nano = Nano({
|
DatabaseImpl.nano = buildNano(couchInfo)
|
||||||
url: couchInfo.url,
|
|
||||||
requestDefaults: {
|
|
||||||
headers: {
|
|
||||||
Authorization: couchInfo.cookie,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
parseUrl: false,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async exists() {
|
async exists() {
|
||||||
|
@ -50,6 +71,10 @@ export class DatabaseImpl implements Database {
|
||||||
return response.status === 200
|
return response.status === 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private nano() {
|
||||||
|
return this.instanceNano || DatabaseImpl.nano
|
||||||
|
}
|
||||||
|
|
||||||
async checkSetup() {
|
async checkSetup() {
|
||||||
let shouldCreate = !this.pouchOpts?.skip_setup
|
let shouldCreate = !this.pouchOpts?.skip_setup
|
||||||
// check exists in a lightweight fashion
|
// check exists in a lightweight fashion
|
||||||
|
@ -58,9 +83,16 @@ export class DatabaseImpl implements Database {
|
||||||
throw new Error("DB does not exist")
|
throw new Error("DB does not exist")
|
||||||
}
|
}
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
await DatabaseImpl.nano.db.create(this.name)
|
try {
|
||||||
|
await this.nano().db.create(this.name)
|
||||||
|
} catch (err: any) {
|
||||||
|
// Handling race conditions
|
||||||
|
if (err.statusCode !== 412) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return DatabaseImpl.nano.db.use(this.name)
|
return this.nano().db.use(this.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateOutput(fnc: any) {
|
private async updateOutput(fnc: any) {
|
||||||
|
@ -101,6 +133,13 @@ export class DatabaseImpl implements Database {
|
||||||
return this.updateOutput(() => db.destroy(_id, _rev))
|
return this.updateOutput(() => db.destroy(_id, _rev))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async post(document: AnyDocument, opts?: DatabasePutOpts) {
|
||||||
|
if (!document._id) {
|
||||||
|
document._id = newid()
|
||||||
|
}
|
||||||
|
return this.put(document, opts)
|
||||||
|
}
|
||||||
|
|
||||||
async put(document: AnyDocument, opts?: DatabasePutOpts) {
|
async put(document: AnyDocument, opts?: DatabasePutOpts) {
|
||||||
if (!document._id) {
|
if (!document._id) {
|
||||||
throw new Error("Cannot store document without _id field.")
|
throw new Error("Cannot store document without _id field.")
|
||||||
|
@ -146,7 +185,7 @@ export class DatabaseImpl implements Database {
|
||||||
|
|
||||||
async destroy() {
|
async destroy() {
|
||||||
try {
|
try {
|
||||||
await DatabaseImpl.nano.db.destroy(this.name)
|
return await this.nano().db.destroy(this.name)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// didn't exist, don't worry
|
// didn't exist, don't worry
|
||||||
if (err.statusCode === 404) {
|
if (err.statusCode === 404) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
|
|
||||||
export const getCouchInfo = () => {
|
export const getCouchInfo = (connection?: string) => {
|
||||||
const urlInfo = getUrlInfo()
|
const urlInfo = getUrlInfo(connection)
|
||||||
let username
|
let username
|
||||||
let password
|
let password
|
||||||
if (env.COUCH_DB_USERNAME) {
|
if (env.COUCH_DB_USERNAME) {
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const getPouch = (opts: PouchOptions = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.replication) {
|
if (opts.replication) {
|
||||||
const replicationStream = require("pouchdb-replication-stream")
|
const replicationStream = require("@budibase/pouchdb-replication-stream")
|
||||||
PouchDB.plugin(replicationStream.plugin)
|
PouchDB.plugin(replicationStream.plugin)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
|
PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
|
||||||
|
|
|
@ -6,12 +6,6 @@ import { DatabaseImpl } from "../db"
|
||||||
const dbList = new Set()
|
const dbList = new Set()
|
||||||
|
|
||||||
export function getDB(dbName?: string, opts?: any): Database {
|
export function getDB(dbName?: string, opts?: any): Database {
|
||||||
// TODO: once using the test image, need to remove this
|
|
||||||
if (env.isTest()) {
|
|
||||||
dbList.add(dbName)
|
|
||||||
// @ts-ignore
|
|
||||||
return getPouchDB(dbName, opts)
|
|
||||||
}
|
|
||||||
return new DatabaseImpl(dbName, opts)
|
return new DatabaseImpl(dbName, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
require("../../../tests")
|
require("../../../tests")
|
||||||
const { getDB } = require("../")
|
const { structures } = require("../../../tests")
|
||||||
|
const { getDB } = require("../db")
|
||||||
|
|
||||||
describe("db", () => {
|
describe("db", () => {
|
||||||
|
|
||||||
describe("getDB", () => {
|
describe("getDB", () => {
|
||||||
it("returns a db", async () => {
|
it("returns a db", async () => {
|
||||||
const db = getDB("test")
|
|
||||||
|
const dbName = structures.db.id()
|
||||||
|
const db = getDB(dbName)
|
||||||
expect(db).toBeDefined()
|
expect(db).toBeDefined()
|
||||||
expect(db._adapter).toBe("memory")
|
expect(db.name).toBe(dbName)
|
||||||
expect(db.prefix).toBe("_pouch_")
|
|
||||||
expect(db.name).toBe("test")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("uses the custom put function", async () => {
|
it("uses the custom put function", async () => {
|
||||||
const db = getDB("test")
|
const db = getDB(structures.db.id())
|
||||||
let doc = { _id: "test" }
|
let doc = { _id: "test" }
|
||||||
await db.put(doc)
|
await db.put(doc)
|
||||||
doc = await db.get(doc._id)
|
doc = await db.get(doc._id)
|
||||||
|
@ -23,4 +23,3 @@ describe("db", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ const {
|
||||||
const { generateAppID, getPlatformUrl, getScopedConfig } = require("../utils")
|
const { generateAppID, getPlatformUrl, getScopedConfig } = require("../utils")
|
||||||
const tenancy = require("../../tenancy")
|
const tenancy = require("../../tenancy")
|
||||||
const { Config, DEFAULT_TENANT_ID } = require("../../constants")
|
const { Config, DEFAULT_TENANT_ID } = require("../../constants")
|
||||||
|
import { generator } from "../../../tests"
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
|
|
||||||
describe("utils", () => {
|
describe("utils", () => {
|
||||||
|
@ -66,17 +67,16 @@ describe("utils", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const DB_URL = "http://dburl.com"
|
|
||||||
const DEFAULT_URL = "http://localhost:10000"
|
const DEFAULT_URL = "http://localhost:10000"
|
||||||
const ENV_URL = "http://env.com"
|
const ENV_URL = "http://env.com"
|
||||||
|
|
||||||
const setDbPlatformUrl = async () => {
|
const setDbPlatformUrl = async (dbUrl: string) => {
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
db.put({
|
await db.put({
|
||||||
_id: "config_settings",
|
_id: "config_settings",
|
||||||
type: Config.SETTINGS,
|
type: Config.SETTINGS,
|
||||||
config: {
|
config: {
|
||||||
platformUrl: DB_URL,
|
platformUrl: dbUrl,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -119,9 +119,10 @@ describe("getPlatformUrl", () => {
|
||||||
|
|
||||||
it("gets the platform url from the database", async () => {
|
it("gets the platform url from the database", async () => {
|
||||||
await tenancy.doInTenant(null, async () => {
|
await tenancy.doInTenant(null, async () => {
|
||||||
await setDbPlatformUrl()
|
const dbUrl = generator.url()
|
||||||
|
await setDbPlatformUrl(dbUrl)
|
||||||
const url = await getPlatformUrl()
|
const url = await getPlatformUrl()
|
||||||
expect(url).toBe(DB_URL)
|
expect(url).toBe(dbUrl)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -152,7 +153,7 @@ describe("getPlatformUrl", () => {
|
||||||
|
|
||||||
it("never gets the platform url from the database", async () => {
|
it("never gets the platform url from the database", async () => {
|
||||||
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
await setDbPlatformUrl()
|
await setDbPlatformUrl(generator.url())
|
||||||
const url = await getPlatformUrl()
|
const url = await getPlatformUrl()
|
||||||
expect(url).toBe(TENANT_AWARE_URL)
|
expect(url).toBe(TENANT_AWARE_URL)
|
||||||
})
|
})
|
||||||
|
@ -170,10 +171,11 @@ describe("getScopedConfig", () => {
|
||||||
|
|
||||||
it("returns the platform url with an existing config", async () => {
|
it("returns the platform url with an existing config", async () => {
|
||||||
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
await setDbPlatformUrl()
|
const dbUrl = generator.url()
|
||||||
|
await setDbPlatformUrl(dbUrl)
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
const config = await getScopedConfig(db, { type: Config.SETTINGS })
|
const config = await getScopedConfig(db, { type: Config.SETTINGS })
|
||||||
expect(config.platformUrl).toBe(DB_URL)
|
expect(config.platformUrl).toBe(dbUrl)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
APP_PREFIX,
|
APP_PREFIX,
|
||||||
} from "../constants"
|
} from "../constants"
|
||||||
import { getTenantId, getGlobalDB, getGlobalDBName } from "../context"
|
import { getTenantId, getGlobalDB, getGlobalDBName } from "../context"
|
||||||
import { doWithDB, allDbs, directCouchAllDbs } from "./db"
|
import { doWithDB, directCouchAllDbs } from "./db"
|
||||||
import { getAppMetadata } from "../cache/appMetadata"
|
import { getAppMetadata } from "../cache/appMetadata"
|
||||||
import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
|
import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
|
||||||
import * as events from "../events"
|
import * as events from "../events"
|
||||||
|
@ -262,10 +262,7 @@ export function getStartEndKeyURL(baseKey: any, tenantId?: string) {
|
||||||
*/
|
*/
|
||||||
export async function getAllDbs(opts = { efficient: false }) {
|
export async function getAllDbs(opts = { efficient: false }) {
|
||||||
const efficient = opts && opts.efficient
|
const efficient = opts && opts.efficient
|
||||||
// specifically for testing we use the pouch package for this
|
|
||||||
if (env.isTest()) {
|
|
||||||
return allDbs()
|
|
||||||
}
|
|
||||||
let dbs: any[] = []
|
let dbs: any[] = []
|
||||||
async function addDbs(queryString?: string) {
|
async function addDbs(queryString?: string) {
|
||||||
const json = await directCouchAllDbs(queryString)
|
const json = await directCouchAllDbs(queryString)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
UserPermissionAssignedEvent,
|
UserPermissionAssignedEvent,
|
||||||
UserPermissionRemovedEvent,
|
UserPermissionRemovedEvent,
|
||||||
UserUpdatedEvent,
|
UserUpdatedEvent,
|
||||||
|
UserOnboardingEvent,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
async function created(user: User, timestamp?: number) {
|
async function created(user: User, timestamp?: number) {
|
||||||
|
@ -36,6 +37,13 @@ async function deleted(user: User) {
|
||||||
await publishEvent(Event.USER_DELETED, properties)
|
await publishEvent(Event.USER_DELETED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function onboardingComplete(user: User) {
|
||||||
|
const properties: UserOnboardingEvent = {
|
||||||
|
userId: user._id as string,
|
||||||
|
}
|
||||||
|
await publishEvent(Event.USER_ONBOARDING_COMPLETE, properties)
|
||||||
|
}
|
||||||
|
|
||||||
// PERMISSIONS
|
// PERMISSIONS
|
||||||
|
|
||||||
async function permissionAdminAssigned(user: User, timestamp?: number) {
|
async function permissionAdminAssigned(user: User, timestamp?: number) {
|
||||||
|
@ -126,6 +134,7 @@ export default {
|
||||||
permissionAdminRemoved,
|
permissionAdminRemoved,
|
||||||
permissionBuilderAssigned,
|
permissionBuilderAssigned,
|
||||||
permissionBuilderRemoved,
|
permissionBuilderRemoved,
|
||||||
|
onboardingComplete,
|
||||||
invited,
|
invited,
|
||||||
inviteAccepted,
|
inviteAccepted,
|
||||||
passwordForceReset,
|
passwordForceReset,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import * as tenancy from "../tenancy"
|
||||||
* The env var is formatted as:
|
* The env var is formatted as:
|
||||||
* tenant1:feature1:feature2,tenant2:feature1
|
* tenant1:feature1:feature2,tenant2:feature1
|
||||||
*/
|
*/
|
||||||
function getFeatureFlags() {
|
export function buildFeatureFlags() {
|
||||||
if (!env.TENANT_FEATURE_FLAGS) {
|
if (!env.TENANT_FEATURE_FLAGS) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,6 @@ function getFeatureFlags() {
|
||||||
return tenantFeatureFlags
|
return tenantFeatureFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
const TENANT_FEATURE_FLAGS = getFeatureFlags()
|
|
||||||
|
|
||||||
export function isEnabled(featureFlag: string) {
|
export function isEnabled(featureFlag: string) {
|
||||||
const tenantId = tenancy.getTenantId()
|
const tenantId = tenancy.getTenantId()
|
||||||
const flags = getTenantFeatureFlags(tenantId)
|
const flags = getTenantFeatureFlags(tenantId)
|
||||||
|
@ -36,18 +34,36 @@ export function isEnabled(featureFlag: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTenantFeatureFlags(tenantId: string) {
|
export function getTenantFeatureFlags(tenantId: string) {
|
||||||
const flags = []
|
let flags: string[] = []
|
||||||
|
const envFlags = buildFeatureFlags()
|
||||||
|
if (envFlags) {
|
||||||
|
const globalFlags = envFlags["*"]
|
||||||
|
const tenantFlags = envFlags[tenantId] || []
|
||||||
|
|
||||||
if (TENANT_FEATURE_FLAGS) {
|
// Explicitly exclude tenants from global features if required.
|
||||||
const globalFlags = TENANT_FEATURE_FLAGS["*"]
|
// Prefix the tenant flag with '!'
|
||||||
const tenantFlags = TENANT_FEATURE_FLAGS[tenantId]
|
const tenantOverrides = tenantFlags.reduce(
|
||||||
|
(acc: string[], flag: string) => {
|
||||||
|
if (flag.startsWith("!")) {
|
||||||
|
let stripped = flag.substring(1)
|
||||||
|
acc.push(stripped)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
if (globalFlags) {
|
if (globalFlags) {
|
||||||
flags.push(...globalFlags)
|
flags.push(...globalFlags)
|
||||||
}
|
}
|
||||||
if (tenantFlags) {
|
if (tenantFlags.length) {
|
||||||
flags.push(...tenantFlags)
|
flags.push(...tenantFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Purge any tenant specific overrides
|
||||||
|
flags = flags.filter(flag => {
|
||||||
|
return tenantOverrides.indexOf(flag) == -1 && !flag.startsWith("!")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return flags
|
return flags
|
||||||
|
@ -57,4 +73,5 @@ export enum TenantFeatureFlag {
|
||||||
LICENSING = "LICENSING",
|
LICENSING = "LICENSING",
|
||||||
GOOGLE_SHEETS = "GOOGLE_SHEETS",
|
GOOGLE_SHEETS = "GOOGLE_SHEETS",
|
||||||
USER_GROUPS = "USER_GROUPS",
|
USER_GROUPS = "USER_GROUPS",
|
||||||
|
ONBOARDING_TOUR = "ONBOARDING_TOUR",
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import {
|
||||||
|
TenantFeatureFlag,
|
||||||
|
buildFeatureFlags,
|
||||||
|
getTenantFeatureFlags,
|
||||||
|
} from "../"
|
||||||
|
import env from "../../environment"
|
||||||
|
|
||||||
|
const { ONBOARDING_TOUR, LICENSING, USER_GROUPS } = TenantFeatureFlag
|
||||||
|
|
||||||
|
describe("featureFlags", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
env._set("TENANT_FEATURE_FLAGS", "")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should return no flags when the TENANT_FEATURE_FLAG is empty", async () => {
|
||||||
|
let features = buildFeatureFlags()
|
||||||
|
expect(features).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should generate a map of global and named tenant feature flags from the env value", async () => {
|
||||||
|
env._set(
|
||||||
|
"TENANT_FEATURE_FLAGS",
|
||||||
|
`*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR},tenant2:${USER_GROUPS},tenant1:${LICENSING}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const parsedFlags: Record<string, string[]> = {
|
||||||
|
"*": [ONBOARDING_TOUR],
|
||||||
|
tenant1: [`!${ONBOARDING_TOUR}`, LICENSING],
|
||||||
|
tenant2: [USER_GROUPS],
|
||||||
|
}
|
||||||
|
|
||||||
|
let features = buildFeatureFlags()
|
||||||
|
|
||||||
|
expect(features).toBeDefined()
|
||||||
|
expect(features).toEqual(parsedFlags)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should add feature flag flag only to explicitly configured tenant", async () => {
|
||||||
|
env._set(
|
||||||
|
"TENANT_FEATURE_FLAGS",
|
||||||
|
`*:${LICENSING},*:${USER_GROUPS},tenant1:${ONBOARDING_TOUR}`
|
||||||
|
)
|
||||||
|
|
||||||
|
let tenant1Flags = getTenantFeatureFlags("tenant1")
|
||||||
|
let tenant2Flags = getTenantFeatureFlags("tenant2")
|
||||||
|
|
||||||
|
expect(tenant1Flags).toBeDefined()
|
||||||
|
expect(tenant1Flags).toEqual([LICENSING, USER_GROUPS, ONBOARDING_TOUR])
|
||||||
|
|
||||||
|
expect(tenant2Flags).toBeDefined()
|
||||||
|
expect(tenant2Flags).toEqual([LICENSING, USER_GROUPS])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should exclude tenant1 from global feature flag", async () => {
|
||||||
|
env._set(
|
||||||
|
"TENANT_FEATURE_FLAGS",
|
||||||
|
`*:${LICENSING},*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR}`
|
||||||
|
)
|
||||||
|
|
||||||
|
let tenant1Flags = getTenantFeatureFlags("tenant1")
|
||||||
|
let tenant2Flags = getTenantFeatureFlags("tenant2")
|
||||||
|
|
||||||
|
expect(tenant1Flags).toBeDefined()
|
||||||
|
expect(tenant1Flags).toEqual([LICENSING])
|
||||||
|
|
||||||
|
expect(tenant2Flags).toBeDefined()
|
||||||
|
expect(tenant2Flags).toEqual([LICENSING, ONBOARDING_TOUR])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should explicitly add flags to configured tenants only", async () => {
|
||||||
|
env._set(
|
||||||
|
"TENANT_FEATURE_FLAGS",
|
||||||
|
`tenant1:${ONBOARDING_TOUR},tenant1:${LICENSING},tenant2:${LICENSING}`
|
||||||
|
)
|
||||||
|
|
||||||
|
let tenant1Flags = getTenantFeatureFlags("tenant1")
|
||||||
|
let tenant2Flags = getTenantFeatureFlags("tenant2")
|
||||||
|
|
||||||
|
expect(tenant1Flags).toBeDefined()
|
||||||
|
expect(tenant1Flags).toEqual([ONBOARDING_TOUR, LICENSING])
|
||||||
|
|
||||||
|
expect(tenant2Flags).toBeDefined()
|
||||||
|
expect(tenant2Flags).toEqual([LICENSING])
|
||||||
|
})
|
|
@ -2,7 +2,7 @@ import { newid } from "./utils"
|
||||||
import * as events from "./events"
|
import * as events from "./events"
|
||||||
import { StaticDatabases } from "./db"
|
import { StaticDatabases } from "./db"
|
||||||
import { doWithDB } from "./db"
|
import { doWithDB } from "./db"
|
||||||
import { Installation, IdentityType } from "@budibase/types"
|
import { Installation, IdentityType, Database } from "@budibase/types"
|
||||||
import * as context from "./context"
|
import * as context from "./context"
|
||||||
import semver from "semver"
|
import semver from "semver"
|
||||||
import { bustCache, withCache, TTL, CacheKey } from "./cache/generic"
|
import { bustCache, withCache, TTL, CacheKey } from "./cache/generic"
|
||||||
|
@ -14,6 +14,24 @@ export const getInstall = async (): Promise<Installation> => {
|
||||||
useTenancy: false,
|
useTenancy: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async function createInstallDoc(platformDb: Database) {
|
||||||
|
const install: Installation = {
|
||||||
|
_id: StaticDatabases.PLATFORM_INFO.docs.install,
|
||||||
|
installId: newid(),
|
||||||
|
version: pkg.version,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const resp = await platformDb.put(install)
|
||||||
|
install._rev = resp.rev
|
||||||
|
return install
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.status === 409) {
|
||||||
|
return getInstallFromDB()
|
||||||
|
} else {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getInstallFromDB = async (): Promise<Installation> => {
|
const getInstallFromDB = async (): Promise<Installation> => {
|
||||||
return doWithDB(
|
return doWithDB(
|
||||||
|
@ -26,13 +44,7 @@ const getInstallFromDB = async (): Promise<Installation> => {
|
||||||
)
|
)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.status === 404) {
|
if (e.status === 404) {
|
||||||
install = {
|
install = await createInstallDoc(platformDb)
|
||||||
_id: StaticDatabases.PLATFORM_INFO.docs.install,
|
|
||||||
installId: newid(),
|
|
||||||
version: pkg.version,
|
|
||||||
}
|
|
||||||
const resp = await platformDb.put(install)
|
|
||||||
install._rev = resp.rev
|
|
||||||
} else {
|
} else {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,9 @@ const print = (fn: any, data: any[]) => {
|
||||||
message = message + ` [identityId=${identityId}]`
|
message = message + ` [identityId=${identityId}]`
|
||||||
}
|
}
|
||||||
|
|
||||||
fn(message, data)
|
if (!process.env.CI) {
|
||||||
|
fn(message, data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logging = (ctx: any, next: any) => {
|
const logging = (ctx: any, next: any) => {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`migrations should match snapshot 1`] = `
|
exports[`migrations should match snapshot 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"_id": "migrations",
|
"_id": "migrations",
|
||||||
"_rev": "1-a32b0b708e59eeb006ed5e063cfeb36a",
|
"_rev": "1-2f64479842a0513aa8b97f356b0b9127",
|
||||||
"createdAt": "2020-01-01T00:00:00.000Z",
|
"createdAt": "2020-01-01T00:00:00.000Z",
|
||||||
"test": 1577836800000,
|
"test": 1577836800000,
|
||||||
"updatedAt": "2020-01-01T00:00:00.000Z",
|
"updatedAt": "2020-01-01T00:00:00.000Z",
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
require("../../../tests")
|
require("../../../tests")
|
||||||
const { runMigrations, getMigrationsDoc } = require("../index")
|
const { runMigrations, getMigrationsDoc } = require("../index")
|
||||||
const { getDB } = require("../../db")
|
const { getGlobalDBName, getDB } = require("../../db")
|
||||||
const {
|
|
||||||
StaticDatabases,
|
const { structures, testEnv } = require("../../../tests")
|
||||||
} = require("../../constants")
|
testEnv.multiTenant()
|
||||||
|
|
||||||
let db
|
let db
|
||||||
|
|
||||||
|
@ -17,8 +17,11 @@ describe("migrations", () => {
|
||||||
fn: migrationFunction
|
fn: migrationFunction
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
let tenantId
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
db = getDB(StaticDatabases.GLOBAL.name)
|
tenantId = structures.tenant.id()
|
||||||
|
db = getDB(getGlobalDBName(tenantId))
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -27,7 +30,7 @@ describe("migrations", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const migrate = () => {
|
const migrate = () => {
|
||||||
return runMigrations(MIGRATIONS)
|
return runMigrations(MIGRATIONS, { tenantIds: [tenantId]})
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should run a new migration", async () => {
|
it("should run a new migration", async () => {
|
||||||
|
|
|
@ -361,8 +361,8 @@ export const deleteFolder = async (
|
||||||
Prefix: folder,
|
Prefix: folder,
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: any = await client.listObjects(listParams).promise()
|
const existingObjectsResponse = await client.listObjects(listParams).promise()
|
||||||
if (response.Contents.length === 0) {
|
if (existingObjectsResponse.Contents?.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const deleteParams: any = {
|
const deleteParams: any = {
|
||||||
|
@ -372,13 +372,13 @@ export const deleteFolder = async (
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Contents.forEach((content: any) => {
|
existingObjectsResponse.Contents?.forEach((content: any) => {
|
||||||
deleteParams.Delete.Objects.push({ Key: content.Key })
|
deleteParams.Delete.Objects.push({ Key: content.Key })
|
||||||
})
|
})
|
||||||
|
|
||||||
response = await client.deleteObjects(deleteParams).promise()
|
const deleteResponse = await client.deleteObjects(deleteParams).promise()
|
||||||
// can only empty 1000 items at once
|
// can only empty 1000 items at once
|
||||||
if (response.Deleted.length === 1000) {
|
if (deleteResponse.Deleted?.length === 1000) {
|
||||||
return deleteFolder(bucketName, folder)
|
return deleteFolder(bucketName, folder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,14 @@ import { structures } from "../../../tests"
|
||||||
import * as utils from "../../utils"
|
import * as utils from "../../utils"
|
||||||
import * as events from "../../events"
|
import * as events from "../../events"
|
||||||
import * as db from "../../db"
|
import * as db from "../../db"
|
||||||
import { DEFAULT_TENANT_ID, Header } from "../../constants"
|
import { Header } from "../../constants"
|
||||||
import { doInTenant } from "../../context"
|
import { doInTenant } from "../../context"
|
||||||
|
import { newid } from "../../utils"
|
||||||
|
|
||||||
describe("utils", () => {
|
describe("utils", () => {
|
||||||
describe("platformLogout", () => {
|
describe("platformLogout", () => {
|
||||||
it("should call platform logout", async () => {
|
it("should call platform logout", async () => {
|
||||||
await doInTenant(DEFAULT_TENANT_ID, async () => {
|
await doInTenant(structures.tenant.id(), async () => {
|
||||||
const ctx = structures.koa.newContext()
|
const ctx = structures.koa.newContext()
|
||||||
await utils.platformLogout({ ctx, userId: "test" })
|
await utils.platformLogout({ ctx, userId: "test" })
|
||||||
expect(events.auth.logout).toBeCalledTimes(1)
|
expect(events.auth.logout).toBeCalledTimes(1)
|
||||||
|
@ -54,7 +55,7 @@ describe("utils", () => {
|
||||||
const app = structures.apps.app(expected)
|
const app = structures.apps.app(expected)
|
||||||
|
|
||||||
// set custom url
|
// set custom url
|
||||||
const appUrl = "custom-url"
|
const appUrl = newid()
|
||||||
app.url = `/${appUrl}`
|
app.url = `/${appUrl}`
|
||||||
ctx.path = `/app/${appUrl}`
|
ctx.path = `/app/${appUrl}`
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import env from "../src/environment"
|
||||||
|
import { mocks } from "./utilities"
|
||||||
|
|
||||||
|
// must explicitly enable fetch mock
|
||||||
|
mocks.fetch.enable()
|
||||||
|
|
||||||
|
// mock all dates to 2020-01-01T00:00:00.000Z
|
||||||
|
// use tk.reset() to use real dates in individual tests
|
||||||
|
import tk from "timekeeper"
|
||||||
|
tk.freeze(mocks.date.MOCK_DATE)
|
||||||
|
|
||||||
|
env._set("SELF_HOSTED", "1")
|
||||||
|
env._set("NODE_ENV", "jest")
|
||||||
|
|
||||||
|
if (!process.env.DEBUG) {
|
||||||
|
global.console.log = jest.fn() // console.log are ignored in tests
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!process.env.CI) {
|
||||||
|
// set a longer timeout in dev for debugging
|
||||||
|
// 100 seconds
|
||||||
|
jest.setTimeout(100000)
|
||||||
|
}
|
|
@ -1,28 +1,4 @@
|
||||||
import env from "../src/environment"
|
import env from "../src/environment"
|
||||||
import { mocks } from "./utilities"
|
import { testContainerUtils } from "./utilities"
|
||||||
|
|
||||||
// must explicitly enable fetch mock
|
testContainerUtils.setupEnv(env)
|
||||||
mocks.fetch.enable()
|
|
||||||
|
|
||||||
// mock all dates to 2020-01-01T00:00:00.000Z
|
|
||||||
// use tk.reset() to use real dates in individual tests
|
|
||||||
import tk from "timekeeper"
|
|
||||||
tk.freeze(mocks.date.MOCK_DATE)
|
|
||||||
|
|
||||||
env._set("SELF_HOSTED", "1")
|
|
||||||
env._set("NODE_ENV", "jest")
|
|
||||||
env._set("JWT_SECRET", "test-jwtsecret")
|
|
||||||
env._set("LOG_LEVEL", "silent")
|
|
||||||
env._set("MINIO_URL", "http://localhost")
|
|
||||||
env._set("MINIO_ACCESS_KEY", "test")
|
|
||||||
env._set("MINIO_SECRET_KEY", "test")
|
|
||||||
|
|
||||||
if (!process.env.DEBUG) {
|
|
||||||
global.console.log = jest.fn() // console.log are ignored in tests
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!process.env.CI) {
|
|
||||||
// set a longer timeout in dev for debugging
|
|
||||||
// 100 seconds
|
|
||||||
jest.setTimeout(100000)
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ export * as mocks from "./mocks"
|
||||||
export * as structures from "./structures"
|
export * as structures from "./structures"
|
||||||
export { generator } from "./structures"
|
export { generator } from "./structures"
|
||||||
export * as testEnv from "./testEnv"
|
export * as testEnv from "./testEnv"
|
||||||
|
export * as testContainerUtils from "./testContainerUtils"
|
||||||
|
|
||||||
import * as dbConfig from "./db"
|
import * as dbConfig from "./db"
|
||||||
dbConfig.init()
|
dbConfig.init()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { newid } from "../../../src/newid"
|
||||||
|
|
||||||
|
export function id() {
|
||||||
|
return `db_${newid()}`
|
||||||
|
}
|
|
@ -8,3 +8,5 @@ export * as apps from "./apps"
|
||||||
export * as koa from "./koa"
|
export * as koa from "./koa"
|
||||||
export * as licenses from "./licenses"
|
export * as licenses from "./licenses"
|
||||||
export * as plugins from "./plugins"
|
export * as plugins from "./plugins"
|
||||||
|
export * as tenant from "./tenants"
|
||||||
|
export * as db from "./db"
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { newid } from "../../../src/newid"
|
||||||
|
|
||||||
|
export function id() {
|
||||||
|
return `tenant-${newid()}`
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
function getTestContainerSettings(
|
||||||
|
serverName: string,
|
||||||
|
key: string
|
||||||
|
): string | null {
|
||||||
|
const entry = Object.entries(global).find(
|
||||||
|
([k]) =>
|
||||||
|
k.includes(`_${serverName.toUpperCase()}`) &&
|
||||||
|
k.includes(`_${key.toUpperCase()}__`)
|
||||||
|
)
|
||||||
|
if (!entry) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return entry[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContainerInfo(containerName: string, port: number) {
|
||||||
|
const assignedPort = getTestContainerSettings(
|
||||||
|
containerName.toUpperCase(),
|
||||||
|
`PORT_${port}`
|
||||||
|
)
|
||||||
|
const host = getTestContainerSettings(containerName.toUpperCase(), "IP")
|
||||||
|
return {
|
||||||
|
port: assignedPort,
|
||||||
|
host,
|
||||||
|
url: host && assignedPort && `http://${host}:${assignedPort}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCouchConfig() {
|
||||||
|
return getContainerInfo("couchdb-service", 5984)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMinioConfig() {
|
||||||
|
return getContainerInfo("minio-service", 9000)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupEnv(...envs: any[]) {
|
||||||
|
const configs = [
|
||||||
|
{ key: "COUCH_DB_PORT", value: getCouchConfig().port },
|
||||||
|
{ key: "COUCH_DB_URL", value: getCouchConfig().url },
|
||||||
|
{ key: "MINIO_PORT", value: getMinioConfig().port },
|
||||||
|
{ key: "MINIO_URL", value: getMinioConfig().url },
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const config of configs.filter(x => !!x.value)) {
|
||||||
|
for (const env of envs) {
|
||||||
|
env._set(config.key, config.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "2.2.12-alpha.48",
|
"version": "2.3.2-alpha.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
||||||
"@budibase/string-templates": "2.2.12-alpha.48",
|
"@budibase/string-templates": "2.3.2-alpha.2",
|
||||||
"@spectrum-css/accordion": "3.0.24",
|
"@spectrum-css/accordion": "3.0.24",
|
||||||
"@spectrum-css/actionbutton": "1.0.1",
|
"@spectrum-css/actionbutton": "1.0.1",
|
||||||
"@spectrum-css/actiongroup": "1.0.1",
|
"@spectrum-css/actiongroup": "1.0.1",
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
export let longPressable = false
|
export let longPressable = false
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
export let dataCy = null
|
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let active = false
|
export let active = false
|
||||||
export let fullWidth = false
|
export let fullWidth = false
|
||||||
|
@ -37,7 +36,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
data-cy={dataCy}
|
|
||||||
use:longPress
|
use:longPress
|
||||||
class:spectrum-ActionButton--quiet={quiet}
|
class:spectrum-ActionButton--quiet={quiet}
|
||||||
class:spectrum-ActionButton--emphasized={emphasized}
|
class:spectrum-ActionButton--emphasized={emphasized}
|
||||||
|
@ -86,9 +84,9 @@
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
transition: color ease-out 130ms;
|
transition: color ease-out 130ms;
|
||||||
}
|
}
|
||||||
.is-selected:not(.spectrum-ActionButton--emphasized) {
|
.is-selected:not(.spectrum-ActionButton--emphasized):not(.spectrum-ActionButton--quiet) {
|
||||||
background: var(--spectrum-global-color-gray-300);
|
background: var(--spectrum-global-color-gray-300);
|
||||||
border-color: var(--spectrum-global-color-gray-700);
|
border-color: var(--spectrum-global-color-gray-500);
|
||||||
}
|
}
|
||||||
.noPadding {
|
.noPadding {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let align = "left"
|
export let align = "left"
|
||||||
export let portalTarget
|
export let portalTarget
|
||||||
export let dataCy
|
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let dropdown
|
let dropdown
|
||||||
|
@ -37,7 +36,7 @@
|
||||||
<div use:getAnchor on:click={openMenu}>
|
<div use:getAnchor on:click={openMenu}>
|
||||||
<slot name="control" />
|
<slot name="control" />
|
||||||
</div>
|
</div>
|
||||||
<Popover bind:this={dropdown} {anchor} {align} {portalTarget} {dataCy}>
|
<Popover bind:this={dropdown} {anchor} {align} {portalTarget}>
|
||||||
<Menu>
|
<Menu>
|
||||||
<slot />
|
<slot />
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
|
@ -1,8 +1,21 @@
|
||||||
export default function positionDropdown(
|
export default function positionDropdown(element, opts) {
|
||||||
element,
|
let resizeObserver
|
||||||
{ anchor, align, maxWidth, useAnchorWidth }
|
let latestOpts = opts
|
||||||
) {
|
|
||||||
const update = () => {
|
// We need a static reference to this function so that we can properly
|
||||||
|
// clean up the scroll listener.
|
||||||
|
const scrollUpdate = () => {
|
||||||
|
updatePosition(latestOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the position of the dropdown
|
||||||
|
const updatePosition = opts => {
|
||||||
|
const { anchor, align, maxWidth, useAnchorWidth, offset = 5 } = opts
|
||||||
|
if (!anchor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute bounds
|
||||||
const anchorBounds = anchor.getBoundingClientRect()
|
const anchorBounds = anchor.getBoundingClientRect()
|
||||||
const elementBounds = element.getBoundingClientRect()
|
const elementBounds = element.getBoundingClientRect()
|
||||||
let styles = {
|
let styles = {
|
||||||
|
@ -14,10 +27,12 @@ export default function positionDropdown(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine vertical styles
|
// Determine vertical styles
|
||||||
if (window.innerHeight - anchorBounds.bottom < 100) {
|
if (align === "right-outside") {
|
||||||
styles.top = anchorBounds.top - elementBounds.height - 5
|
styles.top = anchorBounds.top
|
||||||
|
} else if (window.innerHeight - anchorBounds.bottom < 100) {
|
||||||
|
styles.top = anchorBounds.top - elementBounds.height - offset
|
||||||
} else {
|
} else {
|
||||||
styles.top = anchorBounds.bottom + 5
|
styles.top = anchorBounds.bottom + offset
|
||||||
styles.maxHeight = window.innerHeight - anchorBounds.bottom - 20
|
styles.maxHeight = window.innerHeight - anchorBounds.bottom - 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,8 +45,8 @@ export default function positionDropdown(
|
||||||
}
|
}
|
||||||
if (align === "right") {
|
if (align === "right") {
|
||||||
styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width
|
styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width
|
||||||
} else if (align === "right-side") {
|
} else if (align === "right-outside") {
|
||||||
styles.left = anchorBounds.left + anchorBounds.width
|
styles.left = anchorBounds.right + offset
|
||||||
} else {
|
} else {
|
||||||
styles.left = anchorBounds.left
|
styles.left = anchorBounds.left
|
||||||
}
|
}
|
||||||
|
@ -46,23 +61,47 @@ export default function positionDropdown(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The actual svelte action callback which creates observers on the relevant
|
||||||
|
// DOM elements
|
||||||
|
const update = newOpts => {
|
||||||
|
latestOpts = newOpts
|
||||||
|
|
||||||
|
// Cleanup old state
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do nothing if no anchor
|
||||||
|
const { anchor } = newOpts
|
||||||
|
if (!anchor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe both anchor and element and resize the popover as appropriate
|
||||||
|
resizeObserver = new ResizeObserver(() => updatePosition(newOpts))
|
||||||
|
resizeObserver.observe(anchor)
|
||||||
|
resizeObserver.observe(element)
|
||||||
|
resizeObserver.observe(document.body)
|
||||||
|
}
|
||||||
|
|
||||||
// Apply initial styles which don't need to change
|
// Apply initial styles which don't need to change
|
||||||
element.style.position = "absolute"
|
element.style.position = "absolute"
|
||||||
element.style.zIndex = "9999"
|
element.style.zIndex = "9999"
|
||||||
|
|
||||||
// Observe both anchor and element and resize the popover as appropriate
|
// Set up a scroll listener
|
||||||
const resizeObserver = new ResizeObserver(entries => {
|
document.addEventListener("scroll", scrollUpdate, true)
|
||||||
entries.forEach(update)
|
|
||||||
})
|
|
||||||
resizeObserver.observe(anchor)
|
|
||||||
resizeObserver.observe(element)
|
|
||||||
|
|
||||||
document.addEventListener("scroll", update, true)
|
// Perform initial update
|
||||||
|
update(opts)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
update,
|
||||||
destroy() {
|
destroy() {
|
||||||
resizeObserver.disconnect()
|
// Cleanup
|
||||||
document.removeEventListener("scroll", update, true)
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
}
|
||||||
|
document.removeEventListener("scroll", scrollUpdate, true)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,14 @@
|
||||||
export let icon = undefined
|
export let icon = undefined
|
||||||
export let active = false
|
export let active = false
|
||||||
export let tooltip = undefined
|
export let tooltip = undefined
|
||||||
export let dataCy
|
|
||||||
export let newStyles = true
|
export let newStyles = true
|
||||||
|
export let id
|
||||||
|
|
||||||
let showTooltip = false
|
let showTooltip = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
{id}
|
||||||
class:spectrum-Button--cta={cta}
|
class:spectrum-Button--cta={cta}
|
||||||
class:spectrum-Button--primary={primary}
|
class:spectrum-Button--primary={primary}
|
||||||
class:spectrum-Button--secondary={secondary}
|
class:spectrum-Button--secondary={secondary}
|
||||||
|
@ -31,7 +32,6 @@
|
||||||
class:disabled
|
class:disabled
|
||||||
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
|
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
|
||||||
{disabled}
|
{disabled}
|
||||||
data-cy={dataCy}
|
|
||||||
on:click|preventDefault
|
on:click|preventDefault
|
||||||
on:mouseover={() => (showTooltip = true)}
|
on:mouseover={() => (showTooltip = true)}
|
||||||
on:focus={() => (showTooltip = true)}
|
on:focus={() => (showTooltip = true)}
|
||||||
|
|
|
@ -76,13 +76,6 @@
|
||||||
}
|
}
|
||||||
// If time only set date component to 2000-01-01
|
// If time only set date component to 2000-01-01
|
||||||
if (timeOnly) {
|
if (timeOnly) {
|
||||||
// Classic flackpickr causing issues.
|
|
||||||
// When selecting a value for the first time for a "time only" field,
|
|
||||||
// the time is always offset by 1 hour for some reason (regardless of time
|
|
||||||
// zone) so we need to correct it.
|
|
||||||
if (!value && newValue) {
|
|
||||||
newValue = new Date(dates[0].getTime() + 60 * 60 * 1000).toISOString()
|
|
||||||
}
|
|
||||||
newValue = `2000-01-01T${newValue.split("T")[1]}`
|
newValue = `2000-01-01T${newValue.split("T")[1]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +106,7 @@
|
||||||
|
|
||||||
const clearDateOnBackspace = event => {
|
const clearDateOnBackspace = event => {
|
||||||
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
||||||
dispatch("change", null)
|
dispatch("change", "")
|
||||||
flatpickr.close()
|
flatpickr.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let dataCy
|
|
||||||
export let align
|
export let align
|
||||||
export let autofocus = false
|
export let autofocus = false
|
||||||
export let variables
|
export let variables
|
||||||
|
@ -26,7 +25,7 @@
|
||||||
let open = false
|
let open = false
|
||||||
|
|
||||||
//eslint-disable-next-line
|
//eslint-disable-next-line
|
||||||
const STRIP_NAME_REGEX = /(?<=\.)(.*?)(?=\ })/g
|
const STRIP_NAME_REGEX = /(\w+?)(?=\ })/g
|
||||||
|
|
||||||
// Strips the name out of the value which is {{ env.Variable }} resulting in an array like ["Variable"]
|
// Strips the name out of the value which is {{ env.Variable }} resulting in an array like ["Variable"]
|
||||||
$: hbsValue = String(value)?.match(STRIP_NAME_REGEX) || []
|
$: hbsValue = String(value)?.match(STRIP_NAME_REGEX) || []
|
||||||
|
@ -123,7 +122,6 @@
|
||||||
disabled={hbsValue.length || disabled}
|
disabled={hbsValue.length || disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{id}
|
{id}
|
||||||
data-cy={dataCy}
|
|
||||||
value={hbsValue.length ? `{{ ${hbsValue[0]} }}` : value}
|
value={hbsValue.length ? `{{ ${hbsValue[0]} }}` : value}
|
||||||
placeholder={placeholder || ""}
|
placeholder={placeholder || ""}
|
||||||
on:click
|
on:click
|
||||||
|
@ -247,10 +245,6 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-variables-height {
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-variables-text {
|
.no-variables-text {
|
||||||
padding: var(--spacing-m);
|
padding: var(--spacing-m);
|
||||||
color: var(--spectrum-global-color-gray-600);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
|
|
@ -11,14 +11,31 @@
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
export let getOptionTitle = option => option
|
export let getOptionTitle = option => option
|
||||||
|
export let sort = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => dispatch("change", e.target.value)
|
const onChange = e => dispatch("change", e.target.value)
|
||||||
|
|
||||||
|
const getSortedOptions = (options, getLabel, sort) => {
|
||||||
|
if (!options?.length || !Array.isArray(options)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (!sort) {
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
return [...options].sort((a, b) => {
|
||||||
|
const labelA = getLabel(a)
|
||||||
|
const labelB = getLabel(b)
|
||||||
|
return labelA > labelB ? 1 : -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$: parsedOptions = getSortedOptions(options, getOptionLabel, sort)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}>
|
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}>
|
||||||
{#if options && Array.isArray(options)}
|
{#if parsedOptions && Array.isArray(parsedOptions)}
|
||||||
{#each options as option}
|
{#each parsedOptions as option}
|
||||||
<div
|
<div
|
||||||
title={getOptionTitle(option)}
|
title={getOptionTitle(option)}
|
||||||
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
|
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let text = null
|
export let text = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let dataCy = null
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = event => {
|
const onChange = event => {
|
||||||
|
@ -16,7 +15,6 @@
|
||||||
|
|
||||||
<div class="spectrum-Switch spectrum-Switch--emphasized">
|
<div class="spectrum-Switch spectrum-Switch--emphasized">
|
||||||
<input
|
<input
|
||||||
data-cy={dataCy}
|
|
||||||
checked={value}
|
checked={value}
|
||||||
{disabled}
|
{disabled}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let dataCy
|
|
||||||
export let align
|
export let align
|
||||||
export let autofocus = false
|
export let autofocus = false
|
||||||
|
|
||||||
|
@ -89,7 +88,6 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{id}
|
{id}
|
||||||
data-cy={dataCy}
|
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
placeholder={placeholder || ""}
|
placeholder={placeholder || ""}
|
||||||
on:click
|
on:click
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let dataCy
|
|
||||||
export let autofocus
|
export let autofocus
|
||||||
export let variables
|
export let variables
|
||||||
export let showModal
|
export let showModal
|
||||||
|
@ -28,7 +27,6 @@
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {label} {labelPosition} {error}>
|
||||||
<EnvDropdown
|
<EnvDropdown
|
||||||
{dataCy}
|
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let dataCy
|
|
||||||
export let autofocus
|
export let autofocus
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -25,7 +24,6 @@
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {label} {labelPosition} {error}>
|
||||||
<TextField
|
<TextField
|
||||||
{dataCy}
|
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let dataCy
|
|
||||||
export let autofocus
|
export let autofocus
|
||||||
export let options = []
|
export let options = []
|
||||||
|
|
||||||
|
@ -32,7 +31,6 @@
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {label} {labelPosition} {error}>
|
||||||
<InputDropdown
|
<InputDropdown
|
||||||
{dataCy}
|
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
export let getSecondaryOptionColour = () => {}
|
export let getSecondaryOptionColour = () => {}
|
||||||
export let getSecondaryOptionIcon = () => {}
|
export let getSecondaryOptionIcon = () => {}
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let dataCy
|
|
||||||
export let autofocus
|
export let autofocus
|
||||||
export let primaryOptions = []
|
export let primaryOptions = []
|
||||||
export let secondaryOptions = []
|
export let secondaryOptions = []
|
||||||
|
@ -98,7 +97,6 @@
|
||||||
<PickerDropdown
|
<PickerDropdown
|
||||||
{searchTerm}
|
{searchTerm}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{dataCy}
|
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
export let text = null
|
export let text = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
export let error = null
|
||||||
export let dataCy = null
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -19,5 +18,5 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {label} {labelPosition} {error}>
|
||||||
<Switch {dataCy} {error} {disabled} {text} {value} on:change={onChange} />
|
<Switch {error} {disabled} {text} {value} on:change={onChange} />
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const actionMenu = getContext("actionMenu")
|
const actionMenu = getContext("actionMenu")
|
||||||
|
|
||||||
export let dataCy
|
|
||||||
export let icon = undefined
|
export let icon = undefined
|
||||||
export let disabled = undefined
|
export let disabled = undefined
|
||||||
export let noClose = false
|
export let noClose = false
|
||||||
|
@ -35,7 +34,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
data-cy={dataCy}
|
|
||||||
on:click|preventDefault={disabled ? null : onClick}
|
on:click|preventDefault={disabled ? null : onClick}
|
||||||
class="spectrum-Menu-item"
|
class="spectrum-Menu-item"
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
export let secondaryButtonText = undefined
|
export let secondaryButtonText = undefined
|
||||||
export let secondaryAction = undefined
|
export let secondaryAction = undefined
|
||||||
export let secondaryButtonWarning = false
|
export let secondaryButtonWarning = false
|
||||||
export let dataCy = null
|
|
||||||
const { hide, cancel } = getContext(Context.Modal)
|
const { hide, cancel } = getContext(Context.Modal)
|
||||||
let loading = false
|
let loading = false
|
||||||
$: confirmDisabled = disabled || loading
|
$: confirmDisabled = disabled || loading
|
||||||
|
@ -63,7 +63,6 @@
|
||||||
role="dialog"
|
role="dialog"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
data-cy={dataCy}
|
|
||||||
>
|
>
|
||||||
<div class="spectrum-Dialog-grid">
|
<div class="spectrum-Dialog-grid">
|
||||||
{#if title || $$slots.header}
|
{#if title || $$slots.header}
|
||||||
|
|
|
@ -13,19 +13,12 @@
|
||||||
export let anchor
|
export let anchor
|
||||||
export let align = "right"
|
export let align = "right"
|
||||||
export let portalTarget
|
export let portalTarget
|
||||||
export let dataCy
|
|
||||||
export let maxWidth
|
export let maxWidth
|
||||||
export let direction = "bottom"
|
|
||||||
export let showTip = false
|
|
||||||
export let open = false
|
export let open = false
|
||||||
export let useAnchorWidth = false
|
export let useAnchorWidth = false
|
||||||
|
export let dismissible = true
|
||||||
|
export let offset = 5
|
||||||
|
|
||||||
let tipSvg =
|
|
||||||
'<svg xmlns="http://www.w3.org/svg/2000" width="23" height="12" class="spectrum-Popover-tip" > <path class="spectrum-Popover-tip-triangle" d="M 0.7071067811865476 0 L 11.414213562373096 10.707106781186548 L 22.121320343559645 0" /> </svg>'
|
|
||||||
|
|
||||||
$: tooltipClasses = showTip
|
|
||||||
? `spectrum-Popover--withTip spectrum-Popover--${direction}`
|
|
||||||
: ""
|
|
||||||
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
||||||
|
|
||||||
export const show = () => {
|
export const show = () => {
|
||||||
|
@ -67,21 +60,22 @@
|
||||||
<Portal {target}>
|
<Portal {target}>
|
||||||
<div
|
<div
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
use:positionDropdown={{ anchor, align, maxWidth, useAnchorWidth }}
|
use:positionDropdown={{
|
||||||
|
anchor,
|
||||||
|
align,
|
||||||
|
maxWidth,
|
||||||
|
useAnchorWidth,
|
||||||
|
offset,
|
||||||
|
}}
|
||||||
use:clickOutside={{
|
use:clickOutside={{
|
||||||
callback: handleOutsideClick,
|
callback: dismissible ? handleOutsideClick : () => {},
|
||||||
anchor,
|
anchor,
|
||||||
}}
|
}}
|
||||||
on:keydown={handleEscape}
|
on:keydown={handleEscape}
|
||||||
class={"spectrum-Popover is-open " + (tooltipClasses || "")}
|
class="spectrum-Popover is-open"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
data-cy={dataCy}
|
|
||||||
transition:fly|local={{ y: -20, duration: 200 }}
|
transition:fly|local={{ y: -20, duration: 200 }}
|
||||||
>
|
>
|
||||||
{#if showTip}
|
|
||||||
{@html tipSvg}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
@ -93,13 +87,4 @@
|
||||||
border-color: var(--spectrum-global-color-gray-300);
|
border-color: var(--spectrum-global-color-gray-300);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
.spectrum-Popover.is-open.spectrum-Popover--withTip {
|
|
||||||
margin-top: var(--spacing-xs);
|
|
||||||
margin-left: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
:global(.spectrum-Popover--bottom .spectrum-Popover-tip),
|
|
||||||
:global(.spectrum-Popover--top .spectrum-Popover-tip) {
|
|
||||||
left: 90%;
|
|
||||||
margin-left: calc(var(--spectrum-global-dimension-size-150) * -1);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
export let selected = false
|
export let selected = false
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let dataCy
|
|
||||||
export let badge = ""
|
export let badge = ""
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -17,7 +16,6 @@
|
||||||
class:is-selected={selected}
|
class:is-selected={selected}
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
on:click
|
on:click
|
||||||
data-cy={dataCy}
|
|
||||||
>
|
>
|
||||||
{#if heading}
|
{#if heading}
|
||||||
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">
|
<h2 class="spectrum-SideNav-heading" id="nav-heading-{heading}">
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import Portal from "svelte-portal"
|
import Portal from "svelte-portal"
|
||||||
export let title
|
export let title
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
|
export let id
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let selected = getContext("tab")
|
let selected = getContext("tab")
|
||||||
|
@ -31,10 +32,7 @@
|
||||||
$: {
|
$: {
|
||||||
if ($selected.title === title && tab_internal) {
|
if ($selected.title === title && tab_internal) {
|
||||||
if ($selected.info?.left !== tab_internal.getBoundingClientRect().left) {
|
if ($selected.info?.left !== tab_internal.getBoundingClientRect().left) {
|
||||||
$selected = {
|
setTabInfo()
|
||||||
...$selected,
|
|
||||||
info: tab_internal.getBoundingClientRect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +48,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
{id}
|
||||||
bind:this={tab_internal}
|
bind:this={tab_internal}
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
class:is-selected={$selected.title === title}
|
class:is-selected={$selected.title === title}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"baseUrl": "http://localhost:4100",
|
|
||||||
"video": true,
|
|
||||||
"projectId": "bmbemn",
|
|
||||||
"reporter": "cypress-multi-reporters",
|
|
||||||
"reporterOptions": {
|
|
||||||
"configFile": "reporterConfig.json"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"PORT": "4100",
|
|
||||||
"WORKER_PORT": "4200",
|
|
||||||
"JWT_SECRET": "test",
|
|
||||||
"HOST_IP": ""
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"budibase": "CB373643-3FC4-4902-9E31-449C0ED066B6"
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Using fixtures to represent data",
|
|
||||||
"email": "hello@cypress.io",
|
|
||||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"id": 8739,
|
|
||||||
"name": "Jane",
|
|
||||||
"email": "jane@example.com"
|
|
||||||
}
|
|
|
@ -1,232 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "Leanne Graham",
|
|
||||||
"username": "Bret",
|
|
||||||
"email": "Sincere@april.biz",
|
|
||||||
"address": {
|
|
||||||
"street": "Kulas Light",
|
|
||||||
"suite": "Apt. 556",
|
|
||||||
"city": "Gwenborough",
|
|
||||||
"zipcode": "92998-3874",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-37.3159",
|
|
||||||
"lng": "81.1496"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "1-770-736-8031 x56442",
|
|
||||||
"website": "hildegard.org",
|
|
||||||
"company": {
|
|
||||||
"name": "Romaguera-Crona",
|
|
||||||
"catchPhrase": "Multi-layered client-server neural-net",
|
|
||||||
"bs": "harness real-time e-markets"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": "Ervin Howell",
|
|
||||||
"username": "Antonette",
|
|
||||||
"email": "Shanna@melissa.tv",
|
|
||||||
"address": {
|
|
||||||
"street": "Victor Plains",
|
|
||||||
"suite": "Suite 879",
|
|
||||||
"city": "Wisokyburgh",
|
|
||||||
"zipcode": "90566-7771",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-43.9509",
|
|
||||||
"lng": "-34.4618"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "010-692-6593 x09125",
|
|
||||||
"website": "anastasia.net",
|
|
||||||
"company": {
|
|
||||||
"name": "Deckow-Crist",
|
|
||||||
"catchPhrase": "Proactive didactic contingency",
|
|
||||||
"bs": "synergize scalable supply-chains"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"name": "Clementine Bauch",
|
|
||||||
"username": "Samantha",
|
|
||||||
"email": "Nathan@yesenia.net",
|
|
||||||
"address": {
|
|
||||||
"street": "Douglas Extension",
|
|
||||||
"suite": "Suite 847",
|
|
||||||
"city": "McKenziehaven",
|
|
||||||
"zipcode": "59590-4157",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-68.6102",
|
|
||||||
"lng": "-47.0653"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "1-463-123-4447",
|
|
||||||
"website": "ramiro.info",
|
|
||||||
"company": {
|
|
||||||
"name": "Romaguera-Jacobson",
|
|
||||||
"catchPhrase": "Face to face bifurcated interface",
|
|
||||||
"bs": "e-enable strategic applications"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"name": "Patricia Lebsack",
|
|
||||||
"username": "Karianne",
|
|
||||||
"email": "Julianne.OConner@kory.org",
|
|
||||||
"address": {
|
|
||||||
"street": "Hoeger Mall",
|
|
||||||
"suite": "Apt. 692",
|
|
||||||
"city": "South Elvis",
|
|
||||||
"zipcode": "53919-4257",
|
|
||||||
"geo": {
|
|
||||||
"lat": "29.4572",
|
|
||||||
"lng": "-164.2990"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "493-170-9623 x156",
|
|
||||||
"website": "kale.biz",
|
|
||||||
"company": {
|
|
||||||
"name": "Robel-Corkery",
|
|
||||||
"catchPhrase": "Multi-tiered zero tolerance productivity",
|
|
||||||
"bs": "transition cutting-edge web services"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"name": "Chelsey Dietrich",
|
|
||||||
"username": "Kamren",
|
|
||||||
"email": "Lucio_Hettinger@annie.ca",
|
|
||||||
"address": {
|
|
||||||
"street": "Skiles Walks",
|
|
||||||
"suite": "Suite 351",
|
|
||||||
"city": "Roscoeview",
|
|
||||||
"zipcode": "33263",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-31.8129",
|
|
||||||
"lng": "62.5342"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "(254)954-1289",
|
|
||||||
"website": "demarco.info",
|
|
||||||
"company": {
|
|
||||||
"name": "Keebler LLC",
|
|
||||||
"catchPhrase": "User-centric fault-tolerant solution",
|
|
||||||
"bs": "revolutionize end-to-end systems"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"name": "Mrs. Dennis Schulist",
|
|
||||||
"username": "Leopoldo_Corkery",
|
|
||||||
"email": "Karley_Dach@jasper.info",
|
|
||||||
"address": {
|
|
||||||
"street": "Norberto Crossing",
|
|
||||||
"suite": "Apt. 950",
|
|
||||||
"city": "South Christy",
|
|
||||||
"zipcode": "23505-1337",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-71.4197",
|
|
||||||
"lng": "71.7478"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "1-477-935-8478 x6430",
|
|
||||||
"website": "ola.org",
|
|
||||||
"company": {
|
|
||||||
"name": "Considine-Lockman",
|
|
||||||
"catchPhrase": "Synchronised bottom-line interface",
|
|
||||||
"bs": "e-enable innovative applications"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"name": "Kurtis Weissnat",
|
|
||||||
"username": "Elwyn.Skiles",
|
|
||||||
"email": "Telly.Hoeger@billy.biz",
|
|
||||||
"address": {
|
|
||||||
"street": "Rex Trail",
|
|
||||||
"suite": "Suite 280",
|
|
||||||
"city": "Howemouth",
|
|
||||||
"zipcode": "58804-1099",
|
|
||||||
"geo": {
|
|
||||||
"lat": "24.8918",
|
|
||||||
"lng": "21.8984"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "210.067.6132",
|
|
||||||
"website": "elvis.io",
|
|
||||||
"company": {
|
|
||||||
"name": "Johns Group",
|
|
||||||
"catchPhrase": "Configurable multimedia task-force",
|
|
||||||
"bs": "generate enterprise e-tailers"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"name": "Nicholas Runolfsdottir V",
|
|
||||||
"username": "Maxime_Nienow",
|
|
||||||
"email": "Sherwood@rosamond.me",
|
|
||||||
"address": {
|
|
||||||
"street": "Ellsworth Summit",
|
|
||||||
"suite": "Suite 729",
|
|
||||||
"city": "Aliyaview",
|
|
||||||
"zipcode": "45169",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-14.3990",
|
|
||||||
"lng": "-120.7677"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "586.493.6943 x140",
|
|
||||||
"website": "jacynthe.com",
|
|
||||||
"company": {
|
|
||||||
"name": "Abernathy Group",
|
|
||||||
"catchPhrase": "Implemented secondary concept",
|
|
||||||
"bs": "e-enable extensible e-tailers"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"name": "Glenna Reichert",
|
|
||||||
"username": "Delphine",
|
|
||||||
"email": "Chaim_McDermott@dana.io",
|
|
||||||
"address": {
|
|
||||||
"street": "Dayna Park",
|
|
||||||
"suite": "Suite 449",
|
|
||||||
"city": "Bartholomebury",
|
|
||||||
"zipcode": "76495-3109",
|
|
||||||
"geo": {
|
|
||||||
"lat": "24.6463",
|
|
||||||
"lng": "-168.8889"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "(775)976-6794 x41206",
|
|
||||||
"website": "conrad.com",
|
|
||||||
"company": {
|
|
||||||
"name": "Yost and Sons",
|
|
||||||
"catchPhrase": "Switchable contextually-based project",
|
|
||||||
"bs": "aggregate real-time technologies"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"name": "Clementina DuBuque",
|
|
||||||
"username": "Moriah.Stanton",
|
|
||||||
"email": "Rey.Padberg@karina.biz",
|
|
||||||
"address": {
|
|
||||||
"street": "Kattie Turnpike",
|
|
||||||
"suite": "Suite 198",
|
|
||||||
"city": "Lebsackbury",
|
|
||||||
"zipcode": "31428-2261",
|
|
||||||
"geo": {
|
|
||||||
"lat": "-38.2386",
|
|
||||||
"lng": "57.2232"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone": "024-648-3804",
|
|
||||||
"website": "ambrose.net",
|
|
||||||
"company": {
|
|
||||||
"name": "Hoeger LLC",
|
|
||||||
"catchPhrase": "Centralized empowering task-force",
|
|
||||||
"bs": "target end-to-end tables"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,45 +0,0 @@
|
||||||
import filterTests from "../support/filterTests"
|
|
||||||
const interact = require('../support/interact')
|
|
||||||
|
|
||||||
filterTests(['all'], () => {
|
|
||||||
xcontext("Add Multi-Option Datatype", () => {
|
|
||||||
before(() => {
|
|
||||||
cy.login()
|
|
||||||
cy.createTestApp()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should create a new table, with data", () => {
|
|
||||||
cy.createTable("Multi Data")
|
|
||||||
cy.addColumn("Multi Data", "Test Data", "Multi-select", "1\n2\n3\n4\n5")
|
|
||||||
cy.addRowMultiValue(["1", "2", "3", "4", "5"])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should add form with multi select picker, containing 5 options", () => {
|
|
||||||
cy.navigateToFrontend()
|
|
||||||
// Add data provider
|
|
||||||
cy.searchAndAddComponent("Data Provider")
|
|
||||||
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
|
|
||||||
cy.get(interact.DROPDOWN).contains("Multi Data").click()
|
|
||||||
// Add Form with schema to match table
|
|
||||||
cy.searchAndAddComponent("Form")
|
|
||||||
cy.get(interact.DATASOURCE_PROP_CONTROL).click()
|
|
||||||
cy.get(interact.DROPDOWN).contains("Multi Data").click()
|
|
||||||
// Add multi-select picker to form
|
|
||||||
cy.searchAndAddComponent("Multi-select Picker").then(componentId => {
|
|
||||||
cy.get(interact.DATASOURCE_FIELD_CONTROL).type("Test Data").type("{enter}")
|
|
||||||
cy.wait(1000)
|
|
||||||
cy.getComponent(componentId).contains("Choose some options").click()
|
|
||||||
// Check picker has 5 items
|
|
||||||
cy.getComponent(componentId).find("li").should("have.length", 5)
|
|
||||||
// Select all items
|
|
||||||
for (let i = 1; i < 6; i++) {
|
|
||||||
cy.getComponent(componentId).find("li").contains(i).click()
|
|
||||||
}
|
|
||||||
// Check items have been selected
|
|
||||||
cy.getComponent(componentId)
|
|
||||||
.find(interact.SPECTRUM_PICKER_LABEL)
|
|
||||||
.contains("(5)")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,43 +0,0 @@
|
||||||
import filterTests from "../support/filterTests"
|
|
||||||
const interact = require('../support/interact')
|
|
||||||
|
|
||||||
filterTests(['all'], () => {
|
|
||||||
xcontext("Add Radio Buttons", () => {
|
|
||||||
before(() => {
|
|
||||||
cy.login()
|
|
||||||
cy.createTestApp()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should add Radio Buttons options picker on form, add data, and confirm", () => {
|
|
||||||
cy.navigateToFrontend()
|
|
||||||
cy.searchAndAddComponent("Form")
|
|
||||||
cy.searchAndAddComponent("Options Picker").then((componentId) => {
|
|
||||||
// Provide field setting
|
|
||||||
cy.get(interact.DATASOURCE_FIELD_CONTROL).type("1")
|
|
||||||
// Open dropdown and select Radio buttons
|
|
||||||
cy.get(interact.OPTION_TYPE_PROP_CONTROL).click().then(() => {
|
|
||||||
cy.get(interact.SPECTRUM_POPOVER).contains('Radio buttons')
|
|
||||||
.click()
|
|
||||||
})
|
|
||||||
const radioButtonsTotal = 3
|
|
||||||
// Add values and confirm total
|
|
||||||
addRadioButtonData(radioButtonsTotal)
|
|
||||||
cy.getComponent(componentId).find('[type="radio"]')
|
|
||||||
.should('have.length', radioButtonsTotal)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const addRadioButtonData = (totalRadioButtons) => {
|
|
||||||
cy.get(interact.OPTION_SOURCE_PROP_CONROL).click().then(() => {
|
|
||||||
cy.get(interact.SPECTRUM_POPOVER).contains('Custom')
|
|
||||||
.click()
|
|
||||||
.wait(1000)
|
|
||||||
})
|
|
||||||
cy.addCustomSourceOptions(totalRadioButtons)
|
|
||||||
}
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
cy.deleteAllApps()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,116 +0,0 @@
|
||||||
import filterTests from "../../support/filterTests"
|
|
||||||
const interact = require('../../support/interact')
|
|
||||||
|
|
||||||
filterTests(["smoke", "all"], () => {
|
|
||||||
xcontext("Account Portals", () => {
|
|
||||||
|
|
||||||
const bbUserEmail = "bbuser@test.com"
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
cy.login()
|
|
||||||
cy.deleteApp("Cypress Tests")
|
|
||||||
cy.createApp("Cypress Tests", false)
|
|
||||||
|
|
||||||
// Create new user
|
|
||||||
cy.wait(500)
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
|
||||||
cy.createUser(bbUserEmail)
|
|
||||||
cy.contains("bbuser").click()
|
|
||||||
cy.wait(500)
|
|
||||||
|
|
||||||
// Reset password
|
|
||||||
cy.get(".title").within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_ICON).click({ force: true })
|
|
||||||
})
|
|
||||||
cy.get(interact.SPECTRUM_MENU).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
|
||||||
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
|
|
||||||
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
|
|
||||||
|
|
||||||
// Login as new user and set password
|
|
||||||
cy.logOut()
|
|
||||||
cy.get('@pwd').then((pwd) => {
|
|
||||||
cy.login(bbUserEmail, pwd)
|
|
||||||
})
|
|
||||||
|
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
|
||||||
}
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
|
|
||||||
//cy.logoutNoAppGrid()
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should verify Standard Portal", () => {
|
|
||||||
// Development access should be disabled (Admin access is already disabled)
|
|
||||||
cy.login()
|
|
||||||
cy.setUserRole("bbuser", "App User")
|
|
||||||
bbUserLogin()
|
|
||||||
|
|
||||||
// Verify Standard Portal
|
|
||||||
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
|
|
||||||
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
|
|
||||||
cy.get(".app").should('not.exist') // No apps -> no roles assigned to user
|
|
||||||
cy.get(interact.CONTAINER).should('contain', bbUserEmail) // Message containing users email
|
|
||||||
|
|
||||||
cy.logoutNoAppGrid()
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should verify Admin Portal", () => {
|
|
||||||
cy.login()
|
|
||||||
// Configure user role
|
|
||||||
cy.setUserRole("bbuser", "Admin")
|
|
||||||
bbUserLogin()
|
|
||||||
|
|
||||||
// Verify available options for Admin portal
|
|
||||||
cy.get(interact.SPECTRUM_SIDENAV)
|
|
||||||
.should('contain', 'Apps')
|
|
||||||
//.and('contain', 'Usage')
|
|
||||||
.and('contain', 'Users')
|
|
||||||
.and('contain', 'Auth')
|
|
||||||
.and('contain', 'Email')
|
|
||||||
.and('contain', 'Organisation')
|
|
||||||
.and('contain', 'Theming')
|
|
||||||
.and('contain', 'Update')
|
|
||||||
//.and('contain', 'Upgrade')
|
|
||||||
|
|
||||||
cy.logOut()
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should verify Development Portal", () => {
|
|
||||||
// Only Development access should be enabled
|
|
||||||
cy.login()
|
|
||||||
cy.setUserRole("bbuser", "Developer")
|
|
||||||
bbUserLogin()
|
|
||||||
|
|
||||||
// Verify available options for Admin portal
|
|
||||||
cy.get(interact.SPECTRUM_SIDENAV)
|
|
||||||
.should('contain', 'Apps')
|
|
||||||
//.and('contain', 'Usage')
|
|
||||||
.and('not.contain', 'Users')
|
|
||||||
.and('not.contain', 'Auth')
|
|
||||||
.and('not.contain', 'Email')
|
|
||||||
.and('not.contain', 'Organisation')
|
|
||||||
.and('contain', 'Theming')
|
|
||||||
.and('not.contain', 'Update')
|
|
||||||
.and('not.contain', 'Upgrade')
|
|
||||||
|
|
||||||
cy.logOut()
|
|
||||||
})
|
|
||||||
|
|
||||||
const bbUserLogin = () => {
|
|
||||||
// Login as bbuser
|
|
||||||
cy.logOut()
|
|
||||||
cy.login(bbUserEmail, "test")
|
|
||||||
}
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
cy.login()
|
|
||||||
// Delete BB user
|
|
||||||
cy.deleteUser(bbUserEmail)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,178 +0,0 @@
|
||||||
import filterTests from "../../support/filterTests"
|
|
||||||
// const interact = require("../support/interact")
|
|
||||||
|
|
||||||
filterTests(["smoke", "all"], () => {
|
|
||||||
xcontext("Auth Configuration", () => {
|
|
||||||
before(() => {
|
|
||||||
cy.login()
|
|
||||||
})
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
|
||||||
cy.location().should(loc => {
|
|
||||||
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get("[data-cy=new-scope-input]").clear()
|
|
||||||
|
|
||||||
cy.get("div.content").scrollTo("bottom")
|
|
||||||
cy.get("[data-cy=oidc-active]").click()
|
|
||||||
|
|
||||||
cy.get("[data-cy=oidc-active]").should('not.be.checked')
|
|
||||||
|
|
||||||
cy.intercept("POST", "/api/global/configs").as("updateAuth")
|
|
||||||
cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true })
|
|
||||||
cy.wait("@updateAuth")
|
|
||||||
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
|
|
||||||
|
|
||||||
cy.get(".spectrum-Toast-content")
|
|
||||||
.contains("Settings saved")
|
|
||||||
.should("be.visible")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Should allow updating of the OIDC config", () => {
|
|
||||||
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
|
||||||
cy.location().should(loc => {
|
|
||||||
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
|
||||||
})
|
|
||||||
cy.get("div.content").scrollTo("bottom")
|
|
||||||
cy.get(".spectrum-Toast .spectrum-ClearButton").click()
|
|
||||||
|
|
||||||
cy.get("input[data-cy=configUrl]").type("http://budi-auth.com/v2")
|
|
||||||
cy.get("input[data-cy=clientID]").type("34ac6a13-f24a-4b52-c70d-fa544ffd11b2")
|
|
||||||
cy.get("input[data-cy=clientSecret]").type("12A8Q~4nS_DWhOOJ2vWIRsNyDVsdtXPD.Zxa9df_")
|
|
||||||
|
|
||||||
cy.get("button[data-cy=oidc-save]").should("not.be.disabled");
|
|
||||||
|
|
||||||
cy.intercept("POST", "/api/global/configs").as("updateAuth")
|
|
||||||
cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true })
|
|
||||||
cy.wait("@updateAuth")
|
|
||||||
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
|
|
||||||
|
|
||||||
cy.get(".spectrum-Toast-content")
|
|
||||||
.contains("Settings saved")
|
|
||||||
.should("be.visible")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Should display default scopes in advanced config.", () => {
|
|
||||||
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
|
||||||
cy.location().should(loc => {
|
|
||||||
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
|
||||||
})
|
|
||||||
cy.get("div.content").scrollTo("bottom")
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags-item").contains("openid")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("openid").find(".spectrum-ClearButton").should("not.exist")
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags-item").contains("offline_access")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("email")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("profile")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Add a new scopes", () => {
|
|
||||||
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
|
||||||
cy.location().should(loc => {
|
|
||||||
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
|
||||||
})
|
|
||||||
cy.get("div.content").scrollTo("bottom")
|
|
||||||
|
|
||||||
cy.get("[data-cy=new-scope-input]").type("Sample{enter}")
|
|
||||||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 5)
|
|
||||||
cy.get(".spectrum-Tags-item").contains("Sample")
|
|
||||||
|
|
||||||
cy.get(".auth-form input.spectrum-Textfield-input").type("Another ")
|
|
||||||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 6)
|
|
||||||
cy.get(".spectrum-Tags-item").contains("Another")
|
|
||||||
|
|
||||||
cy.get("button[data-cy=oidc-save]").should("not.be.disabled");
|
|
||||||
|
|
||||||
cy.intercept("POST", "/api/global/configs").as("updateAuth")
|
|
||||||
cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true })
|
|
||||||
cy.wait("@updateAuth")
|
|
||||||
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
|
|
||||||
|
|
||||||
cy.reload()
|
|
||||||
|
|
||||||
cy.get("div.content").scrollTo("bottom")
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags-item").contains("openid")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("offline_access")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("email")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("profile")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("Sample")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("Another")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Should allow the removal of auth scopes", () => {
|
|
||||||
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
|
||||||
cy.location().should(loc => {
|
|
||||||
expect(loc.pathname).to.eq("/builder/portal/manage/auth")
|
|
||||||
})
|
|
||||||
cy.get("div.content").scrollTo("bottom")
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags-item").contains("offline_access").parent().find(".spectrum-ClearButton").click()
|
|
||||||
cy.get(".spectrum-Tags-item").contains("profile").parent().find(".spectrum-ClearButton").click()
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist")
|
|
||||||
|
|
||||||
cy.get("button[data-cy=oidc-save]").should("not.be.disabled");
|
|
||||||
|
|
||||||
cy.intercept("POST", "/api/global/configs").as("updateAuth")
|
|
||||||
cy.get("button[data-cy=oidc-save]").contains("Save").click({ force: true })
|
|
||||||
cy.wait("@updateAuth")
|
|
||||||
cy.get("@updateAuth").its("response.statusCode").should("eq", 200)
|
|
||||||
|
|
||||||
cy.get(".spectrum-Toast-content")
|
|
||||||
.contains("Settings saved")
|
|
||||||
.should("be.visible")
|
|
||||||
|
|
||||||
cy.reload()
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Should allow auth scopes to be reset to the core defaults.", () => {
|
|
||||||
cy.get(".spectrum-SideNav li").contains("Auth").click()
|
|
||||||
|
|
||||||
cy.get("div.content").scrollTo("bottom")
|
|
||||||
|
|
||||||
cy.get("[data-cy=restore-oidc-default-scopes]").click({ force: true })
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tags-item").contains("openid")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("offline_access")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("email")
|
|
||||||
cy.get(".spectrum-Tags-item").contains("profile")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Should not allow invalid characters in the auth scopes", () => {
|
|
||||||
cy.get("[data-cy=new-scope-input]").type("thisIsInvalid\\{enter}")
|
|
||||||
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes")
|
|
||||||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
|
||||||
|
|
||||||
cy.get("[data-cy=new-scope-input]").clear()
|
|
||||||
|
|
||||||
cy.get("[data-cy=new-scope-input]").type("alsoInvalid\"{enter}")
|
|
||||||
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes")
|
|
||||||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
|
||||||
|
|
||||||
cy.get("[data-cy=new-scope-input]").clear()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Should not allow duplicate auth scopes", () => {
|
|
||||||
cy.get("[data-cy=new-scope-input]").type("offline_access{enter}")
|
|
||||||
cy.get(".spectrum-Form-itemField .error").contains("Auth scope already exists")
|
|
||||||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4)
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,238 +0,0 @@
|
||||||
import filterTests from "../../support/filterTests"
|
|
||||||
const interact = require('../../support/interact')
|
|
||||||
|
|
||||||
filterTests(["smoke", "all"], () => {
|
|
||||||
xcontext("User Management", () => {
|
|
||||||
before(() => {
|
|
||||||
cy.login()
|
|
||||||
cy.deleteApp("Cypress Tests")
|
|
||||||
cy.createApp("Cypress Tests", false)
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should create a user via basic onboarding", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
|
|
||||||
cy.createUser("bbuser@test.com")
|
|
||||||
cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser")
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should confirm App User role for a New User", () => {
|
|
||||||
cy.contains("bbuser").click()
|
|
||||||
cy.get(".spectrum-Form-itemField").eq(3).should('contain', 'App User')
|
|
||||||
|
|
||||||
// User should not have app access
|
|
||||||
cy.get(".spectrum-Heading").contains("Apps").parent().within(() => {
|
|
||||||
cy.get(interact.LIST_ITEMS, { timeout: 500 }).should("contain", "This user has access to no apps")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Cypress.env("TEST_ENV")) {
|
|
||||||
xit("should assign role types", () => {
|
|
||||||
// 3 apps minimum required - to assign an app to each role type
|
|
||||||
cy.request(`${Cypress.config().baseUrl}/api/applications?status=all`)
|
|
||||||
.its("body")
|
|
||||||
.then(val => {
|
|
||||||
if (val.length < 3) {
|
|
||||||
for (let i = 1; i < 3; i++) {
|
|
||||||
const uuid = () => Cypress._.random(0, 1e6)
|
|
||||||
const name = uuid()
|
|
||||||
if(i < 1){
|
|
||||||
cy.createApp(name, false)
|
|
||||||
} else {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
|
|
||||||
cy.wait(1000)
|
|
||||||
cy.get(interact.CREATE_APP_BUTTON, { timeout: 2000 }).click({ force: true })
|
|
||||||
cy.createAppFromScratch(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Navigate back to the user
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
|
|
||||||
cy.get(interact.SPECTRUM_SIDENAV).contains("Users").click()
|
|
||||||
cy.get(interact.SPECTRUM_TABLE, { timeout: 1000 }).contains("bbuser").click()
|
|
||||||
cy.get(interact.SPECTRUM_HEADING).contains("bbuser", { timeout: 2000})
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
cy.get(interact.SPECTRUM_TABLE, { timeout: 3000})
|
|
||||||
.eq(1)
|
|
||||||
.find(interact.SPECTRUM_TABLE_ROW)
|
|
||||||
.eq(0)
|
|
||||||
.find(interact.SPECTRUM_TABLE_CELL)
|
|
||||||
.eq(0)
|
|
||||||
.click()
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 1000 })
|
|
||||||
.contains("Choose an option")
|
|
||||||
.click()
|
|
||||||
.then(() => {
|
|
||||||
if (i == 0) {
|
|
||||||
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Admin").click({ force: true })
|
|
||||||
}
|
|
||||||
else if (i == 1) {
|
|
||||||
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Power").click({ force: true })
|
|
||||||
}
|
|
||||||
else if (i == 2) {
|
|
||||||
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Basic").click({ force: true })
|
|
||||||
}
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON, { timeout: 2000 })
|
|
||||||
.contains("Update role")
|
|
||||||
.click({ force: true })
|
|
||||||
})
|
|
||||||
cy.reload()
|
|
||||||
cy.wait(1000)
|
|
||||||
}
|
|
||||||
// Confirm roles exist within Configure roles table
|
|
||||||
cy.get(interact.SPECTRUM_TABLE, { timeout: 20000 })
|
|
||||||
.eq(0)
|
|
||||||
.within(assginedRoles => {
|
|
||||||
expect(assginedRoles).to.contain("Admin")
|
|
||||||
expect(assginedRoles).to.contain("Power")
|
|
||||||
expect(assginedRoles).to.contain("Basic")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should unassign role types", () => {
|
|
||||||
// Set each app within Configure roles table to 'No Access'
|
|
||||||
cy.get(interact.SPECTRUM_TABLE)
|
|
||||||
.eq(0)
|
|
||||||
.find(interact.SPECTRUM_TABLE_ROW)
|
|
||||||
.its("length")
|
|
||||||
.then(len => {
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
cy.get(interact.SPECTRUM_TABLE)
|
|
||||||
.eq(0)
|
|
||||||
.find(interact.SPECTRUM_TABLE_ROW)
|
|
||||||
.eq(0)
|
|
||||||
.find(interact.SPECTRUM_TABLE_CELL)
|
|
||||||
.eq(0)
|
|
||||||
.click()
|
|
||||||
.then(() => {
|
|
||||||
cy.get(interact.SPECTRUM_PICKER).eq(1).click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_POPOVER, { timeout: 500 }).contains("No Access").click()
|
|
||||||
})
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON)
|
|
||||||
.contains("Update role")
|
|
||||||
.click({ force: true })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Confirm Configure roles table no longer has any apps in it
|
|
||||||
cy.get(interact.SPECTRUM_TABLE, { timeout: 1000 }).eq(0).contains("No rows found")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
xit("should enable Developer access and verify application access", () => {
|
|
||||||
// Enable Developer access
|
|
||||||
cy.get(interact.FIELD)
|
|
||||||
.eq(4)
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_SWITCH_INPUT).click({ force: true })
|
|
||||||
})
|
|
||||||
// No Access table should now be empty
|
|
||||||
cy.get(interact.CONTAINER)
|
|
||||||
.contains("No Access")
|
|
||||||
.parent()
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_TABLE).contains("No rows found")
|
|
||||||
})
|
|
||||||
|
|
||||||
// Each app within Configure roles should have Admin access
|
|
||||||
cy.get(interact.SPECTRUM_TABLE)
|
|
||||||
.eq(0)
|
|
||||||
.find(interact.SPECTRUM_TABLE_ROW)
|
|
||||||
.its("length")
|
|
||||||
.then(len => {
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
cy.get(interact.SPECTRUM_TABLE)
|
|
||||||
.eq(0)
|
|
||||||
.find(interact.SPECTRUM_TABLE_ROW)
|
|
||||||
.eq(i)
|
|
||||||
.contains("Admin")
|
|
||||||
cy.wait(500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should disable Developer access and verify application access", () => {
|
|
||||||
// Disable Developer access
|
|
||||||
cy.get(interact.FIELD)
|
|
||||||
.eq(4)
|
|
||||||
.within(() => {
|
|
||||||
cy.get(".spectrum-Switch-input").click({ force: true })
|
|
||||||
})
|
|
||||||
// Configure roles table should now be empty
|
|
||||||
cy.get(interact.CONTAINER)
|
|
||||||
.contains("Configure roles")
|
|
||||||
.parent()
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_TABLE).contains("No rows found")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should edit user details within user details page", () => {
|
|
||||||
// Add First name
|
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
|
|
||||||
cy.wait(500)
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).wait(500).clear().click().type("bb")
|
|
||||||
})
|
|
||||||
// Add Last name
|
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
|
|
||||||
cy.wait(500)
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).click().wait(500).clear().type("test")
|
|
||||||
})
|
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).click()
|
|
||||||
// Reload page
|
|
||||||
cy.reload()
|
|
||||||
|
|
||||||
// Confirm details have been saved
|
|
||||||
cy.get(interact.FIELD, { timeout: 20000 }).eq(1).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb")
|
|
||||||
})
|
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).should('have.value', "test")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should reset the users password", () => {
|
|
||||||
cy.get(".title").within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_ICON).click({ force: true })
|
|
||||||
})
|
|
||||||
cy.get(interact.SPECTRUM_MENU).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
// Reset password modal
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID)
|
|
||||||
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").should('not.exist')
|
|
||||||
|
|
||||||
// Logout, then login with new password
|
|
||||||
cy.logOut()
|
|
||||||
cy.get('@pwd').then((pwd) => {
|
|
||||||
cy.login("bbuser@test.com", pwd)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Reset password screen
|
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
|
||||||
}
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
|
|
||||||
|
|
||||||
// Confirm user logged in afer password change
|
|
||||||
cy.login("bbuser@test.com", "test")
|
|
||||||
cy.get(".avatar > .icon").click({ force: true })
|
|
||||||
|
|
||||||
cy.get(".spectrum-Menu-item").contains("Update user information").click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT)
|
|
||||||
.eq(0)
|
|
||||||
.invoke('val').should('eq', 'bbuser@test.com')
|
|
||||||
|
|
||||||
// Logout and login as previous user
|
|
||||||
cy.logoutNoAppGrid()
|
|
||||||
cy.login()
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should delete a user", () => {
|
|
||||||
cy.deleteUser("bbuser@test.com")
|
|
||||||
cy.get(interact.SPECTRUM_TABLE, { timeout: 4000 }).should("not.have.text", "bbuser")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,114 +0,0 @@
|
||||||
import filterTests from "../../support/filterTests"
|
|
||||||
const interact = require('../../support/interact')
|
|
||||||
|
|
||||||
filterTests(["smoke", "all"], () => {
|
|
||||||
context("User Settings Menu", () => {
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
cy.login()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should update user information via user settings menu", () => {
|
|
||||||
const fname = "test"
|
|
||||||
const lname = "user"
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.updateUserInformation(fname, lname)
|
|
||||||
|
|
||||||
// Go to user info and confirm name update
|
|
||||||
cy.contains("Users").click()
|
|
||||||
cy.contains("test@test.com").click()
|
|
||||||
|
|
||||||
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname)
|
|
||||||
})
|
|
||||||
cy.get(interact.FIELD).eq(2).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should allow copying of the users API key", () => {
|
|
||||||
cy.get(".user-dropdown .avatar > .icon", { timeout: 2000 }).click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("View API key").click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_ICON).click({ force: true })
|
|
||||||
})
|
|
||||||
// There may be timing issues with this on the smoke build
|
|
||||||
cy.wait(500)
|
|
||||||
cy.get(".spectrum-Toast-content")
|
|
||||||
.contains("URL copied to clipboard")
|
|
||||||
.should("be.visible")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should allow API key regeneration", () => {
|
|
||||||
cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("View API key").click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_ICON).click({ force: true })
|
|
||||||
})
|
|
||||||
// Get initial API key value
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_CONTENT)
|
|
||||||
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('keyOne')
|
|
||||||
|
|
||||||
// Click re-generate key button
|
|
||||||
cy.get("button").contains("Regenerate key").click({ force: true })
|
|
||||||
|
|
||||||
// Verify API key was changed
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_CONTENT).within(() => {
|
|
||||||
cy.get('@keyOne').then((keyOne) => {
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').should('not.eq', keyOne)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
cy.closeModal()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should update password", () => {
|
|
||||||
// Access Update password modal
|
|
||||||
cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true })
|
|
||||||
|
|
||||||
// Enter new password and update
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
|
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
// password set to 'newpwd'
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("newpwd")
|
|
||||||
}
|
|
||||||
cy.get("button").contains("Update password").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
// Logout & in with new password
|
|
||||||
//cy.logOut()
|
|
||||||
cy.login("test@test.com", "newpwd")
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("should open and close developer mode", () => {
|
|
||||||
cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true })
|
|
||||||
|
|
||||||
// Close developer mode & verify
|
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Close developer mode").click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
|
|
||||||
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
|
|
||||||
cy.get(".app").should('not.exist') // At least one app should be available
|
|
||||||
|
|
||||||
// Open developer mode & verify
|
|
||||||
cy.get(".avatar > .icon").click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Open developer mode").click({ force: true })
|
|
||||||
cy.get(".app-table").should('exist') // config sections available
|
|
||||||
cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available
|
|
||||||
})
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
// Change password back to original value
|
|
||||||
cy.get(".user-dropdown .icon", { timeout: 2000 }).click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Update password").click({ force: true })
|
|
||||||
cy.get(interact.SPECTRUM_DIALOG_GRID).within(() => {
|
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
|
|
||||||
}
|
|
||||||
cy.get("button").contains("Update password").click({ force: true })
|
|
||||||
})
|
|
||||||
// Remove users name
|
|
||||||
cy.updateUserInformation()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,442 +0,0 @@
|
||||||
import filterTests from "../support/filterTests"
|
|
||||||
import clientPackage from "@budibase/client/package.json"
|
|
||||||
|
|
||||||
filterTests(["all"], () => {
|
|
||||||
xcontext("Application Overview screen", () => {
|
|
||||||
before(() => {
|
|
||||||
cy.login()
|
|
||||||
cy.deleteAllApps()
|
|
||||||
cy.createApp("Cypress Tests")
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should be accessible from the applications list", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .title")
|
|
||||||
.eq(0)
|
|
||||||
.invoke("attr", "data-cy")
|
|
||||||
.then($dataCy => {
|
|
||||||
const dataCy = $dataCy
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.location().should(loc => {
|
|
||||||
expect(loc.pathname).to.eq("/builder/portal/overview/" + dataCy)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Find a more suitable place for this.
|
|
||||||
xit("Should allow unlocking in the app list", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
|
|
||||||
cy.get(".appTable .lock-status").eq(0).contains("Locked by you").click()
|
|
||||||
|
|
||||||
cy.unlockApp({ owned: true })
|
|
||||||
|
|
||||||
cy.get(".appTable").should("exist")
|
|
||||||
cy.get(".lock-status").should("not.be.visible")
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should allow unlocking in the app overview screen", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Edit")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.wait(1000)
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".lock-status").eq(0).contains("Locked by you").click()
|
|
||||||
|
|
||||||
cy.unlockApp({ owned: true })
|
|
||||||
|
|
||||||
cy.get(".lock-status").should("not.be.visible")
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should reflect the deploy state of an app that hasn't been published.", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".header-right button.spectrum-Button[data-cy='view-app']").should(
|
|
||||||
"be.disabled"
|
|
||||||
)
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tabs-item.is-selected").contains("Overview")
|
|
||||||
cy.get(".overview-tab").should("be.visible")
|
|
||||||
|
|
||||||
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
|
||||||
cy.get(".status-display").contains("Unpublished")
|
|
||||||
cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should(
|
|
||||||
"exist"
|
|
||||||
)
|
|
||||||
cy.get(".status-text").contains("-")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should reflect the app deployment state", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Edit")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.wait(500)
|
|
||||||
cy.get(".toprightnav button.spectrum-Button", { timeout: 2000 })
|
|
||||||
.contains("Publish")
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']")
|
|
||||||
.should("be.visible")
|
|
||||||
.within(() => {
|
|
||||||
cy.get(".spectrum-Button").contains("Publish").click({ force: true })
|
|
||||||
cy.wait(1000)
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".header-right button.spectrum-Button[data-cy='view-app']").should(
|
|
||||||
"not.be.disabled"
|
|
||||||
)
|
|
||||||
|
|
||||||
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
|
||||||
cy.get(".status-display").contains("Published")
|
|
||||||
cy.get(".status-display .icon svg[aria-label='GlobeCheck']").should(
|
|
||||||
"exist"
|
|
||||||
)
|
|
||||||
cy.get(".status-text").contains("Last published a few seconds ago")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should reflect an application that has been unpublished", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Edit")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.get(".deployment-top-nav svg[aria-label='Globe']").click({
|
|
||||||
force: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get("[data-cy='publish-popover-menu']").should("be.visible")
|
|
||||||
cy.get(
|
|
||||||
"[data-cy='publish-popover-menu'] [data-cy='publish-popover-action']"
|
|
||||||
).click({ force: true })
|
|
||||||
|
|
||||||
cy.get("[data-cy='unpublish-modal']")
|
|
||||||
.should("be.visible")
|
|
||||||
.within(() => {
|
|
||||||
cy.get(".confirm-wrap button").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.wait(1000)
|
|
||||||
cy.get(".appTable .app-row-actions button", { timeout: 10000 })
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
|
||||||
cy.get(".status-display").contains("Unpublished")
|
|
||||||
cy.get(".status-display .icon svg[aria-label='GlobeStrike']").should(
|
|
||||||
"exist"
|
|
||||||
)
|
|
||||||
cy.get(".status-text").contains("Last published a few seconds ago")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should allow the editing of the application icon and colour", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".edit-hover", { timeout: 1000 }).eq(0).click({ force: true })
|
|
||||||
// Select random icon
|
|
||||||
cy.wait(400)
|
|
||||||
cy.get(".grid").within(() => {
|
|
||||||
cy.get(".icon-item")
|
|
||||||
.eq(Math.floor(Math.random() * 23) + 1)
|
|
||||||
.click()
|
|
||||||
})
|
|
||||||
// Select random colour
|
|
||||||
cy.get(".fill").click()
|
|
||||||
cy.get(".colors").within(() => {
|
|
||||||
cy.get(".color")
|
|
||||||
.eq(Math.floor(Math.random() * 33) + 1)
|
|
||||||
.click()
|
|
||||||
})
|
|
||||||
cy.intercept("**/applications/**").as("iconChange")
|
|
||||||
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
|
||||||
cy.wait("@iconChange")
|
|
||||||
cy.get("@iconChange").its("response.statusCode").should("eq", 200)
|
|
||||||
// Confirm icon has changed from default
|
|
||||||
// Confirm colour has been applied
|
|
||||||
cy.get(".spectrum-ActionButton-label").contains("Back").click({ force: true })
|
|
||||||
cy.get(".appTable", { timeout: 2000 }).within(() => {
|
|
||||||
cy.get("[aria-label]")
|
|
||||||
.eq(0)
|
|
||||||
.children()
|
|
||||||
.should("have.attr", "xlink:href")
|
|
||||||
.and("not.contain", "#spectrum-icon-18-Apps")
|
|
||||||
cy.get(".title")
|
|
||||||
.children()
|
|
||||||
.children()
|
|
||||||
.should("have.attr", "style")
|
|
||||||
.and("contains", "color")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should reflect the last time the application was edited", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".header-right button").contains("Edit").click({ force: true })
|
|
||||||
|
|
||||||
cy.navigateToFrontend()
|
|
||||||
|
|
||||||
cy.searchAndAddComponent("Headline").then(componentId => {
|
|
||||||
cy.getComponent(componentId).should("exist")
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".overview-tab [data-cy='edited-by']").within(() => {
|
|
||||||
cy.get(".editor-name").contains("You")
|
|
||||||
cy.get(".last-edit-text").contains("Last edited a few seconds ago")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should reflect application version is up-to-date", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".overview-tab [data-cy='app-version']").within(() => {
|
|
||||||
cy.get(".version-status").contains("You're running the latest!")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Should navigate to the settings tab when clicking the App Version card header", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".spectrum-Tabs-item.is-selected").contains("Overview")
|
|
||||||
cy.get(".overview-tab").should("be.visible")
|
|
||||||
|
|
||||||
cy.get(".overview-tab [data-cy='app-version'] .dash-card-header").click({
|
|
||||||
force: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
|
||||||
cy.get(".settings-tab").should("be.visible")
|
|
||||||
cy.get(".overview-tab").should("not.exist")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Should allow the upgrading of an application, if available.", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.wait(500)
|
|
||||||
|
|
||||||
cy.location().then(loc => {
|
|
||||||
const params = loc.pathname.split("/")
|
|
||||||
const appId = params[params.length - 1]
|
|
||||||
cy.log(appId)
|
|
||||||
//Downgrade the app for the test
|
|
||||||
cy.alterAppVersion(appId, "0.0.1-alpha.0").then(() => {
|
|
||||||
cy.reload()
|
|
||||||
cy.log("Current deployment version: " + clientPackage.version)
|
|
||||||
|
|
||||||
cy.get(".version-status a", { timeout: 5000 }).contains("Update").click()
|
|
||||||
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
|
||||||
|
|
||||||
cy.get(".version-section .page-action button")
|
|
||||||
.contains("Update")
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.intercept("POST", "**/applications/**/client/update").as(
|
|
||||||
"updateVersion"
|
|
||||||
)
|
|
||||||
cy.get(".spectrum-Modal.is-open button")
|
|
||||||
.contains("Update")
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.wait("@updateVersion")
|
|
||||||
.its("response.statusCode")
|
|
||||||
.should("eq", 200)
|
|
||||||
.then(() => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".spectrum-Tabs-item")
|
|
||||||
.contains("Overview")
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".overview-tab [data-cy='app-version']").within(() => {
|
|
||||||
cy.get(".spectrum-Heading").contains(clientPackage.version)
|
|
||||||
cy.get(".version-status").contains("You're running the latest!")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should allow editing of the app details.", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".spectrum-Tabs-item").contains("Settings").click()
|
|
||||||
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
|
||||||
cy.get(".settings-tab").should("be.visible")
|
|
||||||
|
|
||||||
cy.get(".details-section .page-action button")
|
|
||||||
.contains("Edit")
|
|
||||||
.click({ force: true })
|
|
||||||
cy.updateAppName("sample name")
|
|
||||||
|
|
||||||
//publish and check its disabled
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
|
||||||
cy.wait(500)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Edit")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.get(".toprightnav button.spectrum-Button")
|
|
||||||
.contains("Publish")
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".spectrum-Modal [data-cy='deploy-app-modal']")
|
|
||||||
.should("be.visible")
|
|
||||||
.within(() => {
|
|
||||||
cy.get(".spectrum-Button").contains("Publish").click({ force: true })
|
|
||||||
cy.wait(1000)
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 10000 })
|
|
||||||
cy.get(".appTable .app-row-actions button", { timeout: 5000 })
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".spectrum-Tabs-item").contains("Settings").click()
|
|
||||||
cy.get(".spectrum-Tabs-item.is-selected").contains("Settings")
|
|
||||||
|
|
||||||
cy.get(".details-section .page-action .spectrum-Button").scrollIntoView()
|
|
||||||
cy.get(".details-section .page-action .spectrum-Button", { timeout: 1000 }).should(
|
|
||||||
"be.disabled"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should allow copying of the published application Id", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions")
|
|
||||||
.eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.get(".spectrum-Button").contains("Edit").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.publishApp("sample-name")
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".app-overview-actions-icon > .icon").click({ force: true })
|
|
||||||
|
|
||||||
cy.get("[data-cy='app-overview-menu-popover']")
|
|
||||||
.eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.get(".spectrum-Menu-item")
|
|
||||||
.contains("Copy App ID")
|
|
||||||
.click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(".spectrum-Toast-content")
|
|
||||||
.contains("App ID copied to clipboard.")
|
|
||||||
.should("be.visible")
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should allow unpublishing of the application via the Unpublish link", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
|
|
||||||
cy.get(`[data-cy="app-status"]`).within(() => {
|
|
||||||
cy.contains("Unpublish").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get("[data-cy='unpublish-modal']")
|
|
||||||
.should("be.visible")
|
|
||||||
.within(() => {
|
|
||||||
cy.get(".confirm-wrap button").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(".overview-tab [data-cy='app-status']").within(() => {
|
|
||||||
cy.get(".status-display").contains("Unpublished")
|
|
||||||
cy.get(".status-display .icon svg[aria-label='GlobeStrike']")
|
|
||||||
.should("exist")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should allow deleting of the application", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
cy.get(".appTable .app-row-actions button")
|
|
||||||
.contains("Manage")
|
|
||||||
.eq(0)
|
|
||||||
.click({ force: true })
|
|
||||||
cy.get(".app-overview-actions-icon > .icon").click({ force: true })
|
|
||||||
|
|
||||||
cy.get("[data-cy='app-overview-menu-popover']")
|
|
||||||
.eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.get(".spectrum-Menu-item")
|
|
||||||
.contains("Delete")
|
|
||||||
.click({ force: true })
|
|
||||||
cy.wait(500)
|
|
||||||
})
|
|
||||||
|
|
||||||
//The test application was renamed earlier in the spec
|
|
||||||
cy.get(".spectrum-Dialog-grid").within(() => {
|
|
||||||
cy.get("input").type("sample name")
|
|
||||||
cy.get(".spectrum-Button--warning").click()
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.location().should(loc => {
|
|
||||||
expect(loc.pathname).to.eq("/builder/portal/apps")
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(".appTable").should("not.exist")
|
|
||||||
|
|
||||||
cy.get(".welcome .container h1").contains("Let's create your first app!")
|
|
||||||
})
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
cy.deleteAllApps()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,108 +0,0 @@
|
||||||
import filterTests from "../support/filterTests"
|
|
||||||
import { APP_TABLE_APP_NAME, DEPLOY_SUCCESS_MODAL } from "../support/interact";
|
|
||||||
const interact = require('../support/interact')
|
|
||||||
|
|
||||||
filterTests(['all'], () => {
|
|
||||||
xcontext("Publish Application Workflow", () => {
|
|
||||||
before(() => {
|
|
||||||
cy.login()
|
|
||||||
cy.deleteAllApps()
|
|
||||||
cy.createApp("Cypress Tests", false)
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should reflect the unpublished status correctly", () => {
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE_STATUS, { timeout: 3000 }).eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.contains("Unpublished")
|
|
||||||
cy.get(interact.GLOBESTRIKE).should("exist")
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Edit").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(interact.DEPLOYMENT_TOP_NAV_GLOBESTRIKE).should("exist")
|
|
||||||
cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("not.exist")
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should publish an application and correctly reflect that", () => {
|
|
||||||
//Assuming the previous test was run and the unpublished app is open in edit mode.
|
|
||||||
cy.get(interact.TOPRIGHTNAV_BUTTON_SPECTRUM).contains("Publish").click({ force: true })
|
|
||||||
|
|
||||||
cy.get(interact.DEPLOY_APP_MODAL).should("be.visible")
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Publish").click({ force: true })
|
|
||||||
});
|
|
||||||
|
|
||||||
//Verify that the app url is presented correctly to the user
|
|
||||||
cy.get(interact.DEPLOY_SUCCESS_MODAL, { timeout: 1000 })
|
|
||||||
.should("be.visible")
|
|
||||||
.within(() => {
|
|
||||||
let appUrl = Cypress.config().baseUrl + '/app/cypress-tests'
|
|
||||||
cy.get(interact.DEPLOY_APP_URL_INPUT).should('have.value', appUrl)
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Done").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`)
|
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE_STATUS, { timeout: 3000 }).eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.contains("Published")
|
|
||||||
cy.get(interact.GLOBE).should("exist")
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE_ROW_ACTION).eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Manage")
|
|
||||||
cy.get(interact.SPECTRUM_BUTTON).contains("Edit").click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("exist").click({ force: true })
|
|
||||||
|
|
||||||
cy.get(interact.PUBLISH_POPOVER_MENU).should("be.visible")
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.PUBLISH_POPOVER_ACTION).should("exist")
|
|
||||||
cy.get("button").contains("View app").should("exist")
|
|
||||||
cy.get(interact.PUBLISH_POPOVER_MESSAGE).should("have.text", "Last published a few seconds ago")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
xit("Should unpublish an application using the link and reflect the status change", () => {
|
|
||||||
//Assuming the previous test app exists and is published
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000 })
|
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE_STATUS).eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.contains("Published")
|
|
||||||
cy.get("svg[aria-label='Globe']").should("exist")
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(interact.APP_TABLE).eq(0)
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.APP_TABLE_APP_NAME).click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(interact.DEPLOYMENT_TOP_GLOBE).should("exist").click({ force: true })
|
|
||||||
|
|
||||||
cy.get("[data-cy='publish-popover-menu']")
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.PUBLISH_POPOVER_ACTION).click({ force: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.get(interact.UNPUBLISH_MODAL).should("be.visible")
|
|
||||||
.within(() => {
|
|
||||||
cy.get(interact.CONFIRM_WRAP_BUTTON).click({ force: true }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 6000 })
|
|
||||||
cy.wait(500)
|
|
||||||
cy.get(interact.APP_TABLE_STATUS, { timeout: 10000 }).eq(0).contains("Unpublished")
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue