Add server-side searching and pagination to data providers using internal tables
This commit is contained in:
parent
0c457e4eaf
commit
6f6770cef7
|
@ -13,10 +13,11 @@
|
||||||
export let title = "Bindings"
|
export let title = "Bindings"
|
||||||
export let placeholder
|
export let placeholder
|
||||||
export let label
|
export let label
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let bindingDrawer
|
let bindingDrawer
|
||||||
$: tempValue = value
|
$: tempValue = Array.isArray(value) ? value : []
|
||||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
@ -32,12 +33,15 @@
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<Input
|
<Input
|
||||||
{label}
|
{label}
|
||||||
|
{disabled}
|
||||||
value={readableValue}
|
value={readableValue}
|
||||||
on:change={event => onChange(event.detail)}
|
on:change={event => onChange(event.detail)}
|
||||||
{placeholder} />
|
{placeholder} />
|
||||||
<div class="icon" on:click={bindingDrawer.show}>
|
{#if !disabled}
|
||||||
<Icon size="S" name="FlashOn" />
|
<div class="icon" on:click={bindingDrawer.show}>
|
||||||
</div>
|
<Icon size="S" name="FlashOn" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Drawer bind:this={bindingDrawer} {title}>
|
<Drawer bind:this={bindingDrawer} {title}>
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
const inputChanged = ev => {
|
const inputChanged = ev => {
|
||||||
try {
|
try {
|
||||||
values = ev.target.value.split("\n")
|
values = ev.detail.split("\n")
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
values = []
|
values = []
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
import { Select, Label } from "@budibase/bbui"
|
||||||
|
import { currentAsset, store } from "builderStore"
|
||||||
|
import { getActionProviderComponents } from "builderStore/dataBinding"
|
||||||
|
|
||||||
|
export let parameters
|
||||||
|
|
||||||
|
$: actionProviders = getActionProviderComponents(
|
||||||
|
$currentAsset,
|
||||||
|
$store.selectedComponentId,
|
||||||
|
"NextPage"
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<Label small>Data Provider</Label>
|
||||||
|
<Select
|
||||||
|
bind:value={parameters.componentId}
|
||||||
|
options={actionProviders}
|
||||||
|
getOptionLabel={(x) => x._instanceName}
|
||||||
|
getOptionValue={(x) => x._id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root :global(> div) {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: var(--spacing-l);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
import { Select, Label } from "@budibase/bbui"
|
||||||
|
import { currentAsset, store } from "builderStore"
|
||||||
|
import { getActionProviderComponents } from "builderStore/dataBinding"
|
||||||
|
|
||||||
|
export let parameters
|
||||||
|
|
||||||
|
$: actionProviders = getActionProviderComponents(
|
||||||
|
$currentAsset,
|
||||||
|
$store.selectedComponentId,
|
||||||
|
"PrevPage"
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<Label small>Data Provider</Label>
|
||||||
|
<Select
|
||||||
|
bind:value={parameters.componentId}
|
||||||
|
options={actionProviders}
|
||||||
|
getOptionLabel={(x) => x._instanceName}
|
||||||
|
getOptionValue={(x) => x._id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root :global(> div) {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: var(--spacing-l);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -6,6 +6,8 @@ import TriggerAutomation from "./TriggerAutomation.svelte"
|
||||||
import ValidateForm from "./ValidateForm.svelte"
|
import ValidateForm from "./ValidateForm.svelte"
|
||||||
import LogIn from "./LogIn.svelte"
|
import LogIn from "./LogIn.svelte"
|
||||||
import LogOut from "./LogOut.svelte"
|
import LogOut from "./LogOut.svelte"
|
||||||
|
import NextPage from "./NextPage.svelte"
|
||||||
|
import PrevPage from "./PrevPage.svelte"
|
||||||
|
|
||||||
// defines what actions are available, when adding a new one
|
// defines what actions are available, when adding a new one
|
||||||
// the component is the setup panel for the action
|
// the component is the setup panel for the action
|
||||||
|
@ -45,4 +47,12 @@ export default [
|
||||||
name: "Log Out",
|
name: "Log Out",
|
||||||
component: LogOut,
|
component: LogOut,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Next Page",
|
||||||
|
component: NextPage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Previous Page",
|
||||||
|
component: PrevPage,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
DatePicker,
|
||||||
|
ActionButton,
|
||||||
|
Button,
|
||||||
|
Select,
|
||||||
|
Combobox,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { store, currentAsset } from "builderStore"
|
||||||
|
import { getBindableProperties } from "builderStore/dataBinding"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import DrawerBindableInput from "components/common/DrawerBindableInput.svelte"
|
||||||
|
import { generate } from "shortid"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let schemaFields
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const OperatorOptions = {
|
||||||
|
Equals: {
|
||||||
|
value: "equal",
|
||||||
|
label: "Equals",
|
||||||
|
},
|
||||||
|
NotEquals: {
|
||||||
|
value: "notEqual",
|
||||||
|
label: "Not equals",
|
||||||
|
},
|
||||||
|
Empty: {
|
||||||
|
value: "empty",
|
||||||
|
label: "Is empty",
|
||||||
|
},
|
||||||
|
NotEmpty: {
|
||||||
|
value: "notEmpty",
|
||||||
|
label: "Is not empty",
|
||||||
|
},
|
||||||
|
StartsWith: {
|
||||||
|
value: "string",
|
||||||
|
label: "Starts with",
|
||||||
|
},
|
||||||
|
Like: {
|
||||||
|
value: "fuzzy",
|
||||||
|
label: "Like",
|
||||||
|
},
|
||||||
|
MoreThan: {
|
||||||
|
value: "rangeLow",
|
||||||
|
label: "More than",
|
||||||
|
},
|
||||||
|
LessThan: {
|
||||||
|
value: "rangeHigh",
|
||||||
|
label: "Less than",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const BannedTypes = ["link", "attachment"]
|
||||||
|
$: bindableProperties = getBindableProperties(
|
||||||
|
$currentAsset,
|
||||||
|
$store.selectedComponentId
|
||||||
|
)
|
||||||
|
$: fieldOptions = (schemaFields ?? [])
|
||||||
|
.filter(field => !BannedTypes.includes(field.type))
|
||||||
|
.map(field => field.name)
|
||||||
|
|
||||||
|
const addField = () => {
|
||||||
|
value = [
|
||||||
|
...value,
|
||||||
|
{
|
||||||
|
id: generate(),
|
||||||
|
field: null,
|
||||||
|
operator: OperatorOptions.Equals.value,
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeField = id => {
|
||||||
|
value = value.filter(field => field.id !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getValidOperatorsForType = type => {
|
||||||
|
const Op = OperatorOptions
|
||||||
|
if (type === "string") {
|
||||||
|
return [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.StartsWith,
|
||||||
|
Op.Like,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
|
} else if (type === "number") {
|
||||||
|
return [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.MoreThan,
|
||||||
|
Op.LessThan,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
|
} else if (type === "options") {
|
||||||
|
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||||
|
} else if (type === "boolean") {
|
||||||
|
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||||
|
} else if (type === "longform") {
|
||||||
|
return [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.StartsWith,
|
||||||
|
Op.Like,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
|
} else if (type === "datetime") {
|
||||||
|
return [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.MoreThan,
|
||||||
|
Op.LessThan,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFieldChange = (expression, field) => {
|
||||||
|
// Update the field type
|
||||||
|
expression.type = schemaFields.find(x => x.name === field)?.type
|
||||||
|
|
||||||
|
// Ensure a valid operator is set
|
||||||
|
const validOperators = getValidOperatorsForType(expression.type)
|
||||||
|
if (!validOperators.includes(expression.operator)) {
|
||||||
|
expression.operator =
|
||||||
|
validOperators[0]?.value ?? OperatorOptions.Equals.value
|
||||||
|
onOperatorChange(expression, expression.operator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOperatorChange = (expression, operator) => {
|
||||||
|
const noValueOptions = [
|
||||||
|
OperatorOptions.Empty.value,
|
||||||
|
OperatorOptions.NotEmpty.value,
|
||||||
|
]
|
||||||
|
expression.noValue = noValueOptions.includes(operator)
|
||||||
|
if (expression.noValue) {
|
||||||
|
expression.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFieldOptions = field => {
|
||||||
|
const schema = schemaFields.find(x => x.name === field)
|
||||||
|
return schema?.constraints?.inclusion || []
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if value?.length}
|
||||||
|
<div class="fields">
|
||||||
|
{#each value as expression, idx}
|
||||||
|
<Select
|
||||||
|
bind:value={expression.field}
|
||||||
|
options={fieldOptions}
|
||||||
|
on:change={e => onFieldChange(expression, e.detail)}
|
||||||
|
placeholder="Column" />
|
||||||
|
<Select
|
||||||
|
disabled={!expression.field}
|
||||||
|
options={getValidOperatorsForType(expression.type)}
|
||||||
|
bind:value={expression.operator}
|
||||||
|
on:change={e => onOperatorChange(expression, e.detail)}
|
||||||
|
placeholder={null} />
|
||||||
|
{#if ['string', 'longform', 'number'].includes(expression.type)}
|
||||||
|
<DrawerBindableInput
|
||||||
|
disabled={expression.noValue}
|
||||||
|
title={`Value for "${expression.field}"`}
|
||||||
|
value={expression.value}
|
||||||
|
placeholder="Value"
|
||||||
|
bindings={bindableProperties}
|
||||||
|
on:change={event => (expression.value = event.detail)} />
|
||||||
|
{:else if expression.type === 'options'}
|
||||||
|
<Combobox
|
||||||
|
disabled={expression.noValue}
|
||||||
|
options={getFieldOptions(expression.field)}
|
||||||
|
bind:value={expression.value} />
|
||||||
|
{:else if expression.type === 'boolean'}
|
||||||
|
<Combobox
|
||||||
|
disabled
|
||||||
|
options={[{ label: 'True', value: true }, { label: 'False', value: false }]}
|
||||||
|
bind:value={expression.value} />
|
||||||
|
{:else if expression.type === 'datetime'}
|
||||||
|
<DatePicker
|
||||||
|
disabled={expression.noValue}
|
||||||
|
bind:value={expression.value} />
|
||||||
|
{:else}
|
||||||
|
<DrawerBindableInput disabled />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<ActionButton
|
||||||
|
size="S"
|
||||||
|
quiet
|
||||||
|
icon="Close"
|
||||||
|
on:click={() => removeField(expression.id)} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div>
|
||||||
|
<Button icon="AddCircle" size="M" secondary on:click={addField}>
|
||||||
|
Add expression
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fields {
|
||||||
|
display: grid;
|
||||||
|
column-gap: var(--spacing-l);
|
||||||
|
row-gap: var(--spacing-s);
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: 1fr 120px 1fr auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,12 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Drawer, Body, DrawerContent, Layout } from "@budibase/bbui"
|
import {
|
||||||
|
notifications,
|
||||||
|
Button,
|
||||||
|
Drawer,
|
||||||
|
Body,
|
||||||
|
DrawerContent,
|
||||||
|
Layout,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { notifications } from "@budibase/bbui"
|
import {} from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
getDatasourceForProvider,
|
getDatasourceForProvider,
|
||||||
getSchemaForDatasource,
|
getSchemaForDatasource,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import SaveFields from "./EventsEditor/actions/SaveFields.svelte"
|
import FilterBuilder from "./FilterBuilder.svelte"
|
||||||
import { currentAsset } from "builderStore"
|
import { currentAsset } from "builderStore"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -14,11 +21,11 @@
|
||||||
export let value = {}
|
export let value = {}
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
let drawer
|
let drawer
|
||||||
let tempValue = value
|
let tempValue = Array.isArray(value) ? value : []
|
||||||
|
|
||||||
$: schemaFields = getSchemaFields(componentInstance)
|
$: schemaFields = getSchemaFields(componentInstance)
|
||||||
|
|
||||||
const getSchemaFields = (component) => {
|
const getSchemaFields = component => {
|
||||||
const datasource = getDatasourceForProvider($currentAsset, component)
|
const datasource = getDatasourceForProvider($currentAsset, component)
|
||||||
const { schema } = getSchemaForDatasource(datasource)
|
const { schema } = getSchemaForDatasource(datasource)
|
||||||
return Object.values(schema || {})
|
return Object.values(schema || {})
|
||||||
|
@ -29,10 +36,6 @@
|
||||||
notifications.success("Filters saved.")
|
notifications.success("Filters saved.")
|
||||||
drawer.hide()
|
drawer.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFieldsChanged = (event) => {
|
|
||||||
tempValue = event.detail
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button secondary wide on:click={drawer.show}>Define Filters</Button>
|
<Button secondary wide on:click={drawer.show}>Define Filters</Button>
|
||||||
|
@ -48,24 +51,7 @@
|
||||||
constaints.
|
constaints.
|
||||||
{/if}
|
{/if}
|
||||||
</Body>
|
</Body>
|
||||||
<div class="fields">
|
<FilterBuilder bind:value={tempValue} {schemaFields} />
|
||||||
<SaveFields
|
|
||||||
parameterFields={value}
|
|
||||||
{schemaFields}
|
|
||||||
valueLabel="Equals"
|
|
||||||
on:change={onFieldsChanged}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<style>
|
|
||||||
.fields {
|
|
||||||
display: grid;
|
|
||||||
column-gap: var(--spacing-l);
|
|
||||||
row-gap: var(--spacing-s);
|
|
||||||
align-items: center;
|
|
||||||
grid-template-columns: auto 1fr auto 1fr auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
|
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
|
||||||
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
|
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
|
||||||
import EventsEditor from "./PropertyControls/EventsEditor"
|
import EventsEditor from "./PropertyControls/EventsEditor"
|
||||||
import FilterEditor from "./PropertyControls/FilterEditor.svelte"
|
import FilterEditor from "./PropertyControls/FilterEditor/FilterEditor.svelte"
|
||||||
import { IconSelect } from "./PropertyControls/IconSelect"
|
import { IconSelect } from "./PropertyControls/IconSelect"
|
||||||
import ColorPicker from "./PropertyControls/ColorPicker.svelte"
|
import ColorPicker from "./PropertyControls/ColorPicker.svelte"
|
||||||
import StringFieldSelect from "./PropertyControls/StringFieldSelect.svelte"
|
import StringFieldSelect from "./PropertyControls/StringFieldSelect.svelte"
|
||||||
|
|
|
@ -5,14 +5,14 @@ import { enrichRows } from "./rows"
|
||||||
* Fetches a table definition.
|
* Fetches a table definition.
|
||||||
* Since definitions cannot change at runtime, the result is cached.
|
* Since definitions cannot change at runtime, the result is cached.
|
||||||
*/
|
*/
|
||||||
export const fetchTableDefinition = async tableId => {
|
export const fetchTableDefinition = async (tableId) => {
|
||||||
return await API.get({ url: `/api/tables/${tableId}`, cache: true })
|
return await API.get({ url: `/api/tables/${tableId}`, cache: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all rows from a table.
|
* Fetches all rows from a table.
|
||||||
*/
|
*/
|
||||||
export const fetchTableData = async tableId => {
|
export const fetchTableData = async (tableId) => {
|
||||||
const rows = await API.get({ url: `/api/${tableId}/rows` })
|
const rows = await API.get({ url: `/api/${tableId}/rows` })
|
||||||
return await enrichRows(rows, tableId)
|
return await enrichRows(rows, tableId)
|
||||||
}
|
}
|
||||||
|
@ -34,3 +34,35 @@ export const searchTableData = async ({ tableId, search, pagination }) => {
|
||||||
output.rows = await enrichRows(output.rows, tableId)
|
output.rows = await enrichRows(output.rows, tableId)
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches a table using Lucene.
|
||||||
|
*/
|
||||||
|
export const searchTable = async ({
|
||||||
|
tableId,
|
||||||
|
query,
|
||||||
|
raw,
|
||||||
|
bookmark,
|
||||||
|
limit,
|
||||||
|
sort,
|
||||||
|
sortOrder,
|
||||||
|
}) => {
|
||||||
|
if (!tableId || (!query && !raw)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await API.post({
|
||||||
|
url: `/api/search/${tableId}/rows`,
|
||||||
|
body: {
|
||||||
|
query,
|
||||||
|
raw,
|
||||||
|
bookmark,
|
||||||
|
limit,
|
||||||
|
sort,
|
||||||
|
sortOrder,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
rows: await enrichRows(res?.rows, tableId),
|
||||||
|
bookmark: res.bookmark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,4 +5,6 @@ export const TableNames = {
|
||||||
export const ActionTypes = {
|
export const ActionTypes = {
|
||||||
ValidateForm: "ValidateForm",
|
ValidateForm: "ValidateForm",
|
||||||
RefreshDatasource: "RefreshDatasource",
|
RefreshDatasource: "RefreshDatasource",
|
||||||
|
NextPage: "NextPage",
|
||||||
|
PrevPage: "PrevPage",
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,27 +16,27 @@ const saveRowHandler = async (action, context) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteRowHandler = async action => {
|
const deleteRowHandler = async (action) => {
|
||||||
const { tableId, revId, rowId } = action.parameters
|
const { tableId, revId, rowId } = action.parameters
|
||||||
if (tableId && revId && rowId) {
|
if (tableId && revId && rowId) {
|
||||||
await deleteRow({ tableId, rowId, revId })
|
await deleteRow({ tableId, rowId, revId })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggerAutomationHandler = async action => {
|
const triggerAutomationHandler = async (action) => {
|
||||||
const { fields } = action.parameters
|
const { fields } = action.parameters
|
||||||
if (fields) {
|
if (fields) {
|
||||||
await triggerAutomation(action.parameters.automationId, fields)
|
await triggerAutomation(action.parameters.automationId, fields)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigationHandler = action => {
|
const navigationHandler = (action) => {
|
||||||
if (action.parameters.url) {
|
if (action.parameters.url) {
|
||||||
routeStore.actions.navigate(action.parameters.url)
|
routeStore.actions.navigate(action.parameters.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryExecutionHandler = async action => {
|
const queryExecutionHandler = async (action) => {
|
||||||
const { datasourceId, queryId, queryParams } = action.parameters
|
const { datasourceId, queryId, queryParams } = action.parameters
|
||||||
await executeQuery({
|
await executeQuery({
|
||||||
datasourceId,
|
datasourceId,
|
||||||
|
@ -68,7 +68,23 @@ const refreshDatasourceHandler = async (action, context) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginHandler = async action => {
|
const nextPageHandler = async (action, context) => {
|
||||||
|
return await executeActionHandler(
|
||||||
|
context,
|
||||||
|
action.parameters.componentId,
|
||||||
|
ActionTypes.NextPage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevPageHandler = async (action, context) => {
|
||||||
|
return await executeActionHandler(
|
||||||
|
context,
|
||||||
|
action.parameters.componentId,
|
||||||
|
ActionTypes.PrevPage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginHandler = async (action) => {
|
||||||
const { email, password } = action.parameters
|
const { email, password } = action.parameters
|
||||||
await authStore.actions.logIn({ email, password })
|
await authStore.actions.logIn({ email, password })
|
||||||
}
|
}
|
||||||
|
@ -87,6 +103,8 @@ const handlerMap = {
|
||||||
["Refresh Datasource"]: refreshDatasourceHandler,
|
["Refresh Datasource"]: refreshDatasourceHandler,
|
||||||
["Log In"]: loginHandler,
|
["Log In"]: loginHandler,
|
||||||
["Log Out"]: logoutHandler,
|
["Log Out"]: logoutHandler,
|
||||||
|
["Next Page"]: nextPageHandler,
|
||||||
|
["Previous Page"]: prevPageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,9 +114,10 @@ const handlerMap = {
|
||||||
export const enrichButtonActions = (actions, context) => {
|
export const enrichButtonActions = (actions, context) => {
|
||||||
// Prevent button actions in the builder preview
|
// Prevent button actions in the builder preview
|
||||||
if (get(builderStore).inBuilder) {
|
if (get(builderStore).inBuilder) {
|
||||||
return () => {}
|
// TODO uncomment
|
||||||
|
// return () => {}
|
||||||
}
|
}
|
||||||
const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
|
const handlers = actions.map((def) => handlerMap[def["##eventHandlerType"]])
|
||||||
return async () => {
|
return async () => {
|
||||||
for (let i = 0; i < handlers.length; i++) {
|
for (let i = 0; i < handlers.length; i++) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
const { QueryBuilder, buildSearchUrl, search } = require("./utils")
|
const { QueryBuilder, buildSearchUrl, search } = require("./utils")
|
||||||
|
|
||||||
exports.rowSearch = async ctx => {
|
exports.rowSearch = async (ctx) => {
|
||||||
const appId = ctx.appId
|
const appId = ctx.appId
|
||||||
const { tableId } = ctx.params
|
const { tableId } = ctx.params
|
||||||
const { bookmark, query, raw } = ctx.request.body
|
const { bookmark, query, raw, limit, sort, sortOrder } = ctx.request.body
|
||||||
let url
|
let url
|
||||||
if (query) {
|
if (query) {
|
||||||
url = new QueryBuilder(appId, query, bookmark).addTable(tableId).complete()
|
url = new QueryBuilder(appId, query, bookmark, limit, sort, sortOrder)
|
||||||
|
.addTable(tableId)
|
||||||
|
.complete()
|
||||||
} else if (raw) {
|
} else if (raw) {
|
||||||
url = buildSearchUrl({
|
url = buildSearchUrl({
|
||||||
appId,
|
appId,
|
||||||
|
|
|
@ -10,24 +10,43 @@ const fetch = require("node-fetch")
|
||||||
* @param {string|null} bookmark If there were more than the limit specified can send the bookmark that was
|
* @param {string|null} bookmark If there were more than the limit specified can send the bookmark that was
|
||||||
* returned with query for next set of search results.
|
* returned with query for next set of search results.
|
||||||
* @param {number} limit The number of entries to return per query.
|
* @param {number} limit The number of entries to return per query.
|
||||||
|
* @param {string} sort The column to sort by.
|
||||||
|
* @param {string} sortOrder The order to sort by. "ascending" or "descending".
|
||||||
* @param {boolean} excludeDocs By default full rows are returned, if required this can be disabled.
|
* @param {boolean} excludeDocs By default full rows are returned, if required this can be disabled.
|
||||||
* @return {string} The URL which a GET can be performed on to receive results.
|
* @return {string} The URL which a GET can be performed on to receive results.
|
||||||
*/
|
*/
|
||||||
function buildSearchUrl({ appId, query, bookmark, excludeDocs, limit = 50 }) {
|
function buildSearchUrl({
|
||||||
|
appId,
|
||||||
|
query,
|
||||||
|
bookmark,
|
||||||
|
sort,
|
||||||
|
sortOrder,
|
||||||
|
excludeDocs,
|
||||||
|
limit = 50,
|
||||||
|
}) {
|
||||||
let url = `${env.COUCH_DB_URL}/${appId}/_design/database/_search`
|
let url = `${env.COUCH_DB_URL}/${appId}/_design/database/_search`
|
||||||
url += `/${SearchIndexes.ROWS}?q=${query}`
|
url += `/${SearchIndexes.ROWS}?q=${query}`
|
||||||
url += `&limit=${limit}`
|
url += `&limit=${limit}`
|
||||||
if (!excludeDocs) {
|
if (!excludeDocs) {
|
||||||
url += "&include_docs=true"
|
url += "&include_docs=true"
|
||||||
}
|
}
|
||||||
|
if (sort) {
|
||||||
|
const orderChar = sortOrder === "descending" ? "-" : ""
|
||||||
|
url += `&sort="${orderChar}${sort.replace(/ /, "_")}<string>"`
|
||||||
|
}
|
||||||
if (bookmark) {
|
if (bookmark) {
|
||||||
url += `&bookmark=${bookmark}`
|
url += `&bookmark=${bookmark}`
|
||||||
}
|
}
|
||||||
|
console.log(url)
|
||||||
return checkSlashesInUrl(url)
|
return checkSlashesInUrl(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const luceneEscape = (value) => {
|
||||||
|
return `${value}`.replace(/[ #+\-&|!(){}\[\]^"~*?:\\]/g, "\\$&")
|
||||||
|
}
|
||||||
|
|
||||||
class QueryBuilder {
|
class QueryBuilder {
|
||||||
constructor(appId, base) {
|
constructor(appId, base, bookmark, limit, sort, sortOrder) {
|
||||||
this.appId = appId
|
this.appId = appId
|
||||||
this.query = {
|
this.query = {
|
||||||
string: {},
|
string: {},
|
||||||
|
@ -35,10 +54,14 @@ class QueryBuilder {
|
||||||
range: {},
|
range: {},
|
||||||
equal: {},
|
equal: {},
|
||||||
notEqual: {},
|
notEqual: {},
|
||||||
|
empty: {},
|
||||||
|
notEmpty: {},
|
||||||
...base,
|
...base,
|
||||||
}
|
}
|
||||||
this.limit = 50
|
this.bookmark = bookmark
|
||||||
this.bookmark = null
|
this.limit = limit || 50
|
||||||
|
this.sort = sort
|
||||||
|
this.sortOrder = sortOrder || "ascending"
|
||||||
}
|
}
|
||||||
|
|
||||||
setLimit(limit) {
|
setLimit(limit) {
|
||||||
|
@ -79,39 +102,73 @@ class QueryBuilder {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addEmpty(key, value) {
|
||||||
|
this.query.empty[key] = value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
addNotEmpty(key, value) {
|
||||||
|
this.query.notEmpty[key] = value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
addTable(tableId) {
|
addTable(tableId) {
|
||||||
this.query.equal.tableId = tableId
|
this.query.equal.tableId = tableId
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
complete(rawQuery = null) {
|
complete(rawQuery = null) {
|
||||||
let output = ""
|
let output = "*:*"
|
||||||
function build(structure, queryFn) {
|
function build(structure, queryFn) {
|
||||||
for (let [key, value] of Object.entries(structure)) {
|
for (let [key, value] of Object.entries(structure)) {
|
||||||
if (output.length !== 0) {
|
const expression = queryFn(luceneEscape(key.replace(/ /, "_")), value)
|
||||||
output += " AND "
|
if (expression == null) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
output += queryFn(key, value).replace(/ /, "\\ ")
|
output += ` AND ${expression}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.query.string) {
|
if (this.query.string) {
|
||||||
build(this.query.string, (key, value) => `${key}:${value}*`)
|
build(this.query.string, (key, value) => {
|
||||||
|
return value ? `${key}:${luceneEscape(value.toLowerCase())}*` : null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (this.query.range) {
|
if (this.query.range) {
|
||||||
build(
|
build(this.query.range, (key, value) => {
|
||||||
this.query.range,
|
if (!value) {
|
||||||
(key, value) => `${key}:[${value.low} TO ${value.high}]`
|
return null
|
||||||
)
|
}
|
||||||
|
if (isNaN(value.low) || value.low == null || value.low === "") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (isNaN(value.high) || value.high == null || value.high === "") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
console.log(value)
|
||||||
|
return `${key}:[${value.low} TO ${value.high}]`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (this.query.fuzzy) {
|
if (this.query.fuzzy) {
|
||||||
build(this.query.fuzzy, (key, value) => `${key}:${value}~`)
|
build(this.query.fuzzy, (key, value) => {
|
||||||
|
return value ? `${key}:${luceneEscape(value.toLowerCase())}~` : null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (this.query.equal) {
|
if (this.query.equal) {
|
||||||
build(this.query.equal, (key, value) => `${key}:${value}`)
|
build(this.query.equal, (key, value) => {
|
||||||
|
return value ? `${key}:${luceneEscape(value.toLowerCase())}` : null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (this.query.notEqual) {
|
if (this.query.notEqual) {
|
||||||
build(this.query.notEqual, (key, value) => `!${key}:${value}`)
|
build(this.query.notEqual, (key, value) => {
|
||||||
|
return value ? `!${key}:${luceneEscape(value.toLowerCase())}` : null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.query.empty) {
|
||||||
|
build(this.query.empty, (key) => `!${key}:["" TO *]`)
|
||||||
|
}
|
||||||
|
if (this.query.notEmpty) {
|
||||||
|
build(this.query.notEmpty, (key) => `${key}:["" TO *]`)
|
||||||
}
|
}
|
||||||
if (rawQuery) {
|
if (rawQuery) {
|
||||||
output = output.length === 0 ? rawQuery : `&${rawQuery}`
|
output = output.length === 0 ? rawQuery : `&${rawQuery}`
|
||||||
|
@ -121,11 +178,13 @@ class QueryBuilder {
|
||||||
query: output,
|
query: output,
|
||||||
bookmark: this.bookmark,
|
bookmark: this.bookmark,
|
||||||
limit: this.limit,
|
limit: this.limit,
|
||||||
|
sort: this.sort,
|
||||||
|
sortOrder: this.sortOrder,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.search = async query => {
|
exports.search = async (query) => {
|
||||||
const response = await fetch(query, {
|
const response = await fetch(query, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
})
|
})
|
||||||
|
@ -134,7 +193,7 @@ exports.search = async query => {
|
||||||
rows: [],
|
rows: [],
|
||||||
}
|
}
|
||||||
if (json.rows != null && json.rows.length > 0) {
|
if (json.rows != null && json.rows.length > 0) {
|
||||||
output.rows = json.rows.map(row => row.doc)
|
output.rows = json.rows.map((row) => row.doc)
|
||||||
}
|
}
|
||||||
if (json.bookmark) {
|
if (json.bookmark) {
|
||||||
output.bookmark = json.bookmark
|
output.bookmark = json.bookmark
|
||||||
|
|
|
@ -25,11 +25,11 @@ const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR
|
||||||
* @returns {Promise<void>} The view now exists, please note that the next view of this query will actually build it,
|
* @returns {Promise<void>} The view now exists, please note that the next view of this query will actually build it,
|
||||||
* so it may be slow.
|
* so it may be slow.
|
||||||
*/
|
*/
|
||||||
exports.createLinkView = async appId => {
|
exports.createLinkView = async (appId) => {
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
const designDoc = await db.get("_design/database")
|
const designDoc = await db.get("_design/database")
|
||||||
const view = {
|
const view = {
|
||||||
map: function(doc) {
|
map: function (doc) {
|
||||||
// everything in this must remain constant as its going to Pouch, no external variables
|
// everything in this must remain constant as its going to Pouch, no external variables
|
||||||
if (doc.type === "link") {
|
if (doc.type === "link") {
|
||||||
let doc1 = doc.doc1
|
let doc1 = doc.doc1
|
||||||
|
@ -57,7 +57,7 @@ exports.createLinkView = async appId => {
|
||||||
await db.put(designDoc)
|
await db.put(designDoc)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createRoutingView = async appId => {
|
exports.createRoutingView = async (appId) => {
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
const designDoc = await db.get("_design/database")
|
const designDoc = await db.get("_design/database")
|
||||||
const view = {
|
const view = {
|
||||||
|
@ -84,23 +84,28 @@ async function searchIndex(appId, indexName, fnString) {
|
||||||
designDoc.indexes = {
|
designDoc.indexes = {
|
||||||
[indexName]: {
|
[indexName]: {
|
||||||
index: fnString,
|
index: fnString,
|
||||||
|
analyzer: "keyword",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await db.put(designDoc)
|
await db.put(designDoc)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createAllSearchIndex = async appId => {
|
exports.createAllSearchIndex = async (appId) => {
|
||||||
await searchIndex(
|
await searchIndex(
|
||||||
appId,
|
appId,
|
||||||
SearchIndexes.ROWS,
|
SearchIndexes.ROWS,
|
||||||
function(doc) {
|
function (doc) {
|
||||||
function idx(input, prev) {
|
function idx(input, prev) {
|
||||||
for (let key of Object.keys(input)) {
|
for (let key of Object.keys(input)) {
|
||||||
const idxKey = prev != null ? `${prev}.${key}` : key
|
let idxKey = prev != null ? `${prev}.${key}` : key
|
||||||
if (key === "_id" || key === "_rev") {
|
idxKey = idxKey.replace(/ /, "_")
|
||||||
|
if (key === "_id" || key === "_rev" || input[key] == null) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (typeof input[key] !== "object") {
|
if (typeof input[key] === "string") {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
index(idxKey, input[key].toLowerCase(), { store: true })
|
||||||
|
} else if (typeof input[key] !== "object") {
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
index(idxKey, input[key], { store: true })
|
index(idxKey, input[key], { store: true })
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1419,6 +1419,7 @@
|
||||||
"icon": "Data",
|
"icon": "Data",
|
||||||
"styleable": false,
|
"styleable": false,
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
|
"actions": ["NextPage", "PrevPage"],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "dataSource",
|
"type": "dataSource",
|
||||||
|
@ -1470,6 +1471,10 @@
|
||||||
{
|
{
|
||||||
"label": "Loaded",
|
"label": "Loaded",
|
||||||
"key": "loaded"
|
"key": "loaded"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Page Number",
|
||||||
|
"key": "pageNumber"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
export let filter
|
export let filter
|
||||||
export let sortColumn
|
export let sortColumn
|
||||||
export let sortOrder
|
export let sortOrder
|
||||||
export let limit
|
export let limit = 50
|
||||||
|
|
||||||
const { API, styleable, Provider, ActionTypes } = getContext("sdk")
|
const { API, styleable, Provider, ActionTypes } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
@ -16,13 +16,18 @@
|
||||||
// Loading flag for the initial load
|
// Loading flag for the initial load
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
|
||||||
let allRows = []
|
// Provider state
|
||||||
|
let rows = []
|
||||||
let schema = {}
|
let schema = {}
|
||||||
|
let bookmarks = [null]
|
||||||
|
let pageNumber = 0
|
||||||
|
|
||||||
$: fetchData(dataSource)
|
$: query = dataSource?.type === "table" ? buildLuceneQuery(filter) : null
|
||||||
$: filteredRows = filterRows(allRows, filter)
|
$: hasNextPage = bookmarks[pageNumber + 1] != null
|
||||||
$: sortedRows = sortRows(filteredRows, sortColumn, sortOrder)
|
$: hasPrevPage = pageNumber > 0
|
||||||
$: rows = limitRows(sortedRows, limit)
|
$: fetchData(dataSource, query, limit, sortColumn, sortOrder)
|
||||||
|
// $: sortedRows = sortRows(filteredRows, sortColumn, sortOrder)
|
||||||
|
// $: rows = limitRows(sortedRows, limit)
|
||||||
$: getSchema(dataSource)
|
$: getSchema(dataSource)
|
||||||
$: actions = [
|
$: actions = [
|
||||||
{
|
{
|
||||||
|
@ -30,6 +35,14 @@
|
||||||
callback: () => fetchData(dataSource),
|
callback: () => fetchData(dataSource),
|
||||||
metadata: { dataSource },
|
metadata: { dataSource },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: ActionTypes.NextPage,
|
||||||
|
callback: () => nextPage(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: ActionTypes.PrevPage,
|
||||||
|
callback: () => prevPage(),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
$: dataContext = {
|
$: dataContext = {
|
||||||
rows,
|
rows,
|
||||||
|
@ -37,23 +50,82 @@
|
||||||
rowsLength: rows.length,
|
rowsLength: rows.length,
|
||||||
loading,
|
loading,
|
||||||
loaded,
|
loaded,
|
||||||
|
pageNumber: pageNumber + 1,
|
||||||
|
hasNextPage,
|
||||||
|
hasPrevPage,
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = async dataSource => {
|
const buildLuceneQuery = (filter) => {
|
||||||
|
let query = {
|
||||||
|
string: {},
|
||||||
|
fuzzy: {},
|
||||||
|
range: {},
|
||||||
|
equal: {},
|
||||||
|
notEqual: {},
|
||||||
|
empty: {},
|
||||||
|
notEmpty: {},
|
||||||
|
}
|
||||||
|
if (Array.isArray(filter)) {
|
||||||
|
filter.forEach((expression) => {
|
||||||
|
if (expression.operator.startsWith("range")) {
|
||||||
|
let range = {
|
||||||
|
low: Number.MIN_SAFE_INTEGER,
|
||||||
|
high: Number.MAX_SAFE_INTEGER,
|
||||||
|
}
|
||||||
|
if (expression.operator === "rangeLow") {
|
||||||
|
range.low = expression.value
|
||||||
|
} else if (expression.operator === "rangeHigh") {
|
||||||
|
range.high = expression.value
|
||||||
|
}
|
||||||
|
query.range[expression.field] = range
|
||||||
|
} else if (query[expression.operator]) {
|
||||||
|
query[expression.operator][expression.field] = expression.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchData = async (dataSource, query, limit, sortColumn, sortOrder) => {
|
||||||
loading = true
|
loading = true
|
||||||
allRows = await API.fetchDatasource(dataSource)
|
if (dataSource?.type === "table") {
|
||||||
|
const res = await API.searchTable({
|
||||||
|
tableId: dataSource.tableId,
|
||||||
|
query,
|
||||||
|
limit,
|
||||||
|
sort: sortColumn,
|
||||||
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
||||||
|
})
|
||||||
|
pageNumber = 0
|
||||||
|
rows = res.rows
|
||||||
|
|
||||||
|
// Check we have next data
|
||||||
|
const next = await API.searchTable({
|
||||||
|
tableId: dataSource.tableId,
|
||||||
|
query,
|
||||||
|
limit: 1,
|
||||||
|
bookmark: res.bookmark,
|
||||||
|
sort: sortColumn,
|
||||||
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
||||||
|
})
|
||||||
|
if (next.rows?.length) {
|
||||||
|
bookmarks = [null, res.bookmark]
|
||||||
|
} else {
|
||||||
|
bookmarks = [null]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const rows = await API.fetchDatasource(dataSource)
|
||||||
|
rows = inMemoryFilterRows(rows, filter)
|
||||||
|
}
|
||||||
loading = false
|
loading = false
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterRows = (rows, filter) => {
|
const inMemoryFilterRows = (rows, filter) => {
|
||||||
if (!Object.keys(filter || {}).length) {
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
let filteredData = [...rows]
|
let filteredData = [...rows]
|
||||||
Object.entries(filter).forEach(([field, value]) => {
|
Object.entries(filter).forEach(([field, value]) => {
|
||||||
if (value != null && value !== "") {
|
if (value != null && value !== "") {
|
||||||
filteredData = filteredData.filter(row => {
|
filteredData = filteredData.filter((row) => {
|
||||||
return row[field] === value
|
return row[field] === value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -84,7 +156,7 @@
|
||||||
return rows.slice(0, numLimit)
|
return rows.slice(0, numLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSchema = async dataSource => {
|
const getSchema = async (dataSource) => {
|
||||||
if (dataSource?.schema) {
|
if (dataSource?.schema) {
|
||||||
schema = dataSource.schema
|
schema = dataSource.schema
|
||||||
} else if (dataSource?.tableId) {
|
} else if (dataSource?.tableId) {
|
||||||
|
@ -101,6 +173,51 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nextPage = async () => {
|
||||||
|
if (!hasNextPage) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await API.searchTable({
|
||||||
|
tableId: dataSource?.tableId,
|
||||||
|
query,
|
||||||
|
bookmark: bookmarks[pageNumber + 1],
|
||||||
|
limit,
|
||||||
|
sort: sortColumn,
|
||||||
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
||||||
|
})
|
||||||
|
pageNumber++
|
||||||
|
rows = res.rows
|
||||||
|
|
||||||
|
// Check we have next data
|
||||||
|
const next = await API.searchTable({
|
||||||
|
tableId: dataSource.tableId,
|
||||||
|
query,
|
||||||
|
limit: 1,
|
||||||
|
bookmark: res.bookmark,
|
||||||
|
sort: sortColumn,
|
||||||
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
||||||
|
})
|
||||||
|
if (next.rows?.length) {
|
||||||
|
bookmarks[pageNumber + 1] = res.bookmark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevPage = async () => {
|
||||||
|
if (!hasPrevPage) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const res = await API.searchTable({
|
||||||
|
tableId: dataSource?.tableId,
|
||||||
|
query,
|
||||||
|
bookmark: bookmarks[pageNumber - 1],
|
||||||
|
limit,
|
||||||
|
sort: sortColumn,
|
||||||
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
||||||
|
})
|
||||||
|
pageNumber--
|
||||||
|
rows = res.rows
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={$component.styles}>
|
<div use:styleable={$component.styles}>
|
||||||
|
|
Loading…
Reference in New Issue