diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js
index 80fc9eb8f3..b62c2ae1d4 100644
--- a/packages/builder/src/builderStore/store/index.js
+++ b/packages/builder/src/builderStore/store/index.js
@@ -6,7 +6,6 @@ import { writable, get } from "svelte/store"
import api from "../api"
import { DEFAULT_PAGES_OBJECT } from "../../constants"
import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
-import { rename } from "components/userInterface/pagesParsing/renameScreen"
import {
createProps,
makePropsSafe,
@@ -25,6 +24,7 @@ import {
saveCurrentPreviewItem as _saveCurrentPreviewItem,
saveScreenApi as _saveScreenApi,
regenerateCssForCurrentScreen,
+ renameCurrentScreen,
} from "../storeUtils"
export const getStore = () => {
@@ -53,7 +53,6 @@ export const getStore = () => {
store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store)
store.saveScreen = saveScreen(store)
- store.renameScreen = renameScreen(store)
store.deleteScreen = deleteScreen(store)
store.setCurrentScreen = setCurrentScreen(store)
store.setCurrentPage = setCurrentPage(store)
@@ -64,6 +63,7 @@ export const getStore = () => {
store.addChildComponent = addChildComponent(store)
store.selectComponent = selectComponent(store)
store.setComponentProp = setComponentProp(store)
+ store.setPageOrScreenProp = setPageOrScreenProp(store)
store.setComponentStyle = setComponentStyle(store)
store.setComponentCode = setComponentCode(store)
store.setScreenType = setScreenType(store)
@@ -208,46 +208,6 @@ const deleteScreen = store => name => {
})
}
-const renameScreen = store => (oldname, newname) => {
- store.update(s => {
- const { screens, pages, error, changedScreens } = rename(
- s.pages,
- s.screens,
- oldname,
- newname
- )
-
- if (error) {
- // should really do something with this
- return s
- }
-
- s.screens = screens
- s.pages = pages
- if (s.currentPreviewItem.name === oldname)
- s.currentPreviewItem.name = newname
-
- const saveAllChanged = async () => {
- for (let screenName of changedScreens) {
- const changedScreen = getExactComponent(screens, screenName)
- await api.post(`/_builder/api/${s.appId}/screen`, changedScreen)
- }
- }
-
- api
- .patch(`/_builder/api/${s.appId}/screen`, {
- oldname,
- newname,
- })
- .then(() => saveAllChanged())
- .then(() => {
- _savePage(s)
- })
-
- return s
- })
-}
-
const savePage = store => async page => {
store.update(state => {
if (state.currentFrontEndType !== "page" || !state.currentPageName) {
@@ -403,6 +363,18 @@ const setComponentProp = store => (name, value) => {
})
}
+const setPageOrScreenProp = store => (name, value) => {
+ store.update(state => {
+ if (name === "name" && state.currentFrontEndType === "screen") {
+ state = renameCurrentScreen(value, state)
+ } else {
+ state.currentPreviewItem[name] = value
+ _saveCurrentPreviewItem(state)
+ }
+ return state
+ })
+}
+
const setComponentStyle = store => (type, name, value) => {
store.update(state => {
if (!state.currentComponentInfo._styles) {
diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js
index d6aa4d0308..ff951e6b6f 100644
--- a/packages/builder/src/builderStore/storeUtils.js
+++ b/packages/builder/src/builderStore/storeUtils.js
@@ -45,6 +45,19 @@ export const saveScreenApi = (screen, s) => {
.then(() => savePage(s))
}
+export const renameCurrentScreen = (newname, state) => {
+ const oldname = state.currentPreviewItem.name
+ state.currentPreviewItem.name = newname
+ api.patch(
+ `/_builder/api/${state.appId}/pages/${state.currentPageName}/screen`,
+ {
+ oldname,
+ newname,
+ }
+ )
+ return state
+}
+
export const walkProps = (props, action, cancelToken = null) => {
cancelToken = cancelToken || { cancelled: false }
action(props, () => {
diff --git a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte
index 2716cb751b..c020dc70b9 100644
--- a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte
+++ b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte
@@ -13,7 +13,6 @@
import CodeEditor from "./CodeEditor.svelte"
import LayoutEditor from "./LayoutEditor.svelte"
import EventsEditor from "./EventsEditor"
-
import panelStructure from "./temporaryPanelStructure.js"
import CategoryTab from "./CategoryTab.svelte"
import DesignView from "./DesignView.svelte"
@@ -40,28 +39,8 @@
let panelDefinition = {}
- $: {
- if (componentPropDefinition.properties) {
- if (selectedCategory.value === "design") {
- panelDefinition = componentPropDefinition.properties["design"]
- } else {
- let panelDef = componentPropDefinition.properties["settings"]
- if (
- $store.currentFrontEndType === "page" &&
- $store.currentView !== "component"
- ) {
- panelDefinition = [...page, ...panelDef]
- } else if (
- $store.currentFrontEndType === "screen" &&
- $store.currentView !== "component"
- ) {
- panelDefinition = [...screen, ...panelDef]
- } else {
- panelDefinition = panelDef
- }
- }
- }
- }
+ $: panelDefinition = componentPropDefinition.properties &&
+ componentPropDefinition.properties[selectedCategory.value]
const onStyleChanged = store.setComponentStyle
const onPropChanged = store.setComponentProp
@@ -117,6 +96,8 @@
{panelDefinition}
displayNameField={displayName}
onChange={onPropChanged} />
+ onScreenPropChange={store.setPageOrScreenProp}
+ screenOrPageInstance={$store.currentView !== "component" && $store.currentPreviewItem} />
{:else if selectedCategory.value === 'events'}
{/if}
diff --git a/packages/builder/src/components/userInterface/PagesList.svelte b/packages/builder/src/components/userInterface/PagesList.svelte
index 4a87c4ac8a..5a78ef7121 100644
--- a/packages/builder/src/components/userInterface/PagesList.svelte
+++ b/packages/builder/src/components/userInterface/PagesList.svelte
@@ -9,11 +9,11 @@
const pages = [
{
- title: "Main",
+ title: "Private",
id: "main",
},
{
- title: "Login",
+ title: "Public",
id: "unauthenticated",
},
]
diff --git a/packages/builder/src/components/userInterface/SettingsView.svelte b/packages/builder/src/components/userInterface/SettingsView.svelte
index bde6ce2455..4c3f86d2cb 100644
--- a/packages/builder/src/components/userInterface/SettingsView.svelte
+++ b/packages/builder/src/components/userInterface/SettingsView.svelte
@@ -3,23 +3,61 @@
import InputGroup from "../common/Inputs/InputGroup.svelte"
import Input from "../common/Input.svelte"
import Colorpicker from "../common/Colorpicker.svelte"
+ import { goto } from "@sveltech/routify"
import { excludeProps } from "./propertyCategories.js"
+ import Input from "../common/Input.svelte"
export let panelDefinition = []
export let componentDefinition = {}
export let componentInstance = {}
export let onChange = () => {}
export let displayNameField = false
+ export let onScreenPropChange = () => {}
+ export let screenOrPageInstance
const propExistsOnComponentDef = prop => prop in componentDefinition.props
function handleChange(key, data) {
data.target ? onChange(key, data.target.value) : onChange(key, data)
}
+
+ function handleScreenPropChange (name, value) {
+ onScreenPropChange(name,value)
+ if(!isPage && name === "name") {
+ // screen name is changed... change URL
+ $goto(`./:page/${value}`)
+ }
+ }
+
+ const screenDefinition = [
+ { key: "name", label: "Name", control: Input },
+ { key: "description", label: "Description", control: Input },
+ { key: "route", label: "Route", control: Input },
+ ]
+
+ const pageDefinition = [
+ { key: "title", label: "Title", control: Input },
+ { key: "favicon", label: "Favicon", control: Input },
+ ]
+
+ $: isPage = screenOrPageInstance && screenOrPageInstance.favicon
+ $: screenOrPageDefinition = isPage ? pageDefinition : screenDefinition
+
-{#if displayNameField}
-
+
+{#if screenOrPageInstance}
+ {#each screenOrPageDefinition as def}
+
+ {/each}
+
{/if}
{#if panelDefinition && panelDefinition.length > 0}
diff --git a/packages/builder/src/components/userInterface/temporaryPanelStructure.js b/packages/builder/src/components/userInterface/temporaryPanelStructure.js
index afd3a06694..7315e09736 100644
--- a/packages/builder/src/components/userInterface/temporaryPanelStructure.js
+++ b/packages/builder/src/components/userInterface/temporaryPanelStructure.js
@@ -11,6 +11,18 @@ export default {
name: "Basic",
isCategory: true,
children: [
+ {
+ _component: "@budibase/standard-components/embed",
+ icon: "ri-code-line",
+ name: "Embed",
+ description: "Embed content from 3rd party sources",
+ properties: {
+ design: {
+ ...all,
+ },
+ settings: [{ label: "Embed", key: "embed", control: Input }],
+ },
+ },
{
_component: "@budibase/standard-components/container",
name: "Container",
diff --git a/packages/server/package.json b/packages/server/package.json
index 0888ef2ae4..49a5626f6f 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -25,7 +25,7 @@
"scripts": {
"test": "jest routes --runInBand",
"test:integration": "jest workflow --runInBand",
- "test:watch": "jest -w",
+ "test:watch": "jest --watch",
"initialise": "node ../cli/bin/budi init -b local -q",
"budi": "node ../cli/bin/budi",
"dev:builder": "nodemon ../cli/bin/budi run",
diff --git a/packages/server/src/api/controllers/model.js b/packages/server/src/api/controllers/model.js
index 9d06c7a413..650342b33c 100644
--- a/packages/server/src/api/controllers/model.js
+++ b/packages/server/src/api/controllers/model.js
@@ -27,6 +27,23 @@ exports.create = async function(ctx) {
const result = await db.post(newModel)
newModel._rev = result.rev
+ const { schema } = ctx.request.body
+ for (let key in schema) {
+ // model has a linked record
+ if (schema[key].type === "link") {
+ // create the link field in the other model
+ const linkedModel = await db.get(schema[key].modelId)
+ linkedModel.schema[newModel.name] = {
+ type: "link",
+ modelId: newModel._id,
+ constraints: {
+ type: "array",
+ },
+ }
+ await db.put(linkedModel)
+ }
+ }
+
const designDoc = await db.get("_design/database")
designDoc.views = {
...designDoc.views,
@@ -50,7 +67,10 @@ exports.update = async function() {}
exports.destroy = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId)
- await db.remove(ctx.params.modelId, ctx.params.revId)
+ const modelToDelete = await db.get(ctx.params.modelId)
+
+ await db.remove(modelToDelete)
+
const modelViewId = `all_${ctx.params.modelId}`
// Delete all records for that model
@@ -59,6 +79,16 @@ exports.destroy = async function(ctx) {
records.rows.map(record => ({ id: record.id, _deleted: true }))
)
+ // Delete linked record fields in dependent models
+ for (let key in modelToDelete.schema) {
+ const { type, modelId } = modelToDelete.schema[key]
+ if (type === "link") {
+ const linkedModel = await db.get(modelId)
+ delete linkedModel.schema[modelToDelete.name]
+ await db.put(linkedModel)
+ }
+ }
+
// delete the "all" view
const designDoc = await db.get("_design/database")
delete designDoc.views[modelViewId]
diff --git a/packages/server/src/api/controllers/record.js b/packages/server/src/api/controllers/record.js
index 5fab413f04..cf8eb27606 100644
--- a/packages/server/src/api/controllers/record.js
+++ b/packages/server/src/api/controllers/record.js
@@ -61,7 +61,7 @@ exports.fetchView = async function(ctx) {
ctx.body = response.rows.map(row => row.doc)
}
-exports.fetchModel = async function(ctx) {
+exports.fetchModelRecords = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId)
const response = await db.query(`database/all_${ctx.params.modelId}`, {
include_docs: true,
@@ -69,6 +69,15 @@ exports.fetchModel = async function(ctx) {
ctx.body = response.rows.map(row => row.doc)
}
+exports.search = async function(ctx) {
+ const db = new CouchDB(ctx.params.instanceId)
+ const response = await db.allDocs({
+ include_docs: true,
+ ...ctx.request.body,
+ })
+ ctx.body = response.rows.map(row => row.doc)
+}
+
exports.find = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId)
const record = await db.get(ctx.params.recordId)
diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js
index 990714fdc7..e6143d6725 100644
--- a/packages/server/src/api/index.js
+++ b/packages/server/src/api/index.js
@@ -10,6 +10,7 @@ const {
instanceRoutes,
clientRoutes,
applicationRoutes,
+ recordRoutes,
modelRoutes,
viewRoutes,
staticRoutes,
@@ -69,6 +70,9 @@ router.use(viewRoutes.allowedMethods())
router.use(modelRoutes.routes())
router.use(modelRoutes.allowedMethods())
+router.use(recordRoutes.routes())
+router.use(recordRoutes.allowedMethods())
+
router.use(userRoutes.routes())
router.use(userRoutes.allowedMethods())
diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js
index c515d5f437..b50fee788a 100644
--- a/packages/server/src/api/routes/index.js
+++ b/packages/server/src/api/routes/index.js
@@ -5,6 +5,7 @@ const instanceRoutes = require("./instance")
const clientRoutes = require("./client")
const applicationRoutes = require("./application")
const modelRoutes = require("./model")
+const recordRoutes = require("./record")
const viewRoutes = require("./view")
const staticRoutes = require("./static")
const componentRoutes = require("./component")
@@ -18,6 +19,7 @@ module.exports = {
instanceRoutes,
clientRoutes,
applicationRoutes,
+ recordRoutes,
modelRoutes,
viewRoutes,
staticRoutes,
diff --git a/packages/server/src/api/routes/model.js b/packages/server/src/api/routes/model.js
index 388f4618bd..f1ec46dbe5 100644
--- a/packages/server/src/api/routes/model.js
+++ b/packages/server/src/api/routes/model.js
@@ -1,46 +1,10 @@
const Router = require("@koa/router")
const modelController = require("../controllers/model")
-const recordController = require("../controllers/record")
const authorized = require("../../middleware/authorized")
-const {
- READ_MODEL,
- WRITE_MODEL,
- BUILDER,
-} = require("../../utilities/accessLevels")
+const { BUILDER } = require("../../utilities/accessLevels")
const router = Router()
-// records
-
-router
- .get(
- "/api/:instanceId/:modelId/records",
- authorized(READ_MODEL, ctx => ctx.params.modelId),
- recordController.fetchModel
- )
- .get(
- "/api/:instanceId/:modelId/records/:recordId",
- authorized(READ_MODEL, ctx => ctx.params.modelId),
- recordController.find
- )
- .post(
- "/api/:instanceId/:modelId/records",
- authorized(WRITE_MODEL, ctx => ctx.params.modelId),
- recordController.save
- )
- .post(
- "/api/:instanceId/:modelId/records/validate",
- authorized(WRITE_MODEL, ctx => ctx.params.modelId),
- recordController.validate
- )
- .delete(
- "/api/:instanceId/:modelId/records/:recordId/:revId",
- authorized(WRITE_MODEL, ctx => ctx.params.modelId),
- recordController.destroy
- )
-
-// models
-
router
.get("/api/:instanceId/models", authorized(BUILDER), modelController.fetch)
.get("/api/:instanceId/models/:id", authorized(BUILDER), modelController.find)
diff --git a/packages/server/src/api/routes/record.js b/packages/server/src/api/routes/record.js
new file mode 100644
index 0000000000..d555d3d8c8
--- /dev/null
+++ b/packages/server/src/api/routes/record.js
@@ -0,0 +1,36 @@
+const Router = require("@koa/router")
+const recordController = require("../controllers/record")
+const authorized = require("../../middleware/authorized")
+const { READ_MODEL, WRITE_MODEL } = require("../../utilities/accessLevels")
+
+const router = Router()
+
+router
+ .get(
+ "/api/:instanceId/:modelId/records",
+ authorized(READ_MODEL, ctx => ctx.params.modelId),
+ recordController.fetchModelRecords
+ )
+ .get(
+ "/api/:instanceId/:modelId/records/:recordId",
+ authorized(READ_MODEL, ctx => ctx.params.modelId),
+ recordController.find
+ )
+ .post("/api/:instanceId/records/search", recordController.search)
+ .post(
+ "/api/:instanceId/:modelId/records",
+ authorized(WRITE_MODEL, ctx => ctx.params.modelId),
+ recordController.save
+ )
+ .post(
+ "/api/:instanceId/:modelId/records/validate",
+ authorized(WRITE_MODEL, ctx => ctx.params.modelId),
+ recordController.validate
+ )
+ .delete(
+ "/api/:instanceId/:modelId/records/:recordId/:revId",
+ authorized(WRITE_MODEL, ctx => ctx.params.modelId),
+ recordController.destroy
+ )
+
+module.exports = router
diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js
index 6029e080cc..495b841b10 100644
--- a/packages/server/src/api/routes/tests/couchTestUtils.js
+++ b/packages/server/src/api/routes/tests/couchTestUtils.js
@@ -253,3 +253,7 @@ exports.insertDocument = async (databaseId, document) => {
exports.destroyDocument = async (databaseId, documentId) => {
return await new CouchDB(databaseId).destroy(documentId)
}
+
+exports.getDocument = async (databaseId, documentId) => {
+ return await new CouchDB(databaseId).get(documentId)
+}
diff --git a/packages/server/src/api/routes/tests/model.spec.js b/packages/server/src/api/routes/tests/model.spec.js
index 65a44b677a..7134245fb3 100644
--- a/packages/server/src/api/routes/tests/model.spec.js
+++ b/packages/server/src/api/routes/tests/model.spec.js
@@ -3,9 +3,10 @@ const {
createModel,
supertest,
createClientDatabase,
- createApplication ,
+ createApplication,
defaultHeaders,
- builderEndpointShouldBlockNormalUsers
+ builderEndpointShouldBlockNormalUsers,
+ getDocument
} = require("./couchTestUtils")
describe("/models", () => {
@@ -97,7 +98,6 @@ describe("/models", () => {
instanceId: instance._id,
})
})
-
});
describe("destroy", () => {
@@ -108,7 +108,11 @@ describe("/models", () => {
testModel = await createModel(request, instance._id, testModel)
});
- it("returns a success response when a model is deleted.", done => {
+ afterEach(() => {
+ delete testModel._rev
+ })
+
+ it("returns a success response when a model is deleted.", async done => {
request
.delete(`/api/${instance._id}/models/${testModel._id}/${testModel._rev}`)
.set(defaultHeaders)
@@ -120,6 +124,41 @@ describe("/models", () => {
});
})
+ it("deletes linked references to the model after deletion", async done => {
+ const linkedModel = await createModel(request, instance._id, {
+ name: "LinkedModel",
+ type: "model",
+ key: "name",
+ schema: {
+ name: {
+ type: "text",
+ constraints: {
+ type: "string",
+ },
+ },
+ TestModel: {
+ type: "link",
+ modelId: testModel._id,
+ constraints: {
+ type: "array"
+ }
+ }
+ },
+ })
+
+ request
+ .delete(`/api/${instance._id}/models/${testModel._id}/${testModel._rev}`)
+ .set(defaultHeaders)
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end(async (_, res) => {
+ expect(res.res.statusMessage).toEqual(`Model ${testModel._id} deleted.`);
+ const dependentModel = await getDocument(instance._id, linkedModel._id)
+ expect(dependentModel.schema.TestModel).not.toBeDefined();
+ done();
+ });
+ })
+
it("should apply authorization to endpoint", async () => {
await builderEndpointShouldBlockNormalUsers({
request,
diff --git a/packages/server/src/api/routes/tests/record.spec.js b/packages/server/src/api/routes/tests/record.spec.js
index 2c8c542715..22ac67ecdc 100644
--- a/packages/server/src/api/routes/tests/record.spec.js
+++ b/packages/server/src/api/routes/tests/record.spec.js
@@ -110,6 +110,30 @@ describe("/records", () => {
expect(res.body.find(r => r.name === record.name)).toBeDefined()
})
+ it("lists records when queried by their ID", async () => {
+ const newRecord = {
+ modelId: model._id,
+ name: "Second Contact",
+ status: "new"
+ }
+ const record = await createRecord()
+ const secondRecord = await createRecord(newRecord)
+
+ const recordIds = [record.body._id, secondRecord.body._id]
+
+ const res = await request
+ .post(`/api/${instance._id}/records/search`)
+ .set(defaultHeaders)
+ .send({
+ keys: recordIds
+ })
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(res.body.length).toBe(2)
+ expect(res.body.map(response => response._id)).toEqual(expect.arrayContaining(recordIds))
+ })
+
it("load should return 404 when record does not exist", async () => {
await createRecord()
await request
diff --git a/packages/standard-components/components.json b/packages/standard-components/components.json
index a562650b85..c4c0246672 100644
--- a/packages/standard-components/components.json
+++ b/packages/standard-components/components.json
@@ -6,6 +6,13 @@
"component": "button"
}
},
+ "embed": {
+ "name": "Embed",
+ "description": "Embed stuff",
+ "props": {
+ "embed": "string"
+ }
+ },
"Navigation": {
"name": "Navigation",
"description": "A basic header navigation component",
diff --git a/packages/standard-components/src/Embed.svelte b/packages/standard-components/src/Embed.svelte
new file mode 100644
index 0000000000..2640864681
--- /dev/null
+++ b/packages/standard-components/src/Embed.svelte
@@ -0,0 +1,5 @@
+
+
+{@html embed}
diff --git a/packages/standard-components/src/index.js b/packages/standard-components/src/index.js
index 2284ab1fcb..24342066e8 100644
--- a/packages/standard-components/src/index.js
+++ b/packages/standard-components/src/index.js
@@ -21,3 +21,4 @@ export { default as datalist } from "./DataList.svelte"
export { default as list } from "./List.svelte"
export { default as datasearch } from "./DataSearch.svelte"
export { default as datamap } from "./DataMap.svelte"
+export { default as embed } from "./Embed.svelte"