Merge branch 'master' into feature/count-creators-in-groups

This commit is contained in:
jvcalderon 2024-01-24 09:01:58 +01:00
commit 28a9ca0fa7
33 changed files with 209 additions and 119 deletions

View File

@ -1,5 +1,5 @@
{
"version": "2.15.0",
"version": "2.15.5",
"npmClient": "yarn",
"packages": [
"packages/*",

View File

@ -43,7 +43,7 @@
},
TRIGGER_AUTOMATION_RUN: {
disabled: !triggerAutomationRunEnabled,
message: collectDisabledMessage(),
message: "Please upgrade to a paid plan",
},
}
}

View File

@ -77,7 +77,7 @@
</Tab>
<Tab title="Output">
<div class="wrap">
{#if filteredResults?.[idx]?.inputs}
{#if filteredResults?.[idx]?.outputs}
<JsonView
depth={2}
json={filteredResults?.[idx]?.outputs}

View File

@ -184,8 +184,9 @@
}
if (
(idx === 0 && automation.trigger?.event === "row:update") ||
automation.trigger?.event === "row:save"
idx === 0 &&
(automation.trigger?.event === "row:update" ||
automation.trigger?.event === "row:save")
) {
if (name !== "id" && name !== "revision") return `trigger.row.${name}`
}

View File

@ -172,22 +172,6 @@
}
}
}
if (!savingColumn && !originalName) {
let highestNumber = 0
Object.keys(table.schema).forEach(columnName => {
const columnNumber = extractColumnNumber(columnName)
if (columnNumber > highestNumber) {
highestNumber = columnNumber
}
return highestNumber
})
if (highestNumber >= 1) {
editableColumn.name = `Column 0${highestNumber + 1}`
} else {
editableColumn.name = "Column 01"
}
}
if (!savingColumn) {
editableColumn.fieldId = makeFieldId(
@ -389,11 +373,6 @@
deleteColName = ""
}
function extractColumnNumber(columnName) {
const match = columnName.match(/Column (\d+)/)
return match ? parseInt(match[1]) : 0
}
function getAllowedTypes() {
if (
originalName &&

View File

@ -13,6 +13,7 @@
Icon,
} from "@budibase/bbui"
import { capitalise } from "helpers"
import { getFormattedPlanName } from "helpers/planTitle"
import { get } from "svelte/store"
export let resourceId
@ -99,7 +100,9 @@
{#if requiresPlanToModify}
<span class="lock-tag">
<Tags>
<Tag icon="LockClosed">{capitalise(requiresPlanToModify)}</Tag>
<Tag icon="LockClosed"
>{getFormattedPlanName(requiresPlanToModify)}</Tag
>
</Tags>
</span>
{/if}

View File

@ -17,7 +17,7 @@
export let table
let editorModal
let editorModal, editTableNameModal
let confirmDeleteDialog
let error = ""
@ -101,18 +101,21 @@
<Modal bind:this={editorModal} on:show={initForm}>
<ModalContent
bind:this={editTableNameModal}
title="Edit Table"
confirmText="Save"
onConfirm={save}
disabled={updatedName === originalName || error}
>
<Input
label="Table Name"
thin
bind:value={updatedName}
on:input={checkValid}
{error}
/>
<form on:submit|preventDefault={() => editTableNameModal.confirm()}>
<Input
label="Table Name"
thin
bind:value={updatedName}
on:input={checkValid}
{error}
/>
</form>
</ModalContent>
</Modal>
<ConfirmDialog

View File

@ -0,0 +1,11 @@
import { PlanType } from "@budibase/types"
export function getFormattedPlanName(userPlanType) {
let planName = "Free"
if (userPlanType === PlanType.PREMIUM_PLUS) {
planName = "Premium"
} else if (userPlanType === PlanType.ENTERPRISE_BASIC) {
planName = "Enterprise"
}
return `${planName} Plan`
}

View File

@ -15,9 +15,9 @@
<Content showMobileNav>
<SideNav slot="side-nav">
<SideNavItem
text="Automation History"
url={$url("./automation-history")}
active={$isActive("./automation-history")}
text="Automations"
url={$url("./automations")}
active={$isActive("./automations")}
/>
<SideNavItem
text="Backups"

View File

@ -8,6 +8,8 @@
Body,
Heading,
Divider,
Toggle,
notifications,
} from "@budibase/bbui"
import DateTimeRenderer from "components/common/renderers/DateTimeRenderer.svelte"
import StatusRenderer from "./_components/StatusRenderer.svelte"
@ -16,7 +18,7 @@
import { createPaginationStore } from "helpers/pagination"
import { getContext, onDestroy, onMount } from "svelte"
import dayjs from "dayjs"
import { auth, licensing, admin } from "stores/portal"
import { auth, licensing, admin, apps } from "stores/portal"
import { Constants } from "@budibase/frontend-core"
import Portal from "svelte-portal"
@ -35,9 +37,13 @@
let timeRange = null
let loaded = false
$: app = $apps.find(app => app.devId === $store.appId?.includes(app.appId))
$: licensePlan = $auth.user?.license?.plan
$: page = $pageInfo.page
$: fetchLogs(automationId, status, page, timeRange)
$: isCloud = $admin.cloud
$: chainAutomations = app?.automations?.chainAutomations ?? !isCloud
const timeOptions = [
{ value: "90-d", label: "Past 90 days" },
@ -124,6 +130,18 @@
sidePanel.open()
}
async function save({ detail }) {
try {
await apps.update($store.appId, {
automations: {
chainAutomations: detail,
},
})
} catch (error) {
notifications.error("Error updating automation chaining setting")
}
}
onMount(async () => {
await automationStore.actions.fetch()
const params = new URLSearchParams(window.location.search)
@ -150,11 +168,30 @@
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading>Automation History</Heading>
<Body>View the automations your app has executed</Body>
<Heading>Automations</Heading>
<Body size="S">See your automation history and edit advanced settings</Body>
</Layout>
<Divider />
<Layout gap="XS" noPadding>
<Heading size="XS">Chain automations</Heading>
<Body size="S">Allow automations to trigger from other automations</Body>
<div class="setting-spacing">
<Toggle
text={"Enable chaining"}
on:change={e => {
save(e)
}}
value={chainAutomations}
/>
</div>
</Layout>
<Divider />
<Layout gap="XS" noPadding>
<Heading size="XS">History</Heading>
<Body size="S">Free plan stores up to 1 day of automation history</Body>
</Layout>
<div class="controls">
<div class="search">
<div class="select">
@ -237,6 +274,9 @@
{/if}
<style>
.setting-spacing {
padding-top: var(--spacing-s);
}
.controls {
display: flex;
flex-direction: row;

View File

@ -1,5 +1,5 @@
<script>
import { redirect } from "@roxi/routify"
$redirect("../settings/automation-history")
$redirect("../settings/automations")
</script>

View File

@ -15,7 +15,7 @@
import { DashCard, Usage } from "components/usage"
import { PlanModel } from "constants"
import { sdk } from "@budibase/shared-core"
import { PlanType } from "@budibase/types"
import { getFormattedPlanName } from "helpers/planTitle"
let staticUsage = []
let monthlyUsage = []
@ -100,23 +100,6 @@
cancelAt = license?.billing?.subscription?.cancelAt
}
const capitalise = string => {
if (string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
}
const planTitle = () => {
const planType = license?.plan.type
let planName = license?.plan.type
if (planType === PlanType.PREMIUM_PLUS) {
planName = "Premium"
} else if (planType === PlanType.ENTERPRISE_BASIC) {
planName = "Enterprise"
}
return `${capitalise(planName)} Plan`
}
const getDaysRemaining = timestamp => {
if (!timestamp) {
return
@ -227,7 +210,7 @@
<DashCard
description="YOUR CURRENT PLAN"
title={planTitle()}
title={getFormattedPlanName(license?.plan.type)}
{primaryActionText}
primaryAction={showButton ? goToAccountPortal : undefined}
{textRows}

View File

@ -6098,23 +6098,6 @@
}
]
},
{
"tag": "style",
"type": "select",
"label": "Size",
"key": "size",
"options": [
{
"label": "Medium",
"value": "spectrum--medium"
},
{
"label": "Large",
"value": "spectrum--large"
}
],
"defaultValue": "spectrum--medium"
},
{
"tag": "style",
"type": "select",
@ -6131,6 +6114,23 @@
}
],
"defaultValue": "bottom"
},
{
"tag": "style",
"type": "select",
"label": "Size",
"key": "size",
"options": [
{
"label": "Medium",
"value": "spectrum--medium"
},
{
"label": "Large",
"value": "spectrum--large"
}
],
"defaultValue": "spectrum--medium"
}
],
"actions": [

View File

@ -29,7 +29,7 @@
type,
quiet,
disabled,
size,
size: size || "M",
}}
/>
{/each}

View File

@ -88,7 +88,7 @@
<BlockComponent
type="form"
bind:id={formId}
props={{ dataSource, disableValidation: true }}
props={{ dataSource, disableSchemaValidation: true }}
>
{#if title || enrichedSearchColumns?.length || showTitleButton}
<BlockComponent

View File

@ -147,7 +147,7 @@
bind:id={formId}
props={{
dataSource,
disableValidation: true,
disableSchemaValidation: true,
editAutoColumns: true,
size,
}}

View File

@ -14,7 +14,7 @@
// Not exposed as a builder setting. Used internally to disable validation
// for fields rendered in things like search blocks.
export let disableValidation = false
export let disableSchemaValidation = false
// Not exposed as a builder setting. Used internally to allow searching on
// auto columns.
@ -103,7 +103,7 @@
{schema}
{table}
{initialValues}
{disableValidation}
{disableSchemaValidation}
{editAutoColumns}
{currentStep}
>

View File

@ -11,7 +11,7 @@
export let size
export let schema
export let table
export let disableValidation = false
export let disableSchemaValidation = false
export let editAutoColumns = false
// We export this store so that when we remount the inner form we can still
@ -156,17 +156,16 @@
if (!field) {
return
}
// Create validation function based on field schema
const schemaConstraints = schema?.[field]?.constraints
const validator = disableValidation
const schemaConstraints = disableSchemaValidation
? null
: createValidatorFromConstraints(
schemaConstraints,
validationRules,
field,
table
)
: schema?.[field]?.constraints
const validator = createValidatorFromConstraints(
schemaConstraints,
validationRules,
field,
table
)
// Sanitise the default value to ensure it doesn't contain invalid data
defaultValue = sanitiseValue(defaultValue, schema?.[field], type)
@ -332,15 +331,15 @@
const { value, error } = fieldState
// Create new validator
const schemaConstraints = schema?.[field]?.constraints
const validator = disableValidation
const schemaConstraints = disableSchemaValidation
? null
: createValidatorFromConstraints(
schemaConstraints,
validationRules,
field,
table
)
: schema?.[field]?.constraints
const validator = createValidatorFromConstraints(
schemaConstraints,
validationRules,
field,
table
)
// Update validator
fieldInfo.update(state => {

View File

@ -108,8 +108,16 @@
}
}
$: forceFetchRows(filter)
$: debouncedFetchRows(searchTerm, primaryDisplay, defaultValue)
const forceFetchRows = async () => {
// if the filter has changed, then we need to reset the options, clear the selection, and re-fetch
optionsObj = {}
fieldApi.setValue([])
selectedValue = []
debouncedFetchRows(searchTerm, primaryDisplay, defaultValue)
}
const fetchRows = async (searchTerm, primaryDisplay, defaultVal) => {
const allRowsFetched =
$fetch.loaded &&

View File

@ -9,8 +9,11 @@ import {
CreateDatasourceResponse,
Datasource,
DatasourcePlus,
Document,
FetchDatasourceInfoRequest,
FetchDatasourceInfoResponse,
FieldType,
RelationshipFieldMetadata,
SourceName,
UpdateDatasourceResponse,
UserCtx,
@ -218,9 +221,26 @@ async function destroyInternalTablesBySourceId(datasourceId: string) {
[]
)
function updateRevisions(deletedLinks: RelationshipFieldMetadata[]) {
for (const link of deletedLinks) {
datasourceTableDocs.forEach((doc: Document) => {
if (doc._id === link.tableId) {
doc._rev = link.tableRev
}
})
}
}
// Destroy the tables.
for (const table of datasourceTableDocs) {
await sdk.tables.internal.destroy(table)
const deleted = await sdk.tables.internal.destroy(table)
// Update the revisions of any tables that remain to be deleted
const deletedLinks: RelationshipFieldMetadata[] = Object.values(
deleted.table.schema
)
.filter(field => field.type === FieldType.LINK)
.map(field => field as RelationshipFieldMetadata)
updateRevisions(deletedLinks)
}
}

View File

@ -13,6 +13,7 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Senior"],
"Start Date": "2015-02-12T12:00:00.000",
"Badge Photo": [],
},
{
"First Name": "Mandy",
@ -28,6 +29,7 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Senior"],
"Start Date": "2017-09-10T12:00:00.000",
"Badge Photo": [],
},
{
"First Name": "Holly",
@ -43,6 +45,7 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Senior"],
"Start Date": "2022-02-12T12:00:00.000",
"Badge Photo": [],
},
{
"First Name": "Francis",
@ -58,6 +61,7 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Apprentice"],
"Start Date": "2021-03-10T12:00:00.000",
"Badge Photo": [],
},
{
"First Name": "Richard",
@ -73,6 +77,7 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Apprentice"],
"Start Date": "2020-07-09T12:00:00.000",
"Badge Photo": [],
},
{
"First Name": "Donald",
@ -88,6 +93,7 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Junior"],
"Start Date": "2018-04-13T12:00:00.000",
"Badge Photo": [],
},
{
"First Name": "Maria",
@ -103,6 +109,7 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Manager"],
"Start Date": "2016-05-22T12:00:00.000",
"Badge Photo": [],
},
{
"First Name": "Suzy",
@ -118,6 +125,7 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Senior", "Manager"],
"Start Date": "2019-05-01T12:00:00.000",
"Badge Photo": [],
},
{
"First Name": "Patrick",
@ -133,6 +141,7 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Apprentice"],
"Start Date": "2014-08-30T12:00:00.000",
"Badge Photo": [],
},
{
"First Name": "Brayden",
@ -148,5 +157,6 @@ export const employeeImport = [
type: "row",
"Employee Level": ["Contractor"],
"Start Date": "2022-11-09T12:00:00.000",
"Badge Photo": [],
},
]

View File

@ -440,7 +440,7 @@ class LinkController {
if (field.type === FieldTypes.LINK && field.fieldName) {
const linkedTable = await this._db.get<Table>(field.tableId)
delete linkedTable.schema[field.fieldName]
await this._db.put(linkedTable)
field.tableRev = (await this._db.put(linkedTable)).rev
}
} catch (err: any) {
logging.logWarn(err?.message, err)

View File

@ -1,18 +1,11 @@
import { rowEmission, tableEmission } from "./utils"
import mainEmitter from "./index"
import env from "../environment"
import { Table, Row } from "@budibase/types"
import { Table, Row, DocumentType, App } from "@budibase/types"
import { context } from "@budibase/backend-core"
// max number of automations that can chain on top of each other
// TODO: in future make this configurable at the automation level
const MAX_AUTOMATION_CHAIN = env.SELF_HOSTED ? 5 : 0
const MAX_AUTOMATIONS_ALLOWED = 5
/**
* Special emitter which takes the count of automation runs which have occurred and blocks an
* automation from running if it has reached the maximum number of chained automations runs.
* This essentially "fakes" the normal emitter to add some functionality in-between to stop automations
* from getting stuck endlessly chaining.
*/
class AutomationEmitter {
chainCount: number
metadata: { automationChainCount: number }
@ -24,7 +17,23 @@ class AutomationEmitter {
}
}
emitRow(eventName: string, appId: string, row: Row, table?: Table) {
async getMaxAutomationChain() {
const db = context.getAppDB()
const appMetadata = await db.get<App>(DocumentType.APP_METADATA)
let chainAutomations = appMetadata?.automations?.chainAutomations
if (chainAutomations === true) {
return MAX_AUTOMATIONS_ALLOWED
} else if (chainAutomations === undefined && env.SELF_HOSTED) {
return MAX_AUTOMATIONS_ALLOWED
} else {
return 0
}
}
async emitRow(eventName: string, appId: string, row: Row, table?: Table) {
let MAX_AUTOMATION_CHAIN = await this.getMaxAutomationChain()
// don't emit even if we've reached max automation chain
if (this.chainCount >= MAX_AUTOMATION_CHAIN) {
return
@ -39,9 +48,11 @@ class AutomationEmitter {
})
}
emitTable(eventName: string, appId: string, table?: Table) {
async emitTable(eventName: string, appId: string, table?: Table) {
let MAX_AUTOMATION_CHAIN = await this.getMaxAutomationChain()
// don't emit even if we've reached max automation chain
if (this.chainCount > MAX_AUTOMATION_CHAIN) {
if (this.chainCount >= MAX_AUTOMATION_CHAIN) {
return
}

View File

@ -61,7 +61,7 @@ export async function getInheritablePermissions(
export async function allowsExplicitPermissions(resourceId: string) {
if (isViewID(resourceId)) {
const allowed = await features.isViewPermissionEnabled()
const minPlan = !allowed ? PlanType.BUSINESS : undefined
const minPlan = !allowed ? PlanType.PREMIUM_PLUS : undefined
return {
allowed,

View File

@ -94,7 +94,7 @@ export async function setTestFlag(id: string) {
}
export async function checkTestFlag(id: string) {
const flag = await flagClient.get(id)
const flag = await flagClient?.get(id)
return !!(flag && flag.testing)
}

View File

@ -48,6 +48,9 @@ async function checkResponse(
let error
try {
error = await response.json()
if (!error.message) {
error = JSON.stringify(error)
}
} catch (err) {
error = await response.text()
}

View File

@ -112,7 +112,7 @@ module.exports.convertHBSBlock = (block, blockNumber) => {
const list = getHelperList()
for (let layer of layers) {
const parts = splitBySpace(layer)
if (value || parts.length > 1) {
if (value || parts.length > 1 || list[parts[0]]) {
// first of layer should always be the helper
const helper = parts.splice(0, 1)
if (list[helper]) {

View File

@ -127,4 +127,12 @@ describe("Test that the string processing works correctly", () => {
"return `average: ${var1} add: ${var2}`;",
])
})
it("should handle uuids", () => {
const response = convertToJS("This is: {{ uuid }}")
checkLines(response, [
"const var1 = helpers.uuid();",
"return `This is: ${var1}`;",
])
})
})

View File

@ -23,6 +23,7 @@ export interface App extends Document {
automationErrors?: AppMetadataErrors
icon?: AppIcon
features?: AppFeatures
automations?: AutomationSettings
}
export interface AppInstance {
@ -68,3 +69,7 @@ export interface AppFeatures {
componentValidation?: boolean
disableUserMetadata?: boolean
}
export interface AutomationSettings {
chainAutomations?: boolean
}

View File

@ -21,6 +21,7 @@ interface BaseRelationshipFieldMetadata
main?: boolean
fieldName: string
tableId: string
tableRev?: string
subtype?: AutoFieldSubTypes.CREATED_BY | AutoFieldSubTypes.UPDATED_BY
}

View File

@ -6290,6 +6290,11 @@
js-yaml "^3.10.0"
tslib "^2.4.0"
"@zerodevx/svelte-json-view@^1.0.7":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@zerodevx/svelte-json-view/-/svelte-json-view-1.0.7.tgz#abf3efa71dedcb3e9d16bc9cc61d5ea98c8d00b1"
integrity sha512-yW0MV+9BCKOwzt3h86y3xDqYdI5st+Rxk+L5pa0Utq7nlPD+VvxyhL7R1gJoLxQvWwjyAvY/fyUCFTdwDyI14w==
"@zkochan/js-yaml@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826"