Compare commits

...

23 Commits

Author SHA1 Message Date
Torkel Ödegaard
308c818cd7 Merge pull request #14431 from grafana/v5.4.1-cherry-picks
v5.4.1 cherry picks
2018-12-10 15:08:32 +01:00
Marcus Efraimsson
d49d8bf13d fix time regions bugs
(cherry picked from commit 8f26fe0fbb)
2018-12-10 14:54:04 +01:00
Torkel Ödegaard
3701f22d66 bumped version number to v5.4.1 2018-12-10 14:44:52 +01:00
Torkel Ödegaard
4c60ef398a fixed issue with colorpicker position above window, fixes #14412
(cherry picked from commit 0c5fd21327)
2018-12-10 14:39:50 +01:00
Torkel Ödegaard
ec98666de1 fixed issue with singlestat and repeated scopedVars, was only working for time series data sources, and only if there was any series, now scoped vars is always set, fixes #14367
(cherry picked from commit 976d25d6ae)
2018-12-10 14:39:08 +01:00
Torkel Ödegaard
7fe10e2eef fix search tag issues, fixes #14391
(cherry picked from commit e05f6c5397)
2018-12-10 14:38:37 +01:00
Scott Glajch
4a8cd4c023 Add the AWS/SES Cloudwatch metrics of BounceRate and ComplaintRate. Pull request #14399
(cherry picked from commit 62a5cd27ba)
2018-12-10 14:38:16 +01:00
moznion
174be1abab Put issue number to test code
(cherry picked from commit 4397ee61d0)
2018-12-10 14:37:52 +01:00
moznion
4c13e02aef Fix bug what updating user quota doesn't work
Reason is same as 061e06c226

(cherry picked from commit d1e1cde00e)
2018-12-10 14:37:36 +01:00
moznion
4a8a3d40e7 Fix bug what updating org quota doesn't work
3c330c8e4c/pkg/services/sqlstore/quota.go (L106)

In the real use case, `has` that is described by the above code is always `false` because it includes `Updated` in a query.

So this commit fixes this issue.

(cherry picked from commit 061e06c226)
2018-12-10 14:37:26 +01:00
Johannes Schill
01c4b71cfb If user login equals user email, only show the email once #14341
(cherry picked from commit 02b14d33a6)
2018-12-10 14:35:42 +01:00
Johannes Schill
e7cd39a543 UserPicker and TeamPicker should use min-width instead of fixed widths to avoid overflowing form buttons. #14341
(cherry picked from commit 114a264da4)
2018-12-10 14:35:31 +01:00
Torkel Ödegaard
6f241a4bac fix for panel-initialized event not being called
(cherry picked from commit 757cada4a5)
2018-12-10 14:34:46 +01:00
Brian Gann
0a19581c48 redact value for plugin proxy routes
(cherry picked from commit 02365514f9)
2018-12-10 14:34:25 +01:00
Torkel Ödegaard
96cb4df83a fix for panel embedding. Solo panel height was not correctly set. Made panel--solo into panel-solo class. in develop branch we have remove the need for the panel class
(cherry picked from commit e31490ac68)
2018-12-10 14:34:07 +01:00
Tom Nitti
7820775a53 added support for influxdb cumulative_sum function in tsdb
(cherry picked from commit 1e53c12921)
2018-12-10 14:33:41 +01:00
Dominik Henneke
9699133501 Use buildTableConstraint instead of buildSchemaConstraint to find the datatype of a column if using a table from a different database schema
(cherry picked from commit b450b778cb)
2018-12-10 14:32:55 +01:00
flopp999
80ecd8ea8e fixedUnit for Flow:l/min and mL/min
got stranged result with decimalSIPrefix
(cherry picked from commit 8caeb13026)
2018-12-10 14:32:31 +01:00
Marcus Efraimsson
2ab2259091 fix time regions using zero hours
(cherry picked from commit ad33cd5c5c)
2018-12-10 14:32:08 +01:00
Kornelijus Survila
deb305b95f dataproxy: Override incoming Authorization header
(cherry picked from commit 0cafd9a663)
2018-12-10 14:31:28 +01:00
Leonard Gram
d42c17efad build: update latest when pushing docker.
(cherry picked from commit 9a771555f3)
2018-12-04 11:17:50 +01:00
Leonard Gram
972aaef2a6 build: always test publisher.
(cherry picked from commit cb0d58c6f1)
2018-12-03 15:18:10 +01:00
Leonard Gram
ce3982d406 build: packages linked to dl.grafana.com.
(cherry picked from commit ff0730ca1a)
2018-12-03 15:18:10 +01:00
31 changed files with 232 additions and 74 deletions

View File

@@ -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:

View File

@@ -4,7 +4,7 @@
"company": "Grafana Labs"
},
"name": "grafana",
"version": "5.4.0",
"version": "5.4.1",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"

View File

@@ -18,3 +18,8 @@ docker build \
.
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

View File

@@ -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))
}
}
}

View File

@@ -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])
}
}

View File

@@ -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(&quota)
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(&quota)
if err != nil {
return err
}
quota.Updated = time.Now()
quota.Limit = cmd.Limit
if !has {
quota.Created = time.Now()

View File

@@ -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)
})
})
}

View File

@@ -101,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"},

View File

@@ -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,

View File

@@ -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")}

View File

@@ -16,7 +16,7 @@ export function registerAngularDirectives() {
react2AngularDirective('searchResult', SearchResult, []);
react2AngularDirective('tagFilter', TagFilter, [
'tags',
['onSelect', { watchDepth: 'reference' }],
['onChange', { watchDepth: 'reference' }],
['tagOptions', { watchDepth: 'reference' }],
]);
}

View File

@@ -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">

View File

@@ -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,
}));

View File

@@ -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,7 +49,7 @@ 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,
components: {
Option: TagOption,

View File

@@ -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,

View File

@@ -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>

View File

@@ -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 = [];

View File

@@ -590,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/min');
kbn.valueFormats.flowmlpm = kbn.formatBuilders.decimalSIPrefix('mL/min', -1);
kbn.valueFormats.flowlpm = kbn.formatBuilders.fixedUnit('l/min');
kbn.valueFormats.flowmlpm = kbn.formatBuilders.fixedUnit('mL/min');
// Angle
kbn.valueFormats.degree = kbn.formatBuilders.fixedUnit('°');

View File

@@ -223,6 +223,8 @@ export class DashboardModel {
}
panelInitialized(panel: PanelModel) {
panel.initialized();
if (!this.otherPanelInFullscreen(panel)) {
panel.refresh();
}

View File

@@ -132,7 +132,7 @@ export class PanelModel {
}
}
panelInitialized() {
initialized() {
this.events.emit('panel-initialized');
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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');

View File

@@ -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);

View File

@@ -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);

View File

@@ -199,7 +199,6 @@ small,
mark,
.mark {
padding: 0.2em;
background: $alert-warning-bg;
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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 {