Merge branch 'master' of github.com:Budibase/budibase into feature/sqs-arm-support
This commit is contained in:
commit
5ea07146cb
|
@ -68,7 +68,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: showDropzone =
|
$: showDropzone =
|
||||||
(!maximum || (maximum && value?.length < maximum)) && !disabled
|
(!maximum || (maximum && (value?.length || 0) < maximum)) && !disabled
|
||||||
|
|
||||||
async function processFileList(fileList) {
|
async function processFileList(fileList) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -9,7 +9,7 @@ const MAX_DEPTH = 1
|
||||||
const TYPES_TO_SKIP = [
|
const TYPES_TO_SKIP = [
|
||||||
FieldType.FORMULA,
|
FieldType.FORMULA,
|
||||||
FieldType.LONGFORM,
|
FieldType.LONGFORM,
|
||||||
FieldType.ATTACHMENT,
|
FieldType.ATTACHMENTS,
|
||||||
//https://github.com/Budibase/budibase/issues/3030
|
//https://github.com/Budibase/budibase/issues/3030
|
||||||
FieldType.INTERNAL,
|
FieldType.INTERNAL,
|
||||||
]
|
]
|
||||||
|
|
|
@ -394,7 +394,8 @@
|
||||||
FIELDS.BIGINT,
|
FIELDS.BIGINT,
|
||||||
FIELDS.BOOLEAN,
|
FIELDS.BOOLEAN,
|
||||||
FIELDS.DATETIME,
|
FIELDS.DATETIME,
|
||||||
FIELDS.ATTACHMENT,
|
FIELDS.ATTACHMENT_SINGLE,
|
||||||
|
FIELDS.ATTACHMENTS,
|
||||||
FIELDS.LINK,
|
FIELDS.LINK,
|
||||||
FIELDS.FORMULA,
|
FIELDS.FORMULA,
|
||||||
FIELDS.JSON,
|
FIELDS.JSON,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { FieldType, FieldSubtype } from "@budibase/types"
|
||||||
import { Select, Toggle, Multiselect } from "@budibase/bbui"
|
import { Select, Toggle, Multiselect } from "@budibase/bbui"
|
||||||
import { DB_TYPE_INTERNAL, FIELDS } from "constants/backend"
|
import { DB_TYPE_INTERNAL } from "constants/backend"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { parseFile } from "./utils"
|
import { parseFile } from "./utils"
|
||||||
|
|
||||||
|
@ -23,43 +24,47 @@
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{
|
{
|
||||||
label: "Text",
|
label: "Text",
|
||||||
value: FIELDS.STRING.type,
|
value: FieldType.STRING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Number",
|
label: "Number",
|
||||||
value: FIELDS.NUMBER.type,
|
value: FieldType.NUMBER,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Date",
|
label: "Date",
|
||||||
value: FIELDS.DATETIME.type,
|
value: FieldType.DATETIME,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Options",
|
label: "Options",
|
||||||
value: FIELDS.OPTIONS.type,
|
value: FieldType.OPTIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Multi-select",
|
label: "Multi-select",
|
||||||
value: FIELDS.ARRAY.type,
|
value: FieldType.ARRAY.type,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Barcode/QR",
|
label: "Barcode/QR",
|
||||||
value: FIELDS.BARCODEQR.type,
|
value: FieldType.BARCODEQR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Long Form Text",
|
label: "Long Form Text",
|
||||||
value: FIELDS.LONGFORM.type,
|
value: FieldType.LONGFORM,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Attachment",
|
label: "Attachment",
|
||||||
value: FIELDS.ATTACHMENT.type,
|
value: FieldType.ATTACHMENT_SINGLE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Attachment list",
|
||||||
|
value: FieldType.ATTACHMENTS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "User",
|
label: "User",
|
||||||
value: `${FIELDS.USER.type}${FIELDS.USER.subtype}`,
|
value: `${FieldType.BB_REFERENCE}${FieldSubtype.USER}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Users",
|
label: "Users",
|
||||||
value: `${FIELDS.USERS.type}${FIELDS.USERS.subtype}`,
|
value: `${FieldType.BB_REFERENCE}${FieldSubtype.USERS}`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ const componentMap = {
|
||||||
"field/longform": FormFieldSelect,
|
"field/longform": FormFieldSelect,
|
||||||
"field/datetime": FormFieldSelect,
|
"field/datetime": FormFieldSelect,
|
||||||
"field/attachment": FormFieldSelect,
|
"field/attachment": FormFieldSelect,
|
||||||
|
"field/attachment_single": FormFieldSelect,
|
||||||
"field/s3": Input,
|
"field/s3": Input,
|
||||||
"field/link": FormFieldSelect,
|
"field/link": FormFieldSelect,
|
||||||
"field/array": FormFieldSelect,
|
"field/array": FormFieldSelect,
|
||||||
|
|
|
@ -41,7 +41,8 @@ export const FieldTypeToComponentMap = {
|
||||||
[FieldType.BOOLEAN]: "booleanfield",
|
[FieldType.BOOLEAN]: "booleanfield",
|
||||||
[FieldType.LONGFORM]: "longformfield",
|
[FieldType.LONGFORM]: "longformfield",
|
||||||
[FieldType.DATETIME]: "datetimefield",
|
[FieldType.DATETIME]: "datetimefield",
|
||||||
[FieldType.ATTACHMENT]: "attachmentfield",
|
[FieldType.ATTACHMENTS]: "attachmentfield",
|
||||||
|
[FieldType.ATTACHMENT_SINGLE]: "attachmentsinglefield",
|
||||||
[FieldType.LINK]: "relationshipfield",
|
[FieldType.LINK]: "relationshipfield",
|
||||||
[FieldType.JSON]: "jsonfield",
|
[FieldType.JSON]: "jsonfield",
|
||||||
[FieldType.BARCODEQR]: "codescanner",
|
[FieldType.BARCODEQR]: "codescanner",
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
$selectedScreen,
|
$selectedScreen,
|
||||||
datasource
|
datasource
|
||||||
)?.table?.primaryDisplay
|
)?.table?.primaryDisplay
|
||||||
$: schema = getSchema(selectedScreen, datasource)
|
$: schema = getSchema($selectedScreen, datasource)
|
||||||
$: columns = getColumns({
|
$: columns = getColumns({
|
||||||
columns: value,
|
columns: value,
|
||||||
schema,
|
schema,
|
||||||
|
|
|
@ -5,6 +5,9 @@ import {
|
||||||
AutoFieldSubType,
|
AutoFieldSubType,
|
||||||
Hosting,
|
Hosting,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
|
const { TypeIconMap } = Constants
|
||||||
|
|
||||||
export { RelationshipType } from "@budibase/types"
|
export { RelationshipType } from "@budibase/types"
|
||||||
|
|
||||||
|
@ -22,7 +25,7 @@ export const FIELDS = {
|
||||||
STRING: {
|
STRING: {
|
||||||
name: "Text",
|
name: "Text",
|
||||||
type: FieldType.STRING,
|
type: FieldType.STRING,
|
||||||
icon: "Text",
|
icon: TypeIconMap[FieldType.STRING],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
length: {},
|
length: {},
|
||||||
|
@ -32,7 +35,7 @@ export const FIELDS = {
|
||||||
BARCODEQR: {
|
BARCODEQR: {
|
||||||
name: "Barcode/QR",
|
name: "Barcode/QR",
|
||||||
type: FieldType.BARCODEQR,
|
type: FieldType.BARCODEQR,
|
||||||
icon: "Camera",
|
icon: TypeIconMap[FieldType.BARCODEQR],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
length: {},
|
length: {},
|
||||||
|
@ -42,7 +45,7 @@ export const FIELDS = {
|
||||||
LONGFORM: {
|
LONGFORM: {
|
||||||
name: "Long Form Text",
|
name: "Long Form Text",
|
||||||
type: FieldType.LONGFORM,
|
type: FieldType.LONGFORM,
|
||||||
icon: "TextAlignLeft",
|
icon: TypeIconMap[FieldType.LONGFORM],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
length: {},
|
length: {},
|
||||||
|
@ -52,7 +55,7 @@ export const FIELDS = {
|
||||||
OPTIONS: {
|
OPTIONS: {
|
||||||
name: "Options",
|
name: "Options",
|
||||||
type: FieldType.OPTIONS,
|
type: FieldType.OPTIONS,
|
||||||
icon: "Dropdown",
|
icon: TypeIconMap[FieldType.OPTIONS],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -62,7 +65,7 @@ export const FIELDS = {
|
||||||
ARRAY: {
|
ARRAY: {
|
||||||
name: "Multi-select",
|
name: "Multi-select",
|
||||||
type: FieldType.ARRAY,
|
type: FieldType.ARRAY,
|
||||||
icon: "Duplicate",
|
icon: TypeIconMap[FieldType.ARRAY],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -72,7 +75,7 @@ export const FIELDS = {
|
||||||
NUMBER: {
|
NUMBER: {
|
||||||
name: "Number",
|
name: "Number",
|
||||||
type: FieldType.NUMBER,
|
type: FieldType.NUMBER,
|
||||||
icon: "123",
|
icon: TypeIconMap[FieldType.NUMBER],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "number",
|
type: "number",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -82,12 +85,12 @@ export const FIELDS = {
|
||||||
BIGINT: {
|
BIGINT: {
|
||||||
name: "BigInt",
|
name: "BigInt",
|
||||||
type: FieldType.BIGINT,
|
type: FieldType.BIGINT,
|
||||||
icon: "TagBold",
|
icon: TypeIconMap[FieldType.BIGINT],
|
||||||
},
|
},
|
||||||
BOOLEAN: {
|
BOOLEAN: {
|
||||||
name: "Boolean",
|
name: "Boolean",
|
||||||
type: FieldType.BOOLEAN,
|
type: FieldType.BOOLEAN,
|
||||||
icon: "Boolean",
|
icon: TypeIconMap[FieldType.BOOLEAN],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -96,7 +99,7 @@ export const FIELDS = {
|
||||||
DATETIME: {
|
DATETIME: {
|
||||||
name: "Date/Time",
|
name: "Date/Time",
|
||||||
type: FieldType.DATETIME,
|
type: FieldType.DATETIME,
|
||||||
icon: "Calendar",
|
icon: TypeIconMap[FieldType.DATETIME],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
length: {},
|
length: {},
|
||||||
|
@ -107,10 +110,18 @@ export const FIELDS = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ATTACHMENT: {
|
ATTACHMENT_SINGLE: {
|
||||||
name: "Attachment",
|
name: "Attachment",
|
||||||
type: FieldType.ATTACHMENT,
|
type: FieldType.ATTACHMENT_SINGLE,
|
||||||
icon: "Folder",
|
icon: TypeIconMap[FieldType.ATTACHMENT_SINGLE],
|
||||||
|
constraints: {
|
||||||
|
presence: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ATTACHMENTS: {
|
||||||
|
name: "Attachment List",
|
||||||
|
type: FieldType.ATTACHMENTS,
|
||||||
|
icon: TypeIconMap[FieldType.ATTACHMENTS],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -119,7 +130,7 @@ export const FIELDS = {
|
||||||
LINK: {
|
LINK: {
|
||||||
name: "Relationship",
|
name: "Relationship",
|
||||||
type: FieldType.LINK,
|
type: FieldType.LINK,
|
||||||
icon: "Link",
|
icon: TypeIconMap[FieldType.LINK],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -128,19 +139,19 @@ export const FIELDS = {
|
||||||
AUTO: {
|
AUTO: {
|
||||||
name: "Auto Column",
|
name: "Auto Column",
|
||||||
type: FieldType.AUTO,
|
type: FieldType.AUTO,
|
||||||
icon: "MagicWand",
|
icon: TypeIconMap[FieldType.AUTO],
|
||||||
constraints: {},
|
constraints: {},
|
||||||
},
|
},
|
||||||
FORMULA: {
|
FORMULA: {
|
||||||
name: "Formula",
|
name: "Formula",
|
||||||
type: FieldType.FORMULA,
|
type: FieldType.FORMULA,
|
||||||
icon: "Calculator",
|
icon: TypeIconMap[FieldType.FORMULA],
|
||||||
constraints: {},
|
constraints: {},
|
||||||
},
|
},
|
||||||
JSON: {
|
JSON: {
|
||||||
name: "JSON",
|
name: "JSON",
|
||||||
type: FieldType.JSON,
|
type: FieldType.JSON,
|
||||||
icon: "Brackets",
|
icon: TypeIconMap[FieldType.JSON],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "object",
|
type: "object",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -150,13 +161,13 @@ export const FIELDS = {
|
||||||
name: "User",
|
name: "User",
|
||||||
type: FieldType.BB_REFERENCE,
|
type: FieldType.BB_REFERENCE,
|
||||||
subtype: FieldSubtype.USER,
|
subtype: FieldSubtype.USER,
|
||||||
icon: "User",
|
icon: TypeIconMap[FieldType.USER],
|
||||||
},
|
},
|
||||||
USERS: {
|
USERS: {
|
||||||
name: "Users",
|
name: "Users",
|
||||||
type: FieldType.BB_REFERENCE,
|
type: FieldType.BB_REFERENCE,
|
||||||
subtype: FieldSubtype.USERS,
|
subtype: FieldSubtype.USERS,
|
||||||
icon: "User",
|
icon: TypeIconMap[FieldType.USERS],
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
},
|
},
|
||||||
|
@ -299,7 +310,7 @@ export const PaginationLocations = [
|
||||||
|
|
||||||
export const BannedSearchTypes = [
|
export const BannedSearchTypes = [
|
||||||
FieldType.LINK,
|
FieldType.LINK,
|
||||||
FieldType.ATTACHMENT,
|
FieldType.ATTACHMENTS,
|
||||||
FieldType.FORMULA,
|
FieldType.FORMULA,
|
||||||
FieldType.JSON,
|
FieldType.JSON,
|
||||||
"jsonarray",
|
"jsonarray",
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
"optionsfield",
|
"optionsfield",
|
||||||
"booleanfield",
|
"booleanfield",
|
||||||
"longformfield",
|
"longformfield",
|
||||||
|
"attachmentsinglefield",
|
||||||
"attachmentfield",
|
"attachmentfield",
|
||||||
"jsonfield",
|
"jsonfield",
|
||||||
"relationshipfield",
|
"relationshipfield",
|
||||||
|
|
|
@ -6,7 +6,10 @@ import { derived } from "svelte/store"
|
||||||
import { integrations } from "stores/builder/integrations"
|
import { integrations } from "stores/builder/integrations"
|
||||||
|
|
||||||
vi.mock("svelte/store", () => ({
|
vi.mock("svelte/store", () => ({
|
||||||
derived: vi.fn(() => {}),
|
derived: vi.fn(),
|
||||||
|
writable: vi.fn(() => ({
|
||||||
|
subscribe: vi.fn(),
|
||||||
|
})),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock("stores/builder/integrations", () => ({ integrations: vi.fn() }))
|
vi.mock("stores/builder/integrations", () => ({ integrations: vi.fn() }))
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": "./tsconfig.build.json",
|
"extends": "./tsconfig.build.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
|
||||||
"declaration": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"assets/*": ["./assets/*"],
|
"assets/*": ["./assets/*"],
|
||||||
|
|
|
@ -4226,7 +4226,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"attachmentfield": {
|
"attachmentfield": {
|
||||||
"name": "Attachment",
|
"name": "Attachment list",
|
||||||
"icon": "Attach",
|
"icon": "Attach",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
"requiredAncestors": ["form"],
|
"requiredAncestors": ["form"],
|
||||||
|
@ -4322,6 +4322,97 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"attachmentsinglefield": {
|
||||||
|
"name": "Single Attachment",
|
||||||
|
"icon": "Attach",
|
||||||
|
"styles": ["size"],
|
||||||
|
"requiredAncestors": ["form"],
|
||||||
|
"editable": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 200
|
||||||
|
},
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field/attachment_single",
|
||||||
|
"label": "Field",
|
||||||
|
"key": "field",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Label",
|
||||||
|
"key": "label"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Extensions",
|
||||||
|
"key": "extensions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On change",
|
||||||
|
"key": "onChange",
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Field Value",
|
||||||
|
"key": "value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Compact",
|
||||||
|
"key": "compact",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Read only",
|
||||||
|
"key": "disabled",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "validation/attachment",
|
||||||
|
"label": "Validation",
|
||||||
|
"key": "validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Layout",
|
||||||
|
"key": "span",
|
||||||
|
"defaultValue": 6,
|
||||||
|
"hidden": true,
|
||||||
|
"showInBar": true,
|
||||||
|
"barStyle": "buttons",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1 column",
|
||||||
|
"value": 6,
|
||||||
|
"barIcon": "Stop",
|
||||||
|
"barTitle": "1 column"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2 columns",
|
||||||
|
"value": 3,
|
||||||
|
"barIcon": "ColumnTwoA",
|
||||||
|
"barTitle": "2 columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3 columns",
|
||||||
|
"value": 2,
|
||||||
|
"barIcon": "ViewColumn",
|
||||||
|
"barTitle": "3 columns"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"relationshipfield": {
|
"relationshipfield": {
|
||||||
"name": "Relationship Picker",
|
"name": "Relationship Picker",
|
||||||
"icon": "TaskList",
|
"icon": "TaskList",
|
||||||
|
@ -6011,7 +6102,7 @@
|
||||||
"block": true,
|
"block": true,
|
||||||
"name": "Repeater Block",
|
"name": "Repeater Block",
|
||||||
"icon": "ViewList",
|
"icon": "ViewList",
|
||||||
"illegalChildren": ["section"],
|
"illegalChildren": ["section", "rowexplorer"],
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"size": {
|
"size": {
|
||||||
"width": 400,
|
"width": 400,
|
||||||
|
|
|
@ -147,7 +147,8 @@
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 410px;
|
min-height: 230px;
|
||||||
|
height: 410px;
|
||||||
}
|
}
|
||||||
div.in-builder :global(*) {
|
div.in-builder :global(*) {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
[FieldType.BOOLEAN]: "booleanfield",
|
[FieldType.BOOLEAN]: "booleanfield",
|
||||||
[FieldType.LONGFORM]: "longformfield",
|
[FieldType.LONGFORM]: "longformfield",
|
||||||
[FieldType.DATETIME]: "datetimefield",
|
[FieldType.DATETIME]: "datetimefield",
|
||||||
[FieldType.ATTACHMENT]: "attachmentfield",
|
[FieldType.ATTACHMENTS]: "attachmentfield",
|
||||||
|
[FieldType.ATTACHMENT_SINGLE]: "attachmentsinglefield",
|
||||||
[FieldType.LINK]: "relationshipfield",
|
[FieldType.LINK]: "relationshipfield",
|
||||||
[FieldType.JSON]: "jsonfield",
|
[FieldType.JSON]: "jsonfield",
|
||||||
[FieldType.BARCODEQR]: "codescanner",
|
[FieldType.BARCODEQR]: "codescanner",
|
||||||
|
@ -60,7 +61,7 @@
|
||||||
|
|
||||||
function getPropsByType(field) {
|
function getPropsByType(field) {
|
||||||
const propsMapByType = {
|
const propsMapByType = {
|
||||||
[FieldType.ATTACHMENT]: (_field, schema) => {
|
[FieldType.ATTACHMENTS]: (_field, schema) => {
|
||||||
return {
|
return {
|
||||||
maximum: schema?.constraints?.length?.maximum,
|
maximum: schema?.constraints?.length?.maximum,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
import { CoreDropzone } from "@budibase/bbui"
|
import { CoreDropzone } from "@budibase/bbui"
|
||||||
|
import { FieldType } from "@budibase/types"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
export let field
|
export let field
|
||||||
|
@ -14,6 +15,12 @@
|
||||||
export let maximum = undefined
|
export let maximum = undefined
|
||||||
export let span
|
export let span
|
||||||
export let helpText = null
|
export let helpText = null
|
||||||
|
export let type = FieldType.ATTACHMENTS
|
||||||
|
export let fieldApiMapper = {
|
||||||
|
get: value => value,
|
||||||
|
set: value => value,
|
||||||
|
}
|
||||||
|
export let defaultValue = []
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -63,9 +70,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChange = e => {
|
const handleChange = e => {
|
||||||
const changed = fieldApi.setValue(e.detail)
|
const value = fieldApiMapper.set(e.detail)
|
||||||
|
const changed = fieldApi.setValue(value)
|
||||||
if (onChange && changed) {
|
if (onChange && changed) {
|
||||||
onChange({ value: e.detail })
|
onChange({ value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -78,14 +86,14 @@
|
||||||
{validation}
|
{validation}
|
||||||
{span}
|
{span}
|
||||||
{helpText}
|
{helpText}
|
||||||
type="attachment"
|
{type}
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
defaultValue={[]}
|
{defaultValue}
|
||||||
>
|
>
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
<CoreDropzone
|
<CoreDropzone
|
||||||
value={fieldState.value}
|
value={fieldApiMapper.get(fieldState.value)}
|
||||||
disabled={fieldState.disabled || fieldState.readonly}
|
disabled={fieldState.disabled || fieldState.readonly}
|
||||||
error={fieldState.error}
|
error={fieldState.error}
|
||||||
on:change={handleChange}
|
on:change={handleChange}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
import { FieldType } from "@budibase/types"
|
||||||
|
import AttachmentField from "./AttachmentField.svelte"
|
||||||
|
|
||||||
|
const fieldApiMapper = {
|
||||||
|
get: value => (!Array.isArray(value) && value ? [value] : value) || [],
|
||||||
|
set: value => value[0] || null,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AttachmentField
|
||||||
|
{...$$restProps}
|
||||||
|
type={FieldType.ATTACHMENT_SINGLE}
|
||||||
|
maximum={1}
|
||||||
|
defaultValue={null}
|
||||||
|
{fieldApiMapper}
|
||||||
|
/>
|
|
@ -9,6 +9,7 @@ export { default as booleanfield } from "./BooleanField.svelte"
|
||||||
export { default as longformfield } from "./LongFormField.svelte"
|
export { default as longformfield } from "./LongFormField.svelte"
|
||||||
export { default as datetimefield } from "./DateTimeField.svelte"
|
export { default as datetimefield } from "./DateTimeField.svelte"
|
||||||
export { default as attachmentfield } from "./AttachmentField.svelte"
|
export { default as attachmentfield } from "./AttachmentField.svelte"
|
||||||
|
export { default as attachmentsinglefield } from "./AttachmentSingleField.svelte"
|
||||||
export { default as relationshipfield } from "./RelationshipField.svelte"
|
export { default as relationshipfield } from "./RelationshipField.svelte"
|
||||||
export { default as passwordfield } from "./PasswordField.svelte"
|
export { default as passwordfield } from "./PasswordField.svelte"
|
||||||
export { default as formstep } from "./FormStep.svelte"
|
export { default as formstep } from "./FormStep.svelte"
|
||||||
|
|
|
@ -192,7 +192,7 @@ const parseType = (value, type) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse attachments, treating no elements as null
|
// Parse attachments, treating no elements as null
|
||||||
if (type === FieldTypes.ATTACHMENT) {
|
if (type === FieldTypes.ATTACHMENTS) {
|
||||||
if (!Array.isArray(value) || !value.length) {
|
if (!Array.isArray(value) || !value.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
export let invertX = false
|
export let invertX = false
|
||||||
export let invertY = false
|
export let invertY = false
|
||||||
export let schema
|
export let schema
|
||||||
|
export let maximum
|
||||||
|
|
||||||
const { API, notifications } = getContext("grid")
|
const { API, notifications } = getContext("grid")
|
||||||
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
|
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
|
||||||
|
@ -98,7 +99,7 @@
|
||||||
{value}
|
{value}
|
||||||
compact
|
compact
|
||||||
on:change={e => onChange(e.detail)}
|
on:change={e => onChange(e.detail)}
|
||||||
maximum={schema.constraints?.length?.maximum}
|
maximum={maximum || schema.constraints?.length?.maximum}
|
||||||
{processFiles}
|
{processFiles}
|
||||||
{deleteAttachments}
|
{deleteAttachments}
|
||||||
{handleFileTooLarge}
|
{handleFileTooLarge}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script>
|
||||||
|
import AttachmentCell from "./AttachmentCell.svelte"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
export let onChange
|
||||||
|
export let api
|
||||||
|
|
||||||
|
$: arrayValue = (!Array.isArray(value) && value ? [value] : value) || []
|
||||||
|
|
||||||
|
$: onFileChange = value => {
|
||||||
|
value = value[0] || null
|
||||||
|
onChange(value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AttachmentCell
|
||||||
|
bind:api
|
||||||
|
{...$$restProps}
|
||||||
|
maximum={1}
|
||||||
|
value={arrayValue}
|
||||||
|
onChange={onFileChange}
|
||||||
|
/>
|
|
@ -11,6 +11,7 @@ import BooleanCell from "../cells/BooleanCell.svelte"
|
||||||
import FormulaCell from "../cells/FormulaCell.svelte"
|
import FormulaCell from "../cells/FormulaCell.svelte"
|
||||||
import JSONCell from "../cells/JSONCell.svelte"
|
import JSONCell from "../cells/JSONCell.svelte"
|
||||||
import AttachmentCell from "../cells/AttachmentCell.svelte"
|
import AttachmentCell from "../cells/AttachmentCell.svelte"
|
||||||
|
import AttachmentSingleCell from "../cells/AttachmentSingleCell.svelte"
|
||||||
import BBReferenceCell from "../cells/BBReferenceCell.svelte"
|
import BBReferenceCell from "../cells/BBReferenceCell.svelte"
|
||||||
|
|
||||||
const TypeComponentMap = {
|
const TypeComponentMap = {
|
||||||
|
@ -22,7 +23,8 @@ const TypeComponentMap = {
|
||||||
[FieldType.ARRAY]: MultiSelectCell,
|
[FieldType.ARRAY]: MultiSelectCell,
|
||||||
[FieldType.NUMBER]: NumberCell,
|
[FieldType.NUMBER]: NumberCell,
|
||||||
[FieldType.BOOLEAN]: BooleanCell,
|
[FieldType.BOOLEAN]: BooleanCell,
|
||||||
[FieldType.ATTACHMENT]: AttachmentCell,
|
[FieldType.ATTACHMENTS]: AttachmentCell,
|
||||||
|
[FieldType.ATTACHMENT_SINGLE]: AttachmentSingleCell,
|
||||||
[FieldType.LINK]: RelationshipCell,
|
[FieldType.LINK]: RelationshipCell,
|
||||||
[FieldType.FORMULA]: FormulaCell,
|
[FieldType.FORMULA]: FormulaCell,
|
||||||
[FieldType.JSON]: JSONCell,
|
[FieldType.JSON]: JSONCell,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FieldType, FieldTypeSubtypes } from "@budibase/types"
|
import { TypeIconMap } from "../../../constants"
|
||||||
|
|
||||||
export const getColor = (idx, opacity = 0.3) => {
|
export const getColor = (idx, opacity = 0.3) => {
|
||||||
if (idx == null || idx === -1) {
|
if (idx == null || idx === -1) {
|
||||||
|
@ -7,26 +7,6 @@ export const getColor = (idx, opacity = 0.3) => {
|
||||||
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
|
return `hsla(${((idx + 1) * 222) % 360}, 90%, 75%, ${opacity})`
|
||||||
}
|
}
|
||||||
|
|
||||||
const TypeIconMap = {
|
|
||||||
[FieldType.STRING]: "Text",
|
|
||||||
[FieldType.OPTIONS]: "Dropdown",
|
|
||||||
[FieldType.DATETIME]: "Date",
|
|
||||||
[FieldType.BARCODEQR]: "Camera",
|
|
||||||
[FieldType.LONGFORM]: "TextAlignLeft",
|
|
||||||
[FieldType.ARRAY]: "Dropdown",
|
|
||||||
[FieldType.NUMBER]: "123",
|
|
||||||
[FieldType.BOOLEAN]: "Boolean",
|
|
||||||
[FieldType.ATTACHMENT]: "AppleFiles",
|
|
||||||
[FieldType.LINK]: "DataCorrelated",
|
|
||||||
[FieldType.FORMULA]: "Calculator",
|
|
||||||
[FieldType.JSON]: "Brackets",
|
|
||||||
[FieldType.BIGINT]: "TagBold",
|
|
||||||
[FieldType.BB_REFERENCE]: {
|
|
||||||
[FieldTypeSubtypes.BB_REFERENCE.USER]: "User",
|
|
||||||
[FieldTypeSubtypes.BB_REFERENCE.USERS]: "UserGroup",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getColumnIcon = column => {
|
export const getColumnIcon = column => {
|
||||||
if (column.schema.autocolumn) {
|
if (column.schema.autocolumn) {
|
||||||
return "MagicWand"
|
return "MagicWand"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core"
|
export { OperatorOptions, SqlNumberTypeRangeMap } from "@budibase/shared-core"
|
||||||
export { Feature as Features } from "@budibase/types"
|
export { Feature as Features } from "@budibase/types"
|
||||||
import { BpmCorrelationKey } from "@budibase/shared-core"
|
import { BpmCorrelationKey } from "@budibase/shared-core"
|
||||||
|
import { FieldType, FieldTypeSubtypes } from "@budibase/types"
|
||||||
|
|
||||||
// Cookie names
|
// Cookie names
|
||||||
export const Cookies = {
|
export const Cookies = {
|
||||||
|
@ -113,3 +114,27 @@ export const ContextScopes = {
|
||||||
Local: "local",
|
Local: "local",
|
||||||
Global: "global",
|
Global: "global",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TypeIconMap = {
|
||||||
|
[FieldType.STRING]: "Text",
|
||||||
|
[FieldType.OPTIONS]: "Dropdown",
|
||||||
|
[FieldType.DATETIME]: "Calendar",
|
||||||
|
[FieldType.BARCODEQR]: "Camera",
|
||||||
|
[FieldType.LONGFORM]: "TextAlignLeft",
|
||||||
|
[FieldType.ARRAY]: "Duplicate",
|
||||||
|
[FieldType.NUMBER]: "123",
|
||||||
|
[FieldType.BOOLEAN]: "Boolean",
|
||||||
|
[FieldType.ATTACHMENTS]: "Attach",
|
||||||
|
[FieldType.ATTACHMENT_SINGLE]: "Attach",
|
||||||
|
[FieldType.LINK]: "DataCorrelated",
|
||||||
|
[FieldType.FORMULA]: "Calculator",
|
||||||
|
[FieldType.JSON]: "Brackets",
|
||||||
|
[FieldType.BIGINT]: "TagBold",
|
||||||
|
[FieldType.AUTO]: "MagicWand",
|
||||||
|
[FieldType.USER]: "User",
|
||||||
|
[FieldType.USERS]: "UserGroup",
|
||||||
|
[FieldType.BB_REFERENCE]: {
|
||||||
|
[FieldTypeSubtypes.BB_REFERENCE.USER]: "User",
|
||||||
|
[FieldTypeSubtypes.BB_REFERENCE.USERS]: "UserGroup",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit f8e8f87bd52081e1303a5ae92c432ea5b38f3bb4
|
Subproject commit ef186d00241f96037f9fd34d7a3826041977ab3a
|
|
@ -30,8 +30,6 @@ import {
|
||||||
View,
|
View,
|
||||||
RelationshipFieldMetadata,
|
RelationshipFieldMetadata,
|
||||||
FieldType,
|
FieldType,
|
||||||
FieldTypeSubtypes,
|
|
||||||
AttachmentFieldMetadata,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
|
@ -93,26 +91,6 @@ export async function checkForColumnUpdates(
|
||||||
await checkForViewUpdates(updatedTable, deletedColumns, columnRename)
|
await checkForViewUpdates(updatedTable, deletedColumns, columnRename)
|
||||||
}
|
}
|
||||||
|
|
||||||
const changedAttachmentSubtypeColumns = Object.values(
|
|
||||||
updatedTable.schema
|
|
||||||
).filter(
|
|
||||||
(column): column is AttachmentFieldMetadata =>
|
|
||||||
column.type === FieldType.ATTACHMENT &&
|
|
||||||
column.subtype !== oldTable?.schema[column.name]?.subtype
|
|
||||||
)
|
|
||||||
for (const attachmentColumn of changedAttachmentSubtypeColumns) {
|
|
||||||
if (attachmentColumn.subtype === FieldTypeSubtypes.ATTACHMENT.SINGLE) {
|
|
||||||
attachmentColumn.constraints ??= { length: {} }
|
|
||||||
attachmentColumn.constraints.length ??= {}
|
|
||||||
attachmentColumn.constraints.length.maximum = 1
|
|
||||||
attachmentColumn.constraints.length.message =
|
|
||||||
"cannot contain multiple files"
|
|
||||||
} else {
|
|
||||||
delete attachmentColumn.constraints?.length?.maximum
|
|
||||||
delete attachmentColumn.constraints?.length?.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { rows: updatedRows, table: updatedTable }
|
return { rows: updatedRows, table: updatedTable }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,17 @@ import * as setup from "./utilities"
|
||||||
import { context, InternalTable, tenancy } from "@budibase/backend-core"
|
import { context, InternalTable, tenancy } from "@budibase/backend-core"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
|
AttachmentFieldMetadata,
|
||||||
AutoFieldSubType,
|
AutoFieldSubType,
|
||||||
Datasource,
|
Datasource,
|
||||||
|
DateFieldMetadata,
|
||||||
DeleteRow,
|
DeleteRow,
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
FieldType,
|
FieldType,
|
||||||
FieldTypeSubtypes,
|
FieldTypeSubtypes,
|
||||||
FormulaType,
|
FormulaType,
|
||||||
INTERNAL_TABLE_SOURCE_ID,
|
INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
NumberFieldMetadata,
|
||||||
QuotaUsageType,
|
QuotaUsageType,
|
||||||
RelationshipType,
|
RelationshipType,
|
||||||
Row,
|
Row,
|
||||||
|
@ -232,9 +235,14 @@ describe.each([
|
||||||
name: "str",
|
name: "str",
|
||||||
constraints: { type: "string", presence: false },
|
constraints: { type: "string", presence: false },
|
||||||
}
|
}
|
||||||
const attachment: FieldSchema = {
|
const singleAttachment: FieldSchema = {
|
||||||
type: FieldType.ATTACHMENT,
|
type: FieldType.ATTACHMENT_SINGLE,
|
||||||
name: "attachment",
|
name: "single attachment",
|
||||||
|
constraints: { presence: false },
|
||||||
|
}
|
||||||
|
const attachmentList: AttachmentFieldMetadata = {
|
||||||
|
type: FieldType.ATTACHMENTS,
|
||||||
|
name: "attachments",
|
||||||
constraints: { type: "array", presence: false },
|
constraints: { type: "array", presence: false },
|
||||||
}
|
}
|
||||||
const bool: FieldSchema = {
|
const bool: FieldSchema = {
|
||||||
|
@ -242,12 +250,12 @@ describe.each([
|
||||||
name: "boolean",
|
name: "boolean",
|
||||||
constraints: { type: "boolean", presence: false },
|
constraints: { type: "boolean", presence: false },
|
||||||
}
|
}
|
||||||
const number: FieldSchema = {
|
const number: NumberFieldMetadata = {
|
||||||
type: FieldType.NUMBER,
|
type: FieldType.NUMBER,
|
||||||
name: "str",
|
name: "str",
|
||||||
constraints: { type: "number", presence: false },
|
constraints: { type: "number", presence: false },
|
||||||
}
|
}
|
||||||
const datetime: FieldSchema = {
|
const datetime: DateFieldMetadata = {
|
||||||
type: FieldType.DATETIME,
|
type: FieldType.DATETIME,
|
||||||
name: "datetime",
|
name: "datetime",
|
||||||
constraints: {
|
constraints: {
|
||||||
|
@ -297,10 +305,12 @@ describe.each([
|
||||||
boolUndefined: bool,
|
boolUndefined: bool,
|
||||||
boolString: bool,
|
boolString: bool,
|
||||||
boolBool: bool,
|
boolBool: bool,
|
||||||
attachmentNull: attachment,
|
singleAttachmentNull: singleAttachment,
|
||||||
attachmentUndefined: attachment,
|
singleAttachmentUndefined: singleAttachment,
|
||||||
attachmentEmpty: attachment,
|
attachmentListNull: attachmentList,
|
||||||
attachmentEmptyArrayStr: attachment,
|
attachmentListUndefined: attachmentList,
|
||||||
|
attachmentListEmpty: attachmentList,
|
||||||
|
attachmentListEmptyArrayStr: attachmentList,
|
||||||
arrayFieldEmptyArrayStr: arrayField,
|
arrayFieldEmptyArrayStr: arrayField,
|
||||||
arrayFieldArrayStrKnown: arrayField,
|
arrayFieldArrayStrKnown: arrayField,
|
||||||
arrayFieldNull: arrayField,
|
arrayFieldNull: arrayField,
|
||||||
|
@ -336,10 +346,12 @@ describe.each([
|
||||||
boolString: "true",
|
boolString: "true",
|
||||||
boolBool: true,
|
boolBool: true,
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
attachmentNull: null,
|
singleAttachmentNull: null,
|
||||||
attachmentUndefined: undefined,
|
singleAttachmentUndefined: undefined,
|
||||||
attachmentEmpty: "",
|
attachmentListNull: null,
|
||||||
attachmentEmptyArrayStr: "[]",
|
attachmentListUndefined: undefined,
|
||||||
|
attachmentListEmpty: "",
|
||||||
|
attachmentListEmptyArrayStr: "[]",
|
||||||
arrayFieldEmptyArrayStr: "[]",
|
arrayFieldEmptyArrayStr: "[]",
|
||||||
arrayFieldUndefined: undefined,
|
arrayFieldUndefined: undefined,
|
||||||
arrayFieldNull: null,
|
arrayFieldNull: null,
|
||||||
|
@ -368,10 +380,12 @@ describe.each([
|
||||||
expect(row.boolUndefined).toBe(undefined)
|
expect(row.boolUndefined).toBe(undefined)
|
||||||
expect(row.boolString).toBe(true)
|
expect(row.boolString).toBe(true)
|
||||||
expect(row.boolBool).toBe(true)
|
expect(row.boolBool).toBe(true)
|
||||||
expect(row.attachmentNull).toEqual([])
|
expect(row.singleAttachmentNull).toEqual(null)
|
||||||
expect(row.attachmentUndefined).toBe(undefined)
|
expect(row.singleAttachmentUndefined).toBe(undefined)
|
||||||
expect(row.attachmentEmpty).toEqual([])
|
expect(row.attachmentListNull).toEqual([])
|
||||||
expect(row.attachmentEmptyArrayStr).toEqual([])
|
expect(row.attachmentListUndefined).toBe(undefined)
|
||||||
|
expect(row.attachmentListEmpty).toEqual([])
|
||||||
|
expect(row.attachmentListEmptyArrayStr).toEqual([])
|
||||||
expect(row.arrayFieldEmptyArrayStr).toEqual([])
|
expect(row.arrayFieldEmptyArrayStr).toEqual([])
|
||||||
expect(row.arrayFieldNull).toEqual([])
|
expect(row.arrayFieldNull).toEqual([])
|
||||||
expect(row.arrayFieldUndefined).toEqual(undefined)
|
expect(row.arrayFieldUndefined).toEqual(undefined)
|
||||||
|
@ -817,12 +831,44 @@ describe.each([
|
||||||
|
|
||||||
isInternal &&
|
isInternal &&
|
||||||
describe("attachments", () => {
|
describe("attachments", () => {
|
||||||
it("should allow enriching attachment rows", async () => {
|
it("should allow enriching single attachment rows", async () => {
|
||||||
const table = await config.api.table.save(
|
const table = await config.api.table.save(
|
||||||
defaultTable({
|
defaultTable({
|
||||||
schema: {
|
schema: {
|
||||||
attachment: {
|
attachment: {
|
||||||
type: FieldType.ATTACHMENT,
|
type: FieldType.ATTACHMENT_SINGLE,
|
||||||
|
name: "attachment",
|
||||||
|
constraints: { presence: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const attachmentId = `${uuid.v4()}.csv`
|
||||||
|
const row = await config.api.row.save(table._id!, {
|
||||||
|
name: "test",
|
||||||
|
description: "test",
|
||||||
|
attachment: {
|
||||||
|
key: `${config.getAppId()}/attachments/${attachmentId}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
tableId: table._id,
|
||||||
|
})
|
||||||
|
await config.withEnv({ SELF_HOSTED: "true" }, async () => {
|
||||||
|
return context.doInAppContext(config.getAppId(), async () => {
|
||||||
|
const enriched = await outputProcessing(table, [row])
|
||||||
|
expect((enriched as Row[])[0].attachment.url).toBe(
|
||||||
|
`/files/signed/prod-budi-app-assets/${config.getProdAppId()}/attachments/${attachmentId}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should allow enriching attachment list rows", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
defaultTable({
|
||||||
|
schema: {
|
||||||
|
attachment: {
|
||||||
|
type: FieldType.ATTACHMENTS,
|
||||||
name: "attachment",
|
name: "attachment",
|
||||||
constraints: { type: "array", presence: false },
|
constraints: { type: "array", presence: false },
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,7 +2,13 @@ import { tableForDatasource } from "../../../tests/utilities/structures"
|
||||||
import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
|
import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
|
||||||
|
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
import { Datasource, FieldType, Table } from "@budibase/types"
|
import {
|
||||||
|
Datasource,
|
||||||
|
EmptyFilterOption,
|
||||||
|
FieldType,
|
||||||
|
SearchFilters,
|
||||||
|
Table,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
jest.unmock("mssql")
|
jest.unmock("mssql")
|
||||||
|
|
||||||
|
@ -40,35 +46,237 @@ describe.each([
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
describe("strings", () => {
|
||||||
table = await config.api.table.save(
|
beforeEach(async () => {
|
||||||
tableForDatasource(datasource, {
|
table = await config.api.table.save(
|
||||||
schema: {
|
tableForDatasource(datasource, {
|
||||||
name: {
|
schema: {
|
||||||
name: "name",
|
name: {
|
||||||
type: FieldType.STRING,
|
name: "name",
|
||||||
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
})
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const rows = [{ name: "foo" }, { name: "bar" }]
|
||||||
|
|
||||||
|
interface StringSearchTest {
|
||||||
|
query: SearchFilters
|
||||||
|
expected: (typeof rows)[number][]
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringSearchTests: StringSearchTest[] = [
|
||||||
|
{ query: {}, expected: rows },
|
||||||
|
{
|
||||||
|
query: { onEmptyFilter: EmptyFilterOption.RETURN_ALL },
|
||||||
|
expected: rows,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: { onEmptyFilter: EmptyFilterOption.RETURN_NONE },
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
{ query: { string: { name: "foo" } }, expected: [rows[0]] },
|
||||||
|
{ query: { string: { name: "none" } }, expected: [] },
|
||||||
|
{ query: { fuzzy: { name: "oo" } }, expected: [rows[0]] },
|
||||||
|
{ query: { equal: { name: "foo" } }, expected: [rows[0]] },
|
||||||
|
{ query: { notEqual: { name: "foo" } }, expected: [rows[1]] },
|
||||||
|
{ query: { oneOf: { name: ["foo"] } }, expected: [rows[0]] },
|
||||||
|
// { query: { contains: { name: "f" } }, expected: [0] },
|
||||||
|
// { query: { notContains: { name: ["f"] } }, expected: [1] },
|
||||||
|
// { query: { containsAny: { name: ["f"] } }, expected: [0] },
|
||||||
|
]
|
||||||
|
|
||||||
|
it.each(stringSearchTests)(
|
||||||
|
`should be able to run query: $query`,
|
||||||
|
async ({ query, expected }) => {
|
||||||
|
const savedRows = await Promise.all(
|
||||||
|
rows.map(r => config.api.row.save(table._id!, r))
|
||||||
|
)
|
||||||
|
const { rows: foundRows } = await config.api.row.search(table._id!, {
|
||||||
|
tableId: table._id!,
|
||||||
|
query,
|
||||||
|
})
|
||||||
|
expect(foundRows).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
expected.map(r =>
|
||||||
|
expect.objectContaining(savedRows.find(sr => sr.name === r.name)!)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return rows", async () => {
|
describe("number", () => {
|
||||||
const rows = await Promise.all([
|
beforeEach(async () => {
|
||||||
config.api.row.save(table._id!, { name: "foo" }),
|
table = await config.api.table.save(
|
||||||
config.api.row.save(table._id!, { name: "bar" }),
|
tableForDatasource(datasource, {
|
||||||
])
|
schema: {
|
||||||
|
age: {
|
||||||
const result = await config.api.row.search(table._id!, {
|
name: "age",
|
||||||
tableId: table._id!,
|
type: FieldType.NUMBER,
|
||||||
query: {},
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.rows).toEqual(
|
const rows = [{ age: 1 }, { age: 10 }]
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({ _id: rows[0]._id }),
|
interface NumberSearchTest {
|
||||||
expect.objectContaining({ _id: rows[1]._id }),
|
query: SearchFilters
|
||||||
])
|
expected: (typeof rows)[number][]
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberSearchTests: NumberSearchTest[] = [
|
||||||
|
{ query: {}, expected: rows },
|
||||||
|
{
|
||||||
|
query: { onEmptyFilter: EmptyFilterOption.RETURN_ALL },
|
||||||
|
expected: rows,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: { onEmptyFilter: EmptyFilterOption.RETURN_NONE },
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
{ query: { equal: { age: 1 } }, expected: [rows[0]] },
|
||||||
|
{ query: { equal: { age: 2 } }, expected: [] },
|
||||||
|
{ query: { notEqual: { age: 1 } }, expected: [rows[1]] },
|
||||||
|
{ query: { oneOf: { age: [1] } }, expected: [rows[0]] },
|
||||||
|
{ query: { range: { age: { low: 1, high: 5 } } }, expected: [rows[0]] },
|
||||||
|
{ query: { range: { age: { low: 0, high: 1 } } }, expected: [rows[0]] },
|
||||||
|
{ query: { range: { age: { low: 3, high: 4 } } }, expected: [] },
|
||||||
|
{ query: { range: { age: { low: 0, high: 11 } } }, expected: rows },
|
||||||
|
]
|
||||||
|
|
||||||
|
it.each(numberSearchTests)(
|
||||||
|
`should be able to run query: $query`,
|
||||||
|
async ({ query, expected }) => {
|
||||||
|
const savedRows = await Promise.all(
|
||||||
|
rows.map(r => config.api.row.save(table._id!, r))
|
||||||
|
)
|
||||||
|
const { rows: foundRows } = await config.api.row.search(table._id!, {
|
||||||
|
tableId: table._id!,
|
||||||
|
query,
|
||||||
|
})
|
||||||
|
expect(foundRows).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
expected.map(r =>
|
||||||
|
expect.objectContaining(savedRows.find(sr => sr.age === r.age)!)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("dates", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
table = await config.api.table.save(
|
||||||
|
tableForDatasource(datasource, {
|
||||||
|
schema: {
|
||||||
|
dob: {
|
||||||
|
name: "dob",
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const rows = [
|
||||||
|
{ dob: new Date("2020-01-01") },
|
||||||
|
{ dob: new Date("2020-01-10") },
|
||||||
|
]
|
||||||
|
|
||||||
|
interface DateSearchTest {
|
||||||
|
query: SearchFilters
|
||||||
|
expected: (typeof rows)[number][]
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateSearchTests: DateSearchTest[] = [
|
||||||
|
{ query: {}, expected: rows },
|
||||||
|
{
|
||||||
|
query: { onEmptyFilter: EmptyFilterOption.RETURN_ALL },
|
||||||
|
expected: rows,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: { onEmptyFilter: EmptyFilterOption.RETURN_NONE },
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: { equal: { dob: new Date("2020-01-01") } },
|
||||||
|
expected: [rows[0]],
|
||||||
|
},
|
||||||
|
{ query: { equal: { dob: new Date("2020-01-02") } }, expected: [] },
|
||||||
|
{
|
||||||
|
query: { notEqual: { dob: new Date("2020-01-01") } },
|
||||||
|
expected: [rows[1]],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: { oneOf: { dob: [new Date("2020-01-01")] } },
|
||||||
|
expected: [rows[0]],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
range: {
|
||||||
|
dob: {
|
||||||
|
low: new Date("2020-01-01").toISOString(),
|
||||||
|
high: new Date("2020-01-05").toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: [rows[0]],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
range: {
|
||||||
|
dob: {
|
||||||
|
low: new Date("2020-01-01").toISOString(),
|
||||||
|
high: new Date("2020-01-10").toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: rows,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
range: {
|
||||||
|
dob: {
|
||||||
|
low: new Date("2020-01-05").toISOString(),
|
||||||
|
high: new Date("2020-01-10").toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: [rows[1]],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
it.each(dateSearchTests)(
|
||||||
|
`should be able to run query: $query`,
|
||||||
|
async ({ query, expected }) => {
|
||||||
|
// TODO(samwho): most of these work for SQS, but not all. Fix 'em.
|
||||||
|
if (isSqs) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const savedRows = await Promise.all(
|
||||||
|
rows.map(r => config.api.row.save(table._id!, r))
|
||||||
|
)
|
||||||
|
const { rows: foundRows } = await config.api.row.search(table._id!, {
|
||||||
|
tableId: table._id!,
|
||||||
|
query,
|
||||||
|
})
|
||||||
|
expect(foundRows).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
expected.map(r =>
|
||||||
|
expect.objectContaining(
|
||||||
|
savedRows.find(sr => sr.dob === r.dob.toISOString())!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -299,7 +299,7 @@ export const DEFAULT_EMPLOYEE_TABLE_SCHEMA: Table = {
|
||||||
sortable: false,
|
sortable: false,
|
||||||
},
|
},
|
||||||
"Badge Photo": {
|
"Badge Photo": {
|
||||||
type: FieldType.ATTACHMENT,
|
type: FieldType.ATTACHMENTS,
|
||||||
constraints: {
|
constraints: {
|
||||||
type: FieldType.ARRAY,
|
type: FieldType.ARRAY,
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -607,7 +607,7 @@ export const DEFAULT_EXPENSES_TABLE_SCHEMA: Table = {
|
||||||
ignoreTimezones: true,
|
ignoreTimezones: true,
|
||||||
},
|
},
|
||||||
Attachment: {
|
Attachment: {
|
||||||
type: FieldType.ATTACHMENT,
|
type: FieldType.ATTACHMENTS,
|
||||||
constraints: {
|
constraints: {
|
||||||
type: FieldType.ARRAY,
|
type: FieldType.ARRAY,
|
||||||
presence: false,
|
presence: false,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
Automation,
|
Automation,
|
||||||
AutomationTriggerStepId,
|
AutomationTriggerStepId,
|
||||||
RowAttachment,
|
RowAttachment,
|
||||||
|
FieldType,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { getAutomationParams } from "../../../db/utils"
|
import { getAutomationParams } from "../../../db/utils"
|
||||||
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
||||||
|
@ -58,10 +59,19 @@ export async function updateAttachmentColumns(prodAppId: string, db: Database) {
|
||||||
updatedRows = updatedRows.concat(
|
updatedRows = updatedRows.concat(
|
||||||
rows.map(row => {
|
rows.map(row => {
|
||||||
for (let column of columns) {
|
for (let column of columns) {
|
||||||
if (Array.isArray(row[column])) {
|
const columnType = table.schema[column].type
|
||||||
|
if (
|
||||||
|
columnType === FieldType.ATTACHMENTS &&
|
||||||
|
Array.isArray(row[column])
|
||||||
|
) {
|
||||||
row[column] = row[column].map((attachment: RowAttachment) =>
|
row[column] = row[column].map((attachment: RowAttachment) =>
|
||||||
rewriteAttachmentUrl(prodAppId, attachment)
|
rewriteAttachmentUrl(prodAppId, attachment)
|
||||||
)
|
)
|
||||||
|
} else if (
|
||||||
|
columnType === FieldType.ATTACHMENT_SINGLE &&
|
||||||
|
row[column]
|
||||||
|
) {
|
||||||
|
row[column] = rewriteAttachmentUrl(prodAppId, row[column])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return row
|
return row
|
||||||
|
|
|
@ -30,7 +30,10 @@ export async function getRowsWithAttachments(appId: string, table: Table) {
|
||||||
const db = dbCore.getDB(appId)
|
const db = dbCore.getDB(appId)
|
||||||
const attachmentCols: string[] = []
|
const attachmentCols: string[] = []
|
||||||
for (let [key, column] of Object.entries(table.schema)) {
|
for (let [key, column] of Object.entries(table.schema)) {
|
||||||
if (column.type === FieldType.ATTACHMENT) {
|
if (
|
||||||
|
column.type === FieldType.ATTACHMENTS ||
|
||||||
|
column.type === FieldType.ATTACHMENT_SINGLE
|
||||||
|
) {
|
||||||
attachmentCols.push(key)
|
attachmentCols.push(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,13 +175,13 @@ export async function validate({
|
||||||
errors[fieldName] = [`${fieldName} is required`]
|
errors[fieldName] = [`${fieldName} is required`]
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
(type === FieldType.ATTACHMENT || type === FieldType.JSON) &&
|
(type === FieldType.ATTACHMENTS || type === FieldType.JSON) &&
|
||||||
typeof row[fieldName] === "string"
|
typeof row[fieldName] === "string"
|
||||||
) {
|
) {
|
||||||
// this should only happen if there is an error
|
// this should only happen if there is an error
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(row[fieldName])
|
const json = JSON.parse(row[fieldName])
|
||||||
if (type === FieldType.ATTACHMENT) {
|
if (type === FieldType.ATTACHMENTS) {
|
||||||
if (Array.isArray(json)) {
|
if (Array.isArray(json)) {
|
||||||
row[fieldName] = json
|
row[fieldName] = json
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,7 +27,8 @@ const FieldTypeMap: Record<FieldType, SQLiteType> = {
|
||||||
[FieldType.JSON]: SQLiteType.BLOB,
|
[FieldType.JSON]: SQLiteType.BLOB,
|
||||||
[FieldType.INTERNAL]: SQLiteType.BLOB,
|
[FieldType.INTERNAL]: SQLiteType.BLOB,
|
||||||
[FieldType.BARCODEQR]: SQLiteType.BLOB,
|
[FieldType.BARCODEQR]: SQLiteType.BLOB,
|
||||||
[FieldType.ATTACHMENT]: SQLiteType.BLOB,
|
[FieldType.ATTACHMENTS]: SQLiteType.BLOB,
|
||||||
|
[FieldType.ATTACHMENT_SINGLE]: SQLiteType.BLOB,
|
||||||
[FieldType.ARRAY]: SQLiteType.BLOB,
|
[FieldType.ARRAY]: SQLiteType.BLOB,
|
||||||
[FieldType.LINK]: SQLiteType.BLOB,
|
[FieldType.LINK]: SQLiteType.BLOB,
|
||||||
[FieldType.BIGINT]: SQLiteType.REAL,
|
[FieldType.BIGINT]: SQLiteType.REAL,
|
||||||
|
|
|
@ -31,9 +31,13 @@ describe("should be able to re-write attachment URLs", () => {
|
||||||
sourceType: TableSourceType.INTERNAL,
|
sourceType: TableSourceType.INTERNAL,
|
||||||
schema: {
|
schema: {
|
||||||
photo: {
|
photo: {
|
||||||
type: FieldType.ATTACHMENT,
|
type: FieldType.ATTACHMENT_SINGLE,
|
||||||
name: "photo",
|
name: "photo",
|
||||||
},
|
},
|
||||||
|
gallery: {
|
||||||
|
type: FieldType.ATTACHMENTS,
|
||||||
|
name: "gallery",
|
||||||
|
},
|
||||||
otherCol: {
|
otherCol: {
|
||||||
type: FieldType.STRING,
|
type: FieldType.STRING,
|
||||||
name: "otherCol",
|
name: "otherCol",
|
||||||
|
@ -43,7 +47,8 @@ describe("should be able to re-write attachment URLs", () => {
|
||||||
|
|
||||||
for (let i = 0; i < FIND_LIMIT * 4; i++) {
|
for (let i = 0; i < FIND_LIMIT * 4; i++) {
|
||||||
await config.api.row.save(table._id!, {
|
await config.api.row.save(table._id!, {
|
||||||
photo: [attachment],
|
photo: { ...attachment },
|
||||||
|
gallery: [{ ...attachment }, { ...attachment }],
|
||||||
otherCol: "string",
|
otherCol: "string",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -56,8 +61,12 @@ describe("should be able to re-write attachment URLs", () => {
|
||||||
)
|
)
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
expect(row.otherCol).toBe("string")
|
expect(row.otherCol).toBe("string")
|
||||||
expect(row.photo[0].url).toBe("")
|
expect(row.photo.url).toBe("")
|
||||||
expect(row.photo[0].key).toBe(`${db.name}/attachments/a.png`)
|
expect(row.photo.key).toBe(`${db.name}/attachments/a.png`)
|
||||||
|
expect(row.gallery[0].url).toBe("")
|
||||||
|
expect(row.gallery[0].key).toBe(`${db.name}/attachments/a.png`)
|
||||||
|
expect(row.gallery[1].url).toBe("")
|
||||||
|
expect(row.gallery[1].key).toBe(`${db.name}/attachments/a.png`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import { ObjectStoreBuckets } from "../../constants"
|
import { ObjectStoreBuckets } from "../../constants"
|
||||||
import { context, db as dbCore, objectStore } from "@budibase/backend-core"
|
import { context, db as dbCore, objectStore } from "@budibase/backend-core"
|
||||||
import {
|
import { FieldType, RenameColumn, Row, Table } from "@budibase/types"
|
||||||
FieldType,
|
|
||||||
RenameColumn,
|
|
||||||
Row,
|
|
||||||
RowAttachment,
|
|
||||||
Table,
|
|
||||||
} from "@budibase/types"
|
|
||||||
|
|
||||||
export class AttachmentCleanup {
|
export class AttachmentCleanup {
|
||||||
static async coreCleanup(fileListFn: () => string[]): Promise<void> {
|
static async coreCleanup(fileListFn: () => string[]): Promise<void> {
|
||||||
|
@ -25,6 +19,27 @@ export class AttachmentCleanup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static extractAttachmentKeys(
|
||||||
|
type: FieldType,
|
||||||
|
rowData: any
|
||||||
|
): string[] {
|
||||||
|
if (
|
||||||
|
type !== FieldType.ATTACHMENTS &&
|
||||||
|
type !== FieldType.ATTACHMENT_SINGLE
|
||||||
|
) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rowData) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === FieldType.ATTACHMENTS) {
|
||||||
|
return rowData.map((attachment: any) => attachment.key)
|
||||||
|
}
|
||||||
|
return [rowData.key]
|
||||||
|
}
|
||||||
|
|
||||||
private static async tableChange(
|
private static async tableChange(
|
||||||
table: Table,
|
table: Table,
|
||||||
rows: Row[],
|
rows: Row[],
|
||||||
|
@ -34,16 +49,20 @@ export class AttachmentCleanup {
|
||||||
let files: string[] = []
|
let files: string[] = []
|
||||||
const tableSchema = opts.oldTable?.schema || table.schema
|
const tableSchema = opts.oldTable?.schema || table.schema
|
||||||
for (let [key, schema] of Object.entries(tableSchema)) {
|
for (let [key, schema] of Object.entries(tableSchema)) {
|
||||||
if (schema.type !== FieldType.ATTACHMENT) {
|
if (
|
||||||
|
schema.type !== FieldType.ATTACHMENTS &&
|
||||||
|
schema.type !== FieldType.ATTACHMENT_SINGLE
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnRemoved = opts.oldTable && !table.schema[key]
|
const columnRemoved = opts.oldTable && !table.schema[key]
|
||||||
const renaming = opts.rename?.old === key
|
const renaming = opts.rename?.old === key
|
||||||
// old table had this column, new table doesn't - delete it
|
// old table had this column, new table doesn't - delete it
|
||||||
if ((columnRemoved && !renaming) || opts.deleting) {
|
if ((columnRemoved && !renaming) || opts.deleting) {
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
files = files.concat(
|
files = files.concat(
|
||||||
(row[key] || []).map((attachment: any) => attachment.key)
|
AttachmentCleanup.extractAttachmentKeys(schema.type, row[key])
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -68,15 +87,15 @@ export class AttachmentCleanup {
|
||||||
return AttachmentCleanup.coreCleanup(() => {
|
return AttachmentCleanup.coreCleanup(() => {
|
||||||
let files: string[] = []
|
let files: string[] = []
|
||||||
for (let [key, schema] of Object.entries(table.schema)) {
|
for (let [key, schema] of Object.entries(table.schema)) {
|
||||||
if (schema.type !== FieldType.ATTACHMENT) {
|
if (
|
||||||
|
schema.type !== FieldType.ATTACHMENTS &&
|
||||||
|
schema.type !== FieldType.ATTACHMENT_SINGLE
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
if (!Array.isArray(row[key])) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
files = files.concat(
|
files = files.concat(
|
||||||
row[key].map((attachment: any) => attachment.key)
|
AttachmentCleanup.extractAttachmentKeys(schema.type, row[key])
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -88,16 +107,21 @@ export class AttachmentCleanup {
|
||||||
return AttachmentCleanup.coreCleanup(() => {
|
return AttachmentCleanup.coreCleanup(() => {
|
||||||
let files: string[] = []
|
let files: string[] = []
|
||||||
for (let [key, schema] of Object.entries(table.schema)) {
|
for (let [key, schema] of Object.entries(table.schema)) {
|
||||||
if (schema.type !== FieldType.ATTACHMENT) {
|
if (
|
||||||
|
schema.type !== FieldType.ATTACHMENTS &&
|
||||||
|
schema.type !== FieldType.ATTACHMENT_SINGLE
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const oldKeys =
|
|
||||||
opts.oldRow[key]?.map(
|
const oldKeys = AttachmentCleanup.extractAttachmentKeys(
|
||||||
(attachment: RowAttachment) => attachment.key
|
schema.type,
|
||||||
) || []
|
opts.oldRow[key]
|
||||||
const newKeys =
|
)
|
||||||
opts.row[key]?.map((attachment: RowAttachment) => attachment.key) ||
|
const newKeys = AttachmentCleanup.extractAttachmentKeys(
|
||||||
[]
|
schema.type,
|
||||||
|
opts.row[key]
|
||||||
|
)
|
||||||
files = files.concat(
|
files = files.concat(
|
||||||
oldKeys.filter((key: string) => newKeys.indexOf(key) === -1)
|
oldKeys.filter((key: string) => newKeys.indexOf(key) === -1)
|
||||||
)
|
)
|
||||||
|
|
|
@ -148,13 +148,18 @@ export async function inputProcessing(
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove any attachment urls, they are generated on read
|
// remove any attachment urls, they are generated on read
|
||||||
if (field.type === FieldType.ATTACHMENT) {
|
if (field.type === FieldType.ATTACHMENTS) {
|
||||||
const attachments = clonedRow[key]
|
const attachments = clonedRow[key]
|
||||||
if (attachments?.length) {
|
if (attachments?.length) {
|
||||||
attachments.forEach((attachment: RowAttachment) => {
|
attachments.forEach((attachment: RowAttachment) => {
|
||||||
delete attachment.url
|
delete attachment.url
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else if (field.type === FieldType.ATTACHMENT_SINGLE) {
|
||||||
|
const attachment = clonedRow[key]
|
||||||
|
if (attachment?.url) {
|
||||||
|
delete clonedRow[key].url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.type === FieldType.BB_REFERENCE && value) {
|
if (field.type === FieldType.BB_REFERENCE && value) {
|
||||||
|
@ -216,7 +221,7 @@ export async function outputProcessing<T extends Row[] | Row>(
|
||||||
|
|
||||||
// process complex types: attachements, bb references...
|
// process complex types: attachements, bb references...
|
||||||
for (let [property, column] of Object.entries(table.schema)) {
|
for (let [property, column] of Object.entries(table.schema)) {
|
||||||
if (column.type === FieldType.ATTACHMENT) {
|
if (column.type === FieldType.ATTACHMENTS) {
|
||||||
for (let row of enriched) {
|
for (let row of enriched) {
|
||||||
if (row[property] == null || !Array.isArray(row[property])) {
|
if (row[property] == null || !Array.isArray(row[property])) {
|
||||||
continue
|
continue
|
||||||
|
@ -227,6 +232,16 @@ export async function outputProcessing<T extends Row[] | Row>(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else if (column.type === FieldType.ATTACHMENT_SINGLE) {
|
||||||
|
for (let row of enriched) {
|
||||||
|
if (!row[property]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!row[property].url) {
|
||||||
|
row[property].url = objectStore.getAppFileUrl(row[property].key)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
!opts.skipBBReferences &&
|
!opts.skipBBReferences &&
|
||||||
column.type == FieldType.BB_REFERENCE
|
column.type == FieldType.BB_REFERENCE
|
||||||
|
|
|
@ -106,7 +106,7 @@ export const TYPE_TRANSFORM_MAP: any = {
|
||||||
return date
|
return date
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[FieldType.ATTACHMENT]: {
|
[FieldType.ATTACHMENTS]: {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
[null]: [],
|
[null]: [],
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
|
|
|
@ -25,121 +25,155 @@ const mockedDeleteFiles = objectStore.deleteFiles as jest.MockedFunction<
|
||||||
typeof objectStore.deleteFiles
|
typeof objectStore.deleteFiles
|
||||||
>
|
>
|
||||||
|
|
||||||
function table(): Table {
|
const rowGenerators: [
|
||||||
return {
|
string,
|
||||||
name: "table",
|
FieldType.ATTACHMENT_SINGLE | FieldType.ATTACHMENTS,
|
||||||
sourceId: DEFAULT_BB_DATASOURCE_ID,
|
(fileKey?: string) => Row
|
||||||
sourceType: TableSourceType.INTERNAL,
|
][] = [
|
||||||
type: "table",
|
[
|
||||||
schema: {
|
"row with a attachment list column",
|
||||||
attach: {
|
FieldType.ATTACHMENTS,
|
||||||
name: "attach",
|
function rowWithAttachments(fileKey: string = FILE_NAME): Row {
|
||||||
type: FieldType.ATTACHMENT,
|
return {
|
||||||
constraints: {},
|
attach: [
|
||||||
},
|
{
|
||||||
|
size: 1,
|
||||||
|
extension: "jpg",
|
||||||
|
key: fileKey,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
],
|
||||||
}
|
[
|
||||||
|
"row with a single attachment column",
|
||||||
function row(fileKey: string = FILE_NAME): Row {
|
FieldType.ATTACHMENT_SINGLE,
|
||||||
return {
|
function rowWithAttachments(fileKey: string = FILE_NAME): Row {
|
||||||
attach: [
|
return {
|
||||||
{
|
attach: {
|
||||||
size: 1,
|
size: 1,
|
||||||
extension: "jpg",
|
extension: "jpg",
|
||||||
key: fileKey,
|
key: fileKey,
|
||||||
},
|
},
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("attachment cleanup", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockedDeleteFiles.mockClear()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should be able to cleanup a table update", async () => {
|
|
||||||
const originalTable = table()
|
|
||||||
delete originalTable.schema["attach"]
|
|
||||||
await AttachmentCleanup.tableUpdate(originalTable, [row()], {
|
|
||||||
oldTable: table(),
|
|
||||||
})
|
|
||||||
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, [FILE_NAME])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should be able to cleanup a table deletion", async () => {
|
|
||||||
await AttachmentCleanup.tableDelete(table(), [row()])
|
|
||||||
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, [FILE_NAME])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle table column renaming", async () => {
|
|
||||||
const updatedTable = table()
|
|
||||||
updatedTable.schema.attach2 = updatedTable.schema.attach
|
|
||||||
delete updatedTable.schema.attach
|
|
||||||
await AttachmentCleanup.tableUpdate(updatedTable, [row()], {
|
|
||||||
oldTable: table(),
|
|
||||||
rename: { old: "attach", updated: "attach2" },
|
|
||||||
})
|
|
||||||
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("shouldn't cleanup if no table changes", async () => {
|
|
||||||
await AttachmentCleanup.tableUpdate(table(), [row()], { oldTable: table() })
|
|
||||||
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle row updates", async () => {
|
|
||||||
const updatedRow = row()
|
|
||||||
delete updatedRow.attach
|
|
||||||
await AttachmentCleanup.rowUpdate(table(), {
|
|
||||||
row: updatedRow,
|
|
||||||
oldRow: row(),
|
|
||||||
})
|
|
||||||
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, [FILE_NAME])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle row deletion", async () => {
|
|
||||||
await AttachmentCleanup.rowDelete(table(), [row()])
|
|
||||||
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, [FILE_NAME])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle row deletion and not throw when attachments are undefined", async () => {
|
|
||||||
await AttachmentCleanup.rowDelete(table(), [
|
|
||||||
{
|
|
||||||
attach: undefined,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("shouldn't cleanup attachments if row not updated", async () => {
|
|
||||||
await AttachmentCleanup.rowUpdate(table(), { row: row(), oldRow: row() })
|
|
||||||
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should be able to cleanup a column and not throw when attachments are undefined", async () => {
|
|
||||||
const originalTable = table()
|
|
||||||
delete originalTable.schema["attach"]
|
|
||||||
await AttachmentCleanup.tableUpdate(
|
|
||||||
originalTable,
|
|
||||||
[row("file 1"), { attach: undefined }, row("file 2")],
|
|
||||||
{
|
|
||||||
oldTable: table(),
|
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
expect(mockedDeleteFiles).toHaveBeenCalledTimes(1)
|
],
|
||||||
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, ["file 1", "file 2"])
|
]
|
||||||
})
|
|
||||||
|
|
||||||
it("should be able to cleanup a column and not throw when ALL attachments are undefined", async () => {
|
describe.each(rowGenerators)(
|
||||||
const originalTable = table()
|
"attachment cleanup",
|
||||||
delete originalTable.schema["attach"]
|
(_, attachmentFieldType, rowGenerator) => {
|
||||||
await AttachmentCleanup.tableUpdate(
|
function tableGenerator(): Table {
|
||||||
originalTable,
|
return {
|
||||||
[{}, { attach: undefined }],
|
name: "table",
|
||||||
{
|
sourceId: DEFAULT_BB_DATASOURCE_ID,
|
||||||
oldTable: table(),
|
sourceType: TableSourceType.INTERNAL,
|
||||||
|
type: "table",
|
||||||
|
schema: {
|
||||||
|
attach: {
|
||||||
|
name: "attach",
|
||||||
|
type: attachmentFieldType,
|
||||||
|
constraints: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
|
||||||
})
|
beforeEach(() => {
|
||||||
})
|
mockedDeleteFiles.mockClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to cleanup a table update", async () => {
|
||||||
|
const originalTable = tableGenerator()
|
||||||
|
delete originalTable.schema["attach"]
|
||||||
|
await AttachmentCleanup.tableUpdate(originalTable, [rowGenerator()], {
|
||||||
|
oldTable: tableGenerator(),
|
||||||
|
})
|
||||||
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, [FILE_NAME])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to cleanup a table deletion", async () => {
|
||||||
|
await AttachmentCleanup.tableDelete(tableGenerator(), [rowGenerator()])
|
||||||
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, [FILE_NAME])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle table column renaming", async () => {
|
||||||
|
const updatedTable = tableGenerator()
|
||||||
|
updatedTable.schema.attach2 = updatedTable.schema.attach
|
||||||
|
delete updatedTable.schema.attach
|
||||||
|
await AttachmentCleanup.tableUpdate(updatedTable, [rowGenerator()], {
|
||||||
|
oldTable: tableGenerator(),
|
||||||
|
rename: { old: "attach", updated: "attach2" },
|
||||||
|
})
|
||||||
|
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shouldn't cleanup if no table changes", async () => {
|
||||||
|
await AttachmentCleanup.tableUpdate(tableGenerator(), [rowGenerator()], {
|
||||||
|
oldTable: tableGenerator(),
|
||||||
|
})
|
||||||
|
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle row updates", async () => {
|
||||||
|
const updatedRow = rowGenerator()
|
||||||
|
delete updatedRow.attach
|
||||||
|
await AttachmentCleanup.rowUpdate(tableGenerator(), {
|
||||||
|
row: updatedRow,
|
||||||
|
oldRow: rowGenerator(),
|
||||||
|
})
|
||||||
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, [FILE_NAME])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle row deletion", async () => {
|
||||||
|
await AttachmentCleanup.rowDelete(tableGenerator(), [rowGenerator()])
|
||||||
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, [FILE_NAME])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle row deletion and not throw when attachments are undefined", async () => {
|
||||||
|
await AttachmentCleanup.rowDelete(tableGenerator(), [
|
||||||
|
{
|
||||||
|
multipleAttachments: undefined,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shouldn't cleanup attachments if row not updated", async () => {
|
||||||
|
await AttachmentCleanup.rowUpdate(tableGenerator(), {
|
||||||
|
row: rowGenerator(),
|
||||||
|
oldRow: rowGenerator(),
|
||||||
|
})
|
||||||
|
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to cleanup a column and not throw when attachments are undefined", async () => {
|
||||||
|
const originalTable = tableGenerator()
|
||||||
|
delete originalTable.schema["attach"]
|
||||||
|
await AttachmentCleanup.tableUpdate(
|
||||||
|
originalTable,
|
||||||
|
[rowGenerator("file 1"), { attach: undefined }, rowGenerator("file 2")],
|
||||||
|
{
|
||||||
|
oldTable: tableGenerator(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(mockedDeleteFiles).toHaveBeenCalledTimes(1)
|
||||||
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, [
|
||||||
|
"file 1",
|
||||||
|
"file 2",
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to cleanup a column and not throw when ALL attachments are undefined", async () => {
|
||||||
|
const originalTable = tableGenerator()
|
||||||
|
delete originalTable.schema["attach"]
|
||||||
|
await AttachmentCleanup.tableUpdate(
|
||||||
|
originalTable,
|
||||||
|
[{}, { attach: undefined }],
|
||||||
|
{
|
||||||
|
oldTable: tableGenerator(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -73,7 +73,7 @@ describe("rowProcessor - outputProcessing", () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle attachments correctly", async () => {
|
it("should handle attachment list correctly", async () => {
|
||||||
const table: Table = {
|
const table: Table = {
|
||||||
_id: generator.guid(),
|
_id: generator.guid(),
|
||||||
name: "TestTable",
|
name: "TestTable",
|
||||||
|
@ -82,7 +82,7 @@ describe("rowProcessor - outputProcessing", () => {
|
||||||
sourceType: TableSourceType.INTERNAL,
|
sourceType: TableSourceType.INTERNAL,
|
||||||
schema: {
|
schema: {
|
||||||
attach: {
|
attach: {
|
||||||
type: FieldType.ATTACHMENT,
|
type: FieldType.ATTACHMENTS,
|
||||||
name: "attach",
|
name: "attach",
|
||||||
constraints: {},
|
constraints: {},
|
||||||
},
|
},
|
||||||
|
@ -116,6 +116,47 @@ describe("rowProcessor - outputProcessing", () => {
|
||||||
expect(output3.attach[0].url).toBe("aaaa")
|
expect(output3.attach[0].url).toBe("aaaa")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should handle single attachment correctly", async () => {
|
||||||
|
const table: Table = {
|
||||||
|
_id: generator.guid(),
|
||||||
|
name: "TestTable",
|
||||||
|
type: "table",
|
||||||
|
sourceId: INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
sourceType: TableSourceType.INTERNAL,
|
||||||
|
schema: {
|
||||||
|
attach: {
|
||||||
|
type: FieldType.ATTACHMENT_SINGLE,
|
||||||
|
name: "attach",
|
||||||
|
constraints: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const row: { attach: RowAttachment } = {
|
||||||
|
attach: {
|
||||||
|
size: 10,
|
||||||
|
name: "test",
|
||||||
|
extension: "jpg",
|
||||||
|
key: "test.jpg",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = await outputProcessing(table, row, { squash: false })
|
||||||
|
expect(output.attach.url).toBe(
|
||||||
|
"/files/signed/prod-budi-app-assets/test.jpg"
|
||||||
|
)
|
||||||
|
|
||||||
|
row.attach.url = ""
|
||||||
|
const output2 = await outputProcessing(table, row, { squash: false })
|
||||||
|
expect(output2.attach.url).toBe(
|
||||||
|
"/files/signed/prod-budi-app-assets/test.jpg"
|
||||||
|
)
|
||||||
|
|
||||||
|
row.attach.url = "aaaa"
|
||||||
|
const output3 = await outputProcessing(table, row, { squash: false })
|
||||||
|
expect(output3.attach.url).toBe("aaaa")
|
||||||
|
})
|
||||||
|
|
||||||
it("process output even when the field is not empty", async () => {
|
it("process output even when the field is not empty", async () => {
|
||||||
const table: Table = {
|
const table: Table = {
|
||||||
_id: generator.guid(),
|
_id: generator.guid(),
|
||||||
|
|
|
@ -147,6 +147,12 @@ export function parse(rows: Rows, schema: TableSchema): Rows {
|
||||||
utils.unreachable(columnSubtype)
|
utils.unreachable(columnSubtype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
(columnType === FieldType.ATTACHMENTS ||
|
||||||
|
columnType === FieldType.ATTACHMENT_SINGLE) &&
|
||||||
|
typeof columnData === "string"
|
||||||
|
) {
|
||||||
|
parsedRow[columnName] = parseCsvExport(columnData)
|
||||||
} else {
|
} else {
|
||||||
parsedRow[columnName] = columnData
|
parsedRow[columnName] = columnData
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ const allowDisplayColumnByType: Record<FieldType, boolean> = {
|
||||||
[FieldType.INTERNAL]: true,
|
[FieldType.INTERNAL]: true,
|
||||||
[FieldType.BARCODEQR]: true,
|
[FieldType.BARCODEQR]: true,
|
||||||
[FieldType.BIGINT]: true,
|
[FieldType.BIGINT]: true,
|
||||||
|
|
||||||
[FieldType.BOOLEAN]: false,
|
[FieldType.BOOLEAN]: false,
|
||||||
[FieldType.ARRAY]: false,
|
[FieldType.ARRAY]: false,
|
||||||
[FieldType.ATTACHMENT]: false,
|
[FieldType.ATTACHMENTS]: false,
|
||||||
|
[FieldType.ATTACHMENT_SINGLE]: false,
|
||||||
[FieldType.LINK]: false,
|
[FieldType.LINK]: false,
|
||||||
[FieldType.JSON]: false,
|
[FieldType.JSON]: false,
|
||||||
[FieldType.BB_REFERENCE]: false,
|
[FieldType.BB_REFERENCE]: false,
|
||||||
|
@ -34,7 +34,8 @@ const allowSortColumnByType: Record<FieldType, boolean> = {
|
||||||
[FieldType.JSON]: true,
|
[FieldType.JSON]: true,
|
||||||
|
|
||||||
[FieldType.FORMULA]: false,
|
[FieldType.FORMULA]: false,
|
||||||
[FieldType.ATTACHMENT]: false,
|
[FieldType.ATTACHMENTS]: false,
|
||||||
|
[FieldType.ATTACHMENT_SINGLE]: false,
|
||||||
[FieldType.ARRAY]: false,
|
[FieldType.ARRAY]: false,
|
||||||
[FieldType.LINK]: false,
|
[FieldType.LINK]: false,
|
||||||
[FieldType.BB_REFERENCE]: false,
|
[FieldType.BB_REFERENCE]: false,
|
||||||
|
|
|
@ -8,7 +8,8 @@ export enum FieldType {
|
||||||
BOOLEAN = "boolean",
|
BOOLEAN = "boolean",
|
||||||
ARRAY = "array",
|
ARRAY = "array",
|
||||||
DATETIME = "datetime",
|
DATETIME = "datetime",
|
||||||
ATTACHMENT = "attachment",
|
ATTACHMENTS = "attachment",
|
||||||
|
ATTACHMENT_SINGLE = "attachment_single",
|
||||||
LINK = "link",
|
LINK = "link",
|
||||||
FORMULA = "formula",
|
FORMULA = "formula",
|
||||||
AUTO = "auto",
|
AUTO = "auto",
|
||||||
|
@ -38,7 +39,6 @@ export interface Row extends Document {
|
||||||
export enum FieldSubtype {
|
export enum FieldSubtype {
|
||||||
USER = "user",
|
USER = "user",
|
||||||
USERS = "users",
|
USERS = "users",
|
||||||
SINGLE = "single",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The 'as' are required for typescript not to type the outputs as generic FieldSubtype
|
// The 'as' are required for typescript not to type the outputs as generic FieldSubtype
|
||||||
|
@ -47,7 +47,4 @@ export const FieldTypeSubtypes = {
|
||||||
USER: FieldSubtype.USER as FieldSubtype.USER,
|
USER: FieldSubtype.USER as FieldSubtype.USER,
|
||||||
USERS: FieldSubtype.USERS as FieldSubtype.USERS,
|
USERS: FieldSubtype.USERS as FieldSubtype.USERS,
|
||||||
},
|
},
|
||||||
ATTACHMENT: {
|
|
||||||
SINGLE: FieldSubtype.SINGLE as FieldSubtype.SINGLE,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,10 +112,8 @@ export interface BBReferenceFieldMetadata
|
||||||
relationshipType?: RelationshipType
|
relationshipType?: RelationshipType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttachmentFieldMetadata
|
export interface AttachmentFieldMetadata extends BaseFieldSchema {
|
||||||
extends Omit<BaseFieldSchema, "subtype"> {
|
type: FieldType.ATTACHMENTS
|
||||||
type: FieldType.ATTACHMENT
|
|
||||||
subtype?: FieldSubtype.SINGLE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldConstraints {
|
export interface FieldConstraints {
|
||||||
|
@ -164,7 +162,7 @@ interface OtherFieldMetadata extends BaseFieldSchema {
|
||||||
| FieldType.NUMBER
|
| FieldType.NUMBER
|
||||||
| FieldType.LONGFORM
|
| FieldType.LONGFORM
|
||||||
| FieldType.BB_REFERENCE
|
| FieldType.BB_REFERENCE
|
||||||
| FieldType.ATTACHMENT
|
| FieldType.ATTACHMENTS
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,5 +215,5 @@ export function isBBReferenceField(
|
||||||
export function isAttachmentField(
|
export function isAttachmentField(
|
||||||
field: FieldSchema
|
field: FieldSchema
|
||||||
): field is AttachmentFieldMetadata {
|
): field is AttachmentFieldMetadata {
|
||||||
return field.type === FieldType.ATTACHMENT
|
return field.type === FieldType.ATTACHMENTS
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue