Allow data providers to inherit each other and add full client side lucene implementation
This commit is contained in:
parent
b970a01def
commit
1ae8264276
|
@ -136,7 +136,7 @@ const getContextBindings = (asset, componentId) => {
|
||||||
if (!datasource) {
|
if (!datasource) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const info = getSchemaForDatasource(datasource)
|
const info = getSchemaForDatasource(asset, datasource)
|
||||||
schema = info.schema
|
schema = info.schema
|
||||||
readablePrefix = info.table?.name
|
readablePrefix = info.table?.name
|
||||||
}
|
}
|
||||||
|
@ -191,7 +191,7 @@ const getContextBindings = (asset, componentId) => {
|
||||||
*/
|
*/
|
||||||
const getUserBindings = () => {
|
const getUserBindings = () => {
|
||||||
let bindings = []
|
let bindings = []
|
||||||
const { schema } = getSchemaForDatasource({
|
const { schema } = getSchemaForDatasource(null, {
|
||||||
type: "table",
|
type: "table",
|
||||||
tableId: TableNames.USERS,
|
tableId: TableNames.USERS,
|
||||||
})
|
})
|
||||||
|
@ -244,11 +244,15 @@ const getUrlBindings = asset => {
|
||||||
/**
|
/**
|
||||||
* Gets a schema for a datasource object.
|
* Gets a schema for a datasource object.
|
||||||
*/
|
*/
|
||||||
export const getSchemaForDatasource = (datasource, isForm = false) => {
|
export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
|
||||||
let schema, table
|
let schema, table
|
||||||
if (datasource) {
|
if (datasource) {
|
||||||
const { type } = datasource
|
const { type } = datasource
|
||||||
if (type === "query") {
|
if (type === "provider") {
|
||||||
|
const component = findComponent(asset.props, datasource.providerId)
|
||||||
|
const source = getDatasourceForProvider(asset, component)
|
||||||
|
return getSchemaForDatasource(asset, source, isForm)
|
||||||
|
} else if (type === "query") {
|
||||||
const queries = get(queriesStores).list
|
const queries = get(queriesStores).list
|
||||||
table = queries.find(query => query._id === datasource._id)
|
table = queries.find(query => query._id === datasource._id)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -174,7 +174,7 @@ const fieldTypeToComponentMap = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeDatasourceFormComponents(datasource) {
|
export function makeDatasourceFormComponents(datasource) {
|
||||||
const { schema } = getSchemaForDatasource(datasource, true)
|
const { schema } = getSchemaForDatasource(null, datasource, true)
|
||||||
let components = []
|
let components = []
|
||||||
let fields = Object.keys(schema || {})
|
let fields = Object.keys(schema || {})
|
||||||
fields.forEach(field => {
|
fields.forEach(field => {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
import {
|
||||||
|
getBindableProperties,
|
||||||
|
getDataProviderComponents,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
Popover,
|
||||||
|
@ -61,6 +64,17 @@
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
$store.selectedComponentId
|
$store.selectedComponentId
|
||||||
)
|
)
|
||||||
|
$: dataProviders = getDataProviderComponents(
|
||||||
|
$currentAsset,
|
||||||
|
$store.selectedComponentId
|
||||||
|
).map(provider => ({
|
||||||
|
label: provider._instanceName,
|
||||||
|
name: provider._instanceName,
|
||||||
|
providerId: provider._id,
|
||||||
|
value: `{{ literal [${provider._id}] }}`,
|
||||||
|
type: "provider",
|
||||||
|
schema: provider.schema,
|
||||||
|
}))
|
||||||
$: queryBindableProperties = bindableProperties.map(property => ({
|
$: queryBindableProperties = bindableProperties.map(property => ({
|
||||||
...property,
|
...property,
|
||||||
category: property.type === "instance" ? "Component" : "Table",
|
category: property.type === "instance" ? "Component" : "Table",
|
||||||
|
@ -182,7 +196,20 @@
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
<Divider size="S" />
|
||||||
|
<div class="title">
|
||||||
|
<Heading size="XS">Data Providers</Heading>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
{#each dataProviders as provider}
|
||||||
|
<li
|
||||||
|
class:selected={value === provider}
|
||||||
|
on:click={() => handleSelected(provider)}
|
||||||
|
>
|
||||||
|
{provider.label}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
{#if otherSources?.length}
|
{#if otherSources?.length}
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
<div class="title">
|
<div class="title">
|
||||||
|
|
|
@ -14,11 +14,11 @@
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
$store.selectedComponentId
|
$store.selectedComponentId
|
||||||
)
|
)
|
||||||
$: schemaFields = getSchemaFields(parameters?.tableId)
|
$: schemaFields = getSchemaFields($currentAsset, parameters?.tableId)
|
||||||
$: tableOptions = $tables.list || []
|
$: tableOptions = $tables.list || []
|
||||||
|
|
||||||
const getSchemaFields = tableId => {
|
const getSchemaFields = (asset, tableId) => {
|
||||||
const { schema } = getSchemaForDatasource({ type: "table", tableId })
|
const { schema } = getSchemaForDatasource(asset, { type: "table", tableId })
|
||||||
return Object.values(schema || {})
|
return Object.values(schema || {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchemaForDatasource(datasource).schema
|
$: schema = getSchemaForDatasource($currentAsset, datasource).schema
|
||||||
$: options = Object.keys(schema || {})
|
$: options = Object.keys(schema || {})
|
||||||
$: boundValue = getValidValue(value, options)
|
$: boundValue = getValidValue(value, options)
|
||||||
|
|
||||||
|
|
|
@ -27,19 +27,16 @@
|
||||||
? tempValue.length
|
? tempValue.length
|
||||||
: Object.keys(tempValue || {}).length
|
: Object.keys(tempValue || {}).length
|
||||||
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchemaForDatasource(dataSource)?.schema
|
$: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema
|
||||||
$: schemaFields = Object.values(schema || {})
|
$: schemaFields = Object.values(schema || {})
|
||||||
$: internalTable = dataSource?.type === "table"
|
$: internalTable = dataSource?.type === "table"
|
||||||
|
|
||||||
// Reset value if value is wrong type for the datasource.
|
// Reset value if value is wrong type for the datasource.
|
||||||
// Lucene editor needs an array, and simple editor needs an object.
|
// Lucene editor needs an array, and simple editor needs an object.
|
||||||
$: {
|
$: {
|
||||||
if (internalTable && !Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
tempValue = []
|
tempValue = []
|
||||||
dispatch("change", [])
|
dispatch("change", [])
|
||||||
} else if (!internalTable && Array.isArray(value)) {
|
|
||||||
tempValue = {}
|
|
||||||
dispatch("change", {})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,28 +60,7 @@
|
||||||
constaints.
|
constaints.
|
||||||
{/if}
|
{/if}
|
||||||
</Body>
|
</Body>
|
||||||
{#if internalTable}
|
|
||||||
<LuceneFilterBuilder bind:value={tempValue} {schemaFields} />
|
<LuceneFilterBuilder bind:value={tempValue} {schemaFields} />
|
||||||
{:else}
|
|
||||||
<div class="fields">
|
|
||||||
<SaveFields
|
|
||||||
parameterFields={Array.isArray(value) ? {} : value}
|
|
||||||
{schemaFields}
|
|
||||||
valueLabel="Equals"
|
|
||||||
on:change={e => (tempValue = e.detail)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</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>
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
component => component._component === "@budibase/standard-components/form"
|
component => component._component === "@budibase/standard-components/form"
|
||||||
)
|
)
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, form)
|
$: datasource = getDatasourceForProvider($currentAsset, form)
|
||||||
$: schema = getSchemaForDatasource(datasource, true).schema
|
$: schema = getSchemaForDatasource($currentAsset, datasource, true).schema
|
||||||
$: options = getOptions(schema, type)
|
$: options = getOptions(schema, type)
|
||||||
|
|
||||||
const getOptions = (schema, fieldType) => {
|
const getOptions = (schema, fieldType) => {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchemaForDatasource(datasource).schema
|
$: schema = getSchemaForDatasource($currentAsset, datasource).schema
|
||||||
$: options = Object.keys(schema || {})
|
$: options = Object.keys(schema || {})
|
||||||
$: boundValue = getValidOptions(value, options)
|
$: boundValue = getValidOptions(value, options)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { ProgressCircle, Pagination } from "@budibase/bbui"
|
import { ProgressCircle, Pagination } from "@budibase/bbui"
|
||||||
|
import {
|
||||||
|
buildLuceneQuery,
|
||||||
|
luceneQuery,
|
||||||
|
luceneSort,
|
||||||
|
luceneLimit,
|
||||||
|
} from "./lucene"
|
||||||
|
|
||||||
export let dataSource
|
export let dataSource
|
||||||
export let filter
|
export let filter
|
||||||
|
@ -27,7 +33,7 @@
|
||||||
let pageNumber = 0
|
let pageNumber = 0
|
||||||
|
|
||||||
$: internalTable = dataSource?.type === "table"
|
$: internalTable = dataSource?.type === "table"
|
||||||
$: query = internalTable ? buildLuceneQuery(filter) : null
|
$: query = buildLuceneQuery(filter)
|
||||||
$: hasNextPage = bookmarks[pageNumber + 1] != null
|
$: hasNextPage = bookmarks[pageNumber + 1] != null
|
||||||
$: hasPrevPage = pageNumber > 0
|
$: hasPrevPage = pageNumber > 0
|
||||||
$: getSchema(dataSource)
|
$: getSchema(dataSource)
|
||||||
|
@ -48,18 +54,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$: {
|
$: {
|
||||||
// Sort and limit rows in memory when we aren't searching internal tables
|
|
||||||
if (internalTable) {
|
if (internalTable) {
|
||||||
|
// Internal tables are already processed server-side
|
||||||
rows = allRows
|
rows = allRows
|
||||||
} else {
|
} else {
|
||||||
const sortedRows = sortRows(allRows, sortColumn, sortOrder)
|
// For anything else we use client-side implementations to filter, sort
|
||||||
rows = limitRows(sortedRows, limit)
|
// and limit
|
||||||
|
const filtered = luceneQuery(allRows, query)
|
||||||
|
const sorted = luceneSort(filtered, sortColumn, sortOrder, sortType)
|
||||||
|
rows = luceneLimit(sorted, limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$: actions = [
|
$: actions = [
|
||||||
{
|
{
|
||||||
type: ActionTypes.RefreshDatasource,
|
type: ActionTypes.RefreshDatasource,
|
||||||
callback: () => fetchData(dataSource),
|
callback: () => refresh(),
|
||||||
metadata: { dataSource },
|
metadata: { dataSource },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -73,36 +82,18 @@
|
||||||
return type === "number" ? "number" : "string"
|
return type === "number" ? "number" : "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildLuceneQuery = filter => {
|
const refresh = async () => {
|
||||||
let query = {
|
if (schemaLoaded) {
|
||||||
string: {},
|
fetchData(
|
||||||
fuzzy: {},
|
dataSource,
|
||||||
range: {},
|
query,
|
||||||
equal: {},
|
limit,
|
||||||
notEqual: {},
|
sortColumn,
|
||||||
empty: {},
|
sortOrder,
|
||||||
notEmpty: {},
|
sortType,
|
||||||
|
paginate
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (Array.isArray(filter)) {
|
|
||||||
filter.forEach(({ operator, field, type, value }) => {
|
|
||||||
if (operator.startsWith("range")) {
|
|
||||||
if (!query.range[field]) {
|
|
||||||
query.range[field] = {
|
|
||||||
low: type === "number" ? Number.MIN_SAFE_INTEGER : "0000",
|
|
||||||
high: type === "number" ? Number.MAX_SAFE_INTEGER : "9999",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (operator === "rangeLow") {
|
|
||||||
query.range[field].low = value
|
|
||||||
} else if (operator === "rangeHigh") {
|
|
||||||
query.range[field].high = value
|
|
||||||
}
|
|
||||||
} else if (query[operator]) {
|
|
||||||
query[operator][field] = value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return query
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = async (
|
const fetchData = async (
|
||||||
|
@ -116,6 +107,7 @@
|
||||||
) => {
|
) => {
|
||||||
loading = true
|
loading = true
|
||||||
if (dataSource?.type === "table") {
|
if (dataSource?.type === "table") {
|
||||||
|
// For internal tables we use server-side processing
|
||||||
const res = await API.searchTable({
|
const res = await API.searchTable({
|
||||||
tableId: dataSource.tableId,
|
tableId: dataSource.tableId,
|
||||||
query,
|
query,
|
||||||
|
@ -132,55 +124,27 @@
|
||||||
} else {
|
} else {
|
||||||
bookmarks = [null]
|
bookmarks = [null]
|
||||||
}
|
}
|
||||||
|
} else if (dataSource?.type === "provider") {
|
||||||
|
// For providers referencing another provider, just use the rows it
|
||||||
|
// provides
|
||||||
|
allRows = dataSource?.value?.rows ?? []
|
||||||
} else {
|
} else {
|
||||||
const rows = await API.fetchDatasource(dataSource)
|
// For other data sources like queries or views, fetch all rows from the
|
||||||
allRows = inMemoryFilterRows(rows, filter)
|
// server
|
||||||
|
allRows = await API.fetchDatasource(dataSource)
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const inMemoryFilterRows = (rows, filter) => {
|
|
||||||
let filteredData = [...rows]
|
|
||||||
Object.entries(filter || {}).forEach(([field, value]) => {
|
|
||||||
if (value != null && value !== "") {
|
|
||||||
filteredData = filteredData.filter(row => {
|
|
||||||
return row[field] === value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return filteredData
|
|
||||||
}
|
|
||||||
|
|
||||||
const sortRows = (rows, sortColumn, sortOrder) => {
|
|
||||||
if (!sortColumn || !sortOrder) {
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
return rows.slice().sort((a, b) => {
|
|
||||||
const colA = a[sortColumn]
|
|
||||||
const colB = b[sortColumn]
|
|
||||||
if (sortOrder === "Descending") {
|
|
||||||
return colA > colB ? -1 : 1
|
|
||||||
} else {
|
|
||||||
return colA > colB ? 1 : -1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const limitRows = (rows, limit) => {
|
|
||||||
const numLimit = parseFloat(limit)
|
|
||||||
if (isNaN(numLimit)) {
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
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) {
|
||||||
const definition = await API.fetchTableDefinition(dataSource.tableId)
|
const definition = await API.fetchTableDefinition(dataSource.tableId)
|
||||||
schema = definition?.schema ?? {}
|
schema = definition?.schema ?? {}
|
||||||
|
} else if (dataSource?.type === "provider") {
|
||||||
|
schema = dataSource.value?.schema ?? {}
|
||||||
} else {
|
} else {
|
||||||
schema = {}
|
schema = {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
/**
|
||||||
|
* Builds a lucene JSON query from the filter structure generated in the builder
|
||||||
|
* @param filter the builder filter structure
|
||||||
|
*/
|
||||||
|
export const buildLuceneQuery = filter => {
|
||||||
|
let query = {
|
||||||
|
string: {},
|
||||||
|
fuzzy: {},
|
||||||
|
range: {},
|
||||||
|
equal: {},
|
||||||
|
notEqual: {},
|
||||||
|
empty: {},
|
||||||
|
notEmpty: {},
|
||||||
|
}
|
||||||
|
if (Array.isArray(filter)) {
|
||||||
|
filter.forEach(({ operator, field, type, value }) => {
|
||||||
|
if (operator.startsWith("range")) {
|
||||||
|
if (!query.range[field]) {
|
||||||
|
query.range[field] = {
|
||||||
|
low: type === "number" ? Number.MIN_SAFE_INTEGER : "0000",
|
||||||
|
high: type === "number" ? Number.MAX_SAFE_INTEGER : "9999",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (operator === "rangeLow") {
|
||||||
|
query.range[field].low = value
|
||||||
|
} else if (operator === "rangeHigh") {
|
||||||
|
query.range[field].high = value
|
||||||
|
}
|
||||||
|
} else if (query[operator]) {
|
||||||
|
query[operator][field] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a client-side lucene search on an array of data
|
||||||
|
* @param docs the data
|
||||||
|
* @param query the JSON lucene query
|
||||||
|
*/
|
||||||
|
export const luceneQuery = (docs, query) => {
|
||||||
|
if (!query) {
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterates over a set of filters and evaluates a fail function against a doc
|
||||||
|
const match = (type, failFn) => doc => {
|
||||||
|
const filters = Object.entries(query[type] || {})
|
||||||
|
for (let i = 0; i < filters.length; i++) {
|
||||||
|
if (failFn(filters[i][0], filters[i][1], doc)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a string match (fails if the value does not start with the string)
|
||||||
|
const stringMatch = match("string", (key, value, doc) => {
|
||||||
|
return !doc[key] || !doc[key].startsWith(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process a range match
|
||||||
|
const rangeMatch = match("range", (key, value, doc) => {
|
||||||
|
return !doc[key] || doc[key] < value.low || doc[key] > value.high
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process an equal match (fails if the value is different)
|
||||||
|
const equalMatch = match("equal", (key, value, doc) => {
|
||||||
|
return doc[key] !== value
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process a not-equal match (fails if the value is the same)
|
||||||
|
const notEqualMatch = match("notEqual", (key, value, doc) => {
|
||||||
|
return doc[key] === value
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process an empty match (fails if the value is not empty)
|
||||||
|
const emptyMatch = match("empty", (key, value, doc) => {
|
||||||
|
return doc[key] != null && doc[key] !== ""
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process a not-empty match (fails is the value is empty)
|
||||||
|
const notEmptyMatch = match("notEmpty", (key, value, doc) => {
|
||||||
|
return doc[key] == null || doc[key] === ""
|
||||||
|
})
|
||||||
|
|
||||||
|
// Match a document against all criteria
|
||||||
|
const docMatch = doc => {
|
||||||
|
return (
|
||||||
|
stringMatch(doc) &&
|
||||||
|
rangeMatch(doc) &&
|
||||||
|
equalMatch(doc) &&
|
||||||
|
notEqualMatch(doc) &&
|
||||||
|
emptyMatch(doc) &&
|
||||||
|
notEmptyMatch(doc)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process all docs
|
||||||
|
return docs.filter(docMatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a client-side sort from the equivalent server-side lucene sort
|
||||||
|
* parameters.
|
||||||
|
* @param docs the data
|
||||||
|
* @param sort the sort column
|
||||||
|
* @param sortOrder the sort order ("ascending" or "descending")
|
||||||
|
* @param sortType the type of sort ("string" or "number")
|
||||||
|
*/
|
||||||
|
export const luceneSort = (docs, sort, sortOrder, sortType = "string") => {
|
||||||
|
if (!sort || !sortOrder || !sortType) {
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
const parse = sortType === "string" ? x => `${x}` : x => parseFloat(x)
|
||||||
|
return docs.slice().sort((a, b) => {
|
||||||
|
const colA = parse(a[sort])
|
||||||
|
const colB = parse(b[sort])
|
||||||
|
if (sortOrder === "Descending") {
|
||||||
|
return colA > colB ? -1 : 1
|
||||||
|
} else {
|
||||||
|
return colA > colB ? 1 : -1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limits the specified docs to the specified number of rows from the equivalent
|
||||||
|
* server-side lucene limit parameters.
|
||||||
|
* @param docs the data
|
||||||
|
* @param limit the number of docs to limit to
|
||||||
|
*/
|
||||||
|
export const luceneLimit = (docs, limit) => {
|
||||||
|
const numLimit = parseFloat(limit)
|
||||||
|
if (isNaN(numLimit)) {
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
return docs.slice(0, numLimit)
|
||||||
|
}
|
Loading…
Reference in New Issue