Merge branch 'master' of github.com:budibase/budibase into reenable-no-case-declarations
This commit is contained in:
commit
f6669c25cf
|
@ -140,7 +140,7 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml
|
||||||
| ingress.className | string | `""` | What ingress class to use. |
|
| ingress.className | string | `""` | What ingress class to use. |
|
||||||
| ingress.enabled | bool | `true` | Whether to create an Ingress resource pointing to the Budibase proxy. |
|
| ingress.enabled | bool | `true` | Whether to create an Ingress resource pointing to the Budibase proxy. |
|
||||||
| ingress.hosts | list | `[]` | Standard hosts block for the Ingress resource. Defaults to pointing to the Budibase proxy. |
|
| ingress.hosts | list | `[]` | Standard hosts block for the Ingress resource. Defaults to pointing to the Budibase proxy. |
|
||||||
| nameOverride | string | `""` | Override the name of the deploymen. Defaults to {{ .Chart.Name }}. |
|
| nameOverride | string | `""` | Override the name of the deployment. Defaults to {{ .Chart.Name }}. |
|
||||||
| service.port | int | `10000` | Port to expose on the service. |
|
| service.port | int | `10000` | Port to expose on the service. |
|
||||||
| service.type | string | `"ClusterIP"` | Service type for the service that points to the main Budibase proxy pod. |
|
| service.type | string | `"ClusterIP"` | Service type for the service that points to the main Budibase proxy pod. |
|
||||||
| serviceAccount.annotations | object | `{}` | Annotations to add to the service account |
|
| serviceAccount.annotations | object | `{}` | Annotations to add to the service account |
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# -- Passed to all pods created by this chart. Should not ordinarily need to be changed.
|
# -- Passed to all pods created by this chart. Should not ordinarily need to be changed.
|
||||||
imagePullSecrets: []
|
imagePullSecrets: []
|
||||||
# -- Override the name of the deploymen. Defaults to {{ .Chart.Name }}.
|
# -- Override the name of the deployment. Defaults to {{ .Chart.Name }}.
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
|
||||||
serviceAccount:
|
serviceAccount:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.22.1",
|
"version": "2.22.3",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { AnyDocument, Database } from "@budibase/types"
|
import { AnyDocument, Database } from "@budibase/types"
|
||||||
|
|
||||||
import { JobQueue, createQueue } from "../queue"
|
import { JobQueue, Queue, createQueue } from "../queue"
|
||||||
import * as dbUtils from "../db"
|
import * as dbUtils from "../db"
|
||||||
|
|
||||||
interface ProcessDocMessage {
|
interface ProcessDocMessage {
|
||||||
|
@ -12,18 +12,26 @@ interface ProcessDocMessage {
|
||||||
const PERSIST_MAX_ATTEMPTS = 100
|
const PERSIST_MAX_ATTEMPTS = 100
|
||||||
let processor: DocWritethroughProcessor | undefined
|
let processor: DocWritethroughProcessor | undefined
|
||||||
|
|
||||||
export const docWritethroughProcessorQueue = createQueue<ProcessDocMessage>(
|
export class DocWritethroughProcessor {
|
||||||
JobQueue.DOC_WRITETHROUGH_QUEUE,
|
private static _queue: Queue
|
||||||
{
|
|
||||||
jobOptions: {
|
public static get queue() {
|
||||||
attempts: PERSIST_MAX_ATTEMPTS,
|
if (!DocWritethroughProcessor._queue) {
|
||||||
},
|
DocWritethroughProcessor._queue = createQueue<ProcessDocMessage>(
|
||||||
}
|
JobQueue.DOC_WRITETHROUGH_QUEUE,
|
||||||
)
|
{
|
||||||
|
jobOptions: {
|
||||||
|
attempts: PERSIST_MAX_ATTEMPTS,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DocWritethroughProcessor._queue
|
||||||
|
}
|
||||||
|
|
||||||
class DocWritethroughProcessor {
|
|
||||||
init() {
|
init() {
|
||||||
docWritethroughProcessorQueue.process(async message => {
|
DocWritethroughProcessor.queue.process(async message => {
|
||||||
try {
|
try {
|
||||||
await this.persistToDb(message.data)
|
await this.persistToDb(message.data)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
@ -76,7 +84,7 @@ export class DocWritethrough {
|
||||||
}
|
}
|
||||||
|
|
||||||
async patch(data: Record<string, any>) {
|
async patch(data: Record<string, any>) {
|
||||||
await docWritethroughProcessorQueue.add({
|
await DocWritethroughProcessor.queue.add({
|
||||||
dbName: this.db.name,
|
dbName: this.db.name,
|
||||||
docId: this.docId,
|
docId: this.docId,
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { getDB } from "../../db"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DocWritethrough,
|
DocWritethrough,
|
||||||
docWritethroughProcessorQueue,
|
DocWritethroughProcessor,
|
||||||
init,
|
init,
|
||||||
} from "../docWritethrough"
|
} from "../docWritethrough"
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import InMemoryQueue from "../../queue/inMemoryQueue"
|
||||||
const initialTime = Date.now()
|
const initialTime = Date.now()
|
||||||
|
|
||||||
async function waitForQueueCompletion() {
|
async function waitForQueueCompletion() {
|
||||||
const queue: InMemoryQueue = docWritethroughProcessorQueue as never
|
const queue: InMemoryQueue = DocWritethroughProcessor.queue as never
|
||||||
await queue.waitForCompletion()
|
await queue.waitForCompletion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ describe("docWritethrough", () => {
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
const queueMessageSpy = jest.spyOn(docWritethroughProcessorQueue, "add")
|
const queueMessageSpy = jest.spyOn(DocWritethroughProcessor.queue, "add")
|
||||||
|
|
||||||
await config.doInTenant(async () => {
|
await config.doInTenant(async () => {
|
||||||
let patches = await parallelPatch(5)
|
let patches = await parallelPatch(5)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { generator } from "./generator"
|
||||||
|
|
||||||
export function userGroup(): UserGroup {
|
export function userGroup(): UserGroup {
|
||||||
return {
|
return {
|
||||||
name: generator.word(),
|
name: generator.guid(),
|
||||||
icon: generator.word(),
|
icon: generator.word(),
|
||||||
color: generator.word(),
|
color: generator.word(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,3 +279,11 @@ export const buildContextTreeLookupMap = rootComponent => {
|
||||||
})
|
})
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get a flat list of ids for all descendants of a component
|
||||||
|
export const getChildIdsForComponent = component => {
|
||||||
|
return [
|
||||||
|
component._id,
|
||||||
|
...(component?._children ?? []).map(getChildIdsForComponent).flat(1),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
navigationStore,
|
navigationStore,
|
||||||
selectedScreen,
|
selectedScreen,
|
||||||
hoverStore,
|
hoverStore,
|
||||||
|
componentTreeNodesStore,
|
||||||
snippets,
|
snippets,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
@ -132,6 +133,7 @@
|
||||||
error = event.error || "An unknown error occurred"
|
error = event.error || "An unknown error occurred"
|
||||||
} else if (type === "select-component" && data.id) {
|
} else if (type === "select-component" && data.id) {
|
||||||
componentStore.select(data.id)
|
componentStore.select(data.id)
|
||||||
|
componentTreeNodesStore.makeNodeVisible(data.id)
|
||||||
} else if (type === "hover-component") {
|
} else if (type === "hover-component") {
|
||||||
hoverStore.hover(data.id, false)
|
hoverStore.hover(data.id, false)
|
||||||
} else if (type === "update-prop") {
|
} else if (type === "update-prop") {
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
selectedScreen,
|
selectedScreen,
|
||||||
componentStore,
|
componentStore,
|
||||||
selectedComponent,
|
selectedComponent,
|
||||||
|
componentTreeNodesStore,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
import { findComponent } from "helpers/components"
|
import { findComponent, getChildIdsForComponent } from "helpers/components"
|
||||||
import { goto, isActive } from "@roxi/routify"
|
import { goto, isActive } from "@roxi/routify"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import componentTreeNodesStore from "stores/portal/componentTreeNodesStore"
|
|
||||||
|
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let confirmEjectDialog
|
let confirmEjectDialog
|
||||||
|
@ -63,38 +63,25 @@
|
||||||
componentStore.selectNext()
|
componentStore.selectNext()
|
||||||
},
|
},
|
||||||
["ArrowRight"]: component => {
|
["ArrowRight"]: component => {
|
||||||
componentTreeNodesStore.expandNode(component._id)
|
componentTreeNodesStore.expandNodes([component._id])
|
||||||
},
|
},
|
||||||
["ArrowLeft"]: component => {
|
["ArrowLeft"]: component => {
|
||||||
componentTreeNodesStore.collapseNode(component._id)
|
// Select the collapsing root component to ensure the currently selected component is not
|
||||||
|
// hidden in a collapsed node
|
||||||
|
componentStore.select(component._id)
|
||||||
|
componentTreeNodesStore.collapseNodes([component._id])
|
||||||
},
|
},
|
||||||
["Ctrl+ArrowRight"]: component => {
|
["Ctrl+ArrowRight"]: component => {
|
||||||
componentTreeNodesStore.expandNode(component._id)
|
const childIds = getChildIdsForComponent(component)
|
||||||
|
componentTreeNodesStore.expandNodes(childIds)
|
||||||
const expandChildren = component => {
|
|
||||||
const children = component._children ?? []
|
|
||||||
|
|
||||||
children.forEach(child => {
|
|
||||||
componentTreeNodesStore.expandNode(child._id)
|
|
||||||
expandChildren(child)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
expandChildren(component)
|
|
||||||
},
|
},
|
||||||
["Ctrl+ArrowLeft"]: component => {
|
["Ctrl+ArrowLeft"]: component => {
|
||||||
componentTreeNodesStore.collapseNode(component._id)
|
// Select the collapsing root component to ensure the currently selected component is not
|
||||||
|
// hidden in a collapsed node
|
||||||
|
componentStore.select(component._id)
|
||||||
|
|
||||||
const collapseChildren = component => {
|
const childIds = getChildIdsForComponent(component)
|
||||||
const children = component._children ?? []
|
componentTreeNodesStore.collapseNodes(childIds)
|
||||||
|
|
||||||
children.forEach(child => {
|
|
||||||
componentTreeNodesStore.collapseNode(child._id)
|
|
||||||
collapseChildren(child)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
collapseChildren(component)
|
|
||||||
},
|
},
|
||||||
["Escape"]: () => {
|
["Escape"]: () => {
|
||||||
if ($isActive(`./:componentId/new`)) {
|
if ($isActive(`./:componentId/new`)) {
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
componentStore,
|
componentStore,
|
||||||
userSelectedResourceMap,
|
userSelectedResourceMap,
|
||||||
selectedComponent,
|
selectedComponent,
|
||||||
selectedComponentPath,
|
|
||||||
hoverStore,
|
hoverStore,
|
||||||
|
componentTreeNodesStore,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
import {
|
import {
|
||||||
findComponentPath,
|
findComponentPath,
|
||||||
|
@ -17,7 +17,6 @@
|
||||||
} from "helpers/components"
|
} from "helpers/components"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { dndStore } from "./dndStore"
|
import { dndStore } from "./dndStore"
|
||||||
import componentTreeNodesStore from "stores/portal/componentTreeNodesStore"
|
|
||||||
|
|
||||||
export let components = []
|
export let components = []
|
||||||
export let level = 0
|
export let level = 0
|
||||||
|
@ -64,14 +63,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOpen = (component, selectedComponentPath, openNodes) => {
|
const isOpen = component => {
|
||||||
if (!component?._children?.length) {
|
if (!component?._children?.length) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (selectedComponentPath.slice(0, -1).includes(component._id)) {
|
return componentTreeNodesStore.isNodeExpanded(component._id)
|
||||||
return true
|
|
||||||
}
|
|
||||||
return openNodes[`nodeOpen-${component._id}`]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isChildOfSelectedComponent = component => {
|
const isChildOfSelectedComponent = component => {
|
||||||
|
@ -83,6 +79,11 @@
|
||||||
return findComponentPath($selectedComponent, component._id)?.length > 0
|
return findComponentPath($selectedComponent, component._id)?.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleIconClick = componentId => {
|
||||||
|
componentStore.select(componentId)
|
||||||
|
componentTreeNodesStore.toggleNode(componentId)
|
||||||
|
}
|
||||||
|
|
||||||
const hover = hoverStore.hover
|
const hover = hoverStore.hover
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<ul>
|
<ul>
|
||||||
{#each filteredComponents || [] as component, index (component._id)}
|
{#each filteredComponents || [] as component, index (component._id)}
|
||||||
{@const opened = isOpen(component, $selectedComponentPath, openNodes)}
|
{@const opened = isOpen(component, openNodes)}
|
||||||
<li
|
<li
|
||||||
on:click|stopPropagation={() => {
|
on:click|stopPropagation={() => {
|
||||||
componentStore.select(component._id)
|
componentStore.select(component._id)
|
||||||
|
@ -104,7 +105,7 @@
|
||||||
on:dragend={dndStore.actions.reset}
|
on:dragend={dndStore.actions.reset}
|
||||||
on:dragstart={() => dndStore.actions.dragstart(component)}
|
on:dragstart={() => dndStore.actions.dragstart(component)}
|
||||||
on:dragover={dragover(component, index)}
|
on:dragover={dragover(component, index)}
|
||||||
on:iconClick={() => componentTreeNodesStore.toggleNode(component._id)}
|
on:iconClick={() => handleIconClick(component._id)}
|
||||||
on:drop={onDrop}
|
on:drop={onDrop}
|
||||||
hovering={$hoverStore.componentId === component._id}
|
hovering={$hoverStore.componentId === component._id}
|
||||||
on:mouseenter={() => hover(component._id)}
|
on:mouseenter={() => hover(component._id)}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import { createSessionStorageStore } from "@budibase/frontend-core"
|
||||||
|
import { selectedScreen as selectedScreenStore } from "./screens"
|
||||||
|
import { findComponentPath } from "helpers/components"
|
||||||
|
|
||||||
|
const baseStore = createSessionStorageStore("openNodes", {})
|
||||||
|
|
||||||
|
const toggleNode = componentId => {
|
||||||
|
baseStore.update(openNodes => {
|
||||||
|
openNodes[`nodeOpen-${componentId}`] = !openNodes[`nodeOpen-${componentId}`]
|
||||||
|
|
||||||
|
return openNodes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandNodes = componentIds => {
|
||||||
|
baseStore.update(openNodes => {
|
||||||
|
const newNodes = Object.fromEntries(
|
||||||
|
componentIds.map(id => [`nodeOpen-${id}`, true])
|
||||||
|
)
|
||||||
|
|
||||||
|
return { ...openNodes, ...newNodes }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const collapseNodes = componentIds => {
|
||||||
|
baseStore.update(openNodes => {
|
||||||
|
const newNodes = Object.fromEntries(
|
||||||
|
componentIds.map(id => [`nodeOpen-${id}`, false])
|
||||||
|
)
|
||||||
|
|
||||||
|
return { ...openNodes, ...newNodes }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will ensure all parents of a node are expanded so that it is visible in the tree
|
||||||
|
const makeNodeVisible = componentId => {
|
||||||
|
const selectedScreen = get(selectedScreenStore)
|
||||||
|
|
||||||
|
const path = findComponentPath(selectedScreen.props, componentId)
|
||||||
|
|
||||||
|
const componentIds = path.map(component => component._id)
|
||||||
|
|
||||||
|
baseStore.update(openNodes => {
|
||||||
|
const newNodes = Object.fromEntries(
|
||||||
|
componentIds.map(id => [`nodeOpen-${id}`, true])
|
||||||
|
)
|
||||||
|
|
||||||
|
return { ...openNodes, ...newNodes }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNodeExpanded = componentId => {
|
||||||
|
const openNodes = get(baseStore)
|
||||||
|
return !!openNodes[`nodeOpen-${componentId}`]
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = {
|
||||||
|
subscribe: baseStore.subscribe,
|
||||||
|
toggleNode,
|
||||||
|
expandNodes,
|
||||||
|
makeNodeVisible,
|
||||||
|
collapseNodes,
|
||||||
|
isNodeExpanded,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default store
|
|
@ -19,6 +19,7 @@ import {
|
||||||
appStore,
|
appStore,
|
||||||
previewStore,
|
previewStore,
|
||||||
tables,
|
tables,
|
||||||
|
componentTreeNodesStore,
|
||||||
} from "stores/builder/index"
|
} from "stores/builder/index"
|
||||||
import { buildFormSchema, getSchemaForDatasource } from "dataBinding"
|
import { buildFormSchema, getSchemaForDatasource } from "dataBinding"
|
||||||
import {
|
import {
|
||||||
|
@ -29,7 +30,6 @@ import {
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import BudiStore from "../BudiStore"
|
import BudiStore from "../BudiStore"
|
||||||
import { Utils } from "@budibase/frontend-core"
|
import { Utils } from "@budibase/frontend-core"
|
||||||
import componentTreeNodesStore from "stores/portal/componentTreeNodesStore"
|
|
||||||
|
|
||||||
export const INITIAL_COMPONENTS_STATE = {
|
export const INITIAL_COMPONENTS_STATE = {
|
||||||
components: {},
|
components: {},
|
||||||
|
@ -653,8 +653,11 @@ export class ComponentStore extends BudiStore {
|
||||||
this.update(state => {
|
this.update(state => {
|
||||||
state.selectedScreenId = targetScreenId
|
state.selectedScreenId = targetScreenId
|
||||||
state.selectedComponentId = newComponentId
|
state.selectedComponentId = newComponentId
|
||||||
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
||||||
|
componentTreeNodesStore.makeNodeVisible(newComponentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrevious() {
|
getPrevious() {
|
||||||
|
@ -663,7 +666,6 @@ export class ComponentStore extends BudiStore {
|
||||||
const screen = get(selectedScreen)
|
const screen = get(selectedScreen)
|
||||||
const parent = findComponentParent(screen.props, componentId)
|
const parent = findComponentParent(screen.props, componentId)
|
||||||
const index = parent?._children.findIndex(x => x._id === componentId)
|
const index = parent?._children.findIndex(x => x._id === componentId)
|
||||||
const componentTreeNodes = get(componentTreeNodesStore)
|
|
||||||
|
|
||||||
// Check for screen and navigation component edge cases
|
// Check for screen and navigation component edge cases
|
||||||
const screenComponentId = `${screen._id}-screen`
|
const screenComponentId = `${screen._id}-screen`
|
||||||
|
@ -680,16 +682,16 @@ export class ComponentStore extends BudiStore {
|
||||||
|
|
||||||
// If we have siblings above us, choose the sibling or a descendant
|
// If we have siblings above us, choose the sibling or a descendant
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
// If sibling before us accepts children, select a descendant
|
// If sibling before us accepts children, and is not collapsed, select a descendant
|
||||||
const previousSibling = parent._children[index - 1]
|
const previousSibling = parent._children[index - 1]
|
||||||
if (
|
if (
|
||||||
previousSibling._children?.length &&
|
previousSibling._children?.length &&
|
||||||
componentTreeNodes[`nodeOpen-${previousSibling._id}`]
|
componentTreeNodesStore.isNodeExpanded(previousSibling._id)
|
||||||
) {
|
) {
|
||||||
let target = previousSibling
|
let target = previousSibling
|
||||||
while (
|
while (
|
||||||
target._children?.length &&
|
target._children?.length &&
|
||||||
componentTreeNodes[`nodeOpen-${target._id}`]
|
componentTreeNodesStore.isNodeExpanded(target._id)
|
||||||
) {
|
) {
|
||||||
target = target._children[target._children.length - 1]
|
target = target._children[target._children.length - 1]
|
||||||
}
|
}
|
||||||
|
@ -711,7 +713,6 @@ export class ComponentStore extends BudiStore {
|
||||||
const screen = get(selectedScreen)
|
const screen = get(selectedScreen)
|
||||||
const parent = findComponentParent(screen.props, componentId)
|
const parent = findComponentParent(screen.props, componentId)
|
||||||
const index = parent?._children.findIndex(x => x._id === componentId)
|
const index = parent?._children.findIndex(x => x._id === componentId)
|
||||||
const componentTreeNodes = get(componentTreeNodesStore)
|
|
||||||
|
|
||||||
// Check for screen and navigation component edge cases
|
// Check for screen and navigation component edge cases
|
||||||
const screenComponentId = `${screen._id}-screen`
|
const screenComponentId = `${screen._id}-screen`
|
||||||
|
@ -720,11 +721,11 @@ export class ComponentStore extends BudiStore {
|
||||||
return navComponentId
|
return navComponentId
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have children, select first child
|
// If we have children, select first child, and the node is not collapsed
|
||||||
if (
|
if (
|
||||||
component._children?.length &&
|
component._children?.length &&
|
||||||
(state.selectedComponentId === navComponentId ||
|
(state.selectedComponentId === navComponentId ||
|
||||||
componentTreeNodes[`nodeOpen-${component._id}`])
|
componentTreeNodesStore.isNodeExpanded(component._id))
|
||||||
) {
|
) {
|
||||||
return component._children[0]._id
|
return component._children[0]._id
|
||||||
} else if (!parent) {
|
} else if (!parent) {
|
||||||
|
@ -803,7 +804,10 @@ export class ComponentStore extends BudiStore {
|
||||||
// sibling
|
// sibling
|
||||||
const previousSibling = parent._children[index - 1]
|
const previousSibling = parent._children[index - 1]
|
||||||
const definition = this.getDefinition(previousSibling._component)
|
const definition = this.getDefinition(previousSibling._component)
|
||||||
if (definition.hasChildren) {
|
if (
|
||||||
|
definition.hasChildren &&
|
||||||
|
componentTreeNodesStore.isNodeExpanded(previousSibling._id)
|
||||||
|
) {
|
||||||
previousSibling._children.push(originalComponent)
|
previousSibling._children.push(originalComponent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -852,10 +856,13 @@ export class ComponentStore extends BudiStore {
|
||||||
|
|
||||||
// Move below the next sibling if we are not the last sibling
|
// Move below the next sibling if we are not the last sibling
|
||||||
if (index < parent._children.length) {
|
if (index < parent._children.length) {
|
||||||
// If the next sibling has children, become the first child
|
// If the next sibling has children, and is not collapsed, become the first child
|
||||||
const nextSibling = parent._children[index]
|
const nextSibling = parent._children[index]
|
||||||
const definition = this.getDefinition(nextSibling._component)
|
const definition = this.getDefinition(nextSibling._component)
|
||||||
if (definition.hasChildren) {
|
if (
|
||||||
|
definition.hasChildren &&
|
||||||
|
componentTreeNodesStore.isNodeExpanded(nextSibling._id)
|
||||||
|
) {
|
||||||
nextSibling._children.splice(0, 0, originalComponent)
|
nextSibling._children.splice(0, 0, originalComponent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1151,13 +1158,3 @@ export const selectedComponent = derived(
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export const selectedComponentPath = derived(
|
|
||||||
[componentStore, selectedScreen],
|
|
||||||
([$store, $selectedScreen]) => {
|
|
||||||
return findComponentPath(
|
|
||||||
$selectedScreen?.props,
|
|
||||||
$store.selectedComponentId
|
|
||||||
).map(component => component._id)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { layoutStore } from "./layouts.js"
|
import { layoutStore } from "./layouts.js"
|
||||||
import { appStore } from "./app.js"
|
import { appStore } from "./app.js"
|
||||||
import {
|
import { componentStore, selectedComponent } from "./components"
|
||||||
componentStore,
|
|
||||||
selectedComponent,
|
|
||||||
selectedComponentPath,
|
|
||||||
} from "./components"
|
|
||||||
import { navigationStore } from "./navigation.js"
|
import { navigationStore } from "./navigation.js"
|
||||||
import { themeStore } from "./theme.js"
|
import { themeStore } from "./theme.js"
|
||||||
import { screenStore, selectedScreen, sortedScreens } from "./screens.js"
|
import { screenStore, selectedScreen, sortedScreens } from "./screens.js"
|
||||||
|
@ -31,8 +27,10 @@ import { integrations } from "./integrations"
|
||||||
import { sortedIntegrations } from "./sortedIntegrations"
|
import { sortedIntegrations } from "./sortedIntegrations"
|
||||||
import { queries } from "./queries"
|
import { queries } from "./queries"
|
||||||
import { flags } from "./flags"
|
import { flags } from "./flags"
|
||||||
|
import componentTreeNodesStore from "./componentTreeNodes"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
componentTreeNodesStore,
|
||||||
layoutStore,
|
layoutStore,
|
||||||
appStore,
|
appStore,
|
||||||
componentStore,
|
componentStore,
|
||||||
|
@ -51,7 +49,6 @@ export {
|
||||||
isOnlyUser,
|
isOnlyUser,
|
||||||
deploymentStore,
|
deploymentStore,
|
||||||
selectedComponent,
|
selectedComponent,
|
||||||
selectedComponentPath,
|
|
||||||
tables,
|
tables,
|
||||||
views,
|
views,
|
||||||
viewsV2,
|
viewsV2,
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { createSessionStorageStore } from "@budibase/frontend-core"
|
|
||||||
|
|
||||||
const baseStore = createSessionStorageStore("openNodes", {})
|
|
||||||
|
|
||||||
const toggleNode = componentId => {
|
|
||||||
baseStore.update(openNodes => {
|
|
||||||
openNodes[`nodeOpen-${componentId}`] = !openNodes[`nodeOpen-${componentId}`]
|
|
||||||
|
|
||||||
return openNodes
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const expandNode = componentId => {
|
|
||||||
baseStore.update(openNodes => {
|
|
||||||
openNodes[`nodeOpen-${componentId}`] = true
|
|
||||||
|
|
||||||
return openNodes
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const collapseNode = componentId => {
|
|
||||||
baseStore.update(openNodes => {
|
|
||||||
openNodes[`nodeOpen-${componentId}`] = false
|
|
||||||
|
|
||||||
return openNodes
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const store = {
|
|
||||||
subscribe: baseStore.subscribe,
|
|
||||||
toggleNode,
|
|
||||||
expandNode,
|
|
||||||
collapseNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default store
|
|
|
@ -4,6 +4,16 @@
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"baseUrl": "."
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"assets/*": ["./assets/*"],
|
||||||
|
"@budibase/*": [
|
||||||
|
"../*/src/index.ts",
|
||||||
|
"../*/src/index.js",
|
||||||
|
"../*",
|
||||||
|
"../../node_modules/@budibase/*"
|
||||||
|
],
|
||||||
|
"*": ["./src/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"types": ["node", "jest"],
|
"types": ["node", "jest"],
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@budibase/types": ["../types/src"],
|
"@budibase/types": ["../types/src"],
|
||||||
"@budibase/backend-core": ["../backend-core/src"],
|
"@budibase/backend-core": ["../backend-core/src"],
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
.filter(field => !field.autocolumn)
|
.filter(field => !field.autocolumn)
|
||||||
.map(field => ({
|
.map(field => ({
|
||||||
name: field.name,
|
name: field.name,
|
||||||
|
active: true,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,22 +14,35 @@ import {
|
||||||
SessionCookie,
|
SessionCookie,
|
||||||
JsonFieldSubType,
|
JsonFieldSubType,
|
||||||
QueryResponse,
|
QueryResponse,
|
||||||
QueryPreview,
|
|
||||||
QuerySchema,
|
QuerySchema,
|
||||||
FieldType,
|
FieldType,
|
||||||
ExecuteQueryRequest,
|
ExecuteQueryRequest,
|
||||||
ExecuteQueryResponse,
|
ExecuteQueryResponse,
|
||||||
Row,
|
|
||||||
QueryParameter,
|
QueryParameter,
|
||||||
PreviewQueryRequest,
|
PreviewQueryRequest,
|
||||||
PreviewQueryResponse,
|
PreviewQueryResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { ValidQueryNameRegex, utils as JsonUtils } from "@budibase/shared-core"
|
import { ValidQueryNameRegex, utils as JsonUtils } from "@budibase/shared-core"
|
||||||
|
import { findHBSBlocks } from "@budibase/string-templates"
|
||||||
|
|
||||||
const Runner = new Thread(ThreadType.QUERY, {
|
const Runner = new Thread(ThreadType.QUERY, {
|
||||||
timeoutMs: env.QUERY_THREAD_TIMEOUT,
|
timeoutMs: env.QUERY_THREAD_TIMEOUT,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function validateQueryInputs(parameters: Record<string, string>) {
|
||||||
|
for (let entry of Object.entries(parameters)) {
|
||||||
|
const [key, value] = entry
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (findHBSBlocks(value).length !== 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Parameter '${key}' input contains a handlebars binding - this is not allowed.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetch(ctx: UserCtx) {
|
export async function fetch(ctx: UserCtx) {
|
||||||
ctx.body = await sdk.queries.fetch()
|
ctx.body = await sdk.queries.fetch()
|
||||||
}
|
}
|
||||||
|
@ -123,10 +136,10 @@ function getAuthConfig(ctx: UserCtx) {
|
||||||
|
|
||||||
function enrichParameters(
|
function enrichParameters(
|
||||||
queryParameters: QueryParameter[],
|
queryParameters: QueryParameter[],
|
||||||
requestParameters: { [key: string]: string } = {}
|
requestParameters: Record<string, string> = {}
|
||||||
): {
|
): Record<string, string> {
|
||||||
[key: string]: string
|
// first check parameters are all valid
|
||||||
} {
|
validateQueryInputs(requestParameters)
|
||||||
// make sure parameters are fully enriched with defaults
|
// make sure parameters are fully enriched with defaults
|
||||||
for (let parameter of queryParameters) {
|
for (let parameter of queryParameters) {
|
||||||
if (!requestParameters[parameter.name]) {
|
if (!requestParameters[parameter.name]) {
|
||||||
|
|
|
@ -411,6 +411,21 @@ describe("/queries", () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("shouldn't allow handlebars to be passed as parameters", async () => {
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/queries/${query._id}`)
|
||||||
|
.send({
|
||||||
|
parameters: {
|
||||||
|
a: "{{ 'test' }}",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect(400)
|
||||||
|
expect(res.body.message).toEqual(
|
||||||
|
"Parameter 'a' input contains a handlebars binding - this is not allowed."
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("variables", () => {
|
describe("variables", () => {
|
||||||
|
|
|
@ -11,7 +11,7 @@ export interface PreviewQueryResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExecuteQueryRequest {
|
export interface ExecuteQueryRequest {
|
||||||
parameters?: { [key: string]: string }
|
parameters?: Record<string, string>
|
||||||
pagination?: any
|
pagination?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue