@grafana/data: Add serializeParams (#78468)

* Move serializeParams to @grafana/data

* Update comment

* Update solution in Tempo

* Fix type assertions

* Use toUrlParams in serializeParams

* Update data sources

* Update

* Update packages/grafana-data/src/utils/url.ts
This commit is contained in:
Ivana Huckova 2023-11-22 13:15:29 +01:00 committed by GitHub
parent 9a3b2937aa
commit 02090f71d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 70 additions and 20 deletions

View File

@ -6486,11 +6486,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Do not use any type assertions.", "6"],
[0, 0, 0, "Do not use any type assertions.", "7"],
[0, 0, 0, "Do not use any type assertions.", "8"],
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
[0, 0, 0, "Unexpected any. Specify a different type.", "11"]
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
[0, 0, 0, "Unexpected any. Specify a different type.", "9"]
],
"public/app/plugins/datasource/tempo/language_provider.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]

View File

@ -33,6 +33,36 @@ describe('toUrlParams', () => {
});
expect(url).toBe('datasource=testDs%5B%21%27%28%29%2A%5D');
});
it('should encode object properties as url parameters', () => {
const params = urlUtil.serializeParams({
server: 'backend-01',
hasSpace: 'has space',
many: ['1', '2', '3'],
true: true,
number: 20,
isNull: null,
isUndefined: undefined,
oneMore: false,
});
expect(params).toBe(
'server=backend-01&hasSpace=has%20space&many=1&many=2&many=3&true&number=20&isNull=&isUndefined=&oneMore=false'
);
});
it('should not encode special character the same way as angular js', () => {
const params = urlUtil.serializeParams({
server: ':@',
});
expect(params).not.toBe('server=:@');
});
it('should keep booleans', () => {
const url = urlUtil.serializeParams({
bool1: true,
bool2: false,
});
expect(url).toBe('bool1&bool2=false');
});
});
describe('parseKeyValue', () => {

View File

@ -27,7 +27,7 @@ function renderUrl(path: string, query: UrlQueryMap | undefined): string {
return path;
}
function encodeURIComponentAsAngularJS(val: string, pctEncodeSpaces?: boolean) {
function encodeURIComponentAsAngularJS(val: EncodeURIComponentParams, pctEncodeSpaces?: boolean) {
return encodeURIComponent(val)
.replace(/%40/gi, '@')
.replace(/%3A/gi, ':')
@ -40,10 +40,20 @@ function encodeURIComponentAsAngularJS(val: string, pctEncodeSpaces?: boolean) {
});
}
function toUrlParams(a: any) {
type EncodeURIComponentParams = Parameters<typeof encodeURIComponent>[0];
/**
* Encodes URL parameters in the style of AngularJS.
* Use `serializeParams` to encode parameters using `encodeURIComponent` instead.
*/
function toUrlParams(a: any, encodeAsAngularJS = true) {
const s: any[] = [];
const rbracket = /\[\]$/;
const encodingFunction = encodeAsAngularJS
? (value: EncodeURIComponentParams, pctEncodeSpaces?: boolean) =>
encodeURIComponentAsAngularJS(value, pctEncodeSpaces)
: (value: EncodeURIComponentParams, _: boolean) => encodeURIComponent(value);
const isArray = (obj: any) => {
return Object.prototype.toString.call(obj) === '[object Array]';
};
@ -51,10 +61,10 @@ function toUrlParams(a: any) {
const add = (k: string, v: any) => {
v = typeof v === 'function' ? v() : v === null ? '' : v === undefined ? '' : v;
if (typeof v !== 'boolean') {
s[s.length] = encodeURIComponentAsAngularJS(k, true) + '=' + encodeURIComponentAsAngularJS(v, true);
s[s.length] = encodingFunction(k, true) + '=' + encodingFunction(v, true);
} else {
const valueQueryPart = v ? '' : '=' + encodeURIComponentAsAngularJS('false', true);
s[s.length] = encodeURIComponentAsAngularJS(k, true) + valueQueryPart;
const valueQueryPart = v ? '' : '=' + encodingFunction('false', true);
s[s.length] = encodingFunction(k, true) + valueQueryPart;
}
};
@ -92,6 +102,16 @@ function toUrlParams(a: any) {
return buildParams('', a).join('&');
}
/**
* Converts params into a URL-encoded query string.
*
* @param params data to serialize
* @returns A URL-encoded string representing the provided data.
*/
function serializeParams(params: unknown): string {
return toUrlParams(params, false);
}
function appendQueryToUrl(url: string, stringToAppend: string) {
if (stringToAppend !== undefined && stringToAppend !== null && stringToAppend !== '') {
const pos = url.indexOf('?');
@ -198,6 +218,7 @@ export const urlUtil = {
appendQueryToUrl,
getUrlSearchParams,
parseKeyValue,
serializeParams,
};
/**

View File

@ -130,7 +130,7 @@ export async function parseResponseBody<T>(
return textData as any;
}
export function serializeParams(data: Record<string, any>): string {
function serializeParams(data: Record<string, any>): string {
return Object.keys(data)
.map((key) => {
const value = data[key];

View File

@ -13,10 +13,10 @@ import {
FieldType,
MutableDataFrame,
ScopedVars,
urlUtil,
} from '@grafana/data';
import { BackendSrvRequest, getBackendSrv, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { NodeGraphOptions } from 'app/core/components/NodeGraphSettings';
import { serializeParams } from 'app/core/utils/fetch';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { SpanBarOptions } from 'app/features/explore/TraceView/components';
@ -235,7 +235,7 @@ export class JaegerDatasource extends DataSourceApi<JaegerQuery, JaegerJsonData>
data?: Record<string, unknown>,
options?: Partial<BackendSrvRequest>
): Observable<Record<string, any>> {
const params = data ? serializeParams(data) : '';
const params = data ? urlUtil.serializeParams(data) : '';
const url = `${this.instanceSettings.url}${apiUrl}${params.length ? `?${params}` : ''}`;
const req = {
...options,

View File

@ -36,6 +36,7 @@ import {
renderLegendFormat,
LegacyMetricFindQueryOptions,
AdHocVariableFilter,
urlUtil,
} from '@grafana/data';
import { Duration } from '@grafana/lezer-logql';
import { BackendSrvRequest, config, DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
@ -43,7 +44,6 @@ import { DataQuery } from '@grafana/schema';
import { convertToWebSocketUrl } from 'app/core/utils/explore';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { serializeParams } from '../../../core/utils/fetch';
import { queryLogsSample, queryLogsVolume } from '../../../features/logs/logsModel';
import { getLogLevelFromKey } from '../../../features/logs/utils';
import { replaceVariables, returnVariables } from '../prometheus/querybuilder/shared/parsingUtils';
@ -384,7 +384,7 @@ export class LokiDatasource
private createLiveTarget(target: LokiQuery, maxDataPoints: number): LokiLiveTarget {
const query = target.expr;
const baseUrl = this.instanceSettings.url;
const params = serializeParams({ query });
const params = urlUtil.serializeParams({ query });
return {
query,

View File

@ -19,6 +19,7 @@ import {
rangeUtil,
ScopedVars,
TestDataSourceResponse,
urlUtil,
} from '@grafana/data';
import {
BackendSrvRequest,
@ -32,7 +33,6 @@ import {
import { BarGaugeDisplayMode, TableCellDisplayMode, VariableFormatID } from '@grafana/schema';
import { NodeGraphOptions } from 'app/core/components/NodeGraphSettings';
import { TraceToLogsOptions } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
import { serializeParams } from 'app/core/utils/fetch';
import { SpanBarOptions } from 'app/features/explore/TraceView/components';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
@ -691,7 +691,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
data?: unknown,
options?: Partial<BackendSrvRequest>
): Observable<Record<string, any>> {
const params = data ? serializeParams(data) : '';
const params = data ? urlUtil.serializeParams(data) : '';
const url = `${this.instanceSettings.url}${apiUrl}${params.length ? `?${params}` : ''}`;
const req = { ...options, url };
@ -723,7 +723,9 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
if (query.queryType === 'nativeSearch') {
let result = [];
for (const key of ['serviceName', 'spanName', 'search', 'minDuration', 'maxDuration', 'limit']) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
if (query.hasOwnProperty(key) && query[key as keyof TempoQuery]) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
result.push(`${startCase(key)}: ${query[key as keyof TempoQuery]}`);
}
}

View File

@ -10,13 +10,12 @@ import {
FieldType,
createDataFrame,
ScopedVars,
urlUtil,
} from '@grafana/data';
import { BackendSrvRequest, FetchResponse, getBackendSrv, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { NodeGraphOptions } from 'app/core/components/NodeGraphSettings';
import { SpanBarOptions } from 'app/features/explore/TraceView/components';
import { serializeParams } from '../../../core/utils/fetch';
import { apiPrefix } from './constants';
import { ZipkinQuery, ZipkinSpan } from './types';
import { createGraphFrames } from './utils/graphTransform';
@ -104,7 +103,7 @@ export class ZipkinDatasource extends DataSourceApi<ZipkinQuery, ZipkinJsonData>
data?: any,
options?: Partial<BackendSrvRequest>
): Observable<FetchResponse<T>> {
const params = data ? serializeParams(data) : '';
const params = data ? urlUtil.serializeParams(data) : '';
const url = `${this.instanceSettings.url}${apiUrl}${params.length ? `?${params}` : ''}`;
const req = {
...options,