diff --git a/superset-embedded-sdk/package-lock.json b/superset-embedded-sdk/package-lock.json index d525e16bf74b4..067da8b3857a0 100644 --- a/superset-embedded-sdk/package-lock.json +++ b/superset-embedded-sdk/package-lock.json @@ -1,13 +1,16 @@ { "name": "@superset-ui/embedded-sdk", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@superset-ui/embedded-sdk", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "license": "Apache-2.0", + "dependencies": { + "@superset-ui/switchboard": "^0.18.26-0" + }, "devDependencies": { "@babel/cli": "^7.16.8", "@babel/core": "^7.16.12", @@ -1670,6 +1673,11 @@ "dev": true, "optional": true }, + "node_modules/@superset-ui/switchboard": { + "version": "0.18.26-0", + "resolved": "https://registry.npmjs.org/@superset-ui/switchboard/-/switchboard-0.18.26-0.tgz", + "integrity": "sha512-MYvigrspA0EgNU6tA9UrsXcrUYid9YktsbIPx/D4Xd5cWWrJrJl303imQ/SIZbC25faJCd2gL30ORll60Yz3Ww==" + }, "node_modules/@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -2590,9 +2598,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", "dev": true, "funding": [ { @@ -2914,9 +2922,9 @@ } }, "node_modules/jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "version": "27.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.0.tgz", + "integrity": "sha512-8OEHiPNOPTfaWnJ2SUHM8fmgeGq37uuGsQBvGKQJl1f+6WIy6g7G3fE2ruI5294bUKUI9FaCWt5hDvO8HSwsSg==", "dev": true, "dependencies": { "@types/node": "*", @@ -5124,6 +5132,11 @@ "dev": true, "optional": true }, + "@superset-ui/switchboard": { + "version": "0.18.26-0", + "resolved": "https://registry.npmjs.org/@superset-ui/switchboard/-/switchboard-0.18.26-0.tgz", + "integrity": "sha512-MYvigrspA0EgNU6tA9UrsXcrUYid9YktsbIPx/D4Xd5cWWrJrJl303imQ/SIZbC25faJCd2gL30ORll60Yz3Ww==" + }, "@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -5860,9 +5873,9 @@ } }, "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", "dev": true }, "fs-readdir-recursive": { @@ -6085,9 +6098,9 @@ "dev": true }, "jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "version": "27.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.0.tgz", + "integrity": "sha512-8OEHiPNOPTfaWnJ2SUHM8fmgeGq37uuGsQBvGKQJl1f+6WIy6g7G3fE2ruI5294bUKUI9FaCWt5hDvO8HSwsSg==", "dev": true, "requires": { "@types/node": "*", diff --git a/superset-embedded-sdk/package.json b/superset-embedded-sdk/package.json index a41c91ad344ea..fa426bb9efd4f 100644 --- a/superset-embedded-sdk/package.json +++ b/superset-embedded-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@superset-ui/embedded-sdk", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.3", "description": "SDK for embedding resources from Superset into your own application", "access": "public", "keywords": [ @@ -32,6 +32,9 @@ "last 3 safari versions", "last 3 edge versions" ], + "dependencies": { + "@superset-ui/switchboard": "^0.18.26-0" + }, "devDependencies": { "@babel/cli": "^7.16.8", "@babel/core": "^7.16.12", diff --git a/superset-embedded-sdk/src/index.ts b/superset-embedded-sdk/src/index.ts index d29d701d4155a..a1cd1007c2107 100644 --- a/superset-embedded-sdk/src/index.ts +++ b/superset-embedded-sdk/src/index.ts @@ -20,7 +20,7 @@ import { IFRAME_COMMS_MESSAGE_TYPE } from './const'; // We can swap this out for the actual switchboard package once it gets published -import { Switchboard } from '../../superset-frontend/packages/superset-ui-switchboard/src/switchboard'; +import { Switchboard } from '@superset-ui/switchboard'; /** * The function to fetch a guest token from your Host App's backend server. diff --git a/superset-embedded-sdk/tsconfig.json b/superset-embedded-sdk/tsconfig.json index 072b121625a8c..a9ee59739f47b 100644 --- a/superset-embedded-sdk/tsconfig.json +++ b/superset-embedded-sdk/tsconfig.json @@ -3,6 +3,8 @@ // syntax rules "strict": true, + "moduleResolution": "node", + // environment "target": "es6", "lib": ["DOM", "ESNext"], diff --git a/superset-frontend/packages/superset-ui-switchboard/package-lock.json b/superset-frontend/packages/superset-ui-switchboard/package-lock.json index 5c3d95de78fbd..0d56931468339 100644 --- a/superset-frontend/packages/superset-ui-switchboard/package-lock.json +++ b/superset-frontend/packages/superset-ui-switchboard/package-lock.json @@ -1,12 +1,12 @@ { "name": "@superset-ui/switchboard", - "version": "0.18.25", + "version": "0.18.26-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@superset-ui/switchboard", - "version": "0.18.25", + "version": "0.18.26-0", "license": "Apache-2.0" } } diff --git a/superset-frontend/packages/superset-ui-switchboard/package.json b/superset-frontend/packages/superset-ui-switchboard/package.json index e224640a55cde..f7e6c69a1b508 100644 --- a/superset-frontend/packages/superset-ui-switchboard/package.json +++ b/superset-frontend/packages/superset-ui-switchboard/package.json @@ -1,6 +1,6 @@ { "name": "@superset-ui/switchboard", - "version": "0.18.25", + "version": "0.18.26-0", "description": "Switchboard is a library to make it easier to communicate across browser windows using the MessageChannel API", "sideEffects": false, "main": "lib/index.js", diff --git a/superset-frontend/src/components/ListView/Filters/Search.tsx b/superset-frontend/src/components/ListView/Filters/Search.tsx index 482fac17d4b7a..fd61cba5cc79f 100644 --- a/superset-frontend/src/components/ListView/Filters/Search.tsx +++ b/superset-frontend/src/components/ListView/Filters/Search.tsx @@ -51,8 +51,7 @@ export default function SearchFilter({ const [value, setValue] = useState(initialValue || ''); const handleSubmit = () => { if (value) { - // encode plus signs to prevent them from being converted into a space - onSubmit(value.trim().replace(/\+/g, '%2B')); + onSubmit(value.trim()); } }; const handleChange = (e: React.ChangeEvent) => { diff --git a/superset-frontend/src/components/ListView/utils.ts b/superset-frontend/src/components/ListView/utils.ts index 3ce370d054867..346bde0982fc3 100644 --- a/superset-frontend/src/components/ListView/utils.ts +++ b/superset-frontend/src/components/ListView/utils.ts @@ -46,10 +46,17 @@ import { } from './types'; // Define custom RisonParam for proper encoding/decoding; note that -// plus symbols should be encoded to avoid being converted into a space +// %, &, +, and # must be encoded to avoid breaking the url const RisonParam: QueryParamConfig = { encode: (data?: any | null) => - data === undefined ? undefined : rison.encode(data).replace(/\+/g, '%2B'), + data === undefined + ? undefined + : rison + .encode(data) + .replace(/%/g, '%25') + .replace(/&/g, '%26') + .replace(/\+/g, '%2B') + .replace(/#/g, '%23'), decode: (dataStr?: string | string[]) => dataStr === undefined || Array.isArray(dataStr) ? undefined diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx index e5324a926bdd3..80f287295989f 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx @@ -75,7 +75,7 @@ const fetchTimeRange = async ( timeRange: string, endpoints?: TimeRangeEndpoints, ) => { - const query = rison.encode(timeRange); + const query = rison.encode_uri(timeRange); const endpoint = `/api/v1/time_range/?q=${query}`; try { const response = await SupersetClient.get({ endpoint }); diff --git a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx index 416f47a4ab9f6..2934c3cd80ad0 100644 --- a/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx +++ b/superset-frontend/src/views/CRUD/alert/AlertReportModal.tsx @@ -675,7 +675,7 @@ const AlertReportModal: FunctionComponent = ({ const loadDashboardOptions = useMemo( () => (input = '', page: number, pageSize: number) => { - const query = rison.encode({ + const query = rison.encode_uri({ filter: input, page, page_size: pageSize, @@ -749,7 +749,7 @@ const AlertReportModal: FunctionComponent = ({ const loadChartOptions = useMemo( () => (input = '', page: number, pageSize: number) => { - const query = rison.encode({ + const query = rison.encode_uri({ filter: input, page, page_size: pageSize, diff --git a/superset-frontend/src/views/CRUD/hooks.ts b/superset-frontend/src/views/CRUD/hooks.ts index 2c9c2f9930e05..ae4b041f66878 100644 --- a/superset-frontend/src/views/CRUD/hooks.ts +++ b/superset-frontend/src/views/CRUD/hooks.ts @@ -147,7 +147,7 @@ export function useListViewResource( : value, })); - const queryParams = rison.encode({ + const queryParams = rison.encode_uri({ order_column: sortBy[0].id, order_direction: sortBy[0].desc ? 'desc' : 'asc', page: pageIndex, diff --git a/superset-frontend/src/views/CRUD/utils.test.tsx b/superset-frontend/src/views/CRUD/utils.test.tsx index 2b95319d85d5b..ce7139e82a99a 100644 --- a/superset-frontend/src/views/CRUD/utils.test.tsx +++ b/superset-frontend/src/views/CRUD/utils.test.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import rison from 'rison'; import { isNeedsPassword, isAlreadyExists, @@ -171,3 +172,17 @@ test('does not ask for password when the import type is wrong', () => { }; expect(hasTerminalValidation(error.errors)).toBe(true); }); + +test('successfully modified rison to encode correctly', () => { + const problemCharacters = '& # ? ^ { } [ ] | " = + `'; + + problemCharacters.split(' ').forEach(char => { + const testObject = { test: char }; + + const actualEncoding = rison.encode(testObject); + const expectedEncoding = `(test:'${char}')`; // Ex: (test:'&') + + expect(actualEncoding).toEqual(expectedEncoding); + expect(rison.decode(actualEncoding)).toEqual(testObject); + }); +}); diff --git a/superset-frontend/src/views/CRUD/utils.tsx b/superset-frontend/src/views/CRUD/utils.tsx index 64a92e08c63de..174e1aa493108 100644 --- a/superset-frontend/src/views/CRUD/utils.tsx +++ b/superset-frontend/src/views/CRUD/utils.tsx @@ -33,6 +33,35 @@ import { FetchDataConfig } from 'src/components/ListView'; import SupersetText from 'src/utils/textUtils'; import { Dashboard, Filters } from './types'; +// Modifies the rison encoding slightly to match the backend's rison encoding/decoding. Applies globally. +// Code pulled from rison.js (https://github.com/Nanonid/rison), rison is licensed under the MIT license. +(() => { + const risonRef: { + not_idchar: string; + not_idstart: string; + id_ok: RegExp; + next_id: RegExp; + } = rison as any; + + const l = []; + for (let hi = 0; hi < 16; hi += 1) { + for (let lo = 0; lo < 16; lo += 1) { + if (hi + lo === 0) continue; + const c = String.fromCharCode(hi * 16 + lo); + if (!/\w|[-_./~]/.test(c)) + l.push(`\\u00${hi.toString(16)}${lo.toString(16)}`); + } + } + + risonRef.not_idchar = l.join(''); + risonRef.not_idstart = '-0123456789'; + + const idrx = `[^${risonRef.not_idstart}${risonRef.not_idchar}][^${risonRef.not_idchar}]*`; + + risonRef.id_ok = new RegExp(`^${idrx}$`); + risonRef.next_id = new RegExp(idrx, 'g'); +})(); + const createFetchResourceMethod = (method: string) => ( @@ -43,7 +72,7 @@ const createFetchResourceMethod = ) => async (filterValue = '', page: number, pageSize: number) => { const resourceEndpoint = `/api/v1/${resource}/${method}/${relation}`; - const queryParams = rison.encode({ + const queryParams = rison.encode_uri({ filter: filterValue, page, page_size: pageSize,