Merge branch 'master' into feature/count-creators-in-groups
This commit is contained in:
commit
28a9ca0fa7
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.15.0",
|
"version": "2.15.5",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
},
|
},
|
||||||
TRIGGER_AUTOMATION_RUN: {
|
TRIGGER_AUTOMATION_RUN: {
|
||||||
disabled: !triggerAutomationRunEnabled,
|
disabled: !triggerAutomationRunEnabled,
|
||||||
message: collectDisabledMessage(),
|
message: "Please upgrade to a paid plan",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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`
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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": [
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
type,
|
type,
|
||||||
quiet,
|
quiet,
|
||||||
disabled,
|
disabled,
|
||||||
size,
|
size: size || "M",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -147,7 +147,7 @@
|
||||||
bind:id={formId}
|
bind:id={formId}
|
||||||
props={{
|
props={{
|
||||||
dataSource,
|
dataSource,
|
||||||
disableValidation: true,
|
disableSchemaValidation: true,
|
||||||
editAutoColumns: true,
|
editAutoColumns: true,
|
||||||
size,
|
size,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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": [],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]) {
|
||||||
|
|
|
@ -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}`;",
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue