Update parts of builder with core API refactor
This commit is contained in:
parent
55f07340e9
commit
2e73a693db
|
@ -1,8 +1,9 @@
|
||||||
import api from "builderStore/api"
|
import { API } from "api"
|
||||||
import PosthogClient from "./PosthogClient"
|
import PosthogClient from "./PosthogClient"
|
||||||
import IntercomClient from "./IntercomClient"
|
import IntercomClient from "./IntercomClient"
|
||||||
import SentryClient from "./SentryClient"
|
import SentryClient from "./SentryClient"
|
||||||
import { Events } from "./constants"
|
import { Events } from "./constants"
|
||||||
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
const posthog = new PosthogClient(
|
const posthog = new PosthogClient(
|
||||||
process.env.POSTHOG_TOKEN,
|
process.env.POSTHOG_TOKEN,
|
||||||
|
@ -17,13 +18,15 @@ class AnalyticsHub {
|
||||||
}
|
}
|
||||||
|
|
||||||
async activate() {
|
async activate() {
|
||||||
const analyticsStatus = await api.get("/api/analytics")
|
try {
|
||||||
const json = await analyticsStatus.json()
|
// Check analytics are enabled
|
||||||
|
const analyticsStatus = await API.getAnalyticsStatus()
|
||||||
// Analytics disabled
|
if (analyticsStatus.enabled) {
|
||||||
if (!json.enabled) return
|
this.clients.forEach(client => client.init())
|
||||||
|
}
|
||||||
this.clients.forEach(client => client.init())
|
} catch (error) {
|
||||||
|
notifications.error("Error checking analytics status")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identify(id, metadata) {
|
identify(id, metadata) {
|
||||||
|
|
|
@ -1,40 +1,44 @@
|
||||||
|
import {
|
||||||
|
createAPIClient,
|
||||||
|
CookieUtils,
|
||||||
|
Constants,
|
||||||
|
} from "@budibase/frontend-core"
|
||||||
import { store } from "./index"
|
import { store } from "./index"
|
||||||
import { get as svelteGet } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { CookieUtils, Constants } from "@budibase/frontend-core"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
const apiCall =
|
export const API = createAPIClient({
|
||||||
method =>
|
attachHeaders: headers => {
|
||||||
async (url, body, headers = { "Content-Type": "application/json" }) => {
|
// Attach app ID header from store
|
||||||
headers["x-budibase-app-id"] = svelteGet(store).appId
|
headers["x-budibase-app-id"] = get(store).appId
|
||||||
const json = headers["Content-Type"] === "application/json"
|
},
|
||||||
const resp = await fetch(url, {
|
|
||||||
method: method,
|
onError: error => {
|
||||||
body: json ? JSON.stringify(body) : body,
|
const { url, message, status } = error
|
||||||
headers,
|
|
||||||
})
|
// Log all API errors to Sentry
|
||||||
if (resp.status === 403) {
|
// analytics.captureException(error)
|
||||||
|
|
||||||
|
// Show a notification for any errors
|
||||||
|
if (message) {
|
||||||
|
notifications.error(`Error fetching ${url}: ${message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout on 403's
|
||||||
|
if (status === 403) {
|
||||||
|
// Don't do anything if fetching templates.
|
||||||
|
// TODO: clarify why this is here
|
||||||
if (url.includes("/api/templates")) {
|
if (url.includes("/api/templates")) {
|
||||||
return { json: () => [] }
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the auth cookie
|
||||||
CookieUtils.removeCookie(Constants.Cookies.Auth)
|
CookieUtils.removeCookie(Constants.Cookies.Auth)
|
||||||
// reload after removing cookie, go to login
|
|
||||||
|
// Reload after removing cookie, go to login
|
||||||
if (!url.includes("self") && !url.includes("login")) {
|
if (!url.includes("self") && !url.includes("login")) {
|
||||||
location.reload()
|
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"),
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,8 +15,7 @@ import {
|
||||||
database,
|
database,
|
||||||
tables,
|
tables,
|
||||||
} from "stores/backend"
|
} from "stores/backend"
|
||||||
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
|
import { API } from "api"
|
||||||
import api from "../api"
|
|
||||||
import { FrontendTypes } from "constants"
|
import { FrontendTypes } from "constants"
|
||||||
import analytics, { Events } from "analytics"
|
import analytics, { Events } from "analytics"
|
||||||
import {
|
import {
|
||||||
|
@ -29,6 +28,7 @@ import {
|
||||||
} from "../componentUtils"
|
} from "../componentUtils"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { removeBindings } from "../dataBinding"
|
import { removeBindings } from "../dataBinding"
|
||||||
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
const INITIAL_FRONTEND_STATE = {
|
const INITIAL_FRONTEND_STATE = {
|
||||||
apps: [],
|
apps: [],
|
||||||
|
@ -68,15 +68,12 @@ export const getFrontendStore = () => {
|
||||||
store.actions = {
|
store.actions = {
|
||||||
initialise: async pkg => {
|
initialise: async pkg => {
|
||||||
const { layouts, screens, application, clientLibPath } = pkg
|
const { layouts, screens, application, clientLibPath } = pkg
|
||||||
const components = await fetchComponentLibDefinitions(application.appId)
|
|
||||||
// make sure app isn't locked
|
// Fetch component definitions.
|
||||||
if (
|
// Allow errors to propagate.
|
||||||
components &&
|
let components = await API.fetchComponentLibDefinitions(application.appId)
|
||||||
components.status === 400 &&
|
|
||||||
components.message?.includes("lock")
|
// Reset store state
|
||||||
) {
|
|
||||||
throw { ok: false, reason: "locked" }
|
|
||||||
}
|
|
||||||
store.update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
libraries: application.componentLibraries,
|
libraries: application.componentLibraries,
|
||||||
|
@ -100,56 +97,57 @@ export const getFrontendStore = () => {
|
||||||
version: application.version,
|
version: application.version,
|
||||||
revertableVersion: application.revertableVersion,
|
revertableVersion: application.revertableVersion,
|
||||||
}))
|
}))
|
||||||
await hostingStore.actions.fetch()
|
|
||||||
|
|
||||||
// Initialise backend stores
|
// 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)
|
database.set(application.instance)
|
||||||
tables.init()
|
await hostingStore.actions.fetch()
|
||||||
|
await datasources.init()
|
||||||
|
await integrations.init()
|
||||||
|
await queries.init()
|
||||||
|
await tables.init()
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
save: async theme => {
|
save: async theme => {
|
||||||
const appId = get(store).appId
|
const appId = get(store).appId
|
||||||
const response = await api.put(`/api/applications/${appId}`, { theme })
|
const metadata = { appId, theme }
|
||||||
if (response.status === 200) {
|
try {
|
||||||
|
await API.saveAppMetadata(metadata)
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.theme = theme
|
state.theme = theme
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} catch (error) {
|
||||||
throw new Error("Error updating theme")
|
notifications.error("Error updating theme")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
customTheme: {
|
customTheme: {
|
||||||
save: async customTheme => {
|
save: async customTheme => {
|
||||||
const appId = get(store).appId
|
const appId = get(store).appId
|
||||||
const response = await api.put(`/api/applications/${appId}`, {
|
const metadata = { appId, customTheme }
|
||||||
customTheme,
|
try {
|
||||||
})
|
await API.saveAppMetadata(metadata)
|
||||||
if (response.status === 200) {
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.customTheme = customTheme
|
state.customTheme = customTheme
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} catch (error) {
|
||||||
throw new Error("Error updating theme")
|
notifications.error("Error updating custom theme")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
routing: {
|
routing: {
|
||||||
fetch: async () => {
|
fetch: async () => {
|
||||||
const response = await api.get("/api/routing")
|
try {
|
||||||
const json = await response.json()
|
const routes = await API.getAppRoutes()
|
||||||
store.update(state => {
|
console.log(routes)
|
||||||
state.routes = json.routes
|
store.update(state => {
|
||||||
return state
|
state.routes = routes.routes
|
||||||
})
|
return state
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Error fetching app routes")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
|
@ -172,81 +170,100 @@ export const getFrontendStore = () => {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
create: async screen => {
|
create: async screen => {
|
||||||
screen = await store.actions.screens.save(screen)
|
try {
|
||||||
store.update(state => {
|
const savedScreen = await API.saveScreen(screen)
|
||||||
state.selectedScreenId = screen._id
|
store.update(state => {
|
||||||
state.selectedComponentId = screen.props._id
|
state.selectedScreenId = savedScreen._id
|
||||||
state.currentFrontEndType = FrontendTypes.SCREEN
|
state.selectedComponentId = savedScreen.props._id
|
||||||
selectedAccessRole.set(screen.routing.roleId)
|
state.currentFrontEndType = FrontendTypes.SCREEN
|
||||||
return state
|
selectedAccessRole.set(savedScreen.routing.roleId)
|
||||||
})
|
return savedScreen
|
||||||
return screen
|
})
|
||||||
|
|
||||||
|
// Refresh routes
|
||||||
|
await store.actions.routing.fetch()
|
||||||
|
return savedScreen
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Error creating screen")
|
||||||
|
return null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
save: async screen => {
|
save: async screen => {
|
||||||
const creatingNewScreen = screen._id === undefined
|
try {
|
||||||
const response = await api.post(`/api/screens`, screen)
|
const creatingNewScreen = screen._id === undefined
|
||||||
if (response.status !== 200) {
|
const savedScreen = await API.saveScreen(screen)
|
||||||
return
|
store.update(state => {
|
||||||
}
|
const idx = state.screens.findIndex(x => x._id === savedScreen._id)
|
||||||
screen = await response.json()
|
if (idx !== -1) {
|
||||||
await store.actions.routing.fetch()
|
state.screens.splice(idx, 1, savedScreen)
|
||||||
|
} else {
|
||||||
|
state.screens.push(savedScreen)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
|
||||||
store.update(state => {
|
// Refresh routes
|
||||||
const foundScreen = state.screens.findIndex(
|
await store.actions.routing.fetch()
|
||||||
el => el._id === screen._id
|
|
||||||
)
|
// Select the new screen if creating a new one
|
||||||
if (foundScreen !== -1) {
|
if (creatingNewScreen) {
|
||||||
state.screens.splice(foundScreen, 1)
|
store.actions.screens.select(savedScreen._id)
|
||||||
}
|
}
|
||||||
state.screens.push(screen)
|
return savedScreen
|
||||||
return state
|
} catch (error) {
|
||||||
})
|
notifications.error("Error saving screen")
|
||||||
|
return null
|
||||||
if (creatingNewScreen) {
|
|
||||||
store.actions.screens.select(screen._id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return screen
|
|
||||||
},
|
},
|
||||||
delete: async screens => {
|
delete: async screens => {
|
||||||
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
const screensToDelete = Array.isArray(screens) ? screens : [screens]
|
||||||
|
|
||||||
const screenDeletePromises = []
|
// Build array of promises to speed up bulk deletions
|
||||||
store.update(state => {
|
const promises = []
|
||||||
for (let screenToDelete of screensToDelete) {
|
screensToDelete.forEach(screen => {
|
||||||
state.screens = state.screens.filter(
|
// Delete the screen
|
||||||
screen => screen._id !== screenToDelete._id
|
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
|
||||||
)
|
)
|
||||||
screenDeletePromises.push(
|
)
|
||||||
api.delete(
|
})
|
||||||
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
|
|
||||||
)
|
try {
|
||||||
)
|
await Promise.all(promises)
|
||||||
if (screenToDelete._id === state.selectedScreenId) {
|
const deletedIds = screensToDelete.map(screen => screen._id)
|
||||||
|
store.update(state => {
|
||||||
|
// 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
|
state.selectedScreenId = null
|
||||||
}
|
}
|
||||||
//remove the link for this screen
|
return state
|
||||||
screenDeletePromises.push(
|
})
|
||||||
store.actions.components.links.delete(
|
} catch (error) {
|
||||||
screenToDelete.routing.route,
|
notifications.error("Error deleting screens")
|
||||||
screenToDelete.props._instanceName
|
}
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
await Promise.all(screenDeletePromises)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
saveSelected: async () => {
|
saveSelected: async () => {
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
const selectedAsset = get(currentAsset)
|
const selectedAsset = get(currentAsset)
|
||||||
|
|
||||||
if (state.currentFrontEndType !== FrontendTypes.LAYOUT) {
|
if (state.currentFrontEndType !== FrontendTypes.LAYOUT) {
|
||||||
await store.actions.screens.save(selectedAsset)
|
return await store.actions.screens.save(selectedAsset)
|
||||||
} else {
|
} else {
|
||||||
await store.actions.layouts.save(selectedAsset)
|
return await store.actions.layouts.save(selectedAsset)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setDevice: device => {
|
setDevice: device => {
|
||||||
|
@ -270,36 +287,28 @@ export const getFrontendStore = () => {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
save: async layout => {
|
save: async layout => {
|
||||||
const layoutToSave = cloneDeep(layout)
|
try {
|
||||||
const creatingNewLayout = layoutToSave._id === undefined
|
const creatingNewLayout = layout._id === undefined
|
||||||
const response = await api.post(`/api/layouts`, layoutToSave)
|
const savedLayout = await API.saveLayout(layout)
|
||||||
const savedLayout = await response.json()
|
store.update(state => {
|
||||||
|
const idx = state.layouts.findIndex(x => x._id === savedLayout._id)
|
||||||
|
if (idx !== -1) {
|
||||||
|
state.layouts.splice(idx, 1, savedLayout)
|
||||||
|
} else {
|
||||||
|
state.layouts.push(savedLayout)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
|
||||||
// Abort if saving failed
|
// Select layout if creating a new one
|
||||||
if (response.status !== 200) {
|
if (creatingNewLayout) {
|
||||||
return
|
store.actions.layouts.select(savedLayout._id)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
} else {
|
|
||||||
// save new layout
|
|
||||||
state.layouts.push(savedLayout)
|
|
||||||
}
|
}
|
||||||
return state
|
return savedLayout
|
||||||
})
|
} catch (error) {
|
||||||
|
notifications.error("Error saving layout")
|
||||||
// Select layout if creating a new one
|
return null
|
||||||
if (creatingNewLayout) {
|
|
||||||
store.actions.layouts.select(savedLayout._id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return savedLayout
|
|
||||||
},
|
},
|
||||||
find: layoutId => {
|
find: layoutId => {
|
||||||
if (!layoutId) {
|
if (!layoutId) {
|
||||||
|
@ -308,23 +317,26 @@ export const getFrontendStore = () => {
|
||||||
const storeContents = get(store)
|
const storeContents = get(store)
|
||||||
return storeContents.layouts.find(layout => layout._id === layoutId)
|
return storeContents.layouts.find(layout => layout._id === layoutId)
|
||||||
},
|
},
|
||||||
delete: async layoutToDelete => {
|
delete: async layout => {
|
||||||
const response = await api.delete(
|
if (!layout?._id) {
|
||||||
`/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}`
|
return
|
||||||
)
|
}
|
||||||
if (response.status !== 200) {
|
try {
|
||||||
const json = await response.json()
|
await API.deleteLayout({
|
||||||
throw new Error(json.message)
|
layoutId: layout._id,
|
||||||
|
layoutRev: layout._rev,
|
||||||
|
})
|
||||||
|
store.update(state => {
|
||||||
|
// 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
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Failed to delete layout")
|
||||||
}
|
}
|
||||||
store.update(state => {
|
|
||||||
state.layouts = state.layouts.filter(
|
|
||||||
layout => layout._id !== layoutToDelete._id
|
|
||||||
)
|
|
||||||
if (layoutToDelete._id === state.selectedLayoutId) {
|
|
||||||
state.selectedLayoutId = get(mainLayout)._id
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
@ -414,16 +426,12 @@ export const getFrontendStore = () => {
|
||||||
componentName,
|
componentName,
|
||||||
presetProps
|
presetProps
|
||||||
)
|
)
|
||||||
if (!componentInstance) {
|
if (!componentInstance || !asset) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find parent node to attach this component to
|
// Find parent node to attach this component to
|
||||||
let parentComponent
|
let parentComponent
|
||||||
|
|
||||||
if (!asset) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
// Use current screen or layout as parent if no component is selected
|
// Use current screen or layout as parent if no component is selected
|
||||||
const definition = store.actions.components.getDefinition(
|
const definition = store.actions.components.getDefinition(
|
||||||
|
@ -451,19 +459,24 @@ export const getFrontendStore = () => {
|
||||||
parentComponent._children.push(componentInstance)
|
parentComponent._children.push(componentInstance)
|
||||||
|
|
||||||
// Save components and update UI
|
// Save components and update UI
|
||||||
await store.actions.preview.saveSelected()
|
const savedAsset = await store.actions.preview.saveSelected()
|
||||||
store.update(state => {
|
if (savedAsset) {
|
||||||
state.currentView = "component"
|
store.update(state => {
|
||||||
state.selectedComponentId = componentInstance._id
|
state.currentView = "component"
|
||||||
return state
|
state.selectedComponentId = componentInstance._id
|
||||||
})
|
return state
|
||||||
|
})
|
||||||
|
|
||||||
// Log event
|
// Log event
|
||||||
analytics.captureEvent(Events.COMPONENT.CREATED, {
|
analytics.captureEvent(Events.COMPONENT.CREATED, {
|
||||||
name: componentInstance._component,
|
name: componentInstance._component,
|
||||||
})
|
})
|
||||||
|
|
||||||
return componentInstance
|
return componentInstance
|
||||||
|
} else {
|
||||||
|
notifications.error("Failed to create component")
|
||||||
|
return null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
delete: async component => {
|
delete: async component => {
|
||||||
if (!component) {
|
if (!component) {
|
||||||
|
|
|
@ -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"),
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
|
||||||
import api from "builderStore/api"
|
import { API } from "api"
|
||||||
import analytics, { Events } from "analytics"
|
import analytics, { Events } from "analytics"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
@ -9,18 +9,14 @@
|
||||||
|
|
||||||
async function deployApp() {
|
async function deployApp() {
|
||||||
try {
|
try {
|
||||||
const response = await api.post("/api/deploy")
|
await API.deployApp()
|
||||||
if (response.status !== 200) {
|
analytics.captureEvent(Events.APP.PUBLISHED, {
|
||||||
throw new Error(`status ${response.status}`)
|
appId: $store.appId,
|
||||||
} else {
|
})
|
||||||
analytics.captureEvent(Events.APP.PUBLISHED, {
|
notifications.success("Application published successfully")
|
||||||
appId: $store.appId,
|
} catch (error) {
|
||||||
})
|
analytics.captureException(error)
|
||||||
notifications.success(`Application published successfully`)
|
notifications.error("Error publishing app")
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
analytics.captureException(err)
|
|
||||||
notifications.error(`Error publishing app: ${err}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// This will eventually be replaced by the new client implementation when we
|
// This will eventually be replaced by the new client implementation when we
|
||||||
// add a core package.
|
// add a core package.
|
||||||
import { writable, derived, get } from "svelte/store"
|
import { writable, derived, get } from "svelte/store"
|
||||||
import * as API from "../builderStore/api"
|
import { API } from "api"
|
||||||
import { LuceneUtils } from "@budibase/frontend-core"
|
import { LuceneUtils } from "@budibase/frontend-core"
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
|
@ -50,7 +50,7 @@ export const fetchTableData = opts => {
|
||||||
const fetchPage = async bookmark => {
|
const fetchPage = async bookmark => {
|
||||||
lastBookmark = bookmark
|
lastBookmark = bookmark
|
||||||
const { tableId, limit, sortColumn, sortOrder, paginate } = options
|
const { tableId, limit, sortColumn, sortOrder, paginate } = options
|
||||||
const res = await API.post(`/api/${options.tableId}/search`, {
|
return await API.searchTable({
|
||||||
tableId,
|
tableId,
|
||||||
query,
|
query,
|
||||||
limit,
|
limit,
|
||||||
|
@ -60,7 +60,6 @@ export const fetchTableData = opts => {
|
||||||
paginate,
|
paginate,
|
||||||
bookmark,
|
bookmark,
|
||||||
})
|
})
|
||||||
return await res.json()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches a fresh set of results from the server
|
// Fetches a fresh set of results from the server
|
||||||
|
@ -77,7 +76,7 @@ export const fetchTableData = opts => {
|
||||||
// are objects
|
// are objects
|
||||||
let enrichedSchema = schema
|
let enrichedSchema = schema
|
||||||
if (!enrichedSchema) {
|
if (!enrichedSchema) {
|
||||||
const definition = await API.get(`/api/tables/${tableId}`)
|
const definition = await API.fetchTableDefinition(tableId)
|
||||||
enrichedSchema = definition?.schema ?? null
|
enrichedSchema = definition?.schema ?? null
|
||||||
}
|
}
|
||||||
if (enrichedSchema) {
|
if (enrichedSchema) {
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
import { API } from "api"
|
||||||
|
|
||||||
export const integrations = writable({})
|
const createIntegrationsStore = () => {
|
||||||
|
const store = writable(null)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...store,
|
||||||
|
init: async () => {
|
||||||
|
try {
|
||||||
|
const integrations = await API.getIntegrations()
|
||||||
|
store.set(integrations)
|
||||||
|
} catch (error) {
|
||||||
|
store.set(null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const integrations = createIntegrationsStore()
|
||||||
|
|
|
@ -56,6 +56,10 @@ export default ({ mode }) => {
|
||||||
find: "stores",
|
find: "stores",
|
||||||
replacement: path.resolve("./src/stores"),
|
replacement: path.resolve("./src/stores"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
find: "api",
|
||||||
|
replacement: path.resolve("./src/builderStore/api.js"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
find: "constants",
|
find: "constants",
|
||||||
replacement: path.resolve("./src/constants"),
|
replacement: path.resolve("./src/constants"),
|
||||||
|
|
|
@ -7,4 +7,13 @@ export const buildAnalyticsEndpoints = API => ({
|
||||||
url: `/api/analytics/ping`,
|
url: `/api/analytics/ping`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current status of analytics for this environment
|
||||||
|
*/
|
||||||
|
getAnalyticsStatus: async () => {
|
||||||
|
return await API.get({
|
||||||
|
url: "/api/analytics",
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,4 +7,27 @@ export const buildAppEndpoints = API => ({
|
||||||
url: `/api/applications/${appId}/appPackage`,
|
url: `/api/applications/${appId}/appPackage`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves and patches metadata about an app
|
||||||
|
* @param metadata the app metadata to save
|
||||||
|
*/
|
||||||
|
saveAppMetadata: async metadata => {
|
||||||
|
if (!metadata?.appId) {
|
||||||
|
throw API.error("App metadata must have an appId set")
|
||||||
|
}
|
||||||
|
return await API.put({
|
||||||
|
url: `/api/applications/${metadata.appId}`,
|
||||||
|
body: metadata,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deploys the current app.
|
||||||
|
*/
|
||||||
|
deployApp: async () => {
|
||||||
|
return await API.post({
|
||||||
|
url: "/api/deploy",
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
export const buildBuilderEndpoints = 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
|
||||||
|
*/
|
||||||
|
fetchComponentLibDefinitions: async appId => {
|
||||||
|
return await API.get({ url: `/api/${appId}/components/definitions` })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of available integrations.
|
||||||
|
*/
|
||||||
|
getIntegrations: async () => {
|
||||||
|
return await API.get({ url: "/api/integrations" })
|
||||||
|
},
|
||||||
|
})
|
|
@ -8,6 +8,7 @@ import { buildQueryEndpoints } from "./queries"
|
||||||
import { buildRelationshipEndpoints } from "./relationships"
|
import { buildRelationshipEndpoints } from "./relationships"
|
||||||
import { buildRouteEndpoints } from "./routes"
|
import { buildRouteEndpoints } from "./routes"
|
||||||
import { buildRowEndpoints } from "./rows"
|
import { buildRowEndpoints } from "./rows"
|
||||||
|
import { buildScreenEndpoints } from "./screens"
|
||||||
import { buildTableEndpoints } from "./tables"
|
import { buildTableEndpoints } from "./tables"
|
||||||
import { buildViewEndpoints } from "./views"
|
import { buildViewEndpoints } from "./views"
|
||||||
|
|
||||||
|
@ -167,6 +168,7 @@ export const createAPIClient = config => {
|
||||||
...buildRelationshipEndpoints(API),
|
...buildRelationshipEndpoints(API),
|
||||||
...buildRouteEndpoints(API),
|
...buildRouteEndpoints(API),
|
||||||
...buildRowEndpoints(API),
|
...buildRowEndpoints(API),
|
||||||
|
...buildScreenEndpoints(API),
|
||||||
...buildTableEndpoints(API),
|
...buildTableEndpoints(API),
|
||||||
...buildViewEndpoints(API),
|
...buildViewEndpoints(API),
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
export const buildLayoutEndpoints = API => ({
|
||||||
|
/**
|
||||||
|
* Saves a layout.
|
||||||
|
* @param layout the layout to save
|
||||||
|
*/
|
||||||
|
saveLayout: async layout => {
|
||||||
|
return await API.post({
|
||||||
|
url: "/api/layouts",
|
||||||
|
body: layout,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a layout.
|
||||||
|
* @param layoutId the ID of the layout to delete
|
||||||
|
* @param layoutRev the rev of the layout to delete
|
||||||
|
*/
|
||||||
|
deleteLayout: async ({ layoutId, layoutRev }) => {
|
||||||
|
return await API.delete({
|
||||||
|
url: `/api/layouts/${layoutId}/${layoutRev}`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
|
@ -7,4 +7,10 @@ export const buildRouteEndpoints = API => ({
|
||||||
url: `/api/routing/client`,
|
url: `/api/routing/client`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fetchAppRoutes: async () => {
|
||||||
|
return await API.get({
|
||||||
|
url: "/api/routing",
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
export const buildScreenEndpoints = API => ({
|
||||||
|
/**
|
||||||
|
* Saves a screen definition
|
||||||
|
* @param screen the screen to save
|
||||||
|
*/
|
||||||
|
saveScreen: async screen => {
|
||||||
|
return await API.post({
|
||||||
|
url: "/api/screens",
|
||||||
|
body: screen,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a screen.
|
||||||
|
* @param screenId the ID of the screen to delete
|
||||||
|
* @param screenRev the rev of the screen to delete
|
||||||
|
*/
|
||||||
|
deleteScreen: async ({ screenId, screenRev }) => {
|
||||||
|
return await API.delete({
|
||||||
|
url: `/api/screens/${screenId}/${screenRev}`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
Loading…
Reference in New Issue