group by complete
This commit is contained in:
parent
28b4b6fcb9
commit
d08db301af
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)}
|
||||||
</Table>
|
columns={COLUMNS}
|
||||||
|
{data}
|
||||||
|
>
|
||||||
|
<CalculationPopover {view} />
|
||||||
|
<GroupByPopover {view} />
|
||||||
|
</Table>
|
|
@ -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()
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -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>
|
|
@ -28,7 +28,7 @@
|
||||||
open(
|
open(
|
||||||
DeleteTableModal,
|
DeleteTableModal,
|
||||||
{
|
{
|
||||||
onClosed: hideEditor,
|
onClosed: close,
|
||||||
table,
|
table,
|
||||||
},
|
},
|
||||||
{ styleContent: { padding: "0" } }
|
{ styleContent: { padding: "0" } }
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue