Merge branch 'master' of https://github.com/Budibase/budibase into feature/md-datepicker-and-iconbutton
This commit is contained in:
commit
f490bbe04c
|
@ -18,7 +18,12 @@ const _builderProxy = proxy("/_builder", {
|
||||||
})
|
})
|
||||||
|
|
||||||
const apiProxy = proxy(
|
const apiProxy = proxy(
|
||||||
["/_builder/assets/**", "/_builder/api/**", "/_builder/**/componentlibrary"],
|
[
|
||||||
|
"/_builder/assets/**",
|
||||||
|
"/_builder/api/**",
|
||||||
|
"/_builder/**/componentlibrary",
|
||||||
|
"/_builder/instance/**",
|
||||||
|
],
|
||||||
{
|
{
|
||||||
target,
|
target,
|
||||||
logLevel: "debug",
|
logLevel: "debug",
|
||||||
|
|
|
@ -32,3 +32,24 @@ export const makeLibraryUrl = (appName, lib) =>
|
||||||
|
|
||||||
export const libsFromPages = pages =>
|
export const libsFromPages = pages =>
|
||||||
pipe(pages, [values, map(p => p.componentLibraries), flatten, uniq])
|
pipe(pages, [values, map(p => p.componentLibraries), flatten, uniq])
|
||||||
|
|
||||||
|
export const libUrlsForPreview = (appPackage, pageName) => {
|
||||||
|
const resolve = path => {
|
||||||
|
let file = appPackage.components.libraryPaths[path]
|
||||||
|
if (file.startsWith("./")) file = file.substring(2)
|
||||||
|
if (file.startsWith("/")) file = file.substring(1)
|
||||||
|
|
||||||
|
let newPath = path
|
||||||
|
|
||||||
|
if (!newPath.startsWith("./") && !newPath.startsWith("/")) {
|
||||||
|
newPath = `/node_modules/${path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
importPath: `/lib${newPath}/${file}`,
|
||||||
|
libName: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipe([appPackage.pages[pageName]], [libsFromPages, map(resolve)])
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
getBuiltin,
|
getBuiltin,
|
||||||
} from "../userInterface/pagesParsing/createProps"
|
} from "../userInterface/pagesParsing/createProps"
|
||||||
import { expandComponentDefinition } from "../userInterface/pagesParsing/types"
|
import { expandComponentDefinition } from "../userInterface/pagesParsing/types"
|
||||||
import { loadLibs, loadLibUrls } from "./loadComponentLibraries"
|
import { loadLibs, libUrlsForPreview } from "./loadComponentLibraries"
|
||||||
import { buildCodeForScreens } from "./buildCodeForScreens"
|
import { buildCodeForScreens } from "./buildCodeForScreens"
|
||||||
import { generate_screen_css } from "./generate_css"
|
import { generate_screen_css } from "./generate_css"
|
||||||
import { insertCodeMetadata } from "./insertCodeMetadata"
|
import { insertCodeMetadata } from "./insertCodeMetadata"
|
||||||
|
@ -153,9 +153,16 @@ const initialise = (store, initial) => async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
initial.libraries = await loadLibs(appname, pkg)
|
initial.libraries = await loadLibs(appname, pkg)
|
||||||
initial.loadLibraryUrls = () => loadLibUrls(appname, pkg)
|
initial.loadLibraryUrls = pageName => {
|
||||||
|
const libs = libUrlsForPreview(pkg, pageName)
|
||||||
|
return libs
|
||||||
|
}
|
||||||
initial.appname = appname
|
initial.appname = appname
|
||||||
initial.pages = pkg.pages
|
initial.pages = pkg.pages
|
||||||
|
initial.currentInstanceId =
|
||||||
|
pkg.application.instances && pkg.application.instances.length > 0
|
||||||
|
? pkg.application.instances[0].id
|
||||||
|
: ""
|
||||||
initial.hasAppPackage = true
|
initial.hasAppPackage = true
|
||||||
initial.hierarchy = pkg.appDefinition.hierarchy
|
initial.hierarchy = pkg.appDefinition.hierarchy
|
||||||
initial.accessLevels = pkg.accessLevels
|
initial.accessLevels = pkg.accessLevels
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
$: frontendDefinition = {
|
$: frontendDefinition = {
|
||||||
componentLibraries: $store.loadLibraryUrls(),
|
componentLibraries: $store.loadLibraryUrls($store.currentPageName),
|
||||||
page: $store.currentPreviewItem,
|
page: $store.currentPreviewItem,
|
||||||
screens: [{
|
screens: [{
|
||||||
name: "Screen Placeholder",
|
name: "Screen Placeholder",
|
||||||
|
@ -63,7 +63,7 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
appRootPath: "",
|
appRootPath: `/_builder/instance/${$store.appname}/${$store.currentInstanceId}/`,
|
||||||
}
|
}
|
||||||
|
|
||||||
$: backendDefinition = {
|
$: backendDefinition = {
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
import { getFlattenedHierarchy, isRecord, isIndex, isAncestor } from "./hierarchy"
|
||||||
|
import { $, none } from "../common"
|
||||||
|
import { map, filter, some, find } from "lodash/fp"
|
||||||
|
|
||||||
|
export const HierarchyChangeTypes = {
|
||||||
|
recordCreated: "Record Created",
|
||||||
|
recordDeleted: "Record Deleted",
|
||||||
|
recordRenamed: "Record Renamed",
|
||||||
|
recordFieldsChanged: "Record Fields Changed",
|
||||||
|
recordEstimatedRecordTypeChanged: "Record's Estimated Record Count Changed",
|
||||||
|
indexCreated: "Index Created",
|
||||||
|
indexDeleted: "Index Deleted",
|
||||||
|
indexChanged: "index Changed",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const diffHierarchy = (oldHierarchy, newHierarchy) => {
|
||||||
|
const oldHierarchyFlat = getFlattenedHierarchy(oldHierarchy)
|
||||||
|
const newHierarchyFlat = getFlattenedHierarchy(newHierarchy)
|
||||||
|
|
||||||
|
const createdRecords = findCreatedRecords(oldHierarchyFlat, newHierarchyFlat)
|
||||||
|
const deletedRecords = findDeletedRecords(oldHierarchyFlat, newHierarchyFlat)
|
||||||
|
|
||||||
|
return [
|
||||||
|
...createdRecords,
|
||||||
|
...deletedRecords,
|
||||||
|
...findRenamedRecords(oldHierarchyFlat, newHierarchyFlat),
|
||||||
|
...findRecordsWithFieldsChanged(oldHierarchyFlat, newHierarchyFlat),
|
||||||
|
...findRecordsWithEstimatedRecordTypeChanged(oldHierarchyFlat, newHierarchyFlat),
|
||||||
|
...findCreatedIndexes(oldHierarchyFlat, newHierarchyFlat, createdRecords),
|
||||||
|
...findDeletedIndexes(oldHierarchyFlat, newHierarchyFlat, deletedRecords),
|
||||||
|
...findUpdatedIndexes(oldHierarchyFlat, newHierarchyFlat),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeItem = (type, oldNode, newNode) => ({
|
||||||
|
type, oldNode, newNode,
|
||||||
|
})
|
||||||
|
|
||||||
|
const findCreatedRecords = (oldHierarchyFlat, newHierarchyFlat) => {
|
||||||
|
const allCreated = $(newHierarchyFlat, [
|
||||||
|
filter(isRecord),
|
||||||
|
filter(nodeDoesNotExistIn(oldHierarchyFlat)),
|
||||||
|
map(n => changeItem(HierarchyChangeTypes.recordCreated, null, n))
|
||||||
|
])
|
||||||
|
|
||||||
|
return $(allCreated, [
|
||||||
|
filter(r => none(r2 => isAncestor(r.newNode)(r2.newNode))(allCreated))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
const findDeletedRecords = (oldHierarchyFlat, newHierarchyFlat) => {
|
||||||
|
const allDeleted = $(oldHierarchyFlat, [
|
||||||
|
filter(isRecord),
|
||||||
|
filter(nodeDoesNotExistIn(newHierarchyFlat)),
|
||||||
|
map(n => changeItem(HierarchyChangeTypes.recordDeleted, n, null))
|
||||||
|
])
|
||||||
|
|
||||||
|
return $(allDeleted, [
|
||||||
|
filter(r => none(r2 => isAncestor(r.oldNode)(r2.oldNode))(allDeleted))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
const findRenamedRecords = (oldHierarchyFlat, newHierarchyFlat) =>
|
||||||
|
$(oldHierarchyFlat, [
|
||||||
|
filter(isRecord),
|
||||||
|
filter(nodeExistsIn(newHierarchyFlat)),
|
||||||
|
filter(nodeChanged(newHierarchyFlat, (_new,old) =>_new.collectionKey !== old.collectionKey )),
|
||||||
|
map(n => changeItem(
|
||||||
|
HierarchyChangeTypes.recordRenamed,
|
||||||
|
n,
|
||||||
|
findNodeIn(n, newHierarchyFlat))
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
const findRecordsWithFieldsChanged = (oldHierarchyFlat, newHierarchyFlat) =>
|
||||||
|
$(oldHierarchyFlat, [
|
||||||
|
filter(isRecord),
|
||||||
|
filter(nodeExistsIn(newHierarchyFlat)),
|
||||||
|
filter(hasDifferentFields(newHierarchyFlat)),
|
||||||
|
map(n => changeItem(
|
||||||
|
HierarchyChangeTypes.recordFieldsChanged,
|
||||||
|
n,
|
||||||
|
findNodeIn(n, newHierarchyFlat))
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
const findRecordsWithEstimatedRecordTypeChanged = (oldHierarchyFlat, newHierarchyFlat) =>
|
||||||
|
$(oldHierarchyFlat, [
|
||||||
|
filter(isRecord),
|
||||||
|
filter(nodeExistsIn(newHierarchyFlat)),
|
||||||
|
filter(nodeChanged(newHierarchyFlat, (_new,old) =>_new.estimatedRecordCount !== old.estimatedRecordCount)),
|
||||||
|
map(n => changeItem(
|
||||||
|
HierarchyChangeTypes.recordEstimatedRecordTypeChanged,
|
||||||
|
n,
|
||||||
|
findNodeIn(n, newHierarchyFlat))
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
const findCreatedIndexes = (oldHierarchyFlat, newHierarchyFlat, createdRecords) => {
|
||||||
|
const allCreated = $(newHierarchyFlat, [
|
||||||
|
filter(isIndex),
|
||||||
|
filter(nodeDoesNotExistIn(oldHierarchyFlat)),
|
||||||
|
map(n => changeItem(HierarchyChangeTypes.indexCreated, null, n))
|
||||||
|
])
|
||||||
|
|
||||||
|
return $(allCreated, [
|
||||||
|
filter(r => none(r2 => isAncestor(r.newNode)(r2.newNode))(createdRecords))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
const findDeletedIndexes = (oldHierarchyFlat, newHierarchyFlat, deletedRecords) => {
|
||||||
|
const allDeleted = $(oldHierarchyFlat, [
|
||||||
|
filter(isIndex),
|
||||||
|
filter(nodeDoesNotExistIn(newHierarchyFlat)),
|
||||||
|
map(n => changeItem(HierarchyChangeTypes.indexDeleted, n, null))
|
||||||
|
])
|
||||||
|
|
||||||
|
return $(allDeleted, [
|
||||||
|
filter(r => none(r2 => isAncestor(r.oldNode)(r2.oldNode))(deletedRecords))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const findUpdatedIndexes = (oldHierarchyFlat, newHierarchyFlat) =>
|
||||||
|
$(oldHierarchyFlat, [
|
||||||
|
filter(isRecord),
|
||||||
|
filter(nodeExistsIn(newHierarchyFlat)),
|
||||||
|
filter(nodeChanged(newHierarchyFlat, indexHasChanged)),
|
||||||
|
map(n => changeItem(
|
||||||
|
HierarchyChangeTypes.indexChanged,
|
||||||
|
n,
|
||||||
|
findNodeIn(n, newHierarchyFlat))
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
const hasDifferentFields = otherFlatHierarchy => record1 => {
|
||||||
|
|
||||||
|
const record2 = findNodeIn(record1, otherFlatHierarchy)
|
||||||
|
|
||||||
|
if(record1.fields.length !== record2.fields.length) return true
|
||||||
|
|
||||||
|
for(let f1 of record1.fields) {
|
||||||
|
if (none(isFieldSame(f1))(record2.fields)) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexHasChanged = (_new, old) =>
|
||||||
|
_new.map !== old.map
|
||||||
|
|| _new.filter !== old.filter
|
||||||
|
|| _new.getShardName !== old.getShardName
|
||||||
|
|
||||||
|
const isFieldSame = f1 => f2 =>
|
||||||
|
f1.name === f2.name && f1.type === f2.type
|
||||||
|
|
||||||
|
const nodeDoesNotExistIn = inThis => node =>
|
||||||
|
none(n => n.nodeId === node.nodeId)(inThis)
|
||||||
|
|
||||||
|
const nodeExistsIn = inThis => node =>
|
||||||
|
some(n => n.nodeId === node.nodeId)(inThis)
|
||||||
|
|
||||||
|
const nodeChanged = (inThis, isChanged) => node =>
|
||||||
|
some(n => n.nodeId === node.nodeId && isChanged(n, node))(inThis)
|
||||||
|
|
||||||
|
const findNodeIn = (node, inThis) =>
|
||||||
|
find(n => n.nodeId === node.nodeId)(inThis)
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
const changeActions = {
|
||||||
|
rebuildIndex: indexNodeKey => ({
|
||||||
|
type: "rebuildIndex",
|
||||||
|
indexNodeKey,
|
||||||
|
}),
|
||||||
|
reshardRecords: recordNodeKey => ({
|
||||||
|
type: "reshardRecords",
|
||||||
|
recordNodeKey,
|
||||||
|
}),
|
||||||
|
deleteRecords: recordNodeKey => ({
|
||||||
|
type: "reshardRecords",
|
||||||
|
recordNodeKey,
|
||||||
|
}),
|
||||||
|
renameRecord
|
||||||
|
}
|
||||||
|
*/
|
|
@ -0,0 +1,217 @@
|
||||||
|
import { getMemoryTemplateApi } from "./specHelpers"
|
||||||
|
import { diffHierarchy, HierarchyChangeTypes } from "../src/templateApi/diffHierarchy"
|
||||||
|
import { getFlattenedHierarchy } from "../src/templateApi/hierarchy"
|
||||||
|
|
||||||
|
describe("diffHierarchy", () => {
|
||||||
|
|
||||||
|
it("should not show any changes, when hierarchy is unchanged", async () => {
|
||||||
|
const oldHierarchy = (await setup()).root;
|
||||||
|
const newHierarchy = (await setup()).root;
|
||||||
|
const diff = diffHierarchy(oldHierarchy, newHierarchy)
|
||||||
|
expect(diff).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect root record created", async () => {
|
||||||
|
const oldHierarchy = (await setup()).root;
|
||||||
|
const newSetup = (await setup());
|
||||||
|
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false)
|
||||||
|
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: opportunity,
|
||||||
|
oldNode: null,
|
||||||
|
type: HierarchyChangeTypes.recordCreated
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should only detect root record, when newly created root record has children ", async () => {
|
||||||
|
const oldHierarchy = (await setup()).root;
|
||||||
|
const newSetup = (await setup());
|
||||||
|
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false)
|
||||||
|
newSetup.templateApi.getNewRecordTemplate(opportunity, "invoice", true)
|
||||||
|
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: opportunity,
|
||||||
|
oldNode: null,
|
||||||
|
type: HierarchyChangeTypes.recordCreated
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect child record created", async () => {
|
||||||
|
const oldHierarchy = (await setup()).root;
|
||||||
|
const newSetup = (await setup());
|
||||||
|
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.contact, "opportunity", false)
|
||||||
|
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: opportunity,
|
||||||
|
oldNode: null,
|
||||||
|
type: HierarchyChangeTypes.recordCreated
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect root record deleted", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
newSetup.root.children = newSetup.root.children.filter(n => n.name !== "contact")
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: null,
|
||||||
|
oldNode: oldSetup.contact,
|
||||||
|
type: HierarchyChangeTypes.recordDeleted
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect child record deleted", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
newSetup.contact.children = newSetup.contact.children.filter(n => n.name !== "deal")
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: null,
|
||||||
|
oldNode: oldSetup.deal,
|
||||||
|
type: HierarchyChangeTypes.recordDeleted
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect root record renamed", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
newSetup.contact.collectionKey = "CONTACTS"
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: newSetup.contact,
|
||||||
|
oldNode: oldSetup.contact,
|
||||||
|
type: HierarchyChangeTypes.recordRenamed
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect child record renamed", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
newSetup.deal.collectionKey = "CONTACTS"
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: newSetup.deal,
|
||||||
|
oldNode: oldSetup.deal,
|
||||||
|
type: HierarchyChangeTypes.recordRenamed
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect root record field removed", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
newSetup.contact.fields = newSetup.contact.fields.filter(f => f.name !== "name")
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: newSetup.contact,
|
||||||
|
oldNode: oldSetup.contact,
|
||||||
|
type: HierarchyChangeTypes.recordFieldsChanged
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect child record field removed", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
newSetup.deal.fields = newSetup.deal.fields.filter(f => f.name !== "name")
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: newSetup.deal,
|
||||||
|
oldNode: oldSetup.deal,
|
||||||
|
type: HierarchyChangeTypes.recordFieldsChanged
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect record field added", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
const notesField = newSetup.templateApi.getNewField("string")
|
||||||
|
notesField.name = "notes"
|
||||||
|
newSetup.templateApi.addField(newSetup.contact, notesField)
|
||||||
|
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: newSetup.contact,
|
||||||
|
oldNode: oldSetup.contact,
|
||||||
|
type: HierarchyChangeTypes.recordFieldsChanged
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect 1 record field added and 1 removed (total no. fields unchanged)", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
const notesField = newSetup.templateApi.getNewField("string")
|
||||||
|
notesField.name = "notes"
|
||||||
|
newSetup.templateApi.addField(newSetup.contact, notesField)
|
||||||
|
newSetup.contact.fields = newSetup.contact.fields.filter(f => f.name !== "name")
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: newSetup.contact,
|
||||||
|
oldNode: oldSetup.contact,
|
||||||
|
type: HierarchyChangeTypes.recordFieldsChanged
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect root record estimated record count changed", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
newSetup.contact.estimatedRecordCount = 987
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: newSetup.contact,
|
||||||
|
oldNode: oldSetup.contact,
|
||||||
|
type: HierarchyChangeTypes.recordEstimatedRecordTypeChanged
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect root record estimated record count changed", async () => {
|
||||||
|
const oldSetup = (await setup());
|
||||||
|
const newSetup = (await setup());
|
||||||
|
newSetup.deal.estimatedRecordCount = 987
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: newSetup.deal,
|
||||||
|
oldNode: oldSetup.deal,
|
||||||
|
type: HierarchyChangeTypes.recordEstimatedRecordTypeChanged
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect root record created", async () => {
|
||||||
|
const oldHierarchy = (await setup()).root;
|
||||||
|
const newSetup = (await setup());
|
||||||
|
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false)
|
||||||
|
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: opportunity,
|
||||||
|
oldNode: null,
|
||||||
|
type: HierarchyChangeTypes.recordCreated
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const setup = async () => {
|
||||||
|
const { templateApi } = await getMemoryTemplateApi()
|
||||||
|
const root = templateApi.getNewRootLevel()
|
||||||
|
const contact = templateApi.getNewRecordTemplate(root, "contact", true)
|
||||||
|
|
||||||
|
const nameField = templateApi.getNewField("string")
|
||||||
|
nameField.name = "name"
|
||||||
|
const statusField = templateApi.getNewField("string")
|
||||||
|
statusField.name = "status"
|
||||||
|
|
||||||
|
templateApi.addField(contact, nameField)
|
||||||
|
templateApi.addField(contact, statusField)
|
||||||
|
|
||||||
|
const lead = templateApi.getNewRecordTemplate(root, "lead", true)
|
||||||
|
const deal = templateApi.getNewRecordTemplate(contact, "deal", true)
|
||||||
|
|
||||||
|
templateApi.addField(deal, {...nameField})
|
||||||
|
templateApi.addField(deal, {...statusField})
|
||||||
|
|
||||||
|
getFlattenedHierarchy(root)
|
||||||
|
return {
|
||||||
|
root, contact, lead, deal, templateApi,
|
||||||
|
all_contacts: root.indexes[0],
|
||||||
|
all_leads: root.indexes[1],
|
||||||
|
deals_for_contacts: contact.indexes[0]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
|
const { getAppRelativePath } = require("./helpers")
|
||||||
|
|
||||||
const send = require("koa-send")
|
const send = require("koa-send")
|
||||||
|
|
||||||
module.exports = async (ctx, next) => {
|
module.exports = async (ctx, next) => {
|
||||||
const path = ctx.path.replace(`/${ctx.params.appname}`, "")
|
const path = getAppRelativePath(ctx.params.appname, ctx.path)
|
||||||
|
|
||||||
if (path.startsWith("/api/")) {
|
if (path.startsWith("/api/")) {
|
||||||
await next()
|
await next()
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
exports.getRecordKey = (appname, wholePath) =>
|
exports.getRecordKey = (appname, wholePath) =>
|
||||||
wholePath
|
this.getAppRelativePath(appname, wholePath)
|
||||||
.replace(`/${appname}/api/files/`, "")
|
.replace(`/api/files/`, "/")
|
||||||
.replace(`/${appname}/api/lookup_field/`, "")
|
.replace(`/api/lookup_field/`, "/")
|
||||||
.replace(`/${appname}/api/record/`, "")
|
.replace(`/api/record/`, "/")
|
||||||
.replace(`/${appname}/api/listRecords/`, "")
|
.replace(`/api/listRecords/`, "/")
|
||||||
.replace(`/${appname}/api/aggregates/`, "")
|
.replace(`/api/aggregates/`, "/")
|
||||||
|
|
||||||
|
exports.getAppRelativePath = (appname, wholePath) => {
|
||||||
|
const builderInstanceRegex = new RegExp(
|
||||||
|
`\\/_builder\\/instance\\/[^\\/]*\\/[^\\/]*\\/`
|
||||||
|
)
|
||||||
|
|
||||||
|
return wholePath.replace(builderInstanceRegex, "/").replace(`/${appname}`, "")
|
||||||
|
}
|
||||||
|
|
|
@ -46,15 +46,29 @@ module.exports = (config, app) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.path.startsWith("/_builder/instance/_master")) {
|
if (ctx.path.startsWith("/_builder/instance/_master")) {
|
||||||
ctx.instance = ctx.master.getFullAccessApiForMaster()
|
const {
|
||||||
|
instance,
|
||||||
|
publicPath,
|
||||||
|
sharedPath,
|
||||||
|
} = await ctx.master.getFullAccessApiForMaster()
|
||||||
|
ctx.instance = instance
|
||||||
|
ctx.publicPath = publicPath
|
||||||
|
ctx.sharedPath = sharedPath
|
||||||
ctx.isAuthenticated = !!ctx.instance
|
ctx.isAuthenticated = !!ctx.instance
|
||||||
} else if (ctx.path.startsWith("/_builder/instance")) {
|
} else if (ctx.path.startsWith("/_builder/instance")) {
|
||||||
const builderAppName = pathParts[3]
|
const builderAppName = pathParts[3]
|
||||||
const instanceId = pathParts[4]
|
const instanceId = pathParts[4]
|
||||||
ctx.instance = ctx.master.getFullAccessApiForInstanceId(
|
const {
|
||||||
|
bbInstance,
|
||||||
|
publicPath,
|
||||||
|
sharedPath,
|
||||||
|
} = await ctx.master.getFullAccessApiForInstanceId(
|
||||||
builderAppName,
|
builderAppName,
|
||||||
instanceId
|
instanceId
|
||||||
).bbInstance
|
)
|
||||||
|
ctx.instance = bbInstance
|
||||||
|
ctx.publicPath = publicPath
|
||||||
|
ctx.sharedPath = sharedPath
|
||||||
ctx.isAuthenticated = !!ctx.instance
|
ctx.isAuthenticated = !!ctx.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +101,7 @@ module.exports = (config, app) => {
|
||||||
.get("/_builder/*", async (ctx, next) => {
|
.get("/_builder/*", async (ctx, next) => {
|
||||||
const path = ctx.path.replace("/_builder", "")
|
const path = ctx.path.replace("/_builder", "")
|
||||||
|
|
||||||
if (path.startsWith("/api/")) {
|
if (path.startsWith("/api/") || path.startsWith("/instance/")) {
|
||||||
await next()
|
await next()
|
||||||
} else {
|
} else {
|
||||||
await send(ctx, path, { root: builderPath })
|
await send(ctx, path, { root: builderPath })
|
||||||
|
|
|
@ -10,11 +10,19 @@ const screen2 = require("../appPackages/testApp/pages/main/screens/screen2.json"
|
||||||
const { readJSON, pathExists, unlink, readFile } = require("fs-extra")
|
const { readJSON, pathExists, unlink, readFile } = require("fs-extra")
|
||||||
const { getHashedCssPaths } = require("../utilities/builder/convertCssToFiles")
|
const { getHashedCssPaths } = require("../utilities/builder/convertCssToFiles")
|
||||||
const listScreens = require("../utilities/builder/listScreens")
|
const listScreens = require("../utilities/builder/listScreens")
|
||||||
|
const { getApisWithFullAccess } = require("../utilities/budibaseApi")
|
||||||
|
|
||||||
const app = require("./testApp")()
|
const app = require("./testApp")()
|
||||||
testComponents.textbox.name = `./customComponents/textbox`
|
testComponents.textbox.name = `./customComponents/textbox`
|
||||||
testMoreComponents.textbox.name = `./moreCustomComponents/textbox`
|
testMoreComponents.textbox.name = `./moreCustomComponents/textbox`
|
||||||
|
|
||||||
|
let _master
|
||||||
|
const getmaster = async () => {
|
||||||
|
if (!_master)
|
||||||
|
_master = await getApisWithFullAccess({}, app.masterAppPackage)
|
||||||
|
return _master
|
||||||
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const testScreen = "./appPackages/testApp/pages/main/screens/newscreen.json"
|
const testScreen = "./appPackages/testApp/pages/main/screens/newscreen.json"
|
||||||
const testScreenAfterMove =
|
const testScreenAfterMove =
|
||||||
|
@ -24,6 +32,23 @@ beforeAll(async () => {
|
||||||
if (await pathExists(testScreenAfterMove)) await unlink(testScreenAfterMove)
|
if (await pathExists(testScreenAfterMove)) await unlink(testScreenAfterMove)
|
||||||
|
|
||||||
await app.start()
|
await app.start()
|
||||||
|
|
||||||
|
const response = await app
|
||||||
|
.post(`/_master/api/authenticate`, {
|
||||||
|
username: app.credentials.masterOwner.username,
|
||||||
|
password: app.credentials.masterOwner.password,
|
||||||
|
})
|
||||||
|
.expect(statusCodes.OK)
|
||||||
|
|
||||||
|
app.credentials.masterOwner.cookie = response.header["set-cookie"]
|
||||||
|
|
||||||
|
const master = await getmaster()
|
||||||
|
const newApp = master.recordApi.getNew("/applications", "application")
|
||||||
|
newApp.name = "testApp"
|
||||||
|
await app
|
||||||
|
.post(`/_master/api/record/${newApp.key}`, newApp)
|
||||||
|
.set("cookie", app.credentials.masterOwner.cookie)
|
||||||
|
.expect(statusCodes.OK)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => await app.destroy())
|
afterAll(async () => await app.destroy())
|
||||||
|
|
|
@ -147,17 +147,19 @@ const getComponentDefinitions = async (appPath, pages, componentLibrary) => {
|
||||||
|
|
||||||
const components = {}
|
const components = {}
|
||||||
const templates = {}
|
const templates = {}
|
||||||
|
const libraryPaths = {}
|
||||||
|
|
||||||
for (let library of componentLibraries) {
|
for (let library of componentLibraries) {
|
||||||
const info = await componentLibraryInfo(appPath, library)
|
const info = await componentLibraryInfo(appPath, library)
|
||||||
merge(components, info.components)
|
merge(components, info.components)
|
||||||
merge(templates, info.templates)
|
merge(templates, info.templates)
|
||||||
|
libraryPaths[library] = components._lib
|
||||||
}
|
}
|
||||||
|
|
||||||
if (components._lib) delete components._lib
|
if (components._lib) delete components._lib
|
||||||
if (templates._lib) delete templates._lib
|
if (templates._lib) delete templates._lib
|
||||||
|
|
||||||
return { components, templates }
|
return { components, templates, libraryPaths }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.getComponentDefinitions = getComponentDefinitions
|
module.exports.getComponentDefinitions = getComponentDefinitions
|
||||||
|
|
|
@ -156,7 +156,8 @@ module.exports = async context => {
|
||||||
|
|
||||||
const getFullAccessApiForInstanceId = async (appname, instanceId, appId) => {
|
const getFullAccessApiForInstanceId = async (appname, instanceId, appId) => {
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
appId = (await getApplication(appname)).id
|
const app = await getApplication(appname)
|
||||||
|
appId = app.id
|
||||||
}
|
}
|
||||||
const instanceKey = `/applications/${appId}/instances/${instanceId}`
|
const instanceKey = `/applications/${appId}/instances/${instanceId}`
|
||||||
const instance = await bb.recordApi.load(instanceKey)
|
const instance = await bb.recordApi.load(instanceKey)
|
||||||
|
@ -172,15 +173,25 @@ module.exports = async context => {
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
bbInstance: await getApisWithFullAccess(
|
bbInstance: await getApisWithFullAccess(
|
||||||
datastoreModule.getDatastore(dsConfig),
|
getInstanceDatastore(dsConfig),
|
||||||
appPackage
|
appPackage
|
||||||
),
|
),
|
||||||
instance,
|
instance,
|
||||||
|
publicPath: appPackage.mainUiPath,
|
||||||
|
sharedPath: appPackage.sharedPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFullAccessApiForMaster = async () =>
|
const getFullAccessApiForMaster = async () => {
|
||||||
await getApisWithFullAccess(masterDatastore, masterAppPackage(context))
|
const masterPkg = masterAppPackage(context)
|
||||||
|
const instance = await getApisWithFullAccess(masterDatastore, masterPkg)
|
||||||
|
|
||||||
|
return {
|
||||||
|
instance,
|
||||||
|
publicPath: masterPkg.unauthenticatedUiPath,
|
||||||
|
sharedPath: masterPkg.sharedPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getInstanceApiForSession = async (appname, sessionId) => {
|
const getInstanceApiForSession = async (appname, sessionId) => {
|
||||||
if (isMaster(appname)) {
|
if (isMaster(appname)) {
|
||||||
|
|
Loading…
Reference in New Issue