Merge branch 'master' into redux-poc2

This commit is contained in:
Torkel Ödegaard
2018-09-07 14:49:05 +02:00
86 changed files with 1276 additions and 681 deletions

View File

@@ -16,7 +16,7 @@ export function parseTime(value, isUtc = false, asString = false) {
return value;
}
if (!isNaN(value)) {
const epoch = parseInt(value);
const epoch = parseInt(value, 10);
const m = isUtc ? moment.utc(epoch) : moment(epoch);
return asString ? m.format(DATE_FORMAT) : m;
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import renderer from 'react-test-renderer';
import CustomScrollbar from './CustomScrollbar';
describe('CustomScrollbar', () => {
it('renders correctly', () => {
const tree = renderer
.create(
<CustomScrollbar>
<p>Scrollable content</p>
</CustomScrollbar>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,47 @@
import React, { PureComponent } from 'react';
import Scrollbars from 'react-custom-scrollbars';
interface Props {
customClassName?: string;
autoHide?: boolean;
autoHideTimeout?: number;
autoHideDuration?: number;
hideTracksWhenNotNeeded?: boolean;
}
/**
* Wraps component into <Scrollbars> component from `react-custom-scrollbars`
*/
class CustomScrollbar extends PureComponent<Props> {
static defaultProps: Partial<Props> = {
customClassName: 'custom-scrollbars',
autoHide: true,
autoHideTimeout: 200,
autoHideDuration: 200,
hideTracksWhenNotNeeded: false,
};
render() {
const { customClassName, children, ...scrollProps } = this.props;
return (
<Scrollbars
className={customClassName}
autoHeight={true}
autoHeightMin={'100%'}
autoHeightMax={'100%'}
renderTrackHorizontal={props => <div {...props} className="track-horizontal" />}
renderTrackVertical={props => <div {...props} className="track-vertical" />}
renderThumbHorizontal={props => <div {...props} className="thumb-horizontal" />}
renderThumbVertical={props => <div {...props} className="thumb-vertical" />}
renderView={props => <div {...props} className="view" />}
{...scrollProps}
>
{children}
</Scrollbars>
);
}
}
export default CustomScrollbar;

View File

@@ -0,0 +1,86 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CustomScrollbar renders correctly 1`] = `
<div
className="custom-scrollbars"
style={
Object {
"height": "auto",
"maxHeight": "100%",
"minHeight": "100%",
"overflow": "hidden",
"position": "relative",
"width": "100%",
}
}
>
<div
className="view"
style={
Object {
"WebkitOverflowScrolling": "touch",
"bottom": undefined,
"left": undefined,
"marginBottom": 0,
"marginRight": 0,
"maxHeight": "calc(100% + 0px)",
"minHeight": "calc(100% + 0px)",
"overflow": "scroll",
"position": "relative",
"right": undefined,
"top": undefined,
}
}
>
<p>
Scrollable content
</p>
</div>
<div
className="track-horizontal"
style={
Object {
"display": "none",
"height": 6,
"opacity": 0,
"position": "absolute",
"transition": "opacity 200ms",
}
}
>
<div
className="thumb-horizontal"
style={
Object {
"display": "block",
"height": "100%",
"position": "relative",
}
}
/>
</div>
<div
className="track-vertical"
style={
Object {
"display": "none",
"opacity": 0,
"position": "absolute",
"transition": "opacity 200ms",
"width": 6,
}
}
>
<div
className="thumb-vertical"
style={
Object {
"display": "block",
"position": "relative",
"width": "100%",
}
}
/>
</div>
</div>
`;

View File

@@ -55,7 +55,7 @@ export function sqlPartEditorDirective($compile, templateSrv) {
}
function inputBlur($input, paramIndex) {
cancelBlur = setTimeout(function() {
cancelBlur = setTimeout(() => {
switchToLink($input, paramIndex);
}, 200);
}
@@ -95,20 +95,20 @@ export function sqlPartEditorDirective($compile, templateSrv) {
return;
}
const typeaheadSource = function(query, callback) {
const typeaheadSource = (query, callback) => {
if (param.options) {
let options = param.options;
if (param.type === 'int') {
options = _.map(options, function(val) {
options = _.map(options, val => {
return val.toString();
});
}
return options;
}
$scope.$apply(function() {
$scope.handleEvent({ $event: { name: 'get-param-options', param: param } }).then(function(result) {
const dynamicOptions = _.map(result, function(op) {
$scope.$apply(() => {
$scope.handleEvent({ $event: { name: 'get-param-options', param: param } }).then(result => {
const dynamicOptions = _.map(result, op => {
return op.value;
});
@@ -128,7 +128,7 @@ export function sqlPartEditorDirective($compile, templateSrv) {
source: typeaheadSource,
minLength: 0,
items: 1000,
updater: function(value) {
updater: value => {
if (value === part.params[paramIndex]) {
clearTimeout(cancelBlur);
$input.focus();
@@ -150,18 +150,18 @@ export function sqlPartEditorDirective($compile, templateSrv) {
}
}
$scope.showActionsMenu = function() {
$scope.showActionsMenu = () => {
$scope.handleEvent({ $event: { name: 'get-part-actions' } }).then(res => {
$scope.partActions = res;
});
};
$scope.triggerPartAction = function(action) {
$scope.triggerPartAction = action => {
$scope.handleEvent({ $event: { name: 'action', action: action } });
};
function addElementsAndCompile() {
_.each(partDef.params, function(param, index) {
_.each(partDef.params, (param, index) => {
if (param.optional && part.params.length <= index) {
return;
}

View File

@@ -22,6 +22,8 @@ export class Settings {
disableLoginForm: boolean;
defaultDatasource: string;
alertingEnabled: boolean;
alertingErrorOrTimeout: string;
alertingNoDataOrNullValues: string;
authProxyEnabled: boolean;
exploreEnabled: boolean;
ldapEnabled: boolean;

View File

@@ -14,8 +14,8 @@ export class Analytics {
});
const ga = ((window as any).ga =
(window as any).ga ||
//tslint:disable-next-line:only-arrow-functions
function() {
//tslint:disable-line:only-arrow-functions
(ga.q = ga.q || []).push(arguments);
});
ga.l = +new Date();

View File

@@ -52,8 +52,8 @@ function applied(fn, scope) {
if (fn.wrappedInApply) {
return fn;
}
//tslint:disable-next-line:only-arrow-functions
const wrapped: any = function() {
//tslint:disable-line:only-arrow-functions
const args = arguments;
const phase = scope.$root.$$phase;
if (phase === '$apply' || phase === '$digest') {

View File

@@ -111,7 +111,7 @@ export function describeTextRange(expr: any) {
const parts = /^now([-+])(\d+)(\w)/.exec(expr);
if (parts) {
const unit = parts[3];
const amount = parseInt(parts[2]);
const amount = parseInt(parts[2], 10);
const span = spans[unit];
if (span) {
opt.display = isLast ? 'Last ' : 'Next ';

View File

@@ -164,8 +164,8 @@ export class AlertTabCtrl {
alert.conditions.push(this.buildDefaultCondition());
}
alert.noDataState = alert.noDataState || 'no_data';
alert.executionErrorState = alert.executionErrorState || 'alerting';
alert.noDataState = alert.noDataState || config.alertingNoDataOrNullValues;
alert.executionErrorState = alert.executionErrorState || config.alertingErrorOrTimeout;
alert.frequency = alert.frequency || '60s';
alert.handler = alert.handler || 1;
alert.notifications = alert.notifications || [];

View File

@@ -70,7 +70,7 @@ export class TimeSrv {
}
if (!isNaN(value)) {
const epoch = parseInt(value);
const epoch = parseInt(value, 10);
return moment.utc(epoch);
}

View File

@@ -49,7 +49,7 @@ export class DashboardViewState {
getQueryStringState() {
const state = this.$location.search();
state.panelId = parseInt(state.panelId) || null;
state.panelId = parseInt(state.panelId, 10) || null;
state.fullscreen = state.fullscreen ? true : null;
state.edit = state.edit === 'true' || state.edit === true || null;
state.editview = state.editview || null;

View File

@@ -12,7 +12,7 @@ export class SoloPanelCtrl {
appEvents.emit('toggle-sidemenu-hidden');
const params = $location.search();
panelId = parseInt(params.panelId);
panelId = parseInt(params.panelId, 10);
$scope.onAppEvent('dashboard-initialized', $scope.initPanelScope);

View File

@@ -37,7 +37,7 @@ export class PlaylistEditCtrl {
filterFoundPlaylistItems() {
this.filteredDashboards = _.reject(this.dashboardresult, playlistItem => {
return _.find(this.playlistItems, listPlaylistItem => {
return parseInt(listPlaylistItem.value) === playlistItem.id;
return parseInt(listPlaylistItem.value, 10) === playlistItem.id;
});
});

View File

@@ -208,7 +208,7 @@ export class ElasticBucketAggCtrl {
const id = _.reduce(
$scope.target.bucketAggs.concat($scope.target.metrics),
(max, val) => {
return parseInt(val.id) > max ? parseInt(val.id) : max;
return parseInt(val.id, 10) > max ? parseInt(val.id, 10) : max;
},
0
);

View File

@@ -177,7 +177,7 @@ export class ElasticMetricAggCtrl {
const id = _.reduce(
$scope.target.bucketAggs.concat($scope.target.metrics),
(max, val) => {
return parseInt(val.id) > max ? parseInt(val.id) : max;
return parseInt(val.id, 10) > max ? parseInt(val.id, 10) : max;
},
0
);

View File

@@ -314,7 +314,7 @@ export default class InfluxDatasource {
const parts = /^now-(\d+)([d|h|m|s])$/.exec(date);
if (parts) {
const amount = parseInt(parts[1]);
const amount = parseInt(parts[1], 10);
const unit = parts[2];
return 'now() - ' + amount + unit;
}

View File

@@ -5,12 +5,14 @@ export class MssqlDatasource {
id: any;
name: any;
responseParser: ResponseParser;
interval: string;
/** @ngInject */
constructor(instanceSettings, private backendSrv, private $q, private templateSrv) {
this.name = instanceSettings.name;
this.id = instanceSettings.id;
this.responseParser = new ResponseParser(this.$q);
this.interval = (instanceSettings.jsonData || {}).timeInterval;
}
interpolateVariable(value, variable) {

View File

@@ -29,6 +29,21 @@
</div>
</div>
<h3 class="page-heading">MSSQL details</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Min time interval</span>
<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="1m"></input>
<info-popover mode="right-absolute">
A lower limit for the auto group by time interval. Recommended to be set to write frequency,
for example <code>1m</code> if your data is written every minute.
</info-popover>
</div>
</div>
</div>
<div class="gf-form-group">
<div class="grafana-info-box">
<h5>User Permission</h5>

View File

@@ -17,5 +17,10 @@
"alerting": true,
"annotations": true,
"metrics": true
"metrics": true,
"queryOptions": {
"minInterval": true
}
}

View File

@@ -5,12 +5,14 @@ export class MysqlDatasource {
id: any;
name: any;
responseParser: ResponseParser;
interval: string;
/** @ngInject */
constructor(instanceSettings, private backendSrv, private $q, private templateSrv) {
this.name = instanceSettings.name;
this.id = instanceSettings.id;
this.responseParser = new ResponseParser(this.$q);
this.interval = (instanceSettings.jsonData || {}).timeInterval;
}
interpolateVariable(value, variable) {

View File

@@ -24,6 +24,21 @@
</div>
</div>
<h3 class="page-heading">MySQL details</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Min time interval</span>
<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="1m"></input>
<info-popover mode="right-absolute">
A lower limit for the auto group by time interval. Recommended to be set to write frequency,
for example <code>1m</code> if your data is written every minute.
</info-popover>
</div>
</div>
</div>
<div class="gf-form-group">
<div class="grafana-info-box">
<h5>User Permission</h5>

View File

@@ -18,5 +18,10 @@
"alerting": true,
"annotations": true,
"metrics": true
"metrics": true,
"queryOptions": {
"minInterval": true
}
}

View File

@@ -408,11 +408,11 @@ export default class OpenTsDatasource {
};
if (target.counterMax && target.counterMax.length) {
query.rateOptions.counterMax = parseInt(target.counterMax);
query.rateOptions.counterMax = parseInt(target.counterMax, 10);
}
if (target.counterResetValue && target.counterResetValue.length) {
query.rateOptions.resetValue = parseInt(target.counterResetValue);
query.rateOptions.resetValue = parseInt(target.counterResetValue, 10);
}
if (tsdbVersion >= 2) {

View File

@@ -8,6 +8,7 @@ export class PostgresDatasource {
jsonData: any;
responseParser: ResponseParser;
queryModel: PostgresQuery;
interval: string;
/** @ngInject */
constructor(instanceSettings, private backendSrv, private $q, private templateSrv, private timeSrv) {
@@ -16,6 +17,7 @@ export class PostgresDatasource {
this.jsonData = instanceSettings.jsonData;
this.responseParser = new ResponseParser(this.$q);
this.queryModel = new PostgresQuery({});
this.interval = (instanceSettings.jsonData || {}).timeInterval;
}
interpolateVariable(value, variable) {

View File

@@ -61,6 +61,16 @@
</label>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-9">Min time interval</span>
<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="1m"></input>
<info-popover mode="right-absolute">
A lower limit for the auto group by time interval. Recommended to be set to write frequency,
for example <code>1m</code> if your data is written every minute.
</info-popover>
</div>
</div>
<div class="grafana-info-box alert alert-info" ng-show="ctrl.showTimescaleDBHelp">
<div class="alert-body">
<p>

View File

@@ -18,6 +18,10 @@
"alerting": true,
"annotations": true,
"metrics": true
"metrics": true,
"queryOptions": {
"minInterval": true
}
}

View File

@@ -96,7 +96,7 @@ export class PostgresQueryCtrl extends QueryCtrl {
}
updateProjection() {
this.selectParts = _.map(this.target.select, function(parts: any) {
this.selectParts = _.map(this.target.select, (parts: any) => {
return _.map(parts, sqlPart.create).filter(n => n);
});
this.whereParts = _.map(this.target.where, sqlPart.create).filter(n => n);
@@ -104,15 +104,15 @@ export class PostgresQueryCtrl extends QueryCtrl {
}
updatePersistedParts() {
this.target.select = _.map(this.selectParts, function(selectParts) {
return _.map(selectParts, function(part: any) {
this.target.select = _.map(this.selectParts, selectParts => {
return _.map(selectParts, (part: any) => {
return { type: part.def.type, datatype: part.datatype, params: part.params };
});
});
this.target.where = _.map(this.whereParts, function(part: any) {
this.target.where = _.map(this.whereParts, (part: any) => {
return { type: part.def.type, datatype: part.datatype, name: part.name, params: part.params };
});
this.target.group = _.map(this.groupParts, function(part: any) {
this.target.group = _.map(this.groupParts, (part: any) => {
return { type: part.def.type, datatype: part.datatype, params: part.params };
});
}
@@ -355,7 +355,7 @@ export class PostgresQueryCtrl extends QueryCtrl {
switch (partType) {
case 'column':
const parts = _.map(selectParts, function(part: any) {
const parts = _.map(selectParts, (part: any) => {
return sqlPart.create({ type: part.def.type, params: _.clone(part.params) });
});
this.selectParts.push(parts);

View File

@@ -1,22 +1,22 @@
import PostgresQuery from '../postgres_query';
describe('PostgresQuery', function() {
describe('PostgresQuery', () => {
const templateSrv = {
replace: jest.fn(text => text),
};
describe('When initializing', function() {
it('should not be in SQL mode', function() {
describe('When initializing', () => {
it('should not be in SQL mode', () => {
const query = new PostgresQuery({}, templateSrv);
expect(query.target.rawQuery).toBe(false);
});
it('should be in SQL mode for pre query builder queries', function() {
it('should be in SQL mode for pre query builder queries', () => {
const query = new PostgresQuery({ rawSql: 'SELECT 1' }, templateSrv);
expect(query.target.rawQuery).toBe(true);
});
});
describe('When generating time column SQL', function() {
describe('When generating time column SQL', () => {
const query = new PostgresQuery({}, templateSrv);
query.target.timeColumn = 'time';
@@ -25,7 +25,7 @@ describe('PostgresQuery', function() {
expect(query.buildTimeColumn()).toBe('"time" AS "time"');
});
describe('When generating time column SQL with group by time', function() {
describe('When generating time column SQL with group by time', () => {
let query = new PostgresQuery(
{ timeColumn: 'time', group: [{ type: 'time', params: ['5m', 'none'] }] },
templateSrv
@@ -44,7 +44,7 @@ describe('PostgresQuery', function() {
expect(query.buildTimeColumn(false)).toBe('$__unixEpochGroup(time,5m)');
});
describe('When generating metric column SQL', function() {
describe('When generating metric column SQL', () => {
const query = new PostgresQuery({}, templateSrv);
query.target.metricColumn = 'host';
@@ -53,7 +53,7 @@ describe('PostgresQuery', function() {
expect(query.buildMetricColumn()).toBe('"host" AS metric');
});
describe('When generating value column SQL', function() {
describe('When generating value column SQL', () => {
const query = new PostgresQuery({}, templateSrv);
let column = [{ type: 'column', params: ['value'] }];
@@ -76,7 +76,7 @@ describe('PostgresQuery', function() {
);
});
describe('When generating value column SQL with metric column', function() {
describe('When generating value column SQL with metric column', () => {
const query = new PostgresQuery({}, templateSrv);
query.target.metricColumn = 'host';
@@ -110,7 +110,7 @@ describe('PostgresQuery', function() {
);
});
describe('When generating WHERE clause', function() {
describe('When generating WHERE clause', () => {
const query = new PostgresQuery({ where: [] }, templateSrv);
expect(query.buildWhereClause()).toBe('');
@@ -126,7 +126,7 @@ describe('PostgresQuery', function() {
expect(query.buildWhereClause()).toBe('\nWHERE\n $__timeFilter(t) AND\n v = 1');
});
describe('When generating GROUP BY clause', function() {
describe('When generating GROUP BY clause', () => {
const query = new PostgresQuery({ group: [], metricColumn: 'none' }, templateSrv);
expect(query.buildGroupClause()).toBe('');
@@ -136,7 +136,7 @@ describe('PostgresQuery', function() {
expect(query.buildGroupClause()).toBe('\nGROUP BY 1,2');
});
describe('When generating complete statement', function() {
describe('When generating complete statement', () => {
const target = {
timeColumn: 't',
table: 'table',

View File

@@ -37,7 +37,7 @@ export class ResultTransformer {
metricLabel = this.createMetricLabel(metricData.metric, options);
const stepMs = parseInt(options.step) * 1000;
const stepMs = parseInt(options.step, 10) * 1000;
let baseTimestamp = start * 1000;
if (metricData.values === undefined) {

View File

@@ -53,7 +53,7 @@ export class ThresholdManager {
function stopped() {
// calculate graph level
let graphValue = plot.c2p({ left: 0, top: posTop }).y;
graphValue = parseInt(graphValue.toFixed(0));
graphValue = parseInt(graphValue.toFixed(0), 10);
model.value = graphValue;
handleElem.off('mousemove', dragging);

View File

@@ -271,7 +271,7 @@ function pushToYBuckets(buckets, bucketNum, value, point, bounds) {
let count = 1;
// Use the 3rd argument as scale/count
if (point.length > 3) {
count = parseInt(point[2]);
count = parseInt(point[2], 10);
}
if (buckets[bucketNum]) {
buckets[bucketNum].values.push(value);

View File

@@ -493,7 +493,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
const bgColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
const fontScale = parseInt(panel.valueFontSize) / 100;
const fontScale = parseInt(panel.valueFontSize, 10) / 100;
const fontSize = Math.min(dimension / 5, 100) * fontScale;
// Reduce gauge width if threshold labels enabled
const gaugeWidthReduceRatio = panel.gauge.thresholdLabels ? 1.5 : 1;

View File

@@ -294,3 +294,46 @@
padding-top: 1px;
}
}
// Custom styles for 'react-custom-scrollbars'
.custom-scrollbars {
// Fix for Firefox. For some reason sometimes .view container gets a height of its content, but in order to
// make scroll working it should fit outer container size (scroll appears only when inner container size is
// greater than outer one).
display: flex;
flex-grow: 1;
.view {
display: flex;
flex-grow: 1;
}
.track-vertical {
border-radius: 3px;
width: 6px !important;
right: 2px;
bottom: 2px;
top: 2px;
}
.track-horizontal {
border-radius: 3px;
height: 6px !important;
right: 2px;
bottom: 2px;
left: 2px;
}
.thumb-vertical {
@include gradient-vertical($scrollbarBackground, $scrollbarBackground2);
border-radius: 6px;
}
.thumb-horizontal {
@include gradient-horizontal($scrollbarBackground, $scrollbarBackground2);
border-radius: 6px;
}
}