forked from tsai/budibase
8 changed files with 343 additions and 63 deletions
@ -0,0 +1,162 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
Popover, |
||||
|
TextButton, |
||||
|
Button, |
||||
|
Icon, |
||||
|
Input, |
||||
|
Select, |
||||
|
} from "@budibase/bbui" |
||||
|
import { backendUiStore } from "builderStore" |
||||
|
import { notifier } from "builderStore/store/notifications" |
||||
|
import CreateEditRecord from "../modals/CreateEditRecord.svelte" |
||||
|
|
||||
|
const CONDITIONS = [ |
||||
|
{ |
||||
|
name: "Equals", |
||||
|
key: "EQUALS", |
||||
|
}, |
||||
|
{ |
||||
|
name: "Less Than", |
||||
|
key: "LT", |
||||
|
}, |
||||
|
{ |
||||
|
name: "Less Than Or Equal", |
||||
|
key: "LTE", |
||||
|
}, |
||||
|
{ |
||||
|
name: "More Than", |
||||
|
key: "MT", |
||||
|
}, |
||||
|
{ |
||||
|
name: "More Than Or Equal", |
||||
|
key: "MTE", |
||||
|
}, |
||||
|
{ |
||||
|
name: "Contains", |
||||
|
key: "CONTAINS", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
const CONJUNCTIONS = [ |
||||
|
{ |
||||
|
name: "Or", |
||||
|
key: "OR", |
||||
|
}, |
||||
|
{ |
||||
|
name: "And", |
||||
|
key: "AND", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
export let view = {} |
||||
|
|
||||
|
let anchor |
||||
|
let dropdown |
||||
|
let filters = view.filters |
||||
|
|
||||
|
$: viewModel = $backendUiStore.models.find( |
||||
|
({ _id }) => _id === $backendUiStore.selectedView.modelId |
||||
|
) |
||||
|
$: fields = viewModel && Object.keys(viewModel.schema) |
||||
|
|
||||
|
function saveView() { |
||||
|
view.filters = filters |
||||
|
backendUiStore.actions.views.save(view) |
||||
|
notifier.success(`View ${view.name} saved.`) |
||||
|
dropdown.hide() |
||||
|
} |
||||
|
|
||||
|
function removeFilter(idx) { |
||||
|
filters.splice(idx, 1) |
||||
|
filters = filters |
||||
|
} |
||||
|
|
||||
|
function addFilter() { |
||||
|
filters = [...filters, {}] |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div bind:this={anchor}> |
||||
|
<TextButton |
||||
|
text |
||||
|
small |
||||
|
on:click={dropdown.show} |
||||
|
active={filters && filters.length}> |
||||
|
<Icon name="filter" /> |
||||
|
Filter |
||||
|
</TextButton> |
||||
|
</div> |
||||
|
<Popover bind:this={dropdown} {anchor} align="left"> |
||||
|
<h5>Filter</h5> |
||||
|
<div class="input-group-row"> |
||||
|
{#each filters as filter, idx} |
||||
|
{#if idx === 0} |
||||
|
<p>Where</p> |
||||
|
{:else} |
||||
|
<Select secondary thin bind:value={filter.conjunction}> |
||||
|
{#each CONJUNCTIONS as conjunction} |
||||
|
<option value={conjunction.key}>{conjunction.name}</option> |
||||
|
{/each} |
||||
|
</Select> |
||||
|
{/if} |
||||
|
<Select secondary thin bind:value={filter.key}> |
||||
|
{#each fields as field} |
||||
|
<option value={field}>{field}</option> |
||||
|
{/each} |
||||
|
</Select> |
||||
|
<Select secondary thin bind:value={filter.condition}> |
||||
|
{#each CONDITIONS as condition} |
||||
|
<option value={condition.key}>{condition.name}</option> |
||||
|
{/each} |
||||
|
</Select> |
||||
|
<Input |
||||
|
thin |
||||
|
placeholder={filter.key || fields[0]} |
||||
|
bind:value={filter.value} /> |
||||
|
<i class="ri-close-circle-fill" on:click={() => removeFilter(idx)} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
<div class="button-group"> |
||||
|
<Button text on:click={addFilter}>Add Filter</Button> |
||||
|
<div> |
||||
|
<Button secondary on:click={dropdown.hide}>Cancel</Button> |
||||
|
<Button primary on:click={saveView}>Save</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Popover> |
||||
|
|
||||
|
<style> |
||||
|
h5 { |
||||
|
margin-bottom: var(--spacing-l); |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.button-group { |
||||
|
margin-top: var(--spacing-l); |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
:global(.button-group > div > button) { |
||||
|
margin-left: var(--spacing-m); |
||||
|
} |
||||
|
|
||||
|
.ri-close-circle-fill { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.input-group-row { |
||||
|
display: grid; |
||||
|
grid-template-columns: minmax(50px, auto) 1fr 1fr 1fr 15px; |
||||
|
gap: var(--spacing-s); |
||||
|
margin-bottom: var(--spacing-l); |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
p { |
||||
|
margin: 0; |
||||
|
font-size: var(--font-size-xs); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,63 @@ |
|||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
|
||||
|
exports[`viewBuilder Filter creates a view with multiple filters and conjunctions 1`] = ` |
||||
|
Object { |
||||
|
"map": "function (doc) { |
||||
|
if (doc.modelId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" && doc[\\"Name\\"] === \\"Test\\" || doc[\\"Yes\\"] > \\"Value\\") { |
||||
|
emit(doc._id); |
||||
|
} |
||||
|
}", |
||||
|
"meta": Object { |
||||
|
"field": undefined, |
||||
|
"filters": Array [ |
||||
|
Object { |
||||
|
"condition": "EQUALS", |
||||
|
"key": "Name", |
||||
|
"value": "Test", |
||||
|
}, |
||||
|
Object { |
||||
|
"condition": "MT", |
||||
|
"conjunction": "OR", |
||||
|
"key": "Yes", |
||||
|
"value": "Value", |
||||
|
}, |
||||
|
], |
||||
|
"groupBy": undefined, |
||||
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0", |
||||
|
"schema": Object { |
||||
|
"avg": "number", |
||||
|
"count": "number", |
||||
|
"max": "number", |
||||
|
"min": "number", |
||||
|
"sum": "number", |
||||
|
"sumsqr": "number", |
||||
|
}, |
||||
|
}, |
||||
|
"reduce": "_stats", |
||||
|
} |
||||
|
`; |
||||
|
|
||||
|
exports[`viewBuilder Group By creates a view emitting the group by field 1`] = ` |
||||
|
Object { |
||||
|
"map": "function (doc) { |
||||
|
if (doc.modelId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" ) { |
||||
|
emit(doc[\\"age\\"], doc[\\"score\\"]); |
||||
|
} |
||||
|
}", |
||||
|
"meta": Object { |
||||
|
"field": "score", |
||||
|
"filters": Array [], |
||||
|
"groupBy": "age", |
||||
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0", |
||||
|
"schema": Object { |
||||
|
"avg": "number", |
||||
|
"count": "number", |
||||
|
"max": "number", |
||||
|
"min": "number", |
||||
|
"sum": "number", |
||||
|
"sumsqr": "number", |
||||
|
}, |
||||
|
}, |
||||
|
"reduce": "_stats", |
||||
|
} |
||||
|
`; |
||||
@ -0,0 +1,39 @@ |
|||||
|
const statsViewTemplate = require("../viewBuilder"); |
||||
|
|
||||
|
describe("viewBuilder", () => { |
||||
|
|
||||
|
describe("Filter", () => { |
||||
|
it("creates a view with multiple filters and conjunctions", () => { |
||||
|
expect(statsViewTemplate({ |
||||
|
"name": "yeety", |
||||
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0", |
||||
|
"filters": [{ |
||||
|
"value": "Test", |
||||
|
"condition": "EQUALS", |
||||
|
"key": "Name" |
||||
|
}, { |
||||
|
"value": "Value", |
||||
|
"condition": "MT", |
||||
|
"key": "Yes", |
||||
|
"conjunction": "OR" |
||||
|
}] |
||||
|
})).toMatchSnapshot() |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("Calculate", () => { |
||||
|
|
||||
|
}) |
||||
|
|
||||
|
describe("Group By", () => { |
||||
|
it("creates a view emitting the group by field", () => { |
||||
|
expect(statsViewTemplate({ |
||||
|
"name": "Test Scores Grouped By Age", |
||||
|
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0", |
||||
|
"groupBy": "age", |
||||
|
"field": "score", |
||||
|
"filters": [], |
||||
|
})).toMatchSnapshot() |
||||
|
}) |
||||
|
}) |
||||
|
}); |
||||
Loading…
Reference in new issue