mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'upstream/develop' into feat-9887
This commit is contained in:
@@ -11,6 +11,7 @@ export class AlertListCtrl {
|
||||
stateFilters = [
|
||||
{text: 'All', value: null},
|
||||
{text: 'OK', value: 'ok'},
|
||||
{text: 'Not OK', value: 'not_ok'},
|
||||
{text: 'Alerting', value: 'alerting'},
|
||||
{text: 'No Data', value: 'no_data'},
|
||||
{text: 'Paused', value: 'paused'},
|
||||
|
||||
@@ -95,6 +95,7 @@ export class AlertTabCtrl {
|
||||
case "hipchat": return "fa fa-mail-forward";
|
||||
case "pushover": return "fa fa-mobile";
|
||||
case "kafka": return "fa fa-random";
|
||||
case "teams": return "fa fa-windows";
|
||||
}
|
||||
return 'fa fa-bell';
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function annotationTooltipDirective($sanitize, dashboardSrv, contextSrv,
|
||||
text = text + '<br />' + event.text;
|
||||
}
|
||||
} else if (title) {
|
||||
text = title + '<br />' + text;
|
||||
text = title + '<br />' + (_.isString(text) ? text : '');
|
||||
title = '';
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,27 @@ export class DashboardSrv {
|
||||
modalClass: 'modal--narrow'
|
||||
});
|
||||
}
|
||||
|
||||
starDashboard(dashboardId, isStarred) {
|
||||
let promise;
|
||||
|
||||
if (isStarred) {
|
||||
promise = this.backendSrv.delete('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
promise = this.backendSrv.post('/api/user/stars/dashboard/' + dashboardId).then(() => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(res => {
|
||||
if (this.dash && this.dash.id === dashboardId) {
|
||||
this.dash.meta.isStarred = res;
|
||||
}
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.service('dashboardSrv', DashboardSrv);
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
Back to dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li ng-if="!ctrl.dashboard.timepicker.hidden">
|
||||
<gf-time-picker dashboard="ctrl.dashboard"></gf-time-picker>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -49,14 +49,9 @@ export class DashNavCtrl {
|
||||
}
|
||||
|
||||
starDashboard() {
|
||||
if (this.dashboard.meta.isStarred) {
|
||||
return this.backendSrv.delete('/api/user/stars/dashboard/' + this.dashboard.id).then(() => {
|
||||
this.dashboard.meta.isStarred = false;
|
||||
});
|
||||
}
|
||||
|
||||
this.backendSrv.post('/api/user/stars/dashboard/' + this.dashboard.id).then(() => {
|
||||
this.dashboard.meta.isStarred = true;
|
||||
this.dashboardSrv.starDashboard(this.dashboard.id, this.dashboard.meta.isStarred)
|
||||
.then(newState => {
|
||||
this.dashboard.meta.isStarred = newState;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,9 @@ export class SaveDashboardAsModalCtrl {
|
||||
// do not want to create alert dupes
|
||||
if (dashboard.id > 0) {
|
||||
this.clone.panels.forEach(panel => {
|
||||
delete panel.thresholds;
|
||||
if (panel.type === "graph" && panel.alert) {
|
||||
delete panel.thresholds;
|
||||
}
|
||||
delete panel.alert;
|
||||
});
|
||||
}
|
||||
|
||||
62
public/app/features/dashboard/specs/save_as_modal.jest.ts
Normal file
62
public/app/features/dashboard/specs/save_as_modal.jest.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { SaveDashboardAsModalCtrl } from '../save_as_modal';
|
||||
import { describe, it, expect } from 'test/lib/common';
|
||||
|
||||
describe('saving dashboard as', () => {
|
||||
function scenario(name, panel, verify) {
|
||||
describe(name, () => {
|
||||
var json = {
|
||||
title: 'name',
|
||||
panels: [panel],
|
||||
};
|
||||
|
||||
var mockDashboardSrv = {
|
||||
getCurrent: function() {
|
||||
return {
|
||||
id: 5,
|
||||
meta: {},
|
||||
getSaveModelClone: function() {
|
||||
return json;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
var ctrl = new SaveDashboardAsModalCtrl(mockDashboardSrv);
|
||||
var ctx: any = {
|
||||
clone: ctrl.clone,
|
||||
ctrl: ctrl,
|
||||
panel: panel
|
||||
};
|
||||
|
||||
it('verify', () => {
|
||||
verify(ctx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
scenario('default values', {}, ctx => {
|
||||
var clone = ctx.clone;
|
||||
expect(clone.id).toBe(null);
|
||||
expect(clone.title).toBe('name Copy');
|
||||
expect(clone.editable).toBe(true);
|
||||
expect(clone.hideControls).toBe(false);
|
||||
});
|
||||
|
||||
var graphPanel = { id: 1, type: 'graph', alert: { rule: 1 }, thresholds: { value: 3000 } };
|
||||
|
||||
scenario('should remove alert from graph panel', graphPanel, ctx => {
|
||||
expect(ctx.panel.alert).toBe(undefined);
|
||||
});
|
||||
|
||||
scenario('should remove threshold from graph panel', graphPanel, ctx => {
|
||||
expect(ctx.panel.thresholds).toBe(undefined);
|
||||
});
|
||||
|
||||
scenario('singlestat should keep threshold', { id: 1, type: 'singlestat', thresholds: { value: 3000 } }, ctx => {
|
||||
expect(ctx.panel.thresholds).not.toBe(undefined);
|
||||
});
|
||||
|
||||
scenario('table should keep threshold', { id: 1, type: 'table', thresholds: { value: 3000 } }, ctx => {
|
||||
expect(ctx.panel.thresholds).not.toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import {describe, beforeEach, it, expect, sinon, angularMocks} from 'test/lib/co
|
||||
import helpers from 'test/specs/helpers';
|
||||
import '../shareModalCtrl';
|
||||
import config from 'app/core/config';
|
||||
import 'app/features/panellinks/linkSrv';
|
||||
import 'app/features/panellinks/link_srv';
|
||||
|
||||
describe('ShareModalCtrl', function() {
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import moment from 'moment';
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
|
||||
@@ -7,16 +5,16 @@ export function inputDateDirective() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function ($scope, $elem, attrs, ngModel) {
|
||||
link: function($scope, $elem, attrs, ngModel) {
|
||||
var format = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
var fromUser = function (text) {
|
||||
var fromUser = function(text) {
|
||||
if (text.indexOf('now') !== -1) {
|
||||
if (!dateMath.isValid(text)) {
|
||||
ngModel.$setValidity("error", false);
|
||||
ngModel.$setValidity('error', false);
|
||||
return undefined;
|
||||
}
|
||||
ngModel.$setValidity("error", true);
|
||||
ngModel.$setValidity('error', true);
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -28,15 +26,15 @@ export function inputDateDirective() {
|
||||
}
|
||||
|
||||
if (!parsed.isValid()) {
|
||||
ngModel.$setValidity("error", false);
|
||||
ngModel.$setValidity('error', false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
ngModel.$setValidity("error", true);
|
||||
ngModel.$setValidity('error', true);
|
||||
return parsed;
|
||||
};
|
||||
|
||||
var toUser = function (currentValue) {
|
||||
var toUser = function(currentValue) {
|
||||
if (moment.isMoment(currentValue)) {
|
||||
return currentValue.format(format);
|
||||
} else {
|
||||
@@ -46,7 +44,6 @@ export function inputDateDirective() {
|
||||
|
||||
ngModel.$parsers.push(fromUser);
|
||||
ngModel.$formatters.push(toUser);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<div class="editor-row">
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Auto-refresh</span>
|
||||
<input type="text" class="gf-form-input max-width-25" ng-model="ctrl.panel.refresh_intervals" array-join>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Now delay now-</span>
|
||||
<input type="text" class="gf-form-input max-width-25"
|
||||
ng-model="ctrl.panel.nowDelay" placeholder="0m"
|
||||
valid-time-span
|
||||
bs-tooltip="'Enter 1m to ignore the last minute (because it can contain incomplete metrics)'"
|
||||
data-placement="right">
|
||||
</div>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Auto-refresh</span>
|
||||
<input type="text" class="gf-form-input max-width-25" ng-model="ctrl.panel.refresh_intervals" array-join>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Now delay now-</span>
|
||||
<input type="text" class="gf-form-input max-width-25" ng-model="ctrl.panel.nowDelay" placeholder="0m" valid-time-span bs-tooltip="'Enter 1m to ignore the last minute (because it can contain incomplete metrics)'"
|
||||
data-placement="right">
|
||||
</div>
|
||||
<gf-form-switch class="gf-form" label="Hide time picker" checked="ctrl.panel.hidden" label-class="width-10"></gf-form-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,34 +1,38 @@
|
||||
<ul class="nav gf-timepicker-nav">
|
||||
|
||||
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time backward <br> (left arrow key)'" data-placement="bottom">
|
||||
<a ng-click='ctrl.move(-1)'><i class="fa fa-chevron-left"></i></a>
|
||||
</li>
|
||||
<li class="dashnav-zoom-out gf-timepicker-time-control" bs-tooltip="'Time range zoom out <br> CTRL+Z'" data-placement="bottom">
|
||||
<a ng-click='ctrl.zoom(2)'>Zoom Out</a></li>
|
||||
</li>
|
||||
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time forward <br> (right arrow key)'" data-placement="bottom">
|
||||
<a ng-click='ctrl.move(1)'><i class="fa fa-chevron-right"></i></a>
|
||||
</li>
|
||||
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time backward <br> (left arrow key)'" data-placement="bottom">
|
||||
<a ng-click='ctrl.move(-1)'>
|
||||
<i class="fa fa-chevron-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="dashnav-zoom-out gf-timepicker-time-control" bs-tooltip="'Time range zoom out <br> CTRL+Z'" data-placement="bottom">
|
||||
<a ng-click='ctrl.zoom(2)'>Zoom Out</a>
|
||||
</li>
|
||||
</li>
|
||||
<li class="dashnav-move-timeframe gf-timepicker-time-control" bs-tooltip="'Shift time forward <br> (right arrow key)'" data-placement="bottom">
|
||||
<a ng-click='ctrl.move(1)'>
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a bs-tooltip="ctrl.tooltip" data-placement="bottom" ng-click="ctrl.openDropdown()" class="gf-timepicker-nav-btn">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
<span ng-bind="ctrl.rangeString"></span>
|
||||
<span ng-show="ctrl.isUtc" class="gf-timepicker-utc">
|
||||
UTC
|
||||
</span>
|
||||
<li>
|
||||
<a bs-tooltip="ctrl.tooltip" data-placement="bottom" ng-click="ctrl.openDropdown()" class="gf-timepicker-nav-btn">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
<span ng-bind="ctrl.rangeString"></span>
|
||||
<span ng-show="ctrl.isUtc" class="gf-timepicker-utc">
|
||||
UTC
|
||||
</span>
|
||||
|
||||
<span ng-show="ctrl.dashboard.refresh" class="text-warning">
|
||||
|
||||
Refresh every {{ctrl.dashboard.refresh}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<span ng-show="ctrl.dashboard.refresh" class="text-warning">
|
||||
Refresh every {{ctrl.dashboard.refresh}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="dashnav-refresh-action">
|
||||
<a ng-click="ctrl.timeSrv.refreshDashboard()">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="dashnav-refresh-action">
|
||||
<a ng-click="ctrl.timeSrv.refreshDashboard()">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
</div>
|
||||
<div class="gf-form" ng-show="link.type === 'dashboards'">
|
||||
<span class="gf-form-label width-8">With tags</span>
|
||||
<bootstrap-tagsinput ng-model="link.tags" class="width-10" tagclass="label label-tag" placeholder="add tags" style="margin-right: .25rem"></bootstrap-tagsinput>
|
||||
<bootstrap-tagsinput ng-model="link.tags" tagclass="label label-tag" placeholder="add tags" style="margin-right: .25rem"></bootstrap-tagsinput>
|
||||
</div>
|
||||
<gf-form-switch ng-show="link.type === 'dashboards'" class="gf-form" label="As dropdown" checked="link.asDropdown" switch-class="max-width-4" label-class="width-8"></gf-form-switch>
|
||||
<gf-form-switch ng-show="link.type === 'dashboards'" class="gf-form" label="As dropdown" checked="link.asDropdown" switch-class="max-width-4" label-class="width-8" on-change="updated()"></gf-form-switch>
|
||||
<div class="gf-form" ng-show="link.type === 'dashboards' && link.asDropdown">
|
||||
<span class="gf-form-label width-8">Title</span>
|
||||
<input type="text" ng-model="link.title" class="gf-form-input max-width-10" ng-model-onblur ng-change="updated()">
|
||||
|
||||
@@ -226,7 +226,6 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
interval: this.interval,
|
||||
intervalMs: this.intervalMs,
|
||||
targets: this.panel.targets,
|
||||
format: this.panel.renderer === 'png' ? 'png' : 'json',
|
||||
maxDataPoints: this.resolution,
|
||||
scopedVars: scopedVars,
|
||||
cacheTimeout: this.panel.cacheTimeout
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'app/core/utils/kbn',
|
||||
],
|
||||
function (angular, _, kbn) {
|
||||
'use strict';
|
||||
|
||||
kbn = kbn.default;
|
||||
|
||||
angular
|
||||
.module('grafana.services')
|
||||
.service('linkSrv', function(templateSrv, timeSrv) {
|
||||
|
||||
this.getLinkUrl = function(link) {
|
||||
var url = templateSrv.replace(link.url || '');
|
||||
var params = {};
|
||||
|
||||
if (link.keepTime) {
|
||||
var range = timeSrv.timeRangeForUrl();
|
||||
params['from'] = range.from;
|
||||
params['to'] = range.to;
|
||||
}
|
||||
|
||||
if (link.includeVars) {
|
||||
templateSrv.fillVariableValuesForUrl(params);
|
||||
}
|
||||
|
||||
return this.addParamsToUrl(url, params);
|
||||
};
|
||||
|
||||
this.addParamsToUrl = function(url, params) {
|
||||
var paramsArray = [];
|
||||
_.each(params, function(value, key) {
|
||||
if (value === null) { return; }
|
||||
if (value === true) {
|
||||
paramsArray.push(key);
|
||||
}
|
||||
else if (_.isArray(value)) {
|
||||
_.each(value, function(instance) {
|
||||
paramsArray.push(key + '=' + encodeURIComponent(instance));
|
||||
});
|
||||
}
|
||||
else {
|
||||
paramsArray.push(key + '=' + encodeURIComponent(value));
|
||||
}
|
||||
});
|
||||
|
||||
if (paramsArray.length === 0) {
|
||||
return url;
|
||||
}
|
||||
|
||||
return this.appendToQueryString(url, paramsArray.join('&'));
|
||||
};
|
||||
|
||||
this.appendToQueryString = function(url, stringToAppend) {
|
||||
if (!_.isUndefined(stringToAppend) && stringToAppend !== null && stringToAppend !== '') {
|
||||
var pos = url.indexOf('?');
|
||||
if (pos !== -1) {
|
||||
if (url.length - pos > 1) {
|
||||
url += '&';
|
||||
}
|
||||
} else {
|
||||
url += '?';
|
||||
}
|
||||
url += stringToAppend;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
this.getAnchorInfo = function(link) {
|
||||
var info = {};
|
||||
info.href = this.getLinkUrl(link);
|
||||
info.title = templateSrv.replace(link.title || '');
|
||||
return info;
|
||||
};
|
||||
|
||||
this.getPanelLinkAnchorInfo = function(link, scopedVars) {
|
||||
var info = {};
|
||||
if (link.type === 'absolute') {
|
||||
info.target = link.targetBlank ? '_blank' : '_self';
|
||||
info.href = templateSrv.replace(link.url || '', scopedVars);
|
||||
info.title = templateSrv.replace(link.title || '', scopedVars);
|
||||
}
|
||||
else if (link.dashUri) {
|
||||
info.href = 'dashboard/' + link.dashUri + '?';
|
||||
info.title = templateSrv.replace(link.title || '', scopedVars);
|
||||
info.target = link.targetBlank ? '_blank' : '';
|
||||
}
|
||||
else {
|
||||
info.title = templateSrv.replace(link.title || '', scopedVars);
|
||||
var slug = kbn.slugifyForUrl(link.dashboard || '');
|
||||
info.href = 'dashboard/db/' + slug + '?';
|
||||
}
|
||||
|
||||
var params = {};
|
||||
|
||||
if (link.keepTime) {
|
||||
var range = timeSrv.timeRangeForUrl();
|
||||
params['from'] = range.from;
|
||||
params['to'] = range.to;
|
||||
}
|
||||
|
||||
if (link.includeVars) {
|
||||
templateSrv.fillVariableValuesForUrl(params, scopedVars);
|
||||
}
|
||||
|
||||
info.href = this.addParamsToUrl(info.href, params);
|
||||
|
||||
if (link.params) {
|
||||
info.href = this.appendToQueryString(info.href, templateSrv.replace(link.params, scopedVars));
|
||||
}
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
113
public/app/features/panellinks/link_srv.ts
Normal file
113
public/app/features/panellinks/link_srv.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
|
||||
export class LinkSrv {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private templateSrv, private timeSrv) {}
|
||||
|
||||
getLinkUrl(link) {
|
||||
var url = this.templateSrv.replace(link.url || '');
|
||||
var params = {};
|
||||
|
||||
if (link.keepTime) {
|
||||
var range = this.timeSrv.timeRangeForUrl();
|
||||
params['from'] = range.from;
|
||||
params['to'] = range.to;
|
||||
}
|
||||
|
||||
if (link.includeVars) {
|
||||
this.templateSrv.fillVariableValuesForUrl(params);
|
||||
}
|
||||
|
||||
return this.addParamsToUrl(url, params);
|
||||
}
|
||||
|
||||
addParamsToUrl(url, params) {
|
||||
var paramsArray = [];
|
||||
|
||||
_.each(params, function(value, key) {
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
if (value === true) {
|
||||
paramsArray.push(key);
|
||||
} else if (_.isArray(value)) {
|
||||
_.each(value, function(instance) {
|
||||
paramsArray.push(key + '=' + encodeURIComponent(instance));
|
||||
});
|
||||
} else {
|
||||
paramsArray.push(key + '=' + encodeURIComponent(value));
|
||||
}
|
||||
});
|
||||
|
||||
if (paramsArray.length === 0) {
|
||||
return url;
|
||||
}
|
||||
|
||||
return this.appendToQueryString(url, paramsArray.join('&'));
|
||||
}
|
||||
|
||||
appendToQueryString(url, stringToAppend) {
|
||||
if (!_.isUndefined(stringToAppend) && stringToAppend !== null && stringToAppend !== '') {
|
||||
var pos = url.indexOf('?');
|
||||
if (pos !== -1) {
|
||||
if (url.length - pos > 1) {
|
||||
url += '&';
|
||||
}
|
||||
} else {
|
||||
url += '?';
|
||||
}
|
||||
url += stringToAppend;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
getAnchorInfo(link) {
|
||||
var info: any = {};
|
||||
info.href = this.getLinkUrl(link);
|
||||
info.title = this.templateSrv.replace(link.title || '');
|
||||
return info;
|
||||
}
|
||||
|
||||
getPanelLinkAnchorInfo(link, scopedVars) {
|
||||
var info: any = {};
|
||||
if (link.type === 'absolute') {
|
||||
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.dashUri) {
|
||||
info.href = 'dashboard/' + link.dashUri + '?';
|
||||
info.title = this.templateSrv.replace(link.title || '', scopedVars);
|
||||
info.target = link.targetBlank ? '_blank' : '';
|
||||
} else {
|
||||
info.title = this.templateSrv.replace(link.title || '', scopedVars);
|
||||
var slug = kbn.slugifyForUrl(link.dashboard || '');
|
||||
info.href = 'dashboard/db/' + slug + '?';
|
||||
}
|
||||
|
||||
var params = {};
|
||||
|
||||
if (link.keepTime) {
|
||||
var range = this.timeSrv.timeRangeForUrl();
|
||||
params['from'] = range.from;
|
||||
params['to'] = range.to;
|
||||
}
|
||||
|
||||
if (link.includeVars) {
|
||||
this.templateSrv.fillVariableValuesForUrl(params, scopedVars);
|
||||
}
|
||||
|
||||
info.href = this.addParamsToUrl(info.href, params);
|
||||
|
||||
if (link.params) {
|
||||
info.href = this.appendToQueryString(info.href, this.templateSrv.replace(link.params, scopedVars));
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('grafana.services').service('linkSrv', LinkSrv);
|
||||
@@ -1,7 +1,7 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'./linkSrv',
|
||||
'./link_srv',
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
47
public/app/features/panellinks/specs/link_srv.jest.ts
Normal file
47
public/app/features/panellinks/specs/link_srv.jest.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { LinkSrv } from '../link_srv';
|
||||
import _ from 'lodash';
|
||||
|
||||
jest.mock('angular', () => {
|
||||
let AngularJSMock = require('test/mocks/angular');
|
||||
return new AngularJSMock();
|
||||
});
|
||||
|
||||
describe('linkSrv', function() {
|
||||
var linkSrv;
|
||||
var templateSrvMock = {};
|
||||
var timeSrvMock = {};
|
||||
|
||||
beforeEach(() => {
|
||||
linkSrv = new LinkSrv(templateSrvMock, timeSrvMock);
|
||||
});
|
||||
|
||||
describe('when appending query strings', function() {
|
||||
it('add ? to URL if not present', function() {
|
||||
var url = linkSrv.appendToQueryString('http://example.com', 'foo=bar');
|
||||
expect(url).toBe('http://example.com?foo=bar');
|
||||
});
|
||||
|
||||
it('do not add & to URL if ? is present but query string is empty', function() {
|
||||
var url = linkSrv.appendToQueryString('http://example.com?', 'foo=bar');
|
||||
expect(url).toBe('http://example.com?foo=bar');
|
||||
});
|
||||
|
||||
it('add & to URL if query string is present', function() {
|
||||
var url = linkSrv.appendToQueryString('http://example.com?foo=bar', 'hello=world');
|
||||
expect(url).toBe('http://example.com?foo=bar&hello=world');
|
||||
});
|
||||
|
||||
it('do not change the URL if there is nothing to append', function() {
|
||||
_.each(['', undefined, null], function(toAppend) {
|
||||
var url1 = linkSrv.appendToQueryString('http://example.com', toAppend);
|
||||
expect(url1).toBe('http://example.com');
|
||||
|
||||
var url2 = linkSrv.appendToQueryString('http://example.com?', toAppend);
|
||||
expect(url2).toBe('http://example.com?');
|
||||
|
||||
var url3 = linkSrv.appendToQueryString('http://example.com?foo=bar', toAppend);
|
||||
expect(url3).toBe('http://example.com?foo=bar');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
|
||||
import 'app/features/panellinks/linkSrv';
|
||||
import _ from 'lodash';
|
||||
|
||||
describe('linkSrv', function() {
|
||||
var _linkSrv;
|
||||
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
|
||||
beforeEach(angularMocks.inject(function(linkSrv) {
|
||||
_linkSrv = linkSrv;
|
||||
}));
|
||||
|
||||
describe('when appending query strings', function() {
|
||||
|
||||
it('add ? to URL if not present', function() {
|
||||
var url = _linkSrv.appendToQueryString('http://example.com', 'foo=bar');
|
||||
expect(url).to.be('http://example.com?foo=bar');
|
||||
});
|
||||
|
||||
it('do not add & to URL if ? is present but query string is empty', function() {
|
||||
var url = _linkSrv.appendToQueryString('http://example.com?', 'foo=bar');
|
||||
expect(url).to.be('http://example.com?foo=bar');
|
||||
});
|
||||
|
||||
it('add & to URL if query string is present', function() {
|
||||
var url = _linkSrv.appendToQueryString('http://example.com?foo=bar', 'hello=world');
|
||||
expect(url).to.be('http://example.com?foo=bar&hello=world');
|
||||
});
|
||||
|
||||
it('do not change the URL if there is nothing to append', function() {
|
||||
_.each(['', undefined, null], function(toAppend) {
|
||||
var url1 = _linkSrv.appendToQueryString('http://example.com', toAppend);
|
||||
expect(url1).to.be('http://example.com');
|
||||
|
||||
var url2 = _linkSrv.appendToQueryString('http://example.com?', toAppend);
|
||||
expect(url2).to.be('http://example.com?');
|
||||
|
||||
var url3 = _linkSrv.appendToQueryString('http://example.com?foo=bar', toAppend);
|
||||
expect(url3).to.be('http://example.com?foo=bar');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -157,6 +157,10 @@ export class DataSourceEditCtrl {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.current.readOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.current.id) {
|
||||
return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then((result) => {
|
||||
this.current = result.datasource;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
<div class="page-header">
|
||||
<page-h1 model="ctrl.navModel"></page-h1>
|
||||
|
||||
<div ng-if="ctrl.current.readOnly" class="grafana-info-box span8">Disclaimer. This datasource was added by config and cannot be modified using the UI. Please contact your server admin to update this datasource.</div>
|
||||
|
||||
<div class="page-header-tabs" ng-show="ctrl.hasDashboards">
|
||||
<ul class="gf-tabs">
|
||||
<li class="gf-tabs-item">
|
||||
@@ -21,7 +23,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div ng-if="ctrl.tabIndex === 0" class="tab-content">
|
||||
|
||||
<form name="ctrl.editForm" ng-if="ctrl.current">
|
||||
@@ -71,8 +72,8 @@
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-success" ng-click="ctrl.saveChanges()">Save</button>
|
||||
<button type="submit" class="btn btn-danger" ng-show="!ctrl.isNew" ng-click="ctrl.delete()">
|
||||
<button type="submit" class="btn btn-success" ng-disabled="ctrl.current.readOnly" ng-click="ctrl.saveChanges()">Save</button>
|
||||
<button type="submit" class="btn btn-danger" ng-disabled="ctrl.current.readOnly" ng-show="!ctrl.isNew" ng-click="ctrl.delete()">
|
||||
Delete
|
||||
</button>
|
||||
<a class="btn btn-link" href="datasources">Cancel</a>
|
||||
@@ -87,4 +88,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -40,7 +40,10 @@ System.config({
|
||||
css: 'vendor/plugin-css/css.js'
|
||||
},
|
||||
meta: {
|
||||
'*': {esModule: true}
|
||||
'*': {
|
||||
esModule: true,
|
||||
authorization: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user