Merge branch 'master' into backport-v3-view-updates
This commit is contained in:
commit
9ed77d6aea
|
@ -0,0 +1,29 @@
|
||||||
|
<script>
|
||||||
|
export let width
|
||||||
|
export let height
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
{width}
|
||||||
|
{height}
|
||||||
|
viewBox="0 0 13 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9.4179 4.13222C9.4179 3.73121 9.26166 3.35428 8.97913 3.07175C8.41342 2.50538 7.4239 2.50408 6.85753 3.07175L5.64342 4.28586C5.6291 4.30018 5.61543 4.3158 5.60305 4.33143C5.58678 4.3438 5.5718 4.35747 5.55683 4.37244L0.491426 9.43785C0.208245 9.72103 0.052002 10.098 0.052002 10.4983C0.052002 10.8987 0.208245 11.2756 0.491426 11.5588C0.774607 11.842 1.15153 11.9982 1.5519 11.9982C1.95227 11.9982 2.32919 11.842 2.61238 11.5588L8.97848 5.1927C9.26166 4.90952 9.4179 4.53259 9.4179 4.13222ZM1.90539 10.8518C1.7166 11.0406 1.3872 11.0406 1.1984 10.8518C1.10401 10.7574 1.05193 10.6318 1.05193 10.4983C1.05193 10.3649 1.104 10.2392 1.1984 10.1448L5.99821 5.34503L6.70845 6.04875L1.90539 10.8518ZM8.2715 4.48571L7.41544 5.34178L6.7052 4.63805L7.56452 3.77873C7.7533 3.58995 8.08271 3.58929 8.2715 3.77939C8.36589 3.87313 8.41798 3.99877 8.41798 4.13223C8.41798 4.26569 8.3659 4.39132 8.2715 4.48571Z"
|
||||||
|
fill="#C8C8C8"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M11.8552 6.55146L11.0144 6.21913L10.879 5.32449C10.8356 5.03919 10.3737 4.98776 10.2686 5.255L9.93606 6.09642L9.04143 6.23085C8.89951 6.25216 8.78884 6.36658 8.77257 6.50947C8.75629 6.65253 8.83783 6.78826 8.97193 6.84148L9.81335 7.17464L9.94794 8.06862C9.9691 8.21053 10.0835 8.32121 10.2266 8.33748C10.3695 8.35375 10.5052 8.27221 10.5586 8.13811L10.8914 7.29751L11.7855 7.1621C11.9283 7.1403 12.0381 7.02637 12.0544 6.88348C12.0707 6.74058 11.9887 6.60403 11.8552 6.55146Z"
|
||||||
|
fill="#F9634C"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8.94215 1.76145L9.78356 2.0946L9.91815 2.9885C9.93931 3.13049 10.0539 3.24117 10.1968 3.25744C10.3398 3.27371 10.4756 3.19218 10.5288 3.05807L10.8618 2.21739L11.7559 2.08207C11.8985 2.06034 12.0085 1.94633 12.0248 1.80344C12.0411 1.66054 11.959 1.524 11.8254 1.47143L10.9847 1.13909L10.8494 0.244456C10.806 -0.0409246 10.3439 -0.0922745 10.2388 0.174881L9.90643 1.0163L9.0118 1.15089C8.86972 1.17213 8.75905 1.28654 8.74278 1.42952C8.72651 1.57249 8.80804 1.70823 8.94215 1.76145Z"
|
||||||
|
fill="#8488FD"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M3.2379 2.46066L3.92063 2.73091L4.02984 3.45637C4.04709 3.57151 4.14002 3.66135 4.25606 3.67453C4.37194 3.6878 4.48212 3.62163 4.52541 3.51276L4.79557 2.83059L5.52094 2.72074C5.63682 2.70316 5.72601 2.61072 5.73936 2.49468C5.75254 2.37864 5.68597 2.26797 5.57758 2.22533L4.89533 1.95565L4.78548 1.22963C4.75016 0.998038 4.37535 0.956375 4.29007 1.17315L4.0204 1.85597L3.29437 1.96517C3.17915 1.98235 3.08931 2.07527 3.07613 2.19131C3.06294 2.30727 3.12902 2.41737 3.2379 2.46066Z"
|
||||||
|
fill="#F7D804"
|
||||||
|
/>
|
||||||
|
</svg>
|
|
@ -67,6 +67,7 @@
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
"@zerodevx/svelte-json-view": "^1.0.7",
|
"@zerodevx/svelte-json-view": "^1.0.7",
|
||||||
"codemirror": "^5.65.16",
|
"codemirror": "^5.65.16",
|
||||||
|
"cron-parser": "^4.9.0",
|
||||||
"dayjs": "^1.10.8",
|
"dayjs": "^1.10.8",
|
||||||
"downloadjs": "1.4.7",
|
"downloadjs": "1.4.7",
|
||||||
"fast-json-patch": "^3.1.1",
|
"fast-json-patch": "^3.1.1",
|
||||||
|
|
|
@ -1050,7 +1050,7 @@
|
||||||
{:else if value.customType === "cron"}
|
{:else if value.customType === "cron"}
|
||||||
<CronBuilder
|
<CronBuilder
|
||||||
on:change={e => onChange({ [key]: e.detail })}
|
on:change={e => onChange({ [key]: e.detail })}
|
||||||
value={inputData[key]}
|
cronExpression={inputData[key]}
|
||||||
/>
|
/>
|
||||||
{:else if value.customType === "automationFields"}
|
{:else if value.customType === "automationFields"}
|
||||||
<AutomationSelector
|
<AutomationSelector
|
||||||
|
|
|
@ -1,41 +1,70 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Select, Input, Label } from "@budibase/bbui"
|
import {
|
||||||
|
Select,
|
||||||
|
InlineAlert,
|
||||||
|
Input,
|
||||||
|
Label,
|
||||||
|
Layout,
|
||||||
|
notifications,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import { flags } from "stores/builder"
|
import { flags } from "stores/builder"
|
||||||
|
import { licensing } from "stores/portal"
|
||||||
|
import { API } from "api"
|
||||||
|
import MagicWand from "../../../../assets/MagicWand.svelte"
|
||||||
|
|
||||||
import { helpers, REBOOT_CRON } from "@budibase/shared-core"
|
import { helpers, REBOOT_CRON } from "@budibase/shared-core"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value
|
export let cronExpression
|
||||||
|
|
||||||
let error
|
let error
|
||||||
|
let nextExecutions
|
||||||
|
|
||||||
|
// AI prompt
|
||||||
|
let aiCronPrompt = ""
|
||||||
|
let loadingAICronExpression = false
|
||||||
|
|
||||||
|
$: aiEnabled =
|
||||||
|
$licensing.customAIConfigsEnabled || $licensing.budibaseAIEnabled
|
||||||
$: {
|
$: {
|
||||||
const exists = CRON_EXPRESSIONS.some(cron => cron.value === value)
|
if (cronExpression) {
|
||||||
const customIndex = CRON_EXPRESSIONS.findIndex(
|
try {
|
||||||
cron => cron.label === "Custom"
|
nextExecutions = helpers.cron
|
||||||
)
|
.getNextExecutionDates(cronExpression)
|
||||||
|
.join("\n")
|
||||||
if (!exists && customIndex === -1) {
|
} catch (err) {
|
||||||
CRON_EXPRESSIONS[0] = { label: "Custom", value: value }
|
nextExecutions = null
|
||||||
} else if (exists && customIndex !== -1) {
|
}
|
||||||
CRON_EXPRESSIONS.splice(customIndex, 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
if (value !== REBOOT_CRON) {
|
if (e.detail !== REBOOT_CRON) {
|
||||||
error = helpers.cron.validate(e.detail).err
|
error = helpers.cron.validate(e.detail).err
|
||||||
}
|
}
|
||||||
if (e.detail === value || error) {
|
if (e.detail === cronExpression || error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
value = e.detail
|
cronExpression = e.detail
|
||||||
dispatch("change", e.detail)
|
dispatch("change", e.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatePreset = e => {
|
||||||
|
aiCronPrompt = ""
|
||||||
|
onChange(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCronExpression = e => {
|
||||||
|
aiCronPrompt = ""
|
||||||
|
cronExpression = null
|
||||||
|
nextExecutions = null
|
||||||
|
onChange(e)
|
||||||
|
}
|
||||||
|
|
||||||
let touched = false
|
let touched = false
|
||||||
let presets = false
|
|
||||||
|
|
||||||
const CRON_EXPRESSIONS = [
|
const CRON_EXPRESSIONS = [
|
||||||
{
|
{
|
||||||
|
@ -64,45 +93,130 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function generateAICronExpression() {
|
||||||
|
loadingAICronExpression = true
|
||||||
|
try {
|
||||||
|
const response = await API.generateCronExpression({
|
||||||
|
prompt: aiCronPrompt,
|
||||||
|
})
|
||||||
|
cronExpression = response.message
|
||||||
|
dispatch("change", response.message)
|
||||||
|
} catch (err) {
|
||||||
|
notifications.error(err.message)
|
||||||
|
} finally {
|
||||||
|
loadingAICronExpression = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="block-field">
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<Layout noPadding gap="S">
|
||||||
|
<Select
|
||||||
|
on:change={updatePreset}
|
||||||
|
value={cronExpression || "Custom"}
|
||||||
|
secondary
|
||||||
|
extraThin
|
||||||
|
label="Use a Preset (Optional)"
|
||||||
|
options={CRON_EXPRESSIONS}
|
||||||
|
/>
|
||||||
|
{#if aiEnabled}
|
||||||
|
<div class="cron-ai-generator">
|
||||||
<Input
|
<Input
|
||||||
|
bind:value={aiCronPrompt}
|
||||||
|
label="Generate Cron Expression with AI"
|
||||||
|
size="S"
|
||||||
|
placeholder="Run every hour between 1pm to 4pm everyday of the week"
|
||||||
|
/>
|
||||||
|
{#if aiCronPrompt}
|
||||||
|
<div
|
||||||
|
class="icon"
|
||||||
|
class:pulsing-text={loadingAICronExpression}
|
||||||
|
on:click={generateAICronExpression}
|
||||||
|
>
|
||||||
|
<MagicWand height="17" width="17" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<Input
|
||||||
|
label="Cron Expression"
|
||||||
{error}
|
{error}
|
||||||
on:change={onChange}
|
on:change={updateCronExpression}
|
||||||
{value}
|
value={cronExpression}
|
||||||
on:blur={() => (touched = true)}
|
on:blur={() => (touched = true)}
|
||||||
updateOnChange={false}
|
updateOnChange={false}
|
||||||
/>
|
/>
|
||||||
{#if touched && !value}
|
{#if touched && !cronExpression}
|
||||||
<Label><div class="error">Please specify a CRON expression</div></Label>
|
<Label><div class="error">Please specify a CRON expression</div></Label>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="presets">
|
{#if nextExecutions}
|
||||||
<Button on:click={() => (presets = !presets)}
|
<InlineAlert
|
||||||
>{presets ? "Hide" : "Show"} Presets</Button
|
type="info"
|
||||||
>
|
header="Next Executions"
|
||||||
{#if presets}
|
message={nextExecutions}
|
||||||
<Select
|
|
||||||
on:change={onChange}
|
|
||||||
value={value || "Custom"}
|
|
||||||
secondary
|
|
||||||
extraThin
|
|
||||||
label="Presets"
|
|
||||||
options={CRON_EXPRESSIONS}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</Layout>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.presets {
|
.cron-ai-generator {
|
||||||
margin-top: var(--spacing-m);
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.block-field {
|
.icon {
|
||||||
padding-top: var(--spacing-s);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
padding-top: var(--spacing-xs);
|
padding-top: var(--spacing-xs);
|
||||||
color: var(--spectrum-global-color-red-500);
|
color: var(--spectrum-global-color-red-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pulsing-text {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
animation: pulse 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,7 +2,12 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import CreateEditColumn from "components/backend/DataTable/modals/CreateEditColumn.svelte"
|
import CreateEditColumn from "components/backend/DataTable/modals/CreateEditColumn.svelte"
|
||||||
|
|
||||||
const { datasource } = getContext("grid")
|
const { datasource, rows } = getContext("grid")
|
||||||
|
|
||||||
|
const onUpdate = async () => {
|
||||||
|
await datasource.actions.refreshDefinition()
|
||||||
|
await rows.actions.refreshData()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CreateEditColumn on:updatecolumns={datasource.actions.refreshDefinition} />
|
<CreateEditColumn on:updatecolumns={onUpdate} />
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const buildAIEndpoints = API => ({
|
||||||
|
/**
|
||||||
|
* Generates a cron expression from a prompt
|
||||||
|
*/
|
||||||
|
generateCronExpression: async ({ prompt }) => {
|
||||||
|
return await API.post({
|
||||||
|
url: "/api/ai/cron",
|
||||||
|
body: { prompt },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
|
@ -2,6 +2,7 @@ import { Helpers } from "@budibase/bbui"
|
||||||
import { Header } from "@budibase/shared-core"
|
import { Header } from "@budibase/shared-core"
|
||||||
import { ApiVersion } from "../constants"
|
import { ApiVersion } from "../constants"
|
||||||
import { buildAnalyticsEndpoints } from "./analytics"
|
import { buildAnalyticsEndpoints } from "./analytics"
|
||||||
|
import { buildAIEndpoints } from "./ai"
|
||||||
import { buildAppEndpoints } from "./app"
|
import { buildAppEndpoints } from "./app"
|
||||||
import { buildAttachmentEndpoints } from "./attachments"
|
import { buildAttachmentEndpoints } from "./attachments"
|
||||||
import { buildAuthEndpoints } from "./auth"
|
import { buildAuthEndpoints } from "./auth"
|
||||||
|
@ -268,6 +269,7 @@ export const createAPIClient = config => {
|
||||||
// Attach all endpoints
|
// Attach all endpoints
|
||||||
return {
|
return {
|
||||||
...API,
|
...API,
|
||||||
|
...buildAIEndpoints(API),
|
||||||
...buildAnalyticsEndpoints(API),
|
...buildAnalyticsEndpoints(API),
|
||||||
...buildAppEndpoints(API),
|
...buildAppEndpoints(API),
|
||||||
...buildAttachmentEndpoints(API),
|
...buildAttachmentEndpoints(API),
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit e2fe0f9cc856b4ee1a97df96d623b2d87d4e8733
|
Subproject commit aca9828117bb97f54f40ee359f1a3f6e259174e7
|
|
@ -33,6 +33,7 @@ import rowActionRoutes from "./rowAction"
|
||||||
export { default as staticRoutes } from "./static"
|
export { default as staticRoutes } from "./static"
|
||||||
export { default as publicRoutes } from "./public"
|
export { default as publicRoutes } from "./public"
|
||||||
|
|
||||||
|
const aiRoutes = pro.ai
|
||||||
const appBackupRoutes = pro.appBackups
|
const appBackupRoutes = pro.appBackups
|
||||||
const environmentVariableRoutes = pro.environmentVariables
|
const environmentVariableRoutes = pro.environmentVariables
|
||||||
|
|
||||||
|
@ -67,6 +68,7 @@ export const mainRoutes: Router[] = [
|
||||||
debugRoutes,
|
debugRoutes,
|
||||||
environmentVariableRoutes,
|
environmentVariableRoutes,
|
||||||
rowActionRoutes,
|
rowActionRoutes,
|
||||||
|
aiRoutes,
|
||||||
// these need to be handled last as they still use /api/:tableId
|
// these need to be handled last as they still use /api/:tableId
|
||||||
// this could be breaking as koa may recognise other routes as this
|
// this could be breaking as koa may recognise other routes as this
|
||||||
tableRoutes,
|
tableRoutes,
|
||||||
|
|
|
@ -17,44 +17,65 @@ describe("Branching automations", () => {
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
it("should run a multiple nested branching automation", async () => {
|
it("should run a multiple nested branching automation", async () => {
|
||||||
|
const firstLogId = "11111111-1111-1111-1111-111111111111"
|
||||||
|
const branch1LogId = "22222222-2222-2222-2222-222222222222"
|
||||||
|
const branch2LogId = "33333333-3333-3333-3333-333333333333"
|
||||||
|
const branch2Id = "44444444-4444-4444-4444-444444444444"
|
||||||
|
|
||||||
const builder = createAutomationBuilder({
|
const builder = createAutomationBuilder({
|
||||||
name: "Test Trigger with Loop and Create Row",
|
name: "Test Trigger with Loop and Create Row",
|
||||||
})
|
})
|
||||||
|
|
||||||
const results = await builder
|
const results = await builder
|
||||||
.appAction({ fields: {} })
|
.appAction({ fields: {} })
|
||||||
.serverLog({ text: "Starting automation" })
|
.serverLog(
|
||||||
|
{ text: "Starting automation" },
|
||||||
|
{ stepName: "FirstLog", stepId: firstLogId }
|
||||||
|
)
|
||||||
.branch({
|
.branch({
|
||||||
topLevelBranch1: {
|
topLevelBranch1: {
|
||||||
steps: stepBuilder =>
|
steps: stepBuilder =>
|
||||||
stepBuilder.serverLog({ text: "Branch 1" }).branch({
|
stepBuilder
|
||||||
|
.serverLog(
|
||||||
|
{ text: "Branch 1" },
|
||||||
|
{ stepId: "66666666-6666-6666-6666-666666666666" }
|
||||||
|
)
|
||||||
|
.branch({
|
||||||
branch1: {
|
branch1: {
|
||||||
steps: stepBuilder =>
|
steps: stepBuilder =>
|
||||||
stepBuilder.serverLog({ text: "Branch 1.1" }),
|
stepBuilder.serverLog(
|
||||||
|
{ text: "Branch 1.1" },
|
||||||
|
{ stepId: branch1LogId }
|
||||||
|
),
|
||||||
condition: {
|
condition: {
|
||||||
equal: { "{{steps.1.success}}": true },
|
equal: { [`{{ steps.${firstLogId}.success }}`]: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
branch2: {
|
branch2: {
|
||||||
steps: stepBuilder =>
|
steps: stepBuilder =>
|
||||||
stepBuilder.serverLog({ text: "Branch 1.2" }),
|
stepBuilder.serverLog(
|
||||||
|
{ text: "Branch 1.2" },
|
||||||
|
{ stepId: branch2LogId }
|
||||||
|
),
|
||||||
condition: {
|
condition: {
|
||||||
equal: { "{{steps.1.success}}": false },
|
equal: { [`{{ steps.${firstLogId}.success }}`]: false },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
condition: {
|
condition: {
|
||||||
equal: { "{{steps.1.success}}": true },
|
equal: { [`{{ steps.${firstLogId}.success }}`]: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
topLevelBranch2: {
|
topLevelBranch2: {
|
||||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Branch 2" }),
|
steps: stepBuilder =>
|
||||||
|
stepBuilder.serverLog({ text: "Branch 2" }, { stepId: branch2Id }),
|
||||||
condition: {
|
condition: {
|
||||||
equal: { "{{steps.1.success}}": false },
|
equal: { [`{{ steps.${firstLogId}.success }}`]: false },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.run()
|
.run()
|
||||||
|
|
||||||
expect(results.steps[3].outputs.status).toContain("branch1 branch taken")
|
expect(results.steps[3].outputs.status).toContain("branch1 branch taken")
|
||||||
expect(results.steps[4].outputs.message).toContain("Branch 1.1")
|
expect(results.steps[4].outputs.message).toContain("Branch 1.1")
|
||||||
})
|
})
|
||||||
|
|
|
@ -64,18 +64,18 @@ class BaseStepBuilder {
|
||||||
stepId: TStep,
|
stepId: TStep,
|
||||||
stepSchema: Omit<AutomationStep, "id" | "stepId" | "inputs">,
|
stepSchema: Omit<AutomationStep, "id" | "stepId" | "inputs">,
|
||||||
inputs: AutomationStepInputs<TStep>,
|
inputs: AutomationStepInputs<TStep>,
|
||||||
stepName?: string
|
opts?: { stepName?: string; stepId?: string }
|
||||||
): this {
|
): this {
|
||||||
const id = uuidv4()
|
const id = opts?.stepId || uuidv4()
|
||||||
this.steps.push({
|
this.steps.push({
|
||||||
...stepSchema,
|
...stepSchema,
|
||||||
inputs: inputs as any,
|
inputs: inputs as any,
|
||||||
id,
|
id,
|
||||||
stepId,
|
stepId,
|
||||||
name: stepName || stepSchema.name,
|
name: opts?.stepName || stepSchema.name,
|
||||||
})
|
})
|
||||||
if (stepName) {
|
if (opts?.stepName) {
|
||||||
this.stepNames[id] = stepName
|
this.stepNames[id] = opts.stepName
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,6 @@ class BaseStepBuilder {
|
||||||
})
|
})
|
||||||
branchStepInputs.children![key] = stepBuilder.build()
|
branchStepInputs.children![key] = stepBuilder.build()
|
||||||
})
|
})
|
||||||
|
|
||||||
const branchStep: AutomationStep = {
|
const branchStep: AutomationStep = {
|
||||||
...definition,
|
...definition,
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
|
@ -106,80 +105,98 @@ class BaseStepBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEPS
|
// STEPS
|
||||||
createRow(inputs: CreateRowStepInputs, opts?: { stepName?: string }): this {
|
createRow(
|
||||||
|
inputs: CreateRowStepInputs,
|
||||||
|
opts?: { stepName?: string; stepId?: string }
|
||||||
|
): this {
|
||||||
return this.step(
|
return this.step(
|
||||||
AutomationActionStepId.CREATE_ROW,
|
AutomationActionStepId.CREATE_ROW,
|
||||||
BUILTIN_ACTION_DEFINITIONS.CREATE_ROW,
|
BUILTIN_ACTION_DEFINITIONS.CREATE_ROW,
|
||||||
inputs,
|
inputs,
|
||||||
opts?.stepName
|
opts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRow(inputs: UpdateRowStepInputs, opts?: { stepName?: string }): this {
|
updateRow(
|
||||||
|
inputs: UpdateRowStepInputs,
|
||||||
|
opts?: { stepName?: string; stepId?: string }
|
||||||
|
): this {
|
||||||
return this.step(
|
return this.step(
|
||||||
AutomationActionStepId.UPDATE_ROW,
|
AutomationActionStepId.UPDATE_ROW,
|
||||||
BUILTIN_ACTION_DEFINITIONS.UPDATE_ROW,
|
BUILTIN_ACTION_DEFINITIONS.UPDATE_ROW,
|
||||||
inputs,
|
inputs,
|
||||||
opts?.stepName
|
opts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRow(inputs: DeleteRowStepInputs, opts?: { stepName?: string }): this {
|
deleteRow(
|
||||||
|
inputs: DeleteRowStepInputs,
|
||||||
|
opts?: { stepName?: string; stepId?: string }
|
||||||
|
): this {
|
||||||
return this.step(
|
return this.step(
|
||||||
AutomationActionStepId.DELETE_ROW,
|
AutomationActionStepId.DELETE_ROW,
|
||||||
BUILTIN_ACTION_DEFINITIONS.DELETE_ROW,
|
BUILTIN_ACTION_DEFINITIONS.DELETE_ROW,
|
||||||
inputs,
|
inputs,
|
||||||
opts?.stepName
|
opts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendSmtpEmail(
|
sendSmtpEmail(
|
||||||
inputs: SmtpEmailStepInputs,
|
inputs: SmtpEmailStepInputs,
|
||||||
opts?: { stepName?: string }
|
opts?: { stepName?: string; stepId?: string }
|
||||||
): this {
|
): this {
|
||||||
return this.step(
|
return this.step(
|
||||||
AutomationActionStepId.SEND_EMAIL_SMTP,
|
AutomationActionStepId.SEND_EMAIL_SMTP,
|
||||||
BUILTIN_ACTION_DEFINITIONS.SEND_EMAIL_SMTP,
|
BUILTIN_ACTION_DEFINITIONS.SEND_EMAIL_SMTP,
|
||||||
inputs,
|
inputs,
|
||||||
opts?.stepName
|
opts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
executeQuery(
|
executeQuery(
|
||||||
inputs: ExecuteQueryStepInputs,
|
inputs: ExecuteQueryStepInputs,
|
||||||
opts?: { stepName?: string }
|
opts?: { stepName?: string; stepId?: string }
|
||||||
): this {
|
): this {
|
||||||
return this.step(
|
return this.step(
|
||||||
AutomationActionStepId.EXECUTE_QUERY,
|
AutomationActionStepId.EXECUTE_QUERY,
|
||||||
BUILTIN_ACTION_DEFINITIONS.EXECUTE_QUERY,
|
BUILTIN_ACTION_DEFINITIONS.EXECUTE_QUERY,
|
||||||
inputs,
|
inputs,
|
||||||
opts?.stepName
|
opts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
queryRows(inputs: QueryRowsStepInputs, opts?: { stepName?: string }): this {
|
queryRows(
|
||||||
|
inputs: QueryRowsStepInputs,
|
||||||
|
opts?: { stepName?: string; stepId?: string }
|
||||||
|
): this {
|
||||||
return this.step(
|
return this.step(
|
||||||
AutomationActionStepId.QUERY_ROWS,
|
AutomationActionStepId.QUERY_ROWS,
|
||||||
BUILTIN_ACTION_DEFINITIONS.QUERY_ROWS,
|
BUILTIN_ACTION_DEFINITIONS.QUERY_ROWS,
|
||||||
inputs,
|
inputs,
|
||||||
opts?.stepName
|
opts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
loop(inputs: LoopStepInputs, opts?: { stepName?: string }): this {
|
loop(
|
||||||
|
inputs: LoopStepInputs,
|
||||||
|
opts?: { stepName?: string; stepId?: string }
|
||||||
|
): this {
|
||||||
return this.step(
|
return this.step(
|
||||||
AutomationActionStepId.LOOP,
|
AutomationActionStepId.LOOP,
|
||||||
BUILTIN_ACTION_DEFINITIONS.LOOP,
|
BUILTIN_ACTION_DEFINITIONS.LOOP,
|
||||||
inputs,
|
inputs,
|
||||||
opts?.stepName
|
opts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverLog(input: ServerLogStepInputs, opts?: { stepName?: string }): this {
|
serverLog(
|
||||||
|
input: ServerLogStepInputs,
|
||||||
|
opts?: { stepName?: string; stepId?: string }
|
||||||
|
): this {
|
||||||
return this.step(
|
return this.step(
|
||||||
AutomationActionStepId.SERVER_LOG,
|
AutomationActionStepId.SERVER_LOG,
|
||||||
BUILTIN_ACTION_DEFINITIONS.SERVER_LOG,
|
BUILTIN_ACTION_DEFINITIONS.SERVER_LOG,
|
||||||
input,
|
input,
|
||||||
opts?.stepName
|
opts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,8 @@ export interface TriggerOutput {
|
||||||
|
|
||||||
export interface AutomationContext extends AutomationResults {
|
export interface AutomationContext extends AutomationResults {
|
||||||
steps: any[]
|
steps: any[]
|
||||||
stepsByName?: Record<string, any>
|
stepsById: Record<string, any>
|
||||||
|
stepsByName: Record<string, any>
|
||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
trigger: any
|
trigger: any
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ class Orchestrator {
|
||||||
private job: Job
|
private job: Job
|
||||||
private loopStepOutputs: LoopStep[]
|
private loopStepOutputs: LoopStep[]
|
||||||
private stopped: boolean
|
private stopped: boolean
|
||||||
private executionOutput: AutomationContext
|
private executionOutput: Omit<AutomationContext, "stepsByName" | "stepsById">
|
||||||
|
|
||||||
constructor(job: AutomationJob) {
|
constructor(job: AutomationJob) {
|
||||||
let automation = job.data.automation
|
let automation = job.data.automation
|
||||||
|
@ -91,6 +91,7 @@ class Orchestrator {
|
||||||
// step zero is never used as the template string is zero indexed for customer facing
|
// step zero is never used as the template string is zero indexed for customer facing
|
||||||
this.context = {
|
this.context = {
|
||||||
steps: [{}],
|
steps: [{}],
|
||||||
|
stepsById: {},
|
||||||
stepsByName: {},
|
stepsByName: {},
|
||||||
trigger: triggerOutput,
|
trigger: triggerOutput,
|
||||||
}
|
}
|
||||||
|
@ -457,8 +458,9 @@ class Orchestrator {
|
||||||
inputs: steps[stepToLoopIndex].inputs,
|
inputs: steps[stepToLoopIndex].inputs,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.context.stepsById[steps[stepToLoopIndex].id] = tempOutput
|
||||||
const stepName = steps[stepToLoopIndex].name || steps[stepToLoopIndex].id
|
const stepName = steps[stepToLoopIndex].name || steps[stepToLoopIndex].id
|
||||||
this.context.stepsByName![stepName] = tempOutput
|
this.context.stepsByName[stepName] = tempOutput
|
||||||
this.context.steps[this.context.steps.length] = tempOutput
|
this.context.steps[this.context.steps.length] = tempOutput
|
||||||
this.context.steps = this.context.steps.filter(
|
this.context.steps = this.context.steps.filter(
|
||||||
item => !item.hasOwnProperty.call(item, "currentItem")
|
item => !item.hasOwnProperty.call(item, "currentItem")
|
||||||
|
@ -517,7 +519,10 @@ class Orchestrator {
|
||||||
Object.entries(filter).forEach(([_, value]) => {
|
Object.entries(filter).forEach(([_, value]) => {
|
||||||
Object.entries(value).forEach(([field, _]) => {
|
Object.entries(value).forEach(([field, _]) => {
|
||||||
const updatedField = field.replace("{{", "{{ literal ")
|
const updatedField = field.replace("{{", "{{ literal ")
|
||||||
const fromContext = processStringSync(updatedField, this.context)
|
const fromContext = processStringSync(
|
||||||
|
updatedField,
|
||||||
|
this.processContext(this.context)
|
||||||
|
)
|
||||||
toFilter[field] = fromContext
|
toFilter[field] = fromContext
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -563,9 +568,9 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
|
|
||||||
const stepFn = await this.getStepFunctionality(step.stepId)
|
const stepFn = await this.getStepFunctionality(step.stepId)
|
||||||
let inputs = await this.addContextAndProcess(
|
let inputs = await processObject(
|
||||||
originalStepInput,
|
originalStepInput,
|
||||||
this.context
|
this.processContext(this.context)
|
||||||
)
|
)
|
||||||
|
|
||||||
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
|
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
|
||||||
|
@ -594,16 +599,16 @@ class Orchestrator {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private async addContextAndProcess(inputs: any, context: any) {
|
private processContext(context: AutomationContext) {
|
||||||
const processContext = {
|
const processContext = {
|
||||||
...context,
|
...context,
|
||||||
steps: {
|
steps: {
|
||||||
...context.steps,
|
...context.steps,
|
||||||
|
...context.stepsById,
|
||||||
...context.stepsByName,
|
...context.stepsByName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
return processContext
|
||||||
return processObject(inputs, processContext)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleStepOutput(
|
private handleStepOutput(
|
||||||
|
@ -623,6 +628,7 @@ class Orchestrator {
|
||||||
} else {
|
} else {
|
||||||
this.updateExecutionOutput(step.id, step.stepId, step.inputs, outputs)
|
this.updateExecutionOutput(step.id, step.stepId, step.inputs, outputs)
|
||||||
this.context.steps[this.context.steps.length] = outputs
|
this.context.steps[this.context.steps.length] = outputs
|
||||||
|
this.context.stepsById![step.id] = outputs
|
||||||
const stepName = step.name || step.id
|
const stepName = step.name || step.id
|
||||||
this.context.stepsByName![stepName] = outputs
|
this.context.stepsByName![stepName] = outputs
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import cronValidate from "cron-validate"
|
import cronValidate from "cron-validate"
|
||||||
|
import cronParser from "cron-parser"
|
||||||
|
|
||||||
const INPUT_CRON_START = "(Input cron: "
|
const INPUT_CRON_START = "(Input cron: "
|
||||||
const ERROR_SWAPS = {
|
const ERROR_SWAPS = {
|
||||||
|
@ -30,6 +31,19 @@ function improveErrors(errors: string[]): string[] {
|
||||||
return finalErrors
|
return finalErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNextExecutionDates(
|
||||||
|
cronExpression: string,
|
||||||
|
limit: number = 4
|
||||||
|
): string[] {
|
||||||
|
const parsed = cronParser.parseExpression(cronExpression)
|
||||||
|
const nextRuns = []
|
||||||
|
for (let i = 0; i < limit; i++) {
|
||||||
|
nextRuns.push(parsed.next().toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextRuns
|
||||||
|
}
|
||||||
|
|
||||||
export function validate(
|
export function validate(
|
||||||
cronExpression: string
|
cronExpression: string
|
||||||
): { valid: false; err: string[] } | { valid: true } {
|
): { valid: false; err: string[] } | { valid: true } {
|
||||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -817,23 +817,23 @@
|
||||||
tslib "^2.2.0"
|
tslib "^2.2.0"
|
||||||
|
|
||||||
"@azure/msal-browser@^3.11.1":
|
"@azure/msal-browser@^3.11.1":
|
||||||
version "3.23.0"
|
version "3.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.23.0.tgz#446aaf268247e5943f464f007d3aa3a04abfe95b"
|
resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.24.0.tgz#3208047672d0b0c943b0bef5f995d510d6582ae4"
|
||||||
integrity sha512-+QgdMvaeEpdtgRTD7AHHq9aw8uga7mXVHV1KshO1RQ2uI5B55xJ4aEpGlg/ga3H+0arEVcRfT4ZVmX7QLXiCVw==
|
integrity sha512-JGNV9hTYAa7lsum9IMIibn2kKczAojNihGo1hi7pG0kNrcKej530Fl6jxwM05A44/6I079CSn6WxYxbVhKUmWg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@azure/msal-common" "14.14.2"
|
"@azure/msal-common" "14.15.0"
|
||||||
|
|
||||||
"@azure/msal-common@14.14.2":
|
"@azure/msal-common@14.15.0":
|
||||||
version "14.14.2"
|
version "14.15.0"
|
||||||
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.14.2.tgz#583b4ac9c089953718d7a5e2f3b8df2d4dbb17f4"
|
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.15.0.tgz#0e27ac0bb88fe100f4f8d1605b64d5c268636a55"
|
||||||
integrity sha512-XV0P5kSNwDwCA/SjIxTe9mEAsKB0NqGNSuaVrkCCE2lAyBr/D6YtD80Vkdp4tjWnPFwjzkwldjr1xU/facOJog==
|
integrity sha512-ImAQHxmpMneJ/4S8BRFhjt1MZ3bppmpRPYYNyzeQPeFN288YKbb8TmmISQEbtfkQ1BPASvYZU5doIZOPBAqENQ==
|
||||||
|
|
||||||
"@azure/msal-node@^2.9.2":
|
"@azure/msal-node@^2.9.2":
|
||||||
version "2.13.1"
|
version "2.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.13.1.tgz#f144371275b7c3cbe564762b84772a9732457a47"
|
resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.14.0.tgz#7881895d41b03d8b9b38a29550ba3bbb15f73b3c"
|
||||||
integrity sha512-sijfzPNorKt6+9g1/miHwhj6Iapff4mPQx1azmmZExgzUROqWTM1o3ACyxDja0g47VpowFy/sxTM/WsuCyXTiw==
|
integrity sha512-rrfzIpG3Q1rHjVYZmHAEDidWAZZ2cgkxlIcMQ8dHebRISaZ2KCV33Q8Vs+uaV6lxweROabNxKFlR2lIKagZqYg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@azure/msal-common" "14.14.2"
|
"@azure/msal-common" "14.15.0"
|
||||||
jsonwebtoken "^9.0.0"
|
jsonwebtoken "^9.0.0"
|
||||||
uuid "^8.3.0"
|
uuid "^8.3.0"
|
||||||
|
|
||||||
|
@ -9167,6 +9167,13 @@ cron-parser@^4.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
luxon "^3.2.1"
|
luxon "^3.2.1"
|
||||||
|
|
||||||
|
cron-parser@^4.9.0:
|
||||||
|
version "4.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5"
|
||||||
|
integrity sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==
|
||||||
|
dependencies:
|
||||||
|
luxon "^3.2.1"
|
||||||
|
|
||||||
cron-validate@1.4.5:
|
cron-validate@1.4.5:
|
||||||
version "1.4.5"
|
version "1.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/cron-validate/-/cron-validate-1.4.5.tgz#eceb221f7558e6302e5f84c7b3a454fdf4d064c3"
|
resolved "https://registry.yarnpkg.com/cron-validate/-/cron-validate-1.4.5.tgz#eceb221f7558e6302e5f84c7b3a454fdf4d064c3"
|
||||||
|
|
Loading…
Reference in New Issue