diff --git a/e2e/cypress/e2e/timeseries_panel_spec.cy.js b/e2e/cypress/e2e/timeseries_panel_spec.cy.js new file mode 100644 index 00000000..e1e8078e --- /dev/null +++ b/e2e/cypress/e2e/timeseries_panel_spec.cy.js @@ -0,0 +1,19 @@ +const fs = require('fs') + +describe('Timeseries Panel', function() { + + let panelTitles = [] + + before(function() { + let testDir = './tests/timeseries_panel/test_compiled.json' + let uid = 'timeseries-panel' + panelTitles = cy.createDashboardFromUnitTests(testDir, uid) + }) + + it('renders all timeseries panels', function() { + cy.visit('/d/timeseries-panel/timeseries-panel') + for (const title of panelTitles) { + cy.contains(title) + } + }) +}) diff --git a/grafonnet/grafana.libsonnet b/grafonnet/grafana.libsonnet index b94ddf39..58a6dcb5 100644 --- a/grafonnet/grafana.libsonnet +++ b/grafonnet/grafana.libsonnet @@ -8,6 +8,7 @@ link:: import 'link.libsonnet', annotation:: import 'annotation.libsonnet', graphPanel:: import 'graph_panel.libsonnet', + timeseriesPanel:: import 'timeseries_panel.libsonnet', logPanel:: import 'log_panel.libsonnet', tablePanel:: import 'table_panel.libsonnet', singlestat:: import 'singlestat.libsonnet', diff --git a/grafonnet/timeseries_panel.libsonnet b/grafonnet/timeseries_panel.libsonnet new file mode 100644 index 00000000..b4a4b8a6 --- /dev/null +++ b/grafonnet/timeseries_panel.libsonnet @@ -0,0 +1,320 @@ +{ + /** + * Creates a [timeseries panel](https://grafana.com/docs/grafana/next/visualizations/time-series/). + * It requires the time series panel plugin in grafana, which is built-in. + * + * @name timeseriesPanel.new + * + * @param title The title of the graph panel. + * @param description (optional) The description of the panel. + * @param legendMode (default `list`) How to display (or not) the legend. `'list`', `'table'`, or `'hidden`'. + * @param legendPlacement (default `bottom`) Where to display the legend. `'bottom`' or `'right'`. + * @param legendValues (default `[]`) A list of values to calculate and display in the legend. Options are: `'lastNotNull'`, `'last'`, `'firstNotNull'`, `'first'`, `'min'`, `'max'`, `'mean'`, `'sum'`, `'count'`, `'range'`, `'delta'`, `'step'`, `'diff'`, `'logmin'`, `'allIsZero'`, `'allIsNull'`, `'changeCount'`, `'distinctCount'`, `'diffperc'`, `'allValues'`, `'uniqueValues'`. + * @param graphStyle (default `'line'`) The type of graph to draw. `'line'`, `'bars'`, or `'points'`. + * @param lineInterpolation (default `'linear'`) The interpolation method to use when `graphStyle` is `'line'`. `'linear'`, '`smooth'`, `'stepBefore'`, or `'stepAfter'`. + * @param lineStyle (default `solid`) The draw style of the line when `graphStyle` is `'line'`. `'solid'`, `'dash'`, or `'dot'`. + * @param lineDashSegments When `graphStyle` is `'line'`, an array specifying the length of dashes and gaps when `lineStyle` is `'dash'` or the gaps between dots when `lineStyle` is `'dot'`. + * @param lineWidth (default `1`) The thickness of the line to draw when `graphStyle` is `'line'` or `'bars'`. + * @param connectNullValues (default `false`) When graphStyle` is `'line'`, whether to connect null values or not. Can also specify a number of seconds beyond which points will not be connected. `true`, `false`, ``. + * @param fillOpacity (default `0`) The opacity to fill the area beneath the graph when `graphStyle` is `'line'` or `'bars'`. + * @param stackSeries (default `'none'`) How series should be stacked. `'none'`, `'normal'`, or `'100%'`. + * @param barAlignment (default `'center'`) How bars should be aligned when `graphStyle` is `'bars'`. `'left'`, `'center'`, or `'right'`. + * @param gradientMode (default `none`) The gradient style to apply to the fill when `graphStyle` is `'line'` or `'bars'`. `'none'`, `'opacity'`, `'hue'`, or `'scheme'`. + * @param showPoints (default `'auto'`) When `graphStyle` is `'line'` or '`bars``, whether to display datapoints on the graph. `'auto'`, `'always'`, or `'never'`. + * @param pointSize (default `5`) The size of points on the graph. + * @param axisPlacement (default `'auto'`) How the axis should be aligned. `'auto'`, `'left'`, `'right'`, or `'hidden'`. + * @param axisLabel (default `''`) The axis label. + * @param axisWidth (default `'auto'`) The axis width. + * @param axisSoftMin (optional) The soft minimum for the axis. + * @param axisSoftMax (optional) The soft maximum for the axis. + * @param axisShowGridLines (default `'auto'`) Whether to show grid lines. `'auto'`, `true`, or `false` + * @param axisLogBase (optional) If set, scale the axis logarithmically with the given base (valid values `2` or `10`). If not set, the axis will have a linear scale. + * @param repeat (optional) Name of variable that should be used to repeat this panel. + * @param repeatDirection (default `'h'`) 'h' for horizontal or 'v' for vertical. + * @param repeatMaxPerRow (optional) How many panels to limit each row to when repeating horizontally, + * @param tooltip (default `'single'`) The tooltip mode, `'single'`, `'all'`, or `'hidden'`. + * @param tooltipSort (default `'none'`) Value sort order when tooltip mode is `'all'`. `'none'`, `'ascending'`, or `'descending'`. + * @param transparent (optional) Whether to display the panel without a background. + * @param unit (optional) The unit to use for values. + * @param min (optional) The minimum value to display. + * @param max (optional) The maximum value to display. + * @param decimals (optional) The number of decimal points to display. + * @param seriesName (optional) Override the series or field name. + * @param colorMode (default `'palette-classic'`) The color mode to use. + * @param colorBy (default `'last'`) How to determine the color to use. `'last'`, `'min'`, or `'max'`. + * @param fixedColor (optional) The color to use when `colorMode` is `'fixed'`. + * @param noValue (optional) What to show when there is no value. + * @param thresholdDisplay (default `'off'`) How thresholds should be visualised. `'off'`, `'line'`, `'area'`, or `'line+area'` + * @param thresholdMode (default `'absolute'`) Whether thresholds are absolute or a percentage. `'absolute'` or `'percentage'` + * + * @method addTarget(target) Adds a target object. + * @method addTargets(targets) Adds an array of targets. + * @method addValueMapping(value, color, displayText=null) Adds a value mapping. + * @method addRangeMapping(from, to, color, displayText=null) Adds a range mapping. + * @method addRegexMapping(pattern, color, displayText=null) Adds a regular expression mapping. + * @method addSpecialMapping(match, color, displayText=null) Adds a special mapping. + * @method addDataLink(url, title='', newWindow=false) Adds a data link. + * @method addThreshold(color, value=null) Adds a threshold. + * @method addOverridesForField(field, overrides) Add a list of overrides for a named field. + * @method addOverridesForFieldsMatchingRegex(regex, overrides) Add a list of overrides for field names matching a given regex. + * @method addOverridesForFieldsOfType(type, overrides) Add a list of overrides for fields of a given type. + * @method addOverridesForQuery(queryId, overrides) Add a list of overrides for fields from a given query. + */ + new( + title, + description=null, + graphStyle='line', + legendMode='list', + legendPlacement='bottom', + legendValues=[], + lineInterpolation='linear', + lineStyle='solid', + lineDashSegments=null, + lineWidth=1, + barAlignment='center', + connectNullValues=false, + fillOpacity=0, + showPoints='auto', + pointSize=5, + stackSeries='none', + gradientMode='none', + axisPlacement='auto', + axisLabel='', + axisWidth='auto', + axisSoftMin=null, + axisSoftMax=null, + axisShowGridLines='auto', + axisLogBase=null, + repeat=null, + repeatDirection='h', + repeatMaxPerRow=null, + tooltip='single', + tooltipSort=null, + transparent=null, + unit=null, + min=null, + max=null, + decimals=null, + seriesName=null, + colorMode='palette-classic', + fixedColor=null, + colorBy='last', + noValue=null, + thresholdDisplay='off', + thresholdMode='absolute', + ):: + { + [if description != null then 'description']: description, + fieldConfig: { + defaults: { + color: { + mode: colorMode, + [if colorMode == 'fixed' && fixedColor != null then 'fixedColor']: fixedColor, + [if colorBy != 'last' then 'seriesBy']: colorBy, + }, + custom: { + axisLabel: axisLabel, + axisPlacement: axisPlacement, + [if axisWidth != 'auto' then 'axisWidth']: axisWidth, + [if axisSoftMin != null then 'axisSoftMin']: axisSoftMin, + [if axisSoftMax != null then 'axisSoftMax']: axisSoftMax, + [if axisShowGridLines != 'auto' then 'axisGridShow']: axisShowGridLines, + barAlignment: { + left: -1, + center: 0, + right: 1, + }[barAlignment], + drawStyle: graphStyle, + fillOpacity: fillOpacity, + gradientMode: gradientMode, + hideFrom: { + legend: false, + tooltip: false, + viz: false, + }, + lineInterpolation: lineInterpolation, + [if graphStyle == 'line' then 'lineStyle']: { + fill: lineStyle, + [if lineStyle == 'dash' then 'dash']: if lineDashSegments != null then lineDashSegments else [10, 10], + [if lineStyle == 'dot' then 'dash']: if lineDashSegments != null then lineDashSegments else [0, 10], + }, + lineWidth: lineWidth, + pointSize: pointSize, + scaleDistribution: { + type: if axisLogBase == null then 'linear' else 'log', + [if axisLogBase != null then 'log']: axisLogBase, + }, + showPoints: showPoints, + spanNulls: connectNullValues, + stacking: { + group: 'A', + mode: if stackSeries == '100%' then 'percent' else stackSeries, + }, + thresholdsStyle: { + mode: thresholdDisplay, + }, + }, + mappings: [], + thresholds: { + mode: thresholdMode, + steps: [], + }, + [if unit != null then 'unit']: unit, + [if min != null then 'min']: min, + [if max != null then 'max']: max, + [if decimals != null then 'decimals']: decimals, + [if seriesName != null then 'displayName']: seriesName, + [if noValue != null then 'noValue']: noValue, + }, + overrides: [], + }, + links: [], + options: { + legend: { + calcs: legendValues, + displayMode: legendMode, + placement: legendPlacement, + }, + tooltip: { + mode: { single: 'single', all: 'multi', hidden: 'none' }[tooltip], + sort: if tooltip == 'all' && tooltipSort != null + then { ascending: 'asc', descending: 'desc' }[tooltipSort] + else 'none', + }, + }, + [if repeat != null then 'repeat']: repeat, + [if repeat != null then 'repeatDirection']: repeatDirection, + [if repeat != null && repeatDirection == 'h' && repeatMaxPerRow != null then 'maxPerRow']: repeatMaxPerRow, + targets: [], + title: title, + [if transparent != null then 'transparent']: transparent, + type: 'timeseries', + _nextTarget:: 0, + addLink(link):: self { + links+: [link], + }, + addLinks(links):: std.foldl(function(p, t) p.addLink(t), links, self), + addTarget(target):: self { + // automatically ref id in added targets. + // https://github.com/kausalco/public/blob/master/klumps/grafana.libsonnet + local nextTarget = super._nextTarget, + _nextTarget: nextTarget + 1, + targets+: [target { refId: std.char(std.codepoint('A') + nextTarget) }], + }, + addTargets(targets):: std.foldl(function(p, t) p.addTarget(t), targets, self), + _nextMapping:: 0, + local addMapping = function(type, options) self { + local nextMapping = super._nextMapping, + _nextMapping: nextMapping + 1, + fieldConfig+: { defaults+: { mappings+: [{ type: type, options: options(nextMapping) }] } }, + }, + addValueMapping(value, color, displayText=null):: + addMapping(type='value', options=function(index) { + [value]: { + color: color, + index: index, + [if displayText != null then 'text']: displayText, + }, + }), + addRangeMapping(from, to, color, displayText=null):: + addMapping(type='range', options=function(index) { + from: from, + to: to, + result: { + color: color, + index: index, + [if displayText != null then 'text']: displayText, + }, + }), + addRegexMapping(pattern, color, displayText=null):: + addMapping(type='regex', options=function(index) { + pattern: pattern, + result: { + color: color, + index: index, + [if displayText != null then 'text']: displayText, + }, + }), + addSpecialMapping(match, color, displayText=null):: + addMapping(type='special', options=function(index) { + match: match, + result: { + color: color, + index: index, + [if displayText != null then 'text']: displayText, + }, + }), + addDataLink(url, title='', newWindow=false):: self { + fieldConfig+: { + defaults+: { + links+: [ + { + title: title, + url: url, + [if newWindow == true then 'targetBlank']: true, + }, + ], + }, + }, + }, + addThreshold(color, value=null):: self { + fieldConfig+: { defaults+: { thresholds+: { steps+: [{ color: color, value: value }] } } }, + }, + local addOverrides = function(matcher, overrides) self { + fieldConfig+: { overrides+: [{ matcher: matcher, properties: overrides }] }, + }, + addOverridesForField(field, overrides):: + addOverrides(matcher={ id: 'byName', options: field }, overrides=overrides), + addOverridesForFieldsMatchingRegex(regex, overrides):: + addOverrides(matcher={ id: 'byRegexp', options: regex }, overrides=overrides), + addOverridesForFieldsOfType(type, overrides):: + addOverrides(matcher={ id: 'byType', options: type }, overrides=overrides), + addOverridesForQuery(queryId, overrides):: + addOverrides(matcher={ id: 'byFrameRefID', options: queryId }, overrides=overrides), + }, + /** + * Helpers for use with the addOverrides* methods of the timeseries panel. + * + * Example usage: + * grafana.timeseriesPanel.new(title='my panel') + * .addOverridesForField(field='somefield', overrides=[overrides.lineWidth(5)]) + */ + overrides:: { + graphStyle(style):: { id: 'custom.drawStyle', value: style }, + lineInterpolation(type):: { id: 'custom.lineInterpolation', value: type }, + barAlignment(alignment):: { id: 'custom.barAlignment', value: { left: -1, center: 0, right: 1 }[alignment] }, + lineWidth(width):: { id: 'custom.lineWidth', value: width }, + fillOpacity(opacity):: { id: 'custom.fillOpacity', value: opacity }, + gradientMode(mode):: { id: 'custom.gradientMode', value: mode }, + fillBelowTo(series):: { id: 'custom.fillBelowTo', value: series }, + lineStyleSolid():: { id: 'custom.lineStyle', value: { fill: 'solid' } }, + lineStyleDash(segments):: { id: 'custom.lineStyle', value: { fill: 'dash', dash: segments } }, + lineStyleDot(gaps):: { id: 'custom.lineStyle', value: { fill: 'dot', dash: gaps } }, + connectNullValues(setting):: { id: 'custom.spanNulls', value: setting }, + showPoints(setting):: { id: 'custom.showPoints', value: setting }, + pointSize(size):: { id: 'custom.pointSize', value: size }, + stackSeries(mode, group='A'):: { id: 'custom.stacking', value: { mode: mode, group: group } }, + transformSeries(transformation):: { id: 'custom.transform', value: transformation }, + axisPlacement(position):: { id: 'custom.axisPlacement', value: position }, + axisLabel(label):: { id: 'custom.axisLabel', value: label }, + axisWidth(width):: { id: 'custom.axisWidth', value: width }, + axisSoftMin(min):: { id: 'custom.axisSoftMin', value: min }, + axisSoftMax(max):: { id: 'custom.axisSoftMax', value: max }, + axisShowGridLines(setting):: { id: 'custom.axisGridShow', value: setting }, + axisLinear():: { id: 'custom.scaleDistribution', value: { type: 'linear' } }, + axisLog(log):: { id: 'custom.scaleDistribution', value: { type: 'log', log: log } }, + hideSeries(tooltip, viz, legend):: { id: 'custom.hideFrom', value: { tooltip: tooltip, viz: viz, legend: legend } }, + unit(unit):: { id: 'unit', value: unit }, + min(min):: { id: 'min', value: min }, + max(max):: { id: 'max', value: max }, + decimals(places):: { id: 'decimals', value: places }, + displayName(name):: { id: 'displayName', value: name }, + fixedColor(color):: { id: 'color', value: { mode: 'fixed', fixedColor: color } }, + colorScheme(name, colorBy='last'):: { id: 'color', value: { mode: name, seriesBy: colorBy } }, + noValue(value):: { id: 'noValue', value: value }, + dataLinks(links):: { id: 'links', value: links }, + valueMappings(mappings):: { id: 'mappings', value: mappings }, + thresholds(thresholds, type='absolute'):: { id: 'thresholds', value: { mode: type, steps: thresholds } }, + showThresholds(style):: { id: 'custom.thresholdsStyle', value: { mode: style } }, + }, +} diff --git a/tests/timeseries_panel/test.jsonnet b/tests/timeseries_panel/test.jsonnet new file mode 100644 index 00000000..66d8f137 --- /dev/null +++ b/tests/timeseries_panel/test.jsonnet @@ -0,0 +1,50 @@ +local grafana = import 'grafonnet/grafana.libsonnet'; +local timeseries = grafana.timeseriesPanel; + +{ + defaults: + timeseries.new('defaults'), + advanced: + grafana.timeseriesPanel.new( + title='advanced', + description='An advanced timeseries panel configuration', + transparent=true, + unit='s', + min=-10, + max=10, + legendMode='table', + legendPlacement='right', + legendValues=['min', 'max', 'mean'], + lineStyle='dash', + lineDashSegments=[30, 60], + lineWidth=4, + gradientMode='hue', + showPoints='always', + pointSize=10, + connectNullValues=true, + fillOpacity='50', + stackSeries='normal', + axisPlacement='right', + axisLabel='y-axis', + axisWidth=100, + axisSoftMin=5, + axisSoftMax=95, + axisShowGridLines='true', + axisLogBase=2, + tooltip='all', + tooltipSort='ascending', + decimals=2, + seriesName='a name', + colorBy='min', + noValue='none', + ), + thresholds: + grafana.timeseriesPanel.new( + title='thresholds', + description='A panel with threshold markers', + thresholdDisplay='line', + thresholdMode='perecentage', + ).addThreshold({ color: 'yellow', value: 0 }) + .addThreshold({ color: 'green', value: 75 }) + .addThreshold({ color: 'red', value: 90 }), +} diff --git a/tests/timeseries_panel/test_compiled.json b/tests/timeseries_panel/test_compiled.json new file mode 100644 index 00000000..951ff72f --- /dev/null +++ b/tests/timeseries_panel/test_compiled.json @@ -0,0 +1,230 @@ +{ + "advanced": { + "description": "An advanced timeseries panel configuration", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "min" + }, + "custom": { + "axisGridShow": "true", + "axisLabel": "y-axis", + "axisPlacement": "right", + "axisSoftMax": 95, + "axisSoftMin": 5, + "axisWidth": 100, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": "50", + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "dash": [ + 30, + 60 + ], + "fill": "dash" + }, + "lineWidth": 4, + "pointSize": 10, + "scaleDistribution": { + "log": 2, + "type": "log" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "displayName": "a name", + "mappings": [ ], + "max": 10, + "min": -10, + "noValue": "none", + "thresholds": { + "mode": "absolute", + "steps": [ ] + }, + "unit": "s" + }, + "overrides": [ ] + }, + "links": [ ], + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "right" + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "targets": [ ], + "title": "advanced", + "transparent": true, + "type": "timeseries" + }, + "defaults": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [ ], + "thresholds": { + "mode": "absolute", + "steps": [ ] + } + }, + "overrides": [ ] + }, + "links": [ ], + "options": { + "legend": { + "calcs": [ ], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ ], + "title": "defaults", + "type": "timeseries" + }, + "thresholds": { + "description": "A panel with threshold markers", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line" + } + }, + "mappings": [ ], + "thresholds": { + "mode": "perecentage", + "steps": [ + { + "color": { + "color": "yellow", + "value": 0 + }, + "value": null + }, + { + "color": { + "color": "green", + "value": 75 + }, + "value": null + }, + { + "color": { + "color": "red", + "value": 90 + }, + "value": null + } + ] + } + }, + "overrides": [ ] + }, + "links": [ ], + "options": { + "legend": { + "calcs": [ ], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ ], + "title": "thresholds", + "type": "timeseries" + } +} diff --git a/tests/timeseries_panel/timeseries.jsonnet b/tests/timeseries_panel/timeseries.jsonnet new file mode 100644 index 00000000..ae993ca5 --- /dev/null +++ b/tests/timeseries_panel/timeseries.jsonnet @@ -0,0 +1,547 @@ +local grafana = import 'grafonnet/grafana.libsonnet'; + +local overrides = grafana.timeseriesPanel.overrides; +{ + check_defaults: + local dash = grafana.timeseriesPanel.new(title='A title'); + { + sets_title: std.assertEqual('A title', dash.title), + no_description: std.assertEqual(false, std.objectHas(dash, 'description')), + transparent_not_set: std.assertEqual(false, std.objectHas(dash, 'transparent')), + no_links: std.assertEqual([], dash.links), + no_repeat: std.assertEqual([false, false, false], [ + std.objectHas(dash, 'maxPerRow'), + std.objectHas(dash, 'repeat'), + std.objectHas(dash, 'repeatDirection'), + ]), + no_targets: std.assertEqual([], dash.targets), + legend_mode_table_at_bottom: std.assertEqual( + { + displayMode: 'list', + placement: 'bottom', + calcs: [], + }, + dash.options.legend + ), + tooltip_mode_single: std.assertEqual( + ['single', 'none'], + [dash.options.tooltip.mode, dash.options.tooltip.sort] + ), + graph_style: { + lines: std.assertEqual('line', dash.fieldConfig.defaults.custom.drawStyle), + linear_interpolation: std.assertEqual('linear', dash.fieldConfig.defaults.custom.lineInterpolation), + line_width_is_1: std.assertEqual(1, dash.fieldConfig.defaults.custom.lineWidth), + line_is_not_filled: std.assertEqual(0, dash.fieldConfig.defaults.custom.fillOpacity), + no_gradient: std.assertEqual('none', dash.fieldConfig.defaults.custom.gradientMode), + line_style_is_solid: std.assertEqual('solid', dash.fieldConfig.defaults.custom.lineStyle.fill), + nulls_are_not_connected: std.assertEqual(false, dash.fieldConfig.defaults.custom.spanNulls), + show_points_set_to_auto: std.assertEqual('auto', dash.fieldConfig.defaults.custom.showPoints), + point_size_is_5: std.assertEqual(5, dash.fieldConfig.defaults.custom.pointSize), + series_not_stacked: std.assertEqual('none', dash.fieldConfig.defaults.custom.stacking.mode), + bars_aligned_centre: std.assertEqual(0, dash.fieldConfig.defaults.custom.barAlignment), + }, + axis_auto_placement: std.assertEqual('auto', dash.fieldConfig.defaults.custom.axisPlacement), + axis_label_blank: std.assertEqual('', dash.fieldConfig.defaults.custom.axisLabel), + axis_width_not_set: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults.custom, 'axisWidth')), + axis_soft_min_not_set: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults.custom, 'axisSoftMin')), + axis_soft_max_not_set: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults.custom, 'axisSoftMax')), + axis_show_grid_lines_not_set: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults.custom, 'showGridLines')), + axis_linear_scale: std.assertEqual('linear', dash.fieldConfig.defaults.custom.scaleDistribution.type), + unitless_values: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults, 'unit')), + no_minimum: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults, 'min')), + no_maximum: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults, 'max')), + no_decimals: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults, 'decimals')), + classic_palette: std.assertEqual('palette-classic', dash.fieldConfig.defaults.color.mode), + colored_by_last: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults.color, 'seriesBy')), + missing_values_not_mapped: std.assertEqual(false, std.objectHas(dash.fieldConfig.defaults, 'noValue')), + no_value_mappings: std.assertEqual([], dash.fieldConfig.defaults.mappings), + thresholds_not_displayed: std.assertEqual('off', dash.fieldConfig.defaults.custom.thresholdsStyle.mode), + thresholds_are_absolute: std.assertEqual('absolute', dash.fieldConfig.defaults.thresholds.mode), + no_threshold_steps: std.assertEqual([], dash.fieldConfig.defaults.thresholds.steps), + no_overrides: std.assertEqual([], dash.fieldConfig.overrides), + }, + can_set_description: std.assertEqual( + 'A description', + grafana.timeseriesPanel.new(title='', description='A description').description + ), + can_make_transparent: std.assertEqual( + true, + grafana.timeseriesPanel.new(title='', transparent=true).transparent + ), + can_set_unit: std.assertEqual('s', grafana.timeseriesPanel.new(title='', unit='s').fieldConfig.defaults.unit), + can_set_min: std.assertEqual(1234, grafana.timeseriesPanel.new(title='', min=1234).fieldConfig.defaults.min), + can_set_max: std.assertEqual(5678, grafana.timeseriesPanel.new(title='', max=5678).fieldConfig.defaults.max), + can_set_decimals: std.assertEqual(3, grafana.timeseriesPanel.new(title='', decimals=3).fieldConfig.defaults.decimals), + can_override_series_name: std.assertEqual('series name', grafana.timeseriesPanel.new(title='', seriesName='series name').fieldConfig.defaults.displayName), + can_set_fixed_color: std.assertEqual({ mode: 'fixed', fixedColor: 'blue' }, grafana.timeseriesPanel.new(title='', colorMode='fixed', fixedColor='blue').fieldConfig.defaults.color), + can_color_by_min: std.assertEqual('min', grafana.timeseriesPanel.new(title='', colorBy='min').fieldConfig.defaults.color.seriesBy), + can_map_missing_values: std.assertEqual(123, grafana.timeseriesPanel.new(title='', noValue=123).fieldConfig.defaults.noValue), + can_display_thresholds: std.assertEqual('line', grafana.timeseriesPanel.new(title='', thresholdDisplay='line').fieldConfig.defaults.custom.thresholdsStyle.mode), + thresholds_can_be_percentages: std.assertEqual('percentage', grafana.timeseriesPanel.new(title='', thresholdMode='percentage').fieldConfig.defaults.thresholds.mode), + legend_tests: { + can_hide: std.assertEqual('hidden', grafana.timeseriesPanel.new(title='', legendMode='hidden').options.legend.displayMode), + can_display_as_table: std.assertEqual('table', grafana.timeseriesPanel.new(title='', legendMode='table').options.legend.displayMode), + can_position_at_right: std.assertEqual('right', grafana.timeseriesPanel.new(title='', legendPlacement='right').options.legend.placement), + can_calculate_summaries: std.assertEqual(['min', 'max', 'mean'], grafana.timeseriesPanel.new(title='', legendValues=['min', 'max', 'mean']).options.legend.calcs), + }, + graph_style_line_tests: { + can_set_interpolation: std.assertEqual('smooth', grafana.timeseriesPanel.new(title='', lineInterpolation='smooth').fieldConfig.defaults.custom.lineInterpolation), + can_set_line_width: std.assertEqual(5, grafana.timeseriesPanel.new(title='', lineWidth=5).fieldConfig.defaults.custom.lineWidth), + can_set_fill_opacity: std.assertEqual(50, grafana.timeseriesPanel.new(title='', fillOpacity=50).fieldConfig.defaults.custom.fillOpacity), + can_set_gradient_mode: std.assertEqual('opacity', grafana.timeseriesPanel.new(title='', gradientMode='opacity').fieldConfig.defaults.custom.gradientMode), + can_set_dashed_line: std.assertEqual({ fill: 'dash', dash: [10, 10] }, grafana.timeseriesPanel.new(title='', lineStyle='dash').fieldConfig.defaults.custom.lineStyle), + can_set_custom_dash_pattern: std.assertEqual({ fill: 'dash', dash: [12, 34] }, grafana.timeseriesPanel.new(title='', lineStyle='dash', lineDashSegments=[12, 34]).fieldConfig.defaults.custom.lineStyle), + can_set_dotted_line: std.assertEqual({ fill: 'dot', dash: [0, 10] }, grafana.timeseriesPanel.new(title='', lineStyle='dot').fieldConfig.defaults.custom.lineStyle), + can_span_null_values: std.assertEqual(true, grafana.timeseriesPanel.new(title='', connectNullValues=true).fieldConfig.defaults.custom.spanNulls), + can_span_null_values_with_threshold: std.assertEqual(1234, grafana.timeseriesPanel.new(title='', connectNullValues=1234).fieldConfig.defaults.custom.spanNulls), + can_force_points_to_be_shown: std.assertEqual('always', grafana.timeseriesPanel.new(title='', showPoints='always').fieldConfig.defaults.custom.showPoints), + can_force_points_to_be_hidden: std.assertEqual('never', grafana.timeseriesPanel.new(title='', showPoints='never').fieldConfig.defaults.custom.showPoints), + can_set_point_size: std.assertEqual(123, grafana.timeseriesPanel.new(title='', pointSize=123).fieldConfig.defaults.custom.pointSize), + can_stack_series: std.assertEqual('normal', grafana.timeseriesPanel.new(title='', stackSeries='normal').fieldConfig.defaults.custom.stacking.mode), + can_stack_series_normalised: std.assertEqual('percent', grafana.timeseriesPanel.new(title='', stackSeries='100%').fieldConfig.defaults.custom.stacking.mode), + can_align_bars_left: std.assertEqual(-1, grafana.timeseriesPanel.new(title='', barAlignment='left').fieldConfig.defaults.custom.barAlignment), + can_align_bars_right: std.assertEqual(1, grafana.timeseriesPanel.new(title='', barAlignment='right').fieldConfig.defaults.custom.barAlignment), + }, + axis_tests: { + can_align_axis_right: std.assertEqual('right', grafana.timeseriesPanel.new(title='', axisPlacement='right').fieldConfig.defaults.custom.axisPlacement), + can_label_axis: std.assertEqual('Axis label', grafana.timeseriesPanel.new(title='', axisLabel='Axis label').fieldConfig.defaults.custom.axisLabel), + can_set_axis_width: std.assertEqual(123, grafana.timeseriesPanel.new(title='', axisWidth=123).fieldConfig.defaults.custom.axisWidth), + can_set_axis_soft_min: std.assertEqual(123, grafana.timeseriesPanel.new(title='', axisSoftMin=123).fieldConfig.defaults.custom.axisSoftMin), + can_set_axis_soft_max: std.assertEqual(123, grafana.timeseriesPanel.new(title='', axisSoftMax=123).fieldConfig.defaults.custom.axisSoftMax), + can_set_log_scale: std.assertEqual({ type: 'log', log: 2 }, grafana.timeseriesPanel.new(title='', axisLogBase=2).fieldConfig.defaults.custom.scaleDistribution), + }, + repeat_tests: { + repeats_horizontally_by_default: + local repeatPanel = grafana.timeseriesPanel.new(title='', repeat='var'); + std.assertEqual( + ['var', 'h', false], + [repeatPanel.repeat, repeatPanel.repeatDirection, std.objectHas(repeatPanel, 'maxPerRow')] + ), + can_set_direction: + local repeatPanel = grafana.timeseriesPanel.new(title='', repeat='var', repeatDirection='v'); + std.assertEqual( + ['var', 'v', false], + [repeatPanel.repeat, repeatPanel.repeatDirection, std.objectHas(repeatPanel, 'maxPerRow')] + ), + can_limit_horizontal_repeat: + local repeatPanel = grafana.timeseriesPanel.new(title='', repeat='var', repeatMaxPerRow=6); + std.assertEqual( + ['var', 'h', 6], + [repeatPanel.repeat, repeatPanel.repeatDirection, repeatPanel.maxPerRow] + ), + horizontal_repeat_ignored_when_vertical: + std.assertEqual(false, std.objectHas(grafana.timeseriesPanel.new(title='', repeat='var', repeatDirection='v', repeatMaxPerRow=6), 'maxPerRow')), + }, + targets_tests: { + can_add_a_target: + local target = { + datasource: { + type: 'datasource', + uid: 'grafana', + }, + hide: false, + refId: 'A', + }; + std.assertEqual( + [target], + grafana.timeseriesPanel.new(title='').addTarget(target).targets + ), + can_add_two_targets: + local target1 = { + datasource: { + type: 'datasource', + uid: 'grafana', + }, + hide: false, + refId: 'A', + }; + local target2 = { + datasource: { + type: 'datasource', + uid: 'grafana', + }, + hide: false, + refId: 'B', + } + ; + std.assertEqual( + [target1, target2], + grafana.timeseriesPanel.new(title='') + .addTarget(target1) + .addTarget(target2) + .targets + ), + can_add_list_of_targets: + local targets = [ + { + datasource: { + type: 'datasource', + uid: 'grafana', + }, + hide: false, + refId: 'A', + }, + { + datasource: { + type: 'datasource', + uid: 'grafana', + }, + hide: false, + refId: 'B', + }, + ]; + std.assertEqual( + targets, + grafana.timeseriesPanel.new(title='') + .addTargets(targets) + .targets + ), + calculates_refids: + local targets = [ + { + datasource: { + type: 'datasource', + uid: 'grafana', + }, + hide: false, + }, + { + datasource: { + type: 'datasource', + uid: 'grafana', + }, + hide: false, + }, + ]; + std.assertEqual( + [ + targets[0] { refId: 'A' }, + targets[1] { refId: 'B' }, + ], + grafana.timeseriesPanel.new(title='') + .addTarget(targets[0]) + .addTarget(targets[1]) + .targets + ), + }, + datalinks_tests: { + can_add_a_data_link: + std.assertEqual( + [ + { + title: '', + url: 'https://example.org', + }, + ], + grafana.timeseriesPanel.new(title='') + .addDataLink(url='https://example.org') + .fieldConfig.defaults.links + ), + can_add_two_data_links: + std.assertEqual( + [ + { + title: '', + url: 'https://example.org', + }, + { + title: 'A title', + url: 'https://example.org/path', + targetBlank: true, + }, + ], + grafana.timeseriesPanel.new(title='') + .addDataLink(url='https://example.org') + .addDataLink(url='https://example.org/path', title='A title', newWindow=true) + .fieldConfig.defaults.links + ), + }, + mappings_tests: { + can_add_a_value_mapping: + std.assertEqual( + [ + { + type: 'value', + options: { + '123': { + color: 'purple', + index: 0, + }, + }, + }, + ], + grafana.timeseriesPanel.new(title='') + .addValueMapping(value='123', color='purple').fieldConfig.defaults.mappings + ), + can_add_two_value_mappings: + std.assertEqual( + [ + { + type: 'value', + options: { + '123': { + color: 'purple', + index: 0, + }, + }, + }, + { + type: 'value', + options: { + '234': { + text: 'hello mum', + color: 'blue', + index: 1, + }, + }, + }, + ], + grafana.timeseriesPanel.new(title='') + .addValueMapping(value='123', color='purple') + .addValueMapping(value='234', color='blue', displayText='hello mum') + .fieldConfig.defaults.mappings + ), + can_add_a_range_mapping: + std.assertEqual( + [ + { + type: 'range', + options: { + from: 1, + to: 3, + result: { + color: 'yellow', + index: 0, + }, + }, + }, + ], + grafana.timeseriesPanel.new(title='') + .addRangeMapping(from=1, to=3, color='yellow') + .fieldConfig.defaults.mappings + ), + can_add_two_range_mappings: + std.assertEqual( + [ + { + type: 'range', + options: { + from: 1, + to: 3, + result: { + color: 'yellow', + index: 0, + }, + }, + }, + { + type: 'range', + options: { + from: 4, + to: 5, + result: { + color: 'orange', + index: 1, + text: 'some text', + }, + }, + }, + ], + grafana.timeseriesPanel.new(title='') + .addRangeMapping(from=1, to=3, color='yellow') + .addRangeMapping(from=4, to=5, color='orange', displayText='some text') + .fieldConfig.defaults.mappings + ), + can_add_a_regex_mapping: + std.assertEqual( + [ + { + type: 'regex', + options: { + pattern: 'abc', + result: { + color: 'yellow', + index: 0, + }, + }, + }, + ], + grafana.timeseriesPanel.new(title='') + .addRegexMapping(pattern='abc', color='yellow') + .fieldConfig.defaults.mappings + ), + can_add_two_regex_mappings: + std.assertEqual( + [ + { + type: 'regex', + options: { + pattern: 'a.*', + result: { + color: 'yellow', + index: 0, + }, + }, + }, + { + type: 'regex', + options: { + pattern: 'b.*', + result: { + color: 'orange', + index: 1, + text: 'some text', + }, + }, + }, + ], + grafana.timeseriesPanel.new(title='') + .addRegexMapping(pattern='a.*', color='yellow') + .addRegexMapping(pattern='b.*', color='orange', displayText='some text') + .fieldConfig.defaults.mappings + ), + can_add_a_special_mapping: + std.assertEqual( + [ + { + type: 'special', + options: { + match: 'null+nan', + result: { + color: 'yellow', + index: 0, + }, + }, + }, + ], + grafana.timeseriesPanel.new(title='') + .addSpecialMapping(match='null+nan', color='yellow') + .fieldConfig.defaults.mappings + ), + can_add_two_special_mappings: + std.assertEqual( + [ + { + type: 'special', + options: { + match: 'null+nan', + result: { + color: 'yellow', + index: 0, + }, + }, + }, + { + type: 'special', + options: { + match: 'false', + result: { + color: 'orange', + index: 1, + text: 'some text', + }, + }, + }, + ], + grafana.timeseriesPanel.new(title='') + .addSpecialMapping(match='null+nan', color='yellow') + .addSpecialMapping(match='false', color='orange', displayText='some text') + .fieldConfig.defaults.mappings + ), + }, + override_tests: { + can_override_a_named_field: + local overrides = [{ id: 'custom.drawStyle', value: 'bars' }]; + std.assertEqual( + [ + { + matcher: { id: 'byName', options: 'A-series' }, + properties: [{ id: 'custom.drawStyle', value: 'bars' }], + }, + ], + grafana.timeseriesPanel.new(title='') + .addOverridesForField(field='A-series', overrides=overrides) + .fieldConfig.overrides + ), + can_override_fields_matching_regex: + local overrides = [{ id: 'custom.drawStyle', value: 'bars' }]; + std.assertEqual( + [ + { + matcher: { id: 'byRegexp', options: '^[ab].*' }, + properties: [{ id: 'custom.drawStyle', value: 'bars' }], + }, + ], + grafana.timeseriesPanel.new(title='') + .addOverridesForFieldsMatchingRegex(regex='^[ab].*', overrides=overrides) + .fieldConfig.overrides + ), + can_override_fields_of_a_type: + local overrides = [{ id: 'custom.drawStyle', value: 'bars' }]; + std.assertEqual( + [ + { + matcher: { id: 'byType', options: 'number' }, + properties: [{ id: 'custom.drawStyle', value: 'bars' }], + }, + ], + grafana.timeseriesPanel.new(title='') + .addOverridesForFieldsOfType(type='number', overrides=overrides) + .fieldConfig.overrides + ), + can_override_fields_from_query: + std.assertEqual( + [ + { + matcher: { id: 'byFrameRefID', options: 'A' }, + properties: [{ id: 'custom.drawStyle', value: 'bars' }], + }, + ], + grafana.timeseriesPanel.new(title='') + .addOverridesForQuery(queryId='A', overrides=[overrides.graphStyle('bars')]) + .fieldConfig.overrides + ), + }, + tooltip_tests: { + can_hide: std.assertEqual({ + mode: 'none', + sort: 'none', + }, grafana.timeseriesPanel.new(title='', tooltip='hidden').options.tooltip), + can_show_all: std.assertEqual({ + mode: 'multi', + sort: 'none', + }, grafana.timeseriesPanel.new(title='', tooltip='all').options.tooltip), + can_sort_when_all: std.assertEqual({ + mode: 'multi', + sort: 'asc', + }, grafana.timeseriesPanel.new(title='', tooltip='all', tooltipSort='ascending').options.tooltip), + sort_ignored_when_not_all: std.assertEqual({ + mode: 'single', + sort: 'none', + }, grafana.timeseriesPanel.new(title='', tooltip='single', tooltipSort='ascending').options.tooltip), + }, + can_add_a_threshold: + std.assertEqual( + [{ color: 'green', value: null }], + grafana.timeseriesPanel.new(title='') + .addThreshold(color='green') + .fieldConfig.defaults.thresholds.steps + ), + can_add_two_thresholds: + std.assertEqual( + [ + { + color: 'green', + value: null, + }, + { + color: 'red', + value: 80, + }, + ], + grafana.timeseriesPanel.new(title='') + .addThreshold(color='green') + .addThreshold(color='red', value=80) + .fieldConfig.defaults.thresholds.steps + ), +} diff --git a/tests/timeseries_panel/timeseries_compiled.json b/tests/timeseries_panel/timeseries_compiled.json new file mode 100644 index 00000000..ea47b0e8 --- /dev/null +++ b/tests/timeseries_panel/timeseries_compiled.json @@ -0,0 +1,128 @@ +{ + "axis_tests": { + "can_align_axis_right": true, + "can_label_axis": true, + "can_set_axis_soft_max": true, + "can_set_axis_soft_min": true, + "can_set_axis_width": true, + "can_set_log_scale": true + }, + "can_add_a_threshold": true, + "can_add_two_thresholds": true, + "can_color_by_min": true, + "can_display_thresholds": true, + "can_make_transparent": true, + "can_map_missing_values": true, + "can_override_series_name": true, + "can_set_decimals": true, + "can_set_description": true, + "can_set_fixed_color": true, + "can_set_max": true, + "can_set_min": true, + "can_set_unit": true, + "check_defaults": { + "axis_auto_placement": true, + "axis_label_blank": true, + "axis_linear_scale": true, + "axis_show_grid_lines_not_set": true, + "axis_soft_max_not_set": true, + "axis_soft_min_not_set": true, + "axis_width_not_set": true, + "classic_palette": true, + "colored_by_last": true, + "graph_style": { + "bars_aligned_centre": true, + "line_is_not_filled": true, + "line_style_is_solid": true, + "line_width_is_1": true, + "linear_interpolation": true, + "lines": true, + "no_gradient": true, + "nulls_are_not_connected": true, + "point_size_is_5": true, + "series_not_stacked": true, + "show_points_set_to_auto": true + }, + "legend_mode_table_at_bottom": true, + "missing_values_not_mapped": true, + "no_decimals": true, + "no_description": true, + "no_links": true, + "no_maximum": true, + "no_minimum": true, + "no_overrides": true, + "no_repeat": true, + "no_targets": true, + "no_threshold_steps": true, + "no_value_mappings": true, + "sets_title": true, + "thresholds_are_absolute": true, + "thresholds_not_displayed": true, + "tooltip_mode_single": true, + "transparent_not_set": true, + "unitless_values": true + }, + "datalinks_tests": { + "can_add_a_data_link": true, + "can_add_two_data_links": true + }, + "graph_style_line_tests": { + "can_align_bars_left": true, + "can_align_bars_right": true, + "can_force_points_to_be_hidden": true, + "can_force_points_to_be_shown": true, + "can_set_custom_dash_pattern": true, + "can_set_dashed_line": true, + "can_set_dotted_line": true, + "can_set_fill_opacity": true, + "can_set_gradient_mode": true, + "can_set_interpolation": true, + "can_set_line_width": true, + "can_set_point_size": true, + "can_span_null_values": true, + "can_span_null_values_with_threshold": true, + "can_stack_series": true, + "can_stack_series_normalised": true + }, + "legend_tests": { + "can_calculate_summaries": true, + "can_display_as_table": true, + "can_hide": true, + "can_position_at_right": true + }, + "mappings_tests": { + "can_add_a_range_mapping": true, + "can_add_a_regex_mapping": true, + "can_add_a_special_mapping": true, + "can_add_a_value_mapping": true, + "can_add_two_range_mappings": true, + "can_add_two_regex_mappings": true, + "can_add_two_special_mappings": true, + "can_add_two_value_mappings": true + }, + "override_tests": { + "can_override_a_named_field": true, + "can_override_fields_from_query": true, + "can_override_fields_matching_regex": true, + "can_override_fields_of_a_type": true + }, + "repeat_tests": { + "can_limit_horizontal_repeat": true, + "can_set_direction": true, + "horizontal_repeat_ignored_when_vertical": true, + "repeats_horizontally_by_default": true + }, + "targets_tests": { + "calculates_refids": true, + "can_add_a_target": true, + "can_add_list_of_targets": true, + "can_add_two_targets": true + }, + "thresholds_can_be_percentages": true, + "tooltip_tests": { + "can_hide": true, + "can_show_all": true, + "can_sort_when_all": true, + "sort_ignored_when_not_all": true + } +}