search UI complete, server side cursor based pagination

This commit is contained in:
Martin McKeaveney 2021-02-08 17:44:44 +00:00
parent bfa865cf3c
commit 07aeccb36d
8 changed files with 117 additions and 945 deletions

View File

@ -46,6 +46,11 @@
innerVal = value.target.value innerVal = value.target.value
} }
} }
if (type === "number") {
innerVal = parseInt(innerVal)
}
if (typeof innerVal === "string") { if (typeof innerVal === "string") {
onChange(replaceBindings(innerVal)) onChange(replaceBindings(innerVal))
} else { } else {
@ -72,6 +77,7 @@
value={safeValue} value={safeValue}
on:change={handleChange} on:change={handleChange}
onChange={handleChange} onChange={handleChange}
{type}
{...props} {...props}
name={key} /> name={key} />
</div> </div>

View File

@ -5,8 +5,8 @@ import { fetchRelationshipData } from "./relationships"
import { executeQuery } from "./queries" import { executeQuery } from "./queries"
import { enrichRows } from "./rows" import { enrichRows } from "./rows"
export const searchTable = async ({ tableId, search, page, pageSize }) => { export const searchTable = async ({ tableId, search, pagination }) => {
const rows = await searchTableData({ tableId, search, page, pageSize }) const rows = await searchTableData({ tableId, search, pagination })
return rows return rows
// TODO // TODO
// Enrich rows so they can displayed properly // Enrich rows so they can displayed properly

View File

@ -22,18 +22,12 @@ export const fetchTableData = async tableId => {
* @param {String} tableId - id of the table to search * @param {String} tableId - id of the table to search
* @param {Object} search - Mango Compliant search object * @param {Object} search - Mango Compliant search object
*/ */
export const searchTableData = async ({ export const searchTableData = async ({ tableId, search, pagination }) => {
tableId,
search,
cursor,
pageSize,
}) => {
const rows = await API.post({ const rows = await API.post({
url: `/api/${tableId}/rows/search`, url: `/api/${tableId}/rows/search`,
body: { body: {
query: search, query: search,
pageSize, pagination,
cursor,
}, },
}) })
return await enrichRows(rows, tableId) return await enrichRows(rows, tableId)

View File

@ -231,7 +231,7 @@ exports.fetchView = async function(ctx) {
exports.search = async function(ctx) { exports.search = async function(ctx) {
// const appId = ctx.user.appId // const appId = ctx.user.appId
const appId = 'app_5aa5e9cf26694f9ea02f054050d7ae63' const appId = "app_1987903cf3604d459969c80cf17651a0"
// const { pageSize = 10, cursor } = ctx.query // const { pageSize = 10, cursor } = ctx.query
@ -245,7 +245,10 @@ exports.search = async function(ctx) {
const db = new CouchDB(appId) const db = new CouchDB(appId)
const { query, pageSize = 10, cursor } = ctx.request.body const {
query,
pagination: { pageSize = 10, cursor, reverse },
} = ctx.request.body
query.tableId = ctx.params.tableId query.tableId = ctx.params.tableId
@ -257,13 +260,12 @@ exports.search = async function(ctx) {
const response = await db.find({ const response = await db.find({
selector: query, selector: query,
limit: pageSize, limit: pageSize,
// sort: ["_id"], sort: ["_id"],
skip: 1,
}) })
ctx.body = response.docs const rows = response.docs
// TODO: probably attach relationships ctx.body = await linkRows.attachLinkInfo(appId, rows)
// const rows = response.docs.map(row => row.doc)
// ctx.body = await linkRows.attachLinkInfo(appId, rows)
} }
exports.fetchTableRows = async function(ctx) { exports.fetchTableRows = async function(ctx) {

File diff suppressed because it is too large Load Diff

View File

@ -135,12 +135,12 @@
{ {
"type": "multifield", "type": "multifield",
"label": "Columns", "label": "Columns",
"key": "valueColumns", "key": "columns",
"dependsOn": "table" "dependsOn": "table"
}, },
{ {
"type": "number", "type": "number",
"label": "Result Page Size", "label": "Rows Per Page",
"key": "pageSize" "key": "pageSize"
}, },
{ {

View File

@ -1,6 +1,8 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
const ENTER_KEY = 13
const { authStore, styleable } = getContext("sdk") const { authStore, styleable } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
@ -29,7 +31,9 @@
} }
function handleKeydown(evt) { function handleKeydown(evt) {
console.log(evt.keyCode) if (evt.keyCode === ENTER_KEY) {
login()
}
} }
</script> </script>

View File

@ -16,68 +16,97 @@
export let table = [] export let table = []
export let columns = [] export let columns = []
export let pageSize = 50 export let pageSize = 50
export let noRowsMessage = "Feed me some data" export let noRowsMessage = "No Rows"
let rows = [] let rows = []
let loaded = false let loaded = false
// let searchableFields = []
let search = {} let search = {}
let tableDefinition let tableDefinition
let schema = {} let schema
let page = 1
$: columns = Object.keys(schema).filter(key => schema[key].searchable) // pagination
$: cursor = rows[rows.length - 1]?._id let pagination = {
$: page && fetchData(table) page: 1
}
async function fetchData(table) { $: fetchData(table, pagination)
// omit empty strings
$: parsedSearch = Object.keys(search).reduce((acc, next) => search[next] ? { ...acc, [next]: search[next] } : acc, {})
async function fetchData(table, pagination) {
if (!isEmpty(table)) { if (!isEmpty(table)) {
const tableDef = await API.fetchTableDefinition(table) const tableDef = await API.fetchTableDefinition(table)
schema = tableDef.schema schema = tableDef.schema
rows = await API.searchTable({ rows = await API.searchTable({
tableId: table, tableId: table,
search, search: parsedSearch,
pageSize, pagination: {
cursor, pageSize,
...pagination
}
}) })
} }
loaded = true loaded = true
} }
function nextPage() {
// set cursor to last element
pagination = {
// lastCursor: rows[0],
cursor: rows[rows.length - 1]?._id,
reverse: true,
page: pagination.page += 1
}
}
// TODO: implement
function previousPage() {
pagination = {
cursor: lastCursor,
reverse: true,
page: pagination.page - 1
}
}
</script> </script>
<div use:styleable={$component.styles}> <div use:styleable={$component.styles}>
<div class="query-builder"> <div class="query-builder">
{#each columns as field} {#if schema}
<div class="form-field"> {#each columns as field}
<Label extraSmall grey>{schema[field].name}</Label> <div class="form-field">
{#if schema[field].type === 'options'} <Label extraSmall grey>{schema[field].name}</Label>
<Select secondary bind:value={search[field]}> {#if schema[field].type === 'options'}
<option value="">Choose an option</option> <Select secondary bind:value={search[field]}>
{#each schema[field].constraints.inclusion as opt} <option value="">Choose an option</option>
<option>{opt}</option> {#each schema[field].constraints.inclusion as opt}
{/each} <option>{opt}</option>
</Select> {/each}
{:else if schema[field].type === 'datetime'} </Select>
<DatePicker bind:value={search[field]} /> {:else if schema[field].type === 'datetime'}
{:else if schema[field].type === 'boolean'} <DatePicker bind:value={search[field]} />
<Toggle text={schema[field].name} bind:checked={search[field]} /> {:else if schema[field].type === 'boolean'}
{:else if schema[field].type === 'number'} <Toggle text={schema[field].name} bind:checked={search[field]} />
<Input type="number" bind:value={search[field]} /> {:else if schema[field].type === 'number'}
{:else if schema[field].type === 'string'} <Input type="number" bind:value={search[field]} />
<Input bind:value={search[field]} /> {:else if schema[field].type === 'string'}
{/if} <Input bind:value={search[field]} />
</div> {/if}
{/each} </div>
<Button blue on:click={() => fetchData(table)}>Search</Button> {/each}
<Button {/if}
red <div class="actions">
on:click={() => { <Button
search = {} secondary
fetchData(table) on:click={() => {
}}> search = {}
Reset pagination = {
</Button> page: 1
}
}}>
Reset
</Button>
<Button primary on:click={() => fetchData(table)}>Search</Button>
</div>
</div> </div>
{#if rows.length > 0} {#if rows.length > 0}
{#if $component.children === 0 && $builderStore.inBuilder} {#if $component.children === 0 && $builderStore.inBuilder}
@ -90,13 +119,17 @@
{/each} {/each}
{/if} {/if}
{:else if loaded && $builderStore.inBuilder} {:else if loaded && $builderStore.inBuilder}
<p>Feed me some data</p>
{:else}
<p>{noRowsMessage}</p> <p>{noRowsMessage}</p>
{/if} {/if}
<div class="pagination"> <div class="pagination">
{#if page > 1} <!-- {#if pagination.page > 1}
<Button blue on:click={() => (page -= 1)}>Back</Button> <Button blue on:click={previousPage}>Back</Button>
{/if} -->
{#if rows.length === pageSize}
<Button primary on:click={nextPage}>Next</Button>
{/if} {/if}
<Button blue on:click={() => (page += 1)}>Next</Button>
</div> </div>
</div> </div>
@ -115,6 +148,13 @@
border-radius: var(--border-radius-s); border-radius: var(--border-radius-s);
} }
.actions {
display: grid;
grid-gap: var(--spacing-s);
justify-content: flex-end;
grid-auto-flow: column;
}
.form-field { .form-field {
margin-bottom: var(--spacing-m); margin-bottom: var(--spacing-m);
} }