Add JSON payload support for Make and Zapier (#10529)
* Rename Integromat to Make. Update logo. * Add JSON type for automations * Support deprecated values in JSON * Fix json query editor width bug * Push body to schema if missing * Support JSON body * Add JSON payload support for Zapier * Update packages/server/src/automations/steps/make.ts Co-authored-by: Martin McKeaveney <martin@budibase.com> * July -> November * Add unit tests --------- Co-authored-by: Martin McKeaveney <martin@budibase.com>
This commit is contained in:
parent
14906e762c
commit
395cf4a667
|
@ -61,11 +61,63 @@
|
||||||
$: isTrigger = block?.type === "TRIGGER"
|
$: isTrigger = block?.type === "TRIGGER"
|
||||||
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
|
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) => {
|
const getInputData = (testData, blockInputs) => {
|
||||||
let newInputData = testData || blockInputs
|
let newInputData = testData || blockInputs
|
||||||
if (block.event === "app:trigger" && !newInputData?.fields) {
|
if (block.event === "app:trigger" && !newInputData?.fields) {
|
||||||
newInputData = cloneDeep(blockInputs)
|
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
|
inputData = newInputData
|
||||||
setDefaultEnumValues()
|
setDefaultEnumValues()
|
||||||
}
|
}
|
||||||
|
@ -239,7 +291,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
{#each schemaProperties as [key, value]}
|
{#each deprecatedSchemaProperties as [key, value]}
|
||||||
<div class="block-field">
|
<div class="block-field">
|
||||||
{#if key !== "fields"}
|
{#if key !== "fields"}
|
||||||
<Label
|
<Label
|
||||||
|
@ -256,6 +308,28 @@
|
||||||
options={value.enum}
|
options={value.enum}
|
||||||
getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)}
|
getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)}
|
||||||
/>
|
/>
|
||||||
|
{:else if value.type === "json"}
|
||||||
|
<Editor
|
||||||
|
editorHeight="250"
|
||||||
|
editorWidth="448"
|
||||||
|
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)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{:else if value.customType === "column"}
|
{:else if value.customType === "column"}
|
||||||
<Select
|
<Select
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
export let tab = true
|
export let tab = true
|
||||||
export let mode
|
export let mode
|
||||||
export let editorHeight = 500
|
export let editorHeight = 500
|
||||||
|
export let editorWidth = 640
|
||||||
// export let parameters = []
|
// export let parameters = []
|
||||||
|
|
||||||
let width
|
let width
|
||||||
|
@ -169,7 +170,9 @@
|
||||||
{#if label}
|
{#if label}
|
||||||
<Label small>{label}</Label>
|
<Label small>{label}</Label>
|
||||||
{/if}
|
{/if}
|
||||||
<div style={`--code-mirror-height: ${editorHeight}px`}>
|
<div
|
||||||
|
style={`--code-mirror-height: ${editorHeight}px; --code-mirror-width: ${editorWidth}px;`}
|
||||||
|
>
|
||||||
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -183,6 +186,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
div :global(.CodeMirror) {
|
div :global(.CodeMirror) {
|
||||||
|
width: var(--code-mirror-width) !important;
|
||||||
height: var(--code-mirror-height) !important;
|
height: var(--code-mirror-height) !important;
|
||||||
border-radius: var(--border-radius-s);
|
border-radius: var(--border-radius-s);
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
|
|
|
@ -26,6 +26,10 @@ export const definition: AutomationStepSchema = {
|
||||||
type: AutomationIOType.STRING,
|
type: AutomationIOType.STRING,
|
||||||
title: "Webhook URL",
|
title: "Webhook URL",
|
||||||
},
|
},
|
||||||
|
body: {
|
||||||
|
type: AutomationIOType.JSON,
|
||||||
|
title: "Payload",
|
||||||
|
},
|
||||||
value1: {
|
value1: {
|
||||||
type: AutomationIOType.STRING,
|
type: AutomationIOType.STRING,
|
||||||
title: "Input Value 1",
|
title: "Input Value 1",
|
||||||
|
@ -70,7 +74,19 @@ export const definition: AutomationStepSchema = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function run({ inputs }: AutomationStepInput) {
|
export async function run({ inputs }: AutomationStepInput) {
|
||||||
const { url, value1, value2, value3, value4, value5 } = inputs
|
//TODO - Remove deprecated values 1,2,3,4,5 after November 2023
|
||||||
|
const { url, value1, value2, value3, value4, value5, body } = inputs
|
||||||
|
|
||||||
|
let payload = {}
|
||||||
|
try {
|
||||||
|
payload = body?.value ? JSON.parse(body?.value) : {}
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
httpStatus: 400,
|
||||||
|
response: "Invalid payload JSON",
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!url?.trim()?.length) {
|
if (!url?.trim()?.length) {
|
||||||
return {
|
return {
|
||||||
|
@ -89,6 +105,7 @@ export async function run({ inputs }: AutomationStepInput) {
|
||||||
value3,
|
value3,
|
||||||
value4,
|
value4,
|
||||||
value5,
|
value5,
|
||||||
|
...payload,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
|
@ -24,6 +24,10 @@ export const definition: AutomationStepSchema = {
|
||||||
type: AutomationIOType.STRING,
|
type: AutomationIOType.STRING,
|
||||||
title: "Webhook URL",
|
title: "Webhook URL",
|
||||||
},
|
},
|
||||||
|
body: {
|
||||||
|
type: AutomationIOType.JSON,
|
||||||
|
title: "Payload",
|
||||||
|
},
|
||||||
value1: {
|
value1: {
|
||||||
type: AutomationIOType.STRING,
|
type: AutomationIOType.STRING,
|
||||||
title: "Payload Value 1",
|
title: "Payload Value 1",
|
||||||
|
@ -63,7 +67,19 @@ export const definition: AutomationStepSchema = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function run({ inputs }: AutomationStepInput) {
|
export async function run({ inputs }: AutomationStepInput) {
|
||||||
const { url, value1, value2, value3, value4, value5 } = inputs
|
//TODO - Remove deprecated values 1,2,3,4,5 after November 2023
|
||||||
|
const { url, value1, value2, value3, value4, value5, body } = inputs
|
||||||
|
|
||||||
|
let payload = {}
|
||||||
|
try {
|
||||||
|
payload = body?.value ? JSON.parse(body?.value) : {}
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
httpStatus: 400,
|
||||||
|
response: "Invalid payload JSON",
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!url?.trim()?.length) {
|
if (!url?.trim()?.length) {
|
||||||
return {
|
return {
|
||||||
|
@ -85,6 +101,7 @@ export async function run({ inputs }: AutomationStepInput) {
|
||||||
value3,
|
value3,
|
||||||
value4,
|
value4,
|
||||||
value5,
|
value5,
|
||||||
|
...payload,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { getConfig, afterAll, runStep, actions } from "./utilities"
|
||||||
|
|
||||||
|
describe("test the outgoing webhook action", () => {
|
||||||
|
let config = getConfig()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll()
|
||||||
|
|
||||||
|
it("should be able to run the action", async () => {
|
||||||
|
const res = await runStep(actions.integromat.stepId, {
|
||||||
|
value1: "test",
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.response.url).toEqual("http://www.test.com")
|
||||||
|
expect(res.response.method).toEqual("post")
|
||||||
|
expect(res.success).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add the payload props when a JSON string is provided", async () => {
|
||||||
|
const payload = `{"value1":1,"value2":2,"value3":3,"value4":4,"value5":5,"name":"Adam","age":9}`
|
||||||
|
const res = await runStep(actions.integromat.stepId, {
|
||||||
|
value1: "ONE",
|
||||||
|
value2: "TWO",
|
||||||
|
value3: "THREE",
|
||||||
|
value4: "FOUR",
|
||||||
|
value5: "FIVE",
|
||||||
|
body: {
|
||||||
|
value: payload,
|
||||||
|
},
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.response.url).toEqual("http://www.test.com")
|
||||||
|
expect(res.response.method).toEqual("post")
|
||||||
|
expect(res.response.body).toEqual(payload)
|
||||||
|
expect(res.success).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return a 400 if the JSON payload string is malformed", async () => {
|
||||||
|
const payload = `{ value1 1 }`
|
||||||
|
const res = await runStep(actions.integromat.stepId, {
|
||||||
|
value1: "ONE",
|
||||||
|
body: {
|
||||||
|
value: payload,
|
||||||
|
},
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.httpStatus).toEqual(400)
|
||||||
|
expect(res.response).toEqual("Invalid payload JSON")
|
||||||
|
expect(res.success).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,27 +0,0 @@
|
||||||
const setup = require("./utilities")
|
|
||||||
const fetch = require("node-fetch")
|
|
||||||
|
|
||||||
jest.mock("node-fetch")
|
|
||||||
|
|
||||||
describe("test the outgoing webhook action", () => {
|
|
||||||
let inputs
|
|
||||||
let config = setup.getConfig()
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await config.init()
|
|
||||||
inputs = {
|
|
||||||
value1: "test",
|
|
||||||
url: "http://www.test.com",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
|
||||||
|
|
||||||
it("should be able to run the action", async () => {
|
|
||||||
const res = await setup.runStep(setup.actions.zapier.stepId, inputs)
|
|
||||||
expect(res.response.url).toEqual("http://www.test.com")
|
|
||||||
expect(res.response.method).toEqual("post")
|
|
||||||
expect(res.success).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { getConfig, afterAll, runStep, actions } from "./utilities"
|
||||||
|
|
||||||
|
describe("test the outgoing webhook action", () => {
|
||||||
|
let config = getConfig()
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll()
|
||||||
|
|
||||||
|
it("should be able to run the action", async () => {
|
||||||
|
const res = await runStep(actions.zapier.stepId, {
|
||||||
|
value1: "test",
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.response.url).toEqual("http://www.test.com")
|
||||||
|
expect(res.response.method).toEqual("post")
|
||||||
|
expect(res.success).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add the payload props when a JSON string is provided", async () => {
|
||||||
|
const payload = `{ "value1": 1, "value2": 2, "value3": 3, "value4": 4, "value5": 5, "name": "Adam", "age": 9 }`
|
||||||
|
const res = await runStep(actions.zapier.stepId, {
|
||||||
|
value1: "ONE",
|
||||||
|
value2: "TWO",
|
||||||
|
value3: "THREE",
|
||||||
|
value4: "FOUR",
|
||||||
|
value5: "FIVE",
|
||||||
|
body: {
|
||||||
|
value: payload,
|
||||||
|
},
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.response.url).toEqual("http://www.test.com")
|
||||||
|
expect(res.response.method).toEqual("post")
|
||||||
|
expect(res.response.body).toEqual(
|
||||||
|
`{"platform":"budibase","value1":1,"value2":2,"value3":3,"value4":4,"value5":5,"name":"Adam","age":9}`
|
||||||
|
)
|
||||||
|
expect(res.success).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return a 400 if the JSON payload string is malformed", async () => {
|
||||||
|
const payload = `{ value1 1 }`
|
||||||
|
const res = await runStep(actions.zapier.stepId, {
|
||||||
|
value1: "ONE",
|
||||||
|
body: {
|
||||||
|
value: payload,
|
||||||
|
},
|
||||||
|
url: "http://www.test.com",
|
||||||
|
})
|
||||||
|
expect(res.httpStatus).toEqual(400)
|
||||||
|
expect(res.response).toEqual("Invalid payload JSON")
|
||||||
|
expect(res.success).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
|
@ -7,6 +7,7 @@ export enum AutomationIOType {
|
||||||
BOOLEAN = "boolean",
|
BOOLEAN = "boolean",
|
||||||
NUMBER = "number",
|
NUMBER = "number",
|
||||||
ARRAY = "array",
|
ARRAY = "array",
|
||||||
|
JSON = "json",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AutomationCustomIOType {
|
export enum AutomationCustomIOType {
|
||||||
|
|
Loading…
Reference in New Issue