mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
FieldOverride: Support data links via field overrides (#23590)
* Move xss and sanitize packages to grafana-data
* Move text, url and location utils to grafana-data
* Move grafana config types to grafana-data
* Move field display value proxy to grafana-data
* Fix
* Move data links built in vars to grafana-data
* Attach links supplier to when applying field overrides
* Prep tests
* Use links suppliers attached via field overrides
* locationUtil dependencies type
* Move sanitize-url declaration to grafana-data
* Revert "Move sanitize-url declaration to grafana-data"
This reverts commit 11db9f5e55
.
* Fix typo
* fix ts vol1
* Remove import from runtime in data.... Make TS happy at the same time ;)
* Lovely TS, please shut up
* Lovely TS, please shut up vol2
* fix tests
* Fixes
* minor refactor
* Attach get links to FieldDisplayValue for seamless usage
* Update packages/grafana-data/src/field/fieldOverrides.ts
* Make storybook build
This commit is contained in:
parent
e6c9b1305e
commit
d2a13c4715
@ -203,7 +203,6 @@
|
||||
"zone.js": "0.7.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "4.0.0",
|
||||
"@grafana/slate-react": "0.22.9-grafana",
|
||||
"@reduxjs/toolkit": "1.3.4",
|
||||
"@torkelo/react-select": "3.0.8",
|
||||
@ -274,8 +273,7 @@
|
||||
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
|
||||
"tinycolor2": "1.4.1",
|
||||
"tti-polyfill": "0.2.2",
|
||||
"whatwg-fetch": "3.0.0",
|
||||
"xss": "1.0.6"
|
||||
"whatwg-fetch": "3.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"caniuse-db": "1.0.30000772"
|
||||
|
@ -25,7 +25,9 @@
|
||||
"dependencies": {
|
||||
"apache-arrow": "0.16.0",
|
||||
"lodash": "4.17.15",
|
||||
"rxjs": "6.5.5"
|
||||
"rxjs": "6.5.5",
|
||||
"xss": "1.0.6",
|
||||
"@braintree/sanitize-url": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/eslint-config": "^1.0.0-rc1",
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
FieldConfigSource,
|
||||
FieldType,
|
||||
InterpolateFunction,
|
||||
LinkModel,
|
||||
} from '../types';
|
||||
import { DataFrameView } from '../dataframe/DataFrameView';
|
||||
import { GraphSeriesValue } from '../types/graph';
|
||||
@ -77,6 +78,7 @@ export interface FieldDisplay {
|
||||
view?: DataFrameView;
|
||||
colIndex?: number; // The field column index
|
||||
rowIndex?: number; // only filled in when the value is from a row (ie, not a reduction)
|
||||
getLinks?: () => LinkModel[];
|
||||
}
|
||||
|
||||
export interface GetFieldDisplayValuesOptions {
|
||||
@ -113,7 +115,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
|
||||
for (let i = 0; i < series.fields.length && !hitLimit; i++) {
|
||||
const field = series.fields[i];
|
||||
|
||||
const fieldLinksSupplier = field.getLinks;
|
||||
// Show all number fields
|
||||
if (field.type !== FieldType.number) {
|
||||
continue;
|
||||
@ -157,6 +159,12 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
view,
|
||||
colIndex: i,
|
||||
rowIndex: j,
|
||||
getLinks: fieldLinksSupplier
|
||||
? () =>
|
||||
fieldLinksSupplier({
|
||||
valueRowIndex: j,
|
||||
})
|
||||
: () => [],
|
||||
});
|
||||
|
||||
if (values.length >= limit) {
|
||||
@ -193,6 +201,12 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
sparkline,
|
||||
view,
|
||||
colIndex: i,
|
||||
getLinks: fieldLinksSupplier
|
||||
? () =>
|
||||
fieldLinksSupplier({
|
||||
calculatedValue: displayValue,
|
||||
})
|
||||
: () => [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ import {
|
||||
ScopedVars,
|
||||
ApplyFieldOverrideOptions,
|
||||
FieldConfigPropertyItem,
|
||||
LinkModel,
|
||||
InterpolateFunction,
|
||||
ValueLinkConfig,
|
||||
GrafanaTheme,
|
||||
} from '../types';
|
||||
import { fieldMatchers, ReducerID, reduceField } from '../transformations';
|
||||
import { FieldMatcher } from '../types/transformations';
|
||||
@ -19,9 +23,12 @@ import set from 'lodash/set';
|
||||
import unset from 'lodash/unset';
|
||||
import get from 'lodash/get';
|
||||
import { getDisplayProcessor } from './displayProcessor';
|
||||
import { guessFieldTypeForField } from '../dataframe';
|
||||
import { getTimeField, guessFieldTypeForField } from '../dataframe';
|
||||
import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry';
|
||||
import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
|
||||
import { DataLinkBuiltInVars, locationUtil } from '../utils';
|
||||
import { formattedValueToString } from '../valueFormats';
|
||||
import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy';
|
||||
|
||||
interface OverrideProps {
|
||||
match: FieldMatcher;
|
||||
@ -102,10 +109,10 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
|
||||
if (!fieldName) {
|
||||
fieldName = `Field[${fieldIndex}]`;
|
||||
}
|
||||
const fieldScopedVars = { ...scopedVars };
|
||||
fieldScopedVars['__field'] = { text: 'Field', value: { name: fieldName } };
|
||||
|
||||
scopedVars['__field'] = { text: 'Field', value: { name: fieldName } };
|
||||
|
||||
const config: FieldConfig = { ...field.config, scopedVars } || {};
|
||||
const config: FieldConfig = { ...field.config, scopedVars: fieldScopedVars } || {};
|
||||
const context = {
|
||||
field,
|
||||
data: options.data!,
|
||||
@ -182,6 +189,12 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
|
||||
theme: options.theme,
|
||||
timeZone: options.timeZone,
|
||||
});
|
||||
|
||||
// Attach data links supplier
|
||||
f.getLinks = getLinksSupplier(frame, f, fieldScopedVars, context.replaceVariables, {
|
||||
theme: options.theme,
|
||||
});
|
||||
|
||||
return f;
|
||||
});
|
||||
|
||||
@ -314,3 +327,84 @@ export function validateFieldConfig(config: FieldConfig) {
|
||||
config.min = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
const getLinksSupplier = (
|
||||
frame: DataFrame,
|
||||
field: Field,
|
||||
fieldScopedVars: ScopedVars,
|
||||
replaceVariables: InterpolateFunction,
|
||||
options: {
|
||||
theme: GrafanaTheme;
|
||||
}
|
||||
) => (config: ValueLinkConfig): Array<LinkModel<Field>> => {
|
||||
if (!field.config.links || field.config.links.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const timeRangeUrl = locationUtil.getTimeRangeUrlParams();
|
||||
const { timeField } = getTimeField(frame);
|
||||
|
||||
return field.config.links.map(link => {
|
||||
let href = link.url;
|
||||
let dataFrameVars = {};
|
||||
let valueVars = {};
|
||||
|
||||
const info: LinkModel<Field> = {
|
||||
href: locationUtil.assureBaseUrl(href.replace(/\n/g, '')),
|
||||
title: replaceVariables(link.title || ''),
|
||||
target: link.targetBlank ? '_blank' : '_self',
|
||||
origin: field,
|
||||
};
|
||||
|
||||
const variablesQuery = locationUtil.getVariablesUrlParams();
|
||||
|
||||
// We are not displaying reduction result
|
||||
if (config.valueRowIndex !== undefined && !isNaN(config.valueRowIndex)) {
|
||||
const fieldsProxy = getFieldDisplayValuesProxy(frame, config.valueRowIndex, options);
|
||||
valueVars = {
|
||||
raw: field.values.get(config.valueRowIndex),
|
||||
numeric: fieldsProxy[field.name].numeric,
|
||||
text: fieldsProxy[field.name].text,
|
||||
time: timeField ? timeField.values.get(config.valueRowIndex) : undefined,
|
||||
};
|
||||
dataFrameVars = {
|
||||
__data: {
|
||||
value: {
|
||||
name: frame.name,
|
||||
refId: frame.refId,
|
||||
fields: fieldsProxy,
|
||||
},
|
||||
text: 'Data',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
if (config.calculatedValue) {
|
||||
valueVars = {
|
||||
raw: config.calculatedValue.numeric,
|
||||
numeric: config.calculatedValue.numeric,
|
||||
text: formattedValueToString(config.calculatedValue),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
info.href = replaceVariables(info.href, {
|
||||
...fieldScopedVars,
|
||||
__value: {
|
||||
text: 'Value',
|
||||
value: valueVars,
|
||||
},
|
||||
...dataFrameVars,
|
||||
[DataLinkBuiltInVars.keepTime]: {
|
||||
text: timeRangeUrl,
|
||||
value: timeRangeUrl,
|
||||
},
|
||||
[DataLinkBuiltInVars.includeVars]: {
|
||||
text: variablesQuery,
|
||||
value: variablesQuery,
|
||||
},
|
||||
});
|
||||
|
||||
info.href = locationUtil.processUrl(info.href);
|
||||
|
||||
return info;
|
||||
});
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { toDataFrame, applyFieldOverrides, GrafanaTheme } from '@grafana/data';
|
||||
import { getFieldDisplayValuesProxy } from './fieldDisplayValuesProxy';
|
||||
import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy';
|
||||
import { applyFieldOverrides } from './fieldOverrides';
|
||||
import { toDataFrame } from '../dataframe';
|
||||
import { GrafanaTheme } from '../types';
|
||||
|
||||
describe('getFieldDisplayValuesProxy', () => {
|
||||
const data = applyFieldOverrides({
|
||||
@ -40,7 +42,9 @@ describe('getFieldDisplayValuesProxy', () => {
|
||||
|
||||
it('should format the time values in UTC', () => {
|
||||
// Test Proxies in general
|
||||
const p = getFieldDisplayValuesProxy(data, 0);
|
||||
const p = getFieldDisplayValuesProxy(data, 0, {
|
||||
theme: {} as GrafanaTheme,
|
||||
});
|
||||
const time = p.Time;
|
||||
expect(time.numeric).toEqual(1);
|
||||
expect(time.text).toEqual('1970-01-01 00:00:00');
|
||||
@ -51,7 +55,9 @@ describe('getFieldDisplayValuesProxy', () => {
|
||||
});
|
||||
|
||||
it('Lookup by name, index, or title', () => {
|
||||
const p = getFieldDisplayValuesProxy(data, 2);
|
||||
const p = getFieldDisplayValuesProxy(data, 2, {
|
||||
theme: {} as GrafanaTheme,
|
||||
});
|
||||
expect(p.power.numeric).toEqual(300);
|
||||
expect(p['power'].numeric).toEqual(300);
|
||||
expect(p['The Power'].numeric).toEqual(300);
|
||||
@ -59,7 +65,9 @@ describe('getFieldDisplayValuesProxy', () => {
|
||||
});
|
||||
|
||||
it('should return undefined when missing', () => {
|
||||
const p = getFieldDisplayValuesProxy(data, 0);
|
||||
const p = getFieldDisplayValuesProxy(data, 0, {
|
||||
theme: {} as GrafanaTheme,
|
||||
});
|
||||
expect(p.xyz).toBeUndefined();
|
||||
expect(p[100]).toBeUndefined();
|
||||
});
|
@ -1,8 +1,22 @@
|
||||
import { DisplayValue, DataFrame, formattedValueToString, getDisplayProcessor } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import toNumber from 'lodash/toNumber';
|
||||
import { DataFrame, DisplayValue, GrafanaTheme } from '../types';
|
||||
import { getDisplayProcessor } from './displayProcessor';
|
||||
import { formattedValueToString } from '../valueFormats';
|
||||
|
||||
export function getFieldDisplayValuesProxy(frame: DataFrame, rowIndex: number): Record<string, DisplayValue> {
|
||||
/**
|
||||
*
|
||||
* @param frame
|
||||
* @param rowIndex
|
||||
* @param options
|
||||
* @internal
|
||||
*/
|
||||
export function getFieldDisplayValuesProxy(
|
||||
frame: DataFrame,
|
||||
rowIndex: number,
|
||||
options: {
|
||||
theme: GrafanaTheme;
|
||||
}
|
||||
): Record<string, DisplayValue> {
|
||||
return new Proxy({} as Record<string, DisplayValue>, {
|
||||
get: (obj: any, key: string) => {
|
||||
// 1. Match the name
|
||||
@ -23,7 +37,7 @@ export function getFieldDisplayValuesProxy(frame: DataFrame, rowIndex: number):
|
||||
// Lazy load the display processor
|
||||
field.display = getDisplayProcessor({
|
||||
field,
|
||||
theme: config.theme,
|
||||
theme: options.theme,
|
||||
});
|
||||
}
|
||||
const raw = field.values.get(rowIndex);
|
@ -6,3 +6,4 @@ export * from './overrides/processors';
|
||||
export { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
|
||||
|
||||
export { applyFieldOverrides, validateFieldConfig } from './fieldOverrides';
|
||||
export { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy';
|
||||
|
@ -1,3 +1,11 @@
|
||||
export * from './string';
|
||||
export * from './markdown';
|
||||
export * from './text';
|
||||
import { escapeHtml, hasAnsiCodes, sanitize, sanitizeUrl } from './sanitize';
|
||||
|
||||
export const textUtil = {
|
||||
escapeHtml,
|
||||
hasAnsiCodes,
|
||||
sanitize,
|
||||
sanitizeUrl,
|
||||
};
|
||||
|
100
packages/grafana-data/src/types/config.ts
Normal file
100
packages/grafana-data/src/types/config.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { DataSourceInstanceSettings } from './datasource';
|
||||
import { PanelPluginMeta } from './panel';
|
||||
import { GrafanaTheme } from './theme';
|
||||
|
||||
/**
|
||||
* Describes the build information that will be available via the Grafana cofiguration.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface BuildInfo {
|
||||
version: string;
|
||||
commit: string;
|
||||
/**
|
||||
* Is set to true when running Grafana Enterprise edition.
|
||||
*
|
||||
* @deprecated use `licenseInfo.hasLicense` instead
|
||||
*/
|
||||
isEnterprise: boolean;
|
||||
env: string;
|
||||
edition: string;
|
||||
latestVersion: string;
|
||||
hasUpdate: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes available feature toggles in Grafana. These can be configured via the
|
||||
* `conf/custom.ini` to enable features under development or not yet available in
|
||||
* stable version.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface FeatureToggles {
|
||||
transformations: boolean;
|
||||
expressions: boolean;
|
||||
newEdit: boolean;
|
||||
/**
|
||||
* @remarks
|
||||
* Available only in Grafana Enterprise
|
||||
*/
|
||||
meta: boolean;
|
||||
newVariables: boolean;
|
||||
tracingIntegration: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the license information about the current running instance of Grafana.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface LicenseInfo {
|
||||
hasLicense: boolean;
|
||||
expiry: number;
|
||||
licenseUrl: string;
|
||||
stateInfo: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes all the different Grafana configuration values available for an instance.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface GrafanaConfig {
|
||||
datasources: { [str: string]: DataSourceInstanceSettings };
|
||||
panels: { [key: string]: PanelPluginMeta };
|
||||
minRefreshInterval: string;
|
||||
appSubUrl: string;
|
||||
windowTitlePrefix: string;
|
||||
buildInfo: BuildInfo;
|
||||
newPanelTitle: string;
|
||||
bootData: any;
|
||||
externalUserMngLinkUrl: string;
|
||||
externalUserMngLinkName: string;
|
||||
externalUserMngInfo: string;
|
||||
allowOrgCreate: boolean;
|
||||
disableLoginForm: boolean;
|
||||
defaultDatasource: string;
|
||||
alertingEnabled: boolean;
|
||||
alertingErrorOrTimeout: string;
|
||||
alertingNoDataOrNullValues: string;
|
||||
alertingMinInterval: number;
|
||||
authProxyEnabled: boolean;
|
||||
exploreEnabled: boolean;
|
||||
ldapEnabled: boolean;
|
||||
samlEnabled: boolean;
|
||||
autoAssignOrg: boolean;
|
||||
verifyEmailEnabled: boolean;
|
||||
oauth: any;
|
||||
disableUserSignUp: boolean;
|
||||
loginHint: any;
|
||||
passwordHint: any;
|
||||
loginError: any;
|
||||
navTree: any;
|
||||
viewersCanEdit: boolean;
|
||||
editorsCanAdmin: boolean;
|
||||
disableSanitizeHtml: boolean;
|
||||
theme: GrafanaTheme;
|
||||
pluginsToPreload: string[];
|
||||
featureToggles: FeatureToggles;
|
||||
licenseInfo: LicenseInfo;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { ThresholdsConfig } from './thresholds';
|
||||
import { ValueMapping } from './valueMapping';
|
||||
import { QueryResultBase, Labels, NullValueMode } from './data';
|
||||
import { DisplayProcessor } from './displayValue';
|
||||
import { DataLink } from './dataLink';
|
||||
import { DisplayProcessor, DisplayValue } from './displayValue';
|
||||
import { DataLink, LinkModel } from './dataLink';
|
||||
import { Vector } from './vector';
|
||||
import { FieldCalcs } from '../transformations/fieldReducer';
|
||||
import { FieldColor } from './fieldColor';
|
||||
@ -57,6 +57,17 @@ export interface FieldConfig<TOptions extends object = any> {
|
||||
scopedVars?: ScopedVars;
|
||||
}
|
||||
|
||||
export interface ValueLinkConfig {
|
||||
/**
|
||||
* Result of field reduction
|
||||
*/
|
||||
calculatedValue?: DisplayValue;
|
||||
/**
|
||||
* Index of the value row within Field. Should be provided only when value is not a result of a reduction
|
||||
*/
|
||||
valueRowIndex?: number;
|
||||
}
|
||||
|
||||
export interface Field<T = any, V = Vector<T>> {
|
||||
/**
|
||||
* Name of the field (column)
|
||||
@ -87,6 +98,11 @@ export interface Field<T = any, V = Vector<T>> {
|
||||
* Convert a value for display
|
||||
*/
|
||||
display?: DisplayProcessor;
|
||||
|
||||
/**
|
||||
* Get value data links with variables interpolated
|
||||
*/
|
||||
getLinks?: (config: ValueLinkConfig) => Array<LinkModel<Field>>;
|
||||
}
|
||||
|
||||
export interface DataFrame extends QueryResultBase {
|
||||
|
@ -37,7 +37,7 @@ export type LinkTarget = '_blank' | '_self';
|
||||
/**
|
||||
* Processed Link Model. The values are ready to use
|
||||
*/
|
||||
export interface LinkModel<T> {
|
||||
export interface LinkModel<T = any> {
|
||||
href: string;
|
||||
title: string;
|
||||
target: LinkTarget;
|
||||
|
@ -31,3 +31,4 @@ export { AppEvent, AppEvents };
|
||||
|
||||
import * as PanelEvents from './panelEvents';
|
||||
export { PanelEvents };
|
||||
export { GrafanaConfig, BuildInfo, FeatureToggles, LicenseInfo } from './config';
|
||||
|
14
packages/grafana-data/src/utils/dataLinks.ts
Normal file
14
packages/grafana-data/src/utils/dataLinks.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export const DataLinkBuiltInVars = {
|
||||
keepTime: '__url_time_range',
|
||||
timeRangeFrom: '__from',
|
||||
timeRangeTo: '__to',
|
||||
includeVars: '__all_variables',
|
||||
seriesName: '__series.name',
|
||||
fieldName: '__field.name',
|
||||
valueTime: '__value.time',
|
||||
valueNumeric: '__value.numeric',
|
||||
valueText: '__value.text',
|
||||
valueRaw: '__value.raw',
|
||||
// name of the calculation represented by the value
|
||||
valueCalc: '__value.calc',
|
||||
};
|
@ -12,3 +12,6 @@ export { PanelOptionsEditorBuilder, FieldConfigEditorBuilder } from './OptionsUI
|
||||
|
||||
export { getMappedValue } from './valueMappings';
|
||||
export { getFlotPairs, getFlotPairsConstant } from './flotPairs';
|
||||
export { locationUtil } from './location';
|
||||
export { urlUtil, UrlQueryMap, UrlQueryValue } from './url';
|
||||
export { DataLinkBuiltInVars } from './dataLinks';
|
||||
|
21
packages/grafana-data/src/utils/location.test.ts
Normal file
21
packages/grafana-data/src/utils/location.test.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { locationUtil } from './location';
|
||||
|
||||
describe('locationUtil', () => {
|
||||
beforeAll(() => {
|
||||
locationUtil.initialize({
|
||||
getConfig: () => {
|
||||
return { appSubUrl: '/subUrl' } as any;
|
||||
},
|
||||
// @ts-ignore
|
||||
buildParamsFromVariables: () => {},
|
||||
// @ts-ignore
|
||||
getTimeRangeForUrl: () => {},
|
||||
});
|
||||
});
|
||||
describe('With /subUrl as appSubUrl', () => {
|
||||
it('/subUrl should be stripped', () => {
|
||||
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/subUrl/grafana/');
|
||||
expect(urlWithoutMaster).toBe('/grafana/');
|
||||
});
|
||||
});
|
||||
});
|
73
packages/grafana-data/src/utils/location.ts
Normal file
73
packages/grafana-data/src/utils/location.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { GrafanaConfig, RawTimeRange, ScopedVars } from '../types';
|
||||
import { urlUtil } from './url';
|
||||
import { textUtil } from '../text';
|
||||
|
||||
let grafanaConfig: () => GrafanaConfig;
|
||||
let getTimeRangeUrlParams: () => RawTimeRange;
|
||||
let getVariablesUrlParams: (params?: Record<string, any>, scopedVars?: ScopedVars) => string;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url
|
||||
* @internal
|
||||
*/
|
||||
const stripBaseFromUrl = (url: string): string => {
|
||||
const appSubUrl = grafanaConfig ? grafanaConfig().appSubUrl : '';
|
||||
const stripExtraChars = appSubUrl.endsWith('/') ? 1 : 0;
|
||||
const urlWithoutBase =
|
||||
url.length > 0 && url.indexOf(appSubUrl) === 0 ? url.slice(appSubUrl.length - stripExtraChars) : url;
|
||||
|
||||
return urlWithoutBase;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url
|
||||
* @internal
|
||||
*/
|
||||
const assureBaseUrl = (url: string) => {
|
||||
if (url.startsWith('/')) {
|
||||
return `${grafanaConfig ? grafanaConfig().appSubUrl : ''}${stripBaseFromUrl(url)}`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
interface LocationUtilDependencies {
|
||||
getConfig: () => GrafanaConfig;
|
||||
getTimeRangeForUrl: () => RawTimeRange;
|
||||
buildParamsFromVariables: (params: any, scopedVars?: ScopedVars) => string;
|
||||
}
|
||||
|
||||
export const locationUtil = {
|
||||
/**
|
||||
*
|
||||
* @param getConfig
|
||||
* @param buildParamsFromVariables
|
||||
* @param getTimeRangeForUrl
|
||||
* @internal
|
||||
*/
|
||||
initialize: ({ getConfig, buildParamsFromVariables, getTimeRangeForUrl }: LocationUtilDependencies) => {
|
||||
grafanaConfig = getConfig;
|
||||
getTimeRangeUrlParams = getTimeRangeForUrl;
|
||||
getVariablesUrlParams = buildParamsFromVariables;
|
||||
},
|
||||
stripBaseFromUrl,
|
||||
assureBaseUrl,
|
||||
getTimeRangeUrlParams: () => {
|
||||
if (!getTimeRangeUrlParams) {
|
||||
return null;
|
||||
}
|
||||
return urlUtil.toUrlParams(getTimeRangeUrlParams());
|
||||
},
|
||||
getVariablesUrlParams: (scopedVars?: ScopedVars) => {
|
||||
if (!getVariablesUrlParams) {
|
||||
return null;
|
||||
}
|
||||
const params = {};
|
||||
getVariablesUrlParams(params, scopedVars);
|
||||
return urlUtil.toUrlParams(params);
|
||||
},
|
||||
processUrl: (url: string) => {
|
||||
return grafanaConfig().disableSanitizeHtml ? url : textUtil.sanitizeUrl(url);
|
||||
},
|
||||
};
|
@ -1,8 +1,8 @@
|
||||
import { toUrlParams } from '../utils/url';
|
||||
import { urlUtil } from './url';
|
||||
|
||||
describe('toUrlParams', () => {
|
||||
it('should encode object properties as url parameters', () => {
|
||||
const url = toUrlParams({
|
||||
const url = urlUtil.toUrlParams({
|
||||
server: 'backend-01',
|
||||
hasSpace: 'has space',
|
||||
many: ['1', '2', '3'],
|
||||
@ -17,7 +17,7 @@ describe('toUrlParams', () => {
|
||||
|
||||
describe('toUrlParams', () => {
|
||||
it('should encode the same way as angularjs', () => {
|
||||
const url = toUrlParams({
|
||||
const url = urlUtil.toUrlParams({
|
||||
server: ':@',
|
||||
});
|
||||
expect(url).toBe('server=:@');
|
@ -2,16 +2,28 @@
|
||||
* @preserve jquery-param (c) 2015 KNOWLEDGECODE | MIT
|
||||
*/
|
||||
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
/**
|
||||
* Type to represent the value of a single query variable.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type UrlQueryValue = string | number | boolean | string[] | number[] | boolean[] | undefined | null;
|
||||
|
||||
export function renderUrl(path: string, query: UrlQueryMap | undefined): string {
|
||||
/**
|
||||
* Type to represent the values parsed from the query string.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type UrlQueryMap = Record<string, UrlQueryValue>;
|
||||
|
||||
function renderUrl(path: string, query: UrlQueryMap | undefined): string {
|
||||
if (query && Object.keys(query).length > 0) {
|
||||
path += '?' + toUrlParams(query);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function encodeURIComponentAsAngularJS(val: string, pctEncodeSpaces?: boolean) {
|
||||
function encodeURIComponentAsAngularJS(val: string, pctEncodeSpaces?: boolean) {
|
||||
return encodeURIComponent(val)
|
||||
.replace(/%40/gi, '@')
|
||||
.replace(/%3A/gi, ':')
|
||||
@ -21,7 +33,7 @@ export function encodeURIComponentAsAngularJS(val: string, pctEncodeSpaces?: boo
|
||||
.replace(/%20/g, pctEncodeSpaces ? '%20' : '+');
|
||||
}
|
||||
|
||||
export function toUrlParams(a: any) {
|
||||
function toUrlParams(a: any) {
|
||||
const s: any[] = [];
|
||||
const rbracket = /\[\]$/;
|
||||
|
||||
@ -72,7 +84,7 @@ export function toUrlParams(a: any) {
|
||||
return buildParams('', a).join('&');
|
||||
}
|
||||
|
||||
export function appendQueryToUrl(url: string, stringToAppend: string) {
|
||||
function appendQueryToUrl(url: string, stringToAppend: string) {
|
||||
if (stringToAppend !== undefined && stringToAppend !== null && stringToAppend !== '') {
|
||||
const pos = url.indexOf('?');
|
||||
if (pos !== -1) {
|
||||
@ -91,7 +103,7 @@ export function appendQueryToUrl(url: string, stringToAppend: string) {
|
||||
/**
|
||||
* Return search part (as object) of current url
|
||||
*/
|
||||
export function getUrlSearchParams() {
|
||||
function getUrlSearchParams() {
|
||||
const search = window.location.search.substring(1);
|
||||
const searchParamsSegments = search.split('&');
|
||||
const params: any = {};
|
||||
@ -110,3 +122,10 @@ export function getUrlSearchParams() {
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
export const urlUtil = {
|
||||
renderUrl,
|
||||
toUrlParams,
|
||||
appendQueryToUrl,
|
||||
getUrlSearchParams,
|
||||
};
|
@ -3,9 +3,9 @@
|
||||
"declarationDir": "dist",
|
||||
"outDir": "compiled",
|
||||
"rootDirs": ["."],
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
"typeRoots": ["node_modules/@types", "types"]
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"extends": "@grafana/tsconfig",
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/jquery/*.ts"]
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/jquery/*.ts", "../../public/app/types/sanitize-url.d.ts"]
|
||||
}
|
||||
|
@ -1,65 +1,17 @@
|
||||
import merge from 'lodash/merge';
|
||||
import { getTheme } from '@grafana/ui';
|
||||
import { DataSourceInstanceSettings, GrafanaTheme, GrafanaThemeType, PanelPluginMeta } from '@grafana/data';
|
||||
import {
|
||||
DataSourceInstanceSettings,
|
||||
GrafanaTheme,
|
||||
GrafanaThemeType,
|
||||
PanelPluginMeta,
|
||||
GrafanaConfig,
|
||||
LicenseInfo,
|
||||
BuildInfo,
|
||||
FeatureToggles,
|
||||
} from '@grafana/data';
|
||||
|
||||
/**
|
||||
* Describes the build information that will be available via the Grafana cofiguration.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface BuildInfo {
|
||||
version: string;
|
||||
commit: string;
|
||||
/**
|
||||
* Is set to true when running Grafana Enterprise edition.
|
||||
*
|
||||
* @deprecated use `licenseInfo.hasLicense` instead
|
||||
*/
|
||||
isEnterprise: boolean;
|
||||
env: string;
|
||||
edition: string;
|
||||
latestVersion: string;
|
||||
hasUpdate: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes available feature toggles in Grafana. These can be configured via the
|
||||
* `conf/custom.ini` to enable features under development or not yet available in
|
||||
* stable version.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface FeatureToggles {
|
||||
transformations: boolean;
|
||||
expressions: boolean;
|
||||
newEdit: boolean;
|
||||
/**
|
||||
* @remarks
|
||||
* Available only in Grafana Enterprise
|
||||
*/
|
||||
meta: boolean;
|
||||
newVariables: boolean;
|
||||
tracingIntegration: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the license information about the current running instance of Grafana.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface LicenseInfo {
|
||||
hasLicense: boolean;
|
||||
expiry: number;
|
||||
licenseUrl: string;
|
||||
stateInfo: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes all the different Grafana configuration values available for an instance.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GrafanaBootConfig {
|
||||
export class GrafanaBootConfig implements GrafanaConfig {
|
||||
datasources: { [str: string]: DataSourceInstanceSettings } = {};
|
||||
panels: { [key: string]: PanelPluginMeta } = {};
|
||||
minRefreshInterval = '';
|
||||
|
@ -4,6 +4,8 @@
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
import { UrlQueryMap } from '@grafana/data';
|
||||
|
||||
export interface LocationUpdate {
|
||||
/**
|
||||
* Target path where you automatically wants to navigate the user.
|
||||
@ -37,20 +39,6 @@ export interface LocationUpdate {
|
||||
replace?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type to represent the value of a single query variable.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type UrlQueryValue = string | number | boolean | string[] | number[] | boolean[] | undefined | null;
|
||||
|
||||
/**
|
||||
* Type to represent the values parsed from the query string.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type UrlQueryMap = Record<string, UrlQueryValue>;
|
||||
|
||||
/**
|
||||
* If you need to automatically navigate the user to a new place in the application this should
|
||||
* be done via the LocationSrv and it will make sure to update the application state accordingly.
|
||||
|
@ -11,5 +11,5 @@
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"extends": "@grafana/tsconfig",
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/jquery/*.ts"]
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/jquery/*.ts", "../../public/app/types/sanitize-url.d.ts"]
|
||||
}
|
||||
|
@ -8,5 +8,5 @@
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"extends": "@grafana/tsconfig",
|
||||
"include": ["src/**/*.ts", "../../public/app/types/jquery/*.ts"]
|
||||
"include": ["src/**/*.ts", "../../public/app/types/jquery/*.ts", "../../public/app/types/sanitize-url.d.ts"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
},
|
||||
"exclude": ["../dist", "../node_modules"],
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["../src/**/*.ts", "../src/**/*.tsx"]
|
||||
"include": ["../src/**/*.ts", "../src/**/*.tsx", "../../../public/app/types/sanitize-url.d.ts"]
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState, useMemo, useContext, useRef, RefObject, memo, useEffect } from 'react';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { DataLinkSuggestions } from './DataLinkSuggestions';
|
||||
import { ThemeContext, DataLinkBuiltInVars, makeValue } from '../../index';
|
||||
import { ThemeContext, makeValue } from '../../index';
|
||||
import { SelectionReference } from './SelectionReference';
|
||||
import { Portal } from '../index';
|
||||
|
||||
@ -14,7 +14,7 @@ import { css } from 'emotion';
|
||||
import { SlatePrism } from '../../slate-plugins';
|
||||
import { SCHEMA } from '../../utils/slate';
|
||||
import { stylesFactory } from '../../themes';
|
||||
import { GrafanaTheme, VariableSuggestion, VariableOrigin } from '@grafana/data';
|
||||
import { GrafanaTheme, VariableSuggestion, VariableOrigin, DataLinkBuiltInVars } from '@grafana/data';
|
||||
|
||||
const modulo = (a: number, n: number) => a - n * Math.floor(a / n);
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { WithContextMenu } from '../ContextMenu/WithContextMenu';
|
||||
import { LinkModelSupplier } from '@grafana/data';
|
||||
import { LinkModel } from '@grafana/data';
|
||||
import { linkModelToContextMenuItems } from '../../utils/dataLinks';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface DataLinksContextMenuProps {
|
||||
children: (props: { openMenu?: React.MouseEventHandler<HTMLElement>; targetClassName?: string }) => JSX.Element;
|
||||
links?: LinkModelSupplier<any>;
|
||||
links?: () => LinkModel[];
|
||||
}
|
||||
|
||||
export const DataLinksContextMenu: React.FC<DataLinksContextMenuProps> = ({ children, links }) => {
|
||||
|
@ -1,5 +1,10 @@
|
||||
import React from 'react';
|
||||
import { FieldConfigEditorProps, DataLink, DataLinksFieldConfigSettings } from '@grafana/data';
|
||||
import {
|
||||
DataLink,
|
||||
DataLinksFieldConfigSettings,
|
||||
FieldConfigEditorProps,
|
||||
VariableSuggestionsScope,
|
||||
} from '@grafana/data';
|
||||
import { DataLinksInlineEditor } from '../DataLinks/DataLinksInlineEditor/DataLinksInlineEditor';
|
||||
|
||||
export const DataLinksValueEditor: React.FC<FieldConfigEditorProps<DataLink[], DataLinksFieldConfigSettings>> = ({
|
||||
@ -12,7 +17,7 @@ export const DataLinksValueEditor: React.FC<FieldConfigEditorProps<DataLink[], D
|
||||
links={value}
|
||||
onChange={onChange}
|
||||
data={context.data}
|
||||
suggestions={context.getSuggestions ? context.getSuggestions() : []}
|
||||
suggestions={context.getSuggestions ? context.getSuggestions(VariableSuggestionsScope.Values) : []}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,14 +1,33 @@
|
||||
import React, { FC } from 'react';
|
||||
import { TableCellProps } from './types';
|
||||
import { formattedValueToString } from '@grafana/data';
|
||||
import { formattedValueToString, LinkModel } from '@grafana/data';
|
||||
|
||||
export const DefaultCell: FC<TableCellProps> = props => {
|
||||
const { field, cell, tableStyles } = props;
|
||||
const { field, cell, tableStyles, row } = props;
|
||||
let link: LinkModel<any> | undefined;
|
||||
|
||||
if (!field.display) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displayValue = field.display(cell.value);
|
||||
return <div className={tableStyles.tableCell}>{formattedValueToString(displayValue)}</div>;
|
||||
|
||||
if (field.getLinks) {
|
||||
link = field.getLinks({
|
||||
valueRowIndex: row.index,
|
||||
})[0];
|
||||
}
|
||||
const value = formattedValueToString(displayValue);
|
||||
|
||||
return (
|
||||
<div className={tableStyles.tableCell}>
|
||||
{link ? (
|
||||
<a href={link.href} target={link.target} title={link.title}>
|
||||
{value}
|
||||
</a>
|
||||
) : (
|
||||
value
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,26 +1,11 @@
|
||||
import { ContextMenuItem } from '../components/ContextMenu/ContextMenu';
|
||||
import { LinkModelSupplier } from '@grafana/data';
|
||||
|
||||
export const DataLinkBuiltInVars = {
|
||||
keepTime: '__url_time_range',
|
||||
timeRangeFrom: '__from',
|
||||
timeRangeTo: '__to',
|
||||
includeVars: '__all_variables',
|
||||
seriesName: '__series.name',
|
||||
fieldName: '__field.name',
|
||||
valueTime: '__value.time',
|
||||
valueNumeric: '__value.numeric',
|
||||
valueText: '__value.text',
|
||||
valueRaw: '__value.raw',
|
||||
// name of the calculation represented by the value
|
||||
valueCalc: '__value.calc',
|
||||
};
|
||||
import { LinkModel } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* Delays creating links until we need to open the ContextMenu
|
||||
*/
|
||||
export const linkModelToContextMenuItems: (links: LinkModelSupplier<any>) => ContextMenuItem[] = links => {
|
||||
return links.getLinks().map(link => {
|
||||
export const linkModelToContextMenuItems: (links: () => LinkModel[]) => ContextMenuItem[] = links => {
|
||||
return links().map(link => {
|
||||
return {
|
||||
label: link.title,
|
||||
// TODO: rename to href
|
||||
|
@ -11,5 +11,5 @@
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"extends": "@grafana/tsconfig",
|
||||
"include": ["src/**/*.ts*"]
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/sanitize-url.d.ts"]
|
||||
}
|
||||
|
@ -25,9 +25,10 @@ import {
|
||||
getFlotPairs,
|
||||
TimeZone,
|
||||
getDisplayProcessor,
|
||||
textUtil,
|
||||
} from '@grafana/data';
|
||||
import { getThemeColor } from 'app/core/utils/colors';
|
||||
import { hasAnsiCodes } from 'app/core/utils/text';
|
||||
|
||||
import { sortInAscendingOrder, deduplicateLogRowsById } from 'app/core/utils/explore';
|
||||
import { getGraphSeriesModel } from 'app/plugins/panel/graph2/getGraphSeriesModel';
|
||||
|
||||
@ -313,7 +314,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi
|
||||
// This should be string but sometimes isn't (eg elastic) because the dataFrame is not strongly typed.
|
||||
const message: string = typeof messageValue === 'string' ? messageValue : JSON.stringify(messageValue);
|
||||
|
||||
const hasAnsi = hasAnsiCodes(message);
|
||||
const hasAnsi = textUtil.hasAnsiCodes(message);
|
||||
const searchWords = series.meta && series.meta.searchWords ? series.meta.searchWords : [];
|
||||
|
||||
let logLevel = LogLevel.unknown;
|
||||
|
@ -3,7 +3,7 @@ import { Action, createAction } from '@reduxjs/toolkit';
|
||||
import { LocationUpdate } from '@grafana/runtime';
|
||||
|
||||
import { LocationState } from 'app/types';
|
||||
import { renderUrl } from 'app/core/utils/url';
|
||||
import { urlUtil } from '@grafana/data';
|
||||
|
||||
export const initialState: LocationState = {
|
||||
url: '',
|
||||
@ -33,7 +33,7 @@ export const locationReducer = (state: LocationState = initialState, action: Act
|
||||
}
|
||||
|
||||
return {
|
||||
url: renderUrl(path || state.path, query),
|
||||
url: urlUtil.renderUrl(path || state.path, query),
|
||||
path: path || state.path,
|
||||
query: { ...query },
|
||||
routeParams: routeParams || state.routeParams,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { UrlQueryMap } from '@grafana/data';
|
||||
import { findTemplateVarChanges } from './bridge_srv';
|
||||
|
||||
describe('when checking template variables', () => {
|
||||
|
@ -1,12 +1,11 @@
|
||||
import coreModule from 'app/core/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { store } from 'app/store/store';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { ITimeoutService, ILocationService, IWindowService } from 'angular';
|
||||
import { ILocationService, ITimeoutService, IWindowService } from 'angular';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { locationUtil, UrlQueryMap } from '@grafana/data';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { VariableSrv } from 'app/features/templating/all';
|
||||
|
||||
|
@ -3,7 +3,6 @@ import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { getExploreUrl } from 'app/core/utils/explore';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { store } from 'app/store/store';
|
||||
import { AppEventEmitter, CoreEvents } from 'app/types';
|
||||
|
||||
@ -15,6 +14,7 @@ import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { DashboardModel } from '../../features/dashboard/state';
|
||||
import { ShareModal } from 'app/features/dashboard/components/ShareModal';
|
||||
import { SaveDashboardModalProxy } from '../../features/dashboard/components/SaveDashboard/SaveDashboardModalProxy';
|
||||
import { locationUtil } from '@grafana/data';
|
||||
|
||||
export class KeybindingSrv {
|
||||
helpModal: boolean;
|
||||
|
@ -1,16 +0,0 @@
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
|
||||
jest.mock('app/core/config', () => {
|
||||
return {
|
||||
getConfig: () => ({ appSubUrl: '/subUrl' }),
|
||||
};
|
||||
});
|
||||
|
||||
describe('locationUtil', () => {
|
||||
describe('With /subUrl as appSubUrl', () => {
|
||||
it('/subUrl should be stripped', () => {
|
||||
const urlWithoutMaster = locationUtil.stripBaseFromUrl('/subUrl/grafana/');
|
||||
expect(urlWithoutMaster).toBe('/grafana/');
|
||||
});
|
||||
});
|
||||
});
|
@ -21,8 +21,8 @@ import {
|
||||
TimeZone,
|
||||
toUtc,
|
||||
ExploreMode,
|
||||
urlUtil,
|
||||
} from '@grafana/data';
|
||||
import { renderUrl } from 'app/core/utils/url';
|
||||
import store from 'app/core/store';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { getNextRefIdChar } from './query';
|
||||
@ -106,7 +106,7 @@ export async function getExploreUrl(args: GetExploreUrlArguments): Promise<strin
|
||||
}
|
||||
|
||||
const exploreState = JSON.stringify({ ...state, originPanelId: panel.getSavedId() });
|
||||
url = renderUrl('/explore', { left: exploreState });
|
||||
url = urlUtil.renderUrl('/explore', { left: exploreState });
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { getConfig } from 'app/core/config';
|
||||
|
||||
export const stripBaseFromUrl = (url: string): string => {
|
||||
const appSubUrl = getConfig().appSubUrl;
|
||||
const stripExtraChars = appSubUrl.endsWith('/') ? 1 : 0;
|
||||
const urlWithoutBase =
|
||||
url.length > 0 && url.indexOf(appSubUrl) === 0 ? url.slice(appSubUrl.length - stripExtraChars) : url;
|
||||
|
||||
return urlWithoutBase;
|
||||
};
|
||||
|
||||
export const assureBaseUrl = (url: string) => {
|
||||
if (url.startsWith('/')) {
|
||||
return `${getConfig().appSubUrl}${stripBaseFromUrl(url)}`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export default { stripBaseFromUrl, assureBaseUrl };
|
@ -2,8 +2,7 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
// Services & Utils
|
||||
import { DataQuery, ExploreMode, dateTime } from '@grafana/data';
|
||||
import { renderUrl } from 'app/core/utils/url';
|
||||
import { DataQuery, ExploreMode, dateTime, urlUtil } from '@grafana/data';
|
||||
import store from 'app/core/store';
|
||||
import { serializeStateToUrlParam, SortOrder } from './explore';
|
||||
import { getExploreDatasources } from '../../features/explore/state/selectors';
|
||||
@ -172,7 +171,7 @@ export const createUrlFromRichHistory = (query: RichHistoryQuery) => {
|
||||
|
||||
const serializedState = serializeStateToUrlParam(exploreState, true);
|
||||
const baseUrl = /.*(?=\/explore)/.exec(`${window.location.href}`)[0];
|
||||
const url = renderUrl(`${baseUrl}/explore`, { left: serializedState });
|
||||
const url = urlUtil.renderUrl(`${baseUrl}/explore`, { left: serializedState });
|
||||
return url;
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Libaries
|
||||
import React, { PureComponent, FC } from 'react';
|
||||
import React, { FC, PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { css } from 'emotion';
|
||||
// Utils & Services
|
||||
@ -8,7 +8,8 @@ import { PlaylistSrv } from 'app/features/playlist/playlist_srv';
|
||||
// Components
|
||||
import { DashNavButton } from './DashNavButton';
|
||||
import { DashNavTimeControls } from './DashNavTimeControls';
|
||||
import { ModalsController, Icon } from '@grafana/ui';
|
||||
import { Icon, ModalsController } from '@grafana/ui';
|
||||
import { textUtil } from '@grafana/data';
|
||||
import { BackButton } from 'app/core/components/BackButton/BackButton';
|
||||
// State
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
@ -17,7 +18,6 @@ import { DashboardModel } from '../../state';
|
||||
import { CoreEvents, StoreState } from 'app/types';
|
||||
import { ShareModal } from 'app/features/dashboard/components/ShareModal';
|
||||
import { SaveDashboardModalProxy } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardModalProxy';
|
||||
import { sanitizeUrl } from 'app/core/utils/text';
|
||||
|
||||
export interface OwnProps {
|
||||
dashboard: DashboardModel;
|
||||
@ -244,8 +244,8 @@ class DashNav extends PureComponent<Props> {
|
||||
<DashNavButton
|
||||
tooltip="Open original dashboard"
|
||||
classSuffix="snapshot-origin"
|
||||
href={textUtil.sanitizeUrl(snapshotUrl)}
|
||||
icon="link"
|
||||
href={sanitizeUrl(snapshotUrl)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -10,9 +10,8 @@ import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardSrv } from '../../services/DashboardSrv';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { AppEvents, locationUtil } from '@grafana/data';
|
||||
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
|
||||
export class SettingsCtrl {
|
||||
dashboard: DashboardModel;
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { useEffect } from 'react';
|
||||
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { AppEvents, locationUtil } from '@grafana/data';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { SaveDashboardOptions } from './types';
|
||||
import { CoreEvents, StoreState } from 'app/types';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { updateLocation } from 'app/core/reducers/location';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
import { appendQueryToUrl, toUrlParams, getUrlSearchParams } from 'app/core/utils/url';
|
||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import templateSrv from 'app/features/templating/template_srv';
|
||||
import { PanelModel, dateTime } from '@grafana/data';
|
||||
import { PanelModel, dateTime, urlUtil } from '@grafana/data';
|
||||
|
||||
export function buildParams(
|
||||
useCurrentTimeRange: boolean,
|
||||
@ -10,7 +9,7 @@ export function buildParams(
|
||||
selectedTheme?: string,
|
||||
panel?: PanelModel
|
||||
) {
|
||||
const params = getUrlSearchParams();
|
||||
const params = urlUtil.getUrlSearchParams();
|
||||
|
||||
const range = getTimeSrv().timeRange();
|
||||
params.from = range.from.valueOf();
|
||||
@ -61,7 +60,7 @@ export function buildShareUrl(
|
||||
const baseUrl = buildBaseUrl();
|
||||
const params = buildParams(useCurrentTimeRange, includeTemplateVars, selectedTheme, panel);
|
||||
|
||||
return appendQueryToUrl(baseUrl, toUrlParams(params));
|
||||
return urlUtil.appendQueryToUrl(baseUrl, urlUtil.toUrlParams(params));
|
||||
}
|
||||
|
||||
export function buildSoloUrl(
|
||||
@ -77,7 +76,7 @@ export function buildSoloUrl(
|
||||
soloUrl = soloUrl.replace(config.appSubUrl + '/d/', config.appSubUrl + '/d-solo/');
|
||||
delete params.fullscreen;
|
||||
delete params.edit;
|
||||
return appendQueryToUrl(soloUrl, toUrlParams(params));
|
||||
return urlUtil.appendQueryToUrl(soloUrl, urlUtil.toUrlParams(params));
|
||||
}
|
||||
|
||||
export function buildImageUrl(
|
||||
|
@ -1,10 +1,9 @@
|
||||
import _ from 'lodash';
|
||||
import angular, { ILocationService, IScope } from 'angular';
|
||||
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { DashboardModel } from '../../state/DashboardModel';
|
||||
import { CalculateDiffOptions, HistoryListOpts, HistorySrv, RevisionsModel } from './HistorySrv';
|
||||
import { AppEvents, dateTime, DateTimeInput, toUtc } from '@grafana/data';
|
||||
import { AppEvents, dateTime, DateTimeInput, locationUtil, toUtc } from '@grafana/data';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { promiseToDigest } from '../../../../core/utils/promiseToDigest';
|
||||
|
@ -3,12 +3,11 @@ import moment from 'moment'; // eslint-disable-line no-restricted-imports
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { dateMath, AppEvents } from '@grafana/data';
|
||||
import { AppEvents, dateMath, UrlQueryValue } from '@grafana/data';
|
||||
import impressionSrv from 'app/core/services/impression_srv';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardSrv } from './DashboardSrv';
|
||||
import DatasourceSrv from 'app/features/plugins/datasource_srv';
|
||||
import { UrlQueryValue } from '@grafana/runtime';
|
||||
import { GrafanaRootScope } from 'app/routes/GrafanaCtrl';
|
||||
|
||||
export class DashboardLoaderSrv {
|
||||
|
@ -264,7 +264,7 @@ export class TimeSrv {
|
||||
this.$timeout(this.refreshDashboard.bind(this), 0);
|
||||
}
|
||||
|
||||
timeRangeForUrl() {
|
||||
timeRangeForUrl = () => {
|
||||
const range = this.timeRange().raw;
|
||||
|
||||
if (isDateTime(range.from)) {
|
||||
@ -275,7 +275,7 @@ export class TimeSrv {
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
};
|
||||
|
||||
timeRange(): TimeRange {
|
||||
// make copies if they are moment (do not want to return out internal moment, because they are mutable!)
|
||||
|
@ -3,7 +3,7 @@ import { DashboardModel } from '../state/DashboardModel';
|
||||
import { PanelModel } from '../state/PanelModel';
|
||||
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
|
||||
import { expect } from 'test/lib/common';
|
||||
import { DataLinkBuiltInVars } from '@grafana/ui';
|
||||
import { DataLinkBuiltInVars } from '@grafana/data';
|
||||
|
||||
jest.mock('app/core/services/context_srv', () => ({}));
|
||||
|
||||
|
@ -2,12 +2,11 @@
|
||||
import _ from 'lodash';
|
||||
// Utils
|
||||
import getFactors from 'app/core/utils/factors';
|
||||
import { appendQueryToUrl } from 'app/core/utils/url';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
// Types
|
||||
import { PanelModel } from './PanelModel';
|
||||
import { DashboardModel } from './DashboardModel';
|
||||
import { DataLink } from '@grafana/data';
|
||||
import { DataLink, urlUtil, DataLinkBuiltInVars } from '@grafana/data';
|
||||
// Constants
|
||||
import {
|
||||
DEFAULT_PANEL_SPAN,
|
||||
@ -17,7 +16,6 @@ import {
|
||||
GRID_COLUMN_COUNT,
|
||||
MIN_PANEL_HEIGHT,
|
||||
} from 'app/core/constants';
|
||||
import { DataLinkBuiltInVars } from '@grafana/ui';
|
||||
import { isMulti } from 'app/features/variables/guard';
|
||||
import { alignCurrentWithMulti } from 'app/features/variables/shared/multiOptions';
|
||||
|
||||
@ -737,15 +735,15 @@ function upgradePanelLink(link: any): DataLink {
|
||||
}
|
||||
|
||||
if (link.keepTime) {
|
||||
url = appendQueryToUrl(url, `$${DataLinkBuiltInVars.keepTime}`);
|
||||
url = urlUtil.appendQueryToUrl(url, `$${DataLinkBuiltInVars.keepTime}`);
|
||||
}
|
||||
|
||||
if (link.includeVars) {
|
||||
url = appendQueryToUrl(url, `$${DataLinkBuiltInVars.includeVars}`);
|
||||
url = urlUtil.appendQueryToUrl(url, `$${DataLinkBuiltInVars.includeVars}`);
|
||||
}
|
||||
|
||||
if (link.params) {
|
||||
url = appendQueryToUrl(url, link.params);
|
||||
url = urlUtil.appendQueryToUrl(url, link.params);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -10,8 +10,17 @@ import sortByKeys from 'app/core/utils/sort_by_keys';
|
||||
// Types
|
||||
import { GridPos, panelAdded, PanelModel, panelRemoved } from './PanelModel';
|
||||
import { DashboardMigrator } from './DashboardMigrator';
|
||||
import { AppEvent, dateTime, DateTimeInput, isDateTime, PanelEvents, TimeRange, TimeZone, toUtc } from '@grafana/data';
|
||||
import { UrlQueryValue } from '@grafana/runtime';
|
||||
import {
|
||||
AppEvent,
|
||||
dateTime,
|
||||
DateTimeInput,
|
||||
isDateTime,
|
||||
PanelEvents,
|
||||
TimeRange,
|
||||
TimeZone,
|
||||
toUtc,
|
||||
UrlQueryValue,
|
||||
} from '@grafana/data';
|
||||
import { CoreEvents, DashboardMeta, KIOSK_MODE_TV } from 'app/types';
|
||||
import { getConfig } from '../../../core/config';
|
||||
import { GetVariables, getVariables } from 'app/features/variables/state/selectors';
|
||||
|
@ -9,7 +9,6 @@ import { VariableSrv } from 'app/features/templating/variable_srv';
|
||||
import { KeybindingSrv } from 'app/core/services/keybindingSrv';
|
||||
// Actions
|
||||
import { notifyApp, updateLocation } from 'app/core/actions';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import {
|
||||
clearDashboardQueriesToUpdateOnLoad,
|
||||
dashboardInitCompleted,
|
||||
@ -21,7 +20,7 @@ import {
|
||||
// Types
|
||||
import { DashboardDTO, DashboardRouteInfo, StoreState, ThunkDispatch, ThunkResult } from 'app/types';
|
||||
import { DashboardModel } from './DashboardModel';
|
||||
import { DataQuery } from '@grafana/data';
|
||||
import { DataQuery, locationUtil } from '@grafana/data';
|
||||
import { getConfig } from '../../../core/config';
|
||||
import { initDashboardTemplating, processVariables, completeDashboardTemplating } from '../../variables/state/actions';
|
||||
import { emitDashboardViewEvent } from './analyticsProcessor';
|
||||
|
@ -14,18 +14,17 @@ import appEvents from 'app/core/app_events';
|
||||
import { getDataSource, getDataSourceMeta } from '../state/selectors';
|
||||
import {
|
||||
deleteDataSource,
|
||||
loadDataSource,
|
||||
updateDataSource,
|
||||
initDataSourceSettings,
|
||||
loadDataSource,
|
||||
testDataSource,
|
||||
updateDataSource,
|
||||
} from '../state/actions';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { getRouteParamsId } from 'app/core/selectors/location';
|
||||
// Types
|
||||
import { CoreEvents, StoreState } from 'app/types/';
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { DataSourcePluginMeta, DataSourceSettings, NavModel, UrlQueryMap } from '@grafana/data';
|
||||
import { Icon } from '@grafana/ui';
|
||||
import { DataSourcePluginMeta, DataSourceSettings, NavModel } from '@grafana/data';
|
||||
import { getDataSourceLoadingNav } from '../state/navModel';
|
||||
import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
|
||||
import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers';
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { DataSourceSettings, DataSourcePluginMeta } from '@grafana/data';
|
||||
import { DataSourcePluginMeta, DataSourceSettings, UrlQueryValue } from '@grafana/data';
|
||||
import { DataSourcesState } from '../../../types/datasources';
|
||||
import { UrlQueryValue } from '@grafana/runtime';
|
||||
|
||||
export const getDataSources = (state: DataSourcesState) => {
|
||||
const regex = new RegExp(state.searchQuery, 'i');
|
||||
|
@ -2,11 +2,12 @@ import {
|
||||
DataQuery,
|
||||
DataSourceApi,
|
||||
dateTime,
|
||||
ExploreMode,
|
||||
LoadingState,
|
||||
LogsDedupStrategy,
|
||||
toDataFrame,
|
||||
ExploreMode,
|
||||
RawTimeRange,
|
||||
toDataFrame,
|
||||
UrlQueryMap,
|
||||
} from '@grafana/data';
|
||||
|
||||
import {
|
||||
@ -33,7 +34,6 @@ import {
|
||||
} from './actionTypes';
|
||||
import { serializeStateToUrlParam } from 'app/core/utils/explore';
|
||||
import { updateLocation } from '../../../core/actions';
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
|
||||
describe('Explore item reducer', () => {
|
||||
describe('scanning', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AppEvents, DataSourceInstanceSettings, DataSourceSelectItem } from '@grafana/data';
|
||||
import { AppEvents, DataSourceInstanceSettings, DataSourceSelectItem, locationUtil } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import config from 'app/core/config';
|
||||
import {
|
||||
@ -9,7 +9,6 @@ import {
|
||||
InputType,
|
||||
ImportDashboardDTO,
|
||||
} from './reducers';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { ThunkResult } from 'app/types';
|
||||
import { appEvents } from '../../../core/core';
|
||||
|
@ -17,8 +17,7 @@ import { updateLocation } from 'app/core/actions';
|
||||
import { connect } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { AppEvents } from '@grafana/data';
|
||||
import { assureBaseUrl } from 'app/core/utils/location_util';
|
||||
import { AppEvents, locationUtil } from '@grafana/data';
|
||||
|
||||
const roles = [
|
||||
{ label: 'Viewer', value: OrgRole.Viewer },
|
||||
@ -77,7 +76,7 @@ export const UserInviteForm: FC<Props> = ({ updateLocation }) => {
|
||||
</Field>
|
||||
<HorizontalGroup>
|
||||
<Button type="submit">Submit</Button>
|
||||
<LinkButton href={assureBaseUrl(getConfig().appSubUrl + '/org/users')} variant="secondary">
|
||||
<LinkButton href={locationUtil.assureBaseUrl(getConfig().appSubUrl + '/org/users')} variant="secondary">
|
||||
Back
|
||||
</LinkButton>
|
||||
</HorizontalGroup>
|
||||
|
@ -1,19 +1,20 @@
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
import {
|
||||
DataLink,
|
||||
DisplayValue,
|
||||
Field,
|
||||
FieldDisplay,
|
||||
LinkModelSupplier,
|
||||
formattedValueToString,
|
||||
getFieldDisplayValuesProxy,
|
||||
getTimeField,
|
||||
Labels,
|
||||
ScopedVars,
|
||||
ScopedVar,
|
||||
Field,
|
||||
LinkModel,
|
||||
formattedValueToString,
|
||||
DisplayValue,
|
||||
DataLink,
|
||||
LinkModelSupplier,
|
||||
ScopedVar,
|
||||
ScopedVars,
|
||||
} from '@grafana/data';
|
||||
import { getLinkSrv } from './link_srv';
|
||||
import { getFieldDisplayValuesProxy } from './fieldDisplayValuesProxy';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
interface SeriesVars {
|
||||
name?: string;
|
||||
@ -100,7 +101,9 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
|
||||
value: {
|
||||
name: dataFrame.name,
|
||||
refId: dataFrame.refId,
|
||||
fields: getFieldDisplayValuesProxy(dataFrame, value.rowIndex!),
|
||||
fields: getFieldDisplayValuesProxy(dataFrame, value.rowIndex!, {
|
||||
theme: config.theme,
|
||||
}),
|
||||
},
|
||||
text: 'Data',
|
||||
};
|
||||
|
@ -2,23 +2,23 @@ import _ from 'lodash';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import templateSrv, { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { appendQueryToUrl, toUrlParams } from 'app/core/utils/url';
|
||||
import { sanitizeUrl } from 'app/core/utils/text';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { DataLinkBuiltInVars } from '@grafana/ui';
|
||||
import {
|
||||
DataFrame,
|
||||
DataLink,
|
||||
DataLinkBuiltInVars,
|
||||
deprecationWarning,
|
||||
Field,
|
||||
FieldType,
|
||||
KeyValue,
|
||||
LinkModel,
|
||||
locationUtil,
|
||||
ScopedVars,
|
||||
VariableOrigin,
|
||||
VariableSuggestion,
|
||||
VariableSuggestionsScope,
|
||||
urlUtil,
|
||||
textUtil,
|
||||
} from '@grafana/data';
|
||||
|
||||
const timeRangeVars = [
|
||||
@ -253,8 +253,8 @@ export class LinkSrv implements LinkService {
|
||||
this.templateSrv.fillVariableValuesForUrl(params);
|
||||
}
|
||||
|
||||
url = appendQueryToUrl(url, toUrlParams(params));
|
||||
return getConfig().disableSanitizeHtml ? url : sanitizeUrl(url);
|
||||
url = urlUtil.appendQueryToUrl(url, urlUtil.toUrlParams(params));
|
||||
return getConfig().disableSanitizeHtml ? url : textUtil.sanitizeUrl(url);
|
||||
}
|
||||
|
||||
getAnchorInfo(link: any) {
|
||||
@ -269,7 +269,7 @@ export class LinkSrv implements LinkService {
|
||||
*/
|
||||
getDataLinkUIModel = <T>(link: DataLink, scopedVars: ScopedVars, origin: T): LinkModel<T> => {
|
||||
const params: KeyValue = {};
|
||||
const timeRangeUrl = toUrlParams(this.timeSrv.timeRangeForUrl());
|
||||
const timeRangeUrl = urlUtil.toUrlParams(this.timeSrv.timeRangeForUrl());
|
||||
|
||||
let href = link.url;
|
||||
|
||||
@ -302,7 +302,7 @@ export class LinkSrv implements LinkService {
|
||||
|
||||
this.templateSrv.fillVariableValuesForUrl(params, scopedVars);
|
||||
|
||||
const variablesQuery = toUrlParams(params);
|
||||
const variablesQuery = urlUtil.toUrlParams(params);
|
||||
|
||||
info.href = this.templateSrv.replace(info.href, {
|
||||
...scopedVars,
|
||||
@ -316,7 +316,7 @@ export class LinkSrv implements LinkService {
|
||||
},
|
||||
});
|
||||
|
||||
info.href = getConfig().disableSanitizeHtml ? info.href : sanitizeUrl(info.href);
|
||||
info.href = getConfig().disableSanitizeHtml ? info.href : textUtil.sanitizeUrl(info.href);
|
||||
|
||||
return info;
|
||||
};
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { LinkSrv } from '../link_srv';
|
||||
import { DataLinkBuiltInVars } from '@grafana/ui';
|
||||
import { DataLinkBuiltInVars, locationUtil, VariableModel } from '@grafana/data';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { advanceTo } from 'jest-date-mock';
|
||||
import { updateConfig } from '../../../../core/config';
|
||||
import { variableAdapters } from '../../../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../../../variables/query/adapter';
|
||||
|
||||
jest.mock('app/core/core', () => ({
|
||||
appEvents: {
|
||||
@ -46,29 +48,48 @@ describe('linkSrv', () => {
|
||||
timeSrv.setTime({ from: 'now-1h', to: 'now' });
|
||||
_dashboard.refresh = false;
|
||||
|
||||
const _templateSrv = new TemplateSrv();
|
||||
_templateSrv.init([
|
||||
const variablesMock = [
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test1',
|
||||
label: 'Test1',
|
||||
hide: false,
|
||||
current: { value: 'val1' },
|
||||
skipUrlSync: false,
|
||||
getValueForUrl: function() {
|
||||
return this.current.value;
|
||||
return 'val1';
|
||||
},
|
||||
},
|
||||
} as VariableModel,
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test2',
|
||||
label: 'Test2',
|
||||
hide: false,
|
||||
current: { value: 'val2' },
|
||||
skipUrlSync: false,
|
||||
getValueForUrl: function() {
|
||||
return this.current.value;
|
||||
return 'val2';
|
||||
},
|
||||
} as VariableModel,
|
||||
];
|
||||
const _templateSrv = new TemplateSrv({
|
||||
// @ts-ignore
|
||||
getVariables: () => {
|
||||
return variablesMock;
|
||||
},
|
||||
]);
|
||||
// @ts-ignore
|
||||
getVariableWithName: (name: string) => {
|
||||
return variablesMock.filter(v => v.name === name)[0];
|
||||
},
|
||||
});
|
||||
|
||||
linkSrv = new LinkSrv(_templateSrv, timeSrv);
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
variableAdapters.register(createQueryVariableAdapter());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
initLinkSrv();
|
||||
advanceTo(1000000000);
|
||||
@ -217,8 +238,14 @@ describe('linkSrv', () => {
|
||||
`(
|
||||
"when link '$url' and config.appSubUrl set to '$appSubUrl' then result should be '$expected'",
|
||||
({ url, appSubUrl, expected }) => {
|
||||
updateConfig({
|
||||
appSubUrl,
|
||||
locationUtil.initialize({
|
||||
getConfig: () => {
|
||||
return { appSubUrl } as any;
|
||||
},
|
||||
// @ts-ignore
|
||||
buildParamsFromVariables: () => {},
|
||||
// @ts-ignore
|
||||
getTimeRangeForUrl: () => {},
|
||||
});
|
||||
|
||||
const link = linkSrv.getDataLinkUIModel(
|
||||
|
@ -2,14 +2,14 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
// Utils
|
||||
import { toUrlParams } from 'app/core/utils/url';
|
||||
import coreModule from '../../core/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { store } from 'app/store/store';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { locationUtil, urlUtil } from '@grafana/data';
|
||||
|
||||
export const queryParamsToPreserve: { [key: string]: boolean } = {
|
||||
kiosk: true,
|
||||
@ -55,7 +55,7 @@ export class PlaylistSrv {
|
||||
// this is done inside timeout to make sure digest happens after
|
||||
// as this can be called from react
|
||||
this.$timeout(() => {
|
||||
this.$location.url(nextDashboardUrl + '?' + toUrlParams(filteredParams));
|
||||
this.$location.url(nextDashboardUrl + '?' + urlUtil.toUrlParams(filteredParams));
|
||||
});
|
||||
|
||||
this.index++;
|
||||
|
@ -2,15 +2,13 @@
|
||||
import React, { Component } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
// Types
|
||||
import { StoreState } from 'app/types';
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { AppEvents, AppPlugin, AppPluginMeta, NavModel, PluginType, UrlQueryMap } from '@grafana/data';
|
||||
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { getPluginSettings } from './PluginSettingsCache';
|
||||
import { importAppPlugin } from './plugin_loader';
|
||||
import { AppPlugin, AppPluginMeta, PluginType, NavModel, AppEvents } from '@grafana/data';
|
||||
import { getLoadingNav } from './PluginPage';
|
||||
import { getNotFoundNav, getWarningNav } from 'app/core/nav_model_srv';
|
||||
import { appEvents } from 'app/core/core';
|
||||
|
@ -4,21 +4,21 @@ import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import find from 'lodash/find';
|
||||
// Types
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { StoreState, AppNotificationSeverity, CoreEvents } from 'app/types';
|
||||
import { Alert, Tooltip } from '@grafana/ui';
|
||||
import {
|
||||
AppPlugin,
|
||||
GrafanaPlugin,
|
||||
NavModel,
|
||||
NavModelItem,
|
||||
PluginDependencies,
|
||||
PluginInclude,
|
||||
PluginIncludeType,
|
||||
PluginMeta,
|
||||
PluginMetaInfo,
|
||||
PluginType,
|
||||
NavModel,
|
||||
NavModelItem,
|
||||
UrlQueryMap,
|
||||
} from '@grafana/data';
|
||||
import { AppNotificationSeverity, CoreEvents, StoreState } from 'app/types';
|
||||
import { Alert, Tooltip } from '@grafana/ui';
|
||||
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { getPluginSettings } from './PluginSettingsCache';
|
||||
|
@ -1,13 +1,12 @@
|
||||
import React, { FC, memo } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { connect, MapStateToProps } from 'react-redux';
|
||||
import { NavModel } from '@grafana/data';
|
||||
import { NavModel, locationUtil } from '@grafana/data';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
import { StoreState } from 'app/types';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { getRouteParams } from 'app/core/selectors/location';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { ManageDashboards } from './ManageDashboards';
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { TemplateSrv } from '../template_srv';
|
||||
import { convertToStoreState } from 'test/helpers/convertToStoreState';
|
||||
import { getTemplateSrvDependencies } from '../../../../test/helpers/getTemplateSrvDependencies';
|
||||
import { variableAdapters } from '../../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../../variables/query/adapter';
|
||||
|
||||
describe('templateSrv', () => {
|
||||
let _templateSrv: any;
|
||||
@ -448,6 +450,9 @@ describe('templateSrv', () => {
|
||||
});
|
||||
|
||||
describe('fillVariableValuesForUrl with multi value', () => {
|
||||
beforeAll(() => {
|
||||
variableAdapters.register(createQueryVariableAdapter());
|
||||
});
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
{
|
||||
|
@ -1,13 +1,13 @@
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import _ from 'lodash';
|
||||
import { escapeHtml } from 'app/core/utils/text';
|
||||
import { deprecationWarning, ScopedVars, TimeRange } from '@grafana/data';
|
||||
import { deprecationWarning, ScopedVars, textUtil, TimeRange } from '@grafana/data';
|
||||
import { getFilteredVariables, getVariables, getVariableWithName } from '../variables/state/selectors';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { variableRegex } from './utils';
|
||||
import { isAdHoc } from '../variables/guard';
|
||||
import { VariableModel } from './types';
|
||||
import { setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime';
|
||||
import { variableAdapters } from '../variables/adapters';
|
||||
|
||||
function luceneEscape(value: string) {
|
||||
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
|
||||
@ -194,9 +194,9 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
}
|
||||
case 'html': {
|
||||
if (_.isArray(value)) {
|
||||
return escapeHtml(value.join(', '));
|
||||
return textUtil.escapeHtml(value.join(', '));
|
||||
}
|
||||
return escapeHtml(value);
|
||||
return textUtil.escapeHtml(value);
|
||||
}
|
||||
case 'json': {
|
||||
return JSON.stringify(value);
|
||||
@ -399,8 +399,8 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
});
|
||||
}
|
||||
|
||||
fillVariableValuesForUrl(params: any, scopedVars?: ScopedVars) {
|
||||
_.each(this._variables, variable => {
|
||||
fillVariableValuesForUrl = (params: any, scopedVars?: ScopedVars) => {
|
||||
_.each(this.getVariables(), variable => {
|
||||
if (scopedVars && scopedVars[variable.name] !== void 0) {
|
||||
if (scopedVars[variable.name].skipUrlSync) {
|
||||
return;
|
||||
@ -410,10 +410,10 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
if (variable.skipUrlSync) {
|
||||
return;
|
||||
}
|
||||
params['var-' + variable.name] = variable.getValueForUrl();
|
||||
params['var-' + variable.name] = variableAdapters.get(variable.type).getValueForUrl(variable);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
distributeVariable(value: any, variable: any) {
|
||||
value = _.map(value, (val: any, index: number) => {
|
||||
|
@ -9,9 +9,8 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
// Types
|
||||
import { AppEvents, TimeRange } from '@grafana/data';
|
||||
import { AppEvents, TimeRange, UrlQueryMap } from '@grafana/data';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { appEvents, contextSrv } from 'app/core/core';
|
||||
|
||||
export class VariableSrv {
|
||||
|
@ -1,14 +1,15 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect, MapStateToProps, MapDispatchToProps } from 'react-redux';
|
||||
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||
import { StoreState } from 'app/types';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
import { UrlQueryValue, getBackendSrv } from '@grafana/runtime';
|
||||
import { Button, Input, Form, Field } from '@grafana/ui';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { Button, Field, Form, Input } from '@grafana/ui';
|
||||
import { useAsync } from 'react-use';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { UrlQueryValue } from '@grafana/data';
|
||||
|
||||
interface ConnectedProps {
|
||||
code?: UrlQueryValue;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { Reducer } from 'redux';
|
||||
import { UrlQueryValue } from '@grafana/runtime';
|
||||
import { Registry, UrlQueryValue, VariableType } from '@grafana/data';
|
||||
|
||||
import {
|
||||
AdHocVariableModel,
|
||||
@ -16,7 +16,6 @@ import {
|
||||
import { VariableEditorProps } from './editor/types';
|
||||
import { VariablesState } from './state/variablesReducer';
|
||||
import { VariablePickerProps } from './pickers/types';
|
||||
import { Registry, VariableType } from '@grafana/data';
|
||||
import { createQueryVariableAdapter } from './query/adapter';
|
||||
import { createCustomVariableAdapter } from './custom/adapter';
|
||||
import { createTextBoxVariableAdapter } from './textbox/adapter';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toFilters, toUrl } from './urlParser';
|
||||
import { AdHocVariableFilter } from 'app/features/templating/types';
|
||||
import { UrlQueryValue } from '@grafana/runtime';
|
||||
import { UrlQueryValue } from '@grafana/data';
|
||||
|
||||
describe('urlParser', () => {
|
||||
describe('parsing toUrl with no filters', () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { AdHocVariableFilter } from 'app/features/templating/types';
|
||||
import { UrlQueryValue } from '@grafana/runtime';
|
||||
import { UrlQueryValue } from '@grafana/data';
|
||||
import { isArray, isString } from 'lodash';
|
||||
|
||||
export const toUrl = (filters: AdHocVariableFilter[]): string[] => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { AnyAction } from 'redux';
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { UrlQueryMap } from '@grafana/data';
|
||||
|
||||
import { getTemplatingAndLocationRootReducer, getTemplatingRootReducer } from './helpers';
|
||||
import { variableAdapters } from '../adapters';
|
||||
@ -10,20 +10,20 @@ import { createConstantVariableAdapter } from '../constant/adapter';
|
||||
import { reduxTester } from '../../../../test/core/redux/reduxTester';
|
||||
import { TemplatingState } from 'app/features/variables/state/reducers';
|
||||
import {
|
||||
changeVariableMultiValue,
|
||||
initDashboardTemplating,
|
||||
processVariables,
|
||||
setOptionFromUrl,
|
||||
validateVariableSelectionState,
|
||||
changeVariableMultiValue,
|
||||
} from './actions';
|
||||
import {
|
||||
addInitLock,
|
||||
addVariable,
|
||||
changeVariableProp,
|
||||
removeInitLock,
|
||||
removeVariable,
|
||||
resolveInitLock,
|
||||
setCurrentVariableValue,
|
||||
changeVariableProp,
|
||||
} from './sharedReducer';
|
||||
import { NEW_VARIABLE_ID, toVariableIdentifier, toVariablePayload } from './types';
|
||||
import {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import castArray from 'lodash/castArray';
|
||||
import { UrlQueryMap, UrlQueryValue } from '@grafana/runtime';
|
||||
import { AppEvents, TimeRange } from '@grafana/data';
|
||||
import { AppEvents, TimeRange, UrlQueryMap, UrlQueryValue } from '@grafana/data';
|
||||
import angular from 'angular';
|
||||
|
||||
import {
|
||||
@ -8,8 +7,8 @@ import {
|
||||
VariableModel,
|
||||
VariableOption,
|
||||
VariableRefresh,
|
||||
VariableWithOptions,
|
||||
VariableWithMultiSupport,
|
||||
VariableWithOptions,
|
||||
} from '../../templating/types';
|
||||
import { StoreState, ThunkResult } from '../../../types';
|
||||
import { getVariable, getVariables } from './selectors';
|
||||
@ -19,10 +18,10 @@ import { updateLocation } from 'app/core/actions';
|
||||
import {
|
||||
addInitLock,
|
||||
addVariable,
|
||||
changeVariableProp,
|
||||
removeInitLock,
|
||||
resolveInitLock,
|
||||
setCurrentVariableValue,
|
||||
changeVariableProp,
|
||||
} from './sharedReducer';
|
||||
import { toVariableIdentifier, toVariablePayload, VariableIdentifier } from './types';
|
||||
import { appEvents } from 'app/core/core';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { UrlQueryMap } from '@grafana/data';
|
||||
|
||||
import { getTemplatingRootReducer } from './helpers';
|
||||
import { variableAdapters } from '../adapters';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { Button, DataLinkBuiltInVars, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { GrafanaTheme, VariableOrigin } from '@grafana/data';
|
||||
import { Button, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { GrafanaTheme, VariableOrigin, DataLinkBuiltInVars } from '@grafana/data';
|
||||
import { DataLinkConfig } from '../types';
|
||||
import { DataLink } from './DataLink';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { Button, DataLinkBuiltInVars, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { GrafanaTheme, VariableOrigin } from '@grafana/data';
|
||||
import { Button, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { GrafanaTheme, VariableOrigin, DataLinkBuiltInVars } from '@grafana/data';
|
||||
import { DerivedFieldConfig } from '../types';
|
||||
import { DerivedField } from './DerivedField';
|
||||
import { DebugSection } from './DebugSection';
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
getDisplayValueAlignmentFactors,
|
||||
DisplayValueAlignmentFactors,
|
||||
} from '@grafana/data';
|
||||
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||
|
||||
export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
||||
renderValue = (valueProps: VizRepeaterRenderValueProps<FieldDisplay, DisplayValueAlignmentFactors>): JSX.Element => {
|
||||
@ -22,7 +21,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
||||
const { field, display, view, colIndex } = value;
|
||||
|
||||
return (
|
||||
<DataLinksContextMenu links={getFieldLinksSupplier(value)}>
|
||||
<DataLinksContextMenu links={value.getLinks}>
|
||||
{({ openMenu, targetClassName }) => {
|
||||
return (
|
||||
<BarGauge
|
||||
|
@ -10,7 +10,6 @@ import { Gauge, DataLinksContextMenu, VizRepeater, VizRepeaterRenderValueProps }
|
||||
// Types
|
||||
import { GaugeOptions } from './types';
|
||||
import { FieldDisplay, getFieldDisplayValues, VizOrientation, PanelProps } from '@grafana/data';
|
||||
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||
|
||||
export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
||||
renderValue = (valueProps: VizRepeaterRenderValueProps<FieldDisplay>): JSX.Element => {
|
||||
@ -19,7 +18,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
||||
const { field, display } = value;
|
||||
|
||||
return (
|
||||
<DataLinksContextMenu links={getFieldLinksSupplier(value)}>
|
||||
<DataLinksContextMenu links={value.getLinks}>
|
||||
{({ openMenu, targetClassName }) => {
|
||||
return (
|
||||
<Gauge
|
||||
|
@ -1,7 +1,7 @@
|
||||
import $ from 'jquery';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { sanitize } from 'app/core/utils/text';
|
||||
import { textUtil } from '@grafana/data';
|
||||
|
||||
export default function GraphTooltip(this: any, elem: any, dashboard: any, scope: any, getSeriesFn: any) {
|
||||
const self = this;
|
||||
@ -268,10 +268,10 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope
|
||||
}
|
||||
|
||||
series = seriesList[hoverInfo.index];
|
||||
value = sanitize(series.formatValue(hoverInfo.value));
|
||||
value = textUtil.sanitize(series.formatValue(hoverInfo.value));
|
||||
|
||||
const color = sanitize(hoverInfo.color);
|
||||
const label = sanitize(hoverInfo.label);
|
||||
const color = textUtil.sanitize(hoverInfo.color);
|
||||
const label = textUtil.sanitize(hoverInfo.label);
|
||||
|
||||
seriesHtml +=
|
||||
'<div class="graph-tooltip-list-item ' + highlightClass + '"><div class="graph-tooltip-series-name">';
|
||||
@ -283,7 +283,7 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope
|
||||
self.renderAndShow(absoluteTime, seriesHtml, pos, xMode);
|
||||
} else if (item) {
|
||||
// single series tooltip
|
||||
const color = sanitize(item.series.color);
|
||||
const color = textUtil.sanitize(item.series.color);
|
||||
series = seriesList[item.seriesIndex];
|
||||
group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
|
||||
group += '<i class="fa fa-minus" style="color:' + color + ';"></i> ' + series.aliasEscaped + ':</div>';
|
||||
@ -294,7 +294,7 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope
|
||||
value = item.datapoint[1];
|
||||
}
|
||||
|
||||
value = sanitize(series.formatValue(value));
|
||||
value = textUtil.sanitize(series.formatValue(value));
|
||||
absoluteTime = dashboard.formatDate(item.datapoint[0], tooltipFormat);
|
||||
|
||||
group += '<div class="graph-tooltip-value">' + value + '</div>';
|
||||
|
@ -1,19 +1,18 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
import { css } from 'emotion';
|
||||
|
||||
// Utils & Services
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { stylesFactory, CustomScrollbar } from '@grafana/ui';
|
||||
import { CustomScrollbar, stylesFactory } from '@grafana/ui';
|
||||
|
||||
import config from 'app/core/config';
|
||||
import { feedToDataFrame } from './utils';
|
||||
import { sanitize } from 'app/core/utils/text';
|
||||
import { loadRSSFeed } from './rss';
|
||||
|
||||
// Types
|
||||
import { PanelProps, DataFrameView, dateTime } from '@grafana/data';
|
||||
import { PanelProps, DataFrameView, dateTime, GrafanaTheme, textUtil } from '@grafana/data';
|
||||
import { NewsOptions, NewsItem } from './types';
|
||||
import { DEFAULT_FEED_URL, PROXY_PREFIX } from './constants';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface Props extends PanelProps<NewsOptions> {}
|
||||
|
||||
@ -82,7 +81,7 @@ export class NewsPanel extends PureComponent<Props, State> {
|
||||
<div className={styles.title}>{item.title}</div>
|
||||
<div className={styles.date}>{dateTime(item.date).format('MMM DD')} </div>
|
||||
</a>
|
||||
<div className={styles.content} dangerouslySetInnerHTML={{ __html: sanitize(item.content) }} />
|
||||
<div className={styles.content} dangerouslySetInnerHTML={{ __html: textUtil.sanitize(item.content) }} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -4,7 +4,6 @@ import $ from 'jquery';
|
||||
import 'vendor/flot/jquery.flot';
|
||||
import 'vendor/flot/jquery.flot.gauge';
|
||||
import 'app/features/panel/panellinks/link_srv';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
@ -23,6 +22,7 @@ import {
|
||||
getColorFromHexRgbOrName,
|
||||
PanelEvents,
|
||||
formattedValueToString,
|
||||
locationUtil,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { convertOldAngularValueMapping } from '@grafana/ui';
|
||||
|
@ -24,8 +24,6 @@ import {
|
||||
DisplayValueAlignmentFactors,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||
|
||||
export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
|
||||
renderValue = (valueProps: VizRepeaterRenderValueProps<FieldDisplay, DisplayValueAlignmentFactors>): JSX.Element => {
|
||||
const { timeRange, options } = this.props;
|
||||
@ -48,7 +46,7 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
|
||||
}
|
||||
|
||||
return (
|
||||
<DataLinksContextMenu links={getFieldLinksSupplier(value)}>
|
||||
<DataLinksContextMenu links={value.getLinks}>
|
||||
{({ openMenu, targetClassName }) => {
|
||||
return (
|
||||
<BigValue
|
||||
|
@ -9,12 +9,12 @@ import {
|
||||
ScopedVars,
|
||||
stringStartsAsRegEx,
|
||||
stringToJsRegex,
|
||||
textUtil,
|
||||
unEscapeStringFromRegex,
|
||||
} from '@grafana/data';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { ColumnRender, TableRenderModel, ColumnStyle } from './types';
|
||||
import { ColumnOptionsCtrl } from './column_options';
|
||||
import { sanitizeUrl } from 'app/core/utils/text';
|
||||
|
||||
export class TableRenderer {
|
||||
formatters: any[];
|
||||
@ -298,7 +298,7 @@ export class TableRenderer {
|
||||
scopedVars['__cell'] = { value: value, text: value ? value.toString() : '' };
|
||||
|
||||
const cellLink = this.templateSrv.replace(column.style.linkUrl, scopedVars, encodeURIComponent);
|
||||
const sanitizedCellLink = sanitizeUrl(cellLink);
|
||||
const sanitizedCellLink = textUtil.sanitizeUrl(cellLink);
|
||||
|
||||
const cellLinkTooltip = this.templateSrv.replace(column.style.linkTooltip, scopedVars);
|
||||
const cellTarget = column.style.linkTargetBlank ? '_blank' : '';
|
||||
|
@ -1,11 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
import { PanelCtrl } from 'app/plugins/sdk';
|
||||
|
||||
import { sanitize, escapeHtml } from 'app/core/utils/text';
|
||||
import config from 'app/core/config';
|
||||
import { auto, ISCEService } from 'angular';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { PanelEvents } from '@grafana/data';
|
||||
import { PanelEvents, textUtil } from '@grafana/data';
|
||||
import { renderMarkdown } from '@grafana/data';
|
||||
|
||||
const defaultContent = `
|
||||
@ -78,7 +77,7 @@ export class TextPanelCtrl extends PanelCtrl {
|
||||
}
|
||||
|
||||
renderText(content: string) {
|
||||
const safeContent = escapeHtml(content).replace(/\n/g, '<br/>');
|
||||
const safeContent = textUtil.escapeHtml(content).replace(/\n/g, '<br/>');
|
||||
this.updateContent(safeContent);
|
||||
}
|
||||
|
||||
@ -95,7 +94,7 @@ export class TextPanelCtrl extends PanelCtrl {
|
||||
console.log('Text panel error: ', e);
|
||||
}
|
||||
|
||||
this.content = this.$sce.trustAsHtml(config.disableSanitizeHtml ? html : sanitize(html));
|
||||
this.content = this.$sce.trustAsHtml(config.disableSanitizeHtml ? html : textUtil.sanitize(html));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,11 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
import { renderMarkdown } from '@grafana/data';
|
||||
|
||||
import { PanelProps, renderMarkdown, textUtil } from '@grafana/data';
|
||||
// Utils
|
||||
import { sanitize } from 'app/core/utils/text';
|
||||
import config from 'app/core/config';
|
||||
|
||||
// Types
|
||||
import { TextOptions } from './types';
|
||||
import { PanelProps } from '@grafana/data';
|
||||
|
||||
interface Props extends PanelProps<TextOptions> {}
|
||||
interface State {
|
||||
@ -43,7 +39,7 @@ export class TextPanel extends PureComponent<Props, State> {
|
||||
|
||||
html = replaceVariables(html, {}, 'html');
|
||||
|
||||
return config.disableSanitizeHtml ? html : sanitize(html);
|
||||
return config.disableSanitizeHtml ? html : textUtil.sanitize(html);
|
||||
}
|
||||
|
||||
prepareText(content: string): string {
|
||||
|
@ -6,12 +6,12 @@ import Drop from 'tether-drop';
|
||||
|
||||
// Utils and servies
|
||||
import { colors } from '@grafana/ui';
|
||||
import { setBackendSrv, setDataSourceSrv } from '@grafana/runtime';
|
||||
import { getTemplateSrv, setBackendSrv, setDataSourceSrv } from '@grafana/runtime';
|
||||
import config from 'app/core/config';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { profiler } from 'app/core/profiler';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { TimeSrv, setTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { TimeSrv, setTimeSrv, getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { KeybindingSrv, setKeybindingSrv } from 'app/core/services/keybindingSrv';
|
||||
import { AngularLoader, setAngularLoader } from 'app/core/services/AngularLoader';
|
||||
@ -29,7 +29,7 @@ import { BridgeSrv } from 'app/core/services/bridge_srv';
|
||||
import { PlaylistSrv } from 'app/features/playlist/playlist_srv';
|
||||
import { DashboardSrv, setDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { ILocationService, ITimeoutService, IRootScopeService, IAngularEvent } from 'angular';
|
||||
import { AppEvent, AppEvents } from '@grafana/data';
|
||||
import { AppEvent, AppEvents, locationUtil } from '@grafana/data';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
export type GrafanaRootScope = IRootScopeService & AppEventEmitter & AppEventConsumer & { colors: string[] };
|
||||
@ -58,6 +58,13 @@ export class GrafanaCtrl {
|
||||
setKeybindingSrv(keybindingSrv);
|
||||
setDashboardSrv(dashboardSrv);
|
||||
|
||||
locationUtil.initialize({
|
||||
getConfig: () => config,
|
||||
getTimeRangeForUrl: getTimeSrv().timeRangeForUrl,
|
||||
// @ts-ignore
|
||||
buildParamsFromVariables: getTemplateSrv().fillVariableValuesForUrl,
|
||||
});
|
||||
|
||||
const store = configureStore();
|
||||
setLocationSrv({
|
||||
update: (opt: LocationUpdate) => {
|
||||
|
@ -1,9 +1,8 @@
|
||||
import coreModule from 'app/core/core_module';
|
||||
import locationUtil from 'app/core/utils/location_util';
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { locationUtil, UrlQueryMap } from '@grafana/data';
|
||||
import { DashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
||||
import { ILocationService } from 'angular';
|
||||
import { Scope, CoreEvents, AppEventEmitter } from 'app/types';
|
||||
import { AppEventEmitter, CoreEvents, Scope } from 'app/types';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
export class LoadDashboardCtrl {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { UrlQueryMap } from '@grafana/runtime';
|
||||
import { UrlQueryMap } from '@grafana/data';
|
||||
|
||||
export interface LocationState {
|
||||
url: string;
|
||||
|
Loading…
Reference in New Issue
Block a user