Skip to content

Commit b75d2b3

Browse files
authored
Merge pull request #7795 from Budibase/fix/7683
Fix for REST query string URL encoding
2 parents e4185e9 + 7736acd commit b75d2b3

File tree

15 files changed

+141
-100
lines changed

15 files changed

+141
-100
lines changed

packages/bbui/src/Tooltip/TooltipWrapper.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
display: flex;
4848
justify-content: center;
4949
top: 15px;
50-
z-index: 100;
50+
z-index: 200;
5151
width: 160px;
5252
}
5353
.icon {

packages/builder/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"dev:builder": "routify -c dev:vite",
1010
"dev:vite": "vite --host 0.0.0.0",
1111
"rollup": "rollup -c -w",
12+
"test": "jest",
1213
"cy:setup": "ts-node ./cypress/ts/setup.ts",
1314
"cy:setup:ci": "node ./cypress/setup.js",
1415
"cy:open": "cypress open",
@@ -36,7 +37,8 @@
3637
"components(.*)$": "<rootDir>/src/components$1",
3738
"builderStore(.*)$": "<rootDir>/src/builderStore$1",
3839
"stores(.*)$": "<rootDir>/src/stores$1",
39-
"analytics(.*)$": "<rootDir>/src/analytics$1"
40+
"analytics(.*)$": "<rootDir>/src/analytics$1",
41+
"constants/backend": "<rootDir>/src/constants/backend/index.js"
4042
},
4143
"moduleFileExtensions": [
4244
"js",

packages/builder/src/builderStore/dataBinding.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import {
99
import { store } from "builderStore"
1010
import {
1111
queries as queriesStores,
12-
tables as tablesStore,
1312
roles as rolesStore,
13+
tables as tablesStore,
1414
} from "stores/backend"
1515
import {
16-
makePropSafe,
17-
isJSBinding,
1816
decodeJSBinding,
1917
encodeJSBinding,
18+
isJSBinding,
19+
makePropSafe,
2020
} from "@budibase/string-templates"
2121
import { TableNames } from "../constants"
2222
import { JSONUtils } from "@budibase/frontend-core"
@@ -118,8 +118,7 @@ export const readableToRuntimeMap = (bindings, ctx) => {
118118
return {}
119119
}
120120
return Object.keys(ctx).reduce((acc, key) => {
121-
let parsedQuery = readableToRuntimeBinding(bindings, ctx[key])
122-
acc[key] = parsedQuery
121+
acc[key] = readableToRuntimeBinding(bindings, ctx[key])
123122
return acc
124123
}, {})
125124
}
@@ -132,8 +131,7 @@ export const runtimeToReadableMap = (bindings, ctx) => {
132131
return {}
133132
}
134133
return Object.keys(ctx).reduce((acc, key) => {
135-
let parsedQuery = runtimeToReadableBinding(bindings, ctx[key])
136-
acc[key] = parsedQuery
134+
acc[key] = runtimeToReadableBinding(bindings, ctx[key])
137135
return acc
138136
}, {})
139137
}

packages/builder/src/helpers/data/utils.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IntegrationTypes } from "constants/backend"
2+
import { findHBSBlocks } from "@budibase/string-templates"
23

34
export function schemaToFields(schema) {
45
const response = {}
@@ -31,7 +32,7 @@ export function breakQueryString(qs) {
3132
let paramObj = {}
3233
for (let param of params) {
3334
const split = param.split("=")
34-
paramObj[split[0]] = split.slice(1).join("=")
35+
paramObj[split[0]] = decodeURIComponent(split.slice(1).join("="))
3536
}
3637
return paramObj
3738
}
@@ -46,7 +47,19 @@ export function buildQueryString(obj) {
4647
if (str !== "") {
4748
str += "&"
4849
}
49-
str += `${key}=${encodeURIComponent(value || "")}`
50+
const bindings = findHBSBlocks(value)
51+
let count = 0
52+
const bindingMarkers = {}
53+
bindings.forEach(binding => {
54+
const marker = `BINDING...${count++}`
55+
value = value.replace(binding, marker)
56+
bindingMarkers[marker] = binding
57+
})
58+
let encoded = encodeURIComponent(value || "")
59+
Object.entries(bindingMarkers).forEach(([marker, binding]) => {
60+
encoded = encoded.replace(marker, binding)
61+
})
62+
str += `${key}=${encoded}`
5063
}
5164
}
5265
return str
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { breakQueryString, buildQueryString } from "../data/utils"
2+
3+
describe("check query string utils", () => {
4+
const obj1 = {
5+
key1: "123",
6+
key2: " ",
7+
key3: "333",
8+
}
9+
10+
const obj2 = {
11+
key1: "{{ binding.awd }}",
12+
key2: "{{ binding.sed }} ",
13+
}
14+
15+
it("should build a basic query string", () => {
16+
const queryString = buildQueryString(obj1)
17+
expect(queryString).toBe("key1=123&key2=%20%20%20&key3=333")
18+
})
19+
20+
it("should be able to break a basic query string", () => {
21+
const broken = breakQueryString("key1=123&key2=%20%20%20&key3=333")
22+
expect(broken.key1).toBe(obj1.key1)
23+
expect(broken.key2).toBe(obj1.key2)
24+
expect(broken.key3).toBe(obj1.key3)
25+
})
26+
27+
it("should be able to build with a binding", () => {
28+
const queryString = buildQueryString(obj2)
29+
expect(queryString).toBe("key1={{ binding.awd }}&key2={{ binding.sed }}%20%20")
30+
})
31+
32+
it("should be able to break with a binding", () => {
33+
const broken = breakQueryString("key1={{ binding.awd }}&key2={{ binding.sed }}%20%20")
34+
expect(broken.key1).toBe(obj2.key1)
35+
expect(broken.key2).toBe(obj2.key2)
36+
})
37+
})

packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,7 @@
708708
.url-block {
709709
display: flex;
710710
gap: var(--spacing-s);
711+
z-index: 200;
711712
}
712713
.verb {
713714
flex: 1;

packages/builder/src/stores/backend/queries.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { writable, get } from "svelte/store"
22
import { datasources, integrations, tables, views } from "./"
33
import { API } from "api"
4-
import { duplicateName } from "../../helpers/duplicate"
4+
import { duplicateName } from "helpers/duplicate"
55

66
const sortQueries = queryList => {
77
queryList.sort((q1, q2) => {

packages/builder/src/stores/backend/tables.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { get, writable } from "svelte/store"
22
import { datasources, queries, views } from "./"
33
import { cloneDeep } from "lodash/fp"
44
import { API } from "api"
5-
import { SWITCHABLE_TYPES } from "../../constants/backend"
5+
import { SWITCHABLE_TYPES } from "constants/backend"
66

77
export function createTablesStore() {
88
const store = writable({})
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { get } from 'svelte/store'
2-
import api from 'builderStore/api'
1+
import { get } from "svelte/store"
2+
import { API } from "api"
33

4-
jest.mock('builderStore/api');
4+
jest.mock("api")
55

6-
import { SOME_DATASOURCE, SAVE_DATASOURCE} from './fixtures/datasources'
6+
import { SOME_DATASOURCE, SAVE_DATASOURCE } from "./fixtures/datasources"
77

88
import { createDatasourcesStore } from "../datasources"
99
import { queries } from '../queries'
@@ -12,39 +12,39 @@ describe("Datasources Store", () => {
1212
let store = createDatasourcesStore()
1313

1414
beforeEach(async () => {
15-
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE]})
15+
API.getDatasources.mockReturnValue({ json: () => [SOME_DATASOURCE]})
1616
await store.init()
1717
})
1818

1919
it("Initialises correctly", async () => {
20-
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE]})
21-
20+
API.getDatasources.mockReturnValue({ json: () => [SOME_DATASOURCE]})
21+
2222
await store.init()
2323
expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null})
2424
})
2525

2626
it("fetches all the datasources and updates the store", async () => {
27-
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE] })
27+
API.getDatasources.mockReturnValue({ json: () => [SOME_DATASOURCE] })
2828

2929
await store.fetch()
30-
expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null })
30+
expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null })
3131
})
3232

3333
it("selects a datasource", async () => {
3434
store.select(SOME_DATASOURCE._id)
35-
36-
expect(get(store).select).toEqual(SOME_DATASOURCE._id)
35+
36+
expect(get(store).select).toEqual(SOME_DATASOURCE._id)
3737
})
3838

3939
it("resets the queries store when new datasource is selected", async () => {
40-
40+
4141
await store.select(SOME_DATASOURCE._id)
4242
const queriesValue = get(queries)
43-
expect(queriesValue.selected).toEqual(null)
43+
expect(queriesValue.selected).toEqual(null)
4444
})
4545

4646
it("saves the datasource, updates the store and returns status message", async () => {
47-
api.post.mockReturnValue({ status: 200, json: () => SAVE_DATASOURCE})
47+
API.createDatasource.mockReturnValue({ status: 200, json: () => SAVE_DATASOURCE})
4848

4949
await store.save({
5050
name: 'CoolDB',
@@ -56,13 +56,13 @@ describe("Datasources Store", () => {
5656
expect(get(store).list).toEqual(expect.arrayContaining([SAVE_DATASOURCE.datasource]))
5757
})
5858
it("deletes a datasource, updates the store and returns status message", async () => {
59-
api.get.mockReturnValue({ json: () => SOME_DATASOURCE})
59+
API.getDatasources.mockReturnValue({ json: () => SOME_DATASOURCE})
6060

6161
await store.fetch()
6262

63-
api.delete.mockReturnValue({status: 200, message: 'Datasource deleted.'})
63+
API.deleteDatasource.mockReturnValue({status: 200, message: 'Datasource deleted.'})
6464

6565
await store.delete(SOME_DATASOURCE[0])
66-
expect(get(store)).toEqual({ list: [], selected: null})
66+
expect(get(store)).toEqual({ list: [], selected: null})
6767
})
6868
})

packages/builder/src/stores/backend/tests/permissions.spec.js renamed to packages/builder/src/stores/backend/tests/permissions.spec.js.disabled

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import api from 'builderStore/api'
1+
import { API } from "api"
22

3-
jest.mock('builderStore/api');
3+
jest.mock("api")
44

55
const PERMISSIONS_FOR_RESOURCE = {
66
"write": "BASIC",
@@ -13,13 +13,12 @@ describe("Permissions Store", () => {
1313
const store = createPermissionStore()
1414

1515
it("fetches permissions for specific resource", async () => {
16-
api.get.mockReturnValueOnce({ json: () => PERMISSIONS_FOR_RESOURCE})
16+
API.getPermissionForResource.mockReturnValueOnce({ json: () => PERMISSIONS_FOR_RESOURCE})
1717

1818
const resourceId = "ta_013657543b4043b89dbb17e9d3a4723a"
1919

2020
const permissions = await store.forResource(resourceId)
2121

22-
expect(api.get).toBeCalledWith(`/api/permission/${resourceId}`)
2322
expect(permissions).toEqual(PERMISSIONS_FOR_RESOURCE)
2423
})
2524
})

0 commit comments

Comments
 (0)