merge master

This commit is contained in:
ryan
2019-03-12 16:57:30 -07:00
54 changed files with 366 additions and 276 deletions
+3
View File
@@ -5,12 +5,15 @@
### Minor
* **Cloudwatch**: Add AWS RDS MaximumUsedTransactionIDs metric [#15077](https://github.com/grafana/grafana/pull/15077), thx [@activeshadow](https://github.com/activeshadow)
* **Heatmap**: `Middle` bucket bound option [#15683](https://github.com/grafana/grafana/issues/15683)
* **Heatmap**: `Reverse order` option for changing order of buckets [#15683](https://github.com/grafana/grafana/issues/15683)
### Bug Fixes
* **Api**: Invalid org invite code [#10506](https://github.com/grafana/grafana/issues/10506)
* **Datasource**: Handles nil jsondata field gracefully [#14239](https://github.com/grafana/grafana/issues/14239)
* **Gauge**: Interpolate scoped variables in repeated gauges [#15739](https://github.com/grafana/grafana/issues/15739)
* **Datasource**: Empty user/password was not updated when updating datasources [#15608](https://github.com/grafana/grafana/pull/15608), thx [@Maddin-619](https://github.com/Maddin-619)
* **Heatmap**: legend shows wrong colors for small values [#14019](https://github.com/grafana/grafana/issues/14019)
# 6.0.1 (2019-03-06)
+1
View File
@@ -17,6 +17,7 @@
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.1.0",
"@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1",
"@types/angular": "^1.6.6",
"@types/chalk": "^2.2.0",
"@types/classnames": "^2.2.6",
"@types/commander": "^2.12.2",
@@ -1,5 +1,5 @@
.form-field {
margin-bottom: $gf-form-margin;
margin-bottom: $space-xxs;
display: flex;
flex-direction: row;
align-items: center;
@@ -22,13 +22,12 @@ const FONT_SCALE = 1;
export class Gauge extends PureComponent<Props> {
canvasElement: any;
static defaultProps = {
static defaultProps: Partial<Props> = {
maxValue: 100,
minValue: 0,
showThresholdMarkers: true,
showThresholdLabels: false,
thresholds: [],
theme: GrafanaThemeType.Dark,
};
componentDidMount() {
@@ -76,7 +75,7 @@ export class Gauge extends PureComponent<Props> {
const fontSize = Math.min(dimension / 5, 100) * this.getFontScale(value.text.length);
const thresholdLabelFontSize = fontSize / 2.5;
const options = {
const options: any = {
series: {
gauges: {
gauge: {
@@ -3,7 +3,7 @@ $select-input-bg-disabled: $input-bg-disabled;
@mixin select-control() {
width: 100%;
margin-right: $gf-form-margin;
margin-right: $space-xs;
@include border-radius($input-border-radius-sm);
background-color: $input-bg;
}
@@ -17,7 +17,13 @@ $enable-hover-media-query: false !default;
// Control the default styling of most Bootstrap elements by modifying these
// variables. Mostly focused on spacing.
$spacer: ${theme.spacing.m} !default;
$space-xxs: ${theme.spacing.xxs} !default;
$space-xs: ${theme.spacing.xs} !default;
$space-sm: ${theme.spacing.sm} !default;
$space-md: ${theme.spacing.md} !default;
$space-lg: ${theme.spacing.lg} !default;
$space-xl: ${theme.spacing.xl} !default;
$spacer: ${theme.spacing.d} !default;
$spacer-x: $spacer !default;
$spacer-y: $spacer !default;
$spacers: (
@@ -46,7 +52,7 @@ $spacers: (
),
),
) !default;
$border-width: ${theme.border.width.s} !default;
$border-width: ${theme.border.width.sm} !default;
// Grid breakpoints
//
@@ -55,9 +61,9 @@ $border-width: ${theme.border.width.s} !default;
$grid-breakpoints: (
xs: ${theme.breakpoints.xs},
sm: ${theme.breakpoints.s},
md: ${theme.breakpoints.m},
lg: ${theme.breakpoints.l},
sm: ${theme.breakpoints.sm},
md: ${theme.breakpoints.md},
lg: ${theme.breakpoints.lg},
xl: ${theme.breakpoints.xl},
) !default;
@@ -91,12 +97,12 @@ $font-family-base: $font-family-sans-serif !default;
$font-size-root: ${theme.typography.size.root} !default;
$font-size-base: ${theme.typography.size.base} !default;
$font-size-lg: ${theme.typography.size.l} !default;
$font-size-md: ${theme.typography.size.m} !default;
$font-size-sm: ${theme.typography.size.s} !default;
$font-size-lg: ${theme.typography.size.lg} !default;
$font-size-md: ${theme.typography.size.md} !default;
$font-size-sm: ${theme.typography.size.sm} !default;
$font-size-xs: ${theme.typography.size.xs} !default;
$line-height-base: ${theme.typography.lineHeight.l} !default;
$line-height-base: ${theme.typography.lineHeight.lg} !default;
$font-weight-semi-bold: ${theme.typography.weight.semibold};
$font-size-h1: ${theme.typography.heading.h1} !default;
@@ -106,10 +112,9 @@ $font-size-h4: ${theme.typography.heading.h4} !default;
$font-size-h5: ${theme.typography.heading.h5} !default;
$font-size-h6: ${theme.typography.heading.h6} !default;
$headings-margin-bottom: ($spacer / 2) !default;
$headings-font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
$headings-font-weight: ${theme.typography.weight.normal} !default;
$headings-line-height: ${theme.typography.lineHeight.s} !default;
$headings-line-height: ${theme.typography.lineHeight.sm} !default;
$hr-border-width: $border-width !default;
$dt-font-weight: bold !default;
@@ -160,7 +165,6 @@ $input-padding-y-lg: 10px !default;
$input-height: 35px !default;
$gf-form-margin: 3px;
$gf-form-input-height: 35px;
$cursor-disabled: not-allowed !default;
@@ -207,8 +211,7 @@ $btn-semi-transparent: rgba(0, 0, 0, 0.2) !default;
$side-menu-width: 60px;
// dashboard
$panel-margin: 10px;
$dashboard-padding: $panel-margin * 2;
$dashboard-padding: 10px * 2;
$panel-horizontal-padding: 10;
$panel-vertical-padding: 5;
$panel-padding: 0px $panel-horizontal-padding + 0px $panel-vertical-padding + 0px $panel-horizontal-padding + 0px;
@@ -243,9 +246,4 @@ $external-services: (
icon: '',
),
) !default;
:export {
panelhorizontalpadding: $panel-horizontal-padding;
panelverticalpadding: $panel-vertical-padding;
}
`;
+23 -16
View File
@@ -11,9 +11,9 @@ const theme: GrafanaThemeCommons = {
root: '14px',
base: '13px',
xs: '10px',
s: '12px',
m: '14px',
l: '18px',
sm: '12px',
md: '14px',
lg: '18px',
},
heading: {
h1: '28px',
@@ -30,35 +30,42 @@ const theme: GrafanaThemeCommons = {
},
lineHeight: {
xs: 1,
s: 1.1,
m: 4 / 3,
l: 1.5,
sm: 1.1,
md: 4 / 3,
lg: 1.5,
},
},
breakpoints: {
xs: '0',
s: '544px',
m: '768px',
l: '992px',
sm: '544px',
md: '768px',
lg: '992px',
xl: '1200px',
},
spacing: {
xs: '0',
s: '3px',
m: '14px',
l: '21px',
d: '14px',
xxs: '2px',
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
gutter: '30px',
},
border: {
radius: {
xs: '2px',
s: '3px',
m: '5px',
sm: '3px',
md: '5px',
},
width: {
s: '1px',
sm: '1px',
},
},
panelPadding: {
horizontal: 10,
vertical: 5,
},
};
export default theme;
+22 -15
View File
@@ -8,9 +8,9 @@ export interface GrafanaThemeCommons {
// TODO: not sure if should be a part of theme
breakpoints: {
xs: string;
s: string;
m: string;
l: string;
sm: string;
md: string;
lg: string;
xl: string;
};
typography: {
@@ -22,9 +22,9 @@ export interface GrafanaThemeCommons {
root: string;
base: string;
xs: string;
s: string;
m: string;
l: string;
sm: string;
md: string;
lg: string;
};
weight: {
light: number;
@@ -33,9 +33,9 @@ export interface GrafanaThemeCommons {
};
lineHeight: {
xs: number; //1
s: number; //1.1
m: number; // 4/3
l: number; // 1.5
sm: number; //1.1
md: number; // 4/3
lg: number; // 1.5
};
// TODO: Refactor to use size instead of custom defs
heading: {
@@ -48,22 +48,29 @@ export interface GrafanaThemeCommons {
};
};
spacing: {
d: string;
xxs: string;
xs: string;
s: string;
m: string;
l: string;
sm: string;
md: string;
lg: string;
xl: string;
gutter: string;
};
border: {
radius: {
xs: string;
s: string;
m: string;
sm: string;
md: string;
};
width: {
s: string;
sm: string;
};
};
panelPadding: {
horizontal: number;
vertical: number;
};
}
export interface GrafanaTheme extends GrafanaThemeCommons {
@@ -10,13 +10,13 @@ coreModule.directive('jsonTree', [
startExpanded: '@',
rootName: '@',
},
link: (scope, elem) => {
link: (scope: any, elem) => {
const jsonExp = new JsonExplorer(scope.object, 3, {
animateOpen: true,
});
const html = jsonExp.render(true);
elem.html(html);
elem.replaceAll(html);
},
};
},
+1 -1
View File
@@ -14,7 +14,7 @@ coreModule.directive('giveFocus', () => {
}
setTimeout(() => {
element.focus();
const domEl = element[0];
const domEl: any = element[0];
if (domEl.setSelectionRange) {
const pos = element.val().length * 2;
domEl.setSelectionRange(pos, pos);
-15
View File
@@ -1,15 +0,0 @@
import kbn from '../utils/kbn';
describe('stringToJsRegex', () => {
it('should parse the valid regex value', () => {
const output = kbn.stringToJsRegex('/validRegexp/');
expect(output).toBeInstanceOf(RegExp);
});
it('should throw error on invalid regex value', () => {
const input = '/etc/hostname';
expect(() => {
kbn.stringToJsRegex(input);
}).toThrow();
});
});
@@ -20,7 +20,7 @@
.gicon {
font-size: 30px;
margin-right: $spacer;
margin-right: $space-md;
}
&:hover {
@@ -32,16 +32,16 @@
.add-panel-widget__title {
font-size: $font-size-md;
font-weight: $font-weight-semi-bold;
margin-right: $spacer * 2;
margin-right: $space-xl;
}
.add-panel-widget__link {
margin: 0 8px;
margin: 0 $space-sm;
width: 154px;
}
.add-panel-widget__icon {
margin-bottom: 8px;
margin-bottom: $space-sm;
.gicon {
color: white;
@@ -62,7 +62,7 @@
.add-panel-widget__create {
display: inherit;
margin-bottom: 24px;
margin-bottom: $space-lg;
// this is to have the big button appear centered
margin-top: 55px;
}
@@ -72,7 +72,7 @@
}
.add-panel-widget__action {
margin: 0 4px;
margin: 0 $space-xs;
}
.add-panel-widget__btn-container {
@@ -6,11 +6,6 @@ import { cleanUpDashboard } from '../state/actions';
import { getNoPayloadActionCreatorMock, NoPayloadActionCreatorMock } from 'app/core/redux';
import { DashboardRouteInfo, DashboardInitPhase } from 'app/types';
jest.mock('sass/_variables.generated.scss', () => ({
panelhorizontalpadding: 10,
panelVerticalPadding: 10,
}));
jest.mock('app/features/dashboard/components/DashboardSettings/SettingsCtrl', () => ({}));
interface ScenarioContext {
@@ -116,7 +116,7 @@ export class DataPanel extends Component<Props, State> {
this.setState({ loading: LoadingState.Loading });
try {
const ds = await this.dataSourceSrv.get(datasource);
const ds = await this.dataSourceSrv.get(datasource, scopedVars);
// TODO interpolate variables
const minInterval = this.props.minInterval || ds.interval;
@@ -1,10 +1,5 @@
import { PanelChrome } from './PanelChrome';
jest.mock('sass/_variables.generated.scss', () => ({
panelhorizontalpadding: 10,
panelVerticalPadding: 10,
}));
describe('PanelChrome', () => {
let chrome: PanelChrome;
@@ -14,6 +14,7 @@ import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary'
import { applyPanelTimeOverrides, snapshotDataToPanelData } from 'app/features/dashboard/utils/panel';
import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
import { profiler } from 'app/core/profiler';
import config from 'app/core/config';
// Types
import { DashboardModel, PanelModel } from '../state';
@@ -21,7 +22,6 @@ import { PanelPlugin } from 'app/types';
import { DataQueryResponse, TimeRange, LoadingState, PanelData, DataQueryError } from '@grafana/ui';
import { ScopedVars } from '@grafana/ui';
import variables from 'sass/_variables.generated.scss';
import templateSrv from 'app/features/templating/template_srv';
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
@@ -160,8 +160,8 @@ export class PanelChrome extends PureComponent<Props, State> {
panelData={panelData}
timeRange={timeRange}
options={panel.getOptions(plugin.exports.reactPanel.defaults)}
width={width - 2 * variables.panelhorizontalpadding}
height={height - PANEL_HEADER_HEIGHT - variables.panelverticalpadding}
width={width - 2 * config.theme.panelPadding.horizontal}
height={height - PANEL_HEADER_HEIGHT - config.theme.panelPadding.vertical}
renderCounter={renderCounter}
replaceVariables={this.replaceVariables}
/>
@@ -9,7 +9,7 @@ coreModule.directive('datasourceHttpSettings', () => {
},
templateUrl: 'public/app/features/datasources/partials/http_settings.html',
link: {
pre: ($scope, elem, attrs) => {
pre: ($scope: any, elem, attrs) => {
// do not show access option if direct access is disabled
$scope.showAccessOption = $scope.noDirectAccess !== 'true';
$scope.showAccessHelp = false;
@@ -81,7 +81,7 @@ class MetricsPanelCtrl extends PanelCtrl {
// load datasource service
this.datasourceSrv
.get(this.panel.datasource)
.get(this.panel.datasource, this.panel.scopedVars)
.then(this.updateTimeRange.bind(this))
.then(this.issueQueries.bind(this))
.then(this.handleQueryResult.bind(this))
+2 -2
View File
@@ -33,7 +33,7 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
template: panelTemplate,
transclude: true,
scope: { ctrl: '=' },
link: (scope, elem) => {
link: (scope: any, elem) => {
const panelContainer = elem.find('.panel-container');
const panelContent = elem.find('.panel-content');
const cornerInfoElem = elem.find('.panel-info-corner');
@@ -67,7 +67,7 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
// set initial transparency
if (ctrl.panel.transparent) {
transparentLastState = true;
panelContainer.addClass('panel-transparent', true);
panelContainer.addClass('panel-transparent');
}
// update scrollbar after mounting
@@ -7,7 +7,7 @@ import config from 'app/core/config';
import { importPluginModule } from './plugin_loader';
// Types
import { DataSourceApi, DataSourceSelectItem } from '@grafana/ui/src/types';
import { DataSourceApi, DataSourceSelectItem, ScopedVars } from '@grafana/ui/src/types';
export class DatasourceSrv {
datasources: { [name: string]: DataSourceApi };
@@ -21,12 +21,18 @@ export class DatasourceSrv {
this.datasources = {};
}
get(name?: string): Promise<DataSourceApi> {
get(name?: string, scopedVars?: ScopedVars): Promise<DataSourceApi> {
if (!name) {
return this.get(config.defaultDatasource);
}
name = this.templateSrv.replace(name);
// Interpolation here is to support template variable in data source selection
name = this.templateSrv.replace(name, scopedVars, (value, variable) => {
if (Array.isArray(value)) {
return value[0];
}
return value;
});
if (name === 'default') {
return this.get(config.defaultDatasource);
@@ -6,6 +6,8 @@ export class DatasourceVariable implements Variable {
query: string;
options: any;
current: any;
multi: boolean;
includeAll: boolean;
refresh: any;
skipUrlSync: boolean;
@@ -18,6 +20,8 @@ export class DatasourceVariable implements Variable {
regex: '',
options: [],
query: '',
multi: false,
includeAll: false,
refresh: 1,
skipUrlSync: false,
};
@@ -69,9 +73,16 @@ export class DatasourceVariable implements Variable {
}
this.options = options;
if (this.includeAll) {
this.addAllOption();
}
return this.variableSrv.validateVariableSelectionState(this);
}
addAllOption() {
this.options.unshift({ text: 'All', value: '$__all' });
}
dependsOn(variable) {
if (this.regex) {
return containsVariable(this.regex, variable.name);
@@ -84,6 +95,9 @@ export class DatasourceVariable implements Variable {
}
getValueForUrl() {
if (this.current.text === 'All') {
return 'All';
}
return this.current.value;
}
}
@@ -91,5 +105,6 @@ export class DatasourceVariable implements Variable {
variableTypes['datasource'] = {
name: 'Datasource',
ctor: DatasourceVariable,
supportsMulti: true,
description: 'Enabled you to dynamically switch the datasource for multiple panels',
};
@@ -1,5 +1,6 @@
import _ from 'lodash';
import TableModel from 'app/core/table_model';
import { TimeSeries } from '@grafana/ui';
export class ResultTransformer {
constructor(private templateSrv) {}
@@ -18,10 +19,10 @@ export class ResultTransformer {
];
} else if (prometheusResult && options.format === 'heatmap') {
let seriesList = [];
prometheusResult.sort(sortSeriesByLabel);
for (const metricData of prometheusResult) {
seriesList.push(this.transformMetricData(metricData, options, options.start, options.end));
}
seriesList.sort(sortSeriesByLabel);
seriesList = this.transformToHistogramOverTime(seriesList);
return seriesList;
} else if (prometheusResult) {
@@ -197,13 +198,13 @@ export class ResultTransformer {
}
}
function sortSeriesByLabel(s1, s2): number {
function sortSeriesByLabel(s1: TimeSeries, s2: TimeSeries): number {
let le1, le2;
try {
// fail if not integer. might happen with bad queries
le1 = parseHistogramLabel(s1.metric.le);
le2 = parseHistogramLabel(s2.metric.le);
le1 = parseHistogramLabel(s1.target);
le2 = parseHistogramLabel(s2.target);
} catch (err) {
console.log(err);
return 0;
@@ -2,28 +2,6 @@ import coreModule from 'app/core/core_module';
import _ from 'lodash';
import { FilterSegments, DefaultFilterValue } from './filter_segments';
export class StackdriverFilter {
/** @ngInject */
constructor() {
return {
templateUrl: 'public/app/plugins/datasource/stackdriver/partials/query.filter.html',
controller: 'StackdriverFilterCtrl',
controllerAs: 'ctrl',
bindToController: true,
restrict: 'E',
scope: {
labelData: '<',
loading: '<',
groupBys: '<',
filters: '<',
filtersChanged: '&',
groupBysChanged: '&',
hideGroupBys: '<',
},
};
}
}
export class StackdriverFilterCtrl {
defaultRemoveGroupByValue = '-- remove group by --';
resourceTypeValue = 'resource.type';
@@ -193,5 +171,24 @@ export class StackdriverFilterCtrl {
}
}
coreModule.directive('stackdriverFilter', StackdriverFilter);
coreModule.controller('StackdriverFilterCtrl', StackdriverFilterCtrl);
/** @ngInject */
function stackdriverFilter() {
return {
templateUrl: 'public/app/plugins/datasource/stackdriver/partials/query.filter.html',
controller: StackdriverFilterCtrl,
controllerAs: 'ctrl',
bindToController: true,
restrict: 'E',
scope: {
labelData: '<',
loading: '<',
groupBys: '<',
filters: '<',
filtersChanged: '&',
groupBysChanged: '&',
hideGroupBys: '<',
},
};
}
coreModule.directive('stackdriverFilter', stackdriverFilter);
@@ -32,6 +32,7 @@ export class AxesEditorCtrl {
Auto: 'auto',
Upper: 'upper',
Lower: 'lower',
Middle: 'middle',
};
}
@@ -11,6 +11,8 @@ const LEGEND_HEIGHT_PX = 6;
const LEGEND_WIDTH_PX = 100;
const LEGEND_TICK_SIZE = 0;
const LEGEND_VALUE_MARGIN = 0;
const LEGEND_PADDING_LEFT = 10;
const LEGEND_SEGMENT_WIDTH = 10;
/**
* Color legend for heatmap editor.
@@ -19,7 +21,7 @@ coreModule.directive('colorLegend', () => {
return {
restrict: 'E',
template: '<div class="heatmap-color-legend"><svg width="16.5rem" height="24px"></svg></div>',
link: (scope, elem, attrs) => {
link: (scope: any, elem, attrs) => {
const ctrl = scope.ctrl;
const panel = scope.ctrl.panel;
@@ -55,7 +57,7 @@ coreModule.directive('heatmapLegend', () => {
return {
restrict: 'E',
template: `<div class="heatmap-color-legend"><svg width="${LEGEND_WIDTH_PX}px" height="${LEGEND_HEIGHT_PX}px"></svg></div>`,
link: (scope, elem, attrs) => {
link: (scope: any, elem, attrs) => {
const ctrl = scope.ctrl;
const panel = scope.ctrl.panel;
@@ -95,27 +97,27 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal
const legendWidth = Math.floor(legendElem.outerWidth()) - 30;
const legendHeight = legendElem.attr('height');
let rangeStep = 1;
if (rangeTo - rangeFrom > legendWidth) {
rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth);
}
const rangeStep = ((rangeTo - rangeFrom) / legendWidth) * LEGEND_SEGMENT_WIDTH;
const widthFactor = legendWidth / (rangeTo - rangeFrom);
const valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
const colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue);
legend
.append('g')
.attr('class', 'legend-color-bar')
.attr('transform', 'translate(' + LEGEND_PADDING_LEFT + ',0)')
.selectAll('.heatmap-color-legend-rect')
.data(valuesRange)
.enter()
.append('rect')
.attr('x', d => d * widthFactor)
.attr('x', d => Math.round(d * widthFactor))
.attr('y', 0)
.attr('width', rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps
.attr('width', Math.round(rangeStep * widthFactor + 1)) // Overlap rectangles to prevent gaps
.attr('height', legendHeight)
.attr('stroke-width', 0)
.attr('fill', d => colorScale(d));
drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth);
drawLegendValues(elem, rangeFrom, rangeTo, maxValue, minValue, legendWidth, valuesRange);
}
function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue) {
@@ -126,31 +128,31 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue
const legendWidth = Math.floor(legendElem.outerWidth()) - 30;
const legendHeight = legendElem.attr('height');
let rangeStep = 1;
if (rangeTo - rangeFrom > legendWidth) {
rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth);
}
const rangeStep = ((rangeTo - rangeFrom) / legendWidth) * LEGEND_SEGMENT_WIDTH;
const widthFactor = legendWidth / (rangeTo - rangeFrom);
const valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
const opacityScale = getOpacityScale(options, maxValue, minValue);
legend
.append('g')
.attr('class', 'legend-color-bar')
.attr('transform', 'translate(' + LEGEND_PADDING_LEFT + ',0)')
.selectAll('.heatmap-opacity-legend-rect')
.data(valuesRange)
.enter()
.append('rect')
.attr('x', d => d * widthFactor)
.attr('x', d => Math.round(d * widthFactor))
.attr('y', 0)
.attr('width', rangeStep * widthFactor)
.attr('width', Math.round(rangeStep * widthFactor))
.attr('height', legendHeight)
.attr('stroke-width', 0)
.attr('fill', options.cardColor)
.style('opacity', d => opacityScale(d));
drawLegendValues(elem, opacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth);
drawLegendValues(elem, rangeFrom, rangeTo, maxValue, minValue, legendWidth, valuesRange);
}
function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) {
function drawLegendValues(elem, rangeFrom, rangeTo, maxValue, minValue, legendWidth, valuesRange) {
const legendElem = $(elem).find('svg');
const legend = d3.select(legendElem.get(0));
@@ -171,7 +173,7 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal
const colorRect = legendElem.find(':first-child');
const posY = getSvgElemHeight(legendElem) + LEGEND_VALUE_MARGIN;
const posX = getSvgElemX(colorRect);
const posX = getSvgElemX(colorRect) + LEGEND_PADDING_LEFT;
d3.select(legendElem.get(0))
.append('g')
@@ -34,6 +34,7 @@ const panelDefaults = {
},
dataFormat: 'timeseries',
yBucketBound: 'auto',
reverseYBuckets: false,
xAxis: {
show: true,
},
@@ -55,6 +56,7 @@ const panelDefaults = {
showHistogram: false,
},
highlightCards: true,
hideZeroBuckets: false,
};
const colorModes = ['opacity', 'spectrum'];
@@ -97,7 +99,7 @@ const colorSchemes = [
{ name: 'YlOrRd', value: 'interpolateYlOrRd', invert: 'dark' },
];
const dsSupportHistogramSort = ['prometheus', 'elasticsearch'];
const dsSupportHistogramSort = ['elasticsearch'];
export class HeatmapCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
@@ -108,7 +110,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
selectionActivated: boolean;
unitFormats: any;
data: any;
series: any;
series: any[];
timeSrv: any;
dataWarning: any;
decimals: number;
@@ -146,7 +148,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
}
onRender() {
if (!this.range) {
if (!this.range || !this.series) {
return;
}
@@ -204,7 +206,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
yBucketSize = 1;
}
const { cards, cardStats } = convertToCards(bucketsData);
const { cards, cardStats } = convertToCards(bucketsData, this.panel.hideZeroBuckets);
this.data = {
buckets: bucketsData,
@@ -225,13 +227,20 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
this.series.sort(sortSeriesByLabel);
}
if (this.panel.reverseYBuckets) {
this.series.reverse();
}
// Convert histogram to heatmap. Each histogram bucket represented by the series which name is
// a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as X axis labels.
// a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as Y axis labels.
bucketsData = histogramToHeatmap(this.series);
tsBuckets = _.map(this.series, 'label');
const yBucketBound = this.panel.yBucketBound;
if ((panelDatasource === 'prometheus' && yBucketBound !== 'lower') || yBucketBound === 'upper') {
if (
(panelDatasource === 'prometheus' && yBucketBound !== 'lower' && yBucketBound !== 'middle') ||
yBucketBound === 'upper'
) {
// Prometheus labels are upper inclusive bounds, so add empty bottom bucket label.
tsBuckets = [''].concat(tsBuckets);
} else {
@@ -246,7 +255,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
// Always let yBucketSize=1 in 'tsbuckets' mode
yBucketSize = 1;
const { cards, cardStats } = convertToCards(bucketsData);
const { cards, cardStats } = convertToCards(bucketsData, this.panel.hideZeroBuckets);
this.data = {
buckets: bucketsData,
@@ -93,25 +93,43 @@ function parseHistogramLabel(label: string): number {
return value;
}
interface HeatmapCard {
x: number;
y: number;
yBounds: {
top: number | null;
bottom: number | null;
};
values: number[];
count: number;
}
interface HeatmapCardStats {
min: number;
max: number;
}
/**
* Convert buckets into linear array of "cards" - objects, represented heatmap elements.
* @param {Object} buckets
* @return {Array} Array of "card" objects
* @return {Object} Array of "card" objects and stats
*/
function convertToCards(buckets) {
function convertToCards(buckets: any, hideZero = false): { cards: HeatmapCard[]; cardStats: HeatmapCardStats } {
let min = 0,
max = 0;
const cards = [];
const cards: HeatmapCard[] = [];
_.forEach(buckets, xBucket => {
_.forEach(xBucket.buckets, yBucket => {
const card = {
const card: HeatmapCard = {
x: xBucket.x,
y: yBucket.y,
yBounds: yBucket.bounds,
values: yBucket.values,
count: yBucket.count,
};
cards.push(card);
if (!hideZero || card.count !== 0) {
cards.push(card);
}
if (cards.length === 1) {
min = yBucket.count;
@@ -114,7 +114,9 @@ export class HeatmapTooltip {
};
boundBottom = tickFormatter(yBucketIndex);
boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tickFormatter(yBucketIndex + 1) : '';
if (this.panel.yBucketBound !== 'middle') {
boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tickFormatter(yBucketIndex + 1) : '';
}
} else {
// Display 0 if bucket is a special 'zero' bucket
const bottom = yData.y ? yData.bounds.bottom : 0;
@@ -122,8 +124,9 @@ export class HeatmapTooltip {
boundTop = bucketBoundFormatter(yData.bounds.top);
}
valuesNumber = countValueFormatter(yData.count);
const boundStr = boundTop && boundBottom ? `${boundBottom} - ${boundTop}` : boundBottom || boundTop;
tooltipHtml += `<div>
bucket: <b>${boundBottom} - ${boundTop}</b> <br>
bucket: <b>${boundStr}</b> <br>
count: <b>${valuesNumber}</b> <br>
</div>`;
} else {
@@ -40,6 +40,11 @@
</select>
</div>
</div>
<gf-form-switch ng-if="ctrl.panel.dataFormat == 'tsbuckets'"
class="gf-form" label-class="width-8"
label="Reverse order"
checked="ctrl.panel.reverseYBuckets" on-change="ctrl.refresh()">
</gf-form-switch>
</div>
<div class="section gf-form-group" ng-if="ctrl.panel.dataFormat == 'timeseries'">
@@ -63,6 +63,10 @@
<div class="section gf-form-group">
<h5 class="section-heading">Buckets</h5>
<gf-form-switch class="gf-form" label-class="width-8"
label="Hide zero"
checked="ctrl.panel.hideZeroBuckets" on-change="ctrl.render()">
</gf-form-switch>
<div class="gf-form">
<label class="gf-form-label width-8">Space</label>
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right" bs-tooltip="''" ng-model="ctrl.panel.cards.cardPadding" ng-change="ctrl.refresh()" ng-model-onblur>
@@ -379,6 +379,12 @@ export class HeatmapRenderer {
const posX = this.getYAxisWidth(this.heatmap) + Y_AXIS_TICK_PADDING;
this.heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')');
if (this.panel.yBucketBound === 'middle' && tickValues && tickValues.length) {
// Shift Y axis labels to the middle of bucket
const tickShift = 0 - this.chartHeight / (tickValues.length - 1) / 2;
this.heatmap.selectAll('.axis-y text').attr('transform', 'translate(' + 0 + ',' + tickShift + ')');
}
// Remove vertical line in the right of axis labels (called domain in d3)
this.heatmap
.select('.axis-y')
@@ -615,8 +621,8 @@ export class HeatmapRenderer {
w = this.cardWidth;
}
// Card width should be MIN_CARD_SIZE at least
w = Math.max(w, MIN_CARD_SIZE);
// Card width should be MIN_CARD_SIZE at least, but cut cards shouldn't be displayed
w = w > 0 ? Math.max(w, MIN_CARD_SIZE) : 0;
return w;
}
+7 -9
View File
@@ -20,6 +20,12 @@ $enable-hover-media-query: false !default;
// Control the default styling of most Bootstrap elements by modifying these
// variables. Mostly focused on spacing.
$space-xxs: 2px !default;
$space-xs: 4px !default;
$space-sm: 8px !default;
$space-md: 16px !default;
$space-lg: 24px !default;
$space-xl: 32px !default;
$spacer: 14px !default;
$spacer-x: $spacer !default;
$spacer-y: $spacer !default;
@@ -109,7 +115,6 @@ $font-size-h4: 18px !default;
$font-size-h5: 16px !default;
$font-size-h6: 14px !default;
$headings-margin-bottom: ($spacer / 2) !default;
$headings-font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
$headings-font-weight: 400 !default;
$headings-line-height: 1.1 !default;
@@ -163,7 +168,6 @@ $input-padding-y-lg: 10px !default;
$input-height: 35px !default;
$gf-form-margin: 3px;
$gf-form-input-height: 35px;
$cursor-disabled: not-allowed !default;
@@ -210,8 +214,7 @@ $btn-semi-transparent: rgba(0, 0, 0, 0.2) !default;
$side-menu-width: 60px;
// dashboard
$panel-margin: 10px;
$dashboard-padding: $panel-margin * 2;
$dashboard-padding: 10px * 2;
$panel-horizontal-padding: 10;
$panel-vertical-padding: 5;
$panel-padding: 0px $panel-horizontal-padding + 0px $panel-vertical-padding + 0px $panel-horizontal-padding + 0px;
@@ -246,8 +249,3 @@ $external-services: (
icon: '',
),
) !default;
:export {
panelhorizontalpadding: $panel-horizontal-padding;
panelverticalpadding: $panel-vertical-padding;
}
-8
View File
@@ -1,8 +0,0 @@
export interface GrafanaVariables {
panelhorizontalpadding: number;
panelverticalpadding: number;
}
declare const variables: GrafanaVariables;
export default variables;
+1 -1
View File
@@ -109,7 +109,7 @@ h6,
.h4,
.h5,
.h6 {
margin-bottom: $headings-margin-bottom;
margin-bottom: $space-sm;
font-family: $headings-font-family;
font-weight: $headings-font-weight;
line-height: $headings-line-height;
+3 -3
View File
@@ -1,5 +1,5 @@
.add-data-source-header {
margin-bottom: $spacer * 2;
margin-bottom: $space-xl;
padding-top: $spacer;
text-align: center;
}
@@ -7,7 +7,7 @@
.add-data-source-search {
display: flex;
justify-content: center;
margin-bottom: $panel-margin * 2;
margin-bottom: $space-lg;
}
.add-data-source-grid {
@@ -41,6 +41,6 @@
}
.add-data-source-grid-item-logo {
margin: 0 15px;
margin: 0 $space-md;
width: 55px;
}
+1 -1
View File
@@ -7,7 +7,7 @@
.alert {
padding: 15px 20px;
margin-bottom: $panel-margin / 2;
margin-bottom: $space-xs;
text-shadow: 0 2px 0 rgba(255, 255, 255, 0.5);
background: $alert-error-bg;
position: relative;
+14 -21
View File
@@ -1,7 +1,7 @@
.layout-selector {
@include clearfix();
margin-left: $spacer;
margin-left: $space-md;
text-align: right;
button {
@@ -9,7 +9,7 @@
color: $text-color-weak;
box-shadow: $card-shadow;
border: none;
padding: 0.5rem;
padding: $space-sm;
line-height: 1;
font-size: 130%;
float: right;
@@ -35,7 +35,7 @@
}
.card-section {
margin-bottom: $spacer * 2;
margin-bottom: $space-xl;
}
.card-list {
@@ -50,7 +50,7 @@
height: 100%;
background: $card-background;
box-shadow: $card-shadow;
padding: 1rem;
padding: $space-md;
border-radius: 4px;
&:hover {
@@ -58,7 +58,7 @@
}
.label-tag {
margin-left: 6px;
margin-left: $space-sm;
font-size: 11px;
padding: 2px 6px;
}
@@ -80,15 +80,8 @@
overflow: hidden;
}
.card-item-cog {
font-size: 130%;
position: relative;
top: 1rem;
color: $text-muted;
}
.card-item-header {
margin-bottom: $spacer;
margin-bottom: $space-md;
}
.card-item-type {
@@ -110,7 +103,7 @@
}
.card-item-label {
margin-left: 8px;
margin-left: $space-sm;
}
.card-item-sub-name {
@@ -123,7 +116,7 @@
.card-item-sub-name--header {
color: $text-color-weak;
text-transform: uppercase;
margin-bottom: $spacer;
margin-bottom: $space-md;
font-size: $font-size-sm;
font-weight: bold;
}
@@ -136,7 +129,7 @@
.card-item-notice {
font-size: $font-size-sm;
display: inline-block;
margin-left: $spacer;
margin-left: $space-md;
}
.card-item-header-action {
@@ -145,7 +138,7 @@
.card-item-wrapper {
width: 100%;
padding: 0 1rem 1rem 0rem;
padding: 0 $space-md $space-md 0;
}
.card-item-wrapper--clickable {
@@ -153,7 +146,7 @@
}
.card-item-figure {
margin: 0 $spacer $spacer 0;
margin: 0 $space-md $space-md 0;
height: 6rem;
img {
@@ -195,7 +188,7 @@
.card-item-wrapper {
padding: 0;
width: 100%;
margin-bottom: 3px;
margin-bottom: $space-xs;
}
.card-item-wrapper--clickable {
@@ -212,9 +205,9 @@
}
.card-item-figure {
margin: 0 $spacer 0 0;
margin: 0 $space-md 0 0;
img {
width: 3.5rem;
width: 48px;
}
}
@@ -41,7 +41,7 @@
font-size: $font-size-h3;
padding-right: 60px;
white-space: nowrap;
margin-bottom: $spacer;
margin-bottom: $space-md;
i {
font-size: 25px;
@@ -53,7 +53,7 @@
.dashboard-settings__header {
font-size: $font-size-h3;
margin-bottom: $spacer * 2;
margin-bottom: $space-xl;
}
.dashboard-settings__subheader {
@@ -89,13 +89,13 @@
flex-direction: column;
height: 100%;
flex-grow: 1;
margin: $spacer * 3 $spacer * 2 0 0;
margin: 40px $space-xl 0 0;
button {
margin-bottom: 10px;
margin-bottom: $space-sm;
}
}
.dashboard-settings__json-save-button {
margin-top: $spacer;
margin-top: $space-md;
}
+8 -9
View File
@@ -1,8 +1,7 @@
$gf-form-margin: 3px;
$input-border: 1px solid $input-border-color;
.gf-form {
margin-bottom: $gf-form-margin;
margin-bottom: $space-xxs;
display: flex;
flex-direction: row;
align-items: flex-start;
@@ -33,7 +32,7 @@ $input-border: 1px solid $input-border-color;
.gf-form--has-input-icon {
position: relative;
margin-right: $gf-form-margin;
margin-right: $space-xs;
.gf-form-input-icon {
position: absolute;
@@ -82,7 +81,7 @@ $input-border: 1px solid $input-border-color;
align-content: flex-start;
.gf-form + .gf-form {
margin-left: $gf-form-margin;
margin-left: $space-xs;
}
&--nowrap {
@@ -147,14 +146,14 @@ $input-border: 1px solid $input-border-color;
}
.gf-form-label + .gf-form-label {
margin-right: $gf-form-margin;
margin-right: $space-xs;
}
.gf-form-pre {
display: block;
flex-grow: 1;
margin: 0;
margin-right: $gf-form-margin;
margin-right: $space-xs;
border: $input-btn-border-width solid transparent;
border-left: none;
@include border-radius($label-border-radius-sm);
@@ -336,7 +335,7 @@ $input-border: 1px solid $input-border-color;
.gf-form-btn {
padding: $input-padding-y $input-padding-x;
margin-right: $gf-form-margin;
margin-right: $space-xs;
line-height: $input-line-height;
font-size: $font-size-sm;
@@ -354,7 +353,7 @@ $input-border: 1px solid $input-border-color;
}
.gf-form-dropdown-typeahead {
margin-right: $gf-form-margin;
//margin-right: $space-xs; ?
position: relative;
&::after {
@@ -391,7 +390,7 @@ $input-border: 1px solid $input-border-color;
}
&--header {
margin-bottom: $gf-form-margin;
margin-bottom: $space-xxs;
}
&--no-padding {
+1 -1
View File
@@ -5,7 +5,7 @@
margin-bottom: 2rem;
border-top: 3px solid $info-box-border-color;
margin-bottom: $spacer;
margin-right: $gf-form-margin;
margin-right: $space-xs;
box-shadow: $card-shadow;
flex-grow: 1;
+1 -1
View File
@@ -7,7 +7,7 @@
&__spinner {
font-size: 32px;
margin-bottom: $panel-margin;
margin-bottom: $space-sm;
}
&__text {
+2 -2
View File
@@ -21,7 +21,7 @@
}
.panel-editor-container__editor {
margin-top: $panel-margin * 2;
margin-top: $space-lg;
display: flex;
flex-direction: row;
flex: 1 1 0;
@@ -80,7 +80,7 @@
}
.submenu-controls {
padding: 0 $dashboard-padding $panel-margin $dashboard-padding;
padding: 0 $dashboard-padding $space-sm $dashboard-padding;
}
.panel-editor-container__panel {
@@ -66,7 +66,6 @@ $font-size-heatmap-tick: 11px;
height: 18px;
float: left;
white-space: nowrap;
padding-left: 10px;
}
.heatmap-legend-values {
+1 -1
View File
@@ -6,7 +6,7 @@ $column-horizontal-spacing: 10px;
padding: $panel-padding;
padding-top: 10px;
border-radius: $border-radius;
margin: 2 * $panel-margin 0 $panel-margin;
margin: $space-md 0 $space-sm;
border: $panel-border;
flex-direction: column;
}
+4 -4
View File
@@ -165,7 +165,7 @@
font-size: $font-size-sm;
color: $text-color-weak;
border-bottom: 1px solid $dropdownDividerBottom;
margin-bottom: 0.25rem;
margin-bottom: $space-xs;
white-space: nowrap;
}
@@ -190,7 +190,7 @@ li.sidemenu-org-switcher {
display: flex;
align-items: center;
> i.fa.fa-random {
margin-right: 4px;
margin-right: $space-xs;
top: 1px;
}
}
@@ -283,8 +283,8 @@ li.sidemenu-org-switcher {
position: unset;
width: 100%;
float: none;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
margin-top: $space-sm;
margin-bottom: $space-sm;
> li > a {
padding-left: 15px;
+1 -1
View File
@@ -4,7 +4,7 @@
flex-wrap: wrap;
align-content: flex-start;
align-items: flex-start;
padding: 0 0 $panel-margin 0;
padding: 0 0 $space-sm 0;
}
.annotation-disabled,
+14 -13
View File
@@ -65,6 +65,11 @@
font-size: 18px;
min-height: 55px;
line-height: 55px;
justify-content: space-between;
margin-left: $space-xl;
}
.explore-toolbar-header {
justify-content: space-between;
align-items: center;
}
@@ -163,7 +168,7 @@
}
.explore-panel {
margin-top: $panel-margin;
margin-top: $space-sm;
}
.explore-panel__body {
@@ -182,24 +187,20 @@
.explore-panel__header-label {
font-weight: 500;
margin-right: $panel-margin;
margin-right: $space-sm;
font-size: $font-size-h6;
box-shadow: $text-shadow-faint;
}
.explore-panel__header-buttons {
margin-right: $panel-margin;
margin-right: $space-sm;
font-size: $font-size-lg;
line-height: $font-size-h6;
}
.result-options {
margin: 2 * $panel-margin 0;
}
.time-series-disclaimer {
width: 300px;
margin: $panel-margin auto;
margin: $space-sm auto;
padding: 10px 0;
border-radius: $border-radius;
text-align: center;
@@ -207,7 +208,7 @@
.disclaimer-icon {
color: $yellow;
margin-right: $panel-margin/2;
margin-right: $space-xs;
}
.show-all-time-series {
@@ -234,7 +235,7 @@
position: relative;
overflow: hidden;
background: none;
margin: $panel-margin / 2;
margin: $space-xs;
}
.explore-panel__loader--active:after {
@@ -377,7 +378,7 @@
.ReactTable .-pagination {
border-top: none;
box-shadow: none;
margin-top: $panel-margin;
margin-top: $space-sm;
}
.ReactTable .-pagination .-btn {
color: $blue;
@@ -418,7 +419,7 @@
// TODO Experimental
.cheat-sheet-item {
margin: 2 * $panel-margin 0;
margin: $space-lg 0;
width: 50%;
}
@@ -427,6 +428,6 @@
}
.cheat-sheet-item__expression {
margin: $panel-margin/2 0;
margin: $space-xs 0;
cursor: pointer;
}
+3 -3
View File
@@ -8,20 +8,20 @@
// widths
@for $i from 1 through 30 {
.width-#{$i} {
width: ($spacer * $i) - $gf-form-margin !important;
width: ($spacer * $i) - $space-xs !important;
}
}
@for $i from 1 through 30 {
.max-width-#{$i} {
max-width: ($spacer * $i) - $gf-form-margin !important;
max-width: ($spacer * $i) - $space-xs !important;
flex-grow: 1;
}
}
@for $i from 1 through 30 {
.min-width-#{$i} {
min-width: ($spacer * $i) - $gf-form-margin !important;
min-width: ($spacer * $i) - $space-xs !important;
}
}
+7 -10
View File
@@ -123,6 +123,7 @@ export function ServiceTestContext(this: any) {
};
this.createService = name => {
// @ts-ignore
return angularMocks.inject(($q, $rootScope, $httpBackend, $injector, $location, $timeout) => {
self.$q = $q;
self.$rootScope = $rootScope;
@@ -145,7 +146,7 @@ export function DashboardViewStateStub(this: any) {
export function TimeSrvStub(this: any) {
this.init = () => {};
this.time = { from: 'now-1h', to: 'now' };
this.timeRange = function(parse) {
this.timeRange = function(parse: boolean) {
if (parse === false) {
return this.time;
}
@@ -155,11 +156,7 @@ export function TimeSrvStub(this: any) {
};
};
this.replace = target => {
return target;
};
this.setTime = function(time) {
this.setTime = function(time: any) {
this.time = time;
};
}
@@ -174,11 +171,11 @@ export function TemplateSrvStub(this: any) {
this.variables = [];
this.templateSettings = { interpolate: /\[\[([\s\S]+?)\]\]/g };
this.data = {};
this.replace = function(text) {
this.replace = function(text: string) {
return _.template(text, this.templateSettings)(this.data);
};
this.init = () => {};
this.getAdhocFilters = () => {
this.getAdhocFilters = (): any => {
return [];
};
this.fillVariableValuesForUrl = () => {};
@@ -187,10 +184,10 @@ export function TemplateSrvStub(this: any) {
return false;
};
this.variableInitialized = () => {};
this.highlightVariablesAsHtml = str => {
this.highlightVariablesAsHtml = (str: string) => {
return str;
};
this.setGrafanaVariable = function(name, value) {
this.setGrafanaVariable = function(name: string, value: string) {
this.data[name] = value;
};
}
+32 -20
View File
@@ -48,7 +48,7 @@ const colorCodes = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan'
/* ------------------------------------------------------------------------ */
const clean = obj => {
const clean = (obj: any) => {
for (const k in obj) {
if (!obj[k]) {
delete obj[k];
@@ -60,11 +60,11 @@ const clean = obj => {
/* ------------------------------------------------------------------------ */
class Color {
background: string;
background: boolean;
name: string;
brightness: number;
constructor(background?, name?, brightness?) {
constructor(background?: boolean, name?: string, brightness?: number) {
this.background = background;
this.name = name;
this.brightness = brightness;
@@ -82,18 +82,21 @@ class Color {
});
}
defaultBrightness(value) {
defaultBrightness(value: number) {
return new Color(this.background, this.name, this.brightness || value);
}
css(inverted) {
css(inverted: boolean) {
const color = inverted ? this.inverse : this;
// @ts-ignore
const rgbName = (color.brightness === Code.bright && asBright[color.name]) || color.name;
const prop = color.background ? 'background:' : 'color:',
rgb = Colors.rgb[rgbName],
alpha = this.brightness === Code.dim ? 0.5 : 1;
const prop = color.background ? 'background:' : 'color:';
// @ts-ignore
const rgb = Colors.rgb[rgbName];
const alpha = this.brightness === Code.dim ? 0.5 : 1;
return rgb
? prop + 'rgba(' + [...rgb, alpha].join(',') + ');'
@@ -117,17 +120,19 @@ class Code {
value: number;
constructor(n?) {
constructor(n?: string | number) {
if (n !== undefined) {
this.value = Number(n);
}
}
get type() {
// @ts-ignore
return types[Math.floor(this.value / 10)];
}
get subtype() {
// @ts-ignore
return subtypes[this.type][this.value % 10];
}
@@ -135,7 +140,7 @@ class Code {
return this.value ? '\u001b[' + this.value + 'm' : '';
}
static str(x) {
static str(x: string | number) {
return new Code(x).str;
}
@@ -146,16 +151,17 @@ class Code {
/* ------------------------------------------------------------------------ */
const replaceAll = (str, a, b) => str.split(a).join(b);
const replaceAll = (str: string, a: string, b: string) => str.split(a).join(b);
/* ANSI brightness codes do not overlap, e.g. "{bright}{dim}foo" will be rendered bright (not dim).
So we fix it by adding brightness canceling before each brightness code, so the former example gets
converted to "{noBrightness}{bright}{noBrightness}{dim}foo" – this way it gets rendered as expected.
*/
const denormalizeBrightness = s => s.replace(/(\u001b\[(1|2)m)/g, '\u001b[22m$1');
const normalizeBrightness = s => s.replace(/\u001b\[22m(\u001b\[(1|2)m)/g, '$1');
const denormalizeBrightness = (s: string) => s.replace(/(\u001b\[(1|2)m)/g, '\u001b[22m$1');
const normalizeBrightness = (s: string) => s.replace(/\u001b\[22m(\u001b\[(1|2)m)/g, '$1');
// @ts-ignore
const wrap = (x, openCode, closeCode) => {
const open = Code.str(openCode),
close = Code.str(closeCode);
@@ -168,7 +174,7 @@ const wrap = (x, openCode, closeCode) => {
/* ------------------------------------------------------------------------ */
const camel = (a, b) => a + b.charAt(0).toUpperCase() + b.slice(1);
const camel = (a: string, b: string) => a + b.charAt(0).toUpperCase() + b.slice(1);
const stringWrappingMethods = (() =>
[
@@ -216,10 +222,12 @@ const stringWrappingMethods = (() =>
/* ------------------------------------------------------------------------ */
// @ts-ignore
const assignStringWrappingAPI = (target, wrapBefore = target) =>
stringWrappingMethods.reduce(
(memo, [k, open, close]) =>
O.defineProperty(memo, k, {
// @ts-ignore
get: () => assignStringWrappingAPI(str => wrapBefore(wrap(str, open, close))),
}),
@@ -232,7 +240,7 @@ const TEXT = 0,
BRACKET = 1,
CODE = 2;
function rawParse(s) {
function rawParse(s: string) {
let state = TEXT,
buffer = '',
text = '',
@@ -333,7 +341,7 @@ export default class Colors {
/**
* @param {string} s a string containing ANSI escape codes.
*/
constructor(s?) {
constructor(s?: string) {
this.spans = s ? rawParse(s) : [];
}
@@ -342,7 +350,10 @@ export default class Colors {
}
get parsed() {
let color, bgColor, brightness, styles;
let styles: Set<string>;
let brightness: number;
let color: Color;
let bgColor: Color;
function reset() {
(color = new Color()),
@@ -431,6 +442,7 @@ export default class Colors {
if (!(k in String.prototype)) {
O.defineProperty(String.prototype, k, {
get: function() {
// @ts-ignore
return Colors[k](this);
},
});
@@ -444,7 +456,7 @@ export default class Colors {
* @desc parses a string containing ANSI escape codes
* @return {Colors} parsed representation.
*/
static parse(s) {
static parse(s: string) {
return new Colors(s).parsed;
}
@@ -453,7 +465,7 @@ export default class Colors {
* @param {string} s a string containing ANSI escape codes.
* @return {string} clean string.
*/
static strip(s) {
static strip(s: string) {
return s.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]/g, ''); // hope V8 caches the regexp
}
@@ -468,4 +480,4 @@ export default class Colors {
/* ------------------------------------------------------------------------ */
assignStringWrappingAPI(Colors, str => str);
assignStringWrappingAPI(Colors, (str: string) => str);
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
echo "Collecting code stats (typescript errors & more)"
ERROR_COUNT="$(./node_modules/.bin/tsc --project tsconfig.json --noEmit --noImplicitAny true | grep -oP 'Found \K(\d+)')"
DIRECTIVES="$(grep -r -o directive public/app/**/* | wc -l)"
CONTROLLERS="$(grep -r -oP 'class .*Ctrl' public/app/**/* | wc -l)"
echo "Typescript errors: $ERROR_COUNT"
echo "Directives: $DIRECTIVES"
echo "Controllers: $CONTROLLERS"
curl \
-d "{\"metrics\": {
\"ci.code.noImplicitAny\": $ERROR_COUNT,
\"ci.code.directives\": $DIRECTIVES,
\"ci.code.controllers\": $CONTROLLERS
}
}" \
-H "Content-Type: application/json" \
-u ci:$CIRCLE_STATS_PWD \
-X POST https://stats.grafana.org/metric-receiver
curl https://6371:$GRAFANA_MISC_STATS_API_KEY@graphite-us-central1.grafana.net/metrics \
-H 'Content-type: application/json' \
-d '[
{"name":"grafana.ci-code.noImplicitAny", "interval":60, "value": '$ERROR_COUNT', "mtype": "gauge", "time": '$(date +%s)'},
{"name":"grafana.ci-code.directives", "interval":60, "value": '$DIRECTIVES', "mtype": "gauge", "time": '$(date +%s)'},
{"name":"grafana.ci-code.controllers", "interval":60, "value": '$CONTROLLERS', "mtype": "gauge", "time": '$(date +%s)'}
]'
+6
View File
@@ -12,3 +12,9 @@ function exit_if_fail {
exit_if_fail npm run prettier:check
exit_if_fail npm run test
# On master also collect some and send some metrics
branch="$(git rev-parse --abbrev-ref HEAD)"
if [ "${branch}" == "master" ]; then
exit_if_fail ./scripts/circle-metrics.sh
fi
+1
View File
@@ -23,6 +23,7 @@
"noImplicitThis": true,
"noImplicitUseStrict": false,
"noImplicitAny": false,
"downlevelIteration": true,
"noUnusedLocals": true,
"baseUrl": "public",
"pretty": true,
+5
View File
@@ -1514,6 +1514,11 @@
react-input-autosize "^2.2.1"
react-transition-group "^2.2.1"
"@types/angular@^1.6.6":
version "1.6.54"
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.54.tgz#f9d5a03e4da7b021a6dabe5d63e899ed4567a5bd"
integrity sha512-xA1FuozWXeRQ7FClUbvk8ePL+dydBeDoCWRPFTHU5+8uvVtIIfLGiHA8CMkwsbddFCYnTDVbLxG85a/HBx7LtA==
"@types/chalk@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.0.tgz#b7f6e446f4511029ee8e3f43075fb5b73fbaa0ba"