mirror of
https://github.com/grafana/grafana.git
synced 2025-12-21 20:24:41 +08:00
Compare commits
31 Commits
sriram/pos
...
v6.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c11bbdfb4 | ||
|
|
872bc2d973 | ||
|
|
cbace87b56 | ||
|
|
f59b9b6545 | ||
|
|
3ac81e50d7 | ||
|
|
0378c66dcd | ||
|
|
79de911d0a | ||
|
|
eecd09d1c8 | ||
|
|
14ae363aaa | ||
|
|
18a92cc540 | ||
|
|
cfb8912200 | ||
|
|
47c57a1b9d | ||
|
|
ce3f43c6d0 | ||
|
|
6717d43921 | ||
|
|
a951bab782 | ||
|
|
841e140f5b | ||
|
|
bbd2014e9d | ||
|
|
8ce48b98dc | ||
|
|
60419f7e72 | ||
|
|
38e4db88d1 | ||
|
|
d619c529f0 | ||
|
|
a8643d89be | ||
|
|
a7c52c7dc8 | ||
|
|
7ad14532a3 | ||
|
|
23f977f000 | ||
|
|
c172fe8915 | ||
|
|
ddeee1820d | ||
|
|
f28fd41c3b | ||
|
|
57fb967fec | ||
|
|
9046263122 | ||
|
|
2306826cff |
@@ -1211,6 +1211,7 @@ workflows:
|
|||||||
- shellcheck
|
- shellcheck
|
||||||
- mysql-integration-test
|
- mysql-integration-test
|
||||||
- postgres-integration-test
|
- postgres-integration-test
|
||||||
|
filters: *filter-only-master
|
||||||
- build-ee-msi:
|
- build-ee-msi:
|
||||||
requires:
|
requires:
|
||||||
- build-all-enterprise
|
- build-all-enterprise
|
||||||
@@ -1323,6 +1324,7 @@ workflows:
|
|||||||
- shellcheck
|
- shellcheck
|
||||||
- mysql-integration-test
|
- mysql-integration-test
|
||||||
- postgres-integration-test
|
- postgres-integration-test
|
||||||
|
filters: *filter-only-release
|
||||||
- build-ee-msi:
|
- build-ee-msi:
|
||||||
requires:
|
requires:
|
||||||
- build-all-enterprise
|
- build-all-enterprise
|
||||||
|
|||||||
@@ -222,7 +222,7 @@
|
|||||||
"text": "A",
|
"text": "A",
|
||||||
"value": ["A"]
|
"value": ["A"]
|
||||||
},
|
},
|
||||||
"datasource": "TestData DB-1",
|
"datasource": "gdev-testdata",
|
||||||
"definition": "*",
|
"definition": "*",
|
||||||
"hide": 0,
|
"hide": 0,
|
||||||
"includeAll": true,
|
"includeAll": true,
|
||||||
@@ -247,7 +247,7 @@
|
|||||||
"text": "AA",
|
"text": "AA",
|
||||||
"value": ["AA"]
|
"value": ["AA"]
|
||||||
},
|
},
|
||||||
"datasource": "TestData DB-1",
|
"datasource": "gdev-testdata",
|
||||||
"definition": "$datacenter.*",
|
"definition": "$datacenter.*",
|
||||||
"hide": 0,
|
"hide": 0,
|
||||||
"includeAll": true,
|
"includeAll": true,
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"packages": ["packages/*"],
|
"packages": ["packages/*"],
|
||||||
"version": "6.6.0-pre"
|
"version": "6.6.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "grafana",
|
"name": "grafana",
|
||||||
"version": "6.6.0-pre",
|
"version": "6.6.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://github.com/grafana/grafana.git"
|
"url": "http://github.com/grafana/grafana.git"
|
||||||
@@ -266,6 +266,7 @@
|
|||||||
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
|
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
|
||||||
"tinycolor2": "1.4.1",
|
"tinycolor2": "1.4.1",
|
||||||
"tti-polyfill": "0.2.2",
|
"tti-polyfill": "0.2.2",
|
||||||
|
"url-search-params-polyfill": "7.0.1",
|
||||||
"xss": "1.0.3"
|
"xss": "1.0.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/data",
|
"name": "@grafana/data",
|
||||||
"version": "6.6.0-pre",
|
"version": "6.6.0",
|
||||||
"description": "Grafana Data Library",
|
"description": "Grafana Data Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"typescript"
|
"typescript"
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ export abstract class DataSourceApi<
|
|||||||
*/
|
*/
|
||||||
annotationQuery?(options: AnnotationQueryRequest<TQuery>): Promise<AnnotationEvent[]>;
|
annotationQuery?(options: AnnotationQueryRequest<TQuery>): Promise<AnnotationEvent[]>;
|
||||||
|
|
||||||
interpolateVariablesInQueries?(queries: TQuery[]): TQuery[];
|
interpolateVariablesInQueries?(queries: TQuery[], scopedVars: ScopedVars | {}): TQuery[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataInspectorProps<
|
export interface MetadataInspectorProps<
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export interface LogRowModel {
|
|||||||
logLevel: LogLevel;
|
logLevel: LogLevel;
|
||||||
raw: string;
|
raw: string;
|
||||||
searchWords?: string[];
|
searchWords?: string[];
|
||||||
timestamp: string; // ISO with nanosec precision
|
|
||||||
timeFromNow: string;
|
timeFromNow: string;
|
||||||
timeEpochMs: number;
|
timeEpochMs: number;
|
||||||
timeLocal: string;
|
timeLocal: string;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface PanelModel<TOptions = any> {
|
|||||||
id: number;
|
id: number;
|
||||||
options: TOptions;
|
options: TOptions;
|
||||||
pluginVersion?: string;
|
pluginVersion?: string;
|
||||||
|
scopedVars?: ScopedVars;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
getParser,
|
getParser,
|
||||||
LogsParsers,
|
LogsParsers,
|
||||||
calculateStats,
|
calculateStats,
|
||||||
|
getLogLevelFromKey,
|
||||||
} from './logs';
|
} from './logs';
|
||||||
|
|
||||||
describe('getLoglevel()', () => {
|
describe('getLoglevel()', () => {
|
||||||
@@ -23,6 +24,10 @@ describe('getLoglevel()', () => {
|
|||||||
expect(getLogLevel('[Warn]')).toBe('warning');
|
expect(getLogLevel('[Warn]')).toBe('warning');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns correct log level when level is capitalized', () => {
|
||||||
|
expect(getLogLevel('WARN')).toBe(LogLevel.warn);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns log level on line contains a log level', () => {
|
it('returns log level on line contains a log level', () => {
|
||||||
expect(getLogLevel('warn: it is looking bad')).toBe(LogLevel.warn);
|
expect(getLogLevel('warn: it is looking bad')).toBe(LogLevel.warn);
|
||||||
expect(getLogLevel('2007-12-12 12:12:12 [WARN]: it is looking bad')).toBe(LogLevel.warn);
|
expect(getLogLevel('2007-12-12 12:12:12 [WARN]: it is looking bad')).toBe(LogLevel.warn);
|
||||||
@@ -33,6 +38,15 @@ describe('getLoglevel()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getLogLevelFromKey()', () => {
|
||||||
|
it('returns correct log level', () => {
|
||||||
|
expect(getLogLevelFromKey('info')).toBe(LogLevel.info);
|
||||||
|
});
|
||||||
|
it('returns correct log level when level is capitalized', () => {
|
||||||
|
expect(getLogLevelFromKey('INFO')).toBe(LogLevel.info);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('calculateLogsLabelStats()', () => {
|
describe('calculateLogsLabelStats()', () => {
|
||||||
test('should return no stats for empty rows', () => {
|
test('should return no stats for empty rows', () => {
|
||||||
expect(calculateLogsLabelStats([], '')).toEqual([]);
|
expect(calculateLogsLabelStats([], '')).toEqual([]);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function getLogLevel(line: string): LogLevel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getLogLevelFromKey(key: string): LogLevel {
|
export function getLogLevelFromKey(key: string): LogLevel {
|
||||||
const level = (LogLevel as any)[key];
|
const level = (LogLevel as any)[key.toLowerCase()];
|
||||||
if (level) {
|
if (level) {
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/e2e",
|
"name": "@grafana/e2e",
|
||||||
"version": "6.4.0-pre",
|
"version": "6.6.0",
|
||||||
"description": "Grafana End to End Test Library",
|
"description": "Grafana End to End Test Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"grafana",
|
"grafana",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/runtime",
|
"name": "@grafana/runtime",
|
||||||
"version": "6.6.0-pre",
|
"version": "6.6.0",
|
||||||
"description": "Grafana Runtime Library",
|
"description": "Grafana Runtime Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"grafana",
|
"grafana",
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
"build": "grafana-toolkit package:build --scope=runtime"
|
"build": "grafana-toolkit package:build --scope=runtime"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grafana/data": "^6.6.0-pre",
|
"@grafana/data": "6.6.0",
|
||||||
"@grafana/ui": "^6.6.0-pre",
|
"@grafana/ui": "6.6.0",
|
||||||
"systemjs": "0.20.19",
|
"systemjs": "0.20.19",
|
||||||
"systemjs-plugin-css": "0.1.37"
|
"systemjs-plugin-css": "0.1.37"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface BuildInfo {
|
|||||||
commit: string;
|
commit: string;
|
||||||
isEnterprise: boolean; // deprecated: use licenseInfo.hasLicense instead
|
isEnterprise: boolean; // deprecated: use licenseInfo.hasLicense instead
|
||||||
env: string;
|
env: string;
|
||||||
|
edition: string;
|
||||||
latestVersion: string;
|
latestVersion: string;
|
||||||
hasUpdate: boolean;
|
hasUpdate: boolean;
|
||||||
}
|
}
|
||||||
@@ -21,6 +22,8 @@ interface FeatureToggles {
|
|||||||
interface LicenseInfo {
|
interface LicenseInfo {
|
||||||
hasLicense: boolean;
|
hasLicense: boolean;
|
||||||
expiry: number;
|
expiry: number;
|
||||||
|
licenseUrl: string;
|
||||||
|
stateInfo: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GrafanaBootConfig {
|
export class GrafanaBootConfig {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/toolkit",
|
"name": "@grafana/toolkit",
|
||||||
"version": "6.6.0-pre",
|
"version": "6.6.0",
|
||||||
"description": "Grafana Toolkit",
|
"description": "Grafana Toolkit",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"grafana",
|
"grafana",
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "7.6.4",
|
"@babel/core": "7.6.4",
|
||||||
"@babel/preset-env": "7.6.3",
|
"@babel/preset-env": "7.6.3",
|
||||||
"@grafana/data": "^6.6.0-pre",
|
"@grafana/data": "6.6.0",
|
||||||
"@grafana/ui": "^6.6.0-pre",
|
"@grafana/ui": "6.6.0",
|
||||||
"@types/command-exists": "^1.2.0",
|
"@types/command-exists": "^1.2.0",
|
||||||
"@types/execa": "^0.9.0",
|
"@types/execa": "^0.9.0",
|
||||||
"@types/expect-puppeteer": "3.3.1",
|
"@types/expect-puppeteer": "3.3.1",
|
||||||
@@ -62,9 +62,12 @@
|
|||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"inquirer": "^6.3.1",
|
"inquirer": "^6.3.1",
|
||||||
"jest": "24.8.0",
|
"jest": "24.8.0",
|
||||||
|
"jest-canvas-mock": "2.1.2",
|
||||||
"jest-cli": "^24.8.0",
|
"jest-cli": "^24.8.0",
|
||||||
"jest-coverage-badges": "^1.1.2",
|
"jest-coverage-badges": "^1.1.2",
|
||||||
"jest-junit": "^6.4.0",
|
"jest-junit": "^6.4.0",
|
||||||
|
"less": "^3.10.3",
|
||||||
|
"less-loader": "^5.0.0",
|
||||||
"lodash": "4.17.15",
|
"lodash": "4.17.15",
|
||||||
"md5-file": "^4.0.0",
|
"md5-file": "^4.0.0",
|
||||||
"mini-css-extract-plugin": "^0.7.0",
|
"mini-css-extract-plugin": "^0.7.0",
|
||||||
@@ -95,9 +98,7 @@
|
|||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typescript": "3.7.2",
|
"typescript": "3.7.2",
|
||||||
"url-loader": "^2.0.1",
|
"url-loader": "^2.0.1",
|
||||||
"webpack": "4.35.0",
|
"webpack": "4.35.0"
|
||||||
"less": "^3.10.3",
|
|
||||||
"less-loader": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"_moduleAliases": {
|
"_moduleAliases": {
|
||||||
"puppeteer": "node_modules/puppeteer-core"
|
"puppeteer": "node_modules/puppeteer-core"
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ export const prepare = useSpinner<void>('Preparing', async () => {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
// Copy only if local tsconfig does not exist. Otherwise this will work, but have odd behavior
|
// Copy only if local tsconfig does not exist. Otherwise this will work, but have odd behavior
|
||||||
copyIfNonExistent(
|
copyIfNonExistent(
|
||||||
resolvePath(process.cwd(), 'tsconfig.json'),
|
resolvePath(__dirname, '../../config/tsconfig.plugin.local.json'),
|
||||||
resolvePath(__dirname, '../../config/tsconfig.plugin.local.json')
|
resolvePath(process.cwd(), 'tsconfig.json')
|
||||||
),
|
),
|
||||||
// Copy only if local prettierrc does not exist. Otherwise this will work, but have odd behavior
|
// Copy only if local prettierrc does not exist. Otherwise this will work, but have odd behavior
|
||||||
copyIfNonExistent(
|
copyIfNonExistent(
|
||||||
resolvePath(process.cwd(), '.prettierrc.js'),
|
resolvePath(__dirname, '../../config/prettier.plugin.rc.js'),
|
||||||
resolvePath(__dirname, '../../config/prettier.plugin.rc.js')
|
resolvePath(process.cwd(), '.prettierrc.js')
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const jestConfig = (baseDir: string = process.cwd()) => {
|
|||||||
const setupFile = getSetupFile(setupFilePath);
|
const setupFile = getSetupFile(setupFilePath);
|
||||||
const shimsFile = getSetupFile(shimsFilePath);
|
const shimsFile = getSetupFile(shimsFilePath);
|
||||||
|
|
||||||
const setupFiles = [setupFile, shimsFile].filter(f => f);
|
const setupFiles = [setupFile, shimsFile, 'jest-canvas-mock'].filter(f => f);
|
||||||
const defaultJestConfig = {
|
const defaultJestConfig = {
|
||||||
preset: 'ts-jest',
|
preset: 'ts-jest',
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "@grafana/ui",
|
"name": "@grafana/ui",
|
||||||
"version": "6.6.0-pre",
|
"version": "6.6.0",
|
||||||
"description": "Grafana Components Library",
|
"description": "Grafana Components Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"grafana",
|
"grafana",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"build": "grafana-toolkit package:build --scope=ui"
|
"build": "grafana-toolkit package:build --scope=ui"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grafana/data": "^6.6.0-pre",
|
"@grafana/data": "6.6.0",
|
||||||
"@grafana/slate-react": "0.22.9-grafana",
|
"@grafana/slate-react": "0.22.9-grafana",
|
||||||
"@torkelo/react-select": "2.1.1",
|
"@torkelo/react-select": "2.1.1",
|
||||||
"@types/react-color": "2.17.0",
|
"@types/react-color": "2.17.0",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
background: none;
|
background: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.fa {
|
.fa {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
@@ -78,6 +79,11 @@
|
|||||||
|
|
||||||
.alert-body {
|
.alert-body {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $white;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-icon-on-top {
|
.alert-icon-on-top {
|
||||||
|
|||||||
@@ -363,8 +363,9 @@ export class StackedWithChartLayout extends BigValueLayout {
|
|||||||
|
|
||||||
// make title fontsize it's a bit smaller than valueFontSize
|
// make title fontsize it's a bit smaller than valueFontSize
|
||||||
this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize);
|
this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize);
|
||||||
|
|
||||||
// make chart take up onused space
|
// make chart take up onused space
|
||||||
this.chartHeight = height - this.titleFontSize * LINE_HEIGHT - this.valueFontSize * LINE_HEIGHT + height * 0.05;
|
this.chartHeight = height - this.titleFontSize * LINE_HEIGHT - this.valueFontSize * LINE_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
getValueAndTitleContainerStyles() {
|
getValueAndTitleContainerStyles() {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ const setup = (propOverrides?: Partial<Props>, rowOverrides?: Partial<LogRowMode
|
|||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
entry: '',
|
entry: '',
|
||||||
raw: '',
|
raw: '',
|
||||||
timestamp: '',
|
|
||||||
uid: '0',
|
uid: '0',
|
||||||
labels: {},
|
labels: {},
|
||||||
...(rowOverrides || {}),
|
...(rowOverrides || {}),
|
||||||
|
|||||||
@@ -92,17 +92,28 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<tr className={cx(style.logDetailsValue, { [styles.noHoverBackground]: showFieldsStats })}>
|
<tr className={cx(style.logDetailsValue, { [styles.noHoverBackground]: showFieldsStats })}>
|
||||||
{/* Action buttons - show stats/filter results */}
|
{/* Action buttons - show stats/filter results */}
|
||||||
<td title="Ad-hoc statistics" onClick={this.showStats} className={style.logsDetailsIcon}>
|
<td className={style.logsDetailsIcon} colSpan={isLabel ? undefined : 3}>
|
||||||
<i className={`fa fa-signal ${styles.hoverCursor}`} />
|
<i title="Ad-hoc statistics" className={`fa fa-signal ${styles.hoverCursor}`} onClick={this.showStats} />
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td title="Filter for value" onClick={() => isLabel && this.filterLabel()} className={style.logsDetailsIcon}>
|
{isLabel && (
|
||||||
{isLabel && <i className={`fa fa-search-plus ${styles.hoverCursor}`} />}
|
<>
|
||||||
</td>
|
<td className={style.logsDetailsIcon}>
|
||||||
|
<i
|
||||||
<td title="Filter out value" onClick={() => isLabel && this.filterOutLabel()} className={style.logsDetailsIcon}>
|
title="Filter for value"
|
||||||
{isLabel && <i className={`fa fa-search-minus ${styles.hoverCursor}`} />}
|
className={`fa fa-search-plus ${styles.hoverCursor}`}
|
||||||
</td>
|
onClick={this.filterLabel}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td className={style.logsDetailsIcon}>
|
||||||
|
<i
|
||||||
|
title="Filter out value"
|
||||||
|
className={`fa fa-search-minus ${styles.hoverCursor}`}
|
||||||
|
onClick={this.filterOutLabel}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Key - value columns */}
|
{/* Key - value columns */}
|
||||||
<td className={style.logDetailsLabel}>{parsedKey}</td>
|
<td className={style.logDetailsLabel}>{parsedKey}</td>
|
||||||
|
|||||||
@@ -3,7 +3,38 @@ import { getRowContexts } from './LogRowContextProvider';
|
|||||||
|
|
||||||
describe('getRowContexts', () => {
|
describe('getRowContexts', () => {
|
||||||
describe('when called with a DataFrame and results are returned', () => {
|
describe('when called with a DataFrame and results are returned', () => {
|
||||||
it('then the result should be in correct format', async () => {
|
it('then the result should be in correct format and filtered', async () => {
|
||||||
|
const firstResult = new MutableDataFrame({
|
||||||
|
refId: 'B',
|
||||||
|
fields: [
|
||||||
|
{ name: 'ts', type: FieldType.time, values: [3, 2, 1] },
|
||||||
|
{ name: 'line', type: FieldType.string, values: ['3', '2', '1'], labels: {} },
|
||||||
|
{ name: 'id', type: FieldType.string, values: ['3', '2', '1'], labels: {} },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const secondResult = new MutableDataFrame({
|
||||||
|
refId: 'B',
|
||||||
|
fields: [
|
||||||
|
{ name: 'ts', type: FieldType.time, values: [6, 5, 4] },
|
||||||
|
{ name: 'line', type: FieldType.string, values: ['6', '5', '4'], labels: {} },
|
||||||
|
{ name: 'id', type: FieldType.string, values: ['6', '5', '4'], labels: {} },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
let called = false;
|
||||||
|
const getRowContextMock = (row: LogRowModel, options?: any): Promise<DataQueryResponse> => {
|
||||||
|
if (!called) {
|
||||||
|
called = true;
|
||||||
|
return Promise.resolve({ data: [firstResult] });
|
||||||
|
}
|
||||||
|
return Promise.resolve({ data: [secondResult] });
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await getRowContexts(getRowContextMock, row, 10);
|
||||||
|
|
||||||
|
expect(result).toEqual({ data: [[['3', '2']], [['6', '5', '4']]], errors: ['', ''] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('then the result should be in correct format and filtered without uid', async () => {
|
||||||
const firstResult = new MutableDataFrame({
|
const firstResult = new MutableDataFrame({
|
||||||
refId: 'B',
|
refId: 'B',
|
||||||
fields: [
|
fields: [
|
||||||
@@ -18,23 +49,6 @@ describe('getRowContexts', () => {
|
|||||||
{ name: 'line', type: FieldType.string, values: ['6', '5', '4'], labels: {} },
|
{ name: 'line', type: FieldType.string, values: ['6', '5', '4'], labels: {} },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const row: LogRowModel = {
|
|
||||||
entryFieldIndex: 0,
|
|
||||||
rowIndex: 0,
|
|
||||||
dataFrame: new MutableDataFrame(),
|
|
||||||
entry: '4',
|
|
||||||
labels: (null as any) as Labels,
|
|
||||||
hasAnsi: false,
|
|
||||||
raw: '4',
|
|
||||||
logLevel: LogLevel.info,
|
|
||||||
timeEpochMs: 4,
|
|
||||||
timeFromNow: '',
|
|
||||||
timeLocal: '',
|
|
||||||
timeUtc: '',
|
|
||||||
timestamp: '4',
|
|
||||||
uid: '1',
|
|
||||||
};
|
|
||||||
|
|
||||||
let called = false;
|
let called = false;
|
||||||
const getRowContextMock = (row: LogRowModel, options?: any): Promise<DataQueryResponse> => {
|
const getRowContextMock = (row: LogRowModel, options?: any): Promise<DataQueryResponse> => {
|
||||||
if (!called) {
|
if (!called) {
|
||||||
@@ -46,7 +60,7 @@ describe('getRowContexts', () => {
|
|||||||
|
|
||||||
const result = await getRowContexts(getRowContextMock, row, 10);
|
const result = await getRowContexts(getRowContextMock, row, 10);
|
||||||
|
|
||||||
expect(result).toEqual({ data: [[['3', '2', '1']], [['6', '5', '4']]], errors: ['', ''] });
|
expect(result).toEqual({ data: [[['3', '2', '1']], [['6', '5']]], errors: ['', ''] });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,23 +68,6 @@ describe('getRowContexts', () => {
|
|||||||
it('then the result should be in correct format', async () => {
|
it('then the result should be in correct format', async () => {
|
||||||
const firstError = new Error('Error 1');
|
const firstError = new Error('Error 1');
|
||||||
const secondError = new Error('Error 2');
|
const secondError = new Error('Error 2');
|
||||||
const row: LogRowModel = {
|
|
||||||
entryFieldIndex: 0,
|
|
||||||
rowIndex: 0,
|
|
||||||
dataFrame: new MutableDataFrame(),
|
|
||||||
entry: '4',
|
|
||||||
labels: (null as any) as Labels,
|
|
||||||
hasAnsi: false,
|
|
||||||
raw: '4',
|
|
||||||
logLevel: LogLevel.info,
|
|
||||||
timeEpochMs: 4,
|
|
||||||
timeFromNow: '',
|
|
||||||
timeLocal: '',
|
|
||||||
timeUtc: '',
|
|
||||||
timestamp: '4',
|
|
||||||
uid: '1',
|
|
||||||
};
|
|
||||||
|
|
||||||
let called = false;
|
let called = false;
|
||||||
const getRowContextMock = (row: LogRowModel, options?: any): Promise<DataQueryResponse> => {
|
const getRowContextMock = (row: LogRowModel, options?: any): Promise<DataQueryResponse> => {
|
||||||
if (!called) {
|
if (!called) {
|
||||||
@@ -86,3 +83,19 @@ describe('getRowContexts', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const row: LogRowModel = {
|
||||||
|
entryFieldIndex: 0,
|
||||||
|
rowIndex: 0,
|
||||||
|
dataFrame: new MutableDataFrame(),
|
||||||
|
entry: '4',
|
||||||
|
labels: (null as any) as Labels,
|
||||||
|
hasAnsi: false,
|
||||||
|
raw: '4',
|
||||||
|
logLevel: LogLevel.info,
|
||||||
|
timeEpochMs: 4,
|
||||||
|
timeFromNow: '',
|
||||||
|
timeLocal: '',
|
||||||
|
timeUtc: '',
|
||||||
|
uid: '1',
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LogRowModel, toDataFrame, Field } from '@grafana/data';
|
import { LogRowModel, toDataFrame, Field, FieldCache } from '@grafana/data';
|
||||||
import { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import flatten from 'lodash/flatten';
|
import flatten from 'lodash/flatten';
|
||||||
import useAsync from 'react-use/lib/useAsync';
|
import useAsync from 'react-use/lib/useAsync';
|
||||||
|
|
||||||
@@ -45,7 +45,8 @@ export const getRowContexts = async (
|
|||||||
limit,
|
limit,
|
||||||
}),
|
}),
|
||||||
getRowContext(row, {
|
getRowContext(row, {
|
||||||
limit: limit + 1, // Lets add one more to the limit as we're filtering out one row see comment below
|
// The start time is inclusive so we will get the one row we are using as context entry
|
||||||
|
limit: limit + 1,
|
||||||
direction: 'FORWARD',
|
direction: 'FORWARD',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
@@ -62,16 +63,33 @@ export const getRowContexts = async (
|
|||||||
const data: any[] = [];
|
const data: any[] = [];
|
||||||
for (let index = 0; index < dataResult.data.length; index++) {
|
for (let index = 0; index < dataResult.data.length; index++) {
|
||||||
const dataFrame = toDataFrame(dataResult.data[index]);
|
const dataFrame = toDataFrame(dataResult.data[index]);
|
||||||
const timestampField: Field<string> = dataFrame.fields.filter(field => field.name === 'ts')[0];
|
const fieldCache = new FieldCache(dataFrame);
|
||||||
|
const timestampField: Field<string> = fieldCache.getFieldByName('ts')!;
|
||||||
|
const idField: Field<string> | undefined = fieldCache.getFieldByName('id');
|
||||||
|
|
||||||
for (let fieldIndex = 0; fieldIndex < timestampField.values.length; fieldIndex++) {
|
for (let fieldIndex = 0; fieldIndex < timestampField.values.length; fieldIndex++) {
|
||||||
const timestamp = timestampField.values.get(fieldIndex);
|
// TODO: this filtering is datasource dependant so it will make sense to move it there so the API is
|
||||||
|
// to return correct list of lines handling inclusive ranges or how to filter the correct line on the
|
||||||
|
// datasource.
|
||||||
|
|
||||||
// We need to filter out the row we're basing our search from because of how start/end params work in Loki API
|
// Filter out the row that is the one used as a focal point for the context as we will get it in one of the
|
||||||
// see https://github.com/grafana/loki/issues/597#issuecomment-506408980
|
// requests.
|
||||||
// the alternative to create our own add 1 nanosecond method to the a timestamp string would be quite complex
|
if (idField) {
|
||||||
if (timestamp === row.timestamp) {
|
// For Loki this means we filter only the one row. Issue is we could have other rows logged at the same
|
||||||
continue;
|
// ns which came before but they come in the response that search for logs after. This means right now
|
||||||
|
// we will show those as if they came after. This is not strictly correct but seems better than loosing them
|
||||||
|
// and making this correct would mean quite a bit of complexity to shuffle things around and messing up
|
||||||
|
//counts.
|
||||||
|
if (idField.values.get(fieldIndex) === row.uid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to timestamp. This should not happen right now as this feature is implemented only for loki
|
||||||
|
// and that has ID. Later this branch could be used in other DS but mind that this could also filter out
|
||||||
|
// logs which were logged in the same timestamp and that can be a problem depending on the precision.
|
||||||
|
if (parseInt(timestampField.values.get(fieldIndex), 10) === row.timeEpochMs) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineField: Field<string> = dataFrame.fields.filter(field => field.name === 'line')[0];
|
const lineField: Field<string> = dataFrame.fields.filter(field => field.name === 'line')[0];
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ const makeLog = (overrides: Partial<LogRowModel>): LogRowModel => {
|
|||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
raw: entry,
|
raw: entry,
|
||||||
timestamp: '',
|
|
||||||
timeFromNow: '',
|
timeFromNow: '',
|
||||||
timeEpochMs: 1,
|
timeEpochMs: 1,
|
||||||
timeLocal: '',
|
timeLocal: '',
|
||||||
|
|||||||
@@ -151,11 +151,10 @@ export function sharedSingleStatMigrationHandler(panel: PanelModel<SingleStatBas
|
|||||||
|
|
||||||
// Migrate color from simple string to a mode
|
// Migrate color from simple string to a mode
|
||||||
const { defaults } = fieldOptions;
|
const { defaults } = fieldOptions;
|
||||||
if (defaults.color) {
|
if (defaults.color && typeof defaults.color === 'string') {
|
||||||
const old = defaults.color;
|
|
||||||
defaults.color = {
|
defaults.color = {
|
||||||
mode: FieldColorMode.Fixed,
|
mode: FieldColorMode.Fixed,
|
||||||
fixedColor: old,
|
fixedColor: defaults.color,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
TIME_FORMAT,
|
TIME_FORMAT,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { stringToDateTimeType } from '../time';
|
import { stringToDateTimeType } from '../time';
|
||||||
import { isMathString } from '@grafana/data/src/datetime/datemath';
|
|
||||||
|
|
||||||
export const mapOptionToTimeRange = (option: TimeOption, timeZone?: TimeZone): TimeRange => {
|
export const mapOptionToTimeRange = (option: TimeOption, timeZone?: TimeZone): TimeRange => {
|
||||||
return {
|
return {
|
||||||
@@ -41,7 +40,7 @@ export const mapStringsToTimeRange = (from: string, to: string, roundup?: boolea
|
|||||||
const fromDate = stringToDateTimeType(from, roundup, timeZone);
|
const fromDate = stringToDateTimeType(from, roundup, timeZone);
|
||||||
const toDate = stringToDateTimeType(to, roundup, timeZone);
|
const toDate = stringToDateTimeType(to, roundup, timeZone);
|
||||||
|
|
||||||
if (isMathString(from) || isMathString(to)) {
|
if (dateMath.isMathString(from) || dateMath.isMathString(to)) {
|
||||||
return {
|
return {
|
||||||
from: fromDate,
|
from: fromDate,
|
||||||
to: toDate,
|
to: toDate,
|
||||||
|
|||||||
@@ -14,3 +14,4 @@
|
|||||||
@import 'TimePicker/TimeOfDayPicker';
|
@import 'TimePicker/TimeOfDayPicker';
|
||||||
@import 'Tooltip/Tooltip';
|
@import 'Tooltip/Tooltip';
|
||||||
@import 'ValueMappingsEditor/ValueMappingsEditor';
|
@import 'ValueMappingsEditor/ValueMappingsEditor';
|
||||||
|
@import 'Alert/Alert';
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
|
|||||||
"version": setting.BuildVersion,
|
"version": setting.BuildVersion,
|
||||||
"commit": setting.BuildCommit,
|
"commit": setting.BuildCommit,
|
||||||
"buildstamp": setting.BuildStamp,
|
"buildstamp": setting.BuildStamp,
|
||||||
|
"edition": hs.License.Edition(),
|
||||||
"latestVersion": plugins.GrafanaLatestVersion,
|
"latestVersion": plugins.GrafanaLatestVersion,
|
||||||
"hasUpdate": plugins.GrafanaHasUpdate,
|
"hasUpdate": plugins.GrafanaHasUpdate,
|
||||||
"env": setting.Env,
|
"env": setting.Env,
|
||||||
@@ -202,6 +203,8 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
|
|||||||
"licenseInfo": map[string]interface{}{
|
"licenseInfo": map[string]interface{}{
|
||||||
"hasLicense": hs.License.HasLicense(),
|
"hasLicense": hs.License.HasLicense(),
|
||||||
"expiry": hs.License.Expiry(),
|
"expiry": hs.License.Expiry(),
|
||||||
|
"stateInfo": hs.License.StateInfo(),
|
||||||
|
"licenseUrl": hs.License.LicenseURL(c.SignedInUser),
|
||||||
},
|
},
|
||||||
"featureToggles": hs.Cfg.FeatureToggles,
|
"featureToggles": hs.Cfg.FeatureToggles,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.OrgRole == m.ROLE_ADMIN || hs.Cfg.EditorsCanAdmin {
|
if c.OrgRole == m.ROLE_ADMIN || (hs.Cfg.EditorsCanAdmin && c.OrgRole == m.ROLE_EDITOR) {
|
||||||
configNodes = append(configNodes, &dtos.NavLink{
|
configNodes = append(configNodes, &dtos.NavLink{
|
||||||
Text: "Teams",
|
Text: "Teams",
|
||||||
Id: "teams",
|
Id: "teams",
|
||||||
@@ -357,7 +357,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
|
|||||||
Children: []*dtos.NavLink{},
|
Children: []*dtos.NavLink{},
|
||||||
})
|
})
|
||||||
|
|
||||||
hs.HooksService.RunIndexDataHooks(&data)
|
hs.HooksService.RunIndexDataHooks(&data, c)
|
||||||
|
|
||||||
sort.SliceStable(data.NavTree, func(i, j int) bool {
|
sort.SliceStable(data.NavTree, func(i, j int) bool {
|
||||||
return data.NavTree[i].SortWeight < data.NavTree[j].SortWeight
|
return data.NavTree[i].SortWeight < data.NavTree[j].SortWeight
|
||||||
|
|||||||
@@ -9,4 +9,11 @@ type Licensing interface {
|
|||||||
|
|
||||||
// Expiry returns the unix epoch timestamp when the license expires, or 0 if no valid license is provided
|
// Expiry returns the unix epoch timestamp when the license expires, or 0 if no valid license is provided
|
||||||
Expiry() int64
|
Expiry() int64
|
||||||
|
|
||||||
|
// Return edition
|
||||||
|
Edition() string
|
||||||
|
|
||||||
|
LicenseURL(user *SignedInUser) string
|
||||||
|
|
||||||
|
StateInfo() string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package hooks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexDataHook func(indexData *dtos.IndexViewData)
|
type IndexDataHook func(indexData *dtos.IndexViewData, req *models.ReqContext)
|
||||||
|
|
||||||
type HooksService struct {
|
type HooksService struct {
|
||||||
indexDataHooks []IndexDataHook
|
indexDataHooks []IndexDataHook
|
||||||
@@ -23,8 +24,8 @@ func (srv *HooksService) AddIndexDataHook(hook IndexDataHook) {
|
|||||||
srv.indexDataHooks = append(srv.indexDataHooks, hook)
|
srv.indexDataHooks = append(srv.indexDataHooks, hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *HooksService) RunIndexDataHooks(indexData *dtos.IndexViewData) {
|
func (srv *HooksService) RunIndexDataHooks(indexData *dtos.IndexViewData, req *models.ReqContext) {
|
||||||
for _, hook := range srv.indexDataHooks {
|
for _, hook := range srv.indexDataHooks {
|
||||||
hook(indexData)
|
hook(indexData, req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package licensing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/hooks"
|
"github.com/grafana/grafana/pkg/services/hooks"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
@@ -19,14 +20,30 @@ func (*OSSLicensingService) Expiry() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*OSSLicensingService) Edition() string {
|
||||||
|
return "Open Source"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*OSSLicensingService) StateInfo() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *OSSLicensingService) LicenseURL(user *models.SignedInUser) string {
|
||||||
|
if user.IsGrafanaAdmin {
|
||||||
|
return l.Cfg.AppSubUrl + "/admin/upgrading"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "https://grafana.com/products/enterprise/?utm_source=grafana_footer"
|
||||||
|
}
|
||||||
|
|
||||||
func (l *OSSLicensingService) Init() error {
|
func (l *OSSLicensingService) Init() error {
|
||||||
l.HooksService.AddIndexDataHook(func(indexData *dtos.IndexViewData) {
|
l.HooksService.AddIndexDataHook(func(indexData *dtos.IndexViewData, req *models.ReqContext) {
|
||||||
for _, node := range indexData.NavTree {
|
for _, node := range indexData.NavTree {
|
||||||
if node.Id == "admin" {
|
if node.Id == "admin" {
|
||||||
node.Children = append(node.Children, &dtos.NavLink{
|
node.Children = append(node.Children, &dtos.NavLink{
|
||||||
Text: "Upgrade",
|
Text: "Upgrade",
|
||||||
Id: "upgrading",
|
Id: "upgrading",
|
||||||
Url: l.Cfg.AppSubUrl + "/admin/upgrading",
|
Url: l.LicenseURL(req.SignedInUser),
|
||||||
Icon: "fa fa-fw fa-unlock-alt",
|
Icon: "fa fa-fw fa-unlock-alt",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
|
"github.com/grafana/grafana/pkg/util/errutil"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -155,3 +156,15 @@ func (db *Postgres) IsUniqueConstraintViolation(err error) bool {
|
|||||||
func (db *Postgres) IsDeadlock(err error) bool {
|
func (db *Postgres) IsDeadlock(err error) bool {
|
||||||
return db.isThisError(err, "40P01")
|
return db.isThisError(err, "40P01")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *Postgres) PostInsertId(table string, sess *xorm.Session) error {
|
||||||
|
if table != "org" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync primary key sequence of org table
|
||||||
|
if _, err := sess.Exec("SELECT setval('org_id_seq', (SELECT max(id) FROM org));"); err != nil {
|
||||||
|
return errutil.Wrapf(err, "failed to sync primary key for org table")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,11 +36,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DEV = "development"
|
DEV = "development"
|
||||||
PROD = "production"
|
PROD = "production"
|
||||||
TEST = "test"
|
TEST = "test"
|
||||||
APP_NAME = "Grafana"
|
APP_NAME = "Grafana"
|
||||||
APP_NAME_ENTERPRISE = "Grafana Enterprise"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -619,9 +618,6 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
|||||||
Raw = cfg.Raw
|
Raw = cfg.Raw
|
||||||
|
|
||||||
ApplicationName = APP_NAME
|
ApplicationName = APP_NAME
|
||||||
if IsEnterprise {
|
|
||||||
ApplicationName = APP_NAME_ENTERPRISE
|
|
||||||
}
|
|
||||||
|
|
||||||
Env, err = valueAsString(iniFile.Section(""), "app_mode", "development")
|
Env, err = valueAsString(iniFile.Section(""), "app_mode", "development")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ func init() {
|
|||||||
"AWS/DMS": {"CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCIncomingChanges", "CDCLatencySource", "CDCLatencyTarget", "CDCThroughputBandwidthSource", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CPUUtilization", "FreeStorageSpace", "FreeableMemory", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "ReadIOPS", "ReadLatency", "ReadThroughput", "SwapUsage", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
"AWS/DMS": {"CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCIncomingChanges", "CDCLatencySource", "CDCLatencyTarget", "CDCThroughputBandwidthSource", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CPUUtilization", "FreeStorageSpace", "FreeableMemory", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "ReadIOPS", "ReadLatency", "ReadThroughput", "SwapUsage", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||||
"AWS/DocDB": {"BackupRetentionPeriodStorageUsed", "BufferCacheHitRatio", "CPUUtilization", "DatabaseConnections", "DBInstanceReplicaLag", "DBClusterReplicaLagMaximum", "DBClusterReplicaLagMinimum", "DiskQueueDepth", "EngineUptime", "FreeableMemory", "FreeLocalStorage", "NetworkReceiveThroughput", "NetworkThroughput", "NetworkTransmitThroughput", "ReadIOPS", "ReadLatency", "ReadThroughput", "SnapshotStorageUsed", "SwapUsage", "TotalBackupStorageBilled", "VolumeBytesUsed", "VolumeReadIOPs", "VolumeWriteIOPs", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
"AWS/DocDB": {"BackupRetentionPeriodStorageUsed", "BufferCacheHitRatio", "CPUUtilization", "DatabaseConnections", "DBInstanceReplicaLag", "DBClusterReplicaLagMaximum", "DBClusterReplicaLagMinimum", "DiskQueueDepth", "EngineUptime", "FreeableMemory", "FreeLocalStorage", "NetworkReceiveThroughput", "NetworkThroughput", "NetworkTransmitThroughput", "ReadIOPS", "ReadLatency", "ReadThroughput", "SnapshotStorageUsed", "SwapUsage", "TotalBackupStorageBilled", "VolumeBytesUsed", "VolumeReadIOPs", "VolumeWriteIOPs", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||||
"AWS/DX": {"ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelRx", "ConnectionLightLevelTx", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionState"},
|
"AWS/DX": {"ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelRx", "ConnectionLightLevelTx", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionState"},
|
||||||
|
"AWS/DAX": {"CPUUtilization", "NetworkPacketsIn", "NetworkPacketsOut", "GetItemRequestCount", "BatchGetItemRequestCount", "BatchWriteItemRequestCount", "DeleteItemRequestCount", "PutItemRequestCount", "UpdateItemRequestCount", "TransactWriteItemsCount", "TransactGetItemsCount", "ItemCacheHits", "ItemCacheMisses", "QueryCacheHits", "QueryCacheMisses", "ScanCacheHits", "ScanCacheMisses", "TotalRequestCount", "ErrorRequestCount", "FaultRequestCount", "FailedRequestCount", "QueryRequestCount", "ScanRequestCount", "ClientConnections", "EstimatedDbSize", "EvictedSize"},
|
||||||
"AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "PendingReplicationCount", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReplicationLatency", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "ThrottledRequests", "TimeToLiveDeletedItemCount", "UserErrors", "WriteThrottleEvents"},
|
"AWS/DynamoDB": {"ConditionalCheckFailedRequests", "ConsumedReadCapacityUnits", "ConsumedWriteCapacityUnits", "OnlineIndexConsumedWriteCapacity", "OnlineIndexPercentageProgress", "OnlineIndexThrottleEvents", "PendingReplicationCount", "ProvisionedReadCapacityUnits", "ProvisionedWriteCapacityUnits", "ReadThrottleEvents", "ReplicationLatency", "ReturnedBytes", "ReturnedItemCount", "ReturnedRecordsCount", "SuccessfulRequestLatency", "SystemErrors", "ThrottledRequests", "TimeToLiveDeletedItemCount", "UserErrors", "WriteThrottleEvents"},
|
||||||
"AWS/EBS": {"BurstBalance", "VolumeConsumedReadWriteOps", "VolumeIdleTime", "VolumeQueueLength", "VolumeReadBytes", "VolumeReadOps", "VolumeThroughputPercentage", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeWriteBytes", "VolumeWriteOps"},
|
"AWS/EBS": {"BurstBalance", "VolumeConsumedReadWriteOps", "VolumeIdleTime", "VolumeQueueLength", "VolumeReadBytes", "VolumeReadOps", "VolumeThroughputPercentage", "VolumeTotalReadTime", "VolumeTotalWriteTime", "VolumeWriteBytes", "VolumeWriteOps"},
|
||||||
"AWS/EC2": {"CPUCreditBalance", "CPUCreditUsage", "CPUSurplusCreditBalance", "CPUSurplusCreditsCharged", "CPUUtilization", "DiskReadBytes", "DiskReadOps", "DiskWriteBytes", "DiskWriteOps", "EBSByteBalance%", "EBSIOBalance%", "EBSReadBytes", "EBSReadOps", "EBSWriteBytes", "EBSWriteOps", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"},
|
"AWS/EC2": {"CPUCreditBalance", "CPUCreditUsage", "CPUSurplusCreditBalance", "CPUSurplusCreditsCharged", "CPUUtilization", "DiskReadBytes", "DiskReadOps", "DiskWriteBytes", "DiskWriteOps", "EBSByteBalance%", "EBSIOBalance%", "EBSReadBytes", "EBSReadOps", "EBSWriteBytes", "EBSWriteOps", "NetworkIn", "NetworkOut", "NetworkPacketsIn", "NetworkPacketsOut", "StatusCheckFailed", "StatusCheckFailed_Instance", "StatusCheckFailed_System"},
|
||||||
@@ -140,6 +141,7 @@ func init() {
|
|||||||
"AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"},
|
"AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"},
|
||||||
"AWS/DocDB": {"DBClusterIdentifier"},
|
"AWS/DocDB": {"DBClusterIdentifier"},
|
||||||
"AWS/DX": {"ConnectionId"},
|
"AWS/DX": {"ConnectionId"},
|
||||||
|
"AWS/DAX": {"Account", "ClusterId", "NodeId"},
|
||||||
"AWS/DynamoDB": {"GlobalSecondaryIndexName", "Operation", "ReceivingRegion", "StreamLabel", "TableName"},
|
"AWS/DynamoDB": {"GlobalSecondaryIndexName", "Operation", "ReceivingRegion", "StreamLabel", "TableName"},
|
||||||
"AWS/EBS": {"VolumeId"},
|
"AWS/EBS": {"VolumeId"},
|
||||||
"AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"},
|
"AWS/EC2": {"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"},
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func (e *CloudWatchExecutor) transformQueryResponseToQueryResult(cloudwatchRespo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if partialData {
|
if partialData {
|
||||||
queryResult.ErrorString = "Cloudwatch GetMetricData error: Too many datapoints requested - your search have been limited. Please try to reduce the time range"
|
queryResult.ErrorString = "Cloudwatch GetMetricData error: Too many datapoints requested - your search has been limited. Please try to reduce the time range"
|
||||||
}
|
}
|
||||||
|
|
||||||
queryResult.Series = append(queryResult.Series, timeSeries...)
|
queryResult.Series = append(queryResult.Series, timeSeries...)
|
||||||
|
|||||||
@@ -68,8 +68,15 @@ func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time
|
|||||||
var period int
|
var period int
|
||||||
if strings.ToLower(p) == "auto" || p == "" {
|
if strings.ToLower(p) == "auto" || p == "" {
|
||||||
deltaInSeconds := endTime.Sub(startTime).Seconds()
|
deltaInSeconds := endTime.Sub(startTime).Seconds()
|
||||||
periods := []int{60, 300, 900, 3600, 21600}
|
periods := []int{60, 300, 900, 3600, 21600, 86400}
|
||||||
period = closest(periods, int(math.Ceil(deltaInSeconds/2000)))
|
datapoints := int(math.Ceil(deltaInSeconds / 2000))
|
||||||
|
period = periods[len(periods)-1]
|
||||||
|
for _, value := range periods {
|
||||||
|
if datapoints <= value {
|
||||||
|
period = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if regexp.MustCompile(`^\d+$`).Match([]byte(p)) {
|
if regexp.MustCompile(`^\d+$`).Match([]byte(p)) {
|
||||||
period, err = strconv.Atoi(p)
|
period, err = strconv.Atoi(p)
|
||||||
@@ -158,25 +165,3 @@ func sortDimensions(dimensions map[string][]string) map[string][]string {
|
|||||||
}
|
}
|
||||||
return sortedDimensions
|
return sortedDimensions
|
||||||
}
|
}
|
||||||
|
|
||||||
func closest(array []int, num int) int {
|
|
||||||
minDiff := array[len(array)-1]
|
|
||||||
var closest int
|
|
||||||
if num <= array[0] {
|
|
||||||
return array[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if num >= array[len(array)-1] {
|
|
||||||
return array[len(array)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, value := range array {
|
|
||||||
var m = int(math.Abs(float64(num - value)))
|
|
||||||
if m <= minDiff {
|
|
||||||
minDiff = m
|
|
||||||
closest = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return closest
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cloudwatch
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/tsdb"
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
@@ -127,66 +128,86 @@ func TestRequestParser(t *testing.T) {
|
|||||||
"period": "auto",
|
"period": "auto",
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("when time range is short", func() {
|
Convey("when time range is 5 minutes", func() {
|
||||||
query.Set("period", "auto")
|
query.Set("period", "auto")
|
||||||
timeRange := tsdb.NewTimeRange("now-2h", "now-1h")
|
to := time.Now()
|
||||||
from, _ := timeRange.ParseFrom()
|
from := to.Local().Add(time.Minute * time.Duration(5))
|
||||||
to, _ := timeRange.ParseTo()
|
|
||||||
|
|
||||||
res, err := parseRequestQuery(query, "ref1", from, to)
|
res, err := parseRequestQuery(query, "ref1", from, to)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(res.Period, ShouldEqual, 60)
|
So(res.Period, ShouldEqual, 60)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("when time range is 5y", func() {
|
Convey("when time range is 1 day", func() {
|
||||||
timeRange := tsdb.NewTimeRange("now-5y", "now")
|
query.Set("period", "auto")
|
||||||
from, _ := timeRange.ParseFrom()
|
to := time.Now()
|
||||||
to, _ := timeRange.ParseTo()
|
from := to.AddDate(0, 0, -1)
|
||||||
|
|
||||||
|
res, err := parseRequestQuery(query, "ref1", from, to)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.Period, ShouldEqual, 60)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("when time range is 2 days", func() {
|
||||||
|
query.Set("period", "auto")
|
||||||
|
to := time.Now()
|
||||||
|
from := to.AddDate(0, 0, -2)
|
||||||
|
res, err := parseRequestQuery(query, "ref1", from, to)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.Period, ShouldEqual, 300)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("when time range is 7 days", func() {
|
||||||
|
query.Set("period", "auto")
|
||||||
|
to := time.Now()
|
||||||
|
from := to.AddDate(0, 0, -7)
|
||||||
|
|
||||||
|
res, err := parseRequestQuery(query, "ref1", from, to)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.Period, ShouldEqual, 900)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("when time range is 30 days", func() {
|
||||||
|
query.Set("period", "auto")
|
||||||
|
to := time.Now()
|
||||||
|
from := to.AddDate(0, 0, -30)
|
||||||
|
|
||||||
|
res, err := parseRequestQuery(query, "ref1", from, to)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.Period, ShouldEqual, 3600)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("when time range is 90 days", func() {
|
||||||
|
query.Set("period", "auto")
|
||||||
|
to := time.Now()
|
||||||
|
from := to.AddDate(0, 0, -90)
|
||||||
|
|
||||||
res, err := parseRequestQuery(query, "ref1", from, to)
|
res, err := parseRequestQuery(query, "ref1", from, to)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(res.Period, ShouldEqual, 21600)
|
So(res.Period, ShouldEqual, 21600)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
Convey("closest works as expected", func() {
|
Convey("when time range is 1 year", func() {
|
||||||
periods := []int{60, 300, 900, 3600, 21600}
|
query.Set("period", "auto")
|
||||||
Convey("and input is lower than 60", func() {
|
to := time.Now()
|
||||||
So(closest(periods, 6), ShouldEqual, 60)
|
from := to.AddDate(-1, 0, 0)
|
||||||
|
|
||||||
|
res, err := parseRequestQuery(query, "ref1", from, to)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.Period, ShouldEqual, 21600)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("and input is exactly 60", func() {
|
Convey("when time range is 2 years", func() {
|
||||||
So(closest(periods, 60), ShouldEqual, 60)
|
query.Set("period", "auto")
|
||||||
})
|
to := time.Now()
|
||||||
|
from := to.AddDate(-2, 0, 0)
|
||||||
|
|
||||||
Convey("and input is exactly between two steps", func() {
|
res, err := parseRequestQuery(query, "ref1", from, to)
|
||||||
So(closest(periods, 180), ShouldEqual, 300)
|
So(err, ShouldBeNil)
|
||||||
})
|
So(res.Period, ShouldEqual, 86400)
|
||||||
|
|
||||||
Convey("and input is exactly 2000", func() {
|
|
||||||
So(closest(periods, 2000), ShouldEqual, 900)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("and input is exactly 5000", func() {
|
|
||||||
So(closest(periods, 5000), ShouldEqual, 3600)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("and input is exactly 50000", func() {
|
|
||||||
So(closest(periods, 50000), ShouldEqual, 21600)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("and period isn't shorter than min retension for 15 days", func() {
|
|
||||||
So(closest(periods, (60*60*24*15)+1/2000), ShouldBeGreaterThanOrEqualTo, 300)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("and period isn't shorter than min retension for 63 days", func() {
|
|
||||||
So(closest(periods, (60*60*24*63)+1/2000), ShouldBeGreaterThanOrEqualTo, 3600)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("and period isn't shorter than min retension for 455 days", func() {
|
|
||||||
So(closest(periods, (60*60*24*455)+1/2000), ShouldBeGreaterThanOrEqualTo, 21600)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,10 @@ func parseGetMetricDataTimeSeries(metricDataResults map[string]*cloudwatch.Metri
|
|||||||
series.Tags[key] = values[0]
|
series.Tags[key] = values[0]
|
||||||
} else {
|
} else {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
if value == label || value == "*" || strings.Contains(label, value) {
|
if value == label || value == "*" {
|
||||||
series.Tags[key] = label
|
series.Tags[key] = label
|
||||||
|
} else if strings.Contains(label, value) {
|
||||||
|
series.Tags[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
|
|||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
MetricName: "TargetResponseTime",
|
MetricName: "TargetResponseTime",
|
||||||
Dimensions: map[string][]string{
|
Dimensions: map[string][]string{
|
||||||
"LoadBalancer": {"lb2"},
|
"LoadBalancer": {"lb1", "lb2"},
|
||||||
"TargetGroup": {"tg"},
|
"TargetGroup": {"tg"},
|
||||||
},
|
},
|
||||||
Stats: "Average",
|
Stats: "Average",
|
||||||
@@ -65,8 +65,12 @@ func TestCloudWatchResponseParser(t *testing.T) {
|
|||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(partialData, ShouldBeFalse)
|
So(partialData, ShouldBeFalse)
|
||||||
So(timeSeries.Name, ShouldEqual, "lb2 Expanded")
|
So(timeSeries.Name, ShouldEqual, "lb1 Expanded")
|
||||||
So(timeSeries.Tags["LoadBalancer"], ShouldEqual, "lb2")
|
So(timeSeries.Tags["LoadBalancer"], ShouldEqual, "lb1")
|
||||||
|
|
||||||
|
timeSeries2 := (*series)[1]
|
||||||
|
So(timeSeries2.Name, ShouldEqual, "lb2 Expanded")
|
||||||
|
So(timeSeries2.Tags["LoadBalancer"], ShouldEqual, "lb2")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("can expand dimension value using substring", func() {
|
Convey("can expand dimension value using substring", func() {
|
||||||
@@ -110,7 +114,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
|
|||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
MetricName: "TargetResponseTime",
|
MetricName: "TargetResponseTime",
|
||||||
Dimensions: map[string][]string{
|
Dimensions: map[string][]string{
|
||||||
"LoadBalancer": {"lb1"},
|
"LoadBalancer": {"lb1", "lb2"},
|
||||||
"TargetGroup": {"tg"},
|
"TargetGroup": {"tg"},
|
||||||
},
|
},
|
||||||
Stats: "Average",
|
Stats: "Average",
|
||||||
@@ -119,11 +123,14 @@ func TestCloudWatchResponseParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
series, partialData, err := parseGetMetricDataTimeSeries(resp, query)
|
series, partialData, err := parseGetMetricDataTimeSeries(resp, query)
|
||||||
timeSeries := (*series)[0]
|
timeSeries := (*series)[0]
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(partialData, ShouldBeFalse)
|
So(partialData, ShouldBeFalse)
|
||||||
So(timeSeries.Name, ShouldEqual, "lb1 Expanded")
|
So(timeSeries.Name, ShouldEqual, "lb1 Expanded")
|
||||||
So(timeSeries.Tags["LoadBalancer"], ShouldEqual, "lb1")
|
So(timeSeries.Tags["LoadBalancer"], ShouldEqual, "lb1")
|
||||||
|
|
||||||
|
timeSeries2 := (*series)[1]
|
||||||
|
So(timeSeries2.Name, ShouldEqual, "lb2 Expanded")
|
||||||
|
So(timeSeries2.Tags["LoadBalancer"], ShouldEqual, "lb2")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("can expand dimension value using wildcard", func() {
|
Convey("can expand dimension value using wildcard", func() {
|
||||||
|
|||||||
@@ -542,8 +542,11 @@ func getRandomWalk(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery, index int) *tsd
|
|||||||
startValue := query.Model.Get("startValue").MustFloat64(rand.Float64() * 100)
|
startValue := query.Model.Get("startValue").MustFloat64(rand.Float64() * 100)
|
||||||
spread := query.Model.Get("spread").MustFloat64(1)
|
spread := query.Model.Get("spread").MustFloat64(1)
|
||||||
noise := query.Model.Get("noise").MustFloat64(0)
|
noise := query.Model.Get("noise").MustFloat64(0)
|
||||||
min, hasMin := query.Model.Get("min").Float64()
|
|
||||||
max, hasMax := query.Model.Get("max").Float64()
|
min, err := query.Model.Get("min").Float64()
|
||||||
|
hasMin := err == nil
|
||||||
|
max, err := query.Model.Get("max").Float64()
|
||||||
|
hasMax := err == nil
|
||||||
|
|
||||||
points := make(tsdb.TimeSeriesPoints, 0)
|
points := make(tsdb.TimeSeriesPoints, 0)
|
||||||
walker := startValue
|
walker := startValue
|
||||||
@@ -551,12 +554,12 @@ func getRandomWalk(query *tsdb.Query, tsdbQuery *tsdb.TsdbQuery, index int) *tsd
|
|||||||
for i := int64(0); i < 10000 && timeWalkerMs < to; i++ {
|
for i := int64(0); i < 10000 && timeWalkerMs < to; i++ {
|
||||||
nextValue := walker + (rand.Float64() * noise)
|
nextValue := walker + (rand.Float64() * noise)
|
||||||
|
|
||||||
if hasMin == nil && nextValue < min {
|
if hasMin && nextValue < min {
|
||||||
nextValue = min
|
nextValue = min
|
||||||
walker = min
|
walker = min
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasMax == nil && nextValue > max {
|
if hasMax && nextValue > max {
|
||||||
nextValue = max
|
nextValue = max
|
||||||
walker = max
|
walker = max
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import '@babel/polyfill';
|
import '@babel/polyfill';
|
||||||
|
import 'url-search-params-polyfill'; // fetch polyfill needed for PhantomJs rendering
|
||||||
import 'file-saver';
|
import 'file-saver';
|
||||||
import 'lodash';
|
import 'lodash';
|
||||||
import 'jquery';
|
import 'jquery';
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ export interface BrandComponentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LoginLogo: FC<BrandComponentProps> = ({ className }) => {
|
export const LoginLogo: FC<BrandComponentProps> = ({ className }) => {
|
||||||
|
const maxSize = css`
|
||||||
|
max-width: 150px;
|
||||||
|
`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<img className={className} src="public/img/grafana_icon.svg" alt="Grafana" />
|
<img className={cx(className, maxSize)} src="public/img/grafana_icon.svg" alt="Grafana" />
|
||||||
<div className="logo-wordmark" />
|
<div className="logo-wordmark" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export interface FooterLink {
|
|||||||
text: string;
|
text: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
target: string;
|
target?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let getFooterLinks = (): FooterLink[] => {
|
export let getFooterLinks = (): FooterLink[] => {
|
||||||
@@ -17,7 +17,7 @@ export let getFooterLinks = (): FooterLink[] => {
|
|||||||
target: '_blank',
|
target: '_blank',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Support & Enterprise',
|
text: 'Support',
|
||||||
icon: 'fa fa-support',
|
icon: 'fa fa-support',
|
||||||
url: 'https://grafana.com/products/enterprise/?utm_source=grafana_footer',
|
url: 'https://grafana.com/products/enterprise/?utm_source=grafana_footer',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
@@ -32,15 +32,12 @@ export let getFooterLinks = (): FooterLink[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export let getVersionLinks = (): FooterLink[] => {
|
export let getVersionLinks = (): FooterLink[] => {
|
||||||
const { buildInfo } = config;
|
const { buildInfo, licenseInfo } = config;
|
||||||
|
const links: FooterLink[] = [];
|
||||||
|
const stateInfo = licenseInfo.stateInfo ? ` (${licenseInfo.stateInfo})` : '';
|
||||||
|
|
||||||
const links: FooterLink[] = [
|
links.push({ text: `${buildInfo.edition}${stateInfo}`, url: licenseInfo.licenseUrl });
|
||||||
{
|
links.push({ text: `v${buildInfo.version} (${buildInfo.commit})` });
|
||||||
text: `Grafana v${buildInfo.version} (commit: ${buildInfo.commit})`,
|
|
||||||
url: 'https://grafana.com',
|
|
||||||
target: '_blank',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (buildInfo.hasUpdate) {
|
if (buildInfo.hasUpdate) {
|
||||||
links.push({
|
links.push({
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import LoginCtrl from './LoginCtrl';
|
|||||||
import { LoginForm } from './LoginForm';
|
import { LoginForm } from './LoginForm';
|
||||||
import { ChangePassword } from './ChangePassword';
|
import { ChangePassword } from './ChangePassword';
|
||||||
import { Branding } from 'app/core/components/Branding/Branding';
|
import { Branding } from 'app/core/components/Branding/Branding';
|
||||||
|
import { Footer } from 'app/core/components/Footer/Footer';
|
||||||
|
|
||||||
export const LoginPage: FC = () => {
|
export const LoginPage: FC = () => {
|
||||||
return (
|
return (
|
||||||
<Branding.LoginBackground className="login container">
|
<Branding.LoginBackground className="login container">
|
||||||
<div className="login-content">
|
<div className="login-content">
|
||||||
<div className="login-branding">
|
<div className="login-branding">
|
||||||
<Branding.LoginLogo className="logo-icon" />
|
<Branding.LoginLogo className="login-logo" />
|
||||||
</div>
|
</div>
|
||||||
<LoginCtrl>
|
<LoginCtrl>
|
||||||
{({
|
{({
|
||||||
@@ -62,6 +63,7 @@ export const LoginPage: FC = () => {
|
|||||||
|
|
||||||
<div className="clearfix" />
|
<div className="clearfix" />
|
||||||
</div>
|
</div>
|
||||||
|
<Footer />
|
||||||
</Branding.LoginBackground>
|
</Branding.LoginBackground>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -223,7 +223,6 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
expect(logsModel.rows).toHaveLength(2);
|
expect(logsModel.rows).toHaveLength(2);
|
||||||
expect(logsModel.rows).toMatchObject([
|
expect(logsModel.rows).toMatchObject([
|
||||||
{
|
{
|
||||||
timestamp: '2019-04-26T09:28:11.352440161Z',
|
|
||||||
entry: 't=2019-04-26T11:05:28+0200 lvl=info msg="Initializing DatasourceCacheService" logger=server',
|
entry: 't=2019-04-26T11:05:28+0200 lvl=info msg="Initializing DatasourceCacheService" logger=server',
|
||||||
labels: { filename: '/var/log/grafana/grafana.log', job: 'grafana' },
|
labels: { filename: '/var/log/grafana/grafana.log', job: 'grafana' },
|
||||||
logLevel: 'info',
|
logLevel: 'info',
|
||||||
@@ -231,7 +230,6 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
uid: 'foo',
|
uid: 'foo',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamp: '2019-04-26T14:42:50.991981292Z',
|
|
||||||
entry: 't=2019-04-26T16:42:50+0200 lvl=eror msg="new token…t unhashed token=56d9fdc5c8b7400bd51b060eea8ca9d7',
|
entry: 't=2019-04-26T16:42:50+0200 lvl=eror msg="new token…t unhashed token=56d9fdc5c8b7400bd51b060eea8ca9d7',
|
||||||
labels: { filename: '/var/log/grafana/grafana.log', job: 'grafana' },
|
labels: { filename: '/var/log/grafana/grafana.log', job: 'grafana' },
|
||||||
logLevel: 'error',
|
logLevel: 'error',
|
||||||
|
|||||||
@@ -328,14 +328,13 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi
|
|||||||
timeFromNow: time.fromNow(),
|
timeFromNow: time.fromNow(),
|
||||||
timeEpochMs: time.valueOf(),
|
timeEpochMs: time.valueOf(),
|
||||||
timeLocal: time.format(logTimeFormat),
|
timeLocal: time.format(logTimeFormat),
|
||||||
timeUtc: toUtc(ts).format(logTimeFormat),
|
timeUtc: toUtc(time.valueOf()).format(logTimeFormat),
|
||||||
uniqueLabels,
|
uniqueLabels,
|
||||||
hasAnsi,
|
hasAnsi,
|
||||||
searchWords,
|
searchWords,
|
||||||
entry: hasAnsi ? ansicolor.strip(message) : message,
|
entry: hasAnsi ? ansicolor.strip(message) : message,
|
||||||
raw: message,
|
raw: message,
|
||||||
labels: stringField.labels,
|
labels: stringField.labels,
|
||||||
timestamp: ts,
|
|
||||||
uid: idField ? idField.values.get(j) : j.toString(),
|
uid: idField ? idField.values.get(j) : j.toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,15 @@ import {
|
|||||||
} from './explore';
|
} from './explore';
|
||||||
import { ExploreUrlState, ExploreMode } from 'app/types/explore';
|
import { ExploreUrlState, ExploreMode } from 'app/types/explore';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
import { DataQueryError, LogsDedupStrategy, LogsModel, LogLevel, dateTime, MutableDataFrame } from '@grafana/data';
|
import {
|
||||||
|
DataQueryError,
|
||||||
|
LogsDedupStrategy,
|
||||||
|
LogsModel,
|
||||||
|
LogLevel,
|
||||||
|
dateTime,
|
||||||
|
MutableDataFrame,
|
||||||
|
LogRowModel,
|
||||||
|
} from '@grafana/data';
|
||||||
import { RefreshPicker } from '@grafana/ui';
|
import { RefreshPicker } from '@grafana/ui';
|
||||||
|
|
||||||
const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
|
const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
|
||||||
@@ -372,11 +380,10 @@ describe('refreshIntervalToSortOrder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('sortLogsResult', () => {
|
describe('sortLogsResult', () => {
|
||||||
const firstRow = {
|
const firstRow: LogRowModel = {
|
||||||
rowIndex: 0,
|
rowIndex: 0,
|
||||||
entryFieldIndex: 0,
|
entryFieldIndex: 0,
|
||||||
dataFrame: new MutableDataFrame(),
|
dataFrame: new MutableDataFrame(),
|
||||||
timestamp: '2019-01-01T21:00:0.0000000Z',
|
|
||||||
entry: '',
|
entry: '',
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
@@ -389,17 +396,16 @@ describe('sortLogsResult', () => {
|
|||||||
uid: '1',
|
uid: '1',
|
||||||
};
|
};
|
||||||
const sameAsFirstRow = firstRow;
|
const sameAsFirstRow = firstRow;
|
||||||
const secondRow = {
|
const secondRow: LogRowModel = {
|
||||||
rowIndex: 1,
|
rowIndex: 1,
|
||||||
entryFieldIndex: 0,
|
entryFieldIndex: 0,
|
||||||
dataFrame: new MutableDataFrame(),
|
dataFrame: new MutableDataFrame(),
|
||||||
timestamp: '2019-01-01T22:00:0.0000000Z',
|
|
||||||
entry: '',
|
entry: '',
|
||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
logLevel: LogLevel.info,
|
logLevel: LogLevel.info,
|
||||||
raw: '',
|
raw: '',
|
||||||
timeEpochMs: 0,
|
timeEpochMs: 10,
|
||||||
timeFromNow: '',
|
timeFromNow: '',
|
||||||
timeLocal: '',
|
timeLocal: '',
|
||||||
timeUtc: '',
|
timeUtc: '',
|
||||||
|
|||||||
@@ -88,11 +88,12 @@ export async function getExploreUrl(args: GetExploreUrlArguments) {
|
|||||||
const range = timeSrv.timeRangeForUrl();
|
const range = timeSrv.timeRangeForUrl();
|
||||||
let state: Partial<ExploreUrlState> = { range };
|
let state: Partial<ExploreUrlState> = { range };
|
||||||
if (exploreDatasource.interpolateVariablesInQueries) {
|
if (exploreDatasource.interpolateVariablesInQueries) {
|
||||||
|
const scopedVars = panel.scopedVars || {};
|
||||||
state = {
|
state = {
|
||||||
...state,
|
...state,
|
||||||
datasource: exploreDatasource.name,
|
datasource: exploreDatasource.name,
|
||||||
context: 'explore',
|
context: 'explore',
|
||||||
queries: exploreDatasource.interpolateVariablesInQueries(exploreTargets),
|
queries: exploreDatasource.interpolateVariablesInQueries(exploreTargets, scopedVars),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
state = {
|
state = {
|
||||||
@@ -473,11 +474,11 @@ export const getRefIds = (value: any): string[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
export const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
||||||
if (a.timestamp < b.timestamp) {
|
if (a.timeEpochMs < b.timeEpochMs) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.timestamp > b.timestamp) {
|
if (a.timeEpochMs > b.timeEpochMs) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,11 +486,11 @@ export const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sortInDescendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
const sortInDescendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
||||||
if (a.timestamp > b.timestamp) {
|
if (a.timeEpochMs > b.timeEpochMs) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.timestamp < b.timestamp) {
|
if (a.timeEpochMs < b.timeEpochMs) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ export const LicenseChrome: React.FC<Props> = ({ header, editionNotice, subheade
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className="logo-icon"
|
|
||||||
src="/public/img/grafana_icon.svg"
|
src="/public/img/grafana_icon.svg"
|
||||||
alt="Grafana"
|
alt="Grafana"
|
||||||
width="80px"
|
width="80px"
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ exports[`ServerStats Should render table with stats 1`] = `
|
|||||||
className="fa fa-support"
|
className="fa fa-support"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
Support & Enterprise
|
Support
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -198,13 +198,22 @@ exports[`ServerStats Should render table with stats 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://grafana.com"
|
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<i />
|
<i />
|
||||||
|
|
||||||
Grafana vv1.0 (commit: 1)
|
undefined
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
rel="noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i />
|
||||||
|
|
||||||
|
vv1.0 (1)
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ const makeLog = (overides: Partial<LogRowModel>): LogRowModel => {
|
|||||||
hasAnsi: false,
|
hasAnsi: false,
|
||||||
labels: {},
|
labels: {},
|
||||||
raw: entry,
|
raw: entry,
|
||||||
timestamp: '',
|
|
||||||
timeFromNow: '',
|
timeFromNow: '',
|
||||||
timeEpochMs: 1,
|
timeEpochMs: 1,
|
||||||
timeLocal: '',
|
timeLocal: '',
|
||||||
|
|||||||
@@ -187,7 +187,6 @@ describe('ResultProcessor', () => {
|
|||||||
timeFromNow: 'fromNow() jest mocked',
|
timeFromNow: 'fromNow() jest mocked',
|
||||||
timeLocal: 'format() jest mocked',
|
timeLocal: 'format() jest mocked',
|
||||||
timeUtc: 'format() jest mocked',
|
timeUtc: 'format() jest mocked',
|
||||||
timestamp: 300,
|
|
||||||
uid: '2',
|
uid: '2',
|
||||||
uniqueLabels: {},
|
uniqueLabels: {},
|
||||||
},
|
},
|
||||||
@@ -205,7 +204,6 @@ describe('ResultProcessor', () => {
|
|||||||
timeFromNow: 'fromNow() jest mocked',
|
timeFromNow: 'fromNow() jest mocked',
|
||||||
timeLocal: 'format() jest mocked',
|
timeLocal: 'format() jest mocked',
|
||||||
timeUtc: 'format() jest mocked',
|
timeUtc: 'format() jest mocked',
|
||||||
timestamp: 200,
|
|
||||||
uid: '1',
|
uid: '1',
|
||||||
uniqueLabels: {},
|
uniqueLabels: {},
|
||||||
},
|
},
|
||||||
@@ -223,7 +221,6 @@ describe('ResultProcessor', () => {
|
|||||||
timeFromNow: 'fromNow() jest mocked',
|
timeFromNow: 'fromNow() jest mocked',
|
||||||
timeLocal: 'format() jest mocked',
|
timeLocal: 'format() jest mocked',
|
||||||
timeUtc: 'format() jest mocked',
|
timeUtc: 'format() jest mocked',
|
||||||
timestamp: 100,
|
|
||||||
uid: '0',
|
uid: '0',
|
||||||
uniqueLabels: {},
|
uniqueLabels: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -126,10 +126,13 @@ function pluginDirectiveLoader(
|
|||||||
}
|
}
|
||||||
// Annotations
|
// Annotations
|
||||||
case 'annotations-query-ctrl': {
|
case 'annotations-query-ctrl': {
|
||||||
|
const baseUrl = scope.ctrl.currentDatasource.meta.baseUrl;
|
||||||
|
const pluginId = scope.ctrl.currentDatasource.meta.id;
|
||||||
|
|
||||||
return importDataSourcePlugin(scope.ctrl.currentDatasource.meta).then(dsPlugin => {
|
return importDataSourcePlugin(scope.ctrl.currentDatasource.meta).then(dsPlugin => {
|
||||||
return {
|
return {
|
||||||
baseUrl: scope.ctrl.currentDatasource.meta.baseUrl,
|
baseUrl,
|
||||||
name: 'annotations-query-ctrl-' + scope.ctrl.currentDatasource.meta.id,
|
name: 'annotations-query-ctrl-' + pluginId,
|
||||||
bindings: { annotation: '=', datasource: '=' },
|
bindings: { annotation: '=', datasource: '=' },
|
||||||
attrs: {
|
attrs: {
|
||||||
annotation: 'ctrl.currentAnnotation',
|
annotation: 'ctrl.currentAnnotation',
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ describe('templateSrv', () => {
|
|||||||
expect(target).toBe('Server1 nested');
|
expect(target).toBe('Server1 nested');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('built in vars should support objects', () => {
|
||||||
|
_templateSrv.setGlobalVariable('__dashboard', {
|
||||||
|
value: { name: 'hello' },
|
||||||
|
});
|
||||||
|
const target = _templateSrv.replace('${__dashboard.name}');
|
||||||
|
expect(target).toBe('hello');
|
||||||
|
});
|
||||||
|
|
||||||
it('scoped vars should support objects with propert names with dot', () => {
|
it('scoped vars should support objects with propert names with dot', () => {
|
||||||
const target = _templateSrv.replace('${series.name} ${series.nested["field.with.dot"]}', {
|
const target = _templateSrv.replace('${series.name} ${series.nested["field.with.dot"]}', {
|
||||||
series: { value: { name: 'Server1', nested: { 'field.with.dot': 'nested' } } },
|
series: { value: { name: 'Server1', nested: { 'field.with.dot': 'nested' } } },
|
||||||
|
|||||||
@@ -195,6 +195,15 @@ export class TemplateSrv {
|
|||||||
this.grafanaVariables[name] = value;
|
this.grafanaVariables[name] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setGlobalVariable(name: string, variable: any) {
|
||||||
|
this.index = {
|
||||||
|
...this.index,
|
||||||
|
[name]: {
|
||||||
|
current: variable,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getVariableName(expression: string) {
|
getVariableName(expression: string) {
|
||||||
this.regex.lastIndex = 0;
|
this.regex.lastIndex = 0;
|
||||||
const match = this.regex.exec(expression);
|
const match = this.regex.exec(expression);
|
||||||
@@ -295,6 +304,15 @@ export class TemplateSrv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fieldPath) {
|
||||||
|
const fieldValue = this.getVariableValue(variableName, fieldPath, {
|
||||||
|
[variableName]: { value: value, text: '' },
|
||||||
|
});
|
||||||
|
if (fieldValue !== null && fieldValue !== undefined) {
|
||||||
|
return this.formatValue(fieldValue, fmt, variable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const res = this.formatValue(value, fmt, variable);
|
const res = this.formatValue(value, fmt, variable);
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
|||||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TimeRange } from '@grafana/data';
|
import { TimeRange, AppEvents } from '@grafana/data';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
import { UrlQueryMap } from '@grafana/runtime';
|
import { UrlQueryMap } from '@grafana/runtime';
|
||||||
|
import { appEvents } from 'app/core/core';
|
||||||
|
|
||||||
export class VariableSrv {
|
export class VariableSrv {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
@@ -71,9 +72,14 @@ export class VariableSrv {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.$q.all(promises).then(() => {
|
return this.$q
|
||||||
this.dashboard.startRefresh();
|
.all(promises)
|
||||||
});
|
.then(() => {
|
||||||
|
this.dashboard.startRefresh();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
appEvents.emit(AppEvents.alertError, ['Template variable service failed', e.message]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
processVariable(variable: any, queryParams: any) {
|
processVariable(variable: any, queryParams: any) {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export function QueryFieldsEditor({
|
|||||||
placeholder="Select region"
|
placeholder="Select region"
|
||||||
options={regions}
|
options={regions}
|
||||||
allowCustomValue
|
allowCustomValue
|
||||||
onChange={({ value: region }) => onChange({ ...query, region })}
|
onChange={({ value: region }) => onQueryChange({ ...query, region })}
|
||||||
/>
|
/>
|
||||||
</QueryInlineField>
|
</QueryInlineField>
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ export function QueryFieldsEditor({
|
|||||||
placeholder="Select namespace"
|
placeholder="Select namespace"
|
||||||
allowCustomValue
|
allowCustomValue
|
||||||
options={namespaces}
|
options={namespaces}
|
||||||
onChange={({ value: namespace }) => onChange({ ...query, namespace })}
|
onChange={({ value: namespace }) => onQueryChange({ ...query, namespace })}
|
||||||
/>
|
/>
|
||||||
</QueryInlineField>
|
</QueryInlineField>
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ export function QueryFieldsEditor({
|
|||||||
placeholder="Select metric name"
|
placeholder="Select metric name"
|
||||||
allowCustomValue
|
allowCustomValue
|
||||||
loadOptions={loadMetricNames}
|
loadOptions={loadMetricNames}
|
||||||
onChange={({ value: metricName }) => onChange({ ...query, metricName })}
|
onChange={({ value: metricName }) => onQueryChange({ ...query, metricName })}
|
||||||
/>
|
/>
|
||||||
</QueryInlineField>
|
</QueryInlineField>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
DataQueryResponse,
|
DataQueryResponse,
|
||||||
DataFrame,
|
DataFrame,
|
||||||
|
ScopedVars,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { ElasticResponse } from './elastic_response';
|
import { ElasticResponse } from './elastic_response';
|
||||||
import { IndexPattern } from './index_pattern';
|
import { IndexPattern } from './index_pattern';
|
||||||
@@ -264,14 +265,14 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolateVariablesInQueries(queries: ElasticsearchQuery[]): ElasticsearchQuery[] {
|
interpolateVariablesInQueries(queries: ElasticsearchQuery[], scopedVars: ScopedVars): ElasticsearchQuery[] {
|
||||||
let expandedQueries = queries;
|
let expandedQueries = queries;
|
||||||
if (queries && queries.length > 0) {
|
if (queries && queries.length > 0) {
|
||||||
expandedQueries = queries.map(query => {
|
expandedQueries = queries.map(query => {
|
||||||
const expandedQuery = {
|
const expandedQuery = {
|
||||||
...query,
|
...query,
|
||||||
datasource: this.name,
|
datasource: this.name,
|
||||||
query: this.templateSrv.replace(query.query, {}, 'lucene'),
|
query: this.templateSrv.replace(query.query, scopedVars, 'lucene'),
|
||||||
};
|
};
|
||||||
return expandedQuery;
|
return expandedQuery;
|
||||||
});
|
});
|
||||||
@@ -344,7 +345,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
target.metrics = [queryDef.defaultMetricAgg()];
|
target.metrics = [queryDef.defaultMetricAgg()];
|
||||||
// Setting this for metrics queries that are typed as logs
|
// Setting this for metrics queries that are typed as logs
|
||||||
target.isLogsQuery = true;
|
target.isLogsQuery = true;
|
||||||
queryObj = this.queryBuilder.getLogsQuery(target, queryString);
|
queryObj = this.queryBuilder.getLogsQuery(target, adhocFilters, queryString);
|
||||||
} else {
|
} else {
|
||||||
if (target.alias) {
|
if (target.alias) {
|
||||||
target.alias = this.templateSrv.replace(target.alias, options.scopedVars, 'lucene');
|
target.alias = this.templateSrv.replace(target.alias, options.scopedVars, 'lucene');
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ export class ElasticQueryBuilder {
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLogsQuery(target: any, querystring: string) {
|
getLogsQuery(target: any, adhocFilters?: any, querystring?: string) {
|
||||||
let query: any = {
|
let query: any = {
|
||||||
size: 0,
|
size: 0,
|
||||||
query: {
|
query: {
|
||||||
@@ -389,6 +389,8 @@ export class ElasticQueryBuilder {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.addAdhocFilters(query, adhocFilters);
|
||||||
|
|
||||||
if (target.query) {
|
if (target.query) {
|
||||||
query.query.bool.filter.push({
|
query.query.bool.filter.push({
|
||||||
query_string: {
|
query_string: {
|
||||||
|
|||||||
@@ -476,7 +476,6 @@ describe('ElasticQueryBuilder', () => {
|
|||||||
|
|
||||||
it('should set correct explicit sorting', () => {
|
it('should set correct explicit sorting', () => {
|
||||||
const order = testGetTermsQuery({ order: 'desc' });
|
const order = testGetTermsQuery({ order: 'desc' });
|
||||||
console.log({ order });
|
|
||||||
checkSort(order, 'desc');
|
checkSort(order, 'desc');
|
||||||
expect(order._count).toBeUndefined();
|
expect(order._count).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -496,11 +495,68 @@ describe('ElasticQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getTermsQuery should request documents and date histogram', () => {
|
describe('getLogsQuery', () => {
|
||||||
const query = builder.getLogsQuery({}, '');
|
it('should return query with defaults', () => {
|
||||||
console.log({ query });
|
const query = builder.getLogsQuery({}, null, '*');
|
||||||
expect(query).toHaveProperty('query.bool.filter');
|
|
||||||
expect(query.aggs['2']).toHaveProperty('date_histogram');
|
expect(query.size).toEqual(500);
|
||||||
|
|
||||||
|
const expectedQuery = {
|
||||||
|
bool: {
|
||||||
|
filter: [{ range: { '@timestamp': { gte: '$timeFrom', lte: '$timeTo', format: 'epoch_millis' } } }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(query.query).toEqual(expectedQuery);
|
||||||
|
|
||||||
|
expect(query.sort).toEqual({ '@timestamp': { order: 'desc', unmapped_type: 'boolean' } });
|
||||||
|
|
||||||
|
const expectedAggs = {
|
||||||
|
2: {
|
||||||
|
aggs: {},
|
||||||
|
date_histogram: {
|
||||||
|
extended_bounds: { max: '$timeTo', min: '$timeFrom' },
|
||||||
|
field: '@timestamp',
|
||||||
|
format: 'epoch_millis',
|
||||||
|
interval: '$__interval',
|
||||||
|
min_doc_count: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(query.aggs).toMatchObject(expectedAggs);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with querystring', () => {
|
||||||
|
const query = builder.getLogsQuery({ query: 'foo' }, null, 'foo');
|
||||||
|
|
||||||
|
const expectedQuery = {
|
||||||
|
bool: {
|
||||||
|
filter: [
|
||||||
|
{ range: { '@timestamp': { gte: '$timeFrom', lte: '$timeTo', format: 'epoch_millis' } } },
|
||||||
|
{ query_string: { analyze_wildcard: true, query: 'foo' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(query.query).toEqual(expectedQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with adhoc filters', () => {
|
||||||
|
const adhocFilters = [
|
||||||
|
{ key: 'key1', operator: '=', value: 'value1' },
|
||||||
|
{ key: 'key2', operator: '!=', value: 'value2' },
|
||||||
|
{ key: 'key3', operator: '<', value: 'value3' },
|
||||||
|
{ key: 'key4', operator: '>', value: 'value4' },
|
||||||
|
{ key: 'key5', operator: '=~', value: 'value5' },
|
||||||
|
{ key: 'key6', operator: '!~', value: 'value6' },
|
||||||
|
];
|
||||||
|
const query = builder.getLogsQuery({}, adhocFilters, '*');
|
||||||
|
|
||||||
|
expect(query.query.bool.must[0].match_phrase['key1'].query).toBe('value1');
|
||||||
|
expect(query.query.bool.must_not[0].match_phrase['key2'].query).toBe('value2');
|
||||||
|
expect(query.query.bool.filter[1].range['key3'].lt).toBe('value3');
|
||||||
|
expect(query.query.bool.filter[2].range['key4'].gt).toBe('value4');
|
||||||
|
expect(query.query.bool.filter[3].regexp['key5']).toBe('value5');
|
||||||
|
expect(query.query.bool.filter[4].bool.must_not.regexp['key6']).toBe('value6');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -148,14 +148,14 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
|
|||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolateVariablesInQueries(queries: GraphiteQuery[]): GraphiteQuery[] {
|
interpolateVariablesInQueries(queries: GraphiteQuery[], scopedVars: ScopedVars): GraphiteQuery[] {
|
||||||
let expandedQueries = queries;
|
let expandedQueries = queries;
|
||||||
if (queries && queries.length > 0) {
|
if (queries && queries.length > 0) {
|
||||||
expandedQueries = queries.map(query => {
|
expandedQueries = queries.map(query => {
|
||||||
const expandedQuery = {
|
const expandedQuery = {
|
||||||
...query,
|
...query,
|
||||||
datasource: this.name,
|
datasource: this.name,
|
||||||
target: this.templateSrv.replace(query.target),
|
target: this.templateSrv.replace(query.target, scopedVars),
|
||||||
};
|
};
|
||||||
return expandedQuery;
|
return expandedQuery;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { dateMath, DataSourceApi, DataSourceInstanceSettings } from '@grafana/data';
|
import { dateMath, DataSourceApi, DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
|
||||||
import InfluxSeries from './influx_series';
|
import InfluxSeries from './influx_series';
|
||||||
import InfluxQueryModel from './influx_query_model';
|
import InfluxQueryModel from './influx_query_model';
|
||||||
import ResponseParser from './response_parser';
|
import ResponseParser from './response_parser';
|
||||||
@@ -171,7 +171,7 @@ export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxO
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolateVariablesInQueries(queries: InfluxQuery[]): InfluxQuery[] {
|
interpolateVariablesInQueries(queries: InfluxQuery[], scopedVars: ScopedVars): InfluxQuery[] {
|
||||||
if (!queries || queries.length === 0) {
|
if (!queries || queries.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -182,11 +182,11 @@ export default class InfluxDatasource extends DataSourceApi<InfluxQuery, InfluxO
|
|||||||
const expandedQuery = {
|
const expandedQuery = {
|
||||||
...query,
|
...query,
|
||||||
datasource: this.name,
|
datasource: this.name,
|
||||||
measurement: this.templateSrv.replace(query.measurement, null, 'regex'),
|
measurement: this.templateSrv.replace(query.measurement, scopedVars, 'regex'),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (query.rawQuery) {
|
if (query.rawQuery) {
|
||||||
expandedQuery.query = this.templateSrv.replace(query.query, null, 'regex');
|
expandedQuery.query = this.templateSrv.replace(query.query, scopedVars, 'regex');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.tags) {
|
if (query.tags) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import LokiDatasource, { RangeQueryOptions } from './datasource';
|
import LokiDatasource, { RangeQueryOptions } from './datasource';
|
||||||
import { LokiQuery, LokiResultType, LokiResponse, LokiLegacyStreamResponse } from './types';
|
import { LokiQuery, LokiResultType, LokiResponse, LokiLegacyStreamResponse } from './types';
|
||||||
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
import { getQueryOptions } from 'test/helpers/getQueryOptions';
|
||||||
import { AnnotationQueryRequest, DataSourceApi, DataFrame, dateTime, TimeRange } from '@grafana/data';
|
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
import { AnnotationQueryRequest, DataSourceApi, DataFrame, dateTime, TimeRange, FieldCache } from '@grafana/data';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { CustomVariable } from 'app/features/templating/custom_variable';
|
import { CustomVariable } from 'app/features/templating/custom_variable';
|
||||||
import { makeMockLokiDatasource } from './mocks';
|
import { makeMockLokiDatasource } from './mocks';
|
||||||
@@ -196,7 +196,8 @@ describe('LokiDatasource', () => {
|
|||||||
const res = await ds.query(options).toPromise();
|
const res = await ds.query(options).toPromise();
|
||||||
|
|
||||||
const dataFrame = res.data[0] as DataFrame;
|
const dataFrame = res.data[0] as DataFrame;
|
||||||
expect(dataFrame.fields[1].values.get(0)).toBe('hello');
|
const fieldCache = new FieldCache(dataFrame);
|
||||||
|
expect(fieldCache.getFieldByName('line').values.get(0)).toBe('hello');
|
||||||
expect(dataFrame.meta.limit).toBe(20);
|
expect(dataFrame.meta.limit).toBe(20);
|
||||||
expect(dataFrame.meta.searchWords).toEqual(['foo']);
|
expect(dataFrame.meta.searchWords).toEqual(['foo']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Observable, from, merge, of, iif, defer } from 'rxjs';
|
|||||||
import { map, filter, catchError, switchMap, mergeMap } from 'rxjs/operators';
|
import { map, filter, catchError, switchMap, mergeMap } from 'rxjs/operators';
|
||||||
|
|
||||||
// Services & Utils
|
// Services & Utils
|
||||||
import { dateMath } from '@grafana/data';
|
import { DataFrame, dateMath, FieldCache } from '@grafana/data';
|
||||||
import { addLabelToSelector, keepSelectorFilters } from 'app/plugins/datasource/prometheus/add_label_to_query';
|
import { addLabelToSelector, keepSelectorFilters } from 'app/plugins/datasource/prometheus/add_label_to_query';
|
||||||
import { BackendSrv, DatasourceRequestOptions } from 'app/core/services/backend_srv';
|
import { BackendSrv, DatasourceRequestOptions } from 'app/core/services/backend_srv';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
DataQueryResponse,
|
DataQueryResponse,
|
||||||
AnnotationQueryRequest,
|
AnnotationQueryRequest,
|
||||||
|
ScopedVars,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -131,7 +132,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
.filter(target => target.expr && !target.hide)
|
.filter(target => target.expr && !target.hide)
|
||||||
.map(target => ({
|
.map(target => ({
|
||||||
...target,
|
...target,
|
||||||
expr: this.templateSrv.replace(target.expr, {}, this.interpolateQueryExpr),
|
expr: this.templateSrv.replace(target.expr, options.scopedVars, this.interpolateQueryExpr),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (options.exploreMode === ExploreMode.Metrics) {
|
if (options.exploreMode === ExploreMode.Metrics) {
|
||||||
@@ -353,13 +354,13 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interpolateVariablesInQueries(queries: LokiQuery[]): LokiQuery[] {
|
interpolateVariablesInQueries(queries: LokiQuery[], scopedVars: ScopedVars): LokiQuery[] {
|
||||||
let expandedQueries = queries;
|
let expandedQueries = queries;
|
||||||
if (queries && queries.length) {
|
if (queries && queries.length) {
|
||||||
expandedQueries = queries.map(query => ({
|
expandedQueries = queries.map(query => ({
|
||||||
...query,
|
...query,
|
||||||
datasource: this.name,
|
datasource: this.name,
|
||||||
expr: this.templateSrv.replace(query.expr, {}, this.interpolateQueryExpr),
|
expr: this.templateSrv.replace(query.expr, scopedVars, this.interpolateQueryExpr),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,7 +469,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
return Math.ceil(date.valueOf() * 1e6);
|
return Math.ceil(date.valueOf() * 1e6);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLogRowContext = (row: LogRowModel, options?: LokiContextQueryOptions) => {
|
getLogRowContext = (row: LogRowModel, options?: LokiContextQueryOptions): Promise<{ data: DataFrame[] }> => {
|
||||||
const target = this.prepareLogRowContextQueryTarget(
|
const target = this.prepareLogRowContextQueryTarget(
|
||||||
row,
|
row,
|
||||||
(options && options.limit) || 10,
|
(options && options.limit) || 10,
|
||||||
@@ -520,8 +521,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
.map(label => `${label}="${row.labels[label]}"`)
|
.map(label => `${label}="${row.labels[label]}"`)
|
||||||
.join(',');
|
.join(',');
|
||||||
|
|
||||||
const contextTimeBuffer = 2 * 60 * 60 * 1000 * 1e6; // 2h buffer
|
const contextTimeBuffer = 2 * 60 * 60 * 1000; // 2h buffer
|
||||||
const timeEpochNs = row.timeEpochMs * 1e6;
|
|
||||||
const commonTargetOptions = {
|
const commonTargetOptions = {
|
||||||
limit,
|
limit,
|
||||||
query: `{${query}}`,
|
query: `{${query}}`,
|
||||||
@@ -529,18 +529,27 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
direction,
|
direction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fieldCache = new FieldCache(row.dataFrame);
|
||||||
|
const nsField = fieldCache.getFieldByName('tsNs')!;
|
||||||
|
const nsTimestamp = nsField.values.get(row.rowIndex);
|
||||||
|
|
||||||
if (direction === 'BACKWARD') {
|
if (direction === 'BACKWARD') {
|
||||||
return {
|
return {
|
||||||
...commonTargetOptions,
|
...commonTargetOptions,
|
||||||
start: timeEpochNs - contextTimeBuffer,
|
// convert to ns, we loose some precision here but it is not that important at the far points of the context
|
||||||
end: timeEpochNs, // using RFC3339Nano format to avoid precision loss
|
start: row.timeEpochMs - contextTimeBuffer + '000000',
|
||||||
|
end: nsTimestamp,
|
||||||
direction,
|
direction,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...commonTargetOptions,
|
...commonTargetOptions,
|
||||||
start: timeEpochNs, // start param in Loki API is inclusive so we'll have to filter out the row that this request is based from
|
// start param in Loki API is inclusive so we'll have to filter out the row that this request is based from
|
||||||
end: timeEpochNs + contextTimeBuffer,
|
// and any other that were logged in the same ns but before the row. Right now these rows will be lost
|
||||||
|
// because the are before but came it he response that should return only rows after.
|
||||||
|
start: nsTimestamp,
|
||||||
|
// convert to ns, we loose some precision here but it is not that important at the far points of the context
|
||||||
|
end: row.timeEpochMs + contextTimeBuffer + '000000',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export class LiveStreams {
|
|||||||
|
|
||||||
const data = new CircularDataFrame({ capacity: target.size });
|
const data = new CircularDataFrame({ capacity: target.size });
|
||||||
data.addField({ name: 'ts', type: FieldType.time, config: { title: 'Time' } });
|
data.addField({ name: 'ts', type: FieldType.time, config: { title: 'Time' } });
|
||||||
|
data.addField({ name: 'tsNs', type: FieldType.time, config: { title: 'Time ns' } });
|
||||||
data.addField({ name: 'line', type: FieldType.string }).labels = parseLabels(target.query);
|
data.addField({ name: 'line', type: FieldType.string }).labels = parseLabels(target.query);
|
||||||
data.addField({ name: 'labels', type: FieldType.other }); // The labels for each line
|
data.addField({ name: 'labels', type: FieldType.other }); // The labels for each line
|
||||||
data.addField({ name: 'id', type: FieldType.string });
|
data.addField({ name: 'id', type: FieldType.string });
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ const streamResult: LokiStreamResult[] = [
|
|||||||
stream: {
|
stream: {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
},
|
},
|
||||||
values: [['1970-01-01T00:00:00Z', "foo: [32m'bar'[39m"]],
|
values: [['1579857562021616000', "foo: [32m'bar'[39m"]],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stream: {
|
stream: {
|
||||||
bar: 'foo',
|
bar: 'foo',
|
||||||
},
|
},
|
||||||
values: [['1970-01-01T00:00:00Z', "bar: 'foo'"]],
|
values: [['1579857562031616000', "bar: 'foo'"]],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -95,12 +95,12 @@ describe('loki result transformer', () => {
|
|||||||
|
|
||||||
expect(data.length).toBe(2);
|
expect(data.length).toBe(2);
|
||||||
expect(data[0].fields[1].labels['foo']).toEqual('bar');
|
expect(data[0].fields[1].labels['foo']).toEqual('bar');
|
||||||
expect(data[0].fields[0].values.get(0)).toEqual(legacyStreamResult[0].entries[0].ts);
|
expect(data[0].fields[0].values.get(0)).toEqual('2020-01-24T09:19:22.021Z');
|
||||||
expect(data[0].fields[1].values.get(0)).toEqual(legacyStreamResult[0].entries[0].line);
|
expect(data[0].fields[1].values.get(0)).toEqual(streamResult[0].values[0][1]);
|
||||||
expect(data[0].fields[2].values.get(0)).toEqual('dc1e83aa5cd718b42a3cff50fa7e3a6a');
|
expect(data[0].fields[2].values.get(0)).toEqual('2b431b8a98b80b3b2c2f4cd2444ae6cb');
|
||||||
expect(data[1].fields[0].values.get(0)).toEqual(legacyStreamResult[1].entries[0].ts);
|
expect(data[1].fields[0].values.get(0)).toEqual('2020-01-24T09:19:22.031Z');
|
||||||
expect(data[1].fields[1].values.get(0)).toEqual(legacyStreamResult[1].entries[0].line);
|
expect(data[1].fields[1].values.get(0)).toEqual(streamResult[1].values[0][1]);
|
||||||
expect(data[1].fields[2].values.get(0)).toEqual('952fa23552daebbb5747c4e52fb9497d');
|
expect(data[1].fields[2].values.get(0)).toEqual('75d73d66cff40f9d1a1f2d5a0bf295d0');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import {
|
|||||||
ArrayVector,
|
ArrayVector,
|
||||||
MutableDataFrame,
|
MutableDataFrame,
|
||||||
findUniqueLabels,
|
findUniqueLabels,
|
||||||
dateTime,
|
|
||||||
FieldConfig,
|
FieldConfig,
|
||||||
DataFrameView,
|
DataFrameView,
|
||||||
|
dateTime,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import templateSrv from 'app/features/templating/template_srv';
|
import templateSrv from 'app/features/templating/template_srv';
|
||||||
import TableModel from 'app/core/table_model';
|
import TableModel from 'app/core/table_model';
|
||||||
@@ -35,7 +35,7 @@ import { formatQuery, getHighlighterExpressionsFromQuery } from './query_utils';
|
|||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms LokiLogStream structure into a dataFrame. Used when doing standard queries.
|
* Transforms LokiLogStream structure into a dataFrame. Used when doing standard queries and older version of Loki.
|
||||||
*/
|
*/
|
||||||
export function legacyLogStreamToDataFrame(
|
export function legacyLogStreamToDataFrame(
|
||||||
stream: LokiLegacyStreamResult,
|
stream: LokiLegacyStreamResult,
|
||||||
@@ -48,67 +48,80 @@ export function legacyLogStreamToDataFrame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const times = new ArrayVector<string>([]);
|
const times = new ArrayVector<string>([]);
|
||||||
|
const timesNs = new ArrayVector<string>([]);
|
||||||
const lines = new ArrayVector<string>([]);
|
const lines = new ArrayVector<string>([]);
|
||||||
const uids = new ArrayVector<string>([]);
|
const uids = new ArrayVector<string>([]);
|
||||||
|
|
||||||
for (const entry of stream.entries) {
|
for (const entry of stream.entries) {
|
||||||
const ts = entry.ts || entry.timestamp;
|
const ts = entry.ts || entry.timestamp;
|
||||||
|
// iso string with nano precision, will be truncated but is parse-able
|
||||||
times.add(ts);
|
times.add(ts);
|
||||||
|
// So this matches new format, we are loosing precision here, which sucks but no easy way to keep it and this
|
||||||
|
// is for old pre 1.0.0 version Loki so probably does not affect that much.
|
||||||
|
timesNs.add(dateTime(ts).valueOf() + '000000');
|
||||||
lines.add(entry.line);
|
lines.add(entry.line);
|
||||||
uids.add(createUid(ts, stream.labels, entry.line));
|
uids.add(createUid(ts, stream.labels, entry.line));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reverse) {
|
return constructDataFrame(times, timesNs, lines, uids, labels, reverse, refId);
|
||||||
times.buffer = times.buffer.reverse();
|
|
||||||
lines.buffer = lines.buffer.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
refId,
|
|
||||||
fields: [
|
|
||||||
{ name: 'ts', type: FieldType.time, config: { title: 'Time' }, values: times }, // Time
|
|
||||||
{ name: 'line', type: FieldType.string, config: {}, values: lines, labels }, // Line
|
|
||||||
{ name: 'id', type: FieldType.string, config: {}, values: uids },
|
|
||||||
],
|
|
||||||
length: times.length,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms LokiStreamResult structure into a dataFrame. Used when doing standard queries and newer version of Loki.
|
||||||
|
*/
|
||||||
export function lokiStreamResultToDataFrame(stream: LokiStreamResult, reverse?: boolean, refId?: string): DataFrame {
|
export function lokiStreamResultToDataFrame(stream: LokiStreamResult, reverse?: boolean, refId?: string): DataFrame {
|
||||||
const labels: Labels = stream.stream;
|
const labels: Labels = stream.stream;
|
||||||
|
const labelsString = Object.entries(labels)
|
||||||
|
.map(([key, val]) => `${key}="${val}"`)
|
||||||
|
.sort()
|
||||||
|
.join('');
|
||||||
|
|
||||||
const times = new ArrayVector<string>([]);
|
const times = new ArrayVector<string>([]);
|
||||||
|
const timesNs = new ArrayVector<string>([]);
|
||||||
const lines = new ArrayVector<string>([]);
|
const lines = new ArrayVector<string>([]);
|
||||||
const uids = new ArrayVector<string>([]);
|
const uids = new ArrayVector<string>([]);
|
||||||
|
|
||||||
for (const [ts, line] of stream.values) {
|
for (const [ts, line] of stream.values) {
|
||||||
const labelsString = Object.entries(labels)
|
// num ns epoch in string, we convert it to iso string here so it matches old format
|
||||||
.map(([key, val]) => `${key}="${val}"`)
|
times.add(new Date(parseInt(ts.substr(0, ts.length - 6), 10)).toISOString());
|
||||||
.join('');
|
timesNs.add(ts);
|
||||||
|
|
||||||
times.add(
|
|
||||||
dateTime(Number.parseFloat(ts) / 1e6)
|
|
||||||
.utc()
|
|
||||||
.format()
|
|
||||||
);
|
|
||||||
lines.add(line);
|
lines.add(line);
|
||||||
uids.add(createUid(ts, labelsString, line));
|
uids.add(createUid(ts, labelsString, line));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reverse) {
|
return constructDataFrame(times, timesNs, lines, uids, labels, reverse, refId);
|
||||||
times.buffer = times.buffer.reverse();
|
}
|
||||||
lines.buffer = lines.buffer.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
/**
|
||||||
|
* Constructs dataFrame with supplied fields and other data. Also makes sure it is properly reversed if needed.
|
||||||
|
*/
|
||||||
|
function constructDataFrame(
|
||||||
|
times: ArrayVector<string>,
|
||||||
|
timesNs: ArrayVector<string>,
|
||||||
|
lines: ArrayVector<string>,
|
||||||
|
uids: ArrayVector<string>,
|
||||||
|
labels: Labels,
|
||||||
|
reverse?: boolean,
|
||||||
|
refId?: string
|
||||||
|
) {
|
||||||
|
const dataFrame = {
|
||||||
refId,
|
refId,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'ts', type: FieldType.time, config: { title: 'Time' }, values: times }, // Time
|
{ name: 'ts', type: FieldType.time, config: { title: 'Time' }, values: times }, // Time
|
||||||
{ name: 'line', type: FieldType.string, config: {}, values: lines, labels }, // Line
|
{ name: 'line', type: FieldType.string, config: {}, values: lines, labels }, // Line
|
||||||
{ name: 'id', type: FieldType.string, config: {}, values: uids },
|
{ name: 'id', type: FieldType.string, config: {}, values: uids },
|
||||||
|
{ name: 'tsNs', type: FieldType.time, config: { title: 'Time ns' }, values: timesNs }, // Time
|
||||||
],
|
],
|
||||||
length: times.length,
|
length: times.length,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (reverse) {
|
||||||
|
const mutableDataFrame = new MutableDataFrame(dataFrame);
|
||||||
|
mutableDataFrame.reverse();
|
||||||
|
return mutableDataFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,17 +186,18 @@ export function appendResponseToBufferedData(response: LokiTailResponse, data: M
|
|||||||
for (const stream of streams) {
|
for (const stream of streams) {
|
||||||
// Find unique labels
|
// Find unique labels
|
||||||
const unique = findUniqueLabels(stream.stream, baseLabels);
|
const unique = findUniqueLabels(stream.stream, baseLabels);
|
||||||
|
const allLabelsString = Object.entries(stream.stream)
|
||||||
|
.map(([key, val]) => `${key}="${val}"`)
|
||||||
|
.sort()
|
||||||
|
.join('');
|
||||||
|
|
||||||
// Add each line
|
// Add each line
|
||||||
for (const [ts, line] of stream.values) {
|
for (const [ts, line] of stream.values) {
|
||||||
const uniqueLabelsString = Object.entries(unique)
|
data.values.ts.add(ts.substr(0, ts.length - 6));
|
||||||
.map(([key, val]) => `${key}="${val}"`)
|
data.values.tsNs.add(ts);
|
||||||
.join('');
|
|
||||||
|
|
||||||
data.values.ts.add(parseInt(ts, 10) / 1e6);
|
|
||||||
data.values.line.add(line);
|
data.values.line.add(line);
|
||||||
data.values.labels.add(unique);
|
data.values.labels.add(unique);
|
||||||
data.values.id.add(createUid(ts, uniqueLabelsString, line));
|
data.values.id.add(createUid(ts, allLabelsString, line));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import ResponseParser from './response_parser';
|
import ResponseParser from './response_parser';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
import { ScopedVars } from '@grafana/data';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
//Types
|
//Types
|
||||||
@@ -48,14 +49,17 @@ export class MssqlDatasource {
|
|||||||
return quotedValues.join(',');
|
return quotedValues.join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolateVariablesInQueries(queries: MssqlQueryForInterpolation[]): MssqlQueryForInterpolation[] {
|
interpolateVariablesInQueries(
|
||||||
|
queries: MssqlQueryForInterpolation[],
|
||||||
|
scopedVars: ScopedVars
|
||||||
|
): MssqlQueryForInterpolation[] {
|
||||||
let expandedQueries = queries;
|
let expandedQueries = queries;
|
||||||
if (queries && queries.length > 0) {
|
if (queries && queries.length > 0) {
|
||||||
expandedQueries = queries.map(query => {
|
expandedQueries = queries.map(query => {
|
||||||
const expandedQuery = {
|
const expandedQuery = {
|
||||||
...query,
|
...query,
|
||||||
datasource: this.name,
|
datasource: this.name,
|
||||||
rawSql: this.templateSrv.replace(query.rawSql, {}, this.interpolateVariable),
|
rawSql: this.templateSrv.replace(query.rawSql, scopedVars, this.interpolateVariable),
|
||||||
};
|
};
|
||||||
return expandedQuery;
|
return expandedQuery;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||||||
import ResponseParser from './response_parser';
|
import ResponseParser from './response_parser';
|
||||||
import MysqlQuery from 'app/plugins/datasource/mysql/mysql_query';
|
import MysqlQuery from 'app/plugins/datasource/mysql/mysql_query';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
import { ScopedVars } from '@grafana/data';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
//Types
|
//Types
|
||||||
@@ -32,7 +33,8 @@ export class MysqlDatasource {
|
|||||||
interpolateVariable = (value: string, variable: any) => {
|
interpolateVariable = (value: string, variable: any) => {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
if (variable.multi || variable.includeAll) {
|
if (variable.multi || variable.includeAll) {
|
||||||
return this.queryModel.quoteLiteral(value);
|
const result = this.queryModel.quoteLiteral(value);
|
||||||
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -48,14 +50,17 @@ export class MysqlDatasource {
|
|||||||
return quotedValues.join(',');
|
return quotedValues.join(',');
|
||||||
};
|
};
|
||||||
|
|
||||||
interpolateVariablesInQueries(queries: MysqlQueryForInterpolation[]): MysqlQueryForInterpolation[] {
|
interpolateVariablesInQueries(
|
||||||
|
queries: MysqlQueryForInterpolation[],
|
||||||
|
scopedVars: ScopedVars
|
||||||
|
): MysqlQueryForInterpolation[] {
|
||||||
let expandedQueries = queries;
|
let expandedQueries = queries;
|
||||||
if (queries && queries.length > 0) {
|
if (queries && queries.length > 0) {
|
||||||
expandedQueries = queries.map(query => {
|
expandedQueries = queries.map(query => {
|
||||||
const expandedQuery = {
|
const expandedQuery = {
|
||||||
...query,
|
...query,
|
||||||
datasource: this.name,
|
datasource: this.name,
|
||||||
rawSql: this.templateSrv.replace(query.rawSql, {}, this.interpolateVariable),
|
rawSql: this.templateSrv.replace(query.rawSql, scopedVars, this.interpolateVariable),
|
||||||
};
|
};
|
||||||
return expandedQuery;
|
return expandedQuery;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||||||
import ResponseParser from './response_parser';
|
import ResponseParser from './response_parser';
|
||||||
import PostgresQuery from 'app/plugins/datasource/postgres/postgres_query';
|
import PostgresQuery from 'app/plugins/datasource/postgres/postgres_query';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
import { ScopedVars } from '@grafana/data';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
//Types
|
//Types
|
||||||
@@ -50,14 +51,17 @@ export class PostgresDatasource {
|
|||||||
return quotedValues.join(',');
|
return quotedValues.join(',');
|
||||||
};
|
};
|
||||||
|
|
||||||
interpolateVariablesInQueries(queries: PostgresQueryForInterpolation[]): PostgresQueryForInterpolation[] {
|
interpolateVariablesInQueries(
|
||||||
|
queries: PostgresQueryForInterpolation[],
|
||||||
|
scopedVars: ScopedVars
|
||||||
|
): PostgresQueryForInterpolation[] {
|
||||||
let expandedQueries = queries;
|
let expandedQueries = queries;
|
||||||
if (queries && queries.length > 0) {
|
if (queries && queries.length > 0) {
|
||||||
expandedQueries = queries.map(query => {
|
expandedQueries = queries.map(query => {
|
||||||
const expandedQuery = {
|
const expandedQuery = {
|
||||||
...query,
|
...query,
|
||||||
datasource: this.name,
|
datasource: this.name,
|
||||||
rawSql: this.templateSrv.replace(query.rawSql, {}, this.interpolateVariable),
|
rawSql: this.templateSrv.replace(query.rawSql, scopedVars, this.interpolateVariable),
|
||||||
};
|
};
|
||||||
return expandedQuery;
|
return expandedQuery;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
DataQueryResponseData,
|
DataQueryResponseData,
|
||||||
DataSourceApi,
|
DataSourceApi,
|
||||||
DataSourceInstanceSettings,
|
DataSourceInstanceSettings,
|
||||||
|
ScopedVars,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { from, merge, Observable, of, forkJoin } from 'rxjs';
|
import { from, merge, Observable, of, forkJoin } from 'rxjs';
|
||||||
import { filter, map, tap } from 'rxjs/operators';
|
import { filter, map, tap } from 'rxjs/operators';
|
||||||
@@ -608,14 +609,14 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
: { status: 'error', message: response.error };
|
: { status: 'error', message: response.error };
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolateVariablesInQueries(queries: PromQuery[]): PromQuery[] {
|
interpolateVariablesInQueries(queries: PromQuery[], scopedVars: ScopedVars): PromQuery[] {
|
||||||
let expandedQueries = queries;
|
let expandedQueries = queries;
|
||||||
if (queries && queries.length) {
|
if (queries && queries.length) {
|
||||||
expandedQueries = queries.map(query => {
|
expandedQueries = queries.map(query => {
|
||||||
const expandedQuery = {
|
const expandedQuery = {
|
||||||
...query,
|
...query,
|
||||||
datasource: this.name,
|
datasource: this.name,
|
||||||
expr: templateSrv.replace(query.expr, {}, this.interpolateQueryExpr),
|
expr: templateSrv.replace(query.expr, scopedVars, this.interpolateQueryExpr),
|
||||||
};
|
};
|
||||||
return expandedQuery;
|
return expandedQuery;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
class="gf-form-input width-6"
|
class="gf-form-input width-6"
|
||||||
placeholder="auto"
|
placeholder="auto"
|
||||||
ng-model="ctrl.target.startValue"
|
ng-model="ctrl.target.startValue"
|
||||||
min="1"
|
|
||||||
step="1"
|
step="1"
|
||||||
ng-change="ctrl.refresh()" />
|
ng-change="ctrl.refresh()" />
|
||||||
</div>
|
</div>
|
||||||
@@ -95,7 +94,6 @@
|
|||||||
class="gf-form-input width-6"
|
class="gf-form-input width-6"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
ng-model="ctrl.target.min"
|
ng-model="ctrl.target.min"
|
||||||
min="0"
|
|
||||||
step="0.1"
|
step="0.1"
|
||||||
ng-change="ctrl.refresh()" />
|
ng-change="ctrl.refresh()" />
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +103,6 @@
|
|||||||
class="gf-form-input width-6"
|
class="gf-form-input width-6"
|
||||||
placeholder="none"
|
placeholder="none"
|
||||||
ng-model="ctrl.target.max"
|
ng-model="ctrl.target.max"
|
||||||
min="0"
|
|
||||||
step="0.1"
|
step="0.1"
|
||||||
ng-change="ctrl.refresh()" />
|
ng-change="ctrl.refresh()" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { ColumnRender, TableRenderModel, ColumnStyle } from './types';
|
import { ColumnRender, TableRenderModel, ColumnStyle } from './types';
|
||||||
import { ColumnOptionsCtrl } from './column_options';
|
import { ColumnOptionsCtrl } from './column_options';
|
||||||
|
import { sanitizeUrl } from 'app/core/utils/text';
|
||||||
|
|
||||||
export class TableRenderer {
|
export class TableRenderer {
|
||||||
formatters: any[];
|
formatters: any[];
|
||||||
@@ -297,13 +298,15 @@ export class TableRenderer {
|
|||||||
scopedVars['__cell'] = { value: value, text: value ? value.toString() : '' };
|
scopedVars['__cell'] = { value: value, text: value ? value.toString() : '' };
|
||||||
|
|
||||||
const cellLink = this.templateSrv.replace(column.style.linkUrl, scopedVars, encodeURIComponent);
|
const cellLink = this.templateSrv.replace(column.style.linkUrl, scopedVars, encodeURIComponent);
|
||||||
|
const sanitizedCellLink = sanitizeUrl(cellLink);
|
||||||
|
|
||||||
const cellLinkTooltip = this.templateSrv.replace(column.style.linkTooltip, scopedVars);
|
const cellLinkTooltip = this.templateSrv.replace(column.style.linkTooltip, scopedVars);
|
||||||
const cellTarget = column.style.linkTargetBlank ? '_blank' : '';
|
const cellTarget = column.style.linkTargetBlank ? '_blank' : '';
|
||||||
|
|
||||||
cellClasses.push('table-panel-cell-link');
|
cellClasses.push('table-panel-cell-link');
|
||||||
|
|
||||||
columnHtml += `
|
columnHtml += `
|
||||||
<a href="${cellLink}" target="${cellTarget}" data-link-tooltip data-original-title="${cellLinkTooltip}" data-placement="right"${cellStyle}>
|
<a href="${sanitizedCellLink}" target="${cellTarget}" data-link-tooltip data-original-title="${cellLinkTooltip}" data-placement="right"${cellStyle}>
|
||||||
${value}
|
${value}
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -115,12 +115,13 @@ select:-webkit-autofill:focus {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
padding-top: $space-xl;
|
padding: $space-xl;
|
||||||
|
}
|
||||||
|
|
||||||
.logo-icon {
|
.login-logo {
|
||||||
width: 70px;
|
width: 100%;
|
||||||
margin-bottom: 15px;
|
max-width: 250px;
|
||||||
}
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-grafana {
|
.app-grafana {
|
||||||
@@ -285,10 +286,6 @@ select:-webkit-autofill:focus {
|
|||||||
@include media-breakpoint-up(sm) {
|
@include media-breakpoint-up(sm) {
|
||||||
.login-branding {
|
.login-branding {
|
||||||
padding: $space-md;
|
padding: $space-md;
|
||||||
|
|
||||||
.logo-icon {
|
|
||||||
width: 80px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,10 +306,6 @@ select:-webkit-autofill:focus {
|
|||||||
padding: $space-xl;
|
padding: $space-xl;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
border-right: 1px solid $login-border;
|
border-right: 1px solid $login-border;
|
||||||
|
|
||||||
.logo-icon {
|
|
||||||
width: 130px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-button-group {
|
.login-button-group {
|
||||||
|
|||||||
@@ -110,22 +110,22 @@ RUN apt-get update && \
|
|||||||
| tar -xz -C /usr/local && \
|
| tar -xz -C /usr/local && \
|
||||||
git clone https://github.com/raspberrypi/tools.git /opt/rpi-tools --depth=1
|
git clone https://github.com/raspberrypi/tools.git /opt/rpi-tools --depth=1
|
||||||
|
|
||||||
ARG CHKSUM_ARMV7_MUSL=76c5539aed3d46d61c66d60757dd4083ab05420e4252aca04eaeb791e3342dac
|
ARG CHKSUM_ARMV7_MUSL=1b52816ac68d85de19c5248636034105440ea46708062a92cf8f8438fcce70269d44e30b7b39b67e4816db5178e5df9c32000aadd19a43e0ac42cfeac36f72c0
|
||||||
ARG CHKSUM_ARMV8_MUSL=6d4f7249e067ef6d054bcebe9eaf0c581972767f86ad78d2b53d832d1126c52e
|
ARG CHKSUM_ARMV8_MUSL=54f49ee7ee828a31687d915a0b94cf2e70d79f6b942e2ebfca973beaabdfb035f441921b4c16d5bdce66f3acc902d17b54bb9ebefa12d06c1c6c448435276ae8
|
||||||
ARG CHKSUM_AMD64_MUSL=8c487b0f56bf600008e8f37041cbce37f6fe1e001acd3eca8bb16e30e16e8ae7
|
ARG CHKSUM_AMD64_MUSL=f7bcb35b4db01c8e1bd65c9e6dc9bc9681dae1e32d00ca094f91f95cad9799ea3251e01c69148f1547fdbf476d7ae1ba5ebf8da8c87cd073e04e0d3c127743e5
|
||||||
|
|
||||||
# Install musl cross compilers
|
# Install musl cross compilers
|
||||||
RUN cd /tmp && \
|
RUN cd /tmp && \
|
||||||
curl -fO https://musl.cc/arm-linux-musleabihf-cross.tgz && \
|
curl -fO https://musl.cc/arm-linux-musleabihf-cross.tgz && \
|
||||||
([ "$(sha256sum arm-linux-musleabihf-cross.tgz|cut -f1 -d ' ')" = "$CHKSUM_ARMV7_MUSL" ] || (echo "Mismatching checksums armv7"; exit 1)) && \
|
([ "$(sha512sum arm-linux-musleabihf-cross.tgz|cut -f1 -d ' ')" = "$CHKSUM_ARMV7_MUSL" ] || (echo "Mismatching checksums armv7"; exit 1)) && \
|
||||||
tar xf arm-linux-musleabihf-cross.tgz && \
|
tar xf arm-linux-musleabihf-cross.tgz && \
|
||||||
rm arm-linux-musleabihf-cross.tgz && \
|
rm arm-linux-musleabihf-cross.tgz && \
|
||||||
curl -fO https://musl.cc/aarch64-linux-musl-cross.tgz && \
|
curl -fO https://musl.cc/aarch64-linux-musl-cross.tgz && \
|
||||||
([ "$(sha256sum aarch64-linux-musl-cross.tgz|cut -f1 -d ' ')" = "$CHKSUM_ARMV8_MUSL" ] || (echo "Mismatching checksums armv8"; exit 1)) && \
|
([ "$(sha512sum aarch64-linux-musl-cross.tgz|cut -f1 -d ' ')" = "$CHKSUM_ARMV8_MUSL" ] || (echo "Mismatching checksums armv8"; exit 1)) && \
|
||||||
tar xf aarch64-linux-musl-cross.tgz && \
|
tar xf aarch64-linux-musl-cross.tgz && \
|
||||||
rm aarch64-linux-musl-cross.tgz && \
|
rm aarch64-linux-musl-cross.tgz && \
|
||||||
curl -fO https://musl.cc/x86_64-linux-musl-cross.tgz && \
|
curl -fO https://musl.cc/x86_64-linux-musl-cross.tgz && \
|
||||||
([ "$(sha256sum x86_64-linux-musl-cross.tgz|cut -f1 -d ' ')" = "$CHKSUM_AMD64_MUSL" ] || (echo "Mismatching checksums amd64"; exit 1)) && \
|
([ "$(sha512sum x86_64-linux-musl-cross.tgz|cut -f1 -d ' ')" = "$CHKSUM_AMD64_MUSL" ] || (echo "Mismatching checksums amd64"; exit 1)) && \
|
||||||
tar xf x86_64-linux-musl-cross.tgz && \
|
tar xf x86_64-linux-musl-cross.tgz && \
|
||||||
rm x86_64-linux-musl-cross.tgz
|
rm x86_64-linux-musl-cross.tgz
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ EXTRA_OPTS="$@"
|
|||||||
|
|
||||||
# Right now we hack this in into the publish script.
|
# Right now we hack this in into the publish script.
|
||||||
# Eventually we might want to keep a list of all previous releases somewhere.
|
# Eventually we might want to keep a list of all previous releases somewhere.
|
||||||
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v6-0-x/14010"
|
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v6-6-x/24924"
|
||||||
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v6-0/"
|
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v6-6/"
|
||||||
|
|
||||||
./scripts/build/release_publisher/release_publisher \
|
./scripts/build/release_publisher/release_publisher \
|
||||||
--wn "${_whatsNewUrl}" \
|
--wn "${_whatsNewUrl}" \
|
||||||
|
|||||||
@@ -21509,6 +21509,11 @@ url-parse@^1.4.3:
|
|||||||
querystringify "^2.1.1"
|
querystringify "^2.1.1"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
|
||||||
|
url-search-params-polyfill@7.0.1:
|
||||||
|
version "7.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/url-search-params-polyfill/-/url-search-params-polyfill-7.0.1.tgz#b900cd9a0d9d2ff757d500135256f2344879cbff"
|
||||||
|
integrity sha512-bAw7L2E+jn9XHG5P9zrPnHdO0yJub4U+yXJOdpcpkr7OBd9T8oll4lUos0iSGRcDvfZoLUKfx9a6aNmIhJ4+mQ==
|
||||||
|
|
||||||
url@0.11.0, url@^0.11.0:
|
url@0.11.0, url@^0.11.0:
|
||||||
version "0.11.0"
|
version "0.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||||
|
|||||||
Reference in New Issue
Block a user