mirror of
https://github.com/grafana/grafana.git
synced 2025-12-20 16:54:59 +08:00
Compare commits
47 Commits
plugin-dep
...
v5.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d812109ebf | ||
|
|
f44a006cb9 | ||
|
|
43c0405ae1 | ||
|
|
ed05e9de72 | ||
|
|
e43f13bc03 | ||
|
|
308c818cd7 | ||
|
|
d49d8bf13d | ||
|
|
3701f22d66 | ||
|
|
4c60ef398a | ||
|
|
ec98666de1 | ||
|
|
7fe10e2eef | ||
|
|
4a8cd4c023 | ||
|
|
174be1abab | ||
|
|
4c13e02aef | ||
|
|
4a8a3d40e7 | ||
|
|
01c4b71cfb | ||
|
|
e7cd39a543 | ||
|
|
6f241a4bac | ||
|
|
0a19581c48 | ||
|
|
96cb4df83a | ||
|
|
7820775a53 | ||
|
|
9699133501 | ||
|
|
80ecd8ea8e | ||
|
|
2ab2259091 | ||
|
|
deb305b95f | ||
|
|
d42c17efad | ||
|
|
972aaef2a6 | ||
|
|
ce3982d406 | ||
|
|
69c5191926 | ||
|
|
99ee3bbe5a | ||
|
|
01840cbd70 | ||
|
|
23b19543bd | ||
|
|
bb4e5934fb | ||
|
|
fd3821d2f1 | ||
|
|
8b1d0b14b6 | ||
|
|
23c6bea21b | ||
|
|
e3abefa19f | ||
|
|
4ee92bd59c | ||
|
|
780e5153d0 | ||
|
|
be9058d7ef | ||
|
|
3301f96811 | ||
|
|
1c59669da0 | ||
|
|
1ad60be47b | ||
|
|
9ec0af73ec | ||
|
|
8190d10827 | ||
|
|
18b5f630f7 | ||
|
|
9df26af3db |
@@ -162,8 +162,8 @@ jobs:
|
||||
name: Build Grafana.com master publisher
|
||||
command: 'go build -o scripts/publish scripts/build/publish.go'
|
||||
- run:
|
||||
name: Build Grafana.com release publisher
|
||||
command: 'cd scripts/build/release_publisher && go build -o release_publisher .'
|
||||
name: Test and build Grafana.com release publisher
|
||||
command: 'cd scripts/build/release_publisher && go test . && go build -o release_publisher .'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
@@ -191,6 +191,9 @@ jobs:
|
||||
- run:
|
||||
name: sha-sum packages
|
||||
command: 'go run build.go sha-dist'
|
||||
- run:
|
||||
name: Test Grafana.com release publisher
|
||||
command: 'cd scripts/build/release_publisher && go test .'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
@@ -510,6 +513,7 @@ workflows:
|
||||
- grafana-docker-release:
|
||||
requires:
|
||||
- build-all
|
||||
- build-all-enterprise
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
|
||||
@@ -50,7 +50,8 @@ ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bi
|
||||
|
||||
WORKDIR $GF_PATHS_HOME
|
||||
|
||||
RUN apt-get update && apt-get install -qq -y libfontconfig ca-certificates && \
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -qq -y libfontconfig ca-certificates && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -246,6 +246,10 @@ disable_signout_menu = false
|
||||
# URL to redirect the user to after sign out
|
||||
signout_redirect_url =
|
||||
|
||||
# Set to true to attempt login with OAuth automatically, skipping the login screen.
|
||||
# This setting is ignored if multiple OAuth providers are configured.
|
||||
oauth_auto_login = false
|
||||
|
||||
#################################### Anonymous Auth ######################
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
|
||||
@@ -223,6 +223,10 @@ log_queries =
|
||||
# URL to redirect the user to after sign out
|
||||
;signout_redirect_url =
|
||||
|
||||
# Set to true to attempt login with OAuth automatically, skipping the login screen.
|
||||
# This setting is ignored if multiple OAuth providers are configured.
|
||||
;oauth_auto_login = false
|
||||
|
||||
#################################### Anonymous Auth ##########################
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
|
||||
@@ -133,9 +133,9 @@ Macro example | Description
|
||||
------------ | -------------
|
||||
*$__time(dateColumn)* | Will be replaced by an expression to convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec*
|
||||
*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec*
|
||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:06:17Z'*
|
||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-04-21T05:01:17Z'*
|
||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-04-21T05:06:17Z'*
|
||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn BETWEEN FROM_UNIXTIME(1494410783) AND FROM_UNIXTIME(1494410983)*
|
||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *FROM_UNIXTIME(1494410783)*
|
||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *FROM_UNIXTIME(1494410983)*
|
||||
*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),*
|
||||
*$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value.
|
||||
*$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"company": "Grafana Labs"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "5.4.0-pre1",
|
||||
"version": "5.4.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/grafana/grafana.git"
|
||||
|
||||
@@ -25,7 +25,8 @@ ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bi
|
||||
|
||||
WORKDIR $GF_PATHS_HOME
|
||||
|
||||
RUN apt-get update && apt-get install -qq -y libfontconfig ca-certificates curl && \
|
||||
RUN apt-get update && apt-get -y upgrade && \
|
||||
apt-get install -qq -y libfontconfig ca-certificates curl && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
_grafana_tag=$1
|
||||
_raw_grafana_tag=$1
|
||||
_docker_repo=${2:-grafana/grafana-enterprise}
|
||||
|
||||
if echo "$_raw_grafana_tag" | grep -q "^v"; then
|
||||
_grafana_tag=$(echo "${_raw_grafana_tag}" | cut -d "v" -f 2)
|
||||
else
|
||||
_grafana_tag="${_raw_grafana_tag}"
|
||||
fi
|
||||
|
||||
echo "Building and deploying ${_docker_repo}:${_grafana_tag}"
|
||||
|
||||
docker build \
|
||||
--tag "${_docker_repo}:${_grafana_tag}"\
|
||||
--no-cache=true \
|
||||
.
|
||||
|
||||
docker push "${_docker_repo}:${_grafana_tag}"
|
||||
|
||||
if echo "$_raw_grafana_tag" | grep -q "^v" && echo "$_raw_grafana_tag" | grep -qv "beta"; then
|
||||
docker tag "${_docker_repo}:${_grafana_tag}" "${_docker_repo}:latest"
|
||||
docker push "${_docker_repo}:latest"
|
||||
fi
|
||||
|
||||
@@ -277,10 +277,6 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
|
||||
return Error(500, "Failed to save dashboard", err)
|
||||
}
|
||||
|
||||
if err == m.ErrDashboardFailedToUpdateAlertData {
|
||||
return Error(500, "Invalid alert data. Cannot save dashboard", err)
|
||||
}
|
||||
|
||||
c.TimeRequest(metrics.M_Api_Dashboard_Save)
|
||||
return JSON(200, util.DynMap{
|
||||
"status": "success",
|
||||
|
||||
@@ -727,7 +727,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
||||
{SaveError: m.ErrDashboardTitleEmpty, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400},
|
||||
{SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422},
|
||||
{SaveError: m.ErrDashboardFailedToUpdateAlertData, ExpectedStatusCode: 500},
|
||||
{SaveError: m.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500},
|
||||
{SaveError: m.ErrDashboardTypeMismatch, ExpectedStatusCode: 400},
|
||||
{SaveError: m.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400},
|
||||
|
||||
@@ -51,7 +51,7 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
|
||||
if token, err := tokenProvider.getAccessToken(data); err != nil {
|
||||
logger.Error("Failed to get access token", "error", err)
|
||||
} else {
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
|
||||
if token, err := tokenProvider.getJwtAccessToken(ctx, data); err != nil {
|
||||
logger.Error("Failed to get access token", "error", err)
|
||||
} else {
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
|
||||
if err != nil {
|
||||
logger.Error("Failed to get default access token from meta data server", "error", err)
|
||||
} else {
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
|
||||
}
|
||||
|
||||
for key, value := range headers {
|
||||
log.Trace("setting key %v value %v", key, value[0])
|
||||
log.Trace("setting key %v value <redacted>", key)
|
||||
req.Header.Set(key, value[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ var (
|
||||
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
|
||||
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
|
||||
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
|
||||
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
|
||||
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
|
||||
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
|
||||
ErrDashboardTypeMismatch = errors.New("Dashboard cannot be changed to a folder")
|
||||
|
||||
@@ -165,7 +165,7 @@ func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&alertCmd); err != nil {
|
||||
return models.ErrDashboardFailedToUpdateAlertData
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -99,14 +99,14 @@ func UpdateOrgQuota(cmd *m.UpdateOrgQuotaCmd) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
//Check if quota is already defined in the DB
|
||||
quota := m.Quota{
|
||||
Target: cmd.Target,
|
||||
OrgId: cmd.OrgId,
|
||||
Updated: time.Now(),
|
||||
Target: cmd.Target,
|
||||
OrgId: cmd.OrgId,
|
||||
}
|
||||
has, err := sess.Get("a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
quota.Updated = time.Now()
|
||||
quota.Limit = cmd.Limit
|
||||
if !has {
|
||||
quota.Created = time.Now()
|
||||
@@ -201,14 +201,14 @@ func UpdateUserQuota(cmd *m.UpdateUserQuotaCmd) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
//Check if quota is already defined in the DB
|
||||
quota := m.Quota{
|
||||
Target: cmd.Target,
|
||||
UserId: cmd.UserId,
|
||||
Updated: time.Now(),
|
||||
Target: cmd.Target,
|
||||
UserId: cmd.UserId,
|
||||
}
|
||||
has, err := sess.Get("a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
quota.Updated = time.Now()
|
||||
quota.Limit = cmd.Limit
|
||||
if !has {
|
||||
quota.Created = time.Now()
|
||||
|
||||
@@ -2,6 +2,7 @@ package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -168,5 +169,69 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
|
||||
So(query.Result.Limit, ShouldEqual, 5)
|
||||
So(query.Result.Used, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
// related: https://github.com/grafana/grafana/issues/14342
|
||||
Convey("Should org quota updating is successful even if it called multiple time", func() {
|
||||
orgCmd := m.UpdateOrgQuotaCmd{
|
||||
OrgId: orgId,
|
||||
Target: "org_user",
|
||||
Limit: 5,
|
||||
}
|
||||
err := UpdateOrgQuota(&orgCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := m.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1}
|
||||
err = GetOrgQuotaByTarget(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Limit, ShouldEqual, 5)
|
||||
|
||||
// XXX: resolution of `Updated` column is 1sec, so this makes delay
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
orgCmd = m.UpdateOrgQuotaCmd{
|
||||
OrgId: orgId,
|
||||
Target: "org_user",
|
||||
Limit: 10,
|
||||
}
|
||||
err = UpdateOrgQuota(&orgCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query = m.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1}
|
||||
err = GetOrgQuotaByTarget(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Limit, ShouldEqual, 10)
|
||||
})
|
||||
|
||||
// related: https://github.com/grafana/grafana/issues/14342
|
||||
Convey("Should user quota updating is successful even if it called multiple time", func() {
|
||||
userQuotaCmd := m.UpdateUserQuotaCmd{
|
||||
UserId: userId,
|
||||
Target: "org_user",
|
||||
Limit: 5,
|
||||
}
|
||||
err := UpdateUserQuota(&userQuotaCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := m.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1}
|
||||
err = GetUserQuotaByTarget(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Limit, ShouldEqual, 5)
|
||||
|
||||
// XXX: resolution of `Updated` column is 1sec, so this makes delay
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
userQuotaCmd = m.UpdateUserQuotaCmd{
|
||||
UserId: userId,
|
||||
Target: "org_user",
|
||||
Limit: 10,
|
||||
}
|
||||
err = UpdateUserQuota(&userQuotaCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query = m.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1}
|
||||
err = GetUserQuotaByTarget(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Limit, ShouldEqual, 10)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -126,6 +126,18 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
plog.Error("Execute Query Panic", "error", err, "stack", log.Stack(1))
|
||||
if theErr, ok := err.(error); ok {
|
||||
resultChan <- &tsdb.QueryResult{
|
||||
RefId: query.RefId,
|
||||
Error: theErr,
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
queryRes, err := e.executeQuery(ectx, query, queryContext)
|
||||
if ae, ok := err.(awserr.Error); ok && ae.Code() == "500" {
|
||||
return err
|
||||
@@ -146,6 +158,17 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
|
||||
for region, getMetricDataQuery := range getMetricDataQueries {
|
||||
q := getMetricDataQuery
|
||||
eg.Go(func() error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
plog.Error("Execute Get Metric Data Query Panic", "error", err, "stack", log.Stack(1))
|
||||
if theErr, ok := err.(error); ok {
|
||||
resultChan <- &tsdb.QueryResult{
|
||||
Error: theErr,
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
queryResponses, err := e.executeGetMetricDataQuery(ectx, region, q, queryContext)
|
||||
if ae, ok := err.(awserr.Error); ok && ae.Code() == "500" {
|
||||
return err
|
||||
@@ -188,8 +211,8 @@ func (e *CloudWatchExecutor) executeQuery(ctx context.Context, query *CloudWatch
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if endTime.Before(startTime) {
|
||||
return nil, fmt.Errorf("Invalid time range: End time can't be before start time")
|
||||
if !startTime.Before(endTime) {
|
||||
return nil, fmt.Errorf("Invalid time range: Start time must be before end time")
|
||||
}
|
||||
|
||||
params := &cloudwatch.GetMetricStatisticsInput{
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package cloudwatch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
@@ -14,6 +18,24 @@ import (
|
||||
func TestCloudWatch(t *testing.T) {
|
||||
Convey("CloudWatch", t, func() {
|
||||
|
||||
Convey("executeQuery", func() {
|
||||
e := &CloudWatchExecutor{
|
||||
DataSource: &models.DataSource{
|
||||
JsonData: simplejson.New(),
|
||||
},
|
||||
}
|
||||
|
||||
Convey("End time before start time should result in error", func() {
|
||||
_, err := e.executeQuery(context.Background(), &CloudWatchQuery{}, &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("now-1h", "now-2h")})
|
||||
So(err.Error(), ShouldEqual, "Invalid time range: Start time must be before end time")
|
||||
})
|
||||
|
||||
Convey("End time equals start time should result in error", func() {
|
||||
_, err := e.executeQuery(context.Background(), &CloudWatchQuery{}, &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("now-1h", "now-1h")})
|
||||
So(err.Error(), ShouldEqual, "Invalid time range: Start time must be before end time")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("can parse cloudwatch json model", func() {
|
||||
json := `
|
||||
{
|
||||
|
||||
@@ -47,6 +47,7 @@ func init() {
|
||||
"AWS/CloudFront": {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"},
|
||||
"AWS/CloudSearch": {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"},
|
||||
"AWS/CloudHSM": {"HsmUnhealthy", "HsmTemperature", "HsmKeysSessionOccupied", "HsmKeysTokenOccupied", "HsmSslCtxsOccupied", "HsmSessionCount", "HsmUsersAvailable", "HsmUsersMax", "InterfaceEth2OctetsInput", "InterfaceEth2OctetsOutput"},
|
||||
"AWS/CodeBuild": {"BuildDuration", "Builds", "DownloadSourceDuration", "Duration", "FailedBuilds", "FinalizingDuration", "InstallDuration", "PostBuildDuration", "PreBuildDuration", "ProvisioningDuration", "QueuedDuration", "SubmittedDuration", "SucceededBuilds", "UploadArtifactsDuration"},
|
||||
"AWS/Connect": {"CallsBreachingConcurrencyQuota", "CallBackNotDialableNumber", "CallRecordingUploadError", "CallsPerInterval", "ConcurrentCalls", "ConcurrentCallsPercentage", "ContactFlowErrors", "ContactFlowFatalErrors", "LongestQueueWaitTime", "MissedCalls", "MisconfiguredPhoneNumbers", "PublicSigningKeyUsage", "QueueCapacityExceededError", "QueueSize", "ThrottledCalls", "ToInstancePacketLossRate"},
|
||||
"AWS/DMS": {"FreeableMemory", "WriteIOPS", "ReadIOPS", "WriteThroughput", "ReadThroughput", "WriteLatency", "ReadLatency", "SwapUsage", "NetworkTransmitThroughput", "NetworkReceiveThroughput", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "CDCIncomingChanges", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CDCLatencySource", "CDCLatencyTarget"},
|
||||
"AWS/DX": {"ConnectionState", "ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelTx", "ConnectionLightLevelRx"},
|
||||
@@ -100,7 +101,7 @@ func init() {
|
||||
"AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "BurstBalance", "CommitLatency", "CommitThroughput", "BinLogDiskUsage", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "DatabaseConnections", "DDLLatency", "DDLThroughput", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "DMLLatency", "DMLThroughput", "EngineUptime", "FailedSqlStatements", "FreeableMemory", "FreeLocalStorage", "FreeStorageSpace", "InsertLatency", "InsertThroughput", "LoginFailures", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "NetworkThroughput", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "SwapUsage", "TotalConnections", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||
"AWS/Route53": {"ChildHealthCheckHealthyCount", "HealthCheckStatus", "HealthCheckPercentageHealthy", "ConnectionTime", "SSLHandshakeTime", "TimeToFirstByte"},
|
||||
"AWS/S3": {"BucketSizeBytes", "NumberOfObjects", "AllRequests", "GetRequests", "PutRequests", "DeleteRequests", "HeadRequests", "PostRequests", "ListRequests", "BytesDownloaded", "BytesUploaded", "4xxErrors", "5xxErrors", "FirstByteLatency", "TotalRequestLatency"},
|
||||
"AWS/SES": {"Bounce", "Complaint", "Delivery", "Reject", "Send"},
|
||||
"AWS/SES": {"Bounce", "Complaint", "Delivery", "Reject", "Send", "Reputation.BounceRate", "Reputation.ComplaintRate"},
|
||||
"AWS/SNS": {"NumberOfMessagesPublished", "PublishSize", "NumberOfNotificationsDelivered", "NumberOfNotificationsFailed"},
|
||||
"AWS/SQS": {"NumberOfMessagesSent", "SentMessageSize", "NumberOfMessagesReceived", "NumberOfEmptyReceives", "NumberOfMessagesDeleted", "ApproximateAgeOfOldestMessage", "ApproximateNumberOfMessagesDelayed", "ApproximateNumberOfMessagesVisible", "ApproximateNumberOfMessagesNotVisible"},
|
||||
"AWS/States": {"ExecutionTime", "ExecutionThrottled", "ExecutionsAborted", "ExecutionsFailed", "ExecutionsStarted", "ExecutionsSucceeded", "ExecutionsTimedOut", "ActivityRunTime", "ActivityScheduleTime", "ActivityTime", "ActivitiesFailed", "ActivitiesHeartbeatTimedOut", "ActivitiesScheduled", "ActivitiesScheduled", "ActivitiesSucceeded", "ActivitiesTimedOut", "LambdaFunctionRunTime", "LambdaFunctionScheduleTime", "LambdaFunctionTime", "LambdaFunctionsFailed", "LambdaFunctionsHeartbeatTimedOut", "LambdaFunctionsScheduled", "LambdaFunctionsStarted", "LambdaFunctionsSucceeded", "LambdaFunctionsTimedOut"},
|
||||
@@ -123,6 +124,7 @@ func init() {
|
||||
"AWS/CloudFront": {"DistributionId", "Region"},
|
||||
"AWS/CloudSearch": {},
|
||||
"AWS/CloudHSM": {"Region", "ClusterId", "HsmId"},
|
||||
"AWS/CodeBuild": {"ProjectName"},
|
||||
"AWS/Connect": {"InstanceId", "MetricGroup", "Participant", "QueueName", "Stream Type", "Type of Connection"},
|
||||
"AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"},
|
||||
"AWS/DX": {"ConnectionId"},
|
||||
|
||||
@@ -65,7 +65,7 @@ var NewClient = func(ctx context.Context, ds *models.DataSource, timeRange *tsdb
|
||||
clientLog.Debug("Creating new client", "version", version, "timeField", timeField, "indices", strings.Join(indices, ", "))
|
||||
|
||||
switch version {
|
||||
case 2, 5, 56:
|
||||
case 2, 5, 56, 60:
|
||||
return &baseClientImpl{
|
||||
ctx: ctx,
|
||||
ds: ds,
|
||||
|
||||
@@ -90,6 +90,19 @@ func TestClient(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(c.GetVersion(), ShouldEqual, 56)
|
||||
})
|
||||
|
||||
Convey("When version 60 should return v6.0 client", func() {
|
||||
ds := &models.DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||
"esVersion": 60,
|
||||
"timeField": "@timestamp",
|
||||
}),
|
||||
}
|
||||
|
||||
c, err := NewClient(context.Background(), ds, nil)
|
||||
So(err, ShouldBeNil)
|
||||
So(c.GetVersion(), ShouldEqual, 60)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a fake http client", func() {
|
||||
@@ -153,8 +166,6 @@ func TestClient(t *testing.T) {
|
||||
jBody, err := simplejson.NewJson(bodyBytes)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
fmt.Println("body", string(headerBytes))
|
||||
|
||||
So(jHeader.Get("index").MustString(), ShouldEqual, "metrics-2018.05.15")
|
||||
So(jHeader.Get("ignore_unavailable").MustBool(false), ShouldEqual, true)
|
||||
So(jHeader.Get("search_type").MustString(), ShouldEqual, "count")
|
||||
@@ -209,8 +220,6 @@ func TestClient(t *testing.T) {
|
||||
jBody, err := simplejson.NewJson(bodyBytes)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
fmt.Println("body", string(headerBytes))
|
||||
|
||||
So(jHeader.Get("index").MustString(), ShouldEqual, "metrics-2018.05.15")
|
||||
So(jHeader.Get("ignore_unavailable").MustBool(false), ShouldEqual, true)
|
||||
So(jHeader.Get("search_type").MustString(), ShouldEqual, "query_then_fetch")
|
||||
@@ -265,8 +274,6 @@ func TestClient(t *testing.T) {
|
||||
jBody, err := simplejson.NewJson(bodyBytes)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
fmt.Println("body", string(headerBytes))
|
||||
|
||||
So(jHeader.Get("index").MustString(), ShouldEqual, "metrics-2018.05.15")
|
||||
So(jHeader.Get("ignore_unavailable").MustBool(false), ShouldEqual, true)
|
||||
So(jHeader.Get("search_type").MustString(), ShouldEqual, "query_then_fetch")
|
||||
|
||||
@@ -32,6 +32,7 @@ func init() {
|
||||
renders["median"] = QueryDefinition{Renderer: functionRenderer}
|
||||
renders["sum"] = QueryDefinition{Renderer: functionRenderer}
|
||||
renders["mode"] = QueryDefinition{Renderer: functionRenderer}
|
||||
renders["cumulative_sum"] = QueryDefinition{Renderer: functionRenderer}
|
||||
|
||||
renders["holt_winters"] = QueryDefinition{
|
||||
Renderer: functionRenderer,
|
||||
|
||||
@@ -23,6 +23,7 @@ func TestInfluxdbQueryPart(t *testing.T) {
|
||||
{mode: "alias", params: []string{"test"}, input: "mean(value)", expected: `mean(value) AS "test"`},
|
||||
{mode: "count", params: []string{}, input: "distinct(value)", expected: `count(distinct(value))`},
|
||||
{mode: "mode", params: []string{}, input: "value", expected: `mode(value)`},
|
||||
{mode: "cumulative_sum", params: []string{}, input: "mean(value)", expected: `cumulative_sum(mean(value))`},
|
||||
}
|
||||
|
||||
queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("5m", "now")}
|
||||
|
||||
@@ -66,6 +66,10 @@ func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, er
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeFrom":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeTo":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval", name)
|
||||
|
||||
@@ -52,6 +52,20 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, "select '2018-04-12T18:00:00Z'")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, "select '2018-04-12T18:05:00Z'")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeGroup function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -61,6 +61,10 @@ func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, er
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s BETWEEN FROM_UNIXTIME(%d) AND FROM_UNIXTIME(%d)", args[0], m.timeRange.GetFromAsSecondsEpoch(), m.timeRange.GetToAsSecondsEpoch()), nil
|
||||
case "__timeFrom":
|
||||
return fmt.Sprintf("FROM_UNIXTIME(%d)", m.timeRange.GetFromAsSecondsEpoch()), nil
|
||||
case "__timeTo":
|
||||
return fmt.Sprintf("FROM_UNIXTIME(%d)", m.timeRange.GetToAsSecondsEpoch()), nil
|
||||
case "__timeGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval", name)
|
||||
|
||||
@@ -63,6 +63,20 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN FROM_UNIXTIME(%d) AND FROM_UNIXTIME(%d)", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select FROM_UNIXTIME(%d)", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select FROM_UNIXTIME(%d)", to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -761,7 +761,7 @@ func TestMySQL(t *testing.T) {
|
||||
{
|
||||
DataSource: &models.DataSource{JsonData: simplejson.New()},
|
||||
Model: simplejson.NewFromAny(map[string]interface{}{
|
||||
"rawSql": `SELECT time FROM metric_values WHERE time > $__timeFrom() OR time < $__timeFrom() OR 1 < $__unixEpochFrom() OR $__unixEpochTo() > 1 ORDER BY 1`,
|
||||
"rawSql": `SELECT time FROM metric_values WHERE time > $__timeFrom() OR time < $__timeTo() OR 1 < $__unixEpochFrom() OR $__unixEpochTo() > 1 ORDER BY 1`,
|
||||
"format": "time_series",
|
||||
}),
|
||||
RefId: "A",
|
||||
@@ -773,7 +773,7 @@ func TestMySQL(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
queryResult := resp.Results["A"]
|
||||
So(queryResult.Error, ShouldBeNil)
|
||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > '2018-03-15T12:55:00Z' OR time < '2018-03-15T12:55:00Z' OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > FROM_UNIXTIME(1521118500) OR time < FROM_UNIXTIME(1521118800) OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
||||
|
||||
})
|
||||
|
||||
|
||||
@@ -87,6 +87,10 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string,
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeFrom":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeTo":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
|
||||
|
||||
@@ -44,6 +44,20 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, "select '2018-04-12T18:00:00Z'")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, "select '2018-04-12T18:05:00Z'")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeGroup function pre 5.3 compatibility", func() {
|
||||
|
||||
sql, err := engine.Interpolate(query, timeRange, "SELECT $__timeGroup(time_column,'5m'), value")
|
||||
|
||||
@@ -196,8 +196,6 @@ var Interpolate = func(query *Query, timeRange *TimeRange, sql string) (string,
|
||||
|
||||
sql = strings.Replace(sql, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10), -1)
|
||||
sql = strings.Replace(sql, "$__interval", interval.Text, -1)
|
||||
sql = strings.Replace(sql, "$__timeFrom()", fmt.Sprintf("'%s'", timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), -1)
|
||||
sql = strings.Replace(sql, "$__timeTo()", fmt.Sprintf("'%s'", timeRange.GetToAsTimeUTC().Format(time.RFC3339)), -1)
|
||||
sql = strings.Replace(sql, "$__unixEpochFrom()", fmt.Sprintf("%d", timeRange.GetFromAsSecondsEpoch()), -1)
|
||||
sql = strings.Replace(sql, "$__unixEpochTo()", fmt.Sprintf("%d", timeRange.GetToAsSecondsEpoch()), -1)
|
||||
|
||||
|
||||
@@ -44,20 +44,6 @@ func TestSqlEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, "select 60000 ")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := Interpolate(query, timeRange, "select $__timeFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := Interpolate(query, timeRange, "select $__timeTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -16,7 +16,7 @@ export function registerAngularDirectives() {
|
||||
react2AngularDirective('searchResult', SearchResult, []);
|
||||
react2AngularDirective('tagFilter', TagFilter, [
|
||||
'tags',
|
||||
['onSelect', { watchDepth: 'reference' }],
|
||||
['onChange', { watchDepth: 'reference' }],
|
||||
['tagOptions', { watchDepth: 'reference' }],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
|
||||
render() {
|
||||
const { onCancel } = this.props;
|
||||
const newItem = this.state;
|
||||
const pickerClassName = 'width-20';
|
||||
const pickerClassName = 'min-width-20';
|
||||
const isValid = this.isValid();
|
||||
return (
|
||||
<div className="gf-form-inline cta-form">
|
||||
|
||||
@@ -40,7 +40,7 @@ export class UserPicker extends Component<Props, State> {
|
||||
.then(result => {
|
||||
return result.map(user => ({
|
||||
id: user.userId,
|
||||
label: `${user.login} - ${user.email}`,
|
||||
label: user.login === user.email ? user.login : `${user.login} - ${user.email}`,
|
||||
avatarUrl: user.avatarUrl,
|
||||
login: user.login,
|
||||
}));
|
||||
|
||||
@@ -10,7 +10,7 @@ import ResetStyles from 'app/core/components/Picker/ResetStyles';
|
||||
export interface Props {
|
||||
tags: string[];
|
||||
tagOptions: () => any;
|
||||
onSelect: (tag: string) => void;
|
||||
onChange: (tags: string[]) => void;
|
||||
}
|
||||
|
||||
export class TagFilter extends React.Component<Props, any> {
|
||||
@@ -18,12 +18,9 @@ export class TagFilter extends React.Component<Props, any> {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.searchTags = this.searchTags.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
searchTags(query) {
|
||||
onLoadOptions = query => {
|
||||
return this.props.tagOptions().then(options => {
|
||||
return options.map(option => ({
|
||||
value: option.term,
|
||||
@@ -31,18 +28,20 @@ export class TagFilter extends React.Component<Props, any> {
|
||||
count: option.count,
|
||||
}));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onChange(newTags) {
|
||||
this.props.onSelect(newTags);
|
||||
}
|
||||
onChange = (newTags: any[]) => {
|
||||
this.props.onChange(newTags.map(tag => tag.value));
|
||||
};
|
||||
|
||||
render() {
|
||||
const tags = this.props.tags.map(tag => ({ value: tag, label: tag, count: 0 }));
|
||||
|
||||
const selectOptions = {
|
||||
classNamePrefix: 'gf-form-select-box',
|
||||
isMulti: true,
|
||||
defaultOptions: true,
|
||||
loadOptions: this.searchTags,
|
||||
loadOptions: this.onLoadOptions,
|
||||
onChange: this.onChange,
|
||||
className: 'gf-form-input gf-form-input--form-dropdown',
|
||||
placeholder: 'Tags',
|
||||
@@ -50,8 +49,12 @@ export class TagFilter extends React.Component<Props, any> {
|
||||
noOptionsMessage: () => 'No tags found',
|
||||
getOptionValue: i => i.value,
|
||||
getOptionLabel: i => i.label,
|
||||
value: this.props.tags,
|
||||
value: tags,
|
||||
styles: ResetStyles,
|
||||
filterOption: (option, searchQuery) => {
|
||||
const regex = RegExp(searchQuery, 'i');
|
||||
return regex.test(option.value);
|
||||
},
|
||||
components: {
|
||||
Option: TagOption,
|
||||
IndicatorsContainer,
|
||||
|
||||
@@ -44,7 +44,7 @@ export class SeriesColorPicker extends React.Component<SeriesColorPickerProps> {
|
||||
const drop = new Drop({
|
||||
target: this.pickerElem,
|
||||
content: dropContentElem,
|
||||
position: 'top center',
|
||||
position: 'bottom center',
|
||||
classes: 'drop-popover',
|
||||
openOn: 'hover',
|
||||
hoverCloseDelay: 200,
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<tag-filter tags="ctrl.query.tag" tagOptions="ctrl.getTags" onSelect="ctrl.onTagSelect">
|
||||
<tag-filter tags="ctrl.query.tag" tagOptions="ctrl.getTags" onChange="ctrl.onTagFiltersChanged">
|
||||
</tag-filter>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ export class SearchCtrl {
|
||||
appEvents.on('hide-dash-search', this.closeSearch.bind(this), $scope);
|
||||
|
||||
this.initialFolderFilterTitle = 'All';
|
||||
this.getTags = this.getTags.bind(this);
|
||||
this.onTagSelect = this.onTagSelect.bind(this);
|
||||
this.isEditor = contextSrv.isEditor;
|
||||
this.hasEditPermissionInFolders = contextSrv.hasEditPermissionInFolders;
|
||||
}
|
||||
@@ -162,7 +160,7 @@ export class SearchCtrl {
|
||||
const localSearchId = this.currentSearchId;
|
||||
const query = {
|
||||
...this.query,
|
||||
tag: this.query.tag.map(i => i.value),
|
||||
tag: this.query.tag,
|
||||
};
|
||||
|
||||
return this.searchSrv.search(query).then(results => {
|
||||
@@ -195,14 +193,14 @@ export class SearchCtrl {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
getTags() {
|
||||
getTags = () => {
|
||||
return this.searchSrv.getDashboardTags();
|
||||
}
|
||||
};
|
||||
|
||||
onTagSelect(newTags) {
|
||||
this.query.tag = newTags;
|
||||
onTagFiltersChanged = (tags: string[]) => {
|
||||
this.query.tag = tags;
|
||||
this.search();
|
||||
}
|
||||
};
|
||||
|
||||
clearSearchFilter() {
|
||||
this.query.tag = [];
|
||||
|
||||
@@ -428,10 +428,16 @@ kbn.valueFormats.hex0x = (value, decimals) => {
|
||||
};
|
||||
|
||||
kbn.valueFormats.sci = (value, decimals) => {
|
||||
if (value == null) {
|
||||
return '';
|
||||
}
|
||||
return value.toExponential(decimals);
|
||||
};
|
||||
|
||||
kbn.valueFormats.locale = (value, decimals) => {
|
||||
if (value == null) {
|
||||
return '';
|
||||
}
|
||||
return value.toLocaleString(undefined, { maximumFractionDigits: decimals });
|
||||
};
|
||||
|
||||
@@ -584,8 +590,8 @@ kbn.valueFormats.flowcms = kbn.formatBuilders.fixedUnit('cms');
|
||||
kbn.valueFormats.flowcfs = kbn.formatBuilders.fixedUnit('cfs');
|
||||
kbn.valueFormats.flowcfm = kbn.formatBuilders.fixedUnit('cfm');
|
||||
kbn.valueFormats.litreh = kbn.formatBuilders.fixedUnit('l/h');
|
||||
kbn.valueFormats.flowlpm = kbn.formatBuilders.decimalSIPrefix('L');
|
||||
kbn.valueFormats.flowmlpm = kbn.formatBuilders.decimalSIPrefix('L', -1);
|
||||
kbn.valueFormats.flowlpm = kbn.formatBuilders.fixedUnit('l/min');
|
||||
kbn.valueFormats.flowmlpm = kbn.formatBuilders.fixedUnit('mL/min');
|
||||
|
||||
// Angle
|
||||
kbn.valueFormats.degree = kbn.formatBuilders.fixedUnit('°');
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<metric-segment-model property="conditionModel.evaluator.type" options="ctrl.evalFunctions" custom="false" css-class="query-keyword" on-change="ctrl.evaluatorTypeChanged(conditionModel.evaluator)"></metric-segment-model>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-hide="conditionModel.evaluator.params.length === 0" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.evaluatorParamsChanged()"></input>
|
||||
<label class="gf-form-label query-keyword" ng-show="conditionModel.evaluator.params.length === 2">TO</label>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-if="conditionModel.evaluator.params.length === 2" ng-model="conditionModel.evaluator.params[1]" ng-change="ctrl.evaluatorParamsChanged()"></input>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-hide="conditionModel.evaluator.params.length === 0" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.evaluatorParamsChanged()">
|
||||
<label class="gf-form-label query-keyword" ng-show="conditionModel.evaluator.params.length === 2">TO</label>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-if="conditionModel.evaluator.params.length === 2" ng-model="conditionModel.evaluator.params[1]" ng-change="ctrl.evaluatorParamsChanged()">
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
|
||||
@@ -8,9 +8,9 @@ const alertQueryDef = new QueryPartDef({
|
||||
{
|
||||
name: 'from',
|
||||
type: 'string',
|
||||
options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h', '24h', '48h'],
|
||||
options: ['10s', '1m', '5m', '10m', '15m', '1h', '24h', '48h'],
|
||||
},
|
||||
{ name: 'to', type: 'string', options: ['now'] },
|
||||
{ name: 'to', type: 'string', options: ['now', 'now-1m', 'now-5m', 'now-10m', 'now-1h'] },
|
||||
],
|
||||
defaultParams: ['#A', '15m', 'now', 'avg'],
|
||||
});
|
||||
|
||||
@@ -223,6 +223,8 @@ export class DashboardModel {
|
||||
}
|
||||
|
||||
panelInitialized(panel: PanelModel) {
|
||||
panel.initialized();
|
||||
|
||||
if (!this.otherPanelInFullscreen(panel)) {
|
||||
panel.refresh();
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ export class PanelModel {
|
||||
}
|
||||
}
|
||||
|
||||
panelInitialized() {
|
||||
initialized() {
|
||||
this.events.emit('panel-initialized');
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ export function loadDataSourceTypes(): ThunkResult<void> {
|
||||
export function nameExits(dataSources, name) {
|
||||
return (
|
||||
dataSources.filter(dataSource => {
|
||||
return dataSource.name === name;
|
||||
return dataSource.name.toLowerCase() === name.toLowerCase();
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<div class="panel panel--solo" ng-if="panel" style="width: 100%">
|
||||
<div class="panel-solo" ng-if="panel">
|
||||
<plugin-component type="panel">
|
||||
</plugin-component>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@@ -115,7 +115,7 @@ export class TeamMembers extends PureComponent<Props, State> {
|
||||
</button>
|
||||
<h5>Add Team Member</h5>
|
||||
<div className="gf-form-inline">
|
||||
<UserPicker onSelected={this.onUserSelected} className="width-30" />
|
||||
<UserPicker onSelected={this.onUserSelected} className="min-width-30" />
|
||||
{this.state.newTeamMember && (
|
||||
<button className="btn btn-success gf-form-btn" type="submit" onClick={this.onAddUserToTeam}>
|
||||
Add to team
|
||||
|
||||
@@ -58,7 +58,7 @@ exports[`Render should render component 1`] = `
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<UserPicker
|
||||
className="width-30"
|
||||
className="min-width-30"
|
||||
onSelected={[Function]}
|
||||
/>
|
||||
</div>
|
||||
@@ -152,7 +152,7 @@ exports[`Render should render team members 1`] = `
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<UserPicker
|
||||
className="width-30"
|
||||
className="min-width-30"
|
||||
onSelected={[Function]}
|
||||
/>
|
||||
</div>
|
||||
@@ -372,7 +372,7 @@ exports[`Render should render team members when sync enabled 1`] = `
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<UserPicker
|
||||
className="width-30"
|
||||
className="min-width-30"
|
||||
onSelected={[Function]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,22 +2,8 @@ import coreModule from 'app/core/core_module';
|
||||
import _ from 'lodash';
|
||||
import * as queryDef from './query_def';
|
||||
|
||||
export function elasticBucketAgg() {
|
||||
return {
|
||||
templateUrl: 'public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html',
|
||||
controller: 'ElasticBucketAggCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: '=',
|
||||
index: '=',
|
||||
onChange: '&',
|
||||
getFields: '&',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export class ElasticBucketAggCtrl {
|
||||
/** @nginject */
|
||||
/** @ngInject */
|
||||
constructor($scope, uiSegmentSrv, $q, $rootScope) {
|
||||
const bucketAggs = $scope.target.bucketAggs;
|
||||
|
||||
@@ -226,5 +212,18 @@ export class ElasticBucketAggCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export function elasticBucketAgg() {
|
||||
return {
|
||||
templateUrl: 'public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html',
|
||||
controller: ElasticBucketAggCtrl,
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: '=',
|
||||
index: '=',
|
||||
onChange: '&',
|
||||
getFields: '&',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('elasticBucketAgg', elasticBucketAgg);
|
||||
coreModule.controller('ElasticBucketAggCtrl', ElasticBucketAggCtrl);
|
||||
|
||||
@@ -2,22 +2,8 @@ import coreModule from 'app/core/core_module';
|
||||
import _ from 'lodash';
|
||||
import * as queryDef from './query_def';
|
||||
|
||||
export function elasticMetricAgg() {
|
||||
return {
|
||||
templateUrl: 'public/app/plugins/datasource/elasticsearch/partials/metric_agg.html',
|
||||
controller: 'ElasticMetricAggCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: '=',
|
||||
index: '=',
|
||||
onChange: '&',
|
||||
getFields: '&',
|
||||
esVersion: '=',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export class ElasticMetricAggCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope, uiSegmentSrv, $q, $rootScope) {
|
||||
const metricAggs = $scope.target.metrics;
|
||||
$scope.metricAggTypes = queryDef.getMetricAggTypes($scope.esVersion);
|
||||
@@ -209,5 +195,19 @@ export class ElasticMetricAggCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export function elasticMetricAgg() {
|
||||
return {
|
||||
templateUrl: 'public/app/plugins/datasource/elasticsearch/partials/metric_agg.html',
|
||||
controller: ElasticMetricAggCtrl,
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: '=',
|
||||
index: '=',
|
||||
onChange: '&',
|
||||
getFields: '&',
|
||||
esVersion: '=',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('elasticMetricAgg', elasticMetricAgg);
|
||||
coreModule.controller('ElasticMetricAggCtrl', ElasticMetricAggCtrl);
|
||||
|
||||
@@ -28,12 +28,12 @@ An annotation is an event that is overlaid on top of graphs. The query can have
|
||||
Macros:
|
||||
- $__time(column) -> UNIX_TIMESTAMP(column) as time (or as time_sec)
|
||||
- $__timeEpoch(column) -> UNIX_TIMESTAMP(column) as time (or as time_sec)
|
||||
- $__timeFilter(column) -> column BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:01:17Z'
|
||||
- $__timeFilter(column) -> column BETWEEN FROM_UNIXTIME(1492750877) AND FROM_UNIXTIME(1492750877)
|
||||
- $__unixEpochFilter(column) -> time_unix_epoch > 1492750877 AND time_unix_epoch < 1492750877
|
||||
|
||||
Or build your own conditionals using these macros which just return the values:
|
||||
- $__timeFrom() -> '2017-04-21T05:01:17Z'
|
||||
- $__timeTo() -> '2017-04-21T05:01:17Z'
|
||||
- $__timeFrom() -> FROM_UNIXTIME(1492750877)
|
||||
- $__timeTo() -> FROM_UNIXTIME(1492750877)
|
||||
- $__unixEpochFrom() -> 1492750877
|
||||
- $__unixEpochTo() -> 1492750877
|
||||
</pre>
|
||||
|
||||
@@ -151,7 +151,7 @@ Table:
|
||||
Macros:
|
||||
- $__time(column) -> UNIX_TIMESTAMP(column) as time_sec
|
||||
- $__timeEpoch(column) -> UNIX_TIMESTAMP(column) as time_sec
|
||||
- $__timeFilter(column) -> column BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:01:17Z'
|
||||
- $__timeFilter(column) -> column BETWEEN FROM_UNIXTIME(1492750877) AND FROM_UNIXTIME(1492750877)
|
||||
- $__unixEpochFilter(column) -> time_unix_epoch > 1492750877 AND time_unix_epoch < 1492750877
|
||||
- $__timeGroup(column,'5m'[, fillvalue]) -> cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed)
|
||||
by setting fillvalue grafana will fill in missing values according to the interval
|
||||
@@ -169,8 +169,8 @@ GROUP BY 1
|
||||
ORDER BY 1
|
||||
|
||||
Or build your own conditionals using these macros which just return the values:
|
||||
- $__timeFrom() -> '2017-04-21T05:01:17Z'
|
||||
- $__timeTo() -> '2017-04-21T05:01:17Z'
|
||||
- $__timeFrom() -> FROM_UNIXTIME(1492750877)
|
||||
- $__timeTo() -> FROM_UNIXTIME(1492750877)
|
||||
- $__unixEpochFrom() -> 1492750877
|
||||
- $__unixEpochTo() -> 1492750877
|
||||
</pre>
|
||||
|
||||
@@ -151,8 +151,7 @@ table_schema IN (
|
||||
|
||||
buildDatatypeQuery(column: string) {
|
||||
let query = 'SELECT udt_name FROM information_schema.columns WHERE ';
|
||||
query += this.buildSchemaConstraint();
|
||||
query += ' AND table_name = ' + this.quoteIdentAsLiteral(this.target.table);
|
||||
query += this.buildTableConstraint(this.target.table);
|
||||
query += ' AND column_name = ' + this.quoteIdentAsLiteral(column);
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -58,15 +58,7 @@ class GraphElement {
|
||||
|
||||
// panel events
|
||||
this.ctrl.events.on('panel-teardown', this.onPanelTeardown.bind(this));
|
||||
|
||||
/**
|
||||
* Split graph rendering into two parts.
|
||||
* First, calculate series stats in buildFlotPairs() function. Then legend rendering started
|
||||
* (see ctrl.events.on('render') in legend.ts).
|
||||
* When legend is rendered it emits 'legend-rendering-complete' and graph rendered.
|
||||
*/
|
||||
this.ctrl.events.on('render', this.onRender.bind(this));
|
||||
this.ctrl.events.on('legend-rendering-complete', this.onLegendRenderingComplete.bind(this));
|
||||
|
||||
// global events
|
||||
appEvents.on('graph-hover', this.onGraphHover.bind(this), scope);
|
||||
@@ -85,11 +77,20 @@ class GraphElement {
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.annotations = this.ctrl.annotations || [];
|
||||
this.buildFlotPairs(this.data);
|
||||
const graphHeight = this.elem.height();
|
||||
updateLegendValues(this.data, this.panel, graphHeight);
|
||||
|
||||
if (!this.panel.legend.show) {
|
||||
if (this.legendElem.hasChildNodes()) {
|
||||
ReactDOM.unmountComponentAtNode(this.legendElem);
|
||||
}
|
||||
this.renderPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
const { values, min, max, avg, current, total } = this.panel.legend;
|
||||
const { alignAsTable, rightSide, sideWidth, sort, sortDesc, hideEmpty, hideZero } = this.panel.legend;
|
||||
const legendOptions = { alignAsTable, rightSide, sideWidth, sort, sortDesc, hideEmpty, hideZero };
|
||||
@@ -104,12 +105,9 @@ class GraphElement {
|
||||
onColorChange: this.ctrl.onColorChange,
|
||||
onToggleAxis: this.ctrl.onToggleAxis,
|
||||
};
|
||||
const legendReactElem = React.createElement(Legend, legendProps);
|
||||
ReactDOM.render(legendReactElem, this.legendElem, () => this.onLegendRenderingComplete());
|
||||
}
|
||||
|
||||
onLegendRenderingComplete() {
|
||||
this.render_panel();
|
||||
const legendReactElem = React.createElement(Legend, legendProps);
|
||||
ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel());
|
||||
}
|
||||
|
||||
onGraphHover(evt) {
|
||||
@@ -281,7 +279,7 @@ class GraphElement {
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
render_panel() {
|
||||
renderPanel() {
|
||||
this.panelWidth = this.elem.width();
|
||||
if (this.shouldAbortRender()) {
|
||||
return;
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('grafanaGraph', () => {
|
||||
|
||||
//Emulate functions called by event listeners
|
||||
link.buildFlotPairs(link.data);
|
||||
link.render_panel();
|
||||
link.renderPanel();
|
||||
ctx.plotData = ctrl.plot.mock.calls[0][1];
|
||||
|
||||
ctx.plotOptions = ctrl.plot.mock.calls[0][2];
|
||||
|
||||
@@ -130,6 +130,33 @@ describe('TimeRegionManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
plotOptionsScenario('for time from/to region', ctx => {
|
||||
const regions = [{ from: '00:00', to: '05:00', fill: true, colorMode: 'red' }];
|
||||
const from = moment('2018-12-01T00:00+01:00');
|
||||
const to = moment('2018-12-03T23:59+01:00');
|
||||
ctx.setup(regions, from, to);
|
||||
|
||||
it('should add 3 markings', () => {
|
||||
expect(ctx.options.grid.markings.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should add one fill between 00:00 and 05:00 each day', () => {
|
||||
const markings = ctx.options.grid.markings;
|
||||
|
||||
expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-12-01T01:00:00+01:00').format());
|
||||
expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-12-01T06:00:00+01:00').format());
|
||||
expect(markings[0].color).toBe(colorModes.red.color.fill);
|
||||
|
||||
expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-12-02T01:00:00+01:00').format());
|
||||
expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-12-02T06:00:00+01:00').format());
|
||||
expect(markings[1].color).toBe(colorModes.red.color.fill);
|
||||
|
||||
expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-12-03T01:00:00+01:00').format());
|
||||
expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-12-03T06:00:00+01:00').format());
|
||||
expect(markings[2].color).toBe(colorModes.red.color.fill);
|
||||
});
|
||||
});
|
||||
|
||||
plotOptionsScenario('for day of week from/to region', ctx => {
|
||||
const regions = [{ fromDayOfWeek: 7, toDayOfWeek: 7, fill: true, colorMode: 'red' }];
|
||||
const from = moment('2018-01-01T18:45:05+01:00');
|
||||
@@ -211,6 +238,42 @@ describe('TimeRegionManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
plotOptionsScenario('for day of week from/to time region', ctx => {
|
||||
const regions = [{ fromDayOfWeek: 7, from: '23:00', toDayOfWeek: 1, to: '01:40', fill: true, colorMode: 'red' }];
|
||||
const from = moment('2018-12-07T12:51:19+01:00');
|
||||
const to = moment('2018-12-10T13:51:29+01:00');
|
||||
ctx.setup(regions, from, to);
|
||||
|
||||
it('should add 1 marking', () => {
|
||||
expect(ctx.options.grid.markings.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should add one fill between sunday 23:00 and monday 01:40', () => {
|
||||
const markings = ctx.options.grid.markings;
|
||||
|
||||
expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-12-10T00:00:00+01:00').format());
|
||||
expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-12-10T02:40:00+01:00').format());
|
||||
});
|
||||
});
|
||||
|
||||
plotOptionsScenario('for day of week from/to time region', ctx => {
|
||||
const regions = [{ fromDayOfWeek: 6, from: '03:00', toDayOfWeek: 7, to: '02:00', fill: true, colorMode: 'red' }];
|
||||
const from = moment('2018-12-07T12:51:19+01:00');
|
||||
const to = moment('2018-12-10T13:51:29+01:00');
|
||||
ctx.setup(regions, from, to);
|
||||
|
||||
it('should add 1 marking', () => {
|
||||
expect(ctx.options.grid.markings.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should add one fill between saturday 03:00 and sunday 02:00', () => {
|
||||
const markings = ctx.options.grid.markings;
|
||||
|
||||
expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-12-08T04:00:00+01:00').format());
|
||||
expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-12-09T03:00:00+01:00').format());
|
||||
});
|
||||
});
|
||||
|
||||
plotOptionsScenario('for day of week from/to time region with daylight saving time', ctx => {
|
||||
const regions = [{ fromDayOfWeek: 7, from: '20:00', toDayOfWeek: 7, to: '23:00', fill: true, colorMode: 'red' }];
|
||||
const from = moment('2018-03-17T06:00:00+01:00');
|
||||
|
||||
@@ -87,6 +87,14 @@ export class TimeRegionManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timeRegion.from && !timeRegion.to) {
|
||||
timeRegion.to = timeRegion.from;
|
||||
}
|
||||
|
||||
if (!timeRegion.from && timeRegion.to) {
|
||||
timeRegion.from = timeRegion.to;
|
||||
}
|
||||
|
||||
hRange = {
|
||||
from: this.parseTimeRange(timeRegion.from),
|
||||
to: this.parseTimeRange(timeRegion.to),
|
||||
@@ -108,21 +116,13 @@ export class TimeRegionManager {
|
||||
hRange.to.dayOfWeek = Number(timeRegion.toDayOfWeek);
|
||||
}
|
||||
|
||||
if (!hRange.from.h && hRange.to.h) {
|
||||
hRange.from = hRange.to;
|
||||
}
|
||||
|
||||
if (hRange.from.h && !hRange.to.h) {
|
||||
hRange.to = hRange.from;
|
||||
}
|
||||
|
||||
if (hRange.from.dayOfWeek && !hRange.from.h && !hRange.from.m) {
|
||||
if (hRange.from.dayOfWeek && hRange.from.h === null && hRange.from.m === null) {
|
||||
hRange.from.h = 0;
|
||||
hRange.from.m = 0;
|
||||
hRange.from.s = 0;
|
||||
}
|
||||
|
||||
if (hRange.to.dayOfWeek && !hRange.to.h && !hRange.to.m) {
|
||||
if (hRange.to.dayOfWeek && hRange.to.h === null && hRange.to.m === null) {
|
||||
hRange.to.h = 23;
|
||||
hRange.to.m = 59;
|
||||
hRange.to.s = 59;
|
||||
@@ -169,8 +169,16 @@ export class TimeRegionManager {
|
||||
fromEnd.add(hRange.to.h - hRange.from.h, 'hours');
|
||||
} else if (hRange.from.h + hRange.to.h < 23) {
|
||||
fromEnd.add(hRange.to.h, 'hours');
|
||||
|
||||
while (fromEnd.hour() !== hRange.to.h) {
|
||||
fromEnd.add(-1, 'hours');
|
||||
}
|
||||
} else {
|
||||
fromEnd.add(24 - hRange.from.h, 'hours');
|
||||
|
||||
while (fromEnd.hour() !== hRange.to.h) {
|
||||
fromEnd.add(1, 'hours');
|
||||
}
|
||||
}
|
||||
|
||||
fromEnd.set('minute', hRange.to.m);
|
||||
|
||||
@@ -107,7 +107,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
onDataReceived(dataList) {
|
||||
const data: any = {};
|
||||
const data: any = {
|
||||
scopedVars: _.extend({}, this.panel.scopedVars),
|
||||
};
|
||||
|
||||
if (dataList.length > 0 && dataList[0].type === 'table') {
|
||||
this.dataType = 'table';
|
||||
const tableData = dataList.map(this.tableHandler.bind(this));
|
||||
@@ -117,6 +120,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
this.series = dataList.map(this.seriesHandler.bind(this));
|
||||
this.setValues(data);
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
this.render();
|
||||
}
|
||||
@@ -320,7 +324,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
}
|
||||
|
||||
// Add $__name variable for using in prefix or postfix
|
||||
data.scopedVars = _.extend({}, this.panel.scopedVars);
|
||||
data.scopedVars['__name'] = { value: this.series[0].label };
|
||||
}
|
||||
this.setValueMapping(data);
|
||||
|
||||
@@ -199,7 +199,6 @@ small,
|
||||
|
||||
mark,
|
||||
.mark {
|
||||
padding: 0.2em;
|
||||
background: $alert-warning-bg;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,16 +19,23 @@ div.flot-text {
|
||||
|
||||
.panel {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--solo {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
.panel-container {
|
||||
border: none;
|
||||
z-index: $zindex-sidemenu + 1;
|
||||
}
|
||||
.panel-solo {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
.panel-container {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.panel-menu-toggle,
|
||||
.panel-menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 30 {
|
||||
.min-width-#{$i} {
|
||||
min-width: ($spacer * $i) - $gf-form-margin !important;
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 30 {
|
||||
.offset-width-#{$i} {
|
||||
margin-left: ($spacer * $i) !important;
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
EXTRA_OPTS="$@"
|
||||
|
||||
# Right now we hack this in into the publish script.
|
||||
# Right now we hack this in into the publish script.
|
||||
# Eventually we might want to keep a list of all previous releases somewhere.
|
||||
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v5-3-x/10244"
|
||||
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v5-3/"
|
||||
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v5-4-x/12215"
|
||||
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v5-4/"
|
||||
|
||||
./scripts/build/release_publisher/release_publisher \
|
||||
--wn ${_whatsNewUrl} \
|
||||
|
||||
@@ -41,12 +41,12 @@ func main() {
|
||||
var builder releaseBuilder
|
||||
var product string
|
||||
|
||||
archiveProviderRoot := "https://s3-us-west-2.amazonaws.com"
|
||||
archiveProviderRoot := "https://dl.grafana.com"
|
||||
buildArtifacts := completeBuildArtifactConfigurations
|
||||
|
||||
if enterprise {
|
||||
product = "grafana-enterprise"
|
||||
baseUrl = createBaseUrl(archiveProviderRoot, "grafana-enterprise-releases", product, nightly)
|
||||
baseUrl = createBaseUrl(archiveProviderRoot, "enterprise", product, nightly)
|
||||
var err error
|
||||
buildArtifacts, err = filterBuildArtifacts([]artifactFilter{
|
||||
{os: "deb", arch: "amd64"},
|
||||
@@ -61,7 +61,7 @@ func main() {
|
||||
|
||||
} else {
|
||||
product = "grafana"
|
||||
baseUrl = createBaseUrl(archiveProviderRoot, "grafana-releases", product, nightly)
|
||||
baseUrl = createBaseUrl(archiveProviderRoot, "oss", product, nightly)
|
||||
}
|
||||
|
||||
if fromLocal {
|
||||
|
||||
Reference in New Issue
Block a user