Returns all of the items in an array after the specified index. Opposite of before.
\n" }, "arrayify": { @@ -154,7 +154,7 @@ "n" ], "numArgs": 2, - "example": "{{ before [1, 2, 3] 2}} -> [1, 2]", + "example": "{{ before ['a', 'b', 'c', 'd'] 3}} -> ['a', 'b']", "description": "Return all of the items in the collection before the specified count. Opposite of after.
\n" }, "eachIndex": { @@ -182,7 +182,7 @@ "n" ], "numArgs": 2, - "example": "{{first [1, 2, 3, 4] 2}} -> [1, 2]", + "example": "{{first [1, 2, 3, 4] 2}} -> 1,2", "description": "Returns the first item, or first n
items of an array.
Block helper that renders the block if an array has the given value
. Optionally specify an inverse block to render when the array does not have the given value.
Join all elements of array into a string, optionally using a given separator.
\n" }, "equalsLength": { @@ -236,7 +236,7 @@ "options" ], "numArgs": 3, - "example": "{{equalsLength '[1,2,3]' 3}} -> true", + "example": "{{equalsLength [1, 2, 3] 3}} -> true", "description": "Returns true if the the length of the given value
is equal to the given length
. Can be used as a block or inline helper.
Returns the length of the given string or array.
\n" }, "lengthEqual": { @@ -263,7 +263,7 @@ "options" ], "numArgs": 3, - "example": "{{equalsLength '[1,2,3]' 3}} -> true", + "example": "{{equalsLength [1, 2, 3] 3}} -> true", "description": "Returns true if the the length of the given value
is equal to the given length
. Can be used as a block or inline helper.
Block helper that returns the block if the callback returns true for some value in the given array.
\n" }, "sort": { @@ -317,7 +317,7 @@ "props" ], "numArgs": 2, - "example": "{{ sortBy [{a: 'zzz'}, {a: 'aaa'}] 'a' }} -> [{'a':'aaa'}, {'a':'zzz'}]", + "example": "{{ sortBy [{'a': 'zzz'}, {'a': 'aaa'}] 'a' }} -> [{'a':'aaa'},{'a':'zzz'}]", "description": "Sort an array
. If an array of objects is passed, you may optionally pass a key
to sort on as the second argument. You may alternatively pass a sorting function as the second argument.
Use the first item in a collection inside a handlebars block expression. Opposite of withLast.
\n" }, "withGroup": { @@ -357,7 +357,7 @@ "options" ], "numArgs": 3, - "example": "{{#withGroup [1, 2, 3, 4] 2}} {{#each this}} {{.}} {{each}}Block helper that groups array elements by given group size
.
Use the last item or n
items in an array as context inside a block. Opposite of withFirst.
Block helper that sorts a collection and exposes the sorted collection as context inside the block.
\n" }, "unique": { @@ -386,7 +386,7 @@ "options" ], "numArgs": 2, - "example": "{{#each (unique ['a', 'a', 'c', 'b', 'e', 'e']) }} {{.}} {{/each}} -> acbe", + "example": "{{#each (unique ['a', 'a', 'c', 'b', 'e', 'e']) }}{{.}}{{/each}} -> acbe", "description": "Block helper that return an array with all duplicate values removed. Best used along with a each helper.
\n" } }, @@ -396,7 +396,7 @@ "number" ], "numArgs": 1, - "example": "{{ bytes 1386 }} -> 1.4Kb", + "example": "{{ bytes 1386 1 }} -> 1.4 kB", "description": "Format a number to it's equivalent in bytes. If a string is passed, it's length will be formatted and returned. Examples: - 'foo' => 3 B
- 13661855 => 13.66 MB
- 825399 => 825.39 kB
- 1396 => 1.4 kB
Returns a string representing the given number in exponential notation.
\n" }, "toFixed": { @@ -472,7 +472,7 @@ "str" ], "numArgs": 1, - "example": "{{ encodeURI 'https://myurl?Hello There' }} -> https://myurl?Hello%20There", + "example": "{{ encodeURI 'https://myurl?Hello There' }} -> https%3A%2F%2Fmyurl%3FHello%20There", "description": "Encodes a Uniform Resource Identifier (URI) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character.
\n" }, "escape": { @@ -480,7 +480,7 @@ "str" ], "numArgs": 1, - "example": "{{ escape 'https://myurl?Hello+There' }} -> https://myurl?Hello%20There", + "example": "{{ escape 'https://myurl?Hello+There' }} -> https%3A%2F%2Fmyurl%3FHello%2BThere", "description": "Escape the given string by replacing characters with escape sequences. Useful for allowing the string to be used in a URL, etc.
\n" }, "decodeURI": { @@ -488,7 +488,7 @@ "str" ], "numArgs": 1, - "example": "{{ decodeURI 'https://myurl?Hello%20There' }} -> https://myurl?=Hello There", + "example": "{{ decodeURI 'https://myurl?Hello%20There' }} -> https://myurl?Hello There", "description": "Decode a Uniform Resource Identifier (URI) component.
\n" }, "urlResolve": { @@ -513,7 +513,7 @@ "url" ], "numArgs": 1, - "example": "{{ stripQueryString 'https://myurl/api/test?foo=bar' }} -> 'https://myurl/api/test'", + "example": "{{ stripQuerystring 'https://myurl/api/test?foo=bar' }} -> 'https://myurl/api/test'", "description": "Strip the query string from the given url
.
Strip protocol from a url
. Useful for displaying media that may have an 'http' protocol on secure connections.
Like trim, but removes both extraneous whitespace and non-word characters from the beginning and end of a string.
\n" }, "dashcase": { @@ -606,7 +606,7 @@ "length" ], "numArgs": 2, - "example": "{{ellipsis 'foo bar baz', 7}} -> foo bar…", + "example": "{{ellipsis 'foo bar baz' 7}} -> foo bar…", "description": "Truncates a string to the specified length
, and appends it with an elipsis, …
.
Prepends the given string
with the specified prefix
.
Render a block without processing mustache templates inside the block.
\n" - }, "remove": { "args": [ "str", @@ -698,7 +690,7 @@ "substring" ], "numArgs": 2, - "example": "{{remove 'a b a b a b' 'a'}} -> b a b a b", + "example": "{{removeFirst 'a b a b a b' 'a'}} -> ' b a b a b'", "description": "Remove the first occurrence of substring
from the given str
.
Replace the first occurrence of substring a
with substring b
.
Tests whether a string begins with the given prefix.
\n" }, "titleize": { @@ -760,7 +752,7 @@ "str" ], "numArgs": 1, - "example": "{{#titleize 'this is title case' }} -> This Is Title Case", + "example": "{{titleize 'this is title case' }} -> This Is Title Case", "description": "Title case the given string.
\n" }, "trim": { @@ -784,7 +776,7 @@ "string" ], "numArgs": 1, - "example": "{{trimRight ' ABC ' }} -> ' ABC '", + "example": "{{trimRight ' ABC ' }} -> ' ABC'", "description": "Removes extraneous whitespace from the end of a string.
\n" }, "truncate": { @@ -804,7 +796,7 @@ "suffix" ], "numArgs": 3, - "example": "{{truncateWords 'foo bar baz' 1 }} -> foo", + "example": "{{truncateWords 'foo bar baz' 1 }} -> foo…", "description": "Truncate a string to have the specified number of words. Also see truncate.
\n" }, "upcase": { @@ -844,7 +836,7 @@ "options" ], "numArgs": 4, - "example": "{{compare 10 '<' 5 }} -> true", + "example": "{{compare 10 '<' 5 }} -> false", "description": "Render a block when a comparison of the first and third arguments returns true. The second argument is the [arithemetic operator][operators] to use. You may also optionally specify an inverse block to render when falsy.
\n" }, "contains": { @@ -874,7 +866,7 @@ "options" ], "numArgs": 3, - "example": "{{#eq 3 3}} equal{{else}} not equal{{/eq}} -> equal", + "example": "{{#eq 3 3}}equal{{else}}not equal{{/eq}} -> equal", "description": "Block helper that renders a block if a
is equal to b
. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare=''
hash argument for the second value.
Block helper that renders a block if a
is greater than b
. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare=''
hash argument for the second value.
Block helper that renders a block if a
is greater than or equal to b
. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare=''
hash argument for the second value.
Block helper that renders a block if value
has pattern
. If an inverse block is specified it will be rendered when falsy.
Return true if the given value is an even number.
\n" }, "ifNth": { @@ -941,8 +933,8 @@ "options" ], "numArgs": 3, - "example": "{{#ifNth 10 2}} remainder {{else}} no remainder {{/ifNth}} -> remainder", - "description": "Conditionally renders a block if the remainder is zero when a
operand is divided by b
. If an inverse block is specified it will be rendered when the remainder is not zero.
Conditionally renders a block if the remainder is zero when b
operand is divided by a
. If an inverse block is specified it will be rendered when the remainder is not zero.
Block helper that renders a block if value
is an odd number. If an inverse block is specified it will be rendered when falsy.
Block helper that renders a block if a
is equal to b
. If an inverse block is specified it will be rendered when falsy. Similar to eq but does not do strict equality.
Block helper that renders a block if a
is not equal to b
. If an inverse block is specified it will be rendered when falsy. Similar to unlessEq but does not use strict equality for comparisons.
Block helper that renders a block if a
is less than b
. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare=''
hash argument for the second value.
Block helper that renders a block if a
is less than or equal to b
. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare=''
hash argument for the second value.
Block helper that renders a block if neither of the given values are truthy. If an inverse block is specified it will be rendered when falsy.
\n" }, "not": { @@ -1008,7 +1000,7 @@ "options" ], "numArgs": 2, - "example": "{{#not undefined }} falsey {{else}} not falsey {{/not}} -> falsey", + "example": "{{#not undefined }}falsey{{else}}not falsey{{/not}} -> falsey", "description": "Returns true if val
is falsey. Works as a block or inline helper.
Block helper that renders a block if any of the given values is truthy. If an inverse block is specified it will be rendered when falsy.
\n" }, "unlessEq": { @@ -1027,7 +1019,7 @@ "options" ], "numArgs": 3, - "example": "{{#unlessEq 2 1 }} not equal {{else}} equal {{/unlessEq}} -> not equal", + "example": "{{#unlessEq 2 1 }} not equal {{else}} equal {{/unlessEq}} -> ' not equal '", "description": "Block helper that always renders the inverse block unless a
is equal to b
.
Block helper that always renders the inverse block unless a
is greater than b
.
Block helper that always renders the inverse block unless a
is less than b
.
Block helper that always renders the inverse block unless a
is greater than or equal to b
.
Block helper that always renders the inverse block unless a
is less than or equal to b
.
Produce a humanized duration left/until given an amount of time and the type of time measurement.
\n" } } diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 9ba97441bc..151db60380 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -27,7 +27,7 @@ "manifest": "node ./scripts/gen-collection-info.js" }, "dependencies": { - "@budibase/handlebars-helpers": "^0.12.0", + "@budibase/handlebars-helpers": "^0.13.0", "dayjs": "^1.10.8", "handlebars": "^4.7.6", "lodash.clonedeep": "^4.5.0", diff --git a/packages/string-templates/scripts/gen-collection-info.js b/packages/string-templates/scripts/gen-collection-info.js index e42c9ccaf0..b487c4dde4 100644 --- a/packages/string-templates/scripts/gen-collection-info.js +++ b/packages/string-templates/scripts/gen-collection-info.js @@ -36,7 +36,7 @@ const ADDED_HELPERS = { duration: { args: ["time", "durationType"], numArgs: 2, - example: '{{duration timeLeft "seconds"}} -> a few seconds', + example: '{{duration 8 "seconds"}} -> a few seconds', description: "Produce a humanized duration left/until given an amount of time and the type of time measurement.", }, @@ -118,6 +118,8 @@ function getCommentInfo(file, func) { return docs } +const excludeFunctions = { string: ["raw"] } + /** * This script is very specific to purpose, parsing the handlebars-helpers files to attempt to get information about them. */ @@ -136,7 +138,8 @@ function run() { // skip built in functions and ones seen already if ( HelperFunctionBuiltin.indexOf(name) !== -1 || - foundNames.indexOf(name) !== -1 + foundNames.indexOf(name) !== -1 || + excludeFunctions[collection]?.includes(name) ) { continue } diff --git a/packages/string-templates/test/helpers.spec.js b/packages/string-templates/test/helpers.spec.js index 380b0f2833..75de373109 100644 --- a/packages/string-templates/test/helpers.spec.js +++ b/packages/string-templates/test/helpers.spec.js @@ -61,10 +61,10 @@ describe("test the array helpers", () => { }) it("should allow use of the before helper", async () => { - const output = await processString("{{before array 2}}", { + const output = await processString("{{before array 3}}", { array, }) - expect(output).toBe("hi,person,how") + expect(output).toBe("hi,person") }) it("should allow use of the filter helper", async () => { diff --git a/packages/string-templates/test/manifest.spec.js b/packages/string-templates/test/manifest.spec.js new file mode 100644 index 0000000000..506f2eb6f7 --- /dev/null +++ b/packages/string-templates/test/manifest.spec.js @@ -0,0 +1,96 @@ +jest.mock("@budibase/handlebars-helpers/lib/math", () => { + const actual = jest.requireActual("@budibase/handlebars-helpers/lib/math") + + return { + ...actual, + random: () => 10, + } +}) +jest.mock("@budibase/handlebars-helpers/lib/uuid", () => { + const actual = jest.requireActual("@budibase/handlebars-helpers/lib/uuid") + + return { + ...actual, + uuid: () => "f34ebc66-93bd-4f7c-b79b-92b5569138bc", + } +}) + +const fs = require("fs") +const { processString } = require("../src/index.cjs") + +const tk = require("timekeeper") +tk.freeze("2021-01-21T12:00:00") + +const manifest = JSON.parse( + fs.readFileSync(require.resolve("../manifest.json"), "utf8") +) + +const collections = Object.keys(manifest) +const examples = collections.reduce((acc, collection) => { + const functions = Object.keys(manifest[collection]).filter( + fnc => manifest[collection][fnc].example + ) + if (functions.length) { + acc[collection] = functions + } + return acc +}, {}) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") // $& means the whole matched string +} + +function tryParseJson(str) { + if (typeof str !== "string") { + return + } + + try { + return JSON.parse(str.replace(/\'/g, '"')) + } catch (e) { + return + } +} + +describe("manifest", () => { + describe("examples are valid", () => { + describe.each(Object.keys(examples))("%s", collection => { + it.each(examples[collection])("%s", async func => { + const example = manifest[collection][func].example + + let [hbs, js] = example.split("->").map(x => x.trim()) + + const context = { + double: i => i * 2, + isString: x => typeof x === "string", + } + + const arrays = hbs.match(/\[[^/\]]+\]/) + arrays?.forEach((arrayString, i) => { + hbs = hbs.replace(new RegExp(escapeRegExp(arrayString)), `array${i}`) + context[`array${i}`] = JSON.parse(arrayString.replace(/\'/g, '"')) + }) + + if (js === undefined) { + // The function has no return value + return + } + + let result = await processString(hbs, context) + // Trim 's + js = js.replace(/^\'|\'$/g, "") + if ((parsedExpected = tryParseJson(js))) { + if (Array.isArray(parsedExpected)) { + if (typeof parsedExpected[0] === "object") { + js = JSON.stringify(parsedExpected) + } else { + js = parsedExpected.join(",") + } + } + } + result = result.replace(/ /g, " ") + expect(result).toEqual(js) + }) + }) + }) +}) diff --git a/packages/types/src/documents/app/query.ts b/packages/types/src/documents/app/query.ts index c288ed9980..473449bffb 100644 --- a/packages/types/src/documents/app/query.ts +++ b/packages/types/src/documents/app/query.ts @@ -1,12 +1,17 @@ import { Document } from "../document" +export interface QuerySchema { + name?: string + type: string +} + export interface Query extends Document { datasourceId: string name: string parameters: QueryParameter[] fields: RestQueryFields | any transformer: string | null - schema: Record