forked from tsai/budibase
251 changed files with 10121 additions and 6417 deletions
@ -1,76 +0,0 @@ |
|||
# Contributor Covenant Code of Conduct |
|||
|
|||
## Our Pledge |
|||
|
|||
In the interest of fostering an open and welcoming environment, we as |
|||
contributors and maintainers pledge to making participation in our project and |
|||
our community a harassment-free experience for everyone, regardless of age, body |
|||
size, disability, ethnicity, sex characteristics, gender identity and expression, |
|||
level of experience, education, socio-economic status, nationality, personal |
|||
appearance, race, religion, or sexual identity and orientation. |
|||
|
|||
## Our Standards |
|||
|
|||
Examples of behavior that contributes to creating a positive environment |
|||
include: |
|||
|
|||
* Using welcoming and inclusive language |
|||
* Being respectful of differing viewpoints and experiences |
|||
* Gracefully accepting constructive criticism |
|||
* Focusing on what is best for the community |
|||
* Showing empathy towards other community members |
|||
|
|||
Examples of unacceptable behavior by participants include: |
|||
|
|||
* The use of sexualized language or imagery and unwelcome sexual attention or |
|||
advances |
|||
* Trolling, insulting/derogatory comments, and personal or political attacks |
|||
* Public or private harassment |
|||
* Publishing others' private information, such as a physical or electronic |
|||
address, without explicit permission |
|||
* Other conduct which could reasonably be considered inappropriate in a |
|||
professional setting |
|||
|
|||
## Our Responsibilities |
|||
|
|||
Project maintainers are responsible for clarifying the standards of acceptable |
|||
behavior and are expected to take appropriate and fair corrective action in |
|||
response to any instances of unacceptable behavior. |
|||
|
|||
Project maintainers have the right and responsibility to remove, edit, or |
|||
reject comments, commits, code, wiki edits, issues, and other contributions |
|||
that are not aligned to this Code of Conduct, or to ban temporarily or |
|||
permanently any contributor for other behaviors that they deem inappropriate, |
|||
threatening, offensive, or harmful. |
|||
|
|||
## Scope |
|||
|
|||
This Code of Conduct applies both within project spaces and in public spaces |
|||
when an individual is representing the project or its community. Examples of |
|||
representing a project or community include using an official project e-mail |
|||
address, posting via an official social media account, or acting as an appointed |
|||
representative at an online or offline event. Representation of a project may be |
|||
further defined and clarified by project maintainers. |
|||
|
|||
## Enforcement |
|||
|
|||
Instances of abusive, harassing, or otherwise unacceptable behavior may be |
|||
reported by contacting the project team at community@budibase.com. All |
|||
complaints will be reviewed and investigated and will result in a response that |
|||
is deemed necessary and appropriate to the circumstances. The project team is |
|||
obligated to maintain confidentiality with regard to the reporter of an incident. |
|||
Further details of specific enforcement policies may be posted separately. |
|||
|
|||
Project maintainers who do not follow or enforce the Code of Conduct in good |
|||
faith may face temporary or permanent repercussions as determined by other |
|||
members of the project's leadership. |
|||
|
|||
## Attribution |
|||
|
|||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, |
|||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html |
|||
|
|||
[homepage]: https://www.contributor-covenant.org |
|||
|
|||
For answers to common questions about this code of conduct, see |
|||
https://www.contributor-covenant.org/faq |
|||
@ -0,0 +1 @@ |
|||
../docs/CODE_OF_CONDUCT.md |
|||
@ -1,208 +0,0 @@ |
|||
# Contributing |
|||
|
|||
From opening a bug report to creating a pull request: every contribution is appreciated and welcome. If you're planning to implement a new feature or change the api please create an issue first. This way we can ensure that your precious work is not in vain. |
|||
|
|||
## Not Sure Where to Start? |
|||
|
|||
Budibase is a low-code web application builder that creates svelte based web applications. |
|||
|
|||
Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna manages the building and publishing of the budibase packages. At a high level, here are the packages that make up budibase. |
|||
|
|||
- **packages/builder** - contains code for the budibase builder client side svelte application. |
|||
|
|||
- **packages/client** - A module that runs in the browser responsible for reading JSON definition and creating living, breathing web apps from it. |
|||
|
|||
- **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. |
|||
|
|||
## Contributor License Agreement (CLA) |
|||
|
|||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once. If you are submitting a pull request for the first time, just submit a Pull Request and our CLA Bot will give you instructions on how to sign the CLA before merging your Pull Request. |
|||
|
|||
All contributors must sign an [Individual Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/individual-cla.md). |
|||
|
|||
If contributing on behalf of your company, your company must sign a [Corporate Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/corporate-cla.md). If so, please contact us via community@budibase.com. |
|||
|
|||
## Glossary of Terms |
|||
|
|||
To understand the budibase API, it can be helpful to understand the top level entities that make up Budibase. |
|||
|
|||
### Client |
|||
|
|||
A client represents a single budibase customer. Each budibase client will have 1 or more budibase servers. Every client is assigned a unique ID. |
|||
|
|||
### 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. |
|||
|
|||
### Database |
|||
|
|||
An App can have one or more databases. Keeping with our [dendrology](https://en.wikipedia.org/wiki/Dendrology) analogy - think of an database as a branch on the tree. Databases are used to keep data separate for different instances of your app. For example, if you had a CRM app, you may create a database for your US office, and a database for your Australian office. Databases allow us to support [multitenancy](https://www.gartner.com/en/information-technology/glossary/multitenancy) in budibase applications. |
|||
|
|||
### Table |
|||
|
|||
Tables in budibase are almost akin to tables in relational databases. A table may be a "Car" or an "Employee". They are the main building blocks for the creation and management of backend data in budibase. |
|||
|
|||
### View |
|||
|
|||
A View is an advanced feature in budibase that allows you to write a custom query using [MapReduce](https://pouchdb.com/guides/queries.html) queries. Views enable powerful query functionality and calculations, allowing you to do more with your data. |
|||
|
|||
### Page |
|||
|
|||
A page in budibase is actually a single, self contained svelte web app. There are only 2 pages in budibase. The **login** page and the **main** page. |
|||
|
|||
### Screen |
|||
|
|||
A screen is a component within a single page. Generally, screens represent client side routes, and can be switched without refreshing the page. |
|||
|
|||
### Component |
|||
|
|||
A component is the basic frontend building block of a budibase app. |
|||
|
|||
### Component Library |
|||
|
|||
Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`. |
|||
|
|||
## Contributing to Budibase |
|||
|
|||
* Please maintain the existing code style. |
|||
|
|||
* Please try to keep your commits small and focused. |
|||
|
|||
* Please write tests. |
|||
|
|||
* 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. |
|||
|
|||
### Getting Started For Contributors |
|||
#### 1. Prerequisites |
|||
|
|||
NodeJS Version `14.x.x` |
|||
|
|||
*yarn -* `npm install -g yarn` |
|||
|
|||
*jest* - `npm install -g jest` |
|||
|
|||
#### 2. Clone this repository |
|||
|
|||
`git clone https://github.com/Budibase/budibase.git` |
|||
|
|||
then `cd ` into your local copy. |
|||
|
|||
#### 3. Install and Build |
|||
|
|||
| **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash) |
|||
|
|||
To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed. |
|||
|
|||
##### Quick method |
|||
|
|||
`yarn setup` will check that all necessary components are installed and setup the repo for usage. |
|||
|
|||
##### Manual method |
|||
|
|||
The following commands can be executed to manually get Budibase up and running (assuming Docker/Docker Compose has been installed). |
|||
|
|||
`yarn` to install project dependencies |
|||
|
|||
`yarn bootstrap` will install all budibase modules and symlink them together using lerna. |
|||
|
|||
`yarn build` will build all budibase packages. |
|||
|
|||
#### 4. Running |
|||
|
|||
To run the budibase server and builder in dev mode (i.e. with live reloading): |
|||
|
|||
1. Open a new console |
|||
2. `yarn dev` (from root) |
|||
3. Access the builder on http://localhost:10000/builder |
|||
|
|||
This will enable watch mode for both the builder app, server, client library and any component libraries. |
|||
|
|||
#### 5. Debugging using VS Code |
|||
|
|||
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. |
|||
Alternatively to start both components simultaneously select `Start Budibase`. |
|||
|
|||
In addition to the above, the remaining budibase components may be ran in dev mode using: `yarn dev:noserver`. |
|||
|
|||
#### 6. Cleanup |
|||
|
|||
If you wish to delete all the apps created in development and reset the environment then run the following: |
|||
|
|||
1. `yarn nuke:docker` will wipe all the Budibase services |
|||
2. `yarn dev` will restart all the services |
|||
|
|||
### Backend |
|||
|
|||
For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apache.org/), [MinIO](https://min.io/) and [NGINX](https://www.nginx.com/) in Docker compose. This means that to develop Budibase you will need Docker and Docker compose installed. The backend services are then ran separately as Node services with nodemon so that they can be debugged outside of Docker. |
|||
|
|||
### Data Storage |
|||
|
|||
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` |
|||
- Sessions, email tokens |
|||
- `couchdb3_data` |
|||
- Global and app databases |
|||
- `minio_data` |
|||
- App manifest, budibase client, static assets |
|||
|
|||
### Devlopment Modes |
|||
|
|||
A combination of environment variables controls the mode that budibase runs in. |
|||
Yarn commands can be used to mimic the different modes that budibase can be ran in |
|||
|
|||
#### Self Hosted |
|||
The default mode. A single tenant installation with no usage restrictions. |
|||
|
|||
To enable this mode, use: |
|||
``` |
|||
yarn mode:self |
|||
``` |
|||
|
|||
#### Cloud |
|||
The cloud mode, with account portal turned off. |
|||
|
|||
To enable this mode, use: |
|||
``` |
|||
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 |
|||
|
|||
|
|||
To enable this mode, use: |
|||
``` |
|||
yarn mode:account |
|||
``` |
|||
### CI |
|||
An overview of the CI pipelines can be found [here](./workflows/README.md) |
|||
### 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. You should have a fresh Budibase installation. |
|||
### Running tests |
|||
|
|||
#### End-to-end Tests |
|||
|
|||
Budibase uses Cypress to run a number of E2E tests. To run the tests execute the following command in the root folder: |
|||
|
|||
``` |
|||
yarn test:e2e |
|||
``` |
|||
|
|||
Or if you are in the builder you can run `yarn cy:test`. |
|||
|
|||
|
|||
### Other Useful Information |
|||
|
|||
* 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). |
|||
|
|||
* 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. |
|||
@ -0,0 +1 @@ |
|||
../docs/CONTRIBUTING.md |
|||
@ -0,0 +1,77 @@ |
|||
name: Budibase Deploy Release |
|||
|
|||
on: |
|||
workflow_dispatch: |
|||
|
|||
jobs: |
|||
release: |
|||
runs-on: ubuntu-latest |
|||
|
|||
steps: |
|||
- uses: actions/checkout@v2 |
|||
|
|||
- name: Configure AWS Credentials |
|||
uses: aws-actions/configure-aws-credentials@v1 |
|||
with: |
|||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} |
|||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} |
|||
aws-region: eu-west-1 |
|||
|
|||
- name: Fail if branch is not develop |
|||
if: github.ref != 'refs/heads/develop' |
|||
run: | |
|||
echo "Ref is not develop, you must run this job from develop." |
|||
exit 1 |
|||
|
|||
- name: Get the latest budibase release version |
|||
id: version |
|||
run: | |
|||
release_version=$(cat lerna.json | jq -r '.version') |
|||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV |
|||
|
|||
- name: Tag and release Proxy service docker image |
|||
run: | |
|||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD |
|||
yarn build:docker:proxy:release |
|||
docker tag proxy-service budibase/proxy:$RELEASE_TAG |
|||
docker push budibase/proxy:$RELEASE_TAG |
|||
env: |
|||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} |
|||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} |
|||
RELEASE_TAG: k8s-release |
|||
|
|||
- name: Pull values.yaml from budibase-infra |
|||
run: | |
|||
curl -H "Authorization: token ${{ secrets.GH_PERSONAL_TOKEN }}" \ |
|||
-H 'Accept: application/vnd.github.v3.raw' \ |
|||
-o values.release.yaml \ |
|||
-L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/budibase-release/values.yaml |
|||
wc -l values.release.yaml |
|||
|
|||
- name: Deploy to Release Environment |
|||
uses: glopezep/helm@v1.7.1 |
|||
with: |
|||
release: budibase-release |
|||
namespace: budibase |
|||
chart: charts/budibase |
|||
token: ${{ github.token }} |
|||
helm: helm3 |
|||
values: | |
|||
globals: |
|||
appVersion: develop |
|||
ingress: |
|||
enabled: true |
|||
nginx: true |
|||
value-files: >- |
|||
[ |
|||
"values.release.yaml" |
|||
] |
|||
env: |
|||
KUBECONFIG_FILE: '${{ secrets.RELEASE_KUBECONFIG }}' |
|||
|
|||
- name: Discord Webhook Action |
|||
uses: tsickert/discord-webhook@v4.0.0 |
|||
with: |
|||
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} |
|||
content: "Release Env Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Release Env." |
|||
embed-title: ${{ env.RELEASE_VERSION }} |
|||
@ -0,0 +1,76 @@ |
|||
# Contributor Covenant Code of Conduct |
|||
|
|||
## Our Pledge |
|||
|
|||
In the interest of fostering an open and welcoming environment, we as |
|||
contributors and maintainers pledge to making participation in our project and |
|||
our community a harassment-free experience for everyone, regardless of age, body |
|||
size, disability, ethnicity, sex characteristics, gender identity and expression, |
|||
level of experience, education, socio-economic status, nationality, personal |
|||
appearance, race, religion, or sexual identity and orientation. |
|||
|
|||
## Our Standards |
|||
|
|||
Examples of behavior that contributes to creating a positive environment |
|||
include: |
|||
|
|||
* Using welcoming and inclusive language |
|||
* Being respectful of differing viewpoints and experiences |
|||
* Gracefully accepting constructive criticism |
|||
* Focusing on what is best for the community |
|||
* Showing empathy towards other community members |
|||
|
|||
Examples of unacceptable behavior by participants include: |
|||
|
|||
* The use of sexualized language or imagery and unwelcome sexual attention or |
|||
advances |
|||
* Trolling, insulting/derogatory comments, and personal or political attacks |
|||
* Public or private harassment |
|||
* Publishing others' private information, such as a physical or electronic |
|||
address, without explicit permission |
|||
* Other conduct which could reasonably be considered inappropriate in a |
|||
professional setting |
|||
|
|||
## Our Responsibilities |
|||
|
|||
Project maintainers are responsible for clarifying the standards of acceptable |
|||
behavior and are expected to take appropriate and fair corrective action in |
|||
response to any instances of unacceptable behavior. |
|||
|
|||
Project maintainers have the right and responsibility to remove, edit, or |
|||
reject comments, commits, code, wiki edits, issues, and other contributions |
|||
that are not aligned to this Code of Conduct, or to ban temporarily or |
|||
permanently any contributor for other behaviors that they deem inappropriate, |
|||
threatening, offensive, or harmful. |
|||
|
|||
## Scope |
|||
|
|||
This Code of Conduct applies both within project spaces and in public spaces |
|||
when an individual is representing the project or its community. Examples of |
|||
representing a project or community include using an official project e-mail |
|||
address, posting via an official social media account, or acting as an appointed |
|||
representative at an online or offline event. Representation of a project may be |
|||
further defined and clarified by project maintainers. |
|||
|
|||
## Enforcement |
|||
|
|||
Instances of abusive, harassing, or otherwise unacceptable behavior may be |
|||
reported by contacting the project team at community@budibase.com. All |
|||
complaints will be reviewed and investigated and will result in a response that |
|||
is deemed necessary and appropriate to the circumstances. The project team is |
|||
obligated to maintain confidentiality with regard to the reporter of an incident. |
|||
Further details of specific enforcement policies may be posted separately. |
|||
|
|||
Project maintainers who do not follow or enforce the Code of Conduct in good |
|||
faith may face temporary or permanent repercussions as determined by other |
|||
members of the project's leadership. |
|||
|
|||
## Attribution |
|||
|
|||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, |
|||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html |
|||
|
|||
[homepage]: https://www.contributor-covenant.org |
|||
|
|||
For answers to common questions about this code of conduct, see |
|||
https://www.contributor-covenant.org/faq |
|||
@ -0,0 +1,231 @@ |
|||
# Contributing |
|||
|
|||
From opening a bug report to creating a pull request: every contribution is appreciated and welcome. If you're planning to implement a new feature or change the api please [create an issue](https://github.com/Budibase/budibase/issues/new/choose) first. This way we can ensure that your precious work is not in vain. |
|||
|
|||
## Table of contents |
|||
|
|||
- [Quick start](#quick-start) |
|||
- [Status](#status) |
|||
- [What's included](#whats-included) |
|||
- [Bugs and feature requests](#bugs-and-feature-requests) |
|||
|
|||
|
|||
## Not Sure Where to Start? |
|||
|
|||
Budibase is a low-code web application builder that creates svelte-based web applications. |
|||
|
|||
Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna manages the building and publishing of the budibase packages. At a high level, here are the packages that make up budibase. |
|||
|
|||
- **packages/builder** - contains code for the budibase builder client side svelte application. |
|||
|
|||
- **packages/client** - A module that runs in the browser responsible for reading JSON definition and creating living, breathing web apps from it. |
|||
|
|||
- **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. |
|||
|
|||
## Contributor License Agreement (CLA) |
|||
|
|||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once. If you are submitting a pull request for the first time, just submit a Pull Request and our CLA Bot will give you instructions on how to sign the CLA before merging your Pull Request. |
|||
|
|||
All contributors must sign an [Individual Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/individual-cla.md). |
|||
|
|||
If contributing on behalf of your company, your company must sign a [Corporate Contributor License Agreement](https://github.com/budibase/budibase/blob/next/.github/cla/corporate-cla.md). If so, please contact us via community@budibase.com. |
|||
|
|||
## Glossary of Terms |
|||
|
|||
To understand the budibase API, it can be helpful to understand the top level entities that make up Budibase. |
|||
|
|||
### Client |
|||
|
|||
A client represents a single budibase customer. Each budibase client will have 1 or more budibase servers. Every client is assigned a unique ID. |
|||
|
|||
### 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. |
|||
|
|||
### Database |
|||
|
|||
An App can have one or more databases. Keeping with our [dendrology](https://en.wikipedia.org/wiki/Dendrology) analogy - think of an database as a branch on the tree. Databases are used to keep data separate for different instances of your app. For example, if you had a CRM app, you may create a database for your US office, and a database for your Australian office. Databases allow us to support [multitenancy](https://www.gartner.com/en/information-technology/glossary/multitenancy) in budibase applications. |
|||
|
|||
### Table |
|||
|
|||
Tables in budibase are almost akin to tables in relational databases. A table may be a "Car" or an "Employee". They are the main building blocks for the creation and management of backend data in budibase. |
|||
|
|||
### View |
|||
|
|||
A View is an advanced feature in budibase that allows you to write a custom query using [MapReduce](https://pouchdb.com/guides/queries.html) queries. Views enable powerful query functionality and calculations, allowing you to do more with your data. |
|||
|
|||
### Page |
|||
|
|||
A page in budibase is actually a single, self contained svelte web app. There are only 2 pages in budibase. The **login** page and the **main** page. |
|||
|
|||
### Screen |
|||
|
|||
A screen is a component within a single page. Generally, screens represent client side routes, and can be switched without refreshing the page. |
|||
|
|||
### Component |
|||
|
|||
A component is the basic frontend building block of a budibase app. |
|||
|
|||
### Component Library |
|||
|
|||
Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`. |
|||
|
|||
## Contributing to Budibase |
|||
|
|||
* Please maintain the existing code style. |
|||
|
|||
* Please try to keep your commits small and focused. |
|||
|
|||
* Please write tests. |
|||
|
|||
* 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. |
|||
|
|||
### Getting Started For Contributors |
|||
#### 1. Prerequisites |
|||
|
|||
NodeJS Version `14.x.x` |
|||
|
|||
*yarn -* `npm install -g yarn` |
|||
|
|||
*jest* - `npm install -g jest` |
|||
|
|||
#### 2. Clone this repository |
|||
|
|||
`git clone https://github.com/Budibase/budibase.git` |
|||
|
|||
then `cd ` into your local copy. |
|||
|
|||
#### 3. Install and Build |
|||
|
|||
| **NOTE**: On Windows, all yarn commands must be executed on a bash shell (e.g. git bash) |
|||
|
|||
To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed. |
|||
|
|||
##### Quick method |
|||
|
|||
`yarn setup` will check that all necessary components are installed and setup the repo for usage. |
|||
|
|||
##### Manual method |
|||
|
|||
The following commands can be executed to manually get Budibase up and running (assuming Docker/Docker Compose has been installed). |
|||
|
|||
`yarn` to install project dependencies |
|||
|
|||
`yarn bootstrap` will install all budibase modules and symlink them together using lerna. |
|||
|
|||
`yarn build` will build all budibase packages. |
|||
|
|||
#### 4. Running |
|||
|
|||
To run the budibase server and builder in dev mode (i.e. with live reloading): |
|||
|
|||
1. Open a new console |
|||
2. `yarn dev` (from root) |
|||
3. Access the builder on http://localhost:10000/builder |
|||
|
|||
This will enable watch mode for both the builder app, server, client library and any component libraries. |
|||
|
|||
#### 5. Debugging using VS Code |
|||
|
|||
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. |
|||
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`. |
|||
|
|||
#### 6. Cleanup |
|||
|
|||
If you wish to delete all the apps created in development and reset the environment then run the following: |
|||
|
|||
1. `yarn nuke:docker` will wipe all the Budibase services |
|||
2. `yarn dev` will restart all the services |
|||
|
|||
### Backend |
|||
|
|||
For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apache.org/), [MinIO](https://min.io/) and [NGINX](https://www.nginx.com/) in Docker compose. This means that to develop Budibase you will need Docker and Docker compose installed. The backend services are then run separately as Node services with nodemon so that they can be debugged outside of Docker. |
|||
|
|||
### Data Storage |
|||
|
|||
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` |
|||
- Sessions, email tokens |
|||
- `couchdb3_data` |
|||
- Global and app databases |
|||
- `minio_data` |
|||
- App manifest, budibase client, static assets |
|||
|
|||
### Development Modes |
|||
|
|||
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: |
|||
|
|||
#### Self Hosted |
|||
The default mode. A single tenant installation with no usage restrictions. |
|||
|
|||
To enable this mode, use: |
|||
``` |
|||
yarn mode:self |
|||
``` |
|||
|
|||
#### Cloud |
|||
The cloud mode, with account portal turned off. |
|||
|
|||
To enable this mode, use: |
|||
``` |
|||
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 |
|||
|
|||
|
|||
To enable this mode, use: |
|||
``` |
|||
yarn mode:account |
|||
``` |
|||
### CI |
|||
An overview of the CI pipelines can be found [here](./workflows/README.md) |
|||
|
|||
### 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 |
|||
|_ budibase-pro |
|||
``` |
|||
|
|||
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. |
|||
|
|||
### 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. |
|||
### Running tests |
|||
|
|||
#### End-to-end Tests |
|||
|
|||
Budibase uses Cypress to run a number of E2E tests. To run the tests execute the following command in the root folder: |
|||
|
|||
``` |
|||
yarn test:e2e |
|||
``` |
|||
|
|||
Or if you are in the builder you can run `yarn cy:test`. |
|||
|
|||
|
|||
### Other Useful Information |
|||
|
|||
* 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). |
|||
|
|||
* 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. |
|||
@ -0,0 +1,52 @@ |
|||
## Dev Environment on Debian 11 |
|||
|
|||
### Install Node |
|||
|
|||
Budibase requires a recent version of node (14+): |
|||
``` |
|||
curl -sL https://deb.nodesource.com/setup_16.x | sudo bash - |
|||
apt -y install nodejs |
|||
node -v |
|||
``` |
|||
|
|||
### Install npm requirements |
|||
|
|||
``` |
|||
npm install -g yarn jest lerna |
|||
``` |
|||
### Install Docker and Docker Compose |
|||
|
|||
``` |
|||
apt install docker.io |
|||
pip3 install docker-compose |
|||
``` |
|||
### Clone the repo |
|||
``` |
|||
git clone https://github.com/Budibase/budibase.git |
|||
``` |
|||
|
|||
### Check Versions |
|||
|
|||
This setup process was tested on Debian 11 (bullseye) with version numbers show below. Your mileage may vary using anything else. |
|||
|
|||
- Docker: 20.10.5 |
|||
- Docker-Compose: 1.29.2 |
|||
- Node: v16.15.1 |
|||
- Yarn: 1.22.19 |
|||
- Lerna: 5.1.4 |
|||
|
|||
### Build |
|||
|
|||
``` |
|||
cd budibase |
|||
yarn setup |
|||
``` |
|||
The yarn setup command runs several build steps i.e. |
|||
``` |
|||
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev |
|||
``` |
|||
So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. |
|||
|
|||
The dev version will be available on port 10000 i.e. |
|||
|
|||
http://127.0.0.1:10000/builder/admin |
|||
@ -0,0 +1,54 @@ |
|||
## Dev Environment on MAC OSX 12 (Monterey) |
|||
|
|||
### Install Homebrew |
|||
|
|||
Install instructions [here](https://brew.sh/) |
|||
|
|||
### Install Node |
|||
|
|||
Budibase requires a recent version of node (14+): |
|||
``` |
|||
brew install node npm |
|||
node -v |
|||
``` |
|||
|
|||
### Install npm requirements |
|||
|
|||
``` |
|||
npm install -g yarn jest lerna |
|||
``` |
|||
### Install Docker and Docker Compose |
|||
|
|||
``` |
|||
brew install docker docker-compose |
|||
``` |
|||
### Clone the repo |
|||
``` |
|||
git clone https://github.com/Budibase/budibase.git |
|||
``` |
|||
|
|||
### Check Versions |
|||
|
|||
This setup process was tested on Mac OSX 12 (Monterey) with version numbers shown below. Your mileage may vary using anything else. |
|||
|
|||
- Docker: 20.10.14 |
|||
- Docker-Compose: 2.6.0 |
|||
- Node: 18.3.0 |
|||
- Yarn: 1.22.19 |
|||
- Lerna: 5.1.4 |
|||
|
|||
### Build |
|||
|
|||
``` |
|||
cd budibase |
|||
yarn setup |
|||
``` |
|||
The yarn setup command runs several build steps i.e. |
|||
``` |
|||
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev |
|||
``` |
|||
So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. |
|||
|
|||
The dev version will be available on port 10000 i.e. |
|||
|
|||
http://127.0.0.1:10000/builder/admin |
|||
@ -0,0 +1,13 @@ |
|||
#!/bin/bash |
|||
CUSTOM_DOMAIN="$1" |
|||
|
|||
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then |
|||
certbot certonly --webroot --webroot-path="/var/www/html" \ |
|||
--register-unsafely-without-email \ |
|||
--domains $CUSTOM_DOMAIN \ |
|||
--rsa-key-size 4096 \ |
|||
--agree-tos \ |
|||
--force-renewal |
|||
|
|||
nginx -s reload |
|||
fi |
|||
@ -0,0 +1,23 @@ |
|||
#!/bin/bash |
|||
CUSTOM_DOMAIN="$1" |
|||
# Request from Lets Encrypt |
|||
certbot certonly --webroot --webroot-path="/var/www/html" \ |
|||
--register-unsafely-without-email \ |
|||
--domains $CUSTOM_DOMAIN \ |
|||
--rsa-key-size 4096 \ |
|||
--agree-tos \ |
|||
--force-renewal |
|||
|
|||
if (($? != 0)); then |
|||
echo "ERROR: certbot request failed for $CUSTOM_DOMAIN use http on port 80 - exiting" |
|||
exit 1 |
|||
else |
|||
cp /app/letsencrypt/options-ssl-nginx.conf /etc/letsencrypt/options-ssl-nginx.conf |
|||
cp /app/letsencrypt/ssl-dhparams.pem /etc/letsencrypt/ssl-dhparams.pem |
|||
cp /app/letsencrypt/nginx-ssl.conf /etc/nginx/sites-available/nginx-ssl.conf |
|||
sed -i "s/CUSTOM_DOMAIN/$CUSTOM_DOMAIN/g" /etc/nginx/sites-available/nginx-ssl.conf |
|||
ln -s /etc/nginx/sites-available/nginx-ssl.conf /etc/nginx/sites-enabled/nginx-ssl.conf |
|||
|
|||
echo "INFO: restart nginx after certbot request" |
|||
/etc/init.d/nginx restart |
|||
fi |
|||
@ -0,0 +1,96 @@ |
|||
server { |
|||
listen 443 ssl default_server; |
|||
listen [::]:443 ssl default_server; |
|||
server_name _; |
|||
ssl_certificate /etc/letsencrypt/live/CUSTOM_DOMAIN/fullchain.pem; |
|||
ssl_certificate_key /etc/letsencrypt/live/CUSTOM_DOMAIN/privkey.pem; |
|||
include /etc/letsencrypt/options-ssl-nginx.conf; |
|||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; |
|||
|
|||
client_max_body_size 1000m; |
|||
ignore_invalid_headers off; |
|||
proxy_buffering off; |
|||
# port_in_redirect off; |
|||
|
|||
location ^~ /.well-known/acme-challenge/ { |
|||
default_type "text/plain"; |
|||
root /var/www/html; |
|||
break; |
|||
} |
|||
location = /.well-known/acme-challenge/ { |
|||
return 404; |
|||
} |
|||
|
|||
location /app { |
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location = / { |
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location ~ ^/(builder|app_) { |
|||
proxy_http_version 1.1; |
|||
proxy_set_header Connection $connection_upgrade; |
|||
proxy_set_header Upgrade $http_upgrade; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location ~ ^/api/(system|admin|global)/ { |
|||
proxy_pass http://127.0.0.1:4002; |
|||
} |
|||
|
|||
location /worker/ { |
|||
proxy_pass http://127.0.0.1:4002; |
|||
rewrite ^/worker/(.*)$ /$1 break; |
|||
} |
|||
|
|||
location /api/ { |
|||
# calls to the API are rate limited with bursting |
|||
limit_req zone=ratelimit burst=20 nodelay; |
|||
|
|||
# 120s timeout on API requests |
|||
proxy_read_timeout 120s; |
|||
proxy_connect_timeout 120s; |
|||
proxy_send_timeout 120s; |
|||
|
|||
proxy_http_version 1.1; |
|||
proxy_set_header Connection $connection_upgrade; |
|||
proxy_set_header Upgrade $http_upgrade; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
|
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location /db/ { |
|||
proxy_pass http://127.0.0.1:5984; |
|||
rewrite ^/db/(.*)$ /$1 break; |
|||
} |
|||
|
|||
location / { |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
proxy_set_header X-Forwarded-Proto $scheme; |
|||
|
|||
proxy_connect_timeout 300; |
|||
proxy_http_version 1.1; |
|||
proxy_set_header Connection ""; |
|||
chunked_transfer_encoding off; |
|||
proxy_pass http://127.0.0.1:9000; |
|||
} |
|||
|
|||
client_header_timeout 60; |
|||
client_body_timeout 60; |
|||
keepalive_timeout 60; |
|||
|
|||
# gzip |
|||
gzip on; |
|||
gzip_vary on; |
|||
gzip_proxied any; |
|||
gzip_comp_level 6; |
|||
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; |
|||
|
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
# This file contains important security parameters. If you modify this file |
|||
# manually, Certbot will be unable to automatically provide future security |
|||
# updates. Instead, Certbot will print and log an error message with a path to |
|||
# the up-to-date file that you will need to refer to when manually updating |
|||
# this file. |
|||
|
|||
ssl_session_cache shared:le_nginx_SSL:10m; |
|||
ssl_session_timeout 1440m; |
|||
|
|||
ssl_protocols TLSv1.2 TLSv1.3; |
|||
ssl_prefer_server_ciphers off; |
|||
|
|||
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"; |
|||
@ -0,0 +1,8 @@ |
|||
-----BEGIN DH PARAMETERS----- |
|||
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz |
|||
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a |
|||
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 |
|||
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi |
|||
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD |
|||
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== |
|||
-----END DH PARAMETERS----- |
|||
@ -0,0 +1,17 @@ |
|||
#!/bin/bash |
|||
|
|||
echo ${TARGETBUILD} > /buildtarget.txt |
|||
if [[ "${TARGETBUILD}" = "aas" ]]; then |
|||
# Azure AppService uses /home for persisent data & SSH on port 2222 |
|||
mkdir -p /home/budibase/{minio,couchdb} |
|||
mkdir -p /home/budibase/couchdb/data |
|||
chown -R couchdb:couchdb /home/budibase/couchdb/ |
|||
apt update |
|||
apt-get install -y openssh-server |
|||
sed -i 's#dir=/opt/couchdb/data/search#dir=/home/budibase/couchdb/data/search#' /opt/clouseau/clouseau.ini |
|||
sed -i 's#/minio/minio server /minio &#/minio/minio server /home/budibase/minio &#' /runner.sh |
|||
sed -i 's#database_dir = ./data#database_dir = /home/budibase/couchdb/data#' /opt/couchdb/etc/default.ini |
|||
sed -i 's#view_index_dir = ./data#view_index_dir = /home/budibase/couchdb/data#' /opt/couchdb/etc/default.ini |
|||
sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config |
|||
/etc/init.d/ssh restart |
|||
fi |
|||
@ -0,0 +1,40 @@ |
|||
#!/usr/bin/env bash |
|||
healthy=true |
|||
|
|||
if [[ $(curl -Lfk -s -w "%{http_code}\n" http://localhost/ -o /dev/null) -ne 200 ]]; then |
|||
echo 'ERROR: Budibase is not running'; |
|||
healthy=false |
|||
fi |
|||
|
|||
if [[ $(curl -s -w "%{http_code}\n" http://localhost:4001/health -o /dev/null) -ne 200 ]]; then |
|||
echo 'ERROR: Budibase backend is not running'; |
|||
healthy=false |
|||
fi |
|||
|
|||
if [[ $(curl -s -w "%{http_code}\n" http://localhost:4002/health -o /dev/null) -ne 200 ]]; then |
|||
echo 'ERROR: Budibase worker is not running'; |
|||
healthy=false |
|||
fi |
|||
|
|||
if [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/ -o /dev/null) -ne 200 ]]; then |
|||
echo 'ERROR: CouchDB is not running'; |
|||
healthy=false |
|||
fi |
|||
if [[ $(redis-cli -a $REDIS_PASSWORD --no-auth-warning ping) != 'PONG' ]]; then |
|||
echo 'ERROR: Redis is down'; |
|||
healthy=false |
|||
fi |
|||
# mino, clouseau, |
|||
nginx -t -q |
|||
NGINX_STATUS=$? |
|||
|
|||
if [[ $NGINX_STATUS -gt 0 ]]; then |
|||
echo 'ERROR: Nginx config problem'; |
|||
healthy=false |
|||
fi |
|||
|
|||
if [ $healthy == true ]; then |
|||
exit 0 |
|||
else |
|||
exit 1 |
|||
fi |
|||
@ -0,0 +1,112 @@ |
|||
# Docker Single Image for Budibase |
|||
|
|||
## Overview |
|||
As an alternative to running several docker containers via docker-compose, the files under ./hosting/single can be used to build a docker image containing all of the Budibase components (minio, couch, clouseau etc). |
|||
We call this the 'single image' container as the Dockerfile adds all the components to a single docker image. |
|||
|
|||
## Usage |
|||
|
|||
- Amend Environment Variables |
|||
- Build Requirements |
|||
- Build the Image |
|||
- Run the Container |
|||
|
|||
### Amend Environment Variables |
|||
|
|||
Edit the Dockerfile in this directory amending the environment variables to suit your usage. Pay particular attention to changing passwords. |
|||
The CUSTOM_DOMAIN variable will be used to request a certificate from LetsEncrypt and if successful you can point traffic to port 443. If you choose to use the CUSTOM_DOMAIN variable ensure that the DNS for your custom domain points to the public IP address where you are running Budibase - otherwise the certificate issuance will fail. |
|||
If you have other arrangements for a proxy in front of the single image container you can omit the CUSTOM_DOMAIN environment variable and the request to LetsEncrypt will be skipped. You can then point traffic to port 80. |
|||
|
|||
### Build Requirements |
|||
We would suggest building the image with 6GB of RAM and 20GB of free disk space for build artifacts. The resulting image size will use approx 2GB of disk space. |
|||
|
|||
### 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:single |
|||
``` |
|||
If the docker build step fails try running that step again manually with: |
|||
``` |
|||
docker build --build-arg TARGETARCH=amd --no-cache -t budibase:latest -f ./hosting/single/Dockerfile . |
|||
``` |
|||
|
|||
#### Azure App Services |
|||
Azure have some specific requirements for running a container in their App Service. Specifically, installation of SSH to port 2222 and data storage under /home. If you would like to build a budibase container for Azure App Service add the build argument shown below setting it to 'aas'. You can remove the CUSTOM_DOMAIN env variable from the Dockerfile too as Azure terminate SSL before requests reach the container. |
|||
``` |
|||
docker build --build-arg TARGETARCH=amd --build-arg TARGETBUILD=aas -t budibase:latest -f ./hosting/single/Dockerfile . |
|||
``` |
|||
|
|||
### Run the Container |
|||
``` |
|||
docker run -d -p 80:80 -p 443:443 --name budibase budibase:latest |
|||
``` |
|||
Where: |
|||
- -d runs the container in detached mode |
|||
- -p forwards ports from your host to the ports inside the container. If you are already using port 80 on your host for something else you can try running with an alternative port e.g. `-p 8080:80` |
|||
- --name is the name for the container as shown in `docker ps` and can be used with other docker commands e.g. `docker restart budibase` |
|||
|
|||
When the container runs you should be able to access the container over http at your host address e.g. http://1.2.3.4/ or using your custom domain e.g. https://my.custom.domain/ |
|||
|
|||
When the Budibase UI appears you will be prompted to create an account to get started. |
|||
|
|||
### Podman |
|||
The single image container builds fine when using podman in place of docker. You may be prompted for the registry to use for the CouchDB image and the HEALTHCHECK parameter is not OCI compliant so is ignored. |
|||
|
|||
### Check |
|||
There are many things that could go wrong so if your container is not building or running as expected please check the following before opening a support issue. |
|||
Verify the healthcheck status of the container: |
|||
``` |
|||
docker ps |
|||
``` |
|||
Check the container logs: |
|||
``` |
|||
docker logs budibase |
|||
``` |
|||
|
|||
### Support |
|||
This single image build is still a work-in-progress so if you open an issue please provide the following information: |
|||
- The OS and OS version you are building on |
|||
- The versions you are using of docker, docker-compose, yarn, node, lerna |
|||
- For build errors please provide zipped output |
|||
- For container errors please provide zipped container logs |
|||
@ -0,0 +1,94 @@ |
|||
server { |
|||
listen 80 default_server; |
|||
listen [::]:80 default_server; |
|||
server_name _; |
|||
|
|||
client_max_body_size 1000m; |
|||
ignore_invalid_headers off; |
|||
proxy_buffering off; |
|||
# port_in_redirect off; |
|||
|
|||
location ^~ /.well-known/acme-challenge/ { |
|||
default_type "text/plain"; |
|||
root /var/www/html; |
|||
break; |
|||
} |
|||
location = /.well-known/acme-challenge/ { |
|||
return 404; |
|||
} |
|||
|
|||
location /app { |
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location = / { |
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location ~ ^/(builder|app_) { |
|||
proxy_http_version 1.1; |
|||
proxy_set_header Connection $connection_upgrade; |
|||
proxy_set_header Upgrade $http_upgrade; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location ~ ^/api/(system|admin|global)/ { |
|||
proxy_pass http://127.0.0.1:4002; |
|||
} |
|||
|
|||
location /worker/ { |
|||
proxy_pass http://127.0.0.1:4002; |
|||
rewrite ^/worker/(.*)$ /$1 break; |
|||
} |
|||
|
|||
location /api/ { |
|||
# calls to the API are rate limited with bursting |
|||
limit_req zone=ratelimit burst=20 nodelay; |
|||
|
|||
# 120s timeout on API requests |
|||
proxy_read_timeout 120s; |
|||
proxy_connect_timeout 120s; |
|||
proxy_send_timeout 120s; |
|||
|
|||
proxy_http_version 1.1; |
|||
proxy_set_header Connection $connection_upgrade; |
|||
proxy_set_header Upgrade $http_upgrade; |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
|
|||
proxy_pass http://127.0.0.1:4001; |
|||
} |
|||
|
|||
location /db/ { |
|||
proxy_pass http://127.0.0.1:5984; |
|||
rewrite ^/db/(.*)$ /$1 break; |
|||
} |
|||
|
|||
location / { |
|||
proxy_set_header X-Real-IP $remote_addr; |
|||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
|||
proxy_set_header X-Forwarded-Proto $scheme; |
|||
|
|||
proxy_connect_timeout 300; |
|||
proxy_http_version 1.1; |
|||
proxy_set_header Connection ""; |
|||
chunked_transfer_encoding off; |
|||
proxy_pass http://127.0.0.1:9000; |
|||
} |
|||
|
|||
client_header_timeout 60; |
|||
client_body_timeout 60; |
|||
keepalive_timeout 60; |
|||
|
|||
# gzip |
|||
gzip on; |
|||
gzip_vary on; |
|||
gzip_proxied any; |
|||
gzip_comp_level 6; |
|||
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
#!/bin/bash |
|||
id=$(docker run -t -d -p 80:80 budibase:latest) |
|||
docker exec -it $id bash |
|||
docker kill $id |
|||
@ -1,41 +0,0 @@ |
|||
exports.SEPARATOR = "_" |
|||
|
|||
const PRE_APP = "app" |
|||
const PRE_DEV = "dev" |
|||
|
|||
exports.DocumentTypes = { |
|||
USER: "us", |
|||
WORKSPACE: "workspace", |
|||
CONFIG: "config", |
|||
TEMPLATE: "template", |
|||
APP: PRE_APP, |
|||
DEV: PRE_DEV, |
|||
APP_DEV: `${PRE_APP}${exports.SEPARATOR}${PRE_DEV}`, |
|||
APP_METADATA: `${PRE_APP}${exports.SEPARATOR}metadata`, |
|||
ROLE: "role", |
|||
MIGRATIONS: "migrations", |
|||
DEV_INFO: "devinfo", |
|||
} |
|||
|
|||
exports.StaticDatabases = { |
|||
GLOBAL: { |
|||
name: "global-db", |
|||
docs: { |
|||
apiKeys: "apikeys", |
|||
usageQuota: "usage_quota", |
|||
licenseInfo: "license_info", |
|||
}, |
|||
}, |
|||
// contains information about tenancy and so on
|
|||
PLATFORM_INFO: { |
|||
name: "global-info", |
|||
docs: { |
|||
tenants: "tenants", |
|||
install: "install", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
exports.APP_PREFIX = exports.DocumentTypes.APP + exports.SEPARATOR |
|||
exports.APP_DEV = exports.APP_DEV_PREFIX = |
|||
exports.DocumentTypes.APP_DEV + exports.SEPARATOR |
|||
@ -0,0 +1,58 @@ |
|||
export const SEPARATOR = "_" |
|||
export const UNICODE_MAX = "\ufff0" |
|||
|
|||
/** |
|||
* Can be used to create a few different forms of querying a view. |
|||
*/ |
|||
export enum AutomationViewModes { |
|||
ALL = "all", |
|||
AUTOMATION = "automation", |
|||
STATUS = "status", |
|||
} |
|||
|
|||
export enum ViewNames { |
|||
USER_BY_EMAIL = "by_email", |
|||
BY_API_KEY = "by_api_key", |
|||
USER_BY_BUILDERS = "by_builders", |
|||
LINK = "by_link", |
|||
ROUTING = "screen_routes", |
|||
AUTOMATION_LOGS = "automation_logs", |
|||
} |
|||
|
|||
export enum DocumentTypes { |
|||
USER = "us", |
|||
WORKSPACE = "workspace", |
|||
CONFIG = "config", |
|||
TEMPLATE = "template", |
|||
APP = "app", |
|||
DEV = "dev", |
|||
APP_DEV = "app_dev", |
|||
APP_METADATA = "app_metadata", |
|||
ROLE = "role", |
|||
MIGRATIONS = "migrations", |
|||
DEV_INFO = "devinfo", |
|||
AUTOMATION_LOG = "log_au", |
|||
} |
|||
|
|||
export const StaticDatabases = { |
|||
GLOBAL: { |
|||
name: "global-db", |
|||
docs: { |
|||
apiKeys: "apikeys", |
|||
usageQuota: "usage_quota", |
|||
licenseInfo: "license_info", |
|||
}, |
|||
}, |
|||
// contains information about tenancy and so on
|
|||
PLATFORM_INFO: { |
|||
name: "global-info", |
|||
docs: { |
|||
tenants: "tenants", |
|||
install: "install", |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
export const APP_PREFIX = exports.DocumentTypes.APP + exports.SEPARATOR |
|||
export const APP_DEV = exports.DocumentTypes.APP_DEV + exports.SEPARATOR |
|||
export const APP_DEV_PREFIX = APP_DEV |
|||
@ -0,0 +1,62 @@ |
|||
require("../../../tests/utilities/TestConfiguration") |
|||
const getUrlInfo = require("../pouch").getUrlInfo |
|||
|
|||
describe("pouch", () => { |
|||
describe("Couch DB URL parsing", () => { |
|||
it("should handle a null Couch DB URL", () => { |
|||
const info = getUrlInfo(null) |
|||
expect(info.url).toBeUndefined() |
|||
expect(info.auth.username).toBeUndefined() |
|||
}) |
|||
it("should be able to parse a basic Couch DB URL", () => { |
|||
const info = getUrlInfo("http://host.com") |
|||
expect(info.url).toBe("http://host.com") |
|||
expect(info.auth.username).toBeUndefined() |
|||
}) |
|||
it("should be able to parse a Couch DB basic URL with HTTPS", () => { |
|||
const info = getUrlInfo("https://host.com") |
|||
expect(info.url).toBe("https://host.com") |
|||
expect(info.auth.username).toBeUndefined() |
|||
}) |
|||
it("should be able to parse a basic Couch DB URL with a custom port", () => { |
|||
const info = getUrlInfo("https://host.com:1234") |
|||
expect(info.url).toBe("https://host.com:1234") |
|||
expect(info.auth.username).toBeUndefined() |
|||
}) |
|||
it("should be able to parse a Couch DB URL with auth", () => { |
|||
const info = getUrlInfo("https://user:pass@host.com:1234") |
|||
expect(info.url).toBe("https://host.com:1234") |
|||
expect(info.auth.username).toBe("user") |
|||
expect(info.auth.password).toBe("pass") |
|||
}) |
|||
it("should be able to parse a Couch DB URL with auth and special chars", () => { |
|||
const info = getUrlInfo("https://user:s:p@s://@://:d@;][~s@host.com:1234") |
|||
expect(info.url).toBe("https://host.com:1234") |
|||
expect(info.auth.username).toBe("user") |
|||
expect(info.auth.password).toBe("s:p@s://@://:d@;][~s") |
|||
}) |
|||
it("should be able to parse a Couch DB URL without a protocol", () => { |
|||
const info = getUrlInfo("host.com:1234") |
|||
expect(info.url).toBe("http://host.com:1234") |
|||
expect(info.auth.username).toBeUndefined() |
|||
}) |
|||
it("should be able to parse a Couch DB URL with auth and without a protocol", () => { |
|||
const info = getUrlInfo("user:s:p@s://@://:d@;][~s@host.com:1234") |
|||
expect(info.url).toBe("http://host.com:1234") |
|||
expect(info.auth.username).toBe("user") |
|||
expect(info.auth.password).toBe("s:p@s://@://:d@;][~s") |
|||
}) |
|||
it("should be able to parse a Couch DB URL with only username auth", () => { |
|||
const info = getUrlInfo("https://user@host.com:1234") |
|||
expect(info.url).toBe("https://host.com:1234") |
|||
expect(info.auth.username).toBe("user") |
|||
expect(info.auth.password).toBeUndefined() |
|||
}) |
|||
it("should be able to parse a Couch DB URL with only username auth and without a protocol", () => { |
|||
const info = getUrlInfo("user@host.com:1234") |
|||
expect(info.url).toBe("http://host.com:1234") |
|||
expect(info.auth.username).toBe("user") |
|||
expect(info.auth.password).toBeUndefined() |
|||
}) |
|||
}) |
|||
}) |
|||
@ -0,0 +1,131 @@ |
|||
import filterTests from "../../support/filterTests" |
|||
const interact = require('../../support/interact') |
|||
|
|||
filterTests(["smoke", "all"], () => { |
|||
context("Account Portals", () => { |
|||
|
|||
const bbUserEmail = "bbuser@test.com" |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
cy.deleteApp("Cypress Tests") |
|||
cy.createApp("Cypress Tests") |
|||
|
|||
// Create new user
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 1000}) |
|||
cy.createUser(bbUserEmail) |
|||
cy.contains("bbuser").click() |
|||
cy.wait(500) |
|||
|
|||
// Reset password
|
|||
cy.get(".spectrum-ActionButton-label", { timeout: 2000 }).contains("Force password reset").click({ force: true }) |
|||
|
|||
cy.get(".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() |
|||
}) |
|||
|
|||
it("should verify Admin Portal", () => { |
|||
cy.login() |
|||
cy.contains("Users").click() |
|||
cy.contains("bbuser").click() |
|||
|
|||
// Enable Development & Administration access
|
|||
cy.wait(500) |
|||
for (let i = 4; i < 6; i++) { |
|||
cy.get(interact.FIELD).eq(i).within(() => { |
|||
cy.get(interact.SPECTRUM_SWITCH_INPUT).click({ force: true }) |
|||
cy.get(interact.SPECTRUM_SWITCH_INPUT).should('be.enabled') |
|||
}) |
|||
} |
|||
bbUserLogin() |
|||
|
|||
// Verify available options for Admin portal
|
|||
cy.get(".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() |
|||
}) |
|||
|
|||
it("should verify Development Portal", () => { |
|||
// Only Development access should be enabled
|
|||
cy.login() |
|||
cy.contains("Users").click() |
|||
cy.contains("bbuser").click() |
|||
cy.wait(500) |
|||
cy.get(interact.FIELD).eq(5).within(() => { |
|||
cy.get(interact.SPECTRUM_SWITCH_INPUT).click({ force: true }) |
|||
}) |
|||
|
|||
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() |
|||
}) |
|||
|
|||
it("should verify Standard Portal", () => { |
|||
// Development access should be disabled (Admin access is already disabled)
|
|||
cy.login() |
|||
cy.contains("Users").click() |
|||
cy.contains("bbuser").click() |
|||
cy.wait(500) |
|||
cy.get(interact.FIELD).eq(4).within(() => { |
|||
cy.get(interact.SPECTRUM_SWITCH_INPUT).click({ force: true }) |
|||
}) |
|||
|
|||
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() |
|||
}) |
|||
|
|||
const bbUserLogin = () => { |
|||
// Login as bbuser
|
|||
cy.logOut() |
|||
cy.login(bbUserEmail, "test") |
|||
} |
|||
|
|||
after(() => { |
|||
cy.login() |
|||
// Delete BB user
|
|||
cy.deleteUser(bbUserEmail) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -0,0 +1,234 @@ |
|||
import filterTests from "../../support/filterTests" |
|||
const interact = require('../../support/interact') |
|||
|
|||
filterTests(["smoke", "all"], () => { |
|||
context("User Management", () => { |
|||
before(() => { |
|||
cy.login() |
|||
cy.deleteApp("Cypress Tests") |
|||
cy.createApp("Cypress Tests") |
|||
}) |
|||
|
|||
it("should create a user via basic onboarding", () => { |
|||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 1000}) |
|||
cy.createUser("bbuser@test.com") |
|||
cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser") |
|||
}) |
|||
|
|||
it("should confirm basic permission for a New User", () => { |
|||
// Basic permission = development & administraton disabled
|
|||
cy.contains("bbuser").click() |
|||
// Confirm development and admin access are disabled
|
|||
for (let i = 4; i < 6; i++) { |
|||
cy.wait(500) |
|||
cy.get(interact.FIELD).eq(i).within(() => { |
|||
//cy.get(interact.SPECTRUM_SWITCH_INPUT).should('be.disabled')
|
|||
cy.get(".spectrum-Switch-switch").should('not.be.checked') |
|||
}) |
|||
} |
|||
// Existing apps appear within the No Access table
|
|||
cy.get(interact.SPECTRUM_TABLE, { timeout: 500 }).eq(1).should("not.contain", "No rows found") |
|||
// Configure roles table should not contain apps
|
|||
cy.get(interact.SPECTRUM_TABLE).eq(0).contains("No rows found") |
|||
}) |
|||
|
|||
if (Cypress.env("TEST_ENV")) { |
|||
it("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) |
|||
} else { |
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
cy.get(interact.CREATE_APP_BUTTON, { timeout: 1000 }).click({ force: true }) |
|||
cy.createAppFromScratch(name) |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
// Navigate back to the user
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 500}) |
|||
cy.get(interact.SPECTRUM_SIDENAV).contains("Users").click() |
|||
cy.get(interact.SPECTRUM_TABLE, { timeout: 500 }).contains("bbuser").click() |
|||
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: 500 }) |
|||
.contains("Choose an option") |
|||
.click() |
|||
.then(() => { |
|||
if (i == 0) { |
|||
cy.get(interact.SPECTRUM_MENU, { timeout: 1000 }).contains("Admin").click({ force: true }) |
|||
} |
|||
else if (i == 1) { |
|||
cy.get(interact.SPECTRUM_MENU, { timeout: 1000 }).contains("Power").click({ force: true }) |
|||
} |
|||
else if (i == 2) { |
|||
cy.get(interact.SPECTRUM_MENU, { timeout: 1000 }).contains("Basic").click({ force: true }) |
|||
} |
|||
cy.get(interact.SPECTRUM_BUTTON, { timeout: 1000 }) |
|||
.contains("Update role") |
|||
.click({ force: true }) |
|||
}) |
|||
cy.reload() |
|||
cy.wait(1000) |
|||
} |
|||
// Confirm roles exist within Configure roles table
|
|||
cy.get(interact.SPECTRUM_TABLE, { timeout: 2000 }) |
|||
.eq(0) |
|||
.within(assginedRoles => { |
|||
expect(assginedRoles).to.contain("Admin") |
|||
expect(assginedRoles).to.contain("Power") |
|||
expect(assginedRoles).to.contain("Basic") |
|||
}) |
|||
}) |
|||
|
|||
it("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") |
|||
}) |
|||
} |
|||
|
|||
it("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) |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
it("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") |
|||
}) |
|||
}) |
|||
|
|||
it("Should edit user details within user details page", () => { |
|||
// Add First name
|
|||
cy.get(interact.FIELD, { timeout: 500 }).eq(2).within(() => { |
|||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 500 }).type("bb") |
|||
}) |
|||
// Add Last name
|
|||
cy.get(interact.FIELD).eq(3).within(() => { |
|||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).type("test") |
|||
}) |
|||
cy.get(interact.FIELD).eq(0).click() |
|||
// Reload page
|
|||
cy.reload() |
|||
|
|||
// Confirm details have been saved
|
|||
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => { |
|||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb") |
|||
}) |
|||
cy.get(interact.FIELD).eq(3).within(() => { |
|||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 500 }).should('have.value', "test") |
|||
}) |
|||
}) |
|||
|
|||
it("should reset the users password", () => { |
|||
cy.get(interact.REGENERATE, { timeout: 500 }).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 }) |
|||
|
|||
// 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.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() |
|||
}) |
|||
|
|||
it("should delete a user", () => { |
|||
cy.deleteUser("bbuser@test.com") |
|||
cy.get(interact.SPECTRUM_TABLE, { timeout: 4000 }).should("not.have.text", "bbuser") |
|||
}) |
|||
}) |
|||
}) |
|||
@ -0,0 +1,108 @@ |
|||
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(2).within(() => { |
|||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname) |
|||
}) |
|||
cy.get(interact.FIELD).eq(3).within(() => { |
|||
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname) |
|||
}) |
|||
}) |
|||
|
|||
it("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", () => { |
|||
// 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("Re-generate 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 .avatar > .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") |
|||
}) |
|||
|
|||
it("should open and close developer mode", () => { |
|||
cy.get(".user-dropdown .avatar > .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(interact.SPECTRUM_SIDENAV).should('exist') // config sections available
|
|||
cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available
|
|||
cy.get(interact.APP_TABLE).should('exist') // App table available
|
|||
}) |
|||
|
|||
after(() => { |
|||
// Change password back to original value
|
|||
cy.get(".user-dropdown .avatar > .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 }) |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,44 +0,0 @@ |
|||
import filterTests from "../support/filterTests" |
|||
|
|||
filterTests(['all'], () => { |
|||
context("Change Application Icon and Colour", () => { |
|||
before(() => { |
|||
cy.login() |
|||
}) |
|||
|
|||
it("should change the icon and colour for an application", () => { |
|||
// Search for test application
|
|||
cy.applicationInAppTable("Cypress Tests") |
|||
cy.get(".appTable") |
|||
.within(() => { |
|||
cy.get(".app-row-actions-icon").eq(0).click() |
|||
}) |
|||
cy.get(".spectrum-Menu").contains("Edit icon").click() |
|||
// Select random icon
|
|||
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) |
|||
cy.wait(1000) |
|||
// Confirm icon has changed from default
|
|||
// Confirm colour has been applied - There is no default colour
|
|||
cy.get(".appTable") |
|||
.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') |
|||
}) |
|||
cy.deleteAllApps() |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,194 +0,0 @@ |
|||
import filterTests from "../support/filterTests" |
|||
|
|||
filterTests(["smoke", "all"], () => { |
|||
context("Create a User and Assign Roles", () => { |
|||
before(() => { |
|||
cy.login() |
|||
cy.deleteApp("Cypress Tests") |
|||
cy.createApp("Cypress Tests") |
|||
}) |
|||
|
|||
it("should create a user", () => { |
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
cy.wait(1000) |
|||
cy.createUser("bbuser@test.com") |
|||
cy.get(".spectrum-Table").should("contain", "bbuser") |
|||
}) |
|||
|
|||
it("should confirm there is No Access for a New User", () => { |
|||
// Click into the user
|
|||
cy.contains("bbuser").click() |
|||
cy.wait(500) |
|||
// Get No Access table - Confirm it has apps in it
|
|||
cy.get(".spectrum-Table").eq(1).should("not.contain", "No rows found") |
|||
// Get Configure Roles table - Confirm it has no apps
|
|||
cy.get(".spectrum-Table").eq(0).contains("No rows found") |
|||
}) |
|||
|
|||
if (Cypress.env("TEST_ENV")) { |
|||
it("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) |
|||
} else { |
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
cy.wait(500) |
|||
cy.get(`[data-cy="create-app-btn"]`).click({ force: true }) |
|||
cy.createAppFromScratch(name) |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
// Navigate back to the user
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder`) |
|||
cy.wait(500) |
|||
cy.get(".spectrum-SideNav").contains("Users").click() |
|||
cy.wait(500) |
|||
cy.get(".spectrum-Table").contains("bbuser").click() |
|||
cy.wait(1000) |
|||
for (let i = 0; i < 3; i++) { |
|||
cy.get(".spectrum-Table", { timeout: 3000}) |
|||
.eq(1) |
|||
.find(".spectrum-Table-row") |
|||
.eq(0) |
|||
.find(".spectrum-Table-cell") |
|||
.eq(0) |
|||
.click() |
|||
cy.wait(500) |
|||
cy.get(".spectrum-Dialog-grid") |
|||
.contains("Choose an option") |
|||
.click() |
|||
.then(() => { |
|||
cy.wait(1000) |
|||
if (i == 0) { |
|||
cy.get(".spectrum-Menu").contains("Admin").click({ force: true }) |
|||
} |
|||
else if (i == 1) { |
|||
cy.get(".spectrum-Menu").contains("Power").click({ force: true }) |
|||
} |
|||
else if (i == 2) { |
|||
cy.get(".spectrum-Menu").contains("Basic").click({ force: true }) |
|||
} |
|||
cy.wait(1000) |
|||
cy.get(".spectrum-Button") |
|||
.contains("Update role") |
|||
.click({ force: true }) |
|||
}) |
|||
cy.reload() |
|||
} |
|||
// Confirm roles exist within Configure roles table
|
|||
cy.wait(2000) |
|||
cy.get(".spectrum-Table") |
|||
.eq(0) |
|||
.within(assginedRoles => { |
|||
expect(assginedRoles).to.contain("Admin") |
|||
expect(assginedRoles).to.contain("Power") |
|||
expect(assginedRoles).to.contain("Basic") |
|||
}) |
|||
}) |
|||
|
|||
it("should unassign role types", () => { |
|||
// Set each app within Configure roles table to 'No Access'
|
|||
cy.get(".spectrum-Table") |
|||
.eq(0) |
|||
.find(".spectrum-Table-row") |
|||
.its("length") |
|||
.then(len => { |
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".spectrum-Table") |
|||
.eq(0) |
|||
.find(".spectrum-Table-row") |
|||
.eq(0) |
|||
.find(".spectrum-Table-cell") |
|||
.eq(0) |
|||
.click() |
|||
.then(() => { |
|||
cy.get(".spectrum-Picker").eq(1).click({ force: true }) |
|||
cy.wait(500) |
|||
cy.get(".spectrum-Popover").contains("No Access").click() |
|||
}) |
|||
cy.get(".spectrum-Button") |
|||
.contains("Update role") |
|||
.click({ force: true }) |
|||
cy.wait(1000) |
|||
} |
|||
}) |
|||
// Confirm Configure roles table no longer has any apps in it
|
|||
cy.get(".spectrum-Table").eq(0).contains("No rows found") |
|||
}) |
|||
} |
|||
|
|||
it("should enable Developer access", () => { |
|||
// Enable Developer access
|
|||
cy.get(".field") |
|||
.eq(4) |
|||
.within(() => { |
|||
cy.get(".spectrum-Switch-input").click({ force: true }) |
|||
}) |
|||
// No Access table should now be empty
|
|||
cy.get(".container") |
|||
.contains("No Access") |
|||
.parent() |
|||
.within(() => { |
|||
cy.get(".spectrum-Table").contains("No rows found") |
|||
}) |
|||
|
|||
// Each app within Configure roles should have Admin access
|
|||
cy.get(".spectrum-Table") |
|||
.eq(0) |
|||
.find(".spectrum-Table-row") |
|||
.its("length") |
|||
.then(len => { |
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".spectrum-Table") |
|||
.eq(0) |
|||
.find(".spectrum-Table-row") |
|||
.eq(i) |
|||
.contains("Admin") |
|||
cy.wait(500) |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
it("should disable Developer access", () => { |
|||
// Disable Developer access
|
|||
cy.get(".field") |
|||
.eq(4) |
|||
.within(() => { |
|||
cy.get(".spectrum-Switch-input").click({ force: true }) |
|||
}) |
|||
// Configure roles table should now be empty
|
|||
cy.get(".container") |
|||
.contains("Configure roles") |
|||
.parent() |
|||
.within(() => { |
|||
cy.get(".spectrum-Table").contains("No rows found") |
|||
}) |
|||
}) |
|||
|
|||
it("should delete a user", () => { |
|||
// Click Delete user button
|
|||
cy.get(".spectrum-Button") |
|||
.contains("Delete user") |
|||
.click({ force: true }) |
|||
.then(() => { |
|||
// Confirm deletion within modal
|
|||
cy.wait(500) |
|||
cy.get(".spectrum-Dialog-grid").within(() => { |
|||
cy.get(".spectrum-Button") |
|||
.contains("Delete user") |
|||
.click({ force: true }) |
|||
cy.wait(4000) |
|||
}) |
|||
}) |
|||
cy.get(".spectrum-Table").should("not.have.text", "bbuser") |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,56 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify HR Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter HR Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="HR"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for HR templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
|
|||
if (templateNameText == "Job Application Tracker") { |
|||
// Template name should include 'applicant-tracking-system'
|
|||
cy.get('a') |
|||
.should('have.attr', 'href').and('contain', 'applicant-tracking-system') |
|||
} |
|||
else if (templateNameText == "Job Portal App") { |
|||
// Template name should include 'job-portal'
|
|||
const templateNameSplit = templateNameParsed.split('-app')[0] |
|||
cy.get('a') |
|||
.should('have.attr', 'href').and('contain', templateNameSplit) |
|||
} |
|||
else { |
|||
cy.get('a') |
|||
.should('have.attr', 'href').and('contain', templateNameParsed) |
|||
} |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,222 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Job Application Tracker Template Functionality", () => { |
|||
const templateName = "Job Application Tracker" |
|||
const templateNameParsed = templateName.toLowerCase().replace(/\s+/g, '-') |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
cy.deleteApp(templateName) |
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`, { |
|||
onBeforeLoad(win) { |
|||
cy.stub(win, 'open') |
|||
} |
|||
}) |
|||
cy.wait(2000) |
|||
}) |
|||
|
|||
it("should create and publish app with Job Application Tracker template", () => { |
|||
// Select Job Application Tracker template
|
|||
cy.get(".template-thumbnail-text") |
|||
.contains(templateName).parentsUntil(".template-grid").within(() => { |
|||
cy.get(".spectrum-Button").contains("Use template").click({ force: true }) |
|||
}) |
|||
|
|||
// Confirm URL matches template name
|
|||
const appUrl = cy.get(".app-server") |
|||
appUrl.invoke('text').then(appUrlText => { |
|||
expect(appUrlText).to.equal(`${Cypress.config().baseUrl}/app/` + templateNameParsed) |
|||
}) |
|||
|
|||
// Create App
|
|||
cy.get(".spectrum-Dialog-grid").within(() => { |
|||
cy.get(".spectrum-Button").contains("Create app").click({ force: true }) |
|||
}) |
|||
|
|||
// Publish App & Verify it opened
|
|||
cy.wait(2000) // Wait for app to generate
|
|||
cy.publishApp(true) |
|||
cy.window().its('open').should('be.calledOnce') |
|||
}) |
|||
|
|||
it("should add active/inactive vacancies", () => { |
|||
// Visit published app
|
|||
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed) |
|||
|
|||
// loop for active/inactive vacancies
|
|||
for (let i = 0; i < 2; i++) { |
|||
// Vacancies section
|
|||
cy.get(".links").contains("Vacancies").click({ force: true }) |
|||
cy.get(".spectrum-Button").contains("Create New").click() |
|||
|
|||
// Add inactive vacancy
|
|||
// Title
|
|||
cy.get('[data-name="Title"]').within(() => { |
|||
cy.get(".spectrum-Textfield").type("Tester") |
|||
}) |
|||
|
|||
// Closing Date
|
|||
cy.get('[data-name="Closing date"]').within(() => { |
|||
cy.get('[aria-label=Calendar]').click({ force: true }) |
|||
}) |
|||
cy.get("[aria-current=date]").click() |
|||
|
|||
// Department
|
|||
cy.get('[data-name="Department"]').within(() => { |
|||
cy.get(".spectrum-Picker-label").click() |
|||
}) |
|||
cy.get(".spectrum-Menu").find('li').its('length').then(len => { |
|||
cy.get(".spectrum-Menu-item").eq(Math.floor(Math.random() * len)).click() |
|||
}) |
|||
|
|||
// Employment Type
|
|||
cy.get('[data-name="Employment type"]').within(() => { |
|||
cy.get(".spectrum-Picker-label").click() |
|||
}) |
|||
cy.get(".spectrum-Menu").find('li').its('length').then(len => { |
|||
cy.get(".spectrum-Menu-item").eq(Math.floor(Math.random() * len)).click() |
|||
}) |
|||
|
|||
// Salary
|
|||
cy.get('[data-name="Salary ($)"]').within(() => { |
|||
cy.get(".spectrum-Textfield").type(40000) |
|||
}) |
|||
|
|||
// Description
|
|||
cy.get('[data-name="Description"]').within(() => { |
|||
cy.get(".spectrum-Textfield").type("description") |
|||
}) |
|||
|
|||
// Responsibilities
|
|||
cy.get('[data-name="Responsibilities"]').within(() => { |
|||
cy.get(".spectrum-Textfield").type("Responsibilities") |
|||
}) |
|||
|
|||
// Requirements
|
|||
cy.get('[data-name="Requirements"]').within(() => { |
|||
cy.get(".spectrum-Textfield").type("Requirements") |
|||
}) |
|||
|
|||
// Hiring manager
|
|||
cy.get('[data-name="Hiring manager"]').within(() => { |
|||
cy.get(".spectrum-Picker-label").click() |
|||
}) |
|||
cy.get(".spectrum-Menu").find('li').its('length').then(len => { |
|||
cy.get(".spectrum-Menu-item").eq(Math.floor(Math.random() * len)).click() |
|||
}) |
|||
|
|||
// Active
|
|||
if (i == 0) { |
|||
cy.get('[data-name="Active"]').within(() => { |
|||
cy.get(".spectrum-Checkbox-box").click({ force: true }) |
|||
}) |
|||
} |
|||
|
|||
// Location
|
|||
cy.get('[data-name="Location"]').within(() => { |
|||
cy.get(".spectrum-Picker-label").click() |
|||
}) |
|||
cy.get(".spectrum-Menu").find('li').its('length').then(len => { |
|||
cy.get(".spectrum-Menu-item").eq(Math.floor(Math.random() * len)).click() |
|||
}) |
|||
|
|||
// Save vacancy
|
|||
cy.get(".spectrum-Button").contains("Save").click({ force: true }) |
|||
cy.wait(1000) |
|||
|
|||
// Check table was updated
|
|||
cy.get('[data-name="Vacancies Table"]').eq(i).should('contain', 'Tester') |
|||
} |
|||
}) |
|||
|
|||
xit("should filter applications by stage", () => { |
|||
// Visit published app
|
|||
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed) |
|||
cy.wait(1000) |
|||
|
|||
// Applications section
|
|||
cy.get(".links").contains("Applications").click({ force: true }) |
|||
cy.wait(1000) |
|||
|
|||
// Filter by stage - Confirm table updates
|
|||
cy.get(".spectrum-Picker").contains("Filter by stage").click({ force: true }) |
|||
cy.get(".spectrum-Menu").find('li').its('length').then(len => { |
|||
for (let i = 1; i < len; i++) { |
|||
cy.get(".spectrum-Menu-item").eq(i).click() |
|||
const stage = cy.get(".spectrum-Picker-label") |
|||
stage.invoke('text').then(stageText => { |
|||
if (stageText == "1st interview") { |
|||
cy.get(".placeholder").should('contain', 'No rows found') |
|||
} |
|||
else { |
|||
cy.get(".spectrum-Table-row").should('contain', stageText) |
|||
} |
|||
cy.get(".spectrum-Picker").contains(stageText).click({ force: true }) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
xit("should edit an application", () => { |
|||
// Switch application from not hired to hired
|
|||
// Visit published app
|
|||
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed) |
|||
cy.wait(1000) |
|||
|
|||
// Not Hired section
|
|||
cy.get(".links").contains("Not hired").click({ force: true }) |
|||
cy.wait(500) |
|||
|
|||
// View application
|
|||
cy.get(".spectrum-Table").within(() => { |
|||
cy.get(".spectrum-Button").contains("View").click({ force: true }) |
|||
cy.wait(500) |
|||
}) |
|||
|
|||
// Update value for 'Staged'
|
|||
cy.get('[data-name="Stage"]').within(() => { |
|||
cy.get(".spectrum-Picker-label").click() |
|||
}) |
|||
cy.get(".spectrum-Menu").within(() => { |
|||
cy.get(".spectrum-Menu-item").contains("Hired").click() |
|||
}) |
|||
|
|||
// Save application
|
|||
cy.get(".spectrum-Button").contains("Save").click({ force: true }) |
|||
cy.wait(500) |
|||
|
|||
// Hired section
|
|||
cy.get(".links").contains("Hired").click({ force: true }) |
|||
cy.wait(500) |
|||
|
|||
// Verify Table size - Total rows = 2
|
|||
cy.get(".spectrum-Table").find(".spectrum-Table-row").its('length').then((len => { |
|||
expect(len).to.eq(2) |
|||
})) |
|||
}) |
|||
|
|||
xit("should delete an application", () => { |
|||
// Visit published app
|
|||
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed) |
|||
cy.wait(1000) |
|||
|
|||
// Hired section
|
|||
cy.get(".links").contains("Hired").click({ force: true }) |
|||
cy.wait(500) |
|||
|
|||
// View first application
|
|||
cy.get(".spectrum-Table-row").eq(0).within(() => { |
|||
cy.get(".spectrum-Button").contains("View").click({ force: true }) |
|||
cy.wait(500) |
|||
}) |
|||
|
|||
// Delete application
|
|||
cy.get(".spectrum-Button").contains("Delete").click({ force: true }) |
|||
cy.get(".spectrum-Dialog-grid").within(() => { |
|||
cy.get(".spectrum-Button").contains("Confirm").click() |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,60 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify IT Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter IT Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="IT"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for IT templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
|
|||
if (templateNameText == "Hashicorp Scorecard Template") { |
|||
const templateNameSplit = templateNameParsed.split('-template')[0] |
|||
cy.get('a') |
|||
.should('have.attr', 'href').and('contain', templateNameSplit) |
|||
} |
|||
else if (templateNameText == "IT Ticketing System") { |
|||
const templateNameSplit = templateNameParsed.split('it-')[1] |
|||
cy.get('a') |
|||
.should('have.attr', 'href').and('contain', templateNameSplit) |
|||
} |
|||
else if (templateNameText == "IT Incident Report Form") { |
|||
const templateNameSplit = templateNameParsed.split('-form')[0] |
|||
cy.get('a') |
|||
.should('have.attr', 'href').and('contain', templateNameSplit) |
|||
} |
|||
else { |
|||
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed) |
|||
} |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,72 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("IT Ticketing System Template Functionality", () => { |
|||
const templateName = "IT Ticketing System" |
|||
const templateNameParsed = templateName.toLowerCase().replace(/\s+/g, '-') |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
cy.deleteApp(templateName) |
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`, { |
|||
onBeforeLoad(win) { |
|||
cy.stub(win, 'open') |
|||
} |
|||
}) |
|||
cy.wait(2000) |
|||
}) |
|||
|
|||
it("should create and publish app with IT Ticketing System template", () => { |
|||
// Select IT Ticketing System template
|
|||
cy.get(".template-thumbnail-text") |
|||
.contains(templateName).parentsUntil(".template-grid").within(() => { |
|||
cy.get(".spectrum-Button").contains("Use template").click({ force: true }) |
|||
}) |
|||
|
|||
// Confirm URL matches template name
|
|||
const appUrl = cy.get(".app-server") |
|||
appUrl.invoke('text').then(appUrlText => { |
|||
expect(appUrlText).to.equal(`${Cypress.config().baseUrl}/app/` + templateNameParsed) |
|||
}) |
|||
|
|||
// Create App
|
|||
cy.get(".spectrum-Dialog-grid").within(() => { |
|||
cy.get(".spectrum-Button").contains("Create app").click({ force: true }) |
|||
}) |
|||
|
|||
// Publish App & Verify it opened
|
|||
cy.wait(2000) // Wait for app to generate
|
|||
cy.publishApp(true) |
|||
cy.window().its('open').should('be.calledOnce') |
|||
}) |
|||
|
|||
xit("should filter tickets by status", () => { |
|||
// Visit published app
|
|||
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed) |
|||
cy.wait(1000) |
|||
|
|||
// Tickets section
|
|||
cy.get(".links").contains("Tickets").click({ force: true }) |
|||
cy.wait(1000) |
|||
|
|||
// Filter by stage - Confirm table updates
|
|||
cy.get(".spectrum-Picker").contains("Filter by status").click({ force: true }) |
|||
cy.get(".spectrum-Menu").find('li').its('length').then(len => { |
|||
for (let i = 1; i < len; i++) { |
|||
cy.get(".spectrum-Menu-item").eq(i).click() |
|||
const stage = cy.get(".spectrum-Picker-label") |
|||
stage.invoke('text').then(stageText => { |
|||
if (stageText == "In progress" || stageText == "On hold" || stageText == "Triaged") { |
|||
cy.get(".placeholder").should('contain', 'No rows found') |
|||
} |
|||
else { |
|||
cy.get(".spectrum-Table-row").should('contain', stageText) |
|||
} |
|||
cy.get(".spectrum-Picker").contains(stageText).click({ force: true }) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,42 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify Admin Panel Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter Admin Panels Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="Admin Panels"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for Admin Panels templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed) |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,51 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify Aproval Apps Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter Approval Apps Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="Approval Apps"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for Approval Apps templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
|
|||
if (templateNameText == "Content Approval System") { |
|||
// Template name should include 'content-approval'
|
|||
const templateNameSplit = templateNameParsed.split('-system')[0] |
|||
cy.get('a') |
|||
.should('have.attr', 'href').and('contain', templateNameSplit) |
|||
} |
|||
else { |
|||
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed) |
|||
} |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,51 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify Business Apps Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter Business Apps Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="Business Apps"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for Business Apps templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
|
|||
if (templateNameText == "Employee Check-in/Check-Out Template") { |
|||
// Remove / from template name
|
|||
const templateNameReplace = templateNameParsed.replace(/\//g, "-") |
|||
cy.get('a') |
|||
.should('have.attr', 'href').and('contain', templateNameReplace) |
|||
} |
|||
else { |
|||
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed) |
|||
} |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,44 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify Directories Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter Directories Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="Directories"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for Directories templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
const templateNameSplit = templateNameParsed.split('-template')[0] |
|||
cy.get('a') |
|||
.should('have.attr', 'href').and('contain', templateNameSplit) |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,42 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify Forms Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter Forms Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="Forms"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for Forms templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed) |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,43 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify Healthcare Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter Healthcare Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="Healthcare"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for Healthcare templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
|
|||
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed) |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,42 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify Legal Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter Legal Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="Legal"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for Legal templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed) |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,42 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify Logistics Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter Logistics Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="Logistics"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for Logistics templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed) |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,42 +0,0 @@ |
|||
import filterTests from "../../../support/filterTests" |
|||
|
|||
filterTests(["all"], () => { |
|||
context("Verify Manufacturing Template Details", () => { |
|||
|
|||
before(() => { |
|||
cy.login() |
|||
|
|||
// Template navigation
|
|||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/templates`) |
|||
|
|||
// Filter Manufacturing Templates
|
|||
cy.get(".template-category-filters").within(() => { |
|||
cy.get('[data-cy="Manufacturing"]').click() |
|||
}) |
|||
}) |
|||
|
|||
it("should verify the details option for Manufacturing templates", () => { |
|||
cy.get(".template-grid").find(".template-card").its('length') |
|||
.then(len => { |
|||
// Verify template name is within details link
|
|||
for (let i = 0; i < len; i++) { |
|||
cy.get(".template-card").eq(i).within(() => { |
|||
const templateName = cy.get(".template-thumbnail-text") |
|||
templateName.invoke('text') |
|||
.then(templateNameText => { |
|||
const templateNameParsed = templateNameText.toLowerCase().replace(/\s+/g, '-') |
|||
cy.get('a').should('have.attr', 'href').and('contain', templateNameParsed) |
|||
}) |
|||
// Verify correct status from Details link - 200
|
|||
cy.get('a') |
|||
.then(link => { |
|||
cy.request(link.prop('href')) |
|||
.its('status') |
|||
.should('eq', 200) |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue