Merge pull request #13656 from Budibase/budi-8123/test-singleuser-binding

Updating binding tests to test single column
This commit is contained in:
Adria Navarro 2024-05-13 13:32:37 +02:00 committed by GitHub
commit ec61e2e085
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 172 additions and 22 deletions

View File

@ -262,11 +262,19 @@ describe.each([
{ name: "serverDate", appointment: serverTime.toISOString() }, { name: "serverDate", appointment: serverTime.toISOString() },
{ {
name: "single user, session user", name: "single user, session user",
single_user: JSON.stringify([currentUser]), single_user: JSON.stringify(currentUser),
}, },
{ {
name: "single user", name: "single user",
single_user: JSON.stringify([globalUsers[0]]), single_user: JSON.stringify(globalUsers[0]),
},
{
name: "deprecated single user, session user",
deprecated_single_user: JSON.stringify([currentUser]),
},
{
name: "deprecated single user",
deprecated_single_user: JSON.stringify([globalUsers[0]]),
}, },
{ {
name: "multi user", name: "multi user",
@ -276,6 +284,14 @@ describe.each([
name: "multi user with session user", name: "multi user with session user",
multi_user: JSON.stringify([...globalUsers, currentUser]), multi_user: JSON.stringify([...globalUsers, currentUser]),
}, },
{
name: "deprecated multi user",
deprecated_multi_user: JSON.stringify(globalUsers),
},
{
name: "deprecated multi user with session user",
deprecated_multi_user: JSON.stringify([...globalUsers, currentUser]),
},
] ]
} }
@ -301,13 +317,29 @@ describe.each([
appointment: { name: "appointment", type: FieldType.DATETIME }, appointment: { name: "appointment", type: FieldType.DATETIME },
single_user: { single_user: {
name: "single_user", name: "single_user",
type: FieldType.BB_REFERENCE_SINGLE,
subtype: BBReferenceFieldSubType.USER,
},
deprecated_single_user: {
name: "deprecated_single_user",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER, subtype: BBReferenceFieldSubType.USER,
}, },
multi_user: { multi_user: {
name: "multi_user", name: "multi_user",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER,
constraints: {
type: "array",
},
},
deprecated_multi_user: {
name: "deprecated_multi_user",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS, subtype: BBReferenceFieldSubType.USERS,
constraints: {
type: "array",
},
}, },
}) })
await createRows(rows(config.getUser())) await createRows(rows(config.getUser()))
@ -398,7 +430,18 @@ describe.each([
}).toContainExactly([ }).toContainExactly([
{ {
name: "single user, session user", name: "single user, session user",
single_user: [{ _id: config.getUser()._id }], single_user: { _id: config.getUser()._id },
},
])
})
it("should match a deprecated single user row by the session user id", async () => {
await expectQuery({
equal: { deprecated_single_user: "{{ [user]._id }}" },
}).toContainExactly([
{
name: "deprecated single user, session user",
deprecated_single_user: [{ _id: config.getUser()._id }],
}, },
]) ])
}) })
@ -420,6 +463,23 @@ describe.each([
]) ])
}) })
// TODO(samwho): fix for SQS
!isSqs &&
it("should match the session user id in a deprecated multi user field", async () => {
const allUsers = [...globalUsers, config.getUser()].map((user: any) => {
return { _id: user._id }
})
await expectQuery({
contains: { deprecated_multi_user: ["{{ [user]._id }}"] },
}).toContainExactly([
{
name: "deprecated multi user with session user",
deprecated_multi_user: allUsers,
},
])
})
// TODO(samwho): fix for SQS // TODO(samwho): fix for SQS
!isSqs && !isSqs &&
it("should not match the session user id in a multi user field", async () => { it("should not match the session user id in a multi user field", async () => {
@ -436,6 +496,22 @@ describe.each([
]) ])
}) })
// TODO(samwho): fix for SQS
!isSqs &&
it("should not match the session user id in a deprecated multi user field", async () => {
await expectQuery({
notContains: { deprecated_multi_user: ["{{ [user]._id }}"] },
notEmpty: { deprecated_multi_user: true },
}).toContainExactly([
{
name: "deprecated multi user",
deprecated_multi_user: globalUsers.map((user: any) => {
return { _id: user._id }
}),
},
])
})
it("should match the session user id and a user table row id using helpers, user binding and a static user id.", async () => { it("should match the session user id and a user table row id using helpers, user binding and a static user id.", async () => {
await expectQuery({ await expectQuery({
oneOf: { oneOf: {
@ -447,11 +523,31 @@ describe.each([
}).toContainExactly([ }).toContainExactly([
{ {
name: "single user, session user", name: "single user, session user",
single_user: [{ _id: config.getUser()._id }], single_user: { _id: config.getUser()._id },
}, },
{ {
name: "single user", name: "single user",
single_user: [{ _id: globalUsers[0]._id }], single_user: { _id: globalUsers[0]._id },
},
])
})
it("should match the session user id and a user table row id using helpers, user binding and a static user id. (deprecated single user)", async () => {
await expectQuery({
oneOf: {
deprecated_single_user: [
"{{ default [user]._id '_empty_' }}",
globalUsers[0]._id,
],
},
}).toContainExactly([
{
name: "deprecated single user, session user",
deprecated_single_user: [{ _id: config.getUser()._id }],
},
{
name: "deprecated single user",
deprecated_single_user: [{ _id: globalUsers[0]._id }],
}, },
]) ])
}) })
@ -467,7 +563,23 @@ describe.each([
}).toContainExactly([ }).toContainExactly([
{ {
name: "single user", name: "single user",
single_user: [{ _id: globalUsers[0]._id }], single_user: { _id: globalUsers[0]._id },
},
])
})
it("should resolve 'default' helper to '_empty_' when binding resolves to nothing (deprecated single user)", async () => {
await expectQuery({
oneOf: {
deprecated_single_user: [
"{{ default [user]._idx '_empty_' }}",
globalUsers[0]._id,
],
},
}).toContainExactly([
{
name: "deprecated single user",
deprecated_single_user: [{ _id: globalUsers[0]._id }],
}, },
]) ])
}) })

View File

@ -26,6 +26,7 @@ import {
INTERNAL_TABLE_SOURCE_ID, INTERNAL_TABLE_SOURCE_ID,
} from "@budibase/types" } from "@budibase/types"
import environment from "../../environment" import environment from "../../environment"
import { helpers } from "@budibase/shared-core"
type QueryFunction = (query: SqlQuery | SqlQuery[], operation: Operation) => any type QueryFunction = (query: SqlQuery | SqlQuery[], operation: Operation) => any
@ -786,8 +787,7 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
return ( return (
field.type === FieldType.JSON || field.type === FieldType.JSON ||
(field.type === FieldType.BB_REFERENCE && (field.type === FieldType.BB_REFERENCE &&
// Handling old single user type !helpers.schema.isDeprecatedSingleUserColumn(field))
field.constraints?.type === "array")
) )
} }

View File

@ -11,7 +11,7 @@ import {
TableSourceType, TableSourceType,
} from "@budibase/types" } from "@budibase/types"
import { breakExternalTableId, getNativeSql, SqlClient } from "../utils" import { breakExternalTableId, getNativeSql, SqlClient } from "../utils"
import { utils } from "@budibase/shared-core" import { helpers, utils } from "@budibase/shared-core"
import SchemaBuilder = Knex.SchemaBuilder import SchemaBuilder = Knex.SchemaBuilder
import CreateTableBuilder = Knex.CreateTableBuilder import CreateTableBuilder = Knex.CreateTableBuilder
@ -85,7 +85,12 @@ function generateSchema(
break break
case FieldType.ARRAY: case FieldType.ARRAY:
case FieldType.BB_REFERENCE: case FieldType.BB_REFERENCE:
schema.json(key) if (helpers.schema.isDeprecatedSingleUserColumn(column)) {
// This is still required for unit testing, in order to create "deprecated" schemas
schema.text(key)
} else {
schema.json(key)
}
break break
case FieldType.LINK: case FieldType.LINK:
// this side of the relationship doesn't need any SQL work // this side of the relationship doesn't need any SQL work

View File

@ -79,7 +79,9 @@ export async function search(
} }
const table = await sdk.tables.getTable(options.tableId) const table = await sdk.tables.getTable(options.tableId)
options = searchInputMapping(table, options) options = searchInputMapping(table, options, {
isSql: !!table.sql || !!env.SQS_SEARCH_ENABLE,
})
if (isExternalTable) { if (isExternalTable) {
return external.search(options, table) return external.search(options, table)

View File

@ -11,7 +11,7 @@ import {
RowSearchParams, RowSearchParams,
} from "@budibase/types" } from "@budibase/types"
import { db as dbCore, context } from "@budibase/backend-core" import { db as dbCore, context } from "@budibase/backend-core"
import { utils } from "@budibase/shared-core" import { helpers, utils } from "@budibase/shared-core"
export async function paginatedSearch( export async function paginatedSearch(
query: SearchFilters, query: SearchFilters,
@ -49,13 +49,19 @@ function findColumnInQueries(
} }
} }
function userColumnMapping(column: string, options: RowSearchParams) { function userColumnMapping(
column: string,
options: RowSearchParams,
isDeprecatedSingleUserColumn: boolean = false,
isSql: boolean = false
) {
findColumnInQueries(column, options, (filterValue: any): any => { findColumnInQueries(column, options, (filterValue: any): any => {
const isArray = Array.isArray(filterValue), const isArray = Array.isArray(filterValue),
isString = typeof filterValue === "string" isString = typeof filterValue === "string"
if (!isString && !isArray) { if (!isString && !isArray) {
return filterValue return filterValue
} }
const processString = (input: string) => { const processString = (input: string) => {
const rowPrefix = DocumentType.ROW + SEPARATOR const rowPrefix = DocumentType.ROW + SEPARATOR
if (input.startsWith(rowPrefix)) { if (input.startsWith(rowPrefix)) {
@ -64,23 +70,34 @@ function userColumnMapping(column: string, options: RowSearchParams) {
return input return input
} }
} }
let wrapper = (s: string) => s
if (isDeprecatedSingleUserColumn && filterValue && isSql) {
// Decreated single users are stored as stringified arrays of a single value
wrapper = (s: string) => JSON.stringify([s])
}
if (isArray) { if (isArray) {
return filterValue.map(el => { return filterValue.map(el => {
if (typeof el === "string") { if (typeof el === "string") {
return processString(el) return wrapper(processString(el))
} else { } else {
return el return el
} }
}) })
} else { } else {
return processString(filterValue) return wrapper(processString(filterValue))
} }
}) })
} }
// maps through the search parameters to check if any of the inputs are invalid // maps through the search parameters to check if any of the inputs are invalid
// based on the table schema, converts them to something that is valid. // based on the table schema, converts them to something that is valid.
export function searchInputMapping(table: Table, options: RowSearchParams) { export function searchInputMapping(
table: Table,
options: RowSearchParams,
datasourceOptions: { isSql?: boolean } = {}
) {
if (!table?.schema) { if (!table?.schema) {
return options return options
} }
@ -99,7 +116,12 @@ export function searchInputMapping(table: Table, options: RowSearchParams) {
break break
} }
case FieldType.BB_REFERENCE: { case FieldType.BB_REFERENCE: {
userColumnMapping(key, options) userColumnMapping(
key,
options,
helpers.schema.isDeprecatedSingleUserColumn(column),
datasourceOptions.isSql
)
break break
} }
} }

View File

@ -139,8 +139,10 @@ export function parse(rows: Rows, schema: TableSchema): Rows {
? new Date(columnData).toISOString() ? new Date(columnData).toISOString()
: columnData : columnData
} else if (columnType === FieldType.BB_REFERENCE) { } else if (columnType === FieldType.BB_REFERENCE) {
const parsedValues = let parsedValues: { _id: string }[] = columnData || []
(!!columnData && parseCsvExport<{ _id: string }[]>(columnData)) || [] if (columnData) {
parsedValues = parseCsvExport<{ _id: string }[]>(columnData)
}
parsedRow[columnName] = parsedValues?.map(u => u._id) parsedRow[columnName] = parsedValues?.map(u => u._id)
} else if (columnType === FieldType.BB_REFERENCE_SINGLE) { } else if (columnType === FieldType.BB_REFERENCE_SINGLE) {

View File

@ -9,10 +9,11 @@ import {
SearchFilterOperator, SearchFilterOperator,
SortDirection, SortDirection,
SortType, SortType,
FieldConstraints,
} from "@budibase/types" } from "@budibase/types"
import dayjs from "dayjs" import dayjs from "dayjs"
import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants" import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants"
import { deepGet } from "./helpers" import { deepGet, schema } from "./helpers"
const HBS_REGEX = /{{([^{].*?)}}/g const HBS_REGEX = /{{([^{].*?)}}/g
@ -24,6 +25,7 @@ export const getValidOperatorsForType = (
type: FieldType type: FieldType
subtype?: BBReferenceFieldSubType subtype?: BBReferenceFieldSubType
formulaType?: FormulaType formulaType?: FormulaType
constraints?: FieldConstraints
}, },
field?: string, field?: string,
datasource?: Datasource & { tableId: any } datasource?: Datasource & { tableId: any }
@ -68,7 +70,10 @@ export const getValidOperatorsForType = (
ops = numOps ops = numOps
} else if (type === FieldType.FORMULA && formulaType === FormulaType.STATIC) { } else if (type === FieldType.FORMULA && formulaType === FormulaType.STATIC) {
ops = stringOps.concat([Op.MoreThan, Op.LessThan]) ops = stringOps.concat([Op.MoreThan, Op.LessThan])
} else if (type === FieldType.BB_REFERENCE_SINGLE) { } else if (
type === FieldType.BB_REFERENCE_SINGLE ||
schema.isDeprecatedSingleUserColumn(fieldType)
) {
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In] ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
} else if (type === FieldType.BB_REFERENCE) { } else if (type === FieldType.BB_REFERENCE) {
ops = [Op.Contains, Op.NotContains, Op.ContainsAny, Op.Empty, Op.NotEmpty] ops = [Op.Contains, Op.NotContains, Op.ContainsAny, Op.Empty, Op.NotEmpty]

View File

@ -4,7 +4,9 @@ import {
FieldType, FieldType,
} from "@budibase/types" } from "@budibase/types"
export function isDeprecatedSingleUserColumn(schema: FieldSchema) { export function isDeprecatedSingleUserColumn(
schema: Pick<FieldSchema, "type" | "subtype" | "constraints">
) {
const result = const result =
schema.type === FieldType.BB_REFERENCE && schema.type === FieldType.BB_REFERENCE &&
schema.subtype === BBReferenceFieldSubType.USER && schema.subtype === BBReferenceFieldSubType.USER &&