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

View File

@ -5,8 +5,8 @@ import { fetchRelationshipData } from "./relationships"
import { executeQuery } from "./queries"
import { enrichRows } from "./rows"
export const searchTable = async ({ tableId, search, page, pageSize }) => {
const rows = await searchTableData({ tableId, search, page, pageSize })
export const searchTable = async ({ tableId, search, pagination }) => {
const rows = await searchTableData({ tableId, search, pagination })
return rows
// TODO
// 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 {Object} search - Mango Compliant search object
*/
export const searchTableData = async ({
tableId,
search,
cursor,
pageSize,
}) => {
export const searchTableData = async ({ tableId, search, pagination }) => {
const rows = await API.post({
url: `/api/${tableId}/rows/search`,
body: {
query: search,
pageSize,
cursor,
pagination,
},
})
return await enrichRows(rows, tableId)

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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