Merge pull request #13656 from Budibase/budi-8123/test-singleuser-binding
Updating binding tests to test single column
This commit is contained in:
commit
ec61e2e085
8 changed files with 172 additions and 22 deletions
|
@ -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 }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
|
@ -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")
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
Loading…
Reference in a new issue