Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/backend-core/src/utils/outboundFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,9 @@ export async function fetchWithBlacklist<
nextRequest = nextRequestForRedirect(nextRequest, response.status)
if (shouldStripSensitiveHeadersForRedirect(nextUrl, redirectUrl)) {
if (rejectCrossOriginRedirects) {
throw new Error("Redirect to a different origin is not permitted.")
throw new Error(
"This API URL redirects to a different hostname, port, or protocol. Enter the final URL directly (www.example.com instead of example.com)."
)
}
nextRequest = stripSensitiveHeadersForRedirect(nextRequest)
}
Expand Down
4 changes: 3 additions & 1 deletion packages/backend-core/src/utils/tests/outboundFetch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,9 @@ describe("outboundFetch", () => {
fetchWithBlacklist("https://budibase.com/resource", undefined, {
rejectCrossOriginRedirects: true,
})
).rejects.toThrow("Redirect to a different origin is not permitted.")
).rejects.toThrow(
"This API URL redirects to a different hostname, port, or protocol. Enter the final URL directly (www.example.com instead of example.com)."
)
expect(fetchMock).toHaveBeenCalledTimes(1)
})

Expand Down
1 change: 1 addition & 0 deletions packages/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"@testing-library/svelte": "5.2.8",
"@types/shortid": "2.2.0",
"identity-obj-proxy": "3.0.0",
"jsdom": "^21.1.1",
"resize-observer-polyfill": "1.5.1",
"svelte-jester": "1.3.2",
"vite": "8.0.16",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ $isActive

export let datasource
export let query
export let indentLevel = 1

const favourites = workspaceFavouriteStore.lookup

Expand Down Expand Up @@ -94,7 +95,7 @@ $: goto = $gotoStore

<NavItem
on:contextmenu={openContextMenu}
indentLevel={1}
{indentLevel}
{icon}
iconText={iconVerb}
{iconColor}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script>
import { Layout } from "@supertoolmake/bbui"
import { datasources, queries } from "@/stores/builder"
import { WORKSPACE_API_CONFIG_ID } from "@supertoolmake/types"
import QueryNavItem from "./QueryNavItem.svelte"

export let searchTerm

const datasource = {
_id: WORKSPACE_API_CONFIG_ID,
source: "REST",
name: "APIs",
}

$: restDatasourceIds = new Set(
($datasources.list || []).filter((source) => source.source === "REST").map((source) => source._id)
)
$: restQueries = $queries.list.filter(
(query) =>
query.datasourceId === WORKSPACE_API_CONFIG_ID || restDatasourceIds.has(query.datasourceId)
)
$: filteredQueries = restQueries.filter(
(query) => !searchTerm || query.name?.toLowerCase().includes(searchTerm.toLowerCase())
)
</script>

<div class="queries">
{#each filteredQueries as query}
<QueryNavItem {datasource} {query} indentLevel={0} />
{/each}
</div>

{#if searchTerm && filteredQueries.length === 0}
<Layout paddingY="none" paddingX="L">
<div class="no-results">There aren't any APIs matching that name</div>
</Layout>
{/if}

<style>
.queries {
display: flex;
flex-direction: column;
}

.no-results {
color: var(--spectrum-global-color-gray-600);
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { goto, isActive, params } from "@roxi/routify"
import { getContext } from "svelte"
import { API } from "@/api"
import { IntegrationTypes } from "@/constants/backend"
import { WORKSPACE_API_CONFIG_ID } from "@supertoolmake/types"
import { bb } from "@/stores/bb"
import {
appStore,
Expand Down Expand Up @@ -93,7 +94,8 @@ const queryCommands = (queries) => {
)
return queries.map((query) => {
const datasource = datasourceLookup.get(query.datasourceId)
const isRest = datasource?.source === IntegrationTypes.REST
const isRest =
datasource?.source === IntegrationTypes.REST || query.datasourceId === WORKSPACE_API_CONFIG_ID
return {
type: "Query",
name: query.name,
Expand Down
80 changes: 67 additions & 13 deletions packages/builder/src/components/integration/RestQueryViewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import { capitalise } from "@/helpers"
import restUtils, { customQueryIconColor } from "@/helpers/data/utils"
import { getErrorMessage } from "@/helpers/errors"
import { datasources, integrations, queries } from "@/stores/builder"
import { workspaceApis } from "@/stores/builder"
import { WORKSPACE_API_CONFIG_ID } from "@supertoolmake/types"
import ConnectedQueryScreens from "./ConnectedQueryScreens.svelte"
import DynamicVariableModal from "./DynamicVariableModal.svelte"
import {
Expand Down Expand Up @@ -80,7 +82,7 @@ let lastSyncedQueryName
function getSelectedQuery() {
return cloneDeep(
$queries.list.find((q) => q._id === queryId) || {
datasourceId: $params.datasourceId,
datasourceId: $params.datasourceId || WORKSPACE_API_CONFIG_ID,
parameters: [],
fields: {
// only init the objects, everything else is optional strings
Expand All @@ -92,6 +94,34 @@ function getSelectedQuery() {
)
}

const mergeSharedConfig = (legacyDatasource, sharedDatasource) => {
if (!legacyDatasource) {
return sharedDatasource
}
const { url: _sharedUrl, ...sharedConfig } = sharedDatasource.config || {}
const datasourceConfig = legacyDatasource.config || {}
const datasourceAuthIds = new Set((datasourceConfig.authConfigs || []).map((auth) => auth._id))
return {
...legacyDatasource,
config: {
...sharedConfig,
...datasourceConfig,
defaultHeaders: {
...(sharedConfig.defaultHeaders || {}),
...(datasourceConfig.defaultHeaders || {}),
},
staticVariables: {
...(sharedConfig.staticVariables || {}),
...(datasourceConfig.staticVariables || {}),
},
authConfigs: [
...(sharedConfig.authConfigs || []).filter((auth) => !datasourceAuthIds.has(auth._id)),
...(datasourceConfig.authConfigs || []),
],
},
}
}

const cleanUrl = (inputUrl) =>
url
?.replace(/(https)|(http)|[{}:]/g, "")
Expand Down Expand Up @@ -156,11 +186,18 @@ async function saveQuery(redirectIfNew = true) {
const { _id } = await queries.save(toSave.datasourceId, toSave)
saveId = _id
if (dynamicVariables) {
datasource.config.dynamicVariables = rebuildVariables(saveId)
datasource = await datasources.save({
integration: integrationInfo,
datasource,
})
if (toSave.datasourceId === WORKSPACE_API_CONFIG_ID) {
await workspaceApis.save({
...$workspaceApis.datasource.config,
dynamicVariables: rebuildVariables(saveId, $workspaceApis.datasource),
})
} else {
datasource.config.dynamicVariables = rebuildVariables(saveId)
datasource = await datasources.save({
integration: integrationInfo,
datasource,
})
}
}

notifications.success(`Request saved successfully`)
Expand Down Expand Up @@ -266,7 +303,7 @@ const getDynamicVariables = (datasource, queryId, matchFn) => {
}

// convert dynamic variables object back to a list, enrich with query id
const rebuildVariables = (queryId) => {
const rebuildVariables = (queryId, sourceDatasource = datasource) => {
let variables = []
if (dynamicVariables) {
variables = Object.entries(dynamicVariables).map((entry) => {
Expand All @@ -278,7 +315,7 @@ const rebuildVariables = (queryId) => {
})
}

let existing = datasource?.config?.dynamicVariables || []
let existing = sourceDatasource?.config?.dynamicVariables || []
// remove existing query variables (for changes and deletions)
existing = existing.filter((variable) => variable.queryId !== queryId)
// re-add the new query variables
Expand Down Expand Up @@ -317,7 +354,13 @@ const urlChanged = (evt) => {
}
}

$: staticVariables = datasource?.config?.staticVariables || {}
$: staticVariables =
mergeSharedConfig(
query?.datasourceId === WORKSPACE_API_CONFIG_ID
? undefined
: $datasources.list.find((ds) => ds._id === query?.datasourceId),
$workspaceApis.datasource
)?.config?.staticVariables || {}
$: if (query?._id && query._id !== lastSyncedQueryId) {
lastSyncedQueryId = query._id
lastSyncedQueryName = query.name
Expand Down Expand Up @@ -377,7 +420,14 @@ $: url = buildUrl(query?.fields?.path, breakQs)
$: checkQueryName(url)
$: responseSuccess = response?.info?.code >= 200 && response?.info?.code < 400
$: isGet = query?.queryVerb === "read"
$: authConfigs = buildAuthConfigs(datasource)
$: authConfigs = buildAuthConfigs(
mergeSharedConfig(
query?.datasourceId === WORKSPACE_API_CONFIG_ID
? undefined
: $datasources.list.find((ds) => ds._id === query?.datasourceId),
$workspaceApis.datasource
)
)
$: schemaReadOnly = !responseSuccess
$: variablesReadOnly = !responseSuccess
$: showVariablesTab = shouldShowVariables(dynamicVariables, variablesReadOnly)
Expand Down Expand Up @@ -434,12 +484,17 @@ onMount(async () => {

try {
// Clear any unsaved changes to the datasource
await datasources.init()
await Promise.all([datasources.init(), workspaceApis.fetch()])
} catch {
notifications.error("Error getting datasources")
}

datasource = $datasources.list.find((ds) => ds._id === query?.datasourceId)
datasource = mergeSharedConfig(
query?.datasourceId === WORKSPACE_API_CONFIG_ID
? undefined
: $datasources.list.find((ds) => ds._id === query?.datasourceId),
$workspaceApis.datasource
)
const datasourceUrl = datasource?.config.url
const qs = query?.fields.queryString
breakQs = restUtils.breakQueryString(encodeURI(qs ?? ""))
Expand Down Expand Up @@ -676,7 +731,6 @@ onMount(async () => {
bind:authConfigId={query.fields.authConfigId}
bind:authConfigType={query.fields.authConfigType}
{authConfigs}
datasourceId={datasource._id}
/>
</div>
</Tabs>
Expand Down
14 changes: 3 additions & 11 deletions packages/builder/src/components/integration/rest/AuthPicker.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,17 @@ import {
PopoverAlignment,
} from "@supertoolmake/bbui"
import { RestAuthType } from "@supertoolmake/types"
import { goto as gotoStore } from "@roxi/routify"
import { onMount } from "svelte"
import DetailPopover from "@/components/common/DetailPopover.svelte"
import { bb } from "@/stores/bb"
import { appStore, oauth2 } from "@/stores/builder"
import { oauth2 } from "@/stores/builder"

function addBasicConfiguration() {
goto(`/builder/workspace/[application]/apis/datasource/[datasourceId]`, {
application: $appStore.appId,
datasourceId,
tab: "Authentication",
})
bb.settings("/apis/config")
}

function addOAuth2Configuration() {
bb.settings("/general/oauth2")
bb.settings("/apis/oauth2")
}

function selectConfiguration(id: string, type?: RestAuthType) {
Expand All @@ -38,14 +33,11 @@ function selectConfiguration(id: string, type?: RestAuthType) {
popover.hide()
}

$: goto = $gotoStore

type Config = { label: string; value: string }

export let authConfigId: string | undefined
export let authConfigType: RestAuthType | undefined
export let authConfigs: Config[]
export let datasourceId: string

let popover: DetailPopover
let allConfigs: Config[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
type UIWorkspaceApp,
type WorkspaceFavourite,
WorkspaceResource,
WORKSPACE_API_CONFIG_ID,
} from "@supertoolmake/types"
import { goto as gotoStore, isActive, url } from "@roxi/routify"
import BBLogo from "assets/BBLogo.svelte"
Expand Down Expand Up @@ -98,7 +99,8 @@ const generateResourceLookup = (allResourceStores: Readable<AllResourceStores>)
const isRestQuery =
favourite.resourceType === WorkspaceResource.QUERY &&
isQueryResource(resource) &&
datasourceMap[resource.datasourceId]?.source === IntegrationTypes.REST
(datasourceMap[resource.datasourceId]?.source === IntegrationTypes.REST ||
resource.datasourceId === WORKSPACE_API_CONFIG_ID)

const entry: UIFavouriteResource = {
name: resource.name,
Expand Down Expand Up @@ -178,7 +180,10 @@ const resourceLink = (favourite: WorkspaceFavourite): ResourceLinkResult | null
const datasourceMap = get(datasourceLookup) || {}
const query = queriesStore.list?.find((q) => q._id === id)
const datasource = query?.datasourceId ? datasourceMap[query.datasourceId] : undefined
const basePath = helpers.isSQL(datasource) ? "data" : "apis"
const basePath =
query?.datasourceId === WORKSPACE_API_CONFIG_ID || !helpers.isSQL(datasource)
? "apis"
: "data"
return {
path: `${appPrefix}${basePath}/query/[queryId]`,
params: { application: currentAppId, queryId: id },
Expand Down
Loading
Loading