Add bindings support to meeting start / end dates (#11759)

* Deprecate zapier+make values1..5

* Allow dates in meeting invite to be bindable

* Add DrawerBindableSlot component

* Show calendar icon

* refactor

* Use cancel icon

* Disable cancel for JS bindings

* WIP

* WIP

* WIP

* Fix linkedIds svelte binding

* Label and icon update

* Handle arrays

* Support text area

* Add reactivity to text area icon

* Support boolean

* JSON support

* Remove rowControl

* Allow boolean field to have three states

* lint

* Refactor

---------

Co-authored-by: Michael Drury <me@michaeldrury.co.uk>
This commit is contained in:
melohagan 2023-09-29 10:15:36 +01:00 committed by GitHub
parent 2868cd9b2b
commit b4e0d98973
9 changed files with 376 additions and 246 deletions

View File

@ -221,18 +221,6 @@ const automationActions = store => ({
newAutomation.definition.steps.splice(blockIdx, 0, block)
await store.actions.save(newAutomation)
},
/**
* "rowControl" appears to be the name of the flag used to determine whether
* a certain automation block uses values or bindings as inputs
*/
toggleRowControl: async (block, rowControl) => {
const newBlock = { ...block, rowControl }
const newAutomation = store.actions.getUpdatedDefinition(
get(selectedAutomation),
newBlock
)
await store.actions.save(newAutomation)
},
deleteAutomationBlock: async block => {
const automation = get(selectedAutomation)
let newAutomation = cloneDeep(automation)

View File

@ -7,7 +7,6 @@
Detail,
Modal,
Button,
Select,
ActionButton,
notifications,
Label,
@ -39,9 +38,6 @@
step => step.stepId === ActionStepID.COLLECT
)
$: automationId = $selectedAutomation?._id
$: showBindingPicker =
block.stepId === ActionStepID.CREATE_ROW ||
block.stepId === ActionStepID.UPDATE_ROW
$: isTrigger = block.type === "TRIGGER"
$: steps = $selectedAutomation?.definition?.steps ?? []
$: blockIdx = steps.findIndex(step => step.id === block.id)
@ -96,15 +92,6 @@
}
}
/**
* "rowControl" appears to be the name of the flag used to determine whether
* a certain automation block uses values or bindings as inputs
*/
function toggleRowControl(evt) {
const rowControl = evt.detail !== "Use values"
automationStore.actions.toggleRowControl(block, rowControl)
}
async function addLooping() {
const loopDefinition = $automationStore.blockDefinitions.ACTION.LOOP
const loopBlock = automationStore.actions.constructBlock(
@ -189,16 +176,6 @@
Add Looping
</ActionButton>
{/if}
{#if showBindingPicker}
<Select
on:change={toggleRowControl}
defaultValue="Use values"
autoWidth
value={block.rowControl ? "Use bindings" : "Use values"}
options={["Use values", "Use bindings"]}
placeholder={null}
/>
{/if}
<ActionButton
on:click={() => deleteStep()}
icon="DeleteOutline"

View File

@ -23,6 +23,7 @@
import { environment, licensing } from "stores/portal"
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
import DrawerBindableSlot from "../../common/bindings/DrawerBindableSlot.svelte"
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
import CodeEditorModal from "./CodeEditorModal.svelte"
import QuerySelector from "./QuerySelector.svelte"
@ -82,33 +83,6 @@
? [hbAutocomplete([...bindingsToCompletions(bindings, codeMode)])]
: []
/**
* TODO - Remove after November 2023
* *******************************
* Code added to provide backwards compatibility between Values 1,2,3,4,5
* and the new JSON body.
*/
let deprecatedSchemaProperties
$: {
if (block?.stepId === "integromat" || block?.stepId === "zapier") {
deprecatedSchemaProperties = schemaProperties.filter(
prop => !prop[0].startsWith("value")
)
if (!deprecatedSchemaProperties.map(entry => entry[0]).includes("body")) {
deprecatedSchemaProperties.push([
"body",
{
title: "Payload",
type: "json",
},
])
}
} else {
deprecatedSchemaProperties = schemaProperties
}
}
/****************************************************/
const getInputData = (testData, blockInputs) => {
// Test data is not cloned for reactivity
let newInputData = testData || cloneDeep(blockInputs)
@ -118,30 +92,6 @@
newInputData = cloneDeep(blockInputs)
}
/**
* TODO - Remove after November 2023
* *******************************
* Code added to provide backwards compatibility between Values 1,2,3,4,5
* and the new JSON body.
*/
if (
(block?.stepId === "integromat" || block?.stepId === "zapier") &&
!newInputData?.body?.value
) {
let deprecatedValues = {
...newInputData,
}
delete deprecatedValues.url
delete deprecatedValues.body
newInputData = {
url: newInputData.url,
body: {
value: JSON.stringify(deprecatedValues),
},
}
}
/**********************************/
inputData = newInputData
setDefaultEnumValues()
}
@ -337,7 +287,7 @@
</script>
<div class="fields">
{#each deprecatedSchemaProperties as [key, value]}
{#each schemaProperties as [key, value]}
{#if canShowField(key, value)}
<div class="block-field">
{#if key !== "fields" && value.type !== "boolean"}
@ -362,18 +312,6 @@
mode="json"
value={inputData[key]?.value}
on:change={e => {
/**
* TODO - Remove after November 2023
* *******************************
* Code added to provide backwards compatibility between Values 1,2,3,4,5
* and the new JSON body.
*/
delete inputData.value1
delete inputData.value2
delete inputData.value3
delete inputData.value4
delete inputData.value5
/***********************/
onChange(e, key)
}}
/>
@ -386,10 +324,23 @@
/>
</div>
{:else if value.type === "date"}
<DrawerBindableSlot
fillWidth
title={value.title}
panel={AutomationBindingPanel}
type={"date"}
value={inputData[key]}
on:change={e => onChange(e, key)}
{bindings}
allowJS={true}
updateOnChange={false}
drawerLeft="260px"
>
<DatePicker
value={inputData[key]}
on:change={e => onChange(e, key)}
/>
</DrawerBindableSlot>
{:else if value.customType === "column"}
<Select
on:change={e => onChange(e, key)}
@ -469,7 +420,6 @@
/>
{:else if value.customType === "row"}
<RowSelector
{block}
value={inputData[key]}
meta={inputData["meta"] || {}}
on:change={e => {

View File

@ -1,18 +1,14 @@
<script>
import { tables } from "stores/backend"
import { Select, Checkbox } from "@budibase/bbui"
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
import { createEventDispatcher } from "svelte"
import RowSelectorTypes from "./RowSelectorTypes.svelte"
import ModalBindableInput from "../../common/bindings/ModalBindableInput.svelte"
const dispatch = createEventDispatcher()
export let value
export let meta
export let bindings
export let block
export let isTestModal
export let isUpdateRow
@ -25,16 +21,6 @@
let table
let schemaFields
let placeholders = {
number: 10,
boolean: "true",
datetime: "2022-02-16T12:00:00.000Z ",
options: "1",
array: "1 2 3 4",
link: "ro_ta_123_456",
longform: "long form text",
}
$: rowControl = block.rowControl
$: {
table = $tables.list.find(table => table._id === value?.tableId)
schemaFields = Object.entries(table?.schema ?? {})
@ -57,19 +43,13 @@
return value
}
if (type === "boolean") {
if (typeof value === "boolean") {
return value
}
return value === "true"
}
if (type === "number") {
if (typeof value === "number") {
return value
}
return Number(value)
}
if (type === "options") {
if (type === "options" || type === "boolean") {
return value
}
if (type === "array") {
@ -127,9 +107,7 @@
{#if schemaFields.length}
<div class="schema-fields">
{#each schemaFields as [field, schema]}
{#if !schema.autocolumn}
{#if schema.type !== "attachment"}
{#if !rowControl}
{#if !schema.autocolumn && schema.type !== "attachment"}
<RowSelectorTypes
{isTestModal}
{field}
@ -138,23 +116,7 @@
{value}
{onChange}
/>
{:else}
<div>
<svelte:component
this={isTestModal ? ModalBindableInput : DrawerBindableInput}
placeholder={placeholders[schema.type]}
panel={AutomationBindingPanel}
value={Array.isArray(value[field])
? value[field].join(",")
: value[field]}
on:change={e => onChange(e, field, schema.type)}
label={field}
type="string"
bindings={parsedBindings}
fillWidth={true}
allowJS={true}
updateOnChange={false}
/>
{/if}
{#if isUpdateRow && schema.type === "link"}
<div class="checkbox-field">
<Checkbox
@ -165,10 +127,6 @@
/>
</div>
{/if}
</div>
{/if}
{/if}
{/if}
{/each}
</div>
{/if}

View File

@ -1,7 +1,6 @@
<script>
import {
Select,
Toggle,
DatePicker,
Multiselect,
TextArea,
@ -9,6 +8,7 @@
} from "@budibase/bbui"
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
import DrawerBindableSlot from "../../common/bindings/DrawerBindableSlot.svelte"
import ModalBindableInput from "../../common/bindings/ModalBindableInput.svelte"
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
import Editor from "components/integration/QueryEditor.svelte"
@ -31,39 +31,57 @@
}
</script>
{#if schemaHasOptions(schema) && schema.type !== "array"}
<DrawerBindableSlot
fillWidth
title={value.title}
label={field}
panel={AutomationBindingPanel}
type={schema.type}
{schema}
value={value[field]}
on:change={e => onChange(e, field)}
{bindings}
allowJS={true}
updateOnChange={false}
drawerLeft="260px"
>
{#if schemaHasOptions(schema) && schema.type !== "array"}
<Select
on:change={e => onChange(e, field)}
label={field}
value={value[field]}
options={schema.constraints.inclusion}
/>
{:else if schema.type === "datetime"}
{:else if schema.type === "datetime"}
<DatePicker
label={field}
value={value[field]}
on:change={e => onChange(e, field)}
/>
{:else if schema.type === "boolean"}
<Toggle
text={field}
value={value[field]}
{:else if schema.type === "boolean"}
<Select
on:change={e => onChange(e, field)}
label={field}
value={value[field]}
options={[
{ label: "True", value: "true" },
{ label: "False", value: "false" },
]}
/>
{:else if schema.type === "array"}
{:else if schema.type === "array"}
<Multiselect
bind:value={value[field]}
label={field}
options={schema.constraints.inclusion}
on:change={e => onChange(e, field)}
/>
{:else if schema.type === "longform"}
{:else if schema.type === "longform"}
<TextArea
label={field}
bind:value={value[field]}
on:change={e => onChange(e, field)}
/>
{:else if schema.type === "json"}
{:else if schema.type === "json"}
<span>
<Label>{field}</Label>
<Editor
@ -77,13 +95,13 @@
value={value[field]}
/>
</span>
{:else if schema.type === "link"}
{:else if schema.type === "link"}
<LinkedRowSelector
bind:linkedRows={value[field]}
{schema}
on:change={e => onChange(e, field)}
/>
{:else if schema.type === "string" || schema.type === "number"}
{:else if schema.type === "string" || schema.type === "number"}
<svelte:component
this={isTestModal ? ModalBindableInput : DrawerBindableInput}
panel={AutomationBindingPanel}
@ -96,4 +114,5 @@
allowJS={true}
updateOnChange={false}
/>
{/if}
{/if}
</DrawerBindableSlot>

View File

@ -11,11 +11,11 @@
const dispatch = createEventDispatcher()
let rows = []
let linkedIds = (Array.isArray(linkedRows) ? linkedRows : [])?.map(
let linkedIds = []
$: linkedIds = (Array.isArray(linkedRows) ? linkedRows : [])?.map(
row => row?._id || row
)
$: linkedRows = linkedIds
$: label = capitalise(schema.name)
$: linkedTableId = schema.tableId
$: linkedTable = $tables.list.find(table => table._id === linkedTableId)

View File

@ -0,0 +1,250 @@
<script>
import { Icon, Input, Drawer, Button } from "@budibase/bbui"
import {
readableToRuntimeBinding,
runtimeToReadableBinding,
} from "builderStore/dataBinding"
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
import { createEventDispatcher, setContext } from "svelte"
import { isJSBinding } from "@budibase/string-templates"
export let panel = ClientBindingPanel
export let value = ""
export let bindings = []
export let title = "Bindings"
export let placeholder
export let label
export let disabled = false
export let fillWidth
export let allowJS = true
export let allowHelpers = true
export let updateOnChange = true
export let drawerLeft
export let type
export let schema
const dispatch = createEventDispatcher()
let bindingDrawer
let valid = true
let currentVal = value
$: readableValue = runtimeToReadableBinding(bindings, value)
$: tempValue = readableValue
$: isJS = isJSBinding(value)
const saveBinding = () => {
onChange(tempValue)
onBlur()
bindingDrawer.hide()
}
setContext("binding-drawer-actions", {
save: saveBinding,
})
const onChange = value => {
if (type === "link" && value && hasValidLinks(value)) {
currentVal = value.split(",")
} else if (type === "array" && value && hasValidOptions(value)) {
currentVal = value.split(",")
} else {
currentVal = readableToRuntimeBinding(bindings, value)
}
dispatch("change", currentVal)
}
const onBlur = () => {
dispatch("blur", currentVal)
}
const isValidDate = value => {
return !value || !isNaN(new Date(value).valueOf())
}
const hasValidLinks = value => {
let links = []
if (Array.isArray(value)) {
links = value
} else if (value && typeof value === "string") {
links = value.split(",")
} else {
return !value
}
return links.every(link => link.startsWith("ro_"))
}
const hasValidOptions = value => {
let links = []
if (Array.isArray(value)) {
links = value
} else if (value && typeof value === "string") {
links = value.split(",")
} else {
return !value
}
return links.every(link => schema?.constraints?.inclusion?.includes(link))
}
const isValidBoolean = value => {
return value === "false" || value === "true" || value == ""
}
const validationMap = {
date: isValidDate,
datetime: isValidDate,
link: hasValidLinks,
array: hasValidOptions,
longform: value => !isJSBinding(value),
json: value => !isJSBinding(value),
boolean: isValidBoolean,
}
const isValid = value => {
const validate = validationMap[type]
return validate ? validate(value) : true
}
const getIconClass = (value, type) => {
if (type === "longform" && !isJSBinding(value)) {
return "text-area-slot-icon"
}
if (type === "json" && !isJSBinding(value)) {
return "json-slot-icon"
}
if (type !== "string" && type !== "number") {
return "slot-icon"
}
return ""
}
</script>
<div class="control" class:disabled>
{#if !isValid(value)}
<Input
{label}
{disabled}
readonly={isJS}
value={isJS ? "(JavaScript function)" : readableValue}
on:change={event => onChange(event.detail)}
on:blur={onBlur}
{placeholder}
{updateOnChange}
/>
<div
class="icon"
on:click={() => {
if (!isJS) {
dispatch("change", "")
}
}}
>
<Icon disabled={isJS} size="S" name="Close" />
</div>
{:else}
<slot
{label}
{disabled}
readonly={isJS}
value={isJS ? "(JavaScript function)" : readableValue}
{placeholder}
{updateOnChange}
/>
{/if}
{#if !disabled}
<div
class={`icon ${getIconClass(value, type)}`}
on:click={() => {
bindingDrawer.show()
}}
>
<Icon size="S" name="FlashOn" />
</div>
{/if}
</div>
<Drawer
on:drawerHide
on:drawerShow
{fillWidth}
bind:this={bindingDrawer}
{title}
left={drawerLeft}
headless
>
<svelte:fragment slot="description">
Add the objects on the left to enrich your text.
</svelte:fragment>
<Button cta slot="buttons" disabled={!valid} on:click={saveBinding}>
Save
</Button>
<svelte:component
this={panel}
slot="body"
bind:valid
value={readableValue}
on:change={event => (tempValue = event.detail)}
{bindings}
{allowJS}
{allowHelpers}
/>
</Drawer>
<style>
.control {
flex: 1;
position: relative;
}
.slot-icon {
right: 31px !important;
border-right: 1px solid var(--spectrum-alias-border-color);
border-top-right-radius: 0px !important;
border-bottom-right-radius: 0px !important;
}
.text-area-slot-icon {
border-bottom: 1px solid var(--spectrum-alias-border-color);
border-bottom-right-radius: 0px !important;
top: 26px !important;
}
.json-slot-icon {
border-bottom: 1px solid var(--spectrum-alias-border-color);
border-bottom-right-radius: 0px !important;
top: 23px !important;
right: 0px !important;
}
.icon {
right: 1px;
bottom: 1px;
position: absolute;
justify-content: center;
align-items: center;
display: flex;
flex-direction: row;
box-sizing: border-box;
border-left: 1px solid var(--spectrum-alias-border-color);
border-top-right-radius: var(--spectrum-alias-border-radius-regular);
border-bottom-right-radius: var(--spectrum-alias-border-radius-regular);
width: 31px;
color: var(--spectrum-alias-text-color);
background-color: var(--spectrum-global-color-gray-75);
transition: background-color
var(--spectrum-global-animation-duration-100, 130ms),
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
border-color var(--spectrum-global-animation-duration-100, 130ms);
height: calc(var(--spectrum-alias-item-height-m) - 2px);
}
.icon:hover {
cursor: pointer;
color: var(--spectrum-alias-text-color-hover);
background-color: var(--spectrum-global-color-gray-50);
border-color: var(--spectrum-alias-border-color-hover);
}
.control:not(.disabled) :global(.spectrum-Textfield-input) {
padding-right: 40px;
}
</style>

View File

@ -78,8 +78,7 @@ export const definition: AutomationStepSchema = {
}
export async function run({ inputs }: AutomationStepInput) {
//TODO - Remove deprecated values 1,2,3,4,5 after November 2023
const { url, value1, value2, value3, value4, value5, body } = inputs
const { url, body } = inputs
let payload = {}
try {
@ -104,11 +103,6 @@ export async function run({ inputs }: AutomationStepInput) {
response = await fetch(url, {
method: "post",
body: JSON.stringify({
value1,
value2,
value3,
value4,
value5,
...payload,
}),
headers: {

View File

@ -71,8 +71,7 @@ export const definition: AutomationStepSchema = {
}
export async function run({ inputs }: AutomationStepInput) {
//TODO - Remove deprecated values 1,2,3,4,5 after November 2023
const { url, value1, value2, value3, value4, value5, body } = inputs
const { url, body } = inputs
let payload = {}
try {
@ -100,11 +99,6 @@ export async function run({ inputs }: AutomationStepInput) {
method: "post",
body: JSON.stringify({
platform: "budibase",
value1,
value2,
value3,
value4,
value5,
...payload,
}),
headers: {