{#if Object.keys(blockRefs).length} {#each blocks as block, idx (block.id)} @@ -179,9 +179,6 @@ display: flex; flex-direction: column; align-items: center; - max-height: 100%; - height: 100%; - width: 100%; } .header-left { @@ -221,15 +218,26 @@ display: flex; justify-content: space-between; align-items: center; - padding-left: var(--spacing-l); - transition: background 130ms ease-out; + padding: var(--spacing-l); flex: 0 0 60px; padding-right: var(--spacing-xl); + position: absolute; + width: 100%; + box-sizing: border-box; + pointer-events: none; + } + + .header > * { + pointer-events: auto; } .controls { display: flex; - gap: var(--spacing-xl); + gap: var(--spacing-l); + } + + .controls .toggle-active :global(.spectrum-Switch-label) { + margin-right: 0px; } .buttons { @@ -243,11 +251,6 @@ cursor: pointer; } - .disabled { - pointer-events: none; - color: var(--spectrum-global-color-gray-500) !important; - } - .group { border-radius: 4px; display: flex; diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index f55feb97ed..0e5a932aa0 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -51,8 +51,13 @@ if (!blockEle) { return } - const { width, height } = blockEle.getBoundingClientRect() - blockDims = { width: width / $view.scale, height: height / $view.scale } + const { width, height, top, left } = blockEle.getBoundingClientRect() + blockDims = { + width: width / $view.scale, + height: height / $view.scale, + top, + left, + } } const loadSteps = blockRef => { @@ -174,12 +179,21 @@ e.stopPropagation() + updateBlockDims() + + const { clientX, clientY } = e view.update(state => ({ ...state, moveStep: { id: block.id, offsetX: $pos.x, offsetY: $pos.y, + w: blockDims.width, + h: blockDims.height, + mouse: { + x: Math.max(Math.round(clientX - blockDims.left), 0), + y: Math.max(Math.round(clientY - blockDims.top), 0), + }, }, })) } @@ -386,6 +400,7 @@ width: 480px; font-size: 16px; border-radius: 4px; + cursor: default; } .block .wrap { width: 100%; diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemActions.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemActions.svelte index 1542c65650..e3014f3e56 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemActions.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItemActions.svelte @@ -47,5 +47,6 @@ display: flex; gap: var(--spacing-m); padding: 8px 12px; + cursor: default; } diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/StepNode.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/StepNode.svelte index 4132cf3516..a320973333 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/StepNode.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/StepNode.svelte @@ -50,9 +50,12 @@ // Register the trigger as the focus element for the automation // Onload, the canvas will use the dimensions to center the step if (stepEle && step.type === "TRIGGER" && !$view.focusEle) { + const { width, height, left, right, top, bottom, x, y } = + stepEle.getBoundingClientRect() + view.update(state => ({ ...state, - focusEle: stepEle.getBoundingClientRect(), + focusEle: { width, height, left, right, top, bottom, x, y }, })) } }) diff --git a/packages/builder/src/stores/builder/automations.js b/packages/builder/src/stores/builder/automations.js index a03517cef0..365f5a8e03 100644 --- a/packages/builder/src/stores/builder/automations.js +++ b/packages/builder/src/stores/builder/automations.js @@ -78,6 +78,27 @@ const automationActions = store => ({ * @param {Object} automation the automaton to be mutated */ moveBlock: async (sourcePath, destPath, automation) => { + // The last part of the source node address, containing the id. + const pathSource = sourcePath.at(-1) + + // The last part of the destination node address, containing the id. + const pathEnd = destPath.at(-1) + + // Check if dragging a step into its own drag zone + const isOwnDragzone = pathSource.id === pathEnd.id + + // Check if dragging the first branch step into the branch node drag zone + const isFirstBranchStep = + pathEnd.branchStepId && + pathEnd.branchIdx === pathSource.branchIdx && + pathSource.stepIdx === 0 + + // If dragging into an area that will not affect the tree structure + // Ignore the drag and drop. + if (isOwnDragzone || isFirstBranchStep) { + return + } + // Use core delete to remove and return the deleted block // from the automation const { deleted, newAutomation } = store.actions.deleteBlock( @@ -90,9 +111,6 @@ const automationActions = store => ({ const newRefs = {} store.actions.traverse(newRefs, newAutomation) - // The last part of the destination node address, containing the id. - const pathEnd = destPath.at(-1) - let finalPath // If dropping in a branch-step dropzone you need to find // the updated parent step route then add the branch details again diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index 1610f36bdc..2840d82f47 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -2,7 +2,7 @@ import { writable, get } from "svelte/store" import { setContext, onMount } from "svelte" import { Layout, Heading, Body } from "@budibase/bbui" - import ErrorSVG from "@budibase/frontend-core/assets/error.svg" + import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw" import { Constants, CookieUtils } from "@budibase/frontend-core" import { getThemeClassNames } from "@budibase/shared-core" import Component from "./Component.svelte" diff --git a/packages/frontend-core/src/api/other.ts b/packages/frontend-core/src/api/other.ts index b676a00fe7..c39538fd7b 100644 --- a/packages/frontend-core/src/api/other.ts +++ b/packages/frontend-core/src/api/other.ts @@ -1,5 +1,5 @@ import { - FetchBuiltinPermissionsRequest, + FetchBuiltinPermissionsResponse, FetchIntegrationsResponse, GetEnvironmentResponse, GetVersionResponse, @@ -11,7 +11,7 @@ export interface OtherEndpoints { getSystemStatus: () => Promise getBudibaseVersion: () => Promise getIntegrations: () => Promise - getBasePermissions: () => Promise + getBasePermissions: () => Promise getEnvironment: () => Promise } diff --git a/packages/server/src/api/controllers/permission.ts b/packages/server/src/api/controllers/permission.ts index 2ef197dbca..e38c736c20 100644 --- a/packages/server/src/api/controllers/permission.ts +++ b/packages/server/src/api/controllers/permission.ts @@ -9,7 +9,7 @@ import { RemovePermissionRequest, RemovePermissionResponse, FetchResourcePermissionInfoResponse, - FetchBuiltinPermissionsRequest, + FetchBuiltinPermissionsResponse, FetchPermissionLevelsRequest, } from "@budibase/types" import { @@ -22,7 +22,7 @@ import { PermissionUpdateType } from "../../sdk/app/permissions" const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS export function fetchBuiltin( - ctx: UserCtx + ctx: UserCtx ) { ctx.body = Object.values(permissions.getBuiltinPermissions()) } diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 0463c0a565..77c05abb95 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -288,19 +288,21 @@ function replaceTableNamesInFilters( for (const key of Object.keys(filter)) { const matches = key.match(`^(?.+)\\.(?.+)`) - const relation = matches?.groups?.["relation"] + // this is the possible table name which we need to check if it needs to be converted + const relatedTableName = matches?.groups?.["relation"] const field = matches?.groups?.["field"] - if (!relation || !field) { + if (!relatedTableName || !field) { continue } - const table = allTables.find(r => r._id === tableId)! - if (Object.values(table.schema).some(f => f.name === relation)) { + const table = allTables.find(r => r._id === tableId) + const isColumnName = !!table?.schema[relatedTableName] + if (!table || isColumnName) { continue } - const matchedTable = allTables.find(t => t.name === relation) + const matchedTable = allTables.find(t => t.name === relatedTableName) const relationship = Object.values(table.schema).find( f => isRelationshipField(f) && f.tableId === matchedTable?._id ) diff --git a/packages/server/src/api/controllers/row/utils/utils.ts b/packages/server/src/api/controllers/row/utils/utils.ts index 5b60143792..baa811fe90 100644 --- a/packages/server/src/api/controllers/row/utils/utils.ts +++ b/packages/server/src/api/controllers/row/utils/utils.ts @@ -1,6 +1,6 @@ import * as utils from "../../../../db/utils" -import { docIds } from "@budibase/backend-core" +import { docIds, sql } from "@budibase/backend-core" import { Ctx, DatasourcePlusQueryResponse, @@ -69,15 +69,15 @@ export function getSourceId(ctx: Ctx): { tableId: string; viewId?: string } { viewId: sourceId, } } - return { tableId: ctx.params.sourceId } + return { tableId: sql.utils.encodeTableId(ctx.params.sourceId) } } // now check for old way of specifying table ID if (ctx.params?.tableId) { - return { tableId: ctx.params.tableId } + return { tableId: sql.utils.encodeTableId(ctx.params.tableId) } } // check body for a table ID if (ctx.request.body?.tableId) { - return { tableId: ctx.request.body.tableId } + return { tableId: sql.utils.encodeTableId(ctx.request.body.tableId) } } throw new Error("Unable to find table ID in request") } diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index 67f303aac3..e97f48afbe 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -71,18 +71,27 @@ if (descriptions.length) { let tableOrViewId: string let rows: Row[] - async function basicRelationshipTables(type: RelationshipType) { + async function basicRelationshipTables( + type: RelationshipType, + opts?: { + tableName?: string + primaryColumn?: string + otherColumn?: string + } + ) { const relatedTable = await createTable({ - name: { name: "name", type: FieldType.STRING }, + name: { name: opts?.tableName || "name", type: FieldType.STRING }, }) + + const columnName = opts?.primaryColumn || "productCat" + //@ts-ignore - API accepts this structure, will build out rest of definition const tableId = await createTable({ - name: { name: "name", type: FieldType.STRING }, - //@ts-ignore - API accepts this structure, will build out rest of definition - productCat: { + name: { name: opts?.tableName || "name", type: FieldType.STRING }, + [columnName]: { type: FieldType.LINK, relationshipType: type, - name: "productCat", - fieldName: "product", + name: columnName, + fieldName: opts?.otherColumn || "product", tableId: relatedTable, constraints: { type: "array", @@ -2776,6 +2785,42 @@ if (descriptions.length) { }) }) + isSql && + describe("relationship - table with spaces", () => { + let primaryTable: Table, row: Row + + beforeAll(async () => { + const { relatedTable, tableId } = + await basicRelationshipTables( + RelationshipType.ONE_TO_MANY, + { + tableName: "table with spaces", + primaryColumn: "related", + otherColumn: "related", + } + ) + tableOrViewId = tableId + primaryTable = relatedTable + + row = await config.api.row.save(primaryTable._id!, { + name: "foo", + }) + + await config.api.row.save(tableOrViewId, { + name: "foo", + related: [row._id], + }) + }) + + it("should be able to search by table name with spaces", async () => { + await expectQuery({ + equal: { + ["table with spaces.name"]: "foo", + }, + }).toContain([{ name: "foo" }]) + }) + }) + isSql && describe.each([ RelationshipType.MANY_TO_ONE, diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 6c1065e847..70c69b3c60 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -1,4 +1,10 @@ -import { context, db as dbCore, docIds, utils } from "@budibase/backend-core" +import { + context, + db as dbCore, + docIds, + utils, + sql, +} from "@budibase/backend-core" import { DatabaseQueryOpts, Datasource, @@ -328,7 +334,7 @@ export function extractViewInfoFromID(viewId: string) { const regex = new RegExp(`^(?.+)${SEPARATOR}([^${SEPARATOR}]+)$`) const res = regex.exec(viewId) return { - tableId: res!.groups!["tableId"], + tableId: sql.utils.encodeTableId(res!.groups!["tableId"]), } } diff --git a/packages/types/src/api/web/app/permission.ts b/packages/types/src/api/web/app/permission.ts index 1a19fb0834..407dc2be86 100644 --- a/packages/types/src/api/web/app/permission.ts +++ b/packages/types/src/api/web/app/permission.ts @@ -1,6 +1,6 @@ import { BuiltinPermission, PermissionLevel } from "../../../sdk" -export type FetchBuiltinPermissionsRequest = BuiltinPermission[] +export type FetchBuiltinPermissionsResponse = BuiltinPermission[] export type FetchPermissionLevelsRequest = string[] diff --git a/packages/types/src/api/web/global/self.ts b/packages/types/src/api/web/global/self.ts index 4ba51d2cd5..5f21a8ddc5 100644 --- a/packages/types/src/api/web/global/self.ts +++ b/packages/types/src/api/web/global/self.ts @@ -1,7 +1,7 @@ import { DevInfo, User } from "../../../documents" export interface GenerateAPIKeyRequest { - userId: string + userId?: string } export interface GenerateAPIKeyResponse extends DevInfo {} diff --git a/packages/types/src/documents/app/app.ts b/packages/types/src/documents/app/app.ts index 06fca8307c..e31dd1e9ac 100644 --- a/packages/types/src/documents/app/app.ts +++ b/packages/types/src/documents/app/app.ts @@ -28,6 +28,7 @@ export interface App extends Document { upgradableVersion?: string snippets?: Snippet[] creationVersion?: string + updatedBy?: string } export interface AppInstance {