mirror of
https://github.com/apache/superset.git
synced 2024-09-17 11:09:47 -04:00
fix(CRUD/listviews): Errors with rison and search strings using special characters (#18056)
* fix errors with rison and useQueryParams * add test for encode/decode * add rison link and make test case more readable Co-authored-by: Corbin Robb <corbin@Corbins-MacBook-Pro.local>
This commit is contained in:
parent
97a879ef27
commit
c8df84985c
@ -51,8 +51,7 @@ export default function SearchFilter({
|
|||||||
const [value, setValue] = useState(initialValue || '');
|
const [value, setValue] = useState(initialValue || '');
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (value) {
|
if (value) {
|
||||||
// encode plus signs to prevent them from being converted into a space
|
onSubmit(value.trim());
|
||||||
onSubmit(value.trim().replace(/\+/g, '%2B'));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -46,10 +46,17 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
// Define custom RisonParam for proper encoding/decoding; note that
|
// 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<string, any> = {
|
const RisonParam: QueryParamConfig<string, any> = {
|
||||||
encode: (data?: any | null) =>
|
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[]) =>
|
decode: (dataStr?: string | string[]) =>
|
||||||
dataStr === undefined || Array.isArray(dataStr)
|
dataStr === undefined || Array.isArray(dataStr)
|
||||||
? undefined
|
? undefined
|
||||||
|
@ -75,7 +75,7 @@ const fetchTimeRange = async (
|
|||||||
timeRange: string,
|
timeRange: string,
|
||||||
endpoints?: TimeRangeEndpoints,
|
endpoints?: TimeRangeEndpoints,
|
||||||
) => {
|
) => {
|
||||||
const query = rison.encode(timeRange);
|
const query = rison.encode_uri(timeRange);
|
||||||
const endpoint = `/api/v1/time_range/?q=${query}`;
|
const endpoint = `/api/v1/time_range/?q=${query}`;
|
||||||
try {
|
try {
|
||||||
const response = await SupersetClient.get({ endpoint });
|
const response = await SupersetClient.get({ endpoint });
|
||||||
|
@ -675,7 +675,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
|||||||
const loadDashboardOptions = useMemo(
|
const loadDashboardOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
(input = '', page: number, pageSize: number) => {
|
(input = '', page: number, pageSize: number) => {
|
||||||
const query = rison.encode({
|
const query = rison.encode_uri({
|
||||||
filter: input,
|
filter: input,
|
||||||
page,
|
page,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
@ -749,7 +749,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
|||||||
const loadChartOptions = useMemo(
|
const loadChartOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
(input = '', page: number, pageSize: number) => {
|
(input = '', page: number, pageSize: number) => {
|
||||||
const query = rison.encode({
|
const query = rison.encode_uri({
|
||||||
filter: input,
|
filter: input,
|
||||||
page,
|
page,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
|
@ -147,7 +147,7 @@ export function useListViewResource<D extends object = any>(
|
|||||||
: value,
|
: value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const queryParams = rison.encode({
|
const queryParams = rison.encode_uri({
|
||||||
order_column: sortBy[0].id,
|
order_column: sortBy[0].id,
|
||||||
order_direction: sortBy[0].desc ? 'desc' : 'asc',
|
order_direction: sortBy[0].desc ? 'desc' : 'asc',
|
||||||
page: pageIndex,
|
page: pageIndex,
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import rison from 'rison';
|
||||||
import {
|
import {
|
||||||
isNeedsPassword,
|
isNeedsPassword,
|
||||||
isAlreadyExists,
|
isAlreadyExists,
|
||||||
@ -171,3 +172,17 @@ test('does not ask for password when the import type is wrong', () => {
|
|||||||
};
|
};
|
||||||
expect(hasTerminalValidation(error.errors)).toBe(true);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -33,6 +33,35 @@ import { FetchDataConfig } from 'src/components/ListView';
|
|||||||
import SupersetText from 'src/utils/textUtils';
|
import SupersetText from 'src/utils/textUtils';
|
||||||
import { Dashboard, Filters } from './types';
|
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 =
|
const createFetchResourceMethod =
|
||||||
(method: string) =>
|
(method: string) =>
|
||||||
(
|
(
|
||||||
@ -43,7 +72,7 @@ const createFetchResourceMethod =
|
|||||||
) =>
|
) =>
|
||||||
async (filterValue = '', page: number, pageSize: number) => {
|
async (filterValue = '', page: number, pageSize: number) => {
|
||||||
const resourceEndpoint = `/api/v1/${resource}/${method}/${relation}`;
|
const resourceEndpoint = `/api/v1/${resource}/${method}/${relation}`;
|
||||||
const queryParams = rison.encode({
|
const queryParams = rison.encode_uri({
|
||||||
filter: filterValue,
|
filter: filterValue,
|
||||||
page,
|
page,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
|
Loading…
Reference in New Issue
Block a user