diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
index 6ab750c3d6..a8fa700b90 100644
--- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
@@ -13,6 +13,8 @@
Modal,
notifications,
Icon,
+ Checkbox,
+ DatePicker,
} from "@budibase/bbui"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import { automationStore, selectedAutomation } from "builderStore"
@@ -306,6 +308,11 @@
drawer.hide()
}
+ function canShowField(key, value) {
+ const dependsOn = value.dependsOn
+ return !dependsOn || !!inputData[dependsOn]
+ }
+
onMount(async () => {
try {
await environment.loadVariables()
@@ -317,210 +324,233 @@
{#each deprecatedSchemaProperties as [key, value]}
-
- {#if key !== "fields"}
-
- {/if}
- {#if value.type === "string" && value.enum}
-
+ {/if}
{/each}
diff --git a/packages/server/src/automations/steps/sendSmtpEmail.ts b/packages/server/src/automations/steps/sendSmtpEmail.ts
index 448c374ce8..f1ce3a85c2 100644
--- a/packages/server/src/automations/steps/sendSmtpEmail.ts
+++ b/packages/server/src/automations/steps/sendSmtpEmail.ts
@@ -48,6 +48,35 @@ export const definition: AutomationStepSchema = {
type: AutomationIOType.STRING,
title: "HTML Contents",
},
+ addInvite: {
+ type: AutomationIOType.BOOLEAN,
+ title: "Add calendar invite",
+ },
+ startTime: {
+ type: AutomationIOType.DATE,
+ title: "Start Time",
+ dependsOn: "addInvite",
+ },
+ endTime: {
+ type: AutomationIOType.DATE,
+ title: "End Time",
+ dependsOn: "addInvite",
+ },
+ summary: {
+ type: AutomationIOType.STRING,
+ title: "Meeting Summary",
+ dependsOn: "addInvite",
+ },
+ location: {
+ type: AutomationIOType.STRING,
+ title: "Location",
+ dependsOn: "addInvite",
+ },
+ url: {
+ type: AutomationIOType.STRING,
+ title: "URL",
+ dependsOn: "addInvite",
+ },
},
required: ["to", "from", "subject", "contents"],
},
@@ -68,21 +97,43 @@ export const definition: AutomationStepSchema = {
}
export async function run({ inputs }: AutomationStepInput) {
- let { to, from, subject, contents, cc, bcc } = inputs
+ let {
+ to,
+ from,
+ subject,
+ contents,
+ cc,
+ bcc,
+ addInvite,
+ startTime,
+ endTime,
+ summary,
+ location,
+ url,
+ } = inputs
if (!contents) {
contents = "No content
"
}
to = to || undefined
try {
- let response = await sendSmtpEmail(
+ let response = await sendSmtpEmail({
to,
from,
subject,
contents,
cc,
bcc,
- true
- )
+ automation: true,
+ invite: addInvite
+ ? {
+ startTime,
+ endTime,
+ summary,
+ location,
+ url,
+ }
+ : undefined,
+ })
return {
success: true,
response,
diff --git a/packages/server/src/automations/tests/sendSmtpEmail.spec.js b/packages/server/src/automations/tests/sendSmtpEmail.spec.js
deleted file mode 100644
index 998a1f54b2..0000000000
--- a/packages/server/src/automations/tests/sendSmtpEmail.spec.js
+++ /dev/null
@@ -1,71 +0,0 @@
-
-function generateResponse(to, from) {
- return {
- "success": true,
- "response": {
- "accepted": [
- to
- ],
- "envelope": {
- "from": from,
- "to": [
- to
- ]
- },
- "message": `Email sent to ${to}.`
- }
-
- }
-}
-
-const mockFetch = jest.fn(() => ({
- headers: {
- raw: () => {
- return { "content-type": ["application/json"] }
- },
- get: () => ["application/json"],
- },
- json: jest.fn(() => response),
- status: 200,
- text: jest.fn(),
-}))
-jest.mock("node-fetch", () => mockFetch)
-const setup = require("./utilities")
-
-
-describe("test the outgoing webhook action", () => {
- let inputs
- let config = setup.getConfig()
- beforeAll(async () => {
- await config.init()
- })
-
- afterAll(setup.afterAll)
-
- it("should be able to run the action", async () => {
- inputs = {
- to: "user1@test.com",
- from: "admin@test.com",
- subject: "hello",
- contents: "testing",
- }
- let resp = generateResponse(inputs.to, inputs.from)
- mockFetch.mockImplementationOnce(() => ({
- headers: {
- raw: () => {
- return { "content-type": ["application/json"] }
- },
- get: () => ["application/json"],
- },
- json: jest.fn(() => resp),
- status: 200,
- text: jest.fn(),
- }))
- const res = await setup.runStep(setup.actions.SEND_EMAIL_SMTP.stepId, inputs)
- expect(res.response).toEqual(resp)
- expect(res.success).toEqual(true)
-
- })
-
-
-})
diff --git a/packages/server/src/automations/tests/sendSmtpEmail.spec.ts b/packages/server/src/automations/tests/sendSmtpEmail.spec.ts
new file mode 100644
index 0000000000..da274dfffc
--- /dev/null
+++ b/packages/server/src/automations/tests/sendSmtpEmail.spec.ts
@@ -0,0 +1,74 @@
+import * as workerRequests from "../../utilities/workerRequests"
+
+jest.mock("../../utilities/workerRequests", () => ({
+ sendSmtpEmail: jest.fn(),
+}))
+
+function generateResponse(to: string, from: string) {
+ return {
+ success: true,
+ response: {
+ accepted: [to],
+ envelope: {
+ from: from,
+ to: [to],
+ },
+ message: `Email sent to ${to}.`,
+ },
+ }
+}
+
+const setup = require("./utilities")
+
+describe("test the outgoing webhook action", () => {
+ let inputs
+ let config = setup.getConfig()
+ beforeAll(async () => {
+ await config.init()
+ })
+
+ afterAll(setup.afterAll)
+
+ it("should be able to run the action", async () => {
+ jest
+ .spyOn(workerRequests, "sendSmtpEmail")
+ .mockImplementationOnce(async () =>
+ generateResponse("user1@test.com", "admin@test.com")
+ )
+ const invite = {
+ startTime: new Date(),
+ endTime: new Date(),
+ summary: "summary",
+ location: "location",
+ url: "url",
+ }
+ inputs = {
+ to: "user1@test.com",
+ from: "admin@test.com",
+ subject: "hello",
+ contents: "testing",
+ cc: "cc",
+ bcc: "bcc",
+ addInvite: true,
+ ...invite,
+ }
+ let resp = generateResponse(inputs.to, inputs.from)
+ const res = await setup.runStep(
+ setup.actions.SEND_EMAIL_SMTP.stepId,
+ inputs
+ )
+ expect(res.response).toEqual(resp)
+ expect(res.success).toEqual(true)
+ expect(workerRequests.sendSmtpEmail).toHaveBeenCalledTimes(1)
+ expect(workerRequests.sendSmtpEmail).toHaveBeenCalledWith({
+ to: "user1@test.com",
+ from: "admin@test.com",
+ subject: "hello",
+ contents: "testing",
+ cc: "cc",
+ bcc: "bcc",
+ invite,
+ automation: true,
+ })
+ })
+})
diff --git a/packages/server/src/utilities/workerRequests.ts b/packages/server/src/utilities/workerRequests.ts
index 82e1aac428..5230e25bf7 100644
--- a/packages/server/src/utilities/workerRequests.ts
+++ b/packages/server/src/utilities/workerRequests.ts
@@ -9,7 +9,7 @@ import {
env as coreEnv,
} from "@budibase/backend-core"
import { updateAppRole } from "./global"
-import { BBContext, User } from "@budibase/types"
+import { BBContext, User, EmailInvite } from "@budibase/types"
export function request(ctx?: BBContext, request?: any) {
if (!request.headers) {
@@ -65,15 +65,25 @@ async function checkResponse(
}
// have to pass in the tenant ID as this could be coming from an automation
-export async function sendSmtpEmail(
- to: string,
- from: string,
- subject: string,
- contents: string,
- cc: string,
- bcc: string,
+export async function sendSmtpEmail({
+ to,
+ from,
+ subject,
+ contents,
+ cc,
+ bcc,
+ automation,
+ invite,
+}: {
+ to: string
+ from: string
+ subject: string
+ contents: string
+ cc: string
+ bcc: string
automation: boolean
-) {
+ invite?: EmailInvite
+}) {
// tenant ID will be set in header
const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + `/api/global/email/send`),
@@ -88,6 +98,7 @@ export async function sendSmtpEmail(
bcc,
purpose: "custom",
automation,
+ invite,
},
})
)
diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts
index 193a5a414a..d1dbbec21b 100644
--- a/packages/types/src/documents/app/automation.ts
+++ b/packages/types/src/documents/app/automation.ts
@@ -1,5 +1,6 @@
import { Document } from "../document"
import { EventEmitter } from "events"
+import { User } from "../global"
export enum AutomationIOType {
OBJECT = "object",
@@ -8,6 +9,7 @@ export enum AutomationIOType {
NUMBER = "number",
ARRAY = "array",
JSON = "json",
+ DATE = "date",
}
export enum AutomationCustomIOType {
@@ -66,6 +68,33 @@ export enum AutomationActionStepId {
integromat = "integromat",
}
+export interface EmailInvite {
+ startTime: Date
+ endTime: Date
+ summary: string
+ location?: string
+ url?: string
+}
+
+export interface SendEmailOpts {
+ // workspaceId If finer grain controls being used then this will lookup config for workspace.
+ workspaceId?: string
+ // user If sending to an existing user the object can be provided, this is used in the context.
+ user: User
+ // from If sending from an address that is not what is configured in the SMTP config.
+ from?: string
+ // contents If sending a custom email then can supply contents which will be added to it.
+ contents?: string
+ // subject A custom subject can be specified if the config one is not desired.
+ subject?: string
+ // info Pass in a structure of information to be stored alongside the invitation.
+ info?: any
+ cc?: boolean
+ bcc?: boolean
+ automation?: boolean
+ invite?: EmailInvite
+}
+
export const AutomationStepIdArray = [
...Object.values(AutomationActionStepId),
...Object.values(AutomationTriggerStepId),
@@ -90,6 +119,7 @@ interface BaseIOStructure {
customType?: AutomationCustomIOType
title?: string
description?: string
+ dependsOn?: string
enum?: string[]
pretty?: string[]
properties?: {
diff --git a/packages/worker/package.json b/packages/worker/package.json
index b11512f88f..7e8715b162 100644
--- a/packages/worker/package.json
+++ b/packages/worker/package.json
@@ -53,6 +53,7 @@
"elastic-apm-node": "3.38.0",
"global-agent": "3.0.0",
"got": "11.8.3",
+ "ical-generator": "4.1.0",
"joi": "17.6.0",
"koa": "2.13.4",
"koa-body": "4.2.0",
diff --git a/packages/worker/src/api/controllers/global/email.ts b/packages/worker/src/api/controllers/global/email.ts
index f5acad9a66..e352ef0b87 100644
--- a/packages/worker/src/api/controllers/global/email.ts
+++ b/packages/worker/src/api/controllers/global/email.ts
@@ -14,6 +14,7 @@ export async function sendEmail(ctx: BBContext) {
cc,
bcc,
automation,
+ invite,
} = ctx.request.body
let user
if (userId) {
@@ -29,6 +30,7 @@ export async function sendEmail(ctx: BBContext) {
cc,
bcc,
automation,
+ invite,
})
ctx.body = {
...response,
diff --git a/packages/worker/src/utilities/email.ts b/packages/worker/src/utilities/email.ts
index e47d352f87..5d596b0bde 100644
--- a/packages/worker/src/utilities/email.ts
+++ b/packages/worker/src/utilities/email.ts
@@ -4,28 +4,11 @@ import { getTemplateByPurpose, EmailTemplates } from "../constants/templates"
import { getSettingsTemplateContext } from "./templates"
import { processString } from "@budibase/string-templates"
import { getResetPasswordCode, getInviteCode } from "./redis"
-import { User, SMTPInnerConfig } from "@budibase/types"
+import { User, SendEmailOpts, SMTPInnerConfig } from "@budibase/types"
import { configs } from "@budibase/backend-core"
+import ical from "ical-generator"
const nodemailer = require("nodemailer")
-type SendEmailOpts = {
- // workspaceId If finer grain controls being used then this will lookup config for workspace.
- workspaceId?: string
- // user If sending to an existing user the object can be provided, this is used in the context.
- user: User
- // from If sending from an address that is not what is configured in the SMTP config.
- from?: string
- // contents If sending a custom email then can supply contents which will be added to it.
- contents?: string
- // subject A custom subject can be specified if the config one is not desired.
- subject?: string
- // info Pass in a structure of information to be stored alongside the invitation.
- info?: any
- cc?: boolean
- bcc?: boolean
- automation?: boolean
-}
-
const TEST_MODE = env.ENABLE_EMAIL_TEST_MODE && env.isDev()
const TYPE = TemplateType.EMAIL
@@ -200,6 +183,26 @@ export async function sendEmail(
context
)
}
+ if (opts?.invite) {
+ const calendar = ical({
+ name: "Invite",
+ })
+ calendar.createEvent({
+ start: opts.invite.startTime,
+ end: opts.invite.endTime,
+ summary: opts.invite.summary,
+ location: opts.invite.location,
+ url: opts.invite.url,
+ })
+ message = {
+ ...message,
+ icalEvent: {
+ method: "request",
+ content: calendar.toString(),
+ },
+ }
+ }
+
const response = await transport.sendMail(message)
if (TEST_MODE) {
console.log("Test email URL: " + nodemailer.getTestMessageUrl(response))
diff --git a/yarn.lock b/yarn.lock
index 9a57eb4377..ea9823b58b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13668,6 +13668,13 @@ husky@^8.0.3:
resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184"
integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==
+ical-generator@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/ical-generator/-/ical-generator-4.1.0.tgz#2a336c951864c5583a2aa715d16f2edcdfd2d90b"
+ integrity sha512-5GrFDJ8SAOj8cB9P1uEZIfKrNxSZ1R2eOQfZePL+CtdWh4RwNXWe8b0goajz+Hu37vcipG3RVldoa2j57Y20IA==
+ dependencies:
+ uuid-random "^1.3.2"
+
iconv-lite@0.4.24, iconv-lite@^0.4.15, iconv-lite@^0.4.24, iconv-lite@^0.4.5:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -25279,6 +25286,11 @@ utils-merge@1.0.1, utils-merge@1.x.x:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
+uuid-random@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/uuid-random/-/uuid-random-1.3.2.tgz#96715edbaef4e84b1dcf5024b00d16f30220e2d0"
+ integrity sha512-UOzej0Le/UgkbWEO8flm+0y+G+ljUon1QWTEZOq1rnMAsxo2+SckbiZdKzAHHlVh6gJqI1TjC/xwgR50MuCrBQ==
+
uuid@3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"