Merge remote-tracking branch 'origin/master' into davkal/explore-rate-hinting

This commit is contained in:
Marcus Efraimsson
2018-08-21 09:48:07 +02:00
233 changed files with 3432 additions and 3461 deletions

View File

@@ -207,6 +207,7 @@ export class Explore extends React.Component<any, IExploreState> {
datasourceError: null,
datasourceLoading: true,
graphResult: null,
latency: 0,
logsResult: null,
queryErrors: [],
queryHints: [],
@@ -254,7 +255,10 @@ export class Explore extends React.Component<any, IExploreState> {
this.setState({
graphResult: null,
logsResult: null,
latency: 0,
queries: ensureQueries(),
queryErrors: [],
queryHints: [],
tableResult: null,
});
};
@@ -276,8 +280,10 @@ export class Explore extends React.Component<any, IExploreState> {
onClickSplit = () => {
const { onChangeSplit } = this.props;
const state = { ...this.state };
state.queries = state.queries.map(({ edited, ...rest }) => rest);
if (onChangeSplit) {
onChangeSplit(true, this.state);
onChangeSplit(true, state);
}
};

View File

@@ -331,7 +331,7 @@ class QueryField extends React.Component<TypeaheadFieldProps, TypeaheadFieldStat
}
break;
}
case 'Enter':
case 'Tab': {
if (this.menuEl) {
// Dont blur input

View File

@@ -69,8 +69,9 @@ export class TeamMembers extends React.Component<Props, State> {
render() {
const { newTeamMember, isAdding } = this.state;
const members = this.props.team.members.values();
const members = this.props.team.filteredMembers;
const newTeamMemberValue = newTeamMember && newTeamMember.id.toString();
const { team } = this.props;
return (
<div>
@@ -81,7 +82,7 @@ export class TeamMembers extends React.Component<Props, State> {
type="text"
className="gf-form-input"
placeholder="Search members"
value={''}
value={team.search}
onChange={this.onSearchQueryChange}
/>
<i className="gf-form-input-icon fa fa-search" />

View File

@@ -25,6 +25,7 @@ export class HelpCtrl {
{ keys: ['d', 'k'], description: 'Toggle kiosk mode (hides top nav)' },
{ keys: ['d', 'E'], description: 'Expand all rows' },
{ keys: ['d', 'C'], description: 'Collapse all rows' },
{ keys: ['d', 'a'], description: 'Toggle auto fit panels (experimental feature)' },
{ keys: ['mod+o'], description: 'Toggle shared graph crosshair' },
],
'Focused Panel': [

View File

@@ -15,14 +15,7 @@ export class KeybindingSrv {
timepickerOpen = false;
/** @ngInject */
constructor(
private $rootScope,
private $location,
private datasourceSrv,
private timeSrv,
private contextSrv,
private $route
) {
constructor(private $rootScope, private $location, private datasourceSrv, private timeSrv, private contextSrv) {
// clear out all shortcuts on route change
$rootScope.$on('$routeChangeSuccess', () => {
Mousetrap.reset();
@@ -269,10 +262,8 @@ export class KeybindingSrv {
//Autofit panels
this.bind('d a', () => {
this.$location.search('autofitpanels', this.$location.search().autofitpanels ? null : true);
//Force reload
this.$route.reload();
// this has to be a full page reload
window.location.href = window.location.href + '&autofitpanels';
});
}
}

View File

@@ -2,120 +2,118 @@ import angular from 'angular';
import config from 'app/core/config';
import moment from 'moment';
export class ShareModalCtrl {
/** @ngInject */
constructor($scope, $rootScope, $location, $timeout, timeSrv, templateSrv, linkSrv) {
$scope.options = {
forCurrent: true,
includeTemplateVars: true,
theme: 'current',
};
$scope.editor = { index: $scope.tabIndex || 0 };
/** @ngInject */
export function ShareModalCtrl($scope, $rootScope, $location, $timeout, timeSrv, templateSrv, linkSrv) {
$scope.options = {
forCurrent: true,
includeTemplateVars: true,
theme: 'current',
};
$scope.editor = { index: $scope.tabIndex || 0 };
$scope.init = function() {
$scope.modeSharePanel = $scope.panel ? true : false;
$scope.init = function() {
$scope.modeSharePanel = $scope.panel ? true : false;
$scope.tabs = [{ title: 'Link', src: 'shareLink.html' }];
$scope.tabs = [{ title: 'Link', src: 'shareLink.html' }];
if ($scope.modeSharePanel) {
$scope.modalTitle = 'Share Panel';
$scope.tabs.push({ title: 'Embed', src: 'shareEmbed.html' });
} else {
$scope.modalTitle = 'Share';
}
if ($scope.modeSharePanel) {
$scope.modalTitle = 'Share Panel';
$scope.tabs.push({ title: 'Embed', src: 'shareEmbed.html' });
} else {
$scope.modalTitle = 'Share';
}
if (!$scope.dashboard.meta.isSnapshot) {
$scope.tabs.push({ title: 'Snapshot', src: 'shareSnapshot.html' });
}
if (!$scope.dashboard.meta.isSnapshot) {
$scope.tabs.push({ title: 'Snapshot', src: 'shareSnapshot.html' });
}
if (!$scope.dashboard.meta.isSnapshot && !$scope.modeSharePanel) {
$scope.tabs.push({ title: 'Export', src: 'shareExport.html' });
}
if (!$scope.dashboard.meta.isSnapshot && !$scope.modeSharePanel) {
$scope.tabs.push({ title: 'Export', src: 'shareExport.html' });
}
$scope.buildUrl();
};
$scope.buildUrl();
};
$scope.buildUrl = function() {
var baseUrl = $location.absUrl();
var queryStart = baseUrl.indexOf('?');
$scope.buildUrl = function() {
var baseUrl = $location.absUrl();
var queryStart = baseUrl.indexOf('?');
if (queryStart !== -1) {
baseUrl = baseUrl.substring(0, queryStart);
}
if (queryStart !== -1) {
baseUrl = baseUrl.substring(0, queryStart);
}
var params = angular.copy($location.search());
var params = angular.copy($location.search());
var range = timeSrv.timeRange();
params.from = range.from.valueOf();
params.to = range.to.valueOf();
params.orgId = config.bootData.user.orgId;
var range = timeSrv.timeRange();
params.from = range.from.valueOf();
params.to = range.to.valueOf();
params.orgId = config.bootData.user.orgId;
if ($scope.options.includeTemplateVars) {
templateSrv.fillVariableValuesForUrl(params);
}
if ($scope.options.includeTemplateVars) {
templateSrv.fillVariableValuesForUrl(params);
}
if (!$scope.options.forCurrent) {
delete params.from;
delete params.to;
}
if (!$scope.options.forCurrent) {
delete params.from;
delete params.to;
}
if ($scope.options.theme !== 'current') {
params.theme = $scope.options.theme;
}
if ($scope.options.theme !== 'current') {
params.theme = $scope.options.theme;
}
if ($scope.modeSharePanel) {
params.panelId = $scope.panel.id;
params.fullscreen = true;
} else {
delete params.panelId;
delete params.fullscreen;
}
$scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);
var soloUrl = baseUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
soloUrl = soloUrl.replace(config.appSubUrl + '/d/', config.appSubUrl + '/d-solo/');
if ($scope.modeSharePanel) {
params.panelId = $scope.panel.id;
params.fullscreen = true;
} else {
delete params.panelId;
delete params.fullscreen;
delete params.edit;
soloUrl = linkSrv.addParamsToUrl(soloUrl, params);
}
$scope.iframeHtml = '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
$scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);
$scope.imageUrl = soloUrl.replace(
config.appSubUrl + '/dashboard-solo/',
config.appSubUrl + '/render/dashboard-solo/'
);
$scope.imageUrl = $scope.imageUrl.replace(config.appSubUrl + '/d-solo/', config.appSubUrl + '/render/d-solo/');
$scope.imageUrl += '&width=1000&height=500' + $scope.getLocalTimeZone();
};
var soloUrl = baseUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
soloUrl = soloUrl.replace(config.appSubUrl + '/d/', config.appSubUrl + '/d-solo/');
delete params.fullscreen;
delete params.edit;
soloUrl = linkSrv.addParamsToUrl(soloUrl, params);
// This function will try to return the proper full name of the local timezone
// Chrome does not handle the timezone offset (but phantomjs does)
$scope.getLocalTimeZone = function() {
let utcOffset = '&tz=UTC' + encodeURIComponent(moment().format('Z'));
$scope.iframeHtml = '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
// Older browser does not the internationalization API
if (!(<any>window).Intl) {
return utcOffset;
}
$scope.imageUrl = soloUrl.replace(
config.appSubUrl + '/dashboard-solo/',
config.appSubUrl + '/render/dashboard-solo/'
);
$scope.imageUrl = $scope.imageUrl.replace(config.appSubUrl + '/d-solo/', config.appSubUrl + '/render/d-solo/');
$scope.imageUrl += '&width=1000&height=500' + $scope.getLocalTimeZone();
};
const dateFormat = (<any>window).Intl.DateTimeFormat();
if (!dateFormat.resolvedOptions) {
return utcOffset;
}
// This function will try to return the proper full name of the local timezone
// Chrome does not handle the timezone offset (but phantomjs does)
$scope.getLocalTimeZone = function() {
let utcOffset = '&tz=UTC' + encodeURIComponent(moment().format('Z'));
const options = dateFormat.resolvedOptions();
if (!options.timeZone) {
return utcOffset;
}
// Older browser does not the internationalization API
if (!(<any>window).Intl) {
return utcOffset;
}
return '&tz=' + encodeURIComponent(options.timeZone);
};
const dateFormat = (<any>window).Intl.DateTimeFormat();
if (!dateFormat.resolvedOptions) {
return utcOffset;
}
$scope.getShareUrl = function() {
return $scope.shareUrl;
};
}
const options = dateFormat.resolvedOptions();
if (!options.timeZone) {
return utcOffset;
}
return '&tz=' + encodeURIComponent(options.timeZone);
};
$scope.getShareUrl = function() {
return $scope.shareUrl;
};
}
angular.module('grafana.controllers').controller('ShareModalCtrl', ShareModalCtrl);

View File

@@ -0,0 +1,150 @@
import '../shareModalCtrl';
import { ShareModalCtrl } from '../shareModalCtrl';
import config from 'app/core/config';
import { LinkSrv } from 'app/features/panellinks/link_srv';
describe('ShareModalCtrl', () => {
var ctx = <any>{
timeSrv: {
timeRange: () => {
return { from: new Date(1000), to: new Date(2000) };
},
},
$location: {
absUrl: () => 'http://server/#!/test',
search: () => {
return { from: '', to: '' };
},
},
scope: {
dashboard: {
meta: {
isSnapshot: true,
},
},
},
templateSrv: {
fillVariableValuesForUrl: () => {},
},
};
(<any>window).Intl.DateTimeFormat = () => {
return {
resolvedOptions: () => {
return { timeZone: 'UTC' };
},
};
};
beforeEach(() => {
config.bootData = {
user: {
orgId: 1,
},
};
ctx.ctrl = new ShareModalCtrl(
ctx.scope,
{},
ctx.$location,
{},
ctx.timeSrv,
ctx.templateSrv,
new LinkSrv({}, ctx.stimeSrv)
);
});
describe('shareUrl with current time range and panel', () => {
it('should generate share url absolute time', () => {
ctx.scope.panel = { id: 22 };
ctx.scope.init();
expect(ctx.scope.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1&panelId=22&fullscreen');
});
it('should generate render url', () => {
ctx.$location.absUrl = () => 'http://dashboards.grafana.com/d/abcdefghi/my-dash';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).toContain(base + params);
});
it('should generate render url for scripted dashboard', () => {
ctx.$location.absUrl = () => 'http://dashboards.grafana.com/dashboard/script/my-dash.js';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).toContain(base + params);
});
it('should remove panel id when no panel in scope', () => {
ctx.$location.absUrl = () => 'http://server/#!/test';
ctx.scope.options.forCurrent = true;
ctx.scope.panel = null;
ctx.scope.init();
expect(ctx.scope.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1');
});
it('should add theme when specified', () => {
ctx.scope.options.theme = 'light';
ctx.scope.panel = null;
ctx.scope.init();
expect(ctx.scope.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1&theme=light');
});
it('should remove fullscreen from image url when is first param in querystring and modeSharePanel is true', () => {
ctx.$location.search = () => {
return { fullscreen: true, edit: true };
};
ctx.$location.absUrl = () => 'http://server/#!/test?fullscreen&edit';
ctx.scope.modeSharePanel = true;
ctx.scope.panel = { id: 1 };
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).toContain('?fullscreen&edit&from=1000&to=2000&orgId=1&panelId=1');
expect(ctx.scope.imageUrl).toContain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
});
it('should remove edit from image url when is first param in querystring and modeSharePanel is true', () => {
ctx.$location.search = () => {
return { edit: true, fullscreen: true };
};
ctx.$location.absUrl = () => 'http://server/#!/test?edit&fullscreen';
ctx.scope.modeSharePanel = true;
ctx.scope.panel = { id: 1 };
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).toContain('?edit&fullscreen&from=1000&to=2000&orgId=1&panelId=1');
expect(ctx.scope.imageUrl).toContain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
});
it('should include template variables in url', () => {
ctx.$location.search = () => {
return {};
};
ctx.$location.absUrl = () => 'http://server/#!/test';
ctx.scope.options.includeTemplateVars = true;
ctx.templateSrv.fillVariableValuesForUrl = function(params) {
params['var-app'] = 'mupp';
params['var-server'] = 'srv-01';
};
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).toContain(
'http://server/#!/test?from=1000&to=2000&orgId=1&var-app=mupp&var-server=srv-01'
);
});
});
});

View File

@@ -1,122 +0,0 @@
import { describe, beforeEach, it, expect, sinon, angularMocks } from 'test/lib/common';
import helpers from 'test/specs/helpers';
import '../shareModalCtrl';
import config from 'app/core/config';
import 'app/features/panellinks/link_srv';
describe('ShareModalCtrl', function() {
var ctx = new helpers.ControllerTestContext();
function setTime(range) {
ctx.timeSrv.timeRange = sinon.stub().returns(range);
}
beforeEach(function() {
config.bootData = {
user: {
orgId: 1,
},
};
});
setTime({ from: new Date(1000), to: new Date(2000) });
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(angularMocks.module('grafana.services'));
beforeEach(
angularMocks.module(function($compileProvider) {
$compileProvider.preAssignBindingsEnabled(true);
})
);
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('ShareModalCtrl'));
describe('shareUrl with current time range and panel', function() {
it('should generate share url absolute time', function() {
ctx.$location.path('/test');
ctx.scope.panel = { id: 22 };
ctx.scope.init();
expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1&panelId=22&fullscreen');
});
it('should generate render url', function() {
ctx.$location.$$absUrl = 'http://dashboards.grafana.com/d/abcdefghi/my-dash';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).to.contain(base + params);
});
it('should generate render url for scripted dashboard', function() {
ctx.$location.$$absUrl = 'http://dashboards.grafana.com/dashboard/script/my-dash.js';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).to.contain(base + params);
});
it('should remove panel id when no panel in scope', function() {
ctx.$location.path('/test');
ctx.scope.options.forCurrent = true;
ctx.scope.panel = null;
ctx.scope.init();
expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1');
});
it('should add theme when specified', function() {
ctx.$location.path('/test');
ctx.scope.options.theme = 'light';
ctx.scope.panel = null;
ctx.scope.init();
expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1&theme=light');
});
it('should remove fullscreen from image url when is first param in querystring and modeSharePanel is true', function() {
ctx.$location.url('/test?fullscreen&edit');
ctx.scope.modeSharePanel = true;
ctx.scope.panel = { id: 1 };
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).to.contain('?fullscreen&edit&from=1000&to=2000&orgId=1&panelId=1');
expect(ctx.scope.imageUrl).to.contain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
});
it('should remove edit from image url when is first param in querystring and modeSharePanel is true', function() {
ctx.$location.url('/test?edit&fullscreen');
ctx.scope.modeSharePanel = true;
ctx.scope.panel = { id: 1 };
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).to.contain('?edit&fullscreen&from=1000&to=2000&orgId=1&panelId=1');
expect(ctx.scope.imageUrl).to.contain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
});
it('should include template variables in url', function() {
ctx.$location.path('/test');
ctx.scope.options.includeTemplateVars = true;
ctx.templateSrv.fillVariableValuesForUrl = function(params) {
params['var-app'] = 'mupp';
params['var-server'] = 'srv-01';
};
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).to.be(
'http://server/#!/test?from=1000&to=2000&orgId=1&var-app=mupp&var-server=srv-01'
);
});
});
});

View File

@@ -144,8 +144,8 @@ export class DashLinksContainerCtrl {
if (dash.id !== currentDashId) {
memo.push({
title: dash.title,
url: 'dashboard/' + dash.uri,
target: link.target,
url: dash.url,
target: link.target === '_self' ? '' : link.target,
icon: 'fa fa-th-large',
keepTime: link.keepTime,
includeVars: link.includeVars,

View File

@@ -3,53 +3,74 @@
<div class="page-container page-body">
<h3 class="page-sub-heading">User Profile</h3>
<form name="ctrl.userForm" class="gf-form-group">
<form name="ctrl.userForm" class="gf-form-group">
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Name</span>
<input class="gf-form-input max-width-22" type="text" required ng-model="ctrl.user.name" >
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Email</span>
<input class="gf-form-input max-width-22" type="email" ng-readonly="ctrl.readonlyLoginFields" required ng-model="ctrl.user.email">
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Name</span>
<input class="gf-form-input max-width-22" type="text" required ng-model="ctrl.user.name">
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Email</span>
<input class="gf-form-input max-width-22" type="email" ng-readonly="ctrl.readonlyLoginFields" required ng-model="ctrl.user.email">
<i ng-if="ctrl.readonlyLoginFields" class="fa fa-lock gf-form-icon--right-absolute" bs-tooltip="'Login Details Locked - managed in another system.'"></i>
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Username</span>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Username</span>
<input class="gf-form-input max-width-22" type="text" ng-readonly="ctrl.readonlyLoginFields" required ng-model="ctrl.user.login">
<i ng-if="ctrl.readonlyLoginFields" class="fa fa-lock gf-form-icon--right-absolute" bs-tooltip="'Login Details Locked - managed in another system.'"></i>
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
</div>
</form>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
</div>
</form>
<prefs-control mode="user"></prefs-control>
<prefs-control mode="user"></prefs-control>
<h3 class="page-heading" ng-show="ctrl.showOrgsList">Organizations</h3>
<h3 class="page-heading" ng-show="ctrl.showTeamsList">Teams</h3>
<div class="gf-form-group" ng-show="ctrl.showTeamsList">
<table class="filter-table form-inline">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Email</th>
<th>Members</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="team in ctrl.teams">
<td class="width-4 text-center"><img class="filter-table__avatar" src={{team.avatarUrl}}></td>
<td>{{team.name}}</td>
<td>{{team.email}}</td>
<td>{{team.memberCount}}</td>
</tr>
</tbody>
</table>
</div>
<h3 class="page-heading" ng-show="ctrl.showOrgsList">Organizations</h3>
<div class="gf-form-group" ng-show="ctrl.showOrgsList">
<table class="filter-table form-inline">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="org in ctrl.orgs">
<td>{{org.name}}</td>
<td>{{org.role}}</td>
<td class="text-right">
<span class="btn btn-primary btn-mini" ng-show="org.orgId === contextSrv.user.orgId">
Current
</span>
<a ng-click="ctrl.setUsingOrg(org)" class="btn btn-inverse btn-mini" ng-show="org.orgId !== contextSrv.user.orgId">
Select
</a>
</td>
</tr>
</tbody>
</table>
</div>
<table class="filter-table form-inline">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="org in ctrl.orgs">
<td>{{org.name}}</td>
<td>{{org.role}}</td>
<td class="text-right">
<span class="btn btn-primary btn-mini" ng-show="org.orgId === contextSrv.user.orgId">
Current
</span>
<a ng-click="ctrl.setUsingOrg(org)" class="btn btn-inverse btn-mini" ng-show="org.orgId !== contextSrv.user.orgId">
Select
</a>
</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -4,8 +4,10 @@ import { coreModule } from 'app/core/core';
export class ProfileCtrl {
user: any;
old_theme: any;
teams: any = [];
orgs: any = [];
userForm: any;
showTeamsList = false;
showOrgsList = false;
readonlyLoginFields = config.disableLoginForm;
navModel: any;
@@ -13,6 +15,7 @@ export class ProfileCtrl {
/** @ngInject **/
constructor(private backendSrv, private contextSrv, private $location, navModelSrv) {
this.getUser();
this.getUserTeams();
this.getUserOrgs();
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
}
@@ -24,6 +27,13 @@ export class ProfileCtrl {
});
}
getUserTeams() {
this.backendSrv.get('/api/user/teams').then(teams => {
this.teams = teams;
this.showTeamsList = this.teams.length > 0;
});
}
getUserOrgs() {
this.backendSrv.get('/api/user/orgs').then(orgs => {
this.orgs = orgs;

View File

@@ -77,6 +77,10 @@ export class LinkSrv {
info.target = link.targetBlank ? '_blank' : '_self';
info.href = this.templateSrv.replace(link.url || '', scopedVars);
info.title = this.templateSrv.replace(link.title || '', scopedVars);
} else if (link.url) {
info.href = link.url;
info.title = this.templateSrv.replace(link.title || '', scopedVars);
info.target = link.targetBlank ? '_blank' : '';
} else if (link.dashUri) {
info.href = 'dashboard/' + link.dashUri + '?';
info.title = this.templateSrv.replace(link.title || '', scopedVars);

View File

@@ -39,7 +39,12 @@ export class PanelLinksEditorCtrl {
backendSrv.search({ query: link.dashboard }).then(function(hits) {
var dashboard = _.find(hits, { title: link.dashboard });
if (dashboard) {
link.dashUri = dashboard.uri;
if (dashboard.url) {
link.url = dashboard.url;
} else {
// To support legacy url's
link.dashUri = dashboard.uri;
}
link.title = dashboard.title;
}
});

View File

@@ -213,7 +213,7 @@ export class QueryVariable implements Variable {
}
dependsOn(variable) {
return containsVariable(this.query, this.datasource, variable.name);
return containsVariable(this.query, this.datasource, this.regex, variable.name);
}
}

View File

@@ -51,12 +51,16 @@
<i class="btn-service-icon fa fa-github"></i>
Sign in with GitHub
</a>
<a class="btn btn-medium btn-inverse btn-service btn-service--grafanacom login-btn" href="login/grafana_com" target="_self"
<a class="btn btn-medium btn-service btn-service--gitlab login-btn" href="login/gitlab" target="_self" ng-if="oauth.gitlab">
<i class="btn-service-icon fa fa-gitlab"></i>
Sign in with GitLab
</a>
<a class="btn btn-medium btn-service btn-service--grafanacom login-btn" href="login/grafana_com" target="_self"
ng-if="oauth.grafana_com">
<i class="btn-service-icon"></i>
Sign in with Grafana.com
</a>
<a class="btn btn-medium btn-inverse btn-service btn-service--oauth login-btn" href="login/generic_oauth" target="_self"
<a class="btn btn-medium btn-service btn-service--oauth login-btn" href="login/generic_oauth" target="_self"
ng-if="oauth.generic_oauth">
<i class="btn-service-icon fa fa-sign-in"></i>
Sign in with {{oauth.generic_oauth.name}}

View File

@@ -18,18 +18,18 @@
<span class="gf-form-label width-9">For each value of</span>
<dash-repeat-option panel="ctrl.panel"></dash-repeat-option>
</div>
<div class="gf-form" ng-show="ctrl.panel.repeat">
<span class="gf-form-label width-9">Min width</span>
<select class="gf-form-input" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]">
<option value=""></option>
</select>
</div>
<div class="gf-form" ng-show="ctrl.panel.repeat">
<span class="gf-form-label width-9">Direction</span>
<select class="gf-form-input" ng-model="ctrl.panel.repeatDirection" ng-options="f.value as f.text for f in [{value: 'v', text: 'Vertical'}, {value: 'h', text: 'Horizontal'}]">
<option value=""></option>
</select>
</div>
<div class="gf-form" ng-show="ctrl.panel.repeat && ctrl.panel.repeatDirection == 'h'">
<span class="gf-form-label width-9">Min width</span>
<select class="gf-form-input" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]">
<option value=""></option>
</select>
</div>
</div>
<panel-links-editor panel="ctrl.panel"></panel-links-editor>

View File

@@ -973,13 +973,12 @@ export class FuncInstance {
} else if (_.get(_.last(this.def.params), 'multiple')) {
paramType = _.get(_.last(this.def.params), 'type');
}
if (paramType === 'value_or_series') {
// param types that should never be quoted
if (_.includes(['value_or_series', 'boolean', 'int', 'float', 'node'], paramType)) {
return value;
}
if (paramType === 'boolean' && _.includes(['true', 'false'], value)) {
return value;
}
if (_.includes(['int', 'float', 'int_or_interval', 'node_or_tag', 'node'], paramType) && _.isFinite(+value)) {
// param types that might be quoted
if (_.includes(['int_or_interval', 'node_or_tag'], paramType) && _.isFinite(+value)) {
return _.toString(+value);
}
return "'" + value + "'";

View File

@@ -55,6 +55,24 @@ describe('when rendering func instance', function() {
expect(func.render('hello')).toEqual("movingMedian(hello, '5min')");
});
it('should never quote boolean paramater', function() {
var func = gfunc.createFuncInstance('sortByName');
func.params[0] = '$natural';
expect(func.render('hello')).toEqual('sortByName(hello, $natural)');
});
it('should never quote int paramater', function() {
var func = gfunc.createFuncInstance('maximumAbove');
func.params[0] = '$value';
expect(func.render('hello')).toEqual('maximumAbove(hello, $value)');
});
it('should never quote node paramater', function() {
var func = gfunc.createFuncInstance('aliasByNode');
func.params[0] = '$node';
expect(func.render('hello')).toEqual('aliasByNode(hello, $node)');
});
it('should handle metric param and int param and string param', function() {
var func = gfunc.createFuncInstance('groupByNode');
func.params[0] = 5;

Some files were not shown because too many files have changed in this diff Show More