Merge branch 'master' of github.com:Budibase/budibase into client-vite

This commit is contained in:
Andrew Kingston 2024-11-28 09:53:41 +00:00
commit e2e38ff704
No known key found for this signature in database
17 changed files with 380 additions and 155 deletions

28
.github/workflows/readme-openapi.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: ReadMe GitHub Action 🦉
on:
push:
branches:
- master
jobs:
rdme-openapi:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v3
- name: Use Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: yarn
- run: yarn --frozen-lockfile
- name: update specs
run: cd packages/server && yarn specs
- name: Run `openapi` command
uses: readmeio/rdme@v8
with:
rdme: openapi specs/openapi.yaml --key=${{ secrets.README_API_KEY }} --id=6728a74f5918b50036c61841

View File

@ -1,6 +1,6 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "3.2.12", "version": "3.2.13",
"npmClient": "yarn", "npmClient": "yarn",
"concurrency": 20, "concurrency": 20,
"command": { "command": {

View File

@ -5,4 +5,5 @@ package-lock.json
release/ release/
dist/ dist/
routify routify
.routify/ .routify/
.rollup.cache

View File

@ -4,14 +4,14 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
"svelte-check": "svelte-check --no-tsconfig", "build": "routify -b && NODE_OPTIONS=\"--max_old_space_size=4096\" vite build --emptyOutDir",
"build": "yarn svelte-check && routify -b && vite build --emptyOutDir",
"start": "routify -c rollup", "start": "routify -c rollup",
"dev": "routify -c dev:vite", "dev": "routify -c dev:vite",
"dev:vite": "vite --host 0.0.0.0", "dev:vite": "vite --host 0.0.0.0",
"rollup": "rollup -c -w", "rollup": "rollup -c -w",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest" "test:watch": "vitest",
"check:types": "yarn svelte-check"
}, },
"jest": { "jest": {
"globals": { "globals": {
@ -89,6 +89,7 @@
"@babel/plugin-transform-runtime": "^7.13.10", "@babel/plugin-transform-runtime": "^7.13.10",
"@babel/preset-env": "^7.13.12", "@babel/preset-env": "^7.13.12",
"@rollup/plugin-replace": "^5.0.3", "@rollup/plugin-replace": "^5.0.3",
"@rollup/plugin-typescript": "8.3.0",
"@roxi/routify": "2.18.12", "@roxi/routify": "2.18.12",
"@sveltejs/vite-plugin-svelte": "1.4.0", "@sveltejs/vite-plugin-svelte": "1.4.0",
"@testing-library/jest-dom": "6.4.2", "@testing-library/jest-dom": "6.4.2",

View File

@ -8,7 +8,7 @@ import { get } from "svelte/store"
import { auth, navigation } from "./stores/portal" import { auth, navigation } from "./stores/portal"
export const API = createAPIClient({ export const API = createAPIClient({
attachHeaders: headers => { attachHeaders: (headers: Record<string, string>) => {
// Attach app ID header from store // Attach app ID header from store
let appId = get(appStore).appId let appId = get(appStore).appId
if (appId) { if (appId) {
@ -16,13 +16,13 @@ export const API = createAPIClient({
} }
// Add csrf token if authenticated // Add csrf token if authenticated
const user = get(auth).user const user: any = get(auth).user
if (user?.csrfToken) { if (user?.csrfToken) {
headers["x-csrf-token"] = user.csrfToken headers["x-csrf-token"] = user.csrfToken
} }
}, },
onError: error => { onError: (error: any) => {
const { url, message, status, method, handled } = error || {} const { url, message, status, method, handled } = error || {}
// Log any errors that we haven't manually handled // Log any errors that we haven't manually handled
@ -45,14 +45,14 @@ export const API = createAPIClient({
} }
} }
}, },
onMigrationDetected: appId => { onMigrationDetected: (appId: string) => {
const updatingUrl = `/builder/app/updating/${appId}` const updatingUrl = `/builder/app/updating/${appId}`
if (window.location.pathname === updatingUrl) { if (window.location.pathname === updatingUrl) {
return return
} }
get(navigation).goto( get(navigation)?.goto(
`${updatingUrl}?returnUrl=${encodeURIComponent(window.location.pathname)}` `${updatingUrl}?returnUrl=${encodeURIComponent(window.location.pathname)}`
) )
}, },

8
packages/builder/src/index.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
declare module "api" {
const API: {
getPlugins: () => Promise<any>
createPlugin: (plugin: object) => Promise<any>
uploadPlugin: (plugin: FormData) => Promise<any>
deletePlugin: (id: string) => Promise<void>
}
}

View File

@ -1,13 +1,20 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
type GotoFuncType = (path: string) => void
interface Store {
initialisated: boolean
goto: GotoFuncType
}
export function createNavigationStore() { export function createNavigationStore() {
const store = writable({ const store = writable<Store>({
initialisated: false, initialisated: false,
goto: undefined, goto: undefined as any,
}) })
const { set, subscribe } = store const { set, subscribe } = store
const init = gotoFunc => { const 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`

View File

@ -1,16 +1,21 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { PluginSource } from "constants/index"
import { API } from "api" import { API } from "api"
import { PluginSource } from "constants"
interface Plugin {
_id: string
}
export function createPluginsStore() { export function createPluginsStore() {
const { subscribe, set, update } = writable([]) const { subscribe, set, update } = writable<Plugin[]>([])
async function load() { async function load() {
const plugins = await API.getPlugins() const plugins = await API.getPlugins()
set(plugins) set(plugins)
} }
async function deletePlugin(pluginId) { async function deletePlugin(pluginId: string) {
await API.deletePlugin(pluginId) await API.deletePlugin(pluginId)
update(state => { update(state => {
state = state.filter(existing => existing._id !== pluginId) state = state.filter(existing => existing._id !== pluginId)
@ -18,8 +23,8 @@ export function createPluginsStore() {
}) })
} }
async function createPlugin(source, url, auth = null) { async function createPlugin(source: string, url: string, auth = null) {
let pluginData = { let pluginData: any = {
source, source,
url, url,
} }
@ -46,7 +51,7 @@ export function createPluginsStore() {
}) })
} }
async function uploadPlugin(file) { async function uploadPlugin(file: File) {
if (!file) { if (!file) {
return return
} }

View File

@ -9,15 +9,9 @@
"noImplicitAny": true, "noImplicitAny": true,
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"incremental": true "incremental": true,
"skipLibCheck": true
}, },
"include": [ "include": ["./src/**/*"],
"./src/**/*" "exclude": ["node_modules", "**/*.json", "**/*.spec.ts", "**/*.spec.js"]
],
"exclude": [
"node_modules",
"**/*.json",
"**/*.spec.ts",
"**/*.spec.js"
]
} }

View File

@ -3,6 +3,7 @@ import replace from "@rollup/plugin-replace"
import { defineConfig, loadEnv } from "vite" import { defineConfig, loadEnv } from "vite"
import { viteStaticCopy } from "vite-plugin-static-copy" import { viteStaticCopy } from "vite-plugin-static-copy"
import path from "path" import path from "path"
import typescript from "@rollup/plugin-typescript"
const ignoredWarnings = [ const ignoredWarnings = [
"unused-export-let", "unused-export-let",
@ -35,7 +36,7 @@ export default defineConfig(({ mode }) => {
// Copy fonts to an additional path so that svelte's automatic // Copy fonts to an additional path so that svelte's automatic
// prefixing of the base URL path can still resolve assets // prefixing of the base URL path can still resolve assets
copyFonts("builder/fonts"), copyFonts("builder/fonts"),
] ]
return { return {
test: { test: {
@ -61,6 +62,7 @@ export default defineConfig(({ mode }) => {
sourcemap: !isProduction, sourcemap: !isProduction,
}, },
plugins: [ plugins: [
typescript({ outDir: "../server/builder/dist" }),
svelte({ svelte({
hot: !isProduction, hot: !isProduction,
emitCss: true, emitCss: true,

@ -1 +1 @@
Subproject commit 25dd40ee12b048307b558ebcedb36548d6e042cd Subproject commit 21a6a658e0765208fb6ebf21864497f2739dcb99

View File

@ -2166,7 +2166,6 @@
"query": { "query": {
"description": "Search parameters for view", "description": "Search parameters for view",
"type": "object", "type": "object",
"required": [],
"properties": { "properties": {
"logicalOperator": { "logicalOperator": {
"description": "When using groups this defines whether all of the filters must match, or only one of them.", "description": "When using groups this defines whether all of the filters must match, or only one of them.",
@ -2449,7 +2448,6 @@
"query": { "query": {
"description": "Search parameters for view", "description": "Search parameters for view",
"type": "object", "type": "object",
"required": [],
"properties": { "properties": {
"logicalOperator": { "logicalOperator": {
"description": "When using groups this defines whether all of the filters must match, or only one of them.", "description": "When using groups this defines whether all of the filters must match, or only one of them.",
@ -2743,7 +2741,6 @@
"query": { "query": {
"description": "Search parameters for view", "description": "Search parameters for view",
"type": "object", "type": "object",
"required": [],
"properties": { "properties": {
"logicalOperator": { "logicalOperator": {
"description": "When using groups this defines whether all of the filters must match, or only one of them.", "description": "When using groups this defines whether all of the filters must match, or only one of them.",

View File

@ -1802,7 +1802,6 @@ components:
query: query:
description: Search parameters for view description: Search parameters for view
type: object type: object
required: []
properties: properties:
logicalOperator: logicalOperator:
description: When using groups this defines whether all of the filters must description: When using groups this defines whether all of the filters must
@ -2012,7 +2011,6 @@ components:
query: query:
description: Search parameters for view description: Search parameters for view
type: object type: object
required: []
properties: properties:
logicalOperator: logicalOperator:
description: When using groups this defines whether all of the filters must description: When using groups this defines whether all of the filters must
@ -2229,7 +2227,6 @@ components:
query: query:
description: Search parameters for view description: Search parameters for view
type: object type: object
required: []
properties: properties:
logicalOperator: logicalOperator:
description: When using groups this defines whether all of the filters must description: When using groups this defines whether all of the filters must

View File

@ -142,7 +142,6 @@ layeredFilterGroup.items.properties.groups = filterGroup
const viewQuerySchema = { const viewQuerySchema = {
description: "Search parameters for view", description: "Search parameters for view",
type: "object", type: "object",
required: [],
properties: { properties: {
logicalOperator, logicalOperator,
onEmptyFilter: { onEmptyFilter: {

View File

@ -1,6 +1,7 @@
import * as setup from "./utilities" import * as setup from "./utilities"
import { basicTableWithAttachmentField } from "../../tests/utilities/structures" import { basicTableWithAttachmentField } from "../../tests/utilities/structures"
import { objectStore } from "@budibase/backend-core" import { objectStore } from "@budibase/backend-core"
import { createAutomationBuilder } from "./utilities/AutomationTestBuilder"
async function uploadTestFile(filename: string) { async function uploadTestFile(filename: string) {
let bucket = "testbucket" let bucket = "testbucket"
@ -13,6 +14,7 @@ async function uploadTestFile(filename: string) {
return presignedUrl return presignedUrl
} }
describe("test the create row action", () => { describe("test the create row action", () => {
let table: any let table: any
let row: any let row: any
@ -31,30 +33,78 @@ describe("test the create row action", () => {
afterAll(setup.afterAll) afterAll(setup.afterAll)
it("should be able to run the action", async () => { it("should be able to run the action", async () => {
const res = await setup.runStep(config, setup.actions.CREATE_ROW.stepId, { const result = await createAutomationBuilder({
row, name: "Test Create Row Flow",
appId: config.getAppId(),
config,
}) })
expect(res.id).toBeDefined() .appAction({ fields: { status: "new" } })
expect(res.revision).toBeDefined() .serverLog({ text: "Starting create row flow" }, { stepName: "StartLog" })
expect(res.success).toEqual(true) .createRow({ row }, { stepName: "CreateRow" })
const gottenRow = await config.api.row.get(table._id, res.id) .serverLog(
{ text: "Row created with ID: {{ stepsByName.CreateRow.row._id }}" },
{ stepName: "CreationLog" }
)
.run()
expect(result.steps[1].outputs.success).toBeDefined()
expect(result.steps[1].outputs.id).toBeDefined()
expect(result.steps[1].outputs.revision).toBeDefined()
const gottenRow = await config.api.row.get(
table._id,
result.steps[1].outputs.id
)
expect(gottenRow.name).toEqual("test") expect(gottenRow.name).toEqual("test")
expect(gottenRow.description).toEqual("test") expect(gottenRow.description).toEqual("test")
expect(result.steps[2].outputs.message).toContain(
"Row created with ID: " + result.steps[1].outputs.id
)
}) })
it("should return an error (not throw) when bad info provided", async () => { it("should return an error (not throw) when bad info provided", async () => {
const res = await setup.runStep(config, setup.actions.CREATE_ROW.stepId, { const result = await createAutomationBuilder({
row: { name: "Test Create Row Error Flow",
tableId: "invalid", appId: config.getAppId(),
invalid: "invalid", config,
},
}) })
expect(res.success).toEqual(false) .appAction({ fields: { status: "error" } })
.serverLog({ text: "Starting error test flow" }, { stepName: "StartLog" })
.createRow(
{
row: {
tableId: "invalid",
invalid: "invalid",
},
},
{ stepName: "CreateRow" }
)
.run()
expect(result.steps[1].outputs.success).toEqual(false)
}) })
it("should check invalid inputs return an error", async () => { it("should check invalid inputs return an error", async () => {
const res = await setup.runStep(config, setup.actions.CREATE_ROW.stepId, {}) const result = await createAutomationBuilder({
expect(res.success).toEqual(false) name: "Test Create Row Invalid Flow",
appId: config.getAppId(),
config,
})
.appAction({ fields: { status: "invalid" } })
.serverLog({ text: "Testing invalid input" }, { stepName: "StartLog" })
.createRow({ row: {} }, { stepName: "CreateRow" })
.filter({
field: "{{ stepsByName.CreateRow.success }}",
condition: "equal",
value: true,
})
.serverLog(
{ text: "This log should not appear" },
{ stepName: "SkippedLog" }
)
.run()
expect(result.steps[1].outputs.success).toEqual(false)
expect(result.steps.length).toBeLessThan(4)
}) })
it("should check that an attachment field is sent to storage and parsed", async () => { it("should check that an attachment field is sent to storage and parsed", async () => {
@ -76,13 +126,33 @@ describe("test the create row action", () => {
] ]
attachmentRow.file_attachment = attachmentObject attachmentRow.file_attachment = attachmentObject
const res = await setup.runStep(config, setup.actions.CREATE_ROW.stepId, { const result = await createAutomationBuilder({
row: attachmentRow, name: "Test Create Row Attachment Flow",
appId: config.getAppId(),
config,
}) })
.appAction({ fields: { type: "attachment" } })
.serverLog(
{ text: "Processing attachment upload" },
{ stepName: "StartLog" }
)
.createRow({ row: attachmentRow }, { stepName: "CreateRow" })
.filter({
field: "{{ stepsByName.CreateRow.success }}",
condition: "equal",
value: true,
})
.serverLog(
{
text: "Attachment uploaded with key: {{ stepsByName.CreateRow.row.file_attachment.0.key }}",
},
{ stepName: "UploadLog" }
)
.run()
expect(res.success).toEqual(true) expect(result.steps[1].outputs.success).toEqual(true)
expect(res.row.file_attachment[0]).toHaveProperty("key") expect(result.steps[1].outputs.row.file_attachment[0]).toHaveProperty("key")
let s3Key = res.row.file_attachment[0].key let s3Key = result.steps[1].outputs.row.file_attachment[0].key
const client = objectStore.ObjectStore(objectStore.ObjectStoreBuckets.APPS) const client = objectStore.ObjectStore(objectStore.ObjectStoreBuckets.APPS)
@ -111,13 +181,53 @@ describe("test the create row action", () => {
} }
attachmentRow.single_file_attachment = attachmentObject attachmentRow.single_file_attachment = attachmentObject
const res = await setup.runStep(config, setup.actions.CREATE_ROW.stepId, { const result = await createAutomationBuilder({
row: attachmentRow, name: "Test Create Row Single Attachment Flow",
appId: config.getAppId(),
config,
}) })
.appAction({ fields: { type: "single-attachment" } })
.serverLog(
{ text: "Processing single attachment" },
{ stepName: "StartLog" }
)
.createRow({ row: attachmentRow }, { stepName: "CreateRow" })
.branch({
success: {
steps: stepBuilder =>
stepBuilder
.serverLog(
{ text: "Single attachment processed" },
{ stepName: "ProcessLog" }
)
.serverLog(
{
text: "File key: {{ stepsByName.CreateRow.row.single_file_attachment.key }}",
},
{ stepName: "KeyLog" }
),
condition: {
equal: { "{{ stepsByName.CreateRow.success }}": true },
},
},
error: {
steps: stepBuilder =>
stepBuilder.serverLog(
{ text: "Failed to process attachment" },
{ stepName: "ErrorLog" }
),
condition: {
equal: { "{{ stepsByName.CreateRow.success }}": false },
},
},
})
.run()
expect(res.success).toEqual(true) expect(result.steps[1].outputs.success).toEqual(true)
expect(res.row.single_file_attachment).toHaveProperty("key") expect(result.steps[1].outputs.row.single_file_attachment).toHaveProperty(
let s3Key = res.row.single_file_attachment.key "key"
)
let s3Key = result.steps[1].outputs.row.single_file_attachment.key
const client = objectStore.ObjectStore(objectStore.ObjectStoreBuckets.APPS) const client = objectStore.ObjectStore(objectStore.ObjectStoreBuckets.APPS)
@ -146,13 +256,50 @@ describe("test the create row action", () => {
} }
attachmentRow.single_file_attachment = attachmentObject attachmentRow.single_file_attachment = attachmentObject
const res = await setup.runStep(config, setup.actions.CREATE_ROW.stepId, { const result = await createAutomationBuilder({
row: attachmentRow, name: "Test Create Row Invalid Attachment Flow",
appId: config.getAppId(),
config,
}) })
.appAction({ fields: { type: "invalid-attachment" } })
.serverLog(
{ text: "Testing invalid attachment keys" },
{ stepName: "StartLog" }
)
.createRow({ row: attachmentRow }, { stepName: "CreateRow" })
.branch({
success: {
steps: stepBuilder =>
stepBuilder.serverLog(
{ text: "Unexpected success" },
{ stepName: "UnexpectedLog" }
),
condition: {
equal: { "{{ stepsByName.CreateRow.success }}": true },
},
},
error: {
steps: stepBuilder =>
stepBuilder
.serverLog(
{ text: "Expected error occurred" },
{ stepName: "ErrorLog" }
)
.serverLog(
{ text: "Error: {{ stepsByName.CreateRow.response }}" },
{ stepName: "ErrorDetailsLog" }
),
condition: {
equal: { "{{ stepsByName.CreateRow.success }}": false },
},
},
})
.run()
expect(res.success).toEqual(false) expect(result.steps[1].outputs.success).toEqual(false)
expect(res.response).toEqual( expect(result.steps[1].outputs.response).toEqual(
'Error: Attachments must have both "url" and "filename" keys. You have provided: wrongKey, anotherWrongKey' 'Error: Attachments must have both "url" and "filename" keys. You have provided: wrongKey, anotherWrongKey'
) )
expect(result.steps[2].outputs.status).toEqual("No branch condition met")
}) })
}) })

View File

@ -1,52 +1,65 @@
import { createAutomationBuilder } from "./utilities/AutomationTestBuilder"
import * as setup from "./utilities" import * as setup from "./utilities"
describe("test the delete row action", () => { describe("test the delete row action", () => {
let table: any let table: any,
let row: any row: any,
let inputs: any config = setup.getConfig()
let config = setup.getConfig()
beforeEach(async () => { beforeAll(async () => {
await config.init() await config.init()
table = await config.createTable() table = await config.createTable()
row = await config.createRow() row = await config.createRow()
inputs = {
tableId: table._id,
id: row._id,
revision: row._rev,
}
}) })
afterAll(setup.afterAll) afterAll(setup.afterAll)
it("should be able to run the action", async () => { it("should be able to run the delete row action", async () => {
const res = await setup.runStep( const builder = createAutomationBuilder({
config, name: "Delete Row Automation",
setup.actions.DELETE_ROW.stepId, })
inputs
)
expect(res.success).toEqual(true)
expect(res.response).toBeDefined()
expect(res.row._id).toEqual(row._id)
})
it("check usage quota attempts", async () => { await builder
await setup.runInProd(async () => { .appAction({ fields: {} })
await setup.runStep(config, setup.actions.DELETE_ROW.stepId, inputs) .deleteRow({
tableId: table._id,
id: row._id,
revision: row._rev,
})
.run()
await config.api.row.get(table._id, row._id, {
status: 404,
}) })
}) })
it("should check invalid inputs return an error", async () => { it("should check invalid inputs return an error", async () => {
const res = await setup.runStep(config, setup.actions.DELETE_ROW.stepId, {}) const builder = createAutomationBuilder({
expect(res.success).toEqual(false) name: "Invalid Inputs Automation",
})
const results = await builder
.appAction({ fields: {} })
.deleteRow({ tableId: "", id: "", revision: "" })
.run()
expect(results.steps[0].outputs.success).toEqual(false)
}) })
it("should return an error when table doesn't exist", async () => { it("should return an error when table doesn't exist", async () => {
const res = await setup.runStep(config, setup.actions.DELETE_ROW.stepId, { const builder = createAutomationBuilder({
tableId: "invalid", name: "Nonexistent Table Automation",
id: "invalid",
revision: "invalid",
}) })
expect(res.success).toEqual(false)
const results = await builder
.appAction({ fields: {} })
.deleteRow({
tableId: "invalid",
id: "invalid",
revision: "invalid",
})
.run()
expect(results.steps[0].outputs.success).toEqual(false)
}) })
}) })

View File

@ -8,58 +8,83 @@ import {
Table, Table,
TableSourceType, TableSourceType,
} from "@budibase/types" } from "@budibase/types"
import { createAutomationBuilder } from "./utilities/AutomationTestBuilder"
import * as setup from "./utilities" import * as setup from "./utilities"
import * as uuid from "uuid" import * as uuid from "uuid"
describe("test the update row action", () => { describe("test the update row action", () => {
let table: Table, row: Row, inputs: any let table: Table,
let config = setup.getConfig() row: Row,
config = setup.getConfig()
beforeAll(async () => { beforeAll(async () => {
await config.init() await config.init()
table = await config.createTable() table = await config.createTable()
row = await config.createRow() row = await config.createRow()
inputs = {
rowId: row._id,
row: {
...row,
name: "Updated name",
// put a falsy option in to be removed
description: "",
},
}
}) })
afterAll(setup.afterAll) afterAll(setup.afterAll)
it("should be able to run the action", async () => { it("should be able to run the update row action", async () => {
const res = await setup.runStep( const builder = createAutomationBuilder({
config, name: "Update Row Automation",
setup.actions.UPDATE_ROW.stepId, })
inputs
const results = await builder
.appAction({ fields: {} })
.updateRow({
rowId: row._id!,
row: {
...row,
name: "Updated name",
description: "",
},
meta: {},
})
.run()
expect(results.steps[0].outputs.success).toEqual(true)
const updatedRow = await config.api.row.get(
table._id!,
results.steps[0].outputs.id
) )
expect(res.success).toEqual(true)
const updatedRow = await config.api.row.get(table._id!, res.id)
expect(updatedRow.name).toEqual("Updated name") expect(updatedRow.name).toEqual("Updated name")
expect(updatedRow.description).not.toEqual("") expect(updatedRow.description).not.toEqual("")
}) })
it("should check invalid inputs return an error", async () => { it("should check invalid inputs return an error", async () => {
const res = await setup.runStep(config, setup.actions.UPDATE_ROW.stepId, {}) const builder = createAutomationBuilder({
expect(res.success).toEqual(false) name: "Invalid Inputs Automation",
})
const results = await builder
.appAction({ fields: {} })
.updateRow({ meta: {}, row: {}, rowId: "" })
.run()
expect(results.steps[0].outputs.success).toEqual(false)
}) })
it("should return an error when table doesn't exist", async () => { it("should return an error when table doesn't exist", async () => {
const res = await setup.runStep(config, setup.actions.UPDATE_ROW.stepId, { const builder = createAutomationBuilder({
row: { _id: "invalid" }, name: "Nonexistent Table Automation",
rowId: "invalid",
}) })
expect(res.success).toEqual(false)
const results = await builder
.appAction({ fields: {} })
.updateRow({
row: { _id: "invalid" },
rowId: "invalid",
meta: {},
})
.run()
expect(results.steps[0].outputs.success).toEqual(false)
}) })
it("should not overwrite links if those links are not set", async () => { it("should not overwrite links if those links are not set", async () => {
let linkField: FieldSchema = { const linkField: FieldSchema = {
type: FieldType.LINK, type: FieldType.LINK,
name: "", name: "",
fieldName: "", fieldName: "",
@ -71,7 +96,7 @@ describe("test the update row action", () => {
tableId: InternalTable.USER_METADATA, tableId: InternalTable.USER_METADATA,
} }
let table = await config.api.table.save({ const table = await config.api.table.save({
name: uuid.v4(), name: uuid.v4(),
type: "table", type: "table",
sourceType: TableSourceType.INTERNAL, sourceType: TableSourceType.INTERNAL,
@ -82,23 +107,22 @@ describe("test the update row action", () => {
}, },
}) })
let user1 = await config.createUser() const user1 = await config.createUser()
let user2 = await config.createUser() const user2 = await config.createUser()
let row = await config.api.row.save(table._id!, { const row = await config.api.row.save(table._id!, {
user1: [{ _id: user1._id }], user1: [{ _id: user1._id }],
user2: [{ _id: user2._id }], user2: [{ _id: user2._id }],
}) })
let getResp = await config.api.row.get(table._id!, row._id!) const builder = createAutomationBuilder({
expect(getResp.user1[0]._id).toEqual(user1._id) name: "Link Preservation Automation",
expect(getResp.user2[0]._id).toEqual(user2._id) })
let stepResp = await setup.runStep( const results = await builder
config, .appAction({ fields: {} })
setup.actions.UPDATE_ROW.stepId, .updateRow({
{ rowId: row._id!,
rowId: row._id,
row: { row: {
_id: row._id, _id: row._id,
_rev: row._rev, _rev: row._rev,
@ -106,17 +130,19 @@ describe("test the update row action", () => {
user1: [user2._id], user1: [user2._id],
user2: "", user2: "",
}, },
} meta: {},
) })
expect(stepResp.success).toEqual(true) .run()
getResp = await config.api.row.get(table._id!, row._id!) expect(results.steps[0].outputs.success).toEqual(true)
const getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.user1[0]._id).toEqual(user2._id) expect(getResp.user1[0]._id).toEqual(user2._id)
expect(getResp.user2[0]._id).toEqual(user2._id) expect(getResp.user2[0]._id).toEqual(user2._id)
}) })
it("should overwrite links if those links are not set and we ask it do", async () => { it("should overwrite links if those links are not set and we ask it to", async () => {
let linkField: FieldSchema = { const linkField: FieldSchema = {
type: FieldType.LINK, type: FieldType.LINK,
name: "", name: "",
fieldName: "", fieldName: "",
@ -128,7 +154,7 @@ describe("test the update row action", () => {
tableId: InternalTable.USER_METADATA, tableId: InternalTable.USER_METADATA,
} }
let table = await config.api.table.save({ const table = await config.api.table.save({
name: uuid.v4(), name: uuid.v4(),
type: "table", type: "table",
sourceType: TableSourceType.INTERNAL, sourceType: TableSourceType.INTERNAL,
@ -139,23 +165,22 @@ describe("test the update row action", () => {
}, },
}) })
let user1 = await config.createUser() const user1 = await config.createUser()
let user2 = await config.createUser() const user2 = await config.createUser()
let row = await config.api.row.save(table._id!, { const row = await config.api.row.save(table._id!, {
user1: [{ _id: user1._id }], user1: [{ _id: user1._id }],
user2: [{ _id: user2._id }], user2: [{ _id: user2._id }],
}) })
let getResp = await config.api.row.get(table._id!, row._id!) const builder = createAutomationBuilder({
expect(getResp.user1[0]._id).toEqual(user1._id) name: "Link Overwrite Automation",
expect(getResp.user2[0]._id).toEqual(user2._id) })
let stepResp = await setup.runStep( const results = await builder
config, .appAction({ fields: {} })
setup.actions.UPDATE_ROW.stepId, .updateRow({
{ rowId: row._id!,
rowId: row._id,
row: { row: {
_id: row._id, _id: row._id,
_rev: row._rev, _rev: row._rev,
@ -170,11 +195,12 @@ describe("test the update row action", () => {
}, },
}, },
}, },
} })
) .run()
expect(stepResp.success).toEqual(true)
getResp = await config.api.row.get(table._id!, row._id!) expect(results.steps[0].outputs.success).toEqual(true)
const getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.user1[0]._id).toEqual(user2._id) expect(getResp.user1[0]._id).toEqual(user2._id)
expect(getResp.user2).toBeUndefined() expect(getResp.user2).toBeUndefined()
}) })