Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Row action automation display in frontend #14209

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
automationStore,
selectedAutomation,
permissions,
selectedAutomationDisplayData,
} from "stores/builder"
import {
Icon,
Expand All @@ -14,6 +15,7 @@
notifications,
Label,
AbsTooltip,
InlineAlert,
} from "@budibase/bbui"
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
Expand Down Expand Up @@ -49,6 +51,8 @@
$: isAppAction && setPermissions(role)
$: isAppAction && getPermissions(automationId)

$: triggerInfo = $selectedAutomationDisplayData?.triggerInfo

async function setPermissions(role) {
if (!role || !automationId) {
return
Expand Down Expand Up @@ -183,6 +187,12 @@
{block}
{webhookModal}
/>
{#if isTrigger && triggerInfo}
<InlineAlert
header={triggerInfo.type}
message={`This trigger is tied to the row action ${triggerInfo.rowAction.name} on your ${triggerInfo.table.name} table`}
/>
{/if}
{#if lastStep}
<Button on:click={() => testDataModal.show()} cta>
Finish and test automation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
// Check the schema to see if required fields have been entered
$: isError =
!isTriggerValid(trigger) ||
!trigger.schema.outputs.required?.every(
!(trigger.schema.outputs.required || []).every(
mike12345567 marked this conversation as resolved.
Show resolved Hide resolved
required => $memoTestData?.[required] || required !== "row"
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
automation.name.toLowerCase().includes(searchString.toLowerCase())
)
})
.map(automation => ({
...automation,
name:
$automationStore.automationDisplayData[automation._id].displayName ||
automation.name,
}))
.sort((a, b) => {
const lowerA = a.name.toLowerCase()
const lowerB = b.name.toLowerCase()
Expand Down
22 changes: 17 additions & 5 deletions packages/builder/src/stores/builder/automations.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const initialAutomationState = {
ACTION: [],
},
selectedAutomationId: null,
automationDisplayData: {},
}

// If this functions, remove the actions elements
Expand Down Expand Up @@ -58,18 +59,19 @@ const automationActions = store => ({
return response
},
fetch: async () => {
const responses = await Promise.all([
API.getAutomations(),
const [automationResponse, definitions] = await Promise.all([
API.getAutomations({ enrich: true }),
API.getAutomationDefinitions(),
])
store.update(state => {
state.automations = responses[0]
state.automations = automationResponse.automations
state.automations.sort((a, b) => {
return a.name < b.name ? -1 : 1
})
state.automationDisplayData = automationResponse.builderData
state.blockDefinitions = {
TRIGGER: responses[1].trigger,
ACTION: responses[1].action,
TRIGGER: definitions.trigger,
ACTION: definitions.action,
}
return state
})
Expand Down Expand Up @@ -386,3 +388,13 @@ export const selectedAutomation = derived(automationStore, $automationStore => {
x => x._id === $automationStore.selectedAutomationId
)
})

export const selectedAutomationDisplayData = derived(
[automationStore, selectedAutomation],
([$automationStore, $selectedAutomation]) => {
if (!$selectedAutomation._id) {
return null
}
return $automationStore.automationDisplayData[$selectedAutomation._id]
}
)
2 changes: 2 additions & 0 deletions packages/builder/src/stores/builder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
automationStore,
selectedAutomation,
automationHistoryStore,
selectedAutomationDisplayData,
} from "./automations.js"
import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
import { deploymentStore } from "./deployments.js"
Expand Down Expand Up @@ -44,6 +45,7 @@ export {
previewStore,
automationStore,
selectedAutomation,
selectedAutomationDisplayData,
automationHistoryStore,
sortedScreens,
userStore,
Expand Down
9 changes: 7 additions & 2 deletions packages/frontend-core/src/api/automations.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ export const buildAutomationEndpoints = API => ({
/**
* Gets a list of all automations.
*/
getAutomations: async () => {
getAutomations: async ({ enrich }) => {
const params = new URLSearchParams()
if (enrich) {
params.set("enrich", true)
}

return await API.get({
url: "/api/automations",
url: `/api/automations?${params.toString()}`,
})
},

Expand Down
14 changes: 12 additions & 2 deletions packages/server/src/api/controllers/automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
AutomationResults,
UserCtx,
DeleteAutomationResponse,
FetchAutomationResponse,
} from "@budibase/types"
import { getActionDefinitions as actionDefs } from "../../automations/actions"
import sdk from "../../sdk"
Expand Down Expand Up @@ -73,8 +74,17 @@ export async function update(ctx: UserCtx) {
builderSocket?.emitAutomationUpdate(ctx, automation)
}

export async function fetch(ctx: UserCtx) {
ctx.body = await sdk.automations.fetch()
export async function fetch(ctx: UserCtx<void, FetchAutomationResponse>) {
const query: { enrich?: string } = ctx.request.query || {}
const enrich = query.enrich === "true"

const automations = await sdk.automations.fetch()
ctx.body = { automations }
if (enrich) {
ctx.body.builderData = await sdk.automations.utils.getBuilderData(
automations
)
}
}

export async function find(ctx: UserCtx) {
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/api/routes/tests/automation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,9 @@ describe("/automations", () => {
.expect("Content-Type", /json/)
.expect(200)

expect(res.body[0]).toEqual(expect.objectContaining(autoConfig))
expect(res.body.automations[0]).toEqual(
expect.objectContaining(autoConfig)
)
})

it("should apply authorization to endpoint", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const clearAllApps = async (
}

export const clearAllAutomations = async (config: TestConfiguration) => {
const automations = await config.getAllAutomations()
const { automations } = await config.getAllAutomations()
for (let auto of automations) {
await context.doInAppContext(config.getAppId(), async () => {
await config.deleteAutomation(auto)
Expand Down
5 changes: 3 additions & 2 deletions packages/server/src/sdk/app/automations/crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { definitions } from "../../../automations/triggerInfo"
import automations from "."

interface PersistedAutomation extends Automation {
export interface PersistedAutomation extends Automation {
_id: string
_rev: string
}
Expand Down Expand Up @@ -81,7 +81,7 @@ export async function fetch() {
include_docs: true,
})
)
return response.rows.map(row => row.doc)
return response.rows.map(row => row.doc).filter(doc => !!doc)
}

export async function get(automationId: string) {
Expand Down Expand Up @@ -254,6 +254,7 @@ async function checkForWebhooks({ oldAuto, newAuto }: any) {
}
return newAuto
}

function guardInvalidUpdatesAndThrow(
automation: Automation,
oldAutomation: Automation
Expand Down
62 changes: 61 additions & 1 deletion packages/server/src/sdk/app/automations/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,67 @@
import { Automation, AutomationActionStepId } from "@budibase/types"
import {
Automation,
AutomationActionStepId,
AutomationBuilderData,
AutomationTriggerStepId,
TableRowActions,
} from "@budibase/types"
import sdk from "../../../sdk"

export function checkForCollectStep(automation: Automation) {
return automation.definition.steps.some(
(step: any) => step.stepId === AutomationActionStepId.COLLECT
)
}

export async function getBuilderData(
automations: Automation[]
): Promise<Record<string, AutomationBuilderData>> {
const tableNameCache: Record<string, string> = {}
async function getTableName(tableId: string) {
if (!tableNameCache[tableId]) {
const table = await sdk.tables.getTable(tableId)
tableNameCache[tableId] = table.name
}

return tableNameCache[tableId]
}

const rowActionNameCache: Record<string, TableRowActions> = {}
async function getRowActionName(tableId: string, rowActionId: string) {
if (!rowActionNameCache[tableId]) {
const rowActions = await sdk.rowActions.get(tableId)
rowActionNameCache[tableId] = rowActions
}

return rowActionNameCache[tableId].actions[rowActionId]?.name
}

const result: Record<string, AutomationBuilderData> = {}
for (const automation of automations) {
const { trigger } = automation.definition
const isRowAction = trigger.stepId === AutomationTriggerStepId.ROW_ACTION
if (!isRowAction) {
result[automation._id!] = { displayName: automation.name }
continue
}

const { tableId, rowActionId } = trigger.inputs

const tableName = await getTableName(tableId)

const rowActionName = await getRowActionName(tableId, rowActionId)

result[automation._id!] = {
displayName: `${tableName}: ${automation.name}`,
triggerInfo: {
type: "Automation trigger",
table: { id: tableId, name: tableName },
rowAction: {
id: rowActionId,
name: rowActionName,
},
},
}
}
return result
}
17 changes: 9 additions & 8 deletions packages/server/src/sdk/app/rowActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
VirtualDocumentType,
} from "@budibase/types"
import automations from "./automations"
import tables from "./tables"

function ensureUniqueAndThrow(
doc: TableRowActions,
Expand Down Expand Up @@ -45,17 +44,19 @@ export async function create(tableId: string, rowAction: { name: string }) {
doc = { _id: rowActionsId, actions: {} }
}

const { name: tableName } = await tables.getTable(tableId)

ensureUniqueAndThrow(doc, action.name)

const appId = context.getAppId()
if (!appId) {
throw new Error("Could not get the current appId")
}

const newRowActionId = `${
VirtualDocumentType.ROW_ACTION
}${SEPARATOR}${utils.newid()}`

const automation = await automations.create({
name: `${tableName}: ${action.name}`,
name: action.name,
appId,
definition: {
trigger: {
Expand All @@ -68,6 +69,7 @@ export async function create(tableId: string, rowAction: { name: string }) {
stepId: AutomationTriggerStepId.ROW_ACTION,
inputs: {
tableId,
rowActionId: newRowActionId,
},
schema: {
inputs: {
Expand All @@ -88,16 +90,15 @@ export async function create(tableId: string, rowAction: { name: string }) {
},
})

const newId = `${VirtualDocumentType.ROW_ACTION}${SEPARATOR}${utils.newid()}`
doc.actions[newId] = {
doc.actions[newRowActionId] = {
name: action.name,
automationId: automation._id!,
}
await db.put(doc)

return {
id: newId,
...doc.actions[newId],
id: newRowActionId,
...doc.actions[newRowActionId],
}
}

Expand Down
21 changes: 21 additions & 0 deletions packages/types/src/api/web/automation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
import { DocumentDestroyResponse } from "@budibase/nano"
import { Automation } from "../../documents"

export interface DeleteAutomationResponse extends DocumentDestroyResponse {}

export interface AutomationBuilderData {
displayName: string
triggerInfo?: {
type: string
table: {
id: string
name: string
}
rowAction: {
id: string
name: string
}
}
}

export interface FetchAutomationResponse {
automations: Automation[]
builderData?: Record<string, AutomationBuilderData> // The key will be the automationId
}
Loading