cypress tests, lint, feature complete
This commit is contained in:
parent
40bf90c745
commit
1d56d9a2ce
|
@ -1,4 +1,7 @@
|
||||||
|
|
||||||
context('Create a View', () => {
|
context('Create a View', () => {
|
||||||
|
const TOTAL_RECORDS = 6
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.visit('localhost:4001/_builder')
|
cy.visit('localhost:4001/_builder')
|
||||||
cy.createApp('View App', 'View App Description')
|
cy.createApp('View App', 'View App Description')
|
||||||
|
@ -7,6 +10,7 @@ context('Create a View', () => {
|
||||||
cy.addColumn('data', 'age', 'Number')
|
cy.addColumn('data', 'age', 'Number')
|
||||||
cy.addColumn('data', 'rating', 'Number')
|
cy.addColumn('data', 'rating', 'Number')
|
||||||
|
|
||||||
|
// 6 Records
|
||||||
cy.addRecord(["Students", 25, 1])
|
cy.addRecord(["Students", 25, 1])
|
||||||
cy.addRecord(["Students", 20, 3])
|
cy.addRecord(["Students", 20, 3])
|
||||||
cy.addRecord(["Students", 18, 6])
|
cy.addRecord(["Students", 18, 6])
|
||||||
|
@ -15,11 +19,39 @@ context('Create a View', () => {
|
||||||
cy.addRecord(["Teachers", 36, 3])
|
cy.addRecord(["Teachers", 36, 3])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('creates a view', () => {
|
||||||
it('creates a stats view based on age', () => {
|
|
||||||
cy.contains("Create New View").click()
|
cy.contains("Create New View").click()
|
||||||
cy.get("[placeholder='View Name']").type("Test View")
|
cy.get("[placeholder='View Name']").type("Test View")
|
||||||
cy.contains("Save View").click()
|
cy.contains("Save View").click()
|
||||||
|
cy.get(".title").contains("Test View")
|
||||||
|
cy.get("thead th").should(($headers) => {
|
||||||
|
expect($headers).to.have.length(3)
|
||||||
|
const headers = $headers.map((i, header) => Cypress.$(header).text())
|
||||||
|
expect(headers.get()).to.deep.eq([
|
||||||
|
"group",
|
||||||
|
"age",
|
||||||
|
"rating"
|
||||||
|
])
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters the view by age over 10', () => {
|
||||||
|
cy.contains("Filter").click()
|
||||||
|
cy.contains("Add Filter").click()
|
||||||
|
cy.get(".menu-container").find("select").first().select("age")
|
||||||
|
cy.get(".menu-container").find("select").eq(1).select("More Than")
|
||||||
|
cy.get("input[placeholder='age']").type(18)
|
||||||
|
cy.contains("Save").click()
|
||||||
|
cy.get("tbody tr").should(($values) => {
|
||||||
|
expect($values).to.have.length(5)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a stats calculation view based on age', () => {
|
||||||
|
cy.contains("Calculate").click()
|
||||||
|
cy.get(".menu-container").find("select").first().select("Statistics")
|
||||||
|
cy.get(".menu-container").find("select").eq(1).select("age")
|
||||||
|
cy.contains("Save").click()
|
||||||
cy.get("thead th").should(($headers) => {
|
cy.get("thead th").should(($headers) => {
|
||||||
expect($headers).to.have.length(7)
|
expect($headers).to.have.length(7)
|
||||||
const headers = $headers.map((i, header) => Cypress.$(header).text())
|
const headers = $headers.map((i, header) => Cypress.$(header).text())
|
||||||
|
@ -28,8 +60,8 @@ context('Create a View', () => {
|
||||||
"sum",
|
"sum",
|
||||||
"min",
|
"min",
|
||||||
"max",
|
"max",
|
||||||
"sumsqr",
|
|
||||||
"count",
|
"count",
|
||||||
|
"sumsqr",
|
||||||
"avg",
|
"avg",
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
@ -37,17 +69,17 @@ context('Create a View', () => {
|
||||||
const values = $values.map((i, value) => Cypress.$(value).text())
|
const values = $values.map((i, value) => Cypress.$(value).text())
|
||||||
expect(values.get()).to.deep.eq([
|
expect(values.get()).to.deep.eq([
|
||||||
"null",
|
"null",
|
||||||
"173",
|
"155",
|
||||||
"18",
|
"20",
|
||||||
"49",
|
"49",
|
||||||
"5671",
|
"5",
|
||||||
"6",
|
"5347",
|
||||||
"28.833333333333332"
|
"31"
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('groups the stats view by group', () => {
|
it('groups the view by group', () => {
|
||||||
cy.contains("Group By").click()
|
cy.contains("Group By").click()
|
||||||
cy.get("select").select("group")
|
cy.get("select").select("group")
|
||||||
cy.contains("Save").click()
|
cy.contains("Save").click()
|
||||||
|
@ -58,12 +90,12 @@ context('Create a View', () => {
|
||||||
const values = $values.map((i, value) => Cypress.$(value).text())
|
const values = $values.map((i, value) => Cypress.$(value).text())
|
||||||
expect(values.get()).to.deep.eq([
|
expect(values.get()).to.deep.eq([
|
||||||
"Students",
|
"Students",
|
||||||
"88",
|
"70",
|
||||||
"18",
|
"20",
|
||||||
"25",
|
"25",
|
||||||
"1974",
|
"3",
|
||||||
"4",
|
"1650",
|
||||||
"22"
|
"23.333333333333332"
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -77,6 +109,7 @@ context('Create a View', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('deletes a view', () => {
|
it('deletes a view', () => {
|
||||||
|
cy.contains("[data-cy=model-nav-item]", "Test View Updated").click()
|
||||||
cy.contains("[data-cy=model-nav-item]", "Test View Updated").find(".ri-more-line").click()
|
cy.contains("[data-cy=model-nav-item]", "Test View Updated").find(".ri-more-line").click()
|
||||||
cy.contains("Delete").click()
|
cy.contains("Delete").click()
|
||||||
cy.get(".content").contains("button", "Delete").click()
|
cy.get(".content").contains("button", "Delete").click()
|
||||||
|
|
|
@ -9,10 +9,6 @@ const INITIAL_BACKEND_UI_STATE = {
|
||||||
selectedDatabase: {},
|
selectedDatabase: {},
|
||||||
selectedModel: {},
|
selectedModel: {},
|
||||||
draftModel: {},
|
draftModel: {},
|
||||||
tabs: {
|
|
||||||
SETUP_PANEL: "SETUP",
|
|
||||||
NAVIGATION_PANEL: "NAVIGATE",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBackendUiStore = () => {
|
export const getBackendUiStore = () => {
|
||||||
|
@ -128,7 +124,12 @@ export const getBackendUiStore = () => {
|
||||||
},
|
},
|
||||||
save: async view => {
|
save: async view => {
|
||||||
const response = await api.post(`/api/views`, view)
|
const response = await api.post(`/api/views`, view)
|
||||||
const viewMeta = await response.json()
|
const json = await response.json()
|
||||||
|
|
||||||
|
const viewMeta = {
|
||||||
|
name: view.name,
|
||||||
|
...json,
|
||||||
|
}
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const viewModel = state.models.find(
|
const viewModel = state.models.find(
|
||||||
|
@ -136,13 +137,10 @@ export const getBackendUiStore = () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (view.originalName) delete viewModel.views[view.originalName]
|
if (view.originalName) delete viewModel.views[view.originalName]
|
||||||
viewModel.views[view.name] = viewMeta
|
viewModel.views[view.name] = viewMeta
|
||||||
|
|
||||||
state.models = state.models
|
state.models = state.models
|
||||||
state.selectedView = {
|
state.selectedView = viewMeta
|
||||||
name: view.name,
|
|
||||||
...viewMeta
|
|
||||||
}
|
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -159,11 +157,3 @@ export const getBackendUiStore = () => {
|
||||||
|
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store Actions
|
|
||||||
export const createDatabaseForApp = store => appInstance => {
|
|
||||||
store.update(state => {
|
|
||||||
state.appInstances.push(appInstance)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { values, cloneDeep } from "lodash/fp"
|
import { values, cloneDeep } from "lodash/fp"
|
||||||
import { get_capitalised_name } from "../../helpers"
|
import { get_capitalised_name } from "../../helpers"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import * as backendStoreActions from "./backend"
|
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import api from "../api"
|
import api from "../api"
|
||||||
import { DEFAULT_PAGES_OBJECT } from "../../constants"
|
import { DEFAULT_PAGES_OBJECT } from "../../constants"
|
||||||
|
@ -50,8 +49,6 @@ export const getStore = () => {
|
||||||
|
|
||||||
store.setPackage = setPackage(store, initial)
|
store.setPackage = setPackage(store, initial)
|
||||||
|
|
||||||
store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store)
|
|
||||||
|
|
||||||
store.saveScreen = saveScreen(store)
|
store.saveScreen = saveScreen(store)
|
||||||
store.setCurrentScreen = setCurrentScreen(store)
|
store.setCurrentScreen = setCurrentScreen(store)
|
||||||
store.setCurrentPage = setCurrentPage(store)
|
store.setCurrentPage = setCurrentPage(store)
|
||||||
|
|
|
@ -19,19 +19,21 @@
|
||||||
import GroupByPopover from "./popovers/GroupBy.svelte"
|
import GroupByPopover from "./popovers/GroupBy.svelte"
|
||||||
import FilterPopover from "./popovers/Filter.svelte"
|
import FilterPopover from "./popovers/Filter.svelte"
|
||||||
|
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
|
||||||
let data = []
|
let data = []
|
||||||
|
|
||||||
$: ({ name, groupBy, filters, field } = view)
|
$: name = view.name
|
||||||
|
$: filters = view.filters
|
||||||
|
$: field = view.field
|
||||||
|
$: groupBy = view.groupBy
|
||||||
$: !name.startsWith("all_") && filters && fetchViewData(name, field, groupBy)
|
$: !name.startsWith("all_") && filters && fetchViewData(name, field, groupBy)
|
||||||
|
|
||||||
async function fetchViewData(name, field, groupBy) {
|
async function fetchViewData(name, field, groupBy) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams()
|
||||||
|
|
||||||
if (field) params.set("stats", true);
|
if (field) params.set("stats", true)
|
||||||
if (groupBy) params.set("group", groupBy);
|
if (groupBy) params.set("group", groupBy)
|
||||||
|
|
||||||
let QUERY_VIEW_URL = `/api/views/${name}?${params}`
|
let QUERY_VIEW_URL = `/api/views/${name}?${params}`
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,25 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, Select } from "@budibase/bbui"
|
import { Input, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
export let value
|
|
||||||
export let meta
|
export let meta
|
||||||
|
export let value = meta.type === "boolean" ? false : ""
|
||||||
|
|
||||||
const isSelect = meta =>
|
let isSelect =
|
||||||
meta.type === "string" &&
|
meta.type === "string" &&
|
||||||
meta.constraints &&
|
meta.constraints &&
|
||||||
meta.constraints.inclusion &&
|
meta.constraints.inclusion &&
|
||||||
meta.constraints.inclusion.length > 0
|
meta.constraints.inclusion.length > 0
|
||||||
|
|
||||||
let type = determineInputType(meta)
|
let type = determineInputType(meta)
|
||||||
let options = determineOptions(meta)
|
|
||||||
|
|
||||||
value = value || type === "checkbox" ? false : ""
|
|
||||||
|
|
||||||
function determineInputType(meta) {
|
function determineInputType(meta) {
|
||||||
if (meta.type === "datetime") return "date"
|
if (meta.type === "datetime") return "date"
|
||||||
if (meta.type === "number") return "number"
|
if (meta.type === "number") return "number"
|
||||||
if (meta.type === "boolean") return "checkbox"
|
if (meta.type === "boolean") return "checkbox"
|
||||||
if (isSelect(meta)) return "select"
|
if (isSelect) return "select"
|
||||||
|
|
||||||
return "text"
|
return "text"
|
||||||
}
|
}
|
||||||
|
|
||||||
function determineOptions(meta) {
|
|
||||||
return isSelect(meta) ? meta.constraints.inclusion : []
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleInput = event => {
|
const handleInput = event => {
|
||||||
if (event.target.type === "checkbox") {
|
if (event.target.type === "checkbox") {
|
||||||
value = event.target.checked
|
value = event.target.checked
|
||||||
|
@ -46,7 +38,7 @@
|
||||||
{#if type === 'select'}
|
{#if type === 'select'}
|
||||||
<Select thin secondary data-cy="{meta.name}-select" bind:value>
|
<Select thin secondary data-cy="{meta.name}-select" bind:value>
|
||||||
<option />
|
<option />
|
||||||
{#each options as opt}
|
{#each meta.constraints.inclusion as opt}
|
||||||
<option value={opt}>{opt}</option>
|
<option value={opt}>{opt}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
{
|
{
|
||||||
name: "Statistics",
|
name: "Statistics",
|
||||||
key: "stats",
|
key: "stats",
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export let view = {}
|
export let view = {}
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
|
|
||||||
.input-group-row {
|
.input-group-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 50px 1fr 20px 1fr;
|
grid-template-columns: 75px 1fr 20px 1fr;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
margin-bottom: var(--spacing-l);
|
margin-bottom: var(--spacing-l);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -47,10 +47,10 @@
|
||||||
selected={selectedView === viewName}
|
selected={selectedView === viewName}
|
||||||
title={viewName}
|
title={viewName}
|
||||||
icon="ri-eye-line"
|
icon="ri-eye-line"
|
||||||
on:click={() => selectView({
|
on:click={() => (selectedView === viewName ? {} : selectView({
|
||||||
name: viewName,
|
name: viewName,
|
||||||
...model.views[viewName],
|
...model.views[viewName],
|
||||||
})}>
|
}))}>
|
||||||
<EditViewPopover
|
<EditViewPopover
|
||||||
view={{ name: viewName, ...model.views[viewName] }} />
|
view={{ name: viewName, ...model.views[viewName] }} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
|
@ -46,10 +46,10 @@ const controller = {
|
||||||
// add views to model document
|
// add views to model document
|
||||||
const model = await db.get(ctx.request.body.modelId)
|
const model = await db.get(ctx.request.body.modelId)
|
||||||
if (!model.views) model.views = {}
|
if (!model.views) model.views = {}
|
||||||
if (!view.meta.schema) {
|
if (!view.meta.schema) {
|
||||||
view.meta.schema = model.schema
|
view.meta.schema = model.schema
|
||||||
}
|
}
|
||||||
model.views[viewToSave.name] = view.meta
|
model.views[viewToSave.name] = view.meta
|
||||||
|
|
||||||
if (originalName) {
|
if (originalName) {
|
||||||
delete model.views[originalName]
|
delete model.views[originalName]
|
||||||
|
|
|
@ -1,5 +1,46 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`viewBuilder Calculate creates a view with the calculation statistics schema 1`] = `
|
||||||
|
Object {
|
||||||
|
"map": "function (doc) {
|
||||||
|
if (doc.modelId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" ) {
|
||||||
|
emit(doc[\\"_id\\"], doc[\\"myField\\"]);
|
||||||
|
}
|
||||||
|
}",
|
||||||
|
"meta": Object {
|
||||||
|
"calculation": "stats",
|
||||||
|
"field": "myField",
|
||||||
|
"filters": Array [],
|
||||||
|
"groupBy": undefined,
|
||||||
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
|
"schema": Object {
|
||||||
|
"avg": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"count": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"group": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"max": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"min": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"sum": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"sumsqr": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"reduce": "_stats",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`viewBuilder Filter creates a view with multiple filters and conjunctions 1`] = `
|
exports[`viewBuilder Filter creates a view with multiple filters and conjunctions 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"map": "function (doc) {
|
"map": "function (doc) {
|
||||||
|
@ -8,6 +49,7 @@ Object {
|
||||||
}
|
}
|
||||||
}",
|
}",
|
||||||
"meta": Object {
|
"meta": Object {
|
||||||
|
"calculation": undefined,
|
||||||
"field": undefined,
|
"field": undefined,
|
||||||
"filters": Array [
|
"filters": Array [
|
||||||
Object {
|
Object {
|
||||||
|
@ -24,16 +66,8 @@ Object {
|
||||||
],
|
],
|
||||||
"groupBy": undefined,
|
"groupBy": undefined,
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"schema": Object {
|
"schema": undefined,
|
||||||
"avg": "number",
|
|
||||||
"count": "number",
|
|
||||||
"max": "number",
|
|
||||||
"min": "number",
|
|
||||||
"sum": "number",
|
|
||||||
"sumsqr": "number",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"reduce": "_stats",
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -45,18 +79,12 @@ Object {
|
||||||
}
|
}
|
||||||
}",
|
}",
|
||||||
"meta": Object {
|
"meta": Object {
|
||||||
|
"calculation": undefined,
|
||||||
"field": "score",
|
"field": "score",
|
||||||
"filters": Array [],
|
"filters": Array [],
|
||||||
"groupBy": "age",
|
"groupBy": "age",
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"schema": Object {
|
"schema": undefined,
|
||||||
"avg": "number",
|
|
||||||
"count": "number",
|
|
||||||
"max": "number",
|
|
||||||
"min": "number",
|
|
||||||
"sum": "number",
|
|
||||||
"sumsqr": "number",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"reduce": "_stats",
|
"reduce": "_stats",
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const statsViewTemplate = require("../viewBuilder");
|
const viewTemplate = require("../viewBuilder");
|
||||||
|
|
||||||
describe("viewBuilder", () => {
|
describe("viewBuilder", () => {
|
||||||
|
|
||||||
describe("Filter", () => {
|
describe("Filter", () => {
|
||||||
it("creates a view with multiple filters and conjunctions", () => {
|
it("creates a view with multiple filters and conjunctions", () => {
|
||||||
expect(statsViewTemplate({
|
expect(viewTemplate({
|
||||||
"name": "Test View",
|
"name": "Test View",
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"filters": [{
|
"filters": [{
|
||||||
|
@ -22,17 +22,20 @@ describe("viewBuilder", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Calculate", () => {
|
describe("Calculate", () => {
|
||||||
expect(statsViewTemplate({
|
it("creates a view with the calculation statistics schema", () => {
|
||||||
|
expect(viewTemplate({
|
||||||
"name": "Calculate View",
|
"name": "Calculate View",
|
||||||
"field": "myField",
|
"field": "myField",
|
||||||
|
"calculation": "stats",
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"filters": []
|
"filters": []
|
||||||
})).toMatchSnapshot()
|
})).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Group By", () => {
|
describe("Group By", () => {
|
||||||
it("creates a view emitting the group by field", () => {
|
it("creates a view emitting the group by field", () => {
|
||||||
expect(statsViewTemplate({
|
expect(viewTemplate({
|
||||||
"name": "Test Scores Grouped By Age",
|
"name": "Test Scores Grouped By Age",
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"groupBy": "age",
|
"groupBy": "age",
|
||||||
|
|
|
@ -12,27 +12,27 @@ const TOKEN_MAP = {
|
||||||
const SCHEMA_MAP = {
|
const SCHEMA_MAP = {
|
||||||
stats: {
|
stats: {
|
||||||
group: {
|
group: {
|
||||||
type: "string"
|
type: "string",
|
||||||
},
|
},
|
||||||
sum: {
|
sum: {
|
||||||
type: "number"
|
type: "number",
|
||||||
},
|
},
|
||||||
min: {
|
min: {
|
||||||
type: "number"
|
type: "number",
|
||||||
},
|
},
|
||||||
max: {
|
max: {
|
||||||
type: "number"
|
type: "number",
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
type: "number"
|
type: "number",
|
||||||
},
|
},
|
||||||
sumsqr: {
|
sumsqr: {
|
||||||
type: "number"
|
type: "number",
|
||||||
},
|
},
|
||||||
avg: {
|
avg: {
|
||||||
type: "number"
|
type: "number",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,37 +45,46 @@ function parseFilterExpression(filters) {
|
||||||
const expression = []
|
const expression = []
|
||||||
|
|
||||||
for (let filter of filters) {
|
for (let filter of filters) {
|
||||||
if (filter.conjunction) expression.push(TOKEN_MAP[filter.conjunction]);
|
if (filter.conjunction) expression.push(TOKEN_MAP[filter.conjunction])
|
||||||
|
|
||||||
if (filter.condition === "CONTAINS") {
|
if (filter.condition === "CONTAINS") {
|
||||||
expression.push(
|
expression.push(
|
||||||
`doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${
|
`doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${filter.value}")`
|
||||||
filter.value
|
)
|
||||||
}")`)
|
} else {
|
||||||
return
|
expression.push(
|
||||||
|
`doc["${filter.key}"] ${TOKEN_MAP[filter.condition]} "${filter.value}"`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
expression.push(`doc["${filter.key}"] ${TOKEN_MAP[filter.condition]} "${
|
|
||||||
filter.value
|
|
||||||
}"`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return expression.join(" ")
|
return expression.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a CouchDB compliant emit() expression that is used to emit the
|
||||||
|
* correct key/value pairs for custom views.
|
||||||
|
* @param {String?} field - field to use for calculations, if any
|
||||||
|
* @param {String?} groupBy - field to group calculation results on, if any
|
||||||
|
*/
|
||||||
function parseEmitExpression(field, groupBy) {
|
function parseEmitExpression(field, groupBy) {
|
||||||
if (field) return `emit(doc["${groupBy || "_id"}"], doc["${field}"]);`
|
if (field) return `emit(doc["${groupBy || "_id"}"], doc["${field}"]);`
|
||||||
return `emit(doc._id);`
|
return `emit(doc._id);`
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewTemplate({
|
/**
|
||||||
field,
|
* Return a fully parsed CouchDB compliant view definition
|
||||||
modelId,
|
* that will be stored in the design document in the database.
|
||||||
groupBy,
|
*
|
||||||
filters = [],
|
* @param {Object} viewDefinition - the JSON definition for a custom view.
|
||||||
calculation
|
* field: field that calculations will be performed on
|
||||||
}) {
|
* modelId: modelId of the model this view was created from
|
||||||
const parsedFilters = parseFilterExpression(filters)
|
* groupBy: field that calculations will be grouped by. Field must be present for this to be useful
|
||||||
|
* filters: Array of filter objects containing predicates that are parsed into a JS expression
|
||||||
|
* calculation: an optional calculation to be performed over the view data.
|
||||||
|
*/
|
||||||
|
function viewTemplate({ field, modelId, groupBy, filters = [], calculation }) {
|
||||||
|
const parsedFilters = parseFilterExpression(filters)
|
||||||
const filterExpression = parsedFilters ? `&& ${parsedFilters}` : ""
|
const filterExpression = parsedFilters ? `&& ${parsedFilters}` : ""
|
||||||
|
|
||||||
const emitExpression = parseEmitExpression(field, groupBy)
|
const emitExpression = parseEmitExpression(field, groupBy)
|
||||||
|
@ -89,14 +98,14 @@ function viewTemplate({
|
||||||
groupBy,
|
groupBy,
|
||||||
filters,
|
filters,
|
||||||
schema: SCHEMA_MAP[calculation],
|
schema: SCHEMA_MAP[calculation],
|
||||||
calculation
|
calculation,
|
||||||
},
|
},
|
||||||
map: `function (doc) {
|
map: `function (doc) {
|
||||||
if (doc.modelId === "${modelId}" ${filterExpression}) {
|
if (doc.modelId === "${modelId}" ${filterExpression}) {
|
||||||
${emitExpression}
|
${emitExpression}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
...reduction
|
...reduction,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,13 @@
|
||||||
exports[`/views query returns data for the created view 1`] = `
|
exports[`/views query returns data for the created view 1`] = `
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"key": null,
|
"avg": 2333.3333333333335,
|
||||||
"value": Object {
|
"count": 3,
|
||||||
"avg": 2333.3333333333335,
|
"group": null,
|
||||||
"count": 3,
|
"max": 4000,
|
||||||
"max": 4000,
|
"min": 1000,
|
||||||
"min": 1000,
|
"sum": 7000,
|
||||||
"sum": 7000,
|
"sumsqr": 21000000,
|
||||||
"sumsqr": 21000000,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
@ -19,26 +17,22 @@ Array [
|
||||||
exports[`/views query returns data for the created view using a group by 1`] = `
|
exports[`/views query returns data for the created view using a group by 1`] = `
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"key": "One",
|
"avg": 1500,
|
||||||
"value": Object {
|
"count": 2,
|
||||||
"avg": 1500,
|
"group": "One",
|
||||||
"count": 2,
|
"max": 2000,
|
||||||
"max": 2000,
|
"min": 1000,
|
||||||
"min": 1000,
|
"sum": 3000,
|
||||||
"sum": 3000,
|
"sumsqr": 5000000,
|
||||||
"sumsqr": 5000000,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"key": "Two",
|
"avg": 4000,
|
||||||
"value": Object {
|
"count": 1,
|
||||||
"avg": 4000,
|
"group": "Two",
|
||||||
"count": 1,
|
"max": 4000,
|
||||||
"max": 4000,
|
"min": 4000,
|
||||||
"min": 4000,
|
"sum": 4000,
|
||||||
"sum": 4000,
|
"sumsqr": 16000000,
|
||||||
"sumsqr": 16000000,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -66,13 +66,14 @@ describe("/views", () => {
|
||||||
TestView: {
|
TestView: {
|
||||||
field: "Price",
|
field: "Price",
|
||||||
modelId: model._id,
|
modelId: model._id,
|
||||||
|
filters: [],
|
||||||
schema: {
|
schema: {
|
||||||
sum: "number",
|
name: {
|
||||||
min: "number",
|
type: "text",
|
||||||
max: "number",
|
constraints: {
|
||||||
count: "number",
|
type: "string"
|
||||||
sumsqr: "number",
|
}
|
||||||
avg: "number"
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue