Merge branch 'chore/npmless-builds' into chore/pipeline_npm_version_updates
# Conflicts: # packages/backend-core/package.json # packages/bbui/package.json # packages/builder/package.json # packages/cli/package.json # packages/client/package.json # packages/frontend-core/package.json # packages/sdk/package.json # packages/server/package.json # packages/shared-core/package.json # packages/string-templates/package.json # packages/types/package.json # packages/worker/package.json # yarn.lock
This commit is contained in:
commit
ce0d527d75
|
@ -222,9 +222,9 @@ http {
|
||||||
rewrite ^/files/signed/(.*)$ /$1 break;
|
rewrite ^/files/signed/(.*)$ /$1 break;
|
||||||
}
|
}
|
||||||
|
|
||||||
client_header_timeout 60;
|
client_header_timeout 120;
|
||||||
client_body_timeout 60;
|
client_body_timeout 120;
|
||||||
keepalive_timeout 60;
|
keepalive_timeout 120;
|
||||||
|
|
||||||
# gzip
|
# gzip
|
||||||
gzip on;
|
gzip on;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.5.6-alpha.28",
|
"version": "2.5.10-alpha.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/backend-core",
|
"packages/backend-core",
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
"passport-jwt": "4.0.0",
|
"passport-jwt": "4.0.0",
|
||||||
"passport-local": "1.0.0",
|
"passport-local": "1.0.0",
|
||||||
"passport-oauth2-refresh": "^2.1.0",
|
"passport-oauth2-refresh": "^2.1.0",
|
||||||
|
"pino": "8.11.0",
|
||||||
|
"pino-http": "8.3.3",
|
||||||
"posthog-node": "1.3.0",
|
"posthog-node": "1.3.0",
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "7.3.0",
|
||||||
"pouchdb-find": "7.2.2",
|
"pouchdb-find": "7.2.2",
|
||||||
|
@ -78,7 +80,6 @@
|
||||||
"jest-serial-runner": "^1.2.1",
|
"jest-serial-runner": "^1.2.1",
|
||||||
"koa": "2.13.4",
|
"koa": "2.13.4",
|
||||||
"nodemon": "2.0.16",
|
"nodemon": "2.0.16",
|
||||||
"pino": "7.11.0",
|
|
||||||
"pino-pretty": "10.0.0",
|
"pino-pretty": "10.0.0",
|
||||||
"pouchdb-adapter-memory": "7.2.2",
|
"pouchdb-adapter-memory": "7.2.2",
|
||||||
"timekeeper": "2.2.0",
|
"timekeeper": "2.2.0",
|
||||||
|
|
|
@ -47,7 +47,7 @@ async function put(
|
||||||
type: LockType.TRY_ONCE,
|
type: LockType.TRY_ONCE,
|
||||||
name: LockName.PERSIST_WRITETHROUGH,
|
name: LockName.PERSIST_WRITETHROUGH,
|
||||||
resource: key,
|
resource: key,
|
||||||
ttl: 1000,
|
ttl: 15000,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const writeDb = async (toWrite: any) => {
|
const writeDb = async (toWrite: any) => {
|
||||||
|
@ -71,6 +71,7 @@ async function put(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!lockResponse.executed) {
|
if (!lockResponse.executed) {
|
||||||
logWarn(`Ignoring redlock conflict in write-through cache`)
|
logWarn(`Ignoring redlock conflict in write-through cache`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -434,7 +434,7 @@ export class QueryBuilder<T> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.#query.empty) {
|
if (this.#query.empty) {
|
||||||
build(this.#query.empty, (key: string) => `!${key}:["" TO *]`)
|
build(this.#query.empty, (key: string) => `(*:* -${key}:["" TO *])`)
|
||||||
}
|
}
|
||||||
if (this.#query.notEmpty) {
|
if (this.#query.notEmpty) {
|
||||||
build(this.#query.notEmpty, (key: string) => `${key}:["" TO *]`)
|
build(this.#query.notEmpty, (key: string) => `${key}:["" TO *]`)
|
||||||
|
|
|
@ -154,6 +154,7 @@ const environment = {
|
||||||
? process.env.ENABLE_SSO_MAINTENANCE_MODE
|
? process.env.ENABLE_SSO_MAINTENANCE_MODE
|
||||||
: false,
|
: false,
|
||||||
VERSION: findVersion(),
|
VERSION: findVersion(),
|
||||||
|
DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER,
|
||||||
_set(key: any, value: any) {
|
_set(key: any, value: any) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {
|
||||||
Event,
|
Event,
|
||||||
LicenseActivatedEvent,
|
LicenseActivatedEvent,
|
||||||
LicensePlanChangedEvent,
|
LicensePlanChangedEvent,
|
||||||
LicenseTierChangedEvent,
|
|
||||||
PlanType,
|
PlanType,
|
||||||
Account,
|
Account,
|
||||||
LicensePortalOpenedEvent,
|
LicensePortalOpenedEvent,
|
||||||
|
@ -11,22 +10,23 @@ import {
|
||||||
LicenseCheckoutOpenedEvent,
|
LicenseCheckoutOpenedEvent,
|
||||||
LicensePaymentFailedEvent,
|
LicensePaymentFailedEvent,
|
||||||
LicensePaymentRecoveredEvent,
|
LicensePaymentRecoveredEvent,
|
||||||
|
PriceDuration,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
async function tierChanged(account: Account, from: number, to: number) {
|
async function planChanged(
|
||||||
const properties: LicenseTierChangedEvent = {
|
account: Account,
|
||||||
accountId: account.accountId,
|
opts: {
|
||||||
to,
|
from: PlanType
|
||||||
from,
|
to: PlanType
|
||||||
|
fromQuantity: number | undefined
|
||||||
|
toQuantity: number | undefined
|
||||||
|
fromDuration: PriceDuration | undefined
|
||||||
|
toDuration: PriceDuration | undefined
|
||||||
}
|
}
|
||||||
await publishEvent(Event.LICENSE_TIER_CHANGED, properties)
|
) {
|
||||||
}
|
|
||||||
|
|
||||||
async function planChanged(account: Account, from: PlanType, to: PlanType) {
|
|
||||||
const properties: LicensePlanChangedEvent = {
|
const properties: LicensePlanChangedEvent = {
|
||||||
accountId: account.accountId,
|
accountId: account.accountId,
|
||||||
to,
|
...opts,
|
||||||
from,
|
|
||||||
}
|
}
|
||||||
await publishEvent(Event.LICENSE_PLAN_CHANGED, properties)
|
await publishEvent(Event.LICENSE_PLAN_CHANGED, properties)
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,6 @@ async function paymentRecovered(account: Account) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
tierChanged,
|
|
||||||
planChanged,
|
planChanged,
|
||||||
activated,
|
activated,
|
||||||
checkoutOpened,
|
checkoutOpened,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export * as correlation from "./correlation/correlation"
|
export * as correlation from "./correlation/correlation"
|
||||||
export { logger, disableLogger } from "./pino/logger"
|
export { logger } from "./pino/logger"
|
||||||
export * from "./alerts"
|
export * from "./alerts"
|
||||||
|
|
||||||
// turn off or on context logging i.e. tenantId, appId etc
|
// turn off or on context logging i.e. tenantId, appId etc
|
||||||
|
|
|
@ -5,184 +5,169 @@ import * as correlation from "../correlation"
|
||||||
import { IdentityType } from "@budibase/types"
|
import { IdentityType } from "@budibase/types"
|
||||||
import { LOG_CONTEXT } from "../index"
|
import { LOG_CONTEXT } from "../index"
|
||||||
|
|
||||||
// CORE LOGGERS - for disabling
|
|
||||||
|
|
||||||
const BUILT_INS = {
|
|
||||||
log: console.log,
|
|
||||||
error: console.error,
|
|
||||||
info: console.info,
|
|
||||||
warn: console.warn,
|
|
||||||
trace: console.trace,
|
|
||||||
debug: console.debug,
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOGGER
|
// LOGGER
|
||||||
|
|
||||||
const pinoOptions: LoggerOptions = {
|
let pinoInstance: pino.Logger | undefined
|
||||||
level: env.LOG_LEVEL,
|
if (!env.DISABLE_PINO_LOGGER) {
|
||||||
formatters: {
|
const pinoOptions: LoggerOptions = {
|
||||||
level: label => {
|
level: env.LOG_LEVEL,
|
||||||
return { level: label.toUpperCase() }
|
formatters: {
|
||||||
},
|
level: label => {
|
||||||
bindings: () => {
|
return { level: label.toUpperCase() }
|
||||||
return {}
|
},
|
||||||
},
|
bindings: () => {
|
||||||
},
|
return {}
|
||||||
timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
|
},
|
||||||
}
|
|
||||||
|
|
||||||
if (env.isDev()) {
|
|
||||||
pinoOptions.transport = {
|
|
||||||
target: "pino-pretty",
|
|
||||||
options: {
|
|
||||||
singleLine: true,
|
|
||||||
},
|
},
|
||||||
|
timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export const logger = pino(pinoOptions)
|
if (env.isDev()) {
|
||||||
|
pinoOptions.transport = {
|
||||||
export function disableLogger() {
|
target: "pino-pretty",
|
||||||
console.log = BUILT_INS.log
|
options: {
|
||||||
console.error = BUILT_INS.error
|
singleLine: true,
|
||||||
console.info = BUILT_INS.info
|
},
|
||||||
console.warn = BUILT_INS.warn
|
|
||||||
console.trace = BUILT_INS.trace
|
|
||||||
console.debug = BUILT_INS.debug
|
|
||||||
}
|
|
||||||
|
|
||||||
// CONSOLE OVERRIDES
|
|
||||||
|
|
||||||
interface MergingObject {
|
|
||||||
objects?: any[]
|
|
||||||
tenantId?: string
|
|
||||||
appId?: string
|
|
||||||
identityId?: string
|
|
||||||
identityType?: IdentityType
|
|
||||||
correlationId?: string
|
|
||||||
err?: Error
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPlainObject(obj: any) {
|
|
||||||
return typeof obj === "object" && obj !== null && !(obj instanceof Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isError(obj: any) {
|
|
||||||
return obj instanceof Error
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMessage(obj: any) {
|
|
||||||
return typeof obj === "string"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backwards compatibility between console logging statements
|
|
||||||
* and pino logging requirements.
|
|
||||||
*/
|
|
||||||
function getLogParams(args: any[]): [MergingObject, string] {
|
|
||||||
let error = undefined
|
|
||||||
let objects: any[] = []
|
|
||||||
let message = ""
|
|
||||||
|
|
||||||
args.forEach(arg => {
|
|
||||||
if (isMessage(arg)) {
|
|
||||||
message = `${message} ${arg}`.trimStart()
|
|
||||||
}
|
|
||||||
if (isPlainObject(arg)) {
|
|
||||||
objects.push(arg)
|
|
||||||
}
|
|
||||||
if (isError(arg)) {
|
|
||||||
error = arg
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const identity = getIdentity()
|
|
||||||
|
|
||||||
let contextObject = {}
|
|
||||||
|
|
||||||
if (LOG_CONTEXT) {
|
|
||||||
contextObject = {
|
|
||||||
tenantId: getTenantId(),
|
|
||||||
appId: getAppId(),
|
|
||||||
identityId: identity?._id,
|
|
||||||
identityType: identity?.type,
|
|
||||||
correlationId: correlation.getId(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergingObject = {
|
pinoInstance = pino(pinoOptions)
|
||||||
objects: objects.length ? objects : undefined,
|
|
||||||
err: error,
|
// CONSOLE OVERRIDES
|
||||||
...contextObject,
|
|
||||||
|
interface MergingObject {
|
||||||
|
objects?: any[]
|
||||||
|
tenantId?: string
|
||||||
|
appId?: string
|
||||||
|
identityId?: string
|
||||||
|
identityType?: IdentityType
|
||||||
|
correlationId?: string
|
||||||
|
err?: Error
|
||||||
}
|
}
|
||||||
|
|
||||||
return [mergingObject, message]
|
function isPlainObject(obj: any) {
|
||||||
}
|
return typeof obj === "object" && obj !== null && !(obj instanceof Error)
|
||||||
|
|
||||||
console.log = (...arg: any[]) => {
|
|
||||||
const [obj, msg] = getLogParams(arg)
|
|
||||||
logger.info(obj, msg)
|
|
||||||
}
|
|
||||||
console.info = (...arg: any[]) => {
|
|
||||||
const [obj, msg] = getLogParams(arg)
|
|
||||||
logger.info(obj, msg)
|
|
||||||
}
|
|
||||||
console.warn = (...arg: any[]) => {
|
|
||||||
const [obj, msg] = getLogParams(arg)
|
|
||||||
logger.warn(obj, msg)
|
|
||||||
}
|
|
||||||
console.error = (...arg: any[]) => {
|
|
||||||
const [obj, msg] = getLogParams(arg)
|
|
||||||
logger.error(obj, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* custom trace impl - this resembles the node trace behaviour rather
|
|
||||||
* than traditional trace logging
|
|
||||||
* @param arg
|
|
||||||
*/
|
|
||||||
console.trace = (...arg: any[]) => {
|
|
||||||
const [obj, msg] = getLogParams(arg)
|
|
||||||
if (!obj.err) {
|
|
||||||
// to get stack trace
|
|
||||||
obj.err = new Error()
|
|
||||||
}
|
}
|
||||||
logger.trace(obj, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug = (...arg: any) => {
|
function isError(obj: any) {
|
||||||
const [obj, msg] = getLogParams(arg)
|
return obj instanceof Error
|
||||||
logger.debug(obj, msg)
|
}
|
||||||
}
|
|
||||||
|
function isMessage(obj: any) {
|
||||||
// CONTEXT
|
return typeof obj === "string"
|
||||||
|
}
|
||||||
const getTenantId = () => {
|
|
||||||
let tenantId
|
/**
|
||||||
try {
|
* Backwards compatibility between console logging statements
|
||||||
tenantId = context.getTenantId()
|
* and pino logging requirements.
|
||||||
} catch (e: any) {
|
*/
|
||||||
// do nothing
|
function getLogParams(args: any[]): [MergingObject, string] {
|
||||||
|
let error = undefined
|
||||||
|
let objects: any[] = []
|
||||||
|
let message = ""
|
||||||
|
|
||||||
|
args.forEach(arg => {
|
||||||
|
if (isMessage(arg)) {
|
||||||
|
message = `${message} ${arg}`.trimStart()
|
||||||
|
}
|
||||||
|
if (isPlainObject(arg)) {
|
||||||
|
objects.push(arg)
|
||||||
|
}
|
||||||
|
if (isError(arg)) {
|
||||||
|
error = arg
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const identity = getIdentity()
|
||||||
|
|
||||||
|
let contextObject = {}
|
||||||
|
|
||||||
|
if (LOG_CONTEXT) {
|
||||||
|
contextObject = {
|
||||||
|
tenantId: getTenantId(),
|
||||||
|
appId: getAppId(),
|
||||||
|
identityId: identity?._id,
|
||||||
|
identityType: identity?.type,
|
||||||
|
correlationId: correlation.getId(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergingObject = {
|
||||||
|
objects: objects.length ? objects : undefined,
|
||||||
|
err: error,
|
||||||
|
...contextObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
return [mergingObject, message]
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log = (...arg: any[]) => {
|
||||||
|
const [obj, msg] = getLogParams(arg)
|
||||||
|
pinoInstance?.info(obj, msg)
|
||||||
|
}
|
||||||
|
console.info = (...arg: any[]) => {
|
||||||
|
const [obj, msg] = getLogParams(arg)
|
||||||
|
pinoInstance?.info(obj, msg)
|
||||||
|
}
|
||||||
|
console.warn = (...arg: any[]) => {
|
||||||
|
const [obj, msg] = getLogParams(arg)
|
||||||
|
pinoInstance?.warn(obj, msg)
|
||||||
|
}
|
||||||
|
console.error = (...arg: any[]) => {
|
||||||
|
const [obj, msg] = getLogParams(arg)
|
||||||
|
pinoInstance?.error(obj, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* custom trace impl - this resembles the node trace behaviour rather
|
||||||
|
* than traditional trace logging
|
||||||
|
* @param arg
|
||||||
|
*/
|
||||||
|
console.trace = (...arg: any[]) => {
|
||||||
|
const [obj, msg] = getLogParams(arg)
|
||||||
|
if (!obj.err) {
|
||||||
|
// to get stack trace
|
||||||
|
obj.err = new Error()
|
||||||
|
}
|
||||||
|
pinoInstance?.trace(obj, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug = (...arg: any) => {
|
||||||
|
const [obj, msg] = getLogParams(arg)
|
||||||
|
pinoInstance?.debug(obj, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONTEXT
|
||||||
|
|
||||||
|
const getTenantId = () => {
|
||||||
|
let tenantId
|
||||||
|
try {
|
||||||
|
tenantId = context.getTenantId()
|
||||||
|
} catch (e: any) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
return tenantId
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAppId = () => {
|
||||||
|
let appId
|
||||||
|
try {
|
||||||
|
appId = context.getAppId()
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
return appId
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIdentity = () => {
|
||||||
|
let identity
|
||||||
|
try {
|
||||||
|
identity = context.getIdentity()
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
return identity
|
||||||
}
|
}
|
||||||
return tenantId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAppId = () => {
|
export const logger = pinoInstance
|
||||||
let appId
|
|
||||||
try {
|
|
||||||
appId = context.getAppId()
|
|
||||||
} catch (e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
return appId
|
|
||||||
}
|
|
||||||
|
|
||||||
const getIdentity = () => {
|
|
||||||
let identity
|
|
||||||
try {
|
|
||||||
identity = context.getIdentity()
|
|
||||||
} catch (e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
return identity
|
|
||||||
}
|
|
||||||
|
|
|
@ -123,7 +123,6 @@ beforeAll(async () => {
|
||||||
jest.spyOn(events.plugin, "imported")
|
jest.spyOn(events.plugin, "imported")
|
||||||
jest.spyOn(events.plugin, "deleted")
|
jest.spyOn(events.plugin, "deleted")
|
||||||
|
|
||||||
jest.spyOn(events.license, "tierChanged")
|
|
||||||
jest.spyOn(events.license, "planChanged")
|
jest.spyOn(events.license, "planChanged")
|
||||||
jest.spyOn(events.license, "activated")
|
jest.spyOn(events.license, "activated")
|
||||||
jest.spyOn(events.license, "checkoutOpened")
|
jest.spyOn(events.license, "checkoutOpened")
|
||||||
|
|
|
@ -7,16 +7,29 @@ import {
|
||||||
PlanType,
|
PlanType,
|
||||||
PriceDuration,
|
PriceDuration,
|
||||||
PurchasedPlan,
|
PurchasedPlan,
|
||||||
|
PurchasedPrice,
|
||||||
Quotas,
|
Quotas,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
export function price(): PurchasedPrice {
|
||||||
|
return {
|
||||||
|
amount: 10000,
|
||||||
|
amountMonthly: 10000,
|
||||||
|
currency: "usd",
|
||||||
|
duration: PriceDuration.MONTHLY,
|
||||||
|
priceId: "price_123",
|
||||||
|
dayPasses: undefined,
|
||||||
|
isPerUser: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const plan = (type: PlanType = PlanType.FREE): PurchasedPlan => {
|
export const plan = (type: PlanType = PlanType.FREE): PurchasedPlan => {
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
usesInvoicing: false,
|
usesInvoicing: false,
|
||||||
minUsers: 1,
|
|
||||||
model: PlanModel.PER_USER,
|
model: PlanModel.PER_USER,
|
||||||
|
price: type !== PlanType.FREE ? price() : undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "0.0.1",
|
"@budibase/bbui": "0.0.1",
|
||||||
"@budibase/client": "0.0.1",
|
|
||||||
"@budibase/frontend-core": "0.0.1",
|
"@budibase/frontend-core": "0.0.1",
|
||||||
"@budibase/shared-core": "0.0.1",
|
"@budibase/shared-core": "0.0.1",
|
||||||
"@budibase/string-templates": "0.0.1",
|
"@budibase/string-templates": "0.0.1",
|
||||||
|
|
|
@ -134,6 +134,7 @@ export const getFrontendStore = () => {
|
||||||
previousTopNavPath: {},
|
previousTopNavPath: {},
|
||||||
version: application.version,
|
version: application.version,
|
||||||
revertableVersion: application.revertableVersion,
|
revertableVersion: application.revertableVersion,
|
||||||
|
upgradableVersion: application.upgradableVersion,
|
||||||
navigation: application.navigation || {},
|
navigation: application.navigation || {},
|
||||||
usedPlugins: application.usedPlugins || [],
|
usedPlugins: application.usedPlugins || [],
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -42,16 +42,7 @@ export const parseFile = e => {
|
||||||
|
|
||||||
reader.addEventListener("load", function (e) {
|
reader.addEventListener("load", function (e) {
|
||||||
const fileData = e.target.result
|
const fileData = e.target.result
|
||||||
|
if (file.type?.includes("json")) {
|
||||||
if (file.type === "text/csv") {
|
|
||||||
API.csvToJson(fileData)
|
|
||||||
.then(rows => {
|
|
||||||
resolveRows(rows)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
reject("can't convert csv to json")
|
|
||||||
})
|
|
||||||
} else if (file.type === "application/json") {
|
|
||||||
const parsedFileData = JSON.parse(fileData)
|
const parsedFileData = JSON.parse(fileData)
|
||||||
|
|
||||||
if (Array.isArray(parsedFileData)) {
|
if (Array.isArray(parsedFileData)) {
|
||||||
|
@ -62,7 +53,13 @@ export const parseFile = e => {
|
||||||
reject("invalid json format")
|
reject("invalid json format")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reject("invalid file type")
|
API.csvToJson(fileData)
|
||||||
|
.then(rows => {
|
||||||
|
resolveRows(rows)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
reject("cannot parse csv")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import clientPackage from "@budibase/client/package.json"
|
|
||||||
|
|
||||||
export function show() {
|
export function show() {
|
||||||
updateModal.show()
|
updateModal.show()
|
||||||
|
@ -25,9 +24,9 @@
|
||||||
|
|
||||||
$: appId = $store.appId
|
$: appId = $store.appId
|
||||||
$: updateAvailable =
|
$: updateAvailable =
|
||||||
clientPackage.version &&
|
$store.upgradableVersion &&
|
||||||
$store.version &&
|
$store.version &&
|
||||||
clientPackage.version !== $store.version
|
$store.upgradableVersion !== $store.version
|
||||||
$: revertAvailable = $store.revertableVersion != null
|
$: revertAvailable = $store.revertableVersion != null
|
||||||
|
|
||||||
const refreshAppPackage = async () => {
|
const refreshAppPackage = async () => {
|
||||||
|
@ -46,7 +45,7 @@
|
||||||
// Don't wait for the async refresh, since this causes modal flashing
|
// Don't wait for the async refresh, since this causes modal flashing
|
||||||
refreshAppPackage()
|
refreshAppPackage()
|
||||||
notifications.success(
|
notifications.success(
|
||||||
`App updated successfully to version ${clientPackage.version}`
|
`App updated successfully to version ${$store.upgradableVersion}`
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error updating app: ${err}`)
|
notifications.error(`Error updating app: ${err}`)
|
||||||
|
@ -91,7 +90,7 @@
|
||||||
{#if updateAvailable}
|
{#if updateAvailable}
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
This app is currently using version <b>{$store.version}</b>, but version
|
This app is currently using version <b>{$store.version}</b>, but version
|
||||||
<b>{clientPackage.version}</b> is available. Updates can contain new features,
|
<b>{$store.upgradableVersion}</b> is available. Updates can contain new features,
|
||||||
performance improvements and bug fixes.
|
performance improvements and bug fixes.
|
||||||
</Body>
|
</Body>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
unlimited = isUnlimited()
|
unlimited = isUnlimited()
|
||||||
percentage = getPercentage()
|
percentage = getPercentage()
|
||||||
if (warnWhenFull && percentage === 100) {
|
if (warnWhenFull && percentage >= 100) {
|
||||||
showWarning = true
|
showWarning = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel title={$selectedLayout?.name} icon="Experience" borderLeft>
|
<Panel title={$selectedLayout?.name} icon="Experience" borderLeft wide>
|
||||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
<Banner type="warning" showCloseButton={false}>
|
<Banner type="warning" showCloseButton={false}>
|
||||||
Custom layouts are being deprecated. They will be removed in a future
|
Custom layouts are being deprecated. They will be removed in a future
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel borderLeft title="Navigation" icon="InfoOutline">
|
<Panel borderLeft title="Navigation" icon="InfoOutline" wide>
|
||||||
<Layout paddingX="L" paddingY="XL" gap="S">
|
<Layout paddingX="L" paddingY="XL" gap="S">
|
||||||
{#if $selectedScreen.layoutId}
|
{#if $selectedScreen.layoutId}
|
||||||
<Banner
|
<Banner
|
||||||
|
|
|
@ -149,6 +149,7 @@
|
||||||
title={$selectedScreen.routing.route}
|
title={$selectedScreen.routing.route}
|
||||||
icon={$selectedScreen.routing.route === "/" ? "Home" : "WebPage"}
|
icon={$selectedScreen.routing.route === "/" ? "Home" : "WebPage"}
|
||||||
borderLeft
|
borderLeft
|
||||||
|
wide
|
||||||
>
|
>
|
||||||
<Layout gap="S" paddingX="L" paddingY="XL">
|
<Layout gap="S" paddingX="L" paddingY="XL">
|
||||||
{#if $selectedScreen.layoutId}
|
{#if $selectedScreen.layoutId}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { Body, Layout } from "@budibase/bbui"
|
import { Body, Layout } from "@budibase/bbui"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel borderLeft title="Theme" icon="InfoOutline">
|
<Panel borderLeft title="Theme" icon="InfoOutline" wide>
|
||||||
<Layout paddingX="L" paddingY="XL">
|
<Layout paddingX="L" paddingY="XL">
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
Your theme is set across all the screens within your app.
|
Your theme is set across all the screens within your app.
|
||||||
|
|
|
@ -43,12 +43,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: quotaUsage = $licensing.quotaUsage
|
$: quotaUsage = $licensing.quotaUsage
|
||||||
|
|
||||||
$: license = $auth.user?.license
|
$: license = $auth.user?.license
|
||||||
|
$: plan = license?.plan
|
||||||
|
$: usesInvoicing = plan?.usesInvoicing
|
||||||
|
|
||||||
$: accountPortalAccess = $auth?.user?.accountPortalAccess
|
$: accountPortalAccess = $auth?.user?.accountPortalAccess
|
||||||
$: quotaReset = quotaUsage?.quotaReset
|
$: quotaReset = quotaUsage?.quotaReset
|
||||||
$: canManagePlan =
|
$: canManagePlan =
|
||||||
($admin.cloud && accountPortalAccess) || (!$admin.cloud && $auth.isAdmin)
|
($admin.cloud && accountPortalAccess) || (!$admin.cloud && $auth.isAdmin)
|
||||||
|
|
||||||
|
$: showButton = !usesInvoicing && accountPortalAccess
|
||||||
|
|
||||||
const setMonthlyUsage = () => {
|
const setMonthlyUsage = () => {
|
||||||
monthlyUsage = []
|
monthlyUsage = []
|
||||||
if (quotaUsage.monthly) {
|
if (quotaUsage.monthly) {
|
||||||
|
@ -121,7 +127,7 @@
|
||||||
const setTextRows = () => {
|
const setTextRows = () => {
|
||||||
textRows = []
|
textRows = []
|
||||||
|
|
||||||
if (cancelAt) {
|
if (cancelAt && !usesInvoicing) {
|
||||||
textRows.push({ message: "Subscription has been cancelled" })
|
textRows.push({ message: "Subscription has been cancelled" })
|
||||||
textRows.push({
|
textRows.push({
|
||||||
message: `${getDaysRemaining(cancelAt)} days remaining`,
|
message: `${getDaysRemaining(cancelAt)} days remaining`,
|
||||||
|
@ -213,7 +219,7 @@
|
||||||
description="YOUR CURRENT PLAN"
|
description="YOUR CURRENT PLAN"
|
||||||
title={planTitle()}
|
title={planTitle()}
|
||||||
{primaryActionText}
|
{primaryActionText}
|
||||||
primaryAction={accountPortalAccess ? goToAccountPortal : undefined}
|
primaryAction={showButton ? goToAccountPortal : undefined}
|
||||||
{textRows}
|
{textRows}
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -224,33 +230,23 @@
|
||||||
<Usage {usage} warnWhenFull={WARN_USAGE.includes(usage.name)} />
|
<Usage {usage} warnWhenFull={WARN_USAGE.includes(usage.name)} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<Heading size="S">Monthly limits</Heading>
|
||||||
|
<div class="detail">
|
||||||
|
<TooltipWrapper tooltip={new Date(quotaReset)}>
|
||||||
|
<Detail size="M">
|
||||||
|
Resets in {daysRemainingInMonth} days
|
||||||
|
</Detail>
|
||||||
|
</TooltipWrapper>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
<Layout noPadding gap="M">
|
||||||
|
{#each monthlyUsage as usage}
|
||||||
|
<Usage {usage} warnWhenFull={WARN_USAGE.includes(usage.name)} />
|
||||||
|
{/each}
|
||||||
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if monthlyUsage.length}
|
|
||||||
<div class="column">
|
|
||||||
<Layout noPadding gap="M">
|
|
||||||
<Layout gap="XS" noPadding>
|
|
||||||
<Heading size="S">Monthly limits</Heading>
|
|
||||||
<div class="detail">
|
|
||||||
<TooltipWrapper tooltip={new Date(quotaReset)}>
|
|
||||||
<Detail size="M">
|
|
||||||
Resets in {daysRemainingInMonth} days
|
|
||||||
</Detail>
|
|
||||||
</TooltipWrapper>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
<Layout noPadding gap="M">
|
|
||||||
{#each monthlyUsage as usage}
|
|
||||||
<Usage
|
|
||||||
{usage}
|
|
||||||
warnWhenFull={WARN_USAGE.includes(usage.name)}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</DashCard>
|
</DashCard>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -176,7 +176,7 @@
|
||||||
<Heading>Backups</Heading>
|
<Heading>Backups</Heading>
|
||||||
{#if !$licensing.backupsEnabled}
|
{#if !$licensing.backupsEnabled}
|
||||||
<Tags>
|
<Tags>
|
||||||
<Tag icon="LockClosed">Pro plan</Tag>
|
<Tag icon="LockClosed">Premium</Tag>
|
||||||
</Tags>
|
</Tags>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import clientPackage from "@budibase/client/package.json"
|
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
import { users, auth, apps, groups, overview } from "stores/portal"
|
import { users, auth, apps, groups, overview } from "stores/portal"
|
||||||
import { fetchData } from "@budibase/frontend-core"
|
import { fetchData } from "@budibase/frontend-core"
|
||||||
|
@ -40,7 +39,7 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
$: updateAvailable = clientPackage.version !== $store.version
|
$: updateAvailable = $store.upgradableVersion !== $store.version
|
||||||
$: isPublished = app?.status === AppStatus.DEPLOYED
|
$: isPublished = app?.status === AppStatus.DEPLOYED
|
||||||
$: appEditorId = !app?.updatedBy ? $auth.user._id : app?.updatedBy
|
$: appEditorId = !app?.updatedBy ? $auth.user._id : app?.updatedBy
|
||||||
$: appEditorText = appEditor?.firstName || appEditor?.email
|
$: appEditorText = appEditor?.firstName || appEditor?.email
|
||||||
|
@ -172,8 +171,8 @@
|
||||||
<Heading size="XS">{$store.version}</Heading>
|
<Heading size="XS">{$store.version}</Heading>
|
||||||
{#if updateAvailable}
|
{#if updateAvailable}
|
||||||
<div class="version-status">
|
<div class="version-status">
|
||||||
New version <strong>{clientPackage.version}</strong> is available
|
New version <strong>{$store.upgradableVersion}</strong> is
|
||||||
-
|
available -
|
||||||
<Link
|
<Link
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
$goto("./version")
|
$goto("./version")
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { Layout, Heading, Body, Divider, Button } from "@budibase/bbui"
|
import { Layout, Heading, Body, Divider, Button } from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import clientPackage from "@budibase/client/package.json"
|
|
||||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||||
|
|
||||||
let versionModal
|
let versionModal
|
||||||
|
|
||||||
$: updateAvailable = clientPackage.version !== $store.version
|
$: updateAvailable = $store.upgradableVersion !== $store.version
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
|
@ -18,7 +17,7 @@
|
||||||
{#if updateAvailable}
|
{#if updateAvailable}
|
||||||
<Body>
|
<Body>
|
||||||
The app is currently using version <strong>{$store.version}</strong>
|
The app is currently using version <strong>{$store.version}</strong>
|
||||||
but version <strong>{clientPackage.version}</strong> is available.
|
but version <strong>{$store.upgradableVersion}</strong> is available.
|
||||||
<br />
|
<br />
|
||||||
Updates can contain new features, performance improvements and bug fixes.
|
Updates can contain new features, performance improvements and bug fixes.
|
||||||
</Body>
|
</Body>
|
||||||
|
|
|
@ -378,7 +378,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if !$licensing.enforceableSSO}
|
{#if !$licensing.enforceableSSO}
|
||||||
<Tags>
|
<Tags>
|
||||||
<Tag icon="LockClosed">Enterprise plan</Tag>
|
<Tag icon="LockClosed">Enterprise</Tag>
|
||||||
</Tags>
|
</Tags>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -213,7 +213,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if isCloud && !brandingEnabled}
|
{#if isCloud && !brandingEnabled}
|
||||||
<Tags>
|
<Tags>
|
||||||
<Tag icon="LockClosed">Pro</Tag>
|
<Tag icon="LockClosed">Premium</Tag>
|
||||||
</Tags>
|
</Tags>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
<Heading size="M">Groups</Heading>
|
<Heading size="M">Groups</Heading>
|
||||||
{#if !$licensing.groupsEnabled}
|
{#if !$licensing.groupsEnabled}
|
||||||
<Tags>
|
<Tags>
|
||||||
<Tag icon="LockClosed">Pro plan</Tag>
|
<Tag icon="LockClosed">Business</Tag>
|
||||||
</Tags>
|
</Tags>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
$: invalidEmails = []
|
$: invalidEmails = []
|
||||||
|
|
||||||
$: userCount = $licensing.userCount + userEmails.length
|
$: userCount = $licensing.userCount + userEmails.length
|
||||||
$: willExceed = userCount > $licensing.userLimit
|
$: willExceed = licensing.willExceedUserLimit(userCount)
|
||||||
|
|
||||||
$: importDisabled =
|
$: importDisabled =
|
||||||
!userEmails.length || !validEmails(userEmails) || !usersRole || willExceed
|
!userEmails.length || !validEmails(userEmails) || !usersRole || willExceed
|
||||||
|
|
|
@ -41,7 +41,7 @@ export function createUsersStore() {
|
||||||
inviteCode,
|
inviteCode,
|
||||||
password,
|
password,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName: !lastName?.trim() ? undefined : lastName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,11 +114,13 @@ export function createUsersStore() {
|
||||||
const getUserRole = ({ admin, builder }) =>
|
const getUserRole = ({ admin, builder }) =>
|
||||||
admin?.global ? "admin" : builder?.global ? "developer" : "appUser"
|
admin?.global ? "admin" : builder?.global ? "developer" : "appUser"
|
||||||
|
|
||||||
const refreshUsage = fn => async args => {
|
const refreshUsage =
|
||||||
const response = await fn(args)
|
fn =>
|
||||||
await licensing.setQuotaUsage()
|
async (...args) => {
|
||||||
return response
|
const response = await fn(...args)
|
||||||
}
|
await licensing.setQuotaUsage()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
|
@ -133,7 +135,7 @@ export function createUsersStore() {
|
||||||
updateInvite,
|
updateInvite,
|
||||||
getUserCountByApp,
|
getUserCountByApp,
|
||||||
// any operation that adds or deletes users
|
// any operation that adds or deletes users
|
||||||
acceptInvite: refreshUsage(acceptInvite),
|
acceptInvite,
|
||||||
create: refreshUsage(create),
|
create: refreshUsage(create),
|
||||||
save: refreshUsage(save),
|
save: refreshUsage(save),
|
||||||
bulkDelete: refreshUsage(bulkDelete),
|
bulkDelete: refreshUsage(bulkDelete),
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"cli-progress": "3.11.2",
|
"cli-progress": "3.11.2",
|
||||||
"commander": "7.1.0",
|
"commander": "7.1.0",
|
||||||
"docker-compose": "0.23.12",
|
"docker-compose": "0.24.0",
|
||||||
"dotenv": "16.0.1",
|
"dotenv": "16.0.1",
|
||||||
"download": "8.0.0",
|
"download": "8.0.0",
|
||||||
"find-free-port": "^2.0.0",
|
"find-free-port": "^2.0.0",
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { logging } from "@budibase/backend-core"
|
process.env.DISABLE_PINO_LOGGER = "1"
|
||||||
logging.disableLogger()
|
|
||||||
import "./prebuilds"
|
import "./prebuilds"
|
||||||
import "./environment"
|
import "./environment"
|
||||||
import { env } from "@budibase/backend-core"
|
import { env } from "@budibase/backend-core"
|
||||||
|
|
|
@ -5225,36 +5225,5 @@
|
||||||
"type": "schema",
|
"type": "schema",
|
||||||
"suffix": "repeater"
|
"suffix": "repeater"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"spreadsheet": {
|
|
||||||
"name": "Spreadsheet",
|
|
||||||
"icon": "ViewGrid",
|
|
||||||
"settings": [
|
|
||||||
{
|
|
||||||
"key": "table",
|
|
||||||
"type": "table",
|
|
||||||
"label": "Table"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "filter",
|
|
||||||
"label": "Filtering",
|
|
||||||
"key": "filter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "field/sortable",
|
|
||||||
"label": "Sort Column",
|
|
||||||
"key": "sortColumn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "select",
|
|
||||||
"label": "Sort Order",
|
|
||||||
"key": "sortOrder",
|
|
||||||
"options": [
|
|
||||||
"Ascending",
|
|
||||||
"Descending"
|
|
||||||
],
|
|
||||||
"defaultValue": "Ascending"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
export let updateValue = rows.actions.updateValue
|
export let updateValue = rows.actions.updateValue
|
||||||
export let invertX = false
|
export let invertX = false
|
||||||
export let invertY = false
|
export let invertY = false
|
||||||
|
export let contentLines = 1
|
||||||
|
|
||||||
const emptyError = writable(null)
|
const emptyError = writable(null)
|
||||||
|
|
||||||
|
@ -84,5 +85,7 @@
|
||||||
{readonly}
|
{readonly}
|
||||||
{invertY}
|
{invertY}
|
||||||
{invertX}
|
{invertX}
|
||||||
|
{contentLines}
|
||||||
/>
|
/>
|
||||||
|
<slot />
|
||||||
</GridCell>
|
</GridCell>
|
||||||
|
|
|
@ -117,6 +117,9 @@
|
||||||
.cell.error {
|
.cell.error {
|
||||||
--cell-color: var(--spectrum-global-color-red-500);
|
--cell-color: var(--spectrum-global-color-red-500);
|
||||||
}
|
}
|
||||||
|
.cell.readonly {
|
||||||
|
--cell-color: var(--spectrum-global-color-gray-600);
|
||||||
|
}
|
||||||
.cell:not(.focused) {
|
.cell:not(.focused) {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,8 @@
|
||||||
$: sortedBy = column.name === $sort.column
|
$: sortedBy = column.name === $sort.column
|
||||||
$: canMoveLeft = orderable && idx > 0
|
$: canMoveLeft = orderable && idx > 0
|
||||||
$: canMoveRight = orderable && idx < $renderedColumns.length - 1
|
$: canMoveRight = orderable && idx < $renderedColumns.length - 1
|
||||||
|
$: ascendingLabel = column.schema?.type === "number" ? "low-high" : "A-Z"
|
||||||
|
$: descendingLabel = column.schema?.type === "number" ? "high-low" : "Z-A"
|
||||||
|
|
||||||
const editColumn = () => {
|
const editColumn = () => {
|
||||||
dispatch("edit-column", column.schema)
|
dispatch("edit-column", column.schema)
|
||||||
|
@ -179,14 +181,14 @@
|
||||||
on:click={sortAscending}
|
on:click={sortAscending}
|
||||||
disabled={column.name === $sort.column && $sort.order === "ascending"}
|
disabled={column.name === $sort.column && $sort.order === "ascending"}
|
||||||
>
|
>
|
||||||
Sort A-Z
|
Sort {ascendingLabel}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="SortOrderDown"
|
icon="SortOrderDown"
|
||||||
on:click={sortDescending}
|
on:click={sortDescending}
|
||||||
disabled={column.name === $sort.column && $sort.order === "descending"}
|
disabled={column.name === $sort.column && $sort.order === "descending"}
|
||||||
>
|
>
|
||||||
Sort Z-A
|
Sort {descendingLabel}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
||||||
Move left
|
Move left
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, tick } from "svelte"
|
import { onMount, tick } from "svelte"
|
||||||
|
import { clickOutside } from "@budibase/bbui"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let focused = false
|
export let focused = false
|
||||||
|
@ -60,6 +61,7 @@
|
||||||
on:change={handleChange}
|
on:change={handleChange}
|
||||||
on:wheel|stopPropagation
|
on:wheel|stopPropagation
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
|
use:clickOutside={close}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="long-form-cell" on:click={editable ? open : null} class:editable>
|
<div class="long-form-cell" on:click={editable ? open : null} class:editable>
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
import TextCell from "./TextCell.svelte"
|
import TextCell from "./TextCell.svelte"
|
||||||
|
|
||||||
export let api
|
export let api
|
||||||
|
export let onChange
|
||||||
|
|
||||||
|
const numberOnChange = value => {
|
||||||
|
const float = parseFloat(value)
|
||||||
|
const newValue = isNaN(float) ? null : float
|
||||||
|
onChange(newValue)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TextCell {...$$props} bind:api type="number" />
|
<TextCell {...$$props} onChange={numberOnChange} bind:api type="number" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon, clickOutside } from "@budibase/bbui"
|
||||||
import { getColor } from "../lib/utils"
|
import { getColor } from "../lib/utils"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
export let api
|
export let api
|
||||||
export let invertX = false
|
export let invertX = false
|
||||||
export let invertY = false
|
export let invertY = false
|
||||||
|
export let contentLines = 1
|
||||||
|
|
||||||
let isOpen = false
|
let isOpen = false
|
||||||
let focusedOptionIdx = null
|
let focusedOptionIdx = null
|
||||||
|
@ -86,7 +87,11 @@
|
||||||
class:open
|
class:open
|
||||||
on:click|self={editable ? open : null}
|
on:click|self={editable ? open : null}
|
||||||
>
|
>
|
||||||
<div class="values" on:click={editable ? open : null}>
|
<div
|
||||||
|
class="values"
|
||||||
|
class:wrap={contentLines > 1}
|
||||||
|
on:click={editable ? open : null}
|
||||||
|
>
|
||||||
{#each values as val}
|
{#each values as val}
|
||||||
{@const color = getOptionColor(val)}
|
{@const color = getOptionColor(val)}
|
||||||
{#if color}
|
{#if color}
|
||||||
|
@ -113,6 +118,7 @@
|
||||||
class:invertX
|
class:invertX
|
||||||
class:invertY
|
class:invertY
|
||||||
on:wheel={e => e.stopPropagation()}
|
on:wheel={e => e.stopPropagation()}
|
||||||
|
use:clickOutside={close}
|
||||||
>
|
>
|
||||||
{#each options as option, idx}
|
{#each options as option, idx}
|
||||||
{@const color = getOptionColor(option)}
|
{@const color = getOptionColor(option)}
|
||||||
|
@ -160,6 +166,9 @@
|
||||||
grid-row-gap: var(--cell-padding);
|
grid-row-gap: var(--cell-padding);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: var(--cell-padding);
|
padding: var(--cell-padding);
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.values.wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getColor } from "../lib/utils"
|
import { getColor } from "../lib/utils"
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { Icon, Input, ProgressCircle } from "@budibase/bbui"
|
import { Icon, Input, ProgressCircle, clickOutside } from "@budibase/bbui"
|
||||||
import { debounce } from "../../../utils/utils"
|
import { debounce } from "../../../utils/utils"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
@ -29,6 +29,7 @@
|
||||||
export let onChange
|
export let onChange
|
||||||
export let invertX = false
|
export let invertX = false
|
||||||
export let invertY = false
|
export let invertY = false
|
||||||
|
export let contentLines = 1
|
||||||
|
|
||||||
const { API, dispatch } = getContext("grid")
|
const { API, dispatch } = getContext("grid")
|
||||||
const color = getColor(0)
|
const color = getColor(0)
|
||||||
|
@ -243,7 +244,11 @@
|
||||||
|
|
||||||
<div class="wrapper" class:editable class:focused style="--color:{color};">
|
<div class="wrapper" class:editable class:focused style="--color:{color};">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="values" on:wheel={e => (focused ? e.stopPropagation() : null)}>
|
<div
|
||||||
|
class="values"
|
||||||
|
class:wrap={editable || contentLines > 1}
|
||||||
|
on:wheel={e => (focused ? e.stopPropagation() : null)}
|
||||||
|
>
|
||||||
{#each value || [] as relationship, idx}
|
{#each value || [] as relationship, idx}
|
||||||
{#if relationship.primaryDisplay}
|
{#if relationship.primaryDisplay}
|
||||||
<div class="badge">
|
<div class="badge">
|
||||||
|
@ -279,7 +284,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<div class="dropdown" class:invertX class:invertY on:wheel|stopPropagation>
|
<div
|
||||||
|
class="dropdown"
|
||||||
|
class:invertX
|
||||||
|
class:invertY
|
||||||
|
on:wheel|stopPropagation
|
||||||
|
use:clickOutside={close}
|
||||||
|
>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<Input
|
<Input
|
||||||
autofocus
|
autofocus
|
||||||
|
@ -376,6 +387,9 @@
|
||||||
grid-row-gap: var(--cell-padding);
|
grid-row-gap: var(--cell-padding);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: var(--cell-padding);
|
padding: var(--cell-padding);
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.values.wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.count {
|
.count {
|
||||||
|
|
|
@ -3,16 +3,13 @@
|
||||||
import { ActionButton, Popover, Select } from "@budibase/bbui"
|
import { ActionButton, Popover, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
const { sort, columns, stickyColumn } = getContext("grid")
|
const { sort, columns, stickyColumn } = getContext("grid")
|
||||||
const orderOptions = [
|
|
||||||
{ label: "A-Z", value: "ascending" },
|
|
||||||
{ label: "Z-A", value: "descending" },
|
|
||||||
]
|
|
||||||
|
|
||||||
let open = false
|
let open = false
|
||||||
let anchor
|
let anchor
|
||||||
|
|
||||||
$: columnOptions = getColumnOptions($stickyColumn, $columns)
|
$: columnOptions = getColumnOptions($stickyColumn, $columns)
|
||||||
$: checkValidSortColumn($sort.column, $stickyColumn, $columns)
|
$: checkValidSortColumn($sort.column, $stickyColumn, $columns)
|
||||||
|
$: orderOptions = getOrderOptions($sort.column, columnOptions)
|
||||||
|
|
||||||
const getColumnOptions = (stickyColumn, columns) => {
|
const getColumnOptions = (stickyColumn, columns) => {
|
||||||
let options = []
|
let options = []
|
||||||
|
@ -20,6 +17,7 @@
|
||||||
options.push({
|
options.push({
|
||||||
label: stickyColumn.label || stickyColumn.name,
|
label: stickyColumn.label || stickyColumn.name,
|
||||||
value: stickyColumn.name,
|
value: stickyColumn.name,
|
||||||
|
type: stickyColumn.schema?.type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
|
@ -27,10 +25,25 @@
|
||||||
...columns.map(col => ({
|
...columns.map(col => ({
|
||||||
label: col.label || col.name,
|
label: col.label || col.name,
|
||||||
value: col.name,
|
value: col.name,
|
||||||
|
type: col.schema?.type,
|
||||||
})),
|
})),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getOrderOptions = (column, columnOptions) => {
|
||||||
|
const type = columnOptions.find(col => col.value === column)?.type
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: type === "number" ? "Low-high" : "A-Z",
|
||||||
|
value: "ascending",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: type === "number" ? "High-low" : "Z-A",
|
||||||
|
value: "descending",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
const updateSortColumn = e => {
|
const updateSortColumn = e => {
|
||||||
sort.update(state => ({
|
sort.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
selectedCellMap,
|
selectedCellMap,
|
||||||
focusedRow,
|
focusedRow,
|
||||||
columnHorizontalInversionIndex,
|
columnHorizontalInversionIndex,
|
||||||
|
contentLines,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
$: rowSelected = !!$selectedRows[row._id]
|
$: rowSelected = !!$selectedRows[row._id]
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
focused={$focusedCellId === cellId}
|
focused={$focusedCellId === cellId}
|
||||||
selectedUser={$selectedCellMap[cellId]}
|
selectedUser={$selectedCellMap[cellId]}
|
||||||
width={column.width}
|
width={column.width}
|
||||||
|
contentLines={$contentLines}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<script>
|
||||||
|
export let keybind
|
||||||
|
export let padded = false
|
||||||
|
export let overlay = false
|
||||||
|
|
||||||
|
$: parsedKeys = parseKeys(keybind)
|
||||||
|
|
||||||
|
const parseKeys = keybind => {
|
||||||
|
return keybind?.split("+").map(key => {
|
||||||
|
if (key.toLowerCase() === "ctrl") {
|
||||||
|
return navigator.platform.startsWith("Mac") ? "⌘" : key
|
||||||
|
} else if (key.toLowerCase() === "enter") {
|
||||||
|
return "↵"
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="keys" class:padded class:overlay>
|
||||||
|
{#each parsedKeys as key}
|
||||||
|
<div class="key">
|
||||||
|
{key}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.keys {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
.keys.padded {
|
||||||
|
padding: var(--cell-padding);
|
||||||
|
}
|
||||||
|
.key {
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
|
color: var(--spectrum-global-color-gray-700);
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.overlay .key {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -7,6 +7,7 @@
|
||||||
import { GutterWidth } from "../lib/constants"
|
import { GutterWidth } from "../lib/constants"
|
||||||
import { NewRowID } from "../lib/constants"
|
import { NewRowID } from "../lib/constants"
|
||||||
import GutterCell from "../cells/GutterCell.svelte"
|
import GutterCell from "../cells/GutterCell.svelte"
|
||||||
|
import KeyboardShortcut from "./KeyboardShortcut.svelte"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
|
@ -27,13 +28,14 @@
|
||||||
columnHorizontalInversionIndex,
|
columnHorizontalInversionIndex,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
|
let visible = false
|
||||||
let isAdding = false
|
let isAdding = false
|
||||||
let newRow = {}
|
let newRow = {}
|
||||||
let offset = 0
|
let offset = 0
|
||||||
|
|
||||||
$: firstColumn = $stickyColumn || $renderedColumns[0]
|
$: firstColumn = $stickyColumn || $renderedColumns[0]
|
||||||
$: width = GutterWidth + ($stickyColumn?.width || 0)
|
$: width = GutterWidth + ($stickyColumn?.width || 0)
|
||||||
$: $tableId, (isAdding = false)
|
$: $tableId, (visible = false)
|
||||||
$: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
|
$: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
|
||||||
|
|
||||||
const shouldInvertY = (offset, inversionIndex, rows) => {
|
const shouldInvertY = (offset, inversionIndex, rows) => {
|
||||||
|
@ -45,7 +47,8 @@
|
||||||
|
|
||||||
const addRow = async () => {
|
const addRow = async () => {
|
||||||
// Blur the active cell and tick to let final value updates propagate
|
// Blur the active cell and tick to let final value updates propagate
|
||||||
$focusedCellAPI?.blur()
|
isAdding = true
|
||||||
|
$focusedCellId = null
|
||||||
await tick()
|
await tick()
|
||||||
|
|
||||||
// Create row
|
// Create row
|
||||||
|
@ -60,17 +63,19 @@
|
||||||
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
|
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isAdding = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
isAdding = false
|
isAdding = false
|
||||||
|
visible = false
|
||||||
$focusedCellId = null
|
$focusedCellId = null
|
||||||
$hoveredRowId = null
|
$hoveredRowId = null
|
||||||
document.removeEventListener("keydown", handleKeyPress)
|
document.removeEventListener("keydown", handleKeyPress)
|
||||||
}
|
}
|
||||||
|
|
||||||
const startAdding = async () => {
|
const startAdding = async () => {
|
||||||
if (isAdding) {
|
if (visible) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +100,7 @@
|
||||||
|
|
||||||
// Update state and select initial cell
|
// Update state and select initial cell
|
||||||
newRow = {}
|
newRow = {}
|
||||||
isAdding = true
|
visible = true
|
||||||
$hoveredRowId = NewRowID
|
$hoveredRowId = NewRowID
|
||||||
if (firstColumn) {
|
if (firstColumn) {
|
||||||
$focusedCellId = `${NewRowID}-${firstColumn.name}`
|
$focusedCellId = `${NewRowID}-${firstColumn.name}`
|
||||||
|
@ -115,7 +120,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyPress = e => {
|
const handleKeyPress = e => {
|
||||||
if (!isAdding) {
|
if (!visible) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
|
@ -137,7 +142,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Only show new row functionality if we have any columns -->
|
<!-- Only show new row functionality if we have any columns -->
|
||||||
{#if isAdding}
|
{#if visible}
|
||||||
<div
|
<div
|
||||||
class="container"
|
class="container"
|
||||||
class:floating={offset > 0}
|
class:floating={offset > 0}
|
||||||
|
@ -148,6 +153,9 @@
|
||||||
<div class="sticky-column" transition:fade={{ duration: 130 }}>
|
<div class="sticky-column" transition:fade={{ duration: 130 }}>
|
||||||
<GutterCell on:expand={addViaModal} rowHovered>
|
<GutterCell on:expand={addViaModal} rowHovered>
|
||||||
<Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
|
<Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
|
||||||
|
{#if isAdding}
|
||||||
|
<div in:fade={{ duration: 130 }} class="loading-overlay" />
|
||||||
|
{/if}
|
||||||
</GutterCell>
|
</GutterCell>
|
||||||
{#if $stickyColumn}
|
{#if $stickyColumn}
|
||||||
{@const cellId = `${NewRowID}-${$stickyColumn.name}`}
|
{@const cellId = `${NewRowID}-${$stickyColumn.name}`}
|
||||||
|
@ -161,7 +169,14 @@
|
||||||
{updateValue}
|
{updateValue}
|
||||||
rowIdx={0}
|
rowIdx={0}
|
||||||
{invertY}
|
{invertY}
|
||||||
/>
|
>
|
||||||
|
{#if $stickyColumn?.schema?.autocolumn}
|
||||||
|
<div class="readonly-overlay">Can't edit auto column</div>
|
||||||
|
{/if}
|
||||||
|
{#if isAdding}
|
||||||
|
<div in:fade={{ duration: 130 }} class="loading-overlay" />
|
||||||
|
{/if}
|
||||||
|
</DataCell>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="normal-columns" transition:fade={{ duration: 130 }}>
|
<div class="normal-columns" transition:fade={{ duration: 130 }}>
|
||||||
|
@ -181,15 +196,32 @@
|
||||||
rowIdx={0}
|
rowIdx={0}
|
||||||
invertX={columnIdx >= $columnHorizontalInversionIndex}
|
invertX={columnIdx >= $columnHorizontalInversionIndex}
|
||||||
{invertY}
|
{invertY}
|
||||||
/>
|
>
|
||||||
|
{#if column?.schema?.autocolumn}
|
||||||
|
<div class="readonly-overlay">Can't edit auto column</div>
|
||||||
|
{/if}
|
||||||
|
{#if isAdding}
|
||||||
|
<div in:fade={{ duration: 130 }} class="loading-overlay" />
|
||||||
|
{/if}
|
||||||
|
</DataCell>
|
||||||
{/key}
|
{/key}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</GridScrollWrapper>
|
</GridScrollWrapper>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons" transition:fade={{ duration: 130 }}>
|
<div class="buttons" transition:fade={{ duration: 130 }}>
|
||||||
<Button size="M" cta on:click={addRow}>Save</Button>
|
<Button size="M" cta on:click={addRow} disabled={isAdding}>
|
||||||
<Button size="M" secondary newStyles on:click={clear}>Cancel</Button>
|
<div class="button-with-keys">
|
||||||
|
Save
|
||||||
|
<KeyboardShortcut overlay keybind="Ctrl+Enter" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
<Button size="M" secondary newStyles on:click={clear}>
|
||||||
|
<div class="button-with-keys">
|
||||||
|
Cancel
|
||||||
|
<KeyboardShortcut overlay keybind="Esc" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -240,6 +272,14 @@
|
||||||
top: calc(var(--row-height) + var(--offset) + 24px);
|
top: calc(var(--row-height) + var(--offset) + 24px);
|
||||||
left: var(--gutter-width);
|
left: var(--gutter-width);
|
||||||
}
|
}
|
||||||
|
.button-with-keys {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.button-with-keys :global(> div) {
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Sticky column styles */
|
/* Sticky column styles */
|
||||||
.sticky-column {
|
.sticky-column {
|
||||||
|
@ -262,4 +302,33 @@
|
||||||
width: 0;
|
width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Readonly cell overlay */
|
||||||
|
.readonly-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: var(--row-height);
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--cell-padding);
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
z-index: 1;
|
||||||
|
user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overlay while row is being added */
|
||||||
|
.loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: var(--row-height);
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
background: var(--spectrum-global-color-gray-400);
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import HeaderCell from "../cells/HeaderCell.svelte"
|
import HeaderCell from "../cells/HeaderCell.svelte"
|
||||||
import { GutterWidth, BlankRowID } from "../lib/constants"
|
import { GutterWidth, BlankRowID } from "../lib/constants"
|
||||||
import GutterCell from "../cells/GutterCell.svelte"
|
import GutterCell from "../cells/GutterCell.svelte"
|
||||||
|
import KeyboardShortcut from "./KeyboardShortcut.svelte"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
rows,
|
rows,
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
focusedRow,
|
focusedRow,
|
||||||
scrollLeft,
|
scrollLeft,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
contentLines,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
$: rowCount = $rows.length
|
$: rowCount = $rows.length
|
||||||
|
@ -85,6 +87,7 @@
|
||||||
selectedUser={$selectedCellMap[cellId]}
|
selectedUser={$selectedCellMap[cellId]}
|
||||||
width={$stickyColumn.width}
|
width={$stickyColumn.width}
|
||||||
column={$stickyColumn}
|
column={$stickyColumn}
|
||||||
|
contentLines={$contentLines}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,7 +106,9 @@
|
||||||
<GridCell
|
<GridCell
|
||||||
width={$stickyColumn.width}
|
width={$stickyColumn.width}
|
||||||
highlighted={$hoveredRowId === BlankRowID}
|
highlighted={$hoveredRowId === BlankRowID}
|
||||||
/>
|
>
|
||||||
|
<KeyboardShortcut padded keybind="Ctrl+Enter" />
|
||||||
|
</GridCell>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -15,8 +15,22 @@
|
||||||
selectedRows,
|
selectedRows,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
|
const ignoredOriginSelectors = [
|
||||||
|
".spectrum-Modal",
|
||||||
|
"#builder-side-panel-container",
|
||||||
|
]
|
||||||
|
|
||||||
// Global key listener which intercepts all key events
|
// Global key listener which intercepts all key events
|
||||||
const handleKeyDown = e => {
|
const handleKeyDown = e => {
|
||||||
|
// Avoid processing events sourced from certain origins
|
||||||
|
if (e.target?.closest) {
|
||||||
|
for (let selector of ignoredOriginSelectors) {
|
||||||
|
if (e.target.closest(selector)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If nothing selected avoid processing further key presses
|
// If nothing selected avoid processing further key presses
|
||||||
if (!$focusedCellId) {
|
if (!$focusedCellId) {
|
||||||
if (e.key === "Tab" || e.key?.startsWith("Arrow")) {
|
if (e.key === "Tab" || e.key?.startsWith("Arrow")) {
|
||||||
|
@ -60,11 +74,6 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid processing events sourced from modals
|
|
||||||
if (e.target?.closest?.(".spectrum-Modal")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
// Handle the key ourselves
|
// Handle the key ourselves
|
||||||
|
|
|
@ -223,7 +223,7 @@ export async function fetchAppPackage(ctx: UserCtx) {
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
application,
|
application: { ...application, upgradableVersion: envCore.VERSION },
|
||||||
screens,
|
screens,
|
||||||
layouts,
|
layouts,
|
||||||
clientLibPath,
|
clientLibPath,
|
||||||
|
|
|
@ -12,7 +12,15 @@ import { getIntegration } from "../../integrations"
|
||||||
import { getDatasourceAndQuery } from "./row/utils"
|
import { getDatasourceAndQuery } from "./row/utils"
|
||||||
import { invalidateDynamicVariables } from "../../threads/utils"
|
import { invalidateDynamicVariables } from "../../threads/utils"
|
||||||
import { db as dbCore, context, events } from "@budibase/backend-core"
|
import { db as dbCore, context, events } from "@budibase/backend-core"
|
||||||
import { UserCtx, Datasource, Row } from "@budibase/types"
|
import {
|
||||||
|
UserCtx,
|
||||||
|
Datasource,
|
||||||
|
Row,
|
||||||
|
CreateDatasourceResponse,
|
||||||
|
UpdateDatasourceResponse,
|
||||||
|
UpdateDatasourceRequest,
|
||||||
|
CreateDatasourceRequest,
|
||||||
|
} from "@budibase/types"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
|
||||||
export async function fetch(ctx: UserCtx) {
|
export async function fetch(ctx: UserCtx) {
|
||||||
|
@ -146,7 +154,7 @@ async function invalidateVariables(
|
||||||
await invalidateDynamicVariables(toInvalidate)
|
await invalidateDynamicVariables(toInvalidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(ctx: UserCtx) {
|
export async function update(ctx: UserCtx<any, UpdateDatasourceResponse>) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const datasourceId = ctx.params.datasourceId
|
const datasourceId = ctx.params.datasourceId
|
||||||
let datasource = await sdk.datasources.get(datasourceId)
|
let datasource = await sdk.datasources.get(datasourceId)
|
||||||
|
@ -187,15 +195,17 @@ export async function update(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function save(ctx: UserCtx) {
|
export async function save(
|
||||||
|
ctx: UserCtx<CreateDatasourceRequest, CreateDatasourceResponse>
|
||||||
|
) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const plus = ctx.request.body.datasource.plus
|
const plus = ctx.request.body.datasource.plus
|
||||||
const fetchSchema = ctx.request.body.fetchSchema
|
const fetchSchema = ctx.request.body.fetchSchema
|
||||||
|
|
||||||
const datasource = {
|
const datasource = {
|
||||||
_id: generateDatasourceID({ plus }),
|
_id: generateDatasourceID({ plus }),
|
||||||
type: plus ? DocumentType.DATASOURCE_PLUS : DocumentType.DATASOURCE,
|
|
||||||
...ctx.request.body.datasource,
|
...ctx.request.body.datasource,
|
||||||
|
type: plus ? DocumentType.DATASOURCE_PLUS : DocumentType.DATASOURCE,
|
||||||
}
|
}
|
||||||
|
|
||||||
let schemaError = null
|
let schemaError = null
|
||||||
|
@ -218,7 +228,7 @@ export async function save(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response: any = {
|
const response: CreateDatasourceResponse = {
|
||||||
datasource: await sdk.datasources.removeSecretSingle(datasource),
|
datasource: await sdk.datasources.removeSecretSingle(datasource),
|
||||||
}
|
}
|
||||||
if (schemaError) {
|
if (schemaError) {
|
||||||
|
|
|
@ -105,7 +105,7 @@ describe("internal search", () => {
|
||||||
"column": "",
|
"column": "",
|
||||||
},
|
},
|
||||||
}, PARAMS)
|
}, PARAMS)
|
||||||
checkLucene(response, `*:* AND !column:["" TO *]`, PARAMS)
|
checkLucene(response, `*:* AND (*:* -column:["" TO *])`, PARAMS)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("test notEmpty query", async () => {
|
it("test notEmpty query", async () => {
|
||||||
|
|
|
@ -27,6 +27,7 @@ export const isProdAppID = dbCore.isProdAppID
|
||||||
export const USER_METDATA_PREFIX = `${DocumentType.ROW}${SEPARATOR}${dbCore.InternalTable.USER_METADATA}${SEPARATOR}`
|
export const USER_METDATA_PREFIX = `${DocumentType.ROW}${SEPARATOR}${dbCore.InternalTable.USER_METADATA}${SEPARATOR}`
|
||||||
export const LINK_USER_METADATA_PREFIX = `${DocumentType.LINK}${SEPARATOR}${dbCore.InternalTable.USER_METADATA}${SEPARATOR}`
|
export const LINK_USER_METADATA_PREFIX = `${DocumentType.LINK}${SEPARATOR}${dbCore.InternalTable.USER_METADATA}${SEPARATOR}`
|
||||||
export const TABLE_ROW_PREFIX = `${DocumentType.ROW}${SEPARATOR}${DocumentType.TABLE}`
|
export const TABLE_ROW_PREFIX = `${DocumentType.ROW}${SEPARATOR}${DocumentType.TABLE}`
|
||||||
|
export const AUTOMATION_LOG_PREFIX = `${DocumentType.AUTOMATION_LOG}${SEPARATOR}`
|
||||||
export const ViewName = dbCore.ViewName
|
export const ViewName = dbCore.ViewName
|
||||||
export const InternalTables = dbCore.InternalTable
|
export const InternalTables = dbCore.InternalTable
|
||||||
export const UNICODE_MAX = dbCore.UNICODE_MAX
|
export const UNICODE_MAX = dbCore.UNICODE_MAX
|
||||||
|
|
|
@ -349,7 +349,7 @@ describe("row api - postgres", () => {
|
||||||
},
|
},
|
||||||
plus: true,
|
plus: true,
|
||||||
source: "POSTGRES",
|
source: "POSTGRES",
|
||||||
type: "datasource",
|
type: "datasource_plus",
|
||||||
_id: expect.any(String),
|
_id: expect.any(String),
|
||||||
_rev: expect.any(String),
|
_rev: expect.any(String),
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
||||||
import { streamFile, createTempFolder } from "../../../utilities/fileSystem"
|
import { streamFile, createTempFolder } from "../../../utilities/fileSystem"
|
||||||
import { ObjectStoreBuckets } from "../../../constants"
|
import { ObjectStoreBuckets } from "../../../constants"
|
||||||
import {
|
import {
|
||||||
|
AUTOMATION_LOG_PREFIX,
|
||||||
LINK_USER_METADATA_PREFIX,
|
LINK_USER_METADATA_PREFIX,
|
||||||
TABLE_ROW_PREFIX,
|
TABLE_ROW_PREFIX,
|
||||||
USER_METDATA_PREFIX,
|
USER_METDATA_PREFIX,
|
||||||
|
@ -20,11 +21,15 @@ const uuid = require("uuid/v4")
|
||||||
const tar = require("tar")
|
const tar = require("tar")
|
||||||
const MemoryStream = require("memorystream")
|
const MemoryStream = require("memorystream")
|
||||||
|
|
||||||
type ExportOpts = {
|
interface DBDumpOpts {
|
||||||
filter?: any
|
filter?: any
|
||||||
exportPath?: string
|
exportPath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportOpts extends DBDumpOpts {
|
||||||
tar?: boolean
|
tar?: boolean
|
||||||
excludeRows?: boolean
|
excludeRows?: boolean
|
||||||
|
excludeLogs?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function tarFilesToTmp(tmpDir: string, files: string[]) {
|
function tarFilesToTmp(tmpDir: string, files: string[]) {
|
||||||
|
@ -49,7 +54,7 @@ function tarFilesToTmp(tmpDir: string, files: string[]) {
|
||||||
* a filter function or the name of the export.
|
* a filter function or the name of the export.
|
||||||
* @return {*} either a readable stream or a string
|
* @return {*} either a readable stream or a string
|
||||||
*/
|
*/
|
||||||
export async function exportDB(dbName: string, opts: ExportOpts = {}) {
|
export async function exportDB(dbName: string, opts: DBDumpOpts = {}) {
|
||||||
const exportOpts = {
|
const exportOpts = {
|
||||||
filter: opts?.filter,
|
filter: opts?.filter,
|
||||||
batch_size: 1000,
|
batch_size: 1000,
|
||||||
|
@ -76,11 +81,14 @@ export async function exportDB(dbName: string, opts: ExportOpts = {}) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineFilter(excludeRows?: boolean) {
|
function defineFilter(excludeRows?: boolean, excludeLogs?: boolean) {
|
||||||
const ids = [USER_METDATA_PREFIX, LINK_USER_METADATA_PREFIX]
|
const ids = [USER_METDATA_PREFIX, LINK_USER_METADATA_PREFIX]
|
||||||
if (excludeRows) {
|
if (excludeRows) {
|
||||||
ids.push(TABLE_ROW_PREFIX)
|
ids.push(TABLE_ROW_PREFIX)
|
||||||
}
|
}
|
||||||
|
if (excludeLogs) {
|
||||||
|
ids.push(AUTOMATION_LOG_PREFIX)
|
||||||
|
}
|
||||||
return (doc: any) =>
|
return (doc: any) =>
|
||||||
!ids.map(key => doc._id.includes(key)).reduce((prev, curr) => prev || curr)
|
!ids.map(key => doc._id.includes(key)).reduce((prev, curr) => prev || curr)
|
||||||
}
|
}
|
||||||
|
@ -130,8 +138,7 @@ export async function exportApp(appId: string, config?: ExportOpts) {
|
||||||
// enforce an export of app DB to the tmp path
|
// enforce an export of app DB to the tmp path
|
||||||
const dbPath = join(tmpPath, DB_EXPORT_FILE)
|
const dbPath = join(tmpPath, DB_EXPORT_FILE)
|
||||||
await exportDB(appId, {
|
await exportDB(appId, {
|
||||||
...config,
|
filter: defineFilter(config?.excludeRows, config?.excludeLogs),
|
||||||
filter: defineFilter(config?.excludeRows),
|
|
||||||
exportPath: dbPath,
|
exportPath: dbPath,
|
||||||
})
|
})
|
||||||
// if tar requested, return where the tarball is
|
// if tar requested, return where the tarball is
|
||||||
|
@ -155,6 +162,10 @@ export async function exportApp(appId: string, config?: ExportOpts) {
|
||||||
* @returns {*} a readable stream of the backup which is written in real time
|
* @returns {*} a readable stream of the backup which is written in real time
|
||||||
*/
|
*/
|
||||||
export async function streamExportApp(appId: string, excludeRows: boolean) {
|
export async function streamExportApp(appId: string, excludeRows: boolean) {
|
||||||
const tmpPath = await exportApp(appId, { excludeRows, tar: true })
|
const tmpPath = await exportApp(appId, {
|
||||||
|
excludeRows,
|
||||||
|
excludeLogs: true,
|
||||||
|
tar: true,
|
||||||
|
})
|
||||||
return streamFile(tmpPath)
|
return streamFile(tmpPath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { QuotaUsage } from "../../documents"
|
import { LicenseOverrides, QuotaUsage } from "../../documents"
|
||||||
|
import { PlanType } from "../../sdk"
|
||||||
|
|
||||||
export interface GetLicenseRequest {
|
export interface GetLicenseRequest {
|
||||||
// All fields should be optional to cater for
|
// All fields should be optional to cater for
|
||||||
|
@ -20,3 +21,8 @@ export interface QuotaTriggeredRequest {
|
||||||
export interface LicenseActivateRequest {
|
export interface LicenseActivateRequest {
|
||||||
installVersion?: string
|
installVersion?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateLicenseRequest {
|
||||||
|
planType?: PlanType
|
||||||
|
overrides?: LicenseOverrides
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Datasource } from "../../../documents"
|
||||||
|
|
||||||
|
export interface CreateDatasourceResponse {
|
||||||
|
datasource: Datasource
|
||||||
|
error?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateDatasourceResponse {
|
||||||
|
datasource: Datasource
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateDatasourceRequest {
|
||||||
|
datasource: Datasource
|
||||||
|
fetchSchema?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateDatasourceRequest extends Datasource {
|
||||||
|
datasource: Datasource
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
export * from "./backup"
|
export * from "./backup"
|
||||||
|
export * from "./datasource"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Feature, Hosting, License, PlanType, Quotas } from "../../sdk"
|
import { Feature, Hosting, License, PlanType, Quotas } from "../../sdk"
|
||||||
|
import { DeepPartial } from "../../shared"
|
||||||
import { QuotaUsage } from "../global"
|
import { QuotaUsage } from "../global"
|
||||||
|
|
||||||
export interface CreateAccount {
|
export interface CreateAccount {
|
||||||
|
@ -25,7 +26,7 @@ export const isCreatePasswordAccount = (
|
||||||
|
|
||||||
export interface LicenseOverrides {
|
export interface LicenseOverrides {
|
||||||
features?: Feature[]
|
features?: Feature[]
|
||||||
quotas?: Quotas
|
quotas?: DeepPartial<Quotas>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Account extends CreateAccount {
|
export interface Account extends CreateAccount {
|
||||||
|
@ -38,6 +39,7 @@ export interface Account extends CreateAccount {
|
||||||
// licensing
|
// licensing
|
||||||
tier: string // deprecated
|
tier: string // deprecated
|
||||||
planType?: PlanType
|
planType?: PlanType
|
||||||
|
/** @deprecated */
|
||||||
planTier?: number
|
planTier?: number
|
||||||
license?: License
|
license?: License
|
||||||
installId?: string
|
installId?: string
|
||||||
|
@ -46,6 +48,7 @@ export interface Account extends CreateAccount {
|
||||||
stripeCustomerId?: string
|
stripeCustomerId?: string
|
||||||
licenseKey?: string
|
licenseKey?: string
|
||||||
licenseKeyActivatedAt?: number
|
licenseKeyActivatedAt?: number
|
||||||
|
licenseRequestedAt?: number
|
||||||
licenseOverrides?: LicenseOverrides
|
licenseOverrides?: LicenseOverrides
|
||||||
quotaUsage?: QuotaUsage
|
quotaUsage?: QuotaUsage
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,3 +42,10 @@ export interface PaginationValues {
|
||||||
page: string | number | null
|
page: string | number | null
|
||||||
limit: number | null
|
limit: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PreviewQueryRequest extends Omit<Query, "parameters"> {
|
||||||
|
parameters: {}
|
||||||
|
flags?: {
|
||||||
|
urlName?: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -138,7 +138,6 @@ export enum Event {
|
||||||
|
|
||||||
// LICENSE
|
// LICENSE
|
||||||
LICENSE_PLAN_CHANGED = "license:plan:changed",
|
LICENSE_PLAN_CHANGED = "license:plan:changed",
|
||||||
LICENSE_TIER_CHANGED = "license:tier:changed",
|
|
||||||
LICENSE_ACTIVATED = "license:activated",
|
LICENSE_ACTIVATED = "license:activated",
|
||||||
LICENSE_PAYMENT_FAILED = "license:payment:failed",
|
LICENSE_PAYMENT_FAILED = "license:payment:failed",
|
||||||
LICENSE_PAYMENT_RECOVERED = "license:payment:recovered",
|
LICENSE_PAYMENT_RECOVERED = "license:payment:recovered",
|
||||||
|
@ -328,7 +327,6 @@ export const AuditedEventFriendlyName: Record<Event, string | undefined> = {
|
||||||
|
|
||||||
// LICENSE - NOT AUDITED
|
// LICENSE - NOT AUDITED
|
||||||
[Event.LICENSE_PLAN_CHANGED]: undefined,
|
[Event.LICENSE_PLAN_CHANGED]: undefined,
|
||||||
[Event.LICENSE_TIER_CHANGED]: undefined,
|
|
||||||
[Event.LICENSE_ACTIVATED]: undefined,
|
[Event.LICENSE_ACTIVATED]: undefined,
|
||||||
[Event.LICENSE_PAYMENT_FAILED]: undefined,
|
[Event.LICENSE_PAYMENT_FAILED]: undefined,
|
||||||
[Event.LICENSE_PAYMENT_RECOVERED]: undefined,
|
[Event.LICENSE_PAYMENT_RECOVERED]: undefined,
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import { PlanType } from "../licensing"
|
import { PlanType, PriceDuration } from "../licensing"
|
||||||
|
|
||||||
export interface LicenseTierChangedEvent {
|
|
||||||
accountId: string
|
|
||||||
from: number
|
|
||||||
to: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LicensePlanChangedEvent {
|
export interface LicensePlanChangedEvent {
|
||||||
accountId: string
|
accountId: string
|
||||||
from: PlanType
|
from: PlanType
|
||||||
to: PlanType
|
to: PlanType
|
||||||
|
// may not be on historical events
|
||||||
|
fromDuration: PriceDuration | undefined
|
||||||
|
toDuration: PriceDuration | undefined
|
||||||
|
fromQuantity: number | undefined
|
||||||
|
toQuantity: number | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LicenseActivatedEvent {
|
export interface LicenseActivatedEvent {
|
||||||
|
|
|
@ -17,7 +17,6 @@ export enum PriceDuration {
|
||||||
export interface AvailablePlan {
|
export interface AvailablePlan {
|
||||||
type: PlanType
|
type: PlanType
|
||||||
maxUsers: number
|
maxUsers: number
|
||||||
minUsers: number
|
|
||||||
prices: AvailablePrice[]
|
prices: AvailablePrice[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +37,6 @@ export interface PurchasedPlan {
|
||||||
type: PlanType
|
type: PlanType
|
||||||
model: PlanModel
|
model: PlanModel
|
||||||
usesInvoicing: boolean
|
usesInvoicing: boolean
|
||||||
minUsers: number
|
|
||||||
price?: PurchasedPrice
|
price?: PurchasedPrice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,12 +55,6 @@ export const isConstantQuota = (
|
||||||
return quotaType === QuotaType.CONSTANT
|
return quotaType === QuotaType.CONSTANT
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Minimums {
|
|
||||||
users: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PlanMinimums = { [key in PlanType]: Minimums }
|
|
||||||
|
|
||||||
export type PlanQuotas = { [key in PlanType]: Quotas | undefined }
|
export type PlanQuotas = { [key in PlanType]: Quotas | undefined }
|
||||||
|
|
||||||
export type MonthlyQuotas = {
|
export type MonthlyQuotas = {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./typeUtils"
|
|
@ -0,0 +1,3 @@
|
||||||
|
export type DeepPartial<T> = {
|
||||||
|
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
|
||||||
|
}
|
|
@ -424,7 +424,9 @@ export const inviteAccept = async (
|
||||||
if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
|
if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
|
||||||
// explicitly re-throw limit exceeded errors
|
// explicitly re-throw limit exceeded errors
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
console.warn("Error inviting user", err)
|
||||||
ctx.throw(400, "Unable to create new user, invitation invalid.")
|
ctx.throw(400, "Unable to create new user, invitation invalid.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Account, AccountMetadata } from "@budibase/types"
|
import { Account, AccountMetadata, Ctx } from "@budibase/types"
|
||||||
import * as accounts from "../../../sdk/accounts"
|
import * as accounts from "../../../sdk/accounts"
|
||||||
|
|
||||||
export const save = async (ctx: any) => {
|
export const save = async (ctx: Ctx<Account, AccountMetadata>) => {
|
||||||
const account = ctx.request.body as Account
|
const account = ctx.request.body as Account
|
||||||
let metadata: AccountMetadata = {
|
let metadata: AccountMetadata = {
|
||||||
_id: accounts.metadata.formatAccountMetadataId(account.accountId),
|
_id: accounts.metadata.formatAccountMetadataId(account.accountId),
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
cellspacing="0"
|
cellspacing="0"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
width="32px"
|
width="32"
|
||||||
|
height="32"
|
||||||
style="margin-right:16px; vertical-align: middle;"
|
style="margin-right:16px; vertical-align: middle;"
|
||||||
alt="Budibase Logo"
|
alt="Budibase Logo"
|
||||||
src="https://i.imgur.com/Xhdt1YP.png"
|
src="https://i.imgur.com/Xhdt1YP.png"
|
||||||
|
|
|
@ -18,8 +18,17 @@ export const saveMetadata = async (
|
||||||
if (existing) {
|
if (existing) {
|
||||||
metadata._rev = existing._rev
|
metadata._rev = existing._rev
|
||||||
}
|
}
|
||||||
const res = await db.put(metadata)
|
try {
|
||||||
metadata._rev = res.rev
|
const res = await db.put(metadata)
|
||||||
|
metadata._rev = res.rev
|
||||||
|
} catch (e: any) {
|
||||||
|
// account can be updated frequently
|
||||||
|
// ignore 409
|
||||||
|
if (e.status !== 409) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,10 @@ function init() {
|
||||||
const envFileJson = {
|
const envFileJson = {
|
||||||
BUDIBASE_URL: "http://localhost:10000",
|
BUDIBASE_URL: "http://localhost:10000",
|
||||||
ACCOUNT_PORTAL_URL: "http://localhost:10001",
|
ACCOUNT_PORTAL_URL: "http://localhost:10001",
|
||||||
|
ACCOUNT_PORTAL_API_KEY: "budibase",
|
||||||
BB_ADMIN_USER_EMAIL: "admin",
|
BB_ADMIN_USER_EMAIL: "admin",
|
||||||
BB_ADMIN_USER_PASSWORD: "admin",
|
BB_ADMIN_USER_PASSWORD: "admin",
|
||||||
|
LOG_LEVEL: "info",
|
||||||
}
|
}
|
||||||
let envFile = ""
|
let envFile = ""
|
||||||
Object.keys(envFileJson).forEach(key => {
|
Object.keys(envFileJson).forEach(key => {
|
||||||
|
|
|
@ -8,6 +8,7 @@ interface ApiOptions {
|
||||||
method?: APIMethod
|
method?: APIMethod
|
||||||
body?: object
|
body?: object
|
||||||
headers?: HeadersInit | undefined
|
headers?: HeadersInit | undefined
|
||||||
|
internal?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AccountInternalAPIClient {
|
export default class AccountInternalAPIClient {
|
||||||
|
@ -18,6 +19,9 @@ export default class AccountInternalAPIClient {
|
||||||
if (!env.ACCOUNT_PORTAL_URL) {
|
if (!env.ACCOUNT_PORTAL_URL) {
|
||||||
throw new Error("Must set ACCOUNT_PORTAL_URL env var")
|
throw new Error("Must set ACCOUNT_PORTAL_URL env var")
|
||||||
}
|
}
|
||||||
|
if (!env.ACCOUNT_PORTAL_API_KEY) {
|
||||||
|
throw new Error("Must set ACCOUNT_PORTAL_API_KEY env var")
|
||||||
|
}
|
||||||
this.host = `${env.ACCOUNT_PORTAL_URL}`
|
this.host = `${env.ACCOUNT_PORTAL_URL}`
|
||||||
this.state = state
|
this.state = state
|
||||||
}
|
}
|
||||||
|
@ -39,6 +43,13 @@ export default class AccountInternalAPIClient {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.internal) {
|
||||||
|
requestOptions.headers = {
|
||||||
|
...requestOptions.headers,
|
||||||
|
...{ "x-budibase-api-key": env.ACCOUNT_PORTAL_API_KEY },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const response = await fetch(`${this.host}${url}`, requestOptions)
|
const response = await fetch(`${this.host}${url}`, requestOptions)
|
||||||
|
|
||||||
|
@ -50,15 +61,20 @@ export default class AccountInternalAPIClient {
|
||||||
body = await response.text()
|
body = await response.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = `${method} ${url} - ${response.status}
|
const data = {
|
||||||
Response body: ${JSON.stringify(body)}
|
request: requestOptions.body,
|
||||||
Request body: ${requestOptions.body}`
|
response: body,
|
||||||
|
}
|
||||||
|
const message = `${method} ${url} - ${response.status}`
|
||||||
|
|
||||||
if (response.status > 499) {
|
if (response.status > 499) {
|
||||||
console.error(message)
|
console.error(message, data)
|
||||||
} else if (response.status >= 400) {
|
} else if (response.status >= 400) {
|
||||||
console.warn(message)
|
console.warn(message, data)
|
||||||
|
} else {
|
||||||
|
console.debug(message, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return [response, body]
|
return [response, body]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import AccountInternalAPIClient from "../AccountInternalAPIClient"
|
import AccountInternalAPIClient from "../AccountInternalAPIClient"
|
||||||
|
import { Account, UpdateLicenseRequest } from "@budibase/types"
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
|
||||||
export default class LicenseAPI {
|
export default class LicenseAPI {
|
||||||
client: AccountInternalAPIClient
|
client: AccountInternalAPIClient
|
||||||
|
@ -6,4 +8,18 @@ export default class LicenseAPI {
|
||||||
constructor(client: AccountInternalAPIClient) {
|
constructor(client: AccountInternalAPIClient) {
|
||||||
this.client = client
|
this.client = client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateLicense(
|
||||||
|
accountId: string,
|
||||||
|
body: UpdateLicenseRequest
|
||||||
|
): Promise<[Response, Account]> {
|
||||||
|
const [response, json] = await this.client.put(
|
||||||
|
`/api/accounts/${accountId}/license`,
|
||||||
|
{
|
||||||
|
body,
|
||||||
|
internal: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,23 @@ if (!LOADED) {
|
||||||
const env = {
|
const env = {
|
||||||
BUDIBASE_URL: process.env.BUDIBASE_URL,
|
BUDIBASE_URL: process.env.BUDIBASE_URL,
|
||||||
ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL,
|
ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL,
|
||||||
|
ACCOUNT_PORTAL_API_KEY: process.env.ACCOUNT_PORTAL_API_KEY,
|
||||||
BB_ADMIN_USER_EMAIL: process.env.BB_ADMIN_USER_EMAIL,
|
BB_ADMIN_USER_EMAIL: process.env.BB_ADMIN_USER_EMAIL,
|
||||||
BB_ADMIN_USER_PASSWORD: process.env.BB_ADMIN_USER_PASSWORD,
|
BB_ADMIN_USER_PASSWORD: process.env.BB_ADMIN_USER_PASSWORD,
|
||||||
|
POSTGRES_HOST: process.env.POSTGRES_HOST,
|
||||||
|
POSTGRES_PORT: process.env.POSTGRES_PORT,
|
||||||
|
POSTGRES_DB: process.env.POSTGRES_DB,
|
||||||
|
POSTGRES_USER: process.env.POSTGRES_USER,
|
||||||
|
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
|
||||||
|
MONGODB_CONNECTION_STRING: process.env.MONGODB_CONNECTION_STRING,
|
||||||
|
MONGODB_DB: process.env.MONGODB_DB,
|
||||||
|
REST_API_BASE_URL: process.env.REST_API_BASE_URL,
|
||||||
|
REST_API_KEY: process.env.REST_API_KEY,
|
||||||
|
MARIADB_HOST: process.env.MARIADB_HOST,
|
||||||
|
MARIADB_PORT: process.env.MARIADB_PORT,
|
||||||
|
MARIADB_DB: process.env.MARIADB_DB,
|
||||||
|
MARIADB_USER: process.env.MARIADB_USER,
|
||||||
|
MARIADB_PASSWORD: process.env.MARIADB_PASSWORD,
|
||||||
}
|
}
|
||||||
|
|
||||||
export = env
|
export = env
|
||||||
|
|
|
@ -7,6 +7,10 @@ import ScreenAPI from "./apis/ScreenAPI"
|
||||||
import SelfAPI from "./apis/SelfAPI"
|
import SelfAPI from "./apis/SelfAPI"
|
||||||
import TableAPI from "./apis/TableAPI"
|
import TableAPI from "./apis/TableAPI"
|
||||||
import UserAPI from "./apis/UserAPI"
|
import UserAPI from "./apis/UserAPI"
|
||||||
|
import DatasourcesAPI from "./apis/DatasourcesAPI"
|
||||||
|
import IntegrationsAPI from "./apis/IntegrationsAPI"
|
||||||
|
import QueriesAPI from "./apis/QueriesAPI"
|
||||||
|
import PermissionsAPI from "./apis/PermissionsAPI"
|
||||||
import BudibaseInternalAPIClient from "./BudibaseInternalAPIClient"
|
import BudibaseInternalAPIClient from "./BudibaseInternalAPIClient"
|
||||||
import { State } from "../../types"
|
import { State } from "../../types"
|
||||||
|
|
||||||
|
@ -22,6 +26,10 @@ export default class BudibaseInternalAPI {
|
||||||
self: SelfAPI
|
self: SelfAPI
|
||||||
tables: TableAPI
|
tables: TableAPI
|
||||||
users: UserAPI
|
users: UserAPI
|
||||||
|
datasources: DatasourcesAPI
|
||||||
|
integrations: IntegrationsAPI
|
||||||
|
queries: QueriesAPI
|
||||||
|
permissions: PermissionsAPI
|
||||||
|
|
||||||
constructor(state: State) {
|
constructor(state: State) {
|
||||||
this.client = new BudibaseInternalAPIClient(state)
|
this.client = new BudibaseInternalAPIClient(state)
|
||||||
|
@ -35,5 +43,9 @@ export default class BudibaseInternalAPI {
|
||||||
this.self = new SelfAPI(this.client)
|
this.self = new SelfAPI(this.client)
|
||||||
this.tables = new TableAPI(this.client)
|
this.tables = new TableAPI(this.client)
|
||||||
this.users = new UserAPI(this.client)
|
this.users = new UserAPI(this.client)
|
||||||
|
this.datasources = new DatasourcesAPI(this.client)
|
||||||
|
this.integrations = new IntegrationsAPI(this.client)
|
||||||
|
this.queries = new QueriesAPI(this.client)
|
||||||
|
this.permissions = new PermissionsAPI(this.client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ class BudibaseInternalAPIClient {
|
||||||
if (!env.BUDIBASE_URL) {
|
if (!env.BUDIBASE_URL) {
|
||||||
throw new Error("Must set BUDIBASE_URL env var")
|
throw new Error("Must set BUDIBASE_URL env var")
|
||||||
}
|
}
|
||||||
this.host = `${env.ACCOUNT_PORTAL_URL}/api`
|
|
||||||
this.host = `${env.BUDIBASE_URL}/api`
|
this.host = `${env.BUDIBASE_URL}/api`
|
||||||
this.state = state
|
this.state = state
|
||||||
}
|
}
|
||||||
|
@ -53,14 +52,18 @@ class BudibaseInternalAPIClient {
|
||||||
body = await response.text()
|
body = await response.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = `${method} ${url} - ${response.status}
|
const data = {
|
||||||
Response body: ${JSON.stringify(body)}
|
request: requestOptions.body,
|
||||||
Request body: ${requestOptions.body}`
|
response: body,
|
||||||
|
}
|
||||||
|
const message = `${method} ${url} - ${response.status}`
|
||||||
|
|
||||||
if (response.status > 499) {
|
if (response.status > 499) {
|
||||||
console.error(message)
|
console.error(message, data)
|
||||||
} else if (response.status >= 400) {
|
} else if (response.status >= 400) {
|
||||||
console.warn(message)
|
console.warn(message, data)
|
||||||
|
} else {
|
||||||
|
console.debug(message, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return [response, body]
|
return [response, body]
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
import {
|
||||||
|
Datasource,
|
||||||
|
CreateDatasourceResponse,
|
||||||
|
UpdateDatasourceResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
|
||||||
|
|
||||||
|
export default class DatasourcesAPI {
|
||||||
|
client: BudibaseInternalAPIClient
|
||||||
|
|
||||||
|
constructor(client: BudibaseInternalAPIClient) {
|
||||||
|
this.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIntegrations(): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.get(`/integrations`)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
const integrationsCount = Object.keys(json).length
|
||||||
|
expect(integrationsCount).toBe(16)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(): Promise<[Response, Datasource[]]> {
|
||||||
|
const [response, json] = await this.client.get(`/datasources`)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(json.length).toBeGreaterThan(0)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTable(dataSourceId: string): Promise<[Response, Datasource]> {
|
||||||
|
const [response, json] = await this.client.get(
|
||||||
|
`/datasources/${dataSourceId}`
|
||||||
|
)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(json._id).toEqual(dataSourceId)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(body: any): Promise<[Response, CreateDatasourceResponse]> {
|
||||||
|
const [response, json] = await this.client.post(`/datasources`, { body })
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(json.datasource._id).toBeDefined()
|
||||||
|
expect(json.datasource._rev).toBeDefined()
|
||||||
|
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(body: any): Promise<[Response, UpdateDatasourceResponse]> {
|
||||||
|
const [response, json] = await this.client.put(`/datasources/${body._id}`, {
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
expect(json.datasource._id).toBeDefined()
|
||||||
|
expect(json.datasource._rev).toBeDefined()
|
||||||
|
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async previewQuery(body: any): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.post(`/queries/preview`, {
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveQuery(body: any): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.post(`/queries`, {
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getQuery(queryId: string): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.get(`/queries/${queryId}`)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getQueryPermissions(queryId: string): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.get(`/permissions/${queryId}`)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(dataSourceId: string, revId: string): Promise<Response> {
|
||||||
|
const [response, json] = await this.client.del(
|
||||||
|
`/datasources/${dataSourceId}/${revId}`
|
||||||
|
)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
|
||||||
|
|
||||||
|
export default class IntegrationsAPI {
|
||||||
|
client: BudibaseInternalAPIClient
|
||||||
|
|
||||||
|
constructor(client: BudibaseInternalAPIClient) {
|
||||||
|
this.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.get(`/integrations`)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
const integrationsCount = Object.keys(json).length
|
||||||
|
expect(integrationsCount).toBeGreaterThan(0)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
|
||||||
|
|
||||||
|
export default class PermissionsAPI {
|
||||||
|
client: BudibaseInternalAPIClient
|
||||||
|
|
||||||
|
constructor(client: BudibaseInternalAPIClient) {
|
||||||
|
this.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(id: string): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.get(`/permissions/${id}`)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { Response } from "node-fetch"
|
||||||
|
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
|
||||||
|
import { PreviewQueryRequest, Query } from "@budibase/types"
|
||||||
|
|
||||||
|
export default class DatasourcesAPI {
|
||||||
|
client: BudibaseInternalAPIClient
|
||||||
|
|
||||||
|
constructor(client: BudibaseInternalAPIClient) {
|
||||||
|
this.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
async preview(body: PreviewQueryRequest): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.post(`/queries/preview`, {
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(body: Query): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.post(`/queries`, {
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(queryId: string): Promise<[Response, any]> {
|
||||||
|
const [response, json] = await this.client.get(`/queries/${queryId}`)
|
||||||
|
expect(response).toHaveStatusCode(200)
|
||||||
|
return [response, json]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Add information about the data source to the fixtures file from 1password
|
||||||
|
export const mongoDB = () => {
|
||||||
|
return {
|
||||||
|
datasource: {
|
||||||
|
name: "MongoDB",
|
||||||
|
source: "MONGODB",
|
||||||
|
type: "datasource",
|
||||||
|
config: {
|
||||||
|
connectionString: process.env.MONGODB_CONNECTION_STRING,
|
||||||
|
db: process.env.MONGODB_DB,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchSchema: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const postgresSQL = () => {
|
||||||
|
return {
|
||||||
|
datasource: {
|
||||||
|
name: "PostgresSQL",
|
||||||
|
plus: true,
|
||||||
|
source: "POSTGRES",
|
||||||
|
type: "datasource",
|
||||||
|
config: {
|
||||||
|
database: process.env.POSTGRES_DB,
|
||||||
|
host: process.env.POSTGRES_HOST,
|
||||||
|
password: process.env.POSTGRES_PASSWORD,
|
||||||
|
port: process.env.POSTGRES_PORT,
|
||||||
|
schema: "public",
|
||||||
|
user: process.env.POSTGRES_USER,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fetchSchema: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const mariaDB = () => {
|
||||||
|
return {
|
||||||
|
datasource: {
|
||||||
|
name: "MariaDB",
|
||||||
|
plus: true,
|
||||||
|
source: "MYSQL",
|
||||||
|
type: "datasource",
|
||||||
|
config: {
|
||||||
|
database: process.env.MARIADB_DB,
|
||||||
|
host: process.env.MARIADB_HOST,
|
||||||
|
password: process.env.MARIADB_PASSWORD,
|
||||||
|
port: process.env.MARIADB_PORT,
|
||||||
|
schema: "public",
|
||||||
|
user: process.env.MARIADB_USER,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fetchSchema: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const restAPI = () => {
|
||||||
|
return {
|
||||||
|
datasource: {
|
||||||
|
name: "RestAPI",
|
||||||
|
source: "REST",
|
||||||
|
type: "datasource",
|
||||||
|
config: {
|
||||||
|
defaultHeaders: {},
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
url: process.env.REST_API_BASE_URL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fetchSchema: false,
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,3 +4,5 @@ export * as rows from "./rows"
|
||||||
export * as screens from "./screens"
|
export * as screens from "./screens"
|
||||||
export * as tables from "./tables"
|
export * as tables from "./tables"
|
||||||
export * as users from "./users"
|
export * as users from "./users"
|
||||||
|
export * as datasources from "./datasources"
|
||||||
|
export * as queries from "./queries"
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { PreviewQueryRequest } from "@budibase/types"
|
||||||
|
|
||||||
|
const query = (datasourceId: string, fields: any): any => {
|
||||||
|
return {
|
||||||
|
datasourceId: datasourceId,
|
||||||
|
fields: fields,
|
||||||
|
name: "Query 1",
|
||||||
|
parameters: {},
|
||||||
|
queryVerb: "read",
|
||||||
|
schema: {},
|
||||||
|
transformer: "return data",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mariaDB = (datasourceId: string): PreviewQueryRequest => {
|
||||||
|
const fields = {
|
||||||
|
sql: "SELECT * FROM employees LIMIT 10;",
|
||||||
|
}
|
||||||
|
return query(datasourceId, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mongoDB = (datasourceId: string): PreviewQueryRequest => {
|
||||||
|
const fields = {
|
||||||
|
extra: {
|
||||||
|
collection: "movies",
|
||||||
|
actionType: "find",
|
||||||
|
},
|
||||||
|
json: "",
|
||||||
|
}
|
||||||
|
return query(datasourceId, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const postgres = (datasourceId: string): PreviewQueryRequest => {
|
||||||
|
const fields = {
|
||||||
|
sql: "SELECT * FROM customers;",
|
||||||
|
}
|
||||||
|
return query(datasourceId, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectedSchemaFields = {
|
||||||
|
mariaDB: {
|
||||||
|
birth_date: "string",
|
||||||
|
emp_no: "number",
|
||||||
|
first_name: "string",
|
||||||
|
gender: "string",
|
||||||
|
hire_date: "string",
|
||||||
|
last_name: "string",
|
||||||
|
},
|
||||||
|
mongoDB: {
|
||||||
|
directors: "array",
|
||||||
|
genres: "array",
|
||||||
|
image: "string",
|
||||||
|
plot: "string",
|
||||||
|
rank: "number",
|
||||||
|
rating: "number",
|
||||||
|
release_date: "string",
|
||||||
|
running_time_secs: "number",
|
||||||
|
title: "string",
|
||||||
|
year: "number",
|
||||||
|
_id: "json",
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
address: "string",
|
||||||
|
city: "string",
|
||||||
|
company_name: "string",
|
||||||
|
contact_name: "string",
|
||||||
|
contact_title: "string",
|
||||||
|
country: "string",
|
||||||
|
customer_id: "string",
|
||||||
|
fax: "string",
|
||||||
|
phone: "string",
|
||||||
|
postal_code: "string",
|
||||||
|
region: "string",
|
||||||
|
},
|
||||||
|
restAPI: {
|
||||||
|
abilities: "array",
|
||||||
|
base_experience: "number",
|
||||||
|
forms: "array",
|
||||||
|
game_indices: "array",
|
||||||
|
height: "number",
|
||||||
|
held_items: "array",
|
||||||
|
id: "number",
|
||||||
|
is_default: "string",
|
||||||
|
location_area_encounters: "string",
|
||||||
|
moves: "array",
|
||||||
|
name: "string",
|
||||||
|
order: "number",
|
||||||
|
past_types: "array",
|
||||||
|
species: "json",
|
||||||
|
sprites: "json",
|
||||||
|
stats: "array",
|
||||||
|
types: "array",
|
||||||
|
weight: "number",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = (datasourceId: string, fields: any, flags: any): any => {
|
||||||
|
return {
|
||||||
|
datasourceId: datasourceId,
|
||||||
|
fields: fields,
|
||||||
|
flags: flags,
|
||||||
|
name: "Query 1",
|
||||||
|
parameters: {},
|
||||||
|
queryVerb: "read",
|
||||||
|
schema: {},
|
||||||
|
transformer: "return data",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const restAPI = (datasourceId: string): PreviewQueryRequest => {
|
||||||
|
const fields = {
|
||||||
|
authConfigId: null,
|
||||||
|
bodyType: "none",
|
||||||
|
disabledHeaders: {},
|
||||||
|
headers: {},
|
||||||
|
pagination: {},
|
||||||
|
path: `${process.env.REST_API_BASE_URL}/pokemon/ditto`,
|
||||||
|
queryString: "",
|
||||||
|
}
|
||||||
|
const flags = {
|
||||||
|
urlName: true,
|
||||||
|
}
|
||||||
|
return request(datasourceId, fields, flags)
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
import TestConfiguration from "../../config/TestConfiguration"
|
|
||||||
import * as fixtures from "../../fixtures"
|
|
||||||
|
|
||||||
describe("Internal API - Data Sources", () => {
|
|
||||||
const config = new TestConfiguration()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.beforeAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await config.afterAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("Create an app with a data source", async () => {
|
|
||||||
// Create app
|
|
||||||
await config.createApp()
|
|
||||||
|
|
||||||
// Create Screen
|
|
||||||
const roleArray = ["BASIC", "POWER", "ADMIN", "PUBLIC"]
|
|
||||||
for (let role in roleArray) {
|
|
||||||
const [response, screen] = await config.api.screens.create(
|
|
||||||
fixtures.screens.generateScreen(roleArray[role])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import TestConfiguration from "../../config/TestConfiguration"
|
||||||
|
import * as fixtures from "../../fixtures"
|
||||||
|
import { Query } from "@budibase/types"
|
||||||
|
|
||||||
|
describe("Internal API - Data Sources: MariaDB", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Create an app with a data source - MariaDB", async () => {
|
||||||
|
// Create app
|
||||||
|
await config.createApp()
|
||||||
|
|
||||||
|
// Get all integrations
|
||||||
|
await config.api.integrations.getAll()
|
||||||
|
|
||||||
|
// Add data source
|
||||||
|
const [dataSourceResponse, dataSourceJson] =
|
||||||
|
await config.api.datasources.add(fixtures.datasources.mariaDB())
|
||||||
|
|
||||||
|
// Update data source
|
||||||
|
const newDataSourceInfo = {
|
||||||
|
...dataSourceJson.datasource,
|
||||||
|
name: "MariaDB2",
|
||||||
|
}
|
||||||
|
const [updatedDataSourceResponse, updatedDataSourceJson] =
|
||||||
|
await config.api.datasources.update(newDataSourceInfo)
|
||||||
|
|
||||||
|
// Query data source
|
||||||
|
const [queryResponse, queryJson] = await config.api.queries.preview(
|
||||||
|
fixtures.queries.mariaDB(updatedDataSourceJson.datasource._id!)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(queryJson.rows.length).toEqual(10)
|
||||||
|
expect(queryJson.schemaFields).toEqual(
|
||||||
|
fixtures.queries.expectedSchemaFields.mariaDB
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save query
|
||||||
|
const datasourcetoSave: Query = {
|
||||||
|
...fixtures.queries.mariaDB(updatedDataSourceJson.datasource._id!),
|
||||||
|
parameters: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const [saveQueryResponse, saveQueryJson] = await config.api.queries.save(
|
||||||
|
datasourcetoSave
|
||||||
|
)
|
||||||
|
// Get Query
|
||||||
|
const [getQueryResponse, getQueryJson] = await config.api.queries.get(
|
||||||
|
<string>saveQueryJson._id
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get Query permissions
|
||||||
|
const [getQueryPermissionsResponse, getQueryPermissionsJson] =
|
||||||
|
await config.api.permissions.getAll(saveQueryJson._id!)
|
||||||
|
|
||||||
|
// Delete data source
|
||||||
|
const deleteResponse = await config.api.datasources.delete(
|
||||||
|
updatedDataSourceJson.datasource._id!,
|
||||||
|
updatedDataSourceJson.datasource._rev!
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,69 @@
|
||||||
|
import TestConfiguration from "../../config/TestConfiguration"
|
||||||
|
import * as fixtures from "../../fixtures"
|
||||||
|
import { Query } from "@budibase/types"
|
||||||
|
|
||||||
|
describe("Internal API - Data Sources: MongoDB", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Create an app with a data source - MongoDB", async () => {
|
||||||
|
// Create app
|
||||||
|
await config.createApp()
|
||||||
|
|
||||||
|
// Get all integrations
|
||||||
|
await config.api.integrations.getAll()
|
||||||
|
|
||||||
|
// Add data source
|
||||||
|
const [dataSourceResponse, dataSourceJson] =
|
||||||
|
await config.api.datasources.add(fixtures.datasources.mongoDB())
|
||||||
|
|
||||||
|
// Update data source
|
||||||
|
const newDataSourceInfo = {
|
||||||
|
...dataSourceJson.datasource,
|
||||||
|
name: "MongoDB2",
|
||||||
|
}
|
||||||
|
const [updatedDataSourceResponse, updatedDataSourceJson] =
|
||||||
|
await config.api.datasources.update(newDataSourceInfo)
|
||||||
|
|
||||||
|
// Query data source
|
||||||
|
const [queryResponse, queryJson] = await config.api.queries.preview(
|
||||||
|
fixtures.queries.mongoDB(updatedDataSourceJson.datasource._id!)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(queryJson.rows.length).toBeGreaterThan(10)
|
||||||
|
expect(queryJson.schemaFields).toEqual(
|
||||||
|
fixtures.queries.expectedSchemaFields.mongoDB
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save query
|
||||||
|
const datasourcetoSave: Query = {
|
||||||
|
...fixtures.queries.mongoDB(updatedDataSourceJson.datasource._id!),
|
||||||
|
parameters: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const [saveQueryResponse, saveQueryJson] = await config.api.queries.save(
|
||||||
|
datasourcetoSave
|
||||||
|
)
|
||||||
|
// Get Query
|
||||||
|
const [getQueryResponse, getQueryJson] = await config.api.queries.get(
|
||||||
|
<string>saveQueryJson._id
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get Query permissions
|
||||||
|
const [getQueryPermissionsResponse, getQueryPermissionsJson] =
|
||||||
|
await config.api.permissions.getAll(saveQueryJson._id!)
|
||||||
|
|
||||||
|
// Delete data source
|
||||||
|
const deleteResponse = await config.api.datasources.delete(
|
||||||
|
updatedDataSourceJson.datasource._id!,
|
||||||
|
updatedDataSourceJson.datasource._rev!
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,69 @@
|
||||||
|
import TestConfiguration from "../../config/TestConfiguration"
|
||||||
|
import * as fixtures from "../../fixtures"
|
||||||
|
import { Query } from "@budibase/types"
|
||||||
|
|
||||||
|
describe("Internal API - Data Sources: PostgresSQL", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Create an app with a data source - PostgresSQL", async () => {
|
||||||
|
// Create app
|
||||||
|
await config.createApp()
|
||||||
|
|
||||||
|
// Get all integrations
|
||||||
|
await config.api.integrations.getAll()
|
||||||
|
|
||||||
|
// Add data source
|
||||||
|
const [dataSourceResponse, dataSourceJson] =
|
||||||
|
await config.api.datasources.add(fixtures.datasources.postgresSQL())
|
||||||
|
|
||||||
|
// Update data source
|
||||||
|
const newDataSourceInfo = {
|
||||||
|
...dataSourceJson.datasource,
|
||||||
|
name: "PostgresSQL2",
|
||||||
|
}
|
||||||
|
const [updatedDataSourceResponse, updatedDataSourceJson] =
|
||||||
|
await config.api.datasources.update(newDataSourceInfo)
|
||||||
|
|
||||||
|
// Query data source
|
||||||
|
const [queryResponse, queryJson] = await config.api.queries.preview(
|
||||||
|
fixtures.queries.postgres(updatedDataSourceJson.datasource._id!)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(queryJson.rows.length).toEqual(91)
|
||||||
|
expect(queryJson.schemaFields).toEqual(
|
||||||
|
fixtures.queries.expectedSchemaFields.postgres
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save query
|
||||||
|
const datasourcetoSave: Query = {
|
||||||
|
...fixtures.queries.postgres(updatedDataSourceJson.datasource._id!),
|
||||||
|
parameters: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const [saveQueryResponse, saveQueryJson] = await config.api.queries.save(
|
||||||
|
datasourcetoSave
|
||||||
|
)
|
||||||
|
// Get Query
|
||||||
|
const [getQueryResponse, getQueryJson] = await config.api.queries.get(
|
||||||
|
saveQueryJson._id!
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get Query permissions
|
||||||
|
const [getQueryPermissionsResponse, getQueryPermissionsJson] =
|
||||||
|
await config.api.permissions.getAll(saveQueryJson._id!)
|
||||||
|
|
||||||
|
// Delete data source
|
||||||
|
const deleteResponse = await config.api.datasources.delete(
|
||||||
|
updatedDataSourceJson.datasource._id!,
|
||||||
|
updatedDataSourceJson.datasource._rev!
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,69 @@
|
||||||
|
import TestConfiguration from "../../config/TestConfiguration"
|
||||||
|
import * as fixtures from "../../fixtures"
|
||||||
|
import { Query } from "@budibase/types"
|
||||||
|
|
||||||
|
describe("Internal API - Data Sources: REST API", () => {
|
||||||
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Create an app with a data source - REST API", async () => {
|
||||||
|
// Create app
|
||||||
|
await config.createApp()
|
||||||
|
|
||||||
|
// Get all integrations
|
||||||
|
await config.api.integrations.getAll()
|
||||||
|
|
||||||
|
// Add data source
|
||||||
|
const [dataSourceResponse, dataSourceJson] =
|
||||||
|
await config.api.datasources.add(fixtures.datasources.restAPI())
|
||||||
|
|
||||||
|
// Update data source
|
||||||
|
const newDataSourceInfo = {
|
||||||
|
...dataSourceJson.datasource,
|
||||||
|
name: "RestAPI - Updated",
|
||||||
|
}
|
||||||
|
const [updatedDataSourceResponse, updatedDataSourceJson] =
|
||||||
|
await config.api.datasources.update(newDataSourceInfo)
|
||||||
|
|
||||||
|
// Query data source
|
||||||
|
const [queryResponse, queryJson] = await config.api.queries.preview(
|
||||||
|
fixtures.queries.restAPI(updatedDataSourceJson.datasource._id!)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(queryJson.rows.length).toEqual(1)
|
||||||
|
expect(queryJson.schemaFields).toEqual(
|
||||||
|
fixtures.queries.expectedSchemaFields.restAPI
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save query
|
||||||
|
const datasourcetoSave: Query = {
|
||||||
|
...fixtures.queries.postgres(updatedDataSourceJson.datasource._id!),
|
||||||
|
parameters: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const [saveQueryResponse, saveQueryJson] = await config.api.queries.save(
|
||||||
|
datasourcetoSave
|
||||||
|
)
|
||||||
|
// Get Query
|
||||||
|
const [getQueryResponse, getQueryJson] = await config.api.queries.get(
|
||||||
|
saveQueryJson._id!
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get Query permissions
|
||||||
|
const [getQueryPermissionsResponse, getQueryPermissionsJson] =
|
||||||
|
await config.api.permissions.getAll(saveQueryJson._id!)
|
||||||
|
|
||||||
|
// Delete data source
|
||||||
|
const deleteResponse = await config.api.datasources.delete(
|
||||||
|
updatedDataSourceJson.datasource._id!,
|
||||||
|
updatedDataSourceJson.datasource._rev!
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,8 +1,8 @@
|
||||||
|
import { DEFAULT_TENANT_ID, logging } from "@budibase/backend-core"
|
||||||
import { AccountInternalAPI } from "../account-api"
|
import { AccountInternalAPI } from "../account-api"
|
||||||
import * as fixtures from "../internal-api/fixtures"
|
import * as fixtures from "../internal-api/fixtures"
|
||||||
import { BudibaseInternalAPI } from "../internal-api"
|
import { BudibaseInternalAPI } from "../internal-api"
|
||||||
import { DEFAULT_TENANT_ID, logging } from "@budibase/backend-core"
|
import { CreateAccountRequest, Feature } from "@budibase/types"
|
||||||
import { CreateAccountRequest } from "@budibase/types"
|
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { APIRequestOpts } from "../types"
|
import { APIRequestOpts } from "../types"
|
||||||
|
|
||||||
|
@ -22,10 +22,35 @@ async function createAccount() {
|
||||||
const account = fixtures.accounts.generateAccount()
|
const account = fixtures.accounts.generateAccount()
|
||||||
await accountsApi.accounts.validateEmail(account.email, API_OPTS)
|
await accountsApi.accounts.validateEmail(account.email, API_OPTS)
|
||||||
await accountsApi.accounts.validateTenantId(account.tenantId, API_OPTS)
|
await accountsApi.accounts.validateTenantId(account.tenantId, API_OPTS)
|
||||||
await accountsApi.accounts.create(account, API_OPTS)
|
const [res, newAccount] = await accountsApi.accounts.create(account, API_OPTS)
|
||||||
|
await updateLicense(newAccount.accountId)
|
||||||
return account
|
return account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UNLIMITED = { value: -1 }
|
||||||
|
|
||||||
|
async function updateLicense(accountId: string) {
|
||||||
|
await accountsApi.licenses.updateLicense(accountId, {
|
||||||
|
overrides: {
|
||||||
|
// add all features
|
||||||
|
features: Object.values(Feature),
|
||||||
|
quotas: {
|
||||||
|
usage: {
|
||||||
|
monthly: {
|
||||||
|
automations: UNLIMITED,
|
||||||
|
},
|
||||||
|
static: {
|
||||||
|
rows: UNLIMITED,
|
||||||
|
users: UNLIMITED,
|
||||||
|
userGroups: UNLIMITED,
|
||||||
|
plugins: UNLIMITED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function loginAsAdmin() {
|
async function loginAsAdmin() {
|
||||||
const username = env.BB_ADMIN_USER_EMAIL!
|
const username = env.BB_ADMIN_USER_EMAIL!
|
||||||
const password = env.BB_ADMIN_USER_PASSWORD!
|
const password = env.BB_ADMIN_USER_PASSWORD!
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
import { logging } from "@budibase/backend-core"
|
||||||
|
logging.LOG_CONTEXT = false
|
||||||
|
|
||||||
jest.setTimeout(60000)
|
jest.setTimeout(60000)
|
||||||
|
|
|
@ -51,14 +51,18 @@ class BudibasePublicAPIClient {
|
||||||
body = await response.text()
|
body = await response.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = `${method} ${url} - ${response.status}
|
const data = {
|
||||||
Response body: ${JSON.stringify(body)}
|
request: requestOptions.body,
|
||||||
Request body: ${requestOptions.body}`
|
response: body,
|
||||||
|
}
|
||||||
|
const message = `${method} ${url} - ${response.status}`
|
||||||
|
|
||||||
if (response.status > 499) {
|
if (response.status > 499) {
|
||||||
console.error(message)
|
console.error(message, data)
|
||||||
} else if (response.status >= 400) {
|
} else if (response.status >= 400) {
|
||||||
console.warn(message)
|
console.warn(message, data)
|
||||||
|
} else {
|
||||||
|
console.debug(message, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return [response, body]
|
return [response, body]
|
||||||
|
|
|
@ -3,9 +3,6 @@ import { AccountInternalAPI } from "../account-api"
|
||||||
import { CreateAppRequest, State } from "../types"
|
import { CreateAppRequest, State } from "../types"
|
||||||
import * as fixtures from "../internal-api/fixtures"
|
import * as fixtures from "../internal-api/fixtures"
|
||||||
|
|
||||||
// TEMP
|
|
||||||
import setup from "../jest/globalSetup"
|
|
||||||
|
|
||||||
export default class BudibaseTestConfiguration {
|
export default class BudibaseTestConfiguration {
|
||||||
// apis
|
// apis
|
||||||
internalApi: BudibaseInternalAPI
|
internalApi: BudibaseInternalAPI
|
||||||
|
@ -23,11 +20,6 @@ export default class BudibaseTestConfiguration {
|
||||||
// LIFECYCLE
|
// LIFECYCLE
|
||||||
|
|
||||||
async beforeAll() {
|
async beforeAll() {
|
||||||
// TEMP - move back to single tenant when we integrate licensing with
|
|
||||||
// the test run - need to use multiple tenants in cloud to get around
|
|
||||||
// app limit restrictions
|
|
||||||
await setup()
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.state.tenantId = global.qa.tenantId
|
this.state.tenantId = global.qa.tenantId
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -8,6 +8,7 @@ export * from "./responseMessage"
|
||||||
export * from "./routing"
|
export * from "./routing"
|
||||||
export * from "./state"
|
export * from "./state"
|
||||||
export * from "./unpublishAppResponse"
|
export * from "./unpublishAppResponse"
|
||||||
|
export * from "./addedDatasource"
|
||||||
|
|
||||||
// re-export public api types
|
// re-export public api types
|
||||||
export * from "@budibase/server/api/controllers/public/mapping/types"
|
export * from "@budibase/server/api/controllers/public/mapping/types"
|
||||||
|
|
97
yarn.lock
97
yarn.lock
|
@ -9518,13 +9518,6 @@ dir-glob@^3.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-type "^4.0.0"
|
path-type "^4.0.0"
|
||||||
|
|
||||||
docker-compose@0.23.12:
|
|
||||||
version "0.23.12"
|
|
||||||
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.12.tgz#fa883b98be08f6926143d06bf9e522ef7ed3210c"
|
|
||||||
integrity sha512-KFbSMqQBuHjTGZGmYDOCO0L4SaML3BsWTId5oSUyaBa22vALuFHNv+UdDWs3HcMylHWKsxCbLB7hnM/nCosWZw==
|
|
||||||
dependencies:
|
|
||||||
yaml "^1.10.2"
|
|
||||||
|
|
||||||
docker-compose@0.23.17:
|
docker-compose@0.23.17:
|
||||||
version "0.23.17"
|
version "0.23.17"
|
||||||
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.17.tgz#8816bef82562d9417dc8c790aa4871350f93a2ba"
|
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.17.tgz#8816bef82562d9417dc8c790aa4871350f93a2ba"
|
||||||
|
@ -9532,6 +9525,13 @@ docker-compose@0.23.17:
|
||||||
dependencies:
|
dependencies:
|
||||||
yaml "^1.10.2"
|
yaml "^1.10.2"
|
||||||
|
|
||||||
|
docker-compose@0.24.0:
|
||||||
|
version "0.24.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.24.0.tgz#474cd38b05b3887a56ffb2c39f16a7ae4d7d5077"
|
||||||
|
integrity sha512-RN/oSPLPa6ZG5e4dHg8tD8EMpd1WJqomNMBQT+d2M5MwcmfrPW/xHTent4TVqX0zZvHemv7qhhNlzXjxCnFaQw==
|
||||||
|
dependencies:
|
||||||
|
yaml "^1.10.2"
|
||||||
|
|
||||||
docker-compose@^0.23.5:
|
docker-compose@^0.23.5:
|
||||||
version "0.23.19"
|
version "0.23.19"
|
||||||
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.19.tgz#9947726e2fe67bdfa9e8efe1ff15aa0de2e10eb8"
|
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.19.tgz#9947726e2fe67bdfa9e8efe1ff15aa0de2e10eb8"
|
||||||
|
@ -11102,7 +11102,7 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||||
|
|
||||||
fast-redact@^3.0.0:
|
fast-redact@^3.0.0, fast-redact@^3.1.1:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa"
|
resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa"
|
||||||
integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==
|
integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==
|
||||||
|
@ -19183,7 +19183,7 @@ pinkie@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||||
integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
|
integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
|
||||||
|
|
||||||
pino-abstract-transport@^1.0.0:
|
pino-abstract-transport@^1.0.0, pino-abstract-transport@v1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3"
|
resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3"
|
||||||
integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==
|
integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==
|
||||||
|
@ -19199,6 +19199,16 @@ pino-abstract-transport@v0.5.0:
|
||||||
duplexify "^4.1.2"
|
duplexify "^4.1.2"
|
||||||
split2 "^4.0.0"
|
split2 "^4.0.0"
|
||||||
|
|
||||||
|
pino-http@8.3.3:
|
||||||
|
version "8.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/pino-http/-/pino-http-8.3.3.tgz#2b140e734bfc6babe0df272a43bb8f36f2b525c0"
|
||||||
|
integrity sha512-p4umsNIXXVu95HD2C8wie/vXH7db5iGRpc+yj1/ZQ3sRtTQLXNjoS6Be5+eI+rQbqCRxen/7k/KSN+qiZubGDw==
|
||||||
|
dependencies:
|
||||||
|
get-caller-file "^2.0.5"
|
||||||
|
pino "^8.0.0"
|
||||||
|
pino-std-serializers "^6.0.0"
|
||||||
|
process-warning "^2.0.0"
|
||||||
|
|
||||||
pino-http@^6.5.0:
|
pino-http@^6.5.0:
|
||||||
version "6.6.0"
|
version "6.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/pino-http/-/pino-http-6.6.0.tgz#d0a1deacada8c93327fdaa48f5bdc94bc43d3407"
|
resolved "https://registry.yarnpkg.com/pino-http/-/pino-http-6.6.0.tgz#d0a1deacada8c93327fdaa48f5bdc94bc43d3407"
|
||||||
|
@ -19244,7 +19254,42 @@ pino-std-serializers@^5.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-5.6.0.tgz#31b141155d6520967c5ec72944d08fb45c490fd3"
|
resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-5.6.0.tgz#31b141155d6520967c5ec72944d08fb45c490fd3"
|
||||||
integrity sha512-VdUXCw8gO+xhir7sFuoYSjTnzB+TMDGxhAC/ph3YS3sdHnXNdsK0wMtADNUltfeGkn2KDxEM21fnjF3RwXyC8A==
|
integrity sha512-VdUXCw8gO+xhir7sFuoYSjTnzB+TMDGxhAC/ph3YS3sdHnXNdsK0wMtADNUltfeGkn2KDxEM21fnjF3RwXyC8A==
|
||||||
|
|
||||||
pino@7.11.0, pino@^7.5.0:
|
pino-std-serializers@^6.0.0:
|
||||||
|
version "6.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.0.tgz#169048c0df3f61352fce56aeb7fb962f1b66ab43"
|
||||||
|
integrity sha512-IWgSzUL8X1w4BIWTwErRgtV8PyOGOOi60uqv0oKuS/fOA8Nco/OeI6lBuc4dyP8MMfdFwyHqTMcBIA7nDiqEqA==
|
||||||
|
|
||||||
|
pino@8.11.0, pino@^8.0.0:
|
||||||
|
version "8.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pino/-/pino-8.11.0.tgz#2a91f454106b13e708a66c74ebc1c2ab7ab38498"
|
||||||
|
integrity sha512-Z2eKSvlrl2rH8p5eveNUnTdd4AjJk8tAsLkHYZQKGHP4WTh2Gi1cOSOs3eWPqaj+niS3gj4UkoreoaWgF3ZWYg==
|
||||||
|
dependencies:
|
||||||
|
atomic-sleep "^1.0.0"
|
||||||
|
fast-redact "^3.1.1"
|
||||||
|
on-exit-leak-free "^2.1.0"
|
||||||
|
pino-abstract-transport v1.0.0
|
||||||
|
pino-std-serializers "^6.0.0"
|
||||||
|
process-warning "^2.0.0"
|
||||||
|
quick-format-unescaped "^4.0.3"
|
||||||
|
real-require "^0.2.0"
|
||||||
|
safe-stable-stringify "^2.3.1"
|
||||||
|
sonic-boom "^3.1.0"
|
||||||
|
thread-stream "^2.0.0"
|
||||||
|
|
||||||
|
pino@^6.11.2:
|
||||||
|
version "6.14.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pino/-/pino-6.14.0.tgz#b745ea87a99a6c4c9b374e4f29ca7910d4c69f78"
|
||||||
|
integrity sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==
|
||||||
|
dependencies:
|
||||||
|
fast-redact "^3.0.0"
|
||||||
|
fast-safe-stringify "^2.0.8"
|
||||||
|
flatstr "^1.0.12"
|
||||||
|
pino-std-serializers "^3.1.0"
|
||||||
|
process-warning "^1.0.0"
|
||||||
|
quick-format-unescaped "^4.0.3"
|
||||||
|
sonic-boom "^1.0.2"
|
||||||
|
|
||||||
|
pino@^7.5.0:
|
||||||
version "7.11.0"
|
version "7.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/pino/-/pino-7.11.0.tgz#0f0ea5c4683dc91388081d44bff10c83125066f6"
|
resolved "https://registry.yarnpkg.com/pino/-/pino-7.11.0.tgz#0f0ea5c4683dc91388081d44bff10c83125066f6"
|
||||||
integrity sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==
|
integrity sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==
|
||||||
|
@ -19261,19 +19306,6 @@ pino@7.11.0, pino@^7.5.0:
|
||||||
sonic-boom "^2.2.1"
|
sonic-boom "^2.2.1"
|
||||||
thread-stream "^0.15.1"
|
thread-stream "^0.15.1"
|
||||||
|
|
||||||
pino@^6.11.2:
|
|
||||||
version "6.14.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/pino/-/pino-6.14.0.tgz#b745ea87a99a6c4c9b374e4f29ca7910d4c69f78"
|
|
||||||
integrity sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==
|
|
||||||
dependencies:
|
|
||||||
fast-redact "^3.0.0"
|
|
||||||
fast-safe-stringify "^2.0.8"
|
|
||||||
flatstr "^1.0.12"
|
|
||||||
pino-std-serializers "^3.1.0"
|
|
||||||
process-warning "^1.0.0"
|
|
||||||
quick-format-unescaped "^4.0.3"
|
|
||||||
sonic-boom "^1.0.2"
|
|
||||||
|
|
||||||
pirates@^4.0.1, pirates@^4.0.4:
|
pirates@^4.0.1, pirates@^4.0.4:
|
||||||
version "4.0.5"
|
version "4.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
|
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
|
||||||
|
@ -20149,6 +20181,11 @@ process-warning@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616"
|
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616"
|
||||||
integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==
|
integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==
|
||||||
|
|
||||||
|
process-warning@^2.0.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.2.0.tgz#008ec76b579820a8e5c35d81960525ca64feb626"
|
||||||
|
integrity sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==
|
||||||
|
|
||||||
process@^0.11.10:
|
process@^0.11.10:
|
||||||
version "0.11.10"
|
version "0.11.10"
|
||||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||||
|
@ -20752,6 +20789,11 @@ real-require@^0.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381"
|
resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381"
|
||||||
integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==
|
integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==
|
||||||
|
|
||||||
|
real-require@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78"
|
||||||
|
integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==
|
||||||
|
|
||||||
recast@^0.10.1:
|
recast@^0.10.1:
|
||||||
version "0.10.43"
|
version "0.10.43"
|
||||||
resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f"
|
resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f"
|
||||||
|
@ -22138,7 +22180,7 @@ sonic-boom@^2.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
atomic-sleep "^1.0.0"
|
atomic-sleep "^1.0.0"
|
||||||
|
|
||||||
sonic-boom@^3.0.0:
|
sonic-boom@^3.0.0, sonic-boom@^3.1.0:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.3.0.tgz#cffab6dafee3b2bcb88d08d589394198bee1838c"
|
resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.3.0.tgz#cffab6dafee3b2bcb88d08d589394198bee1838c"
|
||||||
integrity sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==
|
integrity sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==
|
||||||
|
@ -23310,6 +23352,13 @@ thread-stream@^0.15.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
real-require "^0.1.0"
|
real-require "^0.1.0"
|
||||||
|
|
||||||
|
thread-stream@^2.0.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.3.0.tgz#4fc07fb39eff32ae7bad803cb7dd9598349fed33"
|
||||||
|
integrity sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==
|
||||||
|
dependencies:
|
||||||
|
real-require "^0.2.0"
|
||||||
|
|
||||||
throat@^5.0.0:
|
throat@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
|
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
|
||||||
|
|
Loading…
Reference in New Issue