Context binding for authenticated user in REST API querys. Includes fix for REST datasource UI
This commit is contained in:
parent
b0b69222d1
commit
830f127343
|
@ -49,6 +49,42 @@ export const getBindableProperties = (asset, componentId) => {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all rest bindable data fields
|
||||||
|
*/
|
||||||
|
export const getRestBindings = () => {
|
||||||
|
const userBindings = getUserBindings()
|
||||||
|
return [...userBindings]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility - coverting a map of readable bindings to runtime
|
||||||
|
*/
|
||||||
|
export const readableToRuntimeMap = (bindings, ctx) => {
|
||||||
|
if (!bindings || !ctx) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
return Object.keys(ctx).reduce((acc, key) => {
|
||||||
|
let parsedQuery = readableToRuntimeBinding(bindings, ctx[key])
|
||||||
|
acc[key] = parsedQuery
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility - coverting a map of runtime bindings to readable
|
||||||
|
*/
|
||||||
|
export const runtimeToReadableMap = (bindings, ctx) => {
|
||||||
|
if (!bindings || !ctx) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
return Object.keys(ctx).reduce((acc, key) => {
|
||||||
|
let parsedQuery = runtimeToReadableBinding(bindings, ctx[key])
|
||||||
|
acc[key] = parsedQuery
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the bindable properties exposed by a certain component.
|
* Gets the bindable properties exposed by a certain component.
|
||||||
*/
|
*/
|
||||||
|
@ -298,7 +334,6 @@ const getUserBindings = () => {
|
||||||
providerId: "user",
|
providerId: "user",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,31 @@
|
||||||
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
||||||
import RestAuthenticationBuilder from "./auth/RestAuthenticationBuilder.svelte"
|
import RestAuthenticationBuilder from "./auth/RestAuthenticationBuilder.svelte"
|
||||||
import ViewDynamicVariables from "./variables/ViewDynamicVariables.svelte"
|
import ViewDynamicVariables from "./variables/ViewDynamicVariables.svelte"
|
||||||
|
import {
|
||||||
|
getRestBindings,
|
||||||
|
readableToRuntimeBinding,
|
||||||
|
runtimeToReadableMap,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
export let datasource
|
export let datasource
|
||||||
export let queries
|
export let queries
|
||||||
|
|
||||||
let addHeader
|
let addHeader
|
||||||
|
|
||||||
|
let parsedHeaders = runtimeToReadableMap(
|
||||||
|
getRestBindings(),
|
||||||
|
cloneDeep(datasource?.config?.defaultHeaders)
|
||||||
|
)
|
||||||
|
|
||||||
|
const onDefaultHeaderUpdate = headers => {
|
||||||
|
const flatHeaders = cloneDeep(headers).reduce((acc, entry) => {
|
||||||
|
acc[entry.name] = readableToRuntimeBinding(getRestBindings(), entry.value)
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
datasource.config.defaultHeaders = flatHeaders
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
|
@ -30,8 +50,8 @@
|
||||||
</Body>
|
</Body>
|
||||||
<KeyValueBuilder
|
<KeyValueBuilder
|
||||||
bind:this={addHeader}
|
bind:this={addHeader}
|
||||||
bind:object={datasource.config.defaultHeaders}
|
bind:object={parsedHeaders}
|
||||||
on:change
|
on:change={evt => onDefaultHeaderUpdate(evt.detail)}
|
||||||
noAddButton
|
noAddButton
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -57,7 +57,8 @@
|
||||||
placeholder="Default"
|
placeholder="Default"
|
||||||
thin
|
thin
|
||||||
disabled={bindable}
|
disabled={bindable}
|
||||||
bind:value={binding.default}
|
on:change={evt => onBindingChange(binding.name, evt.detail)}
|
||||||
|
value={runtimeToReadableBinding(bindings, binding.default)}
|
||||||
/>
|
/>
|
||||||
{#if bindable}
|
{#if bindable}
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
|
|
|
@ -12,4 +12,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<slot />
|
{#key $params.selectedDatasource}
|
||||||
|
<slot />
|
||||||
|
{/key}
|
||||||
|
|
|
@ -40,13 +40,22 @@
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { RawRestBodyTypes } from "constants/backend"
|
import { RawRestBodyTypes } from "constants/backend"
|
||||||
|
|
||||||
|
import {
|
||||||
|
getRestBindings,
|
||||||
|
readableToRuntimeBinding,
|
||||||
|
runtimeToReadableBinding,
|
||||||
|
runtimeToReadableMap,
|
||||||
|
readableToRuntimeMap,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
|
|
||||||
let query, datasource
|
let query, datasource
|
||||||
let breakQs = {},
|
let breakQs = {},
|
||||||
bindings = {}
|
requestBindings = {}
|
||||||
let saveId, url
|
let saveId, url
|
||||||
let response, schema, enabledHeaders
|
let response, schema, enabledHeaders
|
||||||
let authConfigId
|
let authConfigId
|
||||||
let dynamicVariables, addVariableModal, varBinding
|
let dynamicVariables, addVariableModal, varBinding
|
||||||
|
let restBindings = getRestBindings()
|
||||||
|
|
||||||
$: datasourceType = datasource?.source
|
$: datasourceType = datasource?.source
|
||||||
$: integrationInfo = $integrations[datasourceType]
|
$: integrationInfo = $integrations[datasourceType]
|
||||||
|
@ -63,8 +72,10 @@
|
||||||
Object.keys(schema || {}).length !== 0 ||
|
Object.keys(schema || {}).length !== 0 ||
|
||||||
Object.keys(query?.schema || {}).length !== 0
|
Object.keys(query?.schema || {}).length !== 0
|
||||||
|
|
||||||
|
$: runtimeUrlQueries = readableToRuntimeMap(restBindings, breakQs)
|
||||||
|
|
||||||
function getSelectedQuery() {
|
function getSelectedQuery() {
|
||||||
return cloneDeep(
|
const cloneQuery = cloneDeep(
|
||||||
$queries.list.find(q => q._id === $queries.selected) || {
|
$queries.list.find(q => q._id === $queries.selected) || {
|
||||||
datasourceId: $params.selectedDatasource,
|
datasourceId: $params.selectedDatasource,
|
||||||
parameters: [],
|
parameters: [],
|
||||||
|
@ -76,6 +87,30 @@
|
||||||
queryVerb: "read",
|
queryVerb: "read",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (cloneQuery?.fields?.headers) {
|
||||||
|
cloneQuery.fields.headers = runtimeToReadableMap(
|
||||||
|
restBindings,
|
||||||
|
cloneQuery.fields.headers
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cloneQuery?.fields?.requestBody) {
|
||||||
|
cloneQuery.fields.requestBody = runtimeToReadableBinding(
|
||||||
|
restBindings,
|
||||||
|
cloneQuery.fields.requestBody
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cloneQuery?.parameters) {
|
||||||
|
const flatParams = restUtils.queryParametersToKeyValue(
|
||||||
|
cloneQuery.parameters
|
||||||
|
)
|
||||||
|
const updatedParams = runtimeToReadableMap(restBindings, flatParams)
|
||||||
|
cloneQuery.parameters = restUtils.keyValueToQueryParameters(updatedParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloneQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkQueryName(inputUrl = null) {
|
function checkQueryName(inputUrl = null) {
|
||||||
|
@ -89,7 +124,9 @@
|
||||||
if (!base) {
|
if (!base) {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
const qs = restUtils.buildQueryString(qsObj)
|
const qs = restUtils.buildQueryString(
|
||||||
|
runtimeToReadableMap(restBindings, qsObj)
|
||||||
|
)
|
||||||
let newUrl = base
|
let newUrl = base
|
||||||
if (base.includes("?")) {
|
if (base.includes("?")) {
|
||||||
newUrl = base.split("?")[0]
|
newUrl = base.split("?")[0]
|
||||||
|
@ -98,14 +135,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildQuery() {
|
function buildQuery() {
|
||||||
const newQuery = { ...query }
|
const newQuery = cloneDeep(query)
|
||||||
const queryString = restUtils.buildQueryString(breakQs)
|
const queryString = restUtils.buildQueryString(runtimeUrlQueries)
|
||||||
|
newQuery.fields.headers = readableToRuntimeMap(
|
||||||
|
restBindings,
|
||||||
|
newQuery.fields.headers
|
||||||
|
)
|
||||||
|
newQuery.fields.requestBody = readableToRuntimeBinding(
|
||||||
|
restBindings,
|
||||||
|
newQuery.fields.requestBody
|
||||||
|
)
|
||||||
newQuery.fields.path = url.split("?")[0]
|
newQuery.fields.path = url.split("?")[0]
|
||||||
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 = restUtils.fieldsToSchema(schema)
|
||||||
newQuery.parameters = restUtils.keyValueToQueryParameters(bindings)
|
|
||||||
|
const parsedRequestBindings = readableToRuntimeMap(
|
||||||
|
restBindings,
|
||||||
|
requestBindings
|
||||||
|
)
|
||||||
|
newQuery.parameters = restUtils.keyValueToQueryParameters(
|
||||||
|
parsedRequestBindings
|
||||||
|
)
|
||||||
|
|
||||||
return newQuery
|
return newQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +180,7 @@
|
||||||
|
|
||||||
async function runQuery() {
|
async function runQuery() {
|
||||||
try {
|
try {
|
||||||
response = await queries.preview(buildQuery(query))
|
response = await queries.preview(buildQuery())
|
||||||
if (response.rows.length === 0) {
|
if (response.rows.length === 0) {
|
||||||
notifications.info("Request did not return any data")
|
notifications.info("Request did not return any data")
|
||||||
} else {
|
} else {
|
||||||
|
@ -250,6 +303,8 @@
|
||||||
const datasourceUrl = datasource?.config.url
|
const datasourceUrl = datasource?.config.url
|
||||||
const qs = query?.fields.queryString
|
const qs = query?.fields.queryString
|
||||||
breakQs = restUtils.breakQueryString(qs)
|
breakQs = restUtils.breakQueryString(qs)
|
||||||
|
breakQs = runtimeToReadableMap(restBindings, breakQs)
|
||||||
|
|
||||||
const path = query.fields.path
|
const path = query.fields.path
|
||||||
if (
|
if (
|
||||||
datasourceUrl &&
|
datasourceUrl &&
|
||||||
|
@ -260,7 +315,7 @@
|
||||||
}
|
}
|
||||||
url = buildUrl(query.fields.path, breakQs)
|
url = buildUrl(query.fields.path, breakQs)
|
||||||
schema = restUtils.schemaToFields(query.schema)
|
schema = restUtils.schemaToFields(query.schema)
|
||||||
bindings = restUtils.queryParametersToKeyValue(query.parameters)
|
requestBindings = restUtils.queryParametersToKeyValue(query.parameters)
|
||||||
authConfigId = getAuthConfigId()
|
authConfigId = getAuthConfigId()
|
||||||
if (!query.fields.disabledHeaders) {
|
if (!query.fields.disabledHeaders) {
|
||||||
query.fields.disabledHeaders = {}
|
query.fields.disabledHeaders = {}
|
||||||
|
@ -344,7 +399,7 @@
|
||||||
<Tabs selected="Bindings" quiet noPadding noHorizPadding onTop>
|
<Tabs selected="Bindings" quiet noPadding noHorizPadding onTop>
|
||||||
<Tab title="Bindings">
|
<Tab title="Bindings">
|
||||||
<KeyValueBuilder
|
<KeyValueBuilder
|
||||||
bind:object={bindings}
|
bind:object={requestBindings}
|
||||||
tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query"
|
tooltip="Set the name of the binding which can be used in Handlebars statements throughout your query"
|
||||||
name="binding"
|
name="binding"
|
||||||
headings
|
headings
|
||||||
|
|
|
@ -121,6 +121,9 @@ export async function preview(ctx: any) {
|
||||||
parameters,
|
parameters,
|
||||||
transformer,
|
transformer,
|
||||||
queryId,
|
queryId,
|
||||||
|
ctx: {
|
||||||
|
user: ctx.user,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { rows, keys, info, extra } = await quotas.addQuery(runFn)
|
const { rows, keys, info, extra } = await quotas.addQuery(runFn)
|
||||||
|
|
|
@ -21,6 +21,8 @@ class QueryRunner {
|
||||||
this.queryId = input.queryId
|
this.queryId = input.queryId
|
||||||
this.noRecursiveQuery = flags.noRecursiveQuery
|
this.noRecursiveQuery = flags.noRecursiveQuery
|
||||||
this.cachedVariables = []
|
this.cachedVariables = []
|
||||||
|
// Additional context items for enrichment
|
||||||
|
this.ctx = input.ctx
|
||||||
// allows the response from a query to be stored throughout this
|
// allows the response from a query to be stored throughout this
|
||||||
// execution so that if it needs to be re-used for another variable
|
// execution so that if it needs to be re-used for another variable
|
||||||
// it can be
|
// it can be
|
||||||
|
@ -38,12 +40,24 @@ class QueryRunner {
|
||||||
|
|
||||||
// pre-query, make sure datasource variables are added to parameters
|
// pre-query, make sure datasource variables are added to parameters
|
||||||
const parameters = await this.addDatasourceVariables()
|
const parameters = await this.addDatasourceVariables()
|
||||||
|
|
||||||
|
// Enrich the parameters with the addition context items.
|
||||||
|
// 'user' is now a reserved variable key in mapping parameters
|
||||||
|
const enrichedParameters = enrichQueryFields(parameters, this.ctx)
|
||||||
|
const enrichedContext = { ...enrichedParameters, ...this.ctx }
|
||||||
|
|
||||||
|
// Parse global headers
|
||||||
|
datasource.config.defaultHeaders = enrichQueryFields(
|
||||||
|
datasource.config.defaultHeaders,
|
||||||
|
enrichedContext
|
||||||
|
)
|
||||||
|
|
||||||
let query
|
let query
|
||||||
// handle SQL injections by interpolating the variables
|
// handle SQL injections by interpolating the variables
|
||||||
if (isSQL(datasource)) {
|
if (isSQL(datasource)) {
|
||||||
query = interpolateSQL(fields, parameters, integration)
|
query = interpolateSQL(fields, enrichedParameters, integration)
|
||||||
} else {
|
} else {
|
||||||
query = enrichQueryFields(fields, parameters)
|
query = enrichQueryFields(fields, enrichedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add pagination values for REST queries
|
// Add pagination values for REST queries
|
||||||
|
@ -67,7 +81,7 @@ class QueryRunner {
|
||||||
if (transformer) {
|
if (transformer) {
|
||||||
const runner = new ScriptRunner(transformer, {
|
const runner = new ScriptRunner(transformer, {
|
||||||
data: rows,
|
data: rows,
|
||||||
params: parameters,
|
params: enrichedParameters,
|
||||||
})
|
})
|
||||||
rows = runner.execute()
|
rows = runner.execute()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue