Merge pull request #1503 from Budibase/cron

Cron Automation trigger
This commit is contained in:
Martin McKeaveney 2021-05-19 19:33:49 +01:00 committed by GitHub
commit e6548a6c92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1451 additions and 17 deletions

View File

@ -10,6 +10,7 @@
import CodeEditorModal from "./CodeEditorModal.svelte" import CodeEditorModal from "./CodeEditorModal.svelte"
import QuerySelector from "./QuerySelector.svelte" import QuerySelector from "./QuerySelector.svelte"
import QueryParamSelector from "./QueryParamSelector.svelte" import QueryParamSelector from "./QueryParamSelector.svelte"
import CronBuilder from "./CronBuilder.svelte"
import Editor from "components/integration/QueryEditor.svelte" import Editor from "components/integration/QueryEditor.svelte"
export let block export let block
@ -76,6 +77,8 @@
/> />
{:else if value.customType === "query"} {:else if value.customType === "query"}
<QuerySelector bind:value={block.inputs[key]} /> <QuerySelector bind:value={block.inputs[key]} />
{:else if value.customType === "cron"}
<CronBuilder bind:value={block.inputs[key]} />
{:else if value.customType === "queryParams"} {:else if value.customType === "queryParams"}
<QueryParamSelector bind:value={block.inputs[key]} {bindings} /> <QueryParamSelector bind:value={block.inputs[key]} {bindings} />
{:else if value.customType === "table"} {:else if value.customType === "table"}

View File

@ -0,0 +1,58 @@
<script>
import { Button, Select, Label, Heading, Input } from "@budibase/bbui"
export let value
let presets = false
const CRON_EXPRESSIONS = [
{
label: "Every Minute",
value: "* * * * *",
},
{
label: "Every Hour",
value: "0 * * * *",
},
{
label: "Every Morning at 8AM",
value: "0 8 * * *",
},
{
label: "Every Night at Midnight",
value: "0 0 * * *",
},
{
label: "Every Budibase Reboot",
value: "@reboot",
},
]
</script>
<div class="block-field">
<Input bind:value />
<div class="presets">
<Button on:click={() => (presets = !presets)}
>{presets ? "Hide" : "Show"} Presets</Button
>
{#if presets}
<Select
bind:value
secondary
extraThin
label="Presets"
options={CRON_EXPRESSIONS}
/>
{/if}
</div>
</div>
<style>
.presets {
margin-top: var(--spacing-m);
}
.block-field {
padding-top: var(--spacing-s);
}
</style>

View File

@ -61,7 +61,6 @@
// Required to check any updated deployment statuses between polls // Required to check any updated deployment statuses between polls
function checkIncomingDeploymentStatus(current, incoming) { function checkIncomingDeploymentStatus(current, incoming) {
console.log(current, incoming)
for (let incomingDeployment of incoming) { for (let incomingDeployment of incoming) {
if ( if (
incomingDeployment.status === DeploymentStatus.FAILURE || incomingDeployment.status === DeploymentStatus.FAILURE ||

View File

@ -6,6 +6,8 @@ const webhooks = require("./webhook")
const { getAutomationParams, generateAutomationID } = require("../../db/utils") const { getAutomationParams, generateAutomationID } = require("../../db/utils")
const WH_STEP_ID = triggers.BUILTIN_DEFINITIONS.WEBHOOK.stepId const WH_STEP_ID = triggers.BUILTIN_DEFINITIONS.WEBHOOK.stepId
const CRON_STEP_ID = triggers.BUILTIN_DEFINITIONS.CRON.stepId
/************************* /*************************
* * * *
* BUILDER FUNCTIONS * * BUILDER FUNCTIONS *
@ -32,6 +34,46 @@ function cleanAutomationInputs(automation) {
return automation return automation
} }
/**
* This function handles checking of any cron jobs need to be created or deleted for automations.
* @param {string} appId The ID of the app in which we are checking for webhooks
* @param {object|undefined} oldAuto The old automation object if updating/deleting
* @param {object|undefined} newAuto The new automation object if creating/updating
*/
async function checkForCronTriggers({ appId, oldAuto, newAuto }) {
const oldTrigger = oldAuto ? oldAuto.definition.trigger : null
const newTrigger = newAuto ? newAuto.definition.trigger : null
function isCronTrigger(auto) {
return (
auto &&
auto.definition.trigger &&
auto.definition.trigger.stepId === CRON_STEP_ID
)
}
const isLive = auto => auto && auto.live
const cronTriggerRemoved =
isCronTrigger(oldAuto) && !isCronTrigger(newAuto) && oldTrigger.cronJobId
const cronTriggerDeactivated = !isLive(newAuto) && isLive(oldAuto)
const cronTriggerActivated = isLive(newAuto) && !isLive(oldAuto)
if (cronTriggerRemoved || cronTriggerDeactivated) {
await triggers.automationQueue.removeRepeatableByKey(oldTrigger.cronJobId)
}
// need to create cron job
else if (isCronTrigger(newAuto) && cronTriggerActivated) {
const job = await triggers.automationQueue.add(
{ automation: newAuto, event: { appId } },
{ repeat: { cron: newTrigger.inputs.cron } }
)
// Assign cron job ID from bull so we can remove it later if the cron trigger is removed
newTrigger.cronJobId = job.id
}
return newAuto
}
/** /**
* This function handles checking if any webhooks need to be created or deleted for automations. * This function handles checking if any webhooks need to be created or deleted for automations.
* @param {string} appId The ID of the app in which we are checking for webhooks * @param {string} appId The ID of the app in which we are checking for webhooks
@ -111,6 +153,10 @@ exports.create = async function (ctx) {
appId: ctx.appId, appId: ctx.appId,
newAuto: automation, newAuto: automation,
}) })
automation = await checkForCronTriggers({
appId: ctx.appId,
newAuto: automation,
})
const response = await db.put(automation) const response = await db.put(automation)
automation._rev = response.rev automation._rev = response.rev
@ -135,6 +181,11 @@ exports.update = async function (ctx) {
oldAuto: oldAutomation, oldAuto: oldAutomation,
newAuto: automation, newAuto: automation,
}) })
automation = await checkForCronTriggers({
appId: ctx.appId,
oldAuto: oldAutomation,
newAuto: automation,
})
const response = await db.put(automation) const response = await db.put(automation)
automation._rev = response.rev automation._rev = response.rev
@ -171,6 +222,10 @@ exports.destroy = async function (ctx) {
appId: ctx.appId, appId: ctx.appId,
oldAuto: oldAutomation, oldAuto: oldAutomation,
}) })
await checkForCronTriggers({
appId: ctx.appId,
oldAuto: oldAutomation,
})
ctx.body = await db.remove(ctx.params.id, ctx.params.rev) ctx.body = await db.remove(ctx.params.id, ctx.params.rev)
} }

View File

@ -12,7 +12,7 @@ const FILTER_STEP_ID = logic.BUILTIN_DEFINITIONS.FILTER.stepId
* inputs and handles any outputs. * inputs and handles any outputs.
*/ */
class Orchestrator { class Orchestrator {
constructor(automation, triggerOutput) { constructor(automation, triggerOutput = {}) {
this._metadata = triggerOutput.metadata this._metadata = triggerOutput.metadata
this._chainCount = this._metadata ? this._metadata.automationChainCount : 0 this._chainCount = this._metadata ? this._metadata.automationChainCount : 0
this._appId = triggerOutput.appId this._appId = triggerOutput.appId

View File

@ -7,9 +7,10 @@ const Queue = env.isTest()
const { getAutomationParams } = require("../db/utils") const { getAutomationParams } = require("../db/utils")
const { coerce } = require("../utilities/rowProcessor") const { coerce } = require("../utilities/rowProcessor")
const { utils } = require("@budibase/auth/redis") const { utils } = require("@budibase/auth/redis")
const { JobQueues } = require("../constants")
const { opts } = utils.getRedisOptions() const { opts } = utils.getRedisOptions()
let automationQueue = new Queue("automationQueue", { redis: opts }) let automationQueue = new Queue(JobQueues.AUTOMATIONS, { redis: opts })
const FAKE_STRING = "TEST" const FAKE_STRING = "TEST"
const FAKE_BOOL = false const FAKE_BOOL = false
@ -196,6 +197,29 @@ const BUILTIN_DEFINITIONS = {
}, },
type: "TRIGGER", type: "TRIGGER",
}, },
CRON: {
name: "Cron Trigger",
event: "cron:trigger",
icon: "ri-timer-line",
tagline: "Cron Trigger (<b>{{inputs.cron}}</b>)",
description: "Triggers automation on a cron schedule.",
stepId: "CRON",
inputs: {},
schema: {
inputs: {
properties: {
cron: {
type: "string",
customType: "cron",
title: "Expression",
},
},
required: ["cron"],
},
outputs: {},
},
type: "TRIGGER",
},
} }
async function queueRelevantRowAutomations(event, eventType) { async function queueRelevantRowAutomations(event, eventType) {

View File

@ -5,6 +5,10 @@ const { ObjectStoreBuckets } = require("@budibase/auth").objectStore
exports.LOGO_URL = exports.LOGO_URL =
"https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg"
exports.JobQueues = {
AUTOMATIONS: "automationQueue",
}
exports.FieldTypes = { exports.FieldTypes = {
STRING: "string", STRING: "string",
LONGFORM: "longform", LONGFORM: "longform",

File diff suppressed because it is too large Load Diff