diff --git a/packages/builder/cypress/integration/createView.spec.js b/packages/builder/cypress/integration/createView.spec.js
index e35688c813..697bc35143 100644
--- a/packages/builder/cypress/integration/createView.spec.js
+++ b/packages/builder/cypress/integration/createView.spec.js
@@ -55,6 +55,11 @@ context("Create a View", () => {
cy.get(".menu-container")
.find("select")
.eq(0)
+ .select("Statistics")
+ cy.wait(50)
+ cy.get(".menu-container")
+ .find("select")
+ .eq(1)
.select("age")
cy.contains("Save").click()
cy.get("thead th div").should($headers => {
diff --git a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
index d3c82e4f7c..5b56e3d125 100644
--- a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
@@ -9,21 +9,23 @@
export let view = {}
let data = []
+ let loading = false
$: name = view.name
// Fetch rows for specified view
$: {
if (!name.startsWith("all_")) {
- fetchViewData(name, view.field, view.groupBy)
+ loading = true
+ fetchViewData(name, view.field, view.groupBy, view.calculation)
}
}
- async function fetchViewData(name, field, groupBy) {
+ async function fetchViewData(name, field, groupBy, calculation) {
const params = new URLSearchParams()
- if (field) {
+ if (calculation) {
params.set("field", field)
- params.set("stats", true)
+ params.set("calculation", calculation)
}
if (groupBy) {
params.set("group", groupBy)
@@ -31,10 +33,11 @@
const QUERY_VIEW_URL = `/api/views/${name}?${params}`
const response = await api.get(QUERY_VIEW_URL)
data = await response.json()
+ loading = false
}
-
+
{#if view.calculation}
diff --git a/packages/builder/src/components/backend/DataTable/buttons/CalculateButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/CalculateButton.svelte
index efe3dcd79d..0452491247 100644
--- a/packages/builder/src/components/backend/DataTable/buttons/CalculateButton.svelte
+++ b/packages/builder/src/components/backend/DataTable/buttons/CalculateButton.svelte
@@ -9,7 +9,11 @@
-
+
Calculate
diff --git a/packages/builder/src/components/backend/DataTable/popovers/CalculatePopover.svelte b/packages/builder/src/components/backend/DataTable/popovers/CalculatePopover.svelte
index 1b079dbb4a..23965a63e0 100644
--- a/packages/builder/src/components/backend/DataTable/popovers/CalculatePopover.svelte
+++ b/packages/builder/src/components/backend/DataTable/popovers/CalculatePopover.svelte
@@ -9,6 +9,14 @@
name: "Statistics",
key: "stats",
},
+ {
+ name: "Count",
+ key: "count",
+ },
+ {
+ name: "Sum",
+ key: "sum",
+ },
]
export let view = {}
@@ -20,11 +28,12 @@
$: fields =
viewTable &&
Object.keys(viewTable.schema).filter(
- field => viewTable.schema[field].type === "number"
+ field =>
+ view.calculation === "count" ||
+ viewTable.schema[field].type === "number"
)
function saveView() {
- if (!view.calculation) view.calculation = "stats"
backendUiStore.actions.views.save(view)
notifier.success(`View ${view.name} saved.`)
onClosed()
@@ -35,25 +44,26 @@
diff --git a/packages/builder/src/components/backend/DataTable/popovers/CreateEditColumnPopover.svelte b/packages/builder/src/components/backend/DataTable/popovers/CreateEditColumnPopover.svelte
index 3748e4ed98..e803ca21cf 100644
--- a/packages/builder/src/components/backend/DataTable/popovers/CreateEditColumnPopover.svelte
+++ b/packages/builder/src/components/backend/DataTable/popovers/CreateEditColumnPopover.svelte
@@ -72,18 +72,17 @@
- {#if !originalName}
-
- {/if}
+
{#if field.type !== 'link'}
No Users found
{/each}
- {:catch error}
+ {:catch err}
Something went wrong when trying to fetch users. Please refresh (CMD + R /
CTRL + R) the page and try again.
{/await}
diff --git a/packages/builder/src/components/userInterface/PropertyControl.svelte b/packages/builder/src/components/userInterface/PropertyControl.svelte
index 91e35f2321..8d21e6fc82 100644
--- a/packages/builder/src/components/userInterface/PropertyControl.svelte
+++ b/packages/builder/src/components/userInterface/PropertyControl.svelte
@@ -13,6 +13,7 @@
import { onMount } from "svelte"
export let label = ""
+ export let bindable = true
export let componentInstance = {}
export let control = null
export let key = ""
@@ -93,7 +94,7 @@
{...props}
name={key} />
- {#if control === Input && !key.startsWith('_')}
+ {#if bindable && control === Input && !key.startsWith('_')}
diff --git a/packages/builder/src/components/userInterface/SettingsView.svelte b/packages/builder/src/components/userInterface/SettingsView.svelte
index 8698701bf3..d821f1f2e2 100644
--- a/packages/builder/src/components/userInterface/SettingsView.svelte
+++ b/packages/builder/src/components/userInterface/SettingsView.svelte
@@ -88,6 +88,7 @@
{#if screenOrPageInstance}
{#each screenOrPageDefinition as def}
- {:then results}
+ {:then _}
{:catch error}
Something went wrong: {error.message}
diff --git a/packages/client/src/render/getAppId.js b/packages/client/src/render/getAppId.js
index e6216e7be5..4cbb6692b5 100644
--- a/packages/client/src/render/getAppId.js
+++ b/packages/client/src/render/getAppId.js
@@ -3,6 +3,8 @@ export const parseAppIdFromCookie = docCookie => {
docCookie.split(";").find(c => c.trim().startsWith("budibase:token")) ||
docCookie.split(";").find(c => c.trim().startsWith("builder:token"))
+ if (!cookie) return location.pathname.replace(/\//g, "")
+
const base64Token = cookie.substring(lengthOfKey)
const user = JSON.parse(atob(base64Token.split(".")[1]))
diff --git a/packages/server/src/api/controllers/row.js b/packages/server/src/api/controllers/row.js
index 4042336741..1e12bf7a78 100644
--- a/packages/server/src/api/controllers/row.js
+++ b/packages/server/src/api/controllers/row.js
@@ -11,6 +11,12 @@ const { cloneDeep } = require("lodash")
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
+const CALCULATION_TYPES = {
+ SUM: "sum",
+ COUNT: "count",
+ STATS: "stats",
+}
+
validateJs.extend(validateJs.validators.datetime, {
parse: function(value) {
return new Date(value).getTime()
@@ -137,7 +143,7 @@ exports.save = async function(ctx) {
exports.fetchView = async function(ctx) {
const instanceId = ctx.user.instanceId
const db = new CouchDB(instanceId)
- const { stats, group, field } = ctx.query
+ const { calculation, group, field } = ctx.query
const viewName = ctx.params.viewName
// if this is a table view being looked for just transfer to that
@@ -148,22 +154,35 @@ exports.fetchView = async function(ctx) {
}
const response = await db.query(`database/${viewName}`, {
- include_docs: !stats,
+ include_docs: !calculation,
group,
})
- if (stats) {
+ if (!calculation) {
+ response.rows = response.rows.map(row => row.doc)
+ ctx.body = await linkRows.attachLinkInfo(instanceId, response.rows)
+ }
+
+ if (calculation === CALCULATION_TYPES.STATS) {
response.rows = response.rows.map(row => ({
group: row.key,
field,
...row.value,
avg: row.value.sum / row.value.count,
}))
- } else {
- response.rows = response.rows.map(row => row.doc)
+ ctx.body = response.rows
}
- ctx.body = await linkRows.attachLinkInfo(instanceId, response.rows)
+ if (
+ calculation === CALCULATION_TYPES.COUNT ||
+ calculation === CALCULATION_TYPES.SUM
+ ) {
+ ctx.body = response.rows.map(row => ({
+ group: row.key,
+ field,
+ value: row.value,
+ }))
+ }
}
exports.fetchTableRows = async function(ctx) {
diff --git a/packages/server/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap b/packages/server/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap
index cd66d040f3..dfe5f78c46 100644
--- a/packages/server/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap
+++ b/packages/server/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap
@@ -45,7 +45,7 @@ exports[`viewBuilder Filter creates a view with multiple filters and conjunction
Object {
"map": "function (doc) {
if (doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" && doc[\\"Name\\"] === \\"Test\\" || doc[\\"Yes\\"] > \\"Value\\") {
- emit(doc._id);
+ emit(doc[\\"_id\\"], doc[\\"undefined\\"]);
}
}",
"meta": Object {
@@ -86,6 +86,5 @@ Object {
"schema": null,
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
},
- "reduce": "_stats",
}
`;
diff --git a/packages/server/src/api/controllers/view/viewBuilder.js b/packages/server/src/api/controllers/view/viewBuilder.js
index df1c3daef9..e9fa29856d 100644
--- a/packages/server/src/api/controllers/view/viewBuilder.js
+++ b/packages/server/src/api/controllers/view/viewBuilder.js
@@ -1,5 +1,6 @@
const TOKEN_MAP = {
EQUALS: "===",
+ NOT_EQUALS: "!==",
LT: "<",
LTE: "<=",
MT: ">",
@@ -22,6 +23,14 @@ const FIELD_PROPERTY = {
}
const SCHEMA_MAP = {
+ sum: {
+ field: "string",
+ value: "number",
+ },
+ count: {
+ field: "string",
+ value: "number",
+ },
stats: {
sum: {
type: "number",
@@ -80,8 +89,7 @@ function parseFilterExpression(filters) {
* @param {String?} groupBy - field to group calculation results on, if any
*/
function parseEmitExpression(field, groupBy) {
- if (field) return `emit(doc["${groupBy || "_id"}"], doc["${field}"]);`
- return `emit(doc._id);`
+ return `emit(doc["${groupBy || "_id"}"], doc["${field}"]);`
}
/**
@@ -101,7 +109,7 @@ function viewTemplate({ field, tableId, groupBy, filters = [], calculation }) {
const emitExpression = parseEmitExpression(field, groupBy)
- const reduction = field ? { reduce: "_stats" } : {}
+ const reduction = field && calculation ? { reduce: `_${calculation}` } : {}
let schema = null
diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.js
index 3c10c7e320..1265b37f5b 100644
--- a/packages/server/src/api/routes/tests/view.spec.js
+++ b/packages/server/src/api/routes/tests/view.spec.js
@@ -18,6 +18,7 @@ describe("/views", () => {
const createView = async (config = {
name: "TestView",
field: "Price",
+ calculation: "stats",
tableId: table._id
}) =>
await request
@@ -65,20 +66,30 @@ describe("/views", () => {
expect(updatedTable.views).toEqual({
TestView: {
field: "Price",
+ calculation: "stats",
tableId: table._id,
filters: [],
schema: {
- name: {
- type: "string",
- constraints: {
- type: "string"
- },
+ sum: {
+ type: "number",
},
- description: {
+ min: {
+ type: "number",
+ },
+ max: {
+ type: "number",
+ },
+ count: {
+ type: "number",
+ },
+ sumsqr: {
+ type: "number",
+ },
+ avg: {
+ type: "number",
+ },
+ field: {
type: "string",
- constraints: {
- type: "string"
- },
},
}
}
@@ -123,7 +134,7 @@ describe("/views", () => {
Price: 4000
})
const res = await request
- .get(`/api/views/TestView?stats=true`)
+ .get(`/api/views/TestView?calculation=stats`)
.set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/)
.expect(200)
@@ -133,6 +144,7 @@ describe("/views", () => {
it("returns data for the created view using a group by", async () => {
await createView({
+ calculation: "stats",
name: "TestView",
field: "Price",
groupBy: "Category",
@@ -154,10 +166,11 @@ describe("/views", () => {
Category: "Two"
})
const res = await request
- .get(`/api/views/TestView?stats=true&group=Category`)
+ .get(`/api/views/TestView?calculation=stats&group=Category`)
.set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/)
.expect(200)
+
expect(res.body.length).toBe(2)
expect(res.body).toMatchSnapshot()
})
diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js
index aebe6c6bd7..ba5da28f63 100644
--- a/packages/server/src/middleware/authenticated.js
+++ b/packages/server/src/middleware/authenticated.js
@@ -34,13 +34,15 @@ module.exports = async (ctx, next) => {
let appId = process.env.CLOUD ? ctx.subdomains[1] : ctx.params.appId
- if (!appId) {
- appId = ctx.referer && ctx.referer.split("/").pop()
+ // if appId can't be determined from path param or subdomain
+ if (!appId && ctx.request.headers.referer) {
+ const url = new URL(ctx.request.headers.referer)
+ // remove leading and trailing slashes from appId
+ appId = url.pathname.replace(/\//g, "")
}
ctx.user = {
- // if appId can't be determined from path param or subdomain
- appId: appId,
+ appId,
}
await next()
return
diff --git a/packages/standard-components/src/DataGrid/AttachmentCell/Button.svelte b/packages/standard-components/src/DataGrid/AttachmentCell/Button.svelte
index 139ffe02aa..12f3cc8e9f 100644
--- a/packages/standard-components/src/DataGrid/AttachmentCell/Button.svelte
+++ b/packages/standard-components/src/DataGrid/AttachmentCell/Button.svelte
@@ -3,4 +3,4 @@
export let files
-
\ No newline at end of file
+
diff --git a/packages/standard-components/src/DataGrid/Component.svelte b/packages/standard-components/src/DataGrid/Component.svelte
index 0d86808961..9d249b2929 100644
--- a/packages/standard-components/src/DataGrid/Component.svelte
+++ b/packages/standard-components/src/DataGrid/Component.svelte
@@ -12,20 +12,25 @@
import AgGrid from "@budibase/svelte-ag-grid"
import CreateRowButton from "./CreateRow/Button.svelte"
- import { TextButton as DeleteButton, Icon, Modal, ModalContent } from "@budibase/bbui"
+ import {
+ TextButton as DeleteButton,
+ Icon,
+ Modal,
+ ModalContent,
+ } from "@budibase/bbui"
export let _bb
export let datasource = {}
export let editable
export let theme = "alpine"
export let height = 500
- export let pagination
+ export let pagination = true
// These can never change at runtime so don't need to be reactive
let canEdit = editable && datasource && datasource.type !== "view"
let canAddDelete = editable && datasource && datasource.type === "table"
- let modal;
+ let modal
let store = _bb.store
let dataLoaded = false
@@ -153,7 +158,10 @@
on:select={({ detail }) => (selectedRows = detail)} />
{/if}
-
+
Are you sure you want to delete {selectedRows.length} row(s)?
diff --git a/packages/standard-components/src/Test/TestApp.svelte b/packages/standard-components/src/Test/TestApp.svelte
index 13504e1e15..0c6da55601 100644
--- a/packages/standard-components/src/Test/TestApp.svelte
+++ b/packages/standard-components/src/Test/TestApp.svelte
@@ -35,7 +35,7 @@
{#await _appPromise}
loading
-{:then _bb}
+{:then _}
{/await}
diff --git a/packages/standard-components/src/attachments/AttachmentList.svelte b/packages/standard-components/src/attachments/AttachmentList.svelte
index e52eeef5a7..64a44ee53d 100644
--- a/packages/standard-components/src/attachments/AttachmentList.svelte
+++ b/packages/standard-components/src/attachments/AttachmentList.svelte
@@ -1,6 +1,6 @@
@@ -31,12 +31,19 @@
{:else}{/if}
{file.name}
-
+
+
+
{/each}
-
+
Are you sure you want to delete this attachment?
@@ -67,7 +74,7 @@
position: relative;
}
- button {
+ button {
display: block;
box-sizing: border-box;
position: absolute;
@@ -85,18 +92,18 @@
border-radius: var(--border-radius-xl);
background: black;
transition: transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1),
- background 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
+ background 0.2s cubic-bezier(0.25, 0.1, 0.25, 1);
-webkit-appearance: none;
outline: none;
- }
- button:hover {
- background-color: var(--grey-8);
- cursor: pointer;
- }
- button:active {
- background-color: var(--grey-9);
- cursor: pointer;
- }
+ }
+ button:hover {
+ background-color: var(--grey-8);
+ cursor: pointer;
+ }
+ button:active {
+ background-color: var(--grey-9);
+ cursor: pointer;
+ }
.file {
position: relative;