(showTooltip = true)}
on:mouseleave={() => (showTooltip = false)}
>
@@ -44,6 +45,7 @@
}
.container {
display: flex;
+ align-items: center;
}
.icon-container {
position: relative;
@@ -64,4 +66,8 @@
.icon {
transform: scale(0.75);
}
+ .icon-small {
+ margin-top: -2px;
+ margin-bottom: -5px;
+ }
diff --git a/packages/bbui/src/Tabs/Tabs.svelte b/packages/bbui/src/Tabs/Tabs.svelte
index c801ff44f..235e103bb 100644
--- a/packages/bbui/src/Tabs/Tabs.svelte
+++ b/packages/bbui/src/Tabs/Tabs.svelte
@@ -11,6 +11,8 @@
export let quiet = false
export let emphasized = false
+ let thisSelected = undefined
+
let _id = id()
const tab = writable({ title: selected, id: _id, emphasized })
setContext("tab", tab)
@@ -20,9 +22,18 @@
const dispatch = createEventDispatcher()
$: {
- if ($tab.title !== selected) {
- selected = $tab.title
- dispatch("select", selected)
+ if (thisSelected !== selected) {
+ thisSelected = selected
+ dispatch("select", thisSelected)
+ } else if ($tab.title !== thisSelected) {
+ thisSelected = $tab.title
+ dispatch("select", thisSelected)
+ }
+ if ($tab.title !== thisSelected) {
+ tab.update(state => {
+ state.title = thisSelected
+ return state
+ })
}
}
diff --git a/packages/builder/assets/bb-spaceship.svg b/packages/builder/assets/bb-spaceship.svg
new file mode 100755
index 000000000..a0bc5a49c
--- /dev/null
+++ b/packages/builder/assets/bb-spaceship.svg
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte
index 796f70b43..18143c207 100644
--- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte
+++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte
@@ -8,6 +8,7 @@
import EditQueryPopover from "./popovers/EditQueryPopover.svelte"
import NavItem from "components/common/NavItem.svelte"
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
+ import { customQueryIconText, customQueryIconColor } from "helpers/data/utils"
import ICONS from "./icons"
let openDataSources = []
@@ -129,6 +130,8 @@
- import { Icon } from "@budibase/bbui"
+ import { Icon, Body } from "@budibase/bbui"
-
+
+
+ Need help? Go to our forums
+
+
+
diff --git a/packages/builder/src/components/common/NavItem.svelte b/packages/builder/src/components/common/NavItem.svelte
index ec7994be8..2d7dc745d 100644
--- a/packages/builder/src/components/common/NavItem.svelte
+++ b/packages/builder/src/components/common/NavItem.svelte
@@ -11,6 +11,8 @@
export let selected = false
export let opened = false
export let draggable = false
+ export let iconText
+ export let iconColor
const dispatch = createEventDispatcher()
@@ -42,9 +44,13 @@
{/if}
- {#if icon}
+ {#if iconText}
+
+ {iconText}
+
+ {:else if icon}
-
+
{/if}
{text}
@@ -123,4 +129,9 @@
justify-content: center;
align-items: center;
}
+
+ .iconText {
+ margin-top: 1px;
+ font-size: var(--spectrum-global-dimension-font-size-50);
+ }
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte
index 220bd4120..62a89af5b 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte
@@ -17,7 +17,7 @@
queries as queriesStore,
} from "stores/backend"
import { datasources, integrations } from "stores/backend"
- import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
+ import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
@@ -148,15 +148,15 @@
/>
{#if value?.type === "query"}
-
+
Save
-
+
{#if getQueryParams(value).length > 0}
-
{/if}
import { Select, Layout, Input, Checkbox } from "@budibase/bbui"
import { datasources, integrations, queries } from "stores/backend"
- import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
+ import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte"
export let parameters
@@ -53,10 +53,10 @@
{#if query?.parameters?.length > 0}
-
({ name, value }))
let fieldActivity = []
@@ -76,8 +79,8 @@
{#if Object.keys(object || {}).length > 0}
{#if headings}
-
Key
-
Value
+
{keyPlaceholder}
+
{valuePlaceholder}
{#if toggle}
Active
{/if}
@@ -85,12 +88,16 @@
{/if}
{#each fields as field, idx}
-
+
{#if options}
{:else}
@@ -127,4 +134,7 @@
.container-active {
grid-template-columns: 1fr 1fr 50px 20px;
}
+ .readOnly {
+ grid-template-columns: 1fr 1fr;
+ }
diff --git a/packages/builder/src/components/integration/QueryParameterBuilder.svelte b/packages/builder/src/components/integration/QueryBindingBuilder.svelte
similarity index 52%
rename from packages/builder/src/components/integration/QueryParameterBuilder.svelte
rename to packages/builder/src/components/integration/QueryBindingBuilder.svelte
index 618cb2b44..dd688dceb 100644
--- a/packages/builder/src/components/integration/QueryParameterBuilder.svelte
+++ b/packages/builder/src/components/integration/QueryBindingBuilder.svelte
@@ -1,5 +1,5 @@
-
-
-
Parameters
+
+
+ Bindings
{#if !bindable}
- Add Param
+ Add Binding
{/if}
{#if !bindable}
- Parameters come in two parts: the parameter name, and a default/fallback
- value.
+ Bindings come in two parts: the binding name, and a default/fallback
+ value. These bindings can be used as Handlebars expressions throughout the
+ query.
{:else}
- Enter a value for each parameter. The default values will be used for any
+ Enter a value for each binding. The default values will be used for any
values left blank.
{/if}
-
- {#each parameters as parameter, idx}
+
+ {#each bindings as binding, idx}
{#if bindable}
onBindingChange(parameter.name, evt.detail)}
+ on:change={evt => onBindingChange(binding.name, evt.detail)}
value={runtimeToReadableBinding(
- bindings,
- customParams?.[parameter.name]
+ bindableOptions,
+ customParams?.[binding.name]
)}
- {bindings}
+ {bindableOptions}
/>
{:else}
- deleteQueryParameter(idx)}
- />
+ deleteQueryBinding(idx)} />
{/if}
{/each}
diff --git a/packages/builder/src/components/integration/QueryViewer.svelte b/packages/builder/src/components/integration/QueryViewer.svelte
index aed3c4730..f14d1d2b8 100644
--- a/packages/builder/src/components/integration/QueryViewer.svelte
+++ b/packages/builder/src/components/integration/QueryViewer.svelte
@@ -17,7 +17,7 @@
import ExtraQueryConfig from "./ExtraQueryConfig.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte"
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
- import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
+ import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
import { datasources, integrations, queries } from "stores/backend"
import { capitalise } from "../../helpers"
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
@@ -120,7 +120,7 @@
config={integrationInfo.extra}
/>
{/if}
-
+
{/if}
{#if shouldShowQueryConfig}
diff --git a/packages/builder/src/helpers/data/utils.js b/packages/builder/src/helpers/data/utils.js
index b1c4139d5..64cb4df5c 100644
--- a/packages/builder/src/helpers/data/utils.js
+++ b/packages/builder/src/helpers/data/utils.js
@@ -1,3 +1,5 @@
+import { IntegrationTypes } from "constants/backend"
+
export function schemaToFields(schema) {
const response = {}
if (schema && typeof schema === "object") {
@@ -49,3 +51,60 @@ export function buildQueryString(obj) {
}
return str
}
+
+export function keyValueToQueryParameters(obj) {
+ let array = []
+ if (obj && typeof obj === "object") {
+ for (let [key, value] of Object.entries(obj)) {
+ array.push({ name: key, default: value })
+ }
+ }
+ return array
+}
+
+export function queryParametersToKeyValue(array) {
+ let obj = {}
+ if (Array.isArray(array)) {
+ for (let param of array) {
+ obj[param.name] = param.default
+ }
+ }
+ return obj
+}
+
+export function customQueryIconText(datasource, query) {
+ if (datasource.source !== IntegrationTypes.REST) {
+ return
+ }
+ switch (query.queryVerb) {
+ case "create":
+ return "POST"
+ case "update":
+ return "PUT"
+ case "read":
+ return "GET"
+ case "delete":
+ return "DELETE"
+ case "patch":
+ return "PATCH"
+ }
+}
+
+export function customQueryIconColor(datasource, query) {
+ if (datasource.source !== IntegrationTypes.REST) {
+ return
+ }
+ switch (query.queryVerb) {
+ case "create":
+ return "#dcc339"
+ case "update":
+ return "#5197ec"
+ case "read":
+ return "#53a761"
+ case "delete":
+ return "#ea7d82"
+ case "patch":
+ default:
+ return
+ }
+}
diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte
index 7d30b8a0a..64572280c 100644
--- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte
@@ -13,6 +13,7 @@
Heading,
RadioGroup,
Label,
+ Body,
TextArea,
Table,
notifications,
@@ -30,6 +31,8 @@
schemaToFields,
breakQueryString,
buildQueryString,
+ keyValueToQueryParameters,
+ queryParametersToKeyValue,
} from "helpers/data/utils"
import {
RestBodyTypes as bodyTypes,
@@ -37,9 +40,12 @@
} from "constants/backend"
import JSONPreview from "components/integration/JSONPreview.svelte"
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
+ import Placeholder from "assets/bb-spaceship.svg"
+ import { cloneDeep } from "lodash/fp"
let query, datasource
- let breakQs = {}
+ let breakQs = {},
+ bindings = {}
let url = ""
let saveId
let response, schema, isGet
@@ -56,7 +62,7 @@
response?.info?.code >= 200 && response?.info?.code <= 206
function getSelectedQuery() {
- return (
+ return cloneDeep(
$queries.list.find(q => q._id === $queries.selected) || {
datasourceId: $params.selectedDatasource,
parameters: [],
@@ -85,7 +91,9 @@
return qs.length > 0 ? `${newUrl}?${qs}` : newUrl
}
- function learnMoreBanner() {}
+ function learnMoreBanner() {
+ window.open("https://docs.budibase.com/building-apps/data/transformers")
+ }
function buildQuery() {
const newQuery = { ...query }
@@ -93,6 +101,7 @@
newQuery.fields.path = url.split("?")[0]
newQuery.fields.queryString = queryString
newQuery.schema = fieldsToSchema(schema)
+ newQuery.parameters = keyValueToQueryParameters(bindings)
return newQuery
}
@@ -101,6 +110,7 @@
try {
const { _id } = await queries.save(toSave.datasourceId, toSave)
saveId = _id
+ query = getSelectedQuery()
notifications.success(`Request saved successfully.`)
} catch (err) {
notifications.error(`Error creating query. ${err.message}`)
@@ -114,6 +124,7 @@
notifications.info("Request did not return any data.")
} else {
response.info = response.info || { code: 200 }
+ schema = response.schema
notifications.success("Request sent successfully.")
}
} catch (err) {
@@ -127,6 +138,7 @@
breakQs = breakQueryString(qs)
url = buildUrl(query.fields.path, breakQs)
schema = schemaToFields(query.schema)
+ bindings = queryParametersToKeyValue(query.parameters)
if (query && !query.transformer) {
query.transformer = "return data"
}
@@ -168,11 +180,21 @@
/>
-
+
Send
-
+
+
+
+
@@ -219,62 +241,88 @@
-
-
- {#if !response}
- Response
- {:else}
-
-
-
-
+
+
+
+ {#if !response && Object.keys(schema).length === 0}
+ Response
+
+
+
+ {"enter a url in the textbox above and click send to get a response".toUpperCase()}
-
-
-
-
-
-
-
-
+
+ {:else}
+
{#if response}
-
+
+
+
+
+
{/if}
-
-
-
- Status: {response?.info.code}
-
-
- Time: {response?.info.time}
-
-
- Size: {response?.info.size}
-
- Save query
-
-
- {/if}
-
+ {#if schema || response}
+
+
+
+ {/if}
+ {#if response}
+
+
+
+
+
+
+
+
+ {#if response}
+
+ {/if}
+
+
+
+
+ Status: {response?.info.code}
+
+
+ Time: {response?.info.time}
+
+
+ Size: {response?.info.size}
+
+ Save query
+
+ {/if}
+
+ {/if}
+
+
{/if}
@@ -284,6 +332,9 @@
margin: 0 auto;
height: 100%;
}
+ .table {
+ width: 960px;
+ }
.url-block {
display: flex;
gap: var(--spacing-s);
@@ -297,6 +348,9 @@
.top {
min-height: 50%;
}
+ .bottom {
+ padding-bottom: 50px;
+ }
.stats {
display: flex;
gap: var(--spacing-xl);
@@ -319,4 +373,15 @@
gap: var(--spacing-m);
align-items: center;
}
+ .placeholder-internal {
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ gap: var(--spacing-l);
+ }
+ .placeholder {
+ display: flex;
+ margin-top: var(--spacing-xl);
+ justify-content: center;
+ }
diff --git a/packages/server/__mocks__/node-fetch.ts b/packages/server/__mocks__/node-fetch.ts
index dfb839fe8..8badbd015 100644
--- a/packages/server/__mocks__/node-fetch.ts
+++ b/packages/server/__mocks__/node-fetch.ts
@@ -6,6 +6,9 @@ module FetchMock {
return {
status,
headers: {
+ raw: () => {
+ return { "content-type": ["application/json"] }
+ },
get: () => {
return ["application/json"]
},
diff --git a/packages/server/src/api/controllers/query/index.js b/packages/server/src/api/controllers/query/index.js
index b5c0c9bf9..e12b0039a 100644
--- a/packages/server/src/api/controllers/query/index.js
+++ b/packages/server/src/api/controllers/query/index.js
@@ -123,7 +123,7 @@ async function enrichQueryFields(fields, parameters = {}) {
enrichedQuery.requestBody
)
} catch (err) {
- throw { message: `JSON Invalid - error: ${err}` }
+ // no json found, ignore
}
delete enrichedQuery.customData
}
@@ -151,7 +151,7 @@ exports.preview = async function (ctx) {
const enrichedQuery = await enrichQueryFields(fields, parameters)
try {
- const { rows, keys, info, raw } = await Runner.run({
+ const { rows, keys, info, extra } = await Runner.run({
datasource,
queryVerb,
query: enrichedQuery,
@@ -162,7 +162,7 @@ exports.preview = async function (ctx) {
rows,
schemaFields: [...new Set(keys)],
info,
- raw,
+ extra,
}
} catch (err) {
ctx.throw(400, err)
diff --git a/packages/server/src/api/controllers/query/validation.js b/packages/server/src/api/controllers/query/validation.js
index 2613e6806..a17a752ca 100644
--- a/packages/server/src/api/controllers/query/validation.js
+++ b/packages/server/src/api/controllers/query/validation.js
@@ -19,6 +19,7 @@ exports.queryValidation = () => {
extra: Joi.object().optional(),
schema: Joi.object({}).required().unknown(true),
transformer: Joi.string().optional(),
+ flags: Joi.object().optional(),
})
}
diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts
index 51310eef3..fe1c3ca11 100644
--- a/packages/server/src/integrations/rest.ts
+++ b/packages/server/src/integrations/rest.ts
@@ -62,6 +62,18 @@ module RestModule {
const { formatBytes } = require("../utilities")
const { performance } = require("perf_hooks")
+ interface RestQuery {
+ path: string
+ queryString?: string
+ headers: { [key: string]: any }
+ enabledHeaders: { [key: string]: any }
+ requestBody: any
+ bodyType: string
+ json: object
+ method: string
+ authConfigId: string
+ }
+
interface RestConfig {
url: string
defaultHeaders: {
@@ -70,13 +82,6 @@ module RestModule {
authConfigs: AuthConfig[]
}
- interface Request {
- path: string
- queryString?: string
- headers?: string
- json?: any
- }
-
const SCHEMA: Integration = {
docs: "https://github.com/node-fetch/node-fetch",
description:
@@ -140,7 +145,7 @@ module RestModule {
}
async parseResponse(response: any) {
- let data, raw
+ let data, raw, headers
const contentType = response.headers.get("content-type")
if (contentType && contentType.indexOf("application/json") !== -1) {
data = await response.json()
@@ -149,8 +154,12 @@ module RestModule {
data = await response.text()
raw = data
}
- const size = formatBytes(response.headers.get("content-length") || 0)
+ const size = formatBytes(response.headers.get("content-length") || Buffer.byteLength(raw, "utf8"))
const time = `${Math.round(performance.now() - this.startTimeMs)}ms`
+ headers = response.headers.raw()
+ for (let [key, value] of Object.entries(headers)) {
+ headers[key] = Array.isArray(value) ? value[0] : value
+ }
return {
data,
info: {
@@ -158,55 +167,74 @@ module RestModule {
size,
time,
},
- raw,
+ extra: {
+ raw,
+ headers,
+ },
}
}
getUrl(path: string, queryString: string): string {
const main = `${path}?${queryString}`
- if (!this.config.url) {
- return main
- } else {
- return `${this.config.url}/${main}`
+ let complete = !this.config.url ? main : `${this.config.url}/${main}`
+ if (!complete.startsWith("http")) {
+ complete = `http://${complete}`
}
+ return complete
}
- processAuth(authConfigId: string) {
- if (!this.config.authConfigs || !authConfigId) {
- return
- }
- const authConfig = this.config.authConfigs.filter(
- c => c._id === authConfigId
- )[0]
- let config
- switch (authConfig.type) {
- case AuthType.BASIC:
- config = authConfig.config as BasicAuthConfig
- this.headers.Authorization = `Basic ${Buffer.from(
- `${config.username}:${config.password}`
- ).toString("base64")}`
- break
- case AuthType.BEARER:
- config = authConfig.config as BearerAuthConfig
- this.headers.Authorization = `Bearer ${config.token}`
- break
+ getAuthHeaders(authConfigId: string): { [key: string]: any }{
+ let headers: any = {}
+
+ if (this.config.authConfigs && authConfigId) {
+ const authConfig = this.config.authConfigs.filter(
+ c => c._id === authConfigId
+ )[0]
+ let config
+ switch (authConfig.type) {
+ case AuthType.BASIC:
+ config = authConfig.config as BasicAuthConfig
+ headers.Authorization = `Basic ${Buffer.from(
+ `${config.username}:${config.password}`
+ ).toString("base64")}`
+ break
+ case AuthType.BEARER:
+ config = authConfig.config as BearerAuthConfig
+ headers.Authorization = `Bearer ${config.token}`
+ break
+ }
}
+
+ return headers
}
- async _req({
- path = "",
- queryString = "",
- headers = {},
- json = {},
- method = "GET",
- authConfigId = "",
- }) {
+ async _req(query: RestQuery) {
+ const { path = "", queryString = "", headers = {}, method = "GET", enabledHeaders, bodyType, requestBody, authConfigId } = query
+
+ const authHeaders = this.getAuthHeaders(authConfigId)
+
this.headers = {
...this.config.defaultHeaders,
...headers,
+ ...authHeaders,
+ }
+
+ if (enabledHeaders) {
+ for (let headerKey of Object.keys(this.headers)) {
+ if (!enabledHeaders[headerKey]) {
+ delete this.headers[headerKey]
+ }
+ }
}
- this.processAuth(authConfigId)
+ let json
+ if (bodyType === BodyTypes.JSON && requestBody) {
+ try {
+ json = JSON.parse(requestBody)
+ } catch (err) {
+ throw "Invalid JSON for request body"
+ }
+ }
const input: any = { method, headers: this.headers }
if (json && typeof json === "object" && Object.keys(json).length > 0) {
@@ -218,23 +246,23 @@ module RestModule {
return await this.parseResponse(response)
}
- async create(opts: Request) {
+ async create(opts: RestQuery) {
return this._req({ ...opts, method: "POST" })
}
- async read(opts: Request) {
+ async read(opts: RestQuery) {
return this._req({ ...opts, method: "GET" })
}
- async update(opts: Request) {
+ async update(opts: RestQuery) {
return this._req({ ...opts, method: "PUT" })
}
- async patch(opts: Request) {
+ async patch(opts: RestQuery) {
return this._req({ ...opts, method: "PATCH" })
}
- async delete(opts: Request) {
+ async delete(opts: RestQuery) {
return this._req({ ...opts, method: "DELETE" })
}
}
diff --git a/packages/server/src/integrations/tests/rest.spec.js b/packages/server/src/integrations/tests/rest.spec.js
index 0523115ad..6c1d98912 100644
--- a/packages/server/src/integrations/tests/rest.spec.js
+++ b/packages/server/src/integrations/tests/rest.spec.js
@@ -1,6 +1,9 @@
jest.mock("node-fetch", () =>
jest.fn(() => ({
headers: {
+ raw: () => {
+ return { "content-type": ["application/json"] }
+ },
get: () => ["application/json"]
},
json: jest.fn(),
@@ -25,6 +28,7 @@ describe("REST Integration", () => {
config = new TestConfiguration({
url: BASE_URL,
})
+ jest.clearAllMocks()
})
it("calls the create method with the correct params", async () => {
@@ -34,9 +38,10 @@ describe("REST Integration", () => {
headers: {
Accept: "application/json",
},
- json: {
+ bodyType: "json",
+ requestBody: JSON.stringify({
name: "test",
- },
+ }),
}
const response = await config.integration.create(query)
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
@@ -61,6 +66,7 @@ describe("REST Integration", () => {
headers: {
Accept: "text/html",
},
+ method: "GET",
})
})
@@ -71,13 +77,14 @@ describe("REST Integration", () => {
headers: {
Accept: "application/json",
},
- json: {
+ bodyType: "json",
+ requestBody: JSON.stringify({
name: "test",
- },
+ }),
}
const response = await config.integration.update(query)
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
- method: "POST",
+ method: "PUT",
body: '{"name":"test"}',
headers: {
Accept: "application/json",
@@ -92,9 +99,10 @@ describe("REST Integration", () => {
headers: {
Accept: "application/json",
},
- json: {
+ bodyType: "json",
+ requestBody: JSON.stringify({
name: "test",
- },
+ }),
}
const response = await config.integration.delete(query)
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
@@ -102,6 +110,7 @@ describe("REST Integration", () => {
headers: {
Accept: "application/json",
},
+ body: '{"name":"test"}',
})
})
diff --git a/packages/server/src/threads/query.js b/packages/server/src/threads/query.js
index d94c4e6c2..9645bfcd4 100644
--- a/packages/server/src/threads/query.js
+++ b/packages/server/src/threads/query.js
@@ -19,8 +19,8 @@ function hasExtraData(response) {
return (
typeof response === "object" &&
!Array.isArray(response) &&
- response.data &&
- response.info
+ response.data != null &&
+ response.info != null
)
}
@@ -34,11 +34,11 @@ async function runAndTransform(datasource, queryVerb, query, transformer) {
let output = formatResponse(await integration[queryVerb](query))
let rows = output,
info = undefined,
- raw = undefined
+ extra = undefined
if (hasExtraData(output)) {
rows = output.data
info = output.info
- raw = output.raw
+ extra = output.extra
}
// transform as required
@@ -64,7 +64,7 @@ async function runAndTransform(datasource, queryVerb, query, transformer) {
integration.end()
}
- return { rows, keys, info, raw }
+ return { rows, keys, info, extra }
}
module.exports = (input, callback) => {