@budibase/server 2.5.10-alpha.0 → 2.5.10
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/builder/assets/{index.24635afb.js → index.2c527ae9.js} +1 -1
- package/builder/index.html +1 -1
- package/dist/api/controllers/row/internal.js +5 -2
- package/dist/api/controllers/row/utils.js +2 -2
- package/dist/api/controllers/table/index.js +2 -2
- package/dist/api/controllers/view/exporters.js +3 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utilities/csv.js +33 -0
- package/dist/utilities/schema.js +5 -1
- package/package.json +8 -8
- package/src/api/controllers/row/internal.ts +4 -1
- package/src/api/controllers/row/utils.ts +2 -2
- package/src/api/controllers/table/index.ts +2 -2
- package/src/api/controllers/view/exporters.ts +3 -1
- package/src/utilities/csv.ts +22 -0
- package/src/utilities/schema.ts +8 -0
- package/src/utilities/tests/csv.spec.ts +33 -0
|
@@ -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;
|
package/dist/utilities/schema.js
CHANGED
|
@@ -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
|
|
4
|
+
"version": "2.5.10",
|
|
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
|
|
49
|
-
"@budibase/client": "2.5.10
|
|
50
|
-
"@budibase/pro": "2.5.
|
|
51
|
-
"@budibase/shared-core": "2.5.10
|
|
52
|
-
"@budibase/string-templates": "2.5.10
|
|
53
|
-
"@budibase/types": "2.5.10
|
|
48
|
+
"@budibase/backend-core": "^2.5.10",
|
|
49
|
+
"@budibase/client": "^2.5.10",
|
|
50
|
+
"@budibase/pro": "2.5.10-alpha.1",
|
|
51
|
+
"@budibase/shared-core": "^2.5.10",
|
|
52
|
+
"@budibase/string-templates": "^2.5.10",
|
|
53
|
+
"@budibase/types": "^2.5.10",
|
|
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": "
|
|
179
|
+
"gitHead": "1911cdce9c927798be9380ddfe87a009a2e0bac0"
|
|
180
180
|
}
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
Table,
|
|
38
38
|
} from "@budibase/types"
|
|
39
39
|
|
|
40
|
-
|
|
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
|
|
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
|
|
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
|
-
:
|
|
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
|
+
}
|
package/src/utilities/schema.ts
CHANGED
|
@@ -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
|
+
})
|