diff --git a/lerna.json b/lerna.json index 3db0c4b0d5..ff83ec21b4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.99-alpha.4", + "version": "0.9.102", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index 9d87ec93d1..119eed8821 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.99-alpha.4", + "version": "0.9.102", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index dc6c4f69e9..287da0cac7 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.99-alpha.4", + "version": "0.9.102", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 09d0673523..6e0a7ab58c 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.99-alpha.4", + "version": "0.9.102", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.99-alpha.4", - "@budibase/client": "^0.9.99-alpha.4", - "@budibase/colorpicker": "^1.1.2", - "@budibase/string-templates": "^0.9.99-alpha.4", + "@budibase/bbui": "^0.9.102", + "@budibase/client": "^0.9.102", + "@budibase/colorpicker": "1.1.2", + "@budibase/string-templates": "^0.9.102", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte index 8fe753cba8..660a822898 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte @@ -2,6 +2,7 @@ import { Select, Label, notifications, ModalContent } from "@budibase/bbui" import { tables, views } from "stores/backend" import analytics from "analytics" + import { FIELDS } from "constants/backend" const CALCULATIONS = [ { @@ -25,13 +26,16 @@ ) $: fields = viewTable && - Object.keys(viewTable.schema).filter( - field => - view.calculation === "count" || - // don't want to perform calculations based on auto ID - (viewTable.schema[field].type === "number" && - !viewTable.schema[field].autocolumn) - ) + Object.keys(viewTable.schema).filter(fieldName => { + const field = viewTable.schema[fieldName] + return ( + field.type !== FIELDS.FORMULA.type && + field.type !== FIELDS.LINK.type && + (view.calculation === "count" || + // don't want to perform calculations based on auto ID + (field.type === "number" && !field.autocolumn)) + ) + }) function saveView() { views.save(view) diff --git a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte index 740027a8fd..0b63c0bd4d 100644 --- a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte @@ -18,10 +18,12 @@ let exportFormat = FORMATS[0].key async function exportView() { + const filename = `export.${exportFormat}` download( `/api/views/export?view=${encodeURIComponent( view - )}&format=${exportFormat}` + )}&format=${exportFormat}`, + filename ) } diff --git a/packages/builder/src/components/backend/DataTable/modals/GroupByModal.svelte b/packages/builder/src/components/backend/DataTable/modals/GroupByModal.svelte index b566a24ca8..b0df0ef1da 100644 --- a/packages/builder/src/components/backend/DataTable/modals/GroupByModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/GroupByModal.svelte @@ -11,7 +11,11 @@ $: fields = viewTable && Object.entries(viewTable.schema) - .filter(entry => entry[1].type !== FIELDS.LINK.type) + .filter( + entry => + entry[1].type !== FIELDS.LINK.type && + entry[1].type !== FIELDS.FORMULA.type + ) .map(([key]) => key) function saveView() { diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/CreateEditRelationship/CreateEditRelationship.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/CreateEditRelationship/CreateEditRelationship.svelte index 33ca4608ff..fbc2b401ef 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/CreateEditRelationship/CreateEditRelationship.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/CreateEditRelationship/CreateEditRelationship.svelte @@ -199,6 +199,9 @@ delete datasource.entities[toTable.name].schema[originalToName] } + // store the original names so it won't cause an error + originalToName = toRelationship.name + originalFromName = fromRelationship.name await save() await tables.fetch() } diff --git a/packages/cli/package.json b/packages/cli/package.json index 10bbab1ae6..7433079211 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.99-alpha.4", + "version": "0.9.102", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 456d321ff4..b99878fdfc 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.99-alpha.4", + "version": "0.9.102", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -18,9 +18,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.99-alpha.4", - "@budibase/standard-components": "^0.9.99-alpha.4", - "@budibase/string-templates": "^0.9.99-alpha.4", + "@budibase/bbui": "^0.9.102", + "@budibase/standard-components": "^0.9.102", + "@budibase/string-templates": "^0.9.102", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/server/package.json b/packages/server/package.json index ccf31c369f..0e57aa5793 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.99-alpha.4", + "version": "0.9.102", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -62,9 +62,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.99-alpha.4", - "@budibase/client": "^0.9.99-alpha.4", - "@budibase/string-templates": "^0.9.99-alpha.4", + "@budibase/auth": "^0.9.102", + "@budibase/client": "^0.9.102", + "@budibase/string-templates": "^0.9.102", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", @@ -117,7 +117,7 @@ "devDependencies": { "@babel/core": "^7.14.3", "@babel/preset-env": "^7.14.4", - "@budibase/standard-components": "^0.9.99-alpha.4", + "@budibase/standard-components": "^0.9.102", "@jest/test-sequencer": "^24.8.0", "@types/bull": "^3.15.1", "@types/jest": "^26.0.23", diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 92a3838f51..4107937cbd 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -25,10 +25,10 @@ interface ManyRelationship { interface RunConfig { id: string - row: Row filters: SearchFilters sort: SortJson paginate: PaginationJson + row: Row } module External { @@ -89,8 +89,9 @@ module External { // build id array let idParts = [] for (let field of primary) { - if (row[field]) { - idParts.push(row[field]) + const fieldValue = row[`${table.name}.${field}`] + if (fieldValue) { + idParts.push(fieldValue) } } if (idParts.length === 0) { @@ -115,7 +116,11 @@ module External { const thisRow: { [key: string]: any } = {} // filter the row down to what is actually the row (not joined) for (let fieldName of Object.keys(table.schema)) { - thisRow[fieldName] = row[fieldName] + const value = row[`${table.name}.${fieldName}`] + // all responses include "select col as table.col" so that overlaps are handled + if (value) { + thisRow[fieldName] = value + } } thisRow._id = generateIdForRow(row, table) thisRow.tableId = table._id @@ -191,7 +196,7 @@ module External { const isUpdate = !field.through const thisKey: string = isUpdate ? "id" : linkTablePrimary // @ts-ignore - const otherKey: string = isUpdate ? field.foreignKey : tablePrimary + const otherKey: string = isUpdate ? field.fieldName : tablePrimary row[key].map((relationship: any) => { // we don't really support composite keys for relationships, this is why [0] is used manyRelationships.push({ @@ -359,7 +364,7 @@ module External { } } if (cache[fullKey] == null) { - cache[fullKey] = await makeExternalQuery(this.appId, { + const response = await makeExternalQuery(this.appId, { endpoint: getEndpoint(tableId, DataSourceOperation.READ), filters: { equal: { @@ -367,8 +372,12 @@ module External { }, }, }) + // this is the response from knex if no rows found + if (!response[0].read) { + cache[fullKey] = response + } } - return { rows: cache[fullKey], table } + return { rows: cache[fullKey] || [], table } } /** @@ -418,12 +427,16 @@ module External { const { tableName } = breakExternalTableId(tableId) const table = this.tables[tableName] for (let row of rows) { - promises.push( - makeExternalQuery(this.appId, { - endpoint: getEndpoint(tableId, DataSourceOperation.DELETE), - filters: buildFilters(generateIdForRow(row, table), {}, table), - }) - ) + const filters = buildFilters(generateIdForRow(row, table), {}, table) + // safety check, if there are no filters on deletion bad things happen + if (Object.keys(filters).length !== 0) { + promises.push( + makeExternalQuery(this.appId, { + endpoint: getEndpoint(tableId, DataSourceOperation.DELETE), + filters, + }) + ) + } } } await Promise.all(promises) @@ -442,7 +455,7 @@ module External { .filter( column => column[1].type !== FieldTypes.LINK && - !existing.find((field: string) => field.includes(column[0])) + !existing.find((field: string) => field === column[0]) ) .map(column => `${table.name}.${column[0]}`) } diff --git a/packages/server/src/api/controllers/view/exporters.js b/packages/server/src/api/controllers/view/exporters.js index 830aef1832..65b12b48de 100644 --- a/packages/server/src/api/controllers/view/exporters.js +++ b/packages/server/src/api/controllers/view/exporters.js @@ -3,7 +3,11 @@ exports.csv = function (headers, rows) { for (let row of rows) { csv = `${csv}\n${headers - .map(header => `"${row[header]}"`.trim()) + .map(header => { + let val = row[header] + val = typeof val === "object" ? JSON.stringify(val) : val + return `"${val}"`.trim() + }) .join(",")}` } return csv diff --git a/packages/server/src/api/routes/tests/datasource.spec.js b/packages/server/src/api/routes/tests/datasource.spec.js index a041de4310..7387dd3c46 100644 --- a/packages/server/src/api/routes/tests/datasource.spec.js +++ b/packages/server/src/api/routes/tests/datasource.spec.js @@ -82,7 +82,7 @@ describe("/datasources", () => { entityId: "users", }, resource: { - fields: ["name", "age"], + fields: ["users.name", "users.age"], }, filters: { string: { @@ -94,7 +94,7 @@ describe("/datasources", () => { .expect(200) // this is mock data, can't test it expect(res.body).toBeDefined() - expect(pg.queryMock).toHaveBeenCalledWith(`select "name", "age" from "users" where "users"."name" like $1 limit $2`, ["John%", 5000]) + expect(pg.queryMock).toHaveBeenCalledWith(`select "users"."name" as "users.name", "users"."age" as "users.age" from "users" where "users"."name" like $1 limit $2`, ["John%", 5000]) }) }) diff --git a/packages/server/src/db/linkedRows/LinkController.js b/packages/server/src/db/linkedRows/LinkController.js index c8331dec6b..d526bf159b 100644 --- a/packages/server/src/db/linkedRows/LinkController.js +++ b/packages/server/src/db/linkedRows/LinkController.js @@ -322,7 +322,7 @@ class LinkController { // remove schema from other table let linkedTable = await this._db.get(field.tableId) delete linkedTable.schema[field.fieldName] - this._db.put(linkedTable) + await this._db.put(linkedTable) } /** diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index ea48eab793..ca6dcb15fd 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -151,7 +151,9 @@ function buildRead(knex: Knex, json: QueryJson, limit: number): KnexQuery { } // handle select if (resource.fields && resource.fields.length > 0) { - query = query.select(resource.fields) + // select the resources as the format "table.columnName" - this is what is provided + // by the resource builder further up + query = query.select(resource.fields.map(field => `${field} as ${field}`)) } else { query = query.select("*") } diff --git a/packages/server/src/integrations/index.ts b/packages/server/src/integrations/index.ts index c0acd6b225..40b40d0753 100644 --- a/packages/server/src/integrations/index.ts +++ b/packages/server/src/integrations/index.ts @@ -31,8 +31,8 @@ const INTEGRATIONS = { [SourceNames.MONGODB]: mongodb.integration, [SourceNames.ELASTICSEARCH]: elasticsearch.integration, [SourceNames.COUCHDB]: couchdb.integration, - [SourceNames.SQL_SERVER]: s3.integration, - [SourceNames.S3]: sqlServer.integration, + [SourceNames.SQL_SERVER]: sqlServer.integration, + [SourceNames.S3]: s3.integration, [SourceNames.AIRTABLE]: airtable.integration, [SourceNames.MYSQL]: mysql.integration, [SourceNames.ARANGODB]: arangodb.integration, diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index fab151fc0d..d71e85b0d2 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -242,7 +242,7 @@ module MySQLModule { const input = this._query(json, { disableReturning: true }) let row // need to manage returning, a feature mySQL can't do - if (operation === "awdawd") { + if (operation === operation.DELETE) { row = this.getReturningRow(json) } const results = await internalQuery(this.client, input, false) diff --git a/packages/server/src/integrations/tests/sql.spec.js b/packages/server/src/integrations/tests/sql.spec.js index fb57fe79e7..a02a7e8198 100644 --- a/packages/server/src/integrations/tests/sql.spec.js +++ b/packages/server/src/integrations/tests/sql.spec.js @@ -62,12 +62,13 @@ describe("SQL query builder", () => { }) it("should test a read with specific columns", () => { + const nameProp = `${TABLE_NAME}.name`, ageProp = `${TABLE_NAME}.age` const query = sql._query(generateReadJson({ - fields: ["name", "age"] + fields: [nameProp, ageProp] })) expect(query).toEqual({ bindings: [limit], - sql: `select "name", "age" from "${TABLE_NAME}" limit $1` + sql: `select "${TABLE_NAME}"."name" as "${nameProp}", "${TABLE_NAME}"."age" as "${ageProp}" from "${TABLE_NAME}" limit $1` }) }) diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index 03751bb467..e49c1d6ff3 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -40,8 +40,13 @@ export function breakRowIdField(_id: string): any[] { // have to replace on the way back as we swapped out the double quotes // when encoding, but JSON can't handle the single quotes const decoded: string = decodeURIComponent(_id).replace(/'/g, '"') - const parsed = JSON.parse(decoded) - return Array.isArray(parsed) ? parsed : [parsed] + try { + const parsed = JSON.parse(decoded) + return Array.isArray(parsed) ? parsed : [parsed] + } catch (err) { + // wasn't json - likely was handlebars for a many to many + return [_id] + } } export function convertType(type: string, map: { [key: string]: any }) { diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js index 8f2403cc37..f43345b2fe 100644 --- a/packages/server/src/middleware/currentapp.js +++ b/packages/server/src/middleware/currentapp.js @@ -10,7 +10,7 @@ const CouchDB = require("../db") module.exports = async (ctx, next) => { // try to get the appID from the request - const requestAppId = getAppId(ctx) + let requestAppId = getAppId(ctx) // get app cookie if it exists let appCookie = null try { @@ -29,6 +29,8 @@ module.exports = async (ctx, next) => { clearCookie(ctx, Cookies.CurrentApp) return next() } + // if the request app ID wasn't set, update it with the cookie + requestAppId = requestAppId || appId } let appId, diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index 182ad51828..16ce343963 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -1,5 +1,6 @@ const env = require("../environment") -const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants") +const { OBJ_STORE_DIRECTORY } = require("../constants") +const { getAllApps } = require("@budibase/auth/db") const { sanitizeKey } = require("@budibase/auth/src/objectStore") const BB_CDN = "https://cdn.app.budi.live/assets" @@ -52,6 +53,6 @@ exports.clientLibraryPath = appId => { exports.attachmentsRelativeURL = attachmentKey => { return exports.checkSlashesInUrl( - `/${ObjectStoreBuckets.APPS}/${attachmentKey}` + `${exports.objectStoreUrl()}/${attachmentKey}` ) } diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index 618897383b..c067d4de87 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -184,7 +184,13 @@ exports.inputProcessing = (user = {}, table, row) => { } continue } - clonedRow[key] = exports.coerce(value, field.type) + // specific case to delete formula values if they get saved + // type coercion cannot completely remove the field, so have to do it here + if (field.type === FieldTypes.FORMULA) { + delete clonedRow[key] + } else { + clonedRow[key] = exports.coerce(value, field.type) + } } // handle auto columns - this returns an object like {table, row} return processAutoColumn(user, copiedTable, clonedRow) diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index 920d2a207f..9ea82856d5 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -29,11 +29,11 @@ "keywords": [ "svelte" ], - "version": "0.9.99-alpha.4", + "version": "0.9.102", "license": "MIT", "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc", "dependencies": { - "@budibase/bbui": "^0.9.99-alpha.4", + "@budibase/bbui": "^0.9.102", "@spectrum-css/card": "^3.0.3", "@spectrum-css/divider": "^1.0.3", "@spectrum-css/link": "^3.1.3", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 8758561ac5..974fbd7eff 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.99-alpha.4", + "version": "0.9.102", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index db2794b08d..9b02d2d3be 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.99-alpha.4", + "version": "0.9.102", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -23,8 +23,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.99-alpha.4", - "@budibase/string-templates": "^0.9.99-alpha.4", + "@budibase/auth": "^0.9.102", + "@budibase/string-templates": "^0.9.102", "@koa/router": "^8.0.0", "@techpass/passport-openidconnect": "^0.3.0", "aws-sdk": "^2.811.0",