diff --git a/packages/backend-core/src/middleware/passport/local.js b/packages/backend-core/src/middleware/passport/local.js
index f95c3a173e..2149bd3e18 100644
--- a/packages/backend-core/src/middleware/passport/local.js
+++ b/packages/backend-core/src/middleware/passport/local.js
@@ -8,7 +8,7 @@ const { newid } = require("../../hashing")
const { createASession } = require("../../security/sessions")
const { getTenantId } = require("../../tenancy")
-const INVALID_ERR = "Invalid Credentials"
+const INVALID_ERR = "Invalid credentials"
const SSO_NO_PASSWORD = "SSO user does not have a password set"
const EXPIRED = "This account has expired. Please reset your password"
diff --git a/packages/bbui/src/ColorPicker/ColorPicker.svelte b/packages/bbui/src/ColorPicker/ColorPicker.svelte
index ff6a292d1b..1fa950fadc 100644
--- a/packages/bbui/src/ColorPicker/ColorPicker.svelte
+++ b/packages/bbui/src/ColorPicker/ColorPicker.svelte
@@ -5,7 +5,7 @@
import { fly } from "svelte/transition"
import Icon from "../Icon/Icon.svelte"
import Input from "../Form/Input.svelte"
- import { capitalise } from "../utils/helpers"
+ import { capitalise } from "../helpers"
export let value
export let size = "M"
diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte
index 69bfdda72a..c1c4cc866f 100644
--- a/packages/bbui/src/Form/Core/DatePicker.svelte
+++ b/packages/bbui/src/Form/Core/DatePicker.svelte
@@ -5,7 +5,7 @@
import "@spectrum-css/textfield/dist/index-vars.css"
import "@spectrum-css/picker/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
- import { generateID } from "../../utils/helpers"
+ import { uuid } from "../../helpers"
export let id = null
export let disabled = false
@@ -17,7 +17,7 @@
export let timeOnly = false
const dispatch = createEventDispatcher()
- const flatpickrId = `${generateID()}-wrapper`
+ const flatpickrId = `${uuid()}-wrapper`
let open = false
let flatpickr, flatpickrOptions, isTimeOnly
diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte
index 6b8022a36c..d739e751c9 100644
--- a/packages/bbui/src/Form/Core/Dropzone.svelte
+++ b/packages/bbui/src/Form/Core/Dropzone.svelte
@@ -3,7 +3,7 @@
import "@spectrum-css/typography/dist/index-vars.css"
import "@spectrum-css/illustratedmessage/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
- import { generateID } from "../../utils/helpers"
+ import { uuid } from "../../helpers"
import Icon from "../../Icon/Icon.svelte"
import Link from "../../Link/Link.svelte"
import Tag from "../../Tags/Tag.svelte"
@@ -37,7 +37,7 @@
"jfif",
]
- const fieldId = id || generateID()
+ const fieldId = id || uuid()
let selectedImageIdx = 0
let fileDragged = false
let selectedUrl
diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte
index 09ade22627..2aea31857e 100644
--- a/packages/bbui/src/Table/Table.svelte
+++ b/packages/bbui/src/Table/Table.svelte
@@ -4,7 +4,7 @@
import CellRenderer from "./CellRenderer.svelte"
import SelectEditRenderer from "./SelectEditRenderer.svelte"
import { cloneDeep } from "lodash"
- import { deepGet } from "../utils/helpers"
+ import { deepGet } from "../helpers"
/**
* The expected schema is our normal couch schemas for our tables.
diff --git a/packages/bbui/src/utils/helpers.js b/packages/bbui/src/helpers.js
similarity index 61%
rename from packages/bbui/src/utils/helpers.js
rename to packages/bbui/src/helpers.js
index 6cf432f356..e80db76537 100644
--- a/packages/bbui/src/utils/helpers.js
+++ b/packages/bbui/src/helpers.js
@@ -1,11 +1,45 @@
-export const generateID = () => {
- const rand = Math.random().toString(32).substring(2)
-
- // Starts with a letter so that its a valid DOM ID
- return `A${rand}`
+/**
+ * Generates a DOM safe UUID.
+ * Starting with a letter is important to make it DOM safe.
+ * @return {string} a random DOM safe UUID
+ */
+export function uuid() {
+ return "cxxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, c => {
+ const r = (Math.random() * 16) | 0
+ const v = c === "x" ? r : (r & 0x3) | 0x8
+ return v.toString(16)
+ })
}
-export const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
+/**
+ * Capitalises a string
+ * @param string the string to capitalise
+ * @return {string} the capitalised string
+ */
+export const capitalise = string => {
+ if (!string) {
+ return string
+ }
+ return string.substring(0, 1).toUpperCase() + string.substring(1)
+}
+
+/**
+ * Computes a short hash of a string
+ * @param string the string to compute a hash of
+ * @return {string} the hash string
+ */
+export const hashString = string => {
+ if (!string) {
+ return "0"
+ }
+ let hash = 0
+ for (let i = 0; i < string.length; i++) {
+ let char = string.charCodeAt(i)
+ hash = (hash << 5) - hash + char
+ hash = hash & hash // Convert to 32bit integer
+ }
+ return hash.toString()
+}
/**
* Gets a key within an object. The key supports dot syntax for retrieving deep
diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js
index ea5486aadc..d3bc11cf9d 100644
--- a/packages/bbui/src/index.js
+++ b/packages/bbui/src/index.js
@@ -85,5 +85,5 @@ export { default as clickOutside } from "./Actions/click_outside"
// Stores
export { notifications, createNotificationStore } from "./Stores/notifications"
-// Utils
-export * from "./utils/helpers"
+// Helpers
+export * as Helpers from "./helpers"
diff --git a/packages/builder/package.json b/packages/builder/package.json
index bc4db6629c..91e568da64 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -6,8 +6,6 @@
"scripts": {
"build": "routify -b && vite build --emptyOutDir",
"start": "routify -c rollup",
- "test": "jest",
- "test:watch": "jest --watchAll",
"dev:builder": "routify -c dev:vite",
"dev:vite": "vite --host 0.0.0.0",
"rollup": "rollup -c -w",
@@ -68,7 +66,7 @@
"dependencies": {
"@budibase/bbui": "^1.0.50-alpha.5",
"@budibase/client": "^1.0.50-alpha.5",
- "@budibase/colorpicker": "1.1.2",
+ "@budibase/frontend-core": "^1.0.50-alpha.5",
"@budibase/string-templates": "^1.0.50-alpha.5",
"@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1",
diff --git a/packages/builder/src/analytics/index.js b/packages/builder/src/analytics/index.js
index e9edf38d74..3a4118347d 100644
--- a/packages/builder/src/analytics/index.js
+++ b/packages/builder/src/analytics/index.js
@@ -1,4 +1,4 @@
-import api from "builderStore/api"
+import { API } from "api"
import PosthogClient from "./PosthogClient"
import IntercomClient from "./IntercomClient"
import SentryClient from "./SentryClient"
@@ -17,13 +17,11 @@ class AnalyticsHub {
}
async activate() {
- const analyticsStatus = await api.get("/api/analytics")
- const json = await analyticsStatus.json()
-
- // Analytics disabled
- if (!json.enabled) return
-
- this.clients.forEach(client => client.init())
+ // Check analytics are enabled
+ const analyticsStatus = await API.getAnalyticsStatus()
+ if (analyticsStatus.enabled) {
+ this.clients.forEach(client => client.init())
+ }
}
identify(id, metadata) {
diff --git a/packages/builder/src/api.js b/packages/builder/src/api.js
new file mode 100644
index 0000000000..5604db5db8
--- /dev/null
+++ b/packages/builder/src/api.js
@@ -0,0 +1,48 @@
+import {
+ createAPIClient,
+ CookieUtils,
+ Constants,
+} from "@budibase/frontend-core"
+import { store } from "./builderStore"
+import { get } from "svelte/store"
+import { auth } from "./stores/portal"
+
+export const API = createAPIClient({
+ attachHeaders: headers => {
+ // Attach app ID header from store
+ headers["x-budibase-app-id"] = get(store).appId
+
+ // Add csrf token if authenticated
+ const user = get(auth).user
+ if (user?.csrfToken) {
+ headers["x-csrf-token"] = user.csrfToken
+ }
+ },
+
+ onError: error => {
+ const { url, message, status, method, handled } = error || {}
+
+ // Log all API errors to Sentry
+ // analytics.captureException(error)
+
+ // Log any errors that we haven't manually handled
+ if (!handled) {
+ console.error("Unhandled error from API client", error)
+ return
+ }
+
+ // Log all errors to console
+ console.warn(`[Builder] HTTP ${status} on ${method}:${url}\n\t${message}`)
+
+ // Logout on 403's
+ if (status === 403) {
+ // Remove cookies
+ CookieUtils.removeCookie(Constants.Cookies.Auth)
+
+ // Reload after removing cookie, go to login
+ if (!url.includes("self") && !url.includes("login")) {
+ location.reload()
+ }
+ }
+ },
+})
diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js
deleted file mode 100644
index a932799701..0000000000
--- a/packages/builder/src/builderStore/api.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { store } from "./index"
-import { get as svelteGet } from "svelte/store"
-import { removeCookie, Cookies } from "./cookies"
-import { auth } from "stores/portal"
-
-const apiCall =
- method =>
- async (url, body, headers = { "Content-Type": "application/json" }) => {
- headers["x-budibase-app-id"] = svelteGet(store).appId
- headers["x-budibase-api-version"] = "1"
-
- // add csrf token if authenticated
- const user = svelteGet(auth).user
- if (user && user.csrfToken) {
- headers["x-csrf-token"] = user.csrfToken
- }
-
- const json = headers["Content-Type"] === "application/json"
- const resp = await fetch(url, {
- method: method,
- body: json ? JSON.stringify(body) : body,
- headers,
- })
- if (resp.status === 403) {
- if (url.includes("/api/templates")) {
- return { json: () => [] }
- }
- removeCookie(Cookies.Auth)
- // reload after removing cookie, go to login
- if (!url.includes("self") && !url.includes("login")) {
- location.reload()
- }
- }
- return resp
- }
-
-export const post = apiCall("POST")
-export const get = apiCall("GET")
-export const patch = apiCall("PATCH")
-export const del = apiCall("DELETE")
-export const put = apiCall("PUT")
-
-export default {
- post: apiCall("POST"),
- get: apiCall("GET"),
- patch: apiCall("PATCH"),
- delete: apiCall("DELETE"),
- put: apiCall("PUT"),
-}
diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js
index 0f3cffc4fb..23dede9fe2 100644
--- a/packages/builder/src/builderStore/dataBinding.js
+++ b/packages/builder/src/builderStore/dataBinding.js
@@ -15,10 +15,7 @@ import {
encodeJSBinding,
} from "@budibase/string-templates"
import { TableNames } from "../constants"
-import {
- convertJSONSchemaToTableSchema,
- getJSONArrayDatasourceSchema,
-} from "./jsonUtils"
+import { JSONUtils } from "@budibase/frontend-core"
import ActionDefinitions from "components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json"
// Regex to match all instances of template strings
@@ -439,7 +436,7 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
else if (type === "jsonarray") {
table = tables.find(table => table._id === datasource.tableId)
let tableSchema = table?.schema
- schema = getJSONArrayDatasourceSchema(tableSchema, datasource)
+ schema = JSONUtils.getJSONArrayDatasourceSchema(tableSchema, datasource)
}
// Otherwise we assume we're targeting an internal table or a plus
@@ -471,9 +468,12 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
Object.keys(schema).forEach(fieldKey => {
const fieldSchema = schema[fieldKey]
if (fieldSchema?.type === "json") {
- const jsonSchema = convertJSONSchemaToTableSchema(fieldSchema, {
- squashObjects: true,
- })
+ const jsonSchema = JSONUtils.convertJSONSchemaToTableSchema(
+ fieldSchema,
+ {
+ squashObjects: true,
+ }
+ )
Object.keys(jsonSchema).forEach(jsonKey => {
jsonAdditions[`${fieldKey}.${jsonKey}`] = {
type: jsonSchema[jsonKey].type,
diff --git a/packages/builder/src/builderStore/loadComponentLibraries.js b/packages/builder/src/builderStore/loadComponentLibraries.js
deleted file mode 100644
index 8bdfcf7538..0000000000
--- a/packages/builder/src/builderStore/loadComponentLibraries.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { get } from "builderStore/api"
-
-/**
- * Fetches the definitions for component library components. This includes
- * their props and other metadata from components.json.
- * @param {string} appId - ID of the currently running app
- */
-export const fetchComponentLibDefinitions = async appId => {
- const LIB_DEFINITION_URL = `/api/${appId}/components/definitions`
- try {
- const libDefinitionResponse = await get(LIB_DEFINITION_URL)
- return await libDefinitionResponse.json()
- } catch (err) {
- console.error(`Error fetching component definitions for ${appId}`, err)
- }
-}
diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js
index 7bd0ccca22..b901a71cb1 100644
--- a/packages/builder/src/builderStore/store/automation/index.js
+++ b/packages/builder/src/builderStore/store/automation/index.js
@@ -1,26 +1,40 @@
import { writable } from "svelte/store"
-import api from "../../api"
+import { API } from "api"
import Automation from "./Automation"
import { cloneDeep } from "lodash/fp"
import analytics, { Events } from "analytics"
+const initialAutomationState = {
+ automations: [],
+ blockDefinitions: {
+ TRIGGER: [],
+ ACTION: [],
+ },
+ selectedAutomation: null,
+}
+
+export const getAutomationStore = () => {
+ const store = writable(initialAutomationState)
+ store.actions = automationActions(store)
+ return store
+}
+
const automationActions = store => ({
fetch: async () => {
const responses = await Promise.all([
- api.get(`/api/automations`),
- api.get(`/api/automations/definitions/list`),
+ API.getAutomations(),
+ API.getAutomationDefinitions(),
])
- const jsonResponses = await Promise.all(responses.map(x => x.json()))
store.update(state => {
let selected = state.selectedAutomation?.automation
- state.automations = jsonResponses[0]
+ state.automations = responses[0]
state.blockDefinitions = {
- TRIGGER: jsonResponses[1].trigger,
- ACTION: jsonResponses[1].action,
+ TRIGGER: responses[1].trigger,
+ ACTION: responses[1].action,
}
- // if previously selected find the new obj and select it
+ // If previously selected find the new obj and select it
if (selected) {
- selected = jsonResponses[0].filter(
+ selected = responses[0].filter(
automation => automation._id === selected._id
)
state.selectedAutomation = new Automation(selected[0])
@@ -36,40 +50,36 @@ const automationActions = store => ({
steps: [],
},
}
- const CREATE_AUTOMATION_URL = `/api/automations`
- const response = await api.post(CREATE_AUTOMATION_URL, automation)
- const json = await response.json()
+ const response = await API.createAutomation(automation)
store.update(state => {
- state.automations = [...state.automations, json.automation]
- store.actions.select(json.automation)
+ state.automations = [...state.automations, response.automation]
+ store.actions.select(response.automation)
return state
})
},
save: async automation => {
- const UPDATE_AUTOMATION_URL = `/api/automations`
- const response = await api.put(UPDATE_AUTOMATION_URL, automation)
- const json = await response.json()
+ const response = await API.updateAutomation(automation)
store.update(state => {
- const newAutomation = json.automation
+ const updatedAutomation = response.automation
const existingIdx = state.automations.findIndex(
existing => existing._id === automation._id
)
if (existingIdx !== -1) {
- state.automations.splice(existingIdx, 1, newAutomation)
+ state.automations.splice(existingIdx, 1, updatedAutomation)
state.automations = [...state.automations]
- store.actions.select(newAutomation)
+ store.actions.select(updatedAutomation)
return state
}
})
},
delete: async automation => {
- const { _id, _rev } = automation
- const DELETE_AUTOMATION_URL = `/api/automations/${_id}/${_rev}`
- await api.delete(DELETE_AUTOMATION_URL)
-
+ await API.deleteAutomation({
+ automationId: automation?._id,
+ automationRev: automation?._rev,
+ })
store.update(state => {
const existingIdx = state.automations.findIndex(
- existing => existing._id === _id
+ existing => existing._id === automation?._id
)
state.automations.splice(existingIdx, 1)
state.automations = [...state.automations]
@@ -78,16 +88,17 @@ const automationActions = store => ({
return state
})
},
- trigger: async automation => {
- const { _id } = automation
- return await api.post(`/api/automations/${_id}/trigger`)
- },
test: async (automation, testData) => {
- const { _id } = automation
- const response = await api.post(`/api/automations/${_id}/test`, testData)
- const json = await response.json()
store.update(state => {
- state.selectedAutomation.testResults = json
+ state.selectedAutomation.testResults = null
+ return state
+ })
+ const result = await API.testAutomation({
+ automationId: automation?._id,
+ testData,
+ })
+ store.update(state => {
+ state.selectedAutomation.testResults = result
return state
})
},
@@ -143,17 +154,3 @@ const automationActions = store => ({
})
},
})
-
-export const getAutomationStore = () => {
- const INITIAL_AUTOMATION_STATE = {
- automations: [],
- blockDefinitions: {
- TRIGGER: [],
- ACTION: [],
- },
- selectedAutomation: null,
- }
- const store = writable(INITIAL_AUTOMATION_STATE)
- store.actions = automationActions(store)
- return store
-}
diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index d838150efc..9ce66db3c0 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -14,8 +14,7 @@ import {
database,
tables,
} from "stores/backend"
-import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
-import api from "../api"
+import { API } from "api"
import { FrontendTypes } from "constants"
import analytics, { Events } from "analytics"
import {
@@ -26,7 +25,7 @@ import {
findComponent,
getComponentSettings,
} from "../componentUtils"
-import { uuid } from "../uuid"
+import { Helpers } from "@budibase/bbui"
import { removeBindings } from "../dataBinding"
const INITIAL_FRONTEND_STATE = {
@@ -70,15 +69,12 @@ export const getFrontendStore = () => {
},
initialise: async pkg => {
const { layouts, screens, application, clientLibPath } = pkg
- const components = await fetchComponentLibDefinitions(application.appId)
- // make sure app isn't locked
- if (
- components &&
- components.status === 400 &&
- components.message?.includes("lock")
- ) {
- throw { ok: false, reason: "locked" }
- }
+
+ // Fetch component definitions.
+ // Allow errors to propagate.
+ let components = await API.fetchComponentLibDefinitions(application.appId)
+
+ // Reset store state
store.update(state => ({
...state,
libraries: application.componentLibraries,
@@ -91,8 +87,8 @@ export const getFrontendStore = () => {
description: application.description,
appId: application.appId,
url: application.url,
- layouts,
- screens,
+ layouts: layouts || [],
+ screens: screens || [],
theme: application.theme || "spectrum--light",
customTheme: application.customTheme,
hasAppPackage: true,
@@ -104,51 +100,43 @@ export const getFrontendStore = () => {
}))
// Initialise backend stores
- const [_integrations] = await Promise.all([
- api.get("/api/integrations").then(r => r.json()),
- ])
- datasources.init()
- integrations.set(_integrations)
- queries.init()
database.set(application.instance)
- tables.init()
+ await datasources.init()
+ await integrations.init()
+ await queries.init()
+ await tables.init()
},
theme: {
save: async theme => {
const appId = get(store).appId
- const response = await api.put(`/api/applications/${appId}`, { theme })
- if (response.status === 200) {
- store.update(state => {
- state.theme = theme
- return state
- })
- } else {
- throw new Error("Error updating theme")
- }
+ await API.saveAppMetadata({
+ appId,
+ metadata: { theme },
+ })
+ store.update(state => {
+ state.theme = theme
+ return state
+ })
},
},
customTheme: {
save: async customTheme => {
const appId = get(store).appId
- const response = await api.put(`/api/applications/${appId}`, {
- customTheme,
+ await API.saveAppMetadata({
+ appId,
+ metadata: { customTheme },
+ })
+ store.update(state => {
+ state.customTheme = customTheme
+ return state
})
- if (response.status === 200) {
- store.update(state => {
- state.customTheme = customTheme
- return state
- })
- } else {
- throw new Error("Error updating theme")
- }
},
},
routing: {
fetch: async () => {
- const response = await api.get("/api/routing")
- const json = await response.json()
+ const response = await API.fetchAppRoutes()
store.update(state => {
- state.routes = json.routes
+ state.routes = response.routes
return state
})
},
@@ -172,82 +160,76 @@ export const getFrontendStore = () => {
return state
})
},
- create: async screen => {
- screen = await store.actions.screens.save(screen)
- store.update(state => {
- state.selectedScreenId = screen._id
- state.selectedComponentId = screen.props._id
- state.currentFrontEndType = FrontendTypes.SCREEN
- selectedAccessRole.set(screen.routing.roleId)
- return state
- })
- return screen
- },
save: async screen => {
const creatingNewScreen = screen._id === undefined
- const response = await api.post(`/api/screens`, screen)
- if (response.status !== 200) {
- return
- }
- screen = await response.json()
- await store.actions.routing.fetch()
-
+ const savedScreen = await API.saveScreen(screen)
store.update(state => {
- const foundScreen = state.screens.findIndex(
- el => el._id === screen._id
- )
- if (foundScreen !== -1) {
- state.screens.splice(foundScreen, 1)
+ const idx = state.screens.findIndex(x => x._id === savedScreen._id)
+ if (idx !== -1) {
+ state.screens.splice(idx, 1, savedScreen)
+ } else {
+ state.screens.push(savedScreen)
}
- state.screens.push(screen)
return state
})
- if (creatingNewScreen) {
- store.actions.screens.select(screen._id)
- }
+ // Refresh routes
+ await store.actions.routing.fetch()
- return screen
+ // Select the new screen if creating a new one
+ if (creatingNewScreen) {
+ store.actions.screens.select(savedScreen._id)
+ }
+ return savedScreen
},
delete: async screens => {
const screensToDelete = Array.isArray(screens) ? screens : [screens]
- const screenDeletePromises = []
+ // Build array of promises to speed up bulk deletions
+ const promises = []
+ screensToDelete.forEach(screen => {
+ // Delete the screen
+ promises.push(
+ API.deleteScreen({
+ screenId: screen._id,
+ screenRev: screen._rev,
+ })
+ )
+ // Remove links to this screen
+ promises.push(
+ store.actions.components.links.delete(
+ screen.routing.route,
+ screen.props._instanceName
+ )
+ )
+ })
+
+ await Promise.all(promises)
+ const deletedIds = screensToDelete.map(screen => screen._id)
store.update(state => {
- for (let screenToDelete of screensToDelete) {
- state.screens = state.screens.filter(
- screen => screen._id !== screenToDelete._id
- )
- screenDeletePromises.push(
- api.delete(
- `/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
- )
- )
- if (screenToDelete._id === state.selectedScreenId) {
- state.selectedScreenId = null
- }
- //remove the link for this screen
- screenDeletePromises.push(
- store.actions.components.links.delete(
- screenToDelete.routing.route,
- screenToDelete.props._instanceName
- )
- )
+ // Remove deleted screens from state
+ state.screens = state.screens.filter(screen => {
+ return !deletedIds.includes(screen._id)
+ })
+ // Deselect the current screen if it was deleted
+ if (deletedIds.includes(state.selectedScreenId)) {
+ state.selectedScreenId = null
}
return state
})
- await Promise.all(screenDeletePromises)
+
+ // Refresh routes
+ await store.actions.routing.fetch()
},
},
preview: {
saveSelected: async () => {
const state = get(store)
const selectedAsset = get(currentAsset)
-
if (state.currentFrontEndType !== FrontendTypes.LAYOUT) {
- await store.actions.screens.save(selectedAsset)
+ return await store.actions.screens.save(selectedAsset)
} else {
- await store.actions.layouts.save(selectedAsset)
+ return await store.actions.layouts.save(selectedAsset)
}
},
setDevice: device => {
@@ -271,25 +253,13 @@ export const getFrontendStore = () => {
})
},
save: async layout => {
- const layoutToSave = cloneDeep(layout)
- const creatingNewLayout = layoutToSave._id === undefined
- const response = await api.post(`/api/layouts`, layoutToSave)
- const savedLayout = await response.json()
-
- // Abort if saving failed
- if (response.status !== 200) {
- return
- }
-
+ const creatingNewLayout = layout._id === undefined
+ const savedLayout = await API.saveLayout(layout)
store.update(state => {
- const layoutIdx = state.layouts.findIndex(
- stateLayout => stateLayout._id === savedLayout._id
- )
- if (layoutIdx >= 0) {
- // update existing layout
- state.layouts.splice(layoutIdx, 1, savedLayout)
+ const idx = state.layouts.findIndex(x => x._id === savedLayout._id)
+ if (idx !== -1) {
+ state.layouts.splice(idx, 1, savedLayout)
} else {
- // save new layout
state.layouts.push(savedLayout)
}
return state
@@ -299,7 +269,6 @@ export const getFrontendStore = () => {
if (creatingNewLayout) {
store.actions.layouts.select(savedLayout._id)
}
-
return savedLayout
},
find: layoutId => {
@@ -309,21 +278,20 @@ export const getFrontendStore = () => {
const storeContents = get(store)
return storeContents.layouts.find(layout => layout._id === layoutId)
},
- delete: async layoutToDelete => {
- const response = await api.delete(
- `/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}`
- )
- if (response.status !== 200) {
- const json = await response.json()
- throw new Error(json.message)
+ delete: async layout => {
+ if (!layout?._id) {
+ return
}
+ await API.deleteLayout({
+ layoutId: layout._id,
+ layoutRev: layout._rev,
+ })
store.update(state => {
- state.layouts = state.layouts.filter(
- layout => layout._id !== layoutToDelete._id
- )
- if (layoutToDelete._id === state.selectedLayoutId) {
+ // Select main layout if we deleted the selected layout
+ if (layout._id === state.selectedLayoutId) {
state.selectedLayoutId = get(mainLayout)._id
}
+ state.layouts = state.layouts.filter(x => x._id !== layout._id)
return state
})
},
@@ -398,7 +366,7 @@ export const getFrontendStore = () => {
}
return {
- _id: uuid(),
+ _id: Helpers.uuid(),
_component: definition.component,
_styles: { normal: {}, hover: {}, active: {} },
_instanceName: `New ${definition.name}`,
@@ -415,16 +383,12 @@ export const getFrontendStore = () => {
componentName,
presetProps
)
- if (!componentInstance) {
+ if (!componentInstance || !asset) {
return
}
// Find parent node to attach this component to
let parentComponent
-
- if (!asset) {
- return
- }
if (selected) {
// Use current screen or layout as parent if no component is selected
const definition = store.actions.components.getDefinition(
@@ -552,7 +516,7 @@ export const getFrontendStore = () => {
if (!component) {
return
}
- component._id = uuid()
+ component._id = Helpers.uuid()
component._children?.forEach(randomizeIds)
}
randomizeIds(componentToPaste)
@@ -606,11 +570,6 @@ export const getFrontendStore = () => {
selected._styles.custom = style
await store.actions.preview.saveSelected()
},
- resetStyles: async () => {
- const selected = get(selectedComponent)
- selected._styles = { normal: {}, hover: {}, active: {} }
- await store.actions.preview.saveSelected()
- },
updateConditions: async conditions => {
const selected = get(selectedComponent)
selected._conditions = conditions
@@ -665,7 +624,7 @@ export const getFrontendStore = () => {
newLink = cloneDeep(nav._children[0])
// Set our new props
- newLink._id = uuid()
+ newLink._id = Helpers.uuid()
newLink._instanceName = `${title} Link`
newLink.url = url
newLink.text = title
diff --git a/packages/builder/src/builderStore/store/notifications.js b/packages/builder/src/builderStore/store/notifications.js
deleted file mode 100644
index 85e708e92a..0000000000
--- a/packages/builder/src/builderStore/store/notifications.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { writable } from "svelte/store"
-import { generate } from "shortid"
-
-export const notificationStore = writable({
- notifications: [],
-})
-
-export function send(message, type = "default") {
- notificationStore.update(state => {
- state.notifications = [
- ...state.notifications,
- { id: generate(), type, message },
- ]
- return state
- })
-}
-
-export const notifier = {
- danger: msg => send(msg, "danger"),
- warning: msg => send(msg, "warning"),
- info: msg => send(msg, "info"),
- success: msg => send(msg, "success"),
-}
diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js
index 182736a1d5..93aa925aa6 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/utils/Component.js
@@ -1,4 +1,4 @@
-import { uuid } from "builderStore/uuid"
+import { Helpers } from "@budibase/bbui"
import { BaseStructure } from "./BaseStructure"
export class Component extends BaseStructure {
@@ -6,7 +6,7 @@ export class Component extends BaseStructure {
super(false)
this._children = []
this._json = {
- _id: uuid(),
+ _id: Helpers.uuid(),
_component: name,
_styles: {
normal: {},
diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js b/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js
index 04ff1f4ba2..272f627163 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/utils/Screen.js
@@ -1,5 +1,5 @@
import { BaseStructure } from "./BaseStructure"
-import { uuid } from "builderStore/uuid"
+import { Helpers } from "@budibase/bbui"
export class Screen extends BaseStructure {
constructor() {
@@ -7,7 +7,7 @@ export class Screen extends BaseStructure {
this._json = {
layoutId: "layout_private_master",
props: {
- _id: uuid(),
+ _id: Helpers.uuid(),
_component: "@budibase/standard-components/container",
_styles: {
normal: {},
diff --git a/packages/builder/src/builderStore/store/theme.js b/packages/builder/src/builderStore/store/theme.js
index fd6b05df59..d4d7460ed2 100644
--- a/packages/builder/src/builderStore/store/theme.js
+++ b/packages/builder/src/builderStore/store/theme.js
@@ -1,4 +1,4 @@
-import { localStorageStore } from "./localStorage"
+import { createLocalStorageStore } from "@budibase/frontend-core"
export const getThemeStore = () => {
const themeElement = document.documentElement
@@ -6,7 +6,7 @@ export const getThemeStore = () => {
theme: "darkest",
options: ["lightest", "light", "dark", "darkest"],
}
- const store = localStorageStore("bb-theme", initialValue)
+ const store = createLocalStorageStore("bb-theme", initialValue)
// Update theme class when store changes
store.subscribe(state => {
diff --git a/packages/builder/src/builderStore/uuid.js b/packages/builder/src/builderStore/uuid.js
deleted file mode 100644
index 149da83c68..0000000000
--- a/packages/builder/src/builderStore/uuid.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export function uuid() {
- // always want to make this start with a letter, as this makes it
- // easier to use with template string bindings in the client
- return "cxxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, c => {
- const r = (Math.random() * 16) | 0,
- v = c == "x" ? r : (r & 0x3) | 0x8
- return v.toString(16)
- })
-}
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte
index 5ae031e033..4e1e5e1103 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte
@@ -6,6 +6,7 @@
Body,
Icon,
Tooltip,
+ notifications,
} from "@budibase/bbui"
import { automationStore } from "builderStore"
import { admin } from "stores/portal"
@@ -47,15 +48,19 @@
}
async function addBlockToAutomation() {
- const newBlock = $automationStore.selectedAutomation.constructBlock(
- "ACTION",
- actionVal.stepId,
- actionVal
- )
- automationStore.actions.addBlockToAutomation(newBlock, blockIdx + 1)
- await automationStore.actions.save(
- $automationStore.selectedAutomation?.automation
- )
+ try {
+ const newBlock = $automationStore.selectedAutomation.constructBlock(
+ "ACTION",
+ actionVal.stepId,
+ actionVal
+ )
+ automationStore.actions.addBlockToAutomation(newBlock, blockIdx + 1)
+ await automationStore.actions.save(
+ $automationStore.selectedAutomation?.automation
+ )
+ } catch (error) {
+ notifications.error("Error saving automation")
+ }
}
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
index 2d6881d652..777fcd710a 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
@@ -30,26 +30,13 @@
}
async function deleteAutomation() {
- await automationStore.actions.delete(
- $automationStore.selectedAutomation?.automation
- )
- notifications.success("Automation deleted.")
- }
-
- async function testAutomation() {
- const result = await automationStore.actions.trigger(
- $automationStore.selectedAutomation.automation
- )
- if (result.status === 200) {
- notifications.success(
- `Automation ${$automationStore.selectedAutomation.automation.name} triggered successfully.`
- )
- } else {
- notifications.error(
- `Failed to trigger automation ${$automationStore.selectedAutomation.automation.name}.`
+ try {
+ await automationStore.actions.delete(
+ $automationStore.selectedAutomation?.automation
)
+ } catch (error) {
+ notifications.error("Error deleting automation")
}
- return result
}
@@ -85,7 +72,7 @@
animate:flip={{ duration: 500 }}
in:fly|local={{ x: 500, duration: 1500 }}
>
-
+
{/each}
@@ -101,7 +88,7 @@
-
+
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte
index fe94b7e63f..f13a827f31 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte
@@ -10,6 +10,7 @@
Button,
StatusLight,
ActionButton,
+ notifications,
} from "@budibase/bbui"
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
@@ -54,10 +55,14 @@
).every(x => block?.inputs[x])
async function deleteStep() {
- automationStore.actions.deleteAutomationBlock(block)
- await automationStore.actions.save(
- $automationStore.selectedAutomation?.automation
- )
+ try {
+ automationStore.actions.deleteAutomationBlock(block)
+ await automationStore.actions.save(
+ $automationStore.selectedAutomation?.automation
+ )
+ } catch (error) {
+ notifications.error("Error saving notification")
+ }
}
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte
index e43729edbe..ffd59b4e6a 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte
@@ -1,5 +1,12 @@
{
- automationStore.actions.test(
- $automationStore.selectedAutomation?.automation,
- testData
- )
- }}
+ onConfirm={testAutomation}
cancelText="Cancel"
>
{
- automationStore.actions.fetch()
+
+ onMount(async () => {
+ try {
+ await automationStore.actions.fetch()
+ } catch (error) {
+ notifications.error("Error getting automations list")
+ }
})
function selectAutomation(automation) {
diff --git a/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte b/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte
index 36723d7726..4fb912939a 100644
--- a/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte
+++ b/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte
@@ -24,29 +24,33 @@
nameTouched && !name ? "Please specify a name for the automation." : null
async function createAutomation() {
- await automationStore.actions.create({
- name,
- instanceId,
- })
- const newBlock = $automationStore.selectedAutomation.constructBlock(
- "TRIGGER",
- triggerVal.stepId,
- triggerVal
- )
+ try {
+ await automationStore.actions.create({
+ name,
+ instanceId,
+ })
+ const newBlock = $automationStore.selectedAutomation.constructBlock(
+ "TRIGGER",
+ triggerVal.stepId,
+ triggerVal
+ )
- automationStore.actions.addBlockToAutomation(newBlock)
- if (triggerVal.stepId === "WEBHOOK") {
- webhookModal.show
+ automationStore.actions.addBlockToAutomation(newBlock)
+ if (triggerVal.stepId === "WEBHOOK") {
+ webhookModal.show
+ }
+
+ await automationStore.actions.save(
+ $automationStore.selectedAutomation?.automation
+ )
+
+ notifications.success(`Automation ${name} created`)
+
+ $goto(`./${$automationStore.selectedAutomation.automation._id}`)
+ analytics.captureEvent(Events.AUTOMATION.CREATED, { name })
+ } catch (error) {
+ notifications.error("Error creating automation")
}
-
- await automationStore.actions.save(
- $automationStore.selectedAutomation?.automation
- )
-
- notifications.success(`Automation ${name} created.`)
-
- $goto(`./${$automationStore.selectedAutomation.automation._id}`)
- analytics.captureEvent(Events.AUTOMATION.CREATED, { name })
}
$: triggers = Object.entries($automationStore.blockDefinitions.TRIGGER)
diff --git a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte b/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte
index fc12b60676..0d858d7a19 100644
--- a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte
+++ b/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte
@@ -11,9 +11,13 @@
let updateAutomationDialog
async function deleteAutomation() {
- await automationStore.actions.delete(automation)
- notifications.success("Automation deleted.")
- $goto("../automate")
+ try {
+ await automationStore.actions.delete(automation)
+ notifications.success("Automation deleted successfully")
+ $goto("../automate")
+ } catch (error) {
+ notifications.error("Error deleting automation")
+ }
}
diff --git a/packages/builder/src/components/automation/AutomationPanel/UpdateAutomationModal.svelte b/packages/builder/src/components/automation/AutomationPanel/UpdateAutomationModal.svelte
index 64197c3a77..cc2512be8f 100644
--- a/packages/builder/src/components/automation/AutomationPanel/UpdateAutomationModal.svelte
+++ b/packages/builder/src/components/automation/AutomationPanel/UpdateAutomationModal.svelte
@@ -20,14 +20,18 @@
}
async function saveAutomation() {
- const updatedAutomation = {
- ...automation,
- name,
+ try {
+ const updatedAutomation = {
+ ...automation,
+ name,
+ }
+ await automationStore.actions.save(updatedAutomation)
+ notifications.success(`Automation ${name} updated successfully`)
+ analytics.captureEvent(Events.AUTOMATION.SAVED, { name })
+ hide()
+ } catch (error) {
+ notifications.error("Error saving automation")
}
- await automationStore.actions.save(updatedAutomation)
- notifications.success(`Automation ${name} updated successfully.`)
- analytics.captureEvent(Events.AUTOMATION.SAVED, { name })
- hide()
}
function checkValid(evt) {
diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
index 928897d6f5..64b7cff78d 100644
--- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
@@ -11,6 +11,7 @@
Drawer,
Modal,
Detail,
+ notifications,
} from "@budibase/bbui"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
@@ -28,7 +29,7 @@
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import FilterDrawer from "components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte"
// need the client lucene builder to convert to the structure API expects
- import { buildLuceneQuery } from "helpers/lucene"
+ import { LuceneUtils } from "@budibase/frontend-core"
export let block
export let testData
@@ -54,28 +55,32 @@
$: schemaFields = table ? Object.values(table.schema) : []
const onChange = debounce(async function (e, key) {
- if (isTestModal) {
- // Special case for webhook, as it requires a body, but the schema already brings back the body's contents
- if (stepId === "WEBHOOK") {
+ try {
+ if (isTestModal) {
+ // Special case for webhook, as it requires a body, but the schema already brings back the body's contents
+ if (stepId === "WEBHOOK") {
+ automationStore.actions.addTestDataToAutomation({
+ body: {
+ [key]: e.detail,
+ ...$automationStore.selectedAutomation.automation.testData.body,
+ },
+ })
+ }
automationStore.actions.addTestDataToAutomation({
- body: {
- [key]: e.detail,
- ...$automationStore.selectedAutomation.automation.testData.body,
- },
+ [key]: e.detail,
})
+ testData[key] = e.detail
+ await automationStore.actions.save(
+ $automationStore.selectedAutomation?.automation
+ )
+ } else {
+ block.inputs[key] = e.detail
+ await automationStore.actions.save(
+ $automationStore.selectedAutomation?.automation
+ )
}
- automationStore.actions.addTestDataToAutomation({
- [key]: e.detail,
- })
- testData[key] = e.detail
- await automationStore.actions.save(
- $automationStore.selectedAutomation?.automation
- )
- } else {
- block.inputs[key] = e.detail
- await automationStore.actions.save(
- $automationStore.selectedAutomation?.automation
- )
+ } catch (error) {
+ notifications.error("Error saving automation")
}
}, 800)
@@ -131,7 +136,7 @@
}
function saveFilters(key) {
- const filters = buildLuceneQuery(tempFilters)
+ const filters = LuceneUtils.buildLuceneQuery(tempFilters)
const defKey = `${key}-def`
inputData[key] = filters
inputData[defKey] = tempFilters
diff --git a/packages/builder/src/components/automation/Shared/CreateWebhookModal.svelte b/packages/builder/src/components/automation/Shared/CreateWebhookModal.svelte
index 20dd696981..d8bc7fca3b 100644
--- a/packages/builder/src/components/automation/Shared/CreateWebhookModal.svelte
+++ b/packages/builder/src/components/automation/Shared/CreateWebhookModal.svelte
@@ -1,5 +1,5 @@
@@ -91,9 +96,9 @@
schema={enrichedSchema}
{type}
tableId={id}
- data={$search.rows}
+ data={$fetch.rows}
bind:hideAutocolumns
- loading={$search.loading}
+ loading={$fetch.loading}
on:sort={onSort}
allowEditing
disableSorting
@@ -138,11 +143,11 @@
diff --git a/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte b/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte
index a0a06d1866..8ef870caca 100644
--- a/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte
@@ -1,7 +1,8 @@
diff --git a/packages/builder/src/components/backend/DataTable/Table.svelte b/packages/builder/src/components/backend/DataTable/Table.svelte
index 3c646bde68..0333bad611 100644
--- a/packages/builder/src/components/backend/DataTable/Table.svelte
+++ b/packages/builder/src/components/backend/DataTable/Table.svelte
@@ -2,7 +2,7 @@
import { fade } from "svelte/transition"
import { goto, params } from "@roxi/routify"
import { Table, Modal, Heading, notifications, Layout } from "@budibase/bbui"
- import api from "builderStore/api"
+ import { API } from "api"
import Spinner from "components/common/Spinner.svelte"
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
import CreateEditRow from "./modals/CreateEditRow.svelte"
@@ -88,12 +88,17 @@
}
const deleteRows = async () => {
- await api.delete(`/api/${tableId}/rows`, {
- rows: selectedRows,
- })
- data = data.filter(row => !selectedRows.includes(row))
- notifications.success(`Successfully deleted ${selectedRows.length} rows`)
- selectedRows = []
+ try {
+ await API.deleteRows({
+ tableId,
+ rows: selectedRows,
+ })
+ data = data.filter(row => !selectedRows.includes(row))
+ notifications.success(`Successfully deleted ${selectedRows.length} rows`)
+ selectedRows = []
+ } catch (error) {
+ notifications.error("Error deleting rows")
+ }
}
const editRow = row => {
diff --git a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
index a52fbdb177..10c6703623 100644
--- a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
@@ -1,5 +1,5 @@
diff --git a/packages/builder/src/components/backend/DataTable/api.js b/packages/builder/src/components/backend/DataTable/api.js
deleted file mode 100644
index b461c70c4b..0000000000
--- a/packages/builder/src/components/backend/DataTable/api.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import api from "builderStore/api"
-
-export async function createUser(user) {
- const CREATE_USER_URL = `/api/users/metadata`
- const response = await api.post(CREATE_USER_URL, user)
- return await response.json()
-}
-
-export async function saveRow(row, tableId) {
- const SAVE_ROW_URL = `/api/${tableId}/rows`
- const response = await api.post(SAVE_ROW_URL, row)
-
- return await response.json()
-}
-
-export async function deleteRow(row) {
- const DELETE_ROWS_URL = `/api/${row.tableId}/rows`
- return api.delete(DELETE_ROWS_URL, {
- _id: row._id,
- _rev: row._rev,
- })
-}
-
-export async function fetchDataForTable(tableId) {
- const FETCH_ROWS_URL = `/api/${tableId}/rows`
-
- const response = await api.get(FETCH_ROWS_URL)
- const json = await response.json()
-
- if (response.status !== 200) {
- throw new Error(json.message)
- }
- return json
-}
diff --git a/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte
index 50d44eca88..81f54032f6 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte
@@ -38,9 +38,13 @@
})
function saveView() {
- views.save(view)
- notifications.success(`View ${view.name} saved.`)
- analytics.captureEvent(Events.VIEW.ADDED_CALCULATE, { field: view.field })
+ try {
+ views.save(view)
+ notifications.success(`View ${view.name} saved`)
+ analytics.captureEvent(Events.VIEW.ADDED_CALCULATE, { field: view.field })
+ } catch (error) {
+ notifications.error("Error saving view")
+ }
}
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index edac352f8a..c946dbf9d8 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -124,7 +124,7 @@
})
dispatch("updatecolumns")
} catch (err) {
- notifications.error(err)
+ notifications.error("Error saving column")
}
}
@@ -133,17 +133,21 @@
}
function deleteColumn() {
- field.name = deleteColName
- if (field.name === $tables.selected.primaryDisplay) {
- notifications.error("You cannot delete the display column")
- } else {
- tables.deleteField(field)
- notifications.success(`Column ${field.name} deleted.`)
- confirmDeleteDialog.hide()
- hide()
- deletion = false
+ try {
+ field.name = deleteColName
+ if (field.name === $tables.selected.primaryDisplay) {
+ notifications.error("You cannot delete the display column")
+ } else {
+ tables.deleteField(field)
+ notifications.success(`Column ${field.name} deleted.`)
+ confirmDeleteDialog.hide()
+ hide()
+ deletion = false
+ dispatch("updatecolumns")
+ }
+ } catch (error) {
+ notifications.error("Error deleting column")
}
- dispatch("updatecolumns")
}
function handleTypeChange(event) {
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditRow.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditRow.svelte
index 559e8275db..965458e297 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditRow.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditRow.svelte
@@ -3,7 +3,7 @@
import { tables, rows } from "stores/backend"
import { notifications } from "@budibase/bbui"
import RowFieldControl from "../RowFieldControl.svelte"
- import * as api from "../api"
+ import { API } from "api"
import { ModalContent } from "@budibase/bbui"
import ErrorsBox from "components/common/ErrorsBox.svelte"
import { FIELDS } from "constants/backend"
@@ -22,30 +22,30 @@
$: tableSchema = Object.entries(table?.schema ?? {})
async function saveRow() {
- const rowResponse = await api.saveRow(
- { ...row, tableId: table._id },
- table._id
- )
-
- if (rowResponse.errors) {
- errors = Object.entries(rowResponse.errors)
- .map(([key, error]) => ({ dataPath: key, message: error }))
- .flat()
+ errors = []
+ try {
+ await API.saveRow({ ...row, tableId: table._id })
+ notifications.success("Row saved successfully")
+ rows.save()
+ dispatch("updaterows")
+ } catch (error) {
+ if (error.handled) {
+ const response = error.json
+ if (response?.errors) {
+ errors = Object.entries(response.errors)
+ .map(([key, error]) => ({ dataPath: key, message: error }))
+ .flat()
+ } else if (error.status === 400 && response?.validationErrors) {
+ errors = Object.keys(response.validationErrors).map(field => ({
+ message: `${field} ${response.validationErrors[field][0]}`,
+ }))
+ }
+ } else {
+ notifications.error("Failed to save row")
+ }
// Prevent modal closing if there were errors
return false
- } else if (rowResponse.status === 400 && rowResponse.validationErrors) {
- errors = Object.keys(rowResponse.validationErrors).map(field => ({
- message: `${field} ${rowResponse.validationErrors[field][0]}`,
- }))
- return false
- } else if (rowResponse.status >= 400) {
- errors = [{ message: rowResponse.message }]
- return false
}
-
- notifications.success("Row saved successfully.")
- rows.save(rowResponse)
- dispatch("updaterows")
}
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte
index f1de23fb97..088280e266 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte
@@ -4,7 +4,7 @@
import { roles } from "stores/backend"
import { notifications } from "@budibase/bbui"
import RowFieldControl from "../RowFieldControl.svelte"
- import * as backendApi from "../api"
+ import { API } from "api"
import { ModalContent, Select } from "@budibase/bbui"
import ErrorsBox from "components/common/ErrorsBox.svelte"
@@ -53,27 +53,31 @@
return false
}
- const rowResponse = await backendApi.saveRow(
- { ...row, tableId: table._id },
- table._id
- )
- if (rowResponse.errors) {
- if (Array.isArray(rowResponse.errors)) {
- errors = rowResponse.errors.map(error => ({ message: error }))
+ try {
+ await API.saveRow({ ...row, tableId: table._id })
+ notifications.success("User saved successfully")
+ rows.save()
+ dispatch("updaterows")
+ } catch (error) {
+ if (error.handled) {
+ const response = error.json
+ if (response?.errors) {
+ if (Array.isArray(response.errors)) {
+ errors = response.errors.map(error => ({ message: error }))
+ } else {
+ errors = Object.entries(response.errors)
+ .map(([key, error]) => ({ dataPath: key, message: error }))
+ .flat()
+ }
+ } else if (error.status === 400) {
+ errors = [{ message: response?.message || "Unknown error" }]
+ }
} else {
- errors = Object.entries(rowResponse.errors)
- .map(([key, error]) => ({ dataPath: key, message: error }))
- .flat()
+ notifications.error("Error saving user")
}
- return false
- } else if (rowResponse.status === 400 || rowResponse.status === 500) {
- errors = [{ message: rowResponse.message }]
+ // Prevent closing the modal on errors
return false
}
-
- notifications.success("User saved successfully")
- rows.save(rowResponse)
- dispatch("updaterows")
}
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte
index 2f6ec51233..2ea0c1b63b 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte
@@ -12,17 +12,21 @@
function saveView() {
if (views.includes(name)) {
- notifications.error(`View exists with name ${name}.`)
+ notifications.error(`View exists with name ${name}`)
return
}
- viewsStore.save({
- name,
- tableId: $tables.selected._id,
- field,
- })
- notifications.success(`View ${name} created`)
- analytics.captureEvent(Events.VIEW.CREATED, { name })
- $goto(`../../view/${name}`)
+ try {
+ viewsStore.save({
+ name,
+ tableId: $tables.selected._id,
+ field,
+ })
+ notifications.success(`View ${name} created`)
+ analytics.captureEvent(Events.VIEW.CREATED, { name })
+ $goto(`../../view/${name}`)
+ } catch (error) {
+ notifications.error("Error creating view")
+ }
}
diff --git a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte
index 7fa9482fbe..e2ccab11af 100644
--- a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte
@@ -1,7 +1,7 @@
diff --git a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte
index c413ee16ce..d7976cd387 100644
--- a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte
@@ -72,11 +72,15 @@
$: schema = viewTable && viewTable.schema ? viewTable.schema : {}
function saveView() {
- views.save(view)
- notifications.success(`View ${view.name} saved.`)
- analytics.captureEvent(Events.VIEW.ADDED_FILTER, {
- filters: JSON.stringify(view.filters),
- })
+ try {
+ views.save(view)
+ notifications.success(`View ${view.name} saved`)
+ analytics.captureEvent(Events.VIEW.ADDED_FILTER, {
+ filters: JSON.stringify(view.filters),
+ })
+ } catch (error) {
+ notifications.error("Error saving view")
+ }
}
function removeFilter(idx) {
@@ -158,7 +162,7 @@