Merge branch 'state-and-bindings-panels' of github.com:Budibase/budibase into bindings-panel
This commit is contained in:
commit
bdd1f765cc
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "3.2.47",
|
"version": "3.3.1",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"concurrency": 20,
|
"concurrency": 20,
|
||||||
"command": {
|
"command": {
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
const processModals = () => {
|
const processModals = () => {
|
||||||
const defaultCacheFn = key => {
|
const defaultCacheFn = key => {
|
||||||
temporalStore.actions.setExpiring(key, {}, oneDayInSeconds)
|
temporalStore.setExpiring(key, {}, oneDayInSeconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dismissableModals = [
|
const dismissableModals = [
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
return dismissableModals.filter(modal => {
|
return dismissableModals.filter(modal => {
|
||||||
return !temporalStore.actions.getExpiring(modal.key) && modal.criteria()
|
return !temporalStore.getExpiring(modal.key) && modal.criteria()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { BANNER_TYPES } from "@budibase/bbui"
|
||||||
const oneDayInSeconds = 86400
|
const oneDayInSeconds = 86400
|
||||||
|
|
||||||
const defaultCacheFn = key => {
|
const defaultCacheFn = key => {
|
||||||
temporalStore.actions.setExpiring(key, {}, oneDayInSeconds)
|
temporalStore.setExpiring(key, {}, oneDayInSeconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
const upgradeAction = key => {
|
const upgradeAction = key => {
|
||||||
|
@ -148,7 +148,7 @@ export const getBanners = () => {
|
||||||
buildUsersAboveLimitBanner(ExpiringKeys.LICENSING_USERS_ABOVE_LIMIT_BANNER),
|
buildUsersAboveLimitBanner(ExpiringKeys.LICENSING_USERS_ABOVE_LIMIT_BANNER),
|
||||||
].filter(licensingBanner => {
|
].filter(licensingBanner => {
|
||||||
return (
|
return (
|
||||||
!temporalStore.actions.getExpiring(licensingBanner.key) &&
|
!temporalStore.getExpiring(licensingBanner.key) &&
|
||||||
licensingBanner.criteria()
|
licensingBanner.criteria()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,7 +11,7 @@ export const datasourceSelect = {
|
||||||
},
|
},
|
||||||
viewV2: (view, datasources) => {
|
viewV2: (view, datasources) => {
|
||||||
const datasource = datasources
|
const datasource = datasources
|
||||||
.filter(f => f.entities)
|
?.filter(f => f.entities)
|
||||||
.flatMap(d => d.entities)
|
.flatMap(d => d.entities)
|
||||||
.find(ds => ds._id === view.tableId)
|
.find(ds => ds._id === view.tableId)
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
$: useAccountPortal = cloud && !$admin.disableAccountPortal
|
$: useAccountPortal = cloud && !$admin.disableAccountPortal
|
||||||
|
|
||||||
navigation.actions.init($redirect)
|
navigation.init($redirect)
|
||||||
|
|
||||||
const validateTenantId = async () => {
|
const validateTenantId = async () => {
|
||||||
const host = window.location.host
|
const host = window.location.host
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { it, expect, describe, beforeEach, vi } from "vitest"
|
import { it, expect, describe, beforeEach, vi } from "vitest"
|
||||||
import { createAdminStore } from "./admin"
|
import { AdminStore } from "./admin"
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { auth } from "@/stores/portal"
|
import { auth } from "@/stores/portal"
|
||||||
|
@ -46,16 +46,7 @@ describe("admin store", () => {
|
||||||
ctx.writableReturn = { update: vi.fn(), subscribe: vi.fn() }
|
ctx.writableReturn = { update: vi.fn(), subscribe: vi.fn() }
|
||||||
writable.mockReturnValue(ctx.writableReturn)
|
writable.mockReturnValue(ctx.writableReturn)
|
||||||
|
|
||||||
ctx.returnedStore = createAdminStore()
|
ctx.returnedStore = new AdminStore()
|
||||||
})
|
|
||||||
|
|
||||||
it("returns the created store", ctx => {
|
|
||||||
expect(ctx.returnedStore).toEqual({
|
|
||||||
subscribe: expect.toBe(ctx.writableReturn.subscribe),
|
|
||||||
init: expect.toBeFunc(),
|
|
||||||
unload: expect.toBeFunc(),
|
|
||||||
getChecklist: expect.toBeFunc(),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("init method", () => {
|
describe("init method", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { auth } from "@/stores/portal"
|
import { auth } from "@/stores/portal"
|
||||||
import { banner } from "@budibase/bbui"
|
import { banner } from "@budibase/bbui"
|
||||||
|
@ -7,15 +7,17 @@ import {
|
||||||
GetEnvironmentResponse,
|
GetEnvironmentResponse,
|
||||||
SystemStatusResponse,
|
SystemStatusResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { BudiStore } from "../BudiStore"
|
||||||
|
|
||||||
interface PortalAdminStore extends GetEnvironmentResponse {
|
interface AdminState extends GetEnvironmentResponse {
|
||||||
loaded: boolean
|
loaded: boolean
|
||||||
checklist?: ConfigChecklistResponse
|
checklist?: ConfigChecklistResponse
|
||||||
status?: SystemStatusResponse
|
status?: SystemStatusResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createAdminStore() {
|
export class AdminStore extends BudiStore<AdminState> {
|
||||||
const admin = writable<PortalAdminStore>({
|
constructor() {
|
||||||
|
super({
|
||||||
loaded: false,
|
loaded: false,
|
||||||
multiTenancy: false,
|
multiTenancy: false,
|
||||||
cloud: false,
|
cloud: false,
|
||||||
|
@ -24,25 +26,25 @@ export function createAdminStore() {
|
||||||
offlineMode: false,
|
offlineMode: false,
|
||||||
maintenance: [],
|
maintenance: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
async function init() {
|
|
||||||
await getChecklist()
|
|
||||||
await getEnvironment()
|
|
||||||
// enable system status checks in the cloud
|
|
||||||
if (get(admin).cloud) {
|
|
||||||
await getSystemStatus()
|
|
||||||
checkStatus()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
admin.update(store => {
|
async init() {
|
||||||
|
await this.getChecklist()
|
||||||
|
await this.getEnvironment()
|
||||||
|
// enable system status checks in the cloud
|
||||||
|
if (get(this.store).cloud) {
|
||||||
|
await this.getSystemStatus()
|
||||||
|
this.checkStatus()
|
||||||
|
}
|
||||||
|
this.update(store => {
|
||||||
store.loaded = true
|
store.loaded = true
|
||||||
return store
|
return store
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getEnvironment() {
|
async getEnvironment() {
|
||||||
const environment = await API.getEnvironment()
|
const environment = await API.getEnvironment()
|
||||||
admin.update(store => {
|
this.update(store => {
|
||||||
store.multiTenancy = environment.multiTenancy
|
store.multiTenancy = environment.multiTenancy
|
||||||
store.cloud = environment.cloud
|
store.cloud = environment.cloud
|
||||||
store.disableAccountPortal = environment.disableAccountPortal
|
store.disableAccountPortal = environment.disableAccountPortal
|
||||||
|
@ -56,43 +58,36 @@ export function createAdminStore() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkStatus = async () => {
|
async checkStatus() {
|
||||||
const health = get(admin)?.status?.health
|
const health = get(this.store).status?.health
|
||||||
if (!health?.passing) {
|
if (!health?.passing) {
|
||||||
await banner.showStatus()
|
await banner.showStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSystemStatus() {
|
async getSystemStatus() {
|
||||||
const status = await API.getSystemStatus()
|
const status = await API.getSystemStatus()
|
||||||
admin.update(store => {
|
this.update(store => {
|
||||||
store.status = status
|
store.status = status
|
||||||
return store
|
return store
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getChecklist() {
|
async getChecklist() {
|
||||||
const tenantId = get(auth).tenantId
|
const tenantId = get(auth).tenantId
|
||||||
const checklist = await API.getChecklist(tenantId)
|
const checklist = await API.getChecklist(tenantId)
|
||||||
admin.update(store => {
|
this.update(store => {
|
||||||
store.checklist = checklist
|
store.checklist = checklist
|
||||||
return store
|
return store
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function unload() {
|
unload() {
|
||||||
admin.update(store => {
|
this.update(store => {
|
||||||
store.loaded = false
|
store.loaded = false
|
||||||
return store
|
return store
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: admin.subscribe,
|
|
||||||
init,
|
|
||||||
unload,
|
|
||||||
getChecklist,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const admin = createAdminStore()
|
export const admin = new AdminStore()
|
||||||
|
|
|
@ -13,7 +13,7 @@ interface PortalAuditLogsStore {
|
||||||
logs?: SearchAuditLogsResponse
|
logs?: SearchAuditLogsResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuditLogsStore extends BudiStore<PortalAuditLogsStore> {
|
class AuditLogsStore extends BudiStore<PortalAuditLogsStore> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({})
|
super({})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,31 @@
|
||||||
import { writable } from "svelte/store"
|
import { BudiStore } from "../BudiStore"
|
||||||
|
|
||||||
type GotoFuncType = (path: string) => void
|
type GotoFuncType = (path: string) => void
|
||||||
|
|
||||||
interface PortalNavigationStore {
|
interface NavigationState {
|
||||||
initialisated: boolean
|
initialisated: boolean
|
||||||
goto: GotoFuncType
|
goto: GotoFuncType
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createNavigationStore() {
|
class NavigationStore extends BudiStore<NavigationState> {
|
||||||
const store = writable<PortalNavigationStore>({
|
constructor() {
|
||||||
|
super({
|
||||||
initialisated: false,
|
initialisated: false,
|
||||||
goto: undefined as any,
|
goto: undefined as any,
|
||||||
})
|
})
|
||||||
const { set, subscribe } = store
|
}
|
||||||
|
|
||||||
const init = (gotoFunc: GotoFuncType) => {
|
init(gotoFunc: GotoFuncType) {
|
||||||
if (typeof gotoFunc !== "function") {
|
if (typeof gotoFunc !== "function") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`gotoFunc must be a function, found a "${typeof gotoFunc}" instead`
|
`gotoFunc must be a function, found a "${typeof gotoFunc}" instead`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
this.set({
|
||||||
set({
|
|
||||||
initialisated: true,
|
initialisated: true,
|
||||||
goto: gotoFunc,
|
goto: gotoFunc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe,
|
|
||||||
actions: {
|
|
||||||
init,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const navigation = createNavigationStore()
|
export const navigation = new NavigationStore()
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { writable } from "svelte/store"
|
|
||||||
import { API } from "@/api"
|
|
||||||
|
|
||||||
export function templatesStore() {
|
|
||||||
const { subscribe, set } = writable([])
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe,
|
|
||||||
load: async () => {
|
|
||||||
const templates = await API.getAppTemplates()
|
|
||||||
set(templates)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const templates = templatesStore()
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { API } from "@/api"
|
||||||
|
import { BudiStore } from "../BudiStore"
|
||||||
|
import { TemplateMetadata } from "@budibase/types"
|
||||||
|
|
||||||
|
class TemplateStore extends BudiStore<TemplateMetadata[]> {
|
||||||
|
constructor() {
|
||||||
|
super([])
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
const templates = await API.getAppTemplates()
|
||||||
|
this.set(templates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const templates = new TemplateStore()
|
|
@ -1,45 +0,0 @@
|
||||||
import { createLocalStorageStore } from "@budibase/frontend-core"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
|
|
||||||
export const createTemporalStore = () => {
|
|
||||||
const initialValue = {}
|
|
||||||
|
|
||||||
const localStorageKey = `bb-temporal`
|
|
||||||
const store = createLocalStorageStore(localStorageKey, initialValue)
|
|
||||||
|
|
||||||
const setExpiring = (key, data, duration) => {
|
|
||||||
const updated = {
|
|
||||||
...data,
|
|
||||||
expiry: Date.now() + duration * 1000,
|
|
||||||
}
|
|
||||||
|
|
||||||
store.update(state => ({
|
|
||||||
...state,
|
|
||||||
[key]: updated,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const getExpiring = key => {
|
|
||||||
const entry = get(store)[key]
|
|
||||||
if (!entry) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const currentExpiry = entry.expiry
|
|
||||||
if (currentExpiry < Date.now()) {
|
|
||||||
store.update(state => {
|
|
||||||
delete state[key]
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: store.subscribe,
|
|
||||||
actions: { setExpiring, getExpiring },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const temporalStore = createTemporalStore()
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import { BudiStore, PersistenceType } from "../BudiStore"
|
||||||
|
|
||||||
|
type TemporalItem = Record<string, any> & { expiry: number }
|
||||||
|
type TemporalState = Record<string, TemporalItem>
|
||||||
|
|
||||||
|
class TemporalStore extends BudiStore<TemporalState> {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
persistence: {
|
||||||
|
key: "bb-temporal",
|
||||||
|
type: PersistenceType.LOCAL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setExpiring = (
|
||||||
|
key: string,
|
||||||
|
data: Record<string, any>,
|
||||||
|
durationSeconds: number
|
||||||
|
) => {
|
||||||
|
const updated: TemporalItem = {
|
||||||
|
...data,
|
||||||
|
expiry: Date.now() + durationSeconds * 1000,
|
||||||
|
}
|
||||||
|
this.update(state => ({
|
||||||
|
...state,
|
||||||
|
[key]: updated,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
getExpiring(key: string) {
|
||||||
|
const entry = get(this.store)[key]
|
||||||
|
if (!entry) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const currentExpiry = entry.expiry
|
||||||
|
if (currentExpiry < Date.now()) {
|
||||||
|
this.update(state => {
|
||||||
|
delete state[key]
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const temporalStore = new TemporalStore()
|
|
@ -1,37 +0,0 @@
|
||||||
import { createLocalStorageStore } from "@budibase/frontend-core"
|
|
||||||
import { derived } from "svelte/store"
|
|
||||||
import {
|
|
||||||
DefaultBuilderTheme,
|
|
||||||
ensureValidTheme,
|
|
||||||
getThemeClassNames,
|
|
||||||
ThemeOptions,
|
|
||||||
ThemeClassPrefix,
|
|
||||||
} from "@budibase/shared-core"
|
|
||||||
|
|
||||||
export const getThemeStore = () => {
|
|
||||||
const themeElement = document.documentElement
|
|
||||||
const initialValue = {
|
|
||||||
theme: DefaultBuilderTheme,
|
|
||||||
}
|
|
||||||
const store = createLocalStorageStore("bb-theme", initialValue)
|
|
||||||
const derivedStore = derived(store, $store => ({
|
|
||||||
...$store,
|
|
||||||
theme: ensureValidTheme($store.theme, DefaultBuilderTheme),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Update theme class when store changes
|
|
||||||
derivedStore.subscribe(({ theme }) => {
|
|
||||||
const classNames = getThemeClassNames(theme).split(" ")
|
|
||||||
ThemeOptions.forEach(option => {
|
|
||||||
const className = `${ThemeClassPrefix}${option.id}`
|
|
||||||
themeElement.classList.toggle(className, classNames.includes(className))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
...store,
|
|
||||||
subscribe: derivedStore.subscribe,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const themeStore = getThemeStore()
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { derived, Writable } from "svelte/store"
|
||||||
|
import {
|
||||||
|
DefaultBuilderTheme,
|
||||||
|
ensureValidTheme,
|
||||||
|
getThemeClassNames,
|
||||||
|
ThemeOptions,
|
||||||
|
ThemeClassPrefix,
|
||||||
|
} from "@budibase/shared-core"
|
||||||
|
import { Theme } from "@budibase/types"
|
||||||
|
import { DerivedBudiStore, PersistenceType } from "../BudiStore"
|
||||||
|
|
||||||
|
interface ThemeState {
|
||||||
|
theme: Theme
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThemeStore extends DerivedBudiStore<ThemeState, ThemeState> {
|
||||||
|
constructor() {
|
||||||
|
const makeDerivedStore = (store: Writable<ThemeState>) => {
|
||||||
|
return derived(store, $store => ({
|
||||||
|
...$store,
|
||||||
|
theme: ensureValidTheme($store.theme, DefaultBuilderTheme),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
super({ theme: DefaultBuilderTheme }, makeDerivedStore, {
|
||||||
|
persistence: {
|
||||||
|
key: "bb-theme",
|
||||||
|
type: PersistenceType.LOCAL,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update theme class when store changes
|
||||||
|
this.subscribe(({ theme }) => {
|
||||||
|
const classNames = getThemeClassNames(theme).split(" ")
|
||||||
|
ThemeOptions.forEach(option => {
|
||||||
|
const className = `${ThemeClassPrefix}${option.id}`
|
||||||
|
document.documentElement.classList.toggle(
|
||||||
|
className,
|
||||||
|
classNames.includes(className)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const themeStore = new ThemeStore()
|
|
@ -2,7 +2,7 @@ import * as triggers from "../../automations/triggers"
|
||||||
import { sdk as coreSdk } from "@budibase/shared-core"
|
import { sdk as coreSdk } from "@budibase/shared-core"
|
||||||
import { DocumentType } from "../../db/utils"
|
import { DocumentType } from "../../db/utils"
|
||||||
import { updateTestHistory, removeDeprecated } from "../../automations/utils"
|
import { updateTestHistory, removeDeprecated } from "../../automations/utils"
|
||||||
import { setTestFlag, clearTestFlag } from "../../utilities/redis"
|
import { withTestFlag } from "../../utilities/redis"
|
||||||
import { context, cache, events, db as dbCore } from "@budibase/backend-core"
|
import { context, cache, events, db as dbCore } from "@budibase/backend-core"
|
||||||
import { automations, features } from "@budibase/pro"
|
import { automations, features } from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
|
@ -231,24 +231,25 @@ export async function test(
|
||||||
ctx: UserCtx<TestAutomationRequest, TestAutomationResponse>
|
ctx: UserCtx<TestAutomationRequest, TestAutomationResponse>
|
||||||
) {
|
) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let automation = await db.get<Automation>(ctx.params.id)
|
const automation = await db.tryGet<Automation>(ctx.params.id)
|
||||||
await setTestFlag(automation._id!)
|
if (!automation) {
|
||||||
const testInput = prepareTestInput(ctx.request.body)
|
ctx.throw(404, `Automation ${ctx.params.id} not found`)
|
||||||
const response = await triggers.externalTrigger(
|
}
|
||||||
|
|
||||||
|
const { request, appId } = ctx
|
||||||
|
const { body } = request
|
||||||
|
|
||||||
|
ctx.body = await withTestFlag(automation._id!, async () => {
|
||||||
|
const occurredAt = new Date().getTime()
|
||||||
|
await updateTestHistory(appId, automation, { ...body, occurredAt })
|
||||||
|
|
||||||
|
const user = sdk.users.getUserContextBindings(ctx.user)
|
||||||
|
return await triggers.externalTrigger(
|
||||||
automation,
|
automation,
|
||||||
{
|
{ ...prepareTestInput(body), appId, user },
|
||||||
...testInput,
|
|
||||||
appId: ctx.appId,
|
|
||||||
user: sdk.users.getUserContextBindings(ctx.user),
|
|
||||||
},
|
|
||||||
{ getResponses: true }
|
{ getResponses: true }
|
||||||
)
|
)
|
||||||
// save a test history run
|
|
||||||
await updateTestHistory(ctx.appId, automation, {
|
|
||||||
...ctx.request.body,
|
|
||||||
occurredAt: new Date().getTime(),
|
|
||||||
})
|
})
|
||||||
await clearTestFlag(automation._id!)
|
|
||||||
ctx.body = response
|
|
||||||
await events.automation.tested(automation)
|
await events.automation.tested(automation)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,11 @@ import {
|
||||||
sendAutomationAttachmentsToStorage,
|
sendAutomationAttachmentsToStorage,
|
||||||
} from "../automationUtils"
|
} from "../automationUtils"
|
||||||
import { buildCtx } from "./utils"
|
import { buildCtx } from "./utils"
|
||||||
import { CreateRowStepInputs, CreateRowStepOutputs } from "@budibase/types"
|
import {
|
||||||
import { EventEmitter } from "events"
|
ContextEmitter,
|
||||||
|
CreateRowStepInputs,
|
||||||
|
CreateRowStepOutputs,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
export async function run({
|
export async function run({
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -15,7 +18,7 @@ export async function run({
|
||||||
}: {
|
}: {
|
||||||
inputs: CreateRowStepInputs
|
inputs: CreateRowStepInputs
|
||||||
appId: string
|
appId: string
|
||||||
emitter: EventEmitter
|
emitter: ContextEmitter
|
||||||
}): Promise<CreateRowStepOutputs> {
|
}): Promise<CreateRowStepOutputs> {
|
||||||
if (inputs.row == null || inputs.row.tableId == null) {
|
if (inputs.row == null || inputs.row.tableId == null) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { EventEmitter } from "events"
|
|
||||||
import { destroy } from "../../api/controllers/row"
|
import { destroy } from "../../api/controllers/row"
|
||||||
import { buildCtx } from "./utils"
|
import { buildCtx } from "./utils"
|
||||||
import { getError } from "../automationUtils"
|
import { getError } from "../automationUtils"
|
||||||
import { DeleteRowStepInputs, DeleteRowStepOutputs } from "@budibase/types"
|
import {
|
||||||
|
ContextEmitter,
|
||||||
|
DeleteRowStepInputs,
|
||||||
|
DeleteRowStepOutputs,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
export async function run({
|
export async function run({
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -11,7 +14,7 @@ export async function run({
|
||||||
}: {
|
}: {
|
||||||
inputs: DeleteRowStepInputs
|
inputs: DeleteRowStepInputs
|
||||||
appId: string
|
appId: string
|
||||||
emitter: EventEmitter
|
emitter: ContextEmitter
|
||||||
}): Promise<DeleteRowStepOutputs> {
|
}): Promise<DeleteRowStepOutputs> {
|
||||||
if (inputs.id == null) {
|
if (inputs.id == null) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { EventEmitter } from "events"
|
|
||||||
import * as queryController from "../../api/controllers/query"
|
import * as queryController from "../../api/controllers/query"
|
||||||
import { buildCtx } from "./utils"
|
import { buildCtx } from "./utils"
|
||||||
import * as automationUtils from "../automationUtils"
|
import * as automationUtils from "../automationUtils"
|
||||||
import {
|
import {
|
||||||
|
ContextEmitter,
|
||||||
ExecuteQueryStepInputs,
|
ExecuteQueryStepInputs,
|
||||||
ExecuteQueryStepOutputs,
|
ExecuteQueryStepOutputs,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
@ -14,7 +14,7 @@ export async function run({
|
||||||
}: {
|
}: {
|
||||||
inputs: ExecuteQueryStepInputs
|
inputs: ExecuteQueryStepInputs
|
||||||
appId: string
|
appId: string
|
||||||
emitter: EventEmitter
|
emitter: ContextEmitter
|
||||||
}): Promise<ExecuteQueryStepOutputs> {
|
}): Promise<ExecuteQueryStepOutputs> {
|
||||||
if (inputs.query == null) {
|
if (inputs.query == null) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -2,10 +2,10 @@ import * as scriptController from "../../api/controllers/script"
|
||||||
import { buildCtx } from "./utils"
|
import { buildCtx } from "./utils"
|
||||||
import * as automationUtils from "../automationUtils"
|
import * as automationUtils from "../automationUtils"
|
||||||
import {
|
import {
|
||||||
|
ContextEmitter,
|
||||||
ExecuteScriptStepInputs,
|
ExecuteScriptStepInputs,
|
||||||
ExecuteScriptStepOutputs,
|
ExecuteScriptStepOutputs,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { EventEmitter } from "events"
|
|
||||||
|
|
||||||
export async function run({
|
export async function run({
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -16,7 +16,7 @@ export async function run({
|
||||||
inputs: ExecuteScriptStepInputs
|
inputs: ExecuteScriptStepInputs
|
||||||
appId: string
|
appId: string
|
||||||
context: object
|
context: object
|
||||||
emitter: EventEmitter
|
emitter: ContextEmitter
|
||||||
}): Promise<ExecuteScriptStepOutputs> {
|
}): Promise<ExecuteScriptStepOutputs> {
|
||||||
if (inputs.code == null) {
|
if (inputs.code == null) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { EventEmitter } from "events"
|
|
||||||
import * as rowController from "../../api/controllers/row"
|
import * as rowController from "../../api/controllers/row"
|
||||||
import * as automationUtils from "../automationUtils"
|
import * as automationUtils from "../automationUtils"
|
||||||
import { buildCtx } from "./utils"
|
import { buildCtx } from "./utils"
|
||||||
import { UpdateRowStepInputs, UpdateRowStepOutputs } from "@budibase/types"
|
import {
|
||||||
|
ContextEmitter,
|
||||||
|
UpdateRowStepInputs,
|
||||||
|
UpdateRowStepOutputs,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
export async function run({
|
export async function run({
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -11,7 +14,7 @@ export async function run({
|
||||||
}: {
|
}: {
|
||||||
inputs: UpdateRowStepInputs
|
inputs: UpdateRowStepInputs
|
||||||
appId: string
|
appId: string
|
||||||
emitter: EventEmitter
|
emitter: ContextEmitter
|
||||||
}): Promise<UpdateRowStepOutputs> {
|
}): Promise<UpdateRowStepOutputs> {
|
||||||
if (inputs.rowId == null || inputs.row == null) {
|
if (inputs.rowId == null || inputs.row == null) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { EventEmitter } from "events"
|
import { ContextEmitter } from "@budibase/types"
|
||||||
|
|
||||||
export async function getFetchResponse(fetched: any) {
|
export async function getFetchResponse(fetched: any) {
|
||||||
let status = fetched.status,
|
let status = fetched.status,
|
||||||
|
@ -22,7 +22,7 @@ export async function getFetchResponse(fetched: any) {
|
||||||
// opts can contain, body, params and version
|
// opts can contain, body, params and version
|
||||||
export function buildCtx(
|
export function buildCtx(
|
||||||
appId: string,
|
appId: string,
|
||||||
emitter?: EventEmitter | null,
|
emitter?: ContextEmitter | null,
|
||||||
opts: any = {}
|
opts: any = {}
|
||||||
) {
|
) {
|
||||||
const ctx: any = {
|
const ctx: any = {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { v4 as uuidv4 } from "uuid"
|
import { v4 as uuidv4 } from "uuid"
|
||||||
import { testAutomation } from "../../../api/routes/tests/utilities/TestFunctions"
|
|
||||||
import { BUILTIN_ACTION_DEFINITIONS } from "../../actions"
|
import { BUILTIN_ACTION_DEFINITIONS } from "../../actions"
|
||||||
import { TRIGGER_DEFINITIONS } from "../../triggers"
|
import { TRIGGER_DEFINITIONS } from "../../triggers"
|
||||||
import {
|
import {
|
||||||
|
@ -7,7 +6,6 @@ import {
|
||||||
AppActionTriggerOutputs,
|
AppActionTriggerOutputs,
|
||||||
Automation,
|
Automation,
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationResults,
|
|
||||||
AutomationStep,
|
AutomationStep,
|
||||||
AutomationStepInputs,
|
AutomationStepInputs,
|
||||||
AutomationTrigger,
|
AutomationTrigger,
|
||||||
|
@ -24,6 +22,7 @@ import {
|
||||||
ExecuteQueryStepInputs,
|
ExecuteQueryStepInputs,
|
||||||
ExecuteScriptStepInputs,
|
ExecuteScriptStepInputs,
|
||||||
FilterStepInputs,
|
FilterStepInputs,
|
||||||
|
isDidNotTriggerResponse,
|
||||||
LoopStepInputs,
|
LoopStepInputs,
|
||||||
OpenAIStepInputs,
|
OpenAIStepInputs,
|
||||||
QueryRowsStepInputs,
|
QueryRowsStepInputs,
|
||||||
|
@ -36,6 +35,7 @@ import {
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
ServerLogStepInputs,
|
ServerLogStepInputs,
|
||||||
SmtpEmailStepInputs,
|
SmtpEmailStepInputs,
|
||||||
|
TestAutomationRequest,
|
||||||
UpdateRowStepInputs,
|
UpdateRowStepInputs,
|
||||||
WebhookTriggerInputs,
|
WebhookTriggerInputs,
|
||||||
WebhookTriggerOutputs,
|
WebhookTriggerOutputs,
|
||||||
|
@ -279,7 +279,7 @@ class StepBuilder extends BaseStepBuilder {
|
||||||
class AutomationBuilder extends BaseStepBuilder {
|
class AutomationBuilder extends BaseStepBuilder {
|
||||||
private automationConfig: Automation
|
private automationConfig: Automation
|
||||||
private config: TestConfiguration
|
private config: TestConfiguration
|
||||||
private triggerOutputs: any
|
private triggerOutputs: TriggerOutputs
|
||||||
private triggerSet = false
|
private triggerSet = false
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -398,21 +398,19 @@ class AutomationBuilder extends BaseStepBuilder {
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
const automation = await this.save()
|
const automation = await this.save()
|
||||||
const results = await testAutomation(
|
const response = await this.config.api.automation.test(
|
||||||
this.config,
|
automation._id!,
|
||||||
automation,
|
this.triggerOutputs as TestAutomationRequest
|
||||||
this.triggerOutputs
|
|
||||||
)
|
)
|
||||||
return this.processResults(results)
|
|
||||||
|
if (isDidNotTriggerResponse(response)) {
|
||||||
|
throw new Error(response.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private processResults(results: {
|
response.steps.shift()
|
||||||
body: AutomationResults
|
|
||||||
}): AutomationResults {
|
|
||||||
results.body.steps.shift()
|
|
||||||
return {
|
return {
|
||||||
trigger: results.body.trigger,
|
trigger: response.trigger,
|
||||||
steps: results.body.steps,
|
steps: response.steps,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
AutomationRowEvent,
|
AutomationRowEvent,
|
||||||
UserBindings,
|
UserBindings,
|
||||||
AutomationResults,
|
AutomationResults,
|
||||||
|
DidNotTriggerResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { executeInThread } from "../threads/automation"
|
import { executeInThread } from "../threads/automation"
|
||||||
import { dataFilters, sdk } from "@budibase/shared-core"
|
import { dataFilters, sdk } from "@budibase/shared-core"
|
||||||
|
@ -33,14 +34,6 @@ const JOB_OPTS = {
|
||||||
import * as automationUtils from "../automations/automationUtils"
|
import * as automationUtils from "../automations/automationUtils"
|
||||||
import { doesTableExist } from "../sdk/app/tables/getters"
|
import { doesTableExist } from "../sdk/app/tables/getters"
|
||||||
|
|
||||||
type DidNotTriggerResponse = {
|
|
||||||
outputs: {
|
|
||||||
success: false
|
|
||||||
status: AutomationStatus.STOPPED
|
|
||||||
}
|
|
||||||
message: AutomationStoppedReason.TRIGGER_FILTER_NOT_MET
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAllAutomations() {
|
async function getAllAutomations() {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
let automations = await db.allDocs<Automation>(
|
let automations = await db.allDocs<Automation>(
|
||||||
|
@ -156,14 +149,26 @@ export function isAutomationResults(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function externalTrigger(
|
interface AutomationTriggerParams {
|
||||||
automation: Automation,
|
|
||||||
params: {
|
|
||||||
fields: Record<string, any>
|
fields: Record<string, any>
|
||||||
timeout?: number
|
timeout?: number
|
||||||
appId?: string
|
appId?: string
|
||||||
user?: UserBindings
|
user?: UserBindings
|
||||||
},
|
}
|
||||||
|
|
||||||
|
export async function externalTrigger(
|
||||||
|
automation: Automation,
|
||||||
|
params: AutomationTriggerParams,
|
||||||
|
options: { getResponses: true }
|
||||||
|
): Promise<AutomationResults | DidNotTriggerResponse>
|
||||||
|
export async function externalTrigger(
|
||||||
|
automation: Automation,
|
||||||
|
params: AutomationTriggerParams,
|
||||||
|
options?: { getResponses: false }
|
||||||
|
): Promise<AutomationJob | DidNotTriggerResponse>
|
||||||
|
export async function externalTrigger(
|
||||||
|
automation: Automation,
|
||||||
|
params: AutomationTriggerParams,
|
||||||
{ getResponses }: { getResponses?: boolean } = {}
|
{ getResponses }: { getResponses?: boolean } = {}
|
||||||
): Promise<AutomationResults | DidNotTriggerResponse | AutomationJob> {
|
): Promise<AutomationResults | DidNotTriggerResponse | AutomationJob> {
|
||||||
if (automation.disabled) {
|
if (automation.disabled) {
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { Automation, FetchAutomationResponse } from "@budibase/types"
|
import {
|
||||||
|
Automation,
|
||||||
|
FetchAutomationResponse,
|
||||||
|
TestAutomationRequest,
|
||||||
|
TestAutomationResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
import { Expectations, TestAPI } from "./base"
|
import { Expectations, TestAPI } from "./base"
|
||||||
|
|
||||||
export class AutomationAPI extends TestAPI {
|
export class AutomationAPI extends TestAPI {
|
||||||
|
@ -33,4 +38,18 @@ export class AutomationAPI extends TestAPI {
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test = async (
|
||||||
|
id: string,
|
||||||
|
body: TestAutomationRequest,
|
||||||
|
expectations?: Expectations
|
||||||
|
): Promise<TestAutomationResponse> => {
|
||||||
|
return await this._post<TestAutomationResponse>(
|
||||||
|
`/api/automations/${id}/test`,
|
||||||
|
{
|
||||||
|
body,
|
||||||
|
expectations,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
LoopStep,
|
LoopStep,
|
||||||
UserBindings,
|
UserBindings,
|
||||||
isBasicSearchOperator,
|
isBasicSearchOperator,
|
||||||
|
ContextEmitter,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
AutomationContext,
|
AutomationContext,
|
||||||
|
@ -71,6 +72,24 @@ function getLoopIterations(loopStep: LoopStep) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function enrichBaseContext(context: Record<string, any>) {
|
||||||
|
context.env = await sdkUtils.getEnvironmentVariables()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { config } = await configs.getSettingsConfigDoc()
|
||||||
|
context.settings = {
|
||||||
|
url: config.platformUrl,
|
||||||
|
logo: config.logoUrl,
|
||||||
|
company: config.company,
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// if settings doc doesn't exist, make the settings blank
|
||||||
|
context.settings = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The automation orchestrator is a class responsible for executing automations.
|
* The automation orchestrator is a class responsible for executing automations.
|
||||||
* It handles the context of the automation and makes sure each step gets the correct
|
* It handles the context of the automation and makes sure each step gets the correct
|
||||||
|
@ -80,7 +99,7 @@ class Orchestrator {
|
||||||
private chainCount: number
|
private chainCount: number
|
||||||
private appId: string
|
private appId: string
|
||||||
private automation: Automation
|
private automation: Automation
|
||||||
private emitter: any
|
private emitter: ContextEmitter
|
||||||
private context: AutomationContext
|
private context: AutomationContext
|
||||||
private job: Job
|
private job: Job
|
||||||
private loopStepOutputs: LoopStep[]
|
private loopStepOutputs: LoopStep[]
|
||||||
|
@ -270,20 +289,9 @@ class Orchestrator {
|
||||||
appId: this.appId,
|
appId: this.appId,
|
||||||
automationId: this.automation._id,
|
automationId: this.automation._id,
|
||||||
})
|
})
|
||||||
this.context.env = await sdkUtils.getEnvironmentVariables()
|
|
||||||
this.context.user = this.currentUser
|
|
||||||
|
|
||||||
try {
|
await enrichBaseContext(this.context)
|
||||||
const { config } = await configs.getSettingsConfigDoc()
|
this.context.user = this.currentUser
|
||||||
this.context.settings = {
|
|
||||||
url: config.platformUrl,
|
|
||||||
logo: config.logoUrl,
|
|
||||||
company: config.company,
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// if settings doc doesn't exist, make the settings blank
|
|
||||||
this.context.settings = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata
|
let metadata
|
||||||
|
|
||||||
|
|
|
@ -58,30 +58,14 @@ export function checkSlashesInUrl(url: string) {
|
||||||
export async function updateEntityMetadata(
|
export async function updateEntityMetadata(
|
||||||
type: string,
|
type: string,
|
||||||
entityId: string,
|
entityId: string,
|
||||||
updateFn: any
|
updateFn: (metadata: Document) => Document
|
||||||
) {
|
) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const id = generateMetadataID(type, entityId)
|
const id = generateMetadataID(type, entityId)
|
||||||
// read it to see if it exists, we'll overwrite it no matter what
|
const metadata = updateFn((await db.tryGet(id)) || {})
|
||||||
let rev, metadata: Document
|
|
||||||
try {
|
|
||||||
const oldMetadata = await db.get<any>(id)
|
|
||||||
rev = oldMetadata._rev
|
|
||||||
metadata = updateFn(oldMetadata)
|
|
||||||
} catch (err) {
|
|
||||||
rev = null
|
|
||||||
metadata = updateFn({})
|
|
||||||
}
|
|
||||||
metadata._id = id
|
metadata._id = id
|
||||||
if (rev) {
|
|
||||||
metadata._rev = rev
|
|
||||||
}
|
|
||||||
const response = await db.put(metadata)
|
const response = await db.put(metadata)
|
||||||
return {
|
return { ...metadata, _id: id, _rev: response.rev }
|
||||||
...metadata,
|
|
||||||
_id: id,
|
|
||||||
_rev: response.rev,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveEntityMetadata(
|
export async function saveEntityMetadata(
|
||||||
|
@ -89,26 +73,17 @@ export async function saveEntityMetadata(
|
||||||
entityId: string,
|
entityId: string,
|
||||||
metadata: Document
|
metadata: Document
|
||||||
): Promise<Document> {
|
): Promise<Document> {
|
||||||
return updateEntityMetadata(type, entityId, () => {
|
return updateEntityMetadata(type, entityId, () => metadata)
|
||||||
return metadata
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteEntityMetadata(type: string, entityId: string) {
|
export async function deleteEntityMetadata(type: string, entityId: string) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const id = generateMetadataID(type, entityId)
|
const id = generateMetadataID(type, entityId)
|
||||||
let rev
|
const metadata = await db.tryGet(id)
|
||||||
try {
|
if (!metadata) {
|
||||||
const metadata = await db.get<any>(id)
|
return
|
||||||
if (metadata) {
|
|
||||||
rev = metadata._rev
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// don't need to error if it doesn't exist
|
|
||||||
}
|
|
||||||
if (id && rev) {
|
|
||||||
await db.remove(id, rev)
|
|
||||||
}
|
}
|
||||||
|
await db.remove(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function escapeDangerousCharacters(string: string) {
|
export function escapeDangerousCharacters(string: string) {
|
||||||
|
|
|
@ -89,17 +89,22 @@ export async function setDebounce(id: string, seconds: number) {
|
||||||
await debounceClient.store(id, "debouncing", seconds)
|
await debounceClient.store(id, "debouncing", seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setTestFlag(id: string) {
|
|
||||||
await flagClient.store(id, { testing: true }, AUTOMATION_TEST_FLAG_SECONDS)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkTestFlag(id: string) {
|
export async function checkTestFlag(id: string) {
|
||||||
const flag = await flagClient?.get(id)
|
const flag = await flagClient?.get(id)
|
||||||
return !!(flag && flag.testing)
|
return !!(flag && flag.testing)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clearTestFlag(id: string) {
|
export async function withTestFlag<R>(id: string, fn: () => Promise<R>) {
|
||||||
|
// TODO(samwho): this has a bit of a problem where if 2 automations are tested
|
||||||
|
// at the same time, the second one will overwrite the first one's flag. We
|
||||||
|
// should instead use an atomic counter and only clear the flag when the
|
||||||
|
// counter reaches 0.
|
||||||
|
await flagClient.store(id, { testing: true }, AUTOMATION_TEST_FLAG_SECONDS)
|
||||||
|
try {
|
||||||
|
return await fn()
|
||||||
|
} finally {
|
||||||
await devAppClient.delete(id)
|
await devAppClient.delete(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSocketPubSubClients() {
|
export function getSocketPubSubClients() {
|
||||||
|
|
|
@ -2,10 +2,12 @@ import {
|
||||||
Automation,
|
Automation,
|
||||||
AutomationActionStepId,
|
AutomationActionStepId,
|
||||||
AutomationLogPage,
|
AutomationLogPage,
|
||||||
|
AutomationResults,
|
||||||
AutomationStatus,
|
AutomationStatus,
|
||||||
AutomationStepDefinition,
|
AutomationStepDefinition,
|
||||||
AutomationTriggerDefinition,
|
AutomationTriggerDefinition,
|
||||||
AutomationTriggerStepId,
|
AutomationTriggerStepId,
|
||||||
|
DidNotTriggerResponse,
|
||||||
Row,
|
Row,
|
||||||
} from "../../../documents"
|
} from "../../../documents"
|
||||||
import { DocumentDestroyResponse } from "@budibase/nano"
|
import { DocumentDestroyResponse } from "@budibase/nano"
|
||||||
|
@ -74,4 +76,10 @@ export interface TestAutomationRequest {
|
||||||
fields: Record<string, any>
|
fields: Record<string, any>
|
||||||
row?: Row
|
row?: Row
|
||||||
}
|
}
|
||||||
export interface TestAutomationResponse {}
|
export type TestAutomationResponse = AutomationResults | DidNotTriggerResponse
|
||||||
|
|
||||||
|
export function isDidNotTriggerResponse(
|
||||||
|
response: TestAutomationResponse
|
||||||
|
): response is DidNotTriggerResponse {
|
||||||
|
return !!("message" in response && response.message)
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Document } from "../../document"
|
import { Document } from "../../document"
|
||||||
import { EventEmitter } from "events"
|
|
||||||
import { User } from "../../global"
|
import { User } from "../../global"
|
||||||
import { ReadStream } from "fs"
|
import { ReadStream } from "fs"
|
||||||
import { Row } from "../row"
|
import { Row } from "../row"
|
||||||
import { Table } from "../table"
|
import { Table } from "../table"
|
||||||
import { AutomationStep, AutomationTrigger } from "./schema"
|
import { AutomationStep, AutomationTrigger } from "./schema"
|
||||||
|
import { ContextEmitter } from "../../../sdk"
|
||||||
|
|
||||||
export enum AutomationIOType {
|
export enum AutomationIOType {
|
||||||
OBJECT = "object",
|
OBJECT = "object",
|
||||||
|
@ -205,6 +205,14 @@ export interface AutomationResults {
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DidNotTriggerResponse {
|
||||||
|
outputs: {
|
||||||
|
success: false
|
||||||
|
status: AutomationStatus.STOPPED
|
||||||
|
}
|
||||||
|
message: AutomationStoppedReason.TRIGGER_FILTER_NOT_MET
|
||||||
|
}
|
||||||
|
|
||||||
export interface AutomationLog extends AutomationResults, Document {
|
export interface AutomationLog extends AutomationResults, Document {
|
||||||
automationName: string
|
automationName: string
|
||||||
_rev?: string
|
_rev?: string
|
||||||
|
@ -218,7 +226,7 @@ export interface AutomationLogPage {
|
||||||
|
|
||||||
export interface AutomationStepInputBase {
|
export interface AutomationStepInputBase {
|
||||||
context: Record<string, any>
|
context: Record<string, any>
|
||||||
emitter: EventEmitter
|
emitter: ContextEmitter
|
||||||
appId: string
|
appId: string
|
||||||
apiKey?: string
|
apiKey?: string
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue