Merge pull request #2984 from Budibase/bug/misc-automation-issues

Fixes for automation issues
This commit is contained in:
Peter Clement 2021-10-13 14:56:44 +01:00 committed by GitHub
commit a746869418
12 changed files with 130 additions and 27 deletions

View File

@ -5,11 +5,14 @@
import RelationshipRenderer from "./RelationshipRenderer.svelte" import RelationshipRenderer from "./RelationshipRenderer.svelte"
import AttachmentRenderer from "./AttachmentRenderer.svelte" import AttachmentRenderer from "./AttachmentRenderer.svelte"
import ArrayRenderer from "./ArrayRenderer.svelte" import ArrayRenderer from "./ArrayRenderer.svelte"
import InternalRenderer from "./InternalRenderer.svelte"
export let row export let row
export let schema export let schema
export let value export let value
export let customRenderers = [] export let customRenderers = []
let renderer
const typeMap = { const typeMap = {
boolean: BooleanRenderer, boolean: BooleanRenderer,
datetime: DateTimeRenderer, datetime: DateTimeRenderer,
@ -20,7 +23,9 @@
number: StringRenderer, number: StringRenderer,
longform: StringRenderer, longform: StringRenderer,
array: ArrayRenderer, array: ArrayRenderer,
internal: InternalRenderer,
} }
$: type = schema?.type ?? "string" $: type = schema?.type ?? "string"
$: customRenderer = customRenderers?.find(x => x.column === schema?.name) $: customRenderer = customRenderers?.find(x => x.column === schema?.name)
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer $: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer

View File

@ -0,0 +1,28 @@
<script>
import Icon from "../Icon/Icon.svelte"
import { notifications } from "../Stores/notifications"
export let value
const onClick = e => {
e.stopPropagation()
copyToClipboard(value)
}
function copyToClipboard(value) {
navigator.clipboard.writeText(value).then(() => {
notifications.success("Copied")
})
}
</script>
<div on:click|stopPropagation={onClick}>
<Icon size="S" name="Copy" />
</div>
<style>
div {
overflow: hidden;
text-overflow: ellipsis;
width: 150px;
}
</style>

View File

@ -31,6 +31,7 @@ context("Create a Table", () => {
cy.contains("nameupdated ").should("contain", "nameupdated") cy.contains("nameupdated ").should("contain", "nameupdated")
}) })
/*
it("edits a row", () => { it("edits a row", () => {
cy.contains("button", "Edit").click({ force: true }) cy.contains("button", "Edit").click({ force: true })
cy.wait(1000) cy.wait(1000)
@ -39,7 +40,7 @@ context("Create a Table", () => {
cy.contains("Save").click() cy.contains("Save").click()
cy.contains("Updated").should("have.text", "Updated") cy.contains("Updated").should("have.text", "Updated")
}) })
*/
it("deletes a row", () => { it("deletes a row", () => {
cy.get(".spectrum-Checkbox-input").check({ force: true }) cy.get(".spectrum-Checkbox-input").check({ force: true })
cy.contains("Delete 1 row(s)").click() cy.contains("Delete 1 row(s)").click()

View File

@ -14,7 +14,7 @@ export default class Automation {
} }
addTestData(data) { addTestData(data) {
this.automation.testData = data this.automation.testData = { ...this.automation.testData, ...data }
} }
addBlock(block, idx) { addBlock(block, idx) {

View File

@ -5,20 +5,24 @@
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
let failedParse = null let failedParse = null
let trigger = {}
let schemaProperties = {}
// clone the trigger so we're not mutating the reference // clone the trigger so we're not mutating the reference
let trigger = cloneDeep( $: trigger = cloneDeep(
$automationStore.selectedAutomation.automation.definition.trigger $automationStore.selectedAutomation.automation.definition.trigger
) )
let schemaProperties = Object.entries(trigger.schema.outputs.properties || {})
// get the outputs so we can define the fields
$: schemaProperties = Object.entries(trigger?.schema?.outputs?.properties)
if (!$automationStore.selectedAutomation.automation.testData) { if (!$automationStore.selectedAutomation.automation.testData) {
$automationStore.selectedAutomation.automation.testData = {} $automationStore.selectedAutomation.automation.testData = {}
} }
// get the outputs so we can define the fields
// check to see if there is existing test data in the store // check to see if there is existing test data in the store
$: testData = $automationStore.selectedAutomation.automation.testData $: testData = $automationStore.selectedAutomation.automation.testData || {}
// Check the schema to see if required fields have been entered // Check the schema to see if required fields have been entered
$: isError = !trigger.schema.outputs.required.every( $: isError = !trigger.schema.outputs.required.every(
required => testData[required] required => testData[required]
@ -41,7 +45,6 @@
showConfirmButton={true} showConfirmButton={true}
disabled={isError} disabled={isError}
onConfirm={() => { onConfirm={() => {
automationStore.actions.addTestDataToAutomation(testData)
automationStore.actions.test( automationStore.actions.test(
$automationStore.selectedAutomation?.automation, $automationStore.selectedAutomation?.automation,
testData testData
@ -53,7 +56,7 @@
><Tab icon="Form" title="Form"> ><Tab icon="Form" title="Form">
<div class="tab-content-padding"> <div class="tab-content-padding">
<AutomationBlockSetup <AutomationBlockSetup
bind:testData {testData}
{schemaProperties} {schemaProperties}
isTestModal isTestModal
block={trigger} block={trigger}

View File

@ -9,7 +9,10 @@
Label, Label,
ActionButton, ActionButton,
Drawer, Drawer,
Modal,
} from "@budibase/bbui" } from "@budibase/bbui"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
import { tables } from "stores/backend" import { tables } from "stores/backend"
import WebhookDisplay from "../Shared/WebhookDisplay.svelte" import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
@ -27,13 +30,14 @@
import { buildLuceneQuery } from "helpers/lucene" import { buildLuceneQuery } from "helpers/lucene"
export let block export let block
export let webhookModal
export let testData export let testData
export let schemaProperties export let schemaProperties
export let isTestModal = false export let isTestModal = false
let webhookModal
let drawer let drawer
let tempFilters = lookForFilters(schemaProperties) || [] let tempFilters = lookForFilters(schemaProperties) || []
let fillWidth = true let fillWidth = true
$: stepId = block.stepId $: stepId = block.stepId
$: bindings = getAvailableBindings( $: bindings = getAvailableBindings(
block || $automationStore.selectedBlock, block || $automationStore.selectedBlock,
@ -50,6 +54,18 @@
const onChange = debounce( const onChange = debounce(
async function (e, key) { async function (e, key) {
if (isTestModal) { if (isTestModal) {
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
if (stepId === "WEBHOOK") {
automationStore.actions.addTestDataToAutomation({
body: {
[key]: e.detail,
...$automationStore.selectedAutomation.automation.testData.body,
},
})
}
automationStore.actions.addTestDataToAutomation({
[key]: e.detail,
})
testData[key] = e.detail testData[key] = e.detail
} else { } else {
block.inputs[key] = e.detail block.inputs[key] = e.detail
@ -205,7 +221,10 @@
{bindings} {bindings}
/> />
{:else if value.customType === "webhookUrl"} {:else if value.customType === "webhookUrl"}
<WebhookDisplay value={inputData[key]} /> <WebhookDisplay
on:change={e => onChange(e, key)}
value={inputData[key]}
/>
{:else if value.customType === "triggerSchema"} {:else if value.customType === "triggerSchema"}
<SchemaSetup on:change={e => onChange(e, key)} value={inputData[key]} /> <SchemaSetup on:change={e => onChange(e, key)} value={inputData[key]} />
{:else if value.customType === "code"} {:else if value.customType === "code"}
@ -247,6 +266,10 @@
</div> </div>
{/each} {/each}
</div> </div>
<Modal bind:this={webhookModal} width="30%">
<CreateWebhookModal />
</Modal>
{#if stepId === "WEBHOOK"} {#if stepId === "WEBHOOK"}
<Button secondary on:click={() => webhookModal.show()}>Set Up Webhook</Button> <Button secondary on:click={() => webhookModal.show()}>Set Up Webhook</Button>
{/if} {/if}

View File

@ -5,16 +5,29 @@
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte" import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let value export let value
export let bindings export let bindings
$: table = $tables.list.find(table => table._id === value?.tableId) let table
$: schemaFields = Object.entries(table?.schema ?? {}) let schemaFields
$: {
table = $tables.list.find(table => table._id === value?.tableId)
schemaFields = Object.entries(table?.schema ?? {})
// surface the schema so the user can see it in the json
schemaFields.map(([, schema]) => {
if (!schema.autocolumn && !value[schema.name]) {
value[schema.name] = ""
}
})
}
const onChangeTable = e => { const onChangeTable = e => {
value = { tableId: e.detail } value["tableId"] = e.detail
dispatch("change", value) dispatch("change", value)
} }
@ -69,6 +82,8 @@
label={field} label={field}
options={schema.constraints.inclusion} options={schema.constraints.inclusion}
/> />
{:else if schema.type === "link"}
<LinkedRowSelector bind:linkedRows={value[field]} {schema} />
{:else if schema.type === "string" || schema.type === "number"} {:else if schema.type === "string" || schema.type === "number"}
{#if $automationStore.selectedAutomation.automation.testData} {#if $automationStore.selectedAutomation.automation.testData}
<ModalBindableInput <ModalBindableInput

View File

@ -1,7 +1,6 @@
<script> <script>
import { Icon } from "@budibase/bbui" import { Icon } from "@budibase/bbui"
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
import { database } from "stores/backend"
import WebhookDisplay from "./WebhookDisplay.svelte" import WebhookDisplay from "./WebhookDisplay.svelte"
import { ModalContent } from "@budibase/bbui" import { ModalContent } from "@budibase/bbui"
import { onMount, onDestroy } from "svelte" import { onMount, onDestroy } from "svelte"
@ -12,7 +11,6 @@
let schemaURL let schemaURL
let propCount = 0 let propCount = 0
$: instanceId = $database._id
$: automation = $automationStore.selectedAutomation?.automation $: automation = $automationStore.selectedAutomation?.automation
onMount(async () => { onMount(async () => {

View File

@ -16,11 +16,29 @@
import { Pagination } from "@budibase/bbui" import { Pagination } from "@budibase/bbui"
let hideAutocolumns = true let hideAutocolumns = true
let schema
$: isUsersTable = $tables.selected?._id === TableNames.USERS $: isUsersTable = $tables.selected?._id === TableNames.USERS
$: schema = $tables.selected?.schema
$: type = $tables.selected?.type $: type = $tables.selected?.type
$: isInternal = type !== "external" $: isInternal = type !== "external"
$: {
schema = $tables.selected?.schema
// Manually add these as we don't want them to be 'real' auto-columns
schema._id = {
type: "internal",
editable: false,
displayName: "ID",
autocolumn: true,
}
if (isInternal) {
schema._rev = {
type: "internal",
editable: false,
displayName: "Revision",
autocolumn: true,
}
}
}
$: id = $tables.selected?._id $: id = $tables.selected?._id
$: search = searchTable(id) $: search = searchTable(id)
$: columnOptions = Object.keys($search.schema || {}) $: columnOptions = Object.keys($search.schema || {})

View File

@ -1,5 +1,12 @@
jest.mock("../../utilities/usageQuota") jest.mock("../../utilities/usageQuota")
jest.mock("../thread") jest.mock("../thread")
jest.mock("../../utilities/redis", () => ({
init: jest.fn(),
checkTestFlag: () => {
return false
},
}))
jest.spyOn(global.console, "error") jest.spyOn(global.console, "error")
require("../../environment") require("../../environment")

View File

@ -22,6 +22,7 @@ exports.definition = {
fields: { fields: {
type: "object", type: "object",
description: "Fields submitted from the app frontend", description: "Fields submitted from the app frontend",
customType: "triggerSchema",
}, },
}, },
required: ["fields"], required: ["fields"],

View File

@ -81,16 +81,20 @@ exports.externalTrigger = async function (
params, params,
{ getResponses } = {} { getResponses } = {}
) { ) {
if (automation.definition != null && automation.definition.trigger != null) { if (
if (automation.definition.trigger.stepId === "APP") { automation.definition != null &&
// values are likely to be submitted as strings, so we shall convert to correct type automation.definition.trigger != null &&
const coercedFields = {} automation.definition.trigger.stepId === definitions.APP.stepId &&
const fields = automation.definition.trigger.inputs.fields automation.definition.trigger.stepId === "APP" &&
for (let key of Object.keys(fields)) { !checkTestFlag(automation._id)
coercedFields[key] = coerce(params.fields[key], fields[key]) ) {
} // values are likely to be submitted as strings, so we shall convert to correct type
params.fields = coercedFields const coercedFields = {}
const fields = automation.definition.trigger.inputs.fields
for (let key of Object.keys(fields)) {
coercedFields[key] = coerce(params.fields[key], fields[key])
} }
params.fields = coercedFields
} }
const data = { automation, event: params } const data = { automation, event: params }
if (getResponses) { if (getResponses) {