Merge master.
This commit is contained in:
commit
fd635ed080
|
@ -12,6 +12,7 @@
|
||||||
export let getOptionIcon = () => null
|
export let getOptionIcon = () => null
|
||||||
export let getOptionColour = () => null
|
export let getOptionColour = () => null
|
||||||
export let getOptionSubtitle = () => null
|
export let getOptionSubtitle = () => null
|
||||||
|
export let compare = (option, value) => option === value
|
||||||
export let useOptionIconImage = false
|
export let useOptionIconImage = false
|
||||||
export let isOptionEnabled
|
export let isOptionEnabled
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
|
@ -39,8 +40,8 @@
|
||||||
if (!options?.length) {
|
if (!options?.length) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
const index = options.findIndex(
|
const index = options.findIndex((option, idx) =>
|
||||||
(option, idx) => getOptionValue(option, idx) === value
|
compare(getOptionValue(option, idx), value)
|
||||||
)
|
)
|
||||||
return index !== -1 ? getAttribute(options[index], index) : null
|
return index !== -1 ? getAttribute(options[index], index) : null
|
||||||
}
|
}
|
||||||
|
@ -94,7 +95,7 @@
|
||||||
{customPopoverMaxHeight}
|
{customPopoverMaxHeight}
|
||||||
isPlaceholder={value == null || value === ""}
|
isPlaceholder={value == null || value === ""}
|
||||||
placeholderOption={placeholder === false ? null : placeholder}
|
placeholderOption={placeholder === false ? null : placeholder}
|
||||||
isOptionSelected={option => option === value}
|
isOptionSelected={option => compare(option, value)}
|
||||||
onSelectOption={selectOption}
|
onSelectOption={selectOption}
|
||||||
{loading}
|
{loading}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
export let footer = null
|
export let footer = null
|
||||||
export let tag = null
|
export let tag = null
|
||||||
export let helpText = null
|
export let helpText = null
|
||||||
|
export let compare
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
value = e.detail
|
value = e.detail
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{customPopoverHeight}
|
{customPopoverHeight}
|
||||||
{tag}
|
{tag}
|
||||||
|
{compare}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
on:click
|
on:click
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
export let bindingDrawerLeft
|
export let bindingDrawerLeft
|
||||||
export let allowHelpers = true
|
export let allowHelpers = true
|
||||||
export let customButtonText = null
|
export let customButtonText = null
|
||||||
|
export let compare = (option, value) => option === value
|
||||||
|
|
||||||
let fields = Object.entries(object || {}).map(([name, value]) => ({
|
let fields = Object.entries(object || {}).map(([name, value]) => ({
|
||||||
name,
|
name,
|
||||||
|
@ -112,7 +113,12 @@
|
||||||
on:blur={changed}
|
on:blur={changed}
|
||||||
/>
|
/>
|
||||||
{#if options}
|
{#if options}
|
||||||
<Select bind:value={field.value} on:change={changed} {options} />
|
<Select
|
||||||
|
bind:value={field.value}
|
||||||
|
{compare}
|
||||||
|
on:change={changed}
|
||||||
|
{options}
|
||||||
|
/>
|
||||||
{:else if bindings && bindings.length}
|
{:else if bindings && bindings.length}
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
{bindings}
|
{bindings}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import KeyValueBuilder from "../KeyValueBuilder.svelte"
|
import KeyValueBuilder from "../KeyValueBuilder.svelte"
|
||||||
import { SchemaTypeOptions } from "constants/backend"
|
import { SchemaTypeOptionsExpanded } from "constants/backend"
|
||||||
|
|
||||||
export let schema
|
export let schema
|
||||||
export let onSchemaChange = () => {}
|
export let onSchemaChange = () => {}
|
||||||
|
@ -24,6 +24,7 @@
|
||||||
object={schema}
|
object={schema}
|
||||||
name="field"
|
name="field"
|
||||||
headings
|
headings
|
||||||
options={SchemaTypeOptions}
|
options={SchemaTypeOptionsExpanded}
|
||||||
|
compare={(option, value) => option.type === value.type}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
PaginationTypes,
|
PaginationTypes,
|
||||||
RawRestBodyTypes,
|
RawRestBodyTypes,
|
||||||
RestBodyTypes as bodyTypes,
|
RestBodyTypes as bodyTypes,
|
||||||
SchemaTypeOptions,
|
SchemaTypeOptionsExpanded,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import JSONPreview from "components/integration/JSONPreview.svelte"
|
import JSONPreview from "components/integration/JSONPreview.svelte"
|
||||||
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
|
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
|
||||||
|
@ -97,9 +97,7 @@
|
||||||
$: schemaReadOnly = !responseSuccess
|
$: schemaReadOnly = !responseSuccess
|
||||||
$: variablesReadOnly = !responseSuccess
|
$: variablesReadOnly = !responseSuccess
|
||||||
$: showVariablesTab = shouldShowVariables(dynamicVariables, variablesReadOnly)
|
$: showVariablesTab = shouldShowVariables(dynamicVariables, variablesReadOnly)
|
||||||
$: hasSchema =
|
$: hasSchema = Object.keys(schema || {}).length !== 0
|
||||||
Object.keys(schema || {}).length !== 0 ||
|
|
||||||
Object.keys(query?.schema || {}).length !== 0
|
|
||||||
|
|
||||||
$: runtimeUrlQueries = readableToRuntimeMap(mergedBindings, breakQs)
|
$: runtimeUrlQueries = readableToRuntimeMap(mergedBindings, breakQs)
|
||||||
|
|
||||||
|
@ -161,7 +159,7 @@
|
||||||
newQuery.fields.queryString = queryString
|
newQuery.fields.queryString = queryString
|
||||||
newQuery.fields.authConfigId = authConfigId
|
newQuery.fields.authConfigId = authConfigId
|
||||||
newQuery.fields.disabledHeaders = restUtils.flipHeaderState(enabledHeaders)
|
newQuery.fields.disabledHeaders = restUtils.flipHeaderState(enabledHeaders)
|
||||||
newQuery.schema = restUtils.fieldsToSchema(schema)
|
newQuery.schema = schema
|
||||||
|
|
||||||
return newQuery
|
return newQuery
|
||||||
}
|
}
|
||||||
|
@ -231,6 +229,14 @@
|
||||||
notifications.info("Request did not return any data")
|
notifications.info("Request did not return any data")
|
||||||
} else {
|
} else {
|
||||||
response.info = response.info || { code: 200 }
|
response.info = response.info || { code: 200 }
|
||||||
|
// if existing schema, copy over what it is
|
||||||
|
if (schema) {
|
||||||
|
for (let [name, field] of Object.entries(schema)) {
|
||||||
|
if (response.schema[name]) {
|
||||||
|
response.schema[name] = field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
schema = response.schema
|
schema = response.schema
|
||||||
notifications.success("Request sent successfully")
|
notifications.success("Request sent successfully")
|
||||||
}
|
}
|
||||||
|
@ -386,6 +392,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
query = getSelectedQuery()
|
query = getSelectedQuery()
|
||||||
|
schema = query.schema
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Clear any unsaved changes to the datasource
|
// Clear any unsaved changes to the datasource
|
||||||
|
@ -416,7 +423,6 @@
|
||||||
query.fields.path = `${datasource.config.url}/${path ? path : ""}`
|
query.fields.path = `${datasource.config.url}/${path ? path : ""}`
|
||||||
}
|
}
|
||||||
url = buildUrl(query.fields.path, breakQs)
|
url = buildUrl(query.fields.path, breakQs)
|
||||||
schema = restUtils.schemaToFields(query.schema)
|
|
||||||
requestBindings = restUtils.queryParametersToKeyValue(query.parameters)
|
requestBindings = restUtils.queryParametersToKeyValue(query.parameters)
|
||||||
authConfigId = getAuthConfigId()
|
authConfigId = getAuthConfigId()
|
||||||
if (!query.fields.disabledHeaders) {
|
if (!query.fields.disabledHeaders) {
|
||||||
|
@ -682,10 +688,11 @@
|
||||||
bind:object={schema}
|
bind:object={schema}
|
||||||
name="schema"
|
name="schema"
|
||||||
headings
|
headings
|
||||||
options={SchemaTypeOptions}
|
options={SchemaTypeOptionsExpanded}
|
||||||
menuItems={schemaMenuItems}
|
menuItems={schemaMenuItems}
|
||||||
showMenu={!schemaReadOnly}
|
showMenu={!schemaReadOnly}
|
||||||
readOnly={schemaReadOnly}
|
readOnly={schemaReadOnly}
|
||||||
|
compare={(option, value) => option.type === value.type}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -271,6 +271,11 @@ export const SchemaTypeOptions = [
|
||||||
{ label: "Datetime", value: "datetime" },
|
{ label: "Datetime", value: "datetime" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const SchemaTypeOptionsExpanded = SchemaTypeOptions.map(el => ({
|
||||||
|
...el,
|
||||||
|
value: { type: el.value },
|
||||||
|
}))
|
||||||
|
|
||||||
export const RawRestBodyTypes = {
|
export const RawRestBodyTypes = {
|
||||||
NONE: "none",
|
NONE: "none",
|
||||||
FORM: "form",
|
FORM: "form",
|
||||||
|
|
|
@ -1,26 +1,6 @@
|
||||||
import { IntegrationTypes } from "constants/backend"
|
import { IntegrationTypes } from "constants/backend"
|
||||||
import { findHBSBlocks } from "@budibase/string-templates"
|
import { findHBSBlocks } from "@budibase/string-templates"
|
||||||
|
|
||||||
export function schemaToFields(schema) {
|
|
||||||
const response = {}
|
|
||||||
if (schema && typeof schema === "object") {
|
|
||||||
for (let [field, value] of Object.entries(schema)) {
|
|
||||||
response[field] = value?.type || "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fieldsToSchema(fields) {
|
|
||||||
const response = {}
|
|
||||||
if (fields && typeof fields === "object") {
|
|
||||||
for (let [name, type] of Object.entries(fields)) {
|
|
||||||
response[name] = { name, type }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
export function breakQueryString(qs) {
|
export function breakQueryString(qs) {
|
||||||
if (!qs) {
|
if (!qs) {
|
||||||
return {}
|
return {}
|
||||||
|
@ -184,10 +164,8 @@ export const parseToCsv = (headers, rows) => {
|
||||||
export default {
|
export default {
|
||||||
breakQueryString,
|
breakQueryString,
|
||||||
buildQueryString,
|
buildQueryString,
|
||||||
fieldsToSchema,
|
|
||||||
flipHeaderState,
|
flipHeaderState,
|
||||||
keyValueToQueryParameters,
|
keyValueToQueryParameters,
|
||||||
parseToCsv,
|
parseToCsv,
|
||||||
queryParametersToKeyValue,
|
queryParametersToKeyValue,
|
||||||
schemaToFields,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,27 @@
|
||||||
import { PlanType } from "@budibase/types"
|
import { PlanType } from "@budibase/types"
|
||||||
|
|
||||||
export function getFormattedPlanName(userPlanType) {
|
export function getFormattedPlanName(userPlanType) {
|
||||||
let planName = "Free"
|
let planName
|
||||||
if (userPlanType === PlanType.PREMIUM_PLUS) {
|
switch (userPlanType) {
|
||||||
|
case PlanType.PRO:
|
||||||
|
planName = "Pro"
|
||||||
|
break
|
||||||
|
case PlanType.TEAM:
|
||||||
|
planName = "Team"
|
||||||
|
break
|
||||||
|
case PlanType.PREMIUM:
|
||||||
|
case PlanType.PREMIUM_PLUS:
|
||||||
planName = "Premium"
|
planName = "Premium"
|
||||||
} else if (userPlanType === PlanType.ENTERPRISE_BASIC) {
|
break
|
||||||
|
case PlanType.BUSINESS:
|
||||||
|
planName = "Business"
|
||||||
|
break
|
||||||
|
case PlanType.ENTERPRISE_BASIC:
|
||||||
|
case PlanType.ENTERPRISE:
|
||||||
planName = "Enterprise"
|
planName = "Enterprise"
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
planName = "Free" // Default to "Free" if the type is not explicitly handled
|
||||||
}
|
}
|
||||||
return `${planName} Plan`
|
return `${planName} Plan`
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,8 +89,8 @@ export function createQueriesStore() {
|
||||||
// Assume all the fields are strings and create a basic schema from the
|
// Assume all the fields are strings and create a basic schema from the
|
||||||
// unique fields returned by the server
|
// unique fields returned by the server
|
||||||
const schema = {}
|
const schema = {}
|
||||||
for (let [field, type] of Object.entries(result.schemaFields)) {
|
for (let [field, metadata] of Object.entries(result.schema)) {
|
||||||
schema[field] = type || "string"
|
schema[field] = metadata || { type: "string" }
|
||||||
}
|
}
|
||||||
return { ...result, schema, rows: result.rows || [] }
|
return { ...result, schema, rows: result.rows || [] }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { generateQueryID } from "../../../db/utils"
|
import { generateQueryID } from "../../../db/utils"
|
||||||
import { BaseQueryVerbs, FieldTypes } from "../../../constants"
|
import { BaseQueryVerbs } from "../../../constants"
|
||||||
import { Thread, ThreadType } from "../../../threads"
|
import { Thread, ThreadType } from "../../../threads"
|
||||||
import { save as saveDatasource } from "../datasource"
|
import { save as saveDatasource } from "../datasource"
|
||||||
import { RestImporter } from "./import"
|
import { RestImporter } from "./import"
|
||||||
import { invalidateDynamicVariables } from "../../../threads/utils"
|
import { invalidateDynamicVariables } from "../../../threads/utils"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
import { quotas } from "@budibase/pro"
|
|
||||||
import { events, context, utils, constants } from "@budibase/backend-core"
|
import { events, context, utils, constants } from "@budibase/backend-core"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import { QueryEvent } from "../../../threads/definitions"
|
import { QueryEvent, QueryResponse } from "../../../threads/definitions"
|
||||||
import { ConfigType, Query, UserCtx, SessionCookie } from "@budibase/types"
|
import {
|
||||||
|
ConfigType,
|
||||||
|
Query,
|
||||||
|
UserCtx,
|
||||||
|
SessionCookie,
|
||||||
|
QuerySchema,
|
||||||
|
FieldType,
|
||||||
|
} from "@budibase/types"
|
||||||
import { ValidQueryNameRegex } from "@budibase/shared-core"
|
import { ValidQueryNameRegex } from "@budibase/shared-core"
|
||||||
|
|
||||||
const Runner = new Thread(ThreadType.QUERY, {
|
const Runner = new Thread(ThreadType.QUERY, {
|
||||||
|
@ -162,39 +168,43 @@ export async function preview(ctx: UserCtx) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rows, keys, info, extra } = (await Runner.run(inputs)) as any
|
const { rows, keys, info, extra } = await Runner.run<QueryResponse>(inputs)
|
||||||
const schemaFields: any = {}
|
const previewSchema: Record<string, QuerySchema> = {}
|
||||||
|
const makeQuerySchema = (type: FieldType, name: string): QuerySchema => ({
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
})
|
||||||
if (rows?.length > 0) {
|
if (rows?.length > 0) {
|
||||||
for (let key of [...new Set(keys)] as string[]) {
|
for (let key of [...new Set(keys)] as string[]) {
|
||||||
const field = rows[0][key]
|
const field = rows[0][key]
|
||||||
let type = typeof field,
|
let type = typeof field,
|
||||||
fieldType = FieldTypes.STRING
|
fieldMetadata = makeQuerySchema(FieldType.STRING, key)
|
||||||
if (field)
|
if (field)
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "boolean":
|
case "boolean":
|
||||||
schemaFields[key] = FieldTypes.BOOLEAN
|
fieldMetadata = makeQuerySchema(FieldType.BOOLEAN, key)
|
||||||
break
|
break
|
||||||
case "object":
|
case "object":
|
||||||
if (field instanceof Date) {
|
if (field instanceof Date) {
|
||||||
fieldType = FieldTypes.DATETIME
|
fieldMetadata = makeQuerySchema(FieldType.DATETIME, key)
|
||||||
} else if (Array.isArray(field)) {
|
} else if (Array.isArray(field)) {
|
||||||
fieldType = FieldTypes.ARRAY
|
fieldMetadata = makeQuerySchema(FieldType.ARRAY, key)
|
||||||
} else {
|
} else {
|
||||||
fieldType = FieldTypes.JSON
|
fieldMetadata = makeQuerySchema(FieldType.JSON, key)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "number":
|
case "number":
|
||||||
fieldType = FieldTypes.NUMBER
|
fieldMetadata = makeQuerySchema(FieldType.NUMBER, key)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
schemaFields[key] = fieldType
|
previewSchema[key] = fieldMetadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if existing schema, update to include any previous schema keys
|
// if existing schema, update to include any previous schema keys
|
||||||
if (existingSchema) {
|
if (existingSchema) {
|
||||||
for (let key of Object.keys(schemaFields)) {
|
for (let key of Object.keys(previewSchema)) {
|
||||||
if (existingSchema[key]?.type) {
|
if (existingSchema[key]) {
|
||||||
schemaFields[key] = existingSchema[key].type
|
previewSchema[key] = existingSchema[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +213,7 @@ export async function preview(ctx: UserCtx) {
|
||||||
await events.query.previewed(datasource, query)
|
await events.query.previewed(datasource, query)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
rows,
|
rows,
|
||||||
schemaFields,
|
schema: previewSchema,
|
||||||
info,
|
info,
|
||||||
extra,
|
extra,
|
||||||
}
|
}
|
||||||
|
@ -257,7 +267,9 @@ async function execute(
|
||||||
schema: query.schema,
|
schema: query.schema,
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rows, pagination, extra, info } = (await Runner.run(inputs)) as any
|
const { rows, pagination, extra, info } = await Runner.run<QueryResponse>(
|
||||||
|
inputs
|
||||||
|
)
|
||||||
// remove the raw from execution incase transformer being used to hide data
|
// remove the raw from execution incase transformer being used to hide data
|
||||||
if (extra?.raw) {
|
if (extra?.raw) {
|
||||||
delete extra.raw
|
delete extra.raw
|
||||||
|
|
|
@ -235,9 +235,9 @@ describe("/queries", () => {
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
// these responses come from the mock
|
// these responses come from the mock
|
||||||
expect(res.body.schemaFields).toEqual({
|
expect(res.body.schema).toEqual({
|
||||||
a: "string",
|
a: { type: "string", name: "a" },
|
||||||
b: "number",
|
b: { type: "number", name: "b" },
|
||||||
})
|
})
|
||||||
expect(res.body.rows.length).toEqual(1)
|
expect(res.body.rows.length).toEqual(1)
|
||||||
expect(events.query.previewed).toBeCalledTimes(1)
|
expect(events.query.previewed).toBeCalledTimes(1)
|
||||||
|
@ -300,10 +300,10 @@ describe("/queries", () => {
|
||||||
queryString: "test={{ variable2 }}",
|
queryString: "test={{ variable2 }}",
|
||||||
})
|
})
|
||||||
// these responses come from the mock
|
// these responses come from the mock
|
||||||
expect(res.body.schemaFields).toEqual({
|
expect(res.body.schema).toEqual({
|
||||||
opts: "json",
|
opts: { type: "json", name: "opts" },
|
||||||
url: "string",
|
url: { type: "string", name: "url" },
|
||||||
value: "string",
|
value: { type: "string", name: "value" },
|
||||||
})
|
})
|
||||||
expect(res.body.rows[0].url).toEqual("http://www.google.com?test=1")
|
expect(res.body.rows[0].url).toEqual("http://www.google.com?test=1")
|
||||||
})
|
})
|
||||||
|
@ -314,10 +314,10 @@ describe("/queries", () => {
|
||||||
path: "www.google.com",
|
path: "www.google.com",
|
||||||
queryString: "test={{ variable3 }}",
|
queryString: "test={{ variable3 }}",
|
||||||
})
|
})
|
||||||
expect(res.body.schemaFields).toEqual({
|
expect(res.body.schema).toEqual({
|
||||||
opts: "json",
|
opts: { type: "json", name: "opts" },
|
||||||
url: "string",
|
url: { type: "string", name: "url" },
|
||||||
value: "string",
|
value: { type: "string", name: "value" },
|
||||||
})
|
})
|
||||||
expect(res.body.rows[0].url).toContain("doctype%20html")
|
expect(res.body.rows[0].url).toContain("doctype%20html")
|
||||||
})
|
})
|
||||||
|
@ -337,10 +337,10 @@ describe("/queries", () => {
|
||||||
path: "www.failonce.com",
|
path: "www.failonce.com",
|
||||||
queryString: "test={{ variable3 }}",
|
queryString: "test={{ variable3 }}",
|
||||||
})
|
})
|
||||||
expect(res.body.schemaFields).toEqual({
|
expect(res.body.schema).toEqual({
|
||||||
fails: "number",
|
fails: { type: "number", name: "fails" },
|
||||||
opts: "json",
|
opts: { type: "json", name: "opts" },
|
||||||
url: "string",
|
url: { type: "string", name: "url" },
|
||||||
})
|
})
|
||||||
expect(res.body.rows[0].fails).toEqual(1)
|
expect(res.body.rows[0].fails).toEqual(1)
|
||||||
})
|
})
|
||||||
|
|
|
@ -376,8 +376,8 @@ export function checkExternalTables(
|
||||||
errors[name] = "Table must have a primary key."
|
errors[name] = "Table must have a primary key."
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemaFields = Object.keys(table.schema)
|
const columnNames = Object.keys(table.schema)
|
||||||
if (schemaFields.find(f => invalidColumns.includes(f))) {
|
if (columnNames.find(f => invalidColumns.includes(f))) {
|
||||||
errors[name] = "Table contains invalid columns."
|
errors[name] = "Table contains invalid columns."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,27 @@ import { processStringSync } from "@budibase/string-templates"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { getQueryParams, isProdAppID } from "../../../db/utils"
|
import { getQueryParams, isProdAppID } from "../../../db/utils"
|
||||||
import { BaseQueryVerbs } from "../../../constants"
|
import { BaseQueryVerbs } from "../../../constants"
|
||||||
|
import { Query, QuerySchema } from "@budibase/types"
|
||||||
|
|
||||||
|
function updateSchema(query: Query): Query {
|
||||||
|
if (!query.schema) {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
const schema: Record<string, QuerySchema> = {}
|
||||||
|
for (let key of Object.keys(query.schema)) {
|
||||||
|
if (typeof query.schema[key] === "string") {
|
||||||
|
schema[key] = { type: query.schema[key] as string, name: key }
|
||||||
|
} else {
|
||||||
|
schema[key] = query.schema[key] as QuerySchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
query.schema = schema
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSchemas(queries: Query[]): Query[] {
|
||||||
|
return queries.map(query => updateSchema(query))
|
||||||
|
}
|
||||||
|
|
||||||
// simple function to append "readable" to all read queries
|
// simple function to append "readable" to all read queries
|
||||||
function enrichQueries(input: any) {
|
function enrichQueries(input: any) {
|
||||||
|
@ -25,7 +46,7 @@ export async function find(queryId: string) {
|
||||||
delete query.fields
|
delete query.fields
|
||||||
delete query.parameters
|
delete query.parameters
|
||||||
}
|
}
|
||||||
return query
|
return updateSchema(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetch(opts: { enrich: boolean } = { enrich: true }) {
|
export async function fetch(opts: { enrich: boolean } = { enrich: true }) {
|
||||||
|
@ -37,12 +58,11 @@ export async function fetch(opts: { enrich: boolean } = { enrich: true }) {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const queries = body.rows.map((row: any) => row.doc)
|
let queries = body.rows.map((row: any) => row.doc)
|
||||||
if (opts.enrich) {
|
if (opts.enrich) {
|
||||||
return enrichQueries(queries)
|
queries = await enrichQueries(queries)
|
||||||
} else {
|
|
||||||
return queries
|
|
||||||
}
|
}
|
||||||
|
return updateSchemas(queries)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function enrichContext(
|
export async function enrichContext(
|
||||||
|
|
|
@ -278,6 +278,9 @@ class TestConfiguration {
|
||||||
if (params) {
|
if (params) {
|
||||||
request.params = params
|
request.params = params
|
||||||
}
|
}
|
||||||
|
request.throw = (status: number, message: string) => {
|
||||||
|
throw new Error(`Error ${status} - ${message}`)
|
||||||
|
}
|
||||||
return this.doInContext(appId, async () => {
|
return this.doInContext(appId, async () => {
|
||||||
await controlFunc(request)
|
await controlFunc(request)
|
||||||
return request.body
|
return request.body
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { QuerySchema, Row } from "@budibase/types"
|
||||||
|
|
||||||
export type WorkerCallback = (error: any, response?: any) => void
|
export type WorkerCallback = (error: any, response?: any) => void
|
||||||
|
|
||||||
export interface QueryEvent {
|
export interface QueryEvent {
|
||||||
|
@ -11,7 +13,15 @@ export interface QueryEvent {
|
||||||
queryId: string
|
queryId: string
|
||||||
environmentVariables?: Record<string, string>
|
environmentVariables?: Record<string, string>
|
||||||
ctx?: any
|
ctx?: any
|
||||||
schema?: Record<string, { name?: string; type: string }>
|
schema?: Record<string, QuerySchema | string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryResponse {
|
||||||
|
rows: Row[]
|
||||||
|
keys: string[]
|
||||||
|
info: any
|
||||||
|
extra: any
|
||||||
|
pagination: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryVariable {
|
export interface QueryVariable {
|
||||||
|
|
|
@ -74,7 +74,7 @@ export class Thread {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
run(job: AutomationJob | QueryEvent) {
|
run<T>(job: AutomationJob | QueryEvent): Promise<T> {
|
||||||
const timeout = this.timeoutMs
|
const timeout = this.timeoutMs
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
function fire(worker: any) {
|
function fire(worker: any) {
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { default as threadUtils } from "./utils"
|
import { default as threadUtils } from "./utils"
|
||||||
|
|
||||||
threadUtils.threadSetup()
|
threadUtils.threadSetup()
|
||||||
import { WorkerCallback, QueryEvent, QueryVariable } from "./definitions"
|
import {
|
||||||
|
WorkerCallback,
|
||||||
|
QueryEvent,
|
||||||
|
QueryVariable,
|
||||||
|
QueryResponse,
|
||||||
|
} from "./definitions"
|
||||||
import ScriptRunner from "../utilities/scriptRunner"
|
import ScriptRunner from "../utilities/scriptRunner"
|
||||||
import { getIntegration } from "../integrations"
|
import { getIntegration } from "../integrations"
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
@ -9,7 +14,7 @@ import { context, cache, auth } from "@budibase/backend-core"
|
||||||
import { getGlobalIDFromUserMetadataID } from "../db/utils"
|
import { getGlobalIDFromUserMetadataID } from "../db/utils"
|
||||||
import sdk from "../sdk"
|
import sdk from "../sdk"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { SourceName, Query } from "@budibase/types"
|
import { Query } from "@budibase/types"
|
||||||
|
|
||||||
import { isSQL } from "../integrations/utils"
|
import { isSQL } from "../integrations/utils"
|
||||||
import { interpolateSQL } from "../integrations/queries/sql"
|
import { interpolateSQL } from "../integrations/queries/sql"
|
||||||
|
@ -53,7 +58,7 @@ class QueryRunner {
|
||||||
this.hasDynamicVariables = false
|
this.hasDynamicVariables = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(): Promise<any> {
|
async execute(): Promise<QueryResponse> {
|
||||||
let { datasource, fields, queryVerb, transformer, schema } = this
|
let { datasource, fields, queryVerb, transformer, schema } = this
|
||||||
let datasourceClone = cloneDeep(datasource)
|
let datasourceClone = cloneDeep(datasource)
|
||||||
let fieldsClone = cloneDeep(fields)
|
let fieldsClone = cloneDeep(fields)
|
||||||
|
|
|
@ -137,7 +137,7 @@
|
||||||
"n"
|
"n"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{ after [1, 2, 3] 1}} -> [3]",
|
"example": "{{ after ['a', 'b', 'c', 'd'] 2}} -> ['c', 'd']",
|
||||||
"description": "<p>Returns all of the items in an array after the specified index. Opposite of <a href=\"#before\">before</a>.</p>\n"
|
"description": "<p>Returns all of the items in an array after the specified index. Opposite of <a href=\"#before\">before</a>.</p>\n"
|
||||||
},
|
},
|
||||||
"arrayify": {
|
"arrayify": {
|
||||||
|
@ -154,7 +154,7 @@
|
||||||
"n"
|
"n"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{ before [1, 2, 3] 2}} -> [1, 2]",
|
"example": "{{ before ['a', 'b', 'c', 'd'] 3}} -> ['a', 'b']",
|
||||||
"description": "<p>Return all of the items in the collection before the specified count. Opposite of <a href=\"#after\">after</a>.</p>\n"
|
"description": "<p>Return all of the items in the collection before the specified count. Opposite of <a href=\"#after\">after</a>.</p>\n"
|
||||||
},
|
},
|
||||||
"eachIndex": {
|
"eachIndex": {
|
||||||
|
@ -182,7 +182,7 @@
|
||||||
"n"
|
"n"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{first [1, 2, 3, 4] 2}} -> [1, 2]",
|
"example": "{{first [1, 2, 3, 4] 2}} -> 1,2",
|
||||||
"description": "<p>Returns the first item, or first <code>n</code> items of an array.</p>\n"
|
"description": "<p>Returns the first item, or first <code>n</code> items of an array.</p>\n"
|
||||||
},
|
},
|
||||||
"forEach": {
|
"forEach": {
|
||||||
|
@ -200,7 +200,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#inArray [1, 2, 3] 2}} 2 exists {{else}} 2 does not exist {{/inArray}} -> 2 exists",
|
"example": "{{#inArray [1, 2, 3] 2}} 2 exists {{else}} 2 does not exist {{/inArray}} -> ' 2 exists '",
|
||||||
"description": "<p>Block helper that renders the block if an array has the given <code>value</code>. Optionally specify an inverse block to render when the array does not have the given value.</p>\n"
|
"description": "<p>Block helper that renders the block if an array has the given <code>value</code>. Optionally specify an inverse block to render when the array does not have the given value.</p>\n"
|
||||||
},
|
},
|
||||||
"isArray": {
|
"isArray": {
|
||||||
|
@ -226,7 +226,7 @@
|
||||||
"separator"
|
"separator"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{join [1, 2, 3]}} -> '1, 2, 3'",
|
"example": "{{join [1, 2, 3]}} -> 1, 2, 3",
|
||||||
"description": "<p>Join all elements of array into a string, optionally using a given separator.</p>\n"
|
"description": "<p>Join all elements of array into a string, optionally using a given separator.</p>\n"
|
||||||
},
|
},
|
||||||
"equalsLength": {
|
"equalsLength": {
|
||||||
|
@ -236,7 +236,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{equalsLength '[1,2,3]' 3}} -> true",
|
"example": "{{equalsLength [1, 2, 3] 3}} -> true",
|
||||||
"description": "<p>Returns true if the the length of the given <code>value</code> is equal to the given <code>length</code>. Can be used as a block or inline helper.</p>\n"
|
"description": "<p>Returns true if the the length of the given <code>value</code> is equal to the given <code>length</code>. Can be used as a block or inline helper.</p>\n"
|
||||||
},
|
},
|
||||||
"last": {
|
"last": {
|
||||||
|
@ -253,7 +253,7 @@
|
||||||
"value"
|
"value"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"numArgs": 1,
|
||||||
"example": "{{length '[1, 2, 3]'}} -> 3",
|
"example": "{{length [1, 2, 3]}} -> 3",
|
||||||
"description": "<p>Returns the length of the given string or array.</p>\n"
|
"description": "<p>Returns the length of the given string or array.</p>\n"
|
||||||
},
|
},
|
||||||
"lengthEqual": {
|
"lengthEqual": {
|
||||||
|
@ -263,7 +263,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{equalsLength '[1,2,3]' 3}} -> true",
|
"example": "{{equalsLength [1, 2, 3] 3}} -> true",
|
||||||
"description": "<p>Returns true if the the length of the given <code>value</code> is equal to the given <code>length</code>. Can be used as a block or inline helper.</p>\n"
|
"description": "<p>Returns true if the the length of the given <code>value</code> is equal to the given <code>length</code>. Can be used as a block or inline helper.</p>\n"
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
|
@ -299,7 +299,7 @@
|
||||||
"provided"
|
"provided"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#some [1, 'b', 3] isString}} string found {{else}} No string found {{/some}} -> string found",
|
"example": "{{#some [1, \"b\", 3] isString}} string found {{else}} No string found {{/some}} -> ' string found '",
|
||||||
"description": "<p>Block helper that returns the block if the callback returns true for some value in the given array.</p>\n"
|
"description": "<p>Block helper that returns the block if the callback returns true for some value in the given array.</p>\n"
|
||||||
},
|
},
|
||||||
"sort": {
|
"sort": {
|
||||||
|
@ -317,7 +317,7 @@
|
||||||
"props"
|
"props"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"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": "<p>Sort an <code>array</code>. If an array of objects is passed, you may optionally pass a <code>key</code> to sort on as the second argument. You may alternatively pass a sorting function as the second argument.</p>\n"
|
"description": "<p>Sort an <code>array</code>. If an array of objects is passed, you may optionally pass a <code>key</code> to sort on as the second argument. You may alternatively pass a sorting function as the second argument.</p>\n"
|
||||||
},
|
},
|
||||||
"withAfter": {
|
"withAfter": {
|
||||||
|
@ -347,7 +347,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{ withFirst [1, 2, 3] }} {{this}} {{/withFirst}}",
|
"example": "{{#withFirst [1, 2, 3] }}{{this}}{{/withFirst}} -> 1",
|
||||||
"description": "<p>Use the first item in a collection inside a handlebars block expression. Opposite of <a href=\"#withLast\">withLast</a>.</p>\n"
|
"description": "<p>Use the first item in a collection inside a handlebars block expression. Opposite of <a href=\"#withLast\">withLast</a>.</p>\n"
|
||||||
},
|
},
|
||||||
"withGroup": {
|
"withGroup": {
|
||||||
|
@ -357,7 +357,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#withGroup [1, 2, 3, 4] 2}} {{#each this}} {{.}} {{each}} <br> {{/withGroup}} -> 1,2<br> 3,4<br>",
|
"example": "{{#withGroup [1, 2, 3, 4] 2}}{{#each this}}{{.}}{{/each}}<br>{{/withGroup}} -> 12<br>34<br>",
|
||||||
"description": "<p>Block helper that groups array elements by given group <code>size</code>.</p>\n"
|
"description": "<p>Block helper that groups array elements by given group <code>size</code>.</p>\n"
|
||||||
},
|
},
|
||||||
"withLast": {
|
"withLast": {
|
||||||
|
@ -367,7 +367,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#withLast [1, 2, 3, 4]}} {{this}} {{/withLast}} -> 4",
|
"example": "{{#withLast [1, 2, 3, 4]}}{{this}}{{/withLast}} -> 4",
|
||||||
"description": "<p>Use the last item or <code>n</code> items in an array as context inside a block. Opposite of <a href=\"#withFirst\">withFirst</a>.</p>\n"
|
"description": "<p>Use the last item or <code>n</code> items in an array as context inside a block. Opposite of <a href=\"#withFirst\">withFirst</a>.</p>\n"
|
||||||
},
|
},
|
||||||
"withSort": {
|
"withSort": {
|
||||||
|
@ -377,7 +377,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#withSort ['b', 'a', 'c']}} {{this}} {{/withSort}} -> abc",
|
"example": "{{#withSort ['b', 'a', 'c']}}{{this}}{{/withSort}} -> abc",
|
||||||
"description": "<p>Block helper that sorts a collection and exposes the sorted collection as context inside the block.</p>\n"
|
"description": "<p>Block helper that sorts a collection and exposes the sorted collection as context inside the block.</p>\n"
|
||||||
},
|
},
|
||||||
"unique": {
|
"unique": {
|
||||||
|
@ -386,7 +386,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"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": "<p>Block helper that return an array with all duplicate values removed. Best used along with a <a href=\"#each\">each</a> helper.</p>\n"
|
"description": "<p>Block helper that return an array with all duplicate values removed. Best used along with a <a href=\"#each\">each</a> helper.</p>\n"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -396,7 +396,7 @@
|
||||||
"number"
|
"number"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"numArgs": 1,
|
||||||
"example": "{{ bytes 1386 }} -> 1.4Kb",
|
"example": "{{ bytes 1386 1 }} -> 1.4 kB",
|
||||||
"description": "<p>Format a number to it's equivalent in bytes. If a string is passed, it's length will be formatted and returned. <strong>Examples:</strong> - <code>'foo' => 3 B</code> - <code>13661855 => 13.66 MB</code> - <code>825399 => 825.39 kB</code> - <code>1396 => 1.4 kB</code></p>\n"
|
"description": "<p>Format a number to it's equivalent in bytes. If a string is passed, it's length will be formatted and returned. <strong>Examples:</strong> - <code>'foo' => 3 B</code> - <code>13661855 => 13.66 MB</code> - <code>825399 => 825.39 kB</code> - <code>1396 => 1.4 kB</code></p>\n"
|
||||||
},
|
},
|
||||||
"addCommas": {
|
"addCommas": {
|
||||||
|
@ -430,7 +430,7 @@
|
||||||
"fractionDigits"
|
"fractionDigits"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{ toExponential 10123 2 }} -> 101e+4",
|
"example": "{{ toExponential 10123 2 }} -> 1.01e+4",
|
||||||
"description": "<p>Returns a string representing the given number in exponential notation.</p>\n"
|
"description": "<p>Returns a string representing the given number in exponential notation.</p>\n"
|
||||||
},
|
},
|
||||||
"toFixed": {
|
"toFixed": {
|
||||||
|
@ -472,7 +472,7 @@
|
||||||
"str"
|
"str"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"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": "<p>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.</p>\n"
|
"description": "<p>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.</p>\n"
|
||||||
},
|
},
|
||||||
"escape": {
|
"escape": {
|
||||||
|
@ -480,7 +480,7 @@
|
||||||
"str"
|
"str"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"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": "<p>Escape the given string by replacing characters with escape sequences. Useful for allowing the string to be used in a URL, etc.</p>\n"
|
"description": "<p>Escape the given string by replacing characters with escape sequences. Useful for allowing the string to be used in a URL, etc.</p>\n"
|
||||||
},
|
},
|
||||||
"decodeURI": {
|
"decodeURI": {
|
||||||
|
@ -488,7 +488,7 @@
|
||||||
"str"
|
"str"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"numArgs": 1,
|
||||||
"example": "{{ decodeURI 'https://myurl?Hello%20There' }} -> https://myurl?=Hello There",
|
"example": "{{ decodeURI 'https://myurl?Hello%20There' }} -> https://myurl?Hello There",
|
||||||
"description": "<p>Decode a Uniform Resource Identifier (URI) component.</p>\n"
|
"description": "<p>Decode a Uniform Resource Identifier (URI) component.</p>\n"
|
||||||
},
|
},
|
||||||
"urlResolve": {
|
"urlResolve": {
|
||||||
|
@ -513,7 +513,7 @@
|
||||||
"url"
|
"url"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"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": "<p>Strip the query string from the given <code>url</code>.</p>\n"
|
"description": "<p>Strip the query string from the given <code>url</code>.</p>\n"
|
||||||
},
|
},
|
||||||
"stripProtocol": {
|
"stripProtocol": {
|
||||||
|
@ -521,7 +521,7 @@
|
||||||
"str"
|
"str"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"numArgs": 1,
|
||||||
"example": "{{ stripProtocol 'https://myurl/api/test' }} -> 'myurl/api/test'",
|
"example": "{{ stripProtocol 'https://myurl/api/test' }} -> '//myurl/api/test'",
|
||||||
"description": "<p>Strip protocol from a <code>url</code>. Useful for displaying media that may have an 'http' protocol on secure connections.</p>\n"
|
"description": "<p>Strip protocol from a <code>url</code>. Useful for displaying media that may have an 'http' protocol on secure connections.</p>\n"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -573,7 +573,7 @@
|
||||||
"string"
|
"string"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"numArgs": 1,
|
||||||
"example": "{{ chop ' ABC '}} -> 'ABC'",
|
"example": "{{ chop ' ABC '}} -> ABC",
|
||||||
"description": "<p>Like trim, but removes both extraneous whitespace <strong>and non-word characters</strong> from the beginning and end of a string.</p>\n"
|
"description": "<p>Like trim, but removes both extraneous whitespace <strong>and non-word characters</strong> from the beginning and end of a string.</p>\n"
|
||||||
},
|
},
|
||||||
"dashcase": {
|
"dashcase": {
|
||||||
|
@ -606,7 +606,7 @@
|
||||||
"length"
|
"length"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{ellipsis 'foo bar baz', 7}} -> foo bar…",
|
"example": "{{ellipsis 'foo bar baz' 7}} -> foo bar…",
|
||||||
"description": "<p>Truncates a string to the specified <code>length</code>, and appends it with an elipsis, <code>…</code>.</p>\n"
|
"description": "<p>Truncates a string to the specified <code>length</code>, and appends it with an elipsis, <code>…</code>.</p>\n"
|
||||||
},
|
},
|
||||||
"hyphenate": {
|
"hyphenate": {
|
||||||
|
@ -675,14 +675,6 @@
|
||||||
"example": "{{prepend 'bar' 'foo-'}} -> foo-bar",
|
"example": "{{prepend 'bar' 'foo-'}} -> foo-bar",
|
||||||
"description": "<p>Prepends the given <code>string</code> with the specified <code>prefix</code>.</p>\n"
|
"description": "<p>Prepends the given <code>string</code> with the specified <code>prefix</code>.</p>\n"
|
||||||
},
|
},
|
||||||
"raw": {
|
|
||||||
"args": [
|
|
||||||
"options"
|
|
||||||
],
|
|
||||||
"numArgs": 1,
|
|
||||||
"example": "{{{{#raw}}}} {{foo}} {{{{/raw}}}} -> {{foo}}",
|
|
||||||
"description": "<p>Render a block without processing mustache templates inside the block.</p>\n"
|
|
||||||
},
|
|
||||||
"remove": {
|
"remove": {
|
||||||
"args": [
|
"args": [
|
||||||
"str",
|
"str",
|
||||||
|
@ -698,7 +690,7 @@
|
||||||
"substring"
|
"substring"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"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": "<p>Remove the first occurrence of <code>substring</code> from the given <code>str</code>.</p>\n"
|
"description": "<p>Remove the first occurrence of <code>substring</code> from the given <code>str</code>.</p>\n"
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
|
@ -718,7 +710,7 @@
|
||||||
"b"
|
"b"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{replace 'a b a b a b' 'a' 'z'}} -> z b a b a b",
|
"example": "{{replaceFirst 'a b a b a b' 'a' 'z'}} -> z b a b a b",
|
||||||
"description": "<p>Replace the first occurrence of substring <code>a</code> with substring <code>b</code>.</p>\n"
|
"description": "<p>Replace the first occurrence of substring <code>a</code> with substring <code>b</code>.</p>\n"
|
||||||
},
|
},
|
||||||
"sentence": {
|
"sentence": {
|
||||||
|
@ -752,7 +744,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#startsWith 'Goodbye' 'Hello, world!'}} Yep {{else}} Nope {{/startsWith}} -> Nope",
|
"example": "{{#startsWith 'Goodbye' 'Hello, world!'}}Yep{{else}}Nope{{/startsWith}} -> Nope",
|
||||||
"description": "<p>Tests whether a string begins with the given prefix.</p>\n"
|
"description": "<p>Tests whether a string begins with the given prefix.</p>\n"
|
||||||
},
|
},
|
||||||
"titleize": {
|
"titleize": {
|
||||||
|
@ -760,7 +752,7 @@
|
||||||
"str"
|
"str"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"numArgs": 1,
|
||||||
"example": "{{#titleize 'this is title case' }} -> This Is Title Case",
|
"example": "{{titleize 'this is title case' }} -> This Is Title Case",
|
||||||
"description": "<p>Title case the given string.</p>\n"
|
"description": "<p>Title case the given string.</p>\n"
|
||||||
},
|
},
|
||||||
"trim": {
|
"trim": {
|
||||||
|
@ -784,7 +776,7 @@
|
||||||
"string"
|
"string"
|
||||||
],
|
],
|
||||||
"numArgs": 1,
|
"numArgs": 1,
|
||||||
"example": "{{trimRight ' ABC ' }} -> ' ABC '",
|
"example": "{{trimRight ' ABC ' }} -> ' ABC'",
|
||||||
"description": "<p>Removes extraneous whitespace from the end of a string.</p>\n"
|
"description": "<p>Removes extraneous whitespace from the end of a string.</p>\n"
|
||||||
},
|
},
|
||||||
"truncate": {
|
"truncate": {
|
||||||
|
@ -804,7 +796,7 @@
|
||||||
"suffix"
|
"suffix"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{truncateWords 'foo bar baz' 1 }} -> foo",
|
"example": "{{truncateWords 'foo bar baz' 1 }} -> foo…",
|
||||||
"description": "<p>Truncate a string to have the specified number of words. Also see <a href=\"#truncate\">truncate</a>.</p>\n"
|
"description": "<p>Truncate a string to have the specified number of words. Also see <a href=\"#truncate\">truncate</a>.</p>\n"
|
||||||
},
|
},
|
||||||
"upcase": {
|
"upcase": {
|
||||||
|
@ -844,7 +836,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 4,
|
"numArgs": 4,
|
||||||
"example": "{{compare 10 '<' 5 }} -> true",
|
"example": "{{compare 10 '<' 5 }} -> false",
|
||||||
"description": "<p>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.</p>\n"
|
"description": "<p>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.</p>\n"
|
||||||
},
|
},
|
||||||
"contains": {
|
"contains": {
|
||||||
|
@ -874,7 +866,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#eq 3 3}} equal{{else}} not equal{{/eq}} -> equal",
|
"example": "{{#eq 3 3}}equal{{else}}not equal{{/eq}} -> equal",
|
||||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
||||||
},
|
},
|
||||||
"gt": {
|
"gt": {
|
||||||
|
@ -884,7 +876,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#gt 4 3}} greater than{{else}} not greater than{{/gt}} -> greater than",
|
"example": "{{#gt 4 3}} greater than{{else}} not greater than{{/gt}} -> ' greater than'",
|
||||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>greater than</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>greater than</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
||||||
},
|
},
|
||||||
"gte": {
|
"gte": {
|
||||||
|
@ -894,7 +886,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#gte 4 3}} greater than or equal{{else}} not greater than{{/gte}} -> greater than or equal",
|
"example": "{{#gte 4 3}} greater than or equal{{else}} not greater than{{/gte}} -> ' greater than or equal'",
|
||||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>greater than or equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>greater than or equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
||||||
},
|
},
|
||||||
"has": {
|
"has": {
|
||||||
|
@ -904,7 +896,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#has 'foobar' 'foo'}} has it{{else}} doesn't{{/has}} -> has it",
|
"example": "{{#has 'foobar' 'foo'}}has it{{else}}doesn't{{/has}} -> has it",
|
||||||
"description": "<p>Block helper that renders a block if <code>value</code> has <code>pattern</code>. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
"description": "<p>Block helper that renders a block if <code>value</code> has <code>pattern</code>. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
||||||
},
|
},
|
||||||
"isFalsey": {
|
"isFalsey": {
|
||||||
|
@ -931,7 +923,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{#ifEven 2}} even {{else}} odd {{/ifEven}} -> even",
|
"example": "{{#ifEven 2}} even {{else}} odd {{/ifEven}} -> ' even '",
|
||||||
"description": "<p>Return true if the given value is an even number.</p>\n"
|
"description": "<p>Return true if the given value is an even number.</p>\n"
|
||||||
},
|
},
|
||||||
"ifNth": {
|
"ifNth": {
|
||||||
|
@ -941,8 +933,8 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#ifNth 10 2}} remainder {{else}} no remainder {{/ifNth}} -> remainder",
|
"example": "{{#ifNth 2 10}}remainder{{else}}no remainder{{/ifNth}} -> remainder",
|
||||||
"description": "<p>Conditionally renders a block if the remainder is zero when <code>a</code> operand is divided by <code>b</code>. If an inverse block is specified it will be rendered when the remainder is <strong>not zero</strong>.</p>\n"
|
"description": "<p>Conditionally renders a block if the remainder is zero when <code>b</code> operand is divided by <code>a</code>. If an inverse block is specified it will be rendered when the remainder is <strong>not zero</strong>.</p>\n"
|
||||||
},
|
},
|
||||||
"ifOdd": {
|
"ifOdd": {
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -950,7 +942,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{#ifOdd 3}} odd {{else}} even {{/ifOdd}} -> odd",
|
"example": "{{#ifOdd 3}}odd{{else}}even{{/ifOdd}} -> odd",
|
||||||
"description": "<p>Block helper that renders a block if <code>value</code> is <strong>an odd number</strong>. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
"description": "<p>Block helper that renders a block if <code>value</code> is <strong>an odd number</strong>. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
||||||
},
|
},
|
||||||
"is": {
|
"is": {
|
||||||
|
@ -960,7 +952,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#is 3 3}} is {{else}} is not {{/is}} -> is",
|
"example": "{{#is 3 3}} is {{else}} is not {{/is}} -> ' is '",
|
||||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. Similar to <a href=\"#eq\">eq</a> but does not do strict equality.</p>\n"
|
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. Similar to <a href=\"#eq\">eq</a> but does not do strict equality.</p>\n"
|
||||||
},
|
},
|
||||||
"isnt": {
|
"isnt": {
|
||||||
|
@ -970,7 +962,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#isnt 3 3}} isnt {{else}} is {{/isnt}} -> is",
|
"example": "{{#isnt 3 3}} isnt {{else}} is {{/isnt}} -> ' is '",
|
||||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>not equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. Similar to <a href=\"#unlesseq\">unlessEq</a> but does not use strict equality for comparisons.</p>\n"
|
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>not equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. Similar to <a href=\"#unlesseq\">unlessEq</a> but does not use strict equality for comparisons.</p>\n"
|
||||||
},
|
},
|
||||||
"lt": {
|
"lt": {
|
||||||
|
@ -979,7 +971,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{#lt 2 3}} less than {{else}} more than or equal {{/lt}} -> less than",
|
"example": "{{#lt 2 3}} less than {{else}} more than or equal {{/lt}} -> ' less than '",
|
||||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>less than</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>less than</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
||||||
},
|
},
|
||||||
"lte": {
|
"lte": {
|
||||||
|
@ -989,7 +981,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#lte 2 3}} less than or equal {{else}} more than {{/lte}} -> less than or equal",
|
"example": "{{#lte 2 3}} less than or equal {{else}} more than {{/lte}} -> ' less than or equal '",
|
||||||
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>less than or equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
"description": "<p>Block helper that renders a block if <code>a</code> is <strong>less than or equal to</strong> <code>b</code>. If an inverse block is specified it will be rendered when falsy. You may optionally use the <code>compare=''</code> hash argument for the second value.</p>\n"
|
||||||
},
|
},
|
||||||
"neither": {
|
"neither": {
|
||||||
|
@ -999,7 +991,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#neither null null}} both falsey {{else}} both not falsey {{/neither}} -> both falsey",
|
"example": "{{#neither null null}}both falsey{{else}}both not falsey{{/neither}} -> both falsey",
|
||||||
"description": "<p>Block helper that renders a block if <strong>neither of</strong> the given values are truthy. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
"description": "<p>Block helper that renders a block if <strong>neither of</strong> the given values are truthy. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
||||||
},
|
},
|
||||||
"not": {
|
"not": {
|
||||||
|
@ -1008,7 +1000,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{#not undefined }} falsey {{else}} not falsey {{/not}} -> falsey",
|
"example": "{{#not undefined }}falsey{{else}}not falsey{{/not}} -> falsey",
|
||||||
"description": "<p>Returns true if <code>val</code> is falsey. Works as a block or inline helper.</p>\n"
|
"description": "<p>Returns true if <code>val</code> is falsey. Works as a block or inline helper.</p>\n"
|
||||||
},
|
},
|
||||||
"or": {
|
"or": {
|
||||||
|
@ -1017,7 +1009,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{#or 1 2 undefined }} at least one truthy {{else}} all falsey {{/or}} -> at least one truthy",
|
"example": "{{#or 1 2 undefined }} at least one truthy {{else}} all falsey {{/or}} -> ' at least one truthy '",
|
||||||
"description": "<p>Block helper that renders a block if <strong>any of</strong> the given values is truthy. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
"description": "<p>Block helper that renders a block if <strong>any of</strong> the given values is truthy. If an inverse block is specified it will be rendered when falsy.</p>\n"
|
||||||
},
|
},
|
||||||
"unlessEq": {
|
"unlessEq": {
|
||||||
|
@ -1027,7 +1019,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"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": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is equal to <code>b</code></strong>.</p>\n"
|
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is equal to <code>b</code></strong>.</p>\n"
|
||||||
},
|
},
|
||||||
"unlessGt": {
|
"unlessGt": {
|
||||||
|
@ -1037,7 +1029,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#unlessGt 20 1 }} not greater than {{else}} greater than {{/unlessGt}} -> greater than",
|
"example": "{{#unlessGt 20 1 }} not greater than {{else}} greater than {{/unlessGt}} -> ' greater than '",
|
||||||
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is greater than <code>b</code></strong>.</p>\n"
|
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is greater than <code>b</code></strong>.</p>\n"
|
||||||
},
|
},
|
||||||
"unlessLt": {
|
"unlessLt": {
|
||||||
|
@ -1047,7 +1039,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#unlessLt 20 1 }} greater than or equal {{else}} less than {{/unlessLt}} -> greater than or equal",
|
"example": "{{#unlessLt 20 1 }}greater than or equal{{else}}less than{{/unlessLt}} -> greater than or equal",
|
||||||
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is less than <code>b</code></strong>.</p>\n"
|
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is less than <code>b</code></strong>.</p>\n"
|
||||||
},
|
},
|
||||||
"unlessGteq": {
|
"unlessGteq": {
|
||||||
|
@ -1057,7 +1049,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#unlessGteq 20 1 }} less than {{else}} greater than or equal to {{/unlessGteq}} -> greater than or equal to",
|
"example": "{{#unlessGteq 20 1 }} less than {{else}}greater than or equal to{{/unlessGteq}} -> greater than or equal to",
|
||||||
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is greater than or equal to <code>b</code></strong>.</p>\n"
|
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is greater than or equal to <code>b</code></strong>.</p>\n"
|
||||||
},
|
},
|
||||||
"unlessLteq": {
|
"unlessLteq": {
|
||||||
|
@ -1067,7 +1059,7 @@
|
||||||
"options"
|
"options"
|
||||||
],
|
],
|
||||||
"numArgs": 3,
|
"numArgs": 3,
|
||||||
"example": "{{#unlessLteq 20 1 }} greater than {{else}} less than or equal to {{/unlessLteq}} -> greater than",
|
"example": "{{#unlessLteq 20 1 }} greater than {{else}} less than or equal to {{/unlessLteq}} -> ' greater than '",
|
||||||
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is less than or equal to <code>b</code></strong>.</p>\n"
|
"description": "<p>Block helper that always renders the inverse block <strong>unless <code>a</code> is less than or equal to <code>b</code></strong>.</p>\n"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1204,7 +1196,7 @@
|
||||||
"durationType"
|
"durationType"
|
||||||
],
|
],
|
||||||
"numArgs": 2,
|
"numArgs": 2,
|
||||||
"example": "{{duration timeLeft \"seconds\"}} -> a few seconds",
|
"example": "{{duration 8 \"seconds\"}} -> a few seconds",
|
||||||
"description": "<p>Produce a humanized duration left/until given an amount of time and the type of time measurement.</p>\n"
|
"description": "<p>Produce a humanized duration left/until given an amount of time and the type of time measurement.</p>\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
"manifest": "node ./scripts/gen-collection-info.js"
|
"manifest": "node ./scripts/gen-collection-info.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/handlebars-helpers": "^0.12.0",
|
"@budibase/handlebars-helpers": "^0.13.0",
|
||||||
"dayjs": "^1.10.8",
|
"dayjs": "^1.10.8",
|
||||||
"handlebars": "^4.7.6",
|
"handlebars": "^4.7.6",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
|
|
@ -36,7 +36,7 @@ const ADDED_HELPERS = {
|
||||||
duration: {
|
duration: {
|
||||||
args: ["time", "durationType"],
|
args: ["time", "durationType"],
|
||||||
numArgs: 2,
|
numArgs: 2,
|
||||||
example: '{{duration timeLeft "seconds"}} -> a few seconds',
|
example: '{{duration 8 "seconds"}} -> a few seconds',
|
||||||
description:
|
description:
|
||||||
"Produce a humanized duration left/until given an amount of time and the type of time measurement.",
|
"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
|
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.
|
* 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
|
// skip built in functions and ones seen already
|
||||||
if (
|
if (
|
||||||
HelperFunctionBuiltin.indexOf(name) !== -1 ||
|
HelperFunctionBuiltin.indexOf(name) !== -1 ||
|
||||||
foundNames.indexOf(name) !== -1
|
foundNames.indexOf(name) !== -1 ||
|
||||||
|
excludeFunctions[collection]?.includes(name)
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,10 +61,10 @@ describe("test the array helpers", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow use of the before helper", async () => {
|
it("should allow use of the before helper", async () => {
|
||||||
const output = await processString("{{before array 2}}", {
|
const output = await processString("{{before array 3}}", {
|
||||||
array,
|
array,
|
||||||
})
|
})
|
||||||
expect(output).toBe("hi,person,how")
|
expect(output).toBe("hi,person")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow use of the filter helper", async () => {
|
it("should allow use of the filter helper", async () => {
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,12 +1,17 @@
|
||||||
import { Document } from "../document"
|
import { Document } from "../document"
|
||||||
|
|
||||||
|
export interface QuerySchema {
|
||||||
|
name?: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Query extends Document {
|
export interface Query extends Document {
|
||||||
datasourceId: string
|
datasourceId: string
|
||||||
name: string
|
name: string
|
||||||
parameters: QueryParameter[]
|
parameters: QueryParameter[]
|
||||||
fields: RestQueryFields | any
|
fields: RestQueryFields | any
|
||||||
transformer: string | null
|
transformer: string | null
|
||||||
schema: Record<string, { name?: string; type: string }>
|
schema: Record<string, QuerySchema | string>
|
||||||
readable: boolean
|
readable: boolean
|
||||||
queryVerb: string
|
queryVerb: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
yarn build --scope @budibase/server --scope @budibase/worker
|
yarn build --scope @budibase/server --scope @budibase/worker
|
||||||
version=$(./scripts/getCurrentVersion.sh)
|
version=$(./scripts/getCurrentVersion.sh)
|
||||||
docker build -f hosting/single/Dockerfile -t budibase:latest --build-arg BUDIBASE_VERSION=$version .
|
docker build -f hosting/single/Dockerfile -t budibase:latest --build-arg BUDIBASE_VERSION=$version --build-arg TARGETBUILD=single .
|
||||||
|
|
Loading…
Reference in New Issue