From 6b00fb2d8e675b78b14543340aea7c6ed653578e Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 23 Sep 2020 16:15:09 +0100 Subject: [PATCH 01/26] local file upload from apps --- .../builder/src/builderStore/store/backend.js | 2 +- .../src/components/common/Dropzone.svelte | 3 +- packages/server/package.json | 2 +- .../server/src/api/controllers/deploy/aws.js | 20 +- packages/server/src/api/controllers/static.js | 69 +++- packages/server/src/api/routes/static.js | 1 + packages/server/yarn.lock | 9 +- .../standard-components/src/DataForm.svelte | 6 +- .../standard-components/src/DataTable.svelte | 7 +- packages/standard-components/src/api.js | 5 +- .../src/attachments/AttachmentList.svelte | 64 ++++ .../src/attachments/Dropzone.svelte | 299 ++++++++++++++++++ .../src/attachments/fileTypes.js | 5 + 13 files changed, 473 insertions(+), 19 deletions(-) create mode 100644 packages/standard-components/src/attachments/AttachmentList.svelte create mode 100644 packages/standard-components/src/attachments/Dropzone.svelte create mode 100644 packages/standard-components/src/attachments/fileTypes.js diff --git a/packages/builder/src/builderStore/store/backend.js b/packages/builder/src/builderStore/store/backend.js index 3835f44d64..be7dccde6c 100644 --- a/packages/builder/src/builderStore/store/backend.js +++ b/packages/builder/src/builderStore/store/backend.js @@ -28,7 +28,7 @@ export const getBackendUiStore = () => { }, }, records: { - save: () => + save: record => store.update(state => { state.selectedView = state.selectedView return state diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte index a356ff811f..0af4aa5ffd 100644 --- a/packages/builder/src/components/common/Dropzone.svelte +++ b/packages/builder/src/components/common/Dropzone.svelte @@ -35,10 +35,11 @@ return } - const filesToProcess = fileArray.map(({ name, path, size }) => ({ + const filesToProcess = fileArray.map(({ name, path, size, type }) => ({ name, path, size, + type, })) const response = await api.post(`/api/attachments/process`, { diff --git a/packages/server/package.json b/packages/server/package.json index c73ade269f..520870d735 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -58,7 +58,7 @@ "joi": "^17.2.1", "jsonwebtoken": "^8.5.1", "koa": "^2.7.0", - "koa-body": "^4.1.0", + "koa-body": "^4.2.0", "koa-compress": "^4.0.1", "koa-pino-logger": "^3.0.0", "koa-send": "^5.0.0", diff --git a/packages/server/src/api/controllers/deploy/aws.js b/packages/server/src/api/controllers/deploy/aws.js index 91f28793da..2cdc419635 100644 --- a/packages/server/src/api/controllers/deploy/aws.js +++ b/packages/server/src/api/controllers/deploy/aws.js @@ -64,19 +64,30 @@ function walkDir(dirPath, callback) { } } -function prepareUploadForS3({ filePath, s3Key, metadata, s3 }) { - const fileExtension = [...filePath.split(".")].pop() +async function prepareUploadForS3({ filePath, s3Key, metadata, fileType, s3 }) { + const contentType = + fileType || CONTENT_TYPE_MAP[[...filePath.split(".")].pop().toLowerCase()] const fileBytes = fs.readFileSync(filePath) - return s3 + + const upload = await s3 .upload({ Key: s3Key, Body: fileBytes, - ContentType: CONTENT_TYPE_MAP[fileExtension.toLowerCase()], + ContentType: contentType, Metadata: metadata, }) .promise() + + return { + // TODO: return all the passed in file info + ...upload, + url: upload.Location, + key: upload.Key, + } } +exports.prepareUploadForS3 = prepareUploadForS3 + exports.uploadAppAssets = async function({ appId, instanceId, @@ -124,6 +135,7 @@ exports.uploadAppAssets = async function({ if (file.uploaded) continue const attachmentUpload = prepareUploadForS3({ + fileType: file.type, filePath: file.path, s3Key: `assets/${appId}/attachments/${file.name}`, s3, diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js index fd4afb42e7..2ea4691038 100644 --- a/packages/server/src/api/controllers/static.js +++ b/packages/server/src/api/controllers/static.js @@ -4,6 +4,8 @@ const jwt = require("jsonwebtoken") const fetch = require("node-fetch") const fs = require("fs") const uuid = require("uuid") +const AWS = require("aws-sdk") +const { prepareUploadForS3 } = require("./deploy/aws") const { budibaseAppsDir, @@ -22,6 +24,65 @@ exports.serveBuilder = async function(ctx) { await send(ctx, ctx.file, { root: ctx.devPath || builderPath }) } +exports.uploadFile = async function(ctx) { + let files + files = + ctx.request.files.file.length > 1 + ? Array.from(ctx.request.files.file) + : [ctx.request.files.file] + + console.log(files) + + let uploads = [] + + const attachmentsPath = resolve( + budibaseAppsDir(), + ctx.user.appId, + "attachments" + ) + + if (process.env.CLOUD) { + // remote upload + const s3 = new AWS.S3({ + params: { + // TODO: Don't hardcode + Bucket: "", + }, + }) + + // TODO: probably need to UUID this too, so that we don't override by name + uploads = files.map(file => + prepareUploadForS3({ + fileType: file.type, + filePath: file.path, + s3Key: `assets/${ctx.user.appId}/attachments/${file.name}`, + s3, + }) + ) + } else { + uploads = files.map(file => { + const fileExtension = [...file.name.split(".")].pop() + const processedFileName = `${uuid.v4()}.${fileExtension}` + + return fileProcessor.process({ + format: file.format, + type: file.type, + name: file.name, + size: file.size, + path: file.path, + processedFileName, + extension: fileExtension, + outputPath: `${attachmentsPath}/${processedFileName}`, + url: `/attachments/${processedFileName}`, + }) + }) + } + + const responses = await Promise.all(uploads) + + ctx.body = responses +} + exports.processLocalFileUpload = async function(ctx) { const { files } = ctx.request.body @@ -38,14 +99,14 @@ exports.processLocalFileUpload = async function(ctx) { const filesToProcess = files.map(file => { const fileExtension = [...file.path.split(".")].pop() // filenames converted to UUIDs so they are unique - const fileName = `${uuid.v4()}.${fileExtension}` + const processedFileName = `${uuid.v4()}.${fileExtension}` return { ...file, - fileName, + processedFileName, extension: fileExtension, - outputPath: join(attachmentsPath, fileName), - url: join("/attachments", fileName), + outputPath: join(attachmentsPath, processedFileName), + url: join("/attachments", processedFileName), } }) diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index 0ce6a62668..ccb8ee2b57 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -28,6 +28,7 @@ router authorized(BUILDER), controller.processLocalFileUpload ) + .post("/api/attachments/upload", controller.uploadFile) .get("/componentlibrary", controller.serveComponentLibrary) .get("/assets/:file*", controller.serveAppAsset) .get("/attachments/:file*", controller.serveAttachment) diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index a13c7dd4d8..acaa908d81 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -534,10 +534,12 @@ "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== "@types/formidable@^1.0.31": version "1.0.31" resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-1.0.31.tgz#274f9dc2d0a1a9ce1feef48c24ca0859e7ec947b" + integrity sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q== dependencies: "@types/events" "*" "@types/node" "*" @@ -3836,9 +3838,10 @@ kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" -koa-body@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.1.1.tgz#50686d290891fc6f1acb986cf7cfcd605f855ef0" +koa-body@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.2.0.tgz#37229208b820761aca5822d14c5fc55cee31b26f" + integrity sha512-wdGu7b9amk4Fnk/ytH8GuWwfs4fsB5iNkY8kZPpgQVb04QZSv85T0M8reb+cJmvLE8cjPYvBzRikD3s6qz8OoA== dependencies: "@types/formidable" "^1.0.31" co-body "^5.1.1" diff --git a/packages/standard-components/src/DataForm.svelte b/packages/standard-components/src/DataForm.svelte index ba5998e5c6..0b8267c87f 100644 --- a/packages/standard-components/src/DataForm.svelte +++ b/packages/standard-components/src/DataForm.svelte @@ -2,6 +2,7 @@ import { onMount } from "svelte" import { fade } from "svelte/transition" import { Label, DatePicker } from "@budibase/bbui" + import Dropzone from "./attachments/Dropzone.svelte" import debounce from "lodash.debounce" export let _bb @@ -54,8 +55,9 @@ const save = debounce(async () => { for (let field of fields) { // Assign defaults to empty fields to prevent validation issues - if (!(field in record)) + if (!(field in record)) { record[field] = DEFAULTS_FOR_TYPE[schema[field].type] + } } const SAVE_RECORD_URL = `/api/${model}/records` @@ -132,6 +134,8 @@ {:else if schema[field].type === 'string'} + {:else if schema[field].type === 'attachment'} + {/if}
diff --git a/packages/standard-components/src/DataTable.svelte b/packages/standard-components/src/DataTable.svelte index 10f132d017..fe967338f5 100644 --- a/packages/standard-components/src/DataTable.svelte +++ b/packages/standard-components/src/DataTable.svelte @@ -6,6 +6,7 @@ import fsort from "fast-sort" import fetchData from "./fetchData.js" import { isEmpty } from "lodash/fp" + import AttachmentList from "./attachments/AttachmentList.svelte" export let backgroundColor export let color @@ -17,6 +18,7 @@ let headers = [] let sort = {} let sorted = [] + let schema = {} $: cssVariables = { backgroundColor, @@ -83,7 +85,10 @@ {#each sorted as row (row._id)} {#each headers as header} - {#if row[header]} + + {#if Array.isArray(row[header])} + + {:else if row[header]} {row[header]} {/if} {/each} diff --git a/packages/standard-components/src/api.js b/packages/standard-components/src/api.js index da29c70578..1b1535be97 100644 --- a/packages/standard-components/src/api.js +++ b/packages/standard-components/src/api.js @@ -1,7 +1,6 @@ -const apiCall = method => async (url, body) => { - const headers = { +const apiCall = method => async (url, body, headers = { "Content-Type": "application/json", - } +}) => { const response = await fetch(url, { method: method, body: body && JSON.stringify(body), diff --git a/packages/standard-components/src/attachments/AttachmentList.svelte b/packages/standard-components/src/attachments/AttachmentList.svelte new file mode 100644 index 0000000000..9a2813883d --- /dev/null +++ b/packages/standard-components/src/attachments/AttachmentList.svelte @@ -0,0 +1,64 @@ + + + + + diff --git a/packages/standard-components/src/attachments/Dropzone.svelte b/packages/standard-components/src/attachments/Dropzone.svelte new file mode 100644 index 0000000000..ac83fa7bb6 --- /dev/null +++ b/packages/standard-components/src/attachments/Dropzone.svelte @@ -0,0 +1,299 @@ + + +
+
    + {#if selectedImage} +
  • +
    +
    + + {selectedImage.name} +
    +

    + {#if selectedImage.size <= BYTES_IN_MB} + {selectedImage.size / BYTES_IN_KB}KB + {:else}{selectedImage.size / BYTES_IN_MB}MB{/if} +

    +
    +
    + +
    + {#if selectedImageIdx !== 0} + + {/if} + + {#if selectedImageIdx !== files.length - 1} + + {/if} +
  • + {/if} +
+ + + +
+ + diff --git a/packages/standard-components/src/attachments/fileTypes.js b/packages/standard-components/src/attachments/fileTypes.js new file mode 100644 index 0000000000..2ce6958f2d --- /dev/null +++ b/packages/standard-components/src/attachments/fileTypes.js @@ -0,0 +1,5 @@ +export const FILE_TYPES = { + IMAGE: ["png", "tiff", "gif", "raw", "jpg", "jpeg"], + CODE: ["js", "rs", "py", "java", "rb", "hs", "yml"], + DOCUMENT: ["odf", "docx", "doc", "pdf", "csv"], +} From ccebe283ccf11ac7ef2a2f4cd7b3b2ae319a4ad1 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 23 Sep 2020 17:02:06 +0100 Subject: [PATCH 02/26] abstract local file upload logic --- .../server/src/api/controllers/deploy/aws.js | 14 ++- packages/server/src/api/controllers/static.js | 98 ++++++++++++------- packages/server/src/api/routes/static.js | 2 +- .../src/attachments/Dropzone.svelte | 19 ++-- 4 files changed, 84 insertions(+), 49 deletions(-) diff --git a/packages/server/src/api/controllers/deploy/aws.js b/packages/server/src/api/controllers/deploy/aws.js index 2cdc419635..79f2e8b8ea 100644 --- a/packages/server/src/api/controllers/deploy/aws.js +++ b/packages/server/src/api/controllers/deploy/aws.js @@ -64,7 +64,14 @@ function walkDir(dirPath, callback) { } } -async function prepareUploadForS3({ filePath, s3Key, metadata, fileType, s3 }) { +async function prepareUploadForS3({ + filePath, + s3Key, + metadata, + fileType, + s3, + ...file +}) { const contentType = fileType || CONTENT_TYPE_MAP[[...filePath.split(".")].pop().toLowerCase()] const fileBytes = fs.readFileSync(filePath) @@ -79,8 +86,7 @@ async function prepareUploadForS3({ filePath, s3Key, metadata, fileType, s3 }) { .promise() return { - // TODO: return all the passed in file info - ...upload, + ...file, url: upload.Location, key: upload.Key, } @@ -137,7 +143,7 @@ exports.uploadAppAssets = async function({ const attachmentUpload = prepareUploadForS3({ fileType: file.type, filePath: file.path, - s3Key: `assets/${appId}/attachments/${file.name}`, + s3Key: `assets/${appId}/attachments/${file.processedFileName}`, s3, metadata: { accountId }, }) diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js index 2ea4691038..d4bf516f29 100644 --- a/packages/server/src/api/controllers/static.js +++ b/packages/server/src/api/controllers/static.js @@ -31,8 +31,6 @@ exports.uploadFile = async function(ctx) { ? Array.from(ctx.request.files.file) : [ctx.request.files.file] - console.log(files) - let uploads = [] const attachmentsPath = resolve( @@ -45,37 +43,40 @@ exports.uploadFile = async function(ctx) { // remote upload const s3 = new AWS.S3({ params: { - // TODO: Don't hardcode - Bucket: "", + Bucket: "prod-budi-app-assets", }, }) - // TODO: probably need to UUID this too, so that we don't override by name - uploads = files.map(file => - prepareUploadForS3({ - fileType: file.type, - filePath: file.path, - s3Key: `assets/${ctx.user.appId}/attachments/${file.name}`, - s3, - }) - ) - } else { uploads = files.map(file => { const fileExtension = [...file.name.split(".")].pop() const processedFileName = `${uuid.v4()}.${fileExtension}` - return fileProcessor.process({ - format: file.format, - type: file.type, - name: file.name, - size: file.size, - path: file.path, - processedFileName, - extension: fileExtension, - outputPath: `${attachmentsPath}/${processedFileName}`, - url: `/attachments/${processedFileName}`, + return prepareUploadForS3({ + ...file, + fileType: file.type, + filePath: file.path, + s3Key: `assets/${ctx.user.appId}/attachments/${processedFileName}`, + s3, }) }) + } else { + uploads = processLocalFileUploads(files, attachmentsPath) + // uploads = files.map(file => { + // const fileExtension = [...file.name.split(".")].pop() + // const processedFileName = `${uuid.v4()}.${fileExtension}` + + // return fileProcessor.process({ + // format: file.format, + // type: file.type, + // name: file.name, + // size: file.size, + // path: file.path, + // processedFileName, + // extension: fileExtension, + // outputPath: `${attachmentsPath}/${processedFileName}`, + // url: `/attachments/${processedFileName}`, + // }) + // }) } const responses = await Promise.all(uploads) @@ -83,21 +84,13 @@ exports.uploadFile = async function(ctx) { ctx.body = responses } -exports.processLocalFileUpload = async function(ctx) { - const { files } = ctx.request.body - - const attachmentsPath = resolve( - budibaseAppsDir(), - ctx.user.appId, - "attachments" - ) - +function processLocalFileUploads(files, attachmentsPath) { // create attachments dir if it doesnt exist !fs.existsSync(attachmentsPath) && fs.mkdirSync(attachmentsPath, { recursive: true }) const filesToProcess = files.map(file => { - const fileExtension = [...file.path.split(".")].pop() + const fileExtension = [...file.name.split(".")].pop() // filenames converted to UUIDs so they are unique const processedFileName = `${uuid.v4()}.${fileExtension}` @@ -110,10 +103,43 @@ exports.processLocalFileUpload = async function(ctx) { } }) - const fileProcessOperations = filesToProcess.map(file => - fileProcessor.process(file) + return filesToProcess.map(fileProcessor.process) +} + +exports.performLocalFileProcessing = async function(ctx) { + const { files } = ctx.request.body + + const processedFileOutputPath = resolve( + budibaseAppsDir(), + ctx.user.appId, + "attachments" ) + const fileProcessOperations = processLocalFileUploads( + files, + processedFileOutputPath + ) + + // // create attachments dir if it doesnt exist + // !fs.existsSync(attachmentsPath) && + // fs.mkdirSync(attachmentsPath, { recursive: true }) + + // const filesToProcess = files.map(file => { + // const fileExtension = [...file.path.split(".")].pop() + // // filenames converted to UUIDs so they are unique + // const processedFileName = `${uuid.v4()}.${fileExtension}` + + // return { + // ...file, + // processedFileName, + // extension: fileExtension, + // outputPath: join(attachmentsPath, processedFileName), + // url: join("/attachments", processedFileName), + // } + // }) + + // const fileProcessOperations = filesToProcess.map(fileProcessor.process) + try { const processedFiles = await Promise.all(fileProcessOperations) diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index ccb8ee2b57..aa136a3d15 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -26,7 +26,7 @@ router .post( "/api/attachments/process", authorized(BUILDER), - controller.processLocalFileUpload + controller.performLocalFileProcessing ) .post("/api/attachments/upload", controller.uploadFile) .get("/componentlibrary", controller.serveComponentLibrary) diff --git a/packages/standard-components/src/attachments/Dropzone.svelte b/packages/standard-components/src/attachments/Dropzone.svelte index ac83fa7bb6..df93cd0d4e 100644 --- a/packages/standard-components/src/attachments/Dropzone.svelte +++ b/packages/standard-components/src/attachments/Dropzone.svelte @@ -3,7 +3,6 @@ import { Heading, Body, Button } from "@budibase/bbui" import { FILE_TYPES } from "./fileTypes" import api from "../api" - // import api from "builderStore/api" const BYTES_IN_KB = 1000 const BYTES_IN_MB = 1000000 @@ -31,6 +30,14 @@ data.append("file", fileList[i]) } + if (Array.from(fileList).some(file => file.size >= fileSizeLimit)) { + alert( + `Files cannot exceed ${fileSizeLimit / + BYTES_IN_MB}MB. Please try again with smaller files.` + ) + return + } + const response = await fetch("/api/attachments/upload", { method: "POST", body: data, @@ -118,12 +125,7 @@ {/if} - + @@ -183,7 +185,7 @@ display: flex; align-items: center; bottom: var(--spacing-s); - border-radius: 10px; + border-radius: 5px; transition: 0.2s transform; } @@ -264,6 +266,7 @@ .filename { overflow: hidden; + margin-left: 5px; text-overflow: ellipsis; } From fd97c14a50006e81a4b951e2e32edc99554ad1ef Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 23 Sep 2020 17:29:32 +0100 Subject: [PATCH 03/26] :sparkles: lint, tidy up and some simplification --- .../builder/src/builderStore/store/backend.js | 2 +- packages/server/src/api/controllers/static.js | 119 +++++++----------- packages/standard-components/src/api.js | 8 +- .../src/attachments/AttachmentList.svelte | 2 +- 4 files changed, 51 insertions(+), 80 deletions(-) diff --git a/packages/builder/src/builderStore/store/backend.js b/packages/builder/src/builderStore/store/backend.js index be7dccde6c..3835f44d64 100644 --- a/packages/builder/src/builderStore/store/backend.js +++ b/packages/builder/src/builderStore/store/backend.js @@ -28,7 +28,7 @@ export const getBackendUiStore = () => { }, }, records: { - save: record => + save: () => store.update(state => { state.selectedView = state.selectedView return state diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js index d4bf516f29..9fa7a76fa5 100644 --- a/packages/server/src/api/controllers/static.js +++ b/packages/server/src/api/controllers/static.js @@ -31,8 +31,6 @@ exports.uploadFile = async function(ctx) { ? Array.from(ctx.request.files.file) : [ctx.request.files.file] - let uploads = [] - const attachmentsPath = resolve( budibaseAppsDir(), ctx.user.appId, @@ -47,7 +45,7 @@ exports.uploadFile = async function(ctx) { }, }) - uploads = files.map(file => { + const uploads = files.map(file => { const fileExtension = [...file.name.split(".")].pop() const processedFileName = `${uuid.v4()}.${fileExtension}` @@ -59,51 +57,63 @@ exports.uploadFile = async function(ctx) { s3, }) }) - } else { - uploads = processLocalFileUploads(files, attachmentsPath) - // uploads = files.map(file => { - // const fileExtension = [...file.name.split(".")].pop() - // const processedFileName = `${uuid.v4()}.${fileExtension}` - // return fileProcessor.process({ - // format: file.format, - // type: file.type, - // name: file.name, - // size: file.size, - // path: file.path, - // processedFileName, - // extension: fileExtension, - // outputPath: `${attachmentsPath}/${processedFileName}`, - // url: `/attachments/${processedFileName}`, - // }) - // }) + ctx.body = await Promise.all(uploads) + return } - const responses = await Promise.all(uploads) - - ctx.body = responses + ctx.body = await processLocalFileUploads({ + files, + outputPath: attachmentsPath, + instanceId: ctx.user.instanceId, + }) } -function processLocalFileUploads(files, attachmentsPath) { +async function processLocalFileUploads({ files, outputPath, instanceId }) { // create attachments dir if it doesnt exist - !fs.existsSync(attachmentsPath) && - fs.mkdirSync(attachmentsPath, { recursive: true }) + !fs.existsSync(outputPath) && fs.mkdirSync(outputPath, { recursive: true }) const filesToProcess = files.map(file => { const fileExtension = [...file.name.split(".")].pop() // filenames converted to UUIDs so they are unique const processedFileName = `${uuid.v4()}.${fileExtension}` + console.log(file) + return { ...file, processedFileName, extension: fileExtension, - outputPath: join(attachmentsPath, processedFileName), + outputPath: join(outputPath, processedFileName), url: join("/attachments", processedFileName), } }) - return filesToProcess.map(fileProcessor.process) + const fileProcessOperations = filesToProcess.map(fileProcessor.process) + + const processedFiles = await Promise.all(fileProcessOperations) + + let pendingFileUploads + // local document used to track which files need to be uploaded + // db.get throws an error if the document doesn't exist + // need to use a promise to default + const db = new CouchDB(instanceId) + await db + .get("_local/fileuploads") + .then(data => { + pendingFileUploads = data + }) + .catch(() => { + pendingFileUploads = { _id: "_local/fileuploads", uploads: [] } + }) + + pendingFileUploads.uploads = [ + ...processedFiles, + ...pendingFileUploads.uploads, + ] + await db.put(pendingFileUploads) + + return processedFiles } exports.performLocalFileProcessing = async function(ctx) { @@ -115,55 +125,12 @@ exports.performLocalFileProcessing = async function(ctx) { "attachments" ) - const fileProcessOperations = processLocalFileUploads( - files, - processedFileOutputPath - ) - - // // create attachments dir if it doesnt exist - // !fs.existsSync(attachmentsPath) && - // fs.mkdirSync(attachmentsPath, { recursive: true }) - - // const filesToProcess = files.map(file => { - // const fileExtension = [...file.path.split(".")].pop() - // // filenames converted to UUIDs so they are unique - // const processedFileName = `${uuid.v4()}.${fileExtension}` - - // return { - // ...file, - // processedFileName, - // extension: fileExtension, - // outputPath: join(attachmentsPath, processedFileName), - // url: join("/attachments", processedFileName), - // } - // }) - - // const fileProcessOperations = filesToProcess.map(fileProcessor.process) - try { - const processedFiles = await Promise.all(fileProcessOperations) - - let pendingFileUploads - // local document used to track which files need to be uploaded - // db.get throws an error if the document doesn't exist - // need to use a promise to default - const db = new CouchDB(ctx.user.instanceId) - await db - .get("_local/fileuploads") - .then(data => { - pendingFileUploads = data - }) - .catch(() => { - pendingFileUploads = { _id: "_local/fileuploads", uploads: [] } - }) - - pendingFileUploads.uploads = [ - ...processedFiles, - ...pendingFileUploads.uploads, - ] - await db.put(pendingFileUploads) - - ctx.body = processedFiles + ctx.body = await processLocalFileUploads({ + files, + outputPath: processedFileOutputPath, + instanceId: ctx.user.instanceId, + }) } catch (err) { ctx.throw(500, err) } diff --git a/packages/standard-components/src/api.js b/packages/standard-components/src/api.js index 1b1535be97..45e7a8f134 100644 --- a/packages/standard-components/src/api.js +++ b/packages/standard-components/src/api.js @@ -1,6 +1,10 @@ -const apiCall = method => async (url, body, headers = { +const apiCall = method => async ( + url, + body, + headers = { "Content-Type": "application/json", -}) => { + } +) => { const response = await fetch(url, { method: method, body: body && JSON.stringify(body), diff --git a/packages/standard-components/src/attachments/AttachmentList.svelte b/packages/standard-components/src/attachments/AttachmentList.svelte index 9a2813883d..950c1e43b6 100644 --- a/packages/standard-components/src/attachments/AttachmentList.svelte +++ b/packages/standard-components/src/attachments/AttachmentList.svelte @@ -1,5 +1,5 @@
@@ -61,6 +66,7 @@ {#if Object.keys($backendUiStore.selectedModel.schema).length > 0} + {/if} diff --git a/packages/builder/src/components/database/DataTable/ViewDataTable.svelte b/packages/builder/src/components/database/DataTable/ViewDataTable.svelte index 7ffa2a1963..0958737b59 100644 --- a/packages/builder/src/components/database/DataTable/ViewDataTable.svelte +++ b/packages/builder/src/components/database/DataTable/ViewDataTable.svelte @@ -18,6 +18,7 @@ import CalculationPopover from "./popovers/Calculate.svelte" import GroupByPopover from "./popovers/GroupBy.svelte" import FilterPopover from "./popovers/Filter.svelte" + import ExportPopover from "./popovers/Export.svelte" export let view = {} @@ -51,4 +52,5 @@ {#if view.calculation} {/if} + diff --git a/packages/builder/src/components/database/DataTable/popovers/Export.svelte b/packages/builder/src/components/database/DataTable/popovers/Export.svelte new file mode 100644 index 0000000000..4abb31d33e --- /dev/null +++ b/packages/builder/src/components/database/DataTable/popovers/Export.svelte @@ -0,0 +1,71 @@ + + +
+ + + Export + +
+ +
Export Format
+ +
+ + +
+
+ + diff --git a/packages/builder/yarn.lock b/packages/builder/yarn.lock index 27bc9d6dbd..4da16652e2 100644 --- a/packages/builder/yarn.lock +++ b/packages/builder/yarn.lock @@ -717,15 +717,6 @@ sirv-cli "^0.4.6" svelte-flatpickr "^2.4.0" -"@budibase/client@^0.1.21": - version "0.1.21" - resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.1.21.tgz#db414445c132b373f6c25e39d62628eb60cd8ac3" - integrity sha512-/ju0vYbWh9MUjmxkGNlOL4S/VQd4p5mbz5rHu0yt55ak9t/yyzI6PzBBxlucBeRbXYd9OFynFjy1pvYt1v+z9Q== - dependencies: - deep-equal "^2.0.1" - mustache "^4.0.1" - regexparam "^1.3.0" - "@budibase/colorpicker@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@budibase/colorpicker/-/colorpicker-1.0.1.tgz#940c180e7ebba0cb0756c4c8ef13f5dfab58e810" @@ -1402,11 +1393,6 @@ array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" -array-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= - array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -1461,13 +1447,6 @@ atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" -available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" - integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== - dependencies: - array-filter "^1.0.0" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -2417,26 +2396,6 @@ decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" -deep-equal@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.3.tgz#cad1c15277ad78a5c01c49c2dee0f54de8a6a7b0" - integrity sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA== - dependencies: - es-abstract "^1.17.5" - es-get-iterator "^1.1.0" - is-arguments "^1.0.4" - is-date-object "^1.0.2" - is-regex "^1.0.5" - isarray "^2.0.5" - object-is "^1.1.2" - object-keys "^1.1.1" - object.assign "^4.1.0" - regexp.prototype.flags "^1.3.0" - side-channel "^1.0.2" - which-boxed-primitive "^1.0.1" - which-collection "^1.0.1" - which-typed-array "^1.1.2" - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -2606,54 +2565,6 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" -es-abstract@^1.17.4: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.18.0-next.0: - version "1.18.0-next.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc" - integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-get-iterator@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" - integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== - dependencies: - es-abstract "^1.17.4" - has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" - is-string "^1.0.5" - isarray "^2.0.5" - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -2960,7 +2871,7 @@ for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" -foreach@^2.0.5, foreach@~2.0.1: +foreach@~2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= @@ -3334,31 +3245,16 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== - is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -3367,11 +3263,6 @@ is-callable@^1.1.4, is-callable@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" -is-callable@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.1.tgz#4d1e21a4f437509d25ce55f8184350771421c96d" - integrity sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg== - is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -3390,7 +3281,7 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1, is-date-object@^1.0.2: +is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" @@ -3455,25 +3346,10 @@ is-installed-globally@^0.3.2: global-dirs "^2.0.1" is-path-inside "^3.0.1" -is-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== - is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= - -is-number-object@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -3530,18 +3406,6 @@ is-regex@^1.0.5: dependencies: has "^1.0.3" -is-regex@^1.1.0, is-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== - dependencies: - has-symbols "^1.0.1" - -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== - is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3550,41 +3414,16 @@ is-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" -is-string@^1.0.4, is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== - is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" dependencies: has-symbols "^1.0.1" -is-typed-array@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d" - integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ== - dependencies: - available-typed-arrays "^1.0.0" - es-abstract "^1.17.4" - foreach "^2.0.5" - has-symbols "^1.0.1" - is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" -is-weakmap@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" - integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== - -is-weakset@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" - integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== - is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -3605,11 +3444,6 @@ isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isbuffer@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/isbuffer/-/isbuffer-0.0.0.tgz#38c146d9df528b8bf9b0701c3d43cf12df3fc39b" @@ -4729,19 +4563,6 @@ object-inspect@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" -object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== - -object-is@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6" - integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -5252,19 +5073,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -regexparam@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f" - integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g== - regexpu-core@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" @@ -5672,14 +5480,6 @@ shortid@^2.2.15: dependencies: nanoid "^2.1.0" -side-channel@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" - integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== - dependencies: - es-abstract "^1.18.0-next.0" - object-inspect "^1.8.0" - signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -5906,7 +5706,7 @@ string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.0, string.prototype.trimend@^1.0.1: +string.prototype.trimend@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" dependencies: @@ -5929,7 +5729,7 @@ string.prototype.trimright@^2.1.1: es-abstract "^1.17.5" string.prototype.trimend "^1.0.0" -string.prototype.trimstart@^1.0.0, string.prototype.trimstart@^1.0.1: +string.prototype.trimstart@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" dependencies: @@ -6384,43 +6184,10 @@ whatwg-url@^8.0.0: tr46 "^2.0.2" webidl-conversions "^5.0.0" -which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== - dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" - -which-collection@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" - integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== - dependencies: - is-map "^2.0.1" - is-set "^2.0.1" - is-weakmap "^2.0.1" - is-weakset "^2.0.1" - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which-typed-array@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2" - integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ== - dependencies: - available-typed-arrays "^1.0.2" - es-abstract "^1.17.5" - foreach "^2.0.5" - function-bind "^1.1.1" - has-symbols "^1.0.1" - is-typed-array "^1.1.3" - which@^1.2.9, which@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" diff --git a/packages/server/package.json b/packages/server/package.json index 93d4ff9e7c..8b9f4a876b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -92,9 +92,6 @@ "server-destroy": "^1.0.1", "supertest": "^4.0.2" }, - "nodemonConfig": { - "delay": "1000" - }, "jest": { "testEnvironment": "node", "setupFiles": [ diff --git a/packages/server/src/api/controllers/view/exporters.js b/packages/server/src/api/controllers/view/exporters.js new file mode 100644 index 0000000000..c1e5679d2a --- /dev/null +++ b/packages/server/src/api/controllers/view/exporters.js @@ -0,0 +1,14 @@ +exports.csv = function(headers, rows) { + let csv = headers.map(key => `"${key}"`).join(",") + + for (let row of rows) { + csv = `${csv}\n${headers + .map(header => `"${row[header]}"`.trim()) + .join(",")}` + } + return csv +} + +exports.json = function(headers, rows) { + return JSON.stringify(rows, undefined, 2) +} diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js index b04b59e32b..260197aae7 100644 --- a/packages/server/src/api/controllers/view/index.js +++ b/packages/server/src/api/controllers/view/index.js @@ -1,5 +1,9 @@ const CouchDB = require("../../../db") const viewTemplate = require("./viewBuilder") +const fs = require("fs") +const path = require("path") +const os = require("os") +const exporters = require("./exporters") const controller = { fetch: async ctx => { @@ -79,6 +83,48 @@ const controller = { ctx.body = view ctx.message = `View ${ctx.params.viewName} saved successfully.` }, + exportView: async ctx => { + const db = new CouchDB(ctx.user.instanceId) + const view = ctx.request.body + const format = ctx.query.format + + // fetch records for the view + const response = await db.query(`database/${view.name}`, { + include_docs: !view.calculation, + group: view.groupBy, + }) + + if (view.calculation === "stats") { + response.rows = response.rows.map(row => ({ + group: row.key, + field: view.field, + ...row.value, + avg: row.value.sum / row.value.count, + })) + } else { + response.rows = response.rows.map(row => row.doc) + } + + let headers = Object.keys(view.schema) + + const exporter = exporters[format] + const exportedFile = exporter(headers, response.rows) + + const filename = `${view.name}.${format}` + + fs.writeFileSync(path.join(os.tmpdir(), filename), exportedFile) + + ctx.body = { + url: `/api/views/export/download/${filename}`, + name: view.name, + } + }, + downloadExport: async ctx => { + const filename = ctx.params.fileName + + ctx.attachment(filename) + ctx.body = fs.createReadStream(path.join(os.tmpdir(), filename)) + }, } module.exports = controller diff --git a/packages/server/src/api/routes/view.js b/packages/server/src/api/routes/view.js index 53d27b9268..2c88f6d19a 100644 --- a/packages/server/src/api/routes/view.js +++ b/packages/server/src/api/routes/view.js @@ -15,5 +15,11 @@ router .get("/api/views", authorized(BUILDER), viewController.fetch) .delete("/api/views/:viewName", authorized(BUILDER), viewController.destroy) .post("/api/views", authorized(BUILDER), viewController.save) + .post("/api/views/export", authorized(BUILDER), viewController.exportView) + .get( + "/api/views/export/download/:fileName", + authorized(BUILDER), + viewController.downloadExport + ) module.exports = router From 6e9c238054e0aa82e62c837fff0b2b87a6269671 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 24 Sep 2020 15:50:51 +0100 Subject: [PATCH 07/26] use bbui component for dropzone --- packages/builder/package.json | 2 +- .../src/components/common/Dropzone.svelte | 284 +----------------- packages/builder/yarn.lock | 8 +- packages/standard-components/package.json | 2 +- .../src/attachments/Dropzone.svelte | 282 +---------------- 5 files changed, 22 insertions(+), 556 deletions(-) diff --git a/packages/builder/package.json b/packages/builder/package.json index 524025ba5b..33071aff67 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -63,7 +63,7 @@ } }, "dependencies": { - "@budibase/bbui": "^1.33.0", + "@budibase/bbui": "^1.34.6", "@budibase/client": "^0.1.21", "@budibase/colorpicker": "^1.0.1", "@fortawesome/fontawesome-free": "^5.14.0", diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte index 0af4aa5ffd..53cd606143 100644 --- a/packages/builder/src/components/common/Dropzone.svelte +++ b/packages/builder/src/components/common/Dropzone.svelte @@ -1,40 +1,20 @@ -
-
    - {#if selectedImage} -
  • -
    -
    - - {selectedImage.name} -
    -

    - {#if selectedImage.size <= BYTES_IN_MB} - {selectedImage.size / BYTES_IN_KB}KB - {:else}{selectedImage.size / BYTES_IN_MB}MB{/if} -

    -
    -
    - -
    - {#if selectedImageIdx !== 0} - - {/if} - - {#if selectedImageIdx !== files.length - 1} - - {/if} -
  • - {/if} -
- - - -
- - + diff --git a/packages/builder/yarn.lock b/packages/builder/yarn.lock index e2ed2a824c..f640e80567 100644 --- a/packages/builder/yarn.lock +++ b/packages/builder/yarn.lock @@ -688,10 +688,10 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@budibase/bbui@^1.33.0": - version "1.33.0" - resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.33.0.tgz#216b24dd815f45880e9795e66b04848329b0390f" - integrity sha512-Rrt5eLbea014TIfAbT40kP0D0AWNUi8Q0kDr3UZO6Aq4UXgjc0f53ZuJ7Kb66YRDWrqiucjf1FtvOUs3/YaD6g== +"@budibase/bbui@^1.34.6": + version "1.34.6" + resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.34.6.tgz#d94a9c0af52244ded20dfcd7c93dbd6b184460dc" + integrity sha512-FLYKst1WDjQWpZPOm5w31M5mpdc4FZaHNT5UPyE+LTOtVJquUPycyS1Y/lhGjt/QjwP/Gn8wSvwwsD0gCNJvvg== dependencies: sirv-cli "^0.4.6" svelte-flatpickr "^2.4.0" diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index 494a86f2ee..5b3cc17cd7 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -36,7 +36,7 @@ "gitHead": "284cceb9b703c38566c6e6363c022f79a08d5691", "dependencies": { "@beyonk/svelte-googlemaps": "^2.2.0", - "@budibase/bbui": "^1.32.0", + "@budibase/bbui": "^1.34.6", "@fortawesome/fontawesome-free": "^5.14.0", "britecharts": "^2.16.1", "d3-selection": "^1.4.2", diff --git a/packages/standard-components/src/attachments/Dropzone.svelte b/packages/standard-components/src/attachments/Dropzone.svelte index 5d443b2674..9d68c920c2 100644 --- a/packages/standard-components/src/attachments/Dropzone.svelte +++ b/packages/standard-components/src/attachments/Dropzone.svelte @@ -1,26 +1,16 @@ -
-
    - {#if selectedImage} -
  • -
    -
    - - {selectedImage.name} -
    -

    - {#if selectedImage.size <= BYTES_IN_MB} - {selectedImage.size / BYTES_IN_KB}KB - {:else}{selectedImage.size / BYTES_IN_MB}MB{/if} -

    -
    -
    - -
    - {#if selectedImageIdx !== 0} - - {/if} - - {#if selectedImageIdx !== files.length - 1} - - {/if} -
  • - {/if} -
- - - -
- - + From d93e1739de3c8b9355d68bcf54e5211afcfd29cb Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 24 Sep 2020 16:17:33 +0100 Subject: [PATCH 08/26] merge --- .../src/components/database/DataTable/popovers/Export.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/database/DataTable/popovers/Export.svelte b/packages/builder/src/components/database/DataTable/popovers/Export.svelte index 4abb31d33e..b8f8847a0a 100644 --- a/packages/builder/src/components/database/DataTable/popovers/Export.svelte +++ b/packages/builder/src/components/database/DataTable/popovers/Export.svelte @@ -40,7 +40,7 @@
- + Export
From 1019078f793607f373618f532f879a14d3f5e285 Mon Sep 17 00:00:00 2001 From: Michael Shanks Date: Mon, 28 Sep 2020 10:47:18 +0100 Subject: [PATCH 09/26] Fetching analytics userId, when api_key entered --- packages/builder/src/analytics.js | 27 +++++++++++++++++++ .../components/settings/tabs/APIKeys.svelte | 12 ++++++++- .../components/start/CreateAppModal.svelte | 22 ++++++++++++++- packages/builder/src/pages/index.svelte | 2 ++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js index 43b51eb5fb..fc79589e9c 100644 --- a/packages/builder/src/analytics.js +++ b/packages/builder/src/analytics.js @@ -1,5 +1,6 @@ import * as Sentry from "@sentry/browser" import posthog from "posthog-js" +import api from "builderStore/api" function activate() { Sentry.init({ dsn: process.env.SENTRY_DSN }) @@ -9,6 +10,30 @@ function activate() { }) } +function identify(id) { + if (!id) return + posthog.identify(id) + Sentry.configureScope(scope => { + scope.setUser({ id: id }) + }) +} + +async function identifyByApiKey(apiKey) { + const response = await fetch( + `https://03gaine137.execute-api.eu-west-1.amazonaws.com/prod/account/id?api_key=${apiKey.trim()}` + ) + + if (response.status === 200) { + const id = await response.json() + + await api.put("/api/keys/userId", { value: id }) + identify(id) + return true + } + + return false +} + function captureException(err) { Sentry.captureException(err) } @@ -20,6 +45,8 @@ function captureEvent(event) { export default { activate, + identify, + identifyByApiKey, captureException, captureEvent, } diff --git a/packages/builder/src/components/settings/tabs/APIKeys.svelte b/packages/builder/src/components/settings/tabs/APIKeys.svelte index 508eeb3696..6710c0810e 100644 --- a/packages/builder/src/components/settings/tabs/APIKeys.svelte +++ b/packages/builder/src/components/settings/tabs/APIKeys.svelte @@ -3,13 +3,21 @@ import { store } from "builderStore" import api from "builderStore/api" import posthog from "posthog-js" + import analytics from "../../../analytics" let keys = { budibase: "", sendGrid: "" } async function updateKey([key, value]) { + if (key === "budibase") { + const isValid = await analytics.identifyByApiKey(value) + if (!isValid) { + // TODO: add validation message + keys = { ...keys } + return + } + } const response = await api.put(`/api/keys/${key}`, { value }) const res = await response.json() - if (key === "budibase") posthog.identify(value) keys = { ...keys, ...res } } @@ -17,6 +25,8 @@ async function fetchKeys() { const response = await api.get(`/api/keys/`) const res = await response.json() + // dont want this to ever be editable, as its fetched based on Api Key + if (res.userId) delete res.userId keys = res } diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index dbd0eaadb2..20fd48bdb9 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -22,12 +22,31 @@ export let hasKey + let isApiKeyValid + let lastApiKey + let fetchApiKeyPromise + const validateApiKey = async apiKey => { + if (!apiKey) return false + + // make sure we only fetch once + if (isApiKeyValid === undefined || apiKey !== lastApiKey) { + if (!fetchApiKeyPromise) { + fetchApiKeyPromise = analytics.identifyByApiKey(apiKey) + } + isApiKeyValid = await fetchApiKeyPromise + fetchApiKeyPromise = undefined + } + return isApiKeyValid + } + let submitting = false let errors = {} let validationErrors = {} let validationSchemas = [ { - apiKey: string().required("Please enter your API key."), + apiKey: string() + .required("Please enter your API key.") + .test("valid-apikey", "This API key is invalid", validateApiKey), }, { applicationName: string().required("Your application must have a name."), @@ -160,6 +179,7 @@ } function extractErrors({ inner }) { + if (!inner) return {} return inner.reduce((acc, err) => { return { ...acc, [err.path]: err.message } }, {}) diff --git a/packages/builder/src/pages/index.svelte b/packages/builder/src/pages/index.svelte index 102b3e7b65..82bb083e60 100644 --- a/packages/builder/src/pages/index.svelte +++ b/packages/builder/src/pages/index.svelte @@ -9,6 +9,7 @@ import Spinner from "components/common/Spinner.svelte" import CreateAppModal from "components/start/CreateAppModal.svelte" import { Button } from "@budibase/bbui" + import analytics from "../analytics" let promise = getApps() @@ -36,6 +37,7 @@ const apps = await getApps() if (key) { hasKey = true + analytics.identify(key.userId) } else { showCreateAppModal() } From 83261aeadfd9e6e411f73b07b9780810f6cfeddc Mon Sep 17 00:00:00 2001 From: Michael Shanks Date: Tue, 29 Sep 2020 15:26:56 +0100 Subject: [PATCH 10/26] analytics - identify user + extra actions added --- packages/builder/package.json | 2 +- packages/builder/rollup.config.js | 4 ++++ packages/builder/src/analytics.js | 14 +++++++++++--- packages/builder/src/builderStore/index.js | 7 +++---- packages/builder/src/builderStore/store/index.js | 5 ++++- .../AutomationList/CreateAutomationModal.svelte | 2 ++ .../BlockList/AutomationBlock.svelte | 4 ++++ .../database/DataTable/popovers/Calculate.svelte | 2 ++ .../database/DataTable/popovers/Filter.svelte | 2 ++ .../database/DataTable/popovers/View.svelte | 2 ++ .../nav/ModelNavigator/CreateTable.svelte | 2 ++ .../src/components/settings/tabs/APIKeys.svelte | 2 +- .../src/components/start/CreateAppModal.svelte | 4 ++-- .../src/pages/[application]/deploy/index.svelte | 7 +++++-- packages/builder/src/pages/index.svelte | 11 +++++------ packages/builder/yarn.lock | 7 ++++--- packages/server/src/api/controllers/apikeys.js | 1 + 17 files changed, 55 insertions(+), 23 deletions(-) diff --git a/packages/builder/package.json b/packages/builder/package.json index d46504f918..6f2cdd569a 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -75,7 +75,7 @@ "fast-sort": "^2.2.0", "lodash": "^4.17.13", "mustache": "^4.0.1", - "posthog-js": "1.3.1", + "posthog-js": "1.4.5", "shortid": "^2.2.15", "svelte-loading-spinners": "^0.1.1", "svelte-portal": "^0.1.0", diff --git a/packages/builder/rollup.config.js b/packages/builder/rollup.config.js index ffeacf9e52..af51739200 100644 --- a/packages/builder/rollup.config.js +++ b/packages/builder/rollup.config.js @@ -158,6 +158,10 @@ export default { find: "constants", replacement: path.resolve(projectRootDir, "src/constants"), }, + { + find: "analytics", + replacement: path.resolve(projectRootDir, "src/analytics"), + }, ], customResolver, }), diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js index fc79589e9c..e51bb79875 100644 --- a/packages/builder/src/analytics.js +++ b/packages/builder/src/analytics.js @@ -2,15 +2,20 @@ import * as Sentry from "@sentry/browser" import posthog from "posthog-js" import api from "builderStore/api" +const analyticsEnabled = process.env.NODE_ENV === "production" + function activate() { + if (!analyticsEnabled) return Sentry.init({ dsn: process.env.SENTRY_DSN }) if (!process.env.POSTHOG_TOKEN) return posthog.init(process.env.POSTHOG_TOKEN, { api_host: process.env.POSTHOG_URL, }) + posthog.set_config({ persistence: "cookie" }) } function identify(id) { + if (!analyticsEnabled) return if (!id) return posthog.identify(id) Sentry.configureScope(scope => { @@ -19,6 +24,7 @@ function identify(id) { } async function identifyByApiKey(apiKey) { + if (!analyticsEnabled) return true const response = await fetch( `https://03gaine137.execute-api.eu-west-1.amazonaws.com/prod/account/id?api_key=${apiKey.trim()}` ) @@ -35,12 +41,14 @@ async function identifyByApiKey(apiKey) { } function captureException(err) { + if (!analyticsEnabled) return Sentry.captureException(err) } -function captureEvent(event) { - if (!process.env.POSTHOG_TOKEN) return - posthog.capture(event) +function captureEvent(eventName, props = {}) { + if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return + props.sourceApp = "builder" + posthog.capture(eventName, props) } export default { diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index fff862703e..c040403592 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -1,7 +1,7 @@ import { getStore } from "./store" import { getBackendUiStore } from "./store/backend" import { getAutomationStore } from "./store/automation/" -import analytics from "../analytics" +import analytics from "analytics" export const store = getStore() export const backendUiStore = getBackendUiStore() @@ -9,9 +9,8 @@ export const automationStore = getAutomationStore() export const initialise = async () => { try { - if (process.env.NODE_ENV === "production") { - analytics.activate() - } + analytics.activate() + analytics.captureEvent("Builder Started") } catch (err) { console.log(err) } diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js index b64bf78624..70b88eb778 100644 --- a/packages/builder/src/builderStore/store/index.js +++ b/packages/builder/src/builderStore/store/index.js @@ -14,6 +14,7 @@ import { fetchComponentLibDefinitions } from "../loadComponentLibraries" import { buildCodeForScreens } from "../buildCodeForScreens" import { generate_screen_css } from "../generate_css" import { insertCodeMetadata } from "../insertCodeMetadata" +import analytics from "analytics" import { uuid } from "../uuid" import { selectComponent as _selectComponent, @@ -308,7 +309,9 @@ const addChildComponent = store => (componentToAdd, presetProps = {}) => { state.currentView = "component" state.currentComponentInfo = newComponent.props - + analytics.captureEvent("Added Component", { + name: newComponent.props._component, + }) return state }) } diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationList/CreateAutomationModal.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationList/CreateAutomationModal.svelte index 6f07c97f4d..fe50896279 100644 --- a/packages/builder/src/components/automation/AutomationPanel/AutomationList/CreateAutomationModal.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/AutomationList/CreateAutomationModal.svelte @@ -3,6 +3,7 @@ import { notifier } from "builderStore/store/notifications" import ActionButton from "components/common/ActionButton.svelte" import { Input } from "@budibase/bbui" + import analytics from "analytics" export let onClosed @@ -19,6 +20,7 @@ }) onClosed() notifier.success(`Automation ${name} created.`) + analytics.captureEvent("Automation Created", { name }) } diff --git a/packages/builder/src/components/automation/AutomationPanel/BlockList/AutomationBlock.svelte b/packages/builder/src/components/automation/AutomationPanel/BlockList/AutomationBlock.svelte index 884a109de5..b8ac6638ae 100644 --- a/packages/builder/src/components/automation/AutomationPanel/BlockList/AutomationBlock.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/BlockList/AutomationBlock.svelte @@ -1,5 +1,6 @@ diff --git a/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte b/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte index f43fec7ccb..6cba46ed11 100644 --- a/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte +++ b/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte @@ -10,6 +10,7 @@ import { backendUiStore } from "builderStore" import { notifier } from "builderStore/store/notifications" import CreateEditRecord from "../modals/CreateEditRecord.svelte" + import analytics from "analytics" const CALCULATIONS = [ { @@ -35,6 +36,7 @@ function saveView() { backendUiStore.actions.views.save(view) notifier.success(`View ${view.name} saved.`) + analytics.captureEvent("Added View Calculate") dropdown.hide() } diff --git a/packages/builder/src/components/database/DataTable/popovers/Filter.svelte b/packages/builder/src/components/database/DataTable/popovers/Filter.svelte index be40a66291..f11bef7dcb 100644 --- a/packages/builder/src/components/database/DataTable/popovers/Filter.svelte +++ b/packages/builder/src/components/database/DataTable/popovers/Filter.svelte @@ -10,6 +10,7 @@ import { backendUiStore } from "builderStore" import { notifier } from "builderStore/store/notifications" import CreateEditRecord from "../modals/CreateEditRecord.svelte" + import analytics from "analytics" const CONDITIONS = [ { @@ -63,6 +64,7 @@ backendUiStore.actions.views.save(view) notifier.success(`View ${view.name} saved.`) dropdown.hide() + analytics.captureEvent("Added View Filter") } function removeFilter(idx) { diff --git a/packages/builder/src/components/database/DataTable/popovers/View.svelte b/packages/builder/src/components/database/DataTable/popovers/View.svelte index 052c9fdf77..dcd4db51fe 100644 --- a/packages/builder/src/components/database/DataTable/popovers/View.svelte +++ b/packages/builder/src/components/database/DataTable/popovers/View.svelte @@ -11,6 +11,7 @@ import { backendUiStore } from "builderStore" import { notifier } from "builderStore/store/notifications" import CreateEditRecord from "../modals/CreateEditRecord.svelte" + import analytics from "analytics" let anchor let dropdown @@ -37,6 +38,7 @@ }) notifier.success(`View ${name} created`) dropdown.hide() + analytics.captureEvent("View Created", { name }) $goto(`../../../view/${name}`) } diff --git a/packages/builder/src/components/nav/ModelNavigator/CreateTable.svelte b/packages/builder/src/components/nav/ModelNavigator/CreateTable.svelte index 79612ea3ff..3c05b0de00 100644 --- a/packages/builder/src/components/nav/ModelNavigator/CreateTable.svelte +++ b/packages/builder/src/components/nav/ModelNavigator/CreateTable.svelte @@ -3,6 +3,7 @@ import { backendUiStore } from "builderStore" import { notifier } from "builderStore/store/notifications" import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" + import analytics from "analytics" export let table @@ -19,6 +20,7 @@ $goto(`./model/${model._id}`) name = "" dropdown.hide() + analytics.captureEvent("Table Created", { name }) } const onClosed = () => { diff --git a/packages/builder/src/components/settings/tabs/APIKeys.svelte b/packages/builder/src/components/settings/tabs/APIKeys.svelte index 6710c0810e..c99a867e33 100644 --- a/packages/builder/src/components/settings/tabs/APIKeys.svelte +++ b/packages/builder/src/components/settings/tabs/APIKeys.svelte @@ -3,7 +3,7 @@ import { store } from "builderStore" import api from "builderStore/api" import posthog from "posthog-js" - import analytics from "../../../analytics" + import analytics from "analytics" let keys = { budibase: "", sendGrid: "" } diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index 20fd48bdb9..fee827b9d4 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -14,7 +14,7 @@ import { getContext } from "svelte" import { fade } from "svelte/transition" import { post } from "builderStore/api" - import analytics from "../../analytics" + import analytics from "analytics" const { open, close } = getContext("simple-modal") //Move this to context="module" once svelte-forms is updated so that it can bind to stores correctly @@ -141,7 +141,7 @@ name: $createAppStore.values.applicationName, }) const appJson = await appResp.json() - analytics.captureEvent("web_app_created", { + analytics.captureEvent("App Created", { name, appId: appJson._id, }) diff --git a/packages/builder/src/pages/[application]/deploy/index.svelte b/packages/builder/src/pages/[application]/deploy/index.svelte index 209ad4726b..de649a8dd2 100644 --- a/packages/builder/src/pages/[application]/deploy/index.svelte +++ b/packages/builder/src/pages/[application]/deploy/index.svelte @@ -4,7 +4,7 @@ import { notifier } from "builderStore/store/notifications" import api from "builderStore/api" import Spinner from "components/common/Spinner.svelte" - import analytics from "../../../analytics" + import analytics from "analytics" let deployed = false let loading = false @@ -26,10 +26,13 @@ notifier.success(`Your Deployment is Complete.`) deployed = true loading = false - analytics.captureEvent("web_app_deployment", { + analytics.captureEvent("Deployed App", { appId, }) } catch (err) { + analytics.captureEvent("Deploy App Failed", { + appId, + }) analytics.captureException(err) notifier.danger("Deployment unsuccessful. Please try again later.") loading = false diff --git a/packages/builder/src/pages/index.svelte b/packages/builder/src/pages/index.svelte index 82bb083e60..d0c471d30e 100644 --- a/packages/builder/src/pages/index.svelte +++ b/packages/builder/src/pages/index.svelte @@ -9,7 +9,7 @@ import Spinner from "components/common/Spinner.svelte" import CreateAppModal from "components/start/CreateAppModal.svelte" import { Button } from "@budibase/bbui" - import analytics from "../analytics" + import analytics from "analytics" let promise = getApps() @@ -28,16 +28,15 @@ async function fetchKeys() { const response = await api.get(`/api/keys/`) - const res = await response.json() - return res.budibase + return await response.json() } async function checkIfKeysAndApps() { - const key = await fetchKeys() + const keys = await fetchKeys() const apps = await getApps() - if (key) { + if (keys.userId) { hasKey = true - analytics.identify(key.userId) + analytics.identify(keys.userId) } else { showCreateAppModal() } diff --git a/packages/builder/yarn.lock b/packages/builder/yarn.lock index d2a2624160..c7ab89dcd6 100644 --- a/packages/builder/yarn.lock +++ b/packages/builder/yarn.lock @@ -4847,9 +4847,10 @@ posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" -posthog-js@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.3.1.tgz#970acec1423eaa5dba0d2603410c9c70294e16da" +posthog-js@1.4.5: + version "1.4.5" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.4.5.tgz#b16235afe47938bd71eaed4ede3790c8b910ed71" + integrity sha512-Rzc5/DpuX55BqwNEbZB0tLav1gEinnr5H+82cbLiMtXLADlxmCwZiEaVXcC3XOqW0x8bcAEehicx1TbpfBamzA== prelude-ls@~1.1.2: version "1.1.2" diff --git a/packages/server/src/api/controllers/apikeys.js b/packages/server/src/api/controllers/apikeys.js index 35fc29e37e..0fa2a7feda 100644 --- a/packages/server/src/api/controllers/apikeys.js +++ b/packages/server/src/api/controllers/apikeys.js @@ -8,6 +8,7 @@ exports.fetch = async function(ctx) { ctx.body = { budibase: process.env.BUDIBASE_API_KEY, sendgrid: process.env.SENDGRID_API_KEY, + userId: process.env.USERID_API_KEY, } } From c3366055293bb4af986482b792eeea358af9db33 Mon Sep 17 00:00:00 2001 From: Michael Shanks Date: Tue, 29 Sep 2020 15:35:51 +0100 Subject: [PATCH 11/26] added comment --- packages/builder/src/components/start/CreateAppModal.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index fee827b9d4..7f23e73230 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -28,8 +28,10 @@ const validateApiKey = async apiKey => { if (!apiKey) return false - // make sure we only fetch once + // make sure we only fetch once, unless API Key is changed if (isApiKeyValid === undefined || apiKey !== lastApiKey) { + // svelte reactivity was causing a requst to get fired mutiple times + // so, we make everything await the same promise, if one exists if (!fetchApiKeyPromise) { fetchApiKeyPromise = analytics.identifyByApiKey(apiKey) } From 701c82cb1ff4f636025625bba9ee16af67b284c2 Mon Sep 17 00:00:00 2001 From: Michael Shanks Date: Tue, 29 Sep 2020 16:23:34 +0100 Subject: [PATCH 12/26] serve determines whether analytics are enabled --- packages/builder/src/analytics.js | 11 +++++++++-- packages/server/src/api/controllers/analytics.js | 3 +++ packages/server/src/api/index.js | 4 ++++ packages/server/src/api/routes/analytics.js | 10 ++++++++++ packages/server/src/api/routes/index.js | 2 ++ 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 packages/server/src/api/controllers/analytics.js create mode 100644 packages/server/src/api/routes/analytics.js diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js index e51bb79875..64998efd61 100644 --- a/packages/builder/src/analytics.js +++ b/packages/builder/src/analytics.js @@ -2,9 +2,16 @@ import * as Sentry from "@sentry/browser" import posthog from "posthog-js" import api from "builderStore/api" -const analyticsEnabled = process.env.NODE_ENV === "production" +let analyticsEnabled -function activate() { +async function activate() { + if (analyticsEnabled === undefined) { + // only the server knows the true NODE_ENV + // this was an issue as NODE_ENV = 'cypress' on the server, + // but 'production' on the client + const response = await api.get("/api/analytics") + analyticsEnabled = (await response.json()) === true + } if (!analyticsEnabled) return Sentry.init({ dsn: process.env.SENTRY_DSN }) if (!process.env.POSTHOG_TOKEN) return diff --git a/packages/server/src/api/controllers/analytics.js b/packages/server/src/api/controllers/analytics.js new file mode 100644 index 0000000000..827f83c8ff --- /dev/null +++ b/packages/server/src/api/controllers/analytics.js @@ -0,0 +1,3 @@ +exports.isEnabled = async function(ctx) { + ctx.body = JSON.stringify(process.env.NODE_ENV === "production") +} diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index b7f156fb6a..0ab10e3e4d 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -19,6 +19,7 @@ const { automationRoutes, accesslevelRoutes, apiKeysRoutes, + analyticsRoutes, } = require("./routes") const router = new Router() @@ -109,6 +110,9 @@ router.use(accesslevelRoutes.allowedMethods()) router.use(apiKeysRoutes.routes()) router.use(apiKeysRoutes.allowedMethods()) +router.use(analyticsRoutes.routes()) +router.use(analyticsRoutes.allowedMethods()) + router.use(staticRoutes.routes()) router.use(staticRoutes.allowedMethods()) diff --git a/packages/server/src/api/routes/analytics.js b/packages/server/src/api/routes/analytics.js new file mode 100644 index 0000000000..626e3c2994 --- /dev/null +++ b/packages/server/src/api/routes/analytics.js @@ -0,0 +1,10 @@ +const Router = require("@koa/router") +const authorized = require("../../middleware/authorized") +const { BUILDER } = require("../../utilities/accessLevels") +const controller = require("../controllers/analytics") + +const router = Router() + +router.get("/api/analytics", authorized(BUILDER), controller.isEnabled) + +module.exports = router diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index a2b8d3bb6c..0a5b0b1934 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -13,6 +13,7 @@ const automationRoutes = require("./automation") const accesslevelRoutes = require("./accesslevel") const deployRoutes = require("./deploy") const apiKeysRoutes = require("./apikeys") +const analyticsRoutes = require("./analytics") module.exports = { deployRoutes, @@ -30,4 +31,5 @@ module.exports = { automationRoutes, accesslevelRoutes, apiKeysRoutes, + analyticsRoutes, } From a6653997855d722f5fb7f105f2b5f0d2e27f51ae Mon Sep 17 00:00:00 2001 From: Michael Shanks Date: Tue, 29 Sep 2020 16:35:47 +0100 Subject: [PATCH 13/26] Extra analytics logging from code review --- packages/builder/src/analytics.js | 1 + .../components/database/DataTable/popovers/Calculate.svelte | 2 +- .../src/components/database/DataTable/popovers/Filter.svelte | 4 +++- packages/builder/src/components/start/CreateAppModal.svelte | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js index 64998efd61..8761d463c6 100644 --- a/packages/builder/src/analytics.js +++ b/packages/builder/src/analytics.js @@ -50,6 +50,7 @@ async function identifyByApiKey(apiKey) { function captureException(err) { if (!analyticsEnabled) return Sentry.captureException(err) + captureEvent("Error", { error: err.message ? err.message : err }) } function captureEvent(eventName, props = {}) { diff --git a/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte b/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte index 6cba46ed11..e86e7fc922 100644 --- a/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte +++ b/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte @@ -36,7 +36,7 @@ function saveView() { backendUiStore.actions.views.save(view) notifier.success(`View ${view.name} saved.`) - analytics.captureEvent("Added View Calculate") + analytics.captureEvent("Added View Calculate", { field: view.field }) dropdown.hide() } diff --git a/packages/builder/src/components/database/DataTable/popovers/Filter.svelte b/packages/builder/src/components/database/DataTable/popovers/Filter.svelte index f11bef7dcb..8f1b972538 100644 --- a/packages/builder/src/components/database/DataTable/popovers/Filter.svelte +++ b/packages/builder/src/components/database/DataTable/popovers/Filter.svelte @@ -64,7 +64,9 @@ backendUiStore.actions.views.save(view) notifier.success(`View ${view.name} saved.`) dropdown.hide() - analytics.captureEvent("Added View Filter") + analytics.captureEvent("Added View Filter", { + filters: JSON.stringify(view.filters), + }) } function removeFilter(idx) { diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index 7f23e73230..84ddd9f8ec 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -30,6 +30,7 @@ // make sure we only fetch once, unless API Key is changed if (isApiKeyValid === undefined || apiKey !== lastApiKey) { + lastApiKey = apiKey // svelte reactivity was causing a requst to get fired mutiple times // so, we make everything await the same promise, if one exists if (!fetchApiKeyPromise) { From f97369afd2a28bd0f8be05030b6f90f4219bbf34 Mon Sep 17 00:00:00 2001 From: Michael Shanks Date: Tue, 29 Sep 2020 17:28:24 +0100 Subject: [PATCH 14/26] Analytics enabled/disabled via specific ENV variable --- packages/builder/cypress/setup.js | 1 + packages/server/.env.template | 3 ++- packages/server/src/api/controllers/analytics.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/builder/cypress/setup.js b/packages/builder/cypress/setup.js index 1003e6e422..a6dab69583 100644 --- a/packages/builder/cypress/setup.js +++ b/packages/builder/cypress/setup.js @@ -14,6 +14,7 @@ rimraf.sync(homedir) process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE" process.env.NODE_ENV = "cypress" +process.env.ENABLE_ANALYTICS = "false" initialiseBudibase({ dir: homedir, clientId: "cypress-test" }) .then(() => { diff --git a/packages/server/.env.template b/packages/server/.env.template index 165e317b07..4895d0309c 100644 --- a/packages/server/.env.template +++ b/packages/server/.env.template @@ -16,4 +16,5 @@ LOG_LEVEL=error DEPLOYMENT_CREDENTIALS_URL="https://dt4mpwwap8.execute-api.eu-west-1.amazonaws.com/prod/" DEPLOYMENT_DB_URL="https://couchdb.budi.live:5984" -SENTRY_DSN=https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 \ No newline at end of file +SENTRY_DSN=https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 +ENABLE_ANALYTICS="true" \ No newline at end of file diff --git a/packages/server/src/api/controllers/analytics.js b/packages/server/src/api/controllers/analytics.js index 827f83c8ff..025775ac2e 100644 --- a/packages/server/src/api/controllers/analytics.js +++ b/packages/server/src/api/controllers/analytics.js @@ -1,3 +1,3 @@ exports.isEnabled = async function(ctx) { - ctx.body = JSON.stringify(process.env.NODE_ENV === "production") + ctx.body = JSON.stringify(process.env.ENABLE_ANALYTICS === "true") } From 3969460ee020c5cb70ca20d2a98e0bef5fa40870 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 30 Sep 2020 11:57:42 +0100 Subject: [PATCH 15/26] fix select elements --- .../DataTable/popovers/Calculate.svelte | 4 +- .../database/DataTable/popovers/Filter.svelte | 3 + .../DataTable/popovers/GroupBy.svelte | 2 +- .../src/components/settings/UserRow.svelte | 1 + .../src/components/settings/tabs/Users.svelte | 1 + .../src/components/start/Steps/User.svelte | 1 + .../EventsEditor/HandlerSelector.svelte | 139 ------------------ 7 files changed, 9 insertions(+), 142 deletions(-) delete mode 100644 packages/builder/src/components/userInterface/EventsEditor/HandlerSelector.svelte diff --git a/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte b/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte index e86e7fc922..a736ff61e8 100644 --- a/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte +++ b/packages/builder/src/components/database/DataTable/popovers/Calculate.svelte @@ -52,14 +52,14 @@

The

of

+ {#each CONJUNCTIONS as conjunction} {/each} {/if} - {#each fields as field} {/each} diff --git a/packages/builder/src/components/settings/UserRow.svelte b/packages/builder/src/components/settings/UserRow.svelte index 88477f02a8..43ffc33fe2 100644 --- a/packages/builder/src/components/settings/UserRow.svelte +++ b/packages/builder/src/components/settings/UserRow.svelte @@ -15,6 +15,7 @@ name="Name" placeholder="Username" /> diff --git a/packages/builder/src/components/settings/tabs/Users.svelte b/packages/builder/src/components/settings/tabs/Users.svelte index 2b92e08445..aced8bc0a2 100644 --- a/packages/builder/src/components/settings/tabs/Users.svelte +++ b/packages/builder/src/components/settings/tabs/Users.svelte @@ -62,6 +62,7 @@ name="Password" placeholder="Password" /> diff --git a/packages/builder/src/components/start/Steps/User.svelte b/packages/builder/src/components/start/Steps/User.svelte index 90f4045cb4..5b49ed67dd 100644 --- a/packages/builder/src/components/start/Steps/User.svelte +++ b/packages/builder/src/components/start/Steps/User.svelte @@ -22,6 +22,7 @@ type="pasword" error={blurred.password && validationErrors.password} /> diff --git a/packages/builder/src/components/userInterface/EventsEditor/HandlerSelector.svelte b/packages/builder/src/components/userInterface/EventsEditor/HandlerSelector.svelte deleted file mode 100644 index 2876973219..0000000000 --- a/packages/builder/src/components/userInterface/EventsEditor/HandlerSelector.svelte +++ /dev/null @@ -1,139 +0,0 @@ - - -
-
-
- Action - -
- {#if parameters} -
- {#each parameters as parameter, idx} - - {/each} - {/if} - {#if parameters.length > 0} -
- {#if newHandler} - - {:else} - - {/if} -
- {/if} -
-
- - From 253ece59bc52df6031daa199cd9a0b2420098225 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 30 Sep 2020 15:20:26 +0100 Subject: [PATCH 16/26] user creation CI --- packages/builder/src/components/start/Steps/User.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/builder/src/components/start/Steps/User.svelte b/packages/builder/src/components/start/Steps/User.svelte index 5b49ed67dd..90f4045cb4 100644 --- a/packages/builder/src/components/start/Steps/User.svelte +++ b/packages/builder/src/components/start/Steps/User.svelte @@ -22,7 +22,6 @@ type="pasword" error={blurred.password && validationErrors.password} /> From bc854df60ce5ca34a54c31e36df8f3baf5a5f5fe Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 30 Sep 2020 16:53:11 +0100 Subject: [PATCH 17/26] fixing export styling --- .../src/components/database/DataTable/popovers/Export.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/database/DataTable/popovers/Export.svelte b/packages/builder/src/components/database/DataTable/popovers/Export.svelte index b8f8847a0a..00514edb9c 100644 --- a/packages/builder/src/components/database/DataTable/popovers/Export.svelte +++ b/packages/builder/src/components/database/DataTable/popovers/Export.svelte @@ -47,6 +47,7 @@
Export Format