view filters operational
This commit is contained in:
parent
ab5ab97d5e
commit
40bf90c745
|
@ -127,19 +127,22 @@ export const getBackendUiStore = () => {
|
|||
await store.actions.models.fetch()
|
||||
},
|
||||
save: async view => {
|
||||
await api.post(`/api/views`, view)
|
||||
const response = await api.post(`/api/views`, view)
|
||||
const viewMeta = await response.json()
|
||||
|
||||
store.update(state => {
|
||||
const viewModel = state.models.find(
|
||||
model => model._id === view.modelId
|
||||
)
|
||||
// TODO: Cleaner?
|
||||
if (!viewModel.views) viewModel.views = {}
|
||||
|
||||
if (view.originalName) delete viewModel.views[view.originalName]
|
||||
viewModel.views[view.name] = view
|
||||
viewModel.views[view.name] = viewMeta
|
||||
|
||||
state.models = state.models
|
||||
state.selectedView = view
|
||||
state.selectedView = {
|
||||
name: view.name,
|
||||
...viewMeta
|
||||
}
|
||||
return state
|
||||
})
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
import EditRowPopover from "./popovers/EditRow.svelte"
|
||||
import CalculationPopover from "./popovers/Calculate.svelte"
|
||||
|
||||
export let columns = []
|
||||
export let schema = []
|
||||
export let data = []
|
||||
export let title
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
|||
|
||||
let currentPage = 0
|
||||
|
||||
$: columns = schema ? Object.keys(schema) : []
|
||||
$: paginatedData =
|
||||
data && data.length
|
||||
? data.slice(
|
||||
|
@ -46,7 +47,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
{#each columns as header}
|
||||
<th>{header.name}</th>
|
||||
<th>{header}</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -57,7 +58,7 @@
|
|||
{#each paginatedData as row}
|
||||
<tr>
|
||||
{#each columns as header}
|
||||
<td>{getOr(row.default || '', header.key, row)}</td>
|
||||
<td>{getOr('', header, row)}</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
|
|
|
@ -19,57 +19,28 @@
|
|||
import GroupByPopover from "./popovers/GroupBy.svelte"
|
||||
import FilterPopover from "./popovers/Filter.svelte"
|
||||
|
||||
let COLUMNS = [
|
||||
{
|
||||
name: "group",
|
||||
key: "key",
|
||||
default: "All Records",
|
||||
},
|
||||
{
|
||||
name: "sum",
|
||||
key: "value.sum",
|
||||
},
|
||||
{
|
||||
name: "min",
|
||||
key: "value.min",
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
key: "value.max",
|
||||
},
|
||||
{
|
||||
name: "sumsqr",
|
||||
key: "value.sumsqr",
|
||||
},
|
||||
{
|
||||
name: "count",
|
||||
key: "value.count",
|
||||
},
|
||||
{
|
||||
name: "avg",
|
||||
key: "value.avg",
|
||||
},
|
||||
]
|
||||
|
||||
export let view = {}
|
||||
|
||||
let data = []
|
||||
|
||||
$: ({ name, groupBy, filters } = view)
|
||||
$: !name.startsWith("all_") && filters && fetchViewData(name, groupBy)
|
||||
$: ({ name, groupBy, filters, field } = view)
|
||||
$: !name.startsWith("all_") && filters && fetchViewData(name, field, groupBy)
|
||||
|
||||
async function fetchViewData(name, groupBy) {
|
||||
let QUERY_VIEW_URL = `/api/views/${name}?stats=true`
|
||||
if (groupBy) {
|
||||
QUERY_VIEW_URL += `&group=${groupBy}`
|
||||
}
|
||||
async function fetchViewData(name, field, groupBy) {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (field) params.set("stats", true);
|
||||
if (groupBy) params.set("group", groupBy);
|
||||
|
||||
let QUERY_VIEW_URL = `/api/views/${name}?${params}`
|
||||
|
||||
const response = await api.get(QUERY_VIEW_URL)
|
||||
data = await response.json()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Table title={decodeURI(view.name)} columns={COLUMNS} {data}>
|
||||
<Table title={decodeURI(view.name)} schema={view.schema} {data}>
|
||||
<FilterPopover {view} />
|
||||
<CalculationPopover {view} />
|
||||
<GroupByPopover {view} />
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
{
|
||||
name: "Statistics",
|
||||
key: "stats",
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
export let view = {}
|
||||
|
@ -50,12 +50,14 @@
|
|||
<div class="input-group-row">
|
||||
<p>The</p>
|
||||
<Select secondary thin bind:value={view.calculation}>
|
||||
<option value={null} />
|
||||
{#each CALCULATIONS as calculation}
|
||||
<option value={calculation.key}>{calculation.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
<p>of</p>
|
||||
<Select secondary thin bind:value={view.field}>
|
||||
<option value={null} />
|
||||
{#each fields as field}
|
||||
<option value={field}>{field}</option>
|
||||
{/each}
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
let anchor
|
||||
let dropdown
|
||||
let filters = view.filters
|
||||
let filters = view.filters || []
|
||||
|
||||
$: viewModel = $backendUiStore.models.find(
|
||||
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
||||
|
|
|
@ -51,11 +51,6 @@
|
|||
<h5>Create View</h5>
|
||||
<div class="input-group-column">
|
||||
<Input placeholder="View Name" thin bind:value={name} />
|
||||
<Select thin secondary bind:value={field}>
|
||||
{#each fields as field}
|
||||
<option value={field}>{field}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<Button secondary on:click={dropdown.hide}>Cancel</Button>
|
||||
|
|
|
@ -87,12 +87,11 @@ exports.fetchView = async function(ctx) {
|
|||
})
|
||||
|
||||
if (stats) {
|
||||
for (let row of response.rows) {
|
||||
row.value = {
|
||||
...row.value,
|
||||
avg: row.value.sum / row.value.count,
|
||||
}
|
||||
}
|
||||
response.rows = response.rows.map(row => ({
|
||||
group: row.key,
|
||||
...row.value,
|
||||
avg: row.value.sum / row.value.count,
|
||||
}))
|
||||
} else {
|
||||
response.rows = response.rows.map(row => row.doc)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const CouchDB = require("../../../db")
|
||||
const statsViewTemplate = require("./viewBuilder")
|
||||
const viewTemplate = require("./viewBuilder")
|
||||
|
||||
const controller = {
|
||||
fetch: async ctx => {
|
||||
|
@ -25,15 +25,15 @@ const controller = {
|
|||
},
|
||||
save: async ctx => {
|
||||
const db = new CouchDB(ctx.user.instanceId)
|
||||
const { originalName, ...newView } = ctx.request.body
|
||||
const { originalName, ...viewToSave } = ctx.request.body
|
||||
|
||||
const designDoc = await db.get("_design/database")
|
||||
|
||||
const view = statsViewTemplate(newView)
|
||||
const view = viewTemplate(viewToSave)
|
||||
|
||||
designDoc.views = {
|
||||
...designDoc.views,
|
||||
[newView.name]: view,
|
||||
[viewToSave.name]: view,
|
||||
}
|
||||
|
||||
// view has been renamed
|
||||
|
@ -45,10 +45,11 @@ const controller = {
|
|||
|
||||
// add views to model document
|
||||
const model = await db.get(ctx.request.body.modelId)
|
||||
model.views = {
|
||||
...(model.views ? model.views : {}),
|
||||
[newView.name]: view.meta,
|
||||
if (!model.views) model.views = {}
|
||||
if (!view.meta.schema) {
|
||||
view.meta.schema = model.schema
|
||||
}
|
||||
model.views[viewToSave.name] = view.meta
|
||||
|
||||
if (originalName) {
|
||||
delete model.views[originalName]
|
||||
|
@ -56,8 +57,8 @@ const controller = {
|
|||
|
||||
await db.put(model)
|
||||
|
||||
ctx.body = view
|
||||
ctx.message = `View ${newView.name} saved successfully.`
|
||||
ctx.body = model.views[viewToSave.name]
|
||||
ctx.message = `View ${viewToSave.name} saved successfully.`
|
||||
},
|
||||
destroy: async ctx => {
|
||||
const db = new CouchDB(ctx.user.instanceId)
|
||||
|
|
|
@ -5,7 +5,7 @@ describe("viewBuilder", () => {
|
|||
describe("Filter", () => {
|
||||
it("creates a view with multiple filters and conjunctions", () => {
|
||||
expect(statsViewTemplate({
|
||||
"name": "yeety",
|
||||
"name": "Test View",
|
||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||
"filters": [{
|
||||
"value": "Test",
|
||||
|
@ -22,7 +22,12 @@ describe("viewBuilder", () => {
|
|||
})
|
||||
|
||||
describe("Calculate", () => {
|
||||
|
||||
expect(statsViewTemplate({
|
||||
"name": "Calculate View",
|
||||
"field": "myField",
|
||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||
"filters": []
|
||||
})).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe("Group By", () => {
|
||||
|
|
|
@ -9,6 +9,32 @@ const TOKEN_MAP = {
|
|||
OR: "||",
|
||||
}
|
||||
|
||||
const SCHEMA_MAP = {
|
||||
stats: {
|
||||
group: {
|
||||
type: "string"
|
||||
},
|
||||
sum: {
|
||||
type: "number"
|
||||
},
|
||||
min: {
|
||||
type: "number"
|
||||
},
|
||||
max: {
|
||||
type: "number"
|
||||
},
|
||||
count: {
|
||||
type: "number"
|
||||
},
|
||||
sumsqr: {
|
||||
type: "number"
|
||||
},
|
||||
avg: {
|
||||
type: "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the array of filters to create a JS
|
||||
* expression that gets used in a CouchDB view.
|
||||
|
@ -42,35 +68,36 @@ function parseEmitExpression(field, groupBy) {
|
|||
return `emit(doc._id);`
|
||||
}
|
||||
|
||||
function statsViewTemplate({ field, modelId, groupBy, filters = [] }) {
|
||||
const filterExpression = parseFilterExpression(filters)
|
||||
function viewTemplate({
|
||||
field,
|
||||
modelId,
|
||||
groupBy,
|
||||
filters = [],
|
||||
calculation
|
||||
}) {
|
||||
const parsedFilters = parseFilterExpression(filters)
|
||||
const filterExpression = parsedFilters ? `&& ${parsedFilters}` : ""
|
||||
|
||||
const emitExpression = parseEmitExpression(field, groupBy)
|
||||
|
||||
const reduction = field ? { reduce: "_stats" } : {}
|
||||
|
||||
return {
|
||||
meta: {
|
||||
field,
|
||||
modelId,
|
||||
groupBy,
|
||||
filters,
|
||||
schema: {
|
||||
sum: "number",
|
||||
min: "number",
|
||||
max: "number",
|
||||
count: "number",
|
||||
sumsqr: "number",
|
||||
avg: "number",
|
||||
},
|
||||
schema: SCHEMA_MAP[calculation],
|
||||
calculation
|
||||
},
|
||||
map: `function (doc) {
|
||||
if (doc.modelId === "${modelId}" ${
|
||||
filterExpression ? `&& ${filterExpression}` : ""
|
||||
}) {
|
||||
if (doc.modelId === "${modelId}" ${filterExpression}) {
|
||||
${emitExpression}
|
||||
}
|
||||
}`,
|
||||
reduce: "_stats",
|
||||
...reduction
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = statsViewTemplate
|
||||
module.exports = viewTemplate
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue