@budibase/server 2.5.10-alpha.0 → 2.5.10-alpha.1

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.
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.jsonFromCsvString = void 0;
16
+ const csvtojson_1 = __importDefault(require("csvtojson"));
17
+ function jsonFromCsvString(csvString) {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ const castedWithEmptyValues = yield (0, csvtojson_1.default)({ ignoreEmpty: true }).fromString(csvString);
20
+ // By default the csvtojson library casts empty values as empty strings. This is causing issues on conversion.
21
+ // ignoreEmpty will remove the key completly if empty, so creating this empty object will ensure we return the values with the keys but empty values
22
+ const result = yield (0, csvtojson_1.default)({ ignoreEmpty: false }).fromString(csvString);
23
+ result.forEach((r, i) => {
24
+ for (const [key] of Object.entries(r).filter(([key, value]) => value === "")) {
25
+ if (castedWithEmptyValues[i][key] === undefined) {
26
+ r[key] = null;
27
+ }
28
+ }
29
+ });
30
+ return result;
31
+ });
32
+ }
33
+ exports.jsonFromCsvString = jsonFromCsvString;
@@ -39,13 +39,17 @@ function validate(rows, schema) {
39
39
  };
40
40
  rows.forEach(row => {
41
41
  Object.entries(row).forEach(([columnName, columnData]) => {
42
- var _a, _b;
42
+ var _a, _b, _c;
43
43
  const columnType = (_a = schema[columnName]) === null || _a === void 0 ? void 0 : _a.type;
44
44
  const isAutoColumn = (_b = schema[columnName]) === null || _b === void 0 ? void 0 : _b.autocolumn;
45
45
  // If the columnType is not a string, then it's not present in the schema, and should be added to the invalid columns array
46
46
  if (typeof columnType !== "string") {
47
47
  results.invalidColumns.push(columnName);
48
48
  }
49
+ else if (columnData == null &&
50
+ !((_c = schema[columnName].constraints) === null || _c === void 0 ? void 0 : _c.presence)) {
51
+ results.schemaValidation[columnName] = true;
52
+ }
49
53
  else if (
50
54
  // If there's no data for this field don't bother with further checks
51
55
  // If the field is already marked as invalid there's no need for further checks
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.5.10-alpha.0",
4
+ "version": "2.5.10-alpha.1",
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.5.10-alpha.0",
49
- "@budibase/client": "2.5.10-alpha.0",
50
- "@budibase/pro": "2.5.9",
51
- "@budibase/shared-core": "2.5.10-alpha.0",
52
- "@budibase/string-templates": "2.5.10-alpha.0",
53
- "@budibase/types": "2.5.10-alpha.0",
48
+ "@budibase/backend-core": "2.5.10-alpha.1",
49
+ "@budibase/client": "2.5.10-alpha.1",
50
+ "@budibase/pro": "2.5.10-alpha.0",
51
+ "@budibase/shared-core": "2.5.10-alpha.1",
52
+ "@budibase/string-templates": "2.5.10-alpha.1",
53
+ "@budibase/types": "2.5.10-alpha.1",
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": "1d50b1f07c1a77b21d9757534c353631ce3314f8"
179
+ "gitHead": "9fcfb0a6cd4e4003f0e420b4b180b3b069e93191"
180
180
  }
@@ -37,7 +37,7 @@ import {
37
37
  Table,
38
38
  } from "@budibase/types"
39
39
 
40
- const { cleanExportRows } = require("./utils")
40
+ import { cleanExportRows } from "./utils"
41
41
 
42
42
  const CALCULATION_TYPES = {
43
43
  SUM: "sum",
@@ -391,6 +391,9 @@ export async function exportRows(ctx: UserCtx) {
391
391
  const table = await db.get(ctx.params.tableId)
392
392
  const rowIds = ctx.request.body.rows
393
393
  let format = ctx.query.format
394
+ if (typeof format !== "string") {
395
+ ctx.throw(400, "Format parameter is not valid")
396
+ }
394
397
  const { columns, query } = ctx.request.body
395
398
 
396
399
  let result
@@ -137,8 +137,8 @@ export function cleanExportRows(
137
137
  delete schema[column]
138
138
  })
139
139
 
140
- // Intended to avoid 'undefined' in export
141
140
  if (format === Format.CSV) {
141
+ // Intended to append empty values in export
142
142
  const schemaKeys = Object.keys(schema)
143
143
  for (let key of schemaKeys) {
144
144
  if (columns?.length && columns.indexOf(key) > 0) {
@@ -146,7 +146,7 @@ export function cleanExportRows(
146
146
  }
147
147
  for (let row of cleanRows) {
148
148
  if (row[key] == null) {
149
- row[key] = ""
149
+ row[key] = undefined
150
150
  }
151
151
  }
152
152
  }
@@ -10,7 +10,7 @@ import { getDatasourceParams } from "../../../db/utils"
10
10
  import { context, events } from "@budibase/backend-core"
11
11
  import { Table, UserCtx } from "@budibase/types"
12
12
  import sdk from "../../../sdk"
13
- import csv from "csvtojson"
13
+ import { jsonFromCsvString } from "../../../utilities/csv"
14
14
 
15
15
  function pickApi({ tableId, table }: { tableId?: string; table?: Table }) {
16
16
  if (table && !tableId) {
@@ -104,7 +104,7 @@ export async function bulkImport(ctx: UserCtx) {
104
104
  export async function csvToJson(ctx: UserCtx) {
105
105
  const { csvString } = ctx.request.body
106
106
 
107
- const result = await csv().fromString(csvString)
107
+ const result = await jsonFromCsvString(csvString)
108
108
 
109
109
  ctx.status = 200
110
110
  ctx.body = result
@@ -10,7 +10,9 @@ export function csv(headers: string[], rows: Row[]) {
10
10
  val =
11
11
  typeof val === "object" && !(val instanceof Date)
12
12
  ? `"${JSON.stringify(val).replace(/"/g, "'")}"`
13
- : `"${val}"`
13
+ : val !== undefined
14
+ ? `"${val}"`
15
+ : ""
14
16
  return val.trim()
15
17
  })
16
18
  .join(",")}`
@@ -0,0 +1,22 @@
1
+ import csv from "csvtojson"
2
+
3
+ export async function jsonFromCsvString(csvString: string) {
4
+ const castedWithEmptyValues = await csv({ ignoreEmpty: true }).fromString(
5
+ csvString
6
+ )
7
+
8
+ // By default the csvtojson library casts empty values as empty strings. This is causing issues on conversion.
9
+ // ignoreEmpty will remove the key completly if empty, so creating this empty object will ensure we return the values with the keys but empty values
10
+ const result = await csv({ ignoreEmpty: false }).fromString(csvString)
11
+ result.forEach((r, i) => {
12
+ for (const [key] of Object.entries(r).filter(
13
+ ([key, value]) => value === ""
14
+ )) {
15
+ if (castedWithEmptyValues[i][key] === undefined) {
16
+ r[key] = null
17
+ }
18
+ }
19
+ })
20
+
21
+ return result
22
+ }
@@ -4,6 +4,9 @@ interface SchemaColumn {
4
4
  readonly name: string
5
5
  readonly type: FieldTypes
6
6
  readonly autocolumn?: boolean
7
+ readonly constraints?: {
8
+ presence: boolean
9
+ }
7
10
  }
8
11
 
9
12
  interface Schema {
@@ -76,6 +79,11 @@ export function validate(rows: Rows, schema: Schema): ValidationResults {
76
79
  // If the columnType is not a string, then it's not present in the schema, and should be added to the invalid columns array
77
80
  if (typeof columnType !== "string") {
78
81
  results.invalidColumns.push(columnName)
82
+ } else if (
83
+ columnData == null &&
84
+ !schema[columnName].constraints?.presence
85
+ ) {
86
+ results.schemaValidation[columnName] = true
79
87
  } else if (
80
88
  // If there's no data for this field don't bother with further checks
81
89
  // If the field is already marked as invalid there's no need for further checks
@@ -0,0 +1,33 @@
1
+ import { jsonFromCsvString } from "../csv"
2
+
3
+ describe("csv", () => {
4
+ describe("jsonFromCsvString", () => {
5
+ test("multiple lines csv can be casted", async () => {
6
+ const csvString = '"id","title"\n"1","aaa"\n"2","bbb"'
7
+
8
+ const result = await jsonFromCsvString(csvString)
9
+
10
+ expect(result).toEqual([
11
+ { id: "1", title: "aaa" },
12
+ { id: "2", title: "bbb" },
13
+ ])
14
+ result.forEach(r => expect(Object.keys(r)).toEqual(["id", "title"]))
15
+ })
16
+
17
+ test("empty values are casted as undefined", async () => {
18
+ const csvString =
19
+ '"id","optional","title"\n1,,"aaa"\n2,"value","bbb"\n3,,"ccc"'
20
+
21
+ const result = await jsonFromCsvString(csvString)
22
+
23
+ expect(result).toEqual([
24
+ { id: "1", optional: null, title: "aaa" },
25
+ { id: "2", optional: "value", title: "bbb" },
26
+ { id: "3", optional: null, title: "ccc" },
27
+ ])
28
+ result.forEach(r =>
29
+ expect(Object.keys(r)).toEqual(["id", "optional", "title"])
30
+ )
31
+ })
32
+ })
33
+ })