1
0
Fork 0
mirror of synced 2024-09-02 10:41:09 +12:00

Merge branch 'develop' of github.com:Budibase/budibase into plugins-dev-experience

This commit is contained in:
mike12345567 2022-08-11 16:34:01 +01:00
commit 76d427001a
137 changed files with 1547 additions and 1087 deletions

View file

@ -12,19 +12,28 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Fail if branch is not master
if: github.ref != 'refs/heads/master'
run: |
echo "Ref is not master, you must run this job from master."
exit 1
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
node-version: 14.x node-version: 14.x
fetch_depth: 0 fetch_depth: 0
- 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 Docker images (Self Host) - name: Tag and release Docker images (Self Host)
run: | run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
# Get latest release version release_tag=v${{ env.RELEASE_VERSION }}
release_version=$(cat lerna.json | jq -r '.version')
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
release_tag=v$release_version
# Pull apps and worker images # Pull apps and worker images
docker pull budibase/apps:$release_tag docker pull budibase/apps:$release_tag

View file

@ -75,7 +75,7 @@ services:
ports: ports:
- "${MAIN_PORT}:10000" - "${MAIN_PORT}:10000"
container_name: bbproxy container_name: bbproxy
image: proxy-service image: budibase/proxy
environment: environment:
- PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10 - PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10
depends_on: depends_on:

View file

@ -3,15 +3,14 @@
echo ${TARGETBUILD} > /buildtarget.txt echo ${TARGETBUILD} > /buildtarget.txt
if [[ "${TARGETBUILD}" = "aas" ]]; then if [[ "${TARGETBUILD}" = "aas" ]]; then
# Azure AppService uses /home for persisent data & SSH on port 2222 # Azure AppService uses /home for persisent data & SSH on port 2222
mkdir -p /home/{search,minio,couch} DATA_DIR=/home
mkdir -p /home/couch/{dbs,views} mkdir -p $DATA_DIR/{search,minio,couchdb}
chown -R couchdb:couchdb /home/couch/ mkdir -p $DATA_DIR/couchdb/{dbs,views}
chown -R couchdb:couchdb $DATA_DIR/couchdb/
apt update apt update
apt-get install -y openssh-server apt-get install -y openssh-server
sed -i 's#dir=/opt/couchdb/data/search#dir=/home/search#' /opt/clouseau/clouseau.ini
sed -i 's#/minio/minio server /minio &#/minio/minio server /home/minio &#' /runner.sh
sed -i 's#database_dir = ./data#database_dir = /home/couch/dbs#' /opt/couchdb/etc/default.ini
sed -i 's#view_index_dir = ./data#view_index_dir = /home/couch/views#' /opt/couchdb/etc/default.ini
sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config
/etc/init.d/ssh restart /etc/init.d/ssh restart
fi fi
sed -i 's#DATA_DIR#$DATA_DIR#' /opt/clouseau/clouseau.ini /opt/couchdb/etc/local.ini

View file

@ -20,10 +20,10 @@ RUN node /pinVersions.js && yarn && yarn build && /cleanup.sh
FROM couchdb:3.2.1 FROM couchdb:3.2.1
# TARGETARCH can be amd64 or arm e.g. docker build --build-arg TARGETARCH=amd64 # TARGETARCH can be amd64 or arm e.g. docker build --build-arg TARGETARCH=amd64
ARG TARGETARCH amd64 ARG TARGETARCH=amd64
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service) #TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
# e.g. docker build --build-arg TARGETBUILD=aas .... # e.g. docker build --build-arg TARGETBUILD=aas ....
ARG TARGETBUILD single ARG TARGETBUILD=single
ENV TARGETBUILD $TARGETBUILD ENV TARGETBUILD $TARGETBUILD
COPY --from=build /app /app COPY --from=build /app /app
@ -35,6 +35,7 @@ ENV \
BUDIBASE_ENVIRONMENT=PRODUCTION \ BUDIBASE_ENVIRONMENT=PRODUCTION \
CLUSTER_PORT=80 \ CLUSTER_PORT=80 \
# CUSTOM_DOMAIN=budi001.custom.com \ # CUSTOM_DOMAIN=budi001.custom.com \
DATA_DIR=/data \
DEPLOYMENT_ENVIRONMENT=docker \ DEPLOYMENT_ENVIRONMENT=docker \
MINIO_URL=http://localhost:9000 \ MINIO_URL=http://localhost:9000 \
POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU \ POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU \
@ -114,6 +115,7 @@ RUN chmod +x ./healthcheck.sh
ADD hosting/scripts/build-target-paths.sh . ADD hosting/scripts/build-target-paths.sh .
RUN chmod +x ./build-target-paths.sh RUN chmod +x ./build-target-paths.sh
# Script below sets the path for storing data based on $DATA_DIR
# For Azure App Service install SSH & point data locations to /home # For Azure App Service install SSH & point data locations to /home
RUN /build-target-paths.sh RUN /build-target-paths.sh

View file

@ -7,7 +7,7 @@ name=clouseau@127.0.0.1
cookie=monster cookie=monster
; the path where you would like to store the search index files ; the path where you would like to store the search index files
dir=/data/search dir=DATA_DIR/search
; the number of search indexes that can be open simultaneously ; the number of search indexes that can be open simultaneously
max_indexes_open=500 max_indexes_open=500

View file

@ -1,5 +1,5 @@
; CouchDB Configuration Settings ; CouchDB Configuration Settings
[couchdb] [couchdb]
database_dir = /data/couch/dbs database_dir = DATA_DIR/couch/dbs
view_index_dir = /data/couch/views view_index_dir = DATA_DIR/couch/views

View file

@ -1,7 +1,16 @@
#!/bin/bash #!/bin/bash
declare -a ENV_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "MINIO_ACCESS_KEY" "MINIO_SECRET_KEY" "INTERNAL_API_KEY" "JWT_SECRET" "REDIS_PASSWORD") declare -a ENV_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "DATA_DIR" "MINIO_ACCESS_KEY" "MINIO_SECRET_KEY" "INTERNAL_API_KEY" "JWT_SECRET" "REDIS_PASSWORD")
if [ -f "/data/.env" ]; then
export $(cat /data/.env | xargs) # Azure App Service customisations
if [[ "${TARGETBUILD}" = "aas" ]]; then
DATA_DIR=/home
/etc/init.d/ssh start
else
DATA_DIR=${DATA_DIR:-/data}
fi
if [ -f "${DATA_DIR}/.env" ]; then
export $(cat ${DATA_DIR}/.env | xargs)
fi fi
# first randomise any unset environment variables # first randomise any unset environment variables
for ENV_VAR in "${ENV_VARS[@]}" for ENV_VAR in "${ENV_VARS[@]}"
@ -14,21 +23,26 @@ done
if [[ -z "${COUCH_DB_URL}" ]]; then if [[ -z "${COUCH_DB_URL}" ]]; then
export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984 export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984
fi fi
if [ ! -f "/data/.env" ]; then if [ ! -f "${DATA_DIR}/.env" ]; then
touch /data/.env touch ${DATA_DIR}/.env
for ENV_VAR in "${ENV_VARS[@]}" for ENV_VAR in "${ENV_VARS[@]}"
do do
temp=$(eval "echo \$$ENV_VAR") temp=$(eval "echo \$$ENV_VAR")
echo "$ENV_VAR=$temp" >> /data/.env echo "$ENV_VAR=$temp" >> ${DATA_DIR}/.env
done done
echo "COUCH_DB_URL=${COUCH_DB_URL}" >> ${DATA_DIR}/.env
fi fi
export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984
# make these directories in runner, incase of mount # make these directories in runner, incase of mount
mkdir -p /data/couch/{dbs,views} /home/couch/{dbs,views} mkdir -p ${DATA_DIR}/couchdb/{dbs,views}
chown -R couchdb:couchdb /data/couch /home/couch mkdir -p ${DATA_DIR}/minio
mkdir -p ${DATA_DIR}/search
chown -R couchdb:couchdb ${DATA_DIR}/couchdb
redis-server --requirepass $REDIS_PASSWORD & redis-server --requirepass $REDIS_PASSWORD &
/opt/clouseau/bin/clouseau & /opt/clouseau/bin/clouseau &
/minio/minio server /data/minio & /minio/minio server ${DATA_DIR}/minio &
/docker-entrypoint.sh /opt/couchdb/bin/couchdb & /docker-entrypoint.sh /opt/couchdb/bin/couchdb &
/etc/init.d/nginx restart /etc/init.d/nginx restart
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then

View file

@ -1,5 +1,5 @@
{ {
"version": "1.2.28-alpha.0", "version": "1.2.38",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "1.2.28-alpha.0", "version": "1.2.38",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -20,7 +20,7 @@
"test:watch": "jest --watchAll" "test:watch": "jest --watchAll"
}, },
"dependencies": { "dependencies": {
"@budibase/types": "1.2.28-alpha.0", "@budibase/types": "^1.2.38",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0", "aws-sdk": "2.1030.0",
"bcrypt": "5.0.1", "bcrypt": "5.0.1",

View file

@ -1,6 +1,6 @@
const redis = require("../redis/init") const redis = require("../redis/init")
const { doWithDB } = require("../db") const { doWithDB } = require("../db")
const { DocumentTypes } = require("../db/constants") const { DocumentType } = require("../db/constants")
const AppState = { const AppState = {
INVALID: "invalid", INVALID: "invalid",
@ -14,7 +14,7 @@ const populateFromDB = async appId => {
return doWithDB( return doWithDB(
appId, appId,
db => { db => {
return db.get(DocumentTypes.APP_METADATA) return db.get(DocumentType.APP_METADATA)
}, },
{ skip_setup: true } { skip_setup: true }
) )

View file

@ -1,4 +1,4 @@
export enum ContextKeys { export enum ContextKey {
TENANT_ID = "tenantId", TENANT_ID = "tenantId",
GLOBAL_DB = "globalDb", GLOBAL_DB = "globalDb",
APP_ID = "appId", APP_ID = "appId",

View file

@ -1,11 +1,11 @@
import env from "../environment" import env from "../environment"
import { SEPARATOR, DocumentTypes } from "../db/constants" import { SEPARATOR, DocumentType } from "../db/constants"
import cls from "./FunctionContext" import cls from "./FunctionContext"
import { dangerousGetDB, closeDB } from "../db" import { dangerousGetDB, closeDB } from "../db"
import { baseGlobalDBName } from "../tenancy/utils" import { baseGlobalDBName } from "../tenancy/utils"
import { IdentityContext } from "@budibase/types" import { IdentityContext } from "@budibase/types"
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants" import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
import { ContextKeys } from "./constants" import { ContextKey } from "./constants"
import { import {
updateUsing, updateUsing,
closeWithUsing, closeWithUsing,
@ -33,8 +33,8 @@ export const closeTenancy = async () => {
} }
await closeDB(db) await closeDB(db)
// clear from context now that database is closed/task is finished // clear from context now that database is closed/task is finished
cls.setOnContext(ContextKeys.TENANT_ID, null) cls.setOnContext(ContextKey.TENANT_ID, null)
cls.setOnContext(ContextKeys.GLOBAL_DB, null) cls.setOnContext(ContextKey.GLOBAL_DB, null)
} }
// export const isDefaultTenant = () => { // export const isDefaultTenant = () => {
@ -54,7 +54,7 @@ export const getTenantIDFromAppID = (appId: string) => {
return null return null
} }
const split = appId.split(SEPARATOR) const split = appId.split(SEPARATOR)
const hasDev = split[1] === DocumentTypes.DEV const hasDev = split[1] === DocumentType.DEV
if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) { if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) {
return null return null
} }
@ -83,14 +83,14 @@ export const doInTenant = (tenantId: string | null, task: any) => {
// invoke the task // invoke the task
return await task() return await task()
} finally { } finally {
await closeWithUsing(ContextKeys.TENANCY_IN_USE, () => { await closeWithUsing(ContextKey.TENANCY_IN_USE, () => {
return closeTenancy() return closeTenancy()
}) })
} }
} }
const existing = cls.getFromContext(ContextKeys.TENANT_ID) === tenantId const existing = cls.getFromContext(ContextKey.TENANT_ID) === tenantId
return updateUsing(ContextKeys.TENANCY_IN_USE, existing, internal) return updateUsing(ContextKey.TENANCY_IN_USE, existing, internal)
} }
export const doInAppContext = (appId: string, task: any) => { export const doInAppContext = (appId: string, task: any) => {
@ -108,7 +108,7 @@ export const doInAppContext = (appId: string, task: any) => {
setAppTenantId(appId) setAppTenantId(appId)
} }
// set the app ID // set the app ID
cls.setOnContext(ContextKeys.APP_ID, appId) cls.setOnContext(ContextKey.APP_ID, appId)
// preserve the identity // preserve the identity
if (identity) { if (identity) {
@ -118,14 +118,14 @@ export const doInAppContext = (appId: string, task: any) => {
// invoke the task // invoke the task
return await task() return await task()
} finally { } finally {
await closeWithUsing(ContextKeys.APP_IN_USE, async () => { await closeWithUsing(ContextKey.APP_IN_USE, async () => {
await closeAppDBs() await closeAppDBs()
await closeTenancy() await closeTenancy()
}) })
} }
} }
const existing = cls.getFromContext(ContextKeys.APP_ID) === appId const existing = cls.getFromContext(ContextKey.APP_ID) === appId
return updateUsing(ContextKeys.APP_IN_USE, existing, internal) return updateUsing(ContextKey.APP_IN_USE, existing, internal)
} }
export const doInIdentityContext = (identity: IdentityContext, task: any) => { export const doInIdentityContext = (identity: IdentityContext, task: any) => {
@ -135,7 +135,7 @@ export const doInIdentityContext = (identity: IdentityContext, task: any) => {
async function internal(opts = { existing: false }) { async function internal(opts = { existing: false }) {
if (!opts.existing) { if (!opts.existing) {
cls.setOnContext(ContextKeys.IDENTITY, identity) cls.setOnContext(ContextKey.IDENTITY, identity)
// set the tenant so that doInTenant will preserve identity // set the tenant so that doInTenant will preserve identity
if (identity.tenantId) { if (identity.tenantId) {
updateTenantId(identity.tenantId) updateTenantId(identity.tenantId)
@ -146,27 +146,27 @@ export const doInIdentityContext = (identity: IdentityContext, task: any) => {
// invoke the task // invoke the task
return await task() return await task()
} finally { } finally {
await closeWithUsing(ContextKeys.IDENTITY_IN_USE, async () => { await closeWithUsing(ContextKey.IDENTITY_IN_USE, async () => {
setIdentity(null) setIdentity(null)
await closeTenancy() await closeTenancy()
}) })
} }
} }
const existing = cls.getFromContext(ContextKeys.IDENTITY) const existing = cls.getFromContext(ContextKey.IDENTITY)
return updateUsing(ContextKeys.IDENTITY_IN_USE, existing, internal) return updateUsing(ContextKey.IDENTITY_IN_USE, existing, internal)
} }
export const getIdentity = (): IdentityContext | undefined => { export const getIdentity = (): IdentityContext | undefined => {
try { try {
return cls.getFromContext(ContextKeys.IDENTITY) return cls.getFromContext(ContextKey.IDENTITY)
} catch (e) { } catch (e) {
// do nothing - identity is not in context // do nothing - identity is not in context
} }
} }
export const updateTenantId = (tenantId: string | null) => { export const updateTenantId = (tenantId: string | null) => {
cls.setOnContext(ContextKeys.TENANT_ID, tenantId) cls.setOnContext(ContextKey.TENANT_ID, tenantId)
if (env.USE_COUCH) { if (env.USE_COUCH) {
setGlobalDB(tenantId) setGlobalDB(tenantId)
} }
@ -176,7 +176,7 @@ export const updateAppId = async (appId: string) => {
try { try {
// have to close first, before removing the databases from context // have to close first, before removing the databases from context
await closeAppDBs() await closeAppDBs()
cls.setOnContext(ContextKeys.APP_ID, appId) cls.setOnContext(ContextKey.APP_ID, appId)
} catch (err) { } catch (err) {
if (env.isTest()) { if (env.isTest()) {
TEST_APP_ID = appId TEST_APP_ID = appId
@ -189,12 +189,12 @@ export const updateAppId = async (appId: string) => {
export const setGlobalDB = (tenantId: string | null) => { export const setGlobalDB = (tenantId: string | null) => {
const dbName = baseGlobalDBName(tenantId) const dbName = baseGlobalDBName(tenantId)
const db = dangerousGetDB(dbName) const db = dangerousGetDB(dbName)
cls.setOnContext(ContextKeys.GLOBAL_DB, db) cls.setOnContext(ContextKey.GLOBAL_DB, db)
return db return db
} }
export const getGlobalDB = () => { export const getGlobalDB = () => {
const db = cls.getFromContext(ContextKeys.GLOBAL_DB) const db = cls.getFromContext(ContextKey.GLOBAL_DB)
if (!db) { if (!db) {
throw new Error("Global DB not found") throw new Error("Global DB not found")
} }
@ -202,7 +202,7 @@ export const getGlobalDB = () => {
} }
export const isTenantIdSet = () => { export const isTenantIdSet = () => {
const tenantId = cls.getFromContext(ContextKeys.TENANT_ID) const tenantId = cls.getFromContext(ContextKey.TENANT_ID)
return !!tenantId return !!tenantId
} }
@ -210,7 +210,7 @@ export const getTenantId = () => {
if (!isMultiTenant()) { if (!isMultiTenant()) {
return DEFAULT_TENANT_ID return DEFAULT_TENANT_ID
} }
const tenantId = cls.getFromContext(ContextKeys.TENANT_ID) const tenantId = cls.getFromContext(ContextKey.TENANT_ID)
if (!tenantId) { if (!tenantId) {
throw new Error("Tenant id not found") throw new Error("Tenant id not found")
} }
@ -218,7 +218,7 @@ export const getTenantId = () => {
} }
export const getAppId = () => { export const getAppId = () => {
const foundId = cls.getFromContext(ContextKeys.APP_ID) const foundId = cls.getFromContext(ContextKey.APP_ID)
if (!foundId && env.isTest() && TEST_APP_ID) { if (!foundId && env.isTest() && TEST_APP_ID) {
return TEST_APP_ID return TEST_APP_ID
} else { } else {
@ -231,7 +231,7 @@ export const getAppId = () => {
* contained, dev or prod. * contained, dev or prod.
*/ */
export const getAppDB = (opts?: any) => { export const getAppDB = (opts?: any) => {
return getContextDB(ContextKeys.CURRENT_DB, opts) return getContextDB(ContextKey.CURRENT_DB, opts)
} }
/** /**
@ -239,7 +239,7 @@ export const getAppDB = (opts?: any) => {
* contained a development app ID, this will open the prod one. * contained a development app ID, this will open the prod one.
*/ */
export const getProdAppDB = (opts?: any) => { export const getProdAppDB = (opts?: any) => {
return getContextDB(ContextKeys.PROD_DB, opts) return getContextDB(ContextKey.PROD_DB, opts)
} }
/** /**
@ -247,5 +247,5 @@ export const getProdAppDB = (opts?: any) => {
* contained a prod app ID, this will open the dev one. * contained a prod app ID, this will open the dev one.
*/ */
export const getDevAppDB = (opts?: any) => { export const getDevAppDB = (opts?: any) => {
return getContextDB(ContextKeys.DEV_DB, opts) return getContextDB(ContextKey.DEV_DB, opts)
} }

View file

@ -6,7 +6,7 @@ import {
} from "./index" } from "./index"
import cls from "./FunctionContext" import cls from "./FunctionContext"
import { IdentityContext } from "@budibase/types" import { IdentityContext } from "@budibase/types"
import { ContextKeys } from "./constants" import { ContextKey } from "./constants"
import { dangerousGetDB, closeDB } from "../db" import { dangerousGetDB, closeDB } from "../db"
import { isEqual } from "lodash" import { isEqual } from "lodash"
import { getDevelopmentAppID, getProdAppID } from "../db/conversions" import { getDevelopmentAppID, getProdAppID } from "../db/conversions"
@ -47,17 +47,13 @@ export const setAppTenantId = (appId: string) => {
} }
export const setIdentity = (identity: IdentityContext | null) => { export const setIdentity = (identity: IdentityContext | null) => {
cls.setOnContext(ContextKeys.IDENTITY, identity) cls.setOnContext(ContextKey.IDENTITY, identity)
} }
// this function makes sure the PouchDB objects are closed and // this function makes sure the PouchDB objects are closed and
// fully deleted when finished - this protects against memory leaks // fully deleted when finished - this protects against memory leaks
export async function closeAppDBs() { export async function closeAppDBs() {
const dbKeys = [ const dbKeys = [ContextKey.CURRENT_DB, ContextKey.PROD_DB, ContextKey.DEV_DB]
ContextKeys.CURRENT_DB,
ContextKeys.PROD_DB,
ContextKeys.DEV_DB,
]
for (let dbKey of dbKeys) { for (let dbKey of dbKeys) {
const db = cls.getFromContext(dbKey) const db = cls.getFromContext(dbKey)
if (!db) { if (!db) {
@ -68,16 +64,16 @@ export async function closeAppDBs() {
cls.setOnContext(dbKey, null) cls.setOnContext(dbKey, null)
} }
// clear the app ID now that the databases are closed // clear the app ID now that the databases are closed
if (cls.getFromContext(ContextKeys.APP_ID)) { if (cls.getFromContext(ContextKey.APP_ID)) {
cls.setOnContext(ContextKeys.APP_ID, null) cls.setOnContext(ContextKey.APP_ID, null)
} }
if (cls.getFromContext(ContextKeys.DB_OPTS)) { if (cls.getFromContext(ContextKey.DB_OPTS)) {
cls.setOnContext(ContextKeys.DB_OPTS, null) cls.setOnContext(ContextKey.DB_OPTS, null)
} }
} }
export function getContextDB(key: string, opts: any) { export function getContextDB(key: string, opts: any) {
const dbOptsKey = `${key}${ContextKeys.DB_OPTS}` const dbOptsKey = `${key}${ContextKey.DB_OPTS}`
let storedOpts = cls.getFromContext(dbOptsKey) let storedOpts = cls.getFromContext(dbOptsKey)
let db = cls.getFromContext(key) let db = cls.getFromContext(key)
if (db && isEqual(opts, storedOpts)) { if (db && isEqual(opts, storedOpts)) {
@ -88,13 +84,13 @@ export function getContextDB(key: string, opts: any) {
let toUseAppId let toUseAppId
switch (key) { switch (key) {
case ContextKeys.CURRENT_DB: case ContextKey.CURRENT_DB:
toUseAppId = appId toUseAppId = appId
break break
case ContextKeys.PROD_DB: case ContextKey.PROD_DB:
toUseAppId = getProdAppID(appId) toUseAppId = getProdAppID(appId)
break break
case ContextKeys.DEV_DB: case ContextKey.DEV_DB:
toUseAppId = getDevelopmentAppID(appId) toUseAppId = getDevelopmentAppID(appId)
break break
} }

View file

@ -4,13 +4,13 @@ export const UNICODE_MAX = "\ufff0"
/** /**
* Can be used to create a few different forms of querying a view. * Can be used to create a few different forms of querying a view.
*/ */
export enum AutomationViewModes { export enum AutomationViewMode {
ALL = "all", ALL = "all",
AUTOMATION = "automation", AUTOMATION = "automation",
STATUS = "status", STATUS = "status",
} }
export enum ViewNames { export enum ViewName {
USER_BY_APP = "by_app", USER_BY_APP = "by_app",
USER_BY_EMAIL = "by_email2", USER_BY_EMAIL = "by_email2",
BY_API_KEY = "by_api_key", BY_API_KEY = "by_api_key",
@ -21,13 +21,13 @@ export enum ViewNames {
} }
export const DeprecatedViews = { export const DeprecatedViews = {
[ViewNames.USER_BY_EMAIL]: [ [ViewName.USER_BY_EMAIL]: [
// removed due to inaccuracy in view doc filter logic // removed due to inaccuracy in view doc filter logic
"by_email", "by_email",
], ],
} }
export enum DocumentTypes { export enum DocumentType {
USER = "us", USER = "us",
GROUP = "gr", GROUP = "gr",
WORKSPACE = "workspace", WORKSPACE = "workspace",
@ -62,6 +62,6 @@ export const StaticDatabases = {
}, },
} }
export const APP_PREFIX = exports.DocumentTypes.APP + exports.SEPARATOR export const APP_PREFIX = DocumentType.APP + SEPARATOR
export const APP_DEV = exports.DocumentTypes.APP_DEV + exports.SEPARATOR export const APP_DEV = DocumentType.APP_DEV + SEPARATOR
export const APP_DEV_PREFIX = APP_DEV export const APP_DEV_PREFIX = APP_DEV

View file

@ -1,7 +1,7 @@
import { newid } from "../hashing" import { newid } from "../hashing"
import { DEFAULT_TENANT_ID, Configs } from "../constants" import { DEFAULT_TENANT_ID, Configs } from "../constants"
import env from "../environment" import env from "../environment"
import { SEPARATOR, DocumentTypes, UNICODE_MAX, ViewNames } from "./constants" import { SEPARATOR, DocumentType, UNICODE_MAX, ViewName } from "./constants"
import { getTenantId, getGlobalDBName, getGlobalDB } from "../tenancy" import { getTenantId, getGlobalDBName, getGlobalDB } from "../tenancy"
import fetch from "node-fetch" import fetch from "node-fetch"
import { doWithDB, allDbs } from "./index" import { doWithDB, allDbs } from "./index"
@ -58,7 +58,7 @@ export function getDocParams(
/** /**
* Retrieve the correct index for a view based on default design DB. * Retrieve the correct index for a view based on default design DB.
*/ */
export function getQueryIndex(viewName: ViewNames) { export function getQueryIndex(viewName: ViewName) {
return `database/${viewName}` return `database/${viewName}`
} }
@ -67,7 +67,7 @@ export function getQueryIndex(viewName: ViewNames) {
* @returns {string} The new workspace ID which the workspace doc can be stored under. * @returns {string} The new workspace ID which the workspace doc can be stored under.
*/ */
export function generateWorkspaceID() { export function generateWorkspaceID() {
return `${DocumentTypes.WORKSPACE}${SEPARATOR}${newid()}` return `${DocumentType.WORKSPACE}${SEPARATOR}${newid()}`
} }
/** /**
@ -76,8 +76,8 @@ export function generateWorkspaceID() {
export function getWorkspaceParams(id = "", otherProps = {}) { export function getWorkspaceParams(id = "", otherProps = {}) {
return { return {
...otherProps, ...otherProps,
startkey: `${DocumentTypes.WORKSPACE}${SEPARATOR}${id}`, startkey: `${DocumentType.WORKSPACE}${SEPARATOR}${id}`,
endkey: `${DocumentTypes.WORKSPACE}${SEPARATOR}${id}${UNICODE_MAX}`, endkey: `${DocumentType.WORKSPACE}${SEPARATOR}${id}${UNICODE_MAX}`,
} }
} }
@ -86,7 +86,7 @@ export function getWorkspaceParams(id = "", otherProps = {}) {
* @returns {string} The new user ID which the user doc can be stored under. * @returns {string} The new user ID which the user doc can be stored under.
*/ */
export function generateGlobalUserID(id?: any) { export function generateGlobalUserID(id?: any) {
return `${DocumentTypes.USER}${SEPARATOR}${id || newid()}` return `${DocumentType.USER}${SEPARATOR}${id || newid()}`
} }
/** /**
@ -102,8 +102,8 @@ export function getGlobalUserParams(globalId: any, otherProps: any = {}) {
// need to include this incase pagination // need to include this incase pagination
startkey: startkey startkey: startkey
? startkey ? startkey
: `${DocumentTypes.USER}${SEPARATOR}${globalId}`, : `${DocumentType.USER}${SEPARATOR}${globalId}`,
endkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`, endkey: `${DocumentType.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`,
} }
} }
@ -121,7 +121,7 @@ export function getUsersByAppParams(appId: any, otherProps: any = {}) {
* @param ownerId The owner/user of the template, this could be global or a workspace level. * @param ownerId The owner/user of the template, this could be global or a workspace level.
*/ */
export function generateTemplateID(ownerId: any) { export function generateTemplateID(ownerId: any) {
return `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}` return `${DocumentType.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}`
} }
export function generateAppUserID(prodAppId: string, userId: string) { export function generateAppUserID(prodAppId: string, userId: string) {
@ -143,7 +143,7 @@ export function getTemplateParams(
if (templateId) { if (templateId) {
final = templateId final = templateId
} else { } else {
final = `${DocumentTypes.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}` final = `${DocumentType.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}`
} }
return { return {
...otherProps, ...otherProps,
@ -157,14 +157,14 @@ export function getTemplateParams(
* @returns {string} The new role ID which the role doc can be stored under. * @returns {string} The new role ID which the role doc can be stored under.
*/ */
export function generateRoleID(id: any) { export function generateRoleID(id: any) {
return `${DocumentTypes.ROLE}${SEPARATOR}${id || newid()}` return `${DocumentType.ROLE}${SEPARATOR}${id || newid()}`
} }
/** /**
* Gets parameters for retrieving a role, this is a utility function for the getDocParams function. * Gets parameters for retrieving a role, this is a utility function for the getDocParams function.
*/ */
export function getRoleParams(roleId = null, otherProps = {}) { export function getRoleParams(roleId = null, otherProps = {}) {
return getDocParams(DocumentTypes.ROLE, roleId, otherProps) return getDocParams(DocumentType.ROLE, roleId, otherProps)
} }
export function getStartEndKeyURL(base: any, baseKey: any, tenantId = null) { export function getStartEndKeyURL(base: any, baseKey: any, tenantId = null) {
@ -211,9 +211,9 @@ export async function getAllDbs(opts = { efficient: false }) {
await addDbs(couchUrl) await addDbs(couchUrl)
} else { } else {
// get prod apps // get prod apps
await addDbs(getStartEndKeyURL(couchUrl, DocumentTypes.APP, tenantId)) await addDbs(getStartEndKeyURL(couchUrl, DocumentType.APP, tenantId))
// get dev apps // get dev apps
await addDbs(getStartEndKeyURL(couchUrl, DocumentTypes.APP_DEV, tenantId)) await addDbs(getStartEndKeyURL(couchUrl, DocumentType.APP_DEV, tenantId))
// add global db name // add global db name
dbs.push(getGlobalDBName(tenantId)) dbs.push(getGlobalDBName(tenantId))
} }
@ -233,14 +233,18 @@ export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) {
} }
let dbs = await getAllDbs({ efficient }) let dbs = await getAllDbs({ efficient })
const appDbNames = dbs.filter((dbName: any) => { const appDbNames = dbs.filter((dbName: any) => {
if (env.isTest() && !dbName) {
return false
}
const split = dbName.split(SEPARATOR) const split = dbName.split(SEPARATOR)
// it is an app, check the tenantId // it is an app, check the tenantId
if (split[0] === DocumentTypes.APP) { if (split[0] === DocumentType.APP) {
// tenantId is always right before the UUID // tenantId is always right before the UUID
const possibleTenantId = split[split.length - 2] const possibleTenantId = split[split.length - 2]
const noTenantId = const noTenantId =
split.length === 2 || possibleTenantId === DocumentTypes.DEV split.length === 2 || possibleTenantId === DocumentType.DEV
return ( return (
(tenantId === DEFAULT_TENANT_ID && noTenantId) || (tenantId === DEFAULT_TENANT_ID && noTenantId) ||
@ -326,7 +330,7 @@ export async function dbExists(dbName: any) {
export const generateConfigID = ({ type, workspace, user }: any) => { export const generateConfigID = ({ type, workspace, user }: any) => {
const scope = [type, workspace, user].filter(Boolean).join(SEPARATOR) const scope = [type, workspace, user].filter(Boolean).join(SEPARATOR)
return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}` return `${DocumentType.CONFIG}${SEPARATOR}${scope}`
} }
/** /**
@ -340,8 +344,8 @@ export const getConfigParams = (
return { return {
...otherProps, ...otherProps,
startkey: `${DocumentTypes.CONFIG}${SEPARATOR}${scope}`, startkey: `${DocumentType.CONFIG}${SEPARATOR}${scope}`,
endkey: `${DocumentTypes.CONFIG}${SEPARATOR}${scope}${UNICODE_MAX}`, endkey: `${DocumentType.CONFIG}${SEPARATOR}${scope}${UNICODE_MAX}`,
} }
} }
@ -350,7 +354,7 @@ export const getConfigParams = (
* @returns {string} The new dev info ID which info for dev (like api key) can be stored under. * @returns {string} The new dev info ID which info for dev (like api key) can be stored under.
*/ */
export const generateDevInfoID = (userId: any) => { export const generateDevInfoID = (userId: any) => {
return `${DocumentTypes.DEV_INFO}${SEPARATOR}${userId}` return `${DocumentType.DEV_INFO}${SEPARATOR}${userId}`
} }
/** /**

View file

@ -1,6 +1,6 @@
const { const {
DocumentTypes, DocumentType,
ViewNames, ViewName,
DeprecatedViews, DeprecatedViews,
SEPARATOR, SEPARATOR,
} = require("./utils") } = require("./utils")
@ -44,14 +44,14 @@ exports.createNewUserEmailView = async () => {
const view = { const view = {
// if using variables in a map function need to inject them before use // if using variables in a map function need to inject them before use
map: `function(doc) { map: `function(doc) {
if (doc._id.startsWith("${DocumentTypes.USER}${SEPARATOR}")) { if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}")) {
emit(doc.email.toLowerCase(), doc._id) emit(doc.email.toLowerCase(), doc._id)
} }
}`, }`,
} }
designDoc.views = { designDoc.views = {
...designDoc.views, ...designDoc.views,
[ViewNames.USER_BY_EMAIL]: view, [ViewName.USER_BY_EMAIL]: view,
} }
await db.put(designDoc) await db.put(designDoc)
} }
@ -68,7 +68,7 @@ exports.createUserAppView = async () => {
const view = { const view = {
// if using variables in a map function need to inject them before use // if using variables in a map function need to inject them before use
map: `function(doc) { map: `function(doc) {
if (doc._id.startsWith("${DocumentTypes.USER}${SEPARATOR}") && doc.roles) { if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}") && doc.roles) {
for (let prodAppId of Object.keys(doc.roles)) { for (let prodAppId of Object.keys(doc.roles)) {
let emitted = prodAppId + "${SEPARATOR}" + doc._id let emitted = prodAppId + "${SEPARATOR}" + doc._id
emit(emitted, null) emit(emitted, null)
@ -78,7 +78,7 @@ exports.createUserAppView = async () => {
} }
designDoc.views = { designDoc.views = {
...designDoc.views, ...designDoc.views,
[ViewNames.USER_BY_APP]: view, [ViewName.USER_BY_APP]: view,
} }
await db.put(designDoc) await db.put(designDoc)
} }
@ -93,14 +93,14 @@ exports.createApiKeyView = async () => {
} }
const view = { const view = {
map: `function(doc) { map: `function(doc) {
if (doc._id.startsWith("${DocumentTypes.DEV_INFO}") && doc.apiKey) { if (doc._id.startsWith("${DocumentType.DEV_INFO}") && doc.apiKey) {
emit(doc.apiKey, doc.userId) emit(doc.apiKey, doc.userId)
} }
}`, }`,
} }
designDoc.views = { designDoc.views = {
...designDoc.views, ...designDoc.views,
[ViewNames.BY_API_KEY]: view, [ViewName.BY_API_KEY]: view,
} }
await db.put(designDoc) await db.put(designDoc)
} }
@ -123,17 +123,17 @@ exports.createUserBuildersView = async () => {
} }
designDoc.views = { designDoc.views = {
...designDoc.views, ...designDoc.views,
[ViewNames.USER_BY_BUILDERS]: view, [ViewName.USER_BY_BUILDERS]: view,
} }
await db.put(designDoc) await db.put(designDoc)
} }
exports.queryGlobalView = async (viewName, params, db = null) => { exports.queryGlobalView = async (viewName, params, db = null) => {
const CreateFuncByName = { const CreateFuncByName = {
[ViewNames.USER_BY_EMAIL]: exports.createNewUserEmailView, [ViewName.USER_BY_EMAIL]: exports.createNewUserEmailView,
[ViewNames.BY_API_KEY]: exports.createApiKeyView, [ViewName.BY_API_KEY]: exports.createApiKeyView,
[ViewNames.USER_BY_BUILDERS]: exports.createUserBuildersView, [ViewName.USER_BY_BUILDERS]: exports.createUserBuildersView,
[ViewNames.USER_BY_APP]: exports.createUserAppView, [ViewName.USER_BY_APP]: exports.createUserAppView,
} }
// can pass DB in if working with something specific // can pass DB in if working with something specific
if (!db) { if (!db) {

View file

@ -1,27 +0,0 @@
import { EventEmitter } from "events"
import * as context from "../../context"
import { Identity, Event } from "@budibase/types"
export interface EmittedEvent {
tenantId: string
identity: Identity
appId: string | undefined
properties: any
}
class BBEventEmitter extends EventEmitter {
emitEvent(event: Event, properties: any, identity: Identity) {
const tenantId = context.getTenantId()
const appId = context.getAppId()
const emittedEvent: EmittedEvent = {
tenantId,
identity,
appId,
properties,
}
this.emit(event, emittedEvent)
}
}
export const emitter = new BBEventEmitter()

View file

@ -1 +0,0 @@
export * from "./BBEventEmitter"

View file

@ -2,41 +2,6 @@ import { Event } from "@budibase/types"
import { processors } from "./processors" import { processors } from "./processors"
import * as identification from "./identification" import * as identification from "./identification"
import * as backfill from "./backfill" import * as backfill from "./backfill"
import { emitter, EmittedEvent } from "./emit"
import * as context from "../context"
import * as logging from "../logging"
const USE_EMITTER: any[] = [
Event.SERVED_BUILDER,
Event.SERVED_APP,
Event.SERVED_APP_PREVIEW,
]
for (let event of USE_EMITTER) {
emitter.on(event, async (props: EmittedEvent) => {
try {
await context.doInTenant(props.tenantId, async () => {
if (props.appId) {
await context.doInAppContext(props.appId, async () => {
await processors.processEvent(
event as Event,
props.identity,
props.properties
)
})
} else {
await processors.processEvent(
event as Event,
props.identity,
props.properties
)
}
})
} catch (e) {
logging.logAlert(`Unable to process async event ${event}`, e)
}
})
}
export const publishEvent = async ( export const publishEvent = async (
event: Event, event: Event,
@ -46,11 +11,6 @@ export const publishEvent = async (
// in future this should use async events via a distributed queue. // in future this should use async events via a distributed queue.
const identity = await identification.getCurrentIdentity() const identity = await identification.getCurrentIdentity()
if (USE_EMITTER.includes(event)) {
emitter.emitEvent(event, properties, identity)
return
}
const backfilling = await backfill.isBackfillingEvent(event) const backfilling = await backfill.isBackfillingEvent(event)
// no backfill - send the event and exit // no backfill - send the event and exit
if (!backfilling) { if (!backfilling) {

View file

@ -7,22 +7,26 @@ import {
AppServedEvent, AppServedEvent,
} from "@budibase/types" } from "@budibase/types"
export async function servedBuilder() { export async function servedBuilder(timezone: string) {
const properties: BuilderServedEvent = {} const properties: BuilderServedEvent = {
timezone,
}
await publishEvent(Event.SERVED_BUILDER, properties) await publishEvent(Event.SERVED_BUILDER, properties)
} }
export async function servedApp(app: App) { export async function servedApp(app: App, timezone: string) {
const properties: AppServedEvent = { const properties: AppServedEvent = {
appVersion: app.version, appVersion: app.version,
timezone,
} }
await publishEvent(Event.SERVED_APP, properties) await publishEvent(Event.SERVED_APP, properties)
} }
export async function servedAppPreview(app: App) { export async function servedAppPreview(app: App, timezone: string) {
const properties: AppPreviewServedEvent = { const properties: AppPreviewServedEvent = {
appId: app.appId, appId: app.appId,
appVersion: app.version, appVersion: app.version,
timezone,
} }
await publishEvent(Event.SERVED_APP_PREVIEW, properties) await publishEvent(Event.SERVED_APP_PREVIEW, properties)
} }

View file

@ -1,4 +1,5 @@
import errors from "./errors" import errors from "./errors"
const errorClasses = errors.errors const errorClasses = errors.errors
import * as events from "./events" import * as events from "./events"
import * as migrations from "./migrations" import * as migrations from "./migrations"

View file

@ -4,7 +4,7 @@ import { getUser } from "../cache/user"
import { getSession, updateSessionTTL } from "../security/sessions" import { getSession, updateSessionTTL } from "../security/sessions"
import { buildMatcherRegex, matches } from "./matchers" import { buildMatcherRegex, matches } from "./matchers"
import { SEPARATOR } from "../db/constants" import { SEPARATOR } from "../db/constants"
import { ViewNames } from "../db/utils" import { ViewName } from "../db/utils"
import { queryGlobalView } from "../db/views" import { queryGlobalView } from "../db/views"
import { getGlobalDB, doInTenant } from "../tenancy" import { getGlobalDB, doInTenant } from "../tenancy"
import { decrypt } from "../security/encryption" import { decrypt } from "../security/encryption"
@ -43,7 +43,7 @@ async function checkApiKey(apiKey: string, populateUser?: Function) {
const db = getGlobalDB() const db = getGlobalDB()
// api key is encrypted in the database // api key is encrypted in the database
const userId = await queryGlobalView( const userId = await queryGlobalView(
ViewNames.BY_API_KEY, ViewName.BY_API_KEY,
{ {
key: apiKey, key: apiKey,
}, },

View file

@ -1,6 +1,6 @@
import { DEFAULT_TENANT_ID } from "../constants" import { DEFAULT_TENANT_ID } from "../constants"
import { doWithDB } from "../db" import { doWithDB } from "../db"
import { DocumentTypes, StaticDatabases } from "../db/constants" import { DocumentType, StaticDatabases } from "../db/constants"
import { getAllApps } from "../db/utils" import { getAllApps } from "../db/utils"
import environment from "../environment" import environment from "../environment"
import { import {
@ -21,10 +21,10 @@ import {
export const getMigrationsDoc = async (db: any) => { export const getMigrationsDoc = async (db: any) => {
// get the migrations doc // get the migrations doc
try { try {
return await db.get(DocumentTypes.MIGRATIONS) return await db.get(DocumentType.MIGRATIONS)
} catch (err: any) { } catch (err: any) {
if (err.status && err.status === 404) { if (err.status && err.status === 404) {
return { _id: DocumentTypes.MIGRATIONS } return { _id: DocumentType.MIGRATIONS }
} else { } else {
console.error(err) console.error(err)
throw err throw err

View file

@ -3,7 +3,7 @@ const { BUILTIN_PERMISSION_IDS, PermissionLevels } = require("./permissions")
const { const {
generateRoleID, generateRoleID,
getRoleParams, getRoleParams,
DocumentTypes, DocumentType,
SEPARATOR, SEPARATOR,
} = require("../db/utils") } = require("../db/utils")
const { getAppDB } = require("../context") const { getAppDB } = require("../context")
@ -338,7 +338,7 @@ class AccessController {
* Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions). * Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions).
*/ */
exports.getDBRoleID = roleId => { exports.getDBRoleID = roleId => {
if (roleId.startsWith(DocumentTypes.ROLE)) { if (roleId.startsWith(DocumentType.ROLE)) {
return roleId return roleId
} }
return generateRoleID(roleId) return generateRoleID(roleId)
@ -349,8 +349,8 @@ exports.getDBRoleID = roleId => {
*/ */
exports.getExternalRoleID = roleId => { exports.getExternalRoleID = roleId => {
// for built in roles we want to remove the DB role ID element (role_) // for built in roles we want to remove the DB role ID element (role_)
if (roleId.startsWith(DocumentTypes.ROLE) && isBuiltin(roleId)) { if (roleId.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) {
return roleId.split(`${DocumentTypes.ROLE}${SEPARATOR}`)[1] return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1]
} }
return roleId return roleId
} }

View file

@ -1,5 +1,5 @@
const { const {
ViewNames, ViewName,
getUsersByAppParams, getUsersByAppParams,
getProdAppID, getProdAppID,
generateAppUserID, generateAppUserID,
@ -18,7 +18,7 @@ exports.getGlobalUserByEmail = async email => {
throw "Must supply an email address to view" throw "Must supply an email address to view"
} }
return await queryGlobalView(ViewNames.USER_BY_EMAIL, { return await queryGlobalView(ViewName.USER_BY_EMAIL, {
key: email.toLowerCase(), key: email.toLowerCase(),
include_docs: true, include_docs: true,
}) })
@ -32,7 +32,7 @@ exports.searchGlobalUsersByApp = async (appId, opts) => {
include_docs: true, include_docs: true,
}) })
params.startkey = opts && opts.startkey ? opts.startkey : params.startkey params.startkey = opts && opts.startkey ? opts.startkey : params.startkey
let response = await queryGlobalView(ViewNames.USER_BY_APP, params) let response = await queryGlobalView(ViewName.USER_BY_APP, params)
if (!response) { if (!response) {
response = [] response = []
} }
@ -56,7 +56,7 @@ exports.searchGlobalUsersByEmail = async (email, opts) => {
const lcEmail = email.toLowerCase() const lcEmail = email.toLowerCase()
// handle if passing up startkey for pagination // handle if passing up startkey for pagination
const startkey = opts && opts.startkey ? opts.startkey : lcEmail const startkey = opts && opts.startkey ? opts.startkey : lcEmail
let response = await queryGlobalView(ViewNames.USER_BY_EMAIL, { let response = await queryGlobalView(ViewName.USER_BY_EMAIL, {
...opts, ...opts,
startkey, startkey,
endkey: `${lcEmail}${UNICODE_MAX}`, endkey: `${lcEmail}${UNICODE_MAX}`,

View file

@ -1,9 +1,4 @@
const { const { DocumentType, SEPARATOR, ViewName, getAllApps } = require("./db/utils")
DocumentTypes,
SEPARATOR,
ViewNames,
getAllApps,
} = require("./db/utils")
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
const { options } = require("./middleware/passport/jwt") const { options } = require("./middleware/passport/jwt")
const { queryGlobalView } = require("./db/views") const { queryGlobalView } = require("./db/views")
@ -17,7 +12,7 @@ const {
const events = require("./events") const events = require("./events")
const tenancy = require("./tenancy") const tenancy = require("./tenancy")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR const APP_PREFIX = DocumentType.APP + SEPARATOR
const PROD_APP_PREFIX = "/app/" const PROD_APP_PREFIX = "/app/"
function confirmAppId(possibleAppId) { function confirmAppId(possibleAppId) {
@ -154,7 +149,7 @@ exports.isClient = ctx => {
} }
const getBuilders = async () => { const getBuilders = async () => {
const builders = await queryGlobalView(ViewNames.USER_BY_BUILDERS, { const builders = await queryGlobalView(ViewName.USER_BY_BUILDERS, {
include_docs: false, include_docs: false,
}) })

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "1.2.28-alpha.0", "version": "1.2.38",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1", "@adobe/spectrum-css-workflow-icons": "^1.2.1",
"@budibase/string-templates": "1.2.28-alpha.0", "@budibase/string-templates": "^1.2.38",
"@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2", "@spectrum-css/avatar": "^3.0.2",

View file

@ -16,6 +16,7 @@
export let appendTo = undefined export let appendTo = undefined
export let timeOnly = false export let timeOnly = false
export let ignoreTimezones = false export let ignoreTimezones = false
export let time24hr = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const flatpickrId = `${uuid()}-wrapper` const flatpickrId = `${uuid()}-wrapper`
@ -37,6 +38,7 @@
enableTime: timeOnly || enableTime || false, enableTime: timeOnly || enableTime || false,
noCalendar: timeOnly || false, noCalendar: timeOnly || false,
altInput: true, altInput: true,
time_24hr: time24hr || false,
altFormat: timeOnly ? "H:i" : enableTime ? "F j Y, H:i" : "F j, Y", altFormat: timeOnly ? "H:i" : enableTime ? "F j Y, H:i" : "F j, Y",
wrap: true, wrap: true,
appendTo, appendTo,
@ -49,6 +51,12 @@
}, },
} }
$: redrawOptions = {
timeOnly,
enableTime,
time24hr,
}
const handleChange = event => { const handleChange = event => {
const [dates] = event.detail const [dates] = event.detail
const noTimezone = enableTime && !timeOnly && ignoreTimezones const noTimezone = enableTime && !timeOnly && ignoreTimezones
@ -142,7 +150,7 @@
} }
</script> </script>
{#key timeOnly} {#key redrawOptions}
<Flatpickr <Flatpickr
bind:flatpickr bind:flatpickr
value={parseDate(value)} value={parseDate(value)}

View file

@ -10,6 +10,7 @@
export let error = null export let error = null
export let enableTime = true export let enableTime = true
export let timeOnly = false export let timeOnly = false
export let time24hr = false
export let placeholder = null export let placeholder = null
export let appendTo = undefined export let appendTo = undefined
export let ignoreTimezones = false export let ignoreTimezones = false
@ -30,6 +31,7 @@
{placeholder} {placeholder}
{enableTime} {enableTime}
{timeOnly} {timeOnly}
{time24hr}
{appendTo} {appendTo}
{ignoreTimezones} {ignoreTimezones}
on:change={onChange} on:change={onChange}

View file

@ -23,7 +23,7 @@ filterTests(["smoke", "all"], () => {
cy.get(interact.SPECTRUM_ICON).click({ force: true }) cy.get(interact.SPECTRUM_ICON).click({ force: true })
}) })
cy.get(interact.SPECTRUM_MENU).within(() => { cy.get(interact.SPECTRUM_MENU).within(() => {
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force Password Reset").click({ force: true }) cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
}) })
cy.get(interact.SPECTRUM_DIALOG_GRID) cy.get(interact.SPECTRUM_DIALOG_GRID)
@ -41,10 +41,25 @@ filterTests(["smoke", "all"], () => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test") cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).eq(i).type("test")
} }
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true }) cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
//cy.logoutNoAppGrid()
})
it("should verify Standard Portal", () => {
// Development access should be disabled (Admin access is already disabled)
cy.login()
cy.setUserRole("bbuser", "App User")
bbUserLogin()
// Verify Standard Portal
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
cy.get(".app").should('not.exist') // No apps -> no roles assigned to user
cy.get(interact.CONTAINER).should('contain', bbUserEmail) // Message containing users email
cy.logoutNoAppGrid() cy.logoutNoAppGrid()
}) })
xit("should verify Admin Portal", () => { it("should verify Admin Portal", () => {
cy.login() cy.login()
// Configure user role // Configure user role
cy.setUserRole("bbuser", "Admin") cy.setUserRole("bbuser", "Admin")
@ -86,21 +101,6 @@ filterTests(["smoke", "all"], () => {
cy.logOut() cy.logOut()
}) })
it("should verify Standard Portal", () => {
// Development access should be disabled (Admin access is already disabled)
cy.login()
cy.setUserRole("bbuser", "App User")
bbUserLogin()
// Verify Standard Portal
cy.get(interact.SPECTRUM_SIDENAV).should('not.exist') // No config sections
cy.get(interact.CREATE_APP_BUTTON).should('not.exist') // No create app button
cy.get(".app").should('not.exist') // No apps -> no roles assigned to user
cy.get(interact.CONTAINER).should('contain', bbUserEmail) // Message containing users email
cy.logoutNoAppGrid()
})
const bbUserLogin = () => { const bbUserLogin = () => {
// Login as bbuser // Login as bbuser
cy.logOut() cy.logOut()

View file

@ -17,7 +17,7 @@ filterTests(["smoke", "all"], () => {
it("should confirm App User role for a New User", () => { it("should confirm App User role for a New User", () => {
cy.contains("bbuser").click() cy.contains("bbuser").click()
cy.get(".spectrum-Form-itemField").eq(2).should('contain', 'App User') cy.get(".spectrum-Form-itemField").eq(3).should('contain', 'App User')
// User should not have app access // User should not have app access
cy.get(interact.LIST_ITEMS, { timeout: 500 }).should("contain", "No apps") cy.get(interact.LIST_ITEMS, { timeout: 500 }).should("contain", "No apps")
@ -166,12 +166,12 @@ filterTests(["smoke", "all"], () => {
it("Should edit user details within user details page", () => { it("Should edit user details within user details page", () => {
// Add First name // Add First name
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).within(() => { cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
cy.wait(500) cy.wait(500)
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).wait(500).clear().click().type("bb") cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).wait(500).clear().click().type("bb")
}) })
// Add Last name // Add Last name
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => { cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
cy.wait(500) cy.wait(500)
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).click().wait(500).clear().type("test") cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).click().wait(500).clear().type("test")
}) })
@ -180,10 +180,10 @@ filterTests(["smoke", "all"], () => {
cy.reload() cy.reload()
// Confirm details have been saved // Confirm details have been saved
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).within(() => { cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb") cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', "bb")
}) })
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => { cy.get(interact.FIELD, { timeout: 1000 }).eq(2).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).should('have.value', "test") cy.get(interact.SPECTRUM_TEXTFIELD_INPUT, { timeout: 1000 }).should('have.value', "test")
}) })
}) })
@ -193,13 +193,14 @@ filterTests(["smoke", "all"], () => {
cy.get(interact.SPECTRUM_ICON).click({ force: true }) cy.get(interact.SPECTRUM_ICON).click({ force: true })
}) })
cy.get(interact.SPECTRUM_MENU).within(() => { cy.get(interact.SPECTRUM_MENU).within(() => {
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force Password Reset").click({ force: true }) cy.get(interact.SPECTRUM_MENU_ITEM).contains("Force password reset").click({ force: true })
}) })
// Reset password modal // Reset password modal
cy.get(interact.SPECTRUM_DIALOG_GRID) cy.get(interact.SPECTRUM_DIALOG_GRID)
.find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd') .find(interact.SPECTRUM_TEXTFIELD_INPUT).invoke('val').as('pwd')
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true }) cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").click({ force: true })
cy.get(interact.SPECTRUM_BUTTON).contains("Reset password").should('not.exist')
// Logout, then login with new password // Logout, then login with new password
cy.logOut() cy.logOut()
@ -214,6 +215,7 @@ filterTests(["smoke", "all"], () => {
cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true }) cy.get(interact.SPECTRUM_BUTTON).contains("Reset your password").click({ force: true })
// Confirm user logged in afer password change // Confirm user logged in afer password change
cy.login("bbuser@test.com", "test")
cy.get(".avatar > .icon").click({ force: true }) cy.get(".avatar > .icon").click({ force: true })
cy.get(".spectrum-Menu-item").contains("Update user information").click({ force: true }) cy.get(".spectrum-Menu-item").contains("Update user information").click({ force: true })

View file

@ -19,10 +19,10 @@ filterTests(["smoke", "all"], () => {
cy.contains("Users").click() cy.contains("Users").click()
cy.contains("test@test.com").click() cy.contains("test@test.com").click()
cy.get(interact.FIELD, { timeout: 1000 }).eq(0).within(() => { cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname) cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', fname)
}) })
cy.get(interact.FIELD).eq(1).within(() => { cy.get(interact.FIELD).eq(2).within(() => {
cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname) cy.get(interact.SPECTRUM_TEXTFIELD_INPUT).should('have.value', lname)
}) })
}) })
@ -72,7 +72,7 @@ filterTests(["smoke", "all"], () => {
}) })
// Logout & in with new password // Logout & in with new password
cy.logOut() //cy.logOut()
cy.login("test@test.com", "newpwd") cy.login("test@test.com", "newpwd")
}) })
@ -90,7 +90,6 @@ filterTests(["smoke", "all"], () => {
cy.get(interact.SPECTRUM_MENU_ITEM).contains("Open developer mode").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.SPECTRUM_SIDENAV).should('exist') // config sections available
cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available cy.get(interact.CREATE_APP_BUTTON).should('exist') // create app button available
cy.get(interact.APP_TABLE).should('exist') // App table available
}) })
after(() => { after(() => {

View file

@ -94,6 +94,7 @@ filterTests(['smoke', 'all'], () => {
}) })
it("should create the first application from scratch with a default name", () => { it("should create the first application from scratch with a default name", () => {
cy.updateUserInformation("", "")
cy.createApp("", false) cy.createApp("", false)
cy.applicationInAppTable("My app") cy.applicationInAppTable("My app")
cy.deleteApp("My app") cy.deleteApp("My app")

View file

@ -48,7 +48,7 @@ filterTests(["smoke", "all"], () => {
it("deletes a row", () => { it("deletes a row", () => {
cy.get(interact.SPECTRUM_CHECKBOX_INPUT).check({ force: true }) cy.get(interact.SPECTRUM_CHECKBOX_INPUT).check({ force: true })
cy.contains("Delete 1 row(s)").click() cy.contains("Delete 1 row").click()
cy.get(interact.SPECTRUM_MODAL).contains("Delete").click() cy.get(interact.SPECTRUM_MODAL).contains("Delete").click()
cy.contains("RoverUpdated").should("not.exist") cy.contains("RoverUpdated").should("not.exist")
}) })

View file

@ -140,14 +140,14 @@ Cypress.Commands.add("setUserRole", (user, role) => {
// Set Role // Set Role
cy.wait(500) cy.wait(500)
cy.get(".spectrum-Form-itemField") cy.get(".spectrum-Form-itemField")
.eq(2) .eq(3)
.within(() => { .within(() => {
cy.get(".spectrum-Picker-label").click({ force: true }) cy.get(".spectrum-Picker-label").click({ force: true })
}) })
cy.get(".spectrum-Menu").within(() => { cy.get(".spectrum-Menu").within(() => {
cy.get(".spectrum-Menu-itemLabel").contains(role).click({ force: true }) cy.get(".spectrum-Menu-itemLabel").contains(role).click({ force: true })
}) })
cy.get(".spectrum-Form-itemField").eq(2).should("contain", role) cy.get(".spectrum-Form-itemField").eq(3).should("contain", role)
}) })
// APPLICATIONS // APPLICATIONS

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "1.2.28-alpha.0", "version": "1.2.38",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -69,10 +69,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "1.2.28-alpha.0", "@budibase/bbui": "^1.2.38",
"@budibase/client": "1.2.28-alpha.0", "@budibase/client": "^1.2.38",
"@budibase/frontend-core": "1.2.28-alpha.0", "@budibase/frontend-core": "^1.2.38",
"@budibase/string-templates": "1.2.28-alpha.0", "@budibase/string-templates": "^1.2.38",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View file

@ -79,7 +79,7 @@
automationStore.actions.addTestDataToAutomation({ automationStore.actions.addTestDataToAutomation({
body: { body: {
[key]: e.detail, [key]: e.detail,
...$automationStore.selectedAutomation.automation.testData.body, ...$automationStore.selectedAutomation.automation.testData?.body,
}, },
}) })
} }

View file

@ -3,6 +3,7 @@
import { datasources, integrations, queries } from "stores/backend" import { datasources, integrations, queries } from "stores/backend"
import BindingBuilder from "components/integration/QueryBindingBuilder.svelte" import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte" import IntegrationQueryEditor from "components/integration/index.svelte"
import { BUDIBASE_DATASOURCE_ID } from "constants/backend"
export let parameters export let parameters
export let bindings = [] export let bindings = []
@ -11,6 +12,10 @@
$: datasource = $datasources.list.find( $: datasource = $datasources.list.find(
ds => ds._id === parameters.datasourceId ds => ds._id === parameters.datasourceId
) )
// Executequery must exclude budibase datasource
$: executeQueryDatasources = $datasources.list.filter(
x => x._id !== BUDIBASE_DATASOURCE_ID
)
function fetchQueryDefinition(query) { function fetchQueryDefinition(query) {
const source = $datasources.list.find( const source = $datasources.list.find(
@ -24,7 +29,7 @@
<Select <Select
label="Datasource" label="Datasource"
bind:value={parameters.datasourceId} bind:value={parameters.datasourceId}
options={$datasources.list} options={executeQueryDatasources}
getOptionLabel={source => source.name} getOptionLabel={source => source.name}
getOptionValue={source => source._id} getOptionValue={source => source._id}
/> />

View file

@ -163,6 +163,8 @@ export const SWITCHABLE_TYPES = [
...ALLOWABLE_NUMBER_TYPES, ...ALLOWABLE_NUMBER_TYPES,
] ]
export const BUDIBASE_DATASOURCE_ID = "bb_internal"
export const IntegrationTypes = { export const IntegrationTypes = {
POSTGRES: "POSTGRES", POSTGRES: "POSTGRES",
MONGODB: "MONGODB", MONGODB: "MONGODB",

View file

@ -3,6 +3,7 @@
import { admin, auth } from "stores/portal" import { admin, auth } from "stores/portal"
import { onMount } from "svelte" import { onMount } from "svelte"
import { CookieUtils, Constants } from "@budibase/frontend-core" import { CookieUtils, Constants } from "@budibase/frontend-core"
import { API } from "api"
let loaded = false let loaded = false
@ -53,6 +54,9 @@
await auth.setOrganisation(urlTenantId) await auth.setOrganisation(urlTenantId)
} }
} }
async function analyticsPing() {
await API.analyticsPing({ source: "builder" })
}
onMount(async () => { onMount(async () => {
try { try {
@ -73,6 +77,9 @@
// being logged in // being logged in
} }
loaded = true loaded = true
// lastly
await analyticsPing()
}) })
$: { $: {

View file

@ -55,13 +55,16 @@
let saveId, url let saveId, url
let response, schema, enabledHeaders let response, schema, enabledHeaders
let authConfigId let authConfigId
let dynamicVariables, addVariableModal, varBinding let dynamicVariables, addVariableModal, varBinding, globalDynamicBindings
let restBindings = getRestBindings() let restBindings = getRestBindings()
$: staticVariables = datasource?.config?.staticVariables || {} $: staticVariables = datasource?.config?.staticVariables || {}
$: customRequestBindings = toBindingsArray(requestBindings, "Binding") $: customRequestBindings = toBindingsArray(requestBindings, "Binding")
$: dynamicRequestBindings = toBindingsArray(dynamicVariables, "Dynamic") $: globalDynamicRequestBindings = toBindingsArray(
globalDynamicBindings,
"Dynamic"
)
$: dataSourceStaticBindings = toBindingsArray( $: dataSourceStaticBindings = toBindingsArray(
staticVariables, staticVariables,
"Datasource.Static" "Datasource.Static"
@ -70,7 +73,7 @@
$: mergedBindings = [ $: mergedBindings = [
...restBindings, ...restBindings,
...customRequestBindings, ...customRequestBindings,
...dynamicRequestBindings, ...globalDynamicRequestBindings,
...dataSourceStaticBindings, ...dataSourceStaticBindings,
] ]
@ -231,11 +234,11 @@
] ]
// convert dynamic variables list to simple key/val object // convert dynamic variables list to simple key/val object
const getDynamicVariables = (datasource, queryId) => { const getDynamicVariables = (datasource, queryId, matchFn) => {
const variablesList = datasource?.config?.dynamicVariables const variablesList = datasource?.config?.dynamicVariables
if (variablesList && variablesList.length > 0) { if (variablesList && variablesList.length > 0) {
const filtered = queryId const filtered = queryId
? variablesList.filter(variable => variable.queryId === queryId) ? variablesList.filter(variable => matchFn(variable, queryId))
: variablesList : variablesList
return filtered.reduce( return filtered.reduce(
(acc, next) => ({ ...acc, [next.name]: next.value }), (acc, next) => ({ ...acc, [next.name]: next.value }),
@ -367,12 +370,21 @@
if (query && !query.fields.pagination) { if (query && !query.fields.pagination) {
query.fields.pagination = {} query.fields.pagination = {}
} }
dynamicVariables = getDynamicVariables(datasource, query._id) dynamicVariables = getDynamicVariables(
datasource,
query._id,
(variable, queryId) => variable.queryId === queryId
)
globalDynamicBindings = getDynamicVariables(
datasource,
query._id,
(variable, queryId) => variable.queryId !== queryId
)
prettifyQueryRequestBody( prettifyQueryRequestBody(
query, query,
requestBindings, requestBindings,
dynamicVariables, globalDynamicBindings,
staticVariables, staticVariables,
restBindings restBindings
) )
@ -437,7 +449,7 @@
valuePlaceholder="Default" valuePlaceholder="Default"
bindings={[ bindings={[
...restBindings, ...restBindings,
...dynamicRequestBindings, ...globalDynamicRequestBindings,
...dataSourceStaticBindings, ...dataSourceStaticBindings,
]} ]}
bindingDrawerLeft="260px" bindingDrawerLeft="260px"

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "1.2.28-alpha.0", "version": "1.2.38",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View file

@ -43,18 +43,19 @@
"@babel/helper-validator-identifier" "^7.16.7" "@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@budibase/backend-core@1.1.32-alpha.6": "@budibase/backend-core@1.2.28-alpha.0":
version "1.1.32-alpha.6" version "1.2.28-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.1.32-alpha.6.tgz#e9dc1a1989a2a6952f5ce002fcdfef66625f3de8" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.2.28-alpha.0.tgz#f4253825724327e6552000f8c7575134bfaa05cd"
integrity sha512-8oT6veeSmymuJfnu1jAkDAWD4fLj5W0KxNq6GlC+eMWWDZloDF4fMWDpuYTFBeinq1z1GeSFXc9Ak6u+1Z7LtQ== integrity sha512-ueu+NZgkiKrX49E0Zy2rrNE4NLe2HAyl3VolTrZfVxOzu1IvtQ/wJBcGDG84VvSliP+0bOOVO9TiGMY3bvZ/Hw==
dependencies: dependencies:
"@budibase/types" "1.1.32-alpha.6" "@budibase/types" "1.2.28-alpha.0"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0" aws-sdk "2.1030.0"
bcrypt "5.0.1" bcrypt "5.0.1"
dotenv "16.0.1" dotenv "16.0.1"
emitter-listener "1.1.2" emitter-listener "1.1.2"
ioredis "4.28.0" ioredis "4.28.0"
joi "17.6.0"
jsonwebtoken "8.5.1" jsonwebtoken "8.5.1"
koa-passport "4.1.4" koa-passport "4.1.4"
lodash "4.17.21" lodash "4.17.21"
@ -101,10 +102,10 @@
to-gfm-code-block "^0.1.1" to-gfm-code-block "^0.1.1"
year "^0.2.1" year "^0.2.1"
"@budibase/string-templates@^1.2.28": "@budibase/string-templates@1.2.28-alpha.0":
version "1.2.28" version "1.2.28-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.2.28.tgz#563ae6ca150de508caf01add9b61468bd14771df" resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.2.28-alpha.0.tgz#895571c142bcd68852f87e06a395232b3eb5516a"
integrity sha512-AMn+fZ8M5h516JcFngvz9FvmjZjmD0rOs32eYrZ3OlZXmbVI5UQBCYhRyUbdFWJ2UbqPm8scsOSQgwJfq90/eQ== integrity sha512-nXqa0IlSVW0og8NAJUW+ihUhdW8+rK0tskGWIwF+gEfAKd9NMyxoLswIAb7aYLmwdRRJFwhrpMEuF7ed8AojSQ==
dependencies: dependencies:
"@budibase/handlebars-helpers" "^0.11.8" "@budibase/handlebars-helpers" "^0.11.8"
dayjs "^1.10.4" dayjs "^1.10.4"
@ -113,15 +114,10 @@
lodash "^4.17.20" lodash "^4.17.20"
vm2 "^3.9.4" vm2 "^3.9.4"
"@budibase/types@1.1.32-alpha.6": "@budibase/types@1.2.28-alpha.0":
version "1.1.32-alpha.6" version "1.2.28-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.1.32-alpha.6.tgz#95d8d73c7ed6ebc22ff26a44365127a478e19409" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.2.28-alpha.0.tgz#61668b7d5d9b1f85c09d658deed87ec3dc28e290"
integrity sha512-AKKxrzVqGtcSzZZ2fP6i2Vgv6ICN9NEEE1dmzRk9AImZS+XKQ9VgVpdE+4gHgFK7L0gBYAsiaoEpCbbrI/+NoQ== integrity sha512-tYhdUl1+dEtG8h2xoGUl0NXZC5BZYQIhgPK7JkYrqFHuNx+1f6EoHPQ9MMb/WyOxIDZv4gY7QJLg0KeVflofbw==
"@budibase/types@^1.2.31":
version "1.2.31"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.2.31.tgz#4715bca331ecd5eac23f95bfdee2eb147ef57814"
integrity sha512-/R03MleZRMtf6JW/nCKBqd/bBIkbFnwr8EV1Y3t6EySh8fnhM2PdhlWlpf/BrE0zMoiuBn4JMFl2vJ2Mzo/aoA==
"@eslint/eslintrc@^0.4.3": "@eslint/eslintrc@^0.4.3":
version "0.4.3" version "0.4.3"
@ -2489,7 +2485,7 @@ jmespath@0.15.0:
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
integrity sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w== integrity sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==
joi@^17.6.0: joi@17.6.0, joi@^17.6.0:
version "17.6.0" version "17.6.0"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2"
integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==

View file

@ -2875,6 +2875,12 @@
"key": "timeOnly", "key": "timeOnly",
"defaultValue": false "defaultValue": false
}, },
{
"type": "boolean",
"label": "24-Hour time",
"key": "time24hr",
"defaultValue": false
},
{ {
"type": "boolean", "type": "boolean",
"label": "Ignore time zones", "label": "Ignore time zones",

View file

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "1.2.28-alpha.0", "version": "1.2.38",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "1.2.28-alpha.0", "@budibase/bbui": "^1.2.38",
"@budibase/frontend-core": "1.2.28-alpha.0", "@budibase/frontend-core": "^1.2.38",
"@budibase/string-templates": "1.2.28-alpha.0", "@budibase/string-templates": "^1.2.38",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

View file

@ -83,6 +83,8 @@
dataLoaded = true dataLoaded = true
if (get(builderStore).inBuilder) { if (get(builderStore).inBuilder) {
builderStore.actions.notifyLoaded() builderStore.actions.notifyLoaded()
} else {
builderStore.actions.analyticsPing({ source: "app" })
} }
}) })
</script> </script>

View file

@ -8,6 +8,7 @@
export let disabled = false export let disabled = false
export let enableTime = false export let enableTime = false
export let timeOnly = false export let timeOnly = false
export let time24hr = false
export let ignoreTimezones = false export let ignoreTimezones = false
export let validation export let validation
export let defaultValue export let defaultValue
@ -44,6 +45,7 @@
appendTo={document.getElementById("flatpickr-root")} appendTo={document.getElementById("flatpickr-root")}
{enableTime} {enableTime}
{timeOnly} {timeOnly}
{time24hr}
{ignoreTimezones} {ignoreTimezones}
{placeholder} {placeholder}
/> />

View file

@ -1,4 +1,5 @@
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import { API } from "api"
import { devToolsStore } from "./devTools.js" import { devToolsStore } from "./devTools.js"
const dispatchEvent = (type, data = {}) => { const dispatchEvent = (type, data = {}) => {
@ -48,6 +49,13 @@ const createBuilderStore = () => {
notifyLoaded: () => { notifyLoaded: () => {
dispatchEvent("preview-loaded") dispatchEvent("preview-loaded")
}, },
analyticsPing: async () => {
try {
await API.analyticsPing({ source: "app" })
} catch (error) {
// Do nothing
}
},
moveComponent: (componentId, destinationComponentId, mode) => { moveComponent: (componentId, destinationComponentId, mode) => {
dispatchEvent("move-component", { dispatchEvent("move-component", {
componentId, componentId,

View file

@ -1,12 +1,12 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "1.2.28-alpha.0", "version": "1.2.38",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "1.2.28-alpha.0", "@budibase/bbui": "^1.2.38",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"svelte": "^3.46.2" "svelte": "^3.46.2"
} }

View file

@ -7,4 +7,11 @@ export const buildAnalyticsEndpoints = API => ({
url: "/api/bbtel", url: "/api/bbtel",
}) })
}, },
analyticsPing: async ({ source }) => {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
return await API.post({
url: "/api/bbtel/ping",
body: { source, timezone },
})
},
}) })

View file

@ -8,6 +8,7 @@ module MongoMock {
this.insertMany = jest.fn(() => ({ toArray: () => [] })) this.insertMany = jest.fn(() => ({ toArray: () => [] }))
this.find = jest.fn(() => ({ toArray: () => [] })) this.find = jest.fn(() => ({ toArray: () => [] }))
this.findOne = jest.fn() this.findOne = jest.fn()
this.findOneAndUpdate = jest.fn()
this.count = jest.fn() this.count = jest.fn()
this.deleteOne = jest.fn() this.deleteOne = jest.fn()
this.deleteMany = jest.fn(() => ({ toArray: () => [] })) this.deleteMany = jest.fn(() => ({ toArray: () => [] }))
@ -19,6 +20,7 @@ module MongoMock {
find: this.find, find: this.find,
insertMany: this.insertMany, insertMany: this.insertMany,
findOne: this.findOne, findOne: this.findOne,
findOneAndUpdate: this.findOneAndUpdate,
count: this.count, count: this.count,
deleteOne: this.deleteOne, deleteOne: this.deleteOne,
deleteMany: this.deleteMany, deleteMany: this.deleteMany,
@ -31,5 +33,7 @@ module MongoMock {
}) })
} }
mongodb.ObjectID = require("mongodb").ObjectID
module.exports = mongodb module.exports = mongodb
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.2.28-alpha.0", "version": "1.2.38",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -77,11 +77,11 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "10.0.3", "@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "1.2.28-alpha.0", "@budibase/backend-core": "^1.2.38",
"@budibase/client": "1.2.28-alpha.0", "@budibase/client": "^1.2.38",
"@budibase/pro": "1.2.28-alpha.0", "@budibase/pro": "1.2.38",
"@budibase/string-templates": "1.2.28-alpha.0", "@budibase/string-templates": "^1.2.38",
"@budibase/types": "1.2.28-alpha.0", "@budibase/types": "^1.2.38",
"@bull-board/api": "3.7.0", "@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4", "@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View file

@ -1,4 +1,7 @@
import { events } from "@budibase/backend-core" import { events } from "@budibase/backend-core"
import { AnalyticsPingRequest, PingSource } from "@budibase/types"
import { DocumentType, isDevAppID } from "../../db/utils"
import { context } from "@budibase/backend-core"
export const isEnabled = async (ctx: any) => { export const isEnabled = async (ctx: any) => {
const enabled = await events.analytics.enabled() const enabled = await events.analytics.enabled()
@ -6,3 +9,27 @@ export const isEnabled = async (ctx: any) => {
enabled, enabled,
} }
} }
export const ping = async (ctx: any) => {
const body = ctx.request.body as AnalyticsPingRequest
switch (body.source) {
case PingSource.APP: {
const db = context.getAppDB({ skip_setup: true })
const appInfo = await db.get(DocumentType.APP_METADATA)
let appId = context.getAppId()
if (isDevAppID(appId)) {
await events.serve.servedAppPreview(appInfo, body.timezone)
} else {
await events.serve.servedApp(appInfo, body.timezone)
}
break
}
case PingSource.BUILDER: {
await events.serve.servedBuilder(body.timezone)
break
}
}
ctx.status = 200
}

View file

@ -15,7 +15,7 @@ import {
getLayoutParams, getLayoutParams,
getScreenParams, getScreenParams,
generateDevAppID, generateDevAppID,
DocumentTypes, DocumentType,
AppStatus, AppStatus,
} from "../../db/utils" } from "../../db/utils"
const { const {
@ -206,7 +206,7 @@ export const fetchAppDefinition = async (ctx: any) => {
export const fetchAppPackage = async (ctx: any) => { export const fetchAppPackage = async (ctx: any) => {
const db = context.getAppDB() const db = context.getAppDB()
const application = await db.get(DocumentTypes.APP_METADATA) const application = await db.get(DocumentType.APP_METADATA)
const layouts = await getLayouts() const layouts = await getLayouts()
let screens = await getScreens() let screens = await getScreens()
@ -248,13 +248,13 @@ const performAppCreate = async (ctx: any) => {
let _rev let _rev
try { try {
// if template there will be an existing doc // if template there will be an existing doc
const existing = await db.get(DocumentTypes.APP_METADATA) const existing = await db.get(DocumentType.APP_METADATA)
_rev = existing._rev _rev = existing._rev
} catch (err) { } catch (err) {
// nothing to do // nothing to do
} }
const newApplication: App = { const newApplication: App = {
_id: DocumentTypes.APP_METADATA, _id: DocumentType.APP_METADATA,
_rev, _rev,
appId: instance._id, appId: instance._id,
type: "app", type: "app",
@ -383,7 +383,7 @@ export const update = async (ctx: any) => {
export const updateClient = async (ctx: any) => { export const updateClient = async (ctx: any) => {
// Get current app version // Get current app version
const db = context.getAppDB() const db = context.getAppDB()
const application = await db.get(DocumentTypes.APP_METADATA) const application = await db.get(DocumentType.APP_METADATA)
const currentVersion = application.version const currentVersion = application.version
// Update client library and manifest // Update client library and manifest
@ -407,7 +407,7 @@ export const updateClient = async (ctx: any) => {
export const revertClient = async (ctx: any) => { export const revertClient = async (ctx: any) => {
// Check app can be reverted // Check app can be reverted
const db = context.getAppDB() const db = context.getAppDB()
const application = await db.get(DocumentTypes.APP_METADATA) const application = await db.get(DocumentType.APP_METADATA)
if (!application.revertableVersion) { if (!application.revertableVersion) {
ctx.throw(400, "There is no version to revert to") ctx.throw(400, "There is no version to revert to")
} }
@ -439,7 +439,7 @@ const destroyApp = async (ctx: any) => {
} }
const db = isUnpublish ? context.getProdAppDB() : context.getAppDB() const db = isUnpublish ? context.getProdAppDB() : context.getAppDB()
const app = await db.get(DocumentTypes.APP_METADATA) const app = await db.get(DocumentType.APP_METADATA)
const result = await db.destroy() const result = await db.destroy()
if (isUnpublish) { if (isUnpublish) {
@ -526,7 +526,7 @@ export const sync = async (ctx: any, next: any) => {
try { try {
await replication.replicate({ await replication.replicate({
filter: function (doc: any) { filter: function (doc: any) {
return doc._id !== DocumentTypes.APP_METADATA return doc._id !== DocumentType.APP_METADATA
}, },
}) })
} catch (err) { } catch (err) {
@ -550,7 +550,7 @@ export const sync = async (ctx: any, next: any) => {
const updateAppPackage = async (appPackage: any, appId: any) => { const updateAppPackage = async (appPackage: any, appId: any) => {
return context.doInAppContext(appId, async () => { return context.doInAppContext(appId, async () => {
const db = context.getAppDB() const db = context.getAppDB()
const application = await db.get(DocumentTypes.APP_METADATA) const application = await db.get(DocumentType.APP_METADATA)
const newAppPackage = { ...application, ...appPackage } const newAppPackage = { ...application, ...appPackage }
if (appPackage._rev !== application._rev) { if (appPackage._rev !== application._rev) {

View file

@ -3,7 +3,7 @@ const triggers = require("../../automations/triggers")
const { const {
getAutomationParams, getAutomationParams,
generateAutomationID, generateAutomationID,
DocumentTypes, DocumentType,
} = require("../../db/utils") } = require("../../db/utils")
const { const {
checkForWebhooks, checkForWebhooks,
@ -201,7 +201,7 @@ exports.clearLogError = async function (ctx) {
const { automationId, appId } = ctx.request.body const { automationId, appId } = ctx.request.body
await doInAppContext(appId, async () => { await doInAppContext(appId, async () => {
const db = getProdAppDB() const db = getProdAppDB()
const metadata = await db.get(DocumentTypes.APP_METADATA) const metadata = await db.get(DocumentType.APP_METADATA)
if (!automationId) { if (!automationId) {
delete metadata.automationErrors delete metadata.automationErrors
} else if ( } else if (

View file

@ -1,6 +1,6 @@
const { streamBackup } = require("../../utilities/fileSystem") const { streamBackup } = require("../../utilities/fileSystem")
const { events, context } = require("@budibase/backend-core") const { events, context } = require("@budibase/backend-core")
const { DocumentTypes } = require("../../db/utils") const { DocumentType } = require("../../db/utils")
exports.exportAppDump = async function (ctx) { exports.exportAppDump = async function (ctx) {
let { appId, excludeRows } = ctx.query let { appId, excludeRows } = ctx.query
@ -12,7 +12,7 @@ exports.exportAppDump = async function (ctx) {
await context.doInAppContext(appId, async () => { await context.doInAppContext(appId, async () => {
const appDb = context.getAppDB() const appDb = context.getAppDB()
const app = await appDb.get(DocumentTypes.APP_METADATA) const app = await appDb.get(DocumentType.APP_METADATA)
await events.app.exported(app) await events.app.exported(app)
}) })
} }

View file

@ -11,7 +11,7 @@ const {
getGlobalDB, getGlobalDB,
} = require("@budibase/backend-core/tenancy") } = require("@budibase/backend-core/tenancy")
const { create } = require("./application") const { create } = require("./application")
const { getDocParams, DocumentTypes, isDevAppID } = require("../../db/utils") const { getDocParams, DocumentType, isDevAppID } = require("../../db/utils")
async function createApp(appName, appImport) { async function createApp(appName, appImport) {
const ctx = { const ctx = {
@ -31,7 +31,7 @@ exports.exportApps = async ctx => {
} }
const apps = await getAllApps({ all: true }) const apps = await getAllApps({ all: true })
const globalDBString = await exportDB(getGlobalDBName(), { const globalDBString = await exportDB(getGlobalDBName(), {
filter: doc => !doc._id.startsWith(DocumentTypes.USER), filter: doc => !doc._id.startsWith(DocumentType.USER),
}) })
let allDBs = { let allDBs = {
global: globalDBString, global: globalDBString,
@ -97,7 +97,7 @@ exports.importApps = async ctx => {
} }
// if there are any users make sure to remove them // if there are any users make sure to remove them
let users = await getAllDocType(globalDb, DocumentTypes.USER) let users = await getAllDocType(globalDb, DocumentType.USER)
let userDeletionPromises = [] let userDeletionPromises = []
for (let user of users) { for (let user of users) {
userDeletionPromises.push(globalDb.remove(user._id, user._rev)) userDeletionPromises.push(globalDb.remove(user._id, user._rev))

View file

@ -1,10 +1,10 @@
const { DocumentTypes } = require("../../db/utils") const { DocumentType } = require("../../db/utils")
const { getComponentLibraryManifest } = require("../../utilities/fileSystem") const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
exports.fetchAppComponentDefinitions = async function (ctx) { exports.fetchAppComponentDefinitions = async function (ctx) {
const db = getAppDB() const db = getAppDB()
const app = await db.get(DocumentTypes.APP_METADATA) const app = await db.get(DocumentType.APP_METADATA)
let componentManifests = await Promise.all( let componentManifests = await Promise.all(
app.componentLibraries.map(async library => { app.componentLibraries.map(async library => {

View file

@ -2,7 +2,7 @@ const {
generateDatasourceID, generateDatasourceID,
getDatasourceParams, getDatasourceParams,
getQueryParams, getQueryParams,
DocumentTypes, DocumentType,
BudibaseInternalDB, BudibaseInternalDB,
getTableParams, getTableParams,
} = require("../../db/utils") } = require("../../db/utils")
@ -132,7 +132,7 @@ exports.save = async function (ctx) {
const datasource = { const datasource = {
_id: generateDatasourceID({ plus }), _id: generateDatasourceID({ plus }),
type: plus ? DocumentTypes.DATASOURCE_PLUS : DocumentTypes.DATASOURCE, type: plus ? DocumentType.DATASOURCE_PLUS : DocumentType.DATASOURCE,
...ctx.request.body.datasource, ...ctx.request.body.datasource,
} }

View file

@ -4,7 +4,7 @@ import {
getProdAppID, getProdAppID,
getDevelopmentAppID, getDevelopmentAppID,
} from "@budibase/backend-core/db" } from "@budibase/backend-core/db"
import { DocumentTypes, getAutomationParams } from "../../../db/utils" import { DocumentType, getAutomationParams } from "../../../db/utils"
import { import {
disableAllCrons, disableAllCrons,
enableCronTrigger, enableCronTrigger,
@ -52,9 +52,9 @@ async function storeDeploymentHistory(deployment: any) {
let deploymentDoc let deploymentDoc
try { try {
// theres only one deployment doc per app database // theres only one deployment doc per app database
deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS) deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
} catch (err) { } catch (err) {
deploymentDoc = { _id: DocumentTypes.DEPLOYMENTS, history: {} } deploymentDoc = { _id: DocumentType.DEPLOYMENTS, history: {} }
} }
const deploymentId = deploymentJSON._id const deploymentId = deploymentJSON._id
@ -115,7 +115,7 @@ async function deployApp(deployment: any) {
await replication.replicate() await replication.replicate()
console.log("replication complete.. replacing app meta doc") console.log("replication complete.. replacing app meta doc")
const db = getProdAppDB() const db = getProdAppDB()
const appDoc = await db.get(DocumentTypes.APP_METADATA) const appDoc = await db.get(DocumentType.APP_METADATA)
deployment.appUrl = appDoc.url deployment.appUrl = appDoc.url
@ -146,7 +146,7 @@ async function deployApp(deployment: any) {
export async function fetchDeployments(ctx: any) { export async function fetchDeployments(ctx: any) {
try { try {
const db = getAppDB() const db = getAppDB()
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS) const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
const { updated, deployments } = await checkAllDeployments(deploymentDoc) const { updated, deployments } = await checkAllDeployments(deploymentDoc)
if (updated) { if (updated) {
await db.put(deployments) await db.put(deployments)
@ -160,7 +160,7 @@ export async function fetchDeployments(ctx: any) {
export async function deploymentProgress(ctx: any) { export async function deploymentProgress(ctx: any) {
try { try {
const db = getAppDB() const db = getAppDB()
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS) const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
ctx.body = deploymentDoc[ctx.params.deploymentId] ctx.body = deploymentDoc[ctx.params.deploymentId]
} catch (err) { } catch (err) {
ctx.throw( ctx.throw(
@ -173,7 +173,7 @@ export async function deploymentProgress(ctx: any) {
const isFirstDeploy = async () => { const isFirstDeploy = async () => {
try { try {
const db = getProdAppDB() const db = getProdAppDB()
await db.get(DocumentTypes.APP_METADATA) await db.get(DocumentType.APP_METADATA)
} catch (e: any) { } catch (e: any) {
if (e.status === 404) { if (e.status === 404) {
return true return true

View file

@ -4,7 +4,7 @@ const { checkSlashesInUrl } = require("../../utilities")
const { request } = require("../../utilities/workerRequests") const { request } = require("../../utilities/workerRequests")
const { clearLock } = require("../../utilities/redis") const { clearLock } = require("../../utilities/redis")
const { Replication, getProdAppID } = require("@budibase/backend-core/db") const { Replication, getProdAppID } = require("@budibase/backend-core/db")
const { DocumentTypes } = require("../../db/utils") const { DocumentType } = require("../../db/utils")
const { app: appCache } = require("@budibase/backend-core/cache") const { app: appCache } = require("@budibase/backend-core/cache")
const { getProdAppDB, getAppDB } = require("@budibase/backend-core/context") const { getProdAppDB, getAppDB } = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core") const { events } = require("@budibase/backend-core")
@ -87,7 +87,7 @@ exports.revert = async ctx => {
if (info.error) { if (info.error) {
throw info.error throw info.error
} }
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS) const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
if ( if (
!deploymentDoc.history || !deploymentDoc.history ||
Object.keys(deploymentDoc.history).length === 0 Object.keys(deploymentDoc.history).length === 0
@ -110,7 +110,7 @@ exports.revert = async ctx => {
// update appID in reverted app to be dev version again // update appID in reverted app to be dev version again
const db = getAppDB() const db = getAppDB()
const appDoc = await db.get(DocumentTypes.APP_METADATA) const appDoc = await db.get(DocumentType.APP_METADATA)
appDoc.appId = appId appDoc.appId = appId
appDoc.instance._id = appId appDoc.instance._id = appId
await db.put(appDoc) await db.put(appDoc)

View file

@ -1,6 +1,6 @@
const { cloneDeep } = require("lodash") const { cloneDeep } = require("lodash")
const { definitions } = require("../../integrations") const { definitions } = require("../../integrations")
const { SourceNames } = require("../../definitions/datasource") const { SourceName } = require("@budibase/types")
const googlesheets = require("../../integrations/googlesheets") const googlesheets = require("../../integrations/googlesheets")
const { featureFlags } = require("@budibase/backend-core") const { featureFlags } = require("@budibase/backend-core")
@ -10,7 +10,7 @@ exports.fetch = async function (ctx) {
// for google sheets integration google verification // for google sheets integration google verification
if (featureFlags.isEnabled(featureFlags.FeatureFlag.GOOGLE_SHEETS)) { if (featureFlags.isEnabled(featureFlags.FeatureFlag.GOOGLE_SHEETS)) {
defs[SourceNames.GOOGLE_SHEETS] = googlesheets.schema defs[SourceName.GOOGLE_SHEETS] = googlesheets.schema
} }
ctx.body = defs ctx.body = defs

View file

@ -1,6 +1,6 @@
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
import { isExternalTable } from "../../../integrations/utils" import { isExternalTable } from "../../../integrations/utils"
import { APP_PREFIX, DocumentTypes } from "../../../db/utils" import { APP_PREFIX, DocumentType } from "../../../db/utils"
export async function addRev( export async function addRev(
body: { _id?: string; _rev?: string }, body: { _id?: string; _rev?: string },
@ -11,7 +11,7 @@ export async function addRev(
} }
let id = body._id let id = body._id
if (body._id.startsWith(APP_PREFIX)) { if (body._id.startsWith(APP_PREFIX)) {
id = DocumentTypes.APP_METADATA id = DocumentType.APP_METADATA
} }
const db = getAppDB() const db = getAppDB()
const dbDoc = await db.get(id) const dbDoc = await db.get(id)

View file

@ -1,5 +1,5 @@
import { ImportInfo } from "./base" import { ImportInfo } from "./base"
import { Query, QueryParameter } from "../../../../../definitions/datasource" import { Query, QueryParameter } from "@budibase/types"
import { OpenAPIV2 } from "openapi-types" import { OpenAPIV2 } from "openapi-types"
import { OpenAPISource } from "./base/openapi" import { OpenAPISource } from "./base/openapi"
import { URL } from "url" import { URL } from "url"

View file

@ -1,5 +1,5 @@
import { ImportInfo } from "./base" import { ImportInfo } from "./base"
import { Query, QueryParameter } from "../../../../../definitions/datasource" import { Query, QueryParameter } from "@budibase/types"
import { OpenAPIV3 } from "openapi-types" import { OpenAPIV3 } from "openapi-types"
import { OpenAPISource } from "./base/openapi" import { OpenAPISource } from "./base/openapi"
import { URL } from "url" import { URL } from "url"

View file

@ -1,18 +1,16 @@
import { import {
FilterTypes, FilterType,
IncludeRelationships, IncludeRelationship,
Operation, Operation,
PaginationJson, PaginationJson,
RelationshipsJson, RelationshipsJson,
SearchFilters, SearchFilters,
SortJson, SortJson,
} from "../../../definitions/datasource"
import {
Datasource, Datasource,
FieldSchema, FieldSchema,
Row, Row,
Table, Table,
} from "../../../definitions/common" } from "@budibase/types"
import { import {
breakRowIdField, breakRowIdField,
generateRowIdField, generateRowIdField,
@ -128,7 +126,7 @@ module External {
if ( if (
typeof filter !== "object" || typeof filter !== "object" ||
Object.keys(filter).length === 0 || Object.keys(filter).length === 0 ||
key === FilterTypes.ONE_OF key === FilterType.ONE_OF
) { ) {
continue continue
} }
@ -634,7 +632,7 @@ module External {
*/ */
buildFields( buildFields(
table: Table, table: Table,
includeRelations: IncludeRelationships = IncludeRelationships.INCLUDE includeRelations: IncludeRelationship = IncludeRelationship.INCLUDE
) { ) {
function extractRealFields(table: Table, existing: string[] = []) { function extractRealFields(table: Table, existing: string[] = []) {
return Object.entries(table.schema) return Object.entries(table.schema)

View file

@ -3,7 +3,7 @@ const {
generateRowID, generateRowID,
getRowParams, getRowParams,
getTableIDFromRowID, getTableIDFromRowID,
DocumentTypes, DocumentType,
InternalTables, InternalTables,
} = require("../../../db/utils") } = require("../../../db/utils")
const { dangerousGetDB } = require("@budibase/backend-core/db") const { dangerousGetDB } = require("@budibase/backend-core/db")
@ -183,7 +183,7 @@ exports.fetchView = async ctx => {
const viewName = ctx.params.viewName const viewName = ctx.params.viewName
// if this is a table view being looked for just transfer to that // if this is a table view being looked for just transfer to that
if (viewName.startsWith(DocumentTypes.TABLE)) { if (viewName.startsWith(DocumentType.TABLE)) {
ctx.params.tableId = viewName ctx.params.tableId = viewName
return exports.fetch(ctx) return exports.fetch(ctx)
} }

View file

@ -14,11 +14,10 @@ const env = require("../../../environment")
const { clientLibraryPath } = require("../../../utilities") const { clientLibraryPath } = require("../../../utilities")
const { upload } = require("../../../utilities/fileSystem") const { upload } = require("../../../utilities/fileSystem")
const { attachmentsRelativeURL } = require("../../../utilities") const { attachmentsRelativeURL } = require("../../../utilities")
const { DocumentTypes, isDevAppID } = require("../../../db/utils") const { DocumentType } = require("../../../db/utils")
const { getAppDB, getAppId } = require("@budibase/backend-core/context") const { getAppDB, getAppId } = require("@budibase/backend-core/context")
const { setCookie, clearCookie } = require("@budibase/backend-core/utils") const { setCookie, clearCookie } = require("@budibase/backend-core/utils")
const AWS = require("aws-sdk") const AWS = require("aws-sdk")
import { events } from "@budibase/backend-core"
const fs = require("fs") const fs = require("fs")
const { const {
@ -75,9 +74,6 @@ export const toggleBetaUiFeature = async function (ctx: any) {
export const serveBuilder = async function (ctx: any) { export const serveBuilder = async function (ctx: any) {
const builderPath = resolve(TOP_LEVEL_PATH, "builder") const builderPath = resolve(TOP_LEVEL_PATH, "builder")
await send(ctx, ctx.file, { root: builderPath }) await send(ctx, ctx.file, { root: builderPath })
if (ctx.file === "index.html") {
await events.serve.servedBuilder()
}
} }
export const uploadFile = async function (ctx: any) { export const uploadFile = async function (ctx: any) {
@ -103,7 +99,7 @@ export const uploadFile = async function (ctx: any) {
export const serveApp = async function (ctx: any) { export const serveApp = async function (ctx: any) {
const db = getAppDB({ skip_setup: true }) const db = getAppDB({ skip_setup: true })
const appInfo = await db.get(DocumentTypes.APP_METADATA) const appInfo = await db.get(DocumentType.APP_METADATA)
let appId = getAppId() let appId = getAppId()
if (!env.isJest()) { if (!env.isJest()) {
@ -126,12 +122,6 @@ export const serveApp = async function (ctx: any) {
// just return the app info for jest to assert on // just return the app info for jest to assert on
ctx.body = appInfo ctx.body = appInfo
} }
if (isDevAppID(appInfo.appId)) {
await events.serve.servedAppPreview(appInfo)
} else {
await events.serve.servedApp(appInfo)
}
} }
export const serveClientLibrary = async function (ctx: any) { export const serveClientLibrary = async function (ctx: any) {

View file

@ -7,7 +7,7 @@ const { getTable } = require("../table/utils")
const { FieldTypes } = require("../../../constants") const { FieldTypes } = require("../../../constants")
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core") const { events } = require("@budibase/backend-core")
const { DocumentTypes } = require("../../../db/utils") const { DocumentType } = require("../../../db/utils")
const { cloneDeep, isEqual } = require("lodash") const { cloneDeep, isEqual } = require("lodash")
exports.fetch = async ctx => { exports.fetch = async ctx => {
@ -181,7 +181,7 @@ exports.exportView = async ctx => {
ctx.attachment(filename) ctx.attachment(filename)
ctx.body = apiFileReturn(exporter(headers, rows)) ctx.body = apiFileReturn(exporter(headers, rows))
if (viewName.startsWith(DocumentTypes.TABLE)) { if (viewName.startsWith(DocumentType.TABLE)) {
await events.table.exported(table, format) await events.table.exported(table, format)
} else { } else {
await events.view.exported(table, format) await events.view.exported(table, format)

View file

@ -1,8 +1,8 @@
const { const {
ViewNames, ViewName,
generateMemoryViewID, generateMemoryViewID,
getMemoryViewParams, getMemoryViewParams,
DocumentTypes, DocumentType,
SEPARATOR, SEPARATOR,
} = require("../../../db/utils") } = require("../../../db/utils")
const env = require("../../../environment") const env = require("../../../environment")
@ -16,7 +16,7 @@ exports.getView = async viewName => {
return designDoc.views[viewName] return designDoc.views[viewName]
} else { } else {
// This is a table view, don't read the view from the DB // This is a table view, don't read the view from the DB
if (viewName.startsWith(DocumentTypes.TABLE + SEPARATOR)) { if (viewName.startsWith(DocumentType.TABLE + SEPARATOR)) {
return null return null
} }
@ -32,7 +32,7 @@ exports.getViews = async () => {
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
for (let name of Object.keys(designDoc.views)) { for (let name of Object.keys(designDoc.views)) {
// Only return custom views, not built ins // Only return custom views, not built ins
if (Object.values(ViewNames).indexOf(name) !== -1) { if (Object.values(ViewName).indexOf(name) !== -1) {
continue continue
} }
response.push({ response.push({

View file

@ -4,5 +4,6 @@ const controller = require("../controllers/analytics")
const router = Router() const router = Router()
router.get("/api/bbtel", controller.isEnabled) router.get("/api/bbtel", controller.isEnabled)
router.post("/api/bbtel/ping", controller.ping)
module.exports = router module.exports = router

View file

@ -0,0 +1,59 @@
const setup = require("./utilities")
const { events, constants, db } = require("@budibase/backend-core")
describe("/static", () => {
let request = setup.getRequest()
let config = setup.getConfig()
let app
const timezone = "Europe/London"
afterAll(setup.afterAll)
beforeEach(async () => {
app = await config.init()
jest.clearAllMocks()
})
describe("/ping", () => {
it("should ping from builder", async () => {
await request
.post("/api/bbtel/ping")
.send({source: "builder", timezone})
.set(config.defaultHeaders())
.expect(200)
expect(events.serve.servedBuilder).toBeCalledTimes(1)
expect(events.serve.servedBuilder).toBeCalledWith(timezone)
expect(events.serve.servedApp).not.toBeCalled()
expect(events.serve.servedAppPreview).not.toBeCalled()
})
it("should ping from app preview", async () => {
await request
.post("/api/bbtel/ping")
.send({source: "app", timezone})
.set(config.defaultHeaders())
.expect(200)
expect(events.serve.servedAppPreview).toBeCalledTimes(1)
expect(events.serve.servedAppPreview).toBeCalledWith(config.getApp(), timezone)
expect(events.serve.servedApp).not.toBeCalled()
})
it("should ping from app", async () => {
const headers = config.defaultHeaders()
headers[constants.Headers.APP_ID] = config.prodAppId
await request
.post("/api/bbtel/ping")
.send({source: "app", timezone})
.set(headers)
.expect(200)
expect(events.serve.servedApp).toBeCalledTimes(1)
expect(events.serve.servedApp).toBeCalledWith(config.getProdApp(), timezone)
expect(events.serve.servedAppPreview).not.toBeCalled()
})
})
})

View file

@ -36,7 +36,6 @@ describe("/static", () => {
.expect(200) .expect(200)
expect(res.text).toContain("<title>Budibase</title>") expect(res.text).toContain("<title>Budibase</title>")
expect(events.serve.servedBuilder).toBeCalledTimes(1)
}) })
}) })
@ -56,9 +55,6 @@ describe("/static", () => {
.expect(200) .expect(200)
expect(res.body.appId).toBe(config.prodAppId) expect(res.body.appId).toBe(config.prodAppId)
expect(events.serve.servedApp).toBeCalledTimes(1)
expect(events.serve.servedApp).toBeCalledWith(res.body)
expect(events.serve.servedAppPreview).not.toBeCalled()
}) })
it("should serve the app by url", async () => { it("should serve the app by url", async () => {
@ -71,9 +67,6 @@ describe("/static", () => {
.expect(200) .expect(200)
expect(res.body.appId).toBe(config.prodAppId) expect(res.body.appId).toBe(config.prodAppId)
expect(events.serve.servedApp).toBeCalledTimes(1)
expect(events.serve.servedApp).toBeCalledWith(res.body)
expect(events.serve.servedAppPreview).not.toBeCalled()
}) })
it("should serve the app preview by id", async () => { it("should serve the app preview by id", async () => {
@ -83,9 +76,6 @@ describe("/static", () => {
.expect(200) .expect(200)
expect(res.body.appId).toBe(config.appId) expect(res.body.appId).toBe(config.appId)
expect(events.serve.servedAppPreview).toBeCalledTimes(1)
expect(events.serve.servedAppPreview).toBeCalledWith(res.body)
expect(events.serve.servedApp).not.toBeCalled()
}) })
}) })

View file

@ -14,6 +14,16 @@ const SortOrdersPretty = {
[SortOrders.DESCENDING]: "Descending", [SortOrders.DESCENDING]: "Descending",
} }
const EmptyFilterOptions = {
RETURN_ALL: "all",
RETURN_NONE: "none",
}
const EmptyFilterOptionsPretty = {
[EmptyFilterOptions.RETURN_ALL]: "Return all table rows",
[EmptyFilterOptions.RETURN_NONE]: "Return no rows",
}
exports.definition = { exports.definition = {
description: "Query rows from the database", description: "Query rows from the database",
icon: "Search", icon: "Search",
@ -52,6 +62,12 @@ exports.definition = {
title: "Limit", title: "Limit",
customType: "queryLimit", customType: "queryLimit",
}, },
onEmptyFilter: {
pretty: Object.values(EmptyFilterOptionsPretty),
enum: Object.values(EmptyFilterOptions),
type: "string",
title: "When Filter Empty",
},
}, },
required: ["tableId"], required: ["tableId"],
}, },
@ -103,6 +119,10 @@ function typeCoercion(filters, table) {
return filters return filters
} }
const hasNullFilters = filters =>
filters.length === 0 ||
filters.some(filter => filter.value === null || filter.value === "")
exports.run = async function ({ inputs, appId }) { exports.run = async function ({ inputs, appId }) {
const { tableId, filters, sortColumn, sortOrder, limit } = inputs const { tableId, filters, sortColumn, sortOrder, limit } = inputs
const table = await getTable(appId, tableId) const table = await getTable(appId, tableId)
@ -127,9 +147,21 @@ exports.run = async function ({ inputs, appId }) {
version: "1", version: "1",
}) })
try { try {
await rowController.search(ctx) let rows
if (
inputs.onEmptyFilter === EmptyFilterOptions.RETURN_NONE &&
inputs["filters-def"] &&
hasNullFilters(inputs["filters-def"])
) {
rows = []
} else {
await rowController.search(ctx)
rows = ctx.body ? ctx.body.rows : []
}
return { return {
rows: ctx.body ? ctx.body.rows : [], rows,
success: ctx.status === 200, success: ctx.status === 200,
} }
} catch (err) { } catch (err) {

View file

@ -16,7 +16,7 @@ describe("Test a query step automation", () => {
let table let table
let config = setup.getConfig() let config = setup.getConfig()
beforeEach(async () => { beforeAll(async () => {
await config.init() await config.init()
table = await config.createTable() table = await config.createTable()
const row = { const row = {
@ -48,4 +48,70 @@ describe("Test a query step automation", () => {
expect(res.rows.length).toBe(2) expect(res.rows.length).toBe(2)
expect(res.rows[0].name).toBe(NAME) expect(res.rows[0].name).toBe(NAME)
}) })
it("Returns all rows when onEmptyFilter has no value and no filters are passed", async () => {
const inputs = {
tableId: table._id,
filters: {},
sortColumn: "name",
sortOrder: "ascending",
limit: 10,
}
const res = await setup.runStep(setup.actions.QUERY_ROWS.stepId, inputs)
expect(res.success).toBe(true)
expect(res.rows).toBeDefined()
expect(res.rows.length).toBe(2)
expect(res.rows[0].name).toBe(NAME)
})
it("Returns no rows when onEmptyFilter is RETURN_NONE and theres no filters", async () => {
const inputs = {
tableId: table._id,
filters: {},
"filters-def": [],
sortColumn: "name",
sortOrder: "ascending",
limit: 10,
onEmptyFilter: "none",
}
const res = await setup.runStep(setup.actions.QUERY_ROWS.stepId, inputs)
expect(res.success).toBe(false)
expect(res.rows).toBeDefined()
expect(res.rows.length).toBe(0)
})
it("Returns no rows when onEmptyFilters RETURN_NONE and a filter is passed with a null value", async () => {
const inputs = {
tableId: table._id,
onEmptyFilter: "none",
filters: {},
"filters-def": [
{
value: null
}
],
sortColumn: "name",
sortOrder: "ascending",
limit: 10,
}
const res = await setup.runStep(setup.actions.QUERY_ROWS.stepId, inputs)
expect(res.success).toBe(false)
expect(res.rows).toBeDefined()
expect(res.rows.length).toBe(0)
})
it("Returns rows when onEmptyFilter is RETURN_ALL and no filter is passed", async () => {
const inputs = {
tableId: table._id,
onEmptyFilter: "all",
filters: {},
sortColumn: "name",
sortOrder: "ascending",
limit: 10,
}
const res = await setup.runStep(setup.actions.QUERY_ROWS.stepId, inputs)
expect(res.success).toBe(true)
expect(res.rows).toBeDefined()
expect(res.rows.length).toBe(2)
})
}) })

View file

@ -1,5 +1,5 @@
const Sentry = require("@sentry/node") const Sentry = require("@sentry/node")
const { ViewNames, getQueryIndex } = require("../utils") const { ViewName, getQueryIndex } = require("../utils")
const { FieldTypes } = require("../../constants") const { FieldTypes } = require("../../constants")
const { createLinkView } = require("../views/staticViews") const { createLinkView } = require("../views/staticViews")
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
@ -41,7 +41,7 @@ exports.getLinkDocuments = async function (args) {
} }
params.include_docs = !!includeDocs params.include_docs = !!includeDocs
try { try {
let linkRows = (await db.query(getQueryIndex(ViewNames.LINK), params)).rows let linkRows = (await db.query(getQueryIndex(ViewName.LINK), params)).rows
// filter to get unique entries // filter to get unique entries
const foundIds = [] const foundIds = []
linkRows = linkRows.filter(link => { linkRows = linkRows.filter(link => {

View file

@ -1,6 +1,6 @@
const newid = require("./newid") const newid = require("./newid")
const { const {
DocumentTypes: CoreDocTypes, DocumentType: CoreDocTypes,
getRoleParams, getRoleParams,
generateRoleID, generateRoleID,
APP_DEV_PREFIX, APP_DEV_PREFIX,
@ -12,7 +12,7 @@ const {
getDevelopmentAppID, getDevelopmentAppID,
generateAppID, generateAppID,
getQueryIndex, getQueryIndex,
ViewNames, ViewName,
} = require("@budibase/backend-core/db") } = require("@budibase/backend-core/db")
const UNICODE_MAX = "\ufff0" const UNICODE_MAX = "\ufff0"
@ -23,7 +23,7 @@ const AppStatus = {
DEPLOYED: "published", DEPLOYED: "published",
} }
const DocumentTypes = { const DocumentType = {
...CoreDocTypes, ...CoreDocTypes,
TABLE: "ta", TABLE: "ta",
ROW: "ro", ROW: "ro",
@ -67,12 +67,12 @@ exports.APP_PREFIX = APP_PREFIX
exports.APP_DEV_PREFIX = APP_DEV_PREFIX exports.APP_DEV_PREFIX = APP_DEV_PREFIX
exports.isDevAppID = isDevAppID exports.isDevAppID = isDevAppID
exports.isProdAppID = isProdAppID exports.isProdAppID = isProdAppID
exports.USER_METDATA_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` exports.USER_METDATA_PREFIX = `${DocumentType.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
exports.LINK_USER_METADATA_PREFIX = `${DocumentTypes.LINK}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` exports.LINK_USER_METADATA_PREFIX = `${DocumentType.LINK}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
exports.TABLE_ROW_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${DocumentTypes.TABLE}` exports.TABLE_ROW_PREFIX = `${DocumentType.ROW}${SEPARATOR}${DocumentType.TABLE}`
exports.ViewNames = ViewNames exports.ViewName = ViewName
exports.InternalTables = InternalTables exports.InternalTables = InternalTables
exports.DocumentTypes = DocumentTypes exports.DocumentType = DocumentType
exports.SEPARATOR = SEPARATOR exports.SEPARATOR = SEPARATOR
exports.UNICODE_MAX = UNICODE_MAX exports.UNICODE_MAX = UNICODE_MAX
exports.SearchIndexes = SearchIndexes exports.SearchIndexes = SearchIndexes
@ -115,7 +115,7 @@ exports.getDocParams = getDocParams
* Gets parameters for retrieving tables, this is a utility function for the getDocParams function. * Gets parameters for retrieving tables, this is a utility function for the getDocParams function.
*/ */
exports.getTableParams = (tableId = null, otherProps = {}) => { exports.getTableParams = (tableId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.TABLE, tableId, otherProps) return getDocParams(DocumentType.TABLE, tableId, otherProps)
} }
/** /**
@ -123,7 +123,7 @@ exports.getTableParams = (tableId = null, otherProps = {}) => {
* @returns {string} The new table ID which the table doc can be stored under. * @returns {string} The new table ID which the table doc can be stored under.
*/ */
exports.generateTableID = () => { exports.generateTableID = () => {
return `${DocumentTypes.TABLE}${SEPARATOR}${newid()}` return `${DocumentType.TABLE}${SEPARATOR}${newid()}`
} }
/** /**
@ -136,12 +136,12 @@ exports.generateTableID = () => {
*/ */
exports.getRowParams = (tableId = null, rowId = null, otherProps = {}) => { exports.getRowParams = (tableId = null, rowId = null, otherProps = {}) => {
if (tableId == null) { if (tableId == null) {
return getDocParams(DocumentTypes.ROW, null, otherProps) return getDocParams(DocumentType.ROW, null, otherProps)
} }
const endOfKey = rowId == null ? `${tableId}${SEPARATOR}` : rowId const endOfKey = rowId == null ? `${tableId}${SEPARATOR}` : rowId
return getDocParams(DocumentTypes.ROW, endOfKey, otherProps) return getDocParams(DocumentType.ROW, endOfKey, otherProps)
} }
/** /**
@ -151,9 +151,9 @@ exports.getRowParams = (tableId = null, rowId = null, otherProps = {}) => {
*/ */
exports.getTableIDFromRowID = rowId => { exports.getTableIDFromRowID = rowId => {
const components = rowId const components = rowId
.split(DocumentTypes.TABLE + SEPARATOR)[1] .split(DocumentType.TABLE + SEPARATOR)[1]
.split(SEPARATOR) .split(SEPARATOR)
return `${DocumentTypes.TABLE}${SEPARATOR}${components[0]}` return `${DocumentType.TABLE}${SEPARATOR}${components[0]}`
} }
/** /**
@ -164,7 +164,7 @@ exports.getTableIDFromRowID = rowId => {
*/ */
exports.generateRowID = (tableId, id = null) => { exports.generateRowID = (tableId, id = null) => {
id = id || newid() id = id || newid()
return `${DocumentTypes.ROW}${SEPARATOR}${tableId}${SEPARATOR}${id}` return `${DocumentType.ROW}${SEPARATOR}${tableId}${SEPARATOR}${id}`
} }
/** /**
@ -187,7 +187,7 @@ exports.generateUserMetadataID = globalId => {
* Breaks up the ID to get the global ID. * Breaks up the ID to get the global ID.
*/ */
exports.getGlobalIDFromUserMetadataID = id => { exports.getGlobalIDFromUserMetadataID = id => {
const prefix = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` const prefix = `${DocumentType.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
if (!id || !id.includes(prefix)) { if (!id || !id.includes(prefix)) {
return id return id
} }
@ -198,7 +198,7 @@ exports.getGlobalIDFromUserMetadataID = id => {
* Gets parameters for retrieving automations, this is a utility function for the getDocParams function. * Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
*/ */
exports.getAutomationParams = (automationId = null, otherProps = {}) => { exports.getAutomationParams = (automationId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.AUTOMATION, automationId, otherProps) return getDocParams(DocumentType.AUTOMATION, automationId, otherProps)
} }
/** /**
@ -206,7 +206,7 @@ exports.getAutomationParams = (automationId = null, otherProps = {}) => {
* @returns {string} The new automation ID which the automation doc can be stored under. * @returns {string} The new automation ID which the automation doc can be stored under.
*/ */
exports.generateAutomationID = () => { exports.generateAutomationID = () => {
return `${DocumentTypes.AUTOMATION}${SEPARATOR}${newid()}` return `${DocumentType.AUTOMATION}${SEPARATOR}${newid()}`
} }
/** /**
@ -231,14 +231,14 @@ exports.generateLinkID = (
const tables = `${SEPARATOR}${tableId1}${SEPARATOR}${tableId2}` const tables = `${SEPARATOR}${tableId1}${SEPARATOR}${tableId2}`
const rows = `${SEPARATOR}${rowId1}${SEPARATOR}${rowId2}` const rows = `${SEPARATOR}${rowId1}${SEPARATOR}${rowId2}`
const fields = `${SEPARATOR}${fieldName1}${SEPARATOR}${fieldName2}` const fields = `${SEPARATOR}${fieldName1}${SEPARATOR}${fieldName2}`
return `${DocumentTypes.LINK}${tables}${rows}${fields}` return `${DocumentType.LINK}${tables}${rows}${fields}`
} }
/** /**
* Gets parameters for retrieving link docs, this is a utility function for the getDocParams function. * Gets parameters for retrieving link docs, this is a utility function for the getDocParams function.
*/ */
exports.getLinkParams = (otherProps = {}) => { exports.getLinkParams = (otherProps = {}) => {
return getDocParams(DocumentTypes.LINK, null, otherProps) return getDocParams(DocumentType.LINK, null, otherProps)
} }
/** /**
@ -246,14 +246,14 @@ exports.getLinkParams = (otherProps = {}) => {
* @returns {string} The new layout ID which the layout doc can be stored under. * @returns {string} The new layout ID which the layout doc can be stored under.
*/ */
exports.generateLayoutID = id => { exports.generateLayoutID = id => {
return `${DocumentTypes.LAYOUT}${SEPARATOR}${id || newid()}` return `${DocumentType.LAYOUT}${SEPARATOR}${id || newid()}`
} }
/** /**
* Gets parameters for retrieving layout, this is a utility function for the getDocParams function. * Gets parameters for retrieving layout, this is a utility function for the getDocParams function.
*/ */
exports.getLayoutParams = (layoutId = null, otherProps = {}) => { exports.getLayoutParams = (layoutId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.LAYOUT, layoutId, otherProps) return getDocParams(DocumentType.LAYOUT, layoutId, otherProps)
} }
/** /**
@ -261,14 +261,14 @@ exports.getLayoutParams = (layoutId = null, otherProps = {}) => {
* @returns {string} The new screen ID which the screen doc can be stored under. * @returns {string} The new screen ID which the screen doc can be stored under.
*/ */
exports.generateScreenID = () => { exports.generateScreenID = () => {
return `${DocumentTypes.SCREEN}${SEPARATOR}${newid()}` return `${DocumentType.SCREEN}${SEPARATOR}${newid()}`
} }
/** /**
* Gets parameters for retrieving screens, this is a utility function for the getDocParams function. * Gets parameters for retrieving screens, this is a utility function for the getDocParams function.
*/ */
exports.getScreenParams = (screenId = null, otherProps = {}) => { exports.getScreenParams = (screenId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.SCREEN, screenId, otherProps) return getDocParams(DocumentType.SCREEN, screenId, otherProps)
} }
/** /**
@ -276,14 +276,14 @@ exports.getScreenParams = (screenId = null, otherProps = {}) => {
* @returns {string} The new webhook ID which the webhook doc can be stored under. * @returns {string} The new webhook ID which the webhook doc can be stored under.
*/ */
exports.generateWebhookID = () => { exports.generateWebhookID = () => {
return `${DocumentTypes.WEBHOOK}${SEPARATOR}${newid()}` return `${DocumentType.WEBHOOK}${SEPARATOR}${newid()}`
} }
/** /**
* Gets parameters for retrieving a webhook, this is a utility function for the getDocParams function. * Gets parameters for retrieving a webhook, this is a utility function for the getDocParams function.
*/ */
exports.getWebhookParams = (webhookId = null, otherProps = {}) => { exports.getWebhookParams = (webhookId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.WEBHOOK, webhookId, otherProps) return getDocParams(DocumentType.WEBHOOK, webhookId, otherProps)
} }
/** /**
@ -292,7 +292,7 @@ exports.getWebhookParams = (webhookId = null, otherProps = {}) => {
*/ */
exports.generateDatasourceID = ({ plus = false } = {}) => { exports.generateDatasourceID = ({ plus = false } = {}) => {
return `${ return `${
plus ? DocumentTypes.DATASOURCE_PLUS : DocumentTypes.DATASOURCE plus ? DocumentType.DATASOURCE_PLUS : DocumentType.DATASOURCE
}${SEPARATOR}${newid()}` }${SEPARATOR}${newid()}`
} }
@ -300,7 +300,7 @@ exports.generateDatasourceID = ({ plus = false } = {}) => {
* Gets parameters for retrieving a datasource, this is a utility function for the getDocParams function. * Gets parameters for retrieving a datasource, this is a utility function for the getDocParams function.
*/ */
exports.getDatasourceParams = (datasourceId = null, otherProps = {}) => { exports.getDatasourceParams = (datasourceId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.DATASOURCE, datasourceId, otherProps) return getDocParams(DocumentType.DATASOURCE, datasourceId, otherProps)
} }
/** /**
@ -309,7 +309,7 @@ exports.getDatasourceParams = (datasourceId = null, otherProps = {}) => {
*/ */
exports.generateQueryID = datasourceId => { exports.generateQueryID = datasourceId => {
return `${ return `${
DocumentTypes.QUERY DocumentType.QUERY
}${SEPARATOR}${datasourceId}${SEPARATOR}${newid()}` }${SEPARATOR}${datasourceId}${SEPARATOR}${newid()}`
} }
@ -318,14 +318,14 @@ exports.generateQueryID = datasourceId => {
* automations etc. * automations etc.
*/ */
exports.generateAutomationMetadataID = automationId => { exports.generateAutomationMetadataID = automationId => {
return `${DocumentTypes.AUTOMATION_METADATA}${SEPARATOR}${automationId}` return `${DocumentType.AUTOMATION_METADATA}${SEPARATOR}${automationId}`
} }
/** /**
* Retrieve all automation metadata in an app database. * Retrieve all automation metadata in an app database.
*/ */
exports.getAutomationMetadataParams = (otherProps = {}) => { exports.getAutomationMetadataParams = (otherProps = {}) => {
return getDocParams(DocumentTypes.AUTOMATION_METADATA, null, otherProps) return getDocParams(DocumentType.AUTOMATION_METADATA, null, otherProps)
} }
/** /**
@ -333,11 +333,11 @@ exports.getAutomationMetadataParams = (otherProps = {}) => {
*/ */
exports.getQueryParams = (datasourceId = null, otherProps = {}) => { exports.getQueryParams = (datasourceId = null, otherProps = {}) => {
if (datasourceId == null) { if (datasourceId == null) {
return getDocParams(DocumentTypes.QUERY, null, otherProps) return getDocParams(DocumentType.QUERY, null, otherProps)
} }
return getDocParams( return getDocParams(
DocumentTypes.QUERY, DocumentType.QUERY,
`${datasourceId}${SEPARATOR}`, `${datasourceId}${SEPARATOR}`,
otherProps otherProps
) )
@ -348,11 +348,11 @@ exports.getQueryParams = (datasourceId = null, otherProps = {}) => {
* @returns {string} The ID of the flag document that was generated. * @returns {string} The ID of the flag document that was generated.
*/ */
exports.generateUserFlagID = userId => { exports.generateUserFlagID = userId => {
return `${DocumentTypes.USER_FLAG}${SEPARATOR}${userId}` return `${DocumentType.USER_FLAG}${SEPARATOR}${userId}`
} }
exports.generateMetadataID = (type, entityId) => { exports.generateMetadataID = (type, entityId) => {
return `${DocumentTypes.METADATA}${SEPARATOR}${type}${SEPARATOR}${entityId}` return `${DocumentType.METADATA}${SEPARATOR}${type}${SEPARATOR}${entityId}`
} }
exports.getMetadataParams = (type, entityId = null, otherProps = {}) => { exports.getMetadataParams = (type, entityId = null, otherProps = {}) => {
@ -360,19 +360,19 @@ exports.getMetadataParams = (type, entityId = null, otherProps = {}) => {
if (entityId != null) { if (entityId != null) {
docId += entityId docId += entityId
} }
return getDocParams(DocumentTypes.METADATA, docId, otherProps) return getDocParams(DocumentType.METADATA, docId, otherProps)
} }
exports.generateMemoryViewID = viewName => { exports.generateMemoryViewID = viewName => {
return `${DocumentTypes.MEM_VIEW}${SEPARATOR}${viewName}` return `${DocumentType.MEM_VIEW}${SEPARATOR}${viewName}`
} }
exports.getMemoryViewParams = (otherProps = {}) => { exports.getMemoryViewParams = (otherProps = {}) => {
return getDocParams(DocumentTypes.MEM_VIEW, null, otherProps) return getDocParams(DocumentType.MEM_VIEW, null, otherProps)
} }
exports.generatePluginID = (name, version) => { exports.generatePluginID = (name, version) => {
return `${DocumentTypes.PLUGIN}${SEPARATOR}${name}${SEPARATOR}${version}` return `${DocumentType.PLUGIN}${SEPARATOR}${name}${SEPARATOR}${version}`
} }
/** /**

View file

@ -1,11 +1,6 @@
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
const { const { DocumentType, SEPARATOR, ViewName, SearchIndexes } = require("../utils")
DocumentTypes, const SCREEN_PREFIX = DocumentType.SCREEN + SEPARATOR
SEPARATOR,
ViewNames,
SearchIndexes,
} = require("../utils")
const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR
/************************************************** /**************************************************
* INFORMATION * * INFORMATION *
@ -53,7 +48,7 @@ exports.createLinkView = async () => {
} }
designDoc.views = { designDoc.views = {
...designDoc.views, ...designDoc.views,
[ViewNames.LINK]: view, [ViewName.LINK]: view,
} }
await db.put(designDoc) await db.put(designDoc)
} }
@ -74,7 +69,7 @@ exports.createRoutingView = async () => {
} }
designDoc.views = { designDoc.views = {
...designDoc.views, ...designDoc.views,
[ViewNames.ROUTING]: view, [ViewName.ROUTING]: view,
} }
await db.put(designDoc) await db.put(designDoc)
} }

View file

@ -5,14 +5,14 @@ import {
Document, Document,
} from "@budibase/types" } from "@budibase/types"
export enum LoopStepTypes { export enum LoopStepType {
ARRAY = "Array", ARRAY = "Array",
STRING = "String", STRING = "String",
} }
export interface LoopStep extends AutomationStep { export interface LoopStep extends AutomationStep {
inputs: { inputs: {
option: LoopStepTypes option: LoopStepType
[key: string]: any [key: string]: any
} }
} }

View file

@ -1,70 +1,19 @@
export { Query, Datasource } from "./datasource" import { Document } from "@budibase/types"
export {
Query,
Datasource,
FieldSchema,
TableSchema,
Table,
Document,
Row,
} from "@budibase/types"
export interface Base { export interface Application extends Document {
_id?: string _id: string
_rev?: string
}
export interface Application extends Base {
appId?: string appId?: string
} }
export interface FieldSchema {
// TODO: replace with field types enum when done
type: string
externalType?: string
fieldName?: string
name: string
tableId?: string
relationshipType?: string
through?: string
foreignKey?: string
autocolumn?: boolean
subtype?: string
throughFrom?: string
throughTo?: string
formula?: string
formulaType?: string
main?: boolean
ignoreTimezones?: boolean
meta?: {
toTable: string
toKey: string
}
constraints?: {
type?: string
email?: boolean
inclusion?: string[]
length?: {
minimum?: string | number
maximum?: string | number
}
presence?: boolean
}
}
export interface TableSchema {
[key: string]: FieldSchema
}
export interface Table extends Base {
type?: string
views?: {}
name: string
primary?: string[]
schema: TableSchema
primaryDisplay?: string
sourceId?: string
relatedFormula?: string[]
constrained?: string[]
}
export interface Row extends Base {
type?: string
tableId?: string
[key: string]: any
}
interface JsonSchemaField { interface JsonSchemaField {
properties: { properties: {
[key: string]: { [key: string]: {
@ -94,7 +43,7 @@ export interface AutomationStep {
type: string type: string
} }
export interface Automation extends Base { export interface Automation extends Document {
name: string name: string
type: string type: string
appId?: string appId?: string

View file

@ -1,162 +1,13 @@
import { Row, Table, Base } from "./common" /********************************************
import { * This file contains structures which are *
Operation, * internal to the server and don't need to *
QueryTypes, * be exposed for use by other services. *
SortDirection, ********************************************/
SourceNames,
} from "@budibase/types"
// these were previously exported here - moved to types for re-use
export {
Operation,
SortDirection,
QueryTypes,
DatasourceFieldTypes,
SourceNames,
IncludeRelationships,
FilterTypes,
} from "@budibase/types"
export interface QueryDefinition {
type: QueryTypes
displayName?: string
readable?: boolean
customisable?: boolean
fields?: object
urlDisplay?: boolean
}
export interface ExtraQueryConfig {
[key: string]: {
displayName: string
type: string
required: boolean
data?: object
}
}
export interface Integration {
docs: string
plus?: boolean
auth?: { type: string }
relationships?: boolean
description: string
friendlyName: string
type?: string
datasource: {}
query: {
[key: string]: QueryDefinition
}
extra?: ExtraQueryConfig
}
export interface SearchFilters {
allOr?: boolean
string?: {
[key: string]: string
}
fuzzy?: {
[key: string]: string
}
range?: {
[key: string]: {
high: number | string
low: number | string
}
}
equal?: {
[key: string]: any
}
notEqual?: {
[key: string]: any
}
empty?: {
[key: string]: any
}
notEmpty?: {
[key: string]: any
}
oneOf?: {
[key: string]: any[]
}
contains?: {
[key: string]: any
}
}
export interface SortJson {
[key: string]: SortDirection
}
export interface PaginationJson {
limit: number
page?: string | number
}
export interface RenameColumn {
old: string
updated: string
}
export interface RelationshipsJson {
through?: string
from?: string
to?: string
fromPrimary?: string
toPrimary?: string
tableName: string
column: string
}
export interface QueryJson {
endpoint: {
datasourceId: string
entityId: string
operation: Operation
schema?: string
}
resource: {
fields: string[]
}
filters?: SearchFilters
sort?: SortJson
paginate?: PaginationJson
body?: Row | Row[]
table?: Table
meta?: {
table?: Table
tables?: Record<string, Table>
renamed: RenameColumn
}
extra?: {
idFilter?: SearchFilters
}
relationships?: RelationshipsJson[]
}
export interface SqlQuery {
sql: string
bindings?: string[]
}
export interface QueryOptions { export interface QueryOptions {
disableReturning?: boolean disableReturning?: boolean
} }
export interface Datasource extends Base {
type: string
name: string
source: SourceNames
// the config is defined by the schema
config: {
[key: string]: string | number | boolean
}
plus: boolean
entities?: {
[key: string]: Table
}
}
export enum AuthType { export enum AuthType {
BASIC = "basic", BASIC = "basic",
BEARER = "bearer", BEARER = "bearer",
@ -178,25 +29,6 @@ export interface BearerAuthConfig {
token: string token: string
} }
export interface QueryParameter {
name: string
default: string
}
export interface RestQueryFields {
path: string
queryString?: string
headers: { [key: string]: any }
disabledHeaders: { [key: string]: any }
requestBody: any
bodyType: string
json: object
method: string
authConfigId: string
pagination: PaginationConfig | null
paginationValues: PaginationValues | null
}
export interface RestConfig { export interface RestConfig {
url: string url: string
defaultHeaders: { defaultHeaders: {
@ -214,28 +46,3 @@ export interface RestConfig {
} }
] ]
} }
export interface PaginationConfig {
type: string
location: string
pageParam: string
sizeParam: string | null
responseParam: string | null
}
export interface PaginationValues {
page: string | number | null
limit: number | null
}
export interface Query {
_id?: string
datasourceId: string
name: string
parameters: QueryParameter[]
fields: RestQueryFields | any
transformer: string | null
schema: any
readable: boolean
queryVerb: string
}

View file

@ -1,9 +1,9 @@
import { import {
Integration, Integration,
DatasourceFieldTypes, DatasourceFieldType,
QueryTypes, QueryType,
} from "../definitions/datasource" IntegrationBase,
import { IntegrationBase } from "./base/IntegrationBase" } from "@budibase/types"
module AirtableModule { module AirtableModule {
const Airtable = require("airtable") const Airtable = require("airtable")
@ -21,56 +21,61 @@ module AirtableModule {
type: "Spreadsheet", type: "Spreadsheet",
datasource: { datasource: {
apiKey: { apiKey: {
type: DatasourceFieldTypes.PASSWORD, type: DatasourceFieldType.PASSWORD,
default: "enter api key", default: "enter api key",
required: true, required: true,
}, },
base: { base: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "mybase", default: "mybase",
required: true, required: true,
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
fields: { fields: {
table: { table: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
read: { read: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
fields: { fields: {
table: { table: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
view: { view: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
numRecords: { numRecords: {
type: DatasourceFieldTypes.NUMBER, type: DatasourceFieldType.NUMBER,
default: 10, default: 10,
}, },
}, },
}, },
update: { update: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
fields: { fields: {
id: { id: {
type: DatasourceFieldTypes.STRING, display: "Record ID",
type: DatasourceFieldType.STRING,
required: true,
},
table: {
type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
delete: { delete: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
}, },
} }

View file

@ -1,9 +1,9 @@
import { import {
Integration, Integration,
DatasourceFieldTypes, DatasourceFieldType,
QueryTypes, QueryType,
} from "../definitions/datasource" IntegrationBase,
import { IntegrationBase } from "./base/IntegrationBase" } from "@budibase/types"
module ArangoModule { module ArangoModule {
const { Database, aql } = require("arangojs") const { Database, aql } = require("arangojs")
@ -24,35 +24,35 @@ module ArangoModule {
"ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ", "ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ",
datasource: { datasource: {
url: { url: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "http://localhost:8529", default: "http://localhost:8529",
required: true, required: true,
}, },
username: { username: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "root", default: "root",
required: true, required: true,
}, },
password: { password: {
type: DatasourceFieldTypes.PASSWORD, type: DatasourceFieldType.PASSWORD,
required: true, required: true,
}, },
databaseName: { databaseName: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "_system", default: "_system",
required: true, required: true,
}, },
collection: { collection: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
query: { query: {
read: { read: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
create: { create: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
}, },
} }

View file

@ -1,6 +0,0 @@
export interface IntegrationBase {
create?(query: any): Promise<any[] | any>
read?(query: any): Promise<any[] | any>
update?(query: any): Promise<any[] | any>
delete?(query: any): Promise<any[] | any>
}

View file

@ -1,13 +0,0 @@
import { Table } from "../../definitions/common"
import { IntegrationBase } from "./IntegrationBase"
export interface DatasourcePlus extends IntegrationBase {
tables: Record<string, Table>
schemaErrors: Record<string, string>
// if the datasource supports the use of bindings directly (to protect against SQL injection)
// this returns the format of the identifier
getBindingIdentifier(): string
getStringConcat(parts: string[]): string
buildSchema(datasourceId: string, entities: Record<string, Table>): any
}

View file

@ -1,5 +1,4 @@
import { QueryJson } from "../../definitions/datasource" import { QueryJson, Datasource } from "@budibase/types"
import { Datasource } from "../../definitions/common"
const { integrations } = require("../index") const { integrations } = require("../index")
export async function makeExternalQuery( export async function makeExternalQuery(

View file

@ -2,12 +2,12 @@ import { Knex, knex } from "knex"
import { import {
Operation, Operation,
QueryJson, QueryJson,
QueryOptions,
RelationshipsJson, RelationshipsJson,
SearchFilters, SearchFilters,
SortDirection, SortDirection,
} from "../../definitions/datasource" } from "@budibase/types"
import { isIsoDateString, SqlClients } from "../utils" import { QueryOptions } from "../../definitions/datasource"
import { isIsoDateString, SqlClient } from "../utils"
import SqlTableQueryBuilder from "./sqlTable" import SqlTableQueryBuilder from "./sqlTable"
import environment from "../../environment" import environment from "../../environment"
import { removeKeyNumbering } from "./utils" import { removeKeyNumbering } from "./utils"
@ -28,14 +28,14 @@ function likeKey(client: string, key: string): string {
} }
let start: string, end: string let start: string, end: string
switch (client) { switch (client) {
case SqlClients.MY_SQL: case SqlClient.MY_SQL:
start = end = "`" start = end = "`"
break break
case SqlClients.ORACLE: case SqlClient.ORACLE:
case SqlClients.POSTGRES: case SqlClient.POSTGRES:
start = end = '"' start = end = '"'
break break
case SqlClients.MS_SQL: case SqlClient.MS_SQL:
start = "[" start = "["
end = "]" end = "]"
break break
@ -103,7 +103,7 @@ function generateSelectStatement(
if ( if (
columnName && columnName &&
schema?.[columnName] && schema?.[columnName] &&
knex.client.config.client === SqlClients.POSTGRES knex.client.config.client === SqlClient.POSTGRES
) { ) {
const externalType = schema[columnName].externalType const externalType = schema[columnName].externalType
if (externalType?.includes("money")) { if (externalType?.includes("money")) {
@ -148,7 +148,7 @@ class InternalBuilder {
const like = (key: string, value: any) => { const like = (key: string, value: any) => {
const fnc = allOr ? "orWhere" : "where" const fnc = allOr ? "orWhere" : "where"
// postgres supports ilike, nothing else does // postgres supports ilike, nothing else does
if (this.client === SqlClients.POSTGRES) { if (this.client === SqlClient.POSTGRES) {
query = query[fnc](key, "ilike", `%${value}%`) query = query[fnc](key, "ilike", `%${value}%`)
} else { } else {
const rawFnc = `${fnc}Raw` const rawFnc = `${fnc}Raw`
@ -175,7 +175,7 @@ class InternalBuilder {
iterate(filters.string, (key, value) => { iterate(filters.string, (key, value) => {
const fnc = allOr ? "orWhere" : "where" const fnc = allOr ? "orWhere" : "where"
// postgres supports ilike, nothing else does // postgres supports ilike, nothing else does
if (this.client === SqlClients.POSTGRES) { if (this.client === SqlClient.POSTGRES) {
query = query[fnc](key, "ilike", `${value}%`) query = query[fnc](key, "ilike", `${value}%`)
} else { } else {
const rawFnc = `${fnc}Raw` const rawFnc = `${fnc}Raw`
@ -231,7 +231,7 @@ class InternalBuilder {
if (filters.contains) { if (filters.contains) {
const fnc = allOr ? "orWhere" : "where" const fnc = allOr ? "orWhere" : "where"
const rawFnc = `${fnc}Raw` const rawFnc = `${fnc}Raw`
if (this.client === SqlClients.POSTGRES) { if (this.client === SqlClient.POSTGRES) {
iterate(filters.contains, (key: string, value: any) => { iterate(filters.contains, (key: string, value: any) => {
const fieldNames = key.split(/\./g) const fieldNames = key.split(/\./g)
const tableName = fieldNames[0] const tableName = fieldNames[0]
@ -244,7 +244,7 @@ class InternalBuilder {
`"${tableName}"."${columnName}"::jsonb @> '[${value}]'` `"${tableName}"."${columnName}"::jsonb @> '[${value}]'`
) )
}) })
} else if (this.client === SqlClients.MY_SQL) { } else if (this.client === SqlClient.MY_SQL) {
iterate(filters.contains, (key: string, value: any) => { iterate(filters.contains, (key: string, value: any) => {
if (typeof value === "string") { if (typeof value === "string") {
value = `"${value}"` value = `"${value}"`
@ -267,7 +267,7 @@ class InternalBuilder {
const direction = value === SortDirection.ASCENDING ? "asc" : "desc" const direction = value === SortDirection.ASCENDING ? "asc" : "desc"
query = query.orderBy(`${table?.name}.${key}`, direction) query = query.orderBy(`${table?.name}.${key}`, direction)
} }
} else if (this.client === SqlClients.MS_SQL && paginate?.limit) { } else if (this.client === SqlClient.MS_SQL && paginate?.limit) {
// @ts-ignore // @ts-ignore
query = query.orderBy(`${table?.name}.${table?.primary[0]}`) query = query.orderBy(`${table?.name}.${table?.primary[0]}`)
} }
@ -416,7 +416,7 @@ class InternalBuilder {
[tableName]: query, [tableName]: query,
}).select(selectStatement) }).select(selectStatement)
// have to add after as well (this breaks MS-SQL) // have to add after as well (this breaks MS-SQL)
if (this.client !== SqlClients.MS_SQL) { if (this.client !== SqlClient.MS_SQL) {
preQuery = this.addSorting(preQuery, json) preQuery = this.addSorting(preQuery, json)
} }
// handle joins // handle joins
@ -567,9 +567,9 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
// same as delete, manage returning // same as delete, manage returning
if (operation === Operation.CREATE || operation === Operation.UPDATE) { if (operation === Operation.CREATE || operation === Operation.UPDATE) {
let id let id
if (sqlClient === SqlClients.MS_SQL) { if (sqlClient === SqlClient.MS_SQL) {
id = results?.[0].id id = results?.[0].id
} else if (sqlClient === SqlClients.MY_SQL) { } else if (sqlClient === SqlClient.MY_SQL) {
id = results?.insertId id = results?.insertId
} }
row = processFn( row = processFn(

View file

@ -1,10 +1,5 @@
import { Knex, knex } from "knex" import { Knex, knex } from "knex"
import { Table } from "../../definitions/common" import { Operation, QueryJson, RenameColumn, Table } from "@budibase/types"
import {
Operation,
QueryJson,
RenameColumn,
} from "../../definitions/datasource"
import { breakExternalTableId } from "../utils" import { breakExternalTableId } from "../utils"
import SchemaBuilder = Knex.SchemaBuilder import SchemaBuilder = Knex.SchemaBuilder
import CreateTableBuilder = Knex.CreateTableBuilder import CreateTableBuilder = Knex.CreateTableBuilder

View file

@ -1,9 +1,9 @@
import { import {
Integration, Integration,
DatasourceFieldTypes, DatasourceFieldType,
QueryTypes, QueryType,
} from "../definitions/datasource" IntegrationBase,
import { IntegrationBase } from "./base/IntegrationBase" } from "@budibase/types"
module CouchDBModule { module CouchDBModule {
const PouchDB = require("pouchdb") const PouchDB = require("pouchdb")
@ -21,30 +21,30 @@ module CouchDBModule {
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.", "Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
datasource: { datasource: {
url: { url: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "http://localhost:5984", default: "http://localhost:5984",
}, },
database: { database: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
read: { read: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
update: { update: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
delete: { delete: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
fields: { fields: {
id: { id: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },

View file

@ -1,9 +1,9 @@
import { import {
Integration, Integration,
DatasourceFieldTypes, DatasourceFieldType,
QueryTypes, QueryType,
} from "../definitions/datasource" IntegrationBase,
import { IntegrationBase } from "./base/IntegrationBase" } from "@budibase/types"
module DynamoModule { module DynamoModule {
const AWS = require("aws-sdk") const AWS = require("aws-sdk")
@ -24,101 +24,101 @@ module DynamoModule {
type: "Non-relational", type: "Non-relational",
datasource: { datasource: {
region: { region: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "us-east-1", default: "us-east-1",
}, },
accessKeyId: { accessKeyId: {
type: DatasourceFieldTypes.PASSWORD, type: DatasourceFieldType.PASSWORD,
required: true, required: true,
}, },
secretAccessKey: { secretAccessKey: {
type: DatasourceFieldTypes.PASSWORD, type: DatasourceFieldType.PASSWORD,
required: true, required: true,
}, },
endpoint: { endpoint: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: false, required: false,
default: "https://dynamodb.us-east-1.amazonaws.com", default: "https://dynamodb.us-east-1.amazonaws.com",
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
fields: { fields: {
table: { table: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
read: { read: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
readable: true, readable: true,
fields: { fields: {
table: { table: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
index: { index: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
}, },
}, },
}, },
scan: { scan: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
readable: true, readable: true,
fields: { fields: {
table: { table: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
index: { index: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
}, },
}, },
}, },
describe: { describe: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
readable: true, readable: true,
fields: { fields: {
table: { table: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
get: { get: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
readable: true, readable: true,
fields: { fields: {
table: { table: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
update: { update: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
fields: { fields: {
table: { table: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
delete: { delete: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
fields: { fields: {
table: { table: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },

View file

@ -1,9 +1,9 @@
import { import {
Integration, Integration,
DatasourceFieldTypes, DatasourceFieldType,
QueryTypes, QueryType,
} from "../definitions/datasource" IntegrationBase,
import { IntegrationBase } from "./base/IntegrationBase" } from "@budibase/types"
module ElasticsearchModule { module ElasticsearchModule {
const { Client } = require("@elastic/elasticsearch") const { Client } = require("@elastic/elasticsearch")
@ -20,55 +20,55 @@ module ElasticsearchModule {
type: "Non-relational", type: "Non-relational",
datasource: { datasource: {
url: { url: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "http://localhost:9200", default: "http://localhost:9200",
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
fields: { fields: {
index: { index: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
read: { read: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
fields: { fields: {
index: { index: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
update: { update: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
customisable: true, customisable: true,
fields: { fields: {
id: { id: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
index: { index: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
delete: { delete: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
fields: { fields: {
index: { index: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
id: { id: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },

View file

@ -1,9 +1,9 @@
import { import {
DatasourceFieldTypes, DatasourceFieldType,
Integration, Integration,
QueryTypes, QueryType,
} from "../definitions/datasource" IntegrationBase,
import { IntegrationBase } from "./base/IntegrationBase" } from "@budibase/types"
import { Firestore, WhereFilterOp } from "@google-cloud/firestore" import { Firestore, WhereFilterOp } from "@google-cloud/firestore"
module Firebase { module Firebase {
@ -21,46 +21,46 @@ module Firebase {
"Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.", "Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.",
datasource: { datasource: {
email: { email: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
privateKey: { privateKey: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
projectId: { projectId: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
read: { read: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
update: { update: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
delete: { delete: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
}, },
extra: { extra: {
collection: { collection: {
displayName: "Collection", displayName: "Collection",
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
filterField: { filterField: {
displayName: "Filter field", displayName: "Filter field",
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: false, required: false,
}, },
filter: { filter: {
displayName: "Filter comparison", displayName: "Filter comparison",
type: DatasourceFieldTypes.LIST, type: DatasourceFieldType.LIST,
required: false, required: false,
data: { data: {
read: [ read: [
@ -79,7 +79,7 @@ module Firebase {
}, },
filterValue: { filterValue: {
displayName: "Filter value", displayName: "Filter value",
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: false, required: false,
}, },
}, },

View file

@ -1,12 +1,13 @@
import { import {
DatasourceFieldTypes, DatasourceFieldType,
Integration, Integration,
QueryType,
Table,
TableSchema,
QueryJson, QueryJson,
QueryTypes, DatasourcePlus,
} from "../definitions/datasource" } from "@budibase/types"
import { OAuth2Client } from "google-auth-library" import { OAuth2Client } from "google-auth-library"
import { DatasourcePlus } from "./base/datasourcePlus"
import { Table, TableSchema } from "../definitions/common"
import { buildExternalTableId } from "./utils" import { buildExternalTableId } from "./utils"
import { DataSourceOperation, FieldTypes } from "../constants" import { DataSourceOperation, FieldTypes } from "../constants"
import { GoogleSpreadsheet } from "google-spreadsheet" import { GoogleSpreadsheet } from "google-spreadsheet"
@ -53,59 +54,59 @@ module GoogleSheetsModule {
datasource: { datasource: {
spreadsheetId: { spreadsheetId: {
display: "Google Sheet URL", display: "Google Sheet URL",
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
fields: { fields: {
sheet: { sheet: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
row: { row: {
type: QueryTypes.JSON, type: QueryType.JSON,
required: true, required: true,
}, },
}, },
}, },
read: { read: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
fields: { fields: {
sheet: { sheet: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
}, },
update: { update: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
fields: { fields: {
sheet: { sheet: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
rowIndex: { rowIndex: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
row: { row: {
type: QueryTypes.JSON, type: QueryType.JSON,
required: true, required: true,
}, },
}, },
}, },
delete: { delete: {
type: QueryTypes.FIELDS, type: QueryType.FIELDS,
fields: { fields: {
sheet: { sheet: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
rowIndex: { rowIndex: {
type: DatasourceFieldTypes.NUMBER, type: DatasourceFieldType.NUMBER,
required: true, required: true,
}, },
}, },

View file

@ -13,54 +13,54 @@ const googlesheets = require("./googlesheets")
const firebase = require("./firebase") const firebase = require("./firebase")
const redis = require("./redis") const redis = require("./redis")
const snowflake = require("./snowflake") const snowflake = require("./snowflake")
const { SourceNames } = require("../definitions/datasource") const { SourceName } = require("@budibase/types")
const environment = require("../environment") const environment = require("../environment")
const DEFINITIONS = { const DEFINITIONS = {
[SourceNames.POSTGRES]: postgres.schema, [SourceName.POSTGRES]: postgres.schema,
[SourceNames.DYNAMODB]: dynamodb.schema, [SourceName.DYNAMODB]: dynamodb.schema,
[SourceNames.MONGODB]: mongodb.schema, [SourceName.MONGODB]: mongodb.schema,
[SourceNames.ELASTICSEARCH]: elasticsearch.schema, [SourceName.ELASTICSEARCH]: elasticsearch.schema,
[SourceNames.COUCHDB]: couchdb.schema, [SourceName.COUCHDB]: couchdb.schema,
[SourceNames.SQL_SERVER]: sqlServer.schema, [SourceName.SQL_SERVER]: sqlServer.schema,
[SourceNames.S3]: s3.schema, [SourceName.S3]: s3.schema,
[SourceNames.AIRTABLE]: airtable.schema, [SourceName.AIRTABLE]: airtable.schema,
[SourceNames.MYSQL]: mysql.schema, [SourceName.MYSQL]: mysql.schema,
[SourceNames.ARANGODB]: arangodb.schema, [SourceName.ARANGODB]: arangodb.schema,
[SourceNames.REST]: rest.schema, [SourceName.REST]: rest.schema,
[SourceNames.FIRESTORE]: firebase.schema, [SourceName.FIRESTORE]: firebase.schema,
[SourceNames.REDIS]: redis.schema, [SourceName.REDIS]: redis.schema,
[SourceNames.SNOWFLAKE]: snowflake.schema, [SourceName.SNOWFLAKE]: snowflake.schema,
} }
const INTEGRATIONS = { const INTEGRATIONS = {
[SourceNames.POSTGRES]: postgres.integration, [SourceName.POSTGRES]: postgres.integration,
[SourceNames.DYNAMODB]: dynamodb.integration, [SourceName.DYNAMODB]: dynamodb.integration,
[SourceNames.MONGODB]: mongodb.integration, [SourceName.MONGODB]: mongodb.integration,
[SourceNames.ELASTICSEARCH]: elasticsearch.integration, [SourceName.ELASTICSEARCH]: elasticsearch.integration,
[SourceNames.COUCHDB]: couchdb.integration, [SourceName.COUCHDB]: couchdb.integration,
[SourceNames.SQL_SERVER]: sqlServer.integration, [SourceName.SQL_SERVER]: sqlServer.integration,
[SourceNames.S3]: s3.integration, [SourceName.S3]: s3.integration,
[SourceNames.AIRTABLE]: airtable.integration, [SourceName.AIRTABLE]: airtable.integration,
[SourceNames.MYSQL]: mysql.integration, [SourceName.MYSQL]: mysql.integration,
[SourceNames.ARANGODB]: arangodb.integration, [SourceName.ARANGODB]: arangodb.integration,
[SourceNames.REST]: rest.integration, [SourceName.REST]: rest.integration,
[SourceNames.FIRESTORE]: firebase.integration, [SourceName.FIRESTORE]: firebase.integration,
[SourceNames.GOOGLE_SHEETS]: googlesheets.integration, [SourceName.GOOGLE_SHEETS]: googlesheets.integration,
[SourceNames.REDIS]: redis.integration, [SourceName.REDIS]: redis.integration,
[SourceNames.FIREBASE]: firebase.integration, [SourceName.FIREBASE]: firebase.integration,
[SourceNames.SNOWFLAKE]: snowflake.integration, [SourceName.SNOWFLAKE]: snowflake.integration,
} }
// optionally add oracle integration if the oracle binary can be installed // optionally add oracle integration if the oracle binary can be installed
if (process.arch && !process.arch.startsWith("arm")) { if (process.arch && !process.arch.startsWith("arm")) {
const oracle = require("./oracle") const oracle = require("./oracle")
DEFINITIONS[SourceNames.ORACLE] = oracle.schema DEFINITIONS[SourceName.ORACLE] = oracle.schema
INTEGRATIONS[SourceNames.ORACLE] = oracle.integration INTEGRATIONS[SourceName.ORACLE] = oracle.integration
} }
if (environment.SELF_HOSTED) { if (environment.SELF_HOSTED) {
DEFINITIONS[SourceNames.GOOGLE_SHEETS] = googlesheets.schema DEFINITIONS[SourceName.GOOGLE_SHEETS] = googlesheets.schema
} }
module.exports = { module.exports = {

View file

@ -1,20 +1,21 @@
import { import {
DatasourceFieldTypes, DatasourceFieldType,
Integration, Integration,
Operation, Operation,
Table,
TableSchema,
QueryJson, QueryJson,
QueryTypes, QueryType,
SqlQuery, SqlQuery,
} from "../definitions/datasource" DatasourcePlus,
} from "@budibase/types"
import { import {
getSqlQuery, getSqlQuery,
buildExternalTableId, buildExternalTableId,
convertSqlType, convertSqlType,
finaliseExternalTables, finaliseExternalTables,
SqlClients, SqlClient,
} from "./utils" } from "./utils"
import { DatasourcePlus } from "./base/datasourcePlus"
import { Table, TableSchema } from "../definitions/common"
import Sql from "./base/sql" import Sql from "./base/sql"
module MSSQLModule { module MSSQLModule {
@ -47,48 +48,48 @@ module MSSQLModule {
type: "Relational", type: "Relational",
datasource: { datasource: {
user: { user: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "localhost", default: "localhost",
}, },
password: { password: {
type: DatasourceFieldTypes.PASSWORD, type: DatasourceFieldType.PASSWORD,
required: true, required: true,
}, },
server: { server: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "localhost", default: "localhost",
}, },
port: { port: {
type: DatasourceFieldTypes.NUMBER, type: DatasourceFieldType.NUMBER,
required: false, required: false,
default: 1433, default: 1433,
}, },
database: { database: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "root", default: "root",
}, },
schema: { schema: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: DEFAULT_SCHEMA, default: DEFAULT_SCHEMA,
}, },
encrypt: { encrypt: {
type: DatasourceFieldTypes.BOOLEAN, type: DatasourceFieldType.BOOLEAN,
default: true, default: true,
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
read: { read: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
update: { update: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
delete: { delete: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
}, },
} }
@ -112,7 +113,7 @@ module MSSQLModule {
"SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'" "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'"
constructor(config: MSSQLConfig) { constructor(config: MSSQLConfig) {
super(SqlClients.MS_SQL) super(SqlClient.MS_SQL)
this.config = config this.config = config
const clientCfg = { const clientCfg = {
...this.config, ...this.config,

View file

@ -1,9 +1,9 @@
import { import {
Integration, Integration,
DatasourceFieldTypes, DatasourceFieldType,
QueryTypes, QueryType,
} from "../definitions/datasource" IntegrationBase,
import { IntegrationBase } from "./base/IntegrationBase" } from "@budibase/types"
import { import {
MongoClient, MongoClient,
ObjectID, ObjectID,
@ -29,38 +29,38 @@ module MongoDBModule {
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.", "MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
datasource: { datasource: {
connectionString: { connectionString: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "mongodb://localhost:27017", default: "mongodb://localhost:27017",
}, },
db: { db: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
read: { read: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
update: { update: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
delete: { delete: {
type: QueryTypes.JSON, type: QueryType.JSON,
}, },
}, },
extra: { extra: {
collection: { collection: {
displayName: "Collection", displayName: "Collection",
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
actionTypes: { actionTypes: {
displayName: "Action Types", displayName: "Action Types",
type: DatasourceFieldTypes.LIST, type: DatasourceFieldType.LIST,
required: true, required: true,
data: { data: {
read: ["find", "findOne", "findOneAndUpdate", "count", "distinct"], read: ["find", "findOne", "findOneAndUpdate", "count", "distinct"],
@ -92,12 +92,15 @@ module MongoDBModule {
if (json[field] instanceof Object) { if (json[field] instanceof Object) {
json[field] = self.createObjectIds(json[field]) json[field] = self.createObjectIds(json[field])
} }
if (field === "_id" && typeof json[field] === "string") { if (
const id = json["_id"].match( (field === "_id" || field?.startsWith("$")) &&
typeof json[field] === "string"
) {
const id = json[field].match(
/(?<=objectid\(['"]).*(?=['"]\))/gi /(?<=objectid\(['"]).*(?=['"]\))/gi
)?.[0] )?.[0]
if (id) { if (id) {
json["_id"] = ObjectID.createFromHexString(id) json[field] = ObjectID.createFromHexString(id)
} }
} }
} }
@ -114,10 +117,31 @@ module MongoDBModule {
} }
parseQueryParams(params: string, mode: string) { parseQueryParams(params: string, mode: string) {
let queryParams = params.split(/(?<=}),[\n\s]*(?={)/g) let queryParams = []
let group1 = queryParams[0] ? JSON.parse(queryParams[0]) : {} let openCount = 0
let group2 = queryParams[1] ? JSON.parse(queryParams[1]) : {} let inQuotes = false
let group3 = queryParams[2] ? JSON.parse(queryParams[2]) : {} let i = 0
let startIndex = 0
for (let c of params) {
if (c === '"' && i > 0 && params[i - 1] !== "\\") {
inQuotes = !inQuotes
}
if (c === "{" && !inQuotes) {
openCount++
if (openCount === 1) {
startIndex = i
}
} else if (c === "}" && !inQuotes) {
if (openCount === 1) {
queryParams.push(JSON.parse(params.substring(startIndex, i + 1)))
}
openCount--
}
i++
}
let group1 = queryParams[0] ?? {}
let group2 = queryParams[1] ?? {}
let group3 = queryParams[2] ?? {}
if (mode === "update") { if (mode === "update") {
return { return {
filter: group1, filter: group1,
@ -176,7 +200,10 @@ module MongoDBModule {
return await collection.findOne(json) return await collection.findOne(json)
} }
case "findOneAndUpdate": { case "findOneAndUpdate": {
let findAndUpdateJson = json as { if (typeof query.json === "string") {
json = this.parseQueryParams(query.json, "update")
}
let findAndUpdateJson = this.createObjectIds(json) as {
filter: FilterQuery<any> filter: FilterQuery<any>
update: UpdateQuery<any> update: UpdateQuery<any>
options: FindOneAndUpdateOption<any> options: FindOneAndUpdateOption<any>

View file

@ -1,19 +1,20 @@
import { import {
Integration, Integration,
DatasourceFieldTypes, DatasourceFieldType,
QueryTypes, QueryType,
QueryJson, QueryJson,
SqlQuery, SqlQuery,
} from "../definitions/datasource" Table,
import { Table, TableSchema } from "../definitions/common" TableSchema,
DatasourcePlus,
} from "@budibase/types"
import { import {
getSqlQuery, getSqlQuery,
SqlClients, SqlClient,
buildExternalTableId, buildExternalTableId,
convertSqlType, convertSqlType,
finaliseExternalTables, finaliseExternalTables,
} from "./utils" } from "./utils"
import { DatasourcePlus } from "./base/datasourcePlus"
import dayjs from "dayjs" import dayjs from "dayjs"
const { NUMBER_REGEX } = require("../utilities") const { NUMBER_REGEX } = require("../utilities")
import Sql from "./base/sql" import Sql from "./base/sql"
@ -41,51 +42,51 @@ module MySQLModule {
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ", "MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
datasource: { datasource: {
host: { host: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "localhost", default: "localhost",
required: true, required: true,
}, },
port: { port: {
type: DatasourceFieldTypes.NUMBER, type: DatasourceFieldType.NUMBER,
default: 3306, default: 3306,
required: false, required: false,
}, },
user: { user: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "root", default: "root",
required: true, required: true,
}, },
password: { password: {
type: DatasourceFieldTypes.PASSWORD, type: DatasourceFieldType.PASSWORD,
default: "root", default: "root",
required: true, required: true,
}, },
database: { database: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
ssl: { ssl: {
type: DatasourceFieldTypes.OBJECT, type: DatasourceFieldType.OBJECT,
required: false, required: false,
}, },
rejectUnauthorized: { rejectUnauthorized: {
type: DatasourceFieldTypes.BOOLEAN, type: DatasourceFieldType.BOOLEAN,
default: true, default: true,
required: false, required: false,
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
read: { read: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
update: { update: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
delete: { delete: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
}, },
} }
@ -119,7 +120,7 @@ module MySQLModule {
public schemaErrors: Record<string, string> = {} public schemaErrors: Record<string, string> = {}
constructor(config: MySQLConfig) { constructor(config: MySQLConfig) {
super(SqlClients.MY_SQL) super(SqlClient.MY_SQL)
this.config = config this.config = config
if (config.ssl && Object.keys(config.ssl).length === 0) { if (config.ssl && Object.keys(config.ssl).length === 0) {
delete config.ssl delete config.ssl

View file

@ -1,17 +1,19 @@
import { import {
DatasourceFieldTypes, DatasourceFieldType,
Integration, Integration,
Operation, Operation,
QueryJson, QueryJson,
QueryTypes, QueryType,
SqlQuery, SqlQuery,
} from "../definitions/datasource" Table,
DatasourcePlus,
} from "@budibase/types"
import { import {
buildExternalTableId, buildExternalTableId,
convertSqlType, convertSqlType,
finaliseExternalTables, finaliseExternalTables,
getSqlQuery, getSqlQuery,
SqlClients, SqlClient,
} from "./utils" } from "./utils"
import oracledb, { import oracledb, {
BindParameters, BindParameters,
@ -21,8 +23,6 @@ import oracledb, {
Result, Result,
} from "oracledb" } from "oracledb"
import Sql from "./base/sql" import Sql from "./base/sql"
import { Table } from "../definitions/common"
import { DatasourcePlus } from "./base/datasourcePlus"
import { FieldTypes } from "../constants" import { FieldTypes } from "../constants"
module OracleModule { module OracleModule {
@ -45,40 +45,40 @@ module OracleModule {
"Oracle Database is an object-relational database management system developed by Oracle Corporation", "Oracle Database is an object-relational database management system developed by Oracle Corporation",
datasource: { datasource: {
host: { host: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "localhost", default: "localhost",
required: true, required: true,
}, },
port: { port: {
type: DatasourceFieldTypes.NUMBER, type: DatasourceFieldType.NUMBER,
required: true, required: true,
default: 1521, default: 1521,
}, },
database: { database: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
user: { user: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
}, },
password: { password: {
type: DatasourceFieldTypes.PASSWORD, type: DatasourceFieldType.PASSWORD,
required: true, required: true,
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
read: { read: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
update: { update: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
delete: { delete: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
}, },
} }
@ -172,7 +172,7 @@ module OracleModule {
OR cons.status IS NULL) OR cons.status IS NULL)
` `
constructor(config: OracleConfig) { constructor(config: OracleConfig) {
super(SqlClients.ORACLE) super(SqlClient.ORACLE)
this.config = config this.config = config
} }

View file

@ -1,19 +1,19 @@
import { import {
Integration, Integration,
DatasourceFieldTypes, DatasourceFieldType,
QueryTypes, QueryType,
QueryJson, QueryJson,
SqlQuery, SqlQuery,
} from "../definitions/datasource" Table,
import { Table } from "../definitions/common" DatasourcePlus,
} from "@budibase/types"
import { import {
getSqlQuery, getSqlQuery,
buildExternalTableId, buildExternalTableId,
convertSqlType, convertSqlType,
finaliseExternalTables, finaliseExternalTables,
SqlClients, SqlClient,
} from "./utils" } from "./utils"
import { DatasourcePlus } from "./base/datasourcePlus"
import Sql from "./base/sql" import Sql from "./base/sql"
module PostgresModule { module PostgresModule {
@ -52,63 +52,63 @@ module PostgresModule {
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.", "PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.",
datasource: { datasource: {
host: { host: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "localhost", default: "localhost",
required: true, required: true,
}, },
port: { port: {
type: DatasourceFieldTypes.NUMBER, type: DatasourceFieldType.NUMBER,
required: true, required: true,
default: 5432, default: 5432,
}, },
database: { database: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "postgres", default: "postgres",
required: true, required: true,
}, },
user: { user: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "root", default: "root",
required: true, required: true,
}, },
password: { password: {
type: DatasourceFieldTypes.PASSWORD, type: DatasourceFieldType.PASSWORD,
default: "root", default: "root",
required: true, required: true,
}, },
schema: { schema: {
type: DatasourceFieldTypes.STRING, type: DatasourceFieldType.STRING,
default: "public", default: "public",
required: true, required: true,
}, },
ssl: { ssl: {
type: DatasourceFieldTypes.BOOLEAN, type: DatasourceFieldType.BOOLEAN,
default: false, default: false,
required: false, required: false,
}, },
rejectUnauthorized: { rejectUnauthorized: {
type: DatasourceFieldTypes.BOOLEAN, type: DatasourceFieldType.BOOLEAN,
default: false, default: false,
required: false, required: false,
}, },
ca: { ca: {
type: DatasourceFieldTypes.LONGFORM, type: DatasourceFieldType.LONGFORM,
default: false, default: false,
required: false, required: false,
}, },
}, },
query: { query: {
create: { create: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
read: { read: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
update: { update: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
delete: { delete: {
type: QueryTypes.SQL, type: QueryType.SQL,
}, },
}, },
} }
@ -134,7 +134,7 @@ module PostgresModule {
` `
constructor(config: PostgresConfig) { constructor(config: PostgresConfig) {
super(SqlClients.POSTGRES) super(SqlClient.POSTGRES)
this.config = config this.config = config
let newConfig = { let newConfig = {

View file

@ -1,5 +1,5 @@
import { findHBSBlocks, processStringSync } from "@budibase/string-templates" import { findHBSBlocks, processStringSync } from "@budibase/string-templates"
import { DatasourcePlus } from "../base/datasourcePlus" import { DatasourcePlus } from "@budibase/types"
const CONST_CHAR_REGEX = new RegExp("'[^']*'", "g") const CONST_CHAR_REGEX = new RegExp("'[^']*'", "g")

Some files were not shown because too many files have changed in this diff Show more