Merge branch 'develop' of github.com:Budibase/budibase into group-fixes

This commit is contained in:
mike12345567 2022-09-22 18:44:16 +01:00
commit 9b29bd6709
72 changed files with 1307 additions and 455 deletions

24
.github/ISSUE_TEMPLATE/epic.md vendored Normal file
View File

@ -0,0 +1,24 @@
---
name: Epic
about: Plan a new project
title: ''
labels: epic
assignees: ''
---
## Description
Brief summary of what this Epic is, whether it's a larger project, goal, or user story. Describe the job to be done, which persona this Epic is mainly for, or if more multiple, break it down by user and job story.
## Spec
Link to confluence spec
## Teams and Stakeholders
Describe who needs to be kept up-to-date about this Epic, included in discussions, or updated along the way. Stakeholders can be both in Product/Engineering, as well as other teams like Customer Success who might want to keep customers updated on the Epic project.
## Workflow
- [ ] Spec Created and pasted above
- [ ] Product Review
- [ ] Designs created
- [ ] Individual Tasks created and assigned to Epic

View File

@ -76,6 +76,7 @@ affinity: {}
globals:
appVersion: "latest"
budibaseEnv: PRODUCTION
tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS"
enableAnalytics: "1"
sentryDSN: ""
posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU"

View File

@ -1,6 +1,6 @@
#!/bin/bash
declare -a ENV_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "DATA_DIR" "MINIO_ACCESS_KEY" "MINIO_SECRET_KEY" "INTERNAL_API_KEY" "JWT_SECRET" "REDIS_PASSWORD")
declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONMENT" "CLUSTER_PORT" "DEPLOYMENT_ENVIRONMENT" "MINIO_URL" "NODE_ENV" "POSTHOG_TOKEN" "REDIS_URL" "SELF_HOSTED" "WORKER_PORT" "WORKER_URL")
declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONMENT" "CLUSTER_PORT" "DEPLOYMENT_ENVIRONMENT" "MINIO_URL" "NODE_ENV" "POSTHOG_TOKEN" "REDIS_URL" "SELF_HOSTED" "WORKER_PORT" "WORKER_URL" "TENANT_FEATURE_FLAGS")
# Check the env vars set in Dockerfile have come through, AAS seems to drop them
[[ -z "${APP_PORT}" ]] && export APP_PORT=4001
[[ -z "${ARCHITECTURE}" ]] && export ARCHITECTURE=amd
@ -10,6 +10,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME
[[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS"
[[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379
[[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1
[[ -z "${WORKER_PORT}" ]] && export WORKER_PORT=4002

View File

@ -1,5 +1,5 @@
{
"version": "1.3.21-alpha.0",
"version": "1.4.8-alpha.3",
"npmClient": "yarn",
"packages": [
"packages/*"

View File

@ -13,6 +13,7 @@
"js-yaml": "^4.1.0",
"kill-port": "^1.6.1",
"lerna": "3.14.1",
"madge": "^5.0.1",
"prettier": "^2.3.1",
"prettier-plugin-svelte": "^2.3.0",
"rimraf": "^3.0.2",
@ -25,6 +26,7 @@
"bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh",
"build": "lerna run build",
"build:dev": "lerna run prebuild && tsc --build --watch --preserveWatchOutput",
"deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular",
"release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro",
"release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop --exact && yarn release:pro:develop",
"release:pro": "bash scripts/pro/release.sh",

View File

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

View File

@ -2,7 +2,7 @@ import env from "../environment"
import { SEPARATOR, DocumentType } from "../db/constants"
import cls from "./FunctionContext"
import { dangerousGetDB, closeDB } from "../db"
import { baseGlobalDBName } from "../tenancy/utils"
import { baseGlobalDBName } from "../db/tenancy"
import { IdentityContext } from "@budibase/types"
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
import { ContextKey } from "./constants"

View File

@ -45,6 +45,7 @@ export enum DocumentType {
DEV_INFO = "devinfo",
AUTOMATION_LOG = "log_au",
ACCOUNT_METADATA = "acc_metadata",
PLUGIN = "plg",
}
export const StaticDatabases = {

View File

@ -1,5 +1,5 @@
import { DEFAULT_TENANT_ID } from "../constants"
import { StaticDatabases, SEPARATOR } from "../db/constants"
import { StaticDatabases, SEPARATOR } from "./constants"
import { getTenantId } from "../context"
export const getGlobalDBName = (tenantId?: string) => {

View File

@ -3,7 +3,7 @@ import { DEFAULT_TENANT_ID, Configs } from "../constants"
import env from "../environment"
import { SEPARATOR, DocumentType, UNICODE_MAX, ViewName } from "./constants"
import { getTenantId, getGlobalDB } from "../context"
import { getGlobalDBName } from "../tenancy"
import { getGlobalDBName } from "./tenancy"
import fetch from "node-fetch"
import { doWithDB, allDbs } from "./index"
import { getCouchInfo } from "./pouch"
@ -16,6 +16,7 @@ import * as events from "../events"
export * from "./constants"
export * from "./conversions"
export { default as Replication } from "./Replication"
export * from "./tenancy"
/**
* Generates a new app ID.
@ -367,6 +368,21 @@ export const generateDevInfoID = (userId: any) => {
return `${DocumentType.DEV_INFO}${SEPARATOR}${userId}`
}
/**
* Generates a new plugin ID - to be used in the global DB.
* @returns {string} The new plugin ID which a plugin metadata document can be stored under.
*/
export const generatePluginID = (name: string) => {
return `${DocumentType.PLUGIN}${SEPARATOR}${name}`
}
/**
* Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
*/
export const getPluginParams = (pluginId?: string | null, otherProps = {}) => {
return getDocParams(DocumentType.PLUGIN, pluginId, otherProps)
}
/**
* Returns the most granular configuration document from the DB based on the type, workspace and userID passed.
* @param {Object} db - db instance to query

View File

@ -23,9 +23,11 @@ export default class LoggingProcessor implements EventProcessor {
return
}
let timestampString = getTimestampString(timestamp)
console.log(
`[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} `
)
let message = `[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} `
if (env.isDev()) {
message = message + `[debug: [properties=${JSON.stringify(properties)}] ]`
}
console.log(message)
}
async identify(identity: Identity, timestamp?: string | number) {

View File

@ -1,27 +1,78 @@
import { publishEvent } from "../events"
import {
Event,
License,
LicenseActivatedEvent,
LicenseDowngradedEvent,
LicenseUpdatedEvent,
LicenseUpgradedEvent,
LicensePlanChangedEvent,
LicenseTierChangedEvent,
PlanType,
Account,
LicensePortalOpenedEvent,
LicenseCheckoutSuccessEvent,
LicenseCheckoutOpenedEvent,
LicensePaymentFailedEvent,
LicensePaymentRecoveredEvent,
} from "@budibase/types"
// TODO
export async function updgraded(license: License) {
const properties: LicenseUpgradedEvent = {}
await publishEvent(Event.LICENSE_UPGRADED, properties)
export async function tierChanged(account: Account, from: number, to: number) {
const properties: LicenseTierChangedEvent = {
accountId: account.accountId,
to,
from,
}
await publishEvent(Event.LICENSE_TIER_CHANGED, properties)
}
// TODO
export async function downgraded(license: License) {
const properties: LicenseDowngradedEvent = {}
await publishEvent(Event.LICENSE_DOWNGRADED, properties)
export async function planChanged(
account: Account,
from: PlanType,
to: PlanType
) {
const properties: LicensePlanChangedEvent = {
accountId: account.accountId,
to,
from,
}
await publishEvent(Event.LICENSE_PLAN_CHANGED, properties)
}
// TODO
export async function activated(license: License) {
const properties: LicenseActivatedEvent = {}
export async function activated(account: Account) {
const properties: LicenseActivatedEvent = {
accountId: account.accountId,
}
await publishEvent(Event.LICENSE_ACTIVATED, properties)
}
export async function checkoutOpened(account: Account) {
const properties: LicenseCheckoutOpenedEvent = {
accountId: account.accountId,
}
await publishEvent(Event.LICENSE_CHECKOUT_OPENED, properties)
}
export async function checkoutSuccess(account: Account) {
const properties: LicenseCheckoutSuccessEvent = {
accountId: account.accountId,
}
await publishEvent(Event.LICENSE_CHECKOUT_SUCCESS, properties)
}
export async function portalOpened(account: Account) {
const properties: LicensePortalOpenedEvent = {
accountId: account.accountId,
}
await publishEvent(Event.LICENSE_PORTAL_OPENED, properties)
}
export async function paymentFailed(account: Account) {
const properties: LicensePaymentFailedEvent = {
accountId: account.accountId,
}
await publishEvent(Event.LICENSE_PAYMENT_FAILED, properties)
}
export async function paymentRecovered(account: Account) {
const properties: LicensePaymentRecoveredEvent = {
accountId: account.accountId,
}
await publishEvent(Event.LICENSE_PAYMENT_RECOVERED, properties)
}

View File

@ -3,12 +3,8 @@ import { doWithDB } from "../db"
import { DocumentType, StaticDatabases } from "../db/constants"
import { getAllApps } from "../db/utils"
import environment from "../environment"
import {
doInTenant,
getTenantIds,
getGlobalDBName,
getTenantId,
} from "../tenancy"
import { doInTenant, getTenantIds, getTenantId } from "../tenancy"
import { getGlobalDBName } from "../db/tenancy"
import * as context from "../context"
import { DEFINITIONS } from "."
import {

View File

@ -75,6 +75,15 @@ function validateDatasource(schema) {
})
.unknown(true)
.required(),
extra: joi.object().pattern(
joi.string(),
joi.object({
type: joi.string().required(),
displayName: joi.string().required(),
required: joi.boolean(),
data: joi.object(),
})
),
}),
})
runJoi(validator, schema)

View File

@ -1,11 +1,9 @@
import * as context from "../context"
import * as tenancy from "./tenancy"
import * as utils from "./utils"
const pkg = {
...context,
...tenancy,
...utils,
}
export = pkg

View File

@ -1,7 +1,7 @@
import { doWithDB } from "../db"
import { queryPlatformView } from "../db/views"
import { StaticDatabases, ViewName } from "../db/constants"
import { getGlobalDBName } from "./utils"
import { getGlobalDBName } from "../db/tenancy"
import {
getTenantId,
DEFAULT_TENANT_ID,
@ -9,7 +9,7 @@ import {
getTenantIDFromAppID,
} from "../context"
import env from "../environment"
import { PlatformUser, PlatformUserByEmail } from "@budibase/types"
import { PlatformUser } from "@budibase/types"
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name

View File

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

View File

@ -2,7 +2,7 @@ import filterTests from "../support/filterTests"
const interact = require("../support/interact")
filterTests(["all"], () => {
context("Create Components", () => {
xcontext("Create Components", () => {
let headlineId
before(() => {

View File

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

View File

@ -9,14 +9,14 @@ import {
import { store } from "builderStore"
import {
queries as queriesStores,
roles as rolesStore,
tables as tablesStore,
roles as rolesStore,
} from "stores/backend"
import {
makePropSafe,
isJSBinding,
decodeJSBinding,
encodeJSBinding,
isJSBinding,
makePropSafe,
} from "@budibase/string-templates"
import { TableNames } from "../constants"
import { JSONUtils } from "@budibase/frontend-core"
@ -71,17 +71,19 @@ export const getAuthBindings = () => {
runtime: `${safeUser}.${safeOAuth2}.${safeAccessToken}`,
readable: `Current User.OAuthToken`,
key: "accessToken",
display: { name: "OAuthToken" },
},
]
bindings = Object.keys(authBindings).map(key => {
const fieldBinding = authBindings[key]
bindings = authBindings.map(fieldBinding => {
return {
type: "context",
runtimeBinding: fieldBinding.runtime,
readableBinding: fieldBinding.readable,
fieldSchema: { type: "string", name: fieldBinding.key },
providerId: "user",
category: "Current User",
display: fieldBinding.display,
}
})
return bindings
@ -93,7 +95,7 @@ export const getAuthBindings = () => {
* @param {string} prefix A contextual string prefix/path for a user readable binding
* @return {object[]} An array containing readable/runtime binding objects
*/
export const toBindingsArray = (valueMap, prefix) => {
export const toBindingsArray = (valueMap, prefix, category) => {
if (!valueMap) {
return []
}
@ -101,11 +103,20 @@ export const toBindingsArray = (valueMap, prefix) => {
if (!binding || !valueMap[binding]) {
return acc
}
acc.push({
let config = {
type: "context",
runtimeBinding: binding,
readableBinding: `${prefix}.${binding}`,
})
icon: "Brackets",
}
if (category) {
config.category = category
}
acc.push(config)
return acc
}, [])
}
@ -382,21 +393,25 @@ export const getUserBindings = () => {
const { schema } = getSchemaForTable(TableNames.USERS)
const keys = Object.keys(schema).sort()
const safeUser = makePropSafe("user")
keys.forEach(key => {
bindings = keys.reduce((acc, key) => {
const fieldSchema = schema[key]
bindings.push({
type: "context",
runtimeBinding: `${safeUser}.${makePropSafe(key)}`,
readableBinding: `Current User.${key}`,
// Field schema and provider are required to construct relationship
// datasource options, based on bindable properties
fieldSchema,
providerId: "user",
category: "Current User",
icon: "User",
display: fieldSchema,
})
})
if (fieldSchema.type !== "link") {
acc.push({
type: "context",
runtimeBinding: `${safeUser}.${makePropSafe(key)}`,
readableBinding: `Current User.${key}`,
// Field schema and provider are required to construct relationship
// datasource options, based on bindable properties
fieldSchema,
providerId: "user",
category: "Current User",
icon: "User",
})
}
return acc
}, [])
return bindings
}

View File

@ -97,7 +97,7 @@
backgroundColour={templateEntry.background}
icon={templateEntry.icon}
>
{#if $licensing?.usageMetrics?.apps < 100}
{#if !($licensing?.usageMetrics?.apps >= 100)}
<Button
cta
on:click={() => {

View File

@ -1,20 +1,31 @@
<script>
import { Body, Button, Heading, Layout } from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import { getUserBindings } from "builderStore/dataBinding"
import { Body, Button, Heading, Icon, Input, Layout } from "@budibase/bbui"
import {
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "builderStore/dataBinding"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
export let bindable = true
export let queryBindings = []
const userBindings = getUserBindings()
let internalBindings = queryBindings.reduce((acc, binding) => {
acc[binding.name] = binding.default
return acc
}, {})
export let bindings = []
export let customParams = {}
function newQueryBinding() {
queryBindings = [...queryBindings, {}]
}
function deleteQueryBinding(idx) {
queryBindings.splice(idx, 1)
queryBindings = queryBindings
}
// This is necessary due to the way readable and writable bindings are stored.
// The readable binding in the UI gets converted to a UUID value that the client understands
// for parsing, then converted back so we can display it the readable form in the UI
function onBindingChange(param, valueToParse) {
customParams[param] = readableToRuntimeBinding(bindings, valueToParse)
}
</script>
<Layout noPadding={bindable} gap="S">
@ -35,34 +46,57 @@
{/if}
</Body>
<div class="bindings" class:bindable>
<KeyValueBuilder
bind:object={internalBindings}
tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query"
name="binding"
headings
keyPlaceholder="Binding name"
valuePlaceholder="Default"
bindings={[...userBindings]}
bindingDrawerLeft="260px"
on:change={e => {
queryBindings = e.detail.map(binding => {
return {
name: binding.name,
default: binding.value,
}
})
}}
/>
{#each queryBindings as binding, idx}
<Input
placeholder="Binding Name"
thin
disabled={bindable}
bind:value={binding.name}
/>
<Input
placeholder="Default"
thin
disabled={bindable}
on:change={evt => onBindingChange(binding.name, evt.detail)}
bind:value={binding.default}
/>
{#if bindable}
<DrawerBindableInput
title={`Query binding "${binding.name}"`}
placeholder="Value"
thin
on:change={evt => onBindingChange(binding.name, evt.detail)}
value={runtimeToReadableBinding(
bindings,
customParams?.[binding.name]
)}
{bindings}
/>
{:else}
<Icon hoverable name="Close" on:click={() => deleteQueryBinding(idx)} />
{/if}
{/each}
</div>
</Layout>
<style>
.bindings.bindable {
grid-template-columns: 1fr 1fr 1fr;
}
.controls {
display: flex;
align-items: center;
justify-content: space-between;
}
.bindings {
display: grid;
grid-template-columns: 1fr 1fr 5%;
grid-gap: 10px;
align-items: center;
}
.height {
height: 40px;
}

View File

@ -17,7 +17,7 @@
import ExtraQueryConfig from "./ExtraQueryConfig.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte"
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
import BindingBuilder from "components/integration/QueryViewerBindingBuilder.svelte"
import { datasources, integrations, queries } from "stores/backend"
import { capitalise } from "../../helpers"
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"

View File

@ -0,0 +1,69 @@
<script>
import { Body, Button, Heading, Layout } from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import { getUserBindings } from "builderStore/dataBinding"
export let bindable = true
export let queryBindings = []
const userBindings = getUserBindings()
let internalBindings = queryBindings.reduce((acc, binding) => {
acc[binding.name] = binding.default
return acc
}, {})
function newQueryBinding() {
queryBindings = [...queryBindings, {}]
}
</script>
<Layout noPadding={bindable} gap="S">
<div class="controls" class:height={!bindable}>
<Heading size="XS">Bindings</Heading>
{#if !bindable}
<Button secondary on:click={newQueryBinding}>Add Binding</Button>
{/if}
</div>
<Body size="S">
{#if !bindable}
Bindings come in two parts: the binding name, and a default/fallback
value. These bindings can be used as Handlebars expressions throughout the
query.
{:else}
Enter a value for each binding. The default values will be used for any
values left blank.
{/if}
</Body>
<div class="bindings" class:bindable>
<KeyValueBuilder
bind:object={internalBindings}
tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query"
name="binding"
headings
keyPlaceholder="Binding name"
valuePlaceholder="Default"
bindings={[...userBindings]}
bindingDrawerLeft="260px"
on:change={e => {
queryBindings = e.detail.map(binding => {
return {
name: binding.name,
default: binding.value,
}
})
}}
/>
</div>
</Layout>
<style>
.controls {
display: flex;
align-items: center;
justify-content: space-between;
}
.height {
height: 40px;
}
</style>

View File

@ -8,6 +8,7 @@
import { ExpiringKeys } from "./constants"
import { getBanners } from "./licensingBanners"
import { banner } from "@budibase/bbui"
import { FEATURE_FLAGS, isEnabled } from "../../../helpers/featureFlags"
const oneDayInSeconds = 86400
@ -81,7 +82,12 @@
}
}
$: if (userLoaded && licensingLoaded && loaded) {
$: if (
userLoaded &&
licensingLoaded &&
loaded &&
isEnabled(FEATURE_FLAGS.LICENSING)
) {
queuedModals = processModals()
queuedBanners = getBanners()
showNextModal()

View File

@ -60,14 +60,20 @@
$: staticVariables = datasource?.config?.staticVariables || {}
$: customRequestBindings = toBindingsArray(requestBindings, "Binding")
$: customRequestBindings = toBindingsArray(
requestBindings,
"Binding",
"Bindings"
)
$: globalDynamicRequestBindings = toBindingsArray(
globalDynamicBindings,
"Dynamic",
"Dynamic"
)
$: dataSourceStaticBindings = toBindingsArray(
staticVariables,
"Datasource.Static"
"Datasource.Static",
"Datasource Static"
)
$: mergedBindings = [

View File

@ -56,7 +56,11 @@
]
let dragDisabled = true
$: settings = getComponentSettings($selectedComponent?._component)
$: settings = getComponentSettings($selectedComponent?._component)?.concat({
label: "Custom CSS",
key: "_css",
type: "text",
})
$: settingOptions = settings.map(setting => ({
label: setting.label,
value: setting.key,

View File

@ -1,30 +1,41 @@
<script>
import {
TextArea,
DetailSummary,
ActionButton,
Drawer,
DrawerContent,
Layout,
Body,
Button,
notifications,
} from "@budibase/bbui"
import { store } from "builderStore"
import { selectedScreen, store } from "builderStore"
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
import {
getBindableProperties,
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "builderStore/dataBinding"
export let componentInstance
let tempValue
let drawer
$: bindings = getBindableProperties(
$selectedScreen,
$store.selectedComponentId
)
const openDrawer = () => {
tempValue = componentInstance?._styles?.custom
tempValue = runtimeToReadableBinding(
bindings,
componentInstance?._styles?.custom
)
drawer.show()
}
const save = async () => {
try {
await store.actions.components.updateCustomStyle(tempValue)
const value = readableToRuntimeBinding(bindings, tempValue)
await store.actions.components.updateCustomStyle(value)
} catch (error) {
notifications.error("Error updating custom style")
}
@ -42,26 +53,17 @@
</DetailSummary>
{#key componentInstance?._id}
<Drawer bind:this={drawer} title="Custom CSS">
<svelte:fragment slot="description">
Custom CSS overrides all other component styles.
</svelte:fragment>
<Button cta slot="buttons" on:click={save}>Save</Button>
<DrawerContent slot="body">
<div class="content">
<Layout gap="S" noPadding>
<Body size="S">Custom CSS overrides all other component styles.</Body>
<TextArea bind:value={tempValue} placeholder="Enter some CSS..." />
</Layout>
</div>
</DrawerContent>
<svelte:component
this={ClientBindingPanel}
slot="body"
value={tempValue}
on:change={event => (tempValue = event.detail)}
allowJS
{bindings}
/>
</Drawer>
{/key}
<style>
.content {
max-width: 800px;
margin: 0 auto;
}
.content :global(textarea) {
font-family: monospace;
min-height: 240px !important;
font-size: var(--font-size-s);
}
</style>

View File

@ -56,7 +56,7 @@
{
title: "Plugins",
href: "/builder/portal/manage/plugins",
badge: "New",
badge: "Beta",
},
{

View File

@ -10,7 +10,7 @@
Search,
} from "@budibase/bbui"
import { onMount } from "svelte"
import { plugins } from "stores/portal"
import { plugins, admin } from "stores/portal"
import PluginRow from "./_components/PluginRow.svelte"
import AddPluginModal from "./_components/AddPluginModal.svelte"
@ -20,9 +20,12 @@
let filterOptions = [
{ label: "All plugins", value: "all" },
{ label: "Components", value: "component" },
{ label: "Datasources", value: "datasource" },
]
if (!$admin.cloud) {
filterOptions.push({ label: "Datasources", value: "datasource" })
}
$: filteredPlugins = $plugins
.filter(plugin => {
return filter === "all" || plugin.schema.type === filter

View File

@ -3,10 +3,12 @@ import { API } from "api"
import { auth } from "stores/portal"
import { Constants } from "@budibase/frontend-core"
import { StripeStatus } from "components/portal/licensing/constants"
import { FEATURE_FLAGS, isEnabled } from "../../helpers/featureFlags"
export const createLicensingStore = () => {
const DEFAULT = {
plans: {},
usageMetrics: {},
}
const oneDayInMilliseconds = 86400000
@ -27,78 +29,80 @@ export const createLicensingStore = () => {
})
},
getUsageMetrics: async () => {
const quota = get(store).quotaUsage
const license = get(auth).user.license
const now = new Date()
if (isEnabled(FEATURE_FLAGS.LICENSING)) {
const quota = get(store).quotaUsage
const license = get(auth).user.license
const now = new Date()
const getMetrics = (keys, license, quota) => {
if (!license || !quota || !keys) {
return {}
const getMetrics = (keys, license, quota) => {
if (!license || !quota || !keys) {
return {}
}
return keys.reduce((acc, key) => {
const quotaLimit = license[key].value
const quotaUsed = (quota[key] / quotaLimit) * 100
acc[key] = quotaLimit > -1 ? Math.round(quotaUsed) : -1
return acc
}, {})
}
return keys.reduce((acc, key) => {
const quotaLimit = license[key].value
const quotaUsed = (quota[key] / quotaLimit) * 100
acc[key] = quotaLimit > -1 ? Math.round(quotaUsed) : -1
return acc
}, {})
}
const monthlyMetrics = getMetrics(
["dayPasses", "queries", "automations"],
license.quotas.usage.monthly,
quota.monthly.current
)
const staticMetrics = getMetrics(
["apps", "rows"],
license.quotas.usage.static,
quota.usageQuota
)
const getDaysBetween = (dateStart, dateEnd) => {
return dateEnd > dateStart
? Math.round(
(dateEnd.getTime() - dateStart.getTime()) / oneDayInMilliseconds
)
: 0
}
const quotaResetDate = new Date(quota.quotaReset)
const quotaResetDaysRemaining = getDaysBetween(now, quotaResetDate)
const accountDowngraded =
license?.billing?.subscription?.downgradeAt &&
license?.billing?.subscription?.downgradeAt <= now.getTime() &&
license?.billing?.subscription?.status === StripeStatus.PAST_DUE &&
license?.plan.type === Constants.PlanType.FREE
const pastDueAtMilliseconds = license?.billing?.subscription?.pastDueAt
const downgradeAtMilliseconds =
license?.billing?.subscription?.downgradeAt
let pastDueDaysRemaining
let pastDueEndDate
if (pastDueAtMilliseconds && downgradeAtMilliseconds) {
pastDueEndDate = new Date(downgradeAtMilliseconds)
pastDueDaysRemaining = getDaysBetween(
new Date(pastDueAtMilliseconds),
pastDueEndDate
const monthlyMetrics = getMetrics(
["dayPasses", "queries", "automations"],
license.quotas.usage.monthly,
quota.monthly.current
)
const staticMetrics = getMetrics(
["apps", "rows"],
license.quotas.usage.static,
quota.usageQuota
)
}
store.update(state => {
return {
...state,
usageMetrics: { ...monthlyMetrics, ...staticMetrics },
quotaResetDaysRemaining,
quotaResetDate,
accountDowngraded,
accountPastDue: pastDueAtMilliseconds != null,
pastDueEndDate,
pastDueDaysRemaining,
isFreePlan: () => {
return license?.plan.type === Constants.PlanType.FREE
},
const getDaysBetween = (dateStart, dateEnd) => {
return dateEnd > dateStart
? Math.round(
(dateEnd.getTime() - dateStart.getTime()) / oneDayInMilliseconds
)
: 0
}
})
const quotaResetDate = new Date(quota.quotaReset)
const quotaResetDaysRemaining = getDaysBetween(now, quotaResetDate)
const accountDowngraded =
license?.billing?.subscription?.downgradeAt &&
license?.billing?.subscription?.downgradeAt <= now.getTime() &&
license?.billing?.subscription?.status === StripeStatus.PAST_DUE &&
license?.plan.type === Constants.PlanType.FREE
const pastDueAtMilliseconds = license?.billing?.subscription?.pastDueAt
const downgradeAtMilliseconds =
license?.billing?.subscription?.downgradeAt
let pastDueDaysRemaining
let pastDueEndDate
if (pastDueAtMilliseconds && downgradeAtMilliseconds) {
pastDueEndDate = new Date(downgradeAtMilliseconds)
pastDueDaysRemaining = getDaysBetween(
new Date(pastDueAtMilliseconds),
pastDueEndDate
)
}
store.update(state => {
return {
...state,
usageMetrics: { ...monthlyMetrics, ...staticMetrics },
quotaResetDaysRemaining,
quotaResetDate,
accountDowngraded,
accountPastDue: pastDueAtMilliseconds != null,
pastDueEndDate,
pastDueDaysRemaining,
isFreePlan: () => {
return license?.plan.type === Constants.PlanType.FREE
},
}
})
}
},
}

View File

@ -34,8 +34,7 @@ export function createPluginsStore() {
}
let res = await API.createPlugin(pluginData)
let newPlugin = res.plugins[0]
let newPlugin = res.plugin
update(state => {
const currentIdx = state.findIndex(plugin => plugin._id === newPlugin._id)
if (currentIdx >= 0) {

View File

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

View File

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

View File

@ -142,6 +142,10 @@
// Determine and apply settings to the component
$: applySettings(staticSettings, enrichedSettings, conditionalSettings)
// Determine custom css.
// Broken out as a separate variable to minimize reactivity updates.
$: customCSS = cachedSettings?._css
// Scroll the selected element into view
$: selected && scrollIntoView()
@ -151,6 +155,7 @@
children: children.length,
styles: {
...instance._styles,
custom: customCSS,
id,
empty: emptyState,
interactive,
@ -249,14 +254,18 @@
// Get raw settings
let settings = {}
Object.entries(instance)
.filter(([name]) => name === "_conditions" || !name.startsWith("_"))
.filter(([name]) => !name.startsWith("_"))
.forEach(([key, value]) => {
settings[key] = value
})
// Derive static, dynamic and nested settings if the instance changed
let newStaticSettings = { ...settings }
let newDynamicSettings = { ...settings }
// Attach some internal properties
newDynamicSettings["_conditions"] = instance._conditions
newDynamicSettings["_css"] = instance._styles?.custom
// Derive static, dynamic and nested settings if the instance changed
settingsDefinition?.forEach(setting => {
if (setting.nested) {
delete newDynamicSettings[setting.key]
@ -370,6 +379,11 @@
// setting it on initialSettings directly, we avoid a double render.
cachedSettings[key] = allSettings[key]
// Don't update components for internal properties
if (key.startsWith("_")) {
return
}
if (ref?.$$set) {
// Programmatically set the prop to avoid svelte reactive statements
// firing inside components. This circumvents the problems caused by

View File

@ -374,6 +374,11 @@
min-height: 180px;
min-width: 200px;
}
.embedded-map :global(a.map-svg-button) {
display: flex;
justify-content: center;
align-items: center;
}
.embedded-map :global(.leaflet-top),
.embedded-map :global(.leaflet-bottom) {
z-index: 998;

View File

@ -37,7 +37,7 @@ const FullScreenControl = L.Control.extend({
this._fullScreenButton = this._createButton(
options.fullScreenContent,
options.fullScreenTitle,
"map-fullscreen",
"map-fullscreen map-svg-button",
container,
this._fullScreen
)
@ -87,7 +87,7 @@ const LocationControl = L.Control.extend({
this._locationButton = this._createButton(
options.locationContent,
options.locationTitle,
"map-location",
"map-location map-svg-button",
container,
this._location
)

View File

@ -1,5 +1,6 @@
export const PlanType = {
FREE: "free",
PRO: "pro",
TEAM: "team",
BUSINESS: "business",
ENTERPRISE: "enterprise",

View File

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

View File

@ -12,6 +12,7 @@ ENV COUCH_DB_URL=https://couchdb.budi.live:5984
ENV BUDIBASE_ENVIRONMENT=PRODUCTION
ENV SERVICE=app-service
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS
# copy files and install dependencies
COPY . ./

View File

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

View File

@ -1,15 +1,12 @@
const env = require("../../environment")
const { getAllApps } = require("@budibase/backend-core/db")
const { getAllApps, getGlobalDBName } = require("@budibase/backend-core/db")
const {
exportDB,
sendTempFile,
readFileSync,
} = require("../../utilities/fileSystem")
const { stringToReadStream } = require("../../utilities")
const {
getGlobalDBName,
getGlobalDB,
} = require("@budibase/backend-core/tenancy")
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
const { create } = require("./application")
const { getDocParams, DocumentType, isDevAppID } = require("../../db/utils")

View File

@ -1,15 +1,15 @@
const { DocumentType, getPluginParams } = require("../../db/utils")
const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
const { getAppDB } = require("@budibase/backend-core/context")
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
import { DocumentType } from "../../db/utils"
import { Plugin } from "@budibase/types"
import { db as dbCore, context, tenancy } from "@budibase/backend-core"
import { getComponentLibraryManifest } from "../../utilities/fileSystem"
exports.fetchAppComponentDefinitions = async function (ctx) {
exports.fetchAppComponentDefinitions = async function (ctx: any) {
try {
const db = getAppDB()
const db = context.getAppDB()
const app = await db.get(DocumentType.APP_METADATA)
let componentManifests = await Promise.all(
app.componentLibraries.map(async library => {
app.componentLibraries.map(async (library: any) => {
let manifest = await getComponentLibraryManifest(library)
return {
manifest,
@ -17,7 +17,7 @@ exports.fetchAppComponentDefinitions = async function (ctx) {
}
})
)
const definitions = {}
const definitions: { [key: string]: any } = {}
for (let { manifest, library } of componentManifests) {
for (let key of Object.keys(manifest)) {
if (key === "features") {
@ -33,16 +33,16 @@ exports.fetchAppComponentDefinitions = async function (ctx) {
}
// Add custom components
const globalDB = getGlobalDB()
const globalDB = tenancy.getGlobalDB()
const response = await globalDB.allDocs(
getPluginParams(null, {
dbCore.getPluginParams(null, {
include_docs: true,
})
)
response.rows
.map(row => row.doc)
.filter(plugin => plugin.schema.type === "component")
.forEach(plugin => {
.map((row: any) => row.doc)
.filter((plugin: Plugin) => plugin.schema.type === "component")
.forEach((plugin: Plugin) => {
const fullComponentName = `plugin/${plugin.name}`
definitions[fullComponentName] = {
component: fullComponentName,

View File

@ -1,22 +1,16 @@
import { ObjectStoreBuckets } from "../../../constants"
import { loadJSFile } from "../../../utilities/fileSystem"
import { npmUpload, urlUpload, githubUpload, fileUpload } from "./uploaders"
import { getGlobalDB } from "@budibase/backend-core/tenancy"
import { validate } from "@budibase/backend-core/plugins"
import { generatePluginID, getPluginParams } from "../../../db/utils"
import {
uploadDirectory,
deleteFolder,
} from "@budibase/backend-core/objectStore"
import { PluginType, FileType, PluginSource, Plugin } from "@budibase/types"
import { PluginType, FileType, PluginSource } from "@budibase/types"
import env from "../../../environment"
import { ClientAppSocket } from "../../../websocket"
import { events } from "@budibase/backend-core"
import { db as dbCore } from "@budibase/backend-core"
import { plugins } from "@budibase/pro"
export async function getPlugins(type?: PluginType) {
const db = getGlobalDB()
const response = await db.allDocs(
getPluginParams(null, {
dbCore.getPluginParams(null, {
include_docs: true,
})
)
@ -37,7 +31,7 @@ export async function upload(ctx: any) {
let docs = []
// can do single or multiple plugins
for (let plugin of plugins) {
const doc = await processPlugin(plugin, PluginSource.FILE)
const doc = await processUploadedPlugin(plugin, PluginSource.FILE)
docs.push(doc)
}
ctx.body = {
@ -91,18 +85,19 @@ export async function create(ctx: any) {
)
}
const doc = await storePlugin(metadata, directory, source)
const doc = await plugins.storePlugin(metadata, directory, source)
ClientAppSocket.emit("plugins-update", { name, hash: doc.hash })
ctx.body = {
message: "Plugin uploaded successfully",
plugins: [doc],
}
ctx.body = { plugin: doc }
} catch (err: any) {
const errMsg = err?.message ? err?.message : err
ctx.throw(400, `Failed to import plugin: ${errMsg}`)
}
ctx.status = 200
}
export async function fetch(ctx: any) {
@ -110,99 +105,21 @@ export async function fetch(ctx: any) {
}
export async function destroy(ctx: any) {
const db = getGlobalDB()
const { pluginId } = ctx.params
try {
const plugin: Plugin = await db.get(pluginId)
const bucketPath = `${plugin.name}/`
await deleteFolder(ObjectStoreBuckets.PLUGINS, bucketPath)
await plugins.deletePlugin(pluginId)
await db.remove(pluginId, plugin._rev)
await events.plugin.deleted(plugin)
ctx.body = { message: `Plugin ${ctx.params.pluginId} deleted.` }
} catch (err: any) {
const errMsg = err?.message ? err?.message : err
ctx.throw(400, `Failed to delete plugin: ${errMsg}`)
ctx.throw(400, err.message)
}
ctx.message = `Plugin ${ctx.params.pluginId} deleted.`
ctx.status = 200
}
export async function storePlugin(
metadata: any,
directory: any,
export async function processUploadedPlugin(
plugin: FileType,
source?: PluginSource
) {
const db = getGlobalDB()
const version = metadata.package.version,
name = metadata.package.name,
description = metadata.package.description,
hash = metadata.schema.hash
// first open the tarball into tmp directory
const bucketPath = `${name}/`
const files = await uploadDirectory(
ObjectStoreBuckets.PLUGINS,
directory,
bucketPath
)
const jsFile = files.find((file: any) => file.name.endsWith(".js"))
if (!jsFile) {
throw new Error(`Plugin missing .js file.`)
}
// validate the JS for a datasource
if (metadata.schema.type === PluginType.DATASOURCE) {
const js = loadJSFile(directory, jsFile.name)
// TODO: this isn't safe - but we need full node environment
// in future we should do this in a thread for safety
try {
eval(js)
} catch (err: any) {
const message = err?.message ? err.message : JSON.stringify(err)
throw new Error(`JS invalid: ${message}`)
}
}
const jsFileName = jsFile.name
const pluginId = generatePluginID(name)
// overwrite existing docs entirely if they exist
let rev
try {
const existing = await db.get(pluginId)
rev = existing._rev
} catch (err) {
rev = undefined
}
let doc: Plugin = {
_id: pluginId,
_rev: rev,
...metadata,
name,
version,
hash,
description,
jsUrl: `${bucketPath}${jsFileName}`,
}
if (source) {
doc = {
...doc,
source,
}
}
const response = await db.put(doc)
await events.plugin.imported(doc)
ClientAppSocket.emit("plugin-update", { name, hash })
return {
...doc,
_rev: response.rev,
}
}
export async function processPlugin(plugin: FileType, source?: PluginSource) {
const { metadata, directory } = await fileUpload(plugin)
validate(metadata?.schema)
@ -211,5 +128,7 @@ export async function processPlugin(plugin: FileType, source?: PluginSource) {
throw new Error("Only component plugins are supported outside of self-host")
}
return await storePlugin(metadata, directory, source)
const doc = await plugins.storePlugin(metadata, directory, source)
ClientAppSocket.emit("plugin-update", { name: doc.name, hash: doc.hash })
return doc
}

View File

@ -1,17 +1,16 @@
const {
getScreenParams,
generateScreenID,
getPluginParams,
DocumentType,
} = require("../../db/utils")
const { AccessController } = require("@budibase/backend-core/roles")
const { getAppDB } = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core")
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
const { updateAppPackage } = require("./application")
import { getScreenParams, generateScreenID, DocumentType } from "../../db/utils"
import {
events,
context,
tenancy,
db as dbCore,
roles,
} from "@budibase/backend-core"
import { updateAppPackage } from "./application"
import { Plugin, ScreenProps } from "@budibase/types"
exports.fetch = async ctx => {
const db = getAppDB()
exports.fetch = async (ctx: any) => {
const db = context.getAppDB()
const screens = (
await db.allDocs(
@ -19,16 +18,16 @@ exports.fetch = async ctx => {
include_docs: true,
})
)
).rows.map(element => element.doc)
).rows.map((el: any) => el.doc)
ctx.body = await new AccessController().checkScreensAccess(
ctx.body = await new roles.AccessController().checkScreensAccess(
screens,
ctx.user.role._id
)
}
exports.save = async ctx => {
const db = getAppDB()
exports.save = async (ctx: any) => {
const db = context.getAppDB()
let screen = ctx.request.body
let eventFn
@ -40,19 +39,19 @@ exports.save = async ctx => {
const response = await db.put(screen)
// Find any custom components being used
let pluginNames = []
let pluginNames: string[] = []
let pluginAdded = false
findPlugins(screen.props, pluginNames)
if (pluginNames.length) {
const globalDB = getGlobalDB()
const globalDB = tenancy.getGlobalDB()
const pluginsResponse = await globalDB.allDocs(
getPluginParams(null, {
dbCore.getPluginParams(null, {
include_docs: true,
})
)
const requiredPlugins = pluginsResponse.rows
.map(row => row.doc)
.filter(plugin => {
.map((row: any) => row.doc)
.filter((plugin: Plugin) => {
return (
plugin.schema.type === "component" &&
pluginNames.includes(`plugin/${plugin.name}`)
@ -63,8 +62,8 @@ exports.save = async ctx => {
const application = await db.get(DocumentType.APP_METADATA)
let usedPlugins = application.usedPlugins || []
requiredPlugins.forEach(plugin => {
if (!usedPlugins.find(x => x._id === plugin._id)) {
requiredPlugins.forEach((plugin: Plugin) => {
if (!usedPlugins.find((x: Plugin) => x._id === plugin._id)) {
pluginAdded = true
usedPlugins.push({
_id: plugin._id,
@ -93,8 +92,8 @@ exports.save = async ctx => {
}
}
exports.destroy = async ctx => {
const db = getAppDB()
exports.destroy = async (ctx: any) => {
const db = context.getAppDB()
const id = ctx.params.screenId
const screen = await db.get(id)
@ -107,7 +106,7 @@ exports.destroy = async ctx => {
ctx.status = 200
}
const findPlugins = (component, foundPlugins) => {
const findPlugins = (component: ScreenProps, foundPlugins: string[]) => {
if (!component) {
return
}

View File

@ -5,10 +5,12 @@ const { doInAppContext } = require("@budibase/backend-core/context")
const { doInTenant } = require("@budibase/backend-core/tenancy")
const {
quotas,
} = require("@budibase/pro")
const {
QuotaUsageType,
StaticQuotaName,
MonthlyQuotaName,
} = require("@budibase/pro")
} = require("@budibase/types")
describe("/rows", () => {
let request = setup.getRequest()

View File

@ -42,7 +42,6 @@ const DocumentType = {
MEM_VIEW: "view",
USER_FLAG: "flag",
AUTOMATION_METADATA: "meta_au",
PLUGIN: "plg",
}
const InternalTables = {
@ -384,10 +383,3 @@ exports.getMultiIDParams = ids => {
include_docs: true,
}
}
/**
* Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
*/
exports.getPluginParams = (pluginId = null, otherProps = {}) => {
return getDocParams(DocumentType.PLUGIN, pluginId, otherProps)
}

View File

@ -1,6 +1,7 @@
import { getTenantId } from "@budibase/backend-core/tenancy"
import { getAllApps } from "@budibase/backend-core/db"
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
import { quotas } from "@budibase/pro"
import { QuotaUsageType, StaticQuotaName } from "@budibase/types"
export const run = async () => {
// get app count

View File

@ -1,7 +1,8 @@
import { getTenantId } from "@budibase/backend-core/tenancy"
import { getAllApps } from "@budibase/backend-core/db"
import { getUniqueRows } from "../../../utilities/usageQuota/rows"
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
import { quotas } from "@budibase/pro"
import { QuotaUsageType, StaticQuotaName } from "@budibase/types"
export const run = async () => {
// get all rows in all apps

View File

@ -1,6 +1,7 @@
import TestConfig from "../../../../tests/utilities/TestConfiguration"
import * as syncApps from "../syncApps"
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
import { quotas } from "@budibase/pro"
import { QuotaUsageType, StaticQuotaName } from "@budibase/types"
describe("syncApps", () => {
let config = new TestConfig(false)

View File

@ -1,6 +1,7 @@
import TestConfig from "../../../../tests/utilities/TestConfiguration"
import * as syncRows from "../syncRows"
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
import { quotas } from "@budibase/pro"
import { QuotaUsageType, StaticQuotaName } from "@budibase/types"
describe("syncRows", () => {
let config = new TestConfig(false)

View File

@ -112,13 +112,6 @@ exports.loadHandlebarsFile = path => {
return fs.readFileSync(path, "utf8")
}
/**
* Same as above just with a different name.
*/
exports.loadJSFile = (directory, name) => {
return fs.readFileSync(join(directory, name), "utf8")
}
/**
* When return a file from the API need to write the file to the system temporarily so we
* can create a read stream to send.

View File

@ -4,7 +4,7 @@ import chokidar from "chokidar"
import fs from "fs"
import { tenancy } from "@budibase/backend-core"
import { DEFAULT_TENANT_ID } from "@budibase/backend-core/constants"
import { processPlugin } from "./api/controllers/plugin"
import { processUploadedPlugin } from "./api/controllers/plugin"
export function watch() {
const watchPath = path.join(env.PLUGINS_DIR, "./**/*.tar.gz")
@ -28,7 +28,7 @@ export function watch() {
const split = path.split("/")
const name = split[split.length - 1]
console.log("Importing plugin:", path)
await processPlugin({ name, path })
await processUploadedPlugin({ name, path })
} catch (err: any) {
const message = err?.message ? err?.message : err
console.error("Failed to import plugin:", message)

View File

@ -1094,12 +1094,12 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.3.21-alpha.0":
version "1.3.21-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.21-alpha.0.tgz#f64dc43f38e1ba8d43ae99fdb67f1f052a0af68e"
integrity sha512-eVQXs9+ddfjtGYNanXkFCgYYxVwI90To4y1LY3CfUvegcDaJIt26XX9B7uKNwRdWl37XqptwbmaqDfsWlsxIew==
"@budibase/backend-core@1.4.8-alpha.3":
version "1.4.8-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.4.8-alpha.3.tgz#3a291fa610846cd2d15ec816974fe9b4ebc394b0"
integrity sha512-MpZe5xe7mAuJ3sMh87E6go8XaUyCiAjyK+siubvYYP4zI3AVU60fF6rfHT7Q8Cl/taDVhrboDDbHTjk8jkwr5A==
dependencies:
"@budibase/types" "1.3.21-alpha.0"
"@budibase/types" "1.4.8-alpha.3"
"@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0"
@ -1180,13 +1180,13 @@
svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0"
"@budibase/pro@1.3.21-alpha.0":
version "1.3.21-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.21-alpha.0.tgz#e6f3b1b044ff8da0d63fece6b86f10ed076dba6b"
integrity sha512-NPKZEo9Pz+XFA2/6KUFMhhPIpNzUBZ1AZzQeyeCyjZtXA88ipVA7xdhiJp0OJg1eXwo/QWB1KM/HY7q1kKDkWg==
"@budibase/pro@1.4.8-alpha.3":
version "1.4.8-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.4.8-alpha.3.tgz#20a6fe18b87ff73f699eda4b58741519f9aa6bb0"
integrity sha512-m+yvK0okqz0pE6ILAGESETHJHLOgUz8l9+mj/Bc4Wu0P6WyW4li81Wlz9WDcwUW2bSjpuf/MC1CjG05iS4opTQ==
dependencies:
"@budibase/backend-core" "1.3.21-alpha.0"
"@budibase/types" "1.3.21-alpha.0"
"@budibase/backend-core" "1.4.8-alpha.3"
"@budibase/types" "1.4.8-alpha.3"
"@koa/router" "8.0.8"
joi "17.6.0"
node-fetch "^2.6.1"
@ -1209,10 +1209,10 @@
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/types@1.3.21-alpha.0":
version "1.3.21-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.21-alpha.0.tgz#665d76bfce66f3bdef064ac9e3bef13e6567da6d"
integrity sha512-IkNMFfARkRJRzTSeoRJhqI9hxdkwpq7Wodlj6Mk8++zcMm33RD2Yx4htTBAYEWeeTLYPu67hpvEjQ9OhAZUZNA==
"@budibase/types@1.4.8-alpha.3":
version "1.4.8-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.4.8-alpha.3.tgz#85640009c992d37a56938fc601a786f5fd5e1739"
integrity sha512-hqSGL0WXEtEuMsgAJ87jat/6MAUw7fFEuWRcUdnC9QtiF2yB4cjWStqVcfsBtiSt9VZ9xzNzcu1/++nPN/uF9w==
"@bull-board/api@3.7.0":
version "3.7.0"

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/string-templates",
"version": "1.3.21-alpha.0",
"version": "1.4.8-alpha.3",
"description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs",
"module": "dist/bundle.mjs",

View File

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

View File

@ -1,4 +1,4 @@
import { Hosting } from "../../sdk"
import { Feature, Hosting, PlanType, Quotas } from "../../sdk"
export interface CreateAccount {
email: string
@ -22,6 +22,11 @@ export const isCreatePasswordAccount = (
account: CreateAccount
): account is CreatePassswordAccount => account.authType === AuthType.PASSWORD
export interface LicenseOverrides {
features?: Feature[]
quotas?: Quotas
}
export interface Account extends CreateAccount {
// generated
accountId: string
@ -31,9 +36,12 @@ export interface Account extends CreateAccount {
verificationSent: boolean
// licensing
tier: string // deprecated
planType?: PlanType
planTier?: number
stripeCustomerId?: string
licenseKey?: string
licenseKeyActivatedAt?: number
licenseOverrides?: LicenseOverrides
}
export interface PasswordAccount extends Account {

View File

@ -1,5 +1,17 @@
import { Document } from "../document"
export interface ScreenProps extends Document {
_instanceName: string
_styles: { [key: string]: any }
_component: string
_children: ScreenProps[]
size?: string
gap?: string
direction?: string
vAlign?: string
hAlign?: string
}
export interface Screen extends Document {
layoutId?: string
showNavigation?: boolean
@ -9,4 +21,5 @@ export interface Screen extends Document {
roleId: string
homeScreen?: boolean
}
props: ScreenProps
}

View File

@ -2,3 +2,4 @@ export * from "./config"
export * from "./user"
export * from "./userGroup"
export * from "./plugin"
export * from "./quotas"

View File

@ -23,6 +23,7 @@ export interface Plugin extends Document {
jsUrl?: string
source: PluginSource
package: { [key: string]: any }
hash: string
schema: {
type: PluginType
[key: string]: any

View File

@ -0,0 +1,15 @@
import { MonthlyQuotaName, StaticQuotaName } from "../../sdk"
export interface QuotaUsage {
_id: string
_rev?: string
quotaReset: string
usageQuota: {
[key in StaticQuotaName]: number
}
monthly: {
[key: string]: {
[key in MonthlyQuotaName]: number
}
}
}

View File

@ -133,9 +133,14 @@ export enum Event {
AUTOMATION_TRIGGER_UPDATED = "automation:trigger:updated",
// LICENSE
LICENSE_UPGRADED = "license:upgraded",
LICENSE_DOWNGRADED = "license:downgraded",
LICENSE_PLAN_CHANGED = "license:plan:changed",
LICENSE_TIER_CHANGED = "license:tier:changed",
LICENSE_ACTIVATED = "license:activated",
LICENSE_PAYMENT_FAILED = "license:payment:failed",
LICENSE_PAYMENT_RECOVERED = "license:payment:recovered",
LICENSE_CHECKOUT_OPENED = "license:checkout:opened",
LICENSE_CHECKOUT_SUCCESS = "license:checkout:success",
LICENSE_PORTAL_OPENED = "license:portal:opened",
// ACCOUNT
ACCOUNT_CREATED = "account:created",

View File

@ -1,7 +1,37 @@
export interface LicenseUpgradedEvent {}
import { PlanType } from "../licensing"
export interface LicenseDowngradedEvent {}
export interface LicenseTierChangedEvent {
accountId: string
from: number
to: number
}
export interface LicenseUpdatedEvent {}
export interface LicensePlanChangedEvent {
accountId: string
from: PlanType
to: PlanType
}
export interface LicenseActivatedEvent {}
export interface LicenseActivatedEvent {
accountId: string
}
export interface LicenseCheckoutOpenedEvent {
accountId: string
}
export interface LicenseCheckoutSuccessEvent {
accountId: string
}
export interface LicensePortalOpenedEvent {
accountId: string
}
export interface LicensePaymentFailedEvent {
accountId: string
}
export interface LicensePaymentRecoveredEvent {
accountId: string
}

View File

@ -12,6 +12,9 @@ export interface Subscription {
cancelAt: number | null | undefined
currentPeriodStart: number
currentPeriodEnd: number
status: string
pastDueAt?: number | null
downgradeAt?: number
}
export interface Billing {

View File

@ -6,6 +6,7 @@ export interface AccountPlan {
export enum PlanType {
FREE = "free",
PRO = "pro",
TEAM = "team",
BUSINESS = "business",
ENTERPRISE = "enterprise",
}

View File

@ -13,6 +13,8 @@ export enum QuotaType {
export enum StaticQuotaName {
ROWS = "rows",
APPS = "apps",
USER_GROUPS = "userGroups",
PLUGINS = "plugins",
}
export enum MonthlyQuotaName {
@ -22,7 +24,6 @@ export enum MonthlyQuotaName {
}
export enum ConstantQuotaName {
QUERY_TIMEOUT_SECONDS = "queryTimeoutSeconds",
AUTOMATION_LOG_RETENTION_DAYS = "automationLogRetentionDays",
}
@ -54,6 +55,7 @@ export const isConstantQuota = (
export type PlanQuotas = {
[PlanType.FREE]: Quotas
[PlanType.PRO]: Quotas
[PlanType.TEAM]: Quotas
[PlanType.BUSINESS]: Quotas
[PlanType.ENTERPRISE]: Quotas
}
@ -68,10 +70,11 @@ export type Quotas = {
[QuotaUsageType.STATIC]: {
[StaticQuotaName.ROWS]: Quota
[StaticQuotaName.APPS]: Quota
[StaticQuotaName.USER_GROUPS]: Quota
[StaticQuotaName.PLUGINS]: Quota
}
}
[QuotaType.CONSTANT]: {
[ConstantQuotaName.QUERY_TIMEOUT_SECONDS]: Quota
[ConstantQuotaName.AUTOMATION_LOG_RETENTION_DAYS]: Quota
}
}

View File

@ -23,5 +23,6 @@ ENV NODE_ENV=production
ENV CLUSTER_MODE=${CLUSTER_MODE}
ENV SERVICE=worker-service
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS
CMD ["./docker_run.sh"]

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/worker",
"email": "hi@budibase.com",
"version": "1.3.21-alpha.0",
"version": "1.4.8-alpha.3",
"description": "Budibase background service",
"main": "src/index.ts",
"repository": {
@ -36,10 +36,10 @@
"author": "Budibase",
"license": "GPL-3.0",
"dependencies": {
"@budibase/backend-core": "1.3.21-alpha.0",
"@budibase/pro": "1.3.21-alpha.0",
"@budibase/string-templates": "1.3.21-alpha.0",
"@budibase/types": "1.3.21-alpha.0",
"@budibase/backend-core": "1.4.8-alpha.3",
"@budibase/pro": "1.4.8-alpha.3",
"@budibase/string-templates": "1.4.8-alpha.3",
"@budibase/types": "1.4.8-alpha.3",
"@koa/router": "8.0.8",
"@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2",

View File

@ -10,7 +10,9 @@ const router = new Router()
function buildEmailSendValidation() {
// prettier-ignore
return joiValidator.body(Joi.object({
email: Joi.string().email(),
email: Joi.string().email({
multiple: true,
}),
purpose: Joi.string().valid(...Object.values(EmailTemplatePurpose)),
workspaceId: Joi.string().allow("", null),
from: Joi.string().allow("", null),

View File

@ -291,12 +291,12 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.3.21-alpha.0":
version "1.3.21-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.21-alpha.0.tgz#f64dc43f38e1ba8d43ae99fdb67f1f052a0af68e"
integrity sha512-eVQXs9+ddfjtGYNanXkFCgYYxVwI90To4y1LY3CfUvegcDaJIt26XX9B7uKNwRdWl37XqptwbmaqDfsWlsxIew==
"@budibase/backend-core@1.4.8-alpha.3":
version "1.4.8-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.4.8-alpha.3.tgz#3a291fa610846cd2d15ec816974fe9b4ebc394b0"
integrity sha512-MpZe5xe7mAuJ3sMh87E6go8XaUyCiAjyK+siubvYYP4zI3AVU60fF6rfHT7Q8Cl/taDVhrboDDbHTjk8jkwr5A==
dependencies:
"@budibase/types" "1.3.21-alpha.0"
"@budibase/types" "1.4.8-alpha.3"
"@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0"
@ -327,21 +327,21 @@
uuid "8.3.2"
zlib "1.0.5"
"@budibase/pro@1.3.21-alpha.0":
version "1.3.21-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.21-alpha.0.tgz#e6f3b1b044ff8da0d63fece6b86f10ed076dba6b"
integrity sha512-NPKZEo9Pz+XFA2/6KUFMhhPIpNzUBZ1AZzQeyeCyjZtXA88ipVA7xdhiJp0OJg1eXwo/QWB1KM/HY7q1kKDkWg==
"@budibase/pro@1.4.8-alpha.3":
version "1.4.8-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.4.8-alpha.3.tgz#20a6fe18b87ff73f699eda4b58741519f9aa6bb0"
integrity sha512-m+yvK0okqz0pE6ILAGESETHJHLOgUz8l9+mj/Bc4Wu0P6WyW4li81Wlz9WDcwUW2bSjpuf/MC1CjG05iS4opTQ==
dependencies:
"@budibase/backend-core" "1.3.21-alpha.0"
"@budibase/types" "1.3.21-alpha.0"
"@budibase/backend-core" "1.4.8-alpha.3"
"@budibase/types" "1.4.8-alpha.3"
"@koa/router" "8.0.8"
joi "17.6.0"
node-fetch "^2.6.1"
"@budibase/types@1.3.21-alpha.0":
version "1.3.21-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.21-alpha.0.tgz#665d76bfce66f3bdef064ac9e3bef13e6567da6d"
integrity sha512-IkNMFfARkRJRzTSeoRJhqI9hxdkwpq7Wodlj6Mk8++zcMm33RD2Yx4htTBAYEWeeTLYPu67hpvEjQ9OhAZUZNA==
"@budibase/types@1.4.8-alpha.3":
version "1.4.8-alpha.3"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.4.8-alpha.3.tgz#85640009c992d37a56938fc601a786f5fd5e1739"
integrity sha512-hqSGL0WXEtEuMsgAJ87jat/6MAUw7fFEuWRcUdnC9QtiF2yB4cjWStqVcfsBtiSt9VZ9xzNzcu1/++nPN/uF9w==
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"

View File

@ -32,7 +32,7 @@ describe("Public API - /rows endpoints", () => {
expect(row._id).toBeDefined()
})
it("POST - Search rows", async () => {
/*it("POST - Search rows", async () => {
const [response, rows] = await config.rows.search({
query: {
string: {
@ -44,7 +44,7 @@ describe("Public API - /rows endpoints", () => {
expect(rows[0]._id).toEqual(config.context._id)
expect(rows[0].tableId).toEqual(config.context.tableId)
expect(rows[0].testColumn).toEqual(config.context.testColumn)
})
})*/
it("GET - Retrieve a row", async () => {
const [response, row] = await config.rows.read(config.context._id)

645
yarn.lock

File diff suppressed because it is too large Load Diff