diff --git a/devenv/dev-dashboards/panel-singlestat/singlestat_test.json b/devenv/dev-dashboards/panel-singlestat/singlestat_test.json deleted file mode 100644 index 51ba32e03d9..00000000000 --- a/devenv/dev-dashboards/panel-singlestat/singlestat_test.json +++ /dev/null @@ -1,757 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "links": [], - "panels": [ - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], - "datasource": "gdev-testdata", - "decimals": null, - "description": "", - "format": "ms", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 0 - }, - "id": 2, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "postfix", - "postfixFontSize": "50%", - "prefix": "prefix", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "", - "format": "time_series", - "intervalFactor": 1, - "refId": "A", - "scenarioId": "csv_metric_values", - "stringInput": "1,2,3,4,5" - } - ], - "thresholds": "5,10", - "title": "prefix 3 ms (green) postfix + sparkline", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorPrefix": false, - "colorValue": true, - "colors": ["#d44a3a", "rgba(237, 129, 40, 0.89)", "#299c46"], - "datasource": "gdev-testdata", - "decimals": null, - "description": "", - "format": "ms", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 0 - }, - "id": 3, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "", - "format": "time_series", - "intervalFactor": 1, - "refId": "A", - "scenarioId": "csv_metric_values", - "stringInput": "1,2,3,4,5" - } - ], - "thresholds": "5,10", - "title": "3 ms (red) + full height sparkline", - "type": "singlestat", - "valueFontSize": "200%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": true, - "colorPrefix": false, - "colorValue": false, - "colors": ["#d44a3a", "rgba(237, 129, 40, 0.89)", "#299c46"], - "datasource": "gdev-testdata", - "decimals": null, - "description": "", - "format": "ms", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 16, - "y": 0 - }, - "id": 4, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "", - "format": "time_series", - "intervalFactor": 1, - "refId": "A", - "scenarioId": "csv_metric_values", - "stringInput": "1,2,3,4,5" - } - ], - "thresholds": "5,10", - "title": "3 ms + red background", - "type": "singlestat", - "valueFontSize": "200%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorPrefix": false, - "colorValue": true, - "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], - "datasource": "gdev-testdata", - "decimals": null, - "description": "", - "format": "ms", - "gauge": { - "maxValue": 150, - "minValue": 0, - "show": true, - "thresholdLabels": true, - "thresholdMarkers": true - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 7 - }, - "id": 5, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "", - "format": "time_series", - "intervalFactor": 1, - "refId": "A", - "scenarioId": "csv_metric_values", - "stringInput": "10,20,80" - } - ], - "thresholds": "81,90", - "title": "80 ms green gauge, thresholds 81, 90", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorPrefix": false, - "colorValue": true, - "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], - "datasource": "gdev-testdata", - "decimals": null, - "description": "", - "format": "ms", - "gauge": { - "maxValue": 150, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 7 - }, - "id": 6, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "", - "format": "time_series", - "intervalFactor": 1, - "refId": "A", - "scenarioId": "csv_metric_values", - "stringInput": "10,20,80" - } - ], - "thresholds": "81,90", - "title": "80 ms green gauge, thresholds 81, 90, no labels", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorPrefix": false, - "colorValue": true, - "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], - "datasource": "gdev-testdata", - "decimals": null, - "description": "", - "format": "ms", - "gauge": { - "maxValue": 150, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": false - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 16, - "y": 7 - }, - "id": 7, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "", - "format": "time_series", - "intervalFactor": 1, - "refId": "A", - "scenarioId": "csv_metric_values", - "stringInput": "10,20,80" - } - ], - "thresholds": "81,90", - "title": "80 ms green gauge, thresholds 81, 90, no markers or labels", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorPrefix": false, - "colorValue": false, - "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], - "datasource": "gdev-testdata", - "decimals": null, - "description": "", - "format": "none", - "gauge": { - "maxValue": 150, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 4, - "w": 8, - "x": 0, - "y": 14 - }, - "id": 8, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "options": {}, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "Info", - "targets": [ - { - "alias": "", - "expr": "", - "format": "time_series", - "intervalFactor": 1, - "refId": "A", - "scenarioId": "random_walk_table", - "stringInput": "" - } - ], - "thresholds": "81,90", - "title": "TableData 'Info' string Column", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorPrefix": false, - "colorValue": false, - "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], - "datasource": "gdev-testdata", - "decimals": 2, - "description": "", - "format": "celsius", - "gauge": { - "maxValue": 150, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 4, - "w": 8, - "x": 8, - "y": 14 - }, - "id": 9, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "options": {}, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "Min", - "targets": [ - { - "alias": "", - "expr": "", - "format": "time_series", - "intervalFactor": 1, - "refId": "A", - "scenarioId": "random_walk_table", - "stringInput": "" - } - ], - "thresholds": "81,90", - "title": "TableData 'Value' as temp Column", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorPrefix": false, - "colorValue": false, - "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], - "datasource": "gdev-testdata", - "decimals": null, - "description": "", - "format": "dateTimeFromNow", - "gauge": { - "maxValue": 150, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 4, - "w": 8, - "x": 16, - "y": 14 - }, - "id": 10, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "options": {}, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "time", - "targets": [ - { - "alias": "", - "expr": "", - "format": "time_series", - "intervalFactor": 1, - "refId": "A", - "scenarioId": "random_walk", - "stringInput": "" - } - ], - "thresholds": "81,90", - "title": "last_time display (a few seconds ago)", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [], - "valueName": "last_time" - } - ], - "refresh": false, - "revision": 8, - "schemaVersion": 16, - "style": "dark", - "tags": ["gdev", "panel-tests"], - "templating": { - "list": [] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] - }, - "timezone": "browser", - "title": "Panel Tests - Singlestat", - "uid": "singlestat", - "version": 14 -} diff --git a/public/app/core/constants.ts b/public/app/core/constants.ts index 39e9a66064e..c1afc61481e 100644 --- a/public/app/core/constants.ts +++ b/public/app/core/constants.ts @@ -1,3 +1,6 @@ +import { PanelModel } from '../features/dashboard/state'; +import { config } from '@grafana/runtime'; + export const GRID_CELL_HEIGHT = 30; export const GRID_CELL_VMARGIN = 8; export const GRID_COLUMN_COUNT = 24; @@ -14,3 +17,18 @@ export const PANEL_BORDER = 2; export const EDIT_PANEL_ID = 23763571993; export const DEFAULT_PER_PAGE_PAGINATION = 8; + +export const DEPRECATED_PANELS: Record string> = { + singlestat: (panel: PanelModel) => { + // If 'grafana-singlestat-panel' exists, move to that + if (config.panels['grafana-singlestat-panel']) { + return 'grafana-singlestat-panel'; + } + + // Otheriwse use gauge or stat panel + if ((panel as any).gauge && (panel as any).gauge.show) { + return 'gauge'; + } + return 'stat'; + }, +}; diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index aa9b9b983de..b5fd0b065da 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -38,6 +38,7 @@ import { } from './getPanelOptionsWithDefaults'; import { QueryGroupOptions } from 'app/types'; import { PanelModelLibraryPanel } from '../../library-panels/types'; +import { isDeprecatedPanel } from '../utils/panel'; export interface GridPos { x: number; @@ -339,10 +340,9 @@ export class PanelModel implements DataConfigSource { pluginLoaded(plugin: PanelPlugin) { this.plugin = plugin; + const version = getPluginVersion(plugin); if (plugin.onPanelMigration) { - const version = getPluginVersion(plugin); - if (version !== this.pluginVersion) { this.options = plugin.onPanelMigration(this); this.pluginVersion = version; @@ -380,8 +380,7 @@ export class PanelModel implements DataConfigSource { const oldOptions: any = this.getOptionsToRemember(); const prevFieldConfig = this.fieldConfig; const oldPluginId = this.type; - const wasAngular = this.isAngularPlugin(); - + const wasAngular = this.isAngularPlugin() || isDeprecatedPanel(this.type); this.cachedPluginOptions[oldPluginId] = { properties: oldOptions, fieldConfig: prevFieldConfig, diff --git a/public/app/features/dashboard/state/actions.ts b/public/app/features/dashboard/state/actions.ts index a4b76783cac..d771d902355 100644 --- a/public/app/features/dashboard/state/actions.ts +++ b/public/app/features/dashboard/state/actions.ts @@ -15,6 +15,9 @@ import { loadPanelPlugin } from 'app/features/plugins/state/actions'; import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel, ThunkResult } from 'app/types'; import { PanelModel } from './PanelModel'; import { cancelVariables } from '../../variables/state/actions'; +import { isDeprecatedPanel } from '../utils/panel'; +import { DEPRECATED_PANELS } from '../../../core/constants'; +import { getPanelPluginNotFound } from '../dashgrid/PanelPluginError'; import { getTimeSrv } from '../services/TimeSrv'; export function getDashboardPermissions(id: number): ThunkResult { @@ -120,10 +123,27 @@ export function removeDashboard(uri: string): ThunkResult { export function initDashboardPanel(panel: PanelModel): ThunkResult { return async (dispatch, getStore) => { - let plugin = getStore().plugins.panels[panel.type]; + let pluginToLoad = panel.type; + + const isDeprecated = isDeprecatedPanel(panel.type); + let notFound = false; + let plugin = getStore().plugins.panels[pluginToLoad]; if (!plugin) { - plugin = await dispatch(loadPanelPlugin(panel.type)); + try { + plugin = await dispatch(loadPanelPlugin(pluginToLoad)); + } catch (e) { + // When plugin not found + plugin = getPanelPluginNotFound(pluginToLoad); + notFound = true; + } + } + + // if there isn't an "external" plugin with the same name as deprecated one, load the deprecated panel replacement + if (notFound && isDeprecated) { + pluginToLoad = DEPRECATED_PANELS[panel.type](panel); + plugin = await dispatch(loadPanelPlugin(pluginToLoad)); + await dispatch(changePanelPlugin(panel, pluginToLoad)); } if (!panel.plugin) { diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index e33293113ca..c39594c01f0 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -15,7 +15,7 @@ import config from 'app/core/config'; import { getTemplateSrv } from '@grafana/runtime'; // Constants -import { LS_PANEL_COPY_KEY, PANEL_BORDER } from 'app/core/constants'; +import { DEPRECATED_PANELS, LS_PANEL_COPY_KEY, PANEL_BORDER } from 'app/core/constants'; import { ShareModal } from 'app/features/dashboard/components/ShareModal'; import { ShowConfirmModalEvent, ShowModalReactEvent } from '../../../types/events'; @@ -157,3 +157,7 @@ export function calculateInnerPanelHeight(panel: PanelModel, containerHeight: nu const headerHeight = panel.hasTitle() ? config.theme.panelHeaderHeight : 0; return containerHeight - headerHeight - chromePadding - PANEL_BORDER; } + +export function isDeprecatedPanel(panelType: string) { + return !!DEPRECATED_PANELS[panelType]; +} diff --git a/public/app/features/plugins/built_in_plugins.ts b/public/app/features/plugins/built_in_plugins.ts index c7951d43a18..78dc0897239 100644 --- a/public/app/features/plugins/built_in_plugins.ts +++ b/public/app/features/plugins/built_in_plugins.ts @@ -52,8 +52,7 @@ import * as annoListPanel from 'app/plugins/panel/annolist/module'; import * as heatmapPanel from 'app/plugins/panel/heatmap/module'; import * as tablePanel from 'app/plugins/panel/table/module'; import * as oldTablePanel from 'app/plugins/panel/table-old/module'; -import * as singlestatPanel from 'app/plugins/panel/singlestat/module'; -import * as singlestatPanel2 from 'app/plugins/panel/stat/module'; +import * as statPanel from 'app/plugins/panel/stat/module'; import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module'; import * as gaugePanel from 'app/plugins/panel/gauge/module'; import * as pieChartPanel from 'app/plugins/panel/piechart/module'; @@ -102,9 +101,8 @@ const builtInPlugins: any = { 'app/plugins/panel/table-old/module': oldTablePanel, 'app/plugins/panel/news/module': newsPanel, 'app/plugins/panel/live/module': livePanel, + 'app/plugins/panel/stat/module': statPanel, 'app/plugins/panel/debug/module': debugPanel, - 'app/plugins/panel/singlestat/module': singlestatPanel, - 'app/plugins/panel/stat/module': singlestatPanel2, 'app/plugins/panel/gettingstarted/module': gettingStartedPanel, 'app/plugins/panel/gauge/module': gaugePanel, 'app/plugins/panel/piechart/module': pieChartPanel, diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index c0444c87ff7..9e7bbe040cf 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -51,6 +51,7 @@ const bust = `?_cache=${Date.now()}`; function locate(load: { address: string }) { return load.address + bust; } + grafanaRuntime.SystemJS.registry.set('plugin-loader', grafanaRuntime.SystemJS.newModule({ locate: locate })); grafanaRuntime.SystemJS.config({ @@ -214,7 +215,7 @@ export function importAppPlugin(meta: grafanaData.PluginMeta): Promise { const loaded = panelCache[id]; - if (loaded) { return loaded; } @@ -232,7 +232,7 @@ export function importPanelPlugin(id: string): Promise const meta = config.panels[id]; if (!meta) { - return Promise.resolve(getPanelPluginNotFound(id)); + throw new Error(`Plugin ${id} not found`); } panelCache[id] = importPluginModule(meta.module) diff --git a/public/app/plugins/panel/singlestat/README.md b/public/app/plugins/panel/singlestat/README.md deleted file mode 100644 index 56fbc3efda7..00000000000 --- a/public/app/plugins/panel/singlestat/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Singlestat Panel - Native Plugin - -The Singlestat Panel is **included** with Grafana. - -The Singlestat Panel allows you to show the one main summary stat of a SINGLE series. It reduces the series into a single number (by looking at the max, min, average, or sum of values in the series). Singlestat also provides thresholds to color the stat or the Panel background. It can also translate the single number into a text value, and show a sparkline summary of the series. - -Read more about it here: - -[https://grafana.com/docs/grafana/latest/features/panels/singlestat/](https://grafana.com/docs/grafana/latest/features/panels/singlestat/) diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html deleted file mode 100644 index 4308f6f4510..00000000000 --- a/public/app/plugins/panel/singlestat/editor.html +++ /dev/null @@ -1,193 +0,0 @@ -
- -
-
Gauge migration
-

- This panel is deprecated. Please migrate to the new Gauge panel. - -

- -
- -
- -
- NOTE: Sparklines are not supported in the gauge panel -
- -
- NOTE: Prefix is no longer supported but can be done via a custom unit -
- -
- NOTE: Postfix is no longer supported but can be done via a custom unit -
-

-
- -
-
Migration
-

- This panel is deprecated. Please migrate to the new Stat panel. - -

- -
- -
- -
- NOTE: Prefix is no longer supported but can be done via a custom unit -
- -
- NOTE: Postfix is no longer supported but can be done via a custom unit -
-

-
- - -
-
Value
- -
-
- -
- -
-
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
-
- - -
-
- -
- -
-
-
-
-
- - -
-
- -
- -
-
-
-
- - -
-
- - -
-
- -
-
Coloring
-
- - -
-
- - -
-
-
- - -
-
-
- - - - - - - Invert - - -
-
- -
-
Spark lines
- -
- -
- - -
-
- - -
-
- - - - -
-
- - - - -
-
-
- -
-
Gauge
- -
-
- - - -
-
- - -
- - -
-
-
diff --git a/public/app/plugins/panel/singlestat/img/icn-singlestat-panel.svg b/public/app/plugins/panel/singlestat/img/icn-singlestat-panel.svg deleted file mode 100644 index 746687d360f..00000000000 --- a/public/app/plugins/panel/singlestat/img/icn-singlestat-panel.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/app/plugins/panel/singlestat/mappings.html b/public/app/plugins/panel/singlestat/mappings.html deleted file mode 100644 index 717d1ef3f69..00000000000 --- a/public/app/plugins/panel/singlestat/mappings.html +++ /dev/null @@ -1,74 +0,0 @@ -
-
-
- - Type - -
- -
-
-
-
-
-
Set value mappings
-
-
- - - - - - - - -
- -
- -
-
-
-
-
Set range mappings
-
-
- - - - From - - To - - Text - -
- -
- -
-
-
diff --git a/public/app/plugins/panel/singlestat/module.html b/public/app/plugins/panel/singlestat/module.html deleted file mode 100644 index 75a35374566..00000000000 --- a/public/app/plugins/panel/singlestat/module.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts deleted file mode 100644 index f7a371f112a..00000000000 --- a/public/app/plugins/panel/singlestat/module.ts +++ /dev/null @@ -1,756 +0,0 @@ -import _ from 'lodash'; -import { auto } from 'angular'; -import $ from 'jquery'; -import 'vendor/flot/jquery.flot'; -import 'vendor/flot/jquery.flot.gauge'; - -import { - DataFrame, - DisplayValue, - Field, - fieldReducers, - FieldType, - GraphSeriesValue, - KeyValue, - LinkModel, - reduceField, - ReducerID, - LegacyResponseData, - getFlotPairs, - getDisplayProcessor, - PanelEvents, - formattedValueToString, - locationUtil, - getFieldDisplayName, - getColorForTheme, - InterpolateFunction, -} from '@grafana/data'; - -import { convertOldAngularValueMapping } from '@grafana/ui'; - -import config from 'app/core/config'; -import { MetricsPanelCtrl } from 'app/plugins/sdk'; -import { LinkSrv } from 'app/features/panel/panellinks/link_srv'; -import { getProcessedDataFrames } from 'app/features/query/state/runRequest'; - -const BASE_FONT_SIZE = 38; - -export interface ShowData { - field: Field; - value: any; - sparkline: GraphSeriesValue[][]; - display: DisplayValue; - - scopedVars: any; - - thresholds: any[]; - colorMap: any; -} - -class SingleStatCtrl extends MetricsPanelCtrl { - static templateUrl = 'module.html'; - - data: Partial = {}; - fontSizes: any[] = []; - fieldNames: string[] = []; - invalidGaugeRange = false; - - valueNameOptions: any[] = [ - { value: 'min', text: 'Min' }, - { value: 'max', text: 'Max' }, - { value: 'avg', text: 'Average' }, - { value: 'current', text: 'Current' }, - { value: 'total', text: 'Total' }, - { value: 'name', text: 'Name' }, - { value: 'first', text: 'First' }, - { value: 'delta', text: 'Delta' }, - { value: 'diff', text: 'Difference' }, - { value: 'diffperc', text: 'Difference percent' }, - { value: 'range', text: 'Range' }, - { value: 'last_time', text: 'Time of last point' }, - ]; - - // Set and populate defaults - panelDefaults: any = { - links: [], - datasource: null, - maxDataPoints: 100, - interval: null, - targets: [{}], - cacheTimeout: null, - format: 'none', - prefix: '', - postfix: '', - nullText: null, - valueMaps: [{ value: 'null', op: '=', text: 'N/A' }], - mappingTypes: [ - { name: 'value to text', value: 1 }, - { name: 'range to text', value: 2 }, - ], - rangeMaps: [{ from: 'null', to: 'null', text: 'N/A' }], - mappingType: 1, - nullPointMode: 'connected', - valueName: 'avg', - prefixFontSize: '50%', - valueFontSize: '80%', - postfixFontSize: '50%', - thresholds: '', - colorBackground: false, - colorValue: false, - colors: ['#299c46', 'rgba(237, 129, 40, 0.89)', '#d44a3a'], - sparkline: { - show: false, - full: false, - ymin: null, - ymax: null, - lineColor: 'rgb(31, 120, 193)', - fillColor: 'rgba(31, 118, 189, 0.18)', - }, - gauge: { - show: false, - minValue: 0, - maxValue: 100, - thresholdMarkers: true, - thresholdLabels: false, - }, - tableColumn: '', - }; - - /** @ngInject */ - constructor($scope: any, $injector: auto.IInjectorService, private linkSrv: LinkSrv, private $sanitize: any) { - super($scope, $injector); - _.defaults(this.panel, this.panelDefaults); - - this.events.on(PanelEvents.dataFramesReceived, this.onFramesReceived.bind(this)); - this.events.on(PanelEvents.dataSnapshotLoad, this.onSnapshotLoad.bind(this)); - this.events.on(PanelEvents.editModeInitialized, this.onInitEditMode.bind(this)); - - this.useDataFrames = true; - - this.onSparklineColorChange = this.onSparklineColorChange.bind(this); - this.onSparklineFillChange = this.onSparklineFillChange.bind(this); - } - - onInitEditMode() { - this.fontSizes = ['20%', '30%', '50%', '70%', '80%', '100%', '110%', '120%', '150%', '170%', '200%']; - this.addEditorTab('Options', 'public/app/plugins/panel/singlestat/editor.html', 2); - this.addEditorTab('Value Mappings', 'public/app/plugins/panel/singlestat/mappings.html', 3); - } - - migrateToPanel(type: string) { - this.onPluginTypeChange(config.panels[type]); - } - - setUnitFormat() { - return (unit: string) => { - this.panel.format = unit; - this.refresh(); - }; - } - - onSnapshotLoad(dataList: LegacyResponseData[]) { - this.onFramesReceived(getProcessedDataFrames(dataList)); - } - - onFramesReceived(frames: DataFrame[]) { - const { panel } = this; - this.dataList = frames; - - if (frames && frames.length > 1) { - this.data = { - value: 0, - display: { - text: 'Only queries that return single series/table is supported', - numeric: NaN, - }, - }; - this.render(); - return; - } - - const distinct = getDistinctNames(frames); - let fieldInfo: FieldInfo | undefined = distinct.byName[panel.tableColumn]; - - this.fieldNames = distinct.names; - - if (!fieldInfo) { - fieldInfo = distinct.first; - } - - if (!fieldInfo) { - const processor = getDisplayProcessor({ - field: { - config: { - mappings: convertOldAngularValueMapping(this.panel), - noValue: 'No Data', - }, - }, - theme: config.theme, - timeZone: this.dashboard.getTimezone(), - }); - // When we don't have any field - this.data = { - value: null, - display: processor(null), - }; - } else { - this.data = this.processField(fieldInfo); - } - - this.render(); - } - - processField(fieldInfo: FieldInfo) { - const { panel, dashboard } = this; - - const name = getFieldDisplayName(fieldInfo.field, fieldInfo.frame.frame, this.dataList as DataFrame[]); - let calc = panel.valueName; - let calcField = fieldInfo.field; - let val: any = undefined; - - if ('name' === calc) { - val = name; - } else { - if ('last_time' === calc) { - if (fieldInfo.frame.firstTimeField) { - calcField = fieldInfo.frame.firstTimeField; - calc = ReducerID.last; - } - } - - // Normalize functions (avg -> mean, etc) - const r = fieldReducers.getIfExists(calc); - if (r) { - calc = r.id; - // With strings, don't accidentally use a math function - if (calcField.type === FieldType.string) { - const avoid = [ReducerID.mean, ReducerID.sum]; - if (avoid.includes(calc)) { - calc = panel.valueName = ReducerID.first; - } - } - } else { - calc = ReducerID.lastNotNull; - } - - // Calculate the value - val = reduceField({ - field: calcField, - reducers: [calc], - })[calc]; - } - - const processor = getDisplayProcessor({ - field: { - ...fieldInfo.field, - config: { - ...fieldInfo.field.config, - unit: panel.format, - decimals: panel.decimals, - mappings: convertOldAngularValueMapping(panel), - }, - }, - theme: config.theme, - timeZone: dashboard.getTimezone(), - }); - - const sparkline: any[] = []; - const data = { - field: fieldInfo.field, - value: val, - display: processor(val), - scopedVars: _.extend({}, panel.scopedVars), - sparkline, - }; - - data.scopedVars['__name'] = { value: name }; - panel.tableColumn = this.fieldNames.length > 1 ? name : ''; - - // Get the fields for a sparkline - if (panel.sparkline && panel.sparkline.show && fieldInfo.frame.firstTimeField) { - data.sparkline = getFlotPairs({ - xField: fieldInfo.frame.firstTimeField, - yField: fieldInfo.field, - nullValueMode: panel.nullPointMode, - }); - } - - return data; - } - - canModifyText() { - return !this.panel.gauge.show; - } - - setColoring(options: { background: any }) { - if (options.background) { - this.panel.colorValue = false; - this.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; - } else { - this.panel.colorBackground = false; - this.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; - } - this.render(); - } - - invertColorOrder() { - const tmp = this.panel.colors[0]; - this.panel.colors[0] = this.panel.colors[2]; - this.panel.colors[2] = tmp; - this.render(); - } - - onColorChange(panelColorIndex: number) { - return (color: string) => { - this.panel.colors[panelColorIndex] = color; - this.render(); - }; - } - - onSparklineColorChange(newColor: string) { - this.panel.sparkline.lineColor = newColor; - this.render(); - } - - onSparklineFillChange(newColor: string) { - this.panel.sparkline.fillColor = newColor; - this.render(); - } - - removeValueMap(map: any) { - const index = _.indexOf(this.panel.valueMaps, map); - this.panel.valueMaps.splice(index, 1); - this.render(); - } - - addValueMap() { - this.panel.valueMaps.push({ value: '', op: '=', text: '' }); - } - - removeRangeMap(rangeMap: any) { - const index = _.indexOf(this.panel.rangeMaps, rangeMap); - this.panel.rangeMaps.splice(index, 1); - this.render(); - } - - addRangeMap() { - this.panel.rangeMaps.push({ from: '', to: '', text: '' }); - } - - link(scope: any, elem: JQuery, attrs: any, ctrl: any) { - const $location = this.$location; - const linkSrv = this.linkSrv; - const $timeout = this.$timeout; - const $sanitize = this.$sanitize; - const panel = ctrl.panel; - const templateSrv = this.templateSrv; - let linkInfo: LinkModel | null = null; - elem = elem.find('.singlestat-panel'); - - function getPanelContainer() { - return elem.closest('.panel-container'); - } - - function applyColoringThresholds(valueString: string) { - const data = ctrl.data; - const color = getColorForValue(data, data.value); - if (color) { - return '' + valueString + ''; - } - - return valueString; - } - - function getSpan(className: string, fontSizePercent: string, applyColoring: any, value: string) { - value = $sanitize(templateSrv.replace(value, ctrl.data.scopedVars)); - value = applyColoring ? applyColoringThresholds(value) : value; - const pixelSize = (parseInt(fontSizePercent, 10) / 100) * BASE_FONT_SIZE; - return '' + value + ''; - } - - function getBigValueHtml() { - const data: ShowData = ctrl.data; - let body = '
'; - - if (panel.prefix) { - body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.colorPrefix, panel.prefix); - } - - body += getSpan( - 'singlestat-panel-value', - panel.valueFontSize, - panel.colorValue, - formattedValueToString(data.display) - ); - - if (panel.postfix) { - body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.colorPostfix, panel.postfix); - } - - body += '
'; - - return body; - } - - function getValueText() { - const data: ShowData = ctrl.data; - let result = panel.prefix ? templateSrv.replace(panel.prefix, data.scopedVars) : ''; - result += formattedValueToString(data.display); - result += panel.postfix ? templateSrv.replace(panel.postfix, data.scopedVars) : ''; - - return result; - } - - function addGauge() { - const data: ShowData = ctrl.data; - const width = elem.width() || 10; - const height = elem.height() || 10; - - // Allow to use a bit more space for wide gauges - const dimension = Math.min(width, height * 1.3); - - ctrl.invalidGaugeRange = false; - if (panel.gauge.minValue > panel.gauge.maxValue) { - ctrl.invalidGaugeRange = true; - return; - } - - const plotCanvas = $('
'); - const plotCss = { - top: '5px', - margin: 'auto', - position: 'relative', - height: height * 0.9 + 'px', - width: dimension + 'px', - }; - - plotCanvas.css(plotCss); - - const thresholds = []; - - for (let i = 0; i < data.thresholds.length; i++) { - thresholds.push({ - value: data.thresholds[i], - color: data.colorMap[i], - }); - } - thresholds.push({ - value: panel.gauge.maxValue, - color: data.colorMap[data.colorMap.length - 1], - }); - - const bgColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)'; - - 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; - const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio; - const thresholdMarkersWidth = gaugeWidth / 5; - const thresholdLabelFontSize = fontSize / 2.5; - - const options: any = { - series: { - gauges: { - gauge: { - min: panel.gauge.minValue, - max: panel.gauge.maxValue, - background: { color: bgColor }, - border: { color: null }, - shadow: { show: false }, - width: gaugeWidth, - }, - frame: { show: false }, - label: { show: false }, - layout: { margin: 0, thresholdWidth: 0 }, - cell: { border: { width: 0 } }, - threshold: { - values: thresholds, - label: { - show: panel.gauge.thresholdLabels, - margin: thresholdMarkersWidth + 1, - font: { size: thresholdLabelFontSize }, - }, - show: panel.gauge.thresholdMarkers, - width: thresholdMarkersWidth, - }, - value: { - color: panel.colorValue ? getColorForValue(data, data.display.numeric) : null, - formatter: () => { - return getValueText(); - }, - font: { - size: fontSize, - family: config.theme.typography.fontFamily.sansSerif, - }, - }, - show: true, - }, - }, - }; - - elem.append(plotCanvas); - - const plotSeries = { - data: [[0, data.value]], - }; - - $.plot(plotCanvas, [plotSeries], options); - } - - function addSparkline() { - const data: ShowData = ctrl.data; - const width = elem.width() || 30; - - if (width && width < 30) { - // element has not gotten it's width yet - // delay sparkline render - setTimeout(addSparkline, 30); - return; - } - - if (!data.sparkline || !data.sparkline.length) { - // no sparkline data - return; - } - - const height = ctrl.height; - const plotCanvas = $('
'); - const plotCss: any = {}; - plotCss.position = 'absolute'; - plotCss.bottom = '0px'; - - if (panel.sparkline.full) { - plotCss.left = '0px'; - plotCss.width = width + 'px'; - const dynamicHeightMargin = height <= 100 ? 5 : Math.round(height / 100) * 15 + 5; - plotCss.height = height - dynamicHeightMargin + 'px'; - } else { - plotCss.left = '0px'; - plotCss.width = width + 'px'; - plotCss.height = Math.floor(height * 0.25) + 'px'; - } - - plotCanvas.css(plotCss); - - const options = { - legend: { show: false }, - series: { - lines: { - show: true, - fill: 1, - lineWidth: 1, - fillColor: getColorForTheme(panel.sparkline.fillColor, config.theme), - zero: false, - }, - }, - yaxis: { - show: false, - min: panel.sparkline.ymin, - max: panel.sparkline.ymax, - }, - xaxis: { - show: false, - mode: 'time', - min: ctrl.range.from.valueOf(), - max: ctrl.range.to.valueOf(), - }, - grid: { hoverable: false, show: false }, - }; - - elem.append(plotCanvas); - - const plotSeries = { - data: data.sparkline, - color: getColorForTheme(panel.sparkline.lineColor, config.theme), - }; - - $.plot(plotCanvas, [plotSeries], options); - } - - function render() { - if (!ctrl.data) { - return; - } - const { data, panel } = ctrl; - - // get thresholds - data.thresholds = panel.thresholds - ? panel.thresholds.split(',').map((strVale: string) => { - return Number(strVale.trim()); - }) - : []; - - // Map panel colors to hex or rgb/a values - if (panel.colors) { - data.colorMap = panel.colors.map((color: string) => getColorForTheme(color, config.theme)); - } - - const body = panel.gauge.show ? '' : getBigValueHtml(); - - if (panel.colorBackground) { - const color = getColorForValue(data, data.display.numeric); - if (color) { - getPanelContainer().css('background-color', color); - if (scope.fullscreen) { - elem.css('background-color', color); - } else { - elem.css('background-color', ''); - } - } else { - getPanelContainer().css('background-color', ''); - elem.css('background-color', ''); - } - } else { - getPanelContainer().css('background-color', ''); - elem.css('background-color', ''); - } - - elem.html(body); - - if (panel.sparkline.show) { - addSparkline(); - } - - if (panel.gauge.show) { - addGauge(); - } - - elem.toggleClass('pointer', panel.links.length > 0); - - if (panel.links.length > 0) { - const replace: InterpolateFunction = (value, vars) => - templateSrv.replace(value, { ...vars, ...data.scopedVars }); - linkInfo = linkSrv.getDataLinkUIModel(panel.links[0], replace, {}); - } else { - linkInfo = null; - } - } - - function hookupDrilldownLinkTooltip() { - // drilldown link tooltip - const drilldownTooltip = $('
hello
"'); - - elem.mouseleave(() => { - if (panel.links.length === 0) { - return; - } - $timeout(() => { - drilldownTooltip.detach(); - }); - }); - - elem.click((evt) => { - if (!linkInfo) { - return; - } - // ignore title clicks in title - if ($(evt).parents('.panel-header').length > 0) { - return; - } - - if (linkInfo.target === '_blank') { - window.open(linkInfo.href, '_blank'); - return; - } - - if (linkInfo.href.indexOf('http') === 0) { - window.location.href = linkInfo.href; - } else { - $timeout(() => { - $location.url(locationUtil.stripBaseFromUrl(linkInfo!.href)); - }); - } - - drilldownTooltip.detach(); - }); - - elem.mousemove((e) => { - if (!linkInfo) { - return; - } - - drilldownTooltip.text('click to go to: ' + linkInfo.title); - drilldownTooltip.place_tt(e.pageX, e.pageY - 50); - }); - } - - hookupDrilldownLinkTooltip(); - - this.events.on(PanelEvents.render, () => { - render(); - ctrl.renderingCompleted(); - }); - } -} - -function getColorForValue(data: any, value: number) { - if (!_.isFinite(value)) { - return null; - } - - for (let i = data.thresholds.length; i > 0; i--) { - if (value >= data.thresholds[i - 1]) { - return data.colorMap[i]; - } - } - - return _.first(data.colorMap); -} - -//------------------------------------------------ -// Private utility functions -// Something like this should be available in a -// DataFrame[] abstraction helper -//------------------------------------------------ - -interface FrameInfo { - firstTimeField?: Field; - frame: DataFrame; -} - -interface FieldInfo { - field: Field; - frame: FrameInfo; -} - -interface DistinctFieldsInfo { - first?: FieldInfo; - byName: KeyValue; - names: string[]; -} - -function getDistinctNames(data: DataFrame[]): DistinctFieldsInfo { - const distinct: DistinctFieldsInfo = { - byName: {}, - names: [], - }; - for (const frame of data) { - const info: FrameInfo = { frame }; - for (const field of frame.fields) { - if (field.type === FieldType.time) { - if (!info.firstTimeField) { - info.firstTimeField = field; - } - } else { - const f = { field, frame: info }; - if (!distinct.first) { - distinct.first = f; - } - let t = field.config.displayName; - if (t && !distinct.byName[t]) { - distinct.byName[t] = f; - distinct.names.push(t); - } - t = field.name; - if (t && !distinct.byName[t]) { - distinct.byName[t] = f; - distinct.names.push(t); - } - } - } - } - return distinct; -} - -export { SingleStatCtrl, SingleStatCtrl as PanelCtrl, getColorForValue }; diff --git a/public/app/plugins/panel/singlestat/plugin.json b/public/app/plugins/panel/singlestat/plugin.json deleted file mode 100644 index f6e4ec497d3..00000000000 --- a/public/app/plugins/panel/singlestat/plugin.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "panel", - "name": "Singlestat", - "id": "singlestat", - "state": "deprecated", - - "info": { - "description": "Singlestat Panel for Grafana", - "author": { - "name": "Grafana Labs", - "url": "https://grafana.com" - }, - "logos": { - "small": "img/icn-singlestat-panel.svg", - "large": "img/icn-singlestat-panel.svg" - } - } -} diff --git a/public/app/plugins/panel/singlestat/specs/singlestat.test.ts b/public/app/plugins/panel/singlestat/specs/singlestat.test.ts deleted file mode 100644 index 98440f92f87..00000000000 --- a/public/app/plugins/panel/singlestat/specs/singlestat.test.ts +++ /dev/null @@ -1,457 +0,0 @@ -import { SingleStatCtrl, ShowData } from '../module'; -import { dateTime, ReducerID, getFieldDisplayName, LegacyResponseData } from '@grafana/data'; -import { LinkSrv } from 'app/features/panel/panellinks/link_srv'; -import { DashboardModel } from 'app/features/dashboard/state'; - -interface TestContext { - ctrl: SingleStatCtrl; - input: LegacyResponseData[]; - data: Partial; - setup: (setupFunc: any) => void; -} - -describe('SingleStatCtrl', () => { - const ctx: TestContext = {} as TestContext; - const epoch = 1505826363746; - Date.now = () => epoch; - - const $scope = { - $on: () => {}, - }; - - const $injector = { - get: () => {}, - }; - - const $sanitize = {}; - - SingleStatCtrl.prototype.dashboard = ({ - getTimezone: jest.fn(() => 'utc'), - } as any) as DashboardModel; - - function singleStatScenario(desc: string, func: any) { - describe(desc, () => { - ctx.setup = (setupFunc: any) => { - beforeEach(() => { - SingleStatCtrl.prototype.panel = { - events: { - on: () => {}, - emit: () => {}, - }, - }; - - // @ts-ignore - ctx.ctrl = new SingleStatCtrl($scope, $injector, {} as LinkSrv, $sanitize); - setupFunc(); - ctx.ctrl.onSnapshotLoad(ctx.input); - ctx.data = ctx.ctrl.data; - }); - }; - - func(ctx); - }); - } - - singleStatScenario('with defaults', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [ - { - target: 'test.cpu1', - datapoints: [ - [10, 1], - [20, 2], - ], - }, - ]; - }); - - it('Should use series avg as default main value', () => { - expect(ctx.data.value).toBe(15); - }); - - it('should set formatted falue', () => { - expect(ctx.data.display!.text).toBe('15'); - }); - }); - - singleStatScenario('showing serie name instead of value', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [ - { - target: 'test.cpu1', - datapoints: [ - [10, 1], - [20, 2], - ], - }, - ]; - ctx.ctrl.panel.valueName = 'name'; - }); - - it('Should use series avg as default main value', () => { - const name = getFieldDisplayName(ctx.data.field!); - expect(name).toBe('test.cpu1'); - }); - - it('should set formatted value', () => { - expect(ctx.data.display!.text).toBe('test.cpu1'); - }); - }); - - singleStatScenario('showing last iso time instead of value', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [ - { - target: 'test.cpu1', - datapoints: [ - [10, 12], - [20, 1505634997920], - ], - }, - ]; - ctx.ctrl.panel.valueName = 'last_time'; - ctx.ctrl.panel.format = 'dateTimeAsIso'; - ctx.ctrl.dashboard.getTimezone = () => 'browser'; - }); - - it('Should use time instead of value', () => { - expect(ctx.data.value).toBe(1505634997920); - }); - - it('should set formatted value', () => { - expect(dateTime(ctx.data.display!.text).valueOf()).toBe(1505634997000); - }); - }); - - singleStatScenario('showing last iso time instead of value (in UTC)', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [ - { - target: 'test.cpu1', - datapoints: [ - [10, 12], - [20, 5000], - ], - }, - ]; - ctx.ctrl.panel.valueName = 'last_time'; - ctx.ctrl.panel.format = 'dateTimeAsIso'; - ctx.ctrl.dashboard.getTimezone = () => 'utc'; - }); - - it('should set value', () => { - expect(ctx.data.display!.text).toBe('1970-01-01 00:00:05'); - }); - }); - - singleStatScenario('showing last us time instead of value', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [ - { - target: 'test.cpu1', - datapoints: [ - [10, 12], - [20, 1505634997920], - ], - }, - ]; - ctx.ctrl.panel.valueName = 'last_time'; - ctx.ctrl.panel.format = 'dateTimeAsUS'; - ctx.ctrl.dashboard.getTimezone = () => 'browser'; - }); - - it('Should use time instead of value', () => { - expect(ctx.data.value).toBe(1505634997920); - }); - - it('should set formatted value', () => { - expect(ctx.data.display!.text).toBe(dateTime(1505634997920).format('MM/DD/YYYY h:mm:ss a')); - }); - }); - - singleStatScenario('showing last us time instead of value (in UTC)', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [ - { - target: 'test.cpu1', - datapoints: [ - [10, 12], - [20, 5000], - ], - }, - ]; - ctx.ctrl.panel.valueName = 'last_time'; - ctx.ctrl.panel.format = 'dateTimeAsUS'; - ctx.ctrl.dashboard.getTimezone = () => 'utc'; - }); - - it('should set formatted value', () => { - expect(ctx.data.display!.text).toBe('01/01/1970 12:00:05 am'); - }); - }); - - singleStatScenario('showing last time from now instead of value', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [ - { - target: 'test.cpu1', - datapoints: [ - [10, 12], - [20, 1505634997920], - ], - }, - ]; - ctx.ctrl.panel.valueName = 'last_time'; - ctx.ctrl.panel.format = 'dateTimeFromNow'; - }); - - it('Should use time instead of value', () => { - expect(ctx.data.value).toBe(1505634997920); - }); - - it('should set formatted value', () => { - expect(ctx.data.display!.text).toBe('2 days ago'); - }); - }); - - singleStatScenario('showing last time from now instead of value (in UTC)', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [ - { - target: 'test.cpu1', - datapoints: [ - [10, 12], - [20, 1505634997920], - ], - }, - ]; - ctx.ctrl.panel.valueName = 'last_time'; - ctx.ctrl.panel.format = 'dateTimeFromNow'; - }); - - it('should set formatted value', () => { - expect(ctx.data.display!.text).toBe('2 days ago'); - }); - }); - - singleStatScenario( - 'MainValue should use same number for decimals as displayed when checking thresholds', - (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [ - { - target: 'test.cpu1', - datapoints: [ - [99.999, 1], - [99.99999, 2], - ], - }, - ]; - ctx.ctrl.panel.valueName = 'avg'; - ctx.ctrl.panel.format = 'none'; - }); - - it('Should be rounded', () => { - expect(ctx.data.value).toBe(99.999495); - }); - - it('should set formatted value', () => { - expect(ctx.data.display!.text).toBe('100.0'); - }); - } - ); - - singleStatScenario('When value to text mapping is specified', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [{ target: 'test.cpu1', datapoints: [[9.9, 1]] }]; - ctx.ctrl.panel.valueMaps = [{ value: '9.9', text: 'OK' }]; - }); - - it('value should remain', () => { - expect(ctx.data.value).toBe(9.9); - }); - - it('Should replace value with text', () => { - expect(ctx.data.display!.text).toBe('OK'); - }); - }); - - singleStatScenario('When mapping null values and no data', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = []; // No data - ctx.ctrl.panel.valueMaps = [{ value: 'null', text: 'XYZ' }]; - }); - - it('value should be null', () => { - expect(ctx.data.value).toBe(null); - }); - - it('Should replace value with text', () => { - expect(ctx.data.display!.text).toBe('XYZ'); - }); - }); - - singleStatScenario('When range to text mapping is specified for first range', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [{ target: 'test.cpu1', datapoints: [[41, 50]] }]; - ctx.ctrl.panel.mappingType = 2; - ctx.ctrl.panel.rangeMaps = [ - { from: '10', to: '50', text: 'OK' }, - { from: '51', to: '100', text: 'NOT OK' }, - ]; - }); - - it('Should replace value with text OK', () => { - expect(ctx.data.display!.text).toBe('OK'); - }); - }); - - singleStatScenario('When range to text mapping is specified for other ranges', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = [{ target: 'test.cpu1', datapoints: [[65, 75]] }]; - ctx.ctrl.panel.mappingType = 2; - ctx.ctrl.panel.rangeMaps = [ - { from: '10', to: '50', text: 'OK' }, - { from: '51', to: '100', text: 'NOT OK' }, - ]; - }); - - it('Should replace value with text NOT OK', () => { - expect(ctx.data.display!.text).toBe('NOT OK'); - }); - }); - - describe('When table data', () => { - const tableData = [ - { - columns: [{ text: 'Time', type: 'time' }, { text: 'test1' }, { text: 'mean' }, { text: 'test2' }], - rows: [[1492759673649, 'ignore1', 15, 'ignore2']], - type: 'table', - }, - ]; - - singleStatScenario('with default values', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = tableData; - ctx.ctrl.panel.tableColumn = 'mean'; - ctx.ctrl.panel.format = 'none'; - }); - - it('Should use first rows value as default main value', () => { - expect(ctx.data.value).toBe(15); - }); - - it('should set formatted value', () => { - expect(ctx.data.display!.text).toBe('15'); - }); - }); - - singleStatScenario('When table data has multiple columns', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = tableData; - ctx.ctrl.panel.tableColumn = ''; - }); - - it('Should set column to first column that is not time', () => { - expect(ctx.ctrl.panel.tableColumn).toBe('test1'); - }); - }); - - singleStatScenario( - 'MainValue should use same number for decimals as displayed when checking thresholds', - (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = tableData; - ctx.input[0].rows[0] = [1492759673649, 'ignore1', 99.99999, 'ignore2']; - ctx.ctrl.panel.mappingType = 0; - ctx.ctrl.panel.tableColumn = 'mean'; - }); - - it('Should be rounded', () => { - expect(ctx.data.value).toBe(99.99999); - }); - - it('should set formatted falue', () => { - expect(ctx.data.display!.text).toBe('100.0'); - }); - } - ); - - singleStatScenario('When value to text mapping is specified', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = tableData; - ctx.input[0].rows[0] = [1492759673649, 'ignore1', 10, 'ignore2']; - ctx.ctrl.panel.mappingType = 1; - ctx.ctrl.panel.tableColumn = 'mean'; - ctx.ctrl.panel.valueMaps = [{ value: '10', text: 'OK' }]; - }); - - it('value should remain', () => { - expect(ctx.data.value).toBe(10); - }); - - it('Should replace value with text', () => { - expect(ctx.data.display!.text).toBe('OK'); - }); - }); - - singleStatScenario('When range to text mapping is specified for first range', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = tableData; - ctx.input[0].rows[0] = [1492759673649, 'ignore1', 41, 'ignore2']; - ctx.ctrl.panel.tableColumn = 'mean'; - ctx.ctrl.panel.mappingType = 2; - ctx.ctrl.panel.rangeMaps = [ - { from: '10', to: '50', text: 'OK' }, - { from: '51', to: '100', text: 'NOT OK' }, - ]; - }); - - it('Should replace value with text OK', () => { - expect(ctx.data.display!.text).toBe('OK'); - }); - }); - - singleStatScenario('When range to text mapping is specified for other ranges', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = tableData; - ctx.input[0].rows[0] = [1492759673649, 'ignore1', 65, 'ignore2']; - ctx.ctrl.panel.tableColumn = 'mean'; - ctx.ctrl.panel.mappingType = 2; - ctx.ctrl.panel.rangeMaps = [ - { from: '10', to: '50', text: 'OK' }, - { from: '51', to: '100', text: 'NOT OK' }, - ]; - }); - - it('Should replace value with text NOT OK', () => { - expect(ctx.data.display!.text).toBe('NOT OK'); - }); - }); - - singleStatScenario('When value is string', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = tableData; - ctx.input[0].rows[0] = [1492759673649, 'ignore1', 65, 'ignore2']; - ctx.ctrl.panel.tableColumn = 'test1'; - ctx.ctrl.panel.valueName = ReducerID.first; - }); - - it('Should replace value with text NOT OK', () => { - expect(ctx.data.display!.text).toBe('ignore1'); - }); - }); - - singleStatScenario('When value is zero', (ctx: TestContext) => { - ctx.setup(() => { - ctx.input = tableData; - ctx.input[0].rows[0] = [1492759673649, 'ignore1', 0, 'ignore2']; - ctx.ctrl.panel.tableColumn = 'mean'; - }); - - it('Should return zero', () => { - expect(ctx.data.value).toBe(0); - }); - }); - }); -}); diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_panel.test.ts b/public/app/plugins/panel/singlestat/specs/singlestat_panel.test.ts deleted file mode 100644 index 1a88c6bb770..00000000000 --- a/public/app/plugins/panel/singlestat/specs/singlestat_panel.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { getColorForValue } from '../module'; - -describe('grafanaSingleStat', () => { - describe('legacy thresholds', () => { - describe('positive thresholds', () => { - const data: any = { - colorMap: ['green', 'yellow', 'red'], - thresholds: [20, 50], - }; - - it('5 should return green', () => { - expect(getColorForValue(data, 5)).toBe('green'); - }); - - it('19.9 should return green', () => { - expect(getColorForValue(data, 19.9)).toBe('green'); - }); - - it('20 should return yellow', () => { - expect(getColorForValue(data, 20)).toBe('yellow'); - }); - - it('20.1 should return yellow', () => { - expect(getColorForValue(data, 20.1)).toBe('yellow'); - }); - - it('25 should return yellow', () => { - expect(getColorForValue(data, 25)).toBe('yellow'); - }); - - it('50 should return red', () => { - expect(getColorForValue(data, 50)).toBe('red'); - }); - - it('55 should return red', () => { - expect(getColorForValue(data, 55)).toBe('red'); - }); - }); - }); - - describe('negative thresholds', () => { - const data: any = { - colorMap: ['green', 'yellow', 'red'], - thresholds: [0, 20], - }; - - it('-30 should return green', () => { - expect(getColorForValue(data, -30)).toBe('green'); - }); - - it('1 should return green', () => { - expect(getColorForValue(data, 1)).toBe('yellow'); - }); - - it('22 should return green', () => { - expect(getColorForValue(data, 22)).toBe('red'); - }); - }); - - describe('negative thresholds', () => { - const data: any = { - colorMap: ['green', 'yellow', 'red'], - thresholds: [-27, 20], - }; - - it('-30 should return green', () => { - expect(getColorForValue(data, -26)).toBe('yellow'); - }); - }); -});