diff --git a/packages/builder/src/builderStore/store/backend.js b/packages/builder/src/builderStore/store/backend.js
index 28272d9893..7c80f21353 100644
--- a/packages/builder/src/builderStore/store/backend.js
+++ b/packages/builder/src/builderStore/store/backend.js
@@ -59,7 +59,6 @@ export const getBackendUiStore = () => {
store.update(state => {
state.selectedModel = model
state.draftModel = cloneDeep(model)
- state.selectedField = ""
state.selectedView = `all_${model._id}`
return state
}),
@@ -87,10 +86,8 @@ export const getBackendUiStore = () => {
delete: async model => {
await api.delete(`/api/models/${model._id}/${model._rev}`)
store.update(state => {
- state.models = state.models.filter(
- existing => existing._id !== model._id
- )
- state.selectedModel = state.models[0] || {}
+ state.models = state.models.filter(existing => existing._id !== model._id)
+ state.selectedModel = state.models[0] || {}
return state
})
},
@@ -113,14 +110,24 @@ export const getBackendUiStore = () => {
store.actions.models.save(state.draftModel)
return state
})
- },
+ }
},
views: {
select: view =>
store.update(state => {
state.selectedView = view
+ state.selectedModel = {}
return state
}),
+ save: async view => {
+ const response = await api.post(`/api/views`, view)
+ const savedView = await response.json()
+ await store.actions.models.fetch()
+ store.update(state => {
+ state.selectedView = view.name
+ return state
+ })
+ }
},
users: {
create: user =>
diff --git a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte
index fa36018ae8..3caa7e4678 100644
--- a/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte
+++ b/packages/builder/src/components/database/ModelDataTable/ModelDataTable.svelte
@@ -1,6 +1,7 @@
+
+
-
+
+
{title}
diff --git a/packages/builder/src/components/nav/ModelSetupNav/ModelSetupNav.svelte b/packages/builder/src/components/nav/ModelSetupNav/ModelSetupNav.svelte
deleted file mode 100644
index cdbe784421..0000000000
--- a/packages/builder/src/components/nav/ModelSetupNav/ModelSetupNav.svelte
+++ /dev/null
@@ -1,156 +0,0 @@
-
-
-
-
- {#if selectedTab === 'SETUP'}
- {#if $backendUiStore.selectedField}
-
- {:else if $backendUiStore.draftModel.schema}
-
-
-
-
-
- {/if}
-
- {:else if selectedTab === 'DELETE'}
-
-
-
-
- {/if}
-
-
-
-
diff --git a/packages/builder/src/components/nav/ModelSetupNav/index.js b/packages/builder/src/components/nav/ModelSetupNav/index.js
deleted file mode 100644
index e772d21707..0000000000
--- a/packages/builder/src/components/nav/ModelSetupNav/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default as ModelSetupNav } from "./ModelSetupNav.svelte"
diff --git a/packages/builder/src/pages/[application]/backend/_layout.svelte b/packages/builder/src/pages/[application]/backend/_layout.svelte
deleted file mode 100644
index 36fc25c869..0000000000
--- a/packages/builder/src/pages/[application]/backend/_layout.svelte
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
diff --git a/packages/server/src/api/controllers/view/customViews.js b/packages/server/src/api/controllers/view/customViews.js
deleted file mode 100644
index 66e643f809..0000000000
--- a/packages/server/src/api/controllers/view/customViews.js
+++ /dev/null
@@ -1,27 +0,0 @@
-const FORMULA_MAP = {
- SUM: "_sum",
- COUNT: "_count",
- STATS: "_stats"
-};
-
-function customViewTemplate({
- field,
- formula,
- modelId
-}) {
- return {
- meta: {
- field,
- formula,
- modelId
- },
- map: `function (doc) {
- if (doc.modelId === "${modelId}") {
- emit(doc._id, doc["${field}"]);
- }
- }`,
- reduce: "_stats"
- }
-}
-
-module.exports = customViewTemplate
\ No newline at end of file
diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js
index e65cfd97c3..a58bcf7ead 100644
--- a/packages/server/src/api/controllers/view/index.js
+++ b/packages/server/src/api/controllers/view/index.js
@@ -1,23 +1,25 @@
const CouchDB = require("../../../db")
-const customViewTemplate = require("./customViews");
+const statsViewTemplate = require("./viewBuilder");
const controller = {
query: async ctx => {
- // const db = new CouchDB(ctx.user.instanceId)
- const db = new CouchDB("inst_4e6f424_970ca7f2b9e24ec8896eb10862d7f22b")
+ const db = new CouchDB(ctx.user.instanceId)
+ const { meta } = ctx.request.body
const response = await db.query(`database/${ctx.params.viewName}`, {
- group: false
+ group: !!meta.groupBy
})
+ for (row of response.rows) {
+ row.value = {
+ ...row.value,
+ avg: row.value.sum / row.value.count
+ }
+ }
+
ctx.body = response.rows
- // ctx.body = {
- // ...data,
- // avg: data.sum / data.count
- // }
},
fetch: async ctx => {
- // const db = new CouchDB(ctx.user.instanceId)
- const db = new CouchDB("inst_4e6f424_970ca7f2b9e24ec8896eb10862d7f22b")
+ const db = new CouchDB(ctx.user.instanceId)
const designDoc = await db.get("_design/database")
const response = []
@@ -37,14 +39,13 @@ const controller = {
ctx.body = response
},
- create: async ctx => {
- // const db = new CouchDB(ctx.user.instanceId)
- const db = new CouchDB("inst_4e6f424_970ca7f2b9e24ec8896eb10862d7f22b")
+ save: async ctx => {
+ const db = new CouchDB(ctx.user.instanceId)
const newView = ctx.request.body
const designDoc = await db.get("_design/database")
- const view = customViewTemplate(ctx.request.body)
+ const view = statsViewTemplate(newView)
designDoc.views = {
...designDoc.views,
@@ -53,8 +54,17 @@ const controller = {
await db.put(designDoc)
- ctx.body = newView
- ctx.message = `View ${newView.name} created successfully.`
+
+ // add views to model document
+ const model = await db.get(ctx.request.body.modelId)
+ model.views = {
+ ...(model.views ? model.views : {}),
+ [newView.name]: view.meta
+ }
+ await db.put(model)
+
+ ctx.body = view
+ ctx.message = `View ${newView.name} saved successfully.`
},
destroy: async ctx => {
const db = new CouchDB(ctx.user.instanceId)
diff --git a/packages/server/src/api/controllers/view/viewBuilder.js b/packages/server/src/api/controllers/view/viewBuilder.js
new file mode 100644
index 0000000000..76ebd2c437
--- /dev/null
+++ b/packages/server/src/api/controllers/view/viewBuilder.js
@@ -0,0 +1,21 @@
+function statsViewTemplate({
+ field,
+ modelId,
+ groupBy
+}) {
+ return {
+ meta: {
+ field,
+ modelId,
+ groupBy
+ },
+ map: `function (doc) {
+ if (doc.modelId === "${modelId}") {
+ emit(doc["${groupBy || "_id"}"], doc["${field}"]);
+ }
+ }`,
+ reduce: "_stats"
+ }
+}
+
+module.exports = statsViewTemplate
\ No newline at end of file
diff --git a/packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap b/packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap
new file mode 100644
index 0000000000..96ce281ee1
--- /dev/null
+++ b/packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap
@@ -0,0 +1,44 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`/views query returns data for the created view 1`] = `
+Array [
+ Object {
+ "key": null,
+ "value": Object {
+ "avg": 2333.3333333333335,
+ "count": 3,
+ "max": 4000,
+ "min": 1000,
+ "sum": 7000,
+ "sumsqr": 21000000,
+ },
+ },
+]
+`;
+
+exports[`/views query returns data for the created view using a group by 1`] = `
+Array [
+ Object {
+ "key": "One",
+ "value": Object {
+ "avg": 1500,
+ "count": 2,
+ "max": 2000,
+ "min": 1000,
+ "sum": 3000,
+ "sumsqr": 5000000,
+ },
+ },
+ Object {
+ "key": "Two",
+ "value": Object {
+ "avg": 4000,
+ "count": 1,
+ "max": 4000,
+ "min": 4000,
+ "sum": 4000,
+ "sumsqr": 16000000,
+ },
+ },
+]
+`;
diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js
index e87233d458..594c4a3121 100644
--- a/packages/server/src/api/routes/tests/couchTestUtils.js
+++ b/packages/server/src/api/routes/tests/couchTestUtils.js
@@ -193,9 +193,6 @@ const createUserWithPermissions = async (
accessLevelId: accessRes.body._id,
})
- //const db = new CouchDB(instanceId)
- //const designDoc = await db.get("_design/database")
-
const anonUser = {
userId: "ANON",
accessLevelId: ANON_LEVEL_ID,
diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.js
index 5a889bed67..ef173a224a 100644
--- a/packages/server/src/api/routes/tests/view.spec.js
+++ b/packages/server/src/api/routes/tests/view.spec.js
@@ -4,7 +4,8 @@ const {
createInstance,
createModel,
supertest,
- defaultHeaders
+ defaultHeaders,
+ getDocument
} = require("./couchTestUtils")
describe("/views", () => {
@@ -14,6 +15,25 @@ describe("/views", () => {
let instance
let model
+ const createView = async (config = {
+ name: "TestView",
+ field: "Price",
+ modelId: model._id
+ }) =>
+ await request
+ .post(`/api/views`)
+ .send(config)
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ const createRecord = async record => request
+ .post(`/api/${model._id}/records`)
+ .send(record)
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+
beforeAll(async () => {
({ request, server } = await supertest())
await createClientDatabase(request)
@@ -28,46 +48,111 @@ describe("/views", () => {
server.close()
})
- const createView = async () =>
- await request
- .post(`/api/views`)
- .send({
- name: "TestView",
- map: `function(doc) {
- if (doc.id) {
- emit(doc.name, doc._id);
- }
- }`,
- reduce: `function(keys, values) { }`
- })
- .set(defaultHeaders(app._id, instance._id))
- .expect('Content-Type', /json/)
- .expect(200)
-
describe("create", () => {
+ beforeEach(async () => {
+ model = await createModel(request, app._id, instance._id);
+ })
it("returns a success message when the view is successfully created", async () => {
const res = await createView()
- expect(res.res.statusMessage).toEqual("View TestView created successfully.");
- expect(res.body.name).toEqual("TestView");
+ expect(res.res.statusMessage).toEqual("View TestView saved successfully.");
+ })
+
+ it("updates the model record with the new view metadata", async () => {
+ const res = await createView()
+ expect(res.res.statusMessage).toEqual("View TestView saved successfully.");
+ const updatedModel = await getDocument(instance._id, model._id)
+ expect(updatedModel.views).toEqual({
+ TestView: {
+ field: "Price",
+ modelId: model._id
+ }
+ });
})
});
describe("fetch", () => {
-
beforeEach(async () => {
model = await createModel(request, app._id, instance._id);
});
- it("should only return custom views", async () => {
- const view = await createView()
+ it("returns only custom views", async () => {
+ await createView()
const res = await request
.get(`/api/views`)
.set(defaultHeaders(app._id, instance._id))
.expect('Content-Type', /json/)
.expect(200)
expect(res.body.length).toBe(1)
- expect(res.body.find(v => v.name === view.body.name)).toBeDefined()
+ expect(res.body.find(({ name }) => name === "TestView")).toBeDefined()
+ })
+ });
+
+ describe("query", () => {
+ beforeEach(async () => {
+ model = await createModel(request, app._id, instance._id);
+ });
+
+ it("returns data for the created view", async () => {
+ await createView()
+ await createRecord({
+ modelId: model._id,
+ Price: 1000
+ })
+ await createRecord({
+ modelId: model._id,
+ Price: 2000
+ })
+ await createRecord({
+ modelId: model._id,
+ Price: 4000
+ })
+ const res = await request
+ .post(`/api/views/query/TestView`)
+ .send({
+ meta: {}
+ })
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+ expect(res.body.length).toBe(1)
+ expect(res.body).toMatchSnapshot()
+ })
+
+ it("returns data for the created view using a group by", async () => {
+ await createView({
+ name: "TestView",
+ field: "Price",
+ groupBy: "Category",
+ modelId: model._id
+ })
+ await createRecord({
+ modelId: model._id,
+ Price: 1000,
+ Category: "One"
+ })
+ await createRecord({
+ modelId: model._id,
+ Price: 2000,
+ Category: "One"
+ })
+ await createRecord({
+ modelId: model._id,
+ Price: 4000,
+ Category: "Two"
+ })
+ const res = await request
+ .post(`/api/views/query/TestView`)
+ .send({
+ meta: {
+ groupBy: "Category"
+ }
+ })
+ .set(defaultHeaders(app._id, instance._id))
+ .expect('Content-Type', /json/)
+ .expect(200)
+ expect(res.body.length).toBe(2)
+ expect(res.body).toMatchSnapshot()
})
});
});
\ No newline at end of file
diff --git a/packages/server/src/api/routes/view.js b/packages/server/src/api/routes/view.js
index aa839c0d96..549fc1659e 100644
--- a/packages/server/src/api/routes/view.js
+++ b/packages/server/src/api/routes/view.js
@@ -13,9 +13,7 @@ router
recordController.fetchView
)
.get("/api/views", viewController.fetch)
- .get("/api/views/query/:viewName", viewController.query)
- // .patch("/api/:databaseId/views", controller.update);
- // .delete("/api/:instanceId/views/:viewId/:revId", controller.destroy);
- .post("/api/views", viewController.create)
+ .post("/api/views/query/:viewName", viewController.query)
+ .post("/api/views", viewController.save)
module.exports = router
diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js
index 53cb0b2c13..e1ad397d7e 100644
--- a/packages/server/src/middleware/authenticated.js
+++ b/packages/server/src/middleware/authenticated.js
@@ -14,6 +14,10 @@ module.exports = async (ctx, next) => {
return
}
+ // ctx.user = {
+ // instanceId: "inst_4e6f424_970ca7f2b9e24ec8896eb10862d7f22b"
+ // }
+
const appToken = ctx.cookies.get("budibase:token")
const builderToken = ctx.cookies.get("builder:token")