Merge pull request #12000 from Budibase/feature/multi-user-type-column
User type column improvements
This commit is contained in:
commit
4fb163a0c4
|
@ -264,18 +264,23 @@ jobs:
|
||||||
|
|
||||||
if [[ $branch == "master" ]]; then
|
if [[ $branch == "master" ]]; then
|
||||||
base_commit=$(git rev-parse origin/master)
|
base_commit=$(git rev-parse origin/master)
|
||||||
else
|
elif [[ $branch == "develop" ]]; then
|
||||||
base_commit=$(git rev-parse origin/develop)
|
base_commit=$(git rev-parse origin/develop)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ ! -z $base_commit ]]; then
|
||||||
echo "target_branch=$branch"
|
echo "target_branch=$branch"
|
||||||
echo "target_branch=$branch" >> "$GITHUB_OUTPUT"
|
echo "target_branch=$branch" >> "$GITHUB_OUTPUT"
|
||||||
echo "pro_commit=$pro_commit"
|
echo "pro_commit=$pro_commit"
|
||||||
echo "pro_commit=$pro_commit" >> "$GITHUB_OUTPUT"
|
echo "pro_commit=$pro_commit" >> "$GITHUB_OUTPUT"
|
||||||
echo "base_commit=$base_commit"
|
echo "base_commit=$base_commit"
|
||||||
echo "base_commit=$base_commit" >> "$GITHUB_OUTPUT"
|
echo "base_commit=$base_commit" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "Nothing to do - branch to branch merge."
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Check submodule merged to develop
|
- name: Check submodule merged to base branch
|
||||||
|
if: ${{ steps.get_pro_commits.outputs.base_commit != '' }}
|
||||||
uses: actions/github-script@v4
|
uses: actions/github-script@v4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
@ -284,7 +289,7 @@ jobs:
|
||||||
const baseCommit = '${{ steps.get_pro_commits.outputs.base_commit }}';
|
const baseCommit = '${{ steps.get_pro_commits.outputs.base_commit }}';
|
||||||
|
|
||||||
if (submoduleCommit !== baseCommit) {
|
if (submoduleCommit !== baseCommit) {
|
||||||
console.error('Submodule commit does not match the latest commit on the "${{ steps.get_pro_commits.outputs.target_branch }}"" branch.');
|
console.error('Submodule commit does not match the latest commit on the "${{ steps.get_pro_commits.outputs.target_branch }}" branch.');
|
||||||
console.error('Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/develop/docs/getting_started.md')
|
console.error('Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/develop/docs/getting_started.md')
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
"@trendyol/jest-testcontainers": "^2.1.1",
|
"@trendyol/jest-testcontainers": "^2.1.1",
|
||||||
"@types/chance": "1.1.3",
|
"@types/chance": "1.1.3",
|
||||||
"@types/cookies": "0.7.8",
|
"@types/cookies": "0.7.8",
|
||||||
"@types/jest": "29.5.3",
|
"@types/jest": "29.5.5",
|
||||||
"@types/lodash": "4.14.180",
|
"@types/lodash": "4.14.180",
|
||||||
"@types/node": "18.17.0",
|
"@types/node": "18.17.0",
|
||||||
"@types/node-fetch": "2.6.4",
|
"@types/node-fetch": "2.6.4",
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { prefixed, DocumentType } from "@budibase/types"
|
import { prefixed, DocumentType } from "@budibase/types"
|
||||||
export { SEPARATOR, UNICODE_MAX, DocumentType } from "@budibase/types"
|
export {
|
||||||
|
SEPARATOR,
|
||||||
|
UNICODE_MAX,
|
||||||
|
DocumentType,
|
||||||
|
InternalTable,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can be used to create a few different forms of querying a view.
|
* Can be used to create a few different forms of querying a view.
|
||||||
|
@ -30,10 +35,6 @@ export const DeprecatedViews = {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum InternalTable {
|
|
||||||
USER_METADATA = "ta_users",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const StaticDatabases = {
|
export const StaticDatabases = {
|
||||||
GLOBAL: {
|
GLOBAL: {
|
||||||
name: "global-db",
|
name: "global-db",
|
||||||
|
|
|
@ -45,6 +45,11 @@ export function generateGlobalUserID(id?: any) {
|
||||||
return `${DocumentType.USER}${SEPARATOR}${id || newid()}`
|
return `${DocumentType.USER}${SEPARATOR}${id || newid()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isGlobalUserIDRegex = new RegExp(`^${DocumentType.USER}${SEPARATOR}.+`)
|
||||||
|
export function isGlobalUserID(id: string) {
|
||||||
|
return isGlobalUserIDRegex.test(id)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new user ID based on the passed in global ID.
|
* Generates a new user ID based on the passed in global ID.
|
||||||
* @param {string} globalId The ID of the global user.
|
* @param {string} globalId The ID of the global user.
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
import { getBindings } from "components/backend/DataTable/formula"
|
import { getBindings } from "components/backend/DataTable/formula"
|
||||||
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
||||||
import { ValidColumnNameRegex } from "@budibase/shared-core"
|
import { ValidColumnNameRegex } from "@budibase/shared-core"
|
||||||
import { FieldType } from "@budibase/types"
|
import { FieldType, FieldSubtype, SourceName } from "@budibase/types"
|
||||||
import RelationshipSelector from "components/common/RelationshipSelector.svelte"
|
import RelationshipSelector from "components/common/RelationshipSelector.svelte"
|
||||||
|
|
||||||
const AUTO_TYPE = "auto"
|
const AUTO_TYPE = "auto"
|
||||||
|
@ -43,7 +43,6 @@
|
||||||
const NUMBER_TYPE = FIELDS.NUMBER.type
|
const NUMBER_TYPE = FIELDS.NUMBER.type
|
||||||
const JSON_TYPE = FIELDS.JSON.type
|
const JSON_TYPE = FIELDS.JSON.type
|
||||||
const DATE_TYPE = FIELDS.DATETIME.type
|
const DATE_TYPE = FIELDS.DATETIME.type
|
||||||
const USER_REFRENCE_TYPE = FIELDS.BB_REFERENCE_USER.compositeType
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
|
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
|
||||||
|
@ -52,7 +51,19 @@
|
||||||
export let field
|
export let field
|
||||||
|
|
||||||
let mounted = false
|
let mounted = false
|
||||||
let fieldDefinitions = cloneDeep(FIELDS)
|
const fieldDefinitions = Object.values(FIELDS).reduce(
|
||||||
|
// Storing the fields by complex field id
|
||||||
|
(acc, field) => ({
|
||||||
|
...acc,
|
||||||
|
[makeFieldId(field.type, field.subtype)]: field,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
function makeFieldId(type, subtype) {
|
||||||
|
return `${type}${subtype || ""}`.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
let originalName
|
let originalName
|
||||||
let linkEditDisabled
|
let linkEditDisabled
|
||||||
let primaryDisplay
|
let primaryDisplay
|
||||||
|
@ -72,8 +83,8 @@
|
||||||
let jsonSchemaModal
|
let jsonSchemaModal
|
||||||
let allowedTypes = []
|
let allowedTypes = []
|
||||||
let editableColumn = {
|
let editableColumn = {
|
||||||
type: fieldDefinitions.STRING.type,
|
type: FIELDS.STRING.type,
|
||||||
constraints: fieldDefinitions.STRING.constraints,
|
constraints: FIELDS.STRING.constraints,
|
||||||
// Initial value for column name in other table for linked records
|
// Initial value for column name in other table for linked records
|
||||||
fieldName: $tables.selected.name,
|
fieldName: $tables.selected.name,
|
||||||
}
|
}
|
||||||
|
@ -139,9 +150,6 @@
|
||||||
$tables.selected.primaryDisplay == null ||
|
$tables.selected.primaryDisplay == null ||
|
||||||
$tables.selected.primaryDisplay === editableColumn.name
|
$tables.selected.primaryDisplay === editableColumn.name
|
||||||
|
|
||||||
if (editableColumn.type === FieldType.BB_REFERENCE) {
|
|
||||||
editableColumn.type = `${editableColumn.type}_${editableColumn.subtype}`
|
|
||||||
}
|
|
||||||
// Here we are setting the relationship values based on the editableColumn
|
// Here we are setting the relationship values based on the editableColumn
|
||||||
// This part of the code is used when viewing an existing field hence the check
|
// This part of the code is used when viewing an existing field hence the check
|
||||||
// for the tableId
|
// for the tableId
|
||||||
|
@ -172,7 +180,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedTypes = getAllowedTypes()
|
if (!savingColumn) {
|
||||||
|
editableColumn.fieldId = makeFieldId(
|
||||||
|
editableColumn.type,
|
||||||
|
editableColumn.subtype
|
||||||
|
)
|
||||||
|
|
||||||
|
allowedTypes = getAllowedTypes().map(t => ({
|
||||||
|
fieldId: makeFieldId(t.type, t.subtype),
|
||||||
|
...t,
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: initialiseField(field, savingColumn)
|
$: initialiseField(field, savingColumn)
|
||||||
|
@ -249,13 +267,7 @@
|
||||||
|
|
||||||
let saveColumn = cloneDeep(editableColumn)
|
let saveColumn = cloneDeep(editableColumn)
|
||||||
|
|
||||||
// Handle types on composite types
|
delete saveColumn.fieldId
|
||||||
const definition = fieldDefinitions[saveColumn.type.toUpperCase()]
|
|
||||||
if (definition && saveColumn.type === definition.compositeType) {
|
|
||||||
saveColumn.type = definition.type
|
|
||||||
saveColumn.subtype = definition.subtype
|
|
||||||
delete saveColumn.compositeType
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveColumn.type === AUTO_TYPE) {
|
if (saveColumn.type === AUTO_TYPE) {
|
||||||
saveColumn = buildAutoColumn(
|
saveColumn = buildAutoColumn(
|
||||||
|
@ -320,27 +332,33 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTypeChange(event) {
|
function onHandleTypeChange(event) {
|
||||||
|
handleTypeChange(event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTypeChange(type) {
|
||||||
// remove any extra fields that may not be related to this type
|
// remove any extra fields that may not be related to this type
|
||||||
delete editableColumn.autocolumn
|
delete editableColumn.autocolumn
|
||||||
delete editableColumn.subtype
|
delete editableColumn.subtype
|
||||||
delete editableColumn.tableId
|
delete editableColumn.tableId
|
||||||
delete editableColumn.relationshipType
|
delete editableColumn.relationshipType
|
||||||
delete editableColumn.formulaType
|
delete editableColumn.formulaType
|
||||||
|
delete editableColumn.constraints
|
||||||
|
|
||||||
// Add in defaults and initial definition
|
// Add in defaults and initial definition
|
||||||
const definition = fieldDefinitions[event.detail?.toUpperCase()]
|
const definition = fieldDefinitions[type?.toUpperCase()]
|
||||||
if (definition?.constraints) {
|
if (definition?.constraints) {
|
||||||
editableColumn.constraints = definition.constraints
|
editableColumn.constraints = definition.constraints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editableColumn.type = definition.type
|
||||||
|
editableColumn.subtype = definition.subtype
|
||||||
|
|
||||||
// Default relationships many to many
|
// Default relationships many to many
|
||||||
if (editableColumn.type === LINK_TYPE) {
|
if (editableColumn.type === LINK_TYPE) {
|
||||||
editableColumn.relationshipType = RelationshipType.MANY_TO_MANY
|
editableColumn.relationshipType = RelationshipType.MANY_TO_MANY
|
||||||
} else if (editableColumn.type === FORMULA_TYPE) {
|
} else if (editableColumn.type === FORMULA_TYPE) {
|
||||||
editableColumn.formulaType = "dynamic"
|
editableColumn.formulaType = "dynamic"
|
||||||
} else if (editableColumn.type === USER_REFRENCE_TYPE) {
|
|
||||||
editableColumn.relationshipType = RelationshipType.ONE_TO_MANY
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,9 +399,26 @@
|
||||||
return ALLOWABLE_NUMBER_OPTIONS
|
return ALLOWABLE_NUMBER_OPTIONS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isUsers =
|
||||||
|
editableColumn.type === FieldType.BB_REFERENCE &&
|
||||||
|
editableColumn.subtype === FieldSubtype.USERS
|
||||||
|
|
||||||
if (!external) {
|
if (!external) {
|
||||||
return [
|
return [
|
||||||
...Object.values(fieldDefinitions),
|
FIELDS.STRING,
|
||||||
|
FIELDS.BARCODEQR,
|
||||||
|
FIELDS.LONGFORM,
|
||||||
|
FIELDS.OPTIONS,
|
||||||
|
FIELDS.ARRAY,
|
||||||
|
FIELDS.NUMBER,
|
||||||
|
FIELDS.BIGINT,
|
||||||
|
FIELDS.BOOLEAN,
|
||||||
|
FIELDS.DATETIME,
|
||||||
|
FIELDS.ATTACHMENT,
|
||||||
|
FIELDS.LINK,
|
||||||
|
FIELDS.FORMULA,
|
||||||
|
FIELDS.JSON,
|
||||||
|
isUsers ? FIELDS.USERS : FIELDS.USER,
|
||||||
{ name: "Auto Column", type: AUTO_TYPE },
|
{ name: "Auto Column", type: AUTO_TYPE },
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
|
@ -397,7 +432,7 @@
|
||||||
FIELDS.BOOLEAN,
|
FIELDS.BOOLEAN,
|
||||||
FIELDS.FORMULA,
|
FIELDS.FORMULA,
|
||||||
FIELDS.BIGINT,
|
FIELDS.BIGINT,
|
||||||
FIELDS.BB_REFERENCE_USER,
|
isUsers ? FIELDS.USERS : FIELDS.USER,
|
||||||
]
|
]
|
||||||
// no-sql or a spreadsheet
|
// no-sql or a spreadsheet
|
||||||
if (!external || table.sql) {
|
if (!external || table.sql) {
|
||||||
|
@ -472,6 +507,13 @@
|
||||||
return newError
|
return newError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUsersColumn(column) {
|
||||||
|
return (
|
||||||
|
column.type === FieldType.BB_REFERENCE &&
|
||||||
|
[FieldSubtype.USER, FieldSubtype.USERS].includes(column.subtype)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
mounted = true
|
mounted = true
|
||||||
})
|
})
|
||||||
|
@ -489,11 +531,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
<Select
|
<Select
|
||||||
disabled={!typeEnabled}
|
disabled={!typeEnabled}
|
||||||
bind:value={editableColumn.type}
|
bind:value={editableColumn.fieldId}
|
||||||
on:change={handleTypeChange}
|
on:change={onHandleTypeChange}
|
||||||
options={allowedTypes}
|
options={allowedTypes}
|
||||||
getOptionLabel={field => field.name}
|
getOptionLabel={field => field.name}
|
||||||
getOptionValue={field => field.compositeType || field.type}
|
getOptionValue={field => field.fieldId}
|
||||||
getOptionIcon={field => field.icon}
|
getOptionIcon={field => field.icon}
|
||||||
isOptionEnabled={option => {
|
isOptionEnabled={option => {
|
||||||
if (option.type == AUTO_TYPE) {
|
if (option.type == AUTO_TYPE) {
|
||||||
|
@ -555,7 +597,7 @@
|
||||||
<DatePicker bind:value={editableColumn.constraints.datetime.latest} />
|
<DatePicker bind:value={editableColumn.constraints.datetime.latest} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER" && !editableColumn.dateOnly}
|
{#if datasource?.source !== SourceName.ORACLE && datasource?.source !== SourceName.SQL_SERVER && !editableColumn.dateOnly}
|
||||||
<div>
|
<div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<Label>Time zones</Label>
|
<Label>Time zones</Label>
|
||||||
|
@ -659,18 +701,20 @@
|
||||||
<Button primary text on:click={openJsonSchemaEditor}
|
<Button primary text on:click={openJsonSchemaEditor}
|
||||||
>Open schema editor</Button
|
>Open schema editor</Button
|
||||||
>
|
>
|
||||||
{:else if editableColumn.type === USER_REFRENCE_TYPE}
|
{:else if isUsersColumn(editableColumn) && datasource?.source !== SourceName.GOOGLE_SHEETS}
|
||||||
<!-- Disabled temporally -->
|
<Toggle
|
||||||
<!-- <Toggle
|
value={editableColumn.subtype === FieldSubtype.USERS}
|
||||||
value={editableColumn.relationshipType === RelationshipType.MANY_TO_MANY}
|
|
||||||
on:change={e =>
|
on:change={e =>
|
||||||
(editableColumn.relationshipType = e.detail
|
handleTypeChange(
|
||||||
? RelationshipType.MANY_TO_MANY
|
makeFieldId(
|
||||||
: RelationshipType.ONE_TO_MANY)}
|
FieldType.BB_REFERENCE,
|
||||||
|
e.detail ? FieldSubtype.USERS : FieldSubtype.USER
|
||||||
|
)
|
||||||
|
)}
|
||||||
disabled={!isCreating}
|
disabled={!isCreating}
|
||||||
thin
|
thin
|
||||||
text="Allow multiple users"
|
text="Allow multiple users"
|
||||||
/> -->
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if editableColumn.type === AUTO_TYPE || editableColumn.autocolumn}
|
{#if editableColumn.type === AUTO_TYPE || editableColumn.autocolumn}
|
||||||
<Select
|
<Select
|
||||||
|
|
|
@ -49,6 +49,15 @@
|
||||||
label: "Long Form Text",
|
label: "Long Form Text",
|
||||||
value: FIELDS.LONGFORM.type,
|
value: FIELDS.LONGFORM.type,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: "User",
|
||||||
|
value: `${FIELDS.USER.type}${FIELDS.USER.subtype}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Users",
|
||||||
|
value: `${FIELDS.USERS.type}${FIELDS.USERS.subtype}`,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
@ -143,7 +152,7 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
<Select
|
<Select
|
||||||
value={schema[name]?.type}
|
value={`${schema[name]?.type}${schema[name]?.subtype || ""}`}
|
||||||
options={typeOptions}
|
options={typeOptions}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
getOptionLabel={option => option.label}
|
getOptionLabel={option => option.label}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { parseFile } from "./utils"
|
import { parseFile } from "./utils"
|
||||||
|
import { canBeDisplayColumn } from "@budibase/shared-core"
|
||||||
|
|
||||||
export let rows = []
|
export let rows = []
|
||||||
export let schema = {}
|
export let schema = {}
|
||||||
|
@ -10,36 +11,82 @@
|
||||||
export let displayColumn = null
|
export let displayColumn = null
|
||||||
export let promptUpload = false
|
export let promptUpload = false
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = {
|
||||||
{
|
[FIELDS.STRING.type]: {
|
||||||
label: "Text",
|
label: "Text",
|
||||||
value: FIELDS.STRING.type,
|
value: FIELDS.STRING.type,
|
||||||
|
config: {
|
||||||
|
type: FIELDS.STRING.type,
|
||||||
|
constraints: FIELDS.STRING.constraints,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
|
[FIELDS.NUMBER.type]: {
|
||||||
label: "Number",
|
label: "Number",
|
||||||
value: FIELDS.NUMBER.type,
|
value: FIELDS.NUMBER.type,
|
||||||
|
config: {
|
||||||
|
type: FIELDS.NUMBER.type,
|
||||||
|
constraints: FIELDS.NUMBER.constraints,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
|
[FIELDS.DATETIME.type]: {
|
||||||
label: "Date",
|
label: "Date",
|
||||||
value: FIELDS.DATETIME.type,
|
value: FIELDS.DATETIME.type,
|
||||||
|
config: {
|
||||||
|
type: FIELDS.DATETIME.type,
|
||||||
|
constraints: FIELDS.DATETIME.constraints,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
|
[FIELDS.OPTIONS.type]: {
|
||||||
label: "Options",
|
label: "Options",
|
||||||
value: FIELDS.OPTIONS.type,
|
value: FIELDS.OPTIONS.type,
|
||||||
|
config: {
|
||||||
|
type: FIELDS.OPTIONS.type,
|
||||||
|
constraints: FIELDS.OPTIONS.constraints,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
|
[FIELDS.ARRAY.type]: {
|
||||||
label: "Multi-select",
|
label: "Multi-select",
|
||||||
value: FIELDS.ARRAY.type,
|
value: FIELDS.ARRAY.type,
|
||||||
|
config: {
|
||||||
|
type: FIELDS.ARRAY.type,
|
||||||
|
constraints: FIELDS.ARRAY.constraints,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
|
[FIELDS.BARCODEQR.type]: {
|
||||||
label: "Barcode/QR",
|
label: "Barcode/QR",
|
||||||
value: FIELDS.BARCODEQR.type,
|
value: FIELDS.BARCODEQR.type,
|
||||||
|
config: {
|
||||||
|
type: FIELDS.BARCODEQR.type,
|
||||||
|
constraints: FIELDS.BARCODEQR.constraints,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
|
[FIELDS.LONGFORM.type]: {
|
||||||
label: "Long Form Text",
|
label: "Long Form Text",
|
||||||
value: FIELDS.LONGFORM.type,
|
value: FIELDS.LONGFORM.type,
|
||||||
|
config: {
|
||||||
|
type: FIELDS.LONGFORM.type,
|
||||||
|
constraints: FIELDS.LONGFORM.constraints,
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
|
user: {
|
||||||
|
label: "User",
|
||||||
|
value: "user",
|
||||||
|
config: {
|
||||||
|
type: FIELDS.USER.type,
|
||||||
|
subtype: FIELDS.USER.subtype,
|
||||||
|
constraints: FIELDS.USER.constraints,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
label: "Users",
|
||||||
|
value: "users",
|
||||||
|
config: {
|
||||||
|
type: FIELDS.USERS.type,
|
||||||
|
subtype: FIELDS.USERS.subtype,
|
||||||
|
constraints: FIELDS.USERS.constraints,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
let fileInput
|
let fileInput
|
||||||
let error = null
|
let error = null
|
||||||
|
@ -48,10 +95,16 @@
|
||||||
let validation = {}
|
let validation = {}
|
||||||
let validateHash = ""
|
let validateHash = ""
|
||||||
let errors = {}
|
let errors = {}
|
||||||
|
let selectedColumnTypes = {}
|
||||||
|
|
||||||
$: displayColumnOptions = Object.keys(schema || {}).filter(column => {
|
$: displayColumnOptions = Object.keys(schema || {}).filter(column => {
|
||||||
return validation[column]
|
return validation[column] && canBeDisplayColumn(schema[column].type)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$: if (displayColumn && !canBeDisplayColumn(schema[displayColumn].type)) {
|
||||||
|
displayColumn = null
|
||||||
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
// binding in consumer is causing double renders here
|
// binding in consumer is causing double renders here
|
||||||
const newValidateHash = JSON.stringify(rows) + JSON.stringify(schema)
|
const newValidateHash = JSON.stringify(rows) + JSON.stringify(schema)
|
||||||
|
@ -72,6 +125,13 @@
|
||||||
rows = response.rows
|
rows = response.rows
|
||||||
schema = response.schema
|
schema = response.schema
|
||||||
fileName = response.fileName
|
fileName = response.fileName
|
||||||
|
selectedColumnTypes = Object.entries(response.schema).reduce(
|
||||||
|
(acc, [colName, fieldConfig]) => ({
|
||||||
|
...acc,
|
||||||
|
[colName]: fieldConfig.type,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loading = false
|
loading = false
|
||||||
error = e
|
error = e
|
||||||
|
@ -98,8 +158,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChange = (name, e) => {
|
const handleChange = (name, e) => {
|
||||||
schema[name].type = e.detail
|
const { config } = typeOptions[e.detail]
|
||||||
schema[name].constraints = FIELDS[e.detail.toUpperCase()].constraints
|
schema[name].type = config.type
|
||||||
|
schema[name].subtype = config.subtype
|
||||||
|
schema[name].constraints = config.constraints
|
||||||
}
|
}
|
||||||
|
|
||||||
const openFileUpload = (promptUpload, fileInput) => {
|
const openFileUpload = (promptUpload, fileInput) => {
|
||||||
|
@ -142,9 +204,9 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span>{column.name}</span>
|
<span>{column.name}</span>
|
||||||
<Select
|
<Select
|
||||||
bind:value={column.type}
|
bind:value={selectedColumnTypes[column.name]}
|
||||||
on:change={e => handleChange(name, e)}
|
on:change={e => handleChange(name, e)}
|
||||||
options={typeOptions}
|
options={Object.values(typeOptions)}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
getOptionLabel={option => option.label}
|
getOptionLabel={option => option.label}
|
||||||
getOptionValue={option => option.value}
|
getOptionValue={option => option.value}
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
import { FieldType } from "@budibase/types"
|
import { FieldType } from "@budibase/types"
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
import FilterUsers from "./FilterUsers.svelte"
|
import FilterUsers from "./FilterUsers.svelte"
|
||||||
import { RelationshipType } from "constants/backend"
|
|
||||||
|
|
||||||
export let schemaFields
|
export let schemaFields
|
||||||
export let filters = []
|
export let filters = []
|
||||||
|
@ -126,6 +125,7 @@
|
||||||
// Update type based on field
|
// Update type based on field
|
||||||
const fieldSchema = enrichedSchemaFields.find(x => x.name === filter.field)
|
const fieldSchema = enrichedSchemaFields.find(x => x.name === filter.field)
|
||||||
filter.type = fieldSchema?.type
|
filter.type = fieldSchema?.type
|
||||||
|
filter.subtype = fieldSchema?.subtype
|
||||||
|
|
||||||
// Update external type based on field
|
// Update external type based on field
|
||||||
filter.externalType = getSchema(filter)?.externalType
|
filter.externalType = getSchema(filter)?.externalType
|
||||||
|
@ -196,7 +196,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return LuceneUtils.getValidOperatorsForType(
|
return LuceneUtils.getValidOperatorsForType(
|
||||||
filter.type,
|
{ type: filter.type, subtype: filter.subtype },
|
||||||
filter.field,
|
filter.field,
|
||||||
datasource
|
datasource
|
||||||
)
|
)
|
||||||
|
@ -301,9 +301,10 @@
|
||||||
{:else if filter.type === FieldType.BB_REFERENCE}
|
{:else if filter.type === FieldType.BB_REFERENCE}
|
||||||
<FilterUsers
|
<FilterUsers
|
||||||
bind:value={filter.value}
|
bind:value={filter.value}
|
||||||
multiselect={getSchema(filter).relationshipType ===
|
multiselect={[
|
||||||
RelationshipType.MANY_TO_MANY ||
|
OperatorOptions.In.value,
|
||||||
filter.operator === OperatorOptions.In.value}
|
OperatorOptions.ContainsAny.value,
|
||||||
|
].includes(filter.operator)}
|
||||||
disabled={filter.noValue}
|
disabled={filter.noValue}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { FieldType, FieldSubtype } from "@budibase/types"
|
||||||
|
|
||||||
export const FIELDS = {
|
export const FIELDS = {
|
||||||
STRING: {
|
STRING: {
|
||||||
name: "Text",
|
name: "Text",
|
||||||
type: "string",
|
type: FieldType.STRING,
|
||||||
icon: "Text",
|
icon: "Text",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
@ -11,7 +13,7 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
BARCODEQR: {
|
BARCODEQR: {
|
||||||
name: "Barcode/QR",
|
name: "Barcode/QR",
|
||||||
type: "barcodeqr",
|
type: FieldType.BARCODEQR,
|
||||||
icon: "Camera",
|
icon: "Camera",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
@ -21,7 +23,7 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
LONGFORM: {
|
LONGFORM: {
|
||||||
name: "Long Form Text",
|
name: "Long Form Text",
|
||||||
type: "longform",
|
type: FieldType.LONGFORM,
|
||||||
icon: "TextAlignLeft",
|
icon: "TextAlignLeft",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
@ -31,7 +33,7 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
OPTIONS: {
|
OPTIONS: {
|
||||||
name: "Options",
|
name: "Options",
|
||||||
type: "options",
|
type: FieldType.OPTIONS,
|
||||||
icon: "Dropdown",
|
icon: "Dropdown",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
@ -41,7 +43,7 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
ARRAY: {
|
ARRAY: {
|
||||||
name: "Multi-select",
|
name: "Multi-select",
|
||||||
type: "array",
|
type: FieldType.ARRAY,
|
||||||
icon: "Duplicate",
|
icon: "Duplicate",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
|
@ -51,7 +53,7 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
NUMBER: {
|
NUMBER: {
|
||||||
name: "Number",
|
name: "Number",
|
||||||
type: "number",
|
type: FieldType.NUMBER,
|
||||||
icon: "123",
|
icon: "123",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "number",
|
type: "number",
|
||||||
|
@ -61,12 +63,12 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
BIGINT: {
|
BIGINT: {
|
||||||
name: "BigInt",
|
name: "BigInt",
|
||||||
type: "bigint",
|
type: FieldType.BIGINT,
|
||||||
icon: "TagBold",
|
icon: "TagBold",
|
||||||
},
|
},
|
||||||
BOOLEAN: {
|
BOOLEAN: {
|
||||||
name: "Boolean",
|
name: "Boolean",
|
||||||
type: "boolean",
|
type: FieldType.BOOLEAN,
|
||||||
icon: "Boolean",
|
icon: "Boolean",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
|
@ -75,7 +77,7 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
DATETIME: {
|
DATETIME: {
|
||||||
name: "Date/Time",
|
name: "Date/Time",
|
||||||
type: "datetime",
|
type: FieldType.DATETIME,
|
||||||
icon: "Calendar",
|
icon: "Calendar",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
@ -89,7 +91,7 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
ATTACHMENT: {
|
ATTACHMENT: {
|
||||||
name: "Attachment",
|
name: "Attachment",
|
||||||
type: "attachment",
|
type: FieldType.ATTACHMENT,
|
||||||
icon: "Folder",
|
icon: "Folder",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
|
@ -98,7 +100,7 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
LINK: {
|
LINK: {
|
||||||
name: "Relationship",
|
name: "Relationship",
|
||||||
type: "link",
|
type: FieldType.LINK,
|
||||||
icon: "Link",
|
icon: "Link",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
|
@ -107,26 +109,34 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
FORMULA: {
|
FORMULA: {
|
||||||
name: "Formula",
|
name: "Formula",
|
||||||
type: "formula",
|
type: FieldType.FORMULA,
|
||||||
icon: "Calculator",
|
icon: "Calculator",
|
||||||
constraints: {},
|
constraints: {},
|
||||||
},
|
},
|
||||||
JSON: {
|
JSON: {
|
||||||
name: "JSON",
|
name: "JSON",
|
||||||
type: "json",
|
type: FieldType.JSON,
|
||||||
icon: "Brackets",
|
icon: "Brackets",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "object",
|
type: "object",
|
||||||
presence: false,
|
presence: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BB_REFERENCE_USER: {
|
USER: {
|
||||||
name: "User",
|
name: "User",
|
||||||
type: "bb_reference",
|
type: FieldType.BB_REFERENCE,
|
||||||
subtype: "user",
|
subtype: FieldSubtype.USER,
|
||||||
compositeType: "bb_reference_user", // Used for working with the subtype on CreateEditColumn as is it was a primary type
|
|
||||||
icon: "User",
|
icon: "User",
|
||||||
},
|
},
|
||||||
|
USERS: {
|
||||||
|
name: "Users",
|
||||||
|
type: FieldType.BB_REFERENCE,
|
||||||
|
subtype: FieldSubtype.USERS,
|
||||||
|
icon: "User",
|
||||||
|
constraints: {
|
||||||
|
type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AUTO_COLUMN_SUB_TYPES = {
|
export const AUTO_COLUMN_SUB_TYPES = {
|
||||||
|
|
|
@ -118,7 +118,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOperatorOptions = condition => {
|
const getOperatorOptions = condition => {
|
||||||
return LuceneUtils.getValidOperatorsForType(condition.valueType)
|
return LuceneUtils.getValidOperatorsForType({ type: condition.valueType })
|
||||||
}
|
}
|
||||||
|
|
||||||
const onOperatorChange = (condition, newOperator) => {
|
const onOperatorChange = (condition, newOperator) => {
|
||||||
|
@ -137,9 +137,9 @@
|
||||||
condition.referenceValue = null
|
condition.referenceValue = null
|
||||||
|
|
||||||
// Ensure a valid operator is set
|
// Ensure a valid operator is set
|
||||||
const validOperators = LuceneUtils.getValidOperatorsForType(newType).map(
|
const validOperators = LuceneUtils.getValidOperatorsForType({
|
||||||
x => x.value
|
type: newType,
|
||||||
)
|
}).map(x => x.value)
|
||||||
if (!validOperators.includes(condition.operator)) {
|
if (!validOperators.includes(condition.operator)) {
|
||||||
condition.operator =
|
condition.operator =
|
||||||
validOperators[0] ?? Constants.OperatorOptions.Equals.value
|
validOperators[0] ?? Constants.OperatorOptions.Equals.value
|
||||||
|
|
|
@ -5673,11 +5673,6 @@
|
||||||
"label": "Validation",
|
"label": "Validation",
|
||||||
"key": "validation"
|
"key": "validation"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "filter/relationship",
|
|
||||||
"label": "Filtering",
|
|
||||||
"key": "filter"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Search",
|
"label": "Search",
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
|
|
||||||
// Ensure a valid operator is set
|
// Ensure a valid operator is set
|
||||||
const validOperators = LuceneUtils.getValidOperatorsForType(
|
const validOperators = LuceneUtils.getValidOperatorsForType(
|
||||||
expression.type,
|
{ type: expression.type },
|
||||||
expression.field,
|
expression.field,
|
||||||
datasource
|
datasource
|
||||||
).map(x => x.value)
|
).map(x => x.value)
|
||||||
|
@ -125,7 +125,7 @@
|
||||||
<Select
|
<Select
|
||||||
disabled={!filter.field}
|
disabled={!filter.field}
|
||||||
options={LuceneUtils.getValidOperatorsForType(
|
options={LuceneUtils.getValidOperatorsForType(
|
||||||
filter.type,
|
{ type: filter.type, subtype: filter.subtype },
|
||||||
filter.field,
|
filter.field,
|
||||||
datasource
|
datasource
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,9 +1,28 @@
|
||||||
<script>
|
<script>
|
||||||
import RelationshipField from "./RelationshipField.svelte"
|
import RelationshipField from "./RelationshipField.svelte"
|
||||||
|
import { sdk } from "@budibase/shared-core"
|
||||||
|
|
||||||
|
export let defaultValue
|
||||||
|
|
||||||
|
function updateUserIDs(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(val => sdk.users.getGlobalUserID(val))
|
||||||
|
} else {
|
||||||
|
return sdk.users.getGlobalUserID(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateReferences(value) {
|
||||||
|
if (sdk.users.containsUserID(value)) {
|
||||||
|
return updateUserIDs(value)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<RelationshipField
|
<RelationshipField
|
||||||
{...$$props}
|
{...$$props}
|
||||||
datasourceType={"user"}
|
datasourceType={"user"}
|
||||||
primaryDisplay={"email"}
|
primaryDisplay={"email"}
|
||||||
|
defaultValue={updateReferences(defaultValue)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -160,7 +160,9 @@
|
||||||
const handleChange = value => {
|
const handleChange = value => {
|
||||||
const changed = fieldApi.setValue(value)
|
const changed = fieldApi.setValue(value)
|
||||||
if (onChange && changed) {
|
if (onChange && changed) {
|
||||||
onChange({ value })
|
onChange({
|
||||||
|
value,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import RelationshipCell from "./RelationshipCell.svelte"
|
import RelationshipCell from "./RelationshipCell.svelte"
|
||||||
import { FieldSubtype } from "@budibase/types"
|
import { FieldSubtype, RelationshipType } from "@budibase/types"
|
||||||
|
|
||||||
export let api
|
export let api
|
||||||
|
|
||||||
|
@ -12,10 +12,14 @@
|
||||||
...$$props.schema,
|
...$$props.schema,
|
||||||
// This is not really used, just adding some content to be able to render the relationship cell
|
// This is not really used, just adding some content to be able to render the relationship cell
|
||||||
tableId: "external",
|
tableId: "external",
|
||||||
|
relationshipType:
|
||||||
|
subtype === FieldSubtype.USER
|
||||||
|
? RelationshipType.ONE_TO_MANY
|
||||||
|
: RelationshipType.MANY_TO_MANY,
|
||||||
}
|
}
|
||||||
|
|
||||||
async function searchFunction(searchParams) {
|
async function searchFunction(searchParams) {
|
||||||
if (subtype !== FieldSubtype.USER) {
|
if (subtype !== FieldSubtype.USER && subtype !== FieldSubtype.USERS) {
|
||||||
throw `Search for '${subtype}' not implemented`
|
throw `Search for '${subtype}' not implemented`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount, tick } from "svelte"
|
import { getContext, onMount, tick } from "svelte"
|
||||||
import GridCell from "./GridCell.svelte"
|
import { canBeDisplayColumn } from "@budibase/shared-core"
|
||||||
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
|
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
|
||||||
|
import GridCell from "./GridCell.svelte"
|
||||||
import { getColumnIcon } from "../lib/utils"
|
import { getColumnIcon } from "../lib/utils"
|
||||||
|
|
||||||
export let column
|
export let column
|
||||||
|
@ -24,14 +25,6 @@
|
||||||
datasource,
|
datasource,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
const bannedDisplayColumnTypes = [
|
|
||||||
"link",
|
|
||||||
"array",
|
|
||||||
"attachment",
|
|
||||||
"boolean",
|
|
||||||
"json",
|
|
||||||
]
|
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let open = false
|
let open = false
|
||||||
let editIsOpen = false
|
let editIsOpen = false
|
||||||
|
@ -231,8 +224,7 @@
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="Label"
|
icon="Label"
|
||||||
on:click={makeDisplayColumn}
|
on:click={makeDisplayColumn}
|
||||||
disabled={idx === "sticky" ||
|
disabled={idx === "sticky" || !canBeDisplayColumn(column.schema.type)}
|
||||||
bannedDisplayColumnTypes.includes(column.schema.type)}
|
|
||||||
>
|
>
|
||||||
Use as display column
|
Use as display column
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
|
@ -21,6 +21,7 @@ const TypeIconMap = {
|
||||||
bigint: "TagBold",
|
bigint: "TagBold",
|
||||||
bb_reference: {
|
bb_reference: {
|
||||||
user: "User",
|
user: "User",
|
||||||
|
users: "UserGroup",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@
|
||||||
"@trendyol/jest-testcontainers": "2.1.1",
|
"@trendyol/jest-testcontainers": "2.1.1",
|
||||||
"@types/global-agent": "2.1.1",
|
"@types/global-agent": "2.1.1",
|
||||||
"@types/google-spreadsheet": "3.1.5",
|
"@types/google-spreadsheet": "3.1.5",
|
||||||
"@types/jest": "29.5.3",
|
"@types/jest": "29.5.5",
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/koa__router": "8.0.8",
|
"@types/koa__router": "8.0.8",
|
||||||
"@types/lodash": "4.14.180",
|
"@types/lodash": "4.14.180",
|
||||||
|
|
|
@ -156,7 +156,10 @@ export async function destroy(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
const table = await sdk.tables.getTable(row.tableId)
|
const table = await sdk.tables.getTable(row.tableId)
|
||||||
// update the row to include full relationships before deleting them
|
// update the row to include full relationships before deleting them
|
||||||
row = await outputProcessing(table, row, { squash: false })
|
row = await outputProcessing(table, row, {
|
||||||
|
squash: false,
|
||||||
|
skipBBReferences: true,
|
||||||
|
})
|
||||||
// now remove the relationships
|
// now remove the relationships
|
||||||
await linkRows.updateLinks({
|
await linkRows.updateLinks({
|
||||||
eventType: linkRows.EventType.ROW_DELETE,
|
eventType: linkRows.EventType.ROW_DELETE,
|
||||||
|
@ -190,6 +193,7 @@ export async function bulkDestroy(ctx: UserCtx) {
|
||||||
// they need to be the full rows (including previous relationships) for automations
|
// they need to be the full rows (including previous relationships) for automations
|
||||||
const processedRows = (await outputProcessing(table, rows, {
|
const processedRows = (await outputProcessing(table, rows, {
|
||||||
squash: false,
|
squash: false,
|
||||||
|
skipBBReferences: true,
|
||||||
})) as Row[]
|
})) as Row[]
|
||||||
|
|
||||||
// remove the relationships first
|
// remove the relationships first
|
||||||
|
|
|
@ -15,8 +15,8 @@ import { handleRequest } from "../row/external"
|
||||||
import { context, events } from "@budibase/backend-core"
|
import { context, events } from "@budibase/backend-core"
|
||||||
import { isRows, isSchema, parse } from "../../../utilities/schema"
|
import { isRows, isSchema, parse } from "../../../utilities/schema"
|
||||||
import {
|
import {
|
||||||
|
BulkImportRequest,
|
||||||
Datasource,
|
Datasource,
|
||||||
FieldSchema,
|
|
||||||
ManyToManyRelationshipFieldMetadata,
|
ManyToManyRelationshipFieldMetadata,
|
||||||
ManyToOneRelationshipFieldMetadata,
|
ManyToOneRelationshipFieldMetadata,
|
||||||
OneToManyRelationshipFieldMetadata,
|
OneToManyRelationshipFieldMetadata,
|
||||||
|
@ -385,10 +385,10 @@ export async function destroy(ctx: UserCtx) {
|
||||||
return tableToDelete
|
return tableToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bulkImport(ctx: UserCtx) {
|
export async function bulkImport(ctx: UserCtx<BulkImportRequest>) {
|
||||||
const table = await sdk.tables.getTable(ctx.params.tableId)
|
const table = await sdk.tables.getTable(ctx.params.tableId)
|
||||||
const { rows }: { rows: unknown } = ctx.request.body
|
const { rows } = ctx.request.body
|
||||||
const schema: unknown = table.schema
|
const schema = table.schema
|
||||||
|
|
||||||
if (!rows || !isRows(rows) || !isSchema(schema)) {
|
if (!rows || !isRows(rows) || !isSchema(schema)) {
|
||||||
ctx.throw(400, "Provided data import information is invalid.")
|
ctx.throw(400, "Provided data import information is invalid.")
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
import { isExternalTable, isSQL } from "../../../integrations/utils"
|
import { isExternalTable, isSQL } from "../../../integrations/utils"
|
||||||
import { events } from "@budibase/backend-core"
|
import { events } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
|
BulkImportRequest,
|
||||||
FetchTablesResponse,
|
FetchTablesResponse,
|
||||||
SaveTableRequest,
|
SaveTableRequest,
|
||||||
SaveTableResponse,
|
SaveTableResponse,
|
||||||
|
@ -97,7 +98,7 @@ export async function destroy(ctx: UserCtx) {
|
||||||
builderSocket?.emitTableDeletion(ctx, deletedTable)
|
builderSocket?.emitTableDeletion(ctx, deletedTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bulkImport(ctx: UserCtx) {
|
export async function bulkImport(ctx: UserCtx<BulkImportRequest>) {
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
await pickApi({ tableId }).bulkImport(ctx)
|
await pickApi({ tableId }).bulkImport(ctx)
|
||||||
// right now we don't trigger anything for bulk import because it
|
// right now we don't trigger anything for bulk import because it
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from "../../../utilities/rowProcessor"
|
} from "../../../utilities/rowProcessor"
|
||||||
import { runStaticFormulaChecks } from "./bulkFormula"
|
import { runStaticFormulaChecks } from "./bulkFormula"
|
||||||
import {
|
import {
|
||||||
AutoColumnFieldMetadata,
|
BulkImportRequest,
|
||||||
RenameColumn,
|
RenameColumn,
|
||||||
SaveTableRequest,
|
SaveTableRequest,
|
||||||
SaveTableResponse,
|
SaveTableResponse,
|
||||||
|
@ -207,7 +207,7 @@ export async function destroy(ctx: any) {
|
||||||
return tableToDelete
|
return tableToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bulkImport(ctx: any) {
|
export async function bulkImport(ctx: UserCtx<BulkImportRequest>) {
|
||||||
const table = await sdk.tables.getTable(ctx.params.tableId)
|
const table = await sdk.tables.getTable(ctx.params.tableId)
|
||||||
const { rows, identifierFields } = ctx.request.body
|
const { rows, identifierFields } = ctx.request.body
|
||||||
await handleDataImport(ctx.user, table, rows, identifierFields)
|
await handleDataImport(ctx.user, table, rows, identifierFields)
|
||||||
|
|
|
@ -20,7 +20,13 @@ import viewTemplate from "../view/viewBuilder"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { events, context } from "@budibase/backend-core"
|
import { events, context } from "@budibase/backend-core"
|
||||||
import { ContextUser, Datasource, SourceName, Table } from "@budibase/types"
|
import {
|
||||||
|
ContextUser,
|
||||||
|
Datasource,
|
||||||
|
Row,
|
||||||
|
SourceName,
|
||||||
|
Table,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
export async function clearColumns(table: any, columnNames: any) {
|
export async function clearColumns(table: any, columnNames: any) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
@ -144,12 +150,12 @@ export async function importToRows(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleDataImport(
|
export async function handleDataImport(
|
||||||
user: any,
|
user: ContextUser,
|
||||||
table: any,
|
table: Table,
|
||||||
rows: any,
|
rows: Row[],
|
||||||
identifierFields: Array<string> = []
|
identifierFields: Array<string> = []
|
||||||
) {
|
) {
|
||||||
const schema: unknown = table.schema
|
const schema = table.schema
|
||||||
|
|
||||||
if (!rows || !isRows(rows) || !isSchema(schema)) {
|
if (!rows || !isRows(rows) || !isSchema(schema)) {
|
||||||
return table
|
return table
|
||||||
|
|
|
@ -43,3 +43,7 @@ export enum Format {
|
||||||
export function isFormat(format: any): format is Format {
|
export function isFormat(format: any): format is Format {
|
||||||
return Object.values(Format).includes(format as Format)
|
return Object.values(Format).includes(format as Format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseCsvExport<T>(value: string) {
|
||||||
|
return JSON.parse(value?.replace(/'/g, '"')) as T
|
||||||
|
}
|
||||||
|
|
|
@ -1573,16 +1573,12 @@ describe.each([
|
||||||
users: {
|
users: {
|
||||||
name: "users",
|
name: "users",
|
||||||
type: FieldType.BB_REFERENCE,
|
type: FieldType.BB_REFERENCE,
|
||||||
subtype: FieldTypeSubtypes.BB_REFERENCE.USER,
|
subtype: FieldTypeSubtypes.BB_REFERENCE.USERS,
|
||||||
// TODO: users when all merged
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
() => config.createUser(),
|
() => config.createUser(),
|
||||||
(row: Row) => ({
|
(row: Row) => ({
|
||||||
_id: row._id,
|
_id: row._id,
|
||||||
email: row.email,
|
|
||||||
firstName: row.firstName,
|
|
||||||
lastName: row.lastName,
|
|
||||||
primaryDisplay: row.email,
|
primaryDisplay: row.email,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { generator } from "@budibase/backend-core/tests"
|
||||||
import { events, context } from "@budibase/backend-core"
|
import { events, context } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
FieldType,
|
FieldType,
|
||||||
|
SaveTableRequest,
|
||||||
RelationshipType,
|
RelationshipType,
|
||||||
Table,
|
Table,
|
||||||
ViewCalculation,
|
ViewCalculation,
|
||||||
|
@ -52,7 +53,7 @@ describe("/tables", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates a table via data import", async () => {
|
it("creates a table via data import", async () => {
|
||||||
const table = basicTable()
|
const table: SaveTableRequest = basicTable()
|
||||||
table.rows = [{ name: "test-name", description: "test-desc" }]
|
table.rows = [{ name: "test-name", description: "test-desc" }]
|
||||||
|
|
||||||
const res = await createTable(table)
|
const res = await createTable(table)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Knex, knex } from "knex"
|
import { Knex, knex } from "knex"
|
||||||
import {
|
import {
|
||||||
|
FieldSubtype,
|
||||||
NumberFieldMetadata,
|
NumberFieldMetadata,
|
||||||
Operation,
|
Operation,
|
||||||
QueryJson,
|
QueryJson,
|
||||||
|
@ -10,6 +11,7 @@ import { breakExternalTableId } from "../utils"
|
||||||
import SchemaBuilder = Knex.SchemaBuilder
|
import SchemaBuilder = Knex.SchemaBuilder
|
||||||
import CreateTableBuilder = Knex.CreateTableBuilder
|
import CreateTableBuilder = Knex.CreateTableBuilder
|
||||||
import { FieldTypes, RelationshipType } from "../../constants"
|
import { FieldTypes, RelationshipType } from "../../constants"
|
||||||
|
import { utils } from "@budibase/shared-core"
|
||||||
|
|
||||||
function generateSchema(
|
function generateSchema(
|
||||||
schema: CreateTableBuilder,
|
schema: CreateTableBuilder,
|
||||||
|
@ -49,9 +51,21 @@ function generateSchema(
|
||||||
case FieldTypes.OPTIONS:
|
case FieldTypes.OPTIONS:
|
||||||
case FieldTypes.LONGFORM:
|
case FieldTypes.LONGFORM:
|
||||||
case FieldTypes.BARCODEQR:
|
case FieldTypes.BARCODEQR:
|
||||||
case FieldTypes.BB_REFERENCE:
|
|
||||||
schema.text(key)
|
schema.text(key)
|
||||||
break
|
break
|
||||||
|
case FieldTypes.BB_REFERENCE:
|
||||||
|
const subtype = column.subtype as FieldSubtype
|
||||||
|
switch (subtype) {
|
||||||
|
case FieldSubtype.USER:
|
||||||
|
schema.text(key)
|
||||||
|
break
|
||||||
|
case FieldSubtype.USERS:
|
||||||
|
schema.json(key)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw utils.unreachable(subtype)
|
||||||
|
}
|
||||||
|
break
|
||||||
case FieldTypes.NUMBER:
|
case FieldTypes.NUMBER:
|
||||||
// if meta is specified then this is a junction table entry
|
// if meta is specified then this is a junction table entry
|
||||||
if (column.meta && column.meta.toKey && column.meta.toTable) {
|
if (column.meta && column.meta.toKey && column.meta.toTable) {
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { SqlQuery, Table, SearchFilters, Datasource } from "@budibase/types"
|
import {
|
||||||
|
SqlQuery,
|
||||||
|
Table,
|
||||||
|
SearchFilters,
|
||||||
|
Datasource,
|
||||||
|
FieldType,
|
||||||
|
} from "@budibase/types"
|
||||||
import { DocumentType, SEPARATOR } from "../db/utils"
|
import { DocumentType, SEPARATOR } from "../db/utils"
|
||||||
import {
|
import {
|
||||||
FieldTypes,
|
|
||||||
BuildSchemaErrors,
|
BuildSchemaErrors,
|
||||||
InvalidColumns,
|
InvalidColumns,
|
||||||
NoEmptyFilterStrings,
|
NoEmptyFilterStrings,
|
||||||
|
@ -13,57 +18,57 @@ const ROW_ID_REGEX = /^\[.*]$/g
|
||||||
const ENCODED_SPACE = encodeURIComponent(" ")
|
const ENCODED_SPACE = encodeURIComponent(" ")
|
||||||
|
|
||||||
const SQL_NUMBER_TYPE_MAP = {
|
const SQL_NUMBER_TYPE_MAP = {
|
||||||
integer: FieldTypes.NUMBER,
|
integer: FieldType.NUMBER,
|
||||||
int: FieldTypes.NUMBER,
|
int: FieldType.NUMBER,
|
||||||
decimal: FieldTypes.NUMBER,
|
decimal: FieldType.NUMBER,
|
||||||
smallint: FieldTypes.NUMBER,
|
smallint: FieldType.NUMBER,
|
||||||
real: FieldTypes.NUMBER,
|
real: FieldType.NUMBER,
|
||||||
float: FieldTypes.NUMBER,
|
float: FieldType.NUMBER,
|
||||||
numeric: FieldTypes.NUMBER,
|
numeric: FieldType.NUMBER,
|
||||||
mediumint: FieldTypes.NUMBER,
|
mediumint: FieldType.NUMBER,
|
||||||
dec: FieldTypes.NUMBER,
|
dec: FieldType.NUMBER,
|
||||||
double: FieldTypes.NUMBER,
|
double: FieldType.NUMBER,
|
||||||
fixed: FieldTypes.NUMBER,
|
fixed: FieldType.NUMBER,
|
||||||
"double precision": FieldTypes.NUMBER,
|
"double precision": FieldType.NUMBER,
|
||||||
number: FieldTypes.NUMBER,
|
number: FieldType.NUMBER,
|
||||||
binary_float: FieldTypes.NUMBER,
|
binary_float: FieldType.NUMBER,
|
||||||
binary_double: FieldTypes.NUMBER,
|
binary_double: FieldType.NUMBER,
|
||||||
money: FieldTypes.NUMBER,
|
money: FieldType.NUMBER,
|
||||||
smallmoney: FieldTypes.NUMBER,
|
smallmoney: FieldType.NUMBER,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SQL_DATE_TYPE_MAP = {
|
const SQL_DATE_TYPE_MAP = {
|
||||||
timestamp: FieldTypes.DATETIME,
|
timestamp: FieldType.DATETIME,
|
||||||
time: FieldTypes.DATETIME,
|
time: FieldType.DATETIME,
|
||||||
datetime: FieldTypes.DATETIME,
|
datetime: FieldType.DATETIME,
|
||||||
smalldatetime: FieldTypes.DATETIME,
|
smalldatetime: FieldType.DATETIME,
|
||||||
date: FieldTypes.DATETIME,
|
date: FieldType.DATETIME,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SQL_DATE_ONLY_TYPES = ["date"]
|
const SQL_DATE_ONLY_TYPES = ["date"]
|
||||||
const SQL_TIME_ONLY_TYPES = ["time"]
|
const SQL_TIME_ONLY_TYPES = ["time"]
|
||||||
|
|
||||||
const SQL_STRING_TYPE_MAP = {
|
const SQL_STRING_TYPE_MAP = {
|
||||||
varchar: FieldTypes.STRING,
|
varchar: FieldType.STRING,
|
||||||
char: FieldTypes.STRING,
|
char: FieldType.STRING,
|
||||||
nchar: FieldTypes.STRING,
|
nchar: FieldType.STRING,
|
||||||
nvarchar: FieldTypes.STRING,
|
nvarchar: FieldType.STRING,
|
||||||
ntext: FieldTypes.STRING,
|
ntext: FieldType.STRING,
|
||||||
enum: FieldTypes.STRING,
|
enum: FieldType.STRING,
|
||||||
blob: FieldTypes.STRING,
|
blob: FieldType.STRING,
|
||||||
long: FieldTypes.STRING,
|
long: FieldType.STRING,
|
||||||
text: FieldTypes.STRING,
|
text: FieldType.STRING,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SQL_BOOLEAN_TYPE_MAP = {
|
const SQL_BOOLEAN_TYPE_MAP = {
|
||||||
boolean: FieldTypes.BOOLEAN,
|
boolean: FieldType.BOOLEAN,
|
||||||
bit: FieldTypes.BOOLEAN,
|
bit: FieldType.BOOLEAN,
|
||||||
tinyint: FieldTypes.BOOLEAN,
|
tinyint: FieldType.BOOLEAN,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SQL_MISC_TYPE_MAP = {
|
const SQL_MISC_TYPE_MAP = {
|
||||||
json: FieldTypes.JSON,
|
json: FieldType.JSON,
|
||||||
bigint: FieldTypes.BIGINT,
|
bigint: FieldType.BIGINT,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SQL_TYPE_MAP = {
|
const SQL_TYPE_MAP = {
|
||||||
|
@ -154,7 +159,7 @@ export function breakRowIdField(_id: string | { _id: string }): any[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertSqlType(type: string) {
|
export function convertSqlType(type: string) {
|
||||||
let foundType = FieldTypes.STRING
|
let foundType = FieldType.STRING
|
||||||
const lcType = type.toLowerCase()
|
const lcType = type.toLowerCase()
|
||||||
let matchingTypes = []
|
let matchingTypes = []
|
||||||
for (let [external, internal] of Object.entries(SQL_TYPE_MAP)) {
|
for (let [external, internal] of Object.entries(SQL_TYPE_MAP)) {
|
||||||
|
@ -169,7 +174,7 @@ export function convertSqlType(type: string) {
|
||||||
}).internal
|
}).internal
|
||||||
}
|
}
|
||||||
const schema: any = { type: foundType }
|
const schema: any = { type: foundType }
|
||||||
if (foundType === FieldTypes.DATETIME) {
|
if (foundType === FieldType.DATETIME) {
|
||||||
schema.dateOnly = SQL_DATE_ONLY_TYPES.includes(lcType)
|
schema.dateOnly = SQL_DATE_ONLY_TYPES.includes(lcType)
|
||||||
schema.timeOnly = SQL_TIME_ONLY_TYPES.includes(lcType)
|
schema.timeOnly = SQL_TIME_ONLY_TYPES.includes(lcType)
|
||||||
}
|
}
|
||||||
|
@ -212,7 +217,7 @@ export function shouldCopyRelationship(
|
||||||
tableIds: string[]
|
tableIds: string[]
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
column.type === FieldTypes.LINK &&
|
column.type === FieldType.LINK &&
|
||||||
column.tableId &&
|
column.tableId &&
|
||||||
tableIds.includes(column.tableId)
|
tableIds.includes(column.tableId)
|
||||||
)
|
)
|
||||||
|
@ -230,22 +235,23 @@ export function shouldCopySpecialColumn(
|
||||||
column: { type: string },
|
column: { type: string },
|
||||||
fetchedColumn: { type: string } | undefined
|
fetchedColumn: { type: string } | undefined
|
||||||
) {
|
) {
|
||||||
const isFormula = column.type === FieldTypes.FORMULA
|
const isFormula = column.type === FieldType.FORMULA
|
||||||
const specialTypes = [
|
const specialTypes = [
|
||||||
FieldTypes.OPTIONS,
|
FieldType.OPTIONS,
|
||||||
FieldTypes.LONGFORM,
|
FieldType.LONGFORM,
|
||||||
FieldTypes.ARRAY,
|
FieldType.ARRAY,
|
||||||
FieldTypes.FORMULA,
|
FieldType.FORMULA,
|
||||||
|
FieldType.BB_REFERENCE,
|
||||||
]
|
]
|
||||||
// column has been deleted, remove - formulas will never exist, always copy
|
// column has been deleted, remove - formulas will never exist, always copy
|
||||||
if (!isFormula && column && !fetchedColumn) {
|
if (!isFormula && column && !fetchedColumn) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const fetchedIsNumber =
|
const fetchedIsNumber =
|
||||||
!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER
|
!fetchedColumn || fetchedColumn.type === FieldType.NUMBER
|
||||||
return (
|
return (
|
||||||
specialTypes.indexOf(column.type as FieldTypes) !== -1 ||
|
specialTypes.indexOf(column.type as FieldType) !== -1 ||
|
||||||
(fetchedIsNumber && column.type === FieldTypes.BOOLEAN)
|
(fetchedIsNumber && column.type === FieldType.BOOLEAN)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,21 @@ const tableWithUserCol: Table = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("searchInputMapping", () => {
|
const tableWithUsersCol: Table = {
|
||||||
|
_id: tableId,
|
||||||
|
name: "table",
|
||||||
|
schema: {
|
||||||
|
user: {
|
||||||
|
name: "user",
|
||||||
|
type: FieldType.BB_REFERENCE,
|
||||||
|
subtype: FieldTypeSubtypes.BB_REFERENCE.USERS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe.each([tableWithUserCol, tableWithUsersCol])(
|
||||||
|
"searchInputMapping",
|
||||||
|
col => {
|
||||||
const globalUserId = dbCore.generateGlobalUserID()
|
const globalUserId = dbCore.generateGlobalUserID()
|
||||||
const userMedataId = dbCore.generateUserMetadataID(globalUserId)
|
const userMedataId = dbCore.generateUserMetadataID(globalUserId)
|
||||||
|
|
||||||
|
@ -33,7 +47,7 @@ describe("searchInputMapping", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const output = searchInputMapping(tableWithUserCol, params)
|
const output = searchInputMapping(col, params)
|
||||||
expect(output.query.equal!["1:user"]).toBe(globalUserId)
|
expect(output.query.equal!["1:user"]).toBe(globalUserId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -46,7 +60,7 @@ describe("searchInputMapping", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const output = searchInputMapping(tableWithUserCol, params)
|
const output = searchInputMapping(col, params)
|
||||||
expect(output.query.oneOf!["1:user"]).toStrictEqual([
|
expect(output.query.oneOf!["1:user"]).toStrictEqual([
|
||||||
globalUserId,
|
globalUserId,
|
||||||
globalUserId,
|
globalUserId,
|
||||||
|
@ -63,7 +77,7 @@ describe("searchInputMapping", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const output = searchInputMapping(tableWithUserCol, params)
|
const output = searchInputMapping(col, params)
|
||||||
expect(output.query.equal!["1:user"]).toBe(email)
|
expect(output.query.equal!["1:user"]).toBe(email)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -71,7 +85,8 @@ describe("searchInputMapping", () => {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
tableId,
|
tableId,
|
||||||
}
|
}
|
||||||
const output = searchInputMapping(tableWithUserCol, params)
|
const output = searchInputMapping(col, params)
|
||||||
expect(output.query).toBeUndefined()
|
expect(output.query).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -5,8 +5,10 @@ import {
|
||||||
Table,
|
Table,
|
||||||
DocumentType,
|
DocumentType,
|
||||||
SEPARATOR,
|
SEPARATOR,
|
||||||
|
FieldSubtype,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { db as dbCore } from "@budibase/backend-core"
|
import { db as dbCore } from "@budibase/backend-core"
|
||||||
|
import { utils } from "@budibase/shared-core"
|
||||||
|
|
||||||
function findColumnInQueries(
|
function findColumnInQueries(
|
||||||
column: string,
|
column: string,
|
||||||
|
@ -66,8 +68,14 @@ export function searchInputMapping(table: Table, options: SearchParams) {
|
||||||
for (let [key, column] of Object.entries(table.schema)) {
|
for (let [key, column] of Object.entries(table.schema)) {
|
||||||
switch (column.type) {
|
switch (column.type) {
|
||||||
case FieldType.BB_REFERENCE:
|
case FieldType.BB_REFERENCE:
|
||||||
if (column.subtype === FieldTypeSubtypes.BB_REFERENCE.USER) {
|
const subtype = column.subtype as FieldSubtype
|
||||||
|
switch (subtype) {
|
||||||
|
case FieldSubtype.USER:
|
||||||
|
case FieldSubtype.USERS:
|
||||||
userColumnMapping(key, options)
|
userColumnMapping(key, options)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
utils.unreachable(subtype)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ const ROW_PREFIX = DocumentType.ROW + SEPARATOR
|
||||||
export async function processInputBBReferences(
|
export async function processInputBBReferences(
|
||||||
value: string | string[] | { _id: string } | { _id: string }[],
|
value: string | string[] | { _id: string } | { _id: string }[],
|
||||||
subtype: FieldSubtype
|
subtype: FieldSubtype
|
||||||
): Promise<string | null> {
|
): Promise<string | string[] | null> {
|
||||||
let referenceIds: string[] = []
|
let referenceIds: string[] = []
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
|
@ -41,33 +41,39 @@ export async function processInputBBReferences(
|
||||||
|
|
||||||
switch (subtype) {
|
switch (subtype) {
|
||||||
case FieldSubtype.USER:
|
case FieldSubtype.USER:
|
||||||
|
case FieldSubtype.USERS:
|
||||||
const { notFoundIds } = await cache.user.getUsers(referenceIds)
|
const { notFoundIds } = await cache.user.getUsers(referenceIds)
|
||||||
|
|
||||||
if (notFoundIds?.length) {
|
if (notFoundIds?.length) {
|
||||||
throw new InvalidBBRefError(notFoundIds[0], FieldSubtype.USER)
|
throw new InvalidBBRefError(notFoundIds[0], FieldSubtype.USER)
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
if (subtype === FieldSubtype.USERS) {
|
||||||
default:
|
return referenceIds
|
||||||
throw utils.unreachable(subtype)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return referenceIds.join(",") || null
|
return referenceIds.join(",") || null
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw utils.unreachable(subtype)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function processOutputBBReferences(
|
export async function processOutputBBReferences(
|
||||||
value: string,
|
value: string | string[],
|
||||||
subtype: FieldSubtype
|
subtype: FieldSubtype
|
||||||
) {
|
) {
|
||||||
if (typeof value !== "string") {
|
if (value === null || value === undefined) {
|
||||||
// Already processed or nothing to process
|
// Already processed or nothing to process
|
||||||
return value || undefined
|
return value || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const ids = value.split(",").filter(id => !!id)
|
const ids =
|
||||||
|
typeof value === "string" ? value.split(",").filter(id => !!id) : value
|
||||||
|
|
||||||
switch (subtype) {
|
switch (subtype) {
|
||||||
case FieldSubtype.USER:
|
case FieldSubtype.USER:
|
||||||
|
case FieldSubtype.USERS:
|
||||||
const { users } = await cache.user.getUsers(ids)
|
const { users } = await cache.user.getUsers(ids)
|
||||||
if (!users.length) {
|
if (!users.length) {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -76,9 +82,6 @@ export async function processOutputBBReferences(
|
||||||
return users.map(u => ({
|
return users.map(u => ({
|
||||||
_id: u._id,
|
_id: u._id,
|
||||||
primaryDisplay: u.email,
|
primaryDisplay: u.email,
|
||||||
email: u.email,
|
|
||||||
firstName: u.firstName,
|
|
||||||
lastName: u.lastName,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -207,9 +207,14 @@ export async function inputProcessing(
|
||||||
export async function outputProcessing<T extends Row[] | Row>(
|
export async function outputProcessing<T extends Row[] | Row>(
|
||||||
table: Table,
|
table: Table,
|
||||||
rows: T,
|
rows: T,
|
||||||
opts: { squash?: boolean; preserveLinks?: boolean } = {
|
opts: {
|
||||||
|
squash?: boolean
|
||||||
|
preserveLinks?: boolean
|
||||||
|
skipBBReferences?: boolean
|
||||||
|
} = {
|
||||||
squash: true,
|
squash: true,
|
||||||
preserveLinks: false,
|
preserveLinks: false,
|
||||||
|
skipBBReferences: false,
|
||||||
}
|
}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
let safeRows: Row[]
|
let safeRows: Row[]
|
||||||
|
@ -225,10 +230,7 @@ export async function outputProcessing<T extends Row[] | Row>(
|
||||||
? await linkRows.attachFullLinkedDocs(table, safeRows)
|
? await linkRows.attachFullLinkedDocs(table, safeRows)
|
||||||
: safeRows
|
: safeRows
|
||||||
|
|
||||||
// process formulas
|
// process complex types: attachements, bb references...
|
||||||
enriched = processFormulas(table, enriched, { dynamic: true }) as Row[]
|
|
||||||
|
|
||||||
// set the attachments URLs
|
|
||||||
for (let [property, column] of Object.entries(table.schema)) {
|
for (let [property, column] of Object.entries(table.schema)) {
|
||||||
if (column.type === FieldTypes.ATTACHMENT) {
|
if (column.type === FieldTypes.ATTACHMENT) {
|
||||||
for (let row of enriched) {
|
for (let row of enriched) {
|
||||||
|
@ -239,7 +241,10 @@ export async function outputProcessing<T extends Row[] | Row>(
|
||||||
attachment.url = objectStore.getAppFileUrl(attachment.key)
|
attachment.url = objectStore.getAppFileUrl(attachment.key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (column.type == FieldTypes.BB_REFERENCE) {
|
} else if (
|
||||||
|
!opts.skipBBReferences &&
|
||||||
|
column.type == FieldTypes.BB_REFERENCE
|
||||||
|
) {
|
||||||
for (let row of enriched) {
|
for (let row of enriched) {
|
||||||
row[property] = await processOutputBBReferences(
|
row[property] = await processOutputBBReferences(
|
||||||
row[property],
|
row[property],
|
||||||
|
@ -248,6 +253,10 @@ export async function outputProcessing<T extends Row[] | Row>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// process formulas after the complex types had been processed
|
||||||
|
enriched = processFormulas(table, enriched, { dynamic: true }) as Row[]
|
||||||
|
|
||||||
if (opts.squash) {
|
if (opts.squash) {
|
||||||
enriched = (await linkRows.squashLinksToPrimaryDisplay(
|
enriched = (await linkRows.squashLinksToPrimaryDisplay(
|
||||||
table,
|
table,
|
||||||
|
|
|
@ -180,9 +180,6 @@ describe("bbReferenceProcessor", () => {
|
||||||
{
|
{
|
||||||
_id: user._id,
|
_id: user._id,
|
||||||
primaryDisplay: user.email,
|
primaryDisplay: user.email,
|
||||||
email: user.email,
|
|
||||||
firstName: user.firstName,
|
|
||||||
lastName: user.lastName,
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
expect(cacheGetUsersSpy).toBeCalledTimes(1)
|
expect(cacheGetUsersSpy).toBeCalledTimes(1)
|
||||||
|
@ -207,9 +204,6 @@ describe("bbReferenceProcessor", () => {
|
||||||
[user1, user2].map(u => ({
|
[user1, user2].map(u => ({
|
||||||
_id: u._id,
|
_id: u._id,
|
||||||
primaryDisplay: u.email,
|
primaryDisplay: u.email,
|
||||||
email: u.email,
|
|
||||||
firstName: u.firstName,
|
|
||||||
lastName: u.lastName,
|
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
import { FieldSubtype } from "@budibase/types"
|
||||||
import { FieldTypes } from "../constants"
|
import { FieldTypes } from "../constants"
|
||||||
import { ValidColumnNameRegex } from "@budibase/shared-core"
|
import { ValidColumnNameRegex, utils } from "@budibase/shared-core"
|
||||||
|
import { db } from "@budibase/backend-core"
|
||||||
|
import { parseCsvExport } from "../api/controllers/view/exporters"
|
||||||
|
|
||||||
interface SchemaColumn {
|
interface SchemaColumn {
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly type: FieldTypes
|
readonly type: FieldTypes
|
||||||
|
readonly subtype: FieldSubtype
|
||||||
readonly autocolumn?: boolean
|
readonly autocolumn?: boolean
|
||||||
readonly constraints?: {
|
readonly constraints?: {
|
||||||
presence: boolean
|
presence: boolean
|
||||||
|
@ -77,8 +81,14 @@ export function validate(rows: Rows, schema: Schema): ValidationResults {
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
Object.entries(row).forEach(([columnName, columnData]) => {
|
Object.entries(row).forEach(([columnName, columnData]) => {
|
||||||
const columnType = schema[columnName]?.type
|
const columnType = schema[columnName]?.type
|
||||||
|
const columnSubtype = schema[columnName]?.subtype
|
||||||
const isAutoColumn = schema[columnName]?.autocolumn
|
const isAutoColumn = schema[columnName]?.autocolumn
|
||||||
|
|
||||||
|
// If the column had an invalid value we don't want to override it
|
||||||
|
if (results.schemaValidation[columnName] === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If the columnType is not a string, then it's not present in the schema, and should be added to the invalid columns array
|
// If the columnType is not a string, then it's not present in the schema, and should be added to the invalid columns array
|
||||||
if (typeof columnType !== "string") {
|
if (typeof columnType !== "string") {
|
||||||
results.invalidColumns.push(columnName)
|
results.invalidColumns.push(columnName)
|
||||||
|
@ -112,6 +122,11 @@ export function validate(rows: Rows, schema: Schema): ValidationResults {
|
||||||
isNaN(new Date(columnData).getTime())
|
isNaN(new Date(columnData).getTime())
|
||||||
) {
|
) {
|
||||||
results.schemaValidation[columnName] = false
|
results.schemaValidation[columnName] = false
|
||||||
|
} else if (
|
||||||
|
columnType === FieldTypes.BB_REFERENCE &&
|
||||||
|
!isValidBBReference(columnData, columnSubtype)
|
||||||
|
) {
|
||||||
|
results.schemaValidation[columnName] = false
|
||||||
} else {
|
} else {
|
||||||
results.schemaValidation[columnName] = true
|
results.schemaValidation[columnName] = true
|
||||||
}
|
}
|
||||||
|
@ -138,6 +153,7 @@ export function parse(rows: Rows, schema: Schema): Rows {
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnType = schema[columnName].type
|
const columnType = schema[columnName].type
|
||||||
|
const columnSubtype = schema[columnName].subtype
|
||||||
|
|
||||||
if (columnType === FieldTypes.NUMBER) {
|
if (columnType === FieldTypes.NUMBER) {
|
||||||
// If provided must be a valid number
|
// If provided must be a valid number
|
||||||
|
@ -147,6 +163,23 @@ export function parse(rows: Rows, schema: Schema): Rows {
|
||||||
parsedRow[columnName] = columnData
|
parsedRow[columnName] = columnData
|
||||||
? new Date(columnData).toISOString()
|
? new Date(columnData).toISOString()
|
||||||
: columnData
|
: columnData
|
||||||
|
} else if (columnType === FieldTypes.BB_REFERENCE) {
|
||||||
|
const parsedValues =
|
||||||
|
!!columnData && parseCsvExport<{ _id: string }[]>(columnData)
|
||||||
|
if (!parsedValues) {
|
||||||
|
parsedRow[columnName] = undefined
|
||||||
|
} else {
|
||||||
|
switch (columnSubtype) {
|
||||||
|
case FieldSubtype.USER:
|
||||||
|
parsedRow[columnName] = parsedValues[0]?._id
|
||||||
|
break
|
||||||
|
case FieldSubtype.USERS:
|
||||||
|
parsedRow[columnName] = parsedValues.map(u => u._id)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
utils.unreachable(columnSubtype)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
parsedRow[columnName] = columnData
|
parsedRow[columnName] = columnData
|
||||||
}
|
}
|
||||||
|
@ -155,3 +188,32 @@ export function parse(rows: Rows, schema: Schema): Rows {
|
||||||
return parsedRow
|
return parsedRow
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidBBReference(
|
||||||
|
columnData: any,
|
||||||
|
columnSubtype: FieldSubtype
|
||||||
|
): boolean {
|
||||||
|
switch (columnSubtype) {
|
||||||
|
case FieldSubtype.USER:
|
||||||
|
case FieldSubtype.USERS:
|
||||||
|
if (typeof columnData !== "string") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const userArray = parseCsvExport<{ _id: string }[]>(columnData)
|
||||||
|
if (!Array.isArray(userArray)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnSubtype === FieldSubtype.USER && userArray.length > 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const constainsWrongId = userArray.find(
|
||||||
|
user => !db.isGlobalUserID(user._id)
|
||||||
|
)
|
||||||
|
return !constainsWrongId
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw utils.unreachable(columnSubtype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
SearchFilter,
|
SearchFilter,
|
||||||
SearchQuery,
|
SearchQuery,
|
||||||
SearchQueryFields,
|
SearchQueryFields,
|
||||||
|
FieldSubtype,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants"
|
import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants"
|
||||||
import { deepGet } from "./helpers"
|
import { deepGet } from "./helpers"
|
||||||
|
@ -16,7 +17,7 @@ const HBS_REGEX = /{{([^{].*?)}}/g
|
||||||
* Returns the valid operator options for a certain data type
|
* Returns the valid operator options for a certain data type
|
||||||
*/
|
*/
|
||||||
export const getValidOperatorsForType = (
|
export const getValidOperatorsForType = (
|
||||||
type: FieldType,
|
fieldType: { type: FieldType; subtype?: FieldSubtype },
|
||||||
field: string,
|
field: string,
|
||||||
datasource: Datasource & { tableId: any } // TODO: is this table id ever populated?
|
datasource: Datasource & { tableId: any } // TODO: is this table id ever populated?
|
||||||
) => {
|
) => {
|
||||||
|
@ -43,6 +44,7 @@ export const getValidOperatorsForType = (
|
||||||
value: string
|
value: string
|
||||||
label: string
|
label: string
|
||||||
}[] = []
|
}[] = []
|
||||||
|
const { type, subtype } = fieldType
|
||||||
if (type === FieldType.STRING) {
|
if (type === FieldType.STRING) {
|
||||||
ops = stringOps
|
ops = stringOps
|
||||||
} else if (type === FieldType.NUMBER || type === FieldType.BIGINT) {
|
} else if (type === FieldType.NUMBER || type === FieldType.BIGINT) {
|
||||||
|
@ -59,8 +61,10 @@ export const getValidOperatorsForType = (
|
||||||
ops = numOps
|
ops = numOps
|
||||||
} else if (type === FieldType.FORMULA) {
|
} else if (type === FieldType.FORMULA) {
|
||||||
ops = stringOps.concat([Op.MoreThan, Op.LessThan])
|
ops = stringOps.concat([Op.MoreThan, Op.LessThan])
|
||||||
} else if (type === FieldType.BB_REFERENCE) {
|
} else if (type === FieldType.BB_REFERENCE && subtype == FieldSubtype.USER) {
|
||||||
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 && subtype == FieldSubtype.USERS) {
|
||||||
|
ops = [Op.Contains, Op.NotContains, Op.ContainsAny, Op.Empty, Op.NotEmpty]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only allow equal/not equal for _id in SQL tables
|
// Only allow equal/not equal for _id in SQL tables
|
||||||
|
|
|
@ -3,3 +3,4 @@ export * as dataFilters from "./filters"
|
||||||
export * as helpers from "./helpers"
|
export * as helpers from "./helpers"
|
||||||
export * as utils from "./utils"
|
export * as utils from "./utils"
|
||||||
export * as sdk from "./sdk"
|
export * as sdk from "./sdk"
|
||||||
|
export * from "./table"
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { ContextUser, User } from "@budibase/types"
|
import {
|
||||||
|
ContextUser,
|
||||||
|
DocumentType,
|
||||||
|
SEPARATOR,
|
||||||
|
User,
|
||||||
|
InternalTable,
|
||||||
|
} from "@budibase/types"
|
||||||
import { getProdAppID } from "./applications"
|
import { getProdAppID } from "./applications"
|
||||||
|
|
||||||
// checks if a user is specifically a builder, given an app ID
|
// checks if a user is specifically a builder, given an app ID
|
||||||
|
@ -67,3 +73,21 @@ export function hasAdminPermissions(user?: User | ContextUser): boolean {
|
||||||
}
|
}
|
||||||
return !!user.admin?.global
|
return !!user.admin?.global
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getGlobalUserID(userId?: string): string | undefined {
|
||||||
|
if (typeof userId !== "string") {
|
||||||
|
return userId
|
||||||
|
}
|
||||||
|
const prefix = `${DocumentType.ROW}${SEPARATOR}${InternalTable.USER_METADATA}${SEPARATOR}`
|
||||||
|
if (!userId.startsWith(prefix)) {
|
||||||
|
return userId
|
||||||
|
}
|
||||||
|
return userId.split(prefix)[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function containsUserID(value: string | undefined): boolean {
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return value.includes(`${DocumentType.USER}${SEPARATOR}`)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { FieldType } from "@budibase/types"
|
||||||
|
|
||||||
|
const allowDisplayColumnByType: Record<FieldType, boolean> = {
|
||||||
|
[FieldType.STRING]: true,
|
||||||
|
[FieldType.LONGFORM]: true,
|
||||||
|
[FieldType.OPTIONS]: true,
|
||||||
|
[FieldType.NUMBER]: true,
|
||||||
|
[FieldType.DATETIME]: true,
|
||||||
|
[FieldType.FORMULA]: true,
|
||||||
|
[FieldType.AUTO]: true,
|
||||||
|
[FieldType.INTERNAL]: true,
|
||||||
|
[FieldType.BARCODEQR]: true,
|
||||||
|
[FieldType.BIGINT]: true,
|
||||||
|
|
||||||
|
[FieldType.BOOLEAN]: false,
|
||||||
|
[FieldType.ARRAY]: false,
|
||||||
|
[FieldType.ATTACHMENT]: false,
|
||||||
|
[FieldType.LINK]: false,
|
||||||
|
[FieldType.JSON]: false,
|
||||||
|
[FieldType.BB_REFERENCE]: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canBeDisplayColumn(type: FieldType): boolean {
|
||||||
|
return !!allowDisplayColumnByType[type]
|
||||||
|
}
|
|
@ -15,7 +15,8 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@budibase/types": ["../types/src"]
|
"@budibase/types": ["../types/src"]
|
||||||
}
|
},
|
||||||
|
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||||
},
|
},
|
||||||
"include": ["**/*.js", "**/*.ts"],
|
"include": ["**/*.js", "**/*.ts"],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "..",
|
"baseUrl": "..",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"composite": true,
|
"composite": true
|
||||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
Row,
|
||||||
Table,
|
Table,
|
||||||
TableRequest,
|
TableRequest,
|
||||||
TableSchema,
|
TableSchema,
|
||||||
|
@ -18,6 +19,13 @@ export interface TableResponse extends Table {
|
||||||
|
|
||||||
export type FetchTablesResponse = TableResponse[]
|
export type FetchTablesResponse = TableResponse[]
|
||||||
|
|
||||||
export interface SaveTableRequest extends TableRequest {}
|
export interface SaveTableRequest extends TableRequest {
|
||||||
|
rows?: Row[]
|
||||||
|
}
|
||||||
|
|
||||||
export type SaveTableResponse = Table
|
export type SaveTableResponse = Table
|
||||||
|
|
||||||
|
export interface BulkImportRequest {
|
||||||
|
rows: Row[]
|
||||||
|
identifierFields?: Array<string>
|
||||||
|
}
|
||||||
|
|
|
@ -37,10 +37,12 @@ export interface Row extends Document {
|
||||||
|
|
||||||
export enum FieldSubtype {
|
export enum FieldSubtype {
|
||||||
USER = "user",
|
USER = "user",
|
||||||
|
USERS = "users",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FieldTypeSubtypes = {
|
export const FieldTypeSubtypes = {
|
||||||
BB_REFERENCE: {
|
BB_REFERENCE: {
|
||||||
USER: FieldSubtype.USER,
|
USER: FieldSubtype.USER,
|
||||||
|
USERS: FieldSubtype.USERS,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ export interface FormulaFieldMetadata extends BaseFieldSchema {
|
||||||
export interface BBReferenceFieldMetadata
|
export interface BBReferenceFieldMetadata
|
||||||
extends Omit<BaseFieldSchema, "subtype"> {
|
extends Omit<BaseFieldSchema, "subtype"> {
|
||||||
type: FieldType.BB_REFERENCE
|
type: FieldType.BB_REFERENCE
|
||||||
subtype: FieldSubtype.USER
|
subtype: FieldSubtype.USER | FieldSubtype.USERS
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldConstraints {
|
export interface FieldConstraints {
|
||||||
|
|
|
@ -15,7 +15,6 @@ export interface Table extends Document {
|
||||||
constrained?: string[]
|
constrained?: string[]
|
||||||
sql?: boolean
|
sql?: boolean
|
||||||
indexes?: { [key: string]: any }
|
indexes?: { [key: string]: any }
|
||||||
rows?: { [key: string]: any }
|
|
||||||
created?: boolean
|
created?: boolean
|
||||||
rowHeight?: number
|
rowHeight?: number
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,10 @@ export const DocumentTypesToImport: DocumentType[] = [
|
||||||
DocumentType.LAYOUT,
|
DocumentType.LAYOUT,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export enum InternalTable {
|
||||||
|
USER_METADATA = "ta_users",
|
||||||
|
}
|
||||||
|
|
||||||
// these documents don't really exist, they are part of other
|
// these documents don't really exist, they are part of other
|
||||||
// documents or enriched into existence as part of get requests
|
// documents or enriched into existence as part of get requests
|
||||||
export enum VirtualDocumentType {
|
export enum VirtualDocumentType {
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"outDir": "dist"
|
"outDir": "dist",
|
||||||
|
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.spec.js"]
|
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.spec.js"]
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"composite": true,
|
"composite": true
|
||||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
|
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
"@swc/core": "1.3.71",
|
"@swc/core": "1.3.71",
|
||||||
"@swc/jest": "0.2.27",
|
"@swc/jest": "0.2.27",
|
||||||
"@trendyol/jest-testcontainers": "2.1.1",
|
"@trendyol/jest-testcontainers": "2.1.1",
|
||||||
"@types/jest": "29.5.3",
|
"@types/jest": "29.5.5",
|
||||||
"@types/jsonwebtoken": "8.5.1",
|
"@types/jsonwebtoken": "8.5.1",
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/koa__router": "8.0.8",
|
"@types/koa__router": "8.0.8",
|
||||||
|
|
|
@ -4660,6 +4660,14 @@
|
||||||
expect "^29.0.0"
|
expect "^29.0.0"
|
||||||
pretty-format "^29.0.0"
|
pretty-format "^29.0.0"
|
||||||
|
|
||||||
|
"@types/jest@29.5.5":
|
||||||
|
version "29.5.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.5.tgz#727204e06228fe24373df9bae76b90f3e8236a2a"
|
||||||
|
integrity sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg==
|
||||||
|
dependencies:
|
||||||
|
expect "^29.0.0"
|
||||||
|
pretty-format "^29.0.0"
|
||||||
|
|
||||||
"@types/json-schema@*", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8":
|
"@types/json-schema@*", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8":
|
||||||
version "7.0.11"
|
version "7.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||||
|
|
Loading…
Reference in New Issue