mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'origin/master' into davkal/explore-rate-hinting
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -331,7 +331,7 @@ class QueryField extends React.Component<TypeaheadFieldProps, TypeaheadFieldStat
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Enter':
|
||||
case 'Tab': {
|
||||
if (this.menuEl) {
|
||||
// Dont blur input
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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': [
|
||||
|
||||
@@ -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';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
150
public/app/features/dashboard/specs/share_modal_ctrl.jest.ts
Normal file
150
public/app/features/dashboard/specs/share_modal_ctrl.jest.ts
Normal 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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 + "'";
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user