mirror of
https://github.com/grafana/grafana.git
synced 2025-12-20 19:44:55 +08:00
Compare commits
36 Commits
sriram/pos
...
v6.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fef1733b0e | ||
|
|
7219633952 | ||
|
|
2926f9b0b1 | ||
|
|
db39c105ed | ||
|
|
2985098f8e | ||
|
|
bd42db2743 | ||
|
|
c4222bb65a | ||
|
|
9504db83bf | ||
|
|
0992fbc4be | ||
|
|
1f35ce691c | ||
|
|
be84bffa57 | ||
|
|
a2236de67f | ||
|
|
de8f6ac4b1 | ||
|
|
0c7d43b999 | ||
|
|
0974663079 | ||
|
|
9eba279b8d | ||
|
|
da0302f15e | ||
|
|
eff01d2b54 | ||
|
|
cbc515b22c | ||
|
|
638d49dd6e | ||
|
|
9d452256bf | ||
|
|
f941f25831 | ||
|
|
b585fdec6f | ||
|
|
e6c639f6f0 | ||
|
|
03346b6f6f | ||
|
|
9a575f93ad | ||
|
|
21957ad515 | ||
|
|
bd93aad63d | ||
|
|
8ae5980c23 | ||
|
|
eb38581dc1 | ||
|
|
be217d8c0e | ||
|
|
dfbc3bfb1f | ||
|
|
a6c8cd7f3a | ||
|
|
56e4032db3 | ||
|
|
944e526eb9 | ||
|
|
0d6db7e6b8 |
4
.browserslistrc
Normal file
4
.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
||||
>1%,
|
||||
Chrome > 20
|
||||
last 4 versions,
|
||||
Firefox ESR
|
||||
@@ -322,7 +322,7 @@ jobs:
|
||||
|
||||
deploy-enterprise-master:
|
||||
docker:
|
||||
- image: grafana/grafana-ci-deploy:1.2.1
|
||||
- image: grafana/grafana-ci-deploy:1.2.2
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
@@ -347,7 +347,7 @@ jobs:
|
||||
|
||||
deploy-enterprise-release:
|
||||
docker:
|
||||
- image: grafana/grafana-ci-deploy:1.2.1
|
||||
- image: grafana/grafana-ci-deploy:1.2.2
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
@@ -380,7 +380,7 @@ jobs:
|
||||
|
||||
deploy-master:
|
||||
docker:
|
||||
- image: grafana/grafana-ci-deploy:1.2.1
|
||||
- image: grafana/grafana-ci-deploy:1.2.2
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
@@ -411,7 +411,7 @@ jobs:
|
||||
|
||||
deploy-release:
|
||||
docker:
|
||||
- image: grafana/grafana-ci-deploy:1.2.1
|
||||
- image: grafana/grafana-ci-deploy:1.2.2
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
|
||||
1
build.go
1
build.go
@@ -361,6 +361,7 @@ func createPackage(options linuxPackageOptions) {
|
||||
fmt.Printf("pkgArch is set to '%s', generated arch is '%s'\n", pkgArch, options.packageArch)
|
||||
if pkgArch == "armv6" {
|
||||
name += "-rpi"
|
||||
args = append(args, "--replaces", "grafana")
|
||||
}
|
||||
args = append(args, "--name", name)
|
||||
|
||||
|
||||
55
docs/sources/guides/whats-new-in-v6-1.md
Normal file
55
docs/sources/guides/whats-new-in-v6-1.md
Normal file
@@ -0,0 +1,55 @@
|
||||
+++
|
||||
title = "What's New in Grafana v6.1"
|
||||
description = "Feature & improvement highlights for Grafana v6.1"
|
||||
keywords = ["grafana", "new", "documentation", "6.1"]
|
||||
type = "docs"
|
||||
[menu.docs]
|
||||
name = "Version 6.1"
|
||||
identifier = "v6.1"
|
||||
parent = "whatsnew"
|
||||
weight = -12
|
||||
+++
|
||||
|
||||
# What's New in Grafana v6.1
|
||||
|
||||
## Highlights
|
||||
|
||||
### Ad hoc Filtering for Prometheus
|
||||
|
||||
{{< imgbox max-width="30%" img="/img/docs/v61/prometheus-ad-hoc.gif" caption="Ad-hoc filters variable for Prometheus" >}}
|
||||
|
||||
The ad hoc filter feature allows you to create new key/value filters on the fly with autocomplete for both key and values. The filter condition is then automatically applied to all queries on the dashboard. This makes it easier to explore your data in a dashboard without changing queries and without having to add new template variables.
|
||||
|
||||
Other timeseries databases with label-based query languages have had this feature for a while. Recently Prometheus added support for fetching label names from their API and thanks to [Mitsuhiro Tanda](https://github.com/mtanda) implementing it in Grafana, the Prometheus datasource finally supports ad hoc filtering.
|
||||
|
||||
Support for fetching a list of label names was released in Prometheus v2.6.0 so that is a requirement for this feature to work in Grafana.
|
||||
|
||||
### Permissions: Editors can own dashboards, folders and teams they create
|
||||
|
||||
When the dashboard folders feature and permissions system was released in Grafana 5.0, users with the editor role were not allowed to administrate dashboards, folders or teams. In the 6.1 release, we have added a config option so that by default editors are admins for any Dashboard, Folder or Team they create.
|
||||
|
||||
This feature also adds a new Team permission that can be assigned to any user with the editor or viewer role and lets that user add other users to the Team.
|
||||
|
||||
We believe that this is more in line with the Grafana philosophy, as it will allow teams to be more self-organizing. This option will be made permanent if it gets positive feedback from the community so let us know what you think in the [issue on GitHub](https://github.com/grafana/grafana/issues/15590).
|
||||
|
||||
To turn this feature on add the following [config option](/installation/configuration/#editors-can-admin) to your Grafana ini file in the `users` section and then restart the Grafana server:
|
||||
|
||||
```ini
|
||||
[users]
|
||||
editors_can_admin = true
|
||||
```
|
||||
|
||||
### Minor Features and Fixes
|
||||
|
||||
This release contains a lot of small features and fixes:
|
||||
|
||||
- A new keyboard shortcut `d l` toggles all Graph legends in a dashboard.
|
||||
- A small bug fix for Elasticsearch - template variables in the alias field now work properly.
|
||||
- Some new capabilities have been added for datasource plugins that will be of interest to plugin authors:
|
||||
- a new oauth pass-through option.
|
||||
- it is now possible to add user details to requests sent to the dataproxy.
|
||||
- Heatmap and Explore fixes.
|
||||
|
||||
Checkout the [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) file for a complete list of new features, changes, and bug fixes.
|
||||
|
||||
A huge thanks to our community for all the reported issues, bug fixes and feedback.
|
||||
@@ -152,6 +152,7 @@ Content-Type: application/json
|
||||
PUT /api/alert-notifications/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
```
|
||||
|
||||
@@ -170,7 +171,7 @@ Content-Type: application/json
|
||||
Deletes an existing notification channel identified by uid.
|
||||
|
||||
**Example Request**:
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
DELETE /api/alert-notifications/uid/team-a-email-notifier HTTP/1.1
|
||||
Accept: application/json
|
||||
@@ -198,6 +199,7 @@ Content-Type: application/json
|
||||
DELETE /api/alert-notifications/1 HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
@@ -217,7 +219,7 @@ Content-Type: application/json
|
||||
|
||||
**Example Request**:
|
||||
|
||||
**Example Request**:
|
||||
```http
|
||||
POST /api/alert-notifications/test HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
@@ -247,7 +249,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"uid": "cIBgcSjkk",
|
||||
"uid": "new-alert-notification", // optional
|
||||
"name": "new alert notification", //Required
|
||||
"type": "email", //Required
|
||||
"isDefault": false,
|
||||
@@ -267,7 +269,7 @@ Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"uid": "cIBgcSjkk",
|
||||
"uid": "new-alert-notification",
|
||||
"name": "new alert notification",
|
||||
"type": "email",
|
||||
"isDefault": false,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"company": "Grafana Labs"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "6.1.0-pre",
|
||||
"version": "6.1.4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
|
||||
@@ -72,7 +72,7 @@ export class Input extends PureComponent<Props> {
|
||||
const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<input {...inputElementProps} className={inputClassName} />
|
||||
{error && !hideErrorMessage && <span>{error}</span>}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Input renders correctly 1`] = `
|
||||
<div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import uniqueId from 'lodash/uniqueId';
|
||||
import { Input } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
label: string;
|
||||
@@ -39,7 +38,7 @@ export class Switch extends PureComponent<Props, State> {
|
||||
<label htmlFor={labelId} className={`gf-form gf-form-switch-container ${className || ''}`}>
|
||||
{label && <div className={labelClassName}>{label}</div>}
|
||||
<div className={switchClassName}>
|
||||
<Input id={labelId} type="checkbox" checked={checked} onChange={this.internalOnChange} />
|
||||
<input id={labelId} type="checkbox" checked={checked} onChange={this.internalOnChange} />
|
||||
<span className="gf-form-switch__slider" />
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -148,10 +148,10 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"grayBlue": "#212327",
|
||||
"greenBase": "#299c46",
|
||||
"greenShade": "#23843b",
|
||||
"headingColor": "#e3e3e3",
|
||||
"headingColor": "#d8d9da",
|
||||
"inputBlack": "#09090b",
|
||||
"link": "#e3e3e3",
|
||||
"linkDisabled": "#e3e3e3",
|
||||
"link": "#d8d9da",
|
||||
"linkDisabled": "#8e8e8e",
|
||||
"linkExternal": "#33b5e5",
|
||||
"linkHover": "#ffffff",
|
||||
"online": "#299c46",
|
||||
@@ -178,8 +178,8 @@ exports[`Render should render with base threshold 1`] = `
|
||||
},
|
||||
"name": "Grafana Dark",
|
||||
"panelPadding": Object {
|
||||
"horizontal": 10,
|
||||
"vertical": 5,
|
||||
"horizontal": 16,
|
||||
"vertical": 8,
|
||||
},
|
||||
"spacing": Object {
|
||||
"d": "14px",
|
||||
@@ -305,10 +305,10 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"grayBlue": "#212327",
|
||||
"greenBase": "#299c46",
|
||||
"greenShade": "#23843b",
|
||||
"headingColor": "#e3e3e3",
|
||||
"headingColor": "#d8d9da",
|
||||
"inputBlack": "#09090b",
|
||||
"link": "#e3e3e3",
|
||||
"linkDisabled": "#e3e3e3",
|
||||
"link": "#d8d9da",
|
||||
"linkDisabled": "#8e8e8e",
|
||||
"linkExternal": "#33b5e5",
|
||||
"linkHover": "#ffffff",
|
||||
"online": "#299c46",
|
||||
@@ -335,8 +335,8 @@ exports[`Render should render with base threshold 1`] = `
|
||||
},
|
||||
"name": "Grafana Dark",
|
||||
"panelPadding": Object {
|
||||
"horizontal": 10,
|
||||
"vertical": 5,
|
||||
"horizontal": 16,
|
||||
"vertical": 8,
|
||||
},
|
||||
"spacing": Object {
|
||||
"d": "14px",
|
||||
@@ -465,7 +465,13 @@ exports[`Render should render with base threshold 1`] = `
|
||||
type="text"
|
||||
value="Base"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
readOnly={true}
|
||||
|
||||
@@ -197,7 +197,9 @@ $side-menu-width: 60px;
|
||||
|
||||
// dashboard
|
||||
$dashboard-padding: $space-md;
|
||||
$panel-padding: 0 $space-md $space-sm $space-md;
|
||||
$panel-padding: 0 ${theme.panelPadding.horizontal}px ${theme.panelPadding.vertical}px ${
|
||||
theme.panelPadding.horizontal
|
||||
}px;
|
||||
|
||||
// tabs
|
||||
$tabs-padding: 10px 15px 9px;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import tinycolor from 'tinycolor2';
|
||||
import defaultTheme from './default';
|
||||
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
|
||||
|
||||
@@ -66,11 +65,11 @@ const darkTheme: GrafanaTheme = {
|
||||
textWeak: basicColors.gray2,
|
||||
textEmphasis: basicColors.gray5,
|
||||
textFaint: basicColors.dark5,
|
||||
link: new tinycolor(basicColors.white).darken(11).toString(),
|
||||
linkDisabled: new tinycolor(basicColors.white).darken(11).toString(),
|
||||
link: basicColors.gray4,
|
||||
linkDisabled: basicColors.gray2,
|
||||
linkHover: basicColors.white,
|
||||
linkExternal: basicColors.blue,
|
||||
headingColor: new tinycolor(basicColors.white).darken(11).toString(),
|
||||
headingColor: basicColors.gray4,
|
||||
},
|
||||
background: {
|
||||
dropdown: basicColors.dark3,
|
||||
|
||||
@@ -67,8 +67,8 @@ const theme: GrafanaThemeCommons = {
|
||||
},
|
||||
},
|
||||
panelPadding: {
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
},
|
||||
zIndex: {
|
||||
dropdown: '1000',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import tinycolor from 'tinycolor2';
|
||||
import defaultTheme from './default';
|
||||
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
|
||||
|
||||
@@ -65,11 +64,11 @@ const lightTheme: GrafanaTheme = {
|
||||
text: basicColors.gray1,
|
||||
textStrong: basicColors.dark2,
|
||||
textWeak: basicColors.gray2,
|
||||
textEmphasis: basicColors.gray5,
|
||||
textEmphasis: basicColors.dark5,
|
||||
textFaint: basicColors.dark4,
|
||||
link: basicColors.gray1,
|
||||
linkDisabled: new tinycolor(basicColors.gray1).lighten(30).toString(),
|
||||
linkHover: new tinycolor(basicColors.gray1).darken(20).toString(),
|
||||
linkDisabled: basicColors.gray3,
|
||||
linkHover: basicColors.dark1,
|
||||
linkExternal: basicColors.blueLight,
|
||||
headingColor: basicColors.gray1,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,9 @@ export interface DisplayValue {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export type DecimalCount = number | null | undefined;
|
||||
|
||||
export interface DecimalInfo {
|
||||
decimals: number;
|
||||
scaledDecimals: number;
|
||||
decimals: DecimalCount;
|
||||
scaledDecimals: DecimalCount;
|
||||
}
|
||||
|
||||
@@ -158,6 +158,12 @@ describe('Format value', () => {
|
||||
expect(instance(value).text).toEqual('0.02');
|
||||
});
|
||||
|
||||
it('should use override decimals', () => {
|
||||
const value = 100030303;
|
||||
const instance = getDisplayProcessor({ decimals: 2, unit: 'bytes' });
|
||||
expect(instance(value).text).toEqual('95.40 MiB');
|
||||
});
|
||||
|
||||
it('should return mapped value if there are matching value mappings', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
|
||||
@@ -182,5 +188,6 @@ describe('getDecimalsForValue()', () => {
|
||||
expect(getDecimalsForValue(20000)).toEqual({ decimals: 0, scaledDecimals: -2 });
|
||||
expect(getDecimalsForValue(200000)).toEqual({ decimals: 0, scaledDecimals: -3 });
|
||||
expect(getDecimalsForValue(200000000)).toEqual({ decimals: 0, scaledDecimals: -6 });
|
||||
expect(getDecimalsForValue(100, 2)).toEqual({ decimals: 2, scaledDecimals: null });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,8 +8,15 @@ import { getMappedValue } from './valueMappings';
|
||||
import { getColorFromHexRgbOrName } from './namedColorsPalette';
|
||||
|
||||
// Types
|
||||
import { Threshold, ValueMapping, DecimalInfo, DisplayValue, GrafanaTheme, GrafanaThemeType } from '../types';
|
||||
import { DecimalCount } from './valueFormats/valueFormats';
|
||||
import {
|
||||
Threshold,
|
||||
ValueMapping,
|
||||
DecimalInfo,
|
||||
DisplayValue,
|
||||
GrafanaTheme,
|
||||
GrafanaThemeType,
|
||||
DecimalCount,
|
||||
} from '../types';
|
||||
|
||||
export type DisplayProcessor = (value: any) => DisplayValue;
|
||||
|
||||
@@ -69,18 +76,7 @@ export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProce
|
||||
|
||||
if (!isNaN(numeric)) {
|
||||
if (shouldFormat && !_.isBoolean(value)) {
|
||||
let decimals;
|
||||
let scaledDecimals = 0;
|
||||
|
||||
if (!options.decimals) {
|
||||
const decimalInfo = getDecimalsForValue(value);
|
||||
|
||||
decimals = decimalInfo.decimals;
|
||||
scaledDecimals = decimalInfo.scaledDecimals;
|
||||
} else {
|
||||
decimals = options.decimals;
|
||||
}
|
||||
|
||||
const { decimals, scaledDecimals } = getDecimalsForValue(value, options.decimals);
|
||||
text = formatFunc(numeric, decimals, scaledDecimals, options.isUtc);
|
||||
}
|
||||
if (thresholds && thresholds.length > 0) {
|
||||
@@ -159,7 +155,12 @@ export function getColorFromThreshold(value: number, thresholds: Threshold[], th
|
||||
return getColorFromHexRgbOrName(thresholds[0].color, themeType);
|
||||
}
|
||||
|
||||
export function getDecimalsForValue(value: number): DecimalInfo {
|
||||
export function getDecimalsForValue(value: number, decimalOverride?: DecimalCount): DecimalInfo {
|
||||
if (_.isNumber(decimalOverride)) {
|
||||
// It's important that scaledDecimals is null here
|
||||
return { decimals: decimalOverride, scaledDecimals: null };
|
||||
}
|
||||
|
||||
const delta = value / 2;
|
||||
let dec = -Math.floor(Math.log(delta) / Math.LN10);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { toFixed, DecimalCount } from './valueFormats';
|
||||
import { toFixed } from './valueFormats';
|
||||
import { DecimalCount } from '../../types';
|
||||
|
||||
export function toPercent(size: number, decimals: DecimalCount) {
|
||||
if (size === null) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { toFixed, toFixedScaled, DecimalCount } from './valueFormats';
|
||||
import { toFixed, toFixedScaled } from './valueFormats';
|
||||
import { DecimalCount } from '../../types';
|
||||
import moment from 'moment';
|
||||
|
||||
interface IntervalsInSeconds {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { scaledUnits, DecimalCount } from './valueFormats';
|
||||
import { scaledUnits } from './valueFormats';
|
||||
import { DecimalCount } from '../../types';
|
||||
|
||||
export function currency(symbol: string) {
|
||||
const units = ['', 'K', 'M', 'B', 'T'];
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { toFixed, getValueFormat } from './valueFormats';
|
||||
|
||||
describe('valueFormats', () => {
|
||||
describe('toFixed and negative decimals', () => {
|
||||
it('should treat as zero decimals', () => {
|
||||
const str = toFixed(186.123, -2);
|
||||
expect(str).toBe('186');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ms format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('ms')(10000086.123, 1, null);
|
||||
expect(str).toBe('2.8 hour');
|
||||
});
|
||||
});
|
||||
|
||||
describe('kbytes format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('kbytes')(10000000, 3, null);
|
||||
expect(str).toBe('9.537 GiB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deckbytes format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('deckbytes')(10000000, 3, null);
|
||||
expect(str).toBe('10.000 GB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ms format when scaled decimals is 0', () => {
|
||||
it('should use scaledDecimals and add 3', () => {
|
||||
const str = getValueFormat('ms')(1200, 0, 0);
|
||||
expect(str).toBe('1.200 s');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getCategories } from './categories';
|
||||
|
||||
export type DecimalCount = number | null | undefined;
|
||||
import { DecimalCount } from '../../types';
|
||||
|
||||
export type ValueFormatter = (
|
||||
value: number,
|
||||
@@ -57,17 +56,15 @@ export function toFixed(value: number, decimals?: DecimalCount): string {
|
||||
|
||||
export function toFixedScaled(
|
||||
value: number,
|
||||
decimals?: DecimalCount,
|
||||
scaledDecimals?: DecimalCount,
|
||||
additionalDecimals?: DecimalCount,
|
||||
decimals: DecimalCount,
|
||||
scaledDecimals: DecimalCount,
|
||||
additionalDecimals: number,
|
||||
ext?: string
|
||||
) {
|
||||
if (scaledDecimals) {
|
||||
if (additionalDecimals) {
|
||||
return toFixed(value, scaledDecimals + additionalDecimals) + ext;
|
||||
} else {
|
||||
return toFixed(value, scaledDecimals) + ext;
|
||||
}
|
||||
if (scaledDecimals === null || scaledDecimals === undefined) {
|
||||
return toFixed(value, decimals) + ext;
|
||||
} else {
|
||||
return toFixed(value, scaledDecimals + additionalDecimals) + ext;
|
||||
}
|
||||
|
||||
return toFixed(value, decimals) + ext;
|
||||
|
||||
@@ -261,6 +261,10 @@ func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationComma
|
||||
return Error(500, "Failed to update alert notification", err)
|
||||
}
|
||||
|
||||
if cmd.Result == nil {
|
||||
return Error(404, "Alert notification not found", nil)
|
||||
}
|
||||
|
||||
return JSON(200, dtos.NewAlertNotification(cmd.Result))
|
||||
}
|
||||
|
||||
@@ -272,6 +276,10 @@ func UpdateAlertNotificationByUID(c *m.ReqContext, cmd m.UpdateAlertNotification
|
||||
return Error(500, "Failed to update alert notification", err)
|
||||
}
|
||||
|
||||
if cmd.Result == nil {
|
||||
return Error(404, "Alert notification not found", nil)
|
||||
}
|
||||
|
||||
return JSON(200, dtos.NewAlertNotification(cmd.Result))
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ type AlertNotification struct {
|
||||
}
|
||||
|
||||
type CreateAlertNotificationCommand struct {
|
||||
Uid string `json:"-"`
|
||||
Uid string `json:"uid"`
|
||||
Name string `json:"name" binding:"Required"`
|
||||
Type string `json:"type" binding:"Required"`
|
||||
SendReminder bool `json:"sendReminder"`
|
||||
@@ -54,6 +54,7 @@ type CreateAlertNotificationCommand struct {
|
||||
|
||||
type UpdateAlertNotificationCommand struct {
|
||||
Id int64 `json:"id" binding:"Required"`
|
||||
Uid string `json:"uid"`
|
||||
Name string `json:"name" binding:"Required"`
|
||||
Type string `json:"type" binding:"Required"`
|
||||
SendReminder bool `json:"sendReminder"`
|
||||
@@ -68,6 +69,7 @@ type UpdateAlertNotificationCommand struct {
|
||||
|
||||
type UpdateAlertNotificationWithUidCommand struct {
|
||||
Uid string `json:"-"`
|
||||
NewUid string `json:"uid"`
|
||||
Name string `json:"name" binding:"Required"`
|
||||
Type string `json:"type" binding:"Required"`
|
||||
SendReminder bool `json:"sendReminder"`
|
||||
|
||||
@@ -317,6 +317,10 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
|
||||
current.SendReminder = cmd.SendReminder
|
||||
current.DisableResolveMessage = cmd.DisableResolveMessage
|
||||
|
||||
if cmd.Uid != "" {
|
||||
current.Uid = cmd.Uid
|
||||
}
|
||||
|
||||
if current.SendReminder {
|
||||
if cmd.Frequency == "" {
|
||||
return m.ErrNotificationFrequencyNotFound
|
||||
@@ -356,8 +360,13 @@ func UpdateAlertNotificationWithUid(cmd *m.UpdateAlertNotificationWithUidCommand
|
||||
return fmt.Errorf("Cannot update, alert notification uid %s doesn't exist", cmd.Uid)
|
||||
}
|
||||
|
||||
if cmd.NewUid == "" {
|
||||
cmd.NewUid = cmd.Uid
|
||||
}
|
||||
|
||||
updateNotification := &m.UpdateAlertNotificationCommand{
|
||||
Id: current.Id,
|
||||
Uid: cmd.NewUid,
|
||||
Name: cmd.Name,
|
||||
Type: cmd.Type,
|
||||
SendReminder: cmd.SendReminder,
|
||||
@@ -373,6 +382,8 @@ func UpdateAlertNotificationWithUid(cmd *m.UpdateAlertNotificationWithUidCommand
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = updateNotification.Result
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
legendFormat = regexp.MustCompile(`\[\[(\w+?)*\]\]*|\$\s*(\w+?)*`)
|
||||
legendFormat = regexp.MustCompile(`\[\[(\w+)(\.\w+)*\]\]*|\$\s*(\w+?)*`)
|
||||
}
|
||||
|
||||
func (rp *ResponseParser) Parse(response *Response, query *Query) *tsdb.QueryResult {
|
||||
|
||||
@@ -75,7 +75,10 @@ func TestInfluxdbResponseParser(t *testing.T) {
|
||||
{
|
||||
Name: "cpu.upc",
|
||||
Columns: []string{"time", "mean", "sum"},
|
||||
Tags: map[string]string{"datacenter": "America"},
|
||||
Tags: map[string]string{
|
||||
"datacenter": "America",
|
||||
"dc.region.name": "Northeast",
|
||||
},
|
||||
Values: [][]interface{}{
|
||||
{json.Number("111"), json.Number("222"), json.Number("333")},
|
||||
},
|
||||
@@ -159,6 +162,13 @@ func TestInfluxdbResponseParser(t *testing.T) {
|
||||
|
||||
So(result.Series[0].Name, ShouldEqual, "alias America")
|
||||
})
|
||||
|
||||
Convey("tag alias with periods", func() {
|
||||
query := &Query{Alias: "alias [[tag_dc.region.name]]"}
|
||||
result := parser.Parse(response, query)
|
||||
|
||||
So(result.Series[0].Name, ShouldEqual, "alias Northeast")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -22,4 +22,13 @@ describe('ticks', () => {
|
||||
expect(dec.scaledDecimals).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStringPrecision()', () => {
|
||||
it('"3.12" should return 2', () => {
|
||||
expect(ticks.getStringPrecision('3.12')).toBe(2);
|
||||
});
|
||||
it('"asd" should return 0', () => {
|
||||
expect(ticks.getStringPrecision('asd.asd')).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -141,6 +141,7 @@ export function buildQueryTransaction(
|
||||
__interval: { text: interval, value: interval },
|
||||
__interval_ms: { text: intervalMs, value: intervalMs },
|
||||
},
|
||||
maxDataPoints: queryOptions.maxDataPoints,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -201,6 +201,10 @@ export function getPrecision(num: number): number {
|
||||
* Get decimal precision of number stored as a string ("3.14" => 2)
|
||||
*/
|
||||
export function getStringPrecision(num: string): number {
|
||||
if (isNaN((num as unknown) as number)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const dotIndex = num.indexOf('.');
|
||||
if (dotIndex === -1) {
|
||||
return 0;
|
||||
|
||||
@@ -136,10 +136,16 @@ export class DataPanel extends Component<Props, State> {
|
||||
try {
|
||||
const ds = await this.dataSourceSrv.get(datasource, scopedVars);
|
||||
|
||||
// TODO interpolate variables
|
||||
const minInterval = this.props.minInterval || ds.interval;
|
||||
const intervalRes = kbn.calculateInterval(timeRange, widthPixels, minInterval);
|
||||
|
||||
// make shallow copy of scoped vars,
|
||||
// and add built in variables interval and interval_ms
|
||||
const scopedVarsWithInterval = Object.assign({}, scopedVars, {
|
||||
__interval: { text: intervalRes.interval, value: intervalRes.interval },
|
||||
__interval_ms: { text: intervalRes.intervalMs.toString(), value: intervalRes.intervalMs },
|
||||
});
|
||||
|
||||
const queryOptions: DataQueryOptions = {
|
||||
timezone: 'browser',
|
||||
panelId: panelId,
|
||||
@@ -150,7 +156,7 @@ export class DataPanel extends Component<Props, State> {
|
||||
intervalMs: intervalRes.intervalMs,
|
||||
targets: queries,
|
||||
maxDataPoints: maxDataPoints || widthPixels,
|
||||
scopedVars: scopedVars || {},
|
||||
scopedVars: scopedVarsWithInterval,
|
||||
cacheTimeout: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
|
||||
// give angular time to compile
|
||||
setTimeout(() => {
|
||||
this.setState({ hasTextEditMode: !!this.angularScope.toggleEditorMode });
|
||||
}, 10);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
onToggleCollapse = () => {
|
||||
|
||||
@@ -39,14 +39,20 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
|
||||
componentDidMount() {
|
||||
const { panel } = this.props;
|
||||
panel.events.on('refresh', this.onPanelRefresh);
|
||||
|
||||
appEvents.on('ds-request-response', this.onDataSourceResponse);
|
||||
appEvents.on('ds-request-error', this.onRequestError);
|
||||
|
||||
panel.events.on('refresh', this.onPanelRefresh);
|
||||
panel.refresh();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { panel } = this.props;
|
||||
|
||||
appEvents.off('ds-request-response', this.onDataSourceResponse);
|
||||
appEvents.on('ds-request-error', this.onRequestError);
|
||||
|
||||
panel.events.off('refresh', this.onPanelRefresh);
|
||||
}
|
||||
|
||||
@@ -73,6 +79,10 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
}));
|
||||
};
|
||||
|
||||
onRequestError = (err: any) => {
|
||||
this.onDataSourceResponse(err);
|
||||
};
|
||||
|
||||
onDataSourceResponse = (response: any = {}) => {
|
||||
if (this.state.isMocking) {
|
||||
this.handleMocking(response);
|
||||
|
||||
@@ -199,6 +199,15 @@ export interface QueriesImportedPayload {
|
||||
queries: DataQuery[];
|
||||
}
|
||||
|
||||
export interface LoadExploreDataSourcesPayload {
|
||||
exploreId: ExploreId;
|
||||
exploreDatasources: DataSourceSelectItem[];
|
||||
}
|
||||
|
||||
export interface RunQueriesPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a query row after the row with the given index.
|
||||
*/
|
||||
@@ -323,7 +332,8 @@ export const queryTransactionSuccessAction = actionCreatorFactory<QueryTransacti
|
||||
* Remove query row of the given index, as well as associated query results.
|
||||
*/
|
||||
export const removeQueryRowAction = actionCreatorFactory<RemoveQueryRowPayload>('explore/REMOVE_QUERY_ROW').create();
|
||||
export const runQueriesAction = noPayloadActionCreatorFactory('explore/RUN_QUERIES').create();
|
||||
|
||||
export const runQueriesAction = actionCreatorFactory<RunQueriesPayload>('explore/RUN_QUERIES').create();
|
||||
|
||||
/**
|
||||
* Start a scan for more results using the given scanner.
|
||||
|
||||
@@ -513,6 +513,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
|
||||
supportsGraph,
|
||||
supportsLogs,
|
||||
supportsTable,
|
||||
containerWidth,
|
||||
} = getState().explore[exploreId];
|
||||
|
||||
if (!hasNonEmptyQuery(queries)) {
|
||||
@@ -525,7 +526,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
|
||||
// but we're using the datasource interval limit for now
|
||||
const interval = datasourceInstance.interval;
|
||||
|
||||
dispatch(runQueriesAction());
|
||||
dispatch(runQueriesAction({ exploreId }));
|
||||
// Keep table queries first since they need to return quickly
|
||||
if ((ignoreUIState || showingTable) && supportsTable) {
|
||||
dispatch(
|
||||
@@ -551,6 +552,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
|
||||
interval,
|
||||
format: 'time_series',
|
||||
instant: false,
|
||||
maxDataPoints: containerWidth,
|
||||
},
|
||||
makeTimeSeriesList
|
||||
)
|
||||
|
||||
@@ -12,7 +12,14 @@ import {
|
||||
import { ExploreItemState, ExploreState, QueryTransaction, ExploreId, ExploreUpdateState } from 'app/types/explore';
|
||||
import { DataQuery } from '@grafana/ui/src/types';
|
||||
|
||||
import { HigherOrderAction, ActionTypes, SplitCloseActionPayload, splitCloseAction } from './actionTypes';
|
||||
import {
|
||||
HigherOrderAction,
|
||||
ActionTypes,
|
||||
SplitCloseActionPayload,
|
||||
splitCloseAction,
|
||||
runQueriesAction,
|
||||
} from './actionTypes';
|
||||
|
||||
import { reducerFactory } from 'app/core/redux';
|
||||
import {
|
||||
addQueryRowAction,
|
||||
@@ -158,14 +165,8 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
||||
.addMapper({
|
||||
filter: changeSizeAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { range, datasourceInstance } = state;
|
||||
let interval = '1s';
|
||||
if (datasourceInstance && datasourceInstance.interval) {
|
||||
interval = datasourceInstance.interval;
|
||||
}
|
||||
const containerWidth = action.payload.width;
|
||||
const queryIntervals = getIntervals(range, interval, containerWidth);
|
||||
return { ...state, containerWidth, queryIntervals };
|
||||
return { ...state, containerWidth };
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
@@ -250,7 +251,6 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
||||
.addMapper({
|
||||
filter: loadDatasourceSuccessAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { containerWidth, range } = state;
|
||||
const {
|
||||
StartPage,
|
||||
datasourceInstance,
|
||||
@@ -260,11 +260,9 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
||||
supportsLogs,
|
||||
supportsTable,
|
||||
} = action.payload;
|
||||
const queryIntervals = getIntervals(range, datasourceInstance.interval, containerWidth);
|
||||
|
||||
return {
|
||||
...state,
|
||||
queryIntervals,
|
||||
StartPage,
|
||||
datasourceInstance,
|
||||
history,
|
||||
@@ -517,6 +515,21 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
||||
};
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: runQueriesAction,
|
||||
mapper: (state): ExploreItemState => {
|
||||
const { range, datasourceInstance, containerWidth } = state;
|
||||
let interval = '1s';
|
||||
if (datasourceInstance && datasourceInstance.interval) {
|
||||
interval = datasourceInstance.interval;
|
||||
}
|
||||
const queryIntervals = getIntervals(range, interval, containerWidth);
|
||||
return {
|
||||
...state,
|
||||
queryIntervals,
|
||||
};
|
||||
},
|
||||
})
|
||||
.create();
|
||||
|
||||
export const updateChildRefreshState = (
|
||||
|
||||
@@ -9,6 +9,12 @@ import locationUtil from 'app/core/utils/location_util';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import { store } from 'app/store/store';
|
||||
|
||||
export const queryParamsToPreserve: { [key: string]: boolean } = {
|
||||
kiosk: true,
|
||||
autofitpanels: true,
|
||||
orgId: true,
|
||||
};
|
||||
|
||||
export class PlaylistSrv {
|
||||
private cancelPromise: any;
|
||||
private dashboards: Array<{ url: string }>;
|
||||
@@ -41,9 +47,7 @@ export class PlaylistSrv {
|
||||
|
||||
const dash = this.dashboards[this.index];
|
||||
const queryParams = this.$location.search();
|
||||
const filteredParams = _.pickBy(queryParams, key => {
|
||||
return key === 'kiosk' || key === 'autofitpanels' || key === 'orgId';
|
||||
});
|
||||
const filteredParams = _.pickBy(queryParams, (value: any, key: string) => queryParamsToPreserve[key]);
|
||||
const nextDashboardUrl = locationUtil.stripBaseFromUrl(dash.url);
|
||||
|
||||
// this is done inside timeout to make sure digest happens after
|
||||
|
||||
@@ -8,13 +8,13 @@ export default class DefaultVariableQueryEditor extends PureComponent<VariableQu
|
||||
this.state = { value: props.query };
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
this.setState({ value: event.target.value });
|
||||
}
|
||||
onChange = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
this.setState({ value: event.currentTarget.value });
|
||||
};
|
||||
|
||||
handleBlur(event) {
|
||||
this.props.onChange(event.target.value, event.target.value);
|
||||
}
|
||||
onBlur = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
this.props.onChange(event.currentTarget.value, event.currentTarget.value);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
@@ -24,8 +24,8 @@ export default class DefaultVariableQueryEditor extends PureComponent<VariableQu
|
||||
type="text"
|
||||
className="gf-form-input"
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.onChange}
|
||||
onBlur={this.onBlur}
|
||||
placeholder="metric name or tags query"
|
||||
required
|
||||
/>
|
||||
|
||||
@@ -112,6 +112,7 @@ export class CloudWatchQueryParameterCtrl {
|
||||
query = $scope.datasource.getDimensionKeys($scope.target.namespace, $scope.target.region);
|
||||
} else if (segment.type === 'value') {
|
||||
const dimensionKey = $scope.dimSegments[$index - 2].value;
|
||||
delete target.dimensions[dimensionKey];
|
||||
query = $scope.datasource.getDimensionValues(
|
||||
target.region,
|
||||
target.namespace,
|
||||
|
||||
@@ -966,7 +966,6 @@ export class FuncInstance {
|
||||
const str = this.def.name + '(';
|
||||
|
||||
const parameters = _.map(this.params, (value, index) => {
|
||||
const valueInterpolated = replaceVariables(value);
|
||||
let paramType;
|
||||
|
||||
if (index < this.def.params.length) {
|
||||
@@ -980,6 +979,8 @@ export class FuncInstance {
|
||||
return value;
|
||||
}
|
||||
|
||||
const valueInterpolated = _.isString(value) ? replaceVariables(value) : value;
|
||||
|
||||
// param types that might be quoted
|
||||
// To quote variables correctly we need to interpolate it to check if it contains a numeric or string value
|
||||
if (_.includes(['int_or_interval', 'node_or_tag'], paramType) && _.isFinite(+valueInterpolated)) {
|
||||
|
||||
@@ -19,6 +19,7 @@ export default class GraphiteQuery {
|
||||
this.datasource = datasource;
|
||||
this.target = target;
|
||||
this.templateSrv = templateSrv;
|
||||
this.scopedVars = scopedVars;
|
||||
this.parseTarget();
|
||||
|
||||
this.removeTagValue = '-- remove tag --';
|
||||
@@ -162,7 +163,9 @@ export default class GraphiteQuery {
|
||||
|
||||
updateModelTarget(targets) {
|
||||
const wrapFunction = (target: string, func: any) => {
|
||||
return func.render(target, this.templateSrv.replace);
|
||||
return func.render(target, (value: string) => {
|
||||
return this.templateSrv.replace(value, this.scopedVars);
|
||||
});
|
||||
};
|
||||
|
||||
if (!this.target.textEditor) {
|
||||
|
||||
@@ -31,7 +31,8 @@ describe('when creating func instance from func names', () => {
|
||||
});
|
||||
|
||||
function replaceVariablesDummy(str: string) {
|
||||
return str;
|
||||
// important that this does replace
|
||||
return str.replace('asdasdas', 'asdsad');
|
||||
}
|
||||
|
||||
describe('when rendering func instance', () => {
|
||||
|
||||
@@ -398,7 +398,13 @@ Array [
|
||||
>
|
||||
Alias By
|
||||
</label>
|
||||
<div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="gf-form-input gf-form-input width-24"
|
||||
onChange={[Function]}
|
||||
@@ -426,7 +432,13 @@ Array [
|
||||
>
|
||||
Project
|
||||
</span>
|
||||
<div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="gf-form-input gf-form-input width-15"
|
||||
disabled={true}
|
||||
|
||||
@@ -594,7 +594,8 @@ export class HeatmapRenderer {
|
||||
yGridSize = Math.floor((this.yScale(1) - this.yScale(base)) / splitFactor);
|
||||
}
|
||||
|
||||
this.cardWidth = xGridSize - this.cardPadding * 2;
|
||||
const cardWidth = xGridSize - this.cardPadding * 2;
|
||||
this.cardWidth = Math.max(cardWidth, MIN_CARD_SIZE);
|
||||
this.cardHeight = yGridSize ? yGridSize - this.cardPadding * 2 : 0;
|
||||
}
|
||||
|
||||
@@ -611,16 +612,13 @@ export class HeatmapRenderer {
|
||||
}
|
||||
|
||||
getCardWidth(d) {
|
||||
let w;
|
||||
let w = this.cardWidth;
|
||||
if (this.xScale(d.x) < 0) {
|
||||
// Cut card left to prevent overlay
|
||||
const cuttedWidth = this.xScale(d.x) + this.cardWidth;
|
||||
w = cuttedWidth > 0 ? cuttedWidth : 0;
|
||||
w = this.xScale(d.x) + this.cardWidth;
|
||||
} else if (this.xScale(d.x) + this.cardWidth > this.chartWidth) {
|
||||
// Cut card right to prevent overlay
|
||||
w = this.chartWidth - this.xScale(d.x) - this.cardPadding;
|
||||
} else {
|
||||
w = this.cardWidth;
|
||||
}
|
||||
|
||||
// Card width should be MIN_CARD_SIZE at least, but cut cards shouldn't be displayed
|
||||
|
||||
@@ -191,7 +191,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
data.value = 0;
|
||||
data.valueRounded = 0;
|
||||
} else {
|
||||
const decimalInfo = getDecimalsForValue(data.value);
|
||||
const decimalInfo = getDecimalsForValue(data.value, this.panel.decimals);
|
||||
const formatFunc = getValueFormat(this.panel.format);
|
||||
|
||||
data.valueFormatted = formatFunc(
|
||||
@@ -199,7 +199,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
decimalInfo.decimals,
|
||||
decimalInfo.scaledDecimals
|
||||
);
|
||||
data.valueRounded = kbn.roundValue(data.value, this.panel.decimals || 0);
|
||||
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
|
||||
}
|
||||
|
||||
this.setValueMapping(data);
|
||||
@@ -279,17 +279,15 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
data.value = this.series[0].stats[this.panel.valueName];
|
||||
data.flotpairs = this.series[0].flotpairs;
|
||||
|
||||
let decimals = this.panel.decimals;
|
||||
let scaledDecimals = 0;
|
||||
const decimalInfo = getDecimalsForValue(data.value, this.panel.decimals);
|
||||
|
||||
if (!this.panel.decimals) {
|
||||
const decimalInfo = getDecimalsForValue(data.value);
|
||||
decimals = decimalInfo.decimals;
|
||||
scaledDecimals = decimalInfo.scaledDecimals;
|
||||
}
|
||||
|
||||
data.valueFormatted = formatFunc(data.value, decimals, scaledDecimals, this.dashboard.isTimezoneUtc());
|
||||
data.valueRounded = kbn.roundValue(data.value, decimals);
|
||||
data.valueFormatted = formatFunc(
|
||||
data.value,
|
||||
decimalInfo.decimals,
|
||||
decimalInfo.scaledDecimals,
|
||||
this.dashboard.isTimezoneUtc()
|
||||
);
|
||||
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
|
||||
}
|
||||
|
||||
// Add $__name variable for using in prefix or postfix
|
||||
|
||||
@@ -316,6 +316,7 @@ export interface QueryOptions {
|
||||
hinting?: boolean;
|
||||
instant?: boolean;
|
||||
valueWithRefId?: boolean;
|
||||
maxDataPoints?: number;
|
||||
}
|
||||
|
||||
export interface QueryTransaction {
|
||||
|
||||
@@ -102,14 +102,14 @@ $edit-gradient: linear-gradient(180deg, $dark-2 50%, $input-black);
|
||||
|
||||
// Links
|
||||
// -------------------------
|
||||
$link-color: #e3e3e3;
|
||||
$link-color-disabled: #e3e3e3;
|
||||
$link-color: #d8d9da;
|
||||
$link-color-disabled: #8e8e8e;
|
||||
$link-hover-color: #ffffff;
|
||||
$external-link-color: #33b5e5;
|
||||
|
||||
// Typography
|
||||
// -------------------------
|
||||
$headings-color: #e3e3e3;
|
||||
$headings-color: #d8d9da;
|
||||
$abbr-border-color: $gray-2 !default;
|
||||
$text-muted: $text-color-weak;
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ $side-menu-width: 60px;
|
||||
|
||||
// dashboard
|
||||
$dashboard-padding: $space-md;
|
||||
$panel-padding: 0 $space-md $space-sm $space-md;
|
||||
$panel-padding: 0 16px 8px 16px;
|
||||
|
||||
// tabs
|
||||
$tabs-padding: 10px 15px 9px;
|
||||
|
||||
@@ -76,7 +76,7 @@ $text-color: #52545c;
|
||||
$text-color-strong: #41444b;
|
||||
$text-color-weak: #767980;
|
||||
$text-color-faint: #35373f;
|
||||
$text-color-emphasis: #dde4ed;
|
||||
$text-color-emphasis: #41444b;
|
||||
|
||||
$text-shadow-faint: none;
|
||||
|
||||
@@ -89,8 +89,8 @@ $edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
|
||||
// Links
|
||||
// -------------------------
|
||||
$link-color: #52545c;
|
||||
$link-color-disabled: #9ea0a9;
|
||||
$link-hover-color: #222326;
|
||||
$link-color-disabled: #acb6bf;
|
||||
$link-hover-color: #1e2028;
|
||||
$external-link-color: #5794f2;
|
||||
|
||||
// Typography
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
.share-modal-info-text {
|
||||
margin-top: 5px;
|
||||
strong {
|
||||
color: $headings-color;
|
||||
color: $text-color-emphasis;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,10 @@
|
||||
|
||||
// fix for phantomjs
|
||||
.body--phantomjs {
|
||||
.graph-panel {
|
||||
display: -webkit-box;
|
||||
}
|
||||
|
||||
.graph-panel--legend-right {
|
||||
.graph-legend {
|
||||
display: block;
|
||||
|
||||
@@ -335,7 +335,6 @@
|
||||
}
|
||||
|
||||
@mixin left-brand-border-gradient() {
|
||||
border: none;
|
||||
border-image: linear-gradient(rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%);
|
||||
border-image-slice: 1;
|
||||
border-style: solid;
|
||||
|
||||
@@ -18,7 +18,9 @@ RUN pip install -U awscli crcmod && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
ln -s /opt/google-cloud-sdk/bin/gsutil /usr/bin/gsutil && \
|
||||
ln -s /opt/google-cloud-sdk/bin/gcloud /usr/bin/gcloud
|
||||
ln -s /opt/google-cloud-sdk/bin/gcloud /usr/bin/gcloud && \
|
||||
mkdir -p /deb-repo /rpm-repo && \
|
||||
chown circleci:circleci /deb-repo /rpm-repo
|
||||
|
||||
COPY --from=0 /go/bin/aptly /usr/local/bin/aptly
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
_version="1.2.1"
|
||||
_version="1.2.2"
|
||||
_tag="grafana/grafana-ci-deploy:${_version}"
|
||||
|
||||
docker build -t $_tag .
|
||||
|
||||
@@ -6,8 +6,8 @@ EXTRA_OPTS="$@"
|
||||
|
||||
# Right now we hack this in into the publish script.
|
||||
# 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"
|
||||
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v6-0/"
|
||||
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v6-1-x/15772"
|
||||
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v6-1/"
|
||||
|
||||
./scripts/build/release_publisher/release_publisher \
|
||||
--wn ${_whatsNewUrl} \
|
||||
|
||||
@@ -14,21 +14,21 @@ const changelogTaskRunner: TaskRunner<ChangelogOptions> = async ({ milestone })
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
if (!/^\d+$/.test(milestone)) {
|
||||
console.log('Use milestone number not title, find number in milestone url');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await client.get('/issues', {
|
||||
params: {
|
||||
state: 'closed',
|
||||
per_page: 100,
|
||||
labels: 'add to changelog',
|
||||
milestone: milestone,
|
||||
},
|
||||
});
|
||||
|
||||
const issues = res.data.filter(item => {
|
||||
if (!item.milestone) {
|
||||
console.log('Item missing milestone', item.number);
|
||||
return false;
|
||||
}
|
||||
return item.milestone.title === milestone;
|
||||
});
|
||||
const issues = res.data;
|
||||
|
||||
const bugs = _.sortBy(
|
||||
issues.filter(item => {
|
||||
|
||||
@@ -27,13 +27,10 @@ const cherryPickRunner: TaskRunner<CherryPickOptions> = async () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(item.number + ' closed_at ' + item.closed_at + ' ' + item.html_url);
|
||||
console.log(`${item.title} (${item.number}) closed_at ${item.closed_at}`);
|
||||
console.log(`\tURL: ${item.closed_at} ${item.html_url}`);
|
||||
const issueDetails = await client.get(item.pull_request.url);
|
||||
const commits = await client.get(issueDetails.data.commits_url);
|
||||
|
||||
for (const commit of commits.data) {
|
||||
console.log(commit.commit.message + ' sha: ' + commit.sha);
|
||||
}
|
||||
console.log(`\tMerge sha: ${issueDetails.data.merge_commit_sha}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'autoprefixer': {},
|
||||
'postcss-reporter': {},
|
||||
'postcss-browser-reporter': {},
|
||||
}
|
||||
}
|
||||
module.exports = () => {
|
||||
return {
|
||||
plugins: {
|
||||
autoprefixer: {},
|
||||
'postcss-reporter': {},
|
||||
'postcss-browser-reporter': {},
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports = function(options) {
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: options.sourceMap,
|
||||
config: { path: __dirname + '/postcss.config.js' },
|
||||
config: { path: __dirname },
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user