@budibase/server 2.6.10 → 2.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -151,8 +151,6 @@ exports.coerce = coerce;
151
151
  */
152
152
  function inputProcessing(user, table, row, opts) {
153
153
  let clonedRow = cloneDeep(row);
154
- // need to copy the table so it can be differenced on way out
155
- const copiedTable = cloneDeep(table);
156
154
  const dontCleanseKeys = ["type", "_id", "_rev", "tableId"];
157
155
  for (let [key, value] of Object.entries(clonedRow)) {
158
156
  const field = table.schema[key];
@@ -186,7 +184,7 @@ function inputProcessing(user, table, row, opts) {
186
184
  clonedRow._rev = row._rev;
187
185
  }
188
186
  // handle auto columns - this returns an object like {table, row}
189
- return processAutoColumn(user, copiedTable, clonedRow, opts);
187
+ return processAutoColumn(user, table, clonedRow, opts);
190
188
  }
191
189
  exports.inputProcessing = inputProcessing;
192
190
  /**
@@ -4,6 +4,22 @@ exports.TYPE_TRANSFORM_MAP = void 0;
4
4
  // @ts-nocheck
5
5
  const constants_1 = require("../../constants");
6
6
  const backend_core_1 = require("@budibase/backend-core");
7
+ const parseArrayString = value => {
8
+ if (typeof value === "string") {
9
+ if (value === "") {
10
+ return [];
11
+ }
12
+ let result;
13
+ try {
14
+ result = JSON.parse(value.replace(/'/g, '"'));
15
+ return result;
16
+ }
17
+ catch (e) {
18
+ backend_core_1.logging.logAlert("Could not parse row value", e);
19
+ }
20
+ }
21
+ return value;
22
+ };
7
23
  /**
8
24
  * A map of how we convert various properties in rows to each other based on the row type.
9
25
  */
@@ -28,9 +44,9 @@ exports.TYPE_TRANSFORM_MAP = {
28
44
  [undefined]: undefined,
29
45
  },
30
46
  [constants_1.FieldTypes.ARRAY]: {
31
- "": [],
32
47
  [null]: [],
33
48
  [undefined]: undefined,
49
+ parse: parseArrayString,
34
50
  },
35
51
  [constants_1.FieldTypes.STRING]: {
36
52
  "": "",
@@ -72,22 +88,7 @@ exports.TYPE_TRANSFORM_MAP = {
72
88
  [constants_1.FieldTypes.ATTACHMENT]: {
73
89
  [null]: [],
74
90
  [undefined]: undefined,
75
- parse: attachments => {
76
- if (typeof attachments === "string") {
77
- if (attachments === "") {
78
- return [];
79
- }
80
- let result;
81
- try {
82
- result = JSON.parse(attachments);
83
- }
84
- catch (e) {
85
- backend_core_1.logging.logAlert("Could not parse attachments", e);
86
- }
87
- return result;
88
- }
89
- return attachments;
90
- },
91
+ parse: parseArrayString,
91
92
  },
92
93
  [constants_1.FieldTypes.BOOLEAN]: {
93
94
  "": null,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.6.10",
4
+ "version": "2.6.12",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -45,12 +45,12 @@
45
45
  "license": "GPL-3.0",
46
46
  "dependencies": {
47
47
  "@apidevtools/swagger-parser": "10.0.3",
48
- "@budibase/backend-core": "^2.6.10",
49
- "@budibase/client": "^2.6.10",
50
- "@budibase/pro": "2.6.9",
51
- "@budibase/shared-core": "^2.6.10",
52
- "@budibase/string-templates": "^2.6.10",
53
- "@budibase/types": "^2.6.10",
48
+ "@budibase/backend-core": "^2.6.12",
49
+ "@budibase/client": "^2.6.12",
50
+ "@budibase/pro": "2.6.11",
51
+ "@budibase/shared-core": "^2.6.12",
52
+ "@budibase/string-templates": "^2.6.12",
53
+ "@budibase/types": "^2.6.12",
54
54
  "@bull-board/api": "3.7.0",
55
55
  "@bull-board/koa": "3.9.4",
56
56
  "@elastic/elasticsearch": "7.10.0",
@@ -176,5 +176,5 @@
176
176
  "optionalDependencies": {
177
177
  "oracledb": "5.3.0"
178
178
  },
179
- "gitHead": "1b38045f7c3728dc2bcc3919c83784b37734b70b"
179
+ "gitHead": "2b6250160a8cfad3aea51ea69d76ba023f3ebb33"
180
180
  }
@@ -97,6 +97,7 @@ export async function bulkImport(ctx: UserCtx) {
97
97
  // right now we don't trigger anything for bulk import because it
98
98
  // can only be done in the builder, but in the future we may need to
99
99
  // think about events for bulk items
100
+
100
101
  ctx.status = 200
101
102
  ctx.body = { message: `Bulk rows created.` }
102
103
  }
@@ -184,8 +184,13 @@ export async function destroy(ctx: any) {
184
184
  }
185
185
 
186
186
  export async function bulkImport(ctx: any) {
187
+ const db = context.getAppDB()
187
188
  const table = await sdk.tables.getTable(ctx.params.tableId)
188
189
  const { rows } = ctx.request.body
189
190
  await handleDataImport(ctx.user, table, rows)
191
+
192
+ // Ensure auto id and other table updates are persisted
193
+ await db.put(table)
194
+
190
195
  return table
191
196
  }
@@ -129,17 +129,17 @@ export function importToRows(
129
129
  // the real schema of the table passed in, not the clone used for
130
130
  // incrementing auto IDs
131
131
  for (const [fieldName, schema] of Object.entries(originalTable.schema)) {
132
+ const rowVal = Array.isArray(row[fieldName])
133
+ ? row[fieldName]
134
+ : [row[fieldName]]
132
135
  if (
133
136
  (schema.type === FieldTypes.OPTIONS ||
134
137
  schema.type === FieldTypes.ARRAY) &&
135
- row[fieldName] &&
136
- (!schema.constraints!.inclusion ||
137
- schema.constraints!.inclusion.indexOf(row[fieldName]) === -1)
138
+ row[fieldName]
138
139
  ) {
139
- schema.constraints!.inclusion = [
140
- ...schema.constraints!.inclusion!,
141
- row[fieldName],
142
- ]
140
+ let merged = [...schema.constraints!.inclusion!, ...rowVal]
141
+ let superSet = new Set(merged)
142
+ schema.constraints!.inclusion = Array.from(superSet)
143
143
  schema.constraints!.inclusion.sort()
144
144
  }
145
145
  }
@@ -73,18 +73,97 @@ describe("run misc tests", () => {
73
73
  type: "string",
74
74
  },
75
75
  },
76
+ e: {
77
+ name: "Auto ID",
78
+ type: "number",
79
+ subtype: "autoID",
80
+ icon: "ri-magic-line",
81
+ autocolumn: true,
82
+ constraints: {
83
+ type: "number",
84
+ presence: false,
85
+ numericality: {
86
+ greaterThanOrEqualTo: "",
87
+ lessThanOrEqualTo: "",
88
+ },
89
+ },
90
+ },
91
+ f: {
92
+ type: "array",
93
+ constraints: {
94
+ type: "array",
95
+ presence: {
96
+ "allowEmpty": true
97
+ },
98
+ inclusion: [
99
+ "One",
100
+ "Two",
101
+ "Three",
102
+ ]
103
+ },
104
+ name: "Sample Tags",
105
+ sortable: false
106
+ },
107
+ g: {
108
+ type: "options",
109
+ constraints: {
110
+ type: "string",
111
+ presence: false,
112
+ inclusion: [
113
+ "Alpha",
114
+ "Beta",
115
+ "Gamma"
116
+ ]
117
+ },
118
+ name: "Sample Opts"
119
+ }
76
120
  },
77
121
  })
78
-
122
+
123
+ // Shift specific row tests to the row spec
79
124
  await tableUtils.handleDataImport(
80
125
  { userId: "test" },
81
126
  table,
82
- [{ a: '1', b: '2', c: '3', d: '4'}]
127
+ [
128
+ { a: '1', b: '2', c: '3', d: '4', f: "['One']", g: "Alpha" },
129
+ { a: '5', b: '6', c: '7', d: '8', f: "[]", g: undefined},
130
+ { a: '9', b: '10', c: '11', d: '12', f: "['Two','Four']", g: ""},
131
+ { a: '13', b: '14', c: '15', d: '16', g: "Omega"}
132
+ ]
83
133
  )
134
+
135
+ // 4 rows imported, the auto ID starts at 1
136
+ // We expect the handleDataImport function to update the lastID
137
+ expect(table.schema.e.lastID).toEqual(4);
138
+
139
+ // Array/Multi - should have added a new value to the inclusion.
140
+ expect(table.schema.f.constraints.inclusion).toEqual(['Four','One','Three','Two']);
141
+
142
+ // Options - should have a new value in the inclusion
143
+ expect(table.schema.g.constraints.inclusion).toEqual(['Alpha','Beta','Gamma','Omega']);
144
+
84
145
  const rows = await config.getRows()
85
- expect(rows[0].a).toEqual("1")
86
- expect(rows[0].b).toEqual("2")
87
- expect(rows[0].c).toEqual("3")
146
+ expect(rows.length).toEqual(4);
147
+
148
+ const rowOne = rows.find(row => row.e === 1)
149
+ expect(rowOne.a).toEqual("1")
150
+ expect(rowOne.f).toEqual(['One'])
151
+ expect(rowOne.g).toEqual('Alpha')
152
+
153
+ const rowTwo = rows.find(row => row.e === 2)
154
+ expect(rowTwo.a).toEqual("5")
155
+ expect(rowTwo.f).toEqual([])
156
+ expect(rowTwo.g).toEqual(undefined)
157
+
158
+ const rowThree = rows.find(row => row.e === 3)
159
+ expect(rowThree.a).toEqual("9")
160
+ expect(rowThree.f).toEqual(['Two','Four'])
161
+ expect(rowThree.g).toEqual(null)
162
+
163
+ const rowFour = rows.find(row => row.e === 4)
164
+ expect(rowFour.a).toEqual("13")
165
+ expect(rowFour.f).toEqual(undefined)
166
+ expect(rowFour.g).toEqual('Omega')
88
167
  })
89
168
  })
90
169
  })
@@ -34,9 +34,9 @@ describe("/rows", () => {
34
34
  row = basicRow(table._id)
35
35
  })
36
36
 
37
- const loadRow = async (id, status = 200) =>
37
+ const loadRow = async (id, tbl_Id, status = 200) =>
38
38
  await request
39
- .get(`/api/${table._id}/rows/${id}`)
39
+ .get(`/api/${tbl_Id}/rows/${id}`)
40
40
  .set(config.defaultHeaders())
41
41
  .expect("Content-Type", /json/)
42
42
  .expect(status)
@@ -182,8 +182,32 @@ describe("/rows", () => {
182
182
  type: "string",
183
183
  presence: false,
184
184
  datetime: { earliest: "", latest: "" },
185
+ }
186
+ }
187
+ const arrayField = {
188
+ type: "array",
189
+ constraints: {
190
+ type: "array",
191
+ presence: false,
192
+ inclusion: [
193
+ "One",
194
+ "Two",
195
+ "Three",
196
+ ]
185
197
  },
198
+ name: "Sample Tags",
199
+ sortable: false
186
200
  }
201
+ const optsField = {
202
+ fieldName: "Sample Opts",
203
+ name: "Sample Opts",
204
+ type: "options",
205
+ constraints: {
206
+ type: "string",
207
+ presence: false,
208
+ inclusion: [ "Alpha", "Beta", "Gamma" ]
209
+ },
210
+ },
187
211
 
188
212
  table = await config.createTable({
189
213
  name: "TestTable2",
@@ -212,7 +236,15 @@ describe("/rows", () => {
212
236
  attachmentNull: attachment,
213
237
  attachmentUndefined: attachment,
214
238
  attachmentEmpty: attachment,
215
- attachmentEmptyArrayStr: attachment
239
+ attachmentEmptyArrayStr: attachment,
240
+ arrayFieldEmptyArrayStr: arrayField,
241
+ arrayFieldArrayStrKnown: arrayField,
242
+ arrayFieldNull: arrayField,
243
+ arrayFieldUndefined: arrayField,
244
+ optsFieldEmptyStr: optsField,
245
+ optsFieldUndefined: optsField,
246
+ optsFieldNull: optsField,
247
+ optsFieldStrKnown: optsField
216
248
  },
217
249
  })
218
250
 
@@ -241,11 +273,20 @@ describe("/rows", () => {
241
273
  attachmentUndefined: undefined,
242
274
  attachmentEmpty: "",
243
275
  attachmentEmptyArrayStr: "[]",
276
+ arrayFieldEmptyArrayStr: "[]",
277
+ arrayFieldUndefined: undefined,
278
+ arrayFieldNull: null,
279
+ arrayFieldArrayStrKnown: "['One']",
280
+ optsFieldEmptyStr: "",
281
+ optsFieldUndefined: undefined,
282
+ optsFieldNull: null,
283
+ optsFieldStrKnown: 'Alpha'
244
284
  }
245
285
 
246
- const id = (await config.createRow(row))._id
286
+ const createdRow = await config.createRow(row);
287
+ const id = createdRow._id
247
288
 
248
- const saved = (await loadRow(id)).body
289
+ const saved = (await loadRow(id, table._id)).body
249
290
 
250
291
  expect(saved.stringUndefined).toBe(undefined)
251
292
  expect(saved.stringNull).toBe("")
@@ -270,7 +311,15 @@ describe("/rows", () => {
270
311
  expect(saved.attachmentNull).toEqual([])
271
312
  expect(saved.attachmentUndefined).toBe(undefined)
272
313
  expect(saved.attachmentEmpty).toEqual([])
273
- expect(saved.attachmentEmptyArrayStr).toEqual([])
314
+ expect(saved.attachmentEmptyArrayStr).toEqual([])
315
+ expect(saved.arrayFieldEmptyArrayStr).toEqual([])
316
+ expect(saved.arrayFieldNull).toEqual([])
317
+ expect(saved.arrayFieldUndefined).toEqual(undefined)
318
+ expect(saved.optsFieldEmptyStr).toEqual(null)
319
+ expect(saved.optsFieldUndefined).toEqual(undefined)
320
+ expect(saved.optsFieldNull).toEqual(null)
321
+ expect(saved.arrayFieldArrayStrKnown).toEqual(['One'])
322
+ expect(saved.optsFieldStrKnown).toEqual('Alpha')
274
323
  })
275
324
  })
276
325
 
@@ -299,7 +348,7 @@ describe("/rows", () => {
299
348
  expect(res.body.name).toEqual("Updated Name")
300
349
  expect(res.body.description).toEqual(existing.description)
301
350
 
302
- const savedRow = await loadRow(res.body._id)
351
+ const savedRow = await loadRow(res.body._id, table._id)
303
352
 
304
353
  expect(savedRow.body.description).toEqual(existing.description)
305
354
  expect(savedRow.body.name).toEqual("Updated Name")
@@ -401,7 +450,7 @@ describe("/rows", () => {
401
450
  .expect(200)
402
451
 
403
452
  expect(res.body.length).toEqual(2)
404
- await loadRow(row1._id, 404)
453
+ await loadRow(row1._id, table._id, 404)
405
454
  await assertRowUsage(rowUsage - 2)
406
455
  await assertQueryUsage(queryUsage + 1)
407
456
  })
@@ -167,7 +167,10 @@ describe("/tables", () => {
167
167
 
168
168
  expect(events.table.created).not.toHaveBeenCalled()
169
169
  expect(events.rows.imported).toBeCalledTimes(1)
170
- expect(events.rows.imported).toBeCalledWith(table, 1)
170
+ expect(events.rows.imported).toBeCalledWith(expect.objectContaining({
171
+ name: "TestTable",
172
+ _id: table._id
173
+ }), 1)
171
174
  })
172
175
  })
173
176
 
@@ -137,8 +137,7 @@ export function inputProcessing(
137
137
  opts?: AutoColumnProcessingOpts
138
138
  ) {
139
139
  let clonedRow = cloneDeep(row)
140
- // need to copy the table so it can be differenced on way out
141
- const copiedTable = cloneDeep(table)
140
+
142
141
  const dontCleanseKeys = ["type", "_id", "_rev", "tableId"]
143
142
  for (let [key, value] of Object.entries(clonedRow)) {
144
143
  const field = table.schema[key]
@@ -175,7 +174,7 @@ export function inputProcessing(
175
174
  }
176
175
 
177
176
  // handle auto columns - this returns an object like {table, row}
178
- return processAutoColumn(user, copiedTable, clonedRow, opts)
177
+ return processAutoColumn(user, table, clonedRow, opts)
179
178
  }
180
179
 
181
180
  /**
@@ -2,6 +2,22 @@
2
2
  import { FieldTypes } from "../../constants"
3
3
  import { logging } from "@budibase/backend-core"
4
4
 
5
+ const parseArrayString = value => {
6
+ if (typeof value === "string") {
7
+ if (value === "") {
8
+ return []
9
+ }
10
+ let result
11
+ try {
12
+ result = JSON.parse(value.replace(/'/g, '"'))
13
+ return result
14
+ } catch (e) {
15
+ logging.logAlert("Could not parse row value", e)
16
+ }
17
+ }
18
+ return value
19
+ }
20
+
5
21
  /**
6
22
  * A map of how we convert various properties in rows to each other based on the row type.
7
23
  */
@@ -26,9 +42,9 @@ export const TYPE_TRANSFORM_MAP: any = {
26
42
  [undefined]: undefined,
27
43
  },
28
44
  [FieldTypes.ARRAY]: {
29
- "": [],
30
45
  [null]: [],
31
46
  [undefined]: undefined,
47
+ parse: parseArrayString,
32
48
  },
33
49
  [FieldTypes.STRING]: {
34
50
  "": "",
@@ -70,21 +86,7 @@ export const TYPE_TRANSFORM_MAP: any = {
70
86
  [FieldTypes.ATTACHMENT]: {
71
87
  [null]: [],
72
88
  [undefined]: undefined,
73
- parse: attachments => {
74
- if (typeof attachments === "string") {
75
- if (attachments === "") {
76
- return []
77
- }
78
- let result
79
- try {
80
- result = JSON.parse(attachments)
81
- } catch (e) {
82
- logging.logAlert("Could not parse attachments", e)
83
- }
84
- return result
85
- }
86
- return attachments
87
- },
89
+ parse: parseArrayString,
88
90
  },
89
91
  [FieldTypes.BOOLEAN]: {
90
92
  "": null,