Add data-utils with filters
This commit is contained in:
parent
ab7ecda9ec
commit
10cba43ac4
|
@ -0,0 +1 @@
|
||||||
|
node_modules/
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"name": "@budibase/data-utils",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Shared data utils",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"author": "Budibase",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "4.7.3"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
export 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 or equal to",
|
||||||
|
},
|
||||||
|
LessThan: {
|
||||||
|
value: "rangeHigh",
|
||||||
|
label: "Less than or equal to",
|
||||||
|
},
|
||||||
|
Contains: {
|
||||||
|
value: "contains",
|
||||||
|
label: "Contains",
|
||||||
|
},
|
||||||
|
NotContains: {
|
||||||
|
value: "notContains",
|
||||||
|
label: "Does not contain",
|
||||||
|
},
|
||||||
|
In: {
|
||||||
|
value: "oneOf",
|
||||||
|
label: "Is in",
|
||||||
|
},
|
||||||
|
ContainsAny: {
|
||||||
|
value: "containsAny",
|
||||||
|
label: "Has any",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SqlNumberTypeRangeMap = {
|
||||||
|
integer: {
|
||||||
|
max: 2147483647,
|
||||||
|
min: -2147483648,
|
||||||
|
},
|
||||||
|
int: {
|
||||||
|
max: 2147483647,
|
||||||
|
min: -2147483648,
|
||||||
|
},
|
||||||
|
smallint: {
|
||||||
|
max: 32767,
|
||||||
|
min: -32768,
|
||||||
|
},
|
||||||
|
mediumint: {
|
||||||
|
max: 8388607,
|
||||||
|
min: -8388608,
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,427 @@
|
||||||
|
import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants"
|
||||||
|
|
||||||
|
const HBS_REGEX = /{{([^{].*?)}}/g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the valid operator options for a certain data type
|
||||||
|
* @param type the data type
|
||||||
|
*/
|
||||||
|
export const getValidOperatorsForType = (
|
||||||
|
type: string,
|
||||||
|
field: string,
|
||||||
|
datasource: { tableId: string | string[]; type: string }
|
||||||
|
) => {
|
||||||
|
const Op = OperatorOptions
|
||||||
|
const stringOps = [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.StartsWith,
|
||||||
|
Op.Like,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
Op.In,
|
||||||
|
]
|
||||||
|
const numOps = [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.MoreThan,
|
||||||
|
Op.LessThan,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
Op.In,
|
||||||
|
]
|
||||||
|
let ops: any[] = []
|
||||||
|
if (type === "string") {
|
||||||
|
ops = stringOps
|
||||||
|
} else if (type === "number") {
|
||||||
|
ops = numOps
|
||||||
|
} else if (type === "options") {
|
||||||
|
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
|
||||||
|
} else if (type === "array") {
|
||||||
|
ops = [Op.Contains, Op.NotContains, Op.Empty, Op.NotEmpty, Op.ContainsAny]
|
||||||
|
} else if (type === "boolean") {
|
||||||
|
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||||
|
} else if (type === "longform") {
|
||||||
|
ops = stringOps
|
||||||
|
} else if (type === "datetime") {
|
||||||
|
ops = numOps
|
||||||
|
} else if (type === "formula") {
|
||||||
|
ops = stringOps.concat([Op.MoreThan, Op.LessThan])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out "like" for internal tables
|
||||||
|
const externalTable = datasource?.tableId?.includes("datasource_plus")
|
||||||
|
if (datasource?.type === "table" && !externalTable) {
|
||||||
|
ops = ops.filter(x => x !== Op.Like)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow equal/not equal for _id in SQL tables
|
||||||
|
if (field === "_id" && externalTable) {
|
||||||
|
ops = [Op.Equals, Op.NotEquals]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operators which do not support empty strings as values
|
||||||
|
*/
|
||||||
|
export const NoEmptyFilterStrings = [
|
||||||
|
OperatorOptions.StartsWith.value,
|
||||||
|
OperatorOptions.Like.value,
|
||||||
|
OperatorOptions.Equals.value,
|
||||||
|
OperatorOptions.NotEquals.value,
|
||||||
|
OperatorOptions.Contains.value,
|
||||||
|
OperatorOptions.NotContains.value,
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any fields that contain empty strings that would cause inconsistent
|
||||||
|
* behaviour with how backend tables are filtered (no value means no filter).
|
||||||
|
*/
|
||||||
|
const cleanupQuery = (query: { [x: string]: { [x: string]: any } }) => {
|
||||||
|
if (!query) {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
for (let filterField of NoEmptyFilterStrings) {
|
||||||
|
if (!query[filterField]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (let [key, value] of Object.entries(query[filterField])) {
|
||||||
|
if (value == null || value === "") {
|
||||||
|
delete query[filterField][key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a numeric prefix on field names designed to give fields uniqueness
|
||||||
|
*/
|
||||||
|
const removeKeyNumbering = (key: string) => {
|
||||||
|
if (typeof key === "string" && key.match(/\d[0-9]*:/g) != null) {
|
||||||
|
const parts = key.split(":")
|
||||||
|
parts.shift()
|
||||||
|
return parts.join(":")
|
||||||
|
} else {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a lucene JSON query from the filter structure generated in the builder
|
||||||
|
* @param filter the builder filter structure
|
||||||
|
*/
|
||||||
|
export const buildLuceneQuery = (filter: any[]) => {
|
||||||
|
let query = {
|
||||||
|
string: {},
|
||||||
|
fuzzy: {},
|
||||||
|
range: {},
|
||||||
|
equal: {},
|
||||||
|
notEqual: {},
|
||||||
|
empty: {},
|
||||||
|
notEmpty: {},
|
||||||
|
contains: {},
|
||||||
|
notContains: {},
|
||||||
|
oneOf: {},
|
||||||
|
containsAny: {},
|
||||||
|
}
|
||||||
|
if (Array.isArray(filter)) {
|
||||||
|
filter.forEach(expression => {
|
||||||
|
let { operator, field, type, value, externalType } = expression
|
||||||
|
const isHbs =
|
||||||
|
typeof value === "string" && value.match(HBS_REGEX)?.length > 0
|
||||||
|
// Parse all values into correct types
|
||||||
|
if (operator === "allOr") {
|
||||||
|
query.allOr = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
type === "datetime" &&
|
||||||
|
!isHbs &&
|
||||||
|
operator !== "empty" &&
|
||||||
|
operator !== "notEmpty"
|
||||||
|
) {
|
||||||
|
// Ensure date value is a valid date and parse into correct format
|
||||||
|
if (!value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
value = new Date(value).toISOString()
|
||||||
|
} catch (error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === "number" && typeof value === "string") {
|
||||||
|
if (operator === "oneOf") {
|
||||||
|
value = value.split(",").map(item => parseFloat(item))
|
||||||
|
} else if (!isHbs) {
|
||||||
|
value = parseFloat(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === "boolean") {
|
||||||
|
value = `${value}`?.toLowerCase() === "true"
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
["contains", "notContains", "containsAny"].includes(operator) &&
|
||||||
|
type === "array" &&
|
||||||
|
typeof value === "string"
|
||||||
|
) {
|
||||||
|
value = value.split(",")
|
||||||
|
}
|
||||||
|
if (operator.startsWith("range")) {
|
||||||
|
const minint =
|
||||||
|
SqlNumberTypeRangeMap[externalType]?.min || Number.MIN_SAFE_INTEGER
|
||||||
|
const maxint =
|
||||||
|
SqlNumberTypeRangeMap[externalType]?.max || Number.MAX_SAFE_INTEGER
|
||||||
|
if (!query.range[field]) {
|
||||||
|
query.range[field] = {
|
||||||
|
low: type === "number" ? minint : "0000-00-00T00:00:00.000Z",
|
||||||
|
high: type === "number" ? maxint : "9999-00-00T00:00:00.000Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (operator === "rangeLow" && value != null && value !== "") {
|
||||||
|
query.range[field].low = value
|
||||||
|
} else if (operator === "rangeHigh" && value != null && value !== "") {
|
||||||
|
query.range[field].high = value
|
||||||
|
}
|
||||||
|
} else if (query[operator]) {
|
||||||
|
if (type === "boolean") {
|
||||||
|
// Transform boolean filters to cope with null.
|
||||||
|
// "equals false" needs to be "not equals true"
|
||||||
|
// "not equals false" needs to be "equals true"
|
||||||
|
if (operator === "equal" && value === false) {
|
||||||
|
query.notEqual[field] = true
|
||||||
|
} else if (operator === "notEqual" && value === false) {
|
||||||
|
query.equal[field] = true
|
||||||
|
} else {
|
||||||
|
query[operator][field] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query[operator][field] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
const deepGet = (obj: { [x: string]: any }, key: string) => {
|
||||||
|
if (!obj || !key) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
return obj[key]
|
||||||
|
}
|
||||||
|
const split = key.split(".")
|
||||||
|
for (let i = 0; i < split.length; i++) {
|
||||||
|
obj = obj?.[split[i]]
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a client-side lucene search on an array of data
|
||||||
|
* @param docs the data
|
||||||
|
* @param query the JSON lucene query
|
||||||
|
*/
|
||||||
|
export const runLuceneQuery = (
|
||||||
|
docs: any[],
|
||||||
|
query?: { [x: string]: any; sheet?: string }
|
||||||
|
) => {
|
||||||
|
if (!docs || !Array.isArray(docs)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (!query) {
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make query consistent first
|
||||||
|
query = cleanupQuery(query)
|
||||||
|
|
||||||
|
// Iterates over a set of filters and evaluates a fail function against a doc
|
||||||
|
const match =
|
||||||
|
(
|
||||||
|
type: string,
|
||||||
|
failFn: {
|
||||||
|
(docValue: any, testValue: any): boolean
|
||||||
|
(docValue: any, testValue: any): boolean
|
||||||
|
(docValue: any, testValue: any): boolean
|
||||||
|
(docValue: any, testValue: any): boolean
|
||||||
|
(docValue: any, testValue: any): boolean
|
||||||
|
(docValue: any): boolean
|
||||||
|
(docValue: any): boolean
|
||||||
|
(docValue: any, testValue: any): boolean
|
||||||
|
(docValue: any, testValue: any): boolean
|
||||||
|
(docValue: any, testValue: any): boolean
|
||||||
|
(docValue: any, testValue: any): any
|
||||||
|
(arg0: any, arg1: unknown): any
|
||||||
|
}
|
||||||
|
) =>
|
||||||
|
(doc: any) => {
|
||||||
|
const filters = Object.entries(query[type] || {})
|
||||||
|
for (let i = 0; i < filters.length; i++) {
|
||||||
|
const [key, testValue] = filters[i]
|
||||||
|
const docValue = deepGet(doc, removeKeyNumbering(key))
|
||||||
|
if (failFn(docValue, testValue)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a string match (fails if the value does not start with the string)
|
||||||
|
const stringMatch = match("string", (docValue: string, testValue: string) => {
|
||||||
|
return (
|
||||||
|
!docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process a fuzzy match (treat the same as starts with when running locally)
|
||||||
|
const fuzzyMatch = match("fuzzy", (docValue: string, testValue: string) => {
|
||||||
|
return (
|
||||||
|
!docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process a range match
|
||||||
|
const rangeMatch = match(
|
||||||
|
"range",
|
||||||
|
(
|
||||||
|
docValue: string | number | null,
|
||||||
|
testValue: { low: number; high: number }
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
docValue == null ||
|
||||||
|
docValue === "" ||
|
||||||
|
docValue < testValue.low ||
|
||||||
|
docValue > testValue.high
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Process an equal match (fails if the value is different)
|
||||||
|
const equalMatch = match(
|
||||||
|
"equal",
|
||||||
|
(docValue: any, testValue: string | null) => {
|
||||||
|
return testValue != null && testValue !== "" && docValue !== testValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Process a not-equal match (fails if the value is the same)
|
||||||
|
const notEqualMatch = match(
|
||||||
|
"notEqual",
|
||||||
|
(docValue: any, testValue: string | null) => {
|
||||||
|
return testValue != null && testValue !== "" && docValue === testValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Process an empty match (fails if the value is not empty)
|
||||||
|
const emptyMatch = match("empty", (docValue: string | null) => {
|
||||||
|
return docValue != null && docValue !== ""
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process a not-empty match (fails is the value is empty)
|
||||||
|
const notEmptyMatch = match("notEmpty", (docValue: string | null) => {
|
||||||
|
return docValue == null || docValue === ""
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process an includes match (fails if the value is not included)
|
||||||
|
const oneOf = match("oneOf", (docValue: any, testValue: string[]) => {
|
||||||
|
if (typeof testValue === "string") {
|
||||||
|
testValue = testValue.split(",")
|
||||||
|
if (typeof docValue === "number") {
|
||||||
|
testValue = testValue.map((item: string) => parseFloat(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !testValue?.includes(docValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
const containsAny = match(
|
||||||
|
"containsAny",
|
||||||
|
(docValue: string | any[], testValue: any) => {
|
||||||
|
return !docValue?.includes(...testValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const contains = match(
|
||||||
|
"contains",
|
||||||
|
(docValue: string | any[], testValue: any[]) => {
|
||||||
|
return !testValue?.every((item: any) => docValue?.includes(item))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const notContains = match(
|
||||||
|
"notContains",
|
||||||
|
(docValue: string | any[], testValue: any[]) => {
|
||||||
|
return testValue?.every((item: any) => docValue?.includes(item))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Match a document against all criteria
|
||||||
|
const docMatch = (doc: any) => {
|
||||||
|
return (
|
||||||
|
stringMatch(doc) &&
|
||||||
|
fuzzyMatch(doc) &&
|
||||||
|
rangeMatch(doc) &&
|
||||||
|
equalMatch(doc) &&
|
||||||
|
notEqualMatch(doc) &&
|
||||||
|
emptyMatch(doc) &&
|
||||||
|
notEmptyMatch(doc) &&
|
||||||
|
oneOf(doc) &&
|
||||||
|
contains(doc) &&
|
||||||
|
containsAny(doc) &&
|
||||||
|
notContains(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: any[],
|
||||||
|
sort: string | number,
|
||||||
|
sortOrder: string,
|
||||||
|
sortType = "string"
|
||||||
|
) => {
|
||||||
|
if (!sort || !sortOrder || !sortType) {
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
const parse =
|
||||||
|
sortType === "string" ? (x: any) => `${x}` : (x: string) => parseFloat(x)
|
||||||
|
return docs
|
||||||
|
.slice()
|
||||||
|
.sort((a: { [x: string]: any }, b: { [x: string]: any }) => {
|
||||||
|
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: string | any[], limit: string) => {
|
||||||
|
const numLimit = parseFloat(limit)
|
||||||
|
if (isNaN(numLimit)) {
|
||||||
|
return docs
|
||||||
|
}
|
||||||
|
return docs.slice(0, numLimit)
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./constants"
|
||||||
|
export * from "./filters"
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"lib": ["es2020"],
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"incremental": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"types": [ "node", "jest" ],
|
||||||
|
"outDir": "dist",
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.js",
|
||||||
|
"**/*.ts",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"__mocks__"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.build.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"baseUrl": "."
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
typescript@4.7.3:
|
||||||
|
version "4.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
|
||||||
|
integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
|
|
@ -45,6 +45,7 @@
|
||||||
"@apidevtools/swagger-parser": "10.0.3",
|
"@apidevtools/swagger-parser": "10.0.3",
|
||||||
"@budibase/backend-core": "2.3.18-alpha.29",
|
"@budibase/backend-core": "2.3.18-alpha.29",
|
||||||
"@budibase/client": "2.3.18-alpha.29",
|
"@budibase/client": "2.3.18-alpha.29",
|
||||||
|
"@budibase/data-utils": "0.0.1",
|
||||||
"@budibase/pro": "2.3.18-alpha.29",
|
"@budibase/pro": "2.3.18-alpha.29",
|
||||||
"@budibase/string-templates": "2.3.18-alpha.29",
|
"@budibase/string-templates": "2.3.18-alpha.29",
|
||||||
"@budibase/types": "2.3.18-alpha.29",
|
"@budibase/types": "2.3.18-alpha.29",
|
||||||
|
|
|
@ -2,8 +2,11 @@ import {
|
||||||
DatasourceFieldType,
|
DatasourceFieldType,
|
||||||
DatasourcePlus,
|
DatasourcePlus,
|
||||||
Integration,
|
Integration,
|
||||||
|
PaginationJson,
|
||||||
QueryJson,
|
QueryJson,
|
||||||
QueryType,
|
QueryType,
|
||||||
|
SearchFilters,
|
||||||
|
SortJson,
|
||||||
Table,
|
Table,
|
||||||
TableSchema,
|
TableSchema,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
@ -13,6 +16,7 @@ import { DataSourceOperation, FieldTypes } from "../constants"
|
||||||
import { GoogleSpreadsheet } from "google-spreadsheet"
|
import { GoogleSpreadsheet } from "google-spreadsheet"
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
import { configs, HTTPError } from "@budibase/backend-core"
|
import { configs, HTTPError } from "@budibase/backend-core"
|
||||||
|
import { runLuceneQuery } from "@budibase/data-utils"
|
||||||
|
|
||||||
interface GoogleSheetsConfig {
|
interface GoogleSheetsConfig {
|
||||||
spreadsheetId: string
|
spreadsheetId: string
|
||||||
|
@ -237,7 +241,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
const handlers = {
|
const handlers = {
|
||||||
[DataSourceOperation.CREATE]: () =>
|
[DataSourceOperation.CREATE]: () =>
|
||||||
this.create({ sheet, row: json.body }),
|
this.create({ sheet, row: json.body }),
|
||||||
[DataSourceOperation.READ]: () => this.read({ sheet }),
|
[DataSourceOperation.READ]: () => this.read({ ...json, sheet }),
|
||||||
[DataSourceOperation.UPDATE]: () =>
|
[DataSourceOperation.UPDATE]: () =>
|
||||||
this.update({
|
this.update({
|
||||||
// exclude the header row and zero index
|
// exclude the header row and zero index
|
||||||
|
@ -345,14 +349,20 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async read(query: { sheet: string }) {
|
async read(query: {
|
||||||
|
sheet: string
|
||||||
|
filters?: SearchFilters
|
||||||
|
sort?: SortJson
|
||||||
|
paginate?: PaginationJson
|
||||||
|
}) {
|
||||||
try {
|
try {
|
||||||
await this.connect()
|
await this.connect()
|
||||||
const sheet = this.client.sheetsByTitle[query.sheet]
|
const sheet = this.client.sheetsByTitle[query.sheet]
|
||||||
const rows = await sheet.getRows()
|
const rows = await sheet.getRows()
|
||||||
|
const filtered = runLuceneQuery(rows, query.filters)
|
||||||
const headerValues = sheet.headerValues
|
const headerValues = sheet.headerValues
|
||||||
const response = []
|
const response = []
|
||||||
for (let row of rows) {
|
for (let row of filtered) {
|
||||||
response.push(
|
response.push(
|
||||||
this.buildRowObject(headerValues, row._rawData, row._rowNumber)
|
this.buildRowObject(headerValues, row._rawData, row._rowNumber)
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"@budibase/types": ["../types/src"],
|
"@budibase/types": ["../types/src"],
|
||||||
"@budibase/backend-core": ["../backend-core/src"],
|
"@budibase/backend-core": ["../backend-core/src"],
|
||||||
"@budibase/backend-core/*": ["../backend-core/*"],
|
"@budibase/backend-core/*": ["../backend-core/*"],
|
||||||
|
"@budibase/data-utils": ["../data-utils/src"],
|
||||||
"@budibase/pro": ["../../../budibase-pro/packages/pro/src"]
|
"@budibase/pro": ["../../../budibase-pro/packages/pro/src"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -19,15 +20,9 @@
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "../types" },
|
{ "path": "../types" },
|
||||||
{ "path": "../backend-core" },
|
{ "path": "../backend-core" },
|
||||||
|
{ "path": "../data-utils" },
|
||||||
{ "path": "../../../budibase-pro/packages/pro" }
|
{ "path": "../../../budibase-pro/packages/pro" }
|
||||||
],
|
],
|
||||||
"include": [
|
"include": ["src/**/*", "specs", "package.json"],
|
||||||
"src/**/*",
|
"exclude": ["node_modules", "dist"]
|
||||||
"specs",
|
}
|
||||||
"package.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"dist"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue