diff --git a/.eslintrc.json b/.eslintrc.json index ce4b07ca2f..d475bba8d1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -54,7 +54,8 @@ "ignoreRestSiblings": true } ], - "local-rules/no-budibase-imports": "error" + "no-redeclare": "off", + "@typescript-eslint/no-redeclare": "error" } }, { diff --git a/lerna.json b/lerna.json index 16dc73aa30..b29f82c71a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.25.0", + "version": "2.26.1", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 030fec8728..ff35ccee22 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -73,7 +73,6 @@ "chance": "1.1.8", "ioredis-mock": "8.9.0", "jest": "29.7.0", - "jest-environment-node": "29.7.0", "jest-serial-runner": "1.2.1", "pino-pretty": "10.0.0", "pouchdb-adapter-memory": "7.2.2", diff --git a/packages/backend-core/src/cache/user.ts b/packages/backend-core/src/cache/user.ts index ecfa20f99e..d319c5dcb6 100644 --- a/packages/backend-core/src/cache/user.ts +++ b/packages/backend-core/src/cache/user.ts @@ -69,7 +69,7 @@ async function populateUsersFromDB( export async function getUser( userId: string, tenantId?: string, - populateUser?: any + populateUser?: (userId: string, tenantId: string) => Promise ) { if (!populateUser) { populateUser = populateFromDB @@ -83,7 +83,7 @@ export async function getUser( } const client = await redis.getUserClient() // try cache - let user = await client.get(userId) + let user: User = await client.get(userId) if (!user) { user = await populateUser(userId, tenantId) await client.store(userId, user, EXPIRY_SECONDS) diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts index 69dba27c43..51cd4ec2af 100644 --- a/packages/backend-core/src/middleware/authenticated.ts +++ b/packages/backend-core/src/middleware/authenticated.ts @@ -13,7 +13,7 @@ import { getGlobalDB, doInTenant } from "../context" import { decrypt } from "../security/encryption" import * as identity from "../context/identity" import env from "../environment" -import { Ctx, EndpointMatcher, SessionCookie } from "@budibase/types" +import { Ctx, EndpointMatcher, SessionCookie, User } from "@budibase/types" import { InvalidAPIKeyError, ErrorCode } from "../errors" import tracer from "dd-trace" @@ -41,7 +41,10 @@ function finalise(ctx: any, opts: FinaliseOpts = {}) { ctx.version = opts.version } -async function checkApiKey(apiKey: string, populateUser?: Function) { +async function checkApiKey( + apiKey: string, + populateUser?: (userId: string, tenantId: string) => Promise +) { // check both the primary and the fallback internal api keys // this allows for rotation if (isValidInternalAPIKey(apiKey)) { @@ -128,6 +131,7 @@ export default function ( } else { user = await getUser(userId, session.tenantId) } + // @ts-ignore user.csrfToken = session.csrfToken if (session?.lastAccessedAt < timeMinusOneMinute()) { @@ -167,19 +171,25 @@ export default function ( authenticated = false } - if (user) { + const isUser = ( + user: any + ): user is User & { budibaseAccess?: string } => { + return user && user.email + } + + if (isUser(user)) { tracer.setUser({ - id: user?._id, - tenantId: user?.tenantId, - budibaseAccess: user?.budibaseAccess, - status: user?.status, + id: user._id!, + tenantId: user.tenantId, + budibaseAccess: user.budibaseAccess, + status: user.status, }) } // isAuthenticated is a function, so use a variable to be able to check authed state finalise(ctx, { authenticated, user, internal, version, publicEndpoint }) - if (user && user.email) { + if (isUser(user)) { return identity.doInUserContext(user, ctx, next) } else { return next() diff --git a/packages/builder/package.json b/packages/builder/package.json index f29ae3f7f2..a00936bdca 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -93,7 +93,6 @@ "identity-obj-proxy": "^3.0.0", "jest": "29.7.0", "jsdom": "^21.1.1", - "ncp": "^2.0.0", "svelte-jester": "^1.3.2", "vite": "^4.5.0", "vite-plugin-static-copy": "^0.17.0", diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 3bcbed5dcf..a564fa66a2 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -48,6 +48,7 @@ import { TriggerStepID, ActionStepID } from "constants/backend/automations" import { onMount } from "svelte" import { cloneDeep } from "lodash/fp" + import { FIELDS } from "constants/backend" export let block export let testData @@ -228,6 +229,10 @@ categoryName, bindingName ) => { + const field = Object.values(FIELDS).find( + field => field.type === value.type && field.subtype === value.subtype + ) + return { readableBinding: bindingName ? `${bindingName}.${name}` @@ -238,7 +243,7 @@ icon, category: categoryName, display: { - type: value.type, + type: field?.name || value.type, name, rank: isLoopBlock ? idx + 1 : idx - loopBlockCount, }, @@ -282,6 +287,7 @@ for (const key in table?.schema) { schema[key] = { type: table.schema[key].type, + subtype: table.schema[key].subtype, } } // remove the original binding diff --git a/packages/builder/src/components/backend/DataTable/formula.js b/packages/builder/src/components/backend/DataTable/formula.js index b2ddea2abe..ef35d258a4 100644 --- a/packages/builder/src/components/backend/DataTable/formula.js +++ b/packages/builder/src/components/backend/DataTable/formula.js @@ -56,7 +56,7 @@ export function getBindings({ ) } const field = Object.values(FIELDS).find( - field => field.type === schema.type + field => field.type === schema.type && field.subtype === schema.subtype ) const label = path == null ? column : `${path}.0.${column}` diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index ae0cf62ae4..5c47fc3414 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -12,8 +12,13 @@ OptionSelectDnD, Layout, AbsTooltip, + ProgressCircle, } from "@budibase/bbui" - import { SWITCHABLE_TYPES, ValidColumnNameRegex } from "@budibase/shared-core" + import { + SWITCHABLE_TYPES, + ValidColumnNameRegex, + helpers, + } from "@budibase/shared-core" import { createEventDispatcher, getContext, onMount } from "svelte" import { cloneDeep } from "lodash/fp" import { tables, datasources } from "stores/builder" @@ -30,8 +35,8 @@ import { getBindings } from "components/backend/DataTable/formula" import JSONSchemaModal from "./JSONSchemaModal.svelte" import { - FieldType, BBReferenceFieldSubType, + FieldType, SourceName, } from "@budibase/types" import RelationshipSelector from "components/common/RelationshipSelector.svelte" @@ -67,7 +72,6 @@ let savingColumn let deleteColName let jsonSchemaModal - let allowedTypes = [] let editableColumn = { type: FIELDS.STRING.type, constraints: FIELDS.STRING.constraints, @@ -175,6 +179,11 @@ SWITCHABLE_TYPES[field.type] && !editableColumn?.autocolumn) + $: allowedTypes = getAllowedTypes(datasource).map(t => ({ + fieldId: makeFieldId(t.type, t.subtype), + ...t, + })) + const fieldDefinitions = Object.values(FIELDS).reduce( // Storing the fields by complex field id (acc, field) => ({ @@ -188,7 +197,10 @@ // don't make field IDs for auto types if (type === AUTO_TYPE || autocolumn) { return type.toUpperCase() - } else if (type === FieldType.BB_REFERENCE) { + } else if ( + type === FieldType.BB_REFERENCE || + type === FieldType.BB_REFERENCE_SINGLE + ) { return `${type}${subtype || ""}`.toUpperCase() } else { return type.toUpperCase() @@ -226,11 +238,6 @@ editableColumn.subtype, editableColumn.autocolumn ) - - allowedTypes = getAllowedTypes().map(t => ({ - fieldId: makeFieldId(t.type, t.subtype), - ...t, - })) } } @@ -245,11 +252,11 @@ } async function saveColumn() { - savingColumn = true if (errors?.length) { return } + savingColumn = true let saveColumn = cloneDeep(editableColumn) delete saveColumn.fieldId @@ -264,13 +271,6 @@ if (saveColumn.type !== LINK_TYPE) { delete saveColumn.fieldName } - if (isUsersColumn(saveColumn)) { - if (saveColumn.subtype === BBReferenceFieldSubType.USER) { - saveColumn.relationshipType = RelationshipType.ONE_TO_MANY - } else if (saveColumn.subtype === BBReferenceFieldSubType.USERS) { - saveColumn.relationshipType = RelationshipType.MANY_TO_MANY - } - } try { await tables.saveField({ @@ -289,6 +289,8 @@ } } catch (err) { notifications.error(`Error saving column: ${err.message}`) + } finally { + savingColumn = false } } @@ -363,20 +365,36 @@ deleteColName = "" } - function getAllowedTypes() { + function getAllowedTypes(datasource) { if (originalName) { - const possibleTypes = SWITCHABLE_TYPES[field.type] || [ - editableColumn.type, - ] + let possibleTypes = SWITCHABLE_TYPES[field.type] || [editableColumn.type] + if (helpers.schema.isDeprecatedSingleUserColumn(editableColumn)) { + // This will handle old single users columns + return [ + { + ...FIELDS.USER, + type: FieldType.BB_REFERENCE, + subtype: BBReferenceFieldSubType.USER, + }, + ] + } else if ( + editableColumn.type === FieldType.BB_REFERENCE && + editableColumn.subtype === BBReferenceFieldSubType.USERS + ) { + // This will handle old multi users columns + return [ + { + ...FIELDS.USERS, + subtype: BBReferenceFieldSubType.USERS, + }, + ] + } + return Object.entries(FIELDS) .filter(([_, field]) => possibleTypes.includes(field.type)) .map(([_, fieldDefinition]) => fieldDefinition) } - const isUsers = - editableColumn.type === FieldType.BB_REFERENCE && - editableColumn.subtype === BBReferenceFieldSubType.USERS - if (!externalTable) { return [ FIELDS.STRING, @@ -394,7 +412,8 @@ FIELDS.LINK, FIELDS.FORMULA, FIELDS.JSON, - isUsers ? FIELDS.USERS : FIELDS.USER, + FIELDS.USER, + FIELDS.USERS, FIELDS.AUTO, ] } else { @@ -408,8 +427,12 @@ FIELDS.BOOLEAN, FIELDS.FORMULA, FIELDS.BIGINT, - isUsers ? FIELDS.USERS : FIELDS.USER, + FIELDS.USER, ] + + if (datasource && datasource.source !== SourceName.GOOGLE_SHEETS) { + fields.push(FIELDS.USERS) + } // no-sql or a spreadsheet if (!externalTable || table.sql) { fields = [...fields, FIELDS.LINK, FIELDS.ARRAY] @@ -483,15 +506,6 @@ return newError } - function isUsersColumn(column) { - return ( - column.type === FieldType.BB_REFERENCE && - [BBReferenceFieldSubType.USER, BBReferenceFieldSubType.USERS].includes( - column.subtype - ) - ) - } - onMount(() => { mounted = true }) @@ -690,22 +704,6 @@ - {:else if isUsersColumn(editableColumn) && datasource?.source !== SourceName.GOOGLE_SHEETS} - - handleTypeChange( - makeFieldId( - FieldType.BB_REFERENCE, - e.detail - ? BBReferenceFieldSubType.USERS - : BBReferenceFieldSubType.USER - ) - )} - disabled={!isCreating} - thin - text="Allow multiple users" - /> {/if} {#if editableColumn.type === AUTO_TYPE || editableColumn.autocolumn}