Merge branch 'master' of github.com:budibase/budibase into add-lints-for-only

This commit is contained in:
Sam Rose 2024-03-19 15:25:33 +00:00
commit 9d351bcfed
No known key found for this signature in database
19 changed files with 196 additions and 125 deletions

View File

@ -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 |

View File

@ -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:

View File

@ -1,5 +1,5 @@
{ {
"version": "2.22.2", "version": "2.22.3",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*", "packages/*",

View File

@ -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 {
private static _queue: Queue
public static get queue() {
if (!DocWritethroughProcessor._queue) {
DocWritethroughProcessor._queue = createQueue<ProcessDocMessage>(
JobQueue.DOC_WRITETHROUGH_QUEUE, JobQueue.DOC_WRITETHROUGH_QUEUE,
{ {
jobOptions: { jobOptions: {
attempts: PERSIST_MAX_ATTEMPTS, 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,

View File

@ -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)

View File

@ -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),
]
}

View File

@ -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") {

View File

@ -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`)) {

View File

@ -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)}

View File

@ -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

View File

@ -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)
}
)

View File

@ -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,

View File

@ -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

View File

@ -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/*"]
}
} }
} }

View File

@ -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"],

View File

@ -75,6 +75,7 @@
.filter(field => !field.autocolumn) .filter(field => !field.autocolumn)
.map(field => ({ .map(field => ({
name: field.name, name: field.name,
active: true,
})) }))
} }

View File

@ -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]) {

View File

@ -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", () => {

View File

@ -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
} }