mirror of
https://github.com/grafana/grafana.git
synced 2025-09-27 11:04:06 +08:00
Merge branch 'master' into solo-panel-rewrite
This commit is contained in:
@ -333,6 +333,7 @@ jobs:
|
|||||||
docker:
|
docker:
|
||||||
- image: grafana/grafana-ci-deploy:1.2.0
|
- image: grafana/grafana-ci-deploy:1.2.0
|
||||||
steps:
|
steps:
|
||||||
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: .
|
at: .
|
||||||
- run:
|
- run:
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
# 6.0.0-beta2 (unreleased)
|
# 6.0.0-beta2 (unreleased)
|
||||||
|
|
||||||
|
### Minor
|
||||||
|
* **Pushover**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae)
|
||||||
|
|
||||||
# 6.0.0-beta1 (2019-01-30)
|
# 6.0.0-beta1 (2019-01-30)
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { Gauge, Props } from './Gauge';
|
import { Gauge, Props } from './Gauge';
|
||||||
import { TimeSeriesVMs } from '../../types/series';
|
import { TimeSeriesVMs } from '../../types/data';
|
||||||
import { ValueMapping, MappingType } from '../../types';
|
import { ValueMapping, MappingType } from '../../types';
|
||||||
|
|
||||||
jest.mock('jquery', () => ({
|
jest.mock('jquery', () => ({
|
||||||
|
@ -52,3 +52,20 @@ export interface TimeSeriesVMs {
|
|||||||
[index: number]: TimeSeriesVM;
|
[index: number]: TimeSeriesVM;
|
||||||
length: number;
|
length: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Column {
|
||||||
|
text: string;
|
||||||
|
title?: string;
|
||||||
|
type?: string;
|
||||||
|
sort?: boolean;
|
||||||
|
desc?: boolean;
|
||||||
|
filterable?: boolean;
|
||||||
|
unit?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableData {
|
||||||
|
columns: Column[];
|
||||||
|
rows: any[];
|
||||||
|
type: string;
|
||||||
|
columnMap: any;
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { TimeRange, RawTimeRange } from './time';
|
import { TimeRange, RawTimeRange } from './time';
|
||||||
import { TimeSeries } from './series';
|
|
||||||
import { PluginMeta } from './plugin';
|
import { PluginMeta } from './plugin';
|
||||||
|
import { TableData, TimeSeries } from './data';
|
||||||
|
|
||||||
export interface DataQueryResponse {
|
export interface DataQueryResponse {
|
||||||
data: TimeSeries[];
|
data: TimeSeries[] | [TableData];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataQuery {
|
export interface DataQuery {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export * from './series';
|
export * from './data';
|
||||||
export * from './time';
|
export * from './time';
|
||||||
export * from './panel';
|
export * from './panel';
|
||||||
export * from './plugin';
|
export * from './plugin';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { TimeSeries, LoadingState } from './series';
|
import { TimeSeries, LoadingState, TableData } from './data';
|
||||||
import { TimeRange } from './time';
|
import { TimeRange } from './time';
|
||||||
|
|
||||||
export type InterpolateFunction = (value: string, format?: string | Function) => string;
|
export type InterpolateFunction = (value: string, format?: string | Function) => string;
|
||||||
@ -14,6 +14,11 @@ export interface PanelProps<T = any> {
|
|||||||
onInterpolate: InterpolateFunction;
|
onInterpolate: InterpolateFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PanelData {
|
||||||
|
timeSeries?: TimeSeries[];
|
||||||
|
tableData?: TableData;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PanelOptionsProps<T = any> {
|
export interface PanelOptionsProps<T = any> {
|
||||||
options: T;
|
options: T;
|
||||||
onChange: (options: T) => void;
|
onChange: (options: T) => void;
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package notifiers
|
package notifiers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
@ -91,6 +94,7 @@ func NewPushoverNotifier(model *m.AlertNotification) (alerting.Notifier, error)
|
|||||||
retry, _ := strconv.Atoi(model.Settings.Get("retry").MustString())
|
retry, _ := strconv.Atoi(model.Settings.Get("retry").MustString())
|
||||||
expire, _ := strconv.Atoi(model.Settings.Get("expire").MustString())
|
expire, _ := strconv.Atoi(model.Settings.Get("expire").MustString())
|
||||||
sound := model.Settings.Get("sound").MustString()
|
sound := model.Settings.Get("sound").MustString()
|
||||||
|
uploadImage := model.Settings.Get("uploadImage").MustBool(true)
|
||||||
|
|
||||||
if userKey == "" {
|
if userKey == "" {
|
||||||
return nil, alerting.ValidationError{Reason: "User key not given"}
|
return nil, alerting.ValidationError{Reason: "User key not given"}
|
||||||
@ -107,6 +111,7 @@ func NewPushoverNotifier(model *m.AlertNotification) (alerting.Notifier, error)
|
|||||||
Expire: expire,
|
Expire: expire,
|
||||||
Device: device,
|
Device: device,
|
||||||
Sound: sound,
|
Sound: sound,
|
||||||
|
Upload: uploadImage,
|
||||||
log: log.New("alerting.notifier.pushover"),
|
log: log.New("alerting.notifier.pushover"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -120,6 +125,7 @@ type PushoverNotifier struct {
|
|||||||
Expire int
|
Expire int
|
||||||
Device string
|
Device string
|
||||||
Sound string
|
Sound string
|
||||||
|
Upload bool
|
||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,38 +146,22 @@ func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
|
|||||||
if evalContext.Error != nil {
|
if evalContext.Error != nil {
|
||||||
message += fmt.Sprintf("\n<b>Error message:</b> %s", evalContext.Error.Error())
|
message += fmt.Sprintf("\n<b>Error message:</b> %s", evalContext.Error.Error())
|
||||||
}
|
}
|
||||||
if evalContext.ImagePublicUrl != "" {
|
|
||||||
message += fmt.Sprintf("\n<a href=\"%s\">Show graph image</a>", evalContext.ImagePublicUrl)
|
|
||||||
}
|
|
||||||
if message == "" {
|
if message == "" {
|
||||||
message = "Notification message missing (Set a notification message to replace this text.)"
|
message = "Notification message missing (Set a notification message to replace this text.)"
|
||||||
}
|
}
|
||||||
|
|
||||||
q := url.Values{}
|
headers, uploadBody, err := this.genPushoverBody(evalContext, message, ruleUrl)
|
||||||
q.Add("user", this.UserKey)
|
if err != nil {
|
||||||
q.Add("token", this.ApiToken)
|
this.log.Error("Failed to generate body for pushover", "error", err)
|
||||||
q.Add("priority", strconv.Itoa(this.Priority))
|
return err
|
||||||
if this.Priority == 2 {
|
|
||||||
q.Add("retry", strconv.Itoa(this.Retry))
|
|
||||||
q.Add("expire", strconv.Itoa(this.Expire))
|
|
||||||
}
|
}
|
||||||
if this.Device != "" {
|
|
||||||
q.Add("device", this.Device)
|
|
||||||
}
|
|
||||||
if this.Sound != "default" {
|
|
||||||
q.Add("sound", this.Sound)
|
|
||||||
}
|
|
||||||
q.Add("title", evalContext.GetNotificationTitle())
|
|
||||||
q.Add("url", ruleUrl)
|
|
||||||
q.Add("url_title", "Show dashboard with alert")
|
|
||||||
q.Add("message", message)
|
|
||||||
q.Add("html", "1")
|
|
||||||
|
|
||||||
cmd := &m.SendWebhookSync{
|
cmd := &m.SendWebhookSync{
|
||||||
Url: PUSHOVER_ENDPOINT,
|
Url: PUSHOVER_ENDPOINT,
|
||||||
HttpMethod: "POST",
|
HttpMethod: "POST",
|
||||||
HttpHeader: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
|
HttpHeader: headers,
|
||||||
Body: q.Encode(),
|
Body: uploadBody.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
|
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
|
||||||
@ -181,3 +171,109 @@ func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext, message string, ruleUrl string) (map[string]string, bytes.Buffer, error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
var err error
|
||||||
|
w := multipart.NewWriter(&b)
|
||||||
|
|
||||||
|
// Add image only if requested and available
|
||||||
|
if this.Upload && evalContext.ImageOnDiskPath != "" {
|
||||||
|
f, err := os.Open(evalContext.ImageOnDiskPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
fw, err := w.CreateFormFile("attachment", evalContext.ImageOnDiskPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(fw, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the user token
|
||||||
|
err = w.WriteField("user", this.UserKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the api token
|
||||||
|
err = w.WriteField("token", this.ApiToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add priority
|
||||||
|
err = w.WriteField("priority", strconv.Itoa(this.Priority))
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.Priority == 2 {
|
||||||
|
err = w.WriteField("retry", strconv.Itoa(this.Retry))
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.WriteField("expire", strconv.Itoa(this.Expire))
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add device
|
||||||
|
if this.Device != "" {
|
||||||
|
err = w.WriteField("device", this.Device)
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sound
|
||||||
|
if this.Sound != "default" {
|
||||||
|
err = w.WriteField("sound", this.Sound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add title
|
||||||
|
err = w.WriteField("title", evalContext.GetNotificationTitle())
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add URL
|
||||||
|
err = w.WriteField("url", ruleUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
// Add URL title
|
||||||
|
err = w.WriteField("url_title", "Show dashboard with alert")
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add message
|
||||||
|
err = w.WriteField("message", message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as html message
|
||||||
|
err = w.WriteField("html", "1")
|
||||||
|
if err != nil {
|
||||||
|
return nil, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
headers := map[string]string{
|
||||||
|
"Content-Type": w.FormDataContentType(),
|
||||||
|
}
|
||||||
|
return headers, b, nil
|
||||||
|
}
|
||||||
|
@ -151,7 +151,7 @@ func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent
|
|||||||
func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*userAuthToken, error) {
|
func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*userAuthToken, error) {
|
||||||
hashedToken := hashToken(unhashedToken)
|
hashedToken := hashToken(unhashedToken)
|
||||||
if setting.Env == setting.DEV {
|
if setting.Env == setting.DEV {
|
||||||
s.log.Info("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
|
s.log.Debug("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
expireBefore := getTime().Add(time.Duration(-86400*s.Cfg.LoginCookieMaxDays) * time.Second).Unix()
|
expireBefore := getTime().Add(time.Duration(-86400*s.Cfg.LoginCookieMaxDays) * time.Second).Unix()
|
||||||
|
@ -92,7 +92,7 @@ func (dc *NotificationProvisioner) mergeNotifications(notificationToMerge []*not
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Result == nil {
|
if cmd.Result == nil {
|
||||||
dc.log.Info("Inserting alert notification from configuration ", "name", notification.Name, "uid", notification.Uid)
|
dc.log.Debug("inserting alert notification from configuration", "name", notification.Name, "uid", notification.Uid)
|
||||||
insertCmd := &models.CreateAlertNotificationCommand{
|
insertCmd := &models.CreateAlertNotificationCommand{
|
||||||
Uid: notification.Uid,
|
Uid: notification.Uid,
|
||||||
Name: notification.Name,
|
Name: notification.Name,
|
||||||
@ -109,7 +109,7 @@ func (dc *NotificationProvisioner) mergeNotifications(notificationToMerge []*not
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dc.log.Info("Updating alert notification from configuration", "name", notification.Name)
|
dc.log.Debug("updating alert notification from configuration", "name", notification.Name)
|
||||||
updateCmd := &models.UpdateAlertNotificationWithUidCommand{
|
updateCmd := &models.UpdateAlertNotificationWithUidCommand{
|
||||||
Uid: notification.Uid,
|
Uid: notification.Uid,
|
||||||
Name: notification.Name,
|
Name: notification.Name,
|
||||||
|
@ -24,7 +24,7 @@ export class RowOptionsCtrl {
|
|||||||
export function rowOptionsDirective() {
|
export function rowOptionsDirective() {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
templateUrl: 'public/app/features/dashboard/partials/row_options.html',
|
templateUrl: 'public/app/features/dashboard/components/RowOptions/template.html',
|
||||||
controller: RowOptionsCtrl,
|
controller: RowOptionsCtrl,
|
||||||
bindToController: true,
|
bindToController: true,
|
||||||
controllerAs: 'ctrl',
|
controllerAs: 'ctrl',
|
||||||
|
@ -8,13 +8,21 @@ import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource
|
|||||||
// Utils
|
// Utils
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
// Types
|
// Types
|
||||||
import { DataQueryOptions, DataQueryResponse, LoadingState, TimeRange, TimeSeries } from '@grafana/ui/src/types';
|
import {
|
||||||
|
DataQueryOptions,
|
||||||
|
DataQueryResponse,
|
||||||
|
LoadingState,
|
||||||
|
PanelData,
|
||||||
|
TableData,
|
||||||
|
TimeRange,
|
||||||
|
TimeSeries,
|
||||||
|
} from '@grafana/ui';
|
||||||
|
|
||||||
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
||||||
|
|
||||||
interface RenderProps {
|
interface RenderProps {
|
||||||
loading: LoadingState;
|
loading: LoadingState;
|
||||||
timeSeries: TimeSeries[];
|
panelData: PanelData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -129,6 +137,7 @@ export class DataPanel extends Component<Props, State> {
|
|||||||
|
|
||||||
console.log('Issuing DataPanel query', queryOptions);
|
console.log('Issuing DataPanel query', queryOptions);
|
||||||
const resp = await ds.query(queryOptions);
|
const resp = await ds.query(queryOptions);
|
||||||
|
|
||||||
console.log('Issuing DataPanel query Resp', resp);
|
console.log('Issuing DataPanel query Resp', resp);
|
||||||
|
|
||||||
if (this.isUnmounted) {
|
if (this.isUnmounted) {
|
||||||
@ -160,11 +169,27 @@ export class DataPanel extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getPanelData = () => {
|
||||||
|
const { response } = this.state;
|
||||||
|
|
||||||
|
if (response.data.length > 0 && (response.data[0] as TableData).type === 'table') {
|
||||||
|
return {
|
||||||
|
tableData: response.data[0] as TableData,
|
||||||
|
timeSeries: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
timeSeries: response.data as TimeSeries[],
|
||||||
|
tableData: null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { queries } = this.props;
|
const { queries } = this.props;
|
||||||
const { response, loading, isFirstLoad } = this.state;
|
const { loading, isFirstLoad } = this.state;
|
||||||
|
|
||||||
const timeSeries = response.data;
|
const panelData = this.getPanelData();
|
||||||
|
|
||||||
if (isFirstLoad && loading === LoadingState.Loading) {
|
if (isFirstLoad && loading === LoadingState.Loading) {
|
||||||
return this.renderLoadingStates();
|
return this.renderLoadingStates();
|
||||||
@ -190,8 +215,8 @@ export class DataPanel extends Component<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.props.children({
|
{this.props.children({
|
||||||
timeSeries,
|
|
||||||
loading,
|
loading,
|
||||||
|
panelData,
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -14,8 +14,7 @@ import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
|
|||||||
import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
|
import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { PanelModel } from '../state/PanelModel';
|
import { DashboardModel, PanelModel } from '../state';
|
||||||
import { DashboardModel } from '../state/DashboardModel';
|
|
||||||
import { PanelPlugin } from 'app/types';
|
import { PanelPlugin } from 'app/types';
|
||||||
import { TimeRange } from '@grafana/ui';
|
import { TimeRange } from '@grafana/ui';
|
||||||
|
|
||||||
@ -139,7 +138,6 @@ export class PanelChrome extends PureComponent<Props, State> {
|
|||||||
scopedVars={panel.scopedVars}
|
scopedVars={panel.scopedVars}
|
||||||
links={panel.links}
|
links={panel.links}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{panel.snapshotData ? (
|
{panel.snapshotData ? (
|
||||||
this.renderPanel(false, panel.snapshotData, width, height)
|
this.renderPanel(false, panel.snapshotData, width, height)
|
||||||
) : (
|
) : (
|
||||||
@ -152,8 +150,8 @@ export class PanelChrome extends PureComponent<Props, State> {
|
|||||||
refreshCounter={refreshCounter}
|
refreshCounter={refreshCounter}
|
||||||
onDataResponse={this.onDataResponse}
|
onDataResponse={this.onDataResponse}
|
||||||
>
|
>
|
||||||
{({ loading, timeSeries }) => {
|
{({ loading, panelData }) => {
|
||||||
return this.renderPanel(loading, timeSeries, width, height);
|
return this.renderPanel(loading, panelData.timeSeries, width, height);
|
||||||
}}
|
}}
|
||||||
</DataPanel>
|
</DataPanel>
|
||||||
)}
|
)}
|
||||||
|
@ -101,17 +101,6 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="panel-editor-container__editor">
|
<div className="panel-editor-container__editor">
|
||||||
{
|
|
||||||
// <div className="panel-editor__close">
|
|
||||||
// <i className="fa fa-arrow-left" />
|
|
||||||
// </div>
|
|
||||||
// <div className="panel-editor-resizer">
|
|
||||||
// <div className="panel-editor-resizer__handle">
|
|
||||||
// <div className="panel-editor-resizer__handle-dots" />
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className="panel-editor-tabs">
|
<div className="panel-editor-tabs">
|
||||||
{tabs.map(tab => {
|
{tabs.map(tab => {
|
||||||
return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
|
return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
|
||||||
|
@ -5,6 +5,7 @@ import _ from 'lodash';
|
|||||||
import { Emitter } from 'app/core/utils/emitter';
|
import { Emitter } from 'app/core/utils/emitter';
|
||||||
import { PANEL_OPTIONS_KEY_PREFIX } from 'app/core/constants';
|
import { PANEL_OPTIONS_KEY_PREFIX } from 'app/core/constants';
|
||||||
import { DataQuery, TimeSeries } from '@grafana/ui';
|
import { DataQuery, TimeSeries } from '@grafana/ui';
|
||||||
|
import { TableData } from '@grafana/ui/src';
|
||||||
|
|
||||||
export interface GridPos {
|
export interface GridPos {
|
||||||
x: number;
|
x: number;
|
||||||
@ -87,7 +88,7 @@ export class PanelModel {
|
|||||||
datasource: string;
|
datasource: string;
|
||||||
thresholds?: any;
|
thresholds?: any;
|
||||||
|
|
||||||
snapshotData?: TimeSeries[];
|
snapshotData?: TimeSeries[] | [TableData];
|
||||||
timeFrom?: any;
|
timeFrom?: any;
|
||||||
timeShift?: any;
|
timeShift?: any;
|
||||||
hideTimeOverride?: any;
|
hideTimeOverride?: any;
|
||||||
|
@ -22,6 +22,7 @@ const newVariable = index => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class ElasticPipelineVariablesCtrl {
|
export class ElasticPipelineVariablesCtrl {
|
||||||
|
/** @ngInject */
|
||||||
constructor($scope) {
|
constructor($scope) {
|
||||||
$scope.variables = $scope.variables || [newVariable(1)];
|
$scope.variables = $scope.variables || [newVariable(1)];
|
||||||
|
|
||||||
|
@ -43,6 +43,25 @@ describe('TimeRegionManager', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('When colors missing in config', () => {
|
||||||
|
plotOptionsScenario('should not throw an error when fillColor is undefined', ctx => {
|
||||||
|
const regions = [
|
||||||
|
{ fromDayOfWeek: 1, toDayOfWeek: 1, fill: true, line: true, lineColor: '#ffffff', colorMode: 'custom' },
|
||||||
|
];
|
||||||
|
const from = moment('2018-01-01T00:00:00+01:00');
|
||||||
|
const to = moment('2018-01-01T23:59:00+01:00');
|
||||||
|
expect(() => ctx.setup(regions, from, to)).not.toThrow();
|
||||||
|
});
|
||||||
|
plotOptionsScenario('should not throw an error when lineColor is undefined', ctx => {
|
||||||
|
const regions = [
|
||||||
|
{ fromDayOfWeek: 1, toDayOfWeek: 1, fill: true, fillColor: '#ffffff', line: true, colorMode: 'custom' },
|
||||||
|
];
|
||||||
|
const from = moment('2018-01-01T00:00:00+01:00');
|
||||||
|
const to = moment('2018-01-01T23:59:00+01:00');
|
||||||
|
expect(() => ctx.setup(regions, from, to)).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('When creating plot markings using local time', () => {
|
describe('When creating plot markings using local time', () => {
|
||||||
plotOptionsScenario('for day of week region', ctx => {
|
plotOptionsScenario('for day of week region', ctx => {
|
||||||
const regions = [{ fromDayOfWeek: 1, toDayOfWeek: 1, fill: true, line: true, colorMode: 'red' }];
|
const regions = [{ fromDayOfWeek: 1, toDayOfWeek: 1, fill: true, line: true, colorMode: 'red' }];
|
||||||
|
@ -50,8 +50,8 @@ function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition {
|
|||||||
|
|
||||||
if (timeRegion.colorMode === 'custom') {
|
if (timeRegion.colorMode === 'custom') {
|
||||||
return {
|
return {
|
||||||
fill: getColorFromHexRgbOrName(timeRegion.fillColor, theme),
|
fill: timeRegion.fill && timeRegion.fillColor ? getColorFromHexRgbOrName(timeRegion.fillColor, theme) : null,
|
||||||
line: getColorFromHexRgbOrName(timeRegion.lineColor, theme),
|
line: timeRegion.line && timeRegion.lineColor ? getColorFromHexRgbOrName(timeRegion.lineColor, theme) : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +62,8 @@ function getColor(timeRegion, theme: GrafanaTheme): TimeRegionColorDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fill: getColorFromHexRgbOrName(colorMode.color.fill, theme),
|
fill: timeRegion.fill ? getColorFromHexRgbOrName(colorMode.color.fill, theme) : null,
|
||||||
line: getColorFromHexRgbOrName(colorMode.color.line, theme),
|
line: timeRegion.fill ? getColorFromHexRgbOrName(colorMode.color.line, theme) : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user