group by complete

This commit is contained in:
Martin McKeaveney 2020-08-18 16:05:15 +01:00
parent 28b4b6fcb9
commit d08db301af
11 changed files with 155 additions and 54 deletions

View File

@ -93,10 +93,8 @@ export const getBackendUiStore = () => {
// delete the original if renaming // delete the original if renaming
delete state.draftModel.schema[originalName] delete state.draftModel.schema[originalName]
state.draftModel.schema = { state.draftModel.schema[field.name] = cloneDeep(field)
...state.draftModel.schema,
[field.name]: cloneDeep(field),
}
store.actions.models.save(state.draftModel) store.actions.models.save(state.draftModel)
return state return state
}) })
@ -126,8 +124,15 @@ export const getBackendUiStore = () => {
}, },
save: async view => { save: async view => {
await api.post(`/api/views`, view) await api.post(`/api/views`, view)
store.update(state => { store.update(state => {
state.selectedModel.views[view.name] = view 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
state.models = state.models
state.selectedView = view state.selectedView = view
return state return state
}) })

View File

@ -17,47 +17,62 @@
import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte" import ColumnHeaderPopover from "./popovers/ColumnHeader.svelte"
import EditRowPopover from "./popovers/EditRow.svelte" import EditRowPopover from "./popovers/EditRow.svelte"
import CalculationPopover from "./popovers/Calculate.svelte" import CalculationPopover from "./popovers/Calculate.svelte"
import GroupByPopover from "./popovers/GroupBy.svelte"
const COLUMNS = [ let COLUMNS = [
{
name: "Group",
key: "key",
},
{ {
name: "sum", name: "sum",
key: "value.sum", key: "value.sum",
}, },
{ {
name: "min", name: "min",
key: "value.min", key: "value.min"
}, },
{ {
name: "max", name: "max",
key: "value.max", key: "value.max"
}, },
{ {
name: "sumsqr", name: "sumsqr",
key: "value.sumsqr", key: "value.sumsqr"
}, },
{ {
name: "count", name: "count",
key: "value.count", key: "value.count"
}, },
{ {
name: "avg", name: "avg",
key: "value.avg", key: "value.avg"
}, }
] ]
export let view = {}
let data = [] let data = []
$: selectedView = $backendUiStore.selectedView $: viewName = view.name
$: !selectedView.name.startsWith("all_") && fetchViewData(selectedView) $: !viewName.startsWith("all_") && fetchViewData(viewName)
async function fetchViewData() { async function fetchViewData(viewName) {
const QUERY_VIEW_URL = `/api/views/${$backendUiStore.selectedView.name}?stats=true` let QUERY_VIEW_URL = `/api/views/${viewName}?stats=true`
if (view.groupBy) {
QUERY_VIEW_URL += `&group=${view.groupBy}`
}
const response = await api.get(QUERY_VIEW_URL) const response = await api.get(QUERY_VIEW_URL)
data = await response.json() data = await response.json()
} }
</script> </script>
<Table title={decodeURI(selectedView.name)} columns={COLUMNS} {data}> <Table
<CalculationPopover view={selectedView} /> title={decodeURI(view.name)}
columns={COLUMNS}
{data}
>
<CalculationPopover {view} />
<GroupByPopover {view} />
</Table> </Table>

View File

@ -4,7 +4,7 @@
import { store, backendUiStore } from "builderStore" import { store, backendUiStore } from "builderStore"
import * as api from "../api" import * as api from "../api"
export let view export let viewName
export let onClosed export let onClosed
</script> </script>
@ -24,7 +24,7 @@
<ActionButton <ActionButton
alert alert
on:click={async () => { on:click={async () => {
await backendUiStore.actions.views.delete(view) await backendUiStore.actions.views.delete(viewName)
notifier.danger('View deleted') notifier.danger('View deleted')
onClosed() onClosed()
}}> }}>

View File

@ -0,0 +1,80 @@
<script>
import { Popover, Button, Icon, Input, Select } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import CreateEditRecord from "../modals/CreateEditRecord.svelte"
const CALCULATIONS = [
{
name: "Statistics",
key: "stats",
},
]
export let view = {}
let anchor
let dropdown
$: viewModel = $backendUiStore.models.find(
({ _id }) => _id === $backendUiStore.selectedView.modelId
)
$: fields =
viewModel && Object.keys(viewModel.schema)
function saveView() {
backendUiStore.actions.views.save(view)
notifier.success(`View ${view.name} saved.`)
dropdown.hide()
}
</script>
<div bind:this={anchor}>
<Button text small on:click={dropdown.show}>
<Icon name="calculate" />
Group By
</Button>
</div>
<Popover bind:this={dropdown} {anchor} align="left">
<h5>Group By</h5>
<div class="input-group-row">
<p>Group By</p>
<Select secondary thin bind:value={view.groupBy}>
<option value={false}>None</option>
{#each fields as field}
<option value={field}>{field}</option>
{/each}
</Select>
</div>
<div class="button-group">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={saveView}>Save</Button>
</div>
</Popover>
<style>
h5 {
margin-bottom: var(--spacing-l);
font-weight: 500;
}
.button-group {
margin-top: var(--spacing-l);
display: flex;
justify-content: flex-end;
gap: var(--spacing-s);
}
.input-group-row {
display: grid;
grid-template-columns: 50px 1fr 20px 1fr;
gap: var(--spacing-s);
margin-bottom: var(--spacing-l);
align-items: center;
}
p {
margin: 0;
font-size: var(--font-size-xs);
}
</style>

View File

@ -28,7 +28,7 @@
open( open(
DeleteTableModal, DeleteTableModal,
{ {
onClosed: hideEditor, onClosed: close,
table, table,
}, },
{ styleContent: { padding: "0" } } { styleContent: { padding: "0" } }

View File

@ -1,6 +1,7 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
import { FIELDS } from "constants/backend" import { FIELDS } from "constants/backend"
import DeleteViewModal from "components/database/DataTable/modals/DeleteView.svelte" import DeleteViewModal from "components/database/DataTable/modals/DeleteView.svelte"
@ -13,6 +14,7 @@
let dropdown let dropdown
let editing let editing
let originalName = view.name
function showEditor() { function showEditor() {
editing = true editing = true
@ -29,14 +31,18 @@
DeleteViewModal, DeleteViewModal,
{ {
onClosed: close, onClosed: close,
view, viewName: view.name,
}, },
{ styleContent: { padding: "0" } } { styleContent: { padding: "0" } }
) )
} }
function save() { function save() {
backendUiStore.actions.views.save(view) backendUiStore.actions.views.save({
originalName,
...view
})
notifier.success("Renamed View Successfully.")
hideEditor() hideEditor()
} }
</script> </script>
@ -48,7 +54,7 @@
{#if editing} {#if editing}
<h5>Edit View</h5> <h5>Edit View</h5>
<div class="container"> <div class="container">
<Input placeholder="Table Name" thin bind:value={view} /> <Input placeholder="Table Name" thin bind:value={view.name} />
</div> </div>
<footer> <footer>
<div class="button-margin-3"> <div class="button-margin-3">

View File

@ -12,7 +12,8 @@
const { open, close } = getContext("simple-modal") const { open, close } = getContext("simple-modal")
$: selectedView = $backendUiStore.selectedView && $backendUiStore.selectedView.name $: selectedView =
$backendUiStore.selectedView && $backendUiStore.selectedView.name
function selectModel(model) { function selectModel(model) {
backendUiStore.actions.models.select(model) backendUiStore.actions.models.select(model)
@ -40,17 +41,18 @@
on:click={() => selectModel(model)}> on:click={() => selectModel(model)}>
<EditTablePopover table={model} /> <EditTablePopover table={model} />
</ListItem> </ListItem>
{#each Object.keys(model.views || {}) as view} {#each Object.keys(model.views || {}) as viewName}
<ListItem <ListItem
indented indented
selected={selectedView === view} selected={selectedView === viewName}
title={view} title={viewName}
icon="ri-eye-line" icon="ri-eye-line"
on:click={() => selectView({ on:click={() => selectView({
name: view, name: viewName,
...model.views[view] ...model.views[viewName],
})}> })}>
<EditViewPopover {view} /> <EditViewPopover
view={{ name: viewName, ...model.views[viewName] }} />
</ListItem> </ListItem>
{/each} {/each}
{/each} {/each}

View File

@ -13,7 +13,7 @@
</script> </script>
{#if $backendUiStore.selectedDatabase._id && selectedView} {#if $backendUiStore.selectedDatabase._id && selectedView}
<ViewDataTable /> <ViewDataTable view={selectedView} />
{:else} {:else}
<i>create your first table to start building</i> <i>create your first table to start building</i>
{/if} {/if}

View File

@ -80,10 +80,10 @@ exports.save = async function(ctx) {
exports.fetchView = async function(ctx) { exports.fetchView = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId) const db = new CouchDB(ctx.user.instanceId)
const { stats, groupBy } = ctx.query const { stats, group } = ctx.query
const response = await db.query(`database/${ctx.params.viewName}`, { const response = await db.query(`database/${ctx.params.viewName}`, {
include_docs: !stats, include_docs: !stats,
group: !!groupBy group
}) })
if (stats) { if (stats) {

View File

@ -2,22 +2,6 @@ const CouchDB = require("../../../db")
const statsViewTemplate = require("./viewBuilder"); const statsViewTemplate = require("./viewBuilder");
const controller = { const controller = {
query: async ctx => {
const db = new CouchDB(ctx.user.instanceId)
const { meta } = ctx.request.body
const response = await db.query(`database/${ctx.params.viewName}`, {
group: !!meta.groupBy
})
for (row of response.rows) {
row.value = {
...row.value,
avg: row.value.sum / row.value.count
}
}
ctx.body = response.rows
},
fetch: async ctx => { fetch: async ctx => {
const db = new CouchDB(ctx.user.instanceId) const db = new CouchDB(ctx.user.instanceId)
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
@ -41,7 +25,7 @@ const controller = {
}, },
save: async ctx => { save: async ctx => {
const db = new CouchDB(ctx.user.instanceId) const db = new CouchDB(ctx.user.instanceId)
const newView = ctx.request.body const { originalName, ...newView } = ctx.request.body
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
@ -52,6 +36,11 @@ const controller = {
[newView.name]: view, [newView.name]: view,
} }
// view has been renamed
if (originalName) {
delete designDoc.views[originalName]
}
await db.put(designDoc) await db.put(designDoc)
@ -61,6 +50,11 @@ const controller = {
...(model.views ? model.views : {}), ...(model.views ? model.views : {}),
[newView.name]: view.meta [newView.name]: view.meta
} }
if (originalName) {
delete model.views[originalName]
}
await db.put(model) await db.put(model)
ctx.body = view ctx.body = view

View File

@ -13,7 +13,6 @@ router
recordController.fetchView recordController.fetchView
) )
.get("/api/views", authorized(BUILDER), viewController.fetch) .get("/api/views", authorized(BUILDER), viewController.fetch)
// .post("/api/views/query/:viewName", authorized(BUILDER), viewController.query)
.delete("/api/views/:viewName", authorized(BUILDER), viewController.destroy) .delete("/api/views/:viewName", authorized(BUILDER), viewController.destroy)
.post("/api/views", authorized(BUILDER), viewController.save) .post("/api/views", authorized(BUILDER), viewController.save)