Merge remote-tracking branch 'origin/develop' into feature/default-internal-datasource

This commit is contained in:
Dean 2022-11-15 17:11:19 +00:00
commit eb57c6604d
97 changed files with 3683 additions and 2696 deletions

View File

@ -3,6 +3,8 @@ public
dist
packages/server/builder
packages/server/coverage
packages/worker/coverage
packages/backend-core/coverage
packages/server/client
packages/builder/.routify
packages/builder/cypress/support/queryLevelTransformerFunction.js

View File

@ -58,7 +58,7 @@ jobs:
- uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: ./packages/server/coverage/clover.xml
files: ./packages/server/coverage/clover.xml,./packages/worker/coverage/clover.xml,./packages/backend-core/coverage/clover.xml
name: codecov-umbrella
verbose: true

View File

@ -4,6 +4,8 @@ dist
packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
packages/server/builder
packages/server/coverage
packages/worker/coverage
packages/backend-core/coverage
packages/server/client
packages/server/src/definitions/openapi.ts
packages/builder/.routify

View File

@ -66,6 +66,15 @@ http {
proxy_set_header Connection "";
}
location /api/backups/ {
proxy_read_timeout 1800s;
proxy_connect_timeout 1800s;
proxy_send_timeout 1800s;
proxy_pass http://app-service;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location /api/ {
proxy_read_timeout 120s;
proxy_connect_timeout 120s;

View File

@ -116,6 +116,15 @@ http {
rewrite ^/worker/(.*)$ /$1 break;
}
location /api/backups/ {
proxy_read_timeout 1800s;
proxy_connect_timeout 1800s;
proxy_send_timeout 1800s;
proxy_pass http://app-service;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location /api/ {
# calls to the API are rate limited with bursting
limit_req zone=ratelimit burst=20 nodelay;

View File

@ -1,5 +1,5 @@
{
"version": "2.1.19-alpha.0",
"version": "2.1.22-alpha.4",
"npmClient": "yarn",
"packages": [
"packages/*"

View File

@ -18,7 +18,7 @@
"rimraf": "^3.0.2",
"rollup-plugin-replace": "^2.2.0",
"svelte": "^3.38.2",
"typescript": "4.5.5"
"typescript": "4.7.3"
},
"scripts": {
"setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",

View File

@ -0,0 +1,15 @@
const mockS3 = {
headBucket: jest.fn().mockReturnThis(),
deleteObject: jest.fn().mockReturnThis(),
deleteObjects: jest.fn().mockReturnThis(),
createBucket: jest.fn().mockReturnThis(),
listObjects: jest.fn().mockReturnThis(),
promise: jest.fn().mockReturnThis(),
catch: jest.fn(),
}
const AWS = {
S3: jest.fn(() => mockS3),
}
export default AWS

View File

@ -0,0 +1 @@
jest.mock("node-fetch", () => jest.fn())

View File

@ -0,0 +1,20 @@
import { Config } from "@jest/types"
const config: Config.InitialOptions = {
preset: "ts-jest",
testEnvironment: "node",
setupFiles: ["./tests/jestSetup.ts"],
collectCoverageFrom: ["src/**/*.{js,ts}"],
coverageReporters: ["lcov", "json", "clover"],
}
if (!process.env.CI) {
// use sources when not in CI
config.moduleNameMapper = {
"@budibase/types": "<rootDir>/../types/src",
}
} else {
console.log("Running tests with compiled dependency sources")
}
export default config

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
"version": "2.1.19-alpha.0",
"version": "2.1.22-alpha.4",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@ -16,11 +16,11 @@
"prepack": "cp package.json dist",
"build": "tsc -p tsconfig.build.json",
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
"test": "jest",
"test": "jest --coverage",
"test:watch": "jest --watchAll"
},
"dependencies": {
"@budibase/types": "2.1.19-alpha.0",
"@budibase/types": "2.1.22-alpha.4",
"@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0",
@ -52,16 +52,6 @@
"uuid": "8.3.2",
"zlib": "1.0.5"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"moduleNameMapper": {
"@budibase/types": "<rootDir>/../types/src"
},
"setupFiles": [
"./scripts/jestSetup.ts"
]
},
"devDependencies": {
"@types/chance": "1.1.3",
"@types/ioredis": "4.28.0",
@ -77,12 +67,14 @@
"@types/uuid": "8.3.4",
"chance": "1.1.3",
"ioredis-mock": "5.8.0",
"jest": "27.5.1",
"jest": "28.1.1",
"koa": "2.7.0",
"nodemon": "2.0.16",
"pouchdb-adapter-memory": "7.2.2",
"timekeeper": "2.2.0",
"ts-jest": "27.1.5",
"ts-jest": "28.0.4",
"ts-node": "10.8.1",
"tsconfig-paths": "4.0.0",
"typescript": "4.7.3"
},
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"

View File

@ -1,12 +0,0 @@
import env from "../src/environment"
import { mocks } from "../tests/utilities"
// mock all dates to 2020-01-01T00:00:00.000Z
// use tk.reset() to use real dates in individual tests
import tk from "timekeeper"
tk.freeze(mocks.date.MOCK_DATE)
env._set("SELF_HOSTED", "1")
env._set("NODE_ENV", "jest")
env._set("JWT_SECRET", "test-jwtsecret")
env._set("LOG_LEVEL", "silent")

View File

@ -1,4 +1,4 @@
require("../../../tests/utilities/TestConfiguration")
require("../../../tests")
const { Writethrough } = require("../writethrough")
const { dangerousGetDB } = require("../../db")
const tk = require("timekeeper")

View File

@ -1,4 +1,4 @@
import "../../../tests/utilities/TestConfiguration"
import "../../../tests"
import * as context from ".."
import { DEFAULT_TENANT_ID } from "../../constants"
import env from "../../environment"

View File

@ -33,7 +33,7 @@ const checkInitialised = () => {
}
}
export async function init(opts?: PouchOptions) {
export function init(opts?: PouchOptions) {
Pouch = pouch.getPouch(opts)
initialised = true
}

View File

@ -1,5 +1,6 @@
import PouchDB from "pouchdb"
import env from "../environment"
import { PouchOptions } from "@budibase/types"
export const getUrlInfo = (url = env.COUCH_DB_URL) => {
let cleanUrl, username, password, host
@ -82,7 +83,7 @@ export const getCouchInfo = () => {
* This should be rarely used outside of the main application config.
* Exposed for exceptional cases such as in-memory views.
*/
export const getPouch = (opts: any = {}) => {
export const getPouch = (opts: PouchOptions = {}) => {
let { url, cookie } = getCouchInfo()
let POUCH_DB_DEFAULTS = {
prefix: url,

View File

@ -1,4 +1,4 @@
require("../../../tests/utilities/TestConfiguration")
require("../../../tests")
const { dangerousGetDB } = require("../")
describe("db", () => {

View File

@ -1,4 +1,4 @@
require("../../../tests/utilities/TestConfiguration")
require("../../../tests")
const getUrlInfo = require("../pouch").getUrlInfo
describe("pouch", () => {

View File

@ -1,4 +1,4 @@
require("../../../tests/utilities/TestConfiguration");
require("../../../tests");
const {
generateAppID,
getDevelopmentAppID,

View File

@ -1,4 +1,4 @@
import "../../../../../tests/utilities/TestConfiguration"
import "../../../../../tests"
import PosthogProcessor from "../PosthogProcessor"
import { Event, IdentityType, Hosting } from "@budibase/types"
const tk = require("timekeeper")

View File

@ -1,7 +1,6 @@
// Mock data
const mockFetch = require("node-fetch")
const { data } = require("./utilities/mock-data")
const issuer = "mockIssuer"
const sub = "mockSub"
const profile = {
@ -39,8 +38,6 @@ describe("oidc", () => {
const mockStrategy = require("@techpass/passport-openidconnect").Strategy
// mock the request to retrieve the oidc configuration
jest.mock("node-fetch")
const mockFetch = require("node-fetch")
mockFetch.mockReturnValue({
ok: true,
json: () => oidcConfigUrlResponse

View File

@ -1,4 +1,4 @@
require("../../../../tests/utilities/TestConfiguration")
require("../../../../tests")
const { authenticateThirdParty } = require("../third-party-common")
const { data } = require("./utilities/mock-data")
const { DEFAULT_TENANT_ID } = require("../../../constants")

View File

@ -1,4 +1,4 @@
require("../../../tests/utilities/TestConfiguration")
require("../../../tests")
const { runMigrations, getMigrationsDoc } = require("../index")
const { dangerousGetDB } = require("../../db")
const {

View File

@ -22,7 +22,19 @@ type ListParams = {
ContinuationToken?: string
}
type UploadParams = {
bucket: string
filename: string
path: string
type?: string
// can be undefined, we will remove it
metadata?: {
[key: string]: string | undefined
}
}
const CONTENT_TYPE_MAP: any = {
txt: "text/plain",
html: "text/html",
css: "text/css",
js: "application/javascript",
@ -149,20 +161,32 @@ export const upload = async ({
path,
type,
metadata,
}: any) => {
}: UploadParams) => {
const extension = filename.split(".").pop()
const fileBytes = fs.readFileSync(path)
const objectStore = ObjectStore(bucketName)
await makeSureBucketExists(objectStore, bucketName)
let contentType = type
if (!contentType) {
contentType = extension
? CONTENT_TYPE_MAP[extension.toLowerCase()]
: CONTENT_TYPE_MAP.txt
}
const config: any = {
// windows file paths need to be converted to forward slashes for s3
Key: sanitizeKey(filename),
Body: fileBytes,
ContentType: type || CONTENT_TYPE_MAP[extension.toLowerCase()],
ContentType: contentType,
}
if (metadata) {
if (metadata && typeof metadata === "object") {
// remove any nullish keys from the metadata object, as these may be considered invalid
for (let key of Object.keys(metadata)) {
if (!metadata[key] || typeof metadata[key] !== "string") {
delete metadata[key]
}
}
config.Metadata = metadata
}
return objectStore.upload(config).promise()

View File

@ -29,6 +29,7 @@ class InMemoryQueue {
_messages: any[]
_emitter: EventEmitter
_runCount: number
_addCount: number
/**
* The constructor the queue, exactly the same as that of Bulls.
* @param {string} name The name of the queue which is being configured.
@ -41,6 +42,7 @@ class InMemoryQueue {
this._messages = []
this._emitter = new events.EventEmitter()
this._runCount = 0
this._addCount = 0
}
/**
@ -81,6 +83,7 @@ class InMemoryQueue {
throw "Queue only supports carrying JSON."
}
this._messages.push(newJob(this._name, msg))
this._addCount++
this._emitter.emit("message")
}
@ -128,12 +131,9 @@ class InMemoryQueue {
}
async waitForCompletion() {
const currentCount = this._runCount
let increased = false
do {
await timeout(50)
increased = this._runCount > currentCount
} while (!increased)
} while (this._addCount < this._runCount)
}
}

View File

@ -1,21 +1,27 @@
const { cloneDeep } = require("lodash/fp")
const { BUILTIN_PERMISSION_IDS, PermissionLevels } = require("./permissions")
const {
import { BUILTIN_PERMISSION_IDS, PermissionLevels } from "./permissions"
import {
generateRoleID,
getRoleParams,
DocumentType,
SEPARATOR,
} = require("../db/utils")
const { getAppDB } = require("../context")
const { doWithDB } = require("../db")
} from "../db/utils"
import { getAppDB } from "../context"
import { doWithDB } from "../db"
import { Screen, Role as RoleDoc } from "@budibase/types"
const { cloneDeep } = require("lodash/fp")
const BUILTIN_IDS = {
export const BUILTIN_ROLE_IDS = {
ADMIN: "ADMIN",
POWER: "POWER",
BASIC: "BASIC",
PUBLIC: "PUBLIC",
}
const BUILTIN_IDS = {
...BUILTIN_ROLE_IDS,
BUILDER: "BUILDER",
}
// exclude internal roles like builder
const EXTERNAL_BUILTIN_ROLE_IDS = [
BUILTIN_IDS.ADMIN,
@ -24,19 +30,26 @@ const EXTERNAL_BUILTIN_ROLE_IDS = [
BUILTIN_IDS.PUBLIC,
]
function Role(id, name) {
this._id = id
this.name = name
}
export class Role {
_id: string
name: string
permissionId?: string
inherits?: string
Role.prototype.addPermission = function (permissionId) {
this.permissionId = permissionId
return this
}
constructor(id: string, name: string) {
this._id = id
this.name = name
}
Role.prototype.addInheritance = function (inherits) {
this.inherits = inherits
return this
addPermission(permissionId: string) {
this.permissionId = permissionId
return this
}
addInheritance(inherits: string) {
this.inherits = inherits
return this
}
}
const BUILTIN_ROLES = {
@ -57,27 +70,30 @@ const BUILTIN_ROLES = {
),
}
exports.getBuiltinRoles = () => {
export function getBuiltinRoles() {
return cloneDeep(BUILTIN_ROLES)
}
exports.BUILTIN_ROLE_ID_ARRAY = Object.values(BUILTIN_ROLES).map(
export const BUILTIN_ROLE_ID_ARRAY = Object.values(BUILTIN_ROLES).map(
role => role._id
)
exports.BUILTIN_ROLE_NAME_ARRAY = Object.values(BUILTIN_ROLES).map(
export const BUILTIN_ROLE_NAME_ARRAY = Object.values(BUILTIN_ROLES).map(
role => role.name
)
function isBuiltin(role) {
return exports.BUILTIN_ROLE_ID_ARRAY.some(builtin => role.includes(builtin))
export function isBuiltin(role?: string) {
return BUILTIN_ROLE_ID_ARRAY.some(builtin => role?.includes(builtin))
}
/**
* Works through the inheritance ranks to see how far up the builtin stack this ID is.
*/
exports.builtinRoleToNumber = id => {
const builtins = exports.getBuiltinRoles()
export function builtinRoleToNumber(id?: string) {
if (!id) {
return 0
}
const builtins = getBuiltinRoles()
const MAX = Object.values(builtins).length + 1
if (id === BUILTIN_IDS.ADMIN || id === BUILTIN_IDS.BUILDER) {
return MAX
@ -97,14 +113,14 @@ exports.builtinRoleToNumber = id => {
/**
* Converts any role to a number, but has to be async to get the roles from db.
*/
exports.roleToNumber = async id => {
if (exports.isBuiltin(id)) {
return exports.builtinRoleToNumber(id)
export async function roleToNumber(id?: string) {
if (isBuiltin(id)) {
return builtinRoleToNumber(id)
}
const hierarchy = await exports.getUserRoleHierarchy(id)
const hierarchy = (await getUserRoleHierarchy(id)) as RoleDoc[]
for (let role of hierarchy) {
if (isBuiltin(role.inherits)) {
return exports.builtinRoleToNumber(role.inherits) + 1
if (isBuiltin(role?.inherits)) {
return builtinRoleToNumber(role.inherits) + 1
}
}
return 0
@ -113,15 +129,14 @@ exports.roleToNumber = async id => {
/**
* Returns whichever builtin roleID is lower.
*/
exports.lowerBuiltinRoleID = (roleId1, roleId2) => {
export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string) {
if (!roleId1) {
return roleId2
}
if (!roleId2) {
return roleId1
}
return exports.builtinRoleToNumber(roleId1) >
exports.builtinRoleToNumber(roleId2)
return builtinRoleToNumber(roleId1) > builtinRoleToNumber(roleId2)
? roleId2
: roleId1
}
@ -132,11 +147,11 @@ exports.lowerBuiltinRoleID = (roleId1, roleId2) => {
* @param {string|null} roleId The level ID to lookup.
* @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property.
*/
exports.getRole = async roleId => {
export async function getRole(roleId?: string) {
if (!roleId) {
return null
}
let role = {}
let role: any = {}
// built in roles mostly come from the in-code implementation,
// but can be extended by a doc stored about them (e.g. permissions)
if (isBuiltin(roleId)) {
@ -146,10 +161,10 @@ exports.getRole = async roleId => {
}
try {
const db = getAppDB()
const dbRole = await db.get(exports.getDBRoleID(roleId))
const dbRole = await db.get(getDBRoleID(roleId))
role = Object.assign(role, dbRole)
// finalise the ID
role._id = exports.getExternalRoleID(role._id)
role._id = getExternalRoleID(role._id)
} catch (err) {
// only throw an error if there is no role at all
if (Object.keys(role).length === 0) {
@ -162,12 +177,12 @@ exports.getRole = async roleId => {
/**
* Simple function to get all the roles based on the top level user role ID.
*/
async function getAllUserRoles(userRoleId) {
async function getAllUserRoles(userRoleId?: string): Promise<RoleDoc[]> {
// admins have access to all roles
if (userRoleId === BUILTIN_IDS.ADMIN) {
return exports.getAllRoles()
return getAllRoles()
}
let currentRole = await exports.getRole(userRoleId)
let currentRole = await getRole(userRoleId)
let roles = currentRole ? [currentRole] : []
let roleIds = [userRoleId]
// get all the inherited roles
@ -177,7 +192,7 @@ async function getAllUserRoles(userRoleId) {
roleIds.indexOf(currentRole.inherits) === -1
) {
roleIds.push(currentRole.inherits)
currentRole = await exports.getRole(currentRole.inherits)
currentRole = await getRole(currentRole.inherits)
roles.push(currentRole)
}
return roles
@ -191,7 +206,10 @@ async function getAllUserRoles(userRoleId) {
* @returns {Promise<string[]|object[]>} returns an ordered array of the roles, with the first being their
* highest level of access and the last being the lowest level.
*/
exports.getUserRoleHierarchy = async (userRoleId, opts = { idOnly: true }) => {
export async function getUserRoleHierarchy(
userRoleId?: string,
opts = { idOnly: true }
) {
// special case, if they don't have a role then they are a public user
const roles = await getAllUserRoles(userRoleId)
return opts.idOnly ? roles.map(role => role._id) : roles
@ -200,9 +218,12 @@ exports.getUserRoleHierarchy = async (userRoleId, opts = { idOnly: true }) => {
// this function checks that the provided permissions are in an array format
// some templates/older apps will use a simple string instead of array for roles
// convert the string to an array using the theory that write is higher than read
exports.checkForRoleResourceArray = (rolePerms, resourceId) => {
export function checkForRoleResourceArray(
rolePerms: { [key: string]: string[] },
resourceId: string
) {
if (rolePerms && !Array.isArray(rolePerms[resourceId])) {
const permLevel = rolePerms[resourceId]
const permLevel = rolePerms[resourceId] as any
rolePerms[resourceId] = [permLevel]
if (permLevel === PermissionLevels.WRITE) {
rolePerms[resourceId].push(PermissionLevels.READ)
@ -215,7 +236,7 @@ exports.checkForRoleResourceArray = (rolePerms, resourceId) => {
* Given an app ID this will retrieve all of the roles that are currently within that app.
* @return {Promise<object[]>} An array of the role objects that were found.
*/
exports.getAllRoles = async appId => {
export async function getAllRoles(appId?: string) {
if (appId) {
return doWithDB(appId, internal)
} else {
@ -227,30 +248,30 @@ exports.getAllRoles = async appId => {
}
return internal(appDB)
}
async function internal(db) {
let roles = []
async function internal(db: any) {
let roles: RoleDoc[] = []
if (db) {
const body = await db.allDocs(
getRoleParams(null, {
include_docs: true,
})
)
roles = body.rows.map(row => row.doc)
roles = body.rows.map((row: any) => row.doc)
}
const builtinRoles = exports.getBuiltinRoles()
const builtinRoles = getBuiltinRoles()
// need to combine builtin with any DB record of them (for sake of permissions)
for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) {
const builtinRole = builtinRoles[builtinRoleId]
const dbBuiltin = roles.filter(
dbRole => exports.getExternalRoleID(dbRole._id) === builtinRoleId
dbRole => getExternalRoleID(dbRole._id) === builtinRoleId
)[0]
if (dbBuiltin == null) {
roles.push(builtinRole || builtinRoles.BASIC)
} else {
// remove role and all back after combining with the builtin
roles = roles.filter(role => role._id !== dbBuiltin._id)
dbBuiltin._id = exports.getExternalRoleID(dbBuiltin._id)
dbBuiltin._id = getExternalRoleID(dbBuiltin._id)
roles.push(Object.assign(builtinRole, dbBuiltin))
}
}
@ -260,7 +281,7 @@ exports.getAllRoles = async appId => {
continue
}
for (let resourceId of Object.keys(role.permissions)) {
role.permissions = exports.checkForRoleResourceArray(
role.permissions = checkForRoleResourceArray(
role.permissions,
resourceId
)
@ -277,11 +298,11 @@ exports.getAllRoles = async appId => {
* @param subResourceId The sub resource being requested
* @return {Promise<{permissions}|Object>} returns the permissions required to access.
*/
exports.getRequiredResourceRole = async (
permLevel,
{ resourceId, subResourceId }
) => {
const roles = await exports.getAllRoles()
export async function getRequiredResourceRole(
permLevel: string,
{ resourceId, subResourceId }: { resourceId?: string; subResourceId?: string }
) {
const roles = await getAllRoles()
let main = [],
sub = []
for (let role of roles) {
@ -289,8 +310,8 @@ exports.getRequiredResourceRole = async (
if (!role.permissions) {
continue
}
const mainRes = role.permissions[resourceId]
const subRes = role.permissions[subResourceId]
const mainRes = resourceId ? role.permissions[resourceId] : undefined
const subRes = subResourceId ? role.permissions[subResourceId] : undefined
if (mainRes && mainRes.indexOf(permLevel) !== -1) {
main.push(role._id)
} else if (subRes && subRes.indexOf(permLevel) !== -1) {
@ -301,12 +322,13 @@ exports.getRequiredResourceRole = async (
return main.concat(sub)
}
class AccessController {
export class AccessController {
userHierarchies: { [key: string]: string[] }
constructor() {
this.userHierarchies = {}
}
async hasAccess(tryingRoleId, userRoleId) {
async hasAccess(tryingRoleId?: string, userRoleId?: string) {
// special cases, the screen has no role, the roles are the same or the user
// is currently in the builder
if (
@ -318,16 +340,18 @@ class AccessController {
) {
return true
}
let roleIds = this.userHierarchies[userRoleId]
if (!roleIds) {
roleIds = await exports.getUserRoleHierarchy(userRoleId)
let roleIds = userRoleId ? this.userHierarchies[userRoleId] : null
if (!roleIds && userRoleId) {
roleIds = (await getUserRoleHierarchy(userRoleId, {
idOnly: true,
})) as string[]
this.userHierarchies[userRoleId] = roleIds
}
return roleIds.indexOf(tryingRoleId) !== -1
return roleIds?.indexOf(tryingRoleId) !== -1
}
async checkScreensAccess(screens, userRoleId) {
async checkScreensAccess(screens: Screen[], userRoleId: string) {
let accessibleScreens = []
// don't want to handle this with Promise.all as this would mean all custom roles would be
// retrieved at same time, it is likely a custom role will be re-used and therefore want
@ -341,8 +365,8 @@ class AccessController {
return accessibleScreens
}
async checkScreenAccess(screen, userRoleId) {
const roleId = screen && screen.routing ? screen.routing.roleId : null
async checkScreenAccess(screen: Screen, userRoleId: string) {
const roleId = screen && screen.routing ? screen.routing.roleId : undefined
if (await this.hasAccess(roleId, userRoleId)) {
return screen
}
@ -353,8 +377,8 @@ class AccessController {
/**
* Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions).
*/
exports.getDBRoleID = roleId => {
if (roleId.startsWith(DocumentType.ROLE)) {
export function getDBRoleID(roleId?: string) {
if (roleId?.startsWith(DocumentType.ROLE)) {
return roleId
}
return generateRoleID(roleId)
@ -363,15 +387,10 @@ exports.getDBRoleID = roleId => {
/**
* Remove the "role_" from builtin role IDs that have been written to the DB (for permissions).
*/
exports.getExternalRoleID = roleId => {
// for built in roles we want to remove the DB role ID element (role_)
if (roleId.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) {
export function getExternalRoleID(roleId?: string) {
// for built-in roles we want to remove the DB role ID element (role_)
if (roleId?.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) {
return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1]
}
return roleId
}
exports.AccessController = AccessController
exports.BUILTIN_ROLE_IDS = BUILTIN_IDS
exports.isBuiltin = isBuiltin
exports.Role = Role

View File

@ -1,5 +1,4 @@
require("../../tests/utilities/TestConfiguration")
const { structures } = require("../../tests/utilities")
const { structures } = require("../../tests")
const utils = require("../utils")
const events = require("../events")
const { doInTenant, DEFAULT_TENANT_ID }= require("../context")

View File

@ -0,0 +1,23 @@
import env from "../src/environment"
import { mocks } from "./utilities"
// mock all dates to 2020-01-01T00:00:00.000Z
// use tk.reset() to use real dates in individual tests
import tk from "timekeeper"
tk.freeze(mocks.date.MOCK_DATE)
env._set("SELF_HOSTED", "1")
env._set("NODE_ENV", "jest")
env._set("JWT_SECRET", "test-jwtsecret")
env._set("LOG_LEVEL", "silent")
env._set("MINIO_URL", "http://localhost")
env._set("MINIO_ACCESS_KEY", "test")
env._set("MINIO_SECRET_KEY", "test")
global.console.log = jest.fn() // console.log are ignored in tests
if (!process.env.CI) {
// set a longer timeout in dev for debugging
// 100 seconds
jest.setTimeout(100000)
}

View File

@ -1 +0,0 @@
require("./db")

View File

@ -1,6 +0,0 @@
const core = require("../../src/index")
const dbConfig = {
inMemory: true,
allDbs: true,
}
core.init({ db: dbConfig })

View File

@ -0,0 +1,9 @@
import * as db from "../../src/db"
const dbConfig = {
inMemory: true,
}
export const init = () => {
db.init(dbConfig)
}

View File

@ -1,2 +1,5 @@
export * as mocks from "./mocks"
export * as structures from "./structures"
import * as dbConfig from "./db"
dbConfig.init()

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1534,9 +1534,9 @@ lines-and-columns@^1.1.6:
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
loader-utils@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
version "1.4.1"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.1.tgz#278ad7006660bccc4d2c0c1578e17c5c78d5c0e0"
integrity sha512-1Qo97Y2oKaU+Ro2xnDMR26g1BwMT29jNbem1EvcujW2jqt+j5COXyscjM7bLQkM9HaxI7pkWeW7gnI072yMI9Q==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -1623,7 +1623,12 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimist@^1.2.0, minimist@^1.2.5:
minimist@^1.2.0:
version "1.2.7"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
minimist@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==

View File

@ -12,7 +12,7 @@ filterTests(['smoke', 'all'], () => {
cy.createTestTableWithData()
cy.wait(2000)
cy.contains("Automate").click()
cy.get(interact.ADD_BUTTON_SPECTRUM).click()
cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Add automation").click({ force: true })
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
cy.get("input").type("Add Row")
cy.contains("Row Created").click({ force: true })

View File

@ -2,7 +2,7 @@ import filterTests from "../support/filterTests"
const interact = require('../support/interact')
filterTests(["smoke", "all"], () => {
context("Create a Table", () => {
xcontext("Create a Table", () => {
before(() => {
cy.login()
cy.createTestApp()

View File

@ -440,7 +440,7 @@ Cypress.Commands.add("createTable", (tableName, initialTable) => {
// Creates an internal Budibase DB table
if (!initialTable) {
cy.navigateToDataSection()
cy.get(`[data-cy="new-table"]`, { timeout: 2000 }).click()
cy.get(`[data-cy="new-datasource"]`, { timeout: 2000 }).click()
}
cy.wait(2000)
cy.get(".item", { timeout: 2000 })
@ -781,7 +781,7 @@ Cypress.Commands.add("selectExternalDatasource", datasourceName => {
cy.navigateToDataSection()
// Open Datasource modal
cy.get(".nav").within(() => {
cy.get(".add-button").click()
cy.get("[data-cy='new-datasource']").click()
})
// Clicks specified datasource & continue
cy.get(".item-list", { timeout: 1000 }).contains(datasourceName).click()

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "2.1.19-alpha.0",
"version": "2.1.22-alpha.4",
"license": "GPL-3.0",
"private": true,
"scripts": {
@ -71,10 +71,10 @@
}
},
"dependencies": {
"@budibase/bbui": "2.1.19-alpha.0",
"@budibase/client": "2.1.19-alpha.0",
"@budibase/frontend-core": "2.1.19-alpha.0",
"@budibase/string-templates": "2.1.19-alpha.0",
"@budibase/bbui": "2.1.22-alpha.4",
"@budibase/client": "2.1.22-alpha.4",
"@budibase/frontend-core": "2.1.22-alpha.4",
"@budibase/string-templates": "2.1.22-alpha.4",
"@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1",
@ -117,9 +117,9 @@
"start-server-and-test": "^1.12.1",
"svelte": "^3.48.0",
"svelte-jester": "^1.3.2",
"ts-node": "^10.4.0",
"ts-node": "10.8.1",
"tsconfig-paths": "4.0.0",
"typescript": "^4.5.5",
"typescript": "4.7.3",
"vite": "^3.0.8"
},
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"

View File

@ -1,7 +1,7 @@
<script>
import AutomationList from "./AutomationList.svelte"
import CreateAutomationModal from "./CreateAutomationModal.svelte"
import { Icon, Modal, Tabs, Tab } from "@budibase/bbui"
import { Modal, Tabs, Tab, Button, Layout } from "@budibase/bbui"
export let modal
export let webhookModal
@ -10,23 +10,18 @@
<div class="nav">
<Tabs selected="Automations">
<Tab title="Automations">
<Layout paddingX="L" paddingY="L" gap="S">
<Button cta wide on:click={modal.show}>Add automation</Button>
</Layout>
<AutomationList />
<Modal bind:this={modal}>
<CreateAutomationModal {webhookModal} />
</Modal>
</Tab>
</Tabs>
<div class="add-button">
<Icon hoverable name="AddCircle" on:click={modal.show} />
</div>
</div>
<style>
.add-button {
position: absolute;
top: var(--spacing-l);
right: var(--spacing-xl);
}
.nav {
overflow-y: auto;
background: var(--background);

View File

@ -76,7 +76,7 @@
</Body>
<Input
bind:value={name}
on:change={() => (nameTouched = true)}
on:input={() => (nameTouched = true)}
bind:error={nameError}
label="Name"
/>
@ -124,11 +124,14 @@
padding: var(--spectrum-alias-item-padding-s);
background: var(--spectrum-alias-background-color-secondary);
transition: 0.3s all;
border: solid var(--spectrum-alias-border-color);
border-radius: 5px;
box-sizing: border-box;
border-width: 2px;
}
.item:hover {
background: var(--spectrum-alias-background-color-tertiary);
}
.selected {
background: var(--spectrum-alias-background-color-tertiary);
}

View File

@ -18,6 +18,7 @@
export let meta
export let value = defaultValue || (meta.type === "boolean" ? false : "")
export let readonly
export let error
const resolveTimeStamp = timestamp => {
if (!timestamp) {
@ -50,6 +51,7 @@
/>
{:else if type === "datetime"}
<DatePicker
{error}
{label}
timeOnly={isTimeStamp}
enableTime={!meta?.dateOnly}
@ -57,18 +59,23 @@
bind:value
/>
{:else if type === "attachment"}
<Dropzone {label} bind:value />
<Dropzone {label} {error} bind:value />
{:else if type === "boolean"}
<Toggle text={label} bind:value data-cy="{meta.name}-input" />
<Toggle text={label} {error} bind:value data-cy="{meta.name}-input" />
{:else if type === "array" && meta.constraints.inclusion.length !== 0}
<Multiselect bind:value {label} options={meta.constraints.inclusion} />
<Multiselect
bind:value
{error}
{label}
options={meta.constraints.inclusion}
/>
{:else if type === "link"}
<LinkedRowSelector bind:linkedRows={value} schema={meta} />
<LinkedRowSelector {error} bind:linkedRows={value} schema={meta} />
{:else if type === "longform"}
{#if meta.useRichText}
<RichTextField {label} height="150px" bind:value />
<RichTextField {error} {label} height="150px" bind:value />
{:else}
<TextArea {label} height="150px" bind:value />
<TextArea {error} {label} height="150px" bind:value />
{/if}
{:else if type === "json"}
<Label>{label}</Label>
@ -77,12 +84,14 @@
mode="json"
on:change={({ detail }) => (value = detail.value)}
value={stringVal}
{error}
/>
{:else}
<Input
{label}
data-cy="{meta.name}-input"
{type}
{error}
bind:value
disabled={readonly}
/>

View File

@ -19,7 +19,7 @@
$: text = `${item}${selectedRows?.length === 1 ? "" : "s"}`
</script>
<Button icon="Delete" size="s" primary quiet on:click={modal.show}>
<Button icon="Delete" size="s" warning quiet on:click={modal.show}>
Delete
{selectedRows.length}
{text}

View File

@ -5,7 +5,6 @@
import RowFieldControl from "../RowFieldControl.svelte"
import { API } from "api"
import { ModalContent } from "@budibase/bbui"
import ErrorsBox from "components/common/ErrorsBox.svelte"
import { FIELDS } from "constants/backend"
const FORMULA_TYPE = FIELDS.FORMULA.type
@ -32,13 +31,15 @@
if (error.handled) {
const response = error.json
if (response?.errors) {
errors = Object.entries(response.errors)
.map(([key, error]) => ({ dataPath: key, message: error }))
.flat()
} else if (error.status === 400 && response?.validationErrors) {
errors = Object.keys(response.validationErrors).map(field => ({
message: `${field} ${response.validationErrors[field][0]}`,
}))
errors = response.errors
} else if (response?.validationErrors) {
const mappedErrors = {}
for (let field in response.validationErrors) {
mappedErrors[
field
] = `${field} ${response.validationErrors[field][0]}`
}
errors = mappedErrors
}
} else {
notifications.error("Failed to save row")
@ -54,11 +55,10 @@
confirmText={creating ? "Create Row" : "Save Row"}
onConfirm={saveRow}
>
<ErrorsBox {errors} />
{#each tableSchema as [key, meta]}
{#if !meta.autocolumn && meta.type !== FORMULA_TYPE}
<div>
<RowFieldControl {meta} bind:value={row[key]} />
<RowFieldControl error={errors[key]} {meta} bind:value={row[key]} />
</div>
{/if}
{/each}

View File

@ -35,7 +35,6 @@
var(--spectrum-alias-item-padding-m);
background: var(--spectrum-alias-background-color-secondary);
transition: background 0.13s ease-out;
border: solid var(--spectrum-alias-border-color);
border-radius: 5px;
box-sizing: border-box;
border-width: 2px;

View File

@ -201,7 +201,6 @@
var(--spectrum-alias-item-padding-m);
background: var(--spectrum-alias-background-color-secondary);
transition: background 0.13s ease-out;
border: solid var(--spectrum-alias-border-color);
border-radius: 5px;
box-sizing: border-box;
border-width: 2px;

View File

@ -1,7 +1,6 @@
<script>
import { redirect, params } from "@roxi/routify"
import { Icon, Tabs, Tab } from "@budibase/bbui"
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
import { redirect } from "@roxi/routify"
import { Button, Tabs, Tab, Layout } from "@budibase/bbui"
import DatasourceNavigator from "components/backend/DatasourceNavigator/DatasourceNavigator.svelte"
import CreateDatasourceModal from "components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte"
@ -9,10 +8,6 @@
let modal
$: isExternal =
$params.selectedDatasource &&
$params.selectedDatasource !== BUDIBASE_INTERNAL_DB_ID
function selectFirstDatasource() {
$redirect("./table")
}
@ -23,18 +18,15 @@
<div class="nav">
<Tabs {selected} on:select={selectFirstDatasource}>
<Tab title="Sources">
<DatasourceNavigator />
<Layout paddingX="L" paddingY="L" gap="S">
<Button dataCy={`new-datasource`} cta wide on:click={modal.show}
>Add source</Button
>
</Layout>
<CreateDatasourceModal bind:modal />
<DatasourceNavigator />
</Tab>
</Tabs>
<div
class="add-button"
data-cy={`new-${isExternal ? "datasource" : "table"}`}
>
{#if modal}
<Icon hoverable name="AddCircle" on:click={modal.show} />
{/if}
</div>
</div>
<div class="content">
<slot />

View File

@ -7,7 +7,8 @@
"cardsblock",
"repeaterblock",
"formblock",
"chartblock"
"chartblock",
"rowexplorer"
]
},
{
@ -84,4 +85,4 @@
"donut"
]
}
]
]

View File

@ -923,17 +923,12 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==
"@cspotcode/source-map-support@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5"
integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
dependencies:
"@cspotcode/source-map-consumer" "0.8.0"
"@jridgewell/trace-mapping" "0.3.9"
"@cypress/request@^2.88.10":
version "2.88.10"
@ -1182,6 +1177,24 @@
"@types/yargs" "^16.0.0"
chalk "^4.0.0"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@ -5935,12 +5948,12 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
ts-node@^10.4.0:
version "10.4.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7"
integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==
ts-node@10.8.1:
version "10.8.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.1.tgz#ea2bd3459011b52699d7e88daa55a45a1af4f066"
integrity sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==
dependencies:
"@cspotcode/source-map-support" "0.7.0"
"@cspotcode/source-map-support" "^0.8.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
@ -5951,6 +5964,7 @@ ts-node@^10.4.0:
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
tsconfig-paths@4.0.0:
@ -6023,10 +6037,10 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
typescript@^4.5.5:
version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
typescript@4.7.3:
version "4.7.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
@ -6111,6 +6125,11 @@ uuid@^8.3.0, uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
v8-to-istanbul@^7.0.0:
version "7.1.2"
resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1"

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/cli",
"version": "2.1.19-alpha.0",
"version": "2.1.22-alpha.4",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js",
"bin": {
@ -26,9 +26,9 @@
"outputPath": "build"
},
"dependencies": {
"@budibase/backend-core": "2.1.19-alpha.0",
"@budibase/string-templates": "2.1.19-alpha.0",
"@budibase/types": "2.1.19-alpha.0",
"@budibase/backend-core": "2.1.22-alpha.4",
"@budibase/string-templates": "2.1.22-alpha.4",
"@budibase/types": "2.1.22-alpha.4",
"axios": "0.21.2",
"chalk": "4.1.0",
"cli-progress": "3.11.2",

View File

@ -5186,5 +5186,88 @@
"suffix": "repeater"
}
]
},
"rowexplorer": {
"block": true,
"name": "Row Explorer Block",
"icon": "PersonalizationField",
"size": {
"width": 600,
"height": 400
},
"settings": [
{
"type": "table",
"label": "Table",
"key": "dataSource",
"required": true
},
{
"type": "text",
"label": "Height",
"key": "height",
"defaultValue": "426px"
},
{
"section": true,
"name": "Cards",
"settings": [
{
"type": "field",
"label": "Search Field",
"key": "cardSearchField",
"nested": true
},
{
"type": "text",
"key": "cardTitle",
"label": "Title",
"nested": true,
"defaultValue": "Title"
},
{
"type": "text",
"key": "cardSubtitle",
"label": "Subtitle",
"nested": true,
"defaultValue": "Subtitle"
},
{
"type": "text",
"key": "cardDescription",
"label": "Description",
"nested": true,
"defaultValue": "Description"
},
{
"type": "text",
"key": "cardImageURL",
"label": "Image URL",
"nested": true
}
]
},
{
"section": true,
"name": "Details",
"settings": [
{
"type": "text",
"key": "detailTitle",
"label": "Title"
},
{
"type": "multifield",
"label": "Fields",
"key": "detailFields",
"nested": true
}
]
}
],
"context": {
"type": "schema",
"suffix": "repeater"
}
}
}
}

View File

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

View File

@ -0,0 +1,252 @@
<script>
import Block from "components/Block.svelte"
import BlockComponent from "components/BlockComponent.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
import { generate } from "shortid"
export let dataSource
export let height
export let cardTitle
export let cardSubtitle
export let cardDescription
export let cardImageURL
export let cardSearchField
export let detailFields
export let detailTitle
const stateKey = generate()
let listDataProviderId
let listRepeaterId
</script>
<Block>
<BlockComponent
type="container"
props={{
direction: "row",
gap: "M",
}}
styles={{
custom: `
height: ${height} !important;
`,
}}
>
<BlockComponent
type="dataprovider"
order={0}
bind:id={listDataProviderId}
props={{
dataSource,
paginate: true,
limit: 10,
filter: [
{
id: 0,
field: cardSearchField,
operator: "fuzzy",
type: "string",
value: `{{ ${safe("state")}.${safe(stateKey + "-search")} }}`,
valueType: "Binding",
noValue: false,
},
],
}}
styles={{
custom: `
flex: 3;
overflow: scroll;
{{#if (and ${safe("state")}.${safe(stateKey)} ${safe(
"device"
)}.${safe("mobile")}) }}
display: none;
{{/if}}
`,
}}
>
<BlockComponent
type="form"
order={0}
styles={{
normal: {
"margin-bottom": "12px",
},
}}
>
<BlockComponent
type="stringfield"
props={{
placeholder: "Search...",
field: `${stateKey}-search`,
onChange: [
{
parameters: {
key: `${stateKey}-search`,
type: "set",
persist: null,
value: `{{ ${safe("eventContext")}.${safe("value")} }}`,
},
"##eventHandlerType": "Update State",
id: 0,
},
],
}}
/>
</BlockComponent>
<BlockComponent
type="repeater"
order={1}
bind:id={listRepeaterId}
context="repeater"
props={{
dataProvider: `{{ literal ${safe(listDataProviderId)} }}`,
direction: "column",
gap: "S",
noRowsMessage: "No data",
}}
>
<BlockComponent
type="spectrumcard"
props={{
title: cardTitle,
subtitle: cardSubtitle,
description: cardDescription,
imageURL: cardImageURL,
horizontal: true,
buttonOnClick: [
{
parameters: {
key: stateKey,
type: "set",
persist: null,
value: `{{ ${safe(listRepeaterId)}.${safe("_id")} }}`,
},
"##eventHandlerType": "Update State",
id: 0,
},
],
}}
styles={{
normal: {
width: "auto",
},
}}
/>
</BlockComponent>
</BlockComponent>
<BlockComponent
type="container"
order={1}
props={{
hAlign: "center",
vAlign: "middle",
size: "grow",
direction: "column",
}}
styles={{
custom: `
padding: 20px;
background-color: var(--spectrum-global-color-gray-50));
border: 1px solid var(--spectrum-global-color-gray-300);
border-radius: 4px;
flex: 4;
{{#if (or ${safe("state")}.${safe(stateKey)} ${safe("device")}.${safe(
"mobile"
)}) }}
display: none;
{{/if}}
`,
}}
>
<BlockComponent
type="icon"
order={0}
props={{
icon: "ri-list-check-2",
size: "ri-2x",
color: "var(--spectrum-global-color-gray-700)",
}}
styles={{
normal: {
"margin-bottom": "12px",
},
}}
/>
<BlockComponent
type="text"
order={1}
props={{
text: "Select a row to view its fields",
color: "var(--spectrum-global-color-gray-700)",
}}
/>
</BlockComponent>
<BlockComponent
type="container"
order={2}
props={{
hAlign: "center",
vAlign: "top",
size: "grow",
direction: "column",
}}
styles={{
custom: `
background-color: var(--spectrum-global-color-gray-50));
border: 1px solid var(--spectrum-global-color-gray-300);
border-radius: 4px;
padding: 20px;
overflow-y: scroll;
flex: 4;
{{#if (isFalsey ${safe("state")}.${safe(stateKey)}) }}
display: none;
{{/if}}
`,
}}
>
<BlockComponent
type="button"
order={0}
props={{
text: "← Back",
onClick: [
{
parameters: {
key: stateKey,
type: "set",
persist: null,
value: "",
},
"##eventHandlerType": "Update State",
id: 0,
},
],
}}
styles={{
custom: `
align-self: flex-end;
margin-bottom: 16px;
{{#if (not ${safe("device")}.${safe("mobile")}) }}
display: none;
{{/if}}
`,
}}
/>
<BlockComponent
type="formblock"
order={1}
props={{
showSaveButton: true,
dataSource,
actionType: "Update",
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
fields: detailFields,
title: detailTitle,
}}
/>
</BlockComponent>
</BlockComponent>
</Block>

View File

@ -3,3 +3,4 @@ export { default as cardsblock } from "./CardsBlock.svelte"
export { default as repeaterblock } from "./RepeaterBlock.svelte"
export { default as formblock } from "./FormBlock.svelte"
export { default as chartblock } from "./ChartBlock.svelte"
export { default as rowexplorer } from "./RowExplorer.svelte"

View File

@ -671,15 +671,9 @@ has@^1.0.3:
function-bind "^1.1.1"
html5-qrcode@^2.2.1:
<<<<<<< HEAD
version "2.2.4"
resolved "https://registry.yarnpkg.com/html5-qrcode/-/html5-qrcode-2.2.4.tgz#99e4b36fbd8fbc4956036cf3f21ea3e98c3463d1"
integrity sha512-X8wVVsHpNb35tl7KcoCGAboc6Nep2VyT3CIMjFvrfWrHbHTC0yYTjE+DhO/VcswY2MfHy1uB7b1G9+L13gM6dQ==
=======
version "2.2.3"
resolved "https://registry.yarnpkg.com/html5-qrcode/-/html5-qrcode-2.2.3.tgz#5acb826860365e7c7ab91e1e14528ea16a502e8a"
integrity sha512-9CtEz5FVT56T76entiQxyrASzBWl8Rm30NHiQH8T163Eml5LS14BoZlYel9igxbikOt7O8KhvrT3awN1Y2HMqw==
>>>>>>> 50f0a0509d79dcee7a0f12608054279d65662b10
htmlparser2@^6.0.0:
version "6.1.0"
@ -811,9 +805,9 @@ lilconfig@^2.0.3:
integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==
loader-utils@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
version "1.4.1"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.1.tgz#278ad7006660bccc4d2c0c1578e17c5c78d5c0e0"
integrity sha512-1Qo97Y2oKaU+Ro2xnDMR26g1BwMT29jNbem1EvcujW2jqt+j5COXyscjM7bLQkM9HaxI7pkWeW7gnI072yMI9Q==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@ -859,9 +853,9 @@ minimatch@^3.0.2, minimatch@^3.0.4:
brace-expansion "^1.1.7"
minimist@^1.2.0:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
version "1.2.7"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
ms@2.1.2:
version "2.1.2"

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/sdk",
"version": "2.1.19-alpha.0",
"version": "2.1.22-alpha.4",
"description": "Budibase Public API SDK",
"author": "Budibase",
"license": "MPL-2.0",

View File

@ -0,0 +1,32 @@
import { Config } from "@jest/types"
import * as fs from "fs"
const config: Config.InitialOptions = {
preset: "ts-jest",
testEnvironment: "node",
setupFiles: ["./src/tests/jestSetup.ts"],
collectCoverageFrom: [
"src/**/*.{js,ts}",
// The use of coverage with couchdb view functions breaks tests
"!src/db/views/staticViews.*",
],
coverageReporters: ["lcov", "json", "clover"],
}
if (!process.env.CI) {
// use sources when not in CI
config.moduleNameMapper = {
"@budibase/backend-core/(.*)": "<rootDir>/../backend-core/$1",
"@budibase/backend-core": "<rootDir>/../backend-core/src",
"@budibase/types": "<rootDir>/../types/src",
}
// add pro sources if they exist
if (fs.existsSync("../../../budibase-pro")) {
config.moduleNameMapper["@budibase/pro"] =
"<rootDir>/../../../budibase-pro/packages/pro/src"
}
} else {
console.log("Running tests with compiled dependency sources")
}
export default config

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
"version": "2.1.19-alpha.0",
"version": "2.1.22-alpha.4",
"description": "Budibase Web Server",
"main": "src/index.ts",
"repository": {
@ -36,40 +36,6 @@
"env:account:enable": "node scripts/account.js enable",
"env:account:disable": "node scripts/account.js disable"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"moduleNameMapper": {
"@budibase/backend-core/(.*)": "<rootDir>/../backend-core/$1",
"@budibase/backend-core": "<rootDir>/../backend-core/src",
"@budibase/types": "<rootDir>/../types/src"
},
"setupFiles": [
"./scripts/jestSetup.js"
],
"collectCoverageFrom": [
"src/**/*.js",
"!**/node_modules/**",
"!src/db/views/*.js",
"!src/api/controllers/deploy/**/*.js",
"!src/*.js",
"!src/api/controllers/static/**/*",
"!src/db/dynamoClient.js",
"!src/utilities/usageQuota.js",
"!src/api/routes/tests/**/*",
"!src/db/tests/**/*",
"!src/tests/**/*",
"!src/automations/tests/**/*",
"!src/utilities/fileProcessor.js",
"!src/utilities/fileSystem/**/*",
"!src/utilities/redis.js"
],
"coverageReporters": [
"lcov",
"json",
"clover"
]
},
"keywords": [
"budibase"
],
@ -77,11 +43,11 @@
"license": "GPL-3.0",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "2.1.19-alpha.0",
"@budibase/client": "2.1.19-alpha.0",
"@budibase/pro": "2.1.19-alpha.0",
"@budibase/string-templates": "2.1.19-alpha.0",
"@budibase/types": "2.1.19-alpha.0",
"@budibase/backend-core": "2.1.22-alpha.4",
"@budibase/client": "2.1.22-alpha.4",
"@budibase/pro": "2.1.22-alpha.4",
"@budibase/string-templates": "2.1.22-alpha.4",
"@budibase/types": "2.1.22-alpha.4",
"@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0",
@ -179,7 +145,7 @@
"eslint": "6.8.0",
"ioredis-mock": "7.2.0",
"is-wsl": "2.2.0",
"jest": "27.5.1",
"jest": "28.1.1",
"jest-openapi": "0.14.2",
"nodemon": "2.0.15",
"openapi-types": "9.3.1",
@ -190,10 +156,10 @@
"supertest": "4.0.2",
"swagger-jsdoc": "6.1.0",
"timekeeper": "2.2.0",
"ts-jest": "27.1.3",
"ts-node": "10.5.0",
"ts-jest": "28.0.4",
"ts-node": "10.8.1",
"tsconfig-paths": "4.0.0",
"typescript": "4.6.2",
"typescript": "4.7.3",
"update-dotenv": "1.1.1"
},
"optionalDependencies": {

View File

@ -5,6 +5,8 @@ import { isQsTrue } from "../../utilities"
export async function exportAppDump(ctx: any) {
let { appId, excludeRows } = ctx.query
// remove the 120 second limit for the request
ctx.req.setTimeout(0)
const appName = decodeURI(ctx.query.appname)
excludeRows = isQsTrue(excludeRows)
const backupIdentifier = `${appName}-export-${new Date().getTime()}.tar.gz`

View File

@ -1,5 +1,5 @@
const { tmpdir } = require("os")
const env = require("../src/environment")
import env from "../environment"
env._set("SELF_HOSTED", "1")
env._set("NODE_ENV", "jest")
@ -8,8 +8,11 @@ env._set("CLIENT_ID", "test-client-id")
env._set("BUDIBASE_DIR", tmpdir("budibase-unittests"))
env._set("LOG_LEVEL", "silent")
env._set("PORT", 0)
env._set("MINIO_URL", "http://localhost")
env._set("MINIO_ACCESS_KEY", "test")
env._set("MINIO_SECRET_KEY", "test")
const { mocks } = require("@budibase/backend-core/tests")
import { mocks } from "@budibase/backend-core/tests"
// mock all dates to 2020-01-01T00:00:00.000Z
// use tk.reset() to use real dates in individual tests
@ -17,3 +20,9 @@ const tk = require("timekeeper")
tk.freeze(mocks.date.MOCK_DATE)
global.console.log = jest.fn() // console.log are ignored in tests
if (!process.env.CI) {
// set a longer timeout in dev for debugging
// 100 seconds
jest.setTimeout(100000)
}

View File

@ -116,7 +116,9 @@ class TestConfiguration {
if (this.server) {
this.server.close()
}
cleanup(this.allApps.map(app => app.appId))
if (this.allApps) {
cleanup(this.allApps.map(app => app.appId))
}
}
// UTILS

View File

@ -8,7 +8,8 @@
"paths": {
"@budibase/types": ["../types/src"],
"@budibase/backend-core": ["../backend-core/src"],
"@budibase/backend-core/*": ["../backend-core/*"]
"@budibase/backend-core/*": ["../backend-core/*"],
"@budibase/pro": ["../../../budibase-pro/packages/pro/src"]
}
},
"ts-node": {
@ -17,6 +18,7 @@
"references": [
{ "path": "../types" },
{ "path": "../backend-core" },
{ "path": "../../../budibase-pro/packages/pro" }
],
"include": [
"src/**/*",

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/string-templates",
"version": "2.1.19-alpha.0",
"version": "2.1.22-alpha.4",
"description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs",
"module": "dist/bundle.mjs",
@ -44,7 +44,7 @@
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^7.0.2",
"typescript": "^4.5.5"
"typescript": "4.7.3"
},
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
}

View File

@ -4337,10 +4337,10 @@ typeof-article@^0.1.1:
dependencies:
kind-of "^3.1.0"
typescript@^4.5.5:
version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
typescript@4.7.3:
version "4.7.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
uglify-js@^3.1.4:
version "3.14.3"

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/types",
"version": "2.1.19-alpha.0",
"version": "2.1.22-alpha.4",
"description": "Budibase types",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@ -40,6 +40,7 @@ export interface AppBackupMetadata {
}
export interface AppBackup extends Document, AppBackupMetadata {
_id: string
filename?: string
}

View File

@ -3,4 +3,5 @@ import { Document } from "../document"
export interface Role extends Document {
permissionId: string
inherits: string
permissions: { [key: string]: string[] }
}

View File

@ -24,3 +24,9 @@ interface BulkDocResponse {
id: string
rev: string
}
export interface PutResponse {
ok: boolean
id: string
rev: string
}

View File

@ -1,10 +1,10 @@
import PouchDB from "pouchdb"
export type PouchOptions = {
inMemory: boolean
replication: boolean
onDisk: boolean
find: boolean
inMemory?: boolean
replication?: boolean
onDisk?: boolean
find?: boolean
}
export enum SortOption {

View File

@ -0,0 +1,15 @@
const mockS3 = {
headBucket: jest.fn().mockReturnThis(),
deleteObject: jest.fn().mockReturnThis(),
deleteObjects: jest.fn().mockReturnThis(),
createBucket: jest.fn().mockReturnThis(),
listObjects: jest.fn().mockReturnThis(),
promise: jest.fn().mockReturnThis(),
catch: jest.fn(),
}
const AWS = {
S3: jest.fn(() => mockS3),
}
export default AWS

View File

@ -0,0 +1 @@
jest.mock("node-fetch", () => jest.fn())

View File

@ -0,0 +1,28 @@
import { Config } from "@jest/types"
import * as fs from "fs"
const config: Config.InitialOptions = {
preset: "ts-jest",
testEnvironment: "node",
setupFiles: ["./src/tests/jestSetup.ts"],
collectCoverageFrom: ["src/**/*.{js,ts}"],
coverageReporters: ["lcov", "json", "clover"],
}
if (!process.env.CI) {
// use sources when not in CI
config.moduleNameMapper = {
"@budibase/backend-core/(.*)": "<rootDir>/../backend-core/$1",
"@budibase/backend-core": "<rootDir>/../backend-core/src",
"@budibase/types": "<rootDir>/../types/src",
}
// add pro sources if they exist
if (fs.existsSync("../../../budibase-pro")) {
config.moduleNameMapper["@budibase/pro"] =
"<rootDir>/../../../budibase-pro/packages/pro/src"
}
} else {
console.log("Running tests with compiled dependency sources")
}
export default config

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/worker",
"email": "hi@budibase.com",
"version": "2.1.19-alpha.0",
"version": "2.1.22-alpha.4",
"description": "Budibase background service",
"main": "src/index.ts",
"repository": {
@ -22,7 +22,7 @@
"build:docker": "docker build . -t worker-service --label version=$BUDIBASE_RELEASE_VERSION",
"dev:stack:init": "node ./scripts/dev/manage.js init",
"dev:builder": "npm run dev:stack:init && nodemon",
"test": "jest --runInBand",
"test": "jest --coverage --runInBand",
"test:watch": "jest --watch",
"env:multi:enable": "node scripts/multiTenancy.js enable",
"env:multi:disable": "node scripts/multiTenancy.js disable",
@ -36,10 +36,10 @@
"author": "Budibase",
"license": "GPL-3.0",
"dependencies": {
"@budibase/backend-core": "2.1.19-alpha.0",
"@budibase/pro": "2.1.19-alpha.0",
"@budibase/string-templates": "2.1.19-alpha.0",
"@budibase/types": "2.1.19-alpha.0",
"@budibase/backend-core": "2.1.22-alpha.4",
"@budibase/pro": "2.1.22-alpha.4",
"@budibase/string-templates": "2.1.22-alpha.4",
"@budibase/types": "2.1.22-alpha.4",
"@koa/router": "8.0.8",
"@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2",
@ -79,30 +79,18 @@
"@typescript-eslint/parser": "5.12.0",
"copyfiles": "2.4.1",
"eslint": "6.8.0",
"jest": "27.4.7",
"jest": "28.1.1",
"nodemon": "2.0.15",
"pouchdb-adapter-memory": "7.2.2",
"prettier": "2.3.1",
"rimraf": "3.0.2",
"supertest": "6.2.2",
"timekeeper": "2.2.0",
"ts-jest": "27.1.3",
"ts-node": "10.4.0",
"ts-jest": "28.0.4",
"ts-node": "10.8.1",
"tsconfig-paths": "4.0.0",
"typescript": "4.5.5",
"typescript": "4.7.3",
"update-dotenv": "1.1.1"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"moduleNameMapper": {
"@budibase/backend-core/(.*)": "<rootDir>/../backend-core/$1",
"@budibase/backend-core": "<rootDir>/../backend-core/src",
"@budibase/types": "<rootDir>/../types/src"
},
"setupFiles": [
"./scripts/jestSetup.js"
]
},
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
}

View File

@ -1,11 +0,0 @@
const core = require("@budibase/backend-core")
const env = require("../environment")
exports.init = () => {
const dbConfig = {}
if (env.isTest() && !env.COUCH_DB_URL) {
dbConfig.inMemory = true
dbConfig.allDbs = true
}
core.init({ db: dbConfig })
}

View File

@ -0,0 +1,10 @@
import core from "@budibase/backend-core"
import env from "../environment"
export const init = () => {
const dbConfig: any = {}
if (env.isTest() && !env.COUCH_DB_URL) {
dbConfig.inMemory = true
}
core.init({ db: dbConfig })
}

View File

@ -13,7 +13,7 @@ import { Scope } from "@sentry/node"
import { Event } from "@sentry/types/dist/event"
import Application from "koa"
import { bootstrap } from "global-agent"
import db from "./db"
import * as db from "./db"
db.init()
const Koa = require("koa")
const destroyable = require("server-destroy")

View File

@ -1,5 +1,5 @@
import "./mocks"
import dbConfig from "../db"
import * as dbConfig from "../db"
dbConfig.init()
import env from "../environment"
import controllers from "./controllers"

View File

@ -1,6 +1,6 @@
import mocks from "./mocks"
import TestConfiguration from "./TestConfiguration"
import structures from "./structures"
import mocks from "./mocks"
import API from "./api"
const pkg = {

View File

@ -1,12 +1,15 @@
const env = require("../src/environment")
import env from "../environment"
env._set("SELF_HOSTED", "1")
env._set("NODE_ENV", "jest")
env._set("JWT_SECRET", "test-jwtsecret")
env._set("LOG_LEVEL", "silent")
env._set("MULTI_TENANCY", true)
env._set("MINIO_URL", "http://localhost")
env._set("MINIO_ACCESS_KEY", "test")
env._set("MINIO_SECRET_KEY", "test")
const { mocks } = require("@budibase/backend-core/tests")
import { mocks } from "@budibase/backend-core/tests"
// mock all dates to 2020-01-01T00:00:00.000Z
// use tk.reset() to use real dates in individual tests

View File

@ -1,17 +1,17 @@
{
"compilerOptions": {
"target": "es6",
"skipLibCheck": true,
"module": "commonjs",
"lib": ["es2020"],
"allowJs": true,
"outDir": "dist",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"incremental": true,
"types": [ "node", "jest" ],
"outDir": "dist",
"skipLibCheck": true
"types": [ "node", "jest"],
},
"include": [
"src/**/*"
@ -20,7 +20,7 @@
"node_modules",
"dist",
"src/tests",
"**/*.spec.ts",
"**/*.spec.js"
"**/*.spec.js",
"**/*.spec.ts"
]
}

View File

@ -8,7 +8,8 @@
"paths": {
"@budibase/types": ["../types/src"],
"@budibase/backend-core": ["../backend-core/src"],
"@budibase/backend-core/*": ["../backend-core/*"]
"@budibase/backend-core/*": ["../backend-core/*"],
"@budibase/pro": ["../../../budibase-pro/packages/pro/src"]
}
},
"ts-node": {
@ -17,6 +18,7 @@
"references": [
{ "path": "../types" },
{ "path": "../backend-core" },
{ "path": "../../../budibase-pro/packages/pro" }
],
"include": [
"src/**/*",

File diff suppressed because it is too large Load Diff

View File

@ -41,13 +41,13 @@
"@types/node-fetch": "2.6.2",
"chance": "1.1.8",
"env-cmd": "^10.1.0",
"jest": "28.0.2",
"jest": "28.1.1",
"prettier": "2.7.1",
"start-server-and-test": "1.14.0",
"timekeeper": "2.2.0",
"ts-jest": "28.0.8",
"ts-node": "10.9.1",
"tsconfig-paths": "4.1.0",
"ts-node": "10.8.1",
"tsconfig-paths": "4.0.0",
"typescript": "4.7.3"
},
"dependencies": {

View File

@ -2,7 +2,6 @@ import { Application } from "@budibase/server/api/controllers/public/mapping/typ
import { App } from "@budibase/types"
import { Response } from "node-fetch"
import InternalAPIClient from "./InternalAPIClient"
import FormData from "form-data"
import { RouteConfig } from "../fixtures/types/routing"
import { AppPackageResponse } from "../fixtures/types/appPackage"
import { DeployConfig } from "../fixtures/types/deploy"
@ -11,32 +10,52 @@ import { UnpublishAppResponse } from "../fixtures/types/unpublishAppResponse"
export default class AppApi {
api: InternalAPIClient
constructor(apiClient: InternalAPIClient) {
this.api = apiClient
}
async fetch(): Promise<[Response, Application[]]> {
async fetchEmptyAppList(): Promise<[Response, Application[]]> {
const response = await this.api.get(`/applications?status=all`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.length).toEqual(0)
return [response, json]
}
async fetchAllApplications(): Promise<[Response, Application[]]> {
const response = await this.api.get(`/applications?status=all`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.length).toBeGreaterThanOrEqual(1)
return [response, json]
}
async canRender(): Promise<[Response, boolean]> {
const response = await this.api.get("/routing/client")
const json = await response.json()
return [response, Object.keys(json.routes).length > 0]
const publishedAppRenders = Object.keys(json.routes).length > 0
expect(response).toHaveStatusCode(200)
expect(publishedAppRenders).toBe(true)
return [response, publishedAppRenders]
}
async getAppPackage(appId: string): Promise<[Response, AppPackageResponse]> {
const response = await this.api.get(`/applications/${appId}/appPackage`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.application.appId).toEqual(appId)
return [response, json]
}
async publish(): Promise<[Response, DeployConfig]> {
async publish(appUrl: string): Promise<[Response, DeployConfig]> {
const response = await this.api.post("/deploy")
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json).toEqual({
_id: expect.any(String),
appUrl: appUrl,
status: "SUCCESS",
})
return [response, json]
}
@ -57,9 +76,11 @@ export default class AppApi {
async sync(appId: string): Promise<[Response, responseMessage]> {
const response = await this.api.post(`/applications/${appId}/sync`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
return [response, json]
}
// TODO
async updateClient(
appId: string,
body: any
@ -72,21 +93,41 @@ export default class AppApi {
return [response, json]
}
async revert(appId: string): Promise<[Response, responseMessage]> {
async revertPublished(appId: string): Promise<[Response, responseMessage]> {
const response = await this.api.post(`/dev/${appId}/revert`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json).toEqual({
message: "Reverted changes successfully.",
})
return [response, json]
}
async revertUnpublished(appId: string): Promise<[Response, responseMessage]> {
const response = await this.api.post(`/dev/${appId}/revert`)
const json = await response.json()
expect(response).toHaveStatusCode(400)
expect(json).toEqual({
message: "App has not yet been deployed",
status: 400,
})
return [response, json]
}
async delete(appId: string): Promise<[Response, any]> {
const response = await this.api.del(`/applications/${appId}`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
return [response, json]
}
async update(appId: string, body: any): Promise<[Response, Application]> {
async update(appId: string, oldName: string, body: any): Promise<[Response, Application]> {
const response = await this.api.put(`/applications/${appId}`, { body })
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.name).not.toEqual(oldName)
return [response, json]
}
@ -96,9 +137,17 @@ export default class AppApi {
return [response, json]
}
async getRoutes(): Promise<[Response, RouteConfig]> {
async getRoutes(screenExists?: boolean): Promise<[Response, RouteConfig]> {
const response = await this.api.get(`/routing`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
if (screenExists) {
expect(json.routes["/test"]).toBeTruthy()
} else {
expect(json.routes["/test"]).toBeUndefined()
}
return [response, json]
}

View File

@ -4,25 +4,36 @@ import InternalAPIClient from "./InternalAPIClient"
export default class RowsApi {
api: InternalAPIClient
rowAdded: boolean
constructor(apiClient: InternalAPIClient) {
this.api = apiClient
this.rowAdded = false
}
async getAll(tableId: string): Promise<[Response, Row[]]> {
const response = await this.api.get(`/${tableId}/rows`)
const json = await response.json()
if (this.rowAdded) {
expect(response).toHaveStatusCode(200)
expect(json.length).toEqual(1)
}
return [response, json]
}
async add(tableId: string, body: any): Promise<[Response, Row]> {
const response = await this.api.post(`/${tableId}/rows`, { body })
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json._id).toBeDefined()
expect(json._rev).toBeDefined()
expect(json.tableId).toEqual(tableId)
this.rowAdded = true
return [response, json]
}
async delete(tableId: string, body: any): Promise<[Response, Row[]]> {
const response = await this.api.del(`/${tableId}/rows/`, { body })
const json = await response.json()
expect(response).toHaveStatusCode(200)
return [response, json]
}
}

View File

@ -12,12 +12,16 @@ export default class ScreenApi {
async create(body: any): Promise<[Response, Screen]> {
const response = await this.api.post(`/screens`, { body })
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json._id).toBeDefined()
expect(json.routing.roleId).toBe(body.routing.roleId)
return [response, json]
}
async delete(screenId: string, rev: string): Promise<[Response, Screen]> {
const response = await this.api.del(`/screens/${screenId}/${rev}`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
return [response, json]
}
}

View File

@ -21,12 +21,21 @@ export default class TablesApi {
async getTableById(id: string): Promise<[Response, Table]> {
const response = await this.api.get(`/tables/${id}`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json._id).toEqual(id)
return [response, json]
}
async save(body: any): Promise<[Response, Table]> {
async save(body: any, columnAdded?: boolean): Promise<[Response, Table]> {
const response = await this.api.post(`/tables`, { body })
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json._id).toBeDefined()
expect(json._rev).toBeDefined()
if (columnAdded) {
expect(json.schema.TestColumn).toBeDefined()
}
return [response, json]
}
@ -36,6 +45,10 @@ export default class TablesApi {
): Promise<[Response, responseMessage]> {
const response = await this.api.del(`/tables/${id}/${revId}`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.message).toEqual(
`Table ${id} deleted.`
)
return [response, json]
}
}

View File

@ -1,11 +1,12 @@
import generator from "../../generator"
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
const generate = (
overrides: Partial<Application> = {}
): Partial<Application> => ({
name: generator.word(),
url: `/${generator.word()}`,
name: generator.word() + generator.hash(),
url: `/${generator.word() + generator.hash()}`,
...overrides,
})

View File

@ -0,0 +1,159 @@
import TestConfiguration from "../../../config/internal-api/TestConfiguration"
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
import { db } from "@budibase/backend-core"
import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient"
import generateApp from "../../../config/internal-api/fixtures/applications"
import generator from "../../../config/generator"
import generateScreen from "../../../config/internal-api/fixtures/screens"
describe("Internal API - Application creation, update, publish and delete", () => {
const api = new InternalAPIClient()
const config = new TestConfiguration<Application>(api)
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
async function createAppFromTemplate() {
return config.applications.create({
name: generator.word(),
url: `/${generator.word()}`,
useTemplate: "true",
templateName: "Near Miss Register",
templateKey: "app/near-miss-register",
templateFile: undefined,
})
}
it("Get applications without applications", async () => {
await config.applications.fetchEmptyAppList()
})
it("Get all Applications after creating an application", async () => {
await config.applications.create({
...generateApp(),
useTemplate: false,
})
await config.applications.fetchAllApplications()
})
it("Get application details", async () => {
const app = await config.applications.create({
...generateApp(),
useTemplate: false,
})
config.applications.api.appId = app.appId
const [appPackageResponse, appPackageJson] = await config.applications.getAppPackage(<string>app.appId)
expect(appPackageJson.application.name).toEqual(app.name)
expect(appPackageJson.application.version).toEqual(app.version)
expect(appPackageJson.application.url).toEqual(app.url)
expect(appPackageJson.application.tenantId).toEqual(app.tenantId)
expect(appPackageJson.application.status).toEqual(app.status)
})
it("Publish app", async () => {
// create the app
const appName = generator.word()
const app = await createAppFromTemplate()
config.applications.api.appId = app.appId
// check preview renders
await config.applications.canRender()
// publish app
await config.applications.publish(<string>app.url)
// check published app renders
config.applications.api.appId = db.getProdAppID(app.appId)
await config.applications.canRender()
// unpublish app
await config.applications.unpublish(<string>app.appId)
})
it("POST - Sync application before deployment", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
const [syncResponse, sync] = await config.applications.sync(
<string>app.appId
)
expect(sync).toEqual({
message: "App sync not required, app not deployed.",
})
})
it("POST - Sync application after deployment", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
// publish app
await config.applications.publish(<string>app.url)
const [syncResponse, sync] = await config.applications.sync(
<string>app.appId
)
expect(sync).toEqual({
message: "App sync completed successfully.",
})
})
it("PUT - Update an application", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
await config.applications.update(
<string>app.appId,
<string>app.name,
{
name: generator.word(),
}
)
})
it("POST - Revert Changes without changes", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
await config.applications.revertUnpublished(
<string>app.appId
)
})
it("POST - Revert Changes", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
// publish app
await config.applications.publish(<string>app.url)
// Change/add component to the app
await config.screen.create(
generateScreen("BASIC")
)
// // Revert the app to published state
await config.applications.revertPublished(
<string>app.appId
)
// Check screen is removed
await config.applications.getRoutes()
})
it("DELETE - Delete an application", async () => {
const app = await config.applications.create(generateApp())
await config.applications.delete(<string>app.appId)
})
})

View File

@ -1,266 +0,0 @@
import TestConfiguration from "../../../config/internal-api/TestConfiguration"
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
import { db } from "@budibase/backend-core"
import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient"
import generateApp from "../../../config/internal-api/fixtures/applications"
import generator from "../../../config/generator"
import generateScreen from "../../../config/internal-api/fixtures/screens"
import {
generateTable,
generateNewColumnForTable,
} from "../../../config/internal-api/fixtures/table"
import { generateNewRowForTable } from "../../../config/internal-api/fixtures/rows"
describe("Internal API - /applications endpoints", () => {
const api = new InternalAPIClient()
const config = new TestConfiguration<Application>(api)
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
async function createAppFromTemplate() {
return config.applications.create({
name: generator.word(),
url: `/${generator.word()}`,
useTemplate: "true",
templateName: "Near Miss Register",
templateKey: "app/near-miss-register",
templateFile: undefined,
})
}
it("GET - fetch applications", async () => {
await config.applications.create({
...generateApp(),
useTemplate: false,
})
const [response, apps] = await config.applications.fetch()
expect(response).toHaveStatusCode(200)
expect(apps.length).toBeGreaterThanOrEqual(1)
})
it("POST - Create an application", async () => {
config.applications.create(generateApp())
})
it("POST - Publish application", async () => {
// create app
const app = await config.applications.create(generateApp())
// publish app
config.applications.api.appId = app.appId
const [publishResponse, publish] = await config.applications.publish()
expect(publishResponse).toHaveStatusCode(200)
expect(publish).toEqual({
_id: expect.any(String),
appUrl: app.url,
status: "SUCCESS",
})
})
it("Publish app flow", async () => {
// create the app
const appName = generator.word()
const app = await createAppFromTemplate()
config.applications.api.appId = app.appId
// check preview renders
const [previewResponse, previewRenders] =
await config.applications.canRender()
expect(previewResponse).toHaveStatusCode(200)
expect(previewRenders).toBe(true)
// publish app
await config.applications.publish()
// check published app renders
config.applications.api.appId = db.getProdAppID(app.appId)
const [publishedAppResponse, publishedAppRenders] =
await config.applications.canRender()
expect(publishedAppRenders).toBe(true)
// unpublish app
await config.applications.unpublish(<string>app.appId)
})
it("POST - Sync application before deployment", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
const [syncResponse, sync] = await config.applications.sync(
<string>app.appId
)
expect(syncResponse).toHaveStatusCode(200)
expect(sync).toEqual({
message: "App sync not required, app not deployed.",
})
})
it("POST - Sync application after deployment", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
// publish app
await config.applications.publish()
const [syncResponse, sync] = await config.applications.sync(
<string>app.appId
)
expect(syncResponse).toHaveStatusCode(200)
expect(sync).toEqual({
message: "App sync completed successfully.",
})
})
it("PUT - Update an application", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
const [updateResponse, updatedApp] = await config.applications.update(
<string>app.appId,
{
name: generator.word(),
}
)
expect(updateResponse).toHaveStatusCode(200)
expect(updatedApp.name).not.toEqual(app.name)
})
it("POST - Revert Changes without changes", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
const [revertResponse, revert] = await config.applications.revert(
<string>app.appId
)
expect(revertResponse).toHaveStatusCode(400)
expect(revert).toEqual({
message: "App has not yet been deployed",
status: 400,
})
})
it("POST - Revert Changes", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
// publish app
const [publishResponse, publish] = await config.applications.publish()
expect(publishResponse).toHaveStatusCode(200)
expect(publish.status).toEqual("SUCCESS")
// Change/add component to the app
const [screenResponse, screen] = await config.applications.addScreentoApp(
generateScreen("BASIC")
)
expect(screenResponse).toHaveStatusCode(200)
expect(screen._id).toBeDefined()
// // Revert the app to published state
const [revertResponse, revert] = await config.applications.revert(
<string>app.appId
)
expect(revertResponse).toHaveStatusCode(200)
expect(revert).toEqual({
message: "Reverted changes successfully.",
})
// Check screen is removed
const [routesResponse, routes] = await config.applications.getRoutes()
expect(routesResponse).toHaveStatusCode(200)
expect(routes.routes["/test"]).toBeUndefined()
})
it("DELETE - Delete an application", async () => {
const app = await config.applications.create(generateApp())
const [deleteResponse] = await config.applications.delete(<string>app.appId)
expect(deleteResponse).toHaveStatusCode(200)
})
it("Operations on Tables", async () => {
// create the app
const appName = generator.word()
const app = await createAppFromTemplate()
config.applications.api.appId = app.appId
// Get current tables: expect 2 in this template
await config.tables.getAll(2)
// Add new table
const [createdTableResponse, createdTableData] = await config.tables.save(
generateTable()
)
expect(createdTableResponse).toHaveStatusCode(200)
expect(createdTableData._id).toBeDefined()
expect(createdTableData._rev).toBeDefined()
//Table was added
await config.tables.getAll(3)
//Get information about the table
const [tableInfoResponse, tableInfo] = await config.tables.getTableById(
<string>createdTableData._id
)
expect(tableInfoResponse).toHaveStatusCode(200)
expect(tableInfo._id).toEqual(createdTableData._id)
//Add Column to table
const newColumn = generateNewColumnForTable(createdTableData)
const [addColumnResponse, addColumnData] = await config.tables.save(
newColumn
)
expect(addColumnResponse).toHaveStatusCode(200)
expect(addColumnData._id).toEqual(createdTableData._id)
expect(addColumnData.schema.TestColumn).toBeDefined()
//Add Row to table
const newRow = generateNewRowForTable(<string>addColumnData._id)
const [addRowResponse, addRowData] = await config.rows.add(
<string>addColumnData._id,
newRow
)
console.log(addRowData)
expect(addRowResponse).toHaveStatusCode(200)
expect(addRowData._id).toBeDefined()
expect(addRowData._rev).toBeDefined()
expect(addRowData.tableId).toEqual(addColumnData._id)
//Get Row from table
const [getRowResponse, getRowData] = await config.rows.getAll(
<string>addColumnData._id
)
expect(getRowResponse).toHaveStatusCode(200)
expect(getRowData.length).toEqual(1)
//Delete Row from table
const rowToDelete = {
rows: [getRowData[0]],
}
const [deleteRowResponse, deleteRowData] = await config.rows.delete(
<string>addColumnData._id,
rowToDelete
)
expect(deleteRowResponse).toHaveStatusCode(200)
expect(deleteRowData[0]._id).toEqual(getRowData[0]._id)
//Delete the table
const [deleteTableResponse, deleteTable] = await config.tables.delete(
<string>addColumnData._id,
<string>addColumnData._rev
)
expect(deleteTableResponse).toHaveStatusCode(200)
expect(deleteTable.message).toEqual(
`Table ${createdTableData._id} deleted.`
)
//Table was deleted
await config.tables.getAll(2)
})
})

View File

@ -29,8 +29,6 @@ describe("Internal API - /screens endpoints", () => {
const [response, screen] = await config.screen.create(
generateScreen(roleArray[role])
)
expect(response).toHaveStatusCode(200)
expect(screen.routing.roleId).toEqual(roleArray[role])
}
})
@ -40,14 +38,12 @@ describe("Internal API - /screens endpoints", () => {
// Create Screen
appConfig.applications.api.appId = app.appId
const [response, screen] = await config.screen.create(
await config.screen.create(
generateScreen("BASIC")
)
// Check screen exists
const [routesResponse, routes] = await appConfig.applications.getRoutes()
expect(routesResponse).toHaveStatusCode(200)
expect(routes.routes["/test"]).toBeTruthy()
await appConfig.applications.getRoutes(true)
})
it("DELETE - Delete a screen", async () => {
@ -61,7 +57,7 @@ describe("Internal API - /screens endpoints", () => {
)
// Delete Screen
const [response] = await config.screen.delete(screen._id!, screen._rev!)
expect(response).toHaveStatusCode(200)
await config.screen.delete(screen._id!, screen._rev!)
})
})

View File

@ -0,0 +1,95 @@
import TestConfiguration from "../../../config/internal-api/TestConfiguration"
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient"
import generator from "../../../config/generator"
import {
generateTable,
generateNewColumnForTable,
} from "../../../config/internal-api/fixtures/table"
import { generateNewRowForTable } from "../../../config/internal-api/fixtures/rows"
describe("Internal API - Application creation, update, publish and delete", () => {
const api = new InternalAPIClient()
const config = new TestConfiguration<Application>(api)
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
async function createAppFromTemplate() {
return config.applications.create({
name: generator.word(),
url: `/${generator.word()}`,
useTemplate: "true",
templateName: "Near Miss Register",
templateKey: "app/near-miss-register",
templateFile: undefined,
})
}
it("Operations on Tables", async () => {
// create the app
const appName = generator.word()
const app = await createAppFromTemplate()
config.applications.api.appId = app.appId
// Get current tables: expect 2 in this template
await config.tables.getAll(2)
// Add new table
const [createdTableResponse, createdTableData] = await config.tables.save(
generateTable()
)
//Table was added
await config.tables.getAll(3)
//Get information about the table
await config.tables.getTableById(
<string>createdTableData._id
)
//Add Column to table
const newColumn = generateNewColumnForTable(createdTableData)
const [addColumnResponse, addColumnData] = await config.tables.save(
newColumn,
true
)
//Add Row to table
const newRow = generateNewRowForTable(<string>addColumnData._id)
await config.rows.add(
<string>addColumnData._id,
newRow
)
//Get Row from table
const [getRowResponse, getRowData] = await config.rows.getAll(
<string>addColumnData._id
)
//Delete Row from table
const rowToDelete = {
rows: [getRowData[0]],
}
const [deleteRowResponse, deleteRowData] = await config.rows.delete(
<string>addColumnData._id,
rowToDelete
)
expect(deleteRowData[0]._id).toEqual(getRowData[0]._id)
//Delete the table
const [deleteTableResponse, deleteTable] = await config.tables.delete(
<string>addColumnData._id,
<string>addColumnData._rev
)
//Table was deleted
await config.tables.getAll(2)
})
})

View File

@ -1,7 +1,7 @@
#!/bin/bash
#!/bin/bash
if [[ -z "${CI}" ]]; then
echo 'Cannot run insall.sh unless in CI'
echo 'Cannot run install.sh unless in CI'
exit 0
fi

View File

@ -1,5 +1,8 @@
#!/bin/bash
# Fail when any command fails
set -e
if [[ -z "${CI}" ]]; then
echo 'Cannot run release.sh unless in CI'
exit 0
@ -59,22 +62,6 @@ git push
lerna publish $VERSION --yes --force-publish --dist-tag $TAG
#############################################
# POST-PUBLISH - PRO #
#############################################
# Revert build changes on packages/pro/package.json
cd packages/pro
jq '.main = "src/index.ts" | .types = "src/index.ts"' package.json > package.json.tmp && mv package.json.tmp package.json
# Go back to pro repo root
cd -
# Commit and push changes
git add packages/pro/package.json
git commit -m "Prep next development iteration"
git push
#############################################
# POST-PUBLISH - BUDIBASE #
#############################################

View File

@ -1,4 +1,8 @@
#!/bin/bash
# Fail when any command fails
set -e
cd ../
if [[ -d "budibase-pro" ]]; then
cd budibase-pro