diff --git a/packages/builder/src/components/integration/KeyValueBuilder.svelte b/packages/builder/src/components/integration/KeyValueBuilder.svelte index b8d0e0a467..7229712a65 100644 --- a/packages/builder/src/components/integration/KeyValueBuilder.svelte +++ b/packages/builder/src/components/integration/KeyValueBuilder.svelte @@ -125,7 +125,7 @@ {#each menuItems as item} - + item.onClick(field)}> {item.text} {/each} @@ -162,4 +162,7 @@ .readOnly-menu { grid-template-columns: 1fr 1fr 20px; } + .control { + margin-top: 4px; + } diff --git a/packages/builder/src/helpers/data/utils.js b/packages/builder/src/helpers/data/utils.js index 613f533043..e163043470 100644 --- a/packages/builder/src/helpers/data/utils.js +++ b/packages/builder/src/helpers/data/utils.js @@ -119,3 +119,62 @@ export function flipHeaderState(headersActivity) { }) return enabled } + +// convert dynamic variables list to simple key/val object +export function variablesToObject(datasource) { + const variablesList = datasource?.config?.dynamicVariables + if (variablesList && variablesList.length > 0) { + return variablesList.reduce( + (acc, next) => ({ ...acc, [next.name]: next.value }), + {} + ) + } + return {} +} + +// convert dynamic variables object back to a list, enrich with query id +export function rebuildVariables(queryId, variables) { + let vars = [] + if (variables) { + vars = Object.entries(variables).map(entry => { + return { + name: entry[0], + value: entry[1], + queryId, + } + }) + } + return vars +} + +export function shouldShowVariables(dynamicVariables, variablesReadOnly) { + return !!( + dynamicVariables && + // show when editable or when read only and not empty + (!variablesReadOnly || Object.keys(dynamicVariables).length > 0) + ) +} + +export function buildAuthConfigs(datasource) { + if (datasource?.config?.authConfigs) { + return datasource.config.authConfigs.map(c => ({ + label: c.name, + value: c._id, + })) + } + return [] +} + +export default { + breakQueryString, + buildQueryString, + fieldsToSchema, + flipHeaderState, + keyValueToQueryParameters, + queryParametersToKeyValue, + schemaToFields, + variablesToObject, + rebuildVariables, + shouldShowVariables, + buildAuthConfigs, +} diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/_components/DynamicVariableModal.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/_components/DynamicVariableModal.svelte new file mode 100644 index 0000000000..dae6804e25 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/_components/DynamicVariableModal.svelte @@ -0,0 +1,46 @@ + + + + + + + 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 62a418a958..88a460bc8f 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 @@ -1,22 +1,22 @@ + {#if query && queryConfig}
@@ -340,7 +292,10 @@ {#if !$flags.queryTransformerBanner} + window.open( + "https://docs.budibase.com/building-apps/data/transformers" + )} on:change={() => flags.updateFlag("queryTransformerBanner", true)} > @@ -449,9 +404,9 @@ name="Variable" headings keyHeading="Name" - keyPlaceholder="e.g. cookie" + keyPlaceholder="Variable name" valueHeading={`Value`} - valuePlaceholder={`e.g. {{ headers.set-cookie }}`} + valuePlaceholder={`{{ value }}`} readOnly={variablesReadOnly} /> diff --git a/packages/client/manifest.json b/packages/client/manifest.json index de2ebc51e0..b794df814f 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2457,6 +2457,10 @@ "label": "Rows", "key": "rows" }, + { + "label": "Extra Info", + "key": "info" + }, { "label": "Rows Length", "key": "rowsLength" @@ -3178,6 +3182,10 @@ "label": "Rows", "key": "rows" }, + { + "label": "Extra Info", + "key": "info" + }, { "label": "Rows Length", "key": "rowsLength" diff --git a/packages/client/src/api/datasources.js b/packages/client/src/api/datasources.js index 981d8301ca..a1d6b8dfd8 100644 --- a/packages/client/src/api/datasources.js +++ b/packages/client/src/api/datasources.js @@ -15,7 +15,8 @@ export const fetchDatasource = async dataSource => { // Fetch all rows in data source const { type, tableId, fieldName } = dataSource - let rows = [] + let rows = [], + info = {} if (type === "table") { rows = await fetchTableData(tableId) } else if (type === "view") { @@ -28,7 +29,12 @@ export const fetchDatasource = async dataSource => { parameters[param.name] = param.default } } - rows = await executeQuery({ queryId: dataSource._id, parameters }) + const { data, ...rest } = await executeQuery({ + queryId: dataSource._id, + parameters, + }) + info = rest + rows = data } else if (type === FieldTypes.LINK) { rows = await fetchRelationshipData({ rowId: dataSource.rowId, @@ -38,7 +44,7 @@ export const fetchDatasource = async dataSource => { } // Enrich the result is always an array - return Array.isArray(rows) ? rows : [] + return { rows: Array.isArray(rows) ? rows : [], info } } /** diff --git a/packages/client/src/components/app/DataProvider.svelte b/packages/client/src/components/app/DataProvider.svelte index e9d306cc3b..9c79ef8e9a 100644 --- a/packages/client/src/components/app/DataProvider.svelte +++ b/packages/client/src/components/app/DataProvider.svelte @@ -30,6 +30,7 @@ // Provider state let rows = [] let allRows = [] + let info = {} let schema = {} let bookmarks = [null] let pageNumber = 0 @@ -120,6 +121,7 @@ // Build our data context $: dataContext = { rows, + info, schema, rowsLength: rows.length, @@ -206,7 +208,9 @@ } else { // For other data sources like queries or views, fetch all rows from the // server - allRows = await API.fetchDatasource(dataSource) + const data = await API.fetchDatasource(dataSource) + allRows = data.rows + info = data.info } loading = false loaded = true diff --git a/packages/server/src/api/controllers/query/index.js b/packages/server/src/api/controllers/query/index.js index e12b0039a5..ff9d814bc9 100644 --- a/packages/server/src/api/controllers/query/index.js +++ b/packages/server/src/api/controllers/query/index.js @@ -182,13 +182,13 @@ exports.execute = async function (ctx) { // call the relevant CRUD method on the integration class try { - const { rows } = await Runner.run({ + const { rows, extra } = await Runner.run({ datasource, queryVerb: query.queryVerb, query: enrichedQuery, transformer: query.transformer, }) - ctx.body = rows + ctx.body = { data: rows, ...extra } } catch (err) { ctx.throw(400, err) } diff --git a/packages/server/src/automations/steps/executeQuery.js b/packages/server/src/automations/steps/executeQuery.js index 43534209cc..0fa8661561 100644 --- a/packages/server/src/automations/steps/executeQuery.js +++ b/packages/server/src/automations/steps/executeQuery.js @@ -35,6 +35,11 @@ exports.definition = { type: "object", description: "The response from the datasource execution", }, + info: { + type: "object", + description: + "Some query types may return extra data, like headers from a REST query", + }, success: { type: "boolean", description: "Whether the action was successful", @@ -68,13 +73,16 @@ exports.run = async function ({ inputs, appId, emitter }) { try { await queryController.execute(ctx) + const { data, ...rest } = ctx.body return { - response: ctx.body, + response: data, + info: rest, success: true, } } catch (err) { return { success: false, + info: {}, response: automationUtils.getError(err), } }