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", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*", "packages/*",

View File

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

View File

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

View File

@ -184,8 +184,9 @@
} }
if ( if (
(idx === 0 && automation.trigger?.event === "row:update") || idx === 0 &&
automation.trigger?.event === "row:save" (automation.trigger?.event === "row:update" ||
automation.trigger?.event === "row:save")
) { ) {
if (name !== "id" && name !== "revision") return `trigger.row.${name}` 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) { if (!savingColumn) {
editableColumn.fieldId = makeFieldId( editableColumn.fieldId = makeFieldId(
@ -389,11 +373,6 @@
deleteColName = "" deleteColName = ""
} }
function extractColumnNumber(columnName) {
const match = columnName.match(/Column (\d+)/)
return match ? parseInt(match[1]) : 0
}
function getAllowedTypes() { function getAllowedTypes() {
if ( if (
originalName && originalName &&

View File

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

View File

@ -17,7 +17,7 @@
export let table export let table
let editorModal let editorModal, editTableNameModal
let confirmDeleteDialog let confirmDeleteDialog
let error = "" let error = ""
@ -101,18 +101,21 @@
<Modal bind:this={editorModal} on:show={initForm}> <Modal bind:this={editorModal} on:show={initForm}>
<ModalContent <ModalContent
bind:this={editTableNameModal}
title="Edit Table" title="Edit Table"
confirmText="Save" confirmText="Save"
onConfirm={save} onConfirm={save}
disabled={updatedName === originalName || error} disabled={updatedName === originalName || error}
> >
<Input <form on:submit|preventDefault={() => editTableNameModal.confirm()}>
label="Table Name" <Input
thin label="Table Name"
bind:value={updatedName} thin
on:input={checkValid} bind:value={updatedName}
{error} on:input={checkValid}
/> {error}
/>
</form>
</ModalContent> </ModalContent>
</Modal> </Modal>
<ConfirmDialog <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> <Content showMobileNav>
<SideNav slot="side-nav"> <SideNav slot="side-nav">
<SideNavItem <SideNavItem
text="Automation History" text="Automations"
url={$url("./automation-history")} url={$url("./automations")}
active={$isActive("./automation-history")} active={$isActive("./automations")}
/> />
<SideNavItem <SideNavItem
text="Backups" text="Backups"

View File

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

View File

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

View File

@ -15,7 +15,7 @@
import { DashCard, Usage } from "components/usage" import { DashCard, Usage } from "components/usage"
import { PlanModel } from "constants" import { PlanModel } from "constants"
import { sdk } from "@budibase/shared-core" import { sdk } from "@budibase/shared-core"
import { PlanType } from "@budibase/types" import { getFormattedPlanName } from "helpers/planTitle"
let staticUsage = [] let staticUsage = []
let monthlyUsage = [] let monthlyUsage = []
@ -100,23 +100,6 @@
cancelAt = license?.billing?.subscription?.cancelAt 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 => { const getDaysRemaining = timestamp => {
if (!timestamp) { if (!timestamp) {
return return
@ -227,7 +210,7 @@
<DashCard <DashCard
description="YOUR CURRENT PLAN" description="YOUR CURRENT PLAN"
title={planTitle()} title={getFormattedPlanName(license?.plan.type)}
{primaryActionText} {primaryActionText}
primaryAction={showButton ? goToAccountPortal : undefined} primaryAction={showButton ? goToAccountPortal : undefined}
{textRows} {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", "tag": "style",
"type": "select", "type": "select",
@ -6131,6 +6114,23 @@
} }
], ],
"defaultValue": "bottom" "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": [ "actions": [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -108,8 +108,16 @@
} }
} }
$: forceFetchRows(filter)
$: debouncedFetchRows(searchTerm, primaryDisplay, defaultValue) $: 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 fetchRows = async (searchTerm, primaryDisplay, defaultVal) => {
const allRowsFetched = const allRowsFetched =
$fetch.loaded && $fetch.loaded &&

View File

@ -9,8 +9,11 @@ import {
CreateDatasourceResponse, CreateDatasourceResponse,
Datasource, Datasource,
DatasourcePlus, DatasourcePlus,
Document,
FetchDatasourceInfoRequest, FetchDatasourceInfoRequest,
FetchDatasourceInfoResponse, FetchDatasourceInfoResponse,
FieldType,
RelationshipFieldMetadata,
SourceName, SourceName,
UpdateDatasourceResponse, UpdateDatasourceResponse,
UserCtx, 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. // Destroy the tables.
for (const table of datasourceTableDocs) { 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", type: "row",
"Employee Level": ["Senior"], "Employee Level": ["Senior"],
"Start Date": "2015-02-12T12:00:00.000", "Start Date": "2015-02-12T12:00:00.000",
"Badge Photo": [],
}, },
{ {
"First Name": "Mandy", "First Name": "Mandy",
@ -28,6 +29,7 @@ export const employeeImport = [
type: "row", type: "row",
"Employee Level": ["Senior"], "Employee Level": ["Senior"],
"Start Date": "2017-09-10T12:00:00.000", "Start Date": "2017-09-10T12:00:00.000",
"Badge Photo": [],
}, },
{ {
"First Name": "Holly", "First Name": "Holly",
@ -43,6 +45,7 @@ export const employeeImport = [
type: "row", type: "row",
"Employee Level": ["Senior"], "Employee Level": ["Senior"],
"Start Date": "2022-02-12T12:00:00.000", "Start Date": "2022-02-12T12:00:00.000",
"Badge Photo": [],
}, },
{ {
"First Name": "Francis", "First Name": "Francis",
@ -58,6 +61,7 @@ export const employeeImport = [
type: "row", type: "row",
"Employee Level": ["Apprentice"], "Employee Level": ["Apprentice"],
"Start Date": "2021-03-10T12:00:00.000", "Start Date": "2021-03-10T12:00:00.000",
"Badge Photo": [],
}, },
{ {
"First Name": "Richard", "First Name": "Richard",
@ -73,6 +77,7 @@ export const employeeImport = [
type: "row", type: "row",
"Employee Level": ["Apprentice"], "Employee Level": ["Apprentice"],
"Start Date": "2020-07-09T12:00:00.000", "Start Date": "2020-07-09T12:00:00.000",
"Badge Photo": [],
}, },
{ {
"First Name": "Donald", "First Name": "Donald",
@ -88,6 +93,7 @@ export const employeeImport = [
type: "row", type: "row",
"Employee Level": ["Junior"], "Employee Level": ["Junior"],
"Start Date": "2018-04-13T12:00:00.000", "Start Date": "2018-04-13T12:00:00.000",
"Badge Photo": [],
}, },
{ {
"First Name": "Maria", "First Name": "Maria",
@ -103,6 +109,7 @@ export const employeeImport = [
type: "row", type: "row",
"Employee Level": ["Manager"], "Employee Level": ["Manager"],
"Start Date": "2016-05-22T12:00:00.000", "Start Date": "2016-05-22T12:00:00.000",
"Badge Photo": [],
}, },
{ {
"First Name": "Suzy", "First Name": "Suzy",
@ -118,6 +125,7 @@ export const employeeImport = [
type: "row", type: "row",
"Employee Level": ["Senior", "Manager"], "Employee Level": ["Senior", "Manager"],
"Start Date": "2019-05-01T12:00:00.000", "Start Date": "2019-05-01T12:00:00.000",
"Badge Photo": [],
}, },
{ {
"First Name": "Patrick", "First Name": "Patrick",
@ -133,6 +141,7 @@ export const employeeImport = [
type: "row", type: "row",
"Employee Level": ["Apprentice"], "Employee Level": ["Apprentice"],
"Start Date": "2014-08-30T12:00:00.000", "Start Date": "2014-08-30T12:00:00.000",
"Badge Photo": [],
}, },
{ {
"First Name": "Brayden", "First Name": "Brayden",
@ -148,5 +157,6 @@ export const employeeImport = [
type: "row", type: "row",
"Employee Level": ["Contractor"], "Employee Level": ["Contractor"],
"Start Date": "2022-11-09T12:00:00.000", "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) { if (field.type === FieldTypes.LINK && field.fieldName) {
const linkedTable = await this._db.get<Table>(field.tableId) const linkedTable = await this._db.get<Table>(field.tableId)
delete linkedTable.schema[field.fieldName] delete linkedTable.schema[field.fieldName]
await this._db.put(linkedTable) field.tableRev = (await this._db.put(linkedTable)).rev
} }
} catch (err: any) { } catch (err: any) {
logging.logWarn(err?.message, err) logging.logWarn(err?.message, err)

View File

@ -1,18 +1,11 @@
import { rowEmission, tableEmission } from "./utils" import { rowEmission, tableEmission } from "./utils"
import mainEmitter from "./index" import mainEmitter from "./index"
import env from "../environment" 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 const MAX_AUTOMATIONS_ALLOWED = 5
// TODO: in future make this configurable at the automation level
const MAX_AUTOMATION_CHAIN = env.SELF_HOSTED ? 5 : 0
/**
* 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 { class AutomationEmitter {
chainCount: number chainCount: number
metadata: { automationChainCount: 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 // don't emit even if we've reached max automation chain
if (this.chainCount >= MAX_AUTOMATION_CHAIN) { if (this.chainCount >= MAX_AUTOMATION_CHAIN) {
return 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 // don't emit even if we've reached max automation chain
if (this.chainCount > MAX_AUTOMATION_CHAIN) { if (this.chainCount >= MAX_AUTOMATION_CHAIN) {
return return
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -127,4 +127,12 @@ describe("Test that the string processing works correctly", () => {
"return `average: ${var1} add: ${var2}`;", "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 automationErrors?: AppMetadataErrors
icon?: AppIcon icon?: AppIcon
features?: AppFeatures features?: AppFeatures
automations?: AutomationSettings
} }
export interface AppInstance { export interface AppInstance {
@ -68,3 +69,7 @@ export interface AppFeatures {
componentValidation?: boolean componentValidation?: boolean
disableUserMetadata?: boolean disableUserMetadata?: boolean
} }
export interface AutomationSettings {
chainAutomations?: boolean
}

View File

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

View File

@ -6290,6 +6290,11 @@
js-yaml "^3.10.0" js-yaml "^3.10.0"
tslib "^2.4.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": "@zkochan/js-yaml@0.0.6":
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826" resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826"