Merge remote-tracking branch 'origin/develop' into feature/binding-v2-updates
This commit is contained in:
commit
0730c15b14
|
@ -44,7 +44,10 @@ jobs:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn nx run-many -t=build --configuration=production
|
# Run build all the projects
|
||||||
|
- run: yarn build
|
||||||
|
# Check the types of the projects built via esbuild
|
||||||
|
- run: yarn check:types
|
||||||
|
|
||||||
test-libraries:
|
test-libraries:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
@ -54,6 +54,9 @@ jobs:
|
||||||
- run: yarn build --configuration=production
|
- run: yarn build --configuration=production
|
||||||
- run: yarn build:sdk
|
- run: yarn build:sdk
|
||||||
|
|
||||||
|
- name: Reset pro dependencies
|
||||||
|
run: node scripts/resetProDependencies.js
|
||||||
|
|
||||||
- name: Publish budibase packages to NPM
|
- name: Publish budibase packages to NPM
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
|
@ -60,6 +60,9 @@ jobs:
|
||||||
- run: yarn build --configuration=production
|
- run: yarn build --configuration=production
|
||||||
- run: yarn build:sdk
|
- run: yarn build:sdk
|
||||||
|
|
||||||
|
- name: Reset pro dependencies
|
||||||
|
run: node scripts/resetProDependencies.js
|
||||||
|
|
||||||
- name: Publish budibase packages to NPM
|
- name: Publish budibase packages to NPM
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.6.19-alpha.11",
|
"version": "2.6.19-alpha.21",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/backend-core",
|
"packages/backend-core",
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild-plugins/node-resolve": "^0.2.2",
|
"@esbuild-plugins/node-resolve": "^0.2.2",
|
||||||
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
||||||
"@nx/esbuild": "16.2.1",
|
|
||||||
"@nx/js": "16.2.1",
|
"@nx/js": "16.2.1",
|
||||||
"@rollup/plugin-json": "^4.0.2",
|
"@rollup/plugin-json": "^4.0.2",
|
||||||
"@typescript-eslint/parser": "5.45.0",
|
"@typescript-eslint/parser": "5.45.0",
|
||||||
|
@ -34,6 +33,7 @@
|
||||||
"bootstrap": "./scripts/link-dependencies.sh && echo '***BOOTSTRAP ONLY REQUIRED FOR USE WITH ACCOUNT PORTAL***'",
|
"bootstrap": "./scripts/link-dependencies.sh && echo '***BOOTSTRAP ONLY REQUIRED FOR USE WITH ACCOUNT PORTAL***'",
|
||||||
"build": "yarn nx run-many -t=build",
|
"build": "yarn nx run-many -t=build",
|
||||||
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
|
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
|
||||||
|
"check:types": "lerna run check:types --skip-nx-cache",
|
||||||
"backend:bootstrap": "./scripts/scopeBackend.sh && yarn run bootstrap",
|
"backend:bootstrap": "./scripts/scopeBackend.sh && yarn run bootstrap",
|
||||||
"backend:build": "./scripts/scopeBackend.sh 'lerna run --stream build'",
|
"backend:build": "./scripts/scopeBackend.sh 'lerna run --stream build'",
|
||||||
"build:sdk": "lerna run --stream build:sdk",
|
"build:sdk": "lerna run --stream build:sdk",
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
"dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
|
"dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
|
||||||
"dev:server": "yarn run kill-server && lerna run --stream --parallel dev:builder --scope @budibase/worker --scope @budibase/server",
|
"dev:server": "yarn run kill-server && lerna run --stream --parallel dev:builder --scope @budibase/worker --scope @budibase/server",
|
||||||
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built",
|
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built",
|
||||||
"dev:docker": "yarn build && docker-compose -f hosting/docker-compose.dev.yaml -f hosting/docker-compose.build.yaml up --build --scale proxy-service=0 ",
|
"dev:docker": "yarn build && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0",
|
||||||
"test": "lerna run --stream test --stream",
|
"test": "lerna run --stream test --stream",
|
||||||
"lint:eslint": "eslint packages && eslint qa-core",
|
"lint:eslint": "eslint packages && eslint qa-core",
|
||||||
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",
|
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",
|
||||||
|
|
|
@ -90,6 +90,10 @@ export const useScimIntegration = () => {
|
||||||
return useFeature(Feature.SCIM)
|
return useFeature(Feature.SCIM)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useSyncAutomations = () => {
|
||||||
|
return useFeature(Feature.SYNC_AUTOMATIONS)
|
||||||
|
}
|
||||||
|
|
||||||
// QUOTAS
|
// QUOTAS
|
||||||
|
|
||||||
export const setAutomationLogsQuota = (value: number) => {
|
export const setAutomationLogsQuota = (value: number) => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { datasources, tables } from "../stores/backend"
|
||||||
import { IntegrationNames } from "../constants/backend"
|
import { IntegrationNames } from "../constants/backend"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import cloneDeep from "lodash/cloneDeepWith"
|
import cloneDeep from "lodash/cloneDeepWith"
|
||||||
|
import { API } from "api"
|
||||||
|
|
||||||
function prepareData(config) {
|
function prepareData(config) {
|
||||||
let datasource = {}
|
let datasource = {}
|
||||||
|
@ -37,3 +38,9 @@ export async function createRestDatasource(integration) {
|
||||||
const config = cloneDeep(integration)
|
const config = cloneDeep(integration)
|
||||||
return saveDatasource(config)
|
return saveDatasource(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function validateDatasourceConfig(config) {
|
||||||
|
const datasource = prepareData(config)
|
||||||
|
const resp = await API.validateDatasource(datasource)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ActionStepID } from "constants/backend/automations"
|
||||||
import { TableNames } from "../constants"
|
import { TableNames } from "../constants"
|
||||||
import {
|
import {
|
||||||
AUTO_COLUMN_DISPLAY_NAMES,
|
AUTO_COLUMN_DISPLAY_NAMES,
|
||||||
|
@ -53,3 +54,9 @@ export function buildAutoColumn(tableName, name, subtype) {
|
||||||
}
|
}
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function checkForCollectStep(automation) {
|
||||||
|
return automation.definition.steps.some(
|
||||||
|
step => step.stepId === ActionStepID.COLLECT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -6,23 +6,47 @@
|
||||||
Body,
|
Body,
|
||||||
Icon,
|
Icon,
|
||||||
notifications,
|
notifications,
|
||||||
|
Tags,
|
||||||
|
Tag,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore, selectedAutomation } from "builderStore"
|
||||||
import { admin } from "stores/portal"
|
import { admin, licensing } from "stores/portal"
|
||||||
import { externalActions } from "./ExternalActions"
|
import { externalActions } from "./ExternalActions"
|
||||||
|
import { TriggerStepID } from "constants/backend/automations"
|
||||||
|
import { checkForCollectStep } from "builderStore/utils"
|
||||||
|
|
||||||
export let blockIdx
|
export let blockIdx
|
||||||
|
export let lastStep
|
||||||
|
|
||||||
const disabled = {
|
let syncAutomationsEnabled = $licensing.syncAutomationsEnabled
|
||||||
|
let collectBlockAllowedSteps = [TriggerStepID.APP, TriggerStepID.WEBHOOK]
|
||||||
|
let selectedAction
|
||||||
|
let actionVal
|
||||||
|
let actions = Object.entries($automationStore.blockDefinitions.ACTION)
|
||||||
|
|
||||||
|
$: collectBlockExists = checkForCollectStep($selectedAutomation)
|
||||||
|
|
||||||
|
const disabled = () => {
|
||||||
|
return {
|
||||||
SEND_EMAIL_SMTP: {
|
SEND_EMAIL_SMTP: {
|
||||||
disabled: !$admin.checklist.smtp.checked,
|
disabled: !$admin.checklist.smtp.checked,
|
||||||
message: "Please configure SMTP",
|
message: "Please configure SMTP",
|
||||||
},
|
},
|
||||||
|
COLLECT: {
|
||||||
|
disabled: !lastStep || !syncAutomationsEnabled || collectBlockExists,
|
||||||
|
message: collectDisabledMessage(),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedAction
|
const collectDisabledMessage = () => {
|
||||||
let actionVal
|
if (collectBlockExists) {
|
||||||
let actions = Object.entries($automationStore.blockDefinitions.ACTION)
|
return "Only one Collect step allowed"
|
||||||
|
}
|
||||||
|
if (!lastStep) {
|
||||||
|
return "Only available as the last step"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const external = actions.reduce((acc, elm) => {
|
const external = actions.reduce((acc, elm) => {
|
||||||
const [k, v] = elm
|
const [k, v] = elm
|
||||||
|
@ -38,6 +62,15 @@
|
||||||
acc[k] = v
|
acc[k] = v
|
||||||
}
|
}
|
||||||
delete acc.LOOP
|
delete acc.LOOP
|
||||||
|
|
||||||
|
// Filter out Collect block if not App Action or Webhook
|
||||||
|
if (
|
||||||
|
!collectBlockAllowedSteps.includes(
|
||||||
|
$selectedAutomation.definition.trigger.stepId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
delete acc.COLLECT
|
||||||
|
}
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
|
@ -48,7 +81,6 @@
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
console.log(plugins)
|
|
||||||
|
|
||||||
const selectAction = action => {
|
const selectAction = action => {
|
||||||
actionVal = action
|
actionVal = action
|
||||||
|
@ -72,7 +104,7 @@
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Add automation step"
|
title="Add automation step"
|
||||||
confirmText="Save"
|
confirmText="Save"
|
||||||
size="M"
|
size="L"
|
||||||
disabled={!selectedAction}
|
disabled={!selectedAction}
|
||||||
onConfirm={addBlockToAutomation}
|
onConfirm={addBlockToAutomation}
|
||||||
>
|
>
|
||||||
|
@ -107,7 +139,7 @@
|
||||||
<Detail size="S">Actions</Detail>
|
<Detail size="S">Actions</Detail>
|
||||||
<div class="item-list">
|
<div class="item-list">
|
||||||
{#each Object.entries(internal) as [idx, action]}
|
{#each Object.entries(internal) as [idx, action]}
|
||||||
{@const isDisabled = disabled[idx] && disabled[idx].disabled}
|
{@const isDisabled = disabled()[idx] && disabled()[idx].disabled}
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
class:disabled={isDisabled}
|
class:disabled={isDisabled}
|
||||||
|
@ -117,8 +149,14 @@
|
||||||
<div class="item-body">
|
<div class="item-body">
|
||||||
<Icon name={action.icon} />
|
<Icon name={action.icon} />
|
||||||
<Body size="XS">{action.name}</Body>
|
<Body size="XS">{action.name}</Body>
|
||||||
{#if isDisabled}
|
{#if isDisabled && !syncAutomationsEnabled}
|
||||||
<Icon name="Help" tooltip={disabled[idx].message} />
|
<div class="tag-color">
|
||||||
|
<Tags>
|
||||||
|
<Tag icon="LockClosed">Business</Tag>
|
||||||
|
</Tags>
|
||||||
|
</div>
|
||||||
|
{:else if isDisabled}
|
||||||
|
<Icon name="Help" tooltip={disabled()[idx].message} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -152,6 +190,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: var(--spacing-m);
|
margin-left: var(--spacing-m);
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.item-list {
|
.item-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -181,4 +220,8 @@
|
||||||
.disabled :global(.spectrum-Body) {
|
.disabled :global(.spectrum-Body) {
|
||||||
color: var(--spectrum-global-color-gray-600);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-color :global(.spectrum-Tags-item) {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -17,7 +17,11 @@
|
||||||
import ActionModal from "./ActionModal.svelte"
|
import ActionModal from "./ActionModal.svelte"
|
||||||
import FlowItemHeader from "./FlowItemHeader.svelte"
|
import FlowItemHeader from "./FlowItemHeader.svelte"
|
||||||
import RoleSelect from "components/design/settings/controls/RoleSelect.svelte"
|
import RoleSelect from "components/design/settings/controls/RoleSelect.svelte"
|
||||||
import { ActionStepID, TriggerStepID } from "constants/backend/automations"
|
import {
|
||||||
|
ActionStepID,
|
||||||
|
TriggerStepID,
|
||||||
|
Features,
|
||||||
|
} from "constants/backend/automations"
|
||||||
import { permissions } from "stores/backend"
|
import { permissions } from "stores/backend"
|
||||||
|
|
||||||
export let block
|
export let block
|
||||||
|
@ -31,6 +35,9 @@
|
||||||
let showLooping = false
|
let showLooping = false
|
||||||
let role
|
let role
|
||||||
|
|
||||||
|
$: collectBlockExists = $selectedAutomation.definition.steps.some(
|
||||||
|
step => step.stepId === ActionStepID.COLLECT
|
||||||
|
)
|
||||||
$: automationId = $selectedAutomation?._id
|
$: automationId = $selectedAutomation?._id
|
||||||
$: showBindingPicker =
|
$: showBindingPicker =
|
||||||
block.stepId === ActionStepID.CREATE_ROW ||
|
block.stepId === ActionStepID.CREATE_ROW ||
|
||||||
|
@ -184,7 +191,7 @@
|
||||||
{#if !isTrigger}
|
{#if !isTrigger}
|
||||||
<div>
|
<div>
|
||||||
<div class="block-options">
|
<div class="block-options">
|
||||||
{#if !loopBlock}
|
{#if block?.features?.[Features.LOOPING] || !block.features}
|
||||||
<ActionButton on:click={() => addLooping()} icon="Reuse">
|
<ActionButton on:click={() => addLooping()} icon="Reuse">
|
||||||
Add Looping
|
Add Looping
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
@ -224,21 +231,28 @@
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Modal bind:this={actionModal} width="30%">
|
|
||||||
<ActionModal {blockIdx} />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<Modal bind:this={webhookModal} width="30%">
|
|
||||||
<CreateWebhookModal />
|
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="separator" />
|
{#if !collectBlockExists || !lastStep}
|
||||||
<Icon on:click={() => actionModal.show()} hoverable name="AddCircle" size="S" />
|
|
||||||
{#if isTrigger ? totalBlocks > 1 : blockIdx !== totalBlocks - 2}
|
|
||||||
<div class="separator" />
|
<div class="separator" />
|
||||||
|
<Icon
|
||||||
|
on:click={() => actionModal.show()}
|
||||||
|
hoverable
|
||||||
|
name="AddCircle"
|
||||||
|
size="S"
|
||||||
|
/>
|
||||||
|
{#if isTrigger ? totalBlocks > 1 : blockIdx !== totalBlocks - 2}
|
||||||
|
<div class="separator" />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<Modal bind:this={actionModal} width="30%">
|
||||||
|
<ActionModal {lastStep} {blockIdx} />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal bind:this={webhookModal} width="30%">
|
||||||
|
<CreateWebhookModal />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.delete-padding {
|
.delete-padding {
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
config,
|
config,
|
||||||
schema: selected.datasource,
|
schema: selected.datasource,
|
||||||
auth: selected.auth,
|
auth: selected.auth,
|
||||||
|
features: selected.features || [],
|
||||||
}
|
}
|
||||||
if (selected.friendlyName) {
|
if (selected.friendlyName) {
|
||||||
integration.name = selected.friendlyName
|
integration.name = selected.friendlyName
|
||||||
|
|
|
@ -4,55 +4,68 @@
|
||||||
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
|
||||||
import { IntegrationNames } from "constants/backend"
|
import { IntegrationNames } from "constants/backend"
|
||||||
import cloneDeep from "lodash/cloneDeepWith"
|
import cloneDeep from "lodash/cloneDeepWith"
|
||||||
import { saveDatasource as save } from "builderStore/datasource"
|
import {
|
||||||
import { onMount } from "svelte"
|
saveDatasource as save,
|
||||||
|
validateDatasourceConfig,
|
||||||
|
} from "builderStore/datasource"
|
||||||
|
import { DatasourceFeature } from "@budibase/types"
|
||||||
|
|
||||||
export let integration
|
export let integration
|
||||||
export let modal
|
export let modal
|
||||||
|
|
||||||
// kill the reference so the input isn't saved
|
// kill the reference so the input isn't saved
|
||||||
let datasource = cloneDeep(integration)
|
let datasource = cloneDeep(integration)
|
||||||
let skipFetch = false
|
|
||||||
let isValid = false
|
let isValid = false
|
||||||
|
|
||||||
$: name =
|
$: name =
|
||||||
IntegrationNames[datasource.type] || datasource.name || datasource.type
|
IntegrationNames[datasource.type] || datasource.name || datasource.type
|
||||||
|
|
||||||
|
async function validateConfig() {
|
||||||
|
const displayError = message =>
|
||||||
|
notifications.error(message ?? "Error validating datasource")
|
||||||
|
|
||||||
|
let connected = false
|
||||||
|
try {
|
||||||
|
const resp = await validateDatasourceConfig(datasource)
|
||||||
|
if (!resp.connected) {
|
||||||
|
displayError(`Unable to connect - ${resp.error}`)
|
||||||
|
}
|
||||||
|
connected = resp.connected
|
||||||
|
} catch (err) {
|
||||||
|
displayError(err?.message)
|
||||||
|
}
|
||||||
|
return connected
|
||||||
|
}
|
||||||
|
|
||||||
async function saveDatasource() {
|
async function saveDatasource() {
|
||||||
|
if (integration.features[DatasourceFeature.CONNECTION_CHECKING]) {
|
||||||
|
const valid = await validateConfig()
|
||||||
|
if (!valid) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (!datasource.name) {
|
if (!datasource.name) {
|
||||||
datasource.name = name
|
datasource.name = name
|
||||||
}
|
}
|
||||||
const resp = await save(datasource, skipFetch)
|
const resp = await save(datasource)
|
||||||
$goto(`./datasource/${resp._id}`)
|
$goto(`./datasource/${resp._id}`)
|
||||||
notifications.success(`Datasource updated successfully.`)
|
notifications.success(`Datasource created successfully.`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(err?.message ?? "Error saving datasource")
|
notifications.error(err?.message ?? "Error saving datasource")
|
||||||
// prevent the modal from closing
|
// prevent the modal from closing
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
skipFetch = false
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title={`Connect to ${name}`}
|
title={`Connect to ${name}`}
|
||||||
onConfirm={() => saveDatasource()}
|
onConfirm={() => saveDatasource()}
|
||||||
onCancel={() => modal.show()}
|
onCancel={() => modal.show()}
|
||||||
confirmText={datasource.plus
|
confirmText={datasource.plus ? "Connect" : "Save and continue to query"}
|
||||||
? "Save and fetch tables"
|
|
||||||
: "Save and continue to query"}
|
|
||||||
cancelText="Back"
|
cancelText="Back"
|
||||||
showSecondaryButton={datasource.plus}
|
showSecondaryButton={datasource.plus}
|
||||||
secondaryButtonText={datasource.plus ? "Skip table fetch" : undefined}
|
|
||||||
secondaryAction={() => {
|
|
||||||
skipFetch = true
|
|
||||||
saveDatasource()
|
|
||||||
return true
|
|
||||||
}}
|
|
||||||
size="L"
|
size="L"
|
||||||
disabled={!isValid}
|
disabled={!isValid}
|
||||||
>
|
>
|
||||||
|
|
|
@ -126,8 +126,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllBindings = (bindings, eventContextBindings, actions) => {
|
const getAllBindings = (bindings, eventContextBindings, actions) => {
|
||||||
let allBindings = eventContextBindings.concat(bindings)
|
let allBindings = []
|
||||||
|
|
||||||
if (!actions) {
|
if (!actions) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -145,14 +144,35 @@
|
||||||
.forEach(action => {
|
.forEach(action => {
|
||||||
// Check we have a binding for this action, and generate one if not
|
// Check we have a binding for this action, and generate one if not
|
||||||
const stateBinding = makeStateBinding(action.parameters.key)
|
const stateBinding = makeStateBinding(action.parameters.key)
|
||||||
const hasKey = allBindings.some(binding => {
|
const hasKey = bindings.some(binding => {
|
||||||
return binding.runtimeBinding === stateBinding.runtimeBinding
|
return binding.runtimeBinding === stateBinding.runtimeBinding
|
||||||
})
|
})
|
||||||
if (!hasKey) {
|
if (!hasKey) {
|
||||||
allBindings.push(stateBinding)
|
bindings.push(stateBinding)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// Get which indexes are asynchronous automations as we want to filter them out from the bindings
|
||||||
|
const asynchronousAutomationIndexes = actions
|
||||||
|
.map((action, index) => {
|
||||||
|
if (
|
||||||
|
action[EVENT_TYPE_KEY] === "Trigger Automation" &&
|
||||||
|
!action.parameters?.synchronous
|
||||||
|
) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(index => index !== undefined)
|
||||||
|
|
||||||
|
// Based on the above, filter out the asynchronous automations from the bindings
|
||||||
|
if (asynchronousAutomationIndexes) {
|
||||||
|
allBindings = eventContextBindings
|
||||||
|
.filter((binding, index) => {
|
||||||
|
return !asynchronousAutomationIndexes.includes(index)
|
||||||
|
})
|
||||||
|
.concat(bindings)
|
||||||
|
} else {
|
||||||
|
allBindings = eventContextBindings.concat(bindings)
|
||||||
|
}
|
||||||
return allBindings
|
return allBindings
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label, Input, Checkbox } from "@budibase/bbui"
|
import { Select, Label, Input, Checkbox, Icon } from "@budibase/bbui"
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
import { TriggerStepID } from "constants/backend/automations"
|
import { TriggerStepID, ActionStepID } from "constants/backend/automations"
|
||||||
|
|
||||||
export let parameters = {}
|
export let parameters = {}
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
@ -16,6 +16,14 @@
|
||||||
? AUTOMATION_STATUS.EXISTING
|
? AUTOMATION_STATUS.EXISTING
|
||||||
: AUTOMATION_STATUS.NEW
|
: AUTOMATION_STATUS.NEW
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (automationStatus === AUTOMATION_STATUS.NEW) {
|
||||||
|
parameters.synchronous = false
|
||||||
|
}
|
||||||
|
parameters.synchronous = automations.find(
|
||||||
|
automation => automation._id === parameters.automationId
|
||||||
|
)?.synchronous
|
||||||
|
}
|
||||||
$: automations = $automationStore.automations
|
$: automations = $automationStore.automations
|
||||||
.filter(a => a.definition.trigger?.stepId === TriggerStepID.APP)
|
.filter(a => a.definition.trigger?.stepId === TriggerStepID.APP)
|
||||||
.map(automation => {
|
.map(automation => {
|
||||||
|
@ -23,10 +31,15 @@
|
||||||
automation.definition.trigger.inputs.fields || {}
|
automation.definition.trigger.inputs.fields || {}
|
||||||
).map(([name, type]) => ({ name, type }))
|
).map(([name, type]) => ({ name, type }))
|
||||||
|
|
||||||
|
let hasCollectBlock = automation.definition.steps.some(
|
||||||
|
step => step.stepId === ActionStepID.COLLECT
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: automation.name,
|
name: automation.name,
|
||||||
_id: automation._id,
|
_id: automation._id,
|
||||||
schema,
|
schema,
|
||||||
|
synchronous: hasCollectBlock,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
$: hasAutomations = automations && automations.length > 0
|
$: hasAutomations = automations && automations.length > 0
|
||||||
|
@ -35,6 +48,8 @@
|
||||||
)
|
)
|
||||||
$: selectedSchema = selectedAutomation?.schema
|
$: selectedSchema = selectedAutomation?.schema
|
||||||
|
|
||||||
|
$: error = parameters.timeout > 120 ? "Timeout must be less than 120s" : null
|
||||||
|
|
||||||
const onFieldsChanged = e => {
|
const onFieldsChanged = e => {
|
||||||
parameters.fields = Object.entries(e.detail || {}).reduce(
|
parameters.fields = Object.entries(e.detail || {}).reduce(
|
||||||
(acc, [key, value]) => {
|
(acc, [key, value]) => {
|
||||||
|
@ -57,6 +72,14 @@
|
||||||
parameters.fields = {}
|
parameters.fields = {}
|
||||||
parameters.automationId = automations[0]?._id
|
parameters.automationId = automations[0]?._id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onChange = value => {
|
||||||
|
let automationId = value.detail
|
||||||
|
parameters.synchronous = automations.find(
|
||||||
|
automation => automation._id === automationId
|
||||||
|
)?.synchronous
|
||||||
|
parameters.automationId = automationId
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
@ -85,6 +108,7 @@
|
||||||
|
|
||||||
{#if automationStatus === AUTOMATION_STATUS.EXISTING}
|
{#if automationStatus === AUTOMATION_STATUS.EXISTING}
|
||||||
<Select
|
<Select
|
||||||
|
on:change={onChange}
|
||||||
bind:value={parameters.automationId}
|
bind:value={parameters.automationId}
|
||||||
placeholder="Choose automation"
|
placeholder="Choose automation"
|
||||||
options={automations}
|
options={automations}
|
||||||
|
@ -98,6 +122,29 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if parameters.synchronous}
|
||||||
|
<Label small />
|
||||||
|
|
||||||
|
<div class="synchronous-info">
|
||||||
|
<Icon name="Info" />
|
||||||
|
<div>
|
||||||
|
<i
|
||||||
|
>This automation will run synchronously as it contains a Collect
|
||||||
|
step</i
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Label small />
|
||||||
|
|
||||||
|
<div class="timeout-width">
|
||||||
|
<Input
|
||||||
|
label="Timeout in seconds (120 max)"
|
||||||
|
type="number"
|
||||||
|
{error}
|
||||||
|
bind:value={parameters.timeout}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<Label small />
|
<Label small />
|
||||||
<Checkbox
|
<Checkbox
|
||||||
text="Do not display default notification"
|
text="Do not display default notification"
|
||||||
|
@ -133,6 +180,9 @@
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
.timeout-width {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
.params {
|
.params {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -142,6 +192,11 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.synchronous-info {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
.fields {
|
.fields {
|
||||||
margin-top: var(--spacing-l);
|
margin-top: var(--spacing-l);
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -57,7 +57,13 @@
|
||||||
{
|
{
|
||||||
"name": "Trigger Automation",
|
"name": "Trigger Automation",
|
||||||
"type": "application",
|
"type": "application",
|
||||||
"component": "TriggerAutomation"
|
"component": "TriggerAutomation",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Automation Result",
|
||||||
|
"value": "result"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Update Field Value",
|
"name": "Update Field Value",
|
||||||
|
|
|
@ -20,9 +20,14 @@ export const ActionStepID = {
|
||||||
FILTER: "FILTER",
|
FILTER: "FILTER",
|
||||||
QUERY_ROWS: "QUERY_ROWS",
|
QUERY_ROWS: "QUERY_ROWS",
|
||||||
LOOP: "LOOP",
|
LOOP: "LOOP",
|
||||||
|
COLLECT: "COLLECT",
|
||||||
// these used to be lowercase step IDs, maintain for backwards compat
|
// these used to be lowercase step IDs, maintain for backwards compat
|
||||||
discord: "discord",
|
discord: "discord",
|
||||||
slack: "slack",
|
slack: "slack",
|
||||||
zapier: "zapier",
|
zapier: "zapier",
|
||||||
integromat: "integromat",
|
integromat: "integromat",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Features = {
|
||||||
|
LOOPING: "LOOPING",
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
import { isEqual } from "lodash"
|
import { isEqual } from "lodash"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import ImportRestQueriesModal from "components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte"
|
import ImportRestQueriesModal from "components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte"
|
||||||
|
import { API } from "api"
|
||||||
|
import { DatasourceFeature } from "@budibase/types"
|
||||||
|
|
||||||
const querySchema = {
|
const querySchema = {
|
||||||
name: {},
|
name: {},
|
||||||
|
@ -45,7 +47,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function validateConfig() {
|
||||||
|
const displayError = message =>
|
||||||
|
notifications.error(message ?? "Error validating datasource")
|
||||||
|
|
||||||
|
let connected = false
|
||||||
|
try {
|
||||||
|
const resp = await API.validateDatasource(datasource)
|
||||||
|
if (!resp.connected) {
|
||||||
|
displayError(`Unable to connect - ${resp.error}`)
|
||||||
|
}
|
||||||
|
connected = resp.connected
|
||||||
|
} catch (err) {
|
||||||
|
displayError(err?.message)
|
||||||
|
}
|
||||||
|
return connected
|
||||||
|
}
|
||||||
|
|
||||||
const saveDatasource = async () => {
|
const saveDatasource = async () => {
|
||||||
|
if (integration.features[DatasourceFeature.CONNECTION_CHECKING]) {
|
||||||
|
const valid = await validateConfig()
|
||||||
|
if (!valid) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// Create datasource
|
// Create datasource
|
||||||
await datasources.save(datasource)
|
await datasources.save(datasource)
|
||||||
|
|
|
@ -31,6 +31,18 @@
|
||||||
return "Invalid URL"
|
return "Invalid URL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: urlManuallySet = false
|
||||||
|
|
||||||
|
const updateUrl = event => {
|
||||||
|
const appName = event.detail
|
||||||
|
if (urlManuallySet) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedUrl = appName.toLowerCase().replace(/\s+/g, "-")
|
||||||
|
url = encodeURI(parsedUrl)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -43,11 +55,13 @@
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
bind:error={nameError}
|
bind:error={nameError}
|
||||||
validate={validateName}
|
validate={validateName}
|
||||||
|
on:change={updateUrl}
|
||||||
label="Name"
|
label="Name"
|
||||||
/>
|
/>
|
||||||
<FancyInput
|
<FancyInput
|
||||||
bind:value={url}
|
bind:value={url}
|
||||||
bind:error={urlError}
|
bind:error={urlError}
|
||||||
|
on:change={() => (urlManuallySet = true)}
|
||||||
validate={validateUrl}
|
validate={validateUrl}
|
||||||
label="URL"
|
label="URL"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
import { Roles } from "constants/backend"
|
import { Roles } from "constants/backend"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import { validateDatasourceConfig } from "builderStore/datasource"
|
||||||
|
import { DatasourceFeature } from "@budibase/types"
|
||||||
|
|
||||||
let name = "My first app"
|
let name = "My first app"
|
||||||
let url = "my-first-app"
|
let url = "my-first-app"
|
||||||
|
@ -108,7 +110,24 @@
|
||||||
isGoogle,
|
isGoogle,
|
||||||
}) => {
|
}) => {
|
||||||
let app
|
let app
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
datasourceConfig &&
|
||||||
|
plusIntegrations[stage].features[DatasourceFeature.CONNECTION_CHECKING]
|
||||||
|
) {
|
||||||
|
const resp = await validateDatasourceConfig({
|
||||||
|
config: datasourceConfig,
|
||||||
|
type: stage,
|
||||||
|
})
|
||||||
|
if (!resp.connected) {
|
||||||
|
notifications.error(
|
||||||
|
`Unable to connect - ${resp.error ?? "Error validating datasource"}`
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app = await createApp(useSampleData)
|
app = await createApp(useSampleData)
|
||||||
|
|
||||||
let datasource
|
let datasource
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { licensing } from "./licensing"
|
import { licensing } from "./licensing"
|
||||||
import { ConfigType } from "../../../../types/src/documents"
|
import { ConfigType } from "@budibase/types"
|
||||||
|
|
||||||
export const createFeatureStore = () => {
|
export const createFeatureStore = () => {
|
||||||
const internalStore = writable({
|
const internalStore = writable({
|
||||||
|
|
|
@ -116,6 +116,9 @@ export const createLicensingStore = () => {
|
||||||
const auditLogsEnabled = license.features.includes(
|
const auditLogsEnabled = license.features.includes(
|
||||||
Constants.Features.AUDIT_LOGS
|
Constants.Features.AUDIT_LOGS
|
||||||
)
|
)
|
||||||
|
const syncAutomationsEnabled = license.features.includes(
|
||||||
|
Constants.Features.SYNC_AUTOMATIONS
|
||||||
|
)
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -130,6 +133,7 @@ export const createLicensingStore = () => {
|
||||||
environmentVariablesEnabled,
|
environmentVariablesEnabled,
|
||||||
auditLogsEnabled,
|
auditLogsEnabled,
|
||||||
enforceableSSO,
|
enforceableSSO,
|
||||||
|
syncAutomationsEnabled,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -122,13 +122,23 @@ const deleteRowHandler = async action => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggerAutomationHandler = async action => {
|
const triggerAutomationHandler = async action => {
|
||||||
const { fields, notificationOverride } = action.parameters
|
const { fields, notificationOverride, timeout } = action.parameters
|
||||||
if (fields) {
|
if (fields) {
|
||||||
try {
|
try {
|
||||||
await API.triggerAutomation({
|
const result = await API.triggerAutomation({
|
||||||
automationId: action.parameters.automationId,
|
automationId: action.parameters.automationId,
|
||||||
fields,
|
fields,
|
||||||
|
timeout,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Value will exist if automation is synchronous, so return it.
|
||||||
|
if (result.value) {
|
||||||
|
if (!notificationOverride) {
|
||||||
|
notificationStore.actions.success("Automation ran successfully")
|
||||||
|
}
|
||||||
|
return { result }
|
||||||
|
}
|
||||||
|
|
||||||
if (!notificationOverride) {
|
if (!notificationOverride) {
|
||||||
notificationStore.actions.success("Automation triggered")
|
notificationStore.actions.success("Automation triggered")
|
||||||
}
|
}
|
||||||
|
@ -138,7 +148,6 @@ const triggerAutomationHandler = async action => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigationHandler = action => {
|
const navigationHandler = action => {
|
||||||
const { url, peek, externalNewTab } = action.parameters
|
const { url, peek, externalNewTab } = action.parameters
|
||||||
routeStore.actions.navigate(url, peek, externalNewTab)
|
routeStore.actions.navigate(url, peek, externalNewTab)
|
||||||
|
|
|
@ -4,10 +4,10 @@ export const buildAutomationEndpoints = API => ({
|
||||||
* @param automationId the ID of the automation to trigger
|
* @param automationId the ID of the automation to trigger
|
||||||
* @param fields the fields to trigger the automation with
|
* @param fields the fields to trigger the automation with
|
||||||
*/
|
*/
|
||||||
triggerAutomation: async ({ automationId, fields }) => {
|
triggerAutomation: async ({ automationId, fields, timeout }) => {
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: `/api/automations/${automationId}/trigger`,
|
url: `/api/automations/${automationId}/trigger`,
|
||||||
body: { fields },
|
body: { fields, timeout },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -58,4 +58,15 @@ export const buildDatasourceEndpoints = API => ({
|
||||||
url: `/api/datasources/${datasourceId}/${datasourceRev}`,
|
url: `/api/datasources/${datasourceId}/${datasourceRev}`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a datasource configuration
|
||||||
|
* @param datasource the datasource configuration to validate
|
||||||
|
*/
|
||||||
|
validateDatasource: async datasource => {
|
||||||
|
return await API.post({
|
||||||
|
url: `/api/datasources/verify`,
|
||||||
|
body: { datasource },
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -37,6 +37,9 @@
|
||||||
.boolean-cell {
|
.boolean-cell {
|
||||||
padding: 2px var(--cell-padding);
|
padding: 2px var(--cell-padding);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.boolean-cell.editable {
|
.boolean-cell.editable {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let selected
|
export let selected
|
||||||
export let rowFocused
|
export let rowFocused
|
||||||
export let rowIdx
|
export let rowIdx
|
||||||
|
export let topRow = false
|
||||||
export let focused
|
export let focused
|
||||||
export let selectedUser
|
export let selectedUser
|
||||||
export let column
|
export let column
|
||||||
|
@ -68,6 +69,7 @@
|
||||||
{highlighted}
|
{highlighted}
|
||||||
{selected}
|
{selected}
|
||||||
{rowIdx}
|
{rowIdx}
|
||||||
|
{topRow}
|
||||||
{focused}
|
{focused}
|
||||||
{selectedUser}
|
{selectedUser}
|
||||||
{readonly}
|
{readonly}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
export let selectedUser = null
|
export let selectedUser = null
|
||||||
export let error = null
|
export let error = null
|
||||||
export let rowIdx
|
export let rowIdx
|
||||||
|
export let topRow = false
|
||||||
export let defaultHeight = false
|
export let defaultHeight = false
|
||||||
export let center = false
|
export let center = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
|
@ -31,13 +32,14 @@
|
||||||
class:readonly
|
class:readonly
|
||||||
class:default-height={defaultHeight}
|
class:default-height={defaultHeight}
|
||||||
class:selected-other={selectedUser != null}
|
class:selected-other={selectedUser != null}
|
||||||
|
class:alt={rowIdx % 2 === 1}
|
||||||
|
class:top={topRow}
|
||||||
on:focus
|
on:focus
|
||||||
on:mousedown
|
on:mousedown
|
||||||
on:mouseup
|
on:mouseup
|
||||||
on:click
|
on:click
|
||||||
on:contextmenu
|
on:contextmenu
|
||||||
{style}
|
{style}
|
||||||
data-row={rowIdx}
|
|
||||||
>
|
>
|
||||||
{#if error}
|
{#if error}
|
||||||
<div class="label">
|
<div class="label">
|
||||||
|
@ -70,6 +72,9 @@
|
||||||
width: 0;
|
width: 0;
|
||||||
--cell-color: transparent;
|
--cell-color: transparent;
|
||||||
}
|
}
|
||||||
|
.cell.alt {
|
||||||
|
--cell-background: var(--cell-background-alt);
|
||||||
|
}
|
||||||
.cell.default-height {
|
.cell.default-height {
|
||||||
height: var(--default-row-height);
|
height: var(--default-row-height);
|
||||||
}
|
}
|
||||||
|
@ -98,8 +103,8 @@
|
||||||
.cell.selected-other:not(.focused):after {
|
.cell.selected-other:not(.focused):after {
|
||||||
border-radius: 0 2px 2px 2px;
|
border-radius: 0 2px 2px 2px;
|
||||||
}
|
}
|
||||||
.cell[data-row="0"].error:after,
|
.cell.top.error:after,
|
||||||
.cell[data-row="0"].selected-other:not(.focused):after {
|
.cell.top.selected-other:not(.focused):after {
|
||||||
border-radius: 2px 2px 2px 0;
|
border-radius: 2px 2px 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +157,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.cell[data-row="0"] .label {
|
.cell.top .label {
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
border-radius: 0 2px 2px 2px;
|
border-radius: 0 2px 2px 2px;
|
||||||
|
|
|
@ -21,16 +21,7 @@
|
||||||
svelteDispatch("select")
|
svelteDispatch("select")
|
||||||
const id = row?._id
|
const id = row?._id
|
||||||
if (id) {
|
if (id) {
|
||||||
selectedRows.update(state => {
|
selectedRows.actions.toggleRow(id)
|
||||||
let newState = {
|
|
||||||
...state,
|
|
||||||
[id]: !state[id],
|
|
||||||
}
|
|
||||||
if (!newState[id]) {
|
|
||||||
delete newState[id]
|
|
||||||
}
|
|
||||||
return newState
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +38,7 @@
|
||||||
highlighted={rowFocused || rowHovered}
|
highlighted={rowFocused || rowHovered}
|
||||||
selected={rowSelected}
|
selected={rowSelected}
|
||||||
{defaultHeight}
|
{defaultHeight}
|
||||||
|
rowIdx={row?.__idx}
|
||||||
>
|
>
|
||||||
<div class="gutter">
|
<div class="gutter">
|
||||||
{#if $$slots.default}
|
{#if $$slots.default}
|
||||||
|
|
|
@ -196,7 +196,11 @@
|
||||||
<MenuItem disabled={!canMoveRight} icon="ChevronRight" on:click={moveRight}>
|
<MenuItem disabled={!canMoveRight} icon="ChevronRight" on:click={moveRight}>
|
||||||
Move right
|
Move right
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem icon="VisibilityOff" on:click={hideColumn}>Hide column</MenuItem>
|
<MenuItem
|
||||||
|
disabled={idx === "sticky"}
|
||||||
|
icon="VisibilityOff"
|
||||||
|
on:click={hideColumn}>Hide column</MenuItem
|
||||||
|
>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
export let allowExpandRows = true
|
export let allowExpandRows = true
|
||||||
export let allowEditRows = true
|
export let allowEditRows = true
|
||||||
export let allowDeleteRows = true
|
export let allowDeleteRows = true
|
||||||
|
export let stripeRows = false
|
||||||
|
|
||||||
// Unique identifier for DOM nodes inside this instance
|
// Unique identifier for DOM nodes inside this instance
|
||||||
const rand = Math.random()
|
const rand = Math.random()
|
||||||
|
@ -55,6 +56,7 @@
|
||||||
allowExpandRows,
|
allowExpandRows,
|
||||||
allowEditRows,
|
allowEditRows,
|
||||||
allowDeleteRows,
|
allowDeleteRows,
|
||||||
|
stripeRows,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Build up context
|
// Build up context
|
||||||
|
@ -90,6 +92,7 @@
|
||||||
allowExpandRows,
|
allowExpandRows,
|
||||||
allowEditRows,
|
allowEditRows,
|
||||||
allowDeleteRows,
|
allowDeleteRows,
|
||||||
|
stripeRows,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set context for children to consume
|
// Set context for children to consume
|
||||||
|
@ -107,6 +110,7 @@
|
||||||
id="grid-{rand}"
|
id="grid-{rand}"
|
||||||
class:is-resizing={$isResizing}
|
class:is-resizing={$isResizing}
|
||||||
class:is-reordering={$isReordering}
|
class:is-reordering={$isReordering}
|
||||||
|
class:stripe={$config.stripeRows}
|
||||||
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px; --content-lines:{$contentLines};"
|
style="--row-height:{$rowHeight}px; --default-row-height:{DefaultRowHeight}px; --gutter-width:{GutterWidth}px; --max-cell-render-height:{MaxCellRenderHeight}px; --max-cell-render-width-overflow:{MaxCellRenderWidthOverflow}px; --content-lines:{$contentLines};"
|
||||||
>
|
>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
@ -169,6 +173,7 @@
|
||||||
/* Variables */
|
/* Variables */
|
||||||
--cell-background: var(--spectrum-global-color-gray-50);
|
--cell-background: var(--spectrum-global-color-gray-50);
|
||||||
--cell-background-hover: var(--spectrum-global-color-gray-100);
|
--cell-background-hover: var(--spectrum-global-color-gray-100);
|
||||||
|
--cell-background-alt: var(--cell-background);
|
||||||
--cell-padding: 8px;
|
--cell-padding: 8px;
|
||||||
--cell-spacing: 4px;
|
--cell-spacing: 4px;
|
||||||
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
||||||
|
@ -185,6 +190,9 @@
|
||||||
.grid.is-reordering :global(*) {
|
.grid.is-reordering :global(*) {
|
||||||
cursor: grabbing !important;
|
cursor: grabbing !important;
|
||||||
}
|
}
|
||||||
|
.grid.stripe {
|
||||||
|
--cell-background-alt: var(--spectrum-global-color-gray-75);
|
||||||
|
}
|
||||||
|
|
||||||
.grid-data-outer,
|
.grid-data-outer,
|
||||||
.grid-data-inner {
|
.grid-data-inner {
|
||||||
|
|
|
@ -36,7 +36,11 @@
|
||||||
<div bind:this={body} class="grid-body">
|
<div bind:this={body} class="grid-body">
|
||||||
<GridScrollWrapper scrollHorizontally scrollVertically wheelInteractive>
|
<GridScrollWrapper scrollHorizontally scrollVertically wheelInteractive>
|
||||||
{#each $renderedRows as row, idx}
|
{#each $renderedRows as row, idx}
|
||||||
<GridRow {row} {idx} invertY={idx >= $rowVerticalInversionIndex} />
|
<GridRow
|
||||||
|
{row}
|
||||||
|
top={idx === 0}
|
||||||
|
invertY={idx >= $rowVerticalInversionIndex}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{#if $config.allowAddRows && $renderedColumns.length}
|
{#if $config.allowAddRows && $renderedColumns.length}
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import DataCell from "../cells/DataCell.svelte"
|
import DataCell from "../cells/DataCell.svelte"
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
export let idx
|
export let top = false
|
||||||
export let invertY = false
|
export let invertY = false
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -41,7 +41,8 @@
|
||||||
invertX={columnIdx >= $columnHorizontalInversionIndex}
|
invertX={columnIdx >= $columnHorizontalInversionIndex}
|
||||||
highlighted={rowHovered || rowFocused || reorderSource === column.name}
|
highlighted={rowHovered || rowFocused || reorderSource === column.name}
|
||||||
selected={rowSelected}
|
selected={rowSelected}
|
||||||
rowIdx={idx}
|
rowIdx={row.__idx}
|
||||||
|
topRow={top}
|
||||||
focused={$focusedCellId === cellId}
|
focused={$focusedCellId === cellId}
|
||||||
selectedUser={$selectedCellMap[cellId]}
|
selectedUser={$selectedCellMap[cellId]}
|
||||||
width={column.width}
|
width={column.width}
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
border-right: var(--cell-border);
|
border-right: var(--cell-border);
|
||||||
border-bottom: var(--cell-border);
|
border-bottom: var(--cell-border);
|
||||||
background: var(--spectrum-global-color-gray-100);
|
background: var(--spectrum-global-color-gray-100);
|
||||||
z-index: 20;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.add:hover {
|
.add:hover {
|
||||||
background: var(--spectrum-global-color-gray-200);
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background-color: var(--spectrum-global-color-gray-200);
|
background-color: var(--spectrum-global-color-gray-300);
|
||||||
color: var(--spectrum-global-color-gray-700);
|
color: var(--spectrum-global-color-gray-700);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -167,7 +167,7 @@
|
||||||
focused={$focusedCellId === cellId}
|
focused={$focusedCellId === cellId}
|
||||||
width={$stickyColumn.width}
|
width={$stickyColumn.width}
|
||||||
{updateValue}
|
{updateValue}
|
||||||
rowIdx={0}
|
topRow={offset === 0}
|
||||||
{invertY}
|
{invertY}
|
||||||
>
|
>
|
||||||
{#if $stickyColumn?.schema?.autocolumn}
|
{#if $stickyColumn?.schema?.autocolumn}
|
||||||
|
@ -193,7 +193,7 @@
|
||||||
row={newRow}
|
row={newRow}
|
||||||
focused={$focusedCellId === cellId}
|
focused={$focusedCellId === cellId}
|
||||||
width={column.width}
|
width={column.width}
|
||||||
rowIdx={0}
|
topRow={offset === 0}
|
||||||
invertX={columnIdx >= $columnHorizontalInversionIndex}
|
invertX={columnIdx >= $columnHorizontalInversionIndex}
|
||||||
{invertY}
|
{invertY}
|
||||||
>
|
>
|
||||||
|
@ -219,7 +219,7 @@
|
||||||
<Button size="M" secondary newStyles on:click={clear}>
|
<Button size="M" secondary newStyles on:click={clear}>
|
||||||
<div class="button-with-keys">
|
<div class="button-with-keys">
|
||||||
Cancel
|
Cancel
|
||||||
<KeyboardShortcut overlay keybind="Esc" />
|
<KeyboardShortcut keybind="Esc" />
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -82,7 +82,8 @@
|
||||||
{rowFocused}
|
{rowFocused}
|
||||||
selected={rowSelected}
|
selected={rowSelected}
|
||||||
highlighted={rowHovered || rowFocused}
|
highlighted={rowHovered || rowFocused}
|
||||||
rowIdx={idx}
|
rowIdx={row.__idx}
|
||||||
|
topRow={idx === 0}
|
||||||
focused={$focusedCellId === cellId}
|
focused={$focusedCellId === cellId}
|
||||||
selectedUser={$selectedCellMap[cellId]}
|
selectedUser={$selectedCellMap[cellId]}
|
||||||
width={$stickyColumn.width}
|
width={$stickyColumn.width}
|
||||||
|
|
|
@ -224,10 +224,7 @@
|
||||||
if (!id || id === NewRowID) {
|
if (!id || id === NewRowID) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
selectedRows.update(state => {
|
selectedRows.actions.toggleRow(id)
|
||||||
state[id] = !state[id]
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
|
@ -4,9 +4,10 @@ const reorderInitialState = {
|
||||||
sourceColumn: null,
|
sourceColumn: null,
|
||||||
targetColumn: null,
|
targetColumn: null,
|
||||||
breakpoints: [],
|
breakpoints: [],
|
||||||
initialMouseX: null,
|
|
||||||
scrollLeft: 0,
|
|
||||||
gridLeft: 0,
|
gridLeft: 0,
|
||||||
|
width: 0,
|
||||||
|
latestX: 0,
|
||||||
|
increment: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createStores = () => {
|
export const createStores = () => {
|
||||||
|
@ -23,14 +24,24 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const { reorder, columns, visibleColumns, scroll, bounds, stickyColumn, ui } =
|
const {
|
||||||
context
|
reorder,
|
||||||
|
columns,
|
||||||
|
visibleColumns,
|
||||||
|
scroll,
|
||||||
|
bounds,
|
||||||
|
stickyColumn,
|
||||||
|
ui,
|
||||||
|
maxScrollLeft,
|
||||||
|
} = context
|
||||||
|
|
||||||
|
let autoScrollInterval
|
||||||
|
let isAutoScrolling
|
||||||
|
|
||||||
// Callback when dragging on a colum header and starting reordering
|
// Callback when dragging on a colum header and starting reordering
|
||||||
const startReordering = (column, e) => {
|
const startReordering = (column, e) => {
|
||||||
const $visibleColumns = get(visibleColumns)
|
const $visibleColumns = get(visibleColumns)
|
||||||
const $bounds = get(bounds)
|
const $bounds = get(bounds)
|
||||||
const $scroll = get(scroll)
|
|
||||||
const $stickyColumn = get(stickyColumn)
|
const $stickyColumn = get(stickyColumn)
|
||||||
ui.actions.blur()
|
ui.actions.blur()
|
||||||
|
|
||||||
|
@ -51,9 +62,8 @@ export const deriveStores = context => {
|
||||||
sourceColumn: column,
|
sourceColumn: column,
|
||||||
targetColumn: null,
|
targetColumn: null,
|
||||||
breakpoints,
|
breakpoints,
|
||||||
initialMouseX: e.clientX,
|
|
||||||
scrollLeft: $scroll.left,
|
|
||||||
gridLeft: $bounds.left,
|
gridLeft: $bounds.left,
|
||||||
|
width: $bounds.width,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add listeners to handle mouse movement
|
// Add listeners to handle mouse movement
|
||||||
|
@ -66,12 +76,44 @@ export const deriveStores = context => {
|
||||||
|
|
||||||
// Callback when moving the mouse when reordering columns
|
// Callback when moving the mouse when reordering columns
|
||||||
const onReorderMouseMove = e => {
|
const onReorderMouseMove = e => {
|
||||||
|
// Immediately handle the current position
|
||||||
|
const x = e.clientX
|
||||||
|
reorder.update(state => ({
|
||||||
|
...state,
|
||||||
|
latestX: x,
|
||||||
|
}))
|
||||||
|
considerReorderPosition()
|
||||||
|
|
||||||
|
// Check if we need to start auto-scrolling
|
||||||
const $reorder = get(reorder)
|
const $reorder = get(reorder)
|
||||||
|
const proximityCutoff = 140
|
||||||
|
const speedFactor = 8
|
||||||
|
const rightProximity = Math.max(0, $reorder.gridLeft + $reorder.width - x)
|
||||||
|
const leftProximity = Math.max(0, x - $reorder.gridLeft)
|
||||||
|
if (rightProximity < proximityCutoff) {
|
||||||
|
const weight = proximityCutoff - rightProximity
|
||||||
|
const increment = (weight / proximityCutoff) * speedFactor
|
||||||
|
reorder.update(state => ({ ...state, increment }))
|
||||||
|
startAutoScroll()
|
||||||
|
} else if (leftProximity < proximityCutoff) {
|
||||||
|
const weight = -1 * (proximityCutoff - leftProximity)
|
||||||
|
const increment = (weight / proximityCutoff) * speedFactor
|
||||||
|
reorder.update(state => ({ ...state, increment }))
|
||||||
|
startAutoScroll()
|
||||||
|
} else {
|
||||||
|
stopAutoScroll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actual logic to consider the current position and determine the new order
|
||||||
|
const considerReorderPosition = () => {
|
||||||
|
const $reorder = get(reorder)
|
||||||
|
const $scroll = get(scroll)
|
||||||
|
|
||||||
// Compute the closest breakpoint to the current position
|
// Compute the closest breakpoint to the current position
|
||||||
let targetColumn
|
let targetColumn
|
||||||
let minDistance = Number.MAX_SAFE_INTEGER
|
let minDistance = Number.MAX_SAFE_INTEGER
|
||||||
const mouseX = e.clientX - $reorder.gridLeft + $reorder.scrollLeft
|
const mouseX = $reorder.latestX - $reorder.gridLeft + $scroll.left
|
||||||
$reorder.breakpoints.forEach(point => {
|
$reorder.breakpoints.forEach(point => {
|
||||||
const distance = Math.abs(point.x - mouseX)
|
const distance = Math.abs(point.x - mouseX)
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
|
@ -79,7 +121,6 @@ export const deriveStores = context => {
|
||||||
targetColumn = point.column
|
targetColumn = point.column
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (targetColumn !== $reorder.targetColumn) {
|
if (targetColumn !== $reorder.targetColumn) {
|
||||||
reorder.update(state => ({
|
reorder.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -88,8 +129,35 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commences auto-scrolling in a certain direction, triggered when the mouse
|
||||||
|
// approaches the edges of the grid
|
||||||
|
const startAutoScroll = () => {
|
||||||
|
if (isAutoScrolling) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isAutoScrolling = true
|
||||||
|
autoScrollInterval = setInterval(() => {
|
||||||
|
const $maxLeft = get(maxScrollLeft)
|
||||||
|
const { increment } = get(reorder)
|
||||||
|
scroll.update(state => ({
|
||||||
|
...state,
|
||||||
|
left: Math.max(0, Math.min($maxLeft, state.left + increment)),
|
||||||
|
}))
|
||||||
|
considerReorderPosition()
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stops auto scrolling
|
||||||
|
const stopAutoScroll = () => {
|
||||||
|
isAutoScrolling = false
|
||||||
|
clearInterval(autoScrollInterval)
|
||||||
|
}
|
||||||
|
|
||||||
// Callback when stopping reordering columns
|
// Callback when stopping reordering columns
|
||||||
const stopReordering = async () => {
|
const stopReordering = async () => {
|
||||||
|
// Ensure auto-scrolling is stopped
|
||||||
|
stopAutoScroll()
|
||||||
|
|
||||||
// Swap position of columns
|
// Swap position of columns
|
||||||
let { sourceColumn, targetColumn } = get(reorder)
|
let { sourceColumn, targetColumn } = get(reorder)
|
||||||
moveColumn(sourceColumn, targetColumn)
|
moveColumn(sourceColumn, targetColumn)
|
||||||
|
|
|
@ -25,14 +25,33 @@ export const createStores = () => {
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Toggles whether a certain row ID is selected or not
|
||||||
|
const toggleSelectedRow = id => {
|
||||||
|
selectedRows.update(state => {
|
||||||
|
let newState = {
|
||||||
|
...state,
|
||||||
|
[id]: !state[id],
|
||||||
|
}
|
||||||
|
if (!newState[id]) {
|
||||||
|
delete newState[id]
|
||||||
|
}
|
||||||
|
return newState
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
focusedCellId,
|
focusedCellId,
|
||||||
focusedCellAPI,
|
focusedCellAPI,
|
||||||
focusedRowId,
|
focusedRowId,
|
||||||
previousFocusedRowId,
|
previousFocusedRowId,
|
||||||
selectedRows,
|
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
|
selectedRows: {
|
||||||
|
...selectedRows,
|
||||||
|
actions: {
|
||||||
|
toggleRow: toggleSelectedRow,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ export const Features = {
|
||||||
ENFORCEABLE_SSO: "enforceableSSO",
|
ENFORCEABLE_SSO: "enforceableSSO",
|
||||||
BRANDING: "branding",
|
BRANDING: "branding",
|
||||||
SCIM: "scim",
|
SCIM: "scim",
|
||||||
|
SYNC_AUTOMATIONS: "syncAutomations",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Role IDs
|
// Role IDs
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit aea8a4acb0bae6a1036520bf4c6d8cae428cc7d9
|
Subproject commit 3d307df17a53ba25bbf5d9ddc94b1706c813eb6f
|
|
@ -11,6 +11,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "rimraf dist/",
|
"prebuild": "rimraf dist/",
|
||||||
"build": "node ./scripts/build.js",
|
"build": "node ./scripts/build.js",
|
||||||
|
"check:types": "tsc -p tsconfig.build.json --noEmit",
|
||||||
"postbuild": "copyfiles -f ../client/dist/budibase-client.js ../client/manifest.json client",
|
"postbuild": "copyfiles -f ../client/dist/budibase-client.js ../client/manifest.json client",
|
||||||
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
||||||
"debug": "yarn build && node --expose-gc --inspect=9222 dist/index.js",
|
"debug": "yarn build && node --expose-gc --inspect=9222 dist/index.js",
|
||||||
|
@ -47,7 +48,7 @@
|
||||||
"@apidevtools/swagger-parser": "10.0.3",
|
"@apidevtools/swagger-parser": "10.0.3",
|
||||||
"@budibase/backend-core": "0.0.1",
|
"@budibase/backend-core": "0.0.1",
|
||||||
"@budibase/client": "0.0.1",
|
"@budibase/client": "0.0.1",
|
||||||
"@budibase/pro": "develop",
|
"@budibase/pro": "0.0.1",
|
||||||
"@budibase/shared-core": "0.0.1",
|
"@budibase/shared-core": "0.0.1",
|
||||||
"@budibase/string-templates": "0.0.1",
|
"@budibase/string-templates": "0.0.1",
|
||||||
"@budibase/types": "0.0.1",
|
"@budibase/types": "0.0.1",
|
||||||
|
|
|
@ -14,9 +14,16 @@ import { deleteEntityMetadata } from "../../utilities"
|
||||||
import { MetadataTypes } from "../../constants"
|
import { MetadataTypes } from "../../constants"
|
||||||
import { setTestFlag, clearTestFlag } from "../../utilities/redis"
|
import { setTestFlag, clearTestFlag } from "../../utilities/redis"
|
||||||
import { context, cache, events } from "@budibase/backend-core"
|
import { context, cache, events } from "@budibase/backend-core"
|
||||||
import { automations } from "@budibase/pro"
|
import { automations, features } from "@budibase/pro"
|
||||||
import { Automation, BBContext } from "@budibase/types"
|
import {
|
||||||
|
Automation,
|
||||||
|
AutomationActionStepId,
|
||||||
|
AutomationResults,
|
||||||
|
BBContext,
|
||||||
|
} from "@budibase/types"
|
||||||
import { getActionDefinitions as actionDefs } from "../../automations/actions"
|
import { getActionDefinitions as actionDefs } from "../../automations/actions"
|
||||||
|
import sdk from "../../sdk"
|
||||||
|
import { db as dbCore } from "@budibase/backend-core"
|
||||||
|
|
||||||
async function getActionDefinitions() {
|
async function getActionDefinitions() {
|
||||||
return removeDeprecated(await actionDefs())
|
return removeDeprecated(await actionDefs())
|
||||||
|
@ -257,6 +264,26 @@ export async function getDefinitionList(ctx: BBContext) {
|
||||||
export async function trigger(ctx: BBContext) {
|
export async function trigger(ctx: BBContext) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let automation = await db.get(ctx.params.id)
|
let automation = await db.get(ctx.params.id)
|
||||||
|
|
||||||
|
let hasCollectStep = sdk.automations.utils.checkForCollectStep(automation)
|
||||||
|
if (hasCollectStep && (await features.isSyncAutomationsEnabled())) {
|
||||||
|
const response: AutomationResults = await triggers.externalTrigger(
|
||||||
|
automation,
|
||||||
|
{
|
||||||
|
fields: ctx.request.body.fields,
|
||||||
|
timeout: ctx.request.body.timeout * 1000 || 120000,
|
||||||
|
},
|
||||||
|
{ getResponses: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
let collectedValue = response.steps.find(
|
||||||
|
step => step.stepId === AutomationActionStepId.COLLECT
|
||||||
|
)
|
||||||
|
ctx.body = collectedValue?.outputs
|
||||||
|
} else {
|
||||||
|
if (ctx.appId && !dbCore.isProdAppID(ctx.appId)) {
|
||||||
|
ctx.throw(400, "Only apps in production support this endpoint")
|
||||||
|
}
|
||||||
await triggers.externalTrigger(automation, {
|
await triggers.externalTrigger(automation, {
|
||||||
...ctx.request.body,
|
...ctx.request.body,
|
||||||
appId: ctx.appId,
|
appId: ctx.appId,
|
||||||
|
@ -265,6 +292,7 @@ export async function trigger(ctx: BBContext) {
|
||||||
message: `Automation ${automation._id} has been triggered.`,
|
message: `Automation ${automation._id} has been triggered.`,
|
||||||
automation,
|
automation,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareTestInput(input: any) {
|
function prepareTestInput(input: any) {
|
||||||
|
|
|
@ -6,8 +6,11 @@ import {
|
||||||
WebhookActionType,
|
WebhookActionType,
|
||||||
BBContext,
|
BBContext,
|
||||||
Automation,
|
Automation,
|
||||||
|
AutomationActionStepId,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
import * as pro from "@budibase/pro"
|
||||||
|
|
||||||
const toJsonSchema = require("to-json-schema")
|
const toJsonSchema = require("to-json-schema")
|
||||||
const validate = require("jsonschema").validate
|
const validate = require("jsonschema").validate
|
||||||
|
|
||||||
|
@ -78,16 +81,37 @@ export async function trigger(ctx: BBContext) {
|
||||||
if (webhook.action.type === WebhookActionType.AUTOMATION) {
|
if (webhook.action.type === WebhookActionType.AUTOMATION) {
|
||||||
// trigger with both the pure request and then expand it
|
// trigger with both the pure request and then expand it
|
||||||
// incase the user has produced a schema to bind to
|
// incase the user has produced a schema to bind to
|
||||||
|
let hasCollectStep = sdk.automations.utils.checkForCollectStep(target)
|
||||||
|
|
||||||
|
if (hasCollectStep && (await pro.features.isSyncAutomationsEnabled())) {
|
||||||
|
const response = await triggers.externalTrigger(
|
||||||
|
target,
|
||||||
|
{
|
||||||
|
body: ctx.request.body,
|
||||||
|
...ctx.request.body,
|
||||||
|
appId: prodAppId,
|
||||||
|
},
|
||||||
|
{ getResponses: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
let collectedValue = response.steps.find(
|
||||||
|
(step: any) => step.stepId === AutomationActionStepId.COLLECT
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.status = 200
|
||||||
|
ctx.body = collectedValue.outputs
|
||||||
|
} else {
|
||||||
await triggers.externalTrigger(target, {
|
await triggers.externalTrigger(target, {
|
||||||
body: ctx.request.body,
|
body: ctx.request.body,
|
||||||
...ctx.request.body,
|
...ctx.request.body,
|
||||||
appId: prodAppId,
|
appId: prodAppId,
|
||||||
})
|
})
|
||||||
}
|
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Webhook trigger fired successfully",
|
message: "Webhook trigger fired successfully",
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.status === 404) {
|
if (err.status === 404) {
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
|
|
|
@ -65,7 +65,6 @@ router
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/automations/:id/trigger",
|
"/api/automations/:id/trigger",
|
||||||
appInfoMiddleware({ appType: AppType.PROD }),
|
|
||||||
paramResource("id"),
|
paramResource("id"),
|
||||||
authorized(
|
authorized(
|
||||||
permissions.PermissionType.AUTOMATION,
|
permissions.PermissionType.AUTOMATION,
|
||||||
|
|
|
@ -1,15 +1,27 @@
|
||||||
const {
|
import {
|
||||||
checkBuilderEndpoint,
|
checkBuilderEndpoint,
|
||||||
getAllTableRows,
|
getAllTableRows,
|
||||||
clearAllAutomations,
|
clearAllAutomations,
|
||||||
testAutomation,
|
testAutomation,
|
||||||
} = require("./utilities/TestFunctions")
|
} from "./utilities/TestFunctions"
|
||||||
const setup = require("./utilities")
|
import * as setup from "./utilities"
|
||||||
const { basicAutomation, newAutomation, automationTrigger, automationStep } = setup.structures
|
import {
|
||||||
const MAX_RETRIES = 4
|
TRIGGER_DEFINITIONS,
|
||||||
const { TRIGGER_DEFINITIONS, BUILTIN_ACTION_DEFINITIONS } = require("../../../automations")
|
BUILTIN_ACTION_DEFINITIONS,
|
||||||
const { events } = require("@budibase/backend-core")
|
} from "../../../automations"
|
||||||
|
import { events } from "@budibase/backend-core"
|
||||||
|
import sdk from "../../../sdk"
|
||||||
|
import { Automation } from "@budibase/types"
|
||||||
|
import { mocks } from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
|
const MAX_RETRIES = 4
|
||||||
|
let {
|
||||||
|
basicAutomation,
|
||||||
|
newAutomation,
|
||||||
|
automationTrigger,
|
||||||
|
automationStep,
|
||||||
|
collectAutomation,
|
||||||
|
} = setup.structures
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
@ -24,6 +36,7 @@ describe("/automations", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
// @ts-ignore
|
||||||
events.automation.deleted.mockClear()
|
events.automation.deleted.mockClear()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -32,7 +45,7 @@ describe("/automations", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/automations/action/list`)
|
.get(`/api/automations/action/list`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(Object.keys(res.body).length).not.toEqual(0)
|
expect(Object.keys(res.body).length).not.toEqual(0)
|
||||||
|
@ -42,7 +55,7 @@ describe("/automations", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/automations/trigger/list`)
|
.get(`/api/automations/trigger/list`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(Object.keys(res.body).length).not.toEqual(0)
|
expect(Object.keys(res.body).length).not.toEqual(0)
|
||||||
|
@ -52,14 +65,18 @@ describe("/automations", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/automations/definitions/list`)
|
.get(`/api/automations/definitions/list`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
let definitionsLength = Object.keys(BUILTIN_ACTION_DEFINITIONS).length
|
let definitionsLength = Object.keys(BUILTIN_ACTION_DEFINITIONS).length
|
||||||
definitionsLength-- // OUTGOING_WEBHOOK is deprecated
|
definitionsLength-- // OUTGOING_WEBHOOK is deprecated
|
||||||
|
|
||||||
expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(definitionsLength)
|
expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(
|
||||||
expect(Object.keys(res.body.trigger).length).toEqual(Object.keys(TRIGGER_DEFINITIONS).length)
|
definitionsLength
|
||||||
|
)
|
||||||
|
expect(Object.keys(res.body.trigger).length).toEqual(
|
||||||
|
Object.keys(TRIGGER_DEFINITIONS).length
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -72,7 +89,7 @@ describe("/automations", () => {
|
||||||
.post(`/api/automations`)
|
.post(`/api/automations`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.send(automation)
|
.send(automation)
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.message).toEqual("Automation created successfully")
|
expect(res.body.message).toEqual("Automation created successfully")
|
||||||
|
@ -91,7 +108,7 @@ describe("/automations", () => {
|
||||||
.post(`/api/automations`)
|
.post(`/api/automations`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.send(automation)
|
.send(automation)
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.message).toEqual("Automation created successfully")
|
expect(res.body.message).toEqual("Automation created successfully")
|
||||||
|
@ -107,7 +124,7 @@ describe("/automations", () => {
|
||||||
config,
|
config,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `/api/automations`,
|
url: `/api/automations`,
|
||||||
body: automation
|
body: automation,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -118,7 +135,7 @@ describe("/automations", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/automations/${automation._id}`)
|
.get(`/api/automations/${automation._id}`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body._id).toEqual(automation._id)
|
expect(res.body._id).toEqual(automation._id)
|
||||||
expect(res.body._rev).toEqual(automation._rev)
|
expect(res.body._rev).toEqual(automation._rev)
|
||||||
|
@ -134,8 +151,8 @@ describe("/automations", () => {
|
||||||
row: {
|
row: {
|
||||||
name: "{{trigger.row.name}}",
|
name: "{{trigger.row.name}}",
|
||||||
description: "{{trigger.row.description}}",
|
description: "{{trigger.row.description}}",
|
||||||
tableId: table._id
|
tableId: table._id,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
automation.appId = config.appId
|
automation.appId = config.appId
|
||||||
automation = await config.createAutomation(automation)
|
automation = await config.createAutomation(automation)
|
||||||
|
@ -162,23 +179,68 @@ describe("/automations", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("update", () => {
|
describe("trigger", () => {
|
||||||
|
it("does not trigger an automation when not synchronous and in dev", async () => {
|
||||||
|
let automation = newAutomation()
|
||||||
|
automation = await config.createAutomation(automation)
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/automations/${automation._id}/trigger`)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
const update = async (automation) => {
|
expect(res.body.message).toEqual(
|
||||||
|
"Only apps in production support this endpoint"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("triggers a synchronous automation", async () => {
|
||||||
|
mocks.licenses.useSyncAutomations()
|
||||||
|
let automation = collectAutomation()
|
||||||
|
automation = await config.createAutomation(automation)
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/automations/${automation._id}/trigger`)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body.success).toEqual(true)
|
||||||
|
expect(res.body.value).toEqual([1, 2, 3])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("triggers an asynchronous automation", async () => {
|
||||||
|
let automation = newAutomation()
|
||||||
|
automation = await config.createAutomation(automation)
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/automations/${automation._id}/trigger`)
|
||||||
|
.set(config.defaultHeaders({}, true))
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body.message).toEqual(
|
||||||
|
`Automation ${automation._id} has been triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
const update = async (automation: Automation) => {
|
||||||
return request
|
return request
|
||||||
.put(`/api/automations`)
|
.put(`/api/automations`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.send(automation)
|
.send(automation)
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateWithPost = async (automation) => {
|
const updateWithPost = async (automation: Automation) => {
|
||||||
return request
|
return request
|
||||||
.post(`/api/automations`)
|
.post(`/api/automations`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.send(automation)
|
.send(automation)
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +261,9 @@ describe("/automations", () => {
|
||||||
expect(automationRes._rev).not.toEqual(automation._rev)
|
expect(automationRes._rev).not.toEqual(automation._rev)
|
||||||
// content updates
|
// content updates
|
||||||
expect(automationRes.name).toEqual("Updated Name")
|
expect(automationRes.name).toEqual("Updated Name")
|
||||||
expect(message).toEqual(`Automation ${automation._id} updated successfully.`)
|
expect(message).toEqual(
|
||||||
|
`Automation ${automation._id} updated successfully.`
|
||||||
|
)
|
||||||
// events
|
// events
|
||||||
expect(events.automation.created).not.toBeCalled()
|
expect(events.automation.created).not.toBeCalled()
|
||||||
expect(events.automation.stepCreated).not.toBeCalled()
|
expect(events.automation.stepCreated).not.toBeCalled()
|
||||||
|
@ -207,7 +271,6 @@ describe("/automations", () => {
|
||||||
expect(events.automation.triggerUpdated).not.toBeCalled()
|
expect(events.automation.triggerUpdated).not.toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
it("updates a automations name using POST request", async () => {
|
it("updates a automations name using POST request", async () => {
|
||||||
let automation = newAutomation()
|
let automation = newAutomation()
|
||||||
await config.createAutomation(automation)
|
await config.createAutomation(automation)
|
||||||
|
@ -226,7 +289,9 @@ describe("/automations", () => {
|
||||||
expect(automationRes._rev).not.toEqual(automation._rev)
|
expect(automationRes._rev).not.toEqual(automation._rev)
|
||||||
// content updates
|
// content updates
|
||||||
expect(automationRes.name).toEqual("Updated Name")
|
expect(automationRes.name).toEqual("Updated Name")
|
||||||
expect(message).toEqual(`Automation ${automation._id} updated successfully.`)
|
expect(message).toEqual(
|
||||||
|
`Automation ${automation._id} updated successfully.`
|
||||||
|
)
|
||||||
// events
|
// events
|
||||||
expect(events.automation.created).not.toBeCalled()
|
expect(events.automation.created).not.toBeCalled()
|
||||||
expect(events.automation.stepCreated).not.toBeCalled()
|
expect(events.automation.stepCreated).not.toBeCalled()
|
||||||
|
@ -237,7 +302,9 @@ describe("/automations", () => {
|
||||||
it("updates an automation trigger", async () => {
|
it("updates an automation trigger", async () => {
|
||||||
let automation = newAutomation()
|
let automation = newAutomation()
|
||||||
automation = await config.createAutomation(automation)
|
automation = await config.createAutomation(automation)
|
||||||
automation.definition.trigger = automationTrigger(TRIGGER_DEFINITIONS.WEBHOOK)
|
automation.definition.trigger = automationTrigger(
|
||||||
|
TRIGGER_DEFINITIONS.WEBHOOK
|
||||||
|
)
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|
||||||
await update(automation)
|
await update(automation)
|
||||||
|
@ -266,7 +333,6 @@ describe("/automations", () => {
|
||||||
expect(events.automation.triggerUpdated).not.toBeCalled()
|
expect(events.automation.triggerUpdated).not.toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
it("removes automation steps", async () => {
|
it("removes automation steps", async () => {
|
||||||
let automation = newAutomation()
|
let automation = newAutomation()
|
||||||
automation.definition.steps.push(automationStep())
|
automation.definition.steps.push(automationStep())
|
||||||
|
@ -305,11 +371,11 @@ describe("/automations", () => {
|
||||||
it("return all the automations for an instance", async () => {
|
it("return all the automations for an instance", async () => {
|
||||||
await clearAllAutomations(config)
|
await clearAllAutomations(config)
|
||||||
const autoConfig = basicAutomation()
|
const autoConfig = basicAutomation()
|
||||||
automation = await config.createAutomation(autoConfig)
|
await config.createAutomation(autoConfig)
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/automations`)
|
.get(`/api/automations`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body[0]).toEqual(expect.objectContaining(autoConfig))
|
expect(res.body[0]).toEqual(expect.objectContaining(autoConfig))
|
||||||
|
@ -330,7 +396,7 @@ describe("/automations", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.delete(`/api/automations/${automation.id}/${automation.rev}`)
|
.delete(`/api/automations/${automation.id}/${automation.rev}`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect('Content-Type', /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.id).toEqual(automation._id)
|
expect(res.body.id).toEqual(automation._id)
|
||||||
|
@ -346,4 +412,13 @@ describe("/automations", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("checkForCollectStep", () => {
|
||||||
|
it("should return true if a collect step exists in an automation", async () => {
|
||||||
|
let automation = collectAutomation()
|
||||||
|
await config.createAutomation(automation)
|
||||||
|
let res = await sdk.automations.utils.checkForCollectStep(automation)
|
||||||
|
expect(res).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
|
@ -1,11 +1,13 @@
|
||||||
const setup = require("./utilities")
|
import { Webhook } from "@budibase/types"
|
||||||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
import * as setup from "./utilities"
|
||||||
const { basicWebhook, basicAutomation } = setup.structures
|
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
|
||||||
|
import { mocks } from "@budibase/backend-core/tests"
|
||||||
|
const { basicWebhook, basicAutomation, collectAutomation } = setup.structures
|
||||||
|
|
||||||
describe("/webhooks", () => {
|
describe("/webhooks", () => {
|
||||||
let request = setup.getRequest()
|
let request = setup.getRequest()
|
||||||
let config = setup.getConfig()
|
let config = setup.getConfig()
|
||||||
let webhook
|
let webhook: Webhook
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
|
@ -13,10 +15,11 @@ describe("/webhooks", () => {
|
||||||
config.modeSelf()
|
config.modeSelf()
|
||||||
await config.init()
|
await config.init()
|
||||||
const autoConfig = basicAutomation()
|
const autoConfig = basicAutomation()
|
||||||
autoConfig.definition.trigger = {
|
autoConfig.definition.trigger.schema = {
|
||||||
schema: { outputs: { properties: {} } },
|
outputs: { properties: {} },
|
||||||
inputs: {},
|
inputs: { properties: {} },
|
||||||
}
|
}
|
||||||
|
autoConfig.definition.trigger.inputs = {}
|
||||||
await config.createAutomation(autoConfig)
|
await config.createAutomation(autoConfig)
|
||||||
webhook = await config.createWebhook()
|
webhook = await config.createWebhook()
|
||||||
}
|
}
|
||||||
|
@ -97,7 +100,7 @@ describe("/webhooks", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/webhooks/schema/${config.getAppId()}/${webhook._id}`)
|
.post(`/api/webhooks/schema/${config.getAppId()}/${webhook._id}`)
|
||||||
.send({
|
.send({
|
||||||
a: 1
|
a: 1,
|
||||||
})
|
})
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
|
@ -112,7 +115,7 @@ describe("/webhooks", () => {
|
||||||
expect(fetch.body[0]).toBeDefined()
|
expect(fetch.body[0]).toBeDefined()
|
||||||
expect(fetch.body[0].bodySchema).toEqual({
|
expect(fetch.body[0].bodySchema).toEqual({
|
||||||
properties: {
|
properties: {
|
||||||
a: { type: "integer" }
|
a: { type: "integer" },
|
||||||
},
|
},
|
||||||
type: "object",
|
type: "object",
|
||||||
})
|
})
|
||||||
|
@ -131,4 +134,23 @@ describe("/webhooks", () => {
|
||||||
expect(res.body.message).toBeDefined()
|
expect(res.body.message).toBeDefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should trigger a synchronous webhook call ", async () => {
|
||||||
|
mocks.licenses.useSyncAutomations()
|
||||||
|
let automation = collectAutomation()
|
||||||
|
let newAutomation = await config.createAutomation(automation)
|
||||||
|
let syncWebhook = await config.createWebhook(
|
||||||
|
basicWebhook(newAutomation._id)
|
||||||
|
)
|
||||||
|
|
||||||
|
// replicate changes before checking webhook
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/webhooks/trigger/${config.prodAppId}/${syncWebhook._id}`)
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
expect(res.body.success).toEqual(true)
|
||||||
|
expect(res.body.value).toEqual([1, 2, 3])
|
||||||
|
})
|
||||||
})
|
})
|
|
@ -14,6 +14,7 @@ import * as filter from "./steps/filter"
|
||||||
import * as delay from "./steps/delay"
|
import * as delay from "./steps/delay"
|
||||||
import * as queryRow from "./steps/queryRows"
|
import * as queryRow from "./steps/queryRows"
|
||||||
import * as loop from "./steps/loop"
|
import * as loop from "./steps/loop"
|
||||||
|
import * as collect from "./steps/collect"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import {
|
import {
|
||||||
AutomationStepSchema,
|
AutomationStepSchema,
|
||||||
|
@ -39,6 +40,7 @@ const ACTION_IMPLS: Record<
|
||||||
DELAY: delay.run,
|
DELAY: delay.run,
|
||||||
FILTER: filter.run,
|
FILTER: filter.run,
|
||||||
QUERY_ROWS: queryRow.run,
|
QUERY_ROWS: queryRow.run,
|
||||||
|
COLLECT: collect.run,
|
||||||
// these used to be lowercase step IDs, maintain for backwards compat
|
// these used to be lowercase step IDs, maintain for backwards compat
|
||||||
discord: discord.run,
|
discord: discord.run,
|
||||||
slack: slack.run,
|
slack: slack.run,
|
||||||
|
@ -59,6 +61,7 @@ export const BUILTIN_ACTION_DEFINITIONS: Record<string, AutomationStepSchema> =
|
||||||
FILTER: filter.definition,
|
FILTER: filter.definition,
|
||||||
QUERY_ROWS: queryRow.definition,
|
QUERY_ROWS: queryRow.definition,
|
||||||
LOOP: loop.definition,
|
LOOP: loop.definition,
|
||||||
|
COLLECT: collect.definition,
|
||||||
// these used to be lowercase step IDs, maintain for backwards compat
|
// these used to be lowercase step IDs, maintain for backwards compat
|
||||||
discord: discord.definition,
|
discord: discord.definition,
|
||||||
slack: slack.definition,
|
slack: slack.definition,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import environment from "../../environment"
|
||||||
import {
|
import {
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationCustomIOType,
|
AutomationCustomIOType,
|
||||||
|
AutomationFeature,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepSchema,
|
AutomationStepSchema,
|
||||||
|
@ -18,6 +19,9 @@ export const definition: AutomationStepSchema = {
|
||||||
description: "Run a bash script",
|
description: "Run a bash script",
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
stepId: AutomationActionStepId.EXECUTE_BASH,
|
stepId: AutomationActionStepId.EXECUTE_BASH,
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {
|
||||||
|
AutomationActionStepId,
|
||||||
|
AutomationStepSchema,
|
||||||
|
AutomationStepInput,
|
||||||
|
AutomationStepType,
|
||||||
|
AutomationIOType,
|
||||||
|
AutomationFeature,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
export const definition: AutomationStepSchema = {
|
||||||
|
name: "Collect Data",
|
||||||
|
tagline: "Collect data to be sent to design",
|
||||||
|
icon: "Collection",
|
||||||
|
description:
|
||||||
|
"Collects specified data so it can be provided to the design section",
|
||||||
|
type: AutomationStepType.ACTION,
|
||||||
|
internal: true,
|
||||||
|
features: {},
|
||||||
|
stepId: AutomationActionStepId.COLLECT,
|
||||||
|
inputs: {},
|
||||||
|
schema: {
|
||||||
|
inputs: {
|
||||||
|
properties: {
|
||||||
|
collection: {
|
||||||
|
type: AutomationIOType.STRING,
|
||||||
|
title: "What to Collect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["collection"],
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
properties: {
|
||||||
|
success: {
|
||||||
|
type: AutomationIOType.BOOLEAN,
|
||||||
|
description: "Whether the action was successful",
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: AutomationIOType.STRING,
|
||||||
|
description: "Collected data",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["success", "value"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function run({ inputs }: AutomationStepInput) {
|
||||||
|
if (!inputs.collection) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: inputs.collection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { buildCtx } from "./utils"
|
||||||
import {
|
import {
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationCustomIOType,
|
AutomationCustomIOType,
|
||||||
|
AutomationFeature,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepSchema,
|
AutomationStepSchema,
|
||||||
|
@ -17,6 +18,9 @@ export const definition: AutomationStepSchema = {
|
||||||
description: "Add a row to your database",
|
description: "Add a row to your database",
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
stepId: AutomationActionStepId.CREATE_ROW,
|
stepId: AutomationActionStepId.CREATE_ROW,
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
|
|
|
@ -14,6 +14,7 @@ export const definition: AutomationStepSchema = {
|
||||||
description: "Delay the automation until an amount of time has passed",
|
description: "Delay the automation until an amount of time has passed",
|
||||||
stepId: AutomationActionStepId.DELAY,
|
stepId: AutomationActionStepId.DELAY,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {},
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
AutomationStepType,
|
AutomationStepType,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
AutomationCustomIOType,
|
AutomationCustomIOType,
|
||||||
|
AutomationFeature,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
export const definition: AutomationStepSchema = {
|
export const definition: AutomationStepSchema = {
|
||||||
|
@ -18,6 +19,9 @@ export const definition: AutomationStepSchema = {
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
stepId: AutomationActionStepId.DELETE_ROW,
|
stepId: AutomationActionStepId.DELETE_ROW,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepType,
|
AutomationStepType,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
|
AutomationFeature,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
const DEFAULT_USERNAME = "Budibase Automate"
|
const DEFAULT_USERNAME = "Budibase Automate"
|
||||||
|
@ -19,6 +20,9 @@ export const definition: AutomationStepSchema = {
|
||||||
stepId: AutomationActionStepId.discord,
|
stepId: AutomationActionStepId.discord,
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: false,
|
internal: false,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as automationUtils from "../automationUtils"
|
||||||
import {
|
import {
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationCustomIOType,
|
AutomationCustomIOType,
|
||||||
|
AutomationFeature,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepSchema,
|
AutomationStepSchema,
|
||||||
|
@ -18,6 +19,9 @@ export const definition: AutomationStepSchema = {
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
stepId: AutomationActionStepId.EXECUTE_QUERY,
|
stepId: AutomationActionStepId.EXECUTE_QUERY,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as automationUtils from "../automationUtils"
|
||||||
import {
|
import {
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationCustomIOType,
|
AutomationCustomIOType,
|
||||||
|
AutomationFeature,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepSchema,
|
AutomationStepSchema,
|
||||||
|
@ -19,6 +20,9 @@ export const definition: AutomationStepSchema = {
|
||||||
internal: true,
|
internal: true,
|
||||||
stepId: AutomationActionStepId.EXECUTE_SCRIPT,
|
stepId: AutomationActionStepId.EXECUTE_SCRIPT,
|
||||||
inputs: {},
|
inputs: {},
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
properties: {
|
properties: {
|
||||||
|
|
|
@ -28,6 +28,7 @@ export const definition: AutomationStepSchema = {
|
||||||
"Conditionally halt automations which do not meet certain conditions",
|
"Conditionally halt automations which do not meet certain conditions",
|
||||||
type: AutomationStepType.LOGIC,
|
type: AutomationStepType.LOGIC,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {},
|
||||||
stepId: AutomationActionStepId.FILTER,
|
stepId: AutomationActionStepId.FILTER,
|
||||||
inputs: {
|
inputs: {
|
||||||
condition: FilterConditions.EQUAL,
|
condition: FilterConditions.EQUAL,
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const definition: AutomationStepSchema = {
|
||||||
description: "Loop",
|
description: "Loop",
|
||||||
stepId: AutomationActionStepId.LOOP,
|
stepId: AutomationActionStepId.LOOP,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {},
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepType,
|
AutomationStepType,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
|
AutomationFeature,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
export const definition: AutomationStepSchema = {
|
export const definition: AutomationStepSchema = {
|
||||||
|
@ -18,6 +19,9 @@ export const definition: AutomationStepSchema = {
|
||||||
stepId: AutomationActionStepId.integromat,
|
stepId: AutomationActionStepId.integromat,
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: false,
|
internal: false,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -22,6 +22,7 @@ export const definition: AutomationStepSchema = {
|
||||||
description: "Interact with the OpenAI ChatGPT API.",
|
description: "Interact with the OpenAI ChatGPT API.",
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {},
|
||||||
stepId: AutomationActionStepId.OPENAI,
|
stepId: AutomationActionStepId.OPENAI,
|
||||||
inputs: {
|
inputs: {
|
||||||
prompt: "",
|
prompt: "",
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as automationUtils from "../automationUtils"
|
||||||
import {
|
import {
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationCustomIOType,
|
AutomationCustomIOType,
|
||||||
|
AutomationFeature,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepSchema,
|
AutomationStepSchema,
|
||||||
|
@ -32,6 +33,9 @@ export const definition: AutomationStepSchema = {
|
||||||
description: "Send a request of specified method to a URL",
|
description: "Send a request of specified method to a URL",
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
stepId: AutomationActionStepId.OUTGOING_WEBHOOK,
|
stepId: AutomationActionStepId.OUTGOING_WEBHOOK,
|
||||||
inputs: {
|
inputs: {
|
||||||
requestMethod: "POST",
|
requestMethod: "POST",
|
||||||
|
|
|
@ -6,6 +6,7 @@ import * as automationUtils from "../automationUtils"
|
||||||
import {
|
import {
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationCustomIOType,
|
AutomationCustomIOType,
|
||||||
|
AutomationFeature,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepSchema,
|
AutomationStepSchema,
|
||||||
|
@ -42,6 +43,9 @@ export const definition: AutomationStepSchema = {
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
stepId: AutomationActionStepId.QUERY_ROWS,
|
stepId: AutomationActionStepId.QUERY_ROWS,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepType,
|
AutomationStepType,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
|
AutomationFeature,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
export const definition: AutomationStepSchema = {
|
export const definition: AutomationStepSchema = {
|
||||||
|
@ -15,6 +16,9 @@ export const definition: AutomationStepSchema = {
|
||||||
name: "Send Email (SMTP)",
|
name: "Send Email (SMTP)",
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
stepId: AutomationActionStepId.SEND_EMAIL_SMTP,
|
stepId: AutomationActionStepId.SEND_EMAIL_SMTP,
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepType,
|
AutomationStepType,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
|
AutomationFeature,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,6 +20,9 @@ export const definition: AutomationStepSchema = {
|
||||||
description: "Logs the given text to the server (using console.log)",
|
description: "Logs the given text to the server (using console.log)",
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
stepId: AutomationActionStepId.SERVER_LOG,
|
stepId: AutomationActionStepId.SERVER_LOG,
|
||||||
inputs: {
|
inputs: {
|
||||||
text: "",
|
text: "",
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepType,
|
AutomationStepType,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
|
AutomationFeature,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
export const definition: AutomationStepSchema = {
|
export const definition: AutomationStepSchema = {
|
||||||
|
@ -16,6 +17,9 @@ export const definition: AutomationStepSchema = {
|
||||||
stepId: AutomationActionStepId.slack,
|
stepId: AutomationActionStepId.slack,
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: false,
|
internal: false,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { buildCtx } from "./utils"
|
||||||
import {
|
import {
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationCustomIOType,
|
AutomationCustomIOType,
|
||||||
|
AutomationFeature,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepSchema,
|
AutomationStepSchema,
|
||||||
|
@ -17,6 +18,9 @@ export const definition: AutomationStepSchema = {
|
||||||
description: "Update a row in your database",
|
description: "Update a row in your database",
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: true,
|
internal: true,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
stepId: AutomationActionStepId.UPDATE_ROW,
|
stepId: AutomationActionStepId.UPDATE_ROW,
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
AutomationStepInput,
|
AutomationStepInput,
|
||||||
AutomationStepType,
|
AutomationStepType,
|
||||||
AutomationIOType,
|
AutomationIOType,
|
||||||
|
AutomationFeature,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
export const definition: AutomationStepSchema = {
|
export const definition: AutomationStepSchema = {
|
||||||
|
@ -13,6 +14,9 @@ export const definition: AutomationStepSchema = {
|
||||||
stepId: AutomationActionStepId.zapier,
|
stepId: AutomationActionStepId.zapier,
|
||||||
type: AutomationStepType.ACTION,
|
type: AutomationStepType.ACTION,
|
||||||
internal: false,
|
internal: false,
|
||||||
|
features: {
|
||||||
|
[AutomationFeature.LOOPING]: true,
|
||||||
|
},
|
||||||
description: "Trigger a Zapier Zap via webhooks",
|
description: "Trigger a Zapier Zap via webhooks",
|
||||||
tagline: "Trigger a Zapier Zap",
|
tagline: "Trigger a Zapier Zap",
|
||||||
icon: "ri-flashlight-line",
|
icon: "ri-flashlight-line",
|
||||||
|
|
|
@ -10,6 +10,7 @@ import * as utils from "./utils"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { context, db as dbCore } from "@budibase/backend-core"
|
import { context, db as dbCore } from "@budibase/backend-core"
|
||||||
import { Automation, Row, AutomationData, AutomationJob } from "@budibase/types"
|
import { Automation, Row, AutomationData, AutomationJob } from "@budibase/types"
|
||||||
|
import { executeSynchronously } from "../threads/automation"
|
||||||
|
|
||||||
export const TRIGGER_DEFINITIONS = definitions
|
export const TRIGGER_DEFINITIONS = definitions
|
||||||
const JOB_OPTS = {
|
const JOB_OPTS = {
|
||||||
|
@ -91,7 +92,7 @@ emitter.on("row:delete", async function (event) {
|
||||||
|
|
||||||
export async function externalTrigger(
|
export async function externalTrigger(
|
||||||
automation: Automation,
|
automation: Automation,
|
||||||
params: { fields: Record<string, any> },
|
params: { fields: Record<string, any>; timeout?: number },
|
||||||
{ getResponses }: { getResponses?: boolean } = {}
|
{ getResponses }: { getResponses?: boolean } = {}
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
|
@ -118,7 +119,7 @@ export async function externalTrigger(
|
||||||
automation,
|
automation,
|
||||||
}
|
}
|
||||||
const job = { data } as AutomationJob
|
const job = { data } as AutomationJob
|
||||||
return utils.processEvent(job)
|
return executeSynchronously(job)
|
||||||
} else {
|
} else {
|
||||||
return automationQueue.add(data, JOB_OPTS)
|
return automationQueue.add(data, JOB_OPTS)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,8 @@ export async function runView(
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
let fn = (doc: Document, emit: any) => emit(doc._id)
|
let fn = (doc: Document, emit: any) => emit(doc._id)
|
||||||
;(0, eval)(
|
// BUDI-7060 -> indirect eval call appears to cause issues in cloud
|
||||||
"fn = " + view?.map?.replace("function (doc)", "function (doc, emit)")
|
eval("fn = " + view?.map?.replace("function (doc)", "function (doc, emit)"))
|
||||||
)
|
|
||||||
const queryFns: any = {
|
const queryFns: any = {
|
||||||
meta: view.meta,
|
meta: view.meta,
|
||||||
map: fn,
|
map: fn,
|
||||||
|
|
|
@ -20,7 +20,9 @@ const SCHEMA: Integration = {
|
||||||
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.",
|
"Airtable is a spreadsheet-database hybrid, with the features of a database but applied to a spreadsheet.",
|
||||||
friendlyName: "Airtable",
|
friendlyName: "Airtable",
|
||||||
type: "Spreadsheet",
|
type: "Spreadsheet",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
apiKey: {
|
apiKey: {
|
||||||
type: DatasourceFieldType.PASSWORD,
|
type: DatasourceFieldType.PASSWORD,
|
||||||
|
|
|
@ -23,7 +23,9 @@ const SCHEMA: Integration = {
|
||||||
type: "Non-relational",
|
type: "Non-relational",
|
||||||
description:
|
description:
|
||||||
"ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ",
|
"ArangoDB is a scalable open-source multi-model database natively supporting graph, document and search. All supported data models & access patterns can be combined in queries allowing for maximal flexibility. ",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
url: {
|
url: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -20,7 +20,9 @@ const SCHEMA: Integration = {
|
||||||
type: "Non-relational",
|
type: "Non-relational",
|
||||||
description:
|
description:
|
||||||
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
|
"Apache CouchDB is an open-source document-oriented NoSQL database, implemented in Erlang.",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
url: {
|
url: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -25,7 +25,9 @@ const SCHEMA: Integration = {
|
||||||
"Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.",
|
"Amazon DynamoDB is a key-value and document database that delivers single-digit millisecond performance at any scale.",
|
||||||
friendlyName: "DynamoDB",
|
friendlyName: "DynamoDB",
|
||||||
type: "Non-relational",
|
type: "Non-relational",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
region: {
|
region: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -22,7 +22,9 @@ const SCHEMA: Integration = {
|
||||||
"Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.",
|
"Elasticsearch is a search engine based on the Lucene library. It provides a distributed, multitenant-capable full-text search engine with an HTTP web interface and schema-free JSON documents.",
|
||||||
friendlyName: "ElasticSearch",
|
friendlyName: "ElasticSearch",
|
||||||
type: "Non-relational",
|
type: "Non-relational",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
url: {
|
url: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -20,7 +20,9 @@ const SCHEMA: Integration = {
|
||||||
type: "Non-relational",
|
type: "Non-relational",
|
||||||
description:
|
description:
|
||||||
"Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.",
|
"Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
email: {
|
email: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -66,10 +66,10 @@ const SCHEMA: Integration = {
|
||||||
"Create and collaborate on online spreadsheets in real-time and from any device.",
|
"Create and collaborate on online spreadsheets in real-time and from any device.",
|
||||||
friendlyName: "Google Sheets",
|
friendlyName: "Google Sheets",
|
||||||
type: "Spreadsheet",
|
type: "Spreadsheet",
|
||||||
features: [
|
features: {
|
||||||
DatasourceFeature.CONNECTION_CHECKING,
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
DatasourceFeature.FETCH_TABLE_NAMES,
|
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
|
||||||
],
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
spreadsheetId: {
|
spreadsheetId: {
|
||||||
display: "Google Sheet URL",
|
display: "Google Sheet URL",
|
||||||
|
|
|
@ -40,10 +40,10 @@ const SCHEMA: Integration = {
|
||||||
"Microsoft SQL Server is a relational database management system developed by Microsoft. ",
|
"Microsoft SQL Server is a relational database management system developed by Microsoft. ",
|
||||||
friendlyName: "MS SQL Server",
|
friendlyName: "MS SQL Server",
|
||||||
type: "Relational",
|
type: "Relational",
|
||||||
features: [
|
features: {
|
||||||
DatasourceFeature.CONNECTION_CHECKING,
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
DatasourceFeature.FETCH_TABLE_NAMES,
|
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
|
||||||
],
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
user: {
|
user: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -40,7 +40,9 @@ const getSchema = () => {
|
||||||
type: "Non-relational",
|
type: "Non-relational",
|
||||||
description:
|
description:
|
||||||
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
|
"MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era.",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
connectionString: {
|
connectionString: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -36,10 +36,10 @@ const SCHEMA: Integration = {
|
||||||
type: "Relational",
|
type: "Relational",
|
||||||
description:
|
description:
|
||||||
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
|
"MySQL Database Service is a fully managed database service to deploy cloud-native applications. ",
|
||||||
features: [
|
features: {
|
||||||
DatasourceFeature.CONNECTION_CHECKING,
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
DatasourceFeature.FETCH_TABLE_NAMES,
|
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
|
||||||
],
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
host: {
|
host: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -50,10 +50,10 @@ const SCHEMA: Integration = {
|
||||||
type: "Relational",
|
type: "Relational",
|
||||||
description:
|
description:
|
||||||
"Oracle Database is an object-relational database management system developed by Oracle Corporation",
|
"Oracle Database is an object-relational database management system developed by Oracle Corporation",
|
||||||
features: [
|
features: {
|
||||||
DatasourceFeature.CONNECTION_CHECKING,
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
DatasourceFeature.FETCH_TABLE_NAMES,
|
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
|
||||||
],
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
host: {
|
host: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -52,10 +52,10 @@ const SCHEMA: Integration = {
|
||||||
type: "Relational",
|
type: "Relational",
|
||||||
description:
|
description:
|
||||||
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.",
|
"PostgreSQL, also known as Postgres, is a free and open-source relational database management system emphasizing extensibility and SQL compliance.",
|
||||||
features: [
|
features: {
|
||||||
DatasourceFeature.CONNECTION_CHECKING,
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
DatasourceFeature.FETCH_TABLE_NAMES,
|
[DatasourceFeature.FETCH_TABLE_NAMES]: true,
|
||||||
],
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
host: {
|
host: {
|
||||||
type: DatasourceFieldType.STRING,
|
type: DatasourceFieldType.STRING,
|
||||||
|
|
|
@ -21,7 +21,9 @@ const SCHEMA: Integration = {
|
||||||
"Redis is a caching tool, providing powerful key-value store capabilities.",
|
"Redis is a caching tool, providing powerful key-value store capabilities.",
|
||||||
friendlyName: "Redis",
|
friendlyName: "Redis",
|
||||||
type: "Non-relational",
|
type: "Non-relational",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
host: {
|
host: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|
|
@ -24,7 +24,9 @@ const SCHEMA: Integration = {
|
||||||
"Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance.",
|
"Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance.",
|
||||||
friendlyName: "Amazon S3",
|
friendlyName: "Amazon S3",
|
||||||
type: "Object store",
|
type: "Object store",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
region: {
|
region: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|
|
@ -22,7 +22,9 @@ const SCHEMA: Integration = {
|
||||||
"Snowflake is a solution for data warehousing, data lakes, data engineering, data science, data application development, and securely sharing and consuming shared data.",
|
"Snowflake is a solution for data warehousing, data lakes, data engineering, data science, data application development, and securely sharing and consuming shared data.",
|
||||||
friendlyName: "Snowflake",
|
friendlyName: "Snowflake",
|
||||||
type: "Relational",
|
type: "Relational",
|
||||||
features: [DatasourceFeature.CONNECTION_CHECKING],
|
features: {
|
||||||
|
[DatasourceFeature.CONNECTION_CHECKING]: true,
|
||||||
|
},
|
||||||
datasource: {
|
datasource: {
|
||||||
account: {
|
account: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import * as webhook from "./webhook"
|
import * as webhook from "./webhook"
|
||||||
|
import * as utils from "./utils"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
webhook,
|
webhook,
|
||||||
|
utils,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Automation, AutomationActionStepId } from "@budibase/types"
|
||||||
|
|
||||||
|
export function checkForCollectStep(automation: Automation) {
|
||||||
|
return automation.definition.steps.some(
|
||||||
|
(step: any) => step.stepId === AutomationActionStepId.COLLECT
|
||||||
|
)
|
||||||
|
}
|
|
@ -373,7 +373,7 @@ class TestConfiguration {
|
||||||
|
|
||||||
// HEADERS
|
// HEADERS
|
||||||
|
|
||||||
defaultHeaders(extras = {}) {
|
defaultHeaders(extras = {}, prodApp = false) {
|
||||||
const tenantId = this.getTenantId()
|
const tenantId = this.getTenantId()
|
||||||
const authObj: AuthToken = {
|
const authObj: AuthToken = {
|
||||||
userId: this.defaultUserValues.globalUserId,
|
userId: this.defaultUserValues.globalUserId,
|
||||||
|
@ -390,7 +390,9 @@ class TestConfiguration {
|
||||||
...extras,
|
...extras,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.appId) {
|
if (prodApp) {
|
||||||
|
headers[constants.Header.APP_ID] = this.prodAppId
|
||||||
|
} else if (this.appId) {
|
||||||
headers[constants.Header.APP_ID] = this.appId
|
headers[constants.Header.APP_ID] = this.appId
|
||||||
}
|
}
|
||||||
return headers
|
return headers
|
||||||
|
|
|
@ -199,6 +199,48 @@ export function loopAutomation(tableId: string, loopOpts?: any): Automation {
|
||||||
return automation as Automation
|
return automation as Automation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function collectAutomation(tableId?: string): Automation {
|
||||||
|
const automation: any = {
|
||||||
|
name: "looping",
|
||||||
|
type: "automation",
|
||||||
|
definition: {
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "b",
|
||||||
|
type: "ACTION",
|
||||||
|
internal: true,
|
||||||
|
stepId: AutomationActionStepId.EXECUTE_SCRIPT,
|
||||||
|
inputs: {
|
||||||
|
code: "return [1,2,3]",
|
||||||
|
},
|
||||||
|
schema: BUILTIN_ACTION_DEFINITIONS.EXECUTE_SCRIPT.schema,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "c",
|
||||||
|
type: "ACTION",
|
||||||
|
internal: true,
|
||||||
|
stepId: AutomationActionStepId.COLLECT,
|
||||||
|
inputs: {
|
||||||
|
collection: "{{ literal steps.1.value }}",
|
||||||
|
},
|
||||||
|
schema: BUILTIN_ACTION_DEFINITIONS.SERVER_LOG.schema,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
trigger: {
|
||||||
|
id: "a",
|
||||||
|
type: "TRIGGER",
|
||||||
|
event: "row:save",
|
||||||
|
stepId: AutomationTriggerStepId.ROW_SAVED,
|
||||||
|
inputs: {
|
||||||
|
tableId,
|
||||||
|
},
|
||||||
|
schema: TRIGGER_DEFINITIONS.ROW_SAVED.schema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return automation as Automation
|
||||||
|
}
|
||||||
|
|
||||||
export function basicRow(tableId: string) {
|
export function basicRow(tableId: string) {
|
||||||
return {
|
return {
|
||||||
name: "Test Contact",
|
name: "Test Contact",
|
||||||
|
|
|
@ -68,6 +68,7 @@ class Orchestrator {
|
||||||
constructor(job: AutomationJob) {
|
constructor(job: AutomationJob) {
|
||||||
let automation = job.data.automation
|
let automation = job.data.automation
|
||||||
let triggerOutput = job.data.event
|
let triggerOutput = job.data.event
|
||||||
|
let timeout = job.data.event.timeout
|
||||||
const metadata = triggerOutput.metadata
|
const metadata = triggerOutput.metadata
|
||||||
this._chainCount = metadata ? metadata.automationChainCount! : 0
|
this._chainCount = metadata ? metadata.automationChainCount! : 0
|
||||||
this._appId = triggerOutput.appId as string
|
this._appId = triggerOutput.appId as string
|
||||||
|
@ -240,7 +241,9 @@ class Orchestrator {
|
||||||
let loopStepNumber: any = undefined
|
let loopStepNumber: any = undefined
|
||||||
let loopSteps: LoopStep[] | undefined = []
|
let loopSteps: LoopStep[] | undefined = []
|
||||||
let metadata
|
let metadata
|
||||||
|
let timeoutFlag = false
|
||||||
let wasLoopStep = false
|
let wasLoopStep = false
|
||||||
|
let timeout = this._job.data.event.timeout
|
||||||
// check if this is a recurring automation,
|
// check if this is a recurring automation,
|
||||||
if (isProdAppID(this._appId) && isRecurring(automation)) {
|
if (isProdAppID(this._appId) && isRecurring(automation)) {
|
||||||
metadata = await this.getMetadata()
|
metadata = await this.getMetadata()
|
||||||
|
@ -251,6 +254,16 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let step of automation.definition.steps) {
|
for (let step of automation.definition.steps) {
|
||||||
|
if (timeoutFlag) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
setTimeout(() => {
|
||||||
|
timeoutFlag = true
|
||||||
|
}, timeout || 12000)
|
||||||
|
}
|
||||||
|
|
||||||
stepCount++
|
stepCount++
|
||||||
let input: any,
|
let input: any,
|
||||||
iterations = 1,
|
iterations = 1,
|
||||||
|
@ -495,6 +508,32 @@ export function execute(job: Job, callback: WorkerCallback) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function executeSynchronously(job: Job) {
|
||||||
|
const appId = job.data.event.appId
|
||||||
|
if (!appId) {
|
||||||
|
throw new Error("Unable to execute, event doesn't contain app ID.")
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutPromise = new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error("Timeout exceeded"))
|
||||||
|
}, job.data.event.timeout || 12000)
|
||||||
|
})
|
||||||
|
|
||||||
|
return context.doInAppContext(appId, async () => {
|
||||||
|
const envVars = await sdkUtils.getEnvironmentVariables()
|
||||||
|
// put into automation thread for whole context
|
||||||
|
return context.doInEnvironmentContext(envVars, async () => {
|
||||||
|
const automationOrchestrator = new Orchestrator(job)
|
||||||
|
const response = await Promise.race([
|
||||||
|
automationOrchestrator.execute(),
|
||||||
|
timeoutPromise,
|
||||||
|
])
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const removeStalled = async (job: Job) => {
|
export const removeStalled = async (job: Job) => {
|
||||||
const appId = job.data.event.appId
|
const appId = job.data.event.appId
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
|
|
|
@ -57,6 +57,7 @@ export enum AutomationActionStepId {
|
||||||
FILTER = "FILTER",
|
FILTER = "FILTER",
|
||||||
QUERY_ROWS = "QUERY_ROWS",
|
QUERY_ROWS = "QUERY_ROWS",
|
||||||
LOOP = "LOOP",
|
LOOP = "LOOP",
|
||||||
|
COLLECT = "COLLECT",
|
||||||
OPENAI = "OPENAI",
|
OPENAI = "OPENAI",
|
||||||
// these used to be lowercase step IDs, maintain for backwards compat
|
// these used to be lowercase step IDs, maintain for backwards compat
|
||||||
discord = "discord",
|
discord = "discord",
|
||||||
|
@ -123,6 +124,11 @@ export interface AutomationStepSchema {
|
||||||
outputs: InputOutputBlock
|
outputs: InputOutputBlock
|
||||||
}
|
}
|
||||||
custom?: boolean
|
custom?: boolean
|
||||||
|
features?: Partial<Record<AutomationFeature, boolean>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AutomationFeature {
|
||||||
|
LOOPING = "LOOPING",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutomationStep extends AutomationStepSchema {
|
export interface AutomationStep extends AutomationStepSchema {
|
||||||
|
|
|
@ -5,6 +5,7 @@ export interface AutomationDataEvent {
|
||||||
appId?: string
|
appId?: string
|
||||||
metadata?: AutomationMetadata
|
metadata?: AutomationMetadata
|
||||||
automation?: Automation
|
automation?: Automation
|
||||||
|
timeout?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutomationData {
|
export interface AutomationData {
|
||||||
|
|
|
@ -116,7 +116,7 @@ export interface Integration {
|
||||||
docs: string
|
docs: string
|
||||||
plus?: boolean
|
plus?: boolean
|
||||||
auth?: { type: string }
|
auth?: { type: string }
|
||||||
features?: DatasourceFeature[]
|
features?: Partial<Record<DatasourceFeature, boolean>>
|
||||||
relationships?: boolean
|
relationships?: boolean
|
||||||
description: string
|
description: string
|
||||||
friendlyName: string
|
friendlyName: string
|
||||||
|
|
|
@ -8,6 +8,7 @@ export enum Feature {
|
||||||
ENFORCEABLE_SSO = "enforceableSSO",
|
ENFORCEABLE_SSO = "enforceableSSO",
|
||||||
BRANDING = "branding",
|
BRANDING = "branding",
|
||||||
SCIM = "scim",
|
SCIM = "scim",
|
||||||
|
SYNC_AUTOMATIONS = "syncAutomations",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PlanFeatures = { [key in PlanType]: Feature[] | undefined }
|
export type PlanFeatures = { [key in PlanType]: Feature[] | undefined }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
*
|
*
|
||||||
!/dist/
|
!/dist/
|
||||||
!/docker_run.sh
|
!/docker_run.sh
|
||||||
|
!/package.json
|
|
@ -12,7 +12,7 @@ RUN apk add --no-cache --virtual .gyp python3 make g++
|
||||||
RUN yarn global add pm2
|
RUN yarn global add pm2
|
||||||
|
|
||||||
|
|
||||||
COPY dist/package.json .
|
COPY package.json .
|
||||||
RUN yarn install --frozen-lockfile --production=true
|
RUN yarn install --frozen-lockfile --production=true
|
||||||
# Remove unneeded data from file system to reduce image size
|
# Remove unneeded data from file system to reduce image size
|
||||||
RUN apk del .gyp \
|
RUN apk del .gyp \
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "rimraf dist/",
|
"prebuild": "rimraf dist/",
|
||||||
"build": "cd ../.. && nx build @budibase/worker",
|
"build": "node ../../scripts/build.js",
|
||||||
|
"check:types": "tsc -p tsconfig.build.json --noEmit",
|
||||||
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
||||||
"run:docker": "node dist/index.js",
|
"run:docker": "node dist/index.js",
|
||||||
"debug": "yarn build && node --expose-gc --inspect=9223 dist/index.js",
|
"debug": "yarn build && node --expose-gc --inspect=9223 dist/index.js",
|
||||||
|
@ -38,7 +39,7 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "0.0.1",
|
"@budibase/backend-core": "0.0.1",
|
||||||
"@budibase/pro": "develop",
|
"@budibase/pro": "0.0.1",
|
||||||
"@budibase/string-templates": "0.0.1",
|
"@budibase/string-templates": "0.0.1",
|
||||||
"@budibase/types": "0.0.1",
|
"@budibase/types": "0.0.1",
|
||||||
"@koa/router": "8.0.8",
|
"@koa/router": "8.0.8",
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@budibase/worker",
|
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
||||||
"targets": {
|
|
||||||
"build": {
|
|
||||||
"executor": "@nx/esbuild:esbuild",
|
|
||||||
"outputs": ["{options.outputPath}"],
|
|
||||||
"options": {
|
|
||||||
"main": "packages/worker/src/index.ts",
|
|
||||||
"outputPath": "packages/worker/dist",
|
|
||||||
"outputFileName": "index.js",
|
|
||||||
"tsConfig": "packages/worker/tsconfig.build.json",
|
|
||||||
"assets": [
|
|
||||||
{
|
|
||||||
"glob": "**/*.hbs",
|
|
||||||
"input": "packages/worker/src/constants/templates",
|
|
||||||
"output": "."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"external": ["graphql/*", "deasync", "mock-aws-s3", "nock"],
|
|
||||||
"format": ["cjs"],
|
|
||||||
"esbuildOptions": {
|
|
||||||
"outExtension": {
|
|
||||||
".js": ".js"
|
|
||||||
},
|
|
||||||
"sourcemap": true
|
|
||||||
},
|
|
||||||
"minify": true,
|
|
||||||
"generatePackageJson": true,
|
|
||||||
"skipTypeCheck": true
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"skipTypeCheck": false,
|
|
||||||
"esbuildOptions": {
|
|
||||||
"sourcemap": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependsOn": [
|
|
||||||
{
|
|
||||||
"projects": ["@budibase/types", "@budibase/string-templates"],
|
|
||||||
"target": "build"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
const fs = require("fs")
|
||||||
|
const path = require("path")
|
||||||
|
const { execSync } = require("child_process")
|
||||||
|
|
||||||
|
// Get the list of workspaces with mismatched dependencies
|
||||||
|
const output = execSync("yarn --silent workspaces info --json", {
|
||||||
|
encoding: "utf-8",
|
||||||
|
})
|
||||||
|
const data = JSON.parse(output)
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(
|
||||||
|
data["@budibase/pro"].location,
|
||||||
|
"package.json"
|
||||||
|
)
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))
|
||||||
|
|
||||||
|
let hasChanges = false
|
||||||
|
|
||||||
|
const dependencies = data["@budibase/pro"].workspaceDependencies
|
||||||
|
dependencies.forEach(dependency => {
|
||||||
|
if (packageJson.dependencies?.[dependency]) {
|
||||||
|
packageJson.dependencies[dependency] = "0.0.1"
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
|
if (packageJson.devDependencies?.[dependency]) {
|
||||||
|
packageJson.devDependencies[dependency] = "0.0.1"
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
|
if (packageJson.peerDependencies?.[dependency]) {
|
||||||
|
packageJson.peerDependencies[dependency] = "0.0.1"
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Write changes to package.json if there are any
|
||||||
|
if (hasChanges) {
|
||||||
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n")
|
||||||
|
}
|
149
yarn.lock
149
yarn.lock
|
@ -1728,47 +1728,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.6.19-alpha.4":
|
|
||||||
version "2.6.19-alpha.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.6.19-alpha.4.tgz#525dd7ba6c5db4404cf00d1165f79d34a1093826"
|
|
||||||
integrity sha512-1pfOr+J9xYawedVmvqpQ4b/8C2SQP4cKhFmSz5uErM2SCgbRj+JuzOUTPNX0vzAXPvat/kEegt79xThummDvhA==
|
|
||||||
dependencies:
|
|
||||||
"@budibase/nano" "10.1.2"
|
|
||||||
"@budibase/pouchdb-replication-stream" "1.2.10"
|
|
||||||
"@budibase/types" "2.6.19-alpha.4"
|
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
|
||||||
"@techpass/passport-openidconnect" "0.3.2"
|
|
||||||
aws-cloudfront-sign "2.2.0"
|
|
||||||
aws-sdk "2.1030.0"
|
|
||||||
bcrypt "5.0.1"
|
|
||||||
bcryptjs "2.4.3"
|
|
||||||
bull "4.10.1"
|
|
||||||
correlation-id "4.0.0"
|
|
||||||
dotenv "16.0.1"
|
|
||||||
emitter-listener "1.1.2"
|
|
||||||
ioredis "4.28.0"
|
|
||||||
joi "17.6.0"
|
|
||||||
jsonwebtoken "9.0.0"
|
|
||||||
koa-passport "4.1.4"
|
|
||||||
koa-pino-logger "4.0.0"
|
|
||||||
lodash "4.17.21"
|
|
||||||
lodash.isarguments "3.1.0"
|
|
||||||
node-fetch "2.6.7"
|
|
||||||
passport-google-oauth "2.0.0"
|
|
||||||
passport-jwt "4.0.0"
|
|
||||||
passport-local "1.0.0"
|
|
||||||
passport-oauth2-refresh "^2.1.0"
|
|
||||||
pino "8.11.0"
|
|
||||||
pino-http "8.3.3"
|
|
||||||
posthog-node "1.3.0"
|
|
||||||
pouchdb "7.3.0"
|
|
||||||
pouchdb-find "7.2.2"
|
|
||||||
redlock "4.2.0"
|
|
||||||
sanitize-s3-objectkey "0.0.1"
|
|
||||||
semver "7.3.7"
|
|
||||||
tar-fs "2.1.1"
|
|
||||||
uuid "8.3.2"
|
|
||||||
|
|
||||||
"@budibase/bbui@^0.9.139":
|
"@budibase/bbui@^0.9.139":
|
||||||
version "0.9.190"
|
version "0.9.190"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.190.tgz#e1ec400ac90f556bfbc80fc23a04506f1585ea81"
|
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.190.tgz#e1ec400ac90f556bfbc80fc23a04506f1585ea81"
|
||||||
|
@ -1869,32 +1828,6 @@
|
||||||
pouchdb-promise "^6.0.4"
|
pouchdb-promise "^6.0.4"
|
||||||
through2 "^2.0.0"
|
through2 "^2.0.0"
|
||||||
|
|
||||||
"@budibase/pro@develop":
|
|
||||||
version "2.6.19-alpha.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.6.19-alpha.4.tgz#5d4c885ac9ac4ccfb2f8896961aca903c1178750"
|
|
||||||
integrity sha512-iu2QzV8Z77c00muBSK+NVsZdug3lLD0lR+vcKancGEz1PPE4yNIH7g8jB6i8h9agArbx9S2ICeHQqGb6nQaGHQ==
|
|
||||||
dependencies:
|
|
||||||
"@budibase/backend-core" "2.6.19-alpha.4"
|
|
||||||
"@budibase/shared-core" "2.6.19-alpha.4"
|
|
||||||
"@budibase/string-templates" "2.6.19-alpha.4"
|
|
||||||
"@budibase/types" "2.6.19-alpha.4"
|
|
||||||
"@koa/router" "8.0.8"
|
|
||||||
bull "4.10.1"
|
|
||||||
joi "17.6.0"
|
|
||||||
jsonwebtoken "8.5.1"
|
|
||||||
lru-cache "^7.14.1"
|
|
||||||
memorystream "^0.3.1"
|
|
||||||
node-fetch "^2.6.1"
|
|
||||||
scim-patch "^0.7.0"
|
|
||||||
scim2-parse-filter "^0.2.8"
|
|
||||||
|
|
||||||
"@budibase/shared-core@2.6.19-alpha.4":
|
|
||||||
version "2.6.19-alpha.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/shared-core/-/shared-core-2.6.19-alpha.4.tgz#dd22dd0a18ee4d6739b629f461e5caec90706282"
|
|
||||||
integrity sha512-ac6iWSsgz70OYbdA+QHPLpTnRbIZ4OecVc6Y7gnEZ78hZ4S5da8a+73jswuy0/t4YsiT/gjukjzjoihg3NemVg==
|
|
||||||
dependencies:
|
|
||||||
"@budibase/types" "2.6.19-alpha.4"
|
|
||||||
|
|
||||||
"@budibase/standard-components@^0.9.139":
|
"@budibase/standard-components@^0.9.139":
|
||||||
version "0.9.139"
|
version "0.9.139"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.139.tgz#cf8e2b759ae863e469e50272b3ca87f2827e66e3"
|
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.139.tgz#cf8e2b759ae863e469e50272b3ca87f2827e66e3"
|
||||||
|
@ -1913,25 +1846,6 @@
|
||||||
svelte-apexcharts "^1.0.2"
|
svelte-apexcharts "^1.0.2"
|
||||||
svelte-flatpickr "^3.1.0"
|
svelte-flatpickr "^3.1.0"
|
||||||
|
|
||||||
"@budibase/string-templates@2.6.19-alpha.4":
|
|
||||||
version "2.6.19-alpha.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-2.6.19-alpha.4.tgz#92ebd69a6841174b8af91f338c4754ca7c402707"
|
|
||||||
integrity sha512-KsH3NlQcJibRj98Q8zQ3KQHhfSIWPQfvR80MmBTIe05llEZGox4re4pQQUnlMafaUEyNNtIqVnbTJ1XP0LmFng==
|
|
||||||
dependencies:
|
|
||||||
"@budibase/handlebars-helpers" "^0.11.8"
|
|
||||||
dayjs "^1.10.4"
|
|
||||||
handlebars "^4.7.6"
|
|
||||||
handlebars-utils "^1.0.6"
|
|
||||||
lodash "^4.17.20"
|
|
||||||
vm2 "^3.9.15"
|
|
||||||
|
|
||||||
"@budibase/types@2.6.19-alpha.4":
|
|
||||||
version "2.6.19-alpha.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.6.19-alpha.4.tgz#bcf81699329d3f8509e4b0a489211f35b6cfa7ce"
|
|
||||||
integrity sha512-qFsXHZTSigcfCv02aTZGsf17vBT/MC+zK9ky7WZVX4h0sJiE0li4A66/tMaSDz3/vQ7ToPRhJK/p+LOWA/oceg==
|
|
||||||
dependencies:
|
|
||||||
scim-patch "^0.7.0"
|
|
||||||
|
|
||||||
"@bull-board/api@3.7.0":
|
"@bull-board/api@3.7.0":
|
||||||
version "3.7.0"
|
version "3.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.7.0.tgz#231f687187c0cb34e0b97f463917b6aaeb4ef6af"
|
resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.7.0.tgz#231f687187c0cb34e0b97f463917b6aaeb4ef6af"
|
||||||
|
@ -3621,6 +3535,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec@^1.4.13":
|
||||||
|
version "1.4.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||||
|
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||||
|
|
||||||
"@jridgewell/trace-mapping@0.3.9":
|
"@jridgewell/trace-mapping@0.3.9":
|
||||||
version "0.3.9"
|
version "0.3.9"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
|
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
|
||||||
|
@ -4037,13 +3956,6 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nx/devkit" "16.2.2"
|
"@nx/devkit" "16.2.2"
|
||||||
|
|
||||||
"@nrwl/esbuild@16.2.1":
|
|
||||||
version "16.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nrwl/esbuild/-/esbuild-16.2.1.tgz#83f8fbf2c7c541c220ccfff8be17a0e542aa7d01"
|
|
||||||
integrity sha512-qyNpdtPAzk2IhYmK5Qzn9gM1cvEtWFp6vJfgdbCKQngdqioYcJpGtdKhRm6Vcj8mFXF7zDgtUd2ecqJSiJU6VA==
|
|
||||||
dependencies:
|
|
||||||
"@nx/esbuild" "16.2.1"
|
|
||||||
|
|
||||||
"@nrwl/js@16.2.1":
|
"@nrwl/js@16.2.1":
|
||||||
version "16.2.1"
|
version "16.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@nrwl/js/-/js-16.2.1.tgz#6eacfa1f0658ca1e288da86b6c38be4846f2779a"
|
resolved "https://registry.yarnpkg.com/@nrwl/js/-/js-16.2.1.tgz#6eacfa1f0658ca1e288da86b6c38be4846f2779a"
|
||||||
|
@ -4096,21 +4008,6 @@
|
||||||
tmp "~0.2.1"
|
tmp "~0.2.1"
|
||||||
tslib "^2.3.0"
|
tslib "^2.3.0"
|
||||||
|
|
||||||
"@nx/esbuild@16.2.1":
|
|
||||||
version "16.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nx/esbuild/-/esbuild-16.2.1.tgz#157a408a617b5095ba1372ec27b03d6c9aa3b78c"
|
|
||||||
integrity sha512-jG4zsGc1EN+SfEBUky44+4cpj7M0Sf0v3pGWsw4hCOuBeLl6qW+eAcFypDXecDosS7ct/M2nEtlZP45IVDMZgQ==
|
|
||||||
dependencies:
|
|
||||||
"@nrwl/esbuild" "16.2.1"
|
|
||||||
"@nx/devkit" "16.2.1"
|
|
||||||
"@nx/js" "16.2.1"
|
|
||||||
chalk "^4.1.0"
|
|
||||||
dotenv "~10.0.0"
|
|
||||||
fast-glob "3.2.7"
|
|
||||||
fs-extra "^11.1.0"
|
|
||||||
tsconfig-paths "^4.1.2"
|
|
||||||
tslib "^2.3.0"
|
|
||||||
|
|
||||||
"@nx/js@16.2.1":
|
"@nx/js@16.2.1":
|
||||||
version "16.2.1"
|
version "16.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@nx/js/-/js-16.2.1.tgz#41c8c2d610131fa064bbb2b6bb25bd67ed06be79"
|
resolved "https://registry.yarnpkg.com/@nx/js/-/js-16.2.1.tgz#41c8c2d610131fa064bbb2b6bb25bd67ed06be79"
|
||||||
|
@ -4499,7 +4396,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
|
|
||||||
"@rollup/plugin-commonjs@^16.0.0":
|
"@rollup/plugin-commonjs@16.0.0", "@rollup/plugin-commonjs@^16.0.0":
|
||||||
version "16.0.0"
|
version "16.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-16.0.0.tgz#169004d56cd0f0a1d0f35915d31a036b0efe281f"
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-16.0.0.tgz#169004d56cd0f0a1d0f35915d31a036b0efe281f"
|
||||||
integrity sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw==
|
integrity sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw==
|
||||||
|
@ -4582,6 +4479,22 @@
|
||||||
"@rollup/pluginutils" "^3.1.0"
|
"@rollup/pluginutils" "^3.1.0"
|
||||||
magic-string "^0.25.7"
|
magic-string "^0.25.7"
|
||||||
|
|
||||||
|
"@rollup/plugin-replace@^5.0.2":
|
||||||
|
version "5.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz#45f53501b16311feded2485e98419acb8448c61d"
|
||||||
|
integrity sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==
|
||||||
|
dependencies:
|
||||||
|
"@rollup/pluginutils" "^5.0.1"
|
||||||
|
magic-string "^0.27.0"
|
||||||
|
|
||||||
|
"@rollup/plugin-typescript@8.3.0":
|
||||||
|
version "8.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz#bc1077fa5897b980fc27e376c4e377882c63e68b"
|
||||||
|
integrity sha512-I5FpSvLbtAdwJ+naznv+B4sjXZUcIvLLceYpITAn7wAP8W0wqc5noLdGIp9HGVntNhRWXctwPYrSSFQxtl0FPA==
|
||||||
|
dependencies:
|
||||||
|
"@rollup/pluginutils" "^3.1.0"
|
||||||
|
resolve "^1.17.0"
|
||||||
|
|
||||||
"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
|
"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
|
||||||
|
@ -12614,7 +12527,7 @@ fs.realpath@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||||
|
|
||||||
fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2:
|
fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.1, fsevents@~2.3.2:
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||||
|
@ -17662,6 +17575,13 @@ magic-string@^0.26.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
sourcemap-codec "^1.4.8"
|
sourcemap-codec "^1.4.8"
|
||||||
|
|
||||||
|
magic-string@^0.27.0:
|
||||||
|
version "0.27.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3"
|
||||||
|
integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/sourcemap-codec" "^1.4.13"
|
||||||
|
|
||||||
make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.1.0:
|
make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||||
|
@ -22488,6 +22408,13 @@ rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.6.0,
|
||||||
dependencies:
|
dependencies:
|
||||||
estree-walker "^0.6.1"
|
estree-walker "^0.6.1"
|
||||||
|
|
||||||
|
rollup@2.45.2:
|
||||||
|
version "2.45.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.45.2.tgz#8fb85917c9f35605720e92328f3ccbfba6f78b48"
|
||||||
|
integrity sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ==
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.1"
|
||||||
|
|
||||||
rollup@^2.36.2, rollup@^2.44.0, rollup@^2.45.2, rollup@^2.79.1:
|
rollup@^2.36.2, rollup@^2.44.0, rollup@^2.45.2, rollup@^2.79.1:
|
||||||
version "2.79.1"
|
version "2.79.1"
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
|
||||||
|
@ -24498,7 +24425,7 @@ timed-out@^4.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
|
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
|
||||||
integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==
|
integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==
|
||||||
|
|
||||||
timekeeper@2.2.0:
|
timekeeper@2.2.0, timekeeper@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/timekeeper/-/timekeeper-2.2.0.tgz#9645731fce9e3280a18614a57a9d1b72af3ca368"
|
resolved "https://registry.yarnpkg.com/timekeeper/-/timekeeper-2.2.0.tgz#9645731fce9e3280a18614a57a9d1b72af3ca368"
|
||||||
integrity sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A==
|
integrity sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A==
|
||||||
|
|
Loading…
Reference in New Issue