forked from tsai/budibase
454 changed files with 11909 additions and 9306 deletions
@ -0,0 +1,62 @@ |
|||||
|
name: Deploy Budibase Single Container Image to DockerHub |
||||
|
on: |
||||
|
push: |
||||
|
branches: |
||||
|
- "omnibus-action" |
||||
|
- "develop" |
||||
|
- "master" |
||||
|
- "main" |
||||
|
env: |
||||
|
BASE_BRANCH: ${{ github.event.pull_request.base.ref}} |
||||
|
BRANCH: ${{ github.event.pull_request.head.ref }} |
||||
|
CI: true |
||||
|
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }} |
||||
|
REGISTRY_URL: registry.hub.docker.com |
||||
|
jobs: |
||||
|
build: |
||||
|
name: "build" |
||||
|
runs-on: ubuntu-latest |
||||
|
strategy: |
||||
|
matrix: |
||||
|
node-version: [14.x] |
||||
|
steps: |
||||
|
- name: "Checkout" |
||||
|
uses: actions/checkout@v2 |
||||
|
- name: Use Node.js ${{ matrix.node-version }} |
||||
|
uses: actions/setup-node@v1 |
||||
|
with: |
||||
|
node-version: ${{ matrix.node-version }} |
||||
|
- name: Setup QEMU |
||||
|
uses: docker/setup-qemu-action@v1 |
||||
|
- name: Setup Docker Buildx |
||||
|
id: buildx |
||||
|
uses: docker/setup-buildx-action@v1 |
||||
|
- name: Install Pro |
||||
|
run: yarn install:pro $BRANCH $BASE_BRANCH |
||||
|
- name: Run Yarn |
||||
|
run: yarn |
||||
|
- name: Run Yarn Bootstrap |
||||
|
run: yarn bootstrap |
||||
|
- name: Runt Yarn Lint |
||||
|
run: yarn lint |
||||
|
- name: Run Yarn Build |
||||
|
run: yarn build |
||||
|
- name: Login to Docker Hub |
||||
|
uses: docker/login-action@v2 |
||||
|
with: |
||||
|
username: ${{ secrets.DOCKER_USERNAME }} |
||||
|
password: ${{ secrets.DOCKER_API_KEY }} |
||||
|
- name: Get the latest release version |
||||
|
id: version |
||||
|
run: | |
||||
|
release_version=$(cat lerna.json | jq -r '.version') |
||||
|
echo $release_version |
||||
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV |
||||
|
- name: Tag and release Budibase service docker image |
||||
|
uses: docker/build-push-action@v2 |
||||
|
with: |
||||
|
context: . |
||||
|
push: true |
||||
|
platforms: linux/amd64,linux/arm64 |
||||
|
tags: budibase/budibase,budibase/budibase:v${{ env.RELEASE_VERSION }} |
||||
|
file: ./hosting/single/Dockerfile |
||||
@ -0,0 +1 @@ |
|||||
|
network-timeout 100000 |
||||
@ -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,5 @@ |
|||||
|
; CouchDB Configuration Settings |
||||
|
|
||||
|
[couchdb] |
||||
|
database_dir = /data/couch/dbs |
||||
|
view_index_dir = /data/couch/views |
||||
@ -1,125 +0,0 @@ |
|||||
user www-data www-data; |
|
||||
error_log /var/log/nginx/error.log; |
|
||||
pid /var/run/nginx.pid; |
|
||||
worker_processes auto; |
|
||||
worker_rlimit_nofile 8192; |
|
||||
|
|
||||
events { |
|
||||
worker_connections 1024; |
|
||||
} |
|
||||
|
|
||||
http { |
|
||||
limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=20r/s; |
|
||||
proxy_set_header Host $host; |
|
||||
charset utf-8; |
|
||||
sendfile on; |
|
||||
tcp_nopush on; |
|
||||
tcp_nodelay on; |
|
||||
server_tokens off; |
|
||||
types_hash_max_size 2048; |
|
||||
|
|
||||
# buffering |
|
||||
client_header_buffer_size 1k; |
|
||||
client_max_body_size 20M; |
|
||||
ignore_invalid_headers off; |
|
||||
proxy_buffering off; |
|
||||
|
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' |
|
||||
'$status $body_bytes_sent "$http_referer" ' |
|
||||
'"$http_user_agent" "$http_x_forwarded_for"'; |
|
||||
|
|
||||
map $http_upgrade $connection_upgrade { |
|
||||
default "upgrade"; |
|
||||
} |
|
||||
|
|
||||
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,91 @@ |
|||||
|
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,37 @@ |
|||||
|
user www-data www-data; |
||||
|
error_log /var/log/nginx/error.log; |
||||
|
pid /var/run/nginx.pid; |
||||
|
worker_processes auto; |
||||
|
worker_rlimit_nofile 8192; |
||||
|
|
||||
|
events { |
||||
|
worker_connections 1024; |
||||
|
} |
||||
|
|
||||
|
http { |
||||
|
limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=20r/s; |
||||
|
proxy_set_header Host $host; |
||||
|
charset utf-8; |
||||
|
sendfile on; |
||||
|
tcp_nopush on; |
||||
|
tcp_nodelay on; |
||||
|
server_tokens off; |
||||
|
types_hash_max_size 2048; |
||||
|
|
||||
|
# buffering |
||||
|
client_header_buffer_size 1k; |
||||
|
client_max_body_size 20M; |
||||
|
ignore_invalid_headers off; |
||||
|
proxy_buffering off; |
||||
|
|
||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' |
||||
|
'$status $body_bytes_sent "$http_referer" ' |
||||
|
'"$http_user_agent" "$http_x_forwarded_for"'; |
||||
|
|
||||
|
map $http_upgrade $connection_upgrade { |
||||
|
default "upgrade"; |
||||
|
} |
||||
|
|
||||
|
include /etc/nginx/sites-enabled/*; |
||||
|
|
||||
|
} |
||||
@ -1,4 +1,4 @@ |
|||||
#!/bin/bash |
#!/bin/bash |
||||
id=$(docker run -t -d -p 10000:10000 budibase:latest) |
id=$(docker run -t -d -p 8080:80 budibase:latest) |
||||
docker exec -it $id bash |
docker exec -it $id bash |
||||
docker kill $id |
docker kill $id |
||||
|
|||||
@ -1,5 +1,5 @@ |
|||||
module.exports = { |
module.exports = { |
||||
Client: require("./src/redis"), |
Client: require("./src/redis"), |
||||
utils: require("./src/redis/utils"), |
utils: require("./src/redis/utils"), |
||||
clients: require("./src/redis/authRedis"), |
clients: require("./src/redis/init"), |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,92 @@ |
|||||
|
import { getTenantId } from "../../context" |
||||
|
import redis from "../../redis/init" |
||||
|
import RedisWrapper from "../../redis" |
||||
|
|
||||
|
function generateTenantKey(key: string) { |
||||
|
const tenantId = getTenantId() |
||||
|
return `${key}:${tenantId}` |
||||
|
} |
||||
|
|
||||
|
export = class BaseCache { |
||||
|
client: RedisWrapper | undefined |
||||
|
|
||||
|
constructor(client: RedisWrapper | undefined = undefined) { |
||||
|
this.client = client |
||||
|
} |
||||
|
|
||||
|
async getClient() { |
||||
|
return !this.client ? await redis.getCacheClient() : this.client |
||||
|
} |
||||
|
|
||||
|
async keys(pattern: string) { |
||||
|
const client = await this.getClient() |
||||
|
return client.keys(pattern) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Read only from the cache. |
||||
|
*/ |
||||
|
async get(key: string, opts = { useTenancy: true }) { |
||||
|
key = opts.useTenancy ? generateTenantKey(key) : key |
||||
|
const client = await this.getClient() |
||||
|
return client.get(key) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Write to the cache. |
||||
|
*/ |
||||
|
async store( |
||||
|
key: string, |
||||
|
value: any, |
||||
|
ttl: number | null = null, |
||||
|
opts = { useTenancy: true } |
||||
|
) { |
||||
|
key = opts.useTenancy ? generateTenantKey(key) : key |
||||
|
const client = await this.getClient() |
||||
|
await client.store(key, value, ttl) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Remove from cache. |
||||
|
*/ |
||||
|
async delete(key: string, opts = { useTenancy: true }) { |
||||
|
key = opts.useTenancy ? generateTenantKey(key) : key |
||||
|
const client = await this.getClient() |
||||
|
return client.delete(key) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Read from the cache. Write to the cache if not exists. |
||||
|
*/ |
||||
|
async withCache( |
||||
|
key: string, |
||||
|
ttl: number, |
||||
|
fetchFn: any, |
||||
|
opts = { useTenancy: true } |
||||
|
) { |
||||
|
const cachedValue = await this.get(key, opts) |
||||
|
if (cachedValue) { |
||||
|
return cachedValue |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
const fetchedValue = await fetchFn() |
||||
|
|
||||
|
await this.store(key, fetchedValue, ttl, opts) |
||||
|
return fetchedValue |
||||
|
} catch (err) { |
||||
|
console.error("Error fetching before cache - ", err) |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async bustCache(key: string, opts = { client: null }) { |
||||
|
const client = await this.getClient() |
||||
|
try { |
||||
|
await client.delete(generateTenantKey(key)) |
||||
|
} catch (err) { |
||||
|
console.error("Error busting cache - ", err) |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
require("../../../tests/utilities/TestConfiguration") |
||||
|
const { Writethrough } = require("../writethrough") |
||||
|
const { dangerousGetDB } = require("../../db") |
||||
|
const tk = require("timekeeper") |
||||
|
|
||||
|
const START_DATE = Date.now() |
||||
|
tk.freeze(START_DATE) |
||||
|
|
||||
|
const DELAY = 5000 |
||||
|
|
||||
|
const db = dangerousGetDB("test") |
||||
|
const db2 = dangerousGetDB("test2") |
||||
|
const writethrough = new Writethrough(db, DELAY), writethrough2 = new Writethrough(db2, DELAY) |
||||
|
|
||||
|
describe("writethrough", () => { |
||||
|
describe("put", () => { |
||||
|
let first |
||||
|
it("should be able to store, will go to DB", async () => { |
||||
|
const response = await writethrough.put({ _id: "test", value: 1 }) |
||||
|
const output = await db.get(response.id) |
||||
|
first = output |
||||
|
expect(output.value).toBe(1) |
||||
|
}) |
||||
|
|
||||
|
it("second put shouldn't update DB", async () => { |
||||
|
const response = await writethrough.put({ ...first, value: 2 }) |
||||
|
const output = await db.get(response.id) |
||||
|
expect(first._rev).toBe(output._rev) |
||||
|
expect(output.value).toBe(1) |
||||
|
}) |
||||
|
|
||||
|
it("should put it again after delay period", async () => { |
||||
|
tk.freeze(START_DATE + DELAY + 1) |
||||
|
const response = await writethrough.put({ ...first, value: 3 }) |
||||
|
const output = await db.get(response.id) |
||||
|
expect(response.rev).not.toBe(first._rev) |
||||
|
expect(output.value).toBe(3) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("get", () => { |
||||
|
it("should be able to retrieve", async () => { |
||||
|
const response = await writethrough.get("test") |
||||
|
expect(response.value).toBe(3) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("same doc, different databases (tenancy)", () => { |
||||
|
it("should be able to two different databases", async () => { |
||||
|
const resp1 = await writethrough.put({ _id: "db1", value: "first" }) |
||||
|
const resp2 = await writethrough2.put({ _id: "db1", value: "second" }) |
||||
|
expect(resp1.rev).toBeDefined() |
||||
|
expect(resp2.rev).toBeDefined() |
||||
|
expect((await db.get("db1")).value).toBe("first") |
||||
|
expect((await db2.get("db1")).value).toBe("second") |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
@ -0,0 +1,120 @@ |
|||||
|
import BaseCache from "./base" |
||||
|
import { getWritethroughClient } from "../redis/init" |
||||
|
|
||||
|
const DEFAULT_WRITE_RATE_MS = 10000 |
||||
|
let CACHE: BaseCache | null = null |
||||
|
|
||||
|
interface CacheItem { |
||||
|
doc: any |
||||
|
lastWrite: number |
||||
|
} |
||||
|
|
||||
|
async function getCache() { |
||||
|
if (!CACHE) { |
||||
|
const client = await getWritethroughClient() |
||||
|
CACHE = new BaseCache(client) |
||||
|
} |
||||
|
return CACHE |
||||
|
} |
||||
|
|
||||
|
function makeCacheKey(db: PouchDB.Database, key: string) { |
||||
|
return db.name + key |
||||
|
} |
||||
|
|
||||
|
function makeCacheItem(doc: any, lastWrite: number | null = null): CacheItem { |
||||
|
return { doc, lastWrite: lastWrite || Date.now() } |
||||
|
} |
||||
|
|
||||
|
export async function put( |
||||
|
db: PouchDB.Database, |
||||
|
doc: any, |
||||
|
writeRateMs: number = DEFAULT_WRITE_RATE_MS |
||||
|
) { |
||||
|
const cache = await getCache() |
||||
|
const key = doc._id |
||||
|
let cacheItem: CacheItem | undefined = await cache.get(makeCacheKey(db, key)) |
||||
|
const updateDb = !cacheItem || cacheItem.lastWrite < Date.now() - writeRateMs |
||||
|
let output = doc |
||||
|
if (updateDb) { |
||||
|
const writeDb = async (toWrite: any) => { |
||||
|
// doc should contain the _id and _rev
|
||||
|
const response = await db.put(toWrite) |
||||
|
output = { |
||||
|
...doc, |
||||
|
_id: response.id, |
||||
|
_rev: response.rev, |
||||
|
} |
||||
|
} |
||||
|
try { |
||||
|
await writeDb(doc) |
||||
|
} catch (err: any) { |
||||
|
if (err.status !== 409) { |
||||
|
throw err |
||||
|
} else { |
||||
|
// get the rev, update over it - this is risky, may change in future
|
||||
|
const readDoc = await db.get(doc._id) |
||||
|
doc._rev = readDoc._rev |
||||
|
await writeDb(doc) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
// if we are updating the DB then need to set the lastWrite to now
|
||||
|
cacheItem = makeCacheItem(output, updateDb ? null : cacheItem?.lastWrite) |
||||
|
await cache.store(makeCacheKey(db, key), cacheItem) |
||||
|
return { ok: true, id: output._id, rev: output._rev } |
||||
|
} |
||||
|
|
||||
|
export async function get(db: PouchDB.Database, id: string): Promise<any> { |
||||
|
const cache = await getCache() |
||||
|
const cacheKey = makeCacheKey(db, id) |
||||
|
let cacheItem: CacheItem = await cache.get(cacheKey) |
||||
|
if (!cacheItem) { |
||||
|
const doc = await db.get(id) |
||||
|
cacheItem = makeCacheItem(doc) |
||||
|
await cache.store(cacheKey, cacheItem) |
||||
|
} |
||||
|
return cacheItem.doc |
||||
|
} |
||||
|
|
||||
|
export async function remove( |
||||
|
db: PouchDB.Database, |
||||
|
docOrId: any, |
||||
|
rev?: any |
||||
|
): Promise<void> { |
||||
|
const cache = await getCache() |
||||
|
if (!docOrId) { |
||||
|
throw new Error("No ID/Rev provided.") |
||||
|
} |
||||
|
const id = typeof docOrId === "string" ? docOrId : docOrId._id |
||||
|
rev = typeof docOrId === "string" ? rev : docOrId._rev |
||||
|
try { |
||||
|
await cache.delete(makeCacheKey(db, id)) |
||||
|
} finally { |
||||
|
await db.remove(id, rev) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class Writethrough { |
||||
|
db: PouchDB.Database |
||||
|
writeRateMs: number |
||||
|
|
||||
|
constructor( |
||||
|
db: PouchDB.Database, |
||||
|
writeRateMs: number = DEFAULT_WRITE_RATE_MS |
||||
|
) { |
||||
|
this.db = db |
||||
|
this.writeRateMs = writeRateMs |
||||
|
} |
||||
|
|
||||
|
async put(doc: any) { |
||||
|
return put(this.db, doc, this.writeRateMs) |
||||
|
} |
||||
|
|
||||
|
async get(id: string) { |
||||
|
return get(this.db, id) |
||||
|
} |
||||
|
|
||||
|
async remove(docOrId: any, rev?: any) { |
||||
|
return remove(this.db, docOrId, rev) |
||||
|
} |
||||
|
} |
||||
@ -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,86 @@ |
|||||
|
<script> |
||||
|
import "@spectrum-css/slider/dist/index-vars.css" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
|
||||
|
export let value = false |
||||
|
export let id = null |
||||
|
export let disabled = false |
||||
|
export let min = 0 |
||||
|
export let max = 100 |
||||
|
export let step = 1 |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
const onChange = event => { |
||||
|
dispatch("change", event.target.value) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div> |
||||
|
<input |
||||
|
type="range" |
||||
|
{min} |
||||
|
{max} |
||||
|
{step} |
||||
|
{value} |
||||
|
{disabled} |
||||
|
{id} |
||||
|
on:change={onChange} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
div { |
||||
|
display: grid; |
||||
|
place-items: center; |
||||
|
} |
||||
|
|
||||
|
input { |
||||
|
width: 100%; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
-webkit-appearance: none; |
||||
|
background: transparent; |
||||
|
} |
||||
|
input::-webkit-slider-thumb { |
||||
|
-webkit-appearance: none; |
||||
|
} |
||||
|
input:focus { |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
input[type="range"]::-webkit-slider-thumb { |
||||
|
-webkit-appearance: none; |
||||
|
border: 2px solid var(--spectrum-global-color-gray-700); |
||||
|
height: 16px; |
||||
|
width: 16px; |
||||
|
border-radius: 50%; |
||||
|
background: var(--background); |
||||
|
cursor: pointer; |
||||
|
transition: background 130ms ease-out; |
||||
|
margin-top: -7px; |
||||
|
} |
||||
|
input[type="range"]::-moz-range-thumb { |
||||
|
border: 2px solid var(--spectrum-global-color-gray-700); |
||||
|
height: 12px; |
||||
|
width: 12px; |
||||
|
border-radius: 50%; |
||||
|
background: var(--background); |
||||
|
cursor: pointer; |
||||
|
transition: background 130ms ease-out; |
||||
|
} |
||||
|
|
||||
|
input[type="range"]::-webkit-slider-runnable-track { |
||||
|
width: 100%; |
||||
|
height: 2px; |
||||
|
cursor: pointer; |
||||
|
background: var(--spectrum-global-color-gray-300); |
||||
|
border-radius: 2px; |
||||
|
} |
||||
|
input[type="range"]::-moz-range-track { |
||||
|
width: 100%; |
||||
|
height: 2px; |
||||
|
cursor: pointer; |
||||
|
background: var(--spectrum-global-color-gray-300); |
||||
|
border-radius: 2px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,24 @@ |
|||||
|
<script> |
||||
|
import Field from "./Field.svelte" |
||||
|
import Slider from "./Core/Slider.svelte" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
|
||||
|
export let value = null |
||||
|
export let label = null |
||||
|
export let labelPosition = "above" |
||||
|
export let min = 0 |
||||
|
export let max = 100 |
||||
|
export let step = 1 |
||||
|
export let disabled = false |
||||
|
export let error = null |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
const onChange = e => { |
||||
|
value = e.detail |
||||
|
dispatch("change", e.detail) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<Field {label} {labelPosition} {error}> |
||||
|
<Slider {disabled} {value} {min} {max} {step} on:change={onChange} /> |
||||
|
</Field> |
||||
@ -0,0 +1,14 @@ |
|||||
|
<div class="icon-side-nav"> |
||||
|
<slot /> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.icon-side-nav { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: flex-start; |
||||
|
align-items: center; |
||||
|
padding: var(--spacing-s); |
||||
|
gap: var(--spacing-xs); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,56 @@ |
|||||
|
<script> |
||||
|
import Icon from "../Icon/Icon.svelte" |
||||
|
import Tooltip from "../Tooltip/Tooltip.svelte" |
||||
|
import { fade } from "svelte/transition" |
||||
|
|
||||
|
export let icon |
||||
|
export let active = false |
||||
|
export let tooltip |
||||
|
|
||||
|
let showTooltip = false |
||||
|
</script> |
||||
|
|
||||
|
<div |
||||
|
class="icon-side-nav-item" |
||||
|
class:active |
||||
|
on:mouseover={() => (showTooltip = true)} |
||||
|
on:focus={() => (showTooltip = true)} |
||||
|
on:mouseleave={() => (showTooltip = false)} |
||||
|
on:click |
||||
|
> |
||||
|
<Icon name={icon} hoverable /> |
||||
|
{#if tooltip && showTooltip} |
||||
|
<div class="tooltip" in:fade={{ duration: 130, delay: 250 }}> |
||||
|
<Tooltip textWrapping direction="right" text={tooltip} /> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.icon-side-nav-item { |
||||
|
width: 36px; |
||||
|
height: 36px; |
||||
|
display: grid; |
||||
|
place-items: center; |
||||
|
border-radius: 4px; |
||||
|
position: relative; |
||||
|
cursor: pointer; |
||||
|
transition: background 130ms ease-out; |
||||
|
} |
||||
|
.icon-side-nav-item:hover :global(svg), |
||||
|
.active :global(svg) { |
||||
|
color: var(--spectrum-global-color-gray-900); |
||||
|
} |
||||
|
.active { |
||||
|
background: var(--spectrum-global-color-gray-300); |
||||
|
} |
||||
|
.tooltip { |
||||
|
position: absolute; |
||||
|
pointer-events: none; |
||||
|
left: calc(100% - 4px); |
||||
|
top: 50%; |
||||
|
white-space: nowrap; |
||||
|
transform: translateY(-50%); |
||||
|
z-index: 1; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,132 @@ |
|||||
|
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", false) |
||||
|
|
||||
|
// Create new user
|
||||
|
cy.wait(500) |
||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000}) |
||||
|
cy.createUser(bbUserEmail) |
||||
|
cy.contains("bbuser").click() |
||||
|
cy.wait(500) |
||||
|
|
||||
|
// Reset password
|
||||
|
cy.get(".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,237 @@ |
|||||
|
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", false) |
||||
|
}) |
||||
|
|
||||
|
it("should create a user via basic onboarding", () => { |
||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000}) |
||||
|
cy.createUser("bbuser@test.com") |
||||
|
cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser") |
||||
|
}) |
||||
|
|
||||
|
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, false) |
||||
|
} else { |
||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000}) |
||||
|
cy.wait(1000) |
||||
|
cy.get(interact.CREATE_APP_BUTTON, { timeout: 2000 }).click({ force: true }) |
||||
|
cy.createAppFromScratch(name) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
// Navigate back to the user
|
||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000}) |
||||
|
cy.get(interact.SPECTRUM_SIDENAV).contains("Users").click() |
||||
|
cy.get(interact.SPECTRUM_TABLE, { timeout: 1000 }).contains("bbuser").click() |
||||
|
for (let i = 0; i < 3; i++) { |
||||
|
cy.get(interact.SPECTRUM_TABLE, { timeout: 3000}) |
||||
|
.eq(1) |
||||
|
.find(interact.SPECTRUM_TABLE_ROW) |
||||
|
.eq(0) |
||||
|
.find(interact.SPECTRUM_TABLE_CELL) |
||||
|
.eq(0) |
||||
|
.click() |
||||
|
cy.get(interact.SPECTRUM_DIALOG_GRID, { timeout: 1000 }) |
||||
|
.contains("Choose an option") |
||||
|
.click() |
||||
|
.then(() => { |
||||
|
if (i == 0) { |
||||
|
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Admin").click({ force: true }) |
||||
|
} |
||||
|
else if (i == 1) { |
||||
|
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Power").click({ force: true }) |
||||
|
} |
||||
|
else if (i == 2) { |
||||
|
cy.get(interact.SPECTRUM_MENU, { timeout: 2000 }).contains("Basic").click({ force: true }) |
||||
|
} |
||||
|
cy.get(interact.SPECTRUM_BUTTON, { timeout: 2000 }) |
||||
|
.contains("Update role") |
||||
|
.click({ force: true }) |
||||
|
}) |
||||
|
cy.reload({ timeout: 5000 }) |
||||
|
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: 1000 }).eq(2).within(() => { |
||||
|
cy.wait(500) |
||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).wait(500).clear().click().type("bb") |
||||
|
}) |
||||
|
// Add Last name
|
||||
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(3).within(() => { |
||||
|
cy.wait(500) |
||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).click().wait(500).clear().type("test") |
||||
|
}) |
||||
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).click() |
||||
|
// Reload page
|
||||
|
cy.reload() |
||||
|
|
||||
|
// Confirm details have been saved
|
||||
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => { |
||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb") |
||||
|
}) |
||||
|
cy.get(interact.FIELD, { timeout: 1000 }).eq(3).within(() => { |
||||
|
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).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") |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue