@budibase/server 2.3.18-alpha.1 → 2.3.18-alpha.11

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.3.18-alpha.1",
4
+ "version": "2.3.18-alpha.11",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -14,7 +14,7 @@
14
14
  "build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
15
15
  "debug": "yarn build && node --expose-gc --inspect=9222 dist/index.js",
16
16
  "postbuild": "copyfiles -u 1 src/**/*.svelte dist/ && copyfiles -u 1 src/**/*.hbs dist/ && copyfiles -u 1 src/**/*.json dist/",
17
- "test": "jest --coverage --runInBand",
17
+ "test": "bash scripts/test.sh",
18
18
  "test:watch": "jest --watch",
19
19
  "predocker": "copyfiles -f ../client/dist/budibase-client.js ../client/manifest.json client",
20
20
  "build:docker": "yarn run predocker && docker build . -t app-service --label version=$BUDIBASE_RELEASE_VERSION",
@@ -43,11 +43,11 @@
43
43
  "license": "GPL-3.0",
44
44
  "dependencies": {
45
45
  "@apidevtools/swagger-parser": "10.0.3",
46
- "@budibase/backend-core": "2.3.18-alpha.1",
47
- "@budibase/client": "2.3.18-alpha.1",
48
- "@budibase/pro": "2.3.18-alpha.0",
49
- "@budibase/string-templates": "2.3.18-alpha.1",
50
- "@budibase/types": "2.3.18-alpha.1",
46
+ "@budibase/backend-core": "2.3.18-alpha.11",
47
+ "@budibase/client": "2.3.18-alpha.11",
48
+ "@budibase/pro": "2.3.18-alpha.10",
49
+ "@budibase/string-templates": "2.3.18-alpha.11",
50
+ "@budibase/types": "2.3.18-alpha.11",
51
51
  "@bull-board/api": "3.7.0",
52
52
  "@bull-board/koa": "3.9.4",
53
53
  "@elastic/elasticsearch": "7.10.0",
@@ -173,5 +173,5 @@
173
173
  "optionalDependencies": {
174
174
  "oracledb": "5.3.0"
175
175
  },
176
- "gitHead": "865cab09b28f2abcdc0b485437465b167adebdf4"
176
+ "gitHead": "90c32a8e5c78d8861b52e1f99b4e632c70e05841"
177
177
  }
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+
3
+ if [[ -n $CI ]]
4
+ then
5
+ # --runInBand performs better in ci where resources are limited
6
+ echo "jest --coverage --runInBand"
7
+ jest --coverage --runInBand
8
+ else
9
+ # --maxWorkers performs better in development
10
+ echo "jest --coverage --maxWorkers=2"
11
+ jest --coverage --maxWorkers=2
12
+ fi
@@ -65,10 +65,14 @@ export async function create(ctx: BBContext) {
65
65
 
66
66
  // call through to update if already exists
67
67
  if (automation._id && automation._rev) {
68
- return update(ctx)
68
+ await update(ctx)
69
+ return
69
70
  }
70
71
 
71
- automation._id = generateAutomationID()
72
+ // Respect existing IDs if recreating a deleted automation
73
+ if (!automation._id) {
74
+ automation._id = generateAutomationID()
75
+ }
72
76
 
73
77
  automation.type = "automation"
74
78
  automation = cleanAutomationInputs(automation)
@@ -126,6 +130,13 @@ export async function update(ctx: BBContext) {
126
130
  const db = context.getAppDB()
127
131
  let automation = ctx.request.body
128
132
  automation.appId = ctx.appId
133
+
134
+ // Call through to create if it doesn't exist
135
+ if (!automation._id || !automation._rev) {
136
+ await create(ctx)
137
+ return
138
+ }
139
+
129
140
  const oldAutomation = await db.get(automation._id)
130
141
  automation = cleanAutomationInputs(automation)
131
142
  automation = await checkForWebhooks({
@@ -142,7 +142,11 @@ function cleanupConfig(config: RunConfig, table: Table): RunConfig {
142
142
  return config
143
143
  }
144
144
 
145
- function generateIdForRow(row: Row | undefined, table: Table): string {
145
+ function generateIdForRow(
146
+ row: Row | undefined,
147
+ table: Table,
148
+ isLinked: boolean = false
149
+ ): string {
146
150
  const primary = table.primary
147
151
  if (!row || !primary) {
148
152
  return ""
@@ -150,8 +154,12 @@ function generateIdForRow(row: Row | undefined, table: Table): string {
150
154
  // build id array
151
155
  let idParts = []
152
156
  for (let field of primary) {
153
- // need to handle table name + field or just field, depending on if relationships used
154
- const fieldValue = row[`${table.name}.${field}`] || row[field]
157
+ let fieldValue = extractFieldValue({
158
+ row,
159
+ tableName: table.name,
160
+ fieldName: field,
161
+ isLinked,
162
+ })
155
163
  if (fieldValue) {
156
164
  idParts.push(fieldValue)
157
165
  }
@@ -174,18 +182,52 @@ function getEndpoint(tableId: string | undefined, operation: string) {
174
182
  }
175
183
  }
176
184
 
177
- function basicProcessing(row: Row, table: Table): Row {
185
+ // need to handle table name + field or just field, depending on if relationships used
186
+ function extractFieldValue({
187
+ row,
188
+ tableName,
189
+ fieldName,
190
+ isLinked,
191
+ }: {
192
+ row: Row
193
+ tableName: string
194
+ fieldName: string
195
+ isLinked: boolean
196
+ }) {
197
+ let value = row[`${tableName}.${fieldName}`]
198
+ if (value == null && !isLinked) {
199
+ value = row[fieldName]
200
+ }
201
+ return value
202
+ }
203
+
204
+ function basicProcessing({
205
+ row,
206
+ table,
207
+ isLinked,
208
+ }: {
209
+ row: Row
210
+ table: Table
211
+ isLinked: boolean
212
+ }): Row {
178
213
  const thisRow: Row = {}
179
214
  // filter the row down to what is actually the row (not joined)
180
- for (let fieldName of Object.keys(table.schema)) {
181
- const pathValue = row[`${table.name}.${fieldName}`]
182
- const value = pathValue != null ? pathValue : row[fieldName]
215
+ for (let field of Object.values(table.schema)) {
216
+ const fieldName = field.name
217
+
218
+ const value = extractFieldValue({
219
+ row,
220
+ tableName: table.name,
221
+ fieldName,
222
+ isLinked,
223
+ })
224
+
183
225
  // all responses include "select col as table.col" so that overlaps are handled
184
226
  if (value != null) {
185
227
  thisRow[fieldName] = value
186
228
  }
187
229
  }
188
- thisRow._id = generateIdForRow(row, table)
230
+ thisRow._id = generateIdForRow(row, table, isLinked)
189
231
  thisRow.tableId = table._id
190
232
  thisRow._rev = "rev"
191
233
  return processFormulas(table, thisRow)
@@ -293,7 +335,7 @@ export class ExternalRequest {
293
335
  // we're not inserting a doc, will be a bunch of update calls
294
336
  const otherKey: string = field.throughFrom || linkTablePrimary
295
337
  const thisKey: string = field.throughTo || tablePrimary
296
- row[key].map((relationship: any) => {
338
+ row[key].forEach((relationship: any) => {
297
339
  manyRelationships.push({
298
340
  tableId: field.through || field.tableId,
299
341
  isUpdate: false,
@@ -309,7 +351,7 @@ export class ExternalRequest {
309
351
  const thisKey: string = "id"
310
352
  // @ts-ignore
311
353
  const otherKey: string = field.fieldName
312
- row[key].map((relationship: any) => {
354
+ row[key].forEach((relationship: any) => {
313
355
  manyRelationships.push({
314
356
  tableId: field.tableId,
315
357
  isUpdate: true,
@@ -379,7 +421,8 @@ export class ExternalRequest {
379
421
  ) {
380
422
  continue
381
423
  }
382
- let linked = basicProcessing(row, linkedTable)
424
+
425
+ let linked = basicProcessing({ row, table: linkedTable, isLinked: true })
383
426
  if (!linked._id) {
384
427
  continue
385
428
  }
@@ -427,7 +470,10 @@ export class ExternalRequest {
427
470
  )
428
471
  continue
429
472
  }
430
- const thisRow = fixArrayTypes(basicProcessing(row, table), table)
473
+ const thisRow = fixArrayTypes(
474
+ basicProcessing({ row, table, isLinked: false }),
475
+ table
476
+ )
431
477
  if (thisRow._id == null) {
432
478
  throw "Unable to generate row ID for SQL rows"
433
479
  }
@@ -567,19 +613,41 @@ export class ExternalRequest {
567
613
  const { key, tableId, isUpdate, id, ...rest } = relationship
568
614
  const body: { [key: string]: any } = processObjectSync(rest, row, {})
569
615
  const linkTable = this.getTable(tableId)
570
- // @ts-ignore
571
- const linkPrimary = linkTable?.primary[0]
616
+ const relationshipPrimary = linkTable?.primary || []
617
+ const linkPrimary = relationshipPrimary[0]
572
618
  if (!linkTable || !linkPrimary) {
573
619
  return
574
620
  }
621
+
622
+ const linkSecondary = relationshipPrimary[1]
623
+
575
624
  const rows = related[key]?.rows || []
576
- const found = rows.find(
577
- (row: { [key: string]: any }) =>
625
+
626
+ function relationshipMatchPredicate({
627
+ row,
628
+ linkPrimary,
629
+ linkSecondary,
630
+ }: {
631
+ row: { [key: string]: any }
632
+ linkPrimary: string
633
+ linkSecondary?: string
634
+ }) {
635
+ const matchesPrimaryLink =
578
636
  row[linkPrimary] === relationship.id ||
579
637
  row[linkPrimary] === body?.[linkPrimary]
638
+ if (!matchesPrimaryLink || !linkSecondary) {
639
+ return matchesPrimaryLink
640
+ }
641
+
642
+ const matchesSecondayLink = row[linkSecondary] === body?.[linkSecondary]
643
+ return matchesPrimaryLink && matchesSecondayLink
644
+ }
645
+
646
+ const existingRelationship = rows.find((row: { [key: string]: any }) =>
647
+ relationshipMatchPredicate({ row, linkPrimary, linkSecondary })
580
648
  )
581
649
  const operation = isUpdate ? Operation.UPDATE : Operation.CREATE
582
- if (!found) {
650
+ if (!existingRelationship) {
583
651
  promises.push(
584
652
  getDatasourceAndQuery({
585
653
  endpoint: getEndpoint(tableId, operation),
@@ -590,7 +658,7 @@ export class ExternalRequest {
590
658
  )
591
659
  } else {
592
660
  // remove the relationship from cache so it isn't adjusted again
593
- rows.splice(rows.indexOf(found), 1)
661
+ rows.splice(rows.indexOf(existingRelationship), 1)
594
662
  }
595
663
  }
596
664
  // finally cleanup anything that needs to be removed
@@ -629,10 +697,7 @@ export class ExternalRequest {
629
697
  * Creating the specific list of fields that we desire, and excluding the ones that are no use to us
630
698
  * is more performant and has the added benefit of protecting against this scenario.
631
699
  */
632
- buildFields(
633
- table: Table,
634
- includeRelations: IncludeRelationship = IncludeRelationship.INCLUDE
635
- ) {
700
+ buildFields(table: Table, includeRelations: boolean) {
636
701
  function extractRealFields(table: Table, existing: string[] = []) {
637
702
  return Object.entries(table.schema)
638
703
  .filter(
@@ -691,6 +756,10 @@ export class ExternalRequest {
691
756
  }
692
757
  filters = buildFilters(id, filters || {}, table)
693
758
  const relationships = this.buildRelationships(table)
759
+
760
+ const includeSqlRelationships =
761
+ config.includeSqlRelationships === IncludeRelationship.INCLUDE
762
+
694
763
  // clean up row on ingress using schema
695
764
  const processed = this.inputProcessing(row, table)
696
765
  row = processed.row
@@ -708,9 +777,7 @@ export class ExternalRequest {
708
777
  },
709
778
  resource: {
710
779
  // have to specify the fields to avoid column overlap (for SQL)
711
- fields: isSql
712
- ? this.buildFields(table, config.includeSqlRelationships)
713
- : [],
780
+ fields: isSql ? this.buildFields(table, includeSqlRelationships) : [],
714
781
  },
715
782
  filters,
716
783
  sort,
@@ -725,6 +792,7 @@ export class ExternalRequest {
725
792
  table,
726
793
  },
727
794
  }
795
+
728
796
  // can't really use response right now
729
797
  const response = await getDatasourceAndQuery(json)
730
798
  // handle many to many relationships now if we know the ID (could be auto increment)
@@ -58,7 +58,7 @@ export async function patch(ctx: BBContext) {
58
58
  return handleRequest(Operation.UPDATE, tableId, {
59
59
  id: breakRowIdField(id),
60
60
  row: inputs,
61
- includeSqlRelationships: IncludeRelationship.EXCLUDE,
61
+ includeSqlRelationships: IncludeRelationship.INCLUDE,
62
62
  })
63
63
  }
64
64
 
@@ -38,7 +38,7 @@ router
38
38
  "/api/automations",
39
39
  bodyResource("_id"),
40
40
  authorized(permissions.BUILDER),
41
- automationValidator(true),
41
+ automationValidator(false),
42
42
  controller.update
43
43
  )
44
44
  .post(