Merge branch 'master' into fix-typo-in-helm-readme

This commit is contained in:
Sam Rose 2023-12-06 11:02:06 +00:00 committed by GitHub
commit d1f6747166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 446 additions and 240 deletions

View File

@ -249,4 +249,30 @@ http {
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
}
# From https://docs.datadoghq.com/integrations/nginx/?tab=kubernetes
server {
listen 81;
server_name localhost;
access_log off;
allow 127.0.0.1;
deny all;
location /nginx_status {
# Choose your status module
# freely available with open source NGINX
stub_status;
# for open source NGINX < version 1.7.5
# stub_status on;
# available only with NGINX Plus
# status;
# ensures the version information can be retrieved
server_tokens on;
}
}
}

View File

@ -8,6 +8,7 @@ import { derived, get } from "svelte/store"
import { findComponent, findComponentPath } from "./componentUtils"
import { RoleUtils } from "@budibase/frontend-core"
import { createHistoryStore } from "builderStore/store/history"
import { cloneDeep } from "lodash/fp"
export const store = getFrontendStore()
export const automationStore = getAutomationStore()
@ -69,7 +70,14 @@ export const selectedComponent = derived(
if (!$selectedScreen || !$store.selectedComponentId) {
return null
}
return findComponent($selectedScreen?.props, $store.selectedComponentId)
const selected = findComponent(
$selectedScreen?.props,
$store.selectedComponentId
)
const clone = selected ? cloneDeep(selected) : selected
store.actions.components.migrateSettings(clone)
return clone
}
)

View File

@ -601,6 +601,36 @@ export const getFrontendStore = () => {
// Finally try an external table
return validTables.find(table => table.sourceType === DB_TYPE_EXTERNAL)
},
migrateSettings: enrichedComponent => {
const componentPrefix = "@budibase/standard-components"
let migrated = false
if (enrichedComponent?._component == `${componentPrefix}/formblock`) {
// Use default config if the 'buttons' prop has never been initialised
if (!("buttons" in enrichedComponent)) {
enrichedComponent["buttons"] =
Utils.buildDynamicButtonConfig(enrichedComponent)
migrated = true
} else if (enrichedComponent["buttons"] == null) {
// Ignore legacy config if 'buttons' has been reset by 'resetOn'
const { _id, actionType, dataSource } = enrichedComponent
enrichedComponent["buttons"] = Utils.buildDynamicButtonConfig({
_id,
actionType,
dataSource,
})
migrated = true
}
// Ensure existing Formblocks position their buttons at the top.
if (!("buttonPosition" in enrichedComponent)) {
enrichedComponent["buttonPosition"] = "top"
migrated = true
}
}
return migrated
},
enrichEmptySettings: (component, opts) => {
if (!component?._component) {
return
@ -672,7 +702,6 @@ export const getFrontendStore = () => {
component[setting.key] = setting.defaultValue
}
}
// Validate non-empty settings
else {
if (setting.type === "dataProvider") {
@ -722,6 +751,9 @@ export const getFrontendStore = () => {
useDefaultValues: true,
})
// Migrate nested component settings
store.actions.components.migrateSettings(instance)
// Add any extra properties the component needs
let extras = {}
if (definition.hasChildren) {
@ -845,7 +877,13 @@ export const getFrontendStore = () => {
if (!component) {
return false
}
return patchFn(component, screen)
// Mutates the fetched component with updates
const updated = patchFn(component, screen)
// Mutates the component with any required settings updates
const migrated = store.actions.components.migrateSettings(component)
return updated || migrated
}
await store.actions.screens.patch(patchScreen, screenId)
},
@ -1247,9 +1285,13 @@ export const getFrontendStore = () => {
const settings = getComponentSettings(component._component)
const updatedSetting = settings.find(setting => setting.key === name)
const resetFields = settings.filter(
setting => name === setting.resetOn
// Can be a single string or array of strings
const resetFields = settings.filter(setting => {
return (
name === setting.resetOn ||
(Array.isArray(setting.resetOn) && setting.resetOn.includes(name))
)
})
resetFields?.forEach(setting => {
component[setting.key] = null
})
@ -1271,6 +1313,7 @@ export const getFrontendStore = () => {
})
}
component[name] = value
return true
}
},
requestEjectBlock: componentId => {
@ -1278,7 +1321,6 @@ export const getFrontendStore = () => {
},
handleEjectBlock: async (componentId, ejectedDefinition) => {
let nextSelectedComponentId
await store.actions.screens.patch(screen => {
const block = findComponent(screen.props, componentId)
const parent = findComponentParent(screen.props, componentId)

View File

@ -57,16 +57,11 @@
}}
class="buttons"
>
<Icon hoverable size="M" name="Play" />
<Icon size="M" name="Play" />
<div>Run test</div>
</div>
<div class="buttons">
<Icon
disabled={!$automationStore.testResults}
hoverable
size="M"
name="Multiple"
/>
<Icon disabled={!$automationStore.testResults} size="M" name="Multiple" />
<div
class:disabled={!$automationStore.testResults}
on:click={() => {

View File

@ -97,6 +97,7 @@
class:typing={typing && !automationNameError}
class:typing-error={automationNameError}
class="blockSection"
on:click={() => dispatch("toggle")}
>
<div class="splitHeader">
<div class="center-items">
@ -138,7 +139,20 @@
on:input={e => {
automationName = e.target.value.trim()
}}
on:click={startTyping}
on:click={e => {
e.stopPropagation()
startTyping()
}}
on:keydown={async e => {
if (e.key === "Enter") {
typing = false
if (automationNameError) {
automationName = stepNames[block.id] || block?.name
} else {
await saveName()
}
}
}}
on:blur={async () => {
typing = false
if (automationNameError) {
@ -168,7 +182,11 @@
</StatusLight>
</div>
<Icon
on:click={() => dispatch("toggle")}
e.stopPropagation()
on:click={e => {
e.stopPropagation()
dispatch("toggle")
}}
hoverable
name={open ? "ChevronUp" : "ChevronDown"}
/>
@ -195,7 +213,10 @@
{/if}
{#if !showTestStatus}
<Icon
on:click={() => dispatch("toggle")}
on:click={e => {
e.stopPropagation()
dispatch("toggle")
}}
hoverable
name={open ? "ChevronUp" : "ChevronDown"}
/>

View File

@ -1,11 +1,9 @@
<script>
import {
ModalContent,
Tabs,
Tab,
TextArea,
Label,
notifications,
ActionButton,
} from "@budibase/bbui"
import { automationStore, selectedAutomation } from "builderStore"
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
@ -55,19 +53,36 @@
notifications.error(error)
}
}
const toggle = () => {
selectedValues = !selectedValues
selectedJSON = !selectedJSON
}
let selectedValues = true
let selectedJSON = false
</script>
<ModalContent
title="Add test data"
confirmText="Test"
size="M"
confirmText="Run test"
size="L"
showConfirmButton={true}
disabled={isError}
onConfirm={testAutomation}
cancelText="Cancel"
>
<Tabs selected="Form" quiet>
<Tab icon="Form" title="Form">
<div class="size">
<div class="options">
<ActionButton quiet selected={selectedValues} on:click={toggle}
>Use values</ActionButton
>
<ActionButton quiet selected={selectedJSON} on:click={toggle}
>Use JSON</ActionButton
>
</div>
</div>
{#if selectedValues}
<div class="tab-content-padding">
<AutomationBlockSetup
{testData}
@ -75,11 +90,9 @@
isTestModal
block={trigger}
/>
</div></Tab
>
<Tab icon="FileJson" title="JSON">
<div class="tab-content-padding">
<Label>JSON</Label>
</div>
{/if}
{#if selectedJSON}
<div class="text-area-container">
<TextArea
value={JSON.stringify($selectedAutomation.testData, null, 2)}
@ -87,18 +100,22 @@
on:change={e => parseTestJSON(e)}
/>
</div>
</div>
</Tab>
</Tabs>
{/if}
</ModalContent>
<style>
.text-area-container :global(textarea) {
min-height: 200px;
height: 200px;
min-height: 300px;
height: 300px;
}
.tab-content-padding {
padding: 0 var(--spacing-xl);
padding: 0 var(--spacing-s);
}
.options {
display: flex;
align-items: center;
gap: 8px;
}
</style>

View File

@ -9,7 +9,7 @@
<div class="title">
<div class="title-text">
<Icon name="MultipleCheck" />
<div style="padding-left: var(--spacing-l)">Test Details</div>
<div style="padding-left: var(--spacing-l); ">Test Details</div>
</div>
<div style="padding-right: var(--spacing-xl)">
<Icon
@ -40,6 +40,7 @@
display: flex;
flex-direction: row;
align-items: center;
padding-top: var(--spacing-s);
}
.title :global(h1) {

View File

@ -1,20 +1,44 @@
<script>
import AutomationList from "./AutomationList.svelte"
import CreateAutomationModal from "./CreateAutomationModal.svelte"
import { Modal, Button, Layout } from "@budibase/bbui"
import { Modal, Icon } from "@budibase/bbui"
import Panel from "components/design/Panel.svelte"
export let modal
export let webhookModal
</script>
<Panel title="Automations" borderRight>
<Layout paddingX="L" paddingY="XL" gap="S">
<Button cta on:click={modal.show}>Add automation</Button>
</Layout>
<Panel title="Automations" borderRight noHeaderBorder titleCSS={false}>
<span class="panel-title-content" slot="panel-title-content">
<div class="header">
<div>Automations</div>
<div on:click={modal.show} class="add-automation-button">
<Icon name="Add" />
</div>
</div>
</span>
<AutomationList />
</Panel>
<Modal bind:this={modal}>
<CreateAutomationModal {webhookModal} />
</Modal>
<style>
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-m);
}
.add-automation-button {
margin-left: 130px;
color: var(--grey-7);
cursor: pointer;
}
.add-automation-button:hover {
color: var(--ink);
}
</style>

View File

@ -149,7 +149,6 @@
}
const initialiseField = (field, savingColumn) => {
isCreating = !field
if (field && !savingColumn) {
editableColumn = cloneDeep(field)
originalName = editableColumn.name ? editableColumn.name + "" : null
@ -171,7 +170,8 @@
relationshipPart2 = part2
}
}
} else if (!savingColumn) {
}
if (!savingColumn) {
let highestNumber = 0
Object.keys(table.schema).forEach(columnName => {
const columnNumber = extractColumnNumber(columnName)
@ -307,12 +307,6 @@
dispatch("updatecolumns")
gridDispatch("close-edit-column")
if (saveColumn.type === LINK_TYPE) {
// Fetching the new tables
tables.fetch()
// Fetching the new relationships
datasources.fetch()
}
if (originalName) {
notifications.success("Column updated successfully")
} else {
@ -339,11 +333,6 @@
confirmDeleteDialog.hide()
dispatch("updatecolumns")
gridDispatch("close-edit-column")
if (editableColumn.type === LINK_TYPE) {
// Updating the relationships
datasources.fetch()
}
}
} catch (error) {
notifications.error(`Error deleting column: ${error.message}`)
@ -540,8 +529,16 @@
<Layout noPadding gap="S">
{#if mounted}
<Input
value={editableColumn.name}
autofocus
bind:value={editableColumn.name}
on:input={e => {
if (
!uneditable &&
!(linkEditDisabled && editableColumn.type === LINK_TYPE)
) {
editableColumn.name = e.target.value
}
}}
disabled={uneditable ||
(linkEditDisabled && editableColumn.type === LINK_TYPE)}
error={errors?.name}

View File

@ -16,7 +16,8 @@
export let wide = false
export let extraWide = false
export let closeButtonIcon = "Close"
export let noHeaderBorder = false
export let titleCSS = true
$: customHeaderContent = $$slots["panel-header-content"]
$: customTitleContent = $$slots["panel-title-content"]
</script>
@ -32,6 +33,7 @@
class="header"
class:custom={customHeaderContent}
class:borderBottom={borderBottomHeader}
class:noHeaderBorder
>
{#if showBackButton}
<Icon name="ArrowLeft" hoverable on:click={onClickBackButton} />
@ -41,7 +43,7 @@
<Icon name={icon} />
</AbsTooltip>
{/if}
<div class="title">
<div class:title={titleCSS}>
{#if customTitleContent}
<slot name="panel-title-content" />
{:else}
@ -106,6 +108,10 @@
padding: 0 var(--spacing-l);
gap: var(--spacing-m);
}
.noHeaderBorder {
border-bottom: none !important;
}
.header.borderBottom {
border-bottom: var(--border-light);
}

View File

@ -110,7 +110,7 @@
}
.setup {
padding-top: var(--spectrum-global-dimension-size-200);
padding-top: 9px;
border-left: var(--border-light);
display: flex;
flex-direction: column;

View File

@ -81,13 +81,21 @@ export function createTablesStore() {
replaceTable(savedTable._id, savedTable)
select(savedTable._id)
// make sure tables up to date (related)
let tableIdsToFetch = []
let newTableIds = []
for (let column of Object.values(updatedTable?.schema || {})) {
if (column.type === FIELDS.LINK.type) {
tableIdsToFetch.push(column.tableId)
newTableIds.push(column.tableId)
}
}
tableIdsToFetch = [...new Set(tableIdsToFetch)]
let oldTableIds = []
for (let column of Object.values(oldTable?.schema || {})) {
if (column.type === FIELDS.LINK.type) {
oldTableIds.push(column.tableId)
}
}
const tableIdsToFetch = [...new Set([...newTableIds, ...oldTableIds])]
// too many tables to fetch, just get all
if (tableIdsToFetch.length > 3) {
await fetch()

View File

@ -6112,54 +6112,32 @@
}
]
},
{
"tag": "style",
"type": "select",
"label": "Button position",
"key": "buttonPosition",
"options": [
{
"label": "Bottom",
"value": "bottom"
},
{
"label": "Top",
"value": "top"
}
],
"defaultValue": "bottom"
},
{
"section": true,
"name": "Buttons",
"dependsOn": {
"setting": "actionType",
"value": "View",
"invert": true
},
"settings": [
{
"type": "text",
"key": "saveButtonLabel",
"label": "Save button",
"type": "buttonConfiguration",
"key": "buttons",
"nested": true,
"defaultValue": "Save"
},
{
"type": "text",
"key": "deleteButtonLabel",
"label": "Delete button",
"nested": true,
"defaultValue": "Delete",
"dependsOn": {
"setting": "actionType",
"value": "Update"
}
},
{
"type": "url",
"label": "Navigate after button press",
"key": "actionUrl",
"placeholder": "Choose a screen",
"dependsOn": {
"setting": "actionType",
"value": "View",
"invert": true
}
},
{
"type": "boolean",
"label": "Hide notifications",
"key": "notificationOverride",
"defaultValue": false,
"dependsOn": {
"setting": "actionType",
"value": "View",
"invert": true
}
"resetOn": ["actionType", "dataSource"]
}
]
},

View File

@ -5,6 +5,7 @@
import BlockComponent from "components/BlockComponent.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
import { enrichSearchColumns, enrichFilter } from "utils/blocks.js"
import { Utils } from "@budibase/frontend-core"
export let title
export let dataSource
@ -33,6 +34,7 @@
export let notificationOverride
const { fetchDatasourceSchema, API } = getContext("sdk")
const component = getContext("component")
const stateKey = `ID_${generate()}`
let formId
@ -259,16 +261,25 @@
name="Details form block"
type="formblock"
bind:id={detailsFormBlockId}
context="form-edit"
props={{
dataSource,
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
buttonPosition: "top",
buttons: Utils.buildDynamicButtonConfig({
_id: $component.id + "-form-edit",
showDeleteButton: deleteLabel !== "",
showSaveButton: true,
saveButtonLabel: sidePanelSaveLabel || "Save",
deleteButtonLabel: deleteLabel,
notificationOverride,
actionType: "Update",
dataSource,
}),
actionType: "Update",
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
fields: sidePanelFields || normalFields,
title: editTitle,
labelPosition: "left",
notificationOverride,
}}
/>
</BlockComponent>
@ -284,16 +295,23 @@
<BlockComponent
name="New row form block"
type="formblock"
context="form-new"
props={{
dataSource,
showSaveButton: true,
buttonPosition: "top",
buttons: Utils.buildDynamicButtonConfig({
_id: $component.id + "-form-new",
showDeleteButton: false,
saveButtonLabel: sidePanelSaveLabel || "Save", //always show
showSaveButton: true,
saveButtonLabel: "Save",
notificationOverride,
actionType: "Create",
dataSource,
}),
actionType: "Create",
fields: sidePanelFields || normalFields,
title: "Create Row",
labelPosition: "left",
notificationOverride,
}}
/>
</BlockComponent>

View File

@ -4,28 +4,31 @@
import Block from "components/Block.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
import InnerFormBlock from "./InnerFormBlock.svelte"
import { Utils } from "@budibase/frontend-core"
export let actionType
export let dataSource
export let size
export let disabled
export let fields
export let buttons
export let buttonPosition
export let title
export let description
export let showDeleteButton
export let showSaveButton
export let saveButtonLabel
export let deleteButtonLabel
export let rowId
export let actionUrl
export let noRowsMessage
export let notificationOverride
// Accommodate old config to ensure delete button does not reappear
$: deleteLabel = showDeleteButton === false ? "" : deleteButtonLabel?.trim()
$: saveLabel = showSaveButton === false ? "" : saveButtonLabel?.trim()
// Legacy
export let showDeleteButton
export let showSaveButton
export let saveButtonLabel
export let deleteButtonLabel
const { fetchDatasourceSchema } = getContext("sdk")
const component = getContext("component")
const convertOldFieldFormat = fields => {
if (!fields) {
@ -98,11 +101,23 @@
fields: fieldsOrDefault,
title,
description,
saveButtonLabel: saveLabel,
deleteButtonLabel: deleteLabel,
schema,
repeaterId,
notificationOverride,
buttons:
buttons ||
Utils.buildDynamicButtonConfig({
_id: $component.id,
showDeleteButton,
showSaveButton,
saveButtonLabel,
deleteButtonLabel,
notificationOverride,
actionType,
actionUrl,
dataSource,
}),
buttonPosition: buttons ? buttonPosition : "top",
}
const fetchSchema = async () => {
schema = (await fetchDatasourceSchema(dataSource)) || {}

View File

@ -1,22 +1,18 @@
<script>
import BlockComponent from "components/BlockComponent.svelte"
import Placeholder from "components/app/Placeholder.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
import { getContext } from "svelte"
export let dataSource
export let actionUrl
export let actionType
export let size
export let disabled
export let fields
export let title
export let description
export let saveButtonLabel
export let deleteButtonLabel
export let buttons
export let buttonPosition = "bottom"
export let schema
export let repeaterId
export let notificationOverride
const FieldTypeToComponentMap = {
string: "stringfield",
@ -37,74 +33,7 @@
let formId
$: onSave = [
{
"##eventHandlerType": "Validate Form",
parameters: {
componentId: formId,
},
},
{
"##eventHandlerType": "Save Row",
parameters: {
providerId: formId,
tableId: dataSource?.resourceId,
notificationOverride,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Close Side Panel",
},
// Clear a create form once submitted
...(actionType !== "Create"
? []
: [
{
"##eventHandlerType": "Clear Form",
parameters: {
componentId: formId,
},
},
]),
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
$: onDelete = [
{
"##eventHandlerType": "Delete Row",
parameters: {
confirm: true,
tableId: dataSource?.resourceId,
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
notificationOverride,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Close Side Panel",
},
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
$: renderDeleteButton = deleteButtonLabel && actionType === "Update"
$: renderSaveButton = saveButtonLabel && actionType !== "View"
$: renderButtons = renderDeleteButton || renderSaveButton
$: renderHeader = renderButtons || title
$: renderHeader = buttons || title
const getComponentForField = field => {
const fieldSchemaName = field.field || field.name
@ -184,43 +113,15 @@
props={{ text: title || "" }}
order={0}
/>
{#if renderButtons}
{#if buttonPosition == "top"}
<BlockComponent
type="container"
type="buttongroup"
props={{
direction: "row",
hAlign: "stretch",
vAlign: "center",
gap: "M",
wrap: true,
}}
order={1}
>
{#if renderDeleteButton}
<BlockComponent
type="button"
props={{
text: deleteButtonLabel,
onClick: onDelete,
quiet: true,
type: "secondary",
buttons,
}}
order={0}
/>
{/if}
{#if renderSaveButton}
<BlockComponent
type="button"
props={{
text: saveButtonLabel,
onClick: onSave,
type: "cta",
}}
order={1}
/>
{/if}
</BlockComponent>
{/if}
</BlockComponent>
</BlockComponent>
{/if}
@ -245,6 +146,20 @@
</BlockComponent>
{/key}
</BlockComponent>
{#if buttonPosition === "bottom"}
<BlockComponent
type="buttongroup"
props={{
buttons,
}}
styles={{
normal: {
"margin-top": "16",
},
}}
order={1}
/>
{/if}
</BlockComponent>
{:else}
<Placeholder

View File

@ -1,3 +1,6 @@
import { makePropSafe as safe } from "@budibase/string-templates"
import { Helpers } from "@budibase/bbui"
/**
* Utility to wrap an async function and ensure all invocations happen
* sequentially.
@ -106,3 +109,135 @@ export const domDebounce = callback => {
}
}
}
/**
* Build the default FormBlock button configs per actionType
* Parse any legacy button config and mirror its the outcome
*
* @param {any} props
* */
export const buildDynamicButtonConfig = props => {
const {
_id,
actionType,
dataSource,
notificationOverride,
actionUrl,
showDeleteButton,
deleteButtonLabel,
showSaveButton,
saveButtonLabel,
} = props || {}
if (!_id) {
console.log("MISSING ID")
return
}
const formId = `${_id}-form`
const repeaterId = `${_id}-repeater`
const resourceId = dataSource?.resourceId
// Accommodate old config to ensure delete button does not reappear
const deleteText = showDeleteButton === false ? "" : deleteButtonLabel?.trim()
const saveText = showSaveButton === false ? "" : saveButtonLabel?.trim()
const onSave = [
{
"##eventHandlerType": "Validate Form",
parameters: {
componentId: formId,
},
},
{
"##eventHandlerType": "Save Row",
parameters: {
providerId: formId,
tableId: resourceId,
notificationOverride,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Close Side Panel",
},
// Clear a create form once submitted
...(actionType !== "Create"
? []
: [
{
"##eventHandlerType": "Clear Form",
parameters: {
componentId: formId,
},
},
]),
...(actionUrl
? [
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
: []),
]
const onDelete = [
{
"##eventHandlerType": "Delete Row",
parameters: {
confirm: true,
tableId: resourceId,
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
notificationOverride,
},
},
{
"##eventHandlerType": "Close Screen Modal",
},
{
"##eventHandlerType": "Close Side Panel",
},
...(actionUrl
? [
{
"##eventHandlerType": "Navigate To",
parameters: {
url: actionUrl,
},
},
]
: []),
]
const defaultButtons = []
if (["Update", "Create"].includes(actionType) && showSaveButton !== false) {
defaultButtons.push({
text: saveText || "Save",
_id: Helpers.uuid(),
_component: "@budibase/standard-components/button",
onClick: onSave,
type: "cta",
})
}
if (actionType == "Update" && showDeleteButton !== false) {
defaultButtons.push({
text: deleteText || "Delete",
_id: Helpers.uuid(),
_component: "@budibase/standard-components/button",
onClick: onDelete,
quiet: true,
type: "secondary",
})
}
return defaultButtons
}