mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into kbn-formats-refactor
This commit is contained in:
commit
ba9d5115d2
@ -127,7 +127,7 @@ jobs:
|
|||||||
|
|
||||||
build-all:
|
build-all:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/build-container:1.2.1
|
- image: grafana/build-container:1.2.2
|
||||||
working_directory: /go/src/github.com/grafana/grafana
|
working_directory: /go/src/github.com/grafana/grafana
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@ -200,51 +200,51 @@ jobs:
|
|||||||
- dist/grafana*
|
- dist/grafana*
|
||||||
|
|
||||||
grafana-docker-master:
|
grafana-docker-master:
|
||||||
docker:
|
machine:
|
||||||
- image: docker:stable-git
|
image: circleci/classic:201808-01
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- setup_remote_docker
|
|
||||||
- run: docker info
|
- run: docker info
|
||||||
- run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
|
- run: docker run --privileged linuxkit/binfmt:v0.6
|
||||||
|
- run: cp dist/grafana-latest.linux-*.tar.gz packaging/docker
|
||||||
- run: cd packaging/docker && ./build-deploy.sh "master-${CIRCLE_SHA1}"
|
- run: cd packaging/docker && ./build-deploy.sh "master-${CIRCLE_SHA1}"
|
||||||
- run: rm packaging/docker/grafana-latest.linux-x64.tar.gz
|
- run: rm packaging/docker/grafana-latest.linux-*.tar.gz
|
||||||
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
||||||
- run: cd packaging/docker && ./build-enterprise.sh "master"
|
- run: cd packaging/docker && ./build-enterprise.sh "master"
|
||||||
|
|
||||||
|
|
||||||
grafana-docker-pr:
|
grafana-docker-pr:
|
||||||
docker:
|
machine:
|
||||||
- image: docker:stable-git
|
image: circleci/classic:201808-01
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- setup_remote_docker
|
|
||||||
- run: docker info
|
- run: docker info
|
||||||
- run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
|
- run: docker run --privileged linuxkit/binfmt:v0.6
|
||||||
|
- run: cp dist/grafana-latest.linux-*.tar.gz packaging/docker
|
||||||
- run: cd packaging/docker && ./build.sh "${CIRCLE_SHA1}"
|
- run: cd packaging/docker && ./build.sh "${CIRCLE_SHA1}"
|
||||||
|
|
||||||
grafana-docker-release:
|
grafana-docker-release:
|
||||||
docker:
|
machine:
|
||||||
- image: docker:stable-git
|
image: circleci/classic:201808-01
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- setup_remote_docker
|
- run: docker info
|
||||||
- run: docker info
|
- run: docker run --privileged linuxkit/binfmt:v0.6
|
||||||
- run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
|
- run: cp dist/grafana-latest.linux-*.tar.gz packaging/docker
|
||||||
- run: cd packaging/docker && ./build-deploy.sh "${CIRCLE_TAG}"
|
- run: cd packaging/docker && ./build-deploy.sh "${CIRCLE_TAG}"
|
||||||
- run: rm packaging/docker/grafana-latest.linux-x64.tar.gz
|
- run: rm packaging/docker/grafana-latest.linux-*.tar.gz
|
||||||
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
||||||
- run: cd packaging/docker && ./build-enterprise.sh "${CIRCLE_TAG}"
|
- run: cd packaging/docker && ./build-enterprise.sh "${CIRCLE_TAG}"
|
||||||
|
|
||||||
build-enterprise:
|
build-enterprise:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/build-container:1.2.1
|
- image: grafana/build-container:1.2.2
|
||||||
working_directory: /go/src/github.com/grafana/grafana
|
working_directory: /go/src/github.com/grafana/grafana
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@ -276,7 +276,7 @@ jobs:
|
|||||||
|
|
||||||
build-all-enterprise:
|
build-all-enterprise:
|
||||||
docker:
|
docker:
|
||||||
- image: grafana/build-container:1.2.1
|
- image: grafana/build-container:1.2.2
|
||||||
working_directory: /go/src/github.com/grafana/grafana
|
working_directory: /go/src/github.com/grafana/grafana
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
* **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas)
|
* **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas)
|
||||||
* **Units**: Add blood glucose level units mg/dL and mmol/L [#14519](https://github.com/grafana/grafana/issues/14519), thx [@kjedamzik](https://github.com/kjedamzik)
|
* **Units**: Add blood glucose level units mg/dL and mmol/L [#14519](https://github.com/grafana/grafana/issues/14519), thx [@kjedamzik](https://github.com/kjedamzik)
|
||||||
* **Stackdriver**: Aggregating series returns more than one series [#14581](https://github.com/grafana/grafana/issues/14581) and [#13914](https://github.com/grafana/grafana/issues/13914), thx [@kinok](https://github.com/kinok)
|
* **Stackdriver**: Aggregating series returns more than one series [#14581](https://github.com/grafana/grafana/issues/14581) and [#13914](https://github.com/grafana/grafana/issues/13914), thx [@kinok](https://github.com/kinok)
|
||||||
|
* **Docker**: Build and publish docker images for armv7 and arm64 [#14617](https://github.com/grafana/grafana/pull/14617), thx [@johanneswuerbach](https://github.com/johanneswuerbach)
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
* **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486)
|
* **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486)
|
||||||
|
@ -131,7 +131,9 @@ GRAFANA_TEST_DB=postgres go test ./pkg/...
|
|||||||
|
|
||||||
If you have any idea for an improvement or found a bug, do not hesitate to open an issue.
|
If you have any idea for an improvement or found a bug, do not hesitate to open an issue.
|
||||||
And if you have time clone this repo and submit a pull request and help me make Grafana
|
And if you have time clone this repo and submit a pull request and help me make Grafana
|
||||||
the kickass metrics & devops dashboard we all dream about!
|
the kickass metrics & devops dashboard we all dream about!
|
||||||
|
|
||||||
|
Read the [contributing](https://github.com/grafana/grafana/blob/master/CONTRIBUTING.md) guide then check the [`beginner friendly`](https://github.com/grafana/grafana/issues?q=is%3Aopen+is%3Aissue+label%3A%22beginner+friendly%22) label to find issues that are easy and that we would like help with.
|
||||||
|
|
||||||
## Plugin development
|
## Plugin development
|
||||||
|
|
||||||
|
2
build.go
2
build.go
@ -164,6 +164,8 @@ func makeLatestDistCopies() {
|
|||||||
"_amd64.deb": "dist/grafana_latest_amd64.deb",
|
"_amd64.deb": "dist/grafana_latest_amd64.deb",
|
||||||
".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
|
".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
|
||||||
".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
|
".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
|
||||||
|
".linux-armv7.tar.gz": "dist/grafana-latest.linux-armv7.tar.gz",
|
||||||
|
".linux-arm64.tar.gz": "dist/grafana-latest.linux-arm64.tar.gz",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
@ -69,6 +69,7 @@ reporting-disabled = false
|
|||||||
|
|
||||||
unix-socket-enabled = false # enable http service over unix domain socket
|
unix-socket-enabled = false # enable http service over unix domain socket
|
||||||
# bind-socket = "/var/run/influxdb.sock"
|
# bind-socket = "/var/run/influxdb.sock"
|
||||||
|
flux-enabled = true
|
||||||
|
|
||||||
[subscriber]
|
[subscriber]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
@ -51,7 +51,7 @@ When a user creates a new dashboard, a new dashboard JSON object is initialized
|
|||||||
"list": []
|
"list": []
|
||||||
},
|
},
|
||||||
"refresh": "5s",
|
"refresh": "5s",
|
||||||
"schemaVersion": 16,
|
"schemaVersion": 17,
|
||||||
"version": 0,
|
"version": 0,
|
||||||
"links": []
|
"links": []
|
||||||
}
|
}
|
||||||
|
@ -292,9 +292,11 @@ The `direction` controls how the panels will be arranged.
|
|||||||
|
|
||||||
By choosing `horizontal` the panels will be arranged side-by-side. Grafana will automatically adjust the width
|
By choosing `horizontal` the panels will be arranged side-by-side. Grafana will automatically adjust the width
|
||||||
of each repeated panel so that the whole row is filled. Currently, you cannot mix other panels on a row with a repeated
|
of each repeated panel so that the whole row is filled. Currently, you cannot mix other panels on a row with a repeated
|
||||||
panel. Each panel will never be smaller that the provided `Min width` if you have many selected values.
|
panel.
|
||||||
|
|
||||||
By choosing `vertical` the panels will be arranged from top to bottom in a column. The `Min width` doesn't have any effect in this case. The width of the repeated panels will be the same as of the first panel (the original template) being repeated.
|
Set `Max per row` to tell grafana how many panels per row you want at most. It defaults to *4* if you don't set anything.
|
||||||
|
|
||||||
|
By choosing `vertical` the panels will be arranged from top to bottom in a column. The width of the repeated panels will be the same as of the first panel (the original template) being repeated.
|
||||||
|
|
||||||
Only make changes to the first panel (the original template). To have the changes take effect on all panels you need to trigger a dynamic dashboard re-build.
|
Only make changes to the first panel (the original template). To have the changes take effect on all panels you need to trigger a dynamic dashboard re-build.
|
||||||
You can do this by either changing the variable value (that is the basis for the repeat) or reload the dashboard.
|
You can do this by either changing the variable value (that is the basis for the repeat) or reload the dashboard.
|
||||||
|
@ -23,7 +23,10 @@
|
|||||||
"react-highlight-words": "0.11.0",
|
"react-highlight-words": "0.11.0",
|
||||||
"react-popper": "^1.3.0",
|
"react-popper": "^1.3.0",
|
||||||
"react-transition-group": "^2.2.1",
|
"react-transition-group": "^2.2.1",
|
||||||
"react-virtualized": "^9.21.0"
|
"react-virtualized": "^9.21.0",
|
||||||
|
"tether": "^1.4.0",
|
||||||
|
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
|
||||||
|
"tinycolor2": "^1.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/classnames": "^2.2.6",
|
"@types/classnames": "^2.2.6",
|
||||||
@ -33,6 +36,8 @@
|
|||||||
"@types/react": "^16.7.6",
|
"@types/react": "^16.7.6",
|
||||||
"@types/react-custom-scrollbars": "^4.0.5",
|
"@types/react-custom-scrollbars": "^4.0.5",
|
||||||
"@types/react-test-renderer": "^16.0.3",
|
"@types/react-test-renderer": "^16.0.3",
|
||||||
|
"@types/tether-drop": "^1.4.8",
|
||||||
|
"@types/tinycolor2": "^1.4.1",
|
||||||
"react-test-renderer": "^16.7.0",
|
"react-test-renderer": "^16.7.0",
|
||||||
"typescript": "^3.2.2"
|
"typescript": "^3.2.2"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { ColorPalette } from '../components/colorpicker/ColorPalette';
|
import { ColorPalette } from './ColorPalette';
|
||||||
|
|
||||||
describe('CollorPalette', () => {
|
describe('CollorPalette', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { sortedColors } from 'app/core/utils/colors';
|
import { sortedColors } from '../../utils';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
color: string;
|
color: string;
|
||||||
@ -9,13 +9,13 @@ export interface Props {
|
|||||||
export class ColorPalette extends React.Component<Props, any> {
|
export class ColorPalette extends React.Component<Props, any> {
|
||||||
paletteColors: string[];
|
paletteColors: string[];
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.paletteColors = sortedColors;
|
this.paletteColors = sortedColors;
|
||||||
this.onColorSelect = this.onColorSelect.bind(this);
|
this.onColorSelect = this.onColorSelect.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onColorSelect(color) {
|
onColorSelect(color: string) {
|
||||||
return () => {
|
return () => {
|
||||||
this.props.onColorSelect(color);
|
this.props.onColorSelect(color);
|
||||||
};
|
};
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import Drop from 'tether-drop';
|
import Drop from 'tether-drop';
|
||||||
import { ColorPickerPopover } from './ColorPickerPopover';
|
import { ColorPickerPopover } from './ColorPickerPopover';
|
||||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
color: string;
|
color: string;
|
||||||
@ -10,7 +9,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ColorPicker extends React.Component<Props, any> {
|
export class ColorPicker extends React.Component<Props, any> {
|
||||||
pickerElem: HTMLElement;
|
pickerElem: HTMLElement | null;
|
||||||
colorPickerDrop: any;
|
colorPickerDrop: any;
|
||||||
|
|
||||||
openColorPicker = () => {
|
openColorPicker = () => {
|
||||||
@ -20,7 +19,7 @@ export class ColorPicker extends React.Component<Props, any> {
|
|||||||
ReactDOM.render(dropContent, dropContentElem);
|
ReactDOM.render(dropContent, dropContentElem);
|
||||||
|
|
||||||
const drop = new Drop({
|
const drop = new Drop({
|
||||||
target: this.pickerElem,
|
target: this.pickerElem as Element,
|
||||||
content: dropContentElem,
|
content: dropContentElem,
|
||||||
position: 'top center',
|
position: 'top center',
|
||||||
classes: 'drop-popover',
|
classes: 'drop-popover',
|
||||||
@ -28,6 +27,7 @@ export class ColorPicker extends React.Component<Props, any> {
|
|||||||
hoverCloseDelay: 200,
|
hoverCloseDelay: 200,
|
||||||
tetherOptions: {
|
tetherOptions: {
|
||||||
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
|
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
|
||||||
|
attachment: 'bottom center',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ export class ColorPicker extends React.Component<Props, any> {
|
|||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
onColorSelect = color => {
|
onColorSelect = (color: string) => {
|
||||||
this.props.onChange(color);
|
this.props.onChange(color);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,8 +59,3 @@ export class ColorPicker extends React.Component<Props, any> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
react2AngularDirective('colorPicker', ColorPicker, [
|
|
||||||
'color',
|
|
||||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
|
||||||
]);
|
|
@ -14,7 +14,7 @@ export interface Props {
|
|||||||
export class ColorPickerPopover extends React.Component<Props, any> {
|
export class ColorPickerPopover extends React.Component<Props, any> {
|
||||||
pickerNavElem: any;
|
pickerNavElem: any;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
tab: 'palette',
|
tab: 'palette',
|
||||||
@ -23,60 +23,51 @@ export class ColorPickerPopover extends React.Component<Props, any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setPickerNavElem(elem) {
|
setPickerNavElem(elem: any) {
|
||||||
this.pickerNavElem = $(elem);
|
this.pickerNavElem = $(elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
setColor(color) {
|
setColor(color: string) {
|
||||||
const newColor = tinycolor(color);
|
const newColor = tinycolor(color);
|
||||||
if (newColor.isValid()) {
|
if (newColor.isValid()) {
|
||||||
this.setState({
|
this.setState({ color: newColor.toString(), colorString: newColor.toString() });
|
||||||
color: newColor.toString(),
|
|
||||||
colorString: newColor.toString(),
|
|
||||||
});
|
|
||||||
this.props.onColorSelect(color);
|
this.props.onColorSelect(color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sampleColorSelected(color) {
|
sampleColorSelected(color: string) {
|
||||||
this.setColor(color);
|
this.setColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
spectrumColorSelected(color) {
|
spectrumColorSelected(color: any) {
|
||||||
const rgbColor = color.toRgbString();
|
const rgbColor = color.toRgbString();
|
||||||
this.setColor(rgbColor);
|
this.setColor(rgbColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
onColorStringChange(e) {
|
onColorStringChange(e: any) {
|
||||||
const colorString = e.target.value;
|
const colorString = e.target.value;
|
||||||
this.setState({
|
this.setState({ colorString: colorString });
|
||||||
colorString: colorString,
|
|
||||||
});
|
|
||||||
|
|
||||||
const newColor = tinycolor(colorString);
|
const newColor = tinycolor(colorString);
|
||||||
if (newColor.isValid()) {
|
if (newColor.isValid()) {
|
||||||
// Update only color state
|
// Update only color state
|
||||||
const newColorString = newColor.toString();
|
const newColorString = newColor.toString();
|
||||||
this.setState({
|
this.setState({ color: newColorString });
|
||||||
color: newColorString,
|
|
||||||
});
|
|
||||||
this.props.onColorSelect(newColorString);
|
this.props.onColorSelect(newColorString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onColorStringBlur(e) {
|
onColorStringBlur(e: any) {
|
||||||
const colorString = e.target.value;
|
const colorString = e.target.value;
|
||||||
this.setColor(colorString);
|
this.setColor(colorString);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.pickerNavElem.find('li:first').addClass('active');
|
this.pickerNavElem.find('li:first').addClass('active');
|
||||||
this.pickerNavElem.on('show', e => {
|
this.pickerNavElem.on('show', (e: any) => {
|
||||||
// use href attr (#name => name)
|
// use href attr (#name => name)
|
||||||
const tab = e.target.hash.slice(1);
|
const tab = e.target.hash.slice(1);
|
||||||
this.setState({
|
this.setState({ tab: tab });
|
||||||
tab: tab,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
|
|||||||
onToggleAxis: () => {},
|
onToggleAxis: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: SeriesColorPickerProps) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +51,7 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
|
|||||||
remove: true,
|
remove: true,
|
||||||
tetherOptions: {
|
tetherOptions: {
|
||||||
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
|
constraints: [{ to: 'scrollParent', attachment: 'none both' }],
|
||||||
|
attachment: 'bottom center',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ColorPickerPopover } from './ColorPickerPopover';
|
import { ColorPickerPopover } from './ColorPickerPopover';
|
||||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
|
||||||
|
|
||||||
export interface SeriesColorPickerPopoverProps {
|
export interface SeriesColorPickerPopoverProps {
|
||||||
color: string;
|
color: string;
|
||||||
@ -22,7 +21,7 @@ export class SeriesColorPickerPopover extends React.PureComponent<SeriesColorPic
|
|||||||
|
|
||||||
interface AxisSelectorProps {
|
interface AxisSelectorProps {
|
||||||
yaxis: number;
|
yaxis: number;
|
||||||
onToggleAxis: () => void;
|
onToggleAxis?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AxisSelectorState {
|
interface AxisSelectorState {
|
||||||
@ -30,7 +29,7 @@ interface AxisSelectorState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSelectorState> {
|
export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSelectorState> {
|
||||||
constructor(props) {
|
constructor(props: AxisSelectorProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
yaxis: this.props.yaxis,
|
yaxis: this.props.yaxis,
|
||||||
@ -42,7 +41,10 @@ export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSel
|
|||||||
this.setState({
|
this.setState({
|
||||||
yaxis: this.state.yaxis === 2 ? 1 : 2,
|
yaxis: this.state.yaxis === 2 ? 1 : 2,
|
||||||
});
|
});
|
||||||
this.props.onToggleAxis();
|
|
||||||
|
if (this.props.onToggleAxis) {
|
||||||
|
this.props.onToggleAxis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -62,9 +64,3 @@ export class AxisSelector extends React.PureComponent<AxisSelectorProps, AxisSel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [
|
|
||||||
'series',
|
|
||||||
'onColorChange',
|
|
||||||
'onToggleAxis',
|
|
||||||
]);
|
|
@ -13,17 +13,17 @@ export class SpectrumPicker extends React.Component<Props, any> {
|
|||||||
elem: any;
|
elem: any;
|
||||||
isMoving: boolean;
|
isMoving: boolean;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.onSpectrumMove = this.onSpectrumMove.bind(this);
|
this.onSpectrumMove = this.onSpectrumMove.bind(this);
|
||||||
this.setComponentElem = this.setComponentElem.bind(this);
|
this.setComponentElem = this.setComponentElem.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
setComponentElem(elem) {
|
setComponentElem(elem: any) {
|
||||||
this.elem = $(elem);
|
this.elem = $(elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSpectrumMove(color) {
|
onSpectrumMove(color: any) {
|
||||||
this.isMoving = true;
|
this.isMoving = true;
|
||||||
this.props.onColorSelect(color);
|
this.props.onColorSelect(color);
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ export class SpectrumPicker extends React.Component<Props, any> {
|
|||||||
this.elem.spectrum('set', this.props.color);
|
this.elem.spectrum('set', this.props.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps) {
|
componentWillUpdate(nextProps: any) {
|
||||||
// If user move pointer over spectrum field this produce 'move' event and component
|
// If user move pointer over spectrum field this produce 'move' event and component
|
||||||
// may update props.color. We don't want to update spectrum color in this case, so we can use
|
// may update props.color. We don't want to update spectrum color in this case, so we can use
|
||||||
// isMoving flag for tracking moving state. Flag should be cleared in componentDidUpdate() which
|
// isMoving flag for tracking moving state. Flag should be cleared in componentDidUpdate() which
|
@ -0,0 +1,11 @@
|
|||||||
|
import React, { SFC } from 'react';
|
||||||
|
|
||||||
|
interface LoadingPlaceholderProps {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadingPlaceholder: SFC<LoadingPlaceholderProps> = ({ text }) => (
|
||||||
|
<div className="gf-form-group">
|
||||||
|
{text} <i className="fa fa-spinner fa-spin" />
|
||||||
|
</div>
|
||||||
|
);
|
@ -1,7 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
|
|
||||||
export const IndicatorsContainer = props => {
|
export const IndicatorsContainer = (props: any) => {
|
||||||
const isOpen = props.selectProps.menuIsOpen;
|
const isOpen = props.selectProps.menuIsOpen;
|
||||||
return (
|
return (
|
||||||
<components.IndicatorsContainer {...props}>
|
<components.IndicatorsContainer {...props}>
|
@ -1,5 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
|
// @ts-ignore
|
||||||
import { OptionProps } from '@torkelo/react-select/lib/components/Option';
|
import { OptionProps } from '@torkelo/react-select/lib/components/Option';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
@ -1,16 +1,21 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { default as ReactSelect } from '@torkelo/react-select';
|
import { default as ReactSelect } from '@torkelo/react-select';
|
||||||
|
// @ts-ignore
|
||||||
import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
|
import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Option, SingleValue } from './PickerOption';
|
import { SelectOption, SingleValue } from './SelectOption';
|
||||||
import OptionGroup from './OptionGroup';
|
import SelectOptionGroup from './SelectOptionGroup';
|
||||||
import IndicatorsContainer from './IndicatorsContainer';
|
import IndicatorsContainer from './IndicatorsContainer';
|
||||||
import NoOptionsMessage from './NoOptionsMessage';
|
import NoOptionsMessage from './NoOptionsMessage';
|
||||||
import ResetStyles from './ResetStyles';
|
import resetSelectStyles from './resetSelectStyles';
|
||||||
import { CustomScrollbar } from '@grafana/ui';
|
import { CustomScrollbar } from '@grafana/ui';
|
||||||
|
|
||||||
export interface SelectOptionItem {
|
export interface SelectOptionItem {
|
||||||
@ -53,7 +58,7 @@ interface AsyncProps {
|
|||||||
loadingMessage?: () => string;
|
loadingMessage?: () => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MenuList = props => {
|
export const MenuList = (props: any) => {
|
||||||
return (
|
return (
|
||||||
<components.MenuList {...props}>
|
<components.MenuList {...props}>
|
||||||
<CustomScrollbar autoHide={false}>{props.children}</CustomScrollbar>
|
<CustomScrollbar autoHide={false}>{props.children}</CustomScrollbar>
|
||||||
@ -112,11 +117,11 @@ export class Select extends PureComponent<CommonProps & SelectProps> {
|
|||||||
classNamePrefix="gf-form-select-box"
|
classNamePrefix="gf-form-select-box"
|
||||||
className={selectClassNames}
|
className={selectClassNames}
|
||||||
components={{
|
components={{
|
||||||
Option,
|
Option: SelectOption,
|
||||||
SingleValue,
|
SingleValue,
|
||||||
IndicatorsContainer,
|
IndicatorsContainer,
|
||||||
MenuList,
|
MenuList,
|
||||||
Group: OptionGroup,
|
Group: SelectOptionGroup,
|
||||||
}}
|
}}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
value={value}
|
value={value}
|
||||||
@ -127,7 +132,7 @@ export class Select extends PureComponent<CommonProps & SelectProps> {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={options}
|
options={options}
|
||||||
placeholder={placeholder || 'Choose'}
|
placeholder={placeholder || 'Choose'}
|
||||||
styles={ResetStyles}
|
styles={resetSelectStyles()}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isClearable={isClearable}
|
isClearable={isClearable}
|
||||||
@ -212,7 +217,7 @@ export class AsyncSelect extends PureComponent<CommonProps & AsyncProps> {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
defaultOptions={defaultOptions}
|
defaultOptions={defaultOptions}
|
||||||
placeholder={placeholder || 'Choose'}
|
placeholder={placeholder || 'Choose'}
|
||||||
styles={ResetStyles}
|
styles={resetSelectStyles()}
|
||||||
loadingMessage={loadingMessage}
|
loadingMessage={loadingMessage}
|
||||||
noOptionsMessage={noOptionsMessage}
|
noOptionsMessage={noOptionsMessage}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import PickerOption from './PickerOption';
|
import SelectOption from './SelectOption';
|
||||||
|
import { OptionProps } from 'react-select/lib/components/Option';
|
||||||
|
|
||||||
const model = {
|
const model: OptionProps<any> = {
|
||||||
cx: jest.fn(),
|
cx: jest.fn(),
|
||||||
clearValue: jest.fn(),
|
clearValue: jest.fn(),
|
||||||
onSelect: jest.fn(),
|
|
||||||
getStyles: jest.fn(),
|
getStyles: jest.fn(),
|
||||||
getValue: jest.fn(),
|
getValue: jest.fn(),
|
||||||
hasValue: true,
|
hasValue: true,
|
||||||
@ -18,21 +18,31 @@ const model = {
|
|||||||
isFocused: false,
|
isFocused: false,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
innerRef: null,
|
innerRef: null,
|
||||||
innerProps: null,
|
innerProps: {
|
||||||
label: 'Option label',
|
id: '',
|
||||||
type: null,
|
key: '',
|
||||||
children: 'Model title',
|
onClick: jest.fn(),
|
||||||
data: {
|
onMouseOver: jest.fn(),
|
||||||
title: 'Model title',
|
tabIndex: 1,
|
||||||
imgUrl: 'url/to/avatar',
|
|
||||||
label: 'User picker label',
|
|
||||||
},
|
},
|
||||||
|
label: 'Option label',
|
||||||
|
type: 'option',
|
||||||
|
children: 'Model title',
|
||||||
className: 'class-for-user-picker',
|
className: 'class-for-user-picker',
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('PickerOption', () => {
|
describe('SelectOption', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const tree = renderer.create(<PickerOption {...model} />).toJSON();
|
const tree = renderer
|
||||||
|
.create(
|
||||||
|
<SelectOption
|
||||||
|
{...model}
|
||||||
|
data={{
|
||||||
|
imgUrl: 'url/to/avatar',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
.toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,4 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
import { OptionProps } from 'react-select/lib/components/Option';
|
import { OptionProps } from 'react-select/lib/components/Option';
|
||||||
|
|
||||||
@ -10,7 +13,7 @@ interface ExtendedOptionProps extends OptionProps<any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Option = (props: ExtendedOptionProps) => {
|
export const SelectOption = (props: ExtendedOptionProps) => {
|
||||||
const { children, isSelected, data } = props;
|
const { children, isSelected, data } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -28,7 +31,7 @@ export const Option = (props: ExtendedOptionProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// was not able to type this without typescript error
|
// was not able to type this without typescript error
|
||||||
export const SingleValue = props => {
|
export const SingleValue = (props: any) => {
|
||||||
const { children, data } = props;
|
const { children, data } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,4 +44,4 @@ export const SingleValue = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Option;
|
export default SelectOption;
|
@ -9,7 +9,7 @@ interface State {
|
|||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class OptionGroup extends PureComponent<ExtendedGroupProps, State> {
|
export default class SelectOptionGroup extends PureComponent<ExtendedGroupProps, State> {
|
||||||
state = {
|
state = {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
};
|
};
|
||||||
@ -24,7 +24,7 @@ export default class OptionGroup extends PureComponent<ExtendedGroupProps, State
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(nextProps) {
|
componentDidUpdate(nextProps: ExtendedGroupProps) {
|
||||||
if (nextProps.selectProps.inputValue !== '') {
|
if (nextProps.selectProps.inputValue !== '') {
|
||||||
this.setState({ expanded: true });
|
this.setState({ expanded: true });
|
||||||
}
|
}
|
@ -1,7 +1,12 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`PickerOption renders correctly 1`] = `
|
exports[`SelectOption renders correctly 1`] = `
|
||||||
<div>
|
<div
|
||||||
|
id=""
|
||||||
|
onClick={[MockFunction]}
|
||||||
|
onMouseOver={[MockFunction]}
|
||||||
|
tabIndex={1}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="gf-form-select-box__desc-option"
|
className="gf-form-select-box__desc-option"
|
||||||
>
|
>
|
@ -0,0 +1,27 @@
|
|||||||
|
export default function resetSelectStyles() {
|
||||||
|
return {
|
||||||
|
clearIndicator: () => ({}),
|
||||||
|
container: () => ({}),
|
||||||
|
control: () => ({}),
|
||||||
|
dropdownIndicator: () => ({}),
|
||||||
|
group: () => ({}),
|
||||||
|
groupHeading: () => ({}),
|
||||||
|
indicatorsContainer: () => ({}),
|
||||||
|
indicatorSeparator: () => ({}),
|
||||||
|
input: () => ({}),
|
||||||
|
loadingIndicator: () => ({}),
|
||||||
|
loadingMessage: () => ({}),
|
||||||
|
menu: () => ({}),
|
||||||
|
menuList: ({ maxHeight }: { maxHeight: number }) => ({
|
||||||
|
maxHeight,
|
||||||
|
}),
|
||||||
|
multiValue: () => ({}),
|
||||||
|
multiValueLabel: () => ({}),
|
||||||
|
multiValueRemove: () => ({}),
|
||||||
|
noOptionsMessage: () => ({}),
|
||||||
|
option: () => ({}),
|
||||||
|
placeholder: () => ({}),
|
||||||
|
singleValue: () => ({}),
|
||||||
|
valueContainer: () => ({}),
|
||||||
|
};
|
||||||
|
}
|
@ -1,23 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import Thresholds from './Thresholds';
|
|
||||||
import { defaultProps } from './GaugePanelOptions';
|
import { ThresholdsEditor, Props } from './ThresholdsEditor';
|
||||||
import { BasicGaugeColor } from 'app/types';
|
import { BasicGaugeColor } from '../../types';
|
||||||
import { PanelOptionsProps } from '@grafana/ui';
|
|
||||||
import { Options } from './types';
|
|
||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: PanelOptionsProps<Options> = {
|
const props: Props = {
|
||||||
onChange: jest.fn(),
|
onChange: jest.fn(),
|
||||||
options: {
|
thresholds: [],
|
||||||
...defaultProps.options,
|
|
||||||
thresholds: [],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(props, propOverrides);
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
return shallow(<Thresholds {...props} />).instance() as Thresholds;
|
return shallow(<ThresholdsEditor {...props} />).instance() as ThresholdsEditor;
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Add threshold', () => {
|
describe('Add threshold', () => {
|
||||||
@ -31,10 +26,7 @@ describe('Add threshold', () => {
|
|||||||
|
|
||||||
it('should add another threshold above a first', () => {
|
it('should add another threshold above a first', () => {
|
||||||
const instance = setup({
|
const instance = setup({
|
||||||
options: {
|
thresholds: [{ index: 0, value: 50, color: 'rgb(127, 115, 64)' }],
|
||||||
...defaultProps.options,
|
|
||||||
thresholds: [{ index: 0, value: 50, color: 'rgb(127, 115, 64)' }],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.onAddThreshold(1);
|
instance.onAddThreshold(1);
|
@ -1,32 +1,37 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor, { ColorInput } from 'tinycolor2';
|
||||||
import { ColorPicker } from 'app/core/components/colorpicker/ColorPicker';
|
|
||||||
import { BasicGaugeColor, Threshold } from 'app/types';
|
import { Threshold, BasicGaugeColor } from '../../types';
|
||||||
import { PanelOptionsProps } from '@grafana/ui';
|
import { ColorPicker } from '../ColorPicker/ColorPicker';
|
||||||
import { Options } from './types';
|
|
||||||
|
export interface Props {
|
||||||
|
thresholds: Threshold[];
|
||||||
|
onChange: (thresholds: Threshold[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
thresholds: Threshold[];
|
thresholds: Threshold[];
|
||||||
baseColor: string;
|
baseColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Thresholds extends PureComponent<PanelOptionsProps<Options>, State> {
|
export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = { thresholds: props.thresholds, baseColor: BasicGaugeColor.Green };
|
||||||
thresholds: props.options.thresholds,
|
|
||||||
baseColor: props.options.baseColor,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAddThreshold = index => {
|
onAddThreshold = (index: number) => {
|
||||||
const { maxValue, minValue } = this.props.options;
|
const maxValue = 100; // hardcoded for now before we add the base threshold
|
||||||
|
const minValue = 0; // hardcoded for now before we add the base threshold
|
||||||
const { thresholds } = this.state;
|
const { thresholds } = this.state;
|
||||||
|
|
||||||
const newThresholds = thresholds.map(threshold => {
|
const newThresholds = thresholds.map(threshold => {
|
||||||
if (threshold.index >= index) {
|
if (threshold.index >= index) {
|
||||||
threshold = { ...threshold, index: threshold.index + 1 };
|
threshold = {
|
||||||
|
...threshold,
|
||||||
|
index: threshold.index + 1,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return threshold;
|
return threshold;
|
||||||
@ -48,27 +53,32 @@ export default class Thresholds extends PureComponent<PanelOptionsProps<Options>
|
|||||||
if (index === 0 && thresholds.length === 0) {
|
if (index === 0 && thresholds.length === 0) {
|
||||||
color = tinycolor.mix(BasicGaugeColor.Green, BasicGaugeColor.Red, 50).toRgbString();
|
color = tinycolor.mix(BasicGaugeColor.Green, BasicGaugeColor.Red, 50).toRgbString();
|
||||||
} else {
|
} else {
|
||||||
color = tinycolor.mix(thresholds[index - 1].color, BasicGaugeColor.Red, 50).toRgbString();
|
color = tinycolor.mix(thresholds[index - 1].color as ColorInput, BasicGaugeColor.Red, 50).toRgbString();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
thresholds: this.sortThresholds([...newThresholds, { index: index, value: value, color: color }]),
|
thresholds: this.sortThresholds([
|
||||||
|
...newThresholds,
|
||||||
|
{
|
||||||
|
index,
|
||||||
|
value: value as number,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
() => this.updateGauge()
|
() => this.updateGauge()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
onRemoveThreshold = threshold => {
|
onRemoveThreshold = (threshold: Threshold) => {
|
||||||
this.setState(
|
this.setState(
|
||||||
prevState => ({
|
prevState => ({ thresholds: prevState.thresholds.filter(t => t !== threshold) }),
|
||||||
thresholds: prevState.thresholds.filter(t => t !== threshold),
|
|
||||||
}),
|
|
||||||
() => this.updateGauge()
|
() => this.updateGauge()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeThresholdValue = (event, threshold) => {
|
onChangeThresholdValue = (event: any, threshold: Threshold) => {
|
||||||
const { thresholds } = this.state;
|
const { thresholds } = this.state;
|
||||||
|
|
||||||
const newThresholds = thresholds.map(t => {
|
const newThresholds = thresholds.map(t => {
|
||||||
@ -79,12 +89,10 @@ export default class Thresholds extends PureComponent<PanelOptionsProps<Options>
|
|||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({ thresholds: newThresholds });
|
||||||
thresholds: newThresholds,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeThresholdColor = (threshold, color) => {
|
onChangeThresholdColor = (threshold: Threshold, color: string) => {
|
||||||
const { thresholds } = this.state;
|
const { thresholds } = this.state;
|
||||||
|
|
||||||
const newThresholds = thresholds.map(t => {
|
const newThresholds = thresholds.map(t => {
|
||||||
@ -103,20 +111,18 @@ export default class Thresholds extends PureComponent<PanelOptionsProps<Options>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeBaseColor = color => this.props.onChange({ ...this.props.options, baseColor: color });
|
onChangeBaseColor = (color: string) => this.props.onChange(this.state.thresholds);
|
||||||
onBlur = () => {
|
onBlur = () => {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({ thresholds: this.sortThresholds(prevState.thresholds) }));
|
||||||
thresholds: this.sortThresholds(prevState.thresholds),
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.updateGauge();
|
this.updateGauge();
|
||||||
};
|
};
|
||||||
|
|
||||||
updateGauge = () => {
|
updateGauge = () => {
|
||||||
this.props.onChange({ ...this.props.options, thresholds: this.state.thresholds });
|
this.props.onChange(this.state.thresholds);
|
||||||
};
|
};
|
||||||
|
|
||||||
sortThresholds = thresholds => {
|
sortThresholds = (thresholds: Threshold[]) => {
|
||||||
return thresholds.sort((t1, t2) => {
|
return thresholds.sort((t1, t2) => {
|
||||||
return t2.value - t1.value;
|
return t2.value - t1.value;
|
||||||
});
|
});
|
||||||
@ -161,20 +167,8 @@ export default class Thresholds extends PureComponent<PanelOptionsProps<Options>
|
|||||||
return thresholds.map((t, i) => {
|
return thresholds.map((t, i) => {
|
||||||
return (
|
return (
|
||||||
<div key={`${t.value}-${i}`} className="indicator-section">
|
<div key={`${t.value}-${i}`} className="indicator-section">
|
||||||
<div
|
<div onClick={() => this.onAddThreshold(t.index + 1)} style={{ height: '50%', backgroundColor: t.color }} />
|
||||||
onClick={() => this.onAddThreshold(t.index + 1)}
|
<div onClick={() => this.onAddThreshold(t.index)} style={{ height: '50%', backgroundColor: t.color }} />
|
||||||
style={{
|
|
||||||
height: '50%',
|
|
||||||
backgroundColor: t.color,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
onClick={() => this.onAddThreshold(t.index)}
|
|
||||||
style={{
|
|
||||||
height: '50%',
|
|
||||||
backgroundColor: t.color,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -185,14 +179,14 @@ export default class Thresholds extends PureComponent<PanelOptionsProps<Options>
|
|||||||
<div className="indicator-section" style={{ height: '100%' }}>
|
<div className="indicator-section" style={{ height: '100%' }}>
|
||||||
<div
|
<div
|
||||||
onClick={() => this.onAddThreshold(0)}
|
onClick={() => this.onAddThreshold(0)}
|
||||||
style={{ height: '100%', backgroundColor: this.props.options.baseColor }}
|
style={{ height: '100%', backgroundColor: BasicGaugeColor.Green }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBase() {
|
renderBase() {
|
||||||
const { baseColor } = this.props.options;
|
const baseColor = BasicGaugeColor.Green;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="threshold-row threshold-row-base">
|
<div className="threshold-row threshold-row-base">
|
@ -1,3 +1,5 @@
|
|||||||
@import 'CustomScrollbar/CustomScrollbar';
|
@import 'CustomScrollbar/CustomScrollbar';
|
||||||
@import 'DeleteButton/DeleteButton';
|
@import 'DeleteButton/DeleteButton';
|
||||||
|
@import 'ThresholdsEditor/ThresholdsEditor';
|
||||||
@import 'Tooltip/Tooltip';
|
@import 'Tooltip/Tooltip';
|
||||||
|
@import 'Select/Select';
|
||||||
|
@ -2,3 +2,15 @@ export { DeleteButton } from './DeleteButton/DeleteButton';
|
|||||||
export { Tooltip } from './Tooltip/Tooltip';
|
export { Tooltip } from './Tooltip/Tooltip';
|
||||||
export { Portal } from './Portal/Portal';
|
export { Portal } from './Portal/Portal';
|
||||||
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
|
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
|
||||||
|
|
||||||
|
// Select
|
||||||
|
export { Select, AsyncSelect, SelectOptionItem } from './Select/Select';
|
||||||
|
export { IndicatorsContainer } from './Select/IndicatorsContainer';
|
||||||
|
export { NoOptionsMessage } from './Select/NoOptionsMessage';
|
||||||
|
export { default as resetSelectStyles } from './Select/resetSelectStyles';
|
||||||
|
|
||||||
|
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
|
||||||
|
export { ColorPicker } from './ColorPicker/ColorPicker';
|
||||||
|
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
|
||||||
|
export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';
|
||||||
|
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { RangeMap, ValueMap, Threshold } from 'app/types';
|
import { RangeMap, Threshold, ValueMap } from './panel';
|
||||||
|
|
||||||
export interface Options {
|
export interface GaugeOptions {
|
||||||
baseColor: string;
|
baseColor: string;
|
||||||
decimals: number;
|
decimals: number;
|
||||||
mappings: Array<RangeMap | ValueMap>;
|
mappings: Array<RangeMap | ValueMap>;
|
@ -1,3 +1,4 @@
|
|||||||
export * from './series';
|
export * from './series';
|
||||||
export * from './time';
|
export * from './time';
|
||||||
export * from './panel';
|
export * from './panel';
|
||||||
|
export * from './gauge';
|
||||||
|
@ -29,3 +29,35 @@ export interface PanelMenuItem {
|
|||||||
shortcut?: string;
|
shortcut?: string;
|
||||||
subMenu?: PanelMenuItem[];
|
subMenu?: PanelMenuItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Threshold {
|
||||||
|
index: number;
|
||||||
|
value: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BasicGaugeColor {
|
||||||
|
Green = '#299c46',
|
||||||
|
Red = '#d44a3a',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MappingType {
|
||||||
|
ValueToText = 1,
|
||||||
|
RangeToText = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseMap {
|
||||||
|
id: number;
|
||||||
|
operator: string;
|
||||||
|
text: string;
|
||||||
|
type: MappingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ValueMap extends BaseMap {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RangeMap extends BaseMap {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
93
packages/grafana-ui/src/utils/colors.ts
Normal file
93
packages/grafana-ui/src/utils/colors.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
|
export const PALETTE_ROWS = 4;
|
||||||
|
export const PALETTE_COLUMNS = 14;
|
||||||
|
export const DEFAULT_ANNOTATION_COLOR = 'rgba(0, 211, 255, 1)';
|
||||||
|
export const OK_COLOR = 'rgba(11, 237, 50, 1)';
|
||||||
|
export const ALERTING_COLOR = 'rgba(237, 46, 24, 1)';
|
||||||
|
export const NO_DATA_COLOR = 'rgba(150, 150, 150, 1)';
|
||||||
|
export const PENDING_COLOR = 'rgba(247, 149, 32, 1)';
|
||||||
|
export const REGION_FILL_ALPHA = 0.09;
|
||||||
|
|
||||||
|
export const colors = [
|
||||||
|
'#7EB26D', // 0: pale green
|
||||||
|
'#EAB839', // 1: mustard
|
||||||
|
'#6ED0E0', // 2: light blue
|
||||||
|
'#EF843C', // 3: orange
|
||||||
|
'#E24D42', // 4: red
|
||||||
|
'#1F78C1', // 5: ocean
|
||||||
|
'#BA43A9', // 6: purple
|
||||||
|
'#705DA0', // 7: violet
|
||||||
|
'#508642', // 8: dark green
|
||||||
|
'#CCA300', // 9: dark sand
|
||||||
|
'#447EBC',
|
||||||
|
'#C15C17',
|
||||||
|
'#890F02',
|
||||||
|
'#0A437C',
|
||||||
|
'#6D1F62',
|
||||||
|
'#584477',
|
||||||
|
'#B7DBAB',
|
||||||
|
'#F4D598',
|
||||||
|
'#70DBED',
|
||||||
|
'#F9BA8F',
|
||||||
|
'#F29191',
|
||||||
|
'#82B5D8',
|
||||||
|
'#E5A8E2',
|
||||||
|
'#AEA2E0',
|
||||||
|
'#629E51',
|
||||||
|
'#E5AC0E',
|
||||||
|
'#64B0C8',
|
||||||
|
'#E0752D',
|
||||||
|
'#BF1B00',
|
||||||
|
'#0A50A1',
|
||||||
|
'#962D82',
|
||||||
|
'#614D93',
|
||||||
|
'#9AC48A',
|
||||||
|
'#F2C96D',
|
||||||
|
'#65C5DB',
|
||||||
|
'#F9934E',
|
||||||
|
'#EA6460',
|
||||||
|
'#5195CE',
|
||||||
|
'#D683CE',
|
||||||
|
'#806EB7',
|
||||||
|
'#3F6833',
|
||||||
|
'#967302',
|
||||||
|
'#2F575E',
|
||||||
|
'#99440A',
|
||||||
|
'#58140C',
|
||||||
|
'#052B51',
|
||||||
|
'#511749',
|
||||||
|
'#3F2B5B',
|
||||||
|
'#E0F9D7',
|
||||||
|
'#FCEACA',
|
||||||
|
'#CFFAFF',
|
||||||
|
'#F9E2D2',
|
||||||
|
'#FCE2DE',
|
||||||
|
'#BADFF4',
|
||||||
|
'#F9D9F9',
|
||||||
|
'#DEDAF7',
|
||||||
|
];
|
||||||
|
|
||||||
|
function sortColorsByHue(hexColors: string[]) {
|
||||||
|
const hslColors = _.map(hexColors, hexToHsl);
|
||||||
|
|
||||||
|
const sortedHSLColors = _.sortBy(hslColors, ['h']);
|
||||||
|
const chunkedHSLColors = _.chunk(sortedHSLColors, PALETTE_ROWS);
|
||||||
|
const sortedChunkedHSLColors = _.map(chunkedHSLColors, chunk => {
|
||||||
|
return _.sortBy(chunk, 'l');
|
||||||
|
});
|
||||||
|
const flattenedZippedSortedChunkedHSLColors = _.flattenDeep(_.zip(...sortedChunkedHSLColors));
|
||||||
|
|
||||||
|
return _.map(flattenedZippedSortedChunkedHSLColors, hslToHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToHsl(color: string) {
|
||||||
|
return tinycolor(color).toHsl();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hslToHex(color: any) {
|
||||||
|
return tinycolor(color).toHexString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export let sortedColors = sortColorsByHue(colors);
|
@ -1,2 +1,3 @@
|
|||||||
export * from './processTimeSeries';
|
export * from './processTimeSeries';
|
||||||
export * from './valueFormats/valueFormats';
|
export * from './valueFormats/valueFormats';
|
||||||
|
export * from './colors';
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
FROM debian:stretch-slim
|
ARG BASE_IMAGE=debian:stretch-slim
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
ARG GRAFANA_TGZ="grafana-latest.linux-x64.tar.gz"
|
ARG GRAFANA_TGZ="grafana-latest.linux-x64.tar.gz"
|
||||||
|
|
||||||
@ -10,7 +11,8 @@ COPY ${GRAFANA_TGZ} /tmp/grafana.tar.gz
|
|||||||
|
|
||||||
RUN mkdir /tmp/grafana && tar xfvz /tmp/grafana.tar.gz --strip-components=1 -C /tmp/grafana
|
RUN mkdir /tmp/grafana && tar xfvz /tmp/grafana.tar.gz --strip-components=1 -C /tmp/grafana
|
||||||
|
|
||||||
FROM debian:stretch-slim
|
ARG BASE_IMAGE=debian:stretch-slim
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
ARG GF_UID="472"
|
ARG GF_UID="472"
|
||||||
ARG GF_GID="472"
|
ARG GF_GID="472"
|
||||||
|
@ -8,6 +8,5 @@ docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
|
|||||||
./push_to_docker_hub.sh "$_grafana_version"
|
./push_to_docker_hub.sh "$_grafana_version"
|
||||||
|
|
||||||
if echo "$_grafana_version" | grep -q "^master-"; then
|
if echo "$_grafana_version" | grep -q "^master-"; then
|
||||||
apk add --no-cache curl
|
|
||||||
./deploy_to_k8s.sh "grafana/grafana-dev:$_grafana_version"
|
./deploy_to_k8s.sh "grafana/grafana-dev:$_grafana_version"
|
||||||
fi
|
fi
|
||||||
|
@ -1,25 +1,49 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
_grafana_tag=$1
|
_grafana_tag=${1:-}
|
||||||
|
_docker_repo=${2:-grafana/grafana}
|
||||||
|
|
||||||
# If the tag starts with v, treat this as a official release
|
# If the tag starts with v, treat this as a official release
|
||||||
if echo "$_grafana_tag" | grep -q "^v"; then
|
if echo "$_grafana_tag" | grep -q "^v"; then
|
||||||
_grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2)
|
_grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2)
|
||||||
_docker_repo=${2:-grafana/grafana}
|
|
||||||
else
|
else
|
||||||
_grafana_version=$_grafana_tag
|
_grafana_version=$_grafana_tag
|
||||||
_docker_repo=${2:-grafana/grafana-dev}
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Building ${_docker_repo}:${_grafana_version}"
|
echo "Building ${_docker_repo}:${_grafana_version}"
|
||||||
|
|
||||||
docker build \
|
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||||
--tag "${_docker_repo}:${_grafana_version}" \
|
|
||||||
--no-cache=true .
|
# Build grafana image for a specific arch
|
||||||
|
docker_build () {
|
||||||
|
base_image=$1
|
||||||
|
grafana_tgz=$2
|
||||||
|
tag=$3
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
--build-arg BASE_IMAGE=${base_image} \
|
||||||
|
--build-arg GRAFANA_TGZ=${grafana_tgz} \
|
||||||
|
--tag "${tag}" \
|
||||||
|
--no-cache=true .
|
||||||
|
}
|
||||||
|
|
||||||
|
# Tag docker images of all architectures
|
||||||
|
docker_tag_all () {
|
||||||
|
repo=$1
|
||||||
|
tag=$2
|
||||||
|
docker tag "${_docker_repo}:${_grafana_version}" "${repo}:${tag}"
|
||||||
|
docker tag "${_docker_repo}-arm32v7-linux:${_grafana_version}" "${repo}-arm32v7-linux:${tag}"
|
||||||
|
docker tag "${_docker_repo}-arm64v8-linux:${_grafana_version}" "${repo}-arm64v8-linux:${tag}"
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_build "debian:stretch-slim" "grafana-latest.linux-x64.tar.gz" "${_docker_repo}:${_grafana_version}"
|
||||||
|
docker_build "arm32v7/debian:stretch-slim" "grafana-latest.linux-armv7.tar.gz" "${_docker_repo}-arm32v7-linux:${_grafana_version}"
|
||||||
|
docker_build "arm64v8/debian:stretch-slim" "grafana-latest.linux-arm64.tar.gz" "${_docker_repo}-arm64v8-linux:${_grafana_version}"
|
||||||
|
|
||||||
# Tag as 'latest' for official release; otherwise tag as grafana/grafana:master
|
# Tag as 'latest' for official release; otherwise tag as grafana/grafana:master
|
||||||
if echo "$_grafana_tag" | grep -q "^v"; then
|
if echo "$_grafana_tag" | grep -q "^v"; then
|
||||||
docker tag "${_docker_repo}:${_grafana_version}" "${_docker_repo}:latest"
|
docker_tag_all "${_docker_repo}" "latest"
|
||||||
else
|
else
|
||||||
docker tag "${_docker_repo}:${_grafana_version}" "grafana/grafana:master"
|
docker_tag_all "${_docker_repo}" "master"
|
||||||
|
docker tag "${_docker_repo}:${_grafana_version}" "grafana/grafana-dev:${_grafana_version}"
|
||||||
fi
|
fi
|
||||||
|
@ -1,24 +1,46 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
_grafana_tag=$1
|
_grafana_tag=${1:-}
|
||||||
|
_docker_repo=${2:-grafana/grafana}
|
||||||
|
|
||||||
# If the tag starts with v, treat this as a official release
|
# If the tag starts with v, treat this as a official release
|
||||||
if echo "$_grafana_tag" | grep -q "^v"; then
|
if echo "$_grafana_tag" | grep -q "^v"; then
|
||||||
_grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2)
|
_grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2)
|
||||||
_docker_repo=${2:-grafana/grafana}
|
|
||||||
else
|
else
|
||||||
_grafana_version=$_grafana_tag
|
_grafana_version=$_grafana_tag
|
||||||
_docker_repo=${2:-grafana/grafana-dev}
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||||
|
|
||||||
echo "pushing ${_docker_repo}:${_grafana_version}"
|
echo "pushing ${_docker_repo}:${_grafana_version}"
|
||||||
docker push "${_docker_repo}:${_grafana_version}"
|
|
||||||
|
|
||||||
|
docker_push_all () {
|
||||||
|
repo=$1
|
||||||
|
tag=$2
|
||||||
|
|
||||||
|
# Push each image individually
|
||||||
|
docker push "${repo}:${tag}"
|
||||||
|
docker push "${repo}-arm32v7-linux:${tag}"
|
||||||
|
docker push "${repo}-arm64v8-linux:${tag}"
|
||||||
|
|
||||||
|
# Create and push a multi-arch manifest
|
||||||
|
docker manifest create "${repo}:${tag}" \
|
||||||
|
"${repo}:${tag}" \
|
||||||
|
"${repo}-arm32v7-linux:${tag}" \
|
||||||
|
"${repo}-arm64v8-linux:${tag}"
|
||||||
|
|
||||||
|
docker manifest push "${repo}:${tag}"
|
||||||
|
}
|
||||||
|
|
||||||
if echo "$_grafana_tag" | grep -q "^v" && echo "$_grafana_tag" | grep -vq "beta"; then
|
if echo "$_grafana_tag" | grep -q "^v" && echo "$_grafana_tag" | grep -vq "beta"; then
|
||||||
echo "pushing ${_docker_repo}:latest"
|
echo "pushing ${_docker_repo}:latest"
|
||||||
docker push "${_docker_repo}:latest"
|
docker_push_all "${_docker_repo}" "latest"
|
||||||
|
docker_push_all "${_docker_repo}" "${_grafana_version}"
|
||||||
|
elif echo "$_grafana_tag" | grep -q "^v" && echo "$_grafana_tag" | grep -q "beta"; then
|
||||||
|
docker_push_all "${_docker_repo}" "${_grafana_version}"
|
||||||
elif echo "$_grafana_tag" | grep -q "master"; then
|
elif echo "$_grafana_tag" | grep -q "master"; then
|
||||||
echo "pushing grafana/grafana:master"
|
docker_push_all "${_docker_repo}" "master"
|
||||||
docker push grafana/grafana:master
|
docker push "grafana/grafana-dev:${_grafana_version}"
|
||||||
fi
|
fi
|
||||||
|
@ -112,7 +112,7 @@ func NewDashboard(title string) *Dashboard {
|
|||||||
func NewDashboardFolder(title string) *Dashboard {
|
func NewDashboardFolder(title string) *Dashboard {
|
||||||
folder := NewDashboard(title)
|
folder := NewDashboard(title)
|
||||||
folder.IsFolder = true
|
folder.IsFolder = true
|
||||||
folder.Data.Set("schemaVersion", 16)
|
folder.Data.Set("schemaVersion", 17)
|
||||||
folder.Data.Set("version", 0)
|
folder.Data.Set("version", 0)
|
||||||
folder.IsFolder = true
|
folder.IsFolder = true
|
||||||
return folder
|
return folder
|
||||||
|
@ -112,7 +112,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
|
|||||||
|
|
||||||
frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString())
|
frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ValidationError{Reason: "Could not parse frequency"}
|
return nil, ValidationError{Reason: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
rawFor := jsonAlert.Get("for").MustString()
|
rawFor := jsonAlert.Get("for").MustString()
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
package alerting
|
package alerting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFrequencyCannotBeZeroOrLess = errors.New(`"evaluate every" cannot be zero or below`)
|
||||||
|
ErrFrequencyCouldNotBeParsed = errors.New(`"evaluate every" field could not be parsed`)
|
||||||
|
)
|
||||||
|
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Id int64
|
Id int64
|
||||||
OrgId int64
|
OrgId int64
|
||||||
@ -76,7 +81,7 @@ func getTimeDurationStringToSeconds(str string) (int64, error) {
|
|||||||
matches := ValueFormatRegex.FindAllString(str, 1)
|
matches := ValueFormatRegex.FindAllString(str, 1)
|
||||||
|
|
||||||
if len(matches) <= 0 {
|
if len(matches) <= 0 {
|
||||||
return 0, fmt.Errorf("Frequency could not be parsed")
|
return 0, ErrFrequencyCouldNotBeParsed
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := strconv.Atoi(matches[0])
|
value, err := strconv.Atoi(matches[0])
|
||||||
@ -84,6 +89,10 @@ func getTimeDurationStringToSeconds(str string) (int64, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if value == 0 {
|
||||||
|
return 0, ErrFrequencyCannotBeZeroOrLess
|
||||||
|
}
|
||||||
|
|
||||||
unit := UnitFormatRegex.FindAllString(str, 1)[0]
|
unit := UnitFormatRegex.FindAllString(str, 1)[0]
|
||||||
|
|
||||||
if val, ok := unitMultiplier[unit]; ok {
|
if val, ok := unitMultiplier[unit]; ok {
|
||||||
@ -101,7 +110,6 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
|
|||||||
model.PanelId = ruleDef.PanelId
|
model.PanelId = ruleDef.PanelId
|
||||||
model.Name = ruleDef.Name
|
model.Name = ruleDef.Name
|
||||||
model.Message = ruleDef.Message
|
model.Message = ruleDef.Message
|
||||||
model.Frequency = ruleDef.Frequency
|
|
||||||
model.State = ruleDef.State
|
model.State = ruleDef.State
|
||||||
model.LastStateChange = ruleDef.NewStateDate
|
model.LastStateChange = ruleDef.NewStateDate
|
||||||
model.For = ruleDef.For
|
model.For = ruleDef.For
|
||||||
@ -109,6 +117,13 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
|
|||||||
model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting"))
|
model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting"))
|
||||||
model.StateChanges = ruleDef.StateChanges
|
model.StateChanges = ruleDef.StateChanges
|
||||||
|
|
||||||
|
model.Frequency = ruleDef.Frequency
|
||||||
|
// frequency cannot be zero since that would not execute the alert rule.
|
||||||
|
// so we fallback to 60 seconds if `Freqency` is missing
|
||||||
|
if model.Frequency == 0 {
|
||||||
|
model.Frequency = 60
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
|
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
|
||||||
jsonModel := simplejson.NewFromAny(v)
|
jsonModel := simplejson.NewFromAny(v)
|
||||||
id, err := jsonModel.Get("id").Int64()
|
id, err := jsonModel.Get("id").Int64()
|
||||||
|
@ -14,6 +14,36 @@ func (f *FakeCondition) Eval(context *EvalContext) (*ConditionResult, error) {
|
|||||||
return &ConditionResult{}, nil
|
return &ConditionResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAlertRuleFrequencyParsing(t *testing.T) {
|
||||||
|
tcs := []struct {
|
||||||
|
input string
|
||||||
|
err error
|
||||||
|
result int64
|
||||||
|
}{
|
||||||
|
{input: "10s", result: 10},
|
||||||
|
{input: "10m", result: 600},
|
||||||
|
{input: "1h", result: 3600},
|
||||||
|
{input: "1o", result: 1},
|
||||||
|
{input: "0s", err: ErrFrequencyCannotBeZeroOrLess},
|
||||||
|
{input: "0m", err: ErrFrequencyCannotBeZeroOrLess},
|
||||||
|
{input: "0h", err: ErrFrequencyCannotBeZeroOrLess},
|
||||||
|
{input: "0", err: ErrFrequencyCannotBeZeroOrLess},
|
||||||
|
{input: "-1s", err: ErrFrequencyCouldNotBeParsed},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
r, err := getTimeDurationStringToSeconds(tc.input)
|
||||||
|
if err != tc.err {
|
||||||
|
t.Errorf("expected error: '%v' got: '%v'", tc.err, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r != tc.result {
|
||||||
|
t.Errorf("expected result: %d got %d", tc.result, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAlertRuleModel(t *testing.T) {
|
func TestAlertRuleModel(t *testing.T) {
|
||||||
Convey("Testing alert rule", t, func() {
|
Convey("Testing alert rule", t, func() {
|
||||||
|
|
||||||
@ -21,26 +51,6 @@ func TestAlertRuleModel(t *testing.T) {
|
|||||||
return &FakeCondition{}, nil
|
return &FakeCondition{}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Can parse seconds", func() {
|
|
||||||
seconds, _ := getTimeDurationStringToSeconds("10s")
|
|
||||||
So(seconds, ShouldEqual, 10)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Can parse minutes", func() {
|
|
||||||
seconds, _ := getTimeDurationStringToSeconds("10m")
|
|
||||||
So(seconds, ShouldEqual, 600)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Can parse hours", func() {
|
|
||||||
seconds, _ := getTimeDurationStringToSeconds("1h")
|
|
||||||
So(seconds, ShouldEqual, 3600)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("defaults to seconds", func() {
|
|
||||||
seconds, _ := getTimeDurationStringToSeconds("1o")
|
|
||||||
So(seconds, ShouldEqual, 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("should return err for empty string", func() {
|
Convey("should return err for empty string", func() {
|
||||||
_, err := getTimeDurationStringToSeconds("")
|
_, err := getTimeDurationStringToSeconds("")
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
@ -89,5 +99,35 @@ func TestAlertRuleModel(t *testing.T) {
|
|||||||
So(len(alertRule.Notifications), ShouldEqual, 2)
|
So(len(alertRule.Notifications), ShouldEqual, 2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("can construct alert rule model with invalid frequency", func() {
|
||||||
|
json := `
|
||||||
|
{
|
||||||
|
"name": "name2",
|
||||||
|
"description": "desc2",
|
||||||
|
"noDataMode": "critical",
|
||||||
|
"enabled": true,
|
||||||
|
"frequency": "0s",
|
||||||
|
"conditions": [ { "type": "test", "prop": 123 } ],
|
||||||
|
"notifications": []
|
||||||
|
}`
|
||||||
|
|
||||||
|
alertJSON, jsonErr := simplejson.NewJson([]byte(json))
|
||||||
|
So(jsonErr, ShouldBeNil)
|
||||||
|
|
||||||
|
alert := &m.Alert{
|
||||||
|
Id: 1,
|
||||||
|
OrgId: 1,
|
||||||
|
DashboardId: 1,
|
||||||
|
PanelId: 1,
|
||||||
|
Frequency: 0,
|
||||||
|
|
||||||
|
Settings: alertJSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
alertRule, err := NewRuleFromDBAlert(alert)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(alertRule.Frequency, ShouldEqual, 60)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { SearchResult } from './components/search/SearchResult';
|
|||||||
import { TagFilter } from './components/TagFilter/TagFilter';
|
import { TagFilter } from './components/TagFilter/TagFilter';
|
||||||
import { SideMenu } from './components/sidemenu/SideMenu';
|
import { SideMenu } from './components/sidemenu/SideMenu';
|
||||||
import AppNotificationList from './components/AppNotifications/AppNotificationList';
|
import AppNotificationList from './components/AppNotifications/AppNotificationList';
|
||||||
|
import { ColorPicker, SeriesColorPickerPopover } from '@grafana/ui';
|
||||||
|
|
||||||
export function registerAngularDirectives() {
|
export function registerAngularDirectives() {
|
||||||
react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
|
react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
|
||||||
@ -19,4 +20,13 @@ export function registerAngularDirectives() {
|
|||||||
['onChange', { watchDepth: 'reference' }],
|
['onChange', { watchDepth: 'reference' }],
|
||||||
['tagOptions', { watchDepth: 'reference' }],
|
['tagOptions', { watchDepth: 'reference' }],
|
||||||
]);
|
]);
|
||||||
|
react2AngularDirective('colorPicker', ColorPicker, [
|
||||||
|
'color',
|
||||||
|
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||||
|
]);
|
||||||
|
react2AngularDirective('seriesColorPickerPopover', SeriesColorPickerPopover, [
|
||||||
|
'series',
|
||||||
|
'onColorChange',
|
||||||
|
'onToggleAxis',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
||||||
import { TeamPicker, Team } from 'app/core/components/Select/TeamPicker';
|
import { TeamPicker, Team } from 'app/core/components/Select/TeamPicker';
|
||||||
import { Select, SelectOptionItem } from 'app/core/components/Select/Select';
|
import { Select, SelectOptionItem } from '@grafana/ui';
|
||||||
import { User } from 'app/types';
|
import { User } from 'app/types';
|
||||||
import {
|
import {
|
||||||
dashboardPermissionLevels,
|
dashboardPermissionLevels,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Select from 'app/core/components/Select/Select';
|
import { Select } from '@grafana/ui';
|
||||||
import { dashboardPermissionLevels } from 'app/types/acl';
|
import { dashboardPermissionLevels } from 'app/types/acl';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { Select } from 'app/core/components/Select/Select';
|
import { Select } from '@grafana/ui';
|
||||||
import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl';
|
import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl';
|
||||||
import { FolderInfo } from 'app/types';
|
import { FolderInfo } from 'app/types';
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Select from './Select';
|
import { Select } from '@grafana/ui';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { DataSourceSelectItem } from 'app/types';
|
import { DataSourceSelectItem } from 'app/types';
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
export default {
|
|
||||||
clearIndicator: () => ({}),
|
|
||||||
container: () => ({}),
|
|
||||||
control: () => ({}),
|
|
||||||
dropdownIndicator: () => ({}),
|
|
||||||
group: () => ({}),
|
|
||||||
groupHeading: () => ({}),
|
|
||||||
indicatorsContainer: () => ({}),
|
|
||||||
indicatorSeparator: () => ({}),
|
|
||||||
input: () => ({}),
|
|
||||||
loadingIndicator: () => ({}),
|
|
||||||
loadingMessage: () => ({}),
|
|
||||||
menu: () => ({}),
|
|
||||||
menuList: ({ maxHeight }: { maxHeight: number }) => ({
|
|
||||||
maxHeight,
|
|
||||||
}),
|
|
||||||
multiValue: () => ({}),
|
|
||||||
multiValueLabel: () => ({}),
|
|
||||||
multiValueRemove: () => ({}),
|
|
||||||
noOptionsMessage: () => ({}),
|
|
||||||
option: () => ({}),
|
|
||||||
placeholder: () => ({}),
|
|
||||||
singleValue: () => ({}),
|
|
||||||
valueContainer: () => ({}),
|
|
||||||
};
|
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { AsyncSelect } from './Select';
|
import { AsyncSelect } from '@grafana/ui';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import Select from './Select';
|
|
||||||
import { getValueFormats } from '@grafana/ui';
|
import { getValueFormats } from '@grafana/ui';
|
||||||
|
import { Select } from '@grafana/ui';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onChange: (item: any) => void;
|
onChange: (item: any) => void;
|
||||||
|
@ -3,7 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { AsyncSelect } from './Select';
|
import { AsyncSelect } from '@grafana/ui';
|
||||||
|
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
import { Label } from 'app/core/components/Label/Label';
|
||||||
import Select from 'app/core/components/Select/Select';
|
import { Select } from '@grafana/ui';
|
||||||
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
import { DashboardSearchHit } from 'app/types';
|
import { DashboardSearchHit } from 'app/types';
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { NoOptionsMessage, IndicatorsContainer, resetSelectStyles } from '@grafana/ui';
|
||||||
import AsyncSelect from '@torkelo/react-select/lib/Async';
|
import AsyncSelect from '@torkelo/react-select/lib/Async';
|
||||||
|
|
||||||
import { TagOption } from './TagOption';
|
import { TagOption } from './TagOption';
|
||||||
import { TagBadge } from './TagBadge';
|
import { TagBadge } from './TagBadge';
|
||||||
import IndicatorsContainer from 'app/core/components/Select/IndicatorsContainer';
|
|
||||||
import NoOptionsMessage from 'app/core/components/Select/NoOptionsMessage';
|
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
import ResetStyles from 'app/core/components/Select/ResetStyles';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
@ -51,7 +49,7 @@ export class TagFilter extends React.Component<Props, any> {
|
|||||||
getOptionValue: i => i.value,
|
getOptionValue: i => i.value,
|
||||||
getOptionLabel: i => i.label,
|
getOptionLabel: i => i.label,
|
||||||
value: tags,
|
value: tags,
|
||||||
styles: ResetStyles,
|
styles: resetSelectStyles(),
|
||||||
filterOption: (option, searchQuery) => {
|
filterOption: (option, searchQuery) => {
|
||||||
const regex = RegExp(searchQuery, 'i');
|
const regex = RegExp(searchQuery, 'i');
|
||||||
return regex.test(option.value);
|
return regex.test(option.value);
|
||||||
|
@ -13,11 +13,10 @@ import './partials';
|
|||||||
import './components/jsontree/jsontree';
|
import './components/jsontree/jsontree';
|
||||||
import './components/code_editor/code_editor';
|
import './components/code_editor/code_editor';
|
||||||
import './utils/outline';
|
import './utils/outline';
|
||||||
import './components/colorpicker/ColorPicker';
|
|
||||||
import './components/colorpicker/SeriesColorPickerPopover';
|
|
||||||
import './components/colorpicker/spectrum_picker';
|
import './components/colorpicker/spectrum_picker';
|
||||||
import './services/search_srv';
|
import './services/search_srv';
|
||||||
import './services/ng_react';
|
import './services/ng_react';
|
||||||
|
import { colors } from '@grafana/ui/';
|
||||||
|
|
||||||
import { searchDirective } from './components/search/search';
|
import { searchDirective } from './components/search/search';
|
||||||
import { infoPopover } from './components/info_popover';
|
import { infoPopover } from './components/info_popover';
|
||||||
@ -36,7 +35,6 @@ import 'app/core/services/all';
|
|||||||
import './filters/filters';
|
import './filters/filters';
|
||||||
import coreModule from './core_module';
|
import coreModule from './core_module';
|
||||||
import appEvents from './app_events';
|
import appEvents from './app_events';
|
||||||
import colors from './utils/colors';
|
|
||||||
import { assignModelProperties } from './utils/model_utils';
|
import { assignModelProperties } from './utils/model_utils';
|
||||||
import { contextSrv } from './services/context_srv';
|
import { contextSrv } from './services/context_srv';
|
||||||
import { KeybindingSrv } from './services/keybindingSrv';
|
import { KeybindingSrv } from './services/keybindingSrv';
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { colors } from '@grafana/ui';
|
||||||
|
|
||||||
import { TimeSeries } from 'app/core/core';
|
import { TimeSeries } from 'app/core/core';
|
||||||
import colors, { getThemeColor } from 'app/core/utils/colors';
|
import { getThemeColor } from 'app/core/utils/colors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of log level abbreviation to canonical log level.
|
* Mapping of log level abbreviation to canonical log level.
|
||||||
|
8
public/app/core/specs/factors.test.ts
Normal file
8
public/app/core/specs/factors.test.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import getFactors from 'app/core/utils/factors';
|
||||||
|
|
||||||
|
describe('factors', () => {
|
||||||
|
it('should return factors for 12', () => {
|
||||||
|
const factors = getFactors(12);
|
||||||
|
expect(factors).toEqual([1, 2, 3, 4, 6, 12]);
|
||||||
|
});
|
||||||
|
});
|
@ -1,99 +1,5 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import tinycolor from 'tinycolor2';
|
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
|
|
||||||
export const PALETTE_ROWS = 4;
|
|
||||||
export const PALETTE_COLUMNS = 14;
|
|
||||||
export const DEFAULT_ANNOTATION_COLOR = 'rgba(0, 211, 255, 1)';
|
|
||||||
export const OK_COLOR = 'rgba(11, 237, 50, 1)';
|
|
||||||
export const ALERTING_COLOR = 'rgba(237, 46, 24, 1)';
|
|
||||||
export const NO_DATA_COLOR = 'rgba(150, 150, 150, 1)';
|
|
||||||
export const PENDING_COLOR = 'rgba(247, 149, 32, 1)';
|
|
||||||
export const REGION_FILL_ALPHA = 0.09;
|
|
||||||
|
|
||||||
const colors = [
|
|
||||||
'#7EB26D', // 0: pale green
|
|
||||||
'#EAB839', // 1: mustard
|
|
||||||
'#6ED0E0', // 2: light blue
|
|
||||||
'#EF843C', // 3: orange
|
|
||||||
'#E24D42', // 4: red
|
|
||||||
'#1F78C1', // 5: ocean
|
|
||||||
'#BA43A9', // 6: purple
|
|
||||||
'#705DA0', // 7: violet
|
|
||||||
'#508642', // 8: dark green
|
|
||||||
'#CCA300', // 9: dark sand
|
|
||||||
'#447EBC',
|
|
||||||
'#C15C17',
|
|
||||||
'#890F02',
|
|
||||||
'#0A437C',
|
|
||||||
'#6D1F62',
|
|
||||||
'#584477',
|
|
||||||
'#B7DBAB',
|
|
||||||
'#F4D598',
|
|
||||||
'#70DBED',
|
|
||||||
'#F9BA8F',
|
|
||||||
'#F29191',
|
|
||||||
'#82B5D8',
|
|
||||||
'#E5A8E2',
|
|
||||||
'#AEA2E0',
|
|
||||||
'#629E51',
|
|
||||||
'#E5AC0E',
|
|
||||||
'#64B0C8',
|
|
||||||
'#E0752D',
|
|
||||||
'#BF1B00',
|
|
||||||
'#0A50A1',
|
|
||||||
'#962D82',
|
|
||||||
'#614D93',
|
|
||||||
'#9AC48A',
|
|
||||||
'#F2C96D',
|
|
||||||
'#65C5DB',
|
|
||||||
'#F9934E',
|
|
||||||
'#EA6460',
|
|
||||||
'#5195CE',
|
|
||||||
'#D683CE',
|
|
||||||
'#806EB7',
|
|
||||||
'#3F6833',
|
|
||||||
'#967302',
|
|
||||||
'#2F575E',
|
|
||||||
'#99440A',
|
|
||||||
'#58140C',
|
|
||||||
'#052B51',
|
|
||||||
'#511749',
|
|
||||||
'#3F2B5B',
|
|
||||||
'#E0F9D7',
|
|
||||||
'#FCEACA',
|
|
||||||
'#CFFAFF',
|
|
||||||
'#F9E2D2',
|
|
||||||
'#FCE2DE',
|
|
||||||
'#BADFF4',
|
|
||||||
'#F9D9F9',
|
|
||||||
'#DEDAF7',
|
|
||||||
];
|
|
||||||
|
|
||||||
export function sortColorsByHue(hexColors) {
|
|
||||||
const hslColors = _.map(hexColors, hexToHsl);
|
|
||||||
|
|
||||||
let sortedHSLColors = _.sortBy(hslColors, ['h']);
|
|
||||||
sortedHSLColors = _.chunk(sortedHSLColors, PALETTE_ROWS);
|
|
||||||
sortedHSLColors = _.map(sortedHSLColors, chunk => {
|
|
||||||
return _.sortBy(chunk, 'l');
|
|
||||||
});
|
|
||||||
sortedHSLColors = _.flattenDeep(_.zip(...sortedHSLColors));
|
|
||||||
|
|
||||||
return _.map(sortedHSLColors, hslToHex);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hexToHsl(color) {
|
|
||||||
return tinycolor(color).toHsl();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hslToHex(color) {
|
|
||||||
return tinycolor(color).toHexString();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getThemeColor(dark: string, light: string): string {
|
export function getThemeColor(dark: string, light: string): string {
|
||||||
return config.bootData.user.lightTheme ? light : dark;
|
return config.bootData.user.lightTheme ? light : dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let sortedColors = sortColorsByHue(colors);
|
|
||||||
export default colors;
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { colors } from '@grafana/ui';
|
||||||
|
|
||||||
import { renderUrl } from 'app/core/utils/url';
|
import { renderUrl } from 'app/core/utils/url';
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
import colors from 'app/core/utils/colors';
|
|
||||||
import { parse as parseDate } from 'app/core/utils/datemath';
|
import { parse as parseDate } from 'app/core/utils/datemath';
|
||||||
|
|
||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
|
5
public/app/core/utils/factors.ts
Normal file
5
public/app/core/utils/factors.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Returns the factors of a number
|
||||||
|
// Example getFactors(12) -> [1, 2, 3, 4, 6, 12]
|
||||||
|
export default function getFactors(num: number): number[] {
|
||||||
|
return Array.from(new Array(num + 1), (_, i) => i).filter(i => num % i === 0);
|
||||||
|
}
|
@ -14,6 +14,7 @@ import 'app/features/alerting/AlertTabCtrl';
|
|||||||
// Types
|
// Types
|
||||||
import { DashboardModel } from '../dashboard/dashboard_model';
|
import { DashboardModel } from '../dashboard/dashboard_model';
|
||||||
import { PanelModel } from '../dashboard/panel_model';
|
import { PanelModel } from '../dashboard/panel_model';
|
||||||
|
import { TestRuleResult } from './TestRuleResult';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
angularPanel?: AngularComponent;
|
angularPanel?: AngularComponent;
|
||||||
@ -65,9 +66,7 @@ export class AlertTab extends PureComponent<Props> {
|
|||||||
const loader = getAngularLoader();
|
const loader = getAngularLoader();
|
||||||
const template = '<alert-tab />';
|
const template = '<alert-tab />';
|
||||||
|
|
||||||
const scopeProps = {
|
const scopeProps = { ctrl: this.panelCtrl };
|
||||||
ctrl: this.panelCtrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.component = loader.load(this.element, scopeProps, template);
|
this.component = loader.load(this.element, scopeProps, template);
|
||||||
}
|
}
|
||||||
@ -111,6 +110,16 @@ export class AlertTab extends PureComponent<Props> {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderTestRuleResult = () => {
|
||||||
|
const { panel, dashboard } = this.props;
|
||||||
|
return <TestRuleResult panelId={panel.id} dashboard={dashboard} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
testRule = (): EditorToolbarView => ({
|
||||||
|
title: 'Test Rule',
|
||||||
|
render: () => this.renderTestRuleResult(),
|
||||||
|
});
|
||||||
|
|
||||||
onAddAlert = () => {
|
onAddAlert = () => {
|
||||||
this.panelCtrl._enableAlert();
|
this.panelCtrl._enableAlert();
|
||||||
this.component.digest();
|
this.component.digest();
|
||||||
@ -120,7 +129,7 @@ export class AlertTab extends PureComponent<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { alert } = this.props.panel;
|
const { alert } = this.props.panel;
|
||||||
|
|
||||||
const toolbarItems = alert ? [this.stateHistory(), this.deleteAlert()] : [];
|
const toolbarItems = alert ? [this.stateHistory(), this.testRule(), this.deleteAlert()] : [];
|
||||||
|
|
||||||
const model = {
|
const model = {
|
||||||
title: 'Panel has no alert rule defined',
|
title: 'Panel has no alert rule defined',
|
||||||
|
@ -9,8 +9,6 @@ import appEvents from 'app/core/app_events';
|
|||||||
export class AlertTabCtrl {
|
export class AlertTabCtrl {
|
||||||
panel: any;
|
panel: any;
|
||||||
panelCtrl: any;
|
panelCtrl: any;
|
||||||
testing: boolean;
|
|
||||||
testResult: any;
|
|
||||||
subTabIndex: number;
|
subTabIndex: number;
|
||||||
conditionTypes: any;
|
conditionTypes: any;
|
||||||
alert: any;
|
alert: any;
|
||||||
@ -406,21 +404,6 @@ export class AlertTabCtrl {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test() {
|
|
||||||
this.testing = true;
|
|
||||||
this.testResult = false;
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
dashboard: this.dashboardSrv.getCurrent().getSaveModelClone(),
|
|
||||||
panelId: this.panelCtrl.panel.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.backendSrv.post('/api/alerts/test', payload).then(res => {
|
|
||||||
this.testResult = res;
|
|
||||||
this.testing = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
|
43
public/app/features/alerting/TestRuleResult.test.tsx
Normal file
43
public/app/features/alerting/TestRuleResult.test.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { DashboardModel } from '../dashboard/dashboard_model';
|
||||||
|
import { Props, TestRuleResult } from './TestRuleResult';
|
||||||
|
|
||||||
|
jest.mock('app/core/services/backend_srv', () => ({
|
||||||
|
getBackendSrv: () => ({
|
||||||
|
post: jest.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const props: Props = {
|
||||||
|
panelId: 1,
|
||||||
|
dashboard: new DashboardModel({ panels: [{ id: 1 }] }),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
|
const wrapper = shallow(<TestRuleResult {...props} />);
|
||||||
|
|
||||||
|
return { wrapper, instance: wrapper.instance() as TestRuleResult };
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Render', () => {
|
||||||
|
it('should render component', () => {
|
||||||
|
const { wrapper } = setup();
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Life cycle', () => {
|
||||||
|
describe('component did mount', () => {
|
||||||
|
it('should call testRule', () => {
|
||||||
|
const { instance } = setup();
|
||||||
|
instance.testRule = jest.fn();
|
||||||
|
instance.componentDidMount();
|
||||||
|
|
||||||
|
expect(instance.testRule).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
45
public/app/features/alerting/TestRuleResult.tsx
Normal file
45
public/app/features/alerting/TestRuleResult.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter';
|
||||||
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
import { DashboardModel } from '../dashboard/dashboard_model';
|
||||||
|
import { LoadingPlaceholder } from '@grafana/ui/src';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
panelId: number;
|
||||||
|
dashboard: DashboardModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
isLoading: boolean;
|
||||||
|
testRuleResponse: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TestRuleResult extends PureComponent<Props, State> {
|
||||||
|
readonly state: State = {
|
||||||
|
isLoading: false,
|
||||||
|
testRuleResponse: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.testRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
async testRule() {
|
||||||
|
const { panelId, dashboard } = this.props;
|
||||||
|
const payload = { dashboard: dashboard.getSaveModelClone(), panelId };
|
||||||
|
|
||||||
|
this.setState({ isLoading: true });
|
||||||
|
const testRuleResponse = await getBackendSrv().post(`/api/alerts/test`, payload);
|
||||||
|
this.setState({ isLoading: false, testRuleResponse });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { testRuleResponse, isLoading } = this.state;
|
||||||
|
|
||||||
|
if (isLoading === true) {
|
||||||
|
return <LoadingPlaceholder text="Evaluating rule" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <JSONFormatter json={testRuleResponse} />;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render should render component 1`] = `
|
||||||
|
<Component
|
||||||
|
text="Evaluating rule"
|
||||||
|
/>
|
||||||
|
`;
|
@ -121,20 +121,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form-button-row">
|
|
||||||
<button class="btn btn-inverse" ng-click="ctrl.test()">
|
|
||||||
Test Rule
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gf-form-group" ng-if="ctrl.testing">
|
|
||||||
Evaluating rule <i class="fa fa-spinner fa-spin"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gf-form-group" ng-if="ctrl.testResult">
|
|
||||||
<json-tree root-name="result" object="ctrl.testResult" start-expanded="true"></json-tree>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import { MetricsPanelCtrl } from 'app/plugins/sdk';
|
|
||||||
import { AnnotationEvent } from './event';
|
|
||||||
import {
|
import {
|
||||||
OK_COLOR,
|
OK_COLOR,
|
||||||
ALERTING_COLOR,
|
ALERTING_COLOR,
|
||||||
@ -10,7 +8,10 @@ import {
|
|||||||
PENDING_COLOR,
|
PENDING_COLOR,
|
||||||
DEFAULT_ANNOTATION_COLOR,
|
DEFAULT_ANNOTATION_COLOR,
|
||||||
REGION_FILL_ALPHA,
|
REGION_FILL_ALPHA,
|
||||||
} from 'app/core/utils/colors';
|
} from '@grafana/ui';
|
||||||
|
|
||||||
|
import { MetricsPanelCtrl } from 'app/plugins/sdk';
|
||||||
|
import { AnnotationEvent } from './event';
|
||||||
|
|
||||||
export class EventManager {
|
export class EventManager {
|
||||||
event: AnnotationEvent;
|
event: AnnotationEvent;
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from 'app/core/constants';
|
} from 'app/core/constants';
|
||||||
import { PanelModel } from './panel_model';
|
import { PanelModel } from './panel_model';
|
||||||
import { DashboardModel } from './dashboard_model';
|
import { DashboardModel } from './dashboard_model';
|
||||||
|
import getFactors from 'app/core/utils/factors';
|
||||||
|
|
||||||
export class DashboardMigrator {
|
export class DashboardMigrator {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
@ -21,7 +22,7 @@ export class DashboardMigrator {
|
|||||||
let i, j, k, n;
|
let i, j, k, n;
|
||||||
const oldVersion = this.dashboard.schemaVersion;
|
const oldVersion = this.dashboard.schemaVersion;
|
||||||
const panelUpgrades = [];
|
const panelUpgrades = [];
|
||||||
this.dashboard.schemaVersion = 16;
|
this.dashboard.schemaVersion = 17;
|
||||||
|
|
||||||
if (oldVersion === this.dashboard.schemaVersion) {
|
if (oldVersion === this.dashboard.schemaVersion) {
|
||||||
return;
|
return;
|
||||||
@ -368,6 +369,24 @@ export class DashboardMigrator {
|
|||||||
this.upgradeToGridLayout(old);
|
this.upgradeToGridLayout(old);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 17) {
|
||||||
|
panelUpgrades.push(panel => {
|
||||||
|
if (panel.minSpan) {
|
||||||
|
const max = GRID_COLUMN_COUNT / panel.minSpan;
|
||||||
|
const factors = getFactors(GRID_COLUMN_COUNT);
|
||||||
|
// find the best match compared to factors
|
||||||
|
// (ie. [1,2,3,4,6,12,24] for 24 columns)
|
||||||
|
panel.maxPerRow =
|
||||||
|
factors[
|
||||||
|
_.findIndex(factors, o => {
|
||||||
|
return o > max;
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
delete panel.minSpan;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (panelUpgrades.length === 0) {
|
if (panelUpgrades.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui';
|
||||||
|
|
||||||
import { GRID_COLUMN_COUNT, REPEAT_DIR_VERTICAL, GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
|
import { GRID_COLUMN_COUNT, REPEAT_DIR_VERTICAL, GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
|
||||||
import { DEFAULT_ANNOTATION_COLOR } from 'app/core/utils/colors';
|
|
||||||
import { Emitter } from 'app/core/utils/emitter';
|
import { Emitter } from 'app/core/utils/emitter';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import sortByKeys from 'app/core/utils/sort_by_keys';
|
import sortByKeys from 'app/core/utils/sort_by_keys';
|
||||||
@ -442,7 +442,7 @@ export class DashboardModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedOptions = this.getSelectedVariableOptions(variable);
|
const selectedOptions = this.getSelectedVariableOptions(variable);
|
||||||
const minWidth = panel.minSpan || 6;
|
const maxPerRow = panel.maxPerRow || 4;
|
||||||
let xPos = 0;
|
let xPos = 0;
|
||||||
let yPos = panel.gridPos.y;
|
let yPos = panel.gridPos.y;
|
||||||
|
|
||||||
@ -462,7 +462,7 @@ export class DashboardModel {
|
|||||||
} else {
|
} else {
|
||||||
// set width based on how many are selected
|
// set width based on how many are selected
|
||||||
// assumed the repeated panels should take up full row width
|
// assumed the repeated panels should take up full row width
|
||||||
copy.gridPos.w = Math.max(GRID_COLUMN_COUNT / selectedOptions.length, minWidth);
|
copy.gridPos.w = Math.max(GRID_COLUMN_COUNT / selectedOptions.length, GRID_COLUMN_COUNT / maxPerRow);
|
||||||
copy.gridPos.x = xPos;
|
copy.gridPos.x = xPos;
|
||||||
copy.gridPos.y = yPos;
|
copy.gridPos.y = yPos;
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export class EditorTabBody extends PureComponent<Props, State> {
|
|||||||
onToggleToolBarView = (item: EditorToolbarView) => {
|
onToggleToolBarView = (item: EditorToolbarView) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
openView: item,
|
openView: item,
|
||||||
isOpen: !this.state.isOpen,
|
isOpen: this.state.openView !== item || !this.state.isOpen,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React, { PureComponent, SFC } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import 'app/features/panel/metrics_tab';
|
import 'app/features/panel/metrics_tab';
|
||||||
import { EditorTabBody, EditorToolbarView} from './EditorTabBody';
|
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
|
||||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||||
import { QueryInspector } from './QueryInspector';
|
import { QueryInspector } from './QueryInspector';
|
||||||
import { QueryOptions } from './QueryOptions';
|
import { QueryOptions } from './QueryOptions';
|
||||||
@ -36,12 +36,6 @@ interface State {
|
|||||||
isAddingMixed: boolean;
|
isAddingMixed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadingPlaceholderProps {
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoadingPlaceholder: SFC<LoadingPlaceholderProps> = ({ text }) => <h2>{text}</h2>;
|
|
||||||
|
|
||||||
export class QueriesTab extends PureComponent<Props, State> {
|
export class QueriesTab extends PureComponent<Props, State> {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
component: AngularComponent;
|
component: AngularComponent;
|
||||||
@ -134,7 +128,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
renderQueryInspector = () => {
|
renderQueryInspector = () => {
|
||||||
const { panel } = this.props;
|
const { panel } = this.props;
|
||||||
return <QueryInspector panel={panel} LoadingPlaceholder={LoadingPlaceholder} />;
|
return <QueryInspector panel={panel} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderHelp = () => {
|
renderHelp = () => {
|
||||||
|
@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter';
|
import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
||||||
|
import { LoadingPlaceholder } from '@grafana/ui';
|
||||||
|
|
||||||
interface DsQuery {
|
interface DsQuery {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@ -10,7 +11,6 @@ interface DsQuery {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
panel: any;
|
panel: any;
|
||||||
LoadingPlaceholder: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -177,7 +177,6 @@ export class QueryInspector extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { response, isLoading } = this.state.dsQuery;
|
const { response, isLoading } = this.state.dsQuery;
|
||||||
const { LoadingPlaceholder } = this.props;
|
|
||||||
const { isMocking } = this.state;
|
const { isMocking } = this.state;
|
||||||
const openNodes = this.getNrOfOpenNodes();
|
const openNodes = this.getNrOfOpenNodes();
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ export class PanelModel {
|
|||||||
repeatPanelId?: number;
|
repeatPanelId?: number;
|
||||||
repeatDirection?: string;
|
repeatDirection?: string;
|
||||||
repeatedByRow?: boolean;
|
repeatedByRow?: boolean;
|
||||||
minSpan?: number;
|
maxPerRow?: number;
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
panels?: any;
|
panels?: any;
|
||||||
soloMode?: boolean;
|
soloMode?: boolean;
|
||||||
|
@ -127,7 +127,7 @@ describe('DashboardModel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('dashboard schema version should be set to latest', () => {
|
it('dashboard schema version should be set to latest', () => {
|
||||||
expect(model.schemaVersion).toBe(16);
|
expect(model.schemaVersion).toBe(17);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('graph thresholds should be migrated', () => {
|
it('graph thresholds should be migrated', () => {
|
||||||
@ -364,14 +364,6 @@ describe('DashboardModel', () => {
|
|||||||
expect(dashboard.panels.length).toBe(2);
|
expect(dashboard.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('minSpan should be twice', () => {
|
|
||||||
model.rows = [createRow({ height: 8 }, [[6]])];
|
|
||||||
model.rows[0].panels[0] = { minSpan: 12 };
|
|
||||||
|
|
||||||
const dashboard = new DashboardModel(model);
|
|
||||||
expect(dashboard.panels[0].minSpan).toBe(24);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should assign id', () => {
|
it('should assign id', () => {
|
||||||
model.rows = [createRow({ collapse: true, height: 8 }, [[6], [6]])];
|
model.rows = [createRow({ collapse: true, height: 8 }, [[6], [6]])];
|
||||||
model.rows[0].panels[0] = {};
|
model.rows[0].panels[0] = {};
|
||||||
@ -380,6 +372,16 @@ describe('DashboardModel', () => {
|
|||||||
expect(dashboard.panels[0].id).toBe(1);
|
expect(dashboard.panels[0].id).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when migrating from minSpan to maxPerRow', () => {
|
||||||
|
it('maxPerRow should be correct', () => {
|
||||||
|
const model = {
|
||||||
|
panels: [{ minSpan: 8 }],
|
||||||
|
};
|
||||||
|
const dashboard = new DashboardModel(model);
|
||||||
|
expect(dashboard.panels[0].maxPerRow).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createRow(options, panelDescriptions: any[]) {
|
function createRow(options, panelDescriptions: any[]) {
|
||||||
|
@ -143,12 +143,9 @@ export function applyPanelTimeOverrides(panel: PanelModel, timeRange: TimeRange)
|
|||||||
const timeShift = '-' + timeShiftInterpolated;
|
const timeShift = '-' + timeShiftInterpolated;
|
||||||
newTimeData.timeInfo += ' timeshift ' + timeShift;
|
newTimeData.timeInfo += ' timeshift ' + timeShift;
|
||||||
newTimeData.timeRange = {
|
newTimeData.timeRange = {
|
||||||
from: dateMath.parseDateMath(timeShift, timeRange.from, false),
|
from: dateMath.parseDateMath(timeShift, newTimeData.timeRange.from, false),
|
||||||
to: dateMath.parseDateMath(timeShift, timeRange.to, true),
|
to: dateMath.parseDateMath(timeShift, newTimeData.timeRange.to, true),
|
||||||
raw: {
|
raw: newTimeData.timeRange.raw,
|
||||||
from: timeRange.from,
|
|
||||||
to: timeRange.to,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import Remarkable from 'remarkable';
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { profiler } from 'app/core/core';
|
import { profiler } from 'app/core/core';
|
||||||
import { Emitter } from 'app/core/core';
|
import { Emitter } from 'app/core/core';
|
||||||
|
import getFactors from 'app/core/utils/factors';
|
||||||
import {
|
import {
|
||||||
duplicatePanel,
|
duplicatePanel,
|
||||||
copyPanel as copyPanelUtil,
|
copyPanel as copyPanelUtil,
|
||||||
@ -12,7 +13,7 @@ import {
|
|||||||
sharePanel as sharePanelUtil,
|
sharePanel as sharePanelUtil,
|
||||||
} from 'app/features/dashboard/utils/panel';
|
} from 'app/features/dashboard/utils/panel';
|
||||||
|
|
||||||
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants';
|
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants';
|
||||||
|
|
||||||
export class PanelCtrl {
|
export class PanelCtrl {
|
||||||
panel: any;
|
panel: any;
|
||||||
@ -32,6 +33,7 @@ export class PanelCtrl {
|
|||||||
events: Emitter;
|
events: Emitter;
|
||||||
timing: any;
|
timing: any;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
maxPanelsPerRowOptions: number[];
|
||||||
|
|
||||||
constructor($scope, $injector) {
|
constructor($scope, $injector) {
|
||||||
this.$injector = $injector;
|
this.$injector = $injector;
|
||||||
@ -92,6 +94,7 @@ export class PanelCtrl {
|
|||||||
if (!this.editModeInitiated) {
|
if (!this.editModeInitiated) {
|
||||||
this.editModeInitiated = true;
|
this.editModeInitiated = true;
|
||||||
this.events.emit('init-edit-mode', null);
|
this.events.emit('init-edit-mode', null);
|
||||||
|
this.maxPanelsPerRowOptions = getFactors(GRID_COLUMN_COUNT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,12 +32,17 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form" ng-show="ctrl.panel.repeat && ctrl.panel.repeatDirection == 'h'">
|
<div class="gf-form" ng-show="ctrl.panel.repeat && ctrl.panel.repeatDirection == 'h'">
|
||||||
<span class="gf-form-label width-9">Min width</span>
|
<span class="gf-form-label width-9">Max per row</span>
|
||||||
<select class="gf-form-input" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]">
|
<select class="gf-form-input" ng-model="ctrl.panel.maxPerRow" ng-options="f for f in [2,3,4,6,12,24]">
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="gf-form-hint">
|
||||||
|
<div class="gf-form-hint-text muted">
|
||||||
|
Note: You may need to change the variable selection to see this in action.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -254,6 +254,10 @@ export class ElasticDatasource {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target.alias) {
|
||||||
|
target.alias = this.templateSrv.replace(target.alias, options.scopedVars, 'lucene');
|
||||||
|
}
|
||||||
|
|
||||||
const queryString = this.templateSrv.replace(target.query || '*', options.scopedVars, 'lucene');
|
const queryString = this.templateSrv.replace(target.query || '*', options.scopedVars, 'lucene');
|
||||||
const queryObj = this.queryBuilder.build(target, adhocFilters, queryString);
|
const queryObj = this.queryBuilder.build(target, adhocFilters, queryString);
|
||||||
const esQuery = angular.toJson(queryObj);
|
const esQuery = angular.toJson(queryObj);
|
||||||
|
@ -16,7 +16,13 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const templateSrv = {
|
const templateSrv = {
|
||||||
replace: jest.fn(text => text),
|
replace: jest.fn(text => {
|
||||||
|
if (text.startsWith("$")) {
|
||||||
|
return `resolvedVariable`;
|
||||||
|
} else {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}),
|
||||||
getAdhocFilters: jest.fn(() => []),
|
getAdhocFilters: jest.fn(() => []),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,7 +73,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('When issuing metric query with interval pattern', () => {
|
describe('When issuing metric query with interval pattern', () => {
|
||||||
let requestOptions, parts, header;
|
let requestOptions, parts, header, query;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
@ -81,19 +87,22 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
return Promise.resolve({ data: { responses: [] } });
|
return Promise.resolve({ data: { responses: [] } });
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.ds.query({
|
query = {
|
||||||
range: {
|
range: {
|
||||||
from: moment.utc([2015, 4, 30, 10]),
|
from: moment.utc([2015, 4, 30, 10]),
|
||||||
to: moment.utc([2015, 5, 1, 10]),
|
to: moment.utc([2015, 5, 1, 10]),
|
||||||
},
|
},
|
||||||
targets: [
|
targets: [
|
||||||
{
|
{
|
||||||
|
alias: "$varAlias",
|
||||||
bucketAggs: [],
|
bucketAggs: [],
|
||||||
metrics: [{ type: 'raw_document' }],
|
metrics: [{ type: 'raw_document' }],
|
||||||
query: 'escape\\:test',
|
query: 'escape\\:test',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
ctx.ds.query(query);
|
||||||
|
|
||||||
parts = requestOptions.data.split('\n');
|
parts = requestOptions.data.split('\n');
|
||||||
header = angular.fromJson(parts[0]);
|
header = angular.fromJson(parts[0]);
|
||||||
@ -103,6 +112,10 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
expect(header.index).toEqual(['asd-2015.05.30', 'asd-2015.05.31', 'asd-2015.06.01']);
|
expect(header.index).toEqual(['asd-2015.05.30', 'asd-2015.05.31', 'asd-2015.06.01']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should resolve the alias variable', () => {
|
||||||
|
expect(query.targets[0].alias).toEqual('resolvedVariable');
|
||||||
|
});
|
||||||
|
|
||||||
it('should json escape lucene query', () => {
|
it('should json escape lucene query', () => {
|
||||||
const body = angular.fromJson(parts[1]);
|
const body = angular.fromJson(parts[1]);
|
||||||
expect(body.query.bool.filter[1].query_string.query).toBe('escape\\:test');
|
expect(body.query.bool.filter[1].query_string.query).toBe('escape\\:test');
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import kbn from 'app/core/utils/kbn';
|
||||||
|
|
||||||
function renderTagCondition(tag, index) {
|
function renderTagCondition(tag, index) {
|
||||||
let str = '';
|
let str = '';
|
||||||
@ -43,7 +44,7 @@ export class InfluxQueryBuilder {
|
|||||||
} else if (type === 'MEASUREMENTS') {
|
} else if (type === 'MEASUREMENTS') {
|
||||||
query = 'SHOW MEASUREMENTS';
|
query = 'SHOW MEASUREMENTS';
|
||||||
if (withMeasurementFilter) {
|
if (withMeasurementFilter) {
|
||||||
query += ' WITH MEASUREMENT =~ /' + withMeasurementFilter + '/';
|
query += ' WITH MEASUREMENT =~ /' + kbn.regexEscape(withMeasurementFilter) + '/';
|
||||||
}
|
}
|
||||||
} else if (type === 'FIELDS') {
|
} else if (type === 'FIELDS') {
|
||||||
measurement = this.target.measurement;
|
measurement = this.target.measurement;
|
||||||
|
@ -50,6 +50,12 @@ describe('InfluxQueryBuilder', () => {
|
|||||||
expect(query).toBe('SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/ LIMIT 100');
|
expect(query).toBe('SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/ LIMIT 100');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should escape the regex value in measurement query', () => {
|
||||||
|
const builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
|
||||||
|
const query = builder.buildExploreQuery('MEASUREMENTS', undefined, 'abc/edf/');
|
||||||
|
expect(query).toBe('SHOW MEASUREMENTS WITH MEASUREMENT =~ /abc\\/edf\\// LIMIT 100');
|
||||||
|
});
|
||||||
|
|
||||||
it('should have WITH MEASUREMENT WHERE in measurement query for non-empty query with tags', () => {
|
it('should have WITH MEASUREMENT WHERE in measurement query for non-empty query with tags', () => {
|
||||||
const builder = new InfluxQueryBuilder({
|
const builder = new InfluxQueryBuilder({
|
||||||
measurement: '',
|
measurement: '',
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
import { GaugeOptions, PanelOptionsProps } from '@grafana/ui';
|
||||||
|
|
||||||
import { Switch } from 'app/core/components/Switch/Switch';
|
import { Switch } from 'app/core/components/Switch/Switch';
|
||||||
import { Label } from '../../../core/components/Label/Label';
|
import { Label } from '../../../core/components/Label/Label';
|
||||||
import { PanelOptionsProps } from '@grafana/ui';
|
|
||||||
import { Options } from './types';
|
|
||||||
|
|
||||||
export default class GaugeOptions extends PureComponent<PanelOptionsProps<Options>> {
|
export default class GaugeOptionsEditor extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
||||||
onToggleThresholdLabels = () =>
|
onToggleThresholdLabels = () =>
|
||||||
this.props.onChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels });
|
this.props.onChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels });
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { PanelProps, NullValueMode } from '@grafana/ui';
|
import { GaugeOptions, PanelProps, NullValueMode } from '@grafana/ui';
|
||||||
|
|
||||||
import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
|
import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
|
||||||
import Gauge from 'app/viz/Gauge';
|
import Gauge from 'app/viz/Gauge';
|
||||||
import { Options } from './types';
|
|
||||||
|
|
||||||
interface Props extends PanelProps<Options> {}
|
interface Props extends PanelProps<GaugeOptions> {}
|
||||||
|
|
||||||
export class GaugePanel extends PureComponent<Props> {
|
export class GaugePanel extends PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
import { BasicGaugeColor, GaugeOptions, PanelOptionsProps, ThresholdsEditor, Threshold } from '@grafana/ui';
|
||||||
|
|
||||||
import ValueOptions from 'app/plugins/panel/gauge/ValueOptions';
|
import ValueOptions from 'app/plugins/panel/gauge/ValueOptions';
|
||||||
import Thresholds from 'app/plugins/panel/gauge/Thresholds';
|
|
||||||
import { BasicGaugeColor } from 'app/types';
|
|
||||||
import { PanelOptionsProps } from '@grafana/ui';
|
|
||||||
import ValueMappings from 'app/plugins/panel/gauge/ValueMappings';
|
import ValueMappings from 'app/plugins/panel/gauge/ValueMappings';
|
||||||
import { Options } from './types';
|
import GaugeOptionsEditor from './GaugeOptionsEditor';
|
||||||
import GaugeOptions from './GaugeOptions';
|
|
||||||
|
|
||||||
export const defaultProps = {
|
export const defaultProps = {
|
||||||
options: {
|
options: {
|
||||||
@ -24,17 +22,19 @@ export const defaultProps = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class GaugePanelOptions extends PureComponent<PanelOptionsProps<Options>> {
|
export default class GaugePanelOptions extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
||||||
static defaultProps = defaultProps;
|
static defaultProps = defaultProps;
|
||||||
|
|
||||||
|
onThresholdsChanged = (thresholds: Threshold[]) => this.props.onChange({ ...this.props.options, thresholds });
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { onChange, options } = this.props;
|
const { onChange, options } = this.props;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="form-section">
|
<div className="form-section">
|
||||||
<ValueOptions onChange={onChange} options={options} />
|
<ValueOptions onChange={onChange} options={options} />
|
||||||
<GaugeOptions onChange={onChange} options={options} />
|
<GaugeOptionsEditor onChange={onChange} options={options} />
|
||||||
<Thresholds onChange={onChange} options={options} />
|
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-section">
|
<div className="form-section">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
import { MappingType, RangeMap, Select, ValueMap } from '@grafana/ui';
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
import { Label } from 'app/core/components/Label/Label';
|
||||||
import { Select } from 'app/core/components/Select/Select';
|
|
||||||
import { MappingType, RangeMap, ValueMap } from 'app/types';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mapping: ValueMap | RangeMap;
|
mapping: ValueMap | RangeMap;
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import ValueMappings from './ValueMappings';
|
import { GaugeOptions, MappingType, PanelOptionsProps } from '@grafana/ui';
|
||||||
import { MappingType } from 'app/types';
|
|
||||||
import { PanelOptionsProps } from '@grafana/ui';
|
|
||||||
import { Options } from './types';
|
|
||||||
import { defaultProps } from 'app/plugins/panel/gauge/GaugePanelOptions';
|
import { defaultProps } from 'app/plugins/panel/gauge/GaugePanelOptions';
|
||||||
|
|
||||||
|
import ValueMappings from './ValueMappings';
|
||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props: PanelOptionsProps<Options> = {
|
const props: PanelOptionsProps<GaugeOptions> = {
|
||||||
onChange: jest.fn(),
|
onChange: jest.fn(),
|
||||||
options: {
|
options: {
|
||||||
...defaultProps.options,
|
...defaultProps.options,
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
import { GaugeOptions, PanelOptionsProps, MappingType, RangeMap, ValueMap } from '@grafana/ui';
|
||||||
|
|
||||||
import MappingRow from './MappingRow';
|
import MappingRow from './MappingRow';
|
||||||
import { MappingType, RangeMap, ValueMap } from 'app/types';
|
|
||||||
import { PanelOptionsProps } from '@grafana/ui';
|
|
||||||
import { Options } from './types';
|
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
mappings: Array<ValueMap | RangeMap>;
|
mappings: Array<ValueMap | RangeMap>;
|
||||||
nextIdToAdd: number;
|
nextIdToAdd: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ValueMappings extends PureComponent<PanelOptionsProps<Options>, State> {
|
export default class ValueMappings extends PureComponent<PanelOptionsProps<GaugeOptions>, State> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
import { GaugeOptions, PanelOptionsProps } from '@grafana/ui';
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
import { Label } from 'app/core/components/Label/Label';
|
||||||
import Select from 'app/core/components/Select/Select';
|
import { Select} from '@grafana/ui';
|
||||||
import UnitPicker from 'app/core/components/Select/UnitPicker';
|
import UnitPicker from 'app/core/components/Select/UnitPicker';
|
||||||
import { PanelOptionsProps } from '@grafana/ui';
|
|
||||||
import { Options } from './types';
|
|
||||||
|
|
||||||
const statOptions = [
|
const statOptions = [
|
||||||
{ value: 'min', label: 'Min' },
|
{ value: 'min', label: 'Min' },
|
||||||
@ -21,7 +21,7 @@ const statOptions = [
|
|||||||
|
|
||||||
const labelWidth = 6;
|
const labelWidth = 6;
|
||||||
|
|
||||||
export default class ValueOptions extends PureComponent<PanelOptionsProps<Options>> {
|
export default class ValueOptions extends PureComponent<PanelOptionsProps<GaugeOptions>> {
|
||||||
onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value });
|
onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value });
|
||||||
|
|
||||||
onStatChange = stat => this.props.onChange({ ...this.props.options, stat: stat.value });
|
onStatChange = stat => this.props.onChange({ ...this.props.options, stat: stat.value });
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { TimeSeries } from 'app/core/core';
|
import { TimeSeries } from 'app/core/core';
|
||||||
import { SeriesColorPicker } from 'app/core/components/colorpicker/SeriesColorPicker';
|
import { SeriesColorPicker } from '@grafana/ui';
|
||||||
|
|
||||||
export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total'];
|
export const LEGEND_STATS = ['min', 'max', 'avg', 'current', 'total'];
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { colors } from '@grafana/ui';
|
||||||
|
|
||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
import colors from 'app/core/utils/colors';
|
|
||||||
|
|
||||||
export class DataProcessor {
|
export class DataProcessor {
|
||||||
constructor(private panel) {}
|
constructor(private panel) {}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import colors from 'app/core/utils/colors';
|
import { colors } from '@grafana/ui';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { processTimeSeries } from '@grafana/ui/src/utils';
|
import { processTimeSeries } from '@grafana/ui/src/utils';
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import Drop from 'tether-drop';
|
||||||
|
import { colors } from '@grafana/ui';
|
||||||
|
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import { profiler } from 'app/core/profiler';
|
import { profiler } from 'app/core/profiler';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import Drop from 'tether-drop';
|
|
||||||
import colors from 'app/core/utils/colors';
|
|
||||||
import { BackendSrv, setBackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv, setBackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { TimeSrv, setTimeSrv } from 'app/features/dashboard/time_srv';
|
import { TimeSrv, setTimeSrv } from 'app/features/dashboard/time_srv';
|
||||||
import { DatasourceSrv, setDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { DatasourceSrv, setDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
|
@ -9,7 +9,6 @@ import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys';
|
|||||||
import { Invitee, OrgUser, User, UsersState, UserState } from './user';
|
import { Invitee, OrgUser, User, UsersState, UserState } from './user';
|
||||||
import { DataSource, DataSourceSelectItem, DataSourcesState } from './datasources';
|
import { DataSource, DataSourceSelectItem, DataSourcesState } from './datasources';
|
||||||
import { DataQuery, DataQueryResponse, DataQueryOptions } from './series';
|
import { DataQuery, DataQueryResponse, DataQueryOptions } from './series';
|
||||||
import { BasicGaugeColor, MappingType, RangeMap, Threshold, ValueMap } from './panel';
|
|
||||||
import { PluginDashboard, PluginMeta, Plugin, PanelPlugin, PluginsState } from './plugins';
|
import { PluginDashboard, PluginMeta, Plugin, PanelPlugin, PluginsState } from './plugins';
|
||||||
import { Organization, OrganizationState } from './organization';
|
import { Organization, OrganizationState } from './organization';
|
||||||
import {
|
import {
|
||||||
@ -69,13 +68,8 @@ export {
|
|||||||
AppNotificationTimeout,
|
AppNotificationTimeout,
|
||||||
DashboardSearchHit,
|
DashboardSearchHit,
|
||||||
UserState,
|
UserState,
|
||||||
Threshold,
|
|
||||||
ValidationEvents,
|
ValidationEvents,
|
||||||
ValidationRule,
|
ValidationRule,
|
||||||
ValueMap,
|
|
||||||
RangeMap,
|
|
||||||
MappingType,
|
|
||||||
BasicGaugeColor,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface StoreState {
|
export interface StoreState {
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
export interface Threshold {
|
|
||||||
index: number;
|
|
||||||
value: number;
|
|
||||||
color?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum MappingType {
|
|
||||||
ValueToText = 1,
|
|
||||||
RangeToText = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum BasicGaugeColor {
|
|
||||||
Green = '#299c46',
|
|
||||||
Red = '#d44a3a',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BaseMap {
|
|
||||||
id: number;
|
|
||||||
operator: string;
|
|
||||||
text: string;
|
|
||||||
type: MappingType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ValueMap extends BaseMap {
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RangeMap extends BaseMap {
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
import { BasicGaugeColor, TimeSeriesVMs } from '@grafana/ui';
|
||||||
|
|
||||||
import { Gauge, Props } from './Gauge';
|
import { Gauge, Props } from './Gauge';
|
||||||
import { BasicGaugeColor } from '../types';
|
|
||||||
import { TimeSeriesVMs } from '@grafana/ui';
|
|
||||||
|
|
||||||
jest.mock('jquery', () => ({
|
jest.mock('jquery', () => ({
|
||||||
plot: jest.fn(),
|
plot: jest.fn(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { BasicGaugeColor, MappingType, RangeMap, Threshold, ValueMap } from 'app/types';
|
import { BasicGaugeColor, Threshold, TimeSeriesVMs, RangeMap, ValueMap, MappingType } from '@grafana/ui';
|
||||||
import { TimeSeriesVMs } from '@grafana/ui';
|
|
||||||
import config from '../core/config';
|
import config from '../core/config';
|
||||||
import kbn from '../core/utils/kbn';
|
import kbn from '../core/utils/kbn';
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import colors from 'app/core/utils/colors';
|
import { colors } from '@grafana/ui';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TimeSeries, TimeSeriesVMs, NullValueMode } from '@grafana/ui';
|
import { TimeSeries, TimeSeriesVMs, NullValueMode } from '@grafana/ui';
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rows": [],
|
"rows": [],
|
||||||
"schemaVersion": 16,
|
"schemaVersion": 17,
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"templating": {
|
"templating": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// DEPENDENCIES
|
// DEPENDENCIES
|
||||||
@import '../../node_modules/react-table/react-table.css';
|
@import '../../node_modules/react-table/react-table.css';
|
||||||
|
|
||||||
// VENDOR
|
// VENDOR
|
||||||
@ -38,9 +38,6 @@
|
|||||||
@import 'layout/lists';
|
@import 'layout/lists';
|
||||||
@import 'layout/page';
|
@import 'layout/page';
|
||||||
|
|
||||||
// LOAD @grafana/ui components
|
|
||||||
@import '../../packages/grafana-ui/src/index';
|
|
||||||
|
|
||||||
// COMPONENTS
|
// COMPONENTS
|
||||||
@import 'components/scrollbar';
|
@import 'components/scrollbar';
|
||||||
@import 'components/cards';
|
@import 'components/cards';
|
||||||
@ -97,16 +94,17 @@
|
|||||||
@import 'components/page_header';
|
@import 'components/page_header';
|
||||||
@import 'components/dashboard_settings';
|
@import 'components/dashboard_settings';
|
||||||
@import 'components/empty_list_cta';
|
@import 'components/empty_list_cta';
|
||||||
@import 'components/form_select_box';
|
|
||||||
@import 'components/panel_editor';
|
@import 'components/panel_editor';
|
||||||
@import 'components/toolbar';
|
@import 'components/toolbar';
|
||||||
@import 'components/add_data_source.scss';
|
@import 'components/add_data_source.scss';
|
||||||
@import 'components/page_loader';
|
@import 'components/page_loader';
|
||||||
@import 'components/thresholds';
|
|
||||||
@import 'components/toggle_button_group';
|
@import 'components/toggle_button_group';
|
||||||
@import 'components/value-mappings';
|
@import 'components/value-mappings';
|
||||||
@import 'components/popover-box';
|
@import 'components/popover-box';
|
||||||
|
|
||||||
|
// LOAD @grafana/ui components
|
||||||
|
@import '../../packages/grafana-ui/src/index';
|
||||||
|
|
||||||
// PAGES
|
// PAGES
|
||||||
@import 'pages/login';
|
@import 'pages/login';
|
||||||
@import 'pages/dashboard';
|
@import 'pages/dashboard';
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user