diff --git a/packages/grafana-build/README.md b/packages/grafana-build/README.md new file mode 100644 index 00000000000..588d91861d3 --- /dev/null +++ b/packages/grafana-build/README.md @@ -0,0 +1,4 @@ +# Shared build scripts + +Shared build scripts for plugins & internal packages. + diff --git a/packages/grafana-build/package.json b/packages/grafana-build/package.json new file mode 100644 index 00000000000..0bc9340a667 --- /dev/null +++ b/packages/grafana-build/package.json @@ -0,0 +1,12 @@ +{ + "name": "@grafana/build", + "private": true, + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/packages/grafana-ui/README.md b/packages/grafana-ui/README.md new file mode 100644 index 00000000000..1413965f7da --- /dev/null +++ b/packages/grafana-ui/README.md @@ -0,0 +1,3 @@ +# Grafana (WIP) shared component library + +Used by internal & external plugins. diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 0c023817b9f..d4bf80f5dec 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -10,17 +10,19 @@ "author": "", "license": "ISC", "dependencies": { + "@torkelo/react-select": "2.1.1", + "moment": "^2.22.2", "react": "^16.6.3", "react-dom": "^16.6.3", - "react-popper": "^1.3.0", "react-highlight-words": "0.11.0", - "@torkelo/react-select": "2.1.1", + "react-popper": "^1.3.0", "react-transition-group": "^2.2.1", - "moment": "^2.22.2", + "lodash": "^4.17.10", "react-virtualized": "^9.21.0" }, "devDependencies": { "@types/jest": "^23.3.2", + "@types/lodash": "^4.17.10", "@types/react": "^16.7.6", "typescript": "^3.2.2" } diff --git a/public/app/viz/Graph.tsx b/packages/grafana-ui/src/components/Graph/Graph.tsx similarity index 95% rename from public/app/viz/Graph.tsx rename to packages/grafana-ui/src/components/Graph/Graph.tsx index c1330d6ce8a..07edd3f11f0 100644 --- a/public/app/viz/Graph.tsx +++ b/packages/grafana-ui/src/components/Graph/Graph.tsx @@ -1,11 +1,9 @@ // Libraries import $ from 'jquery'; import React, { PureComponent } from 'react'; -import 'vendor/flot/jquery.flot'; -import 'vendor/flot/jquery.flot.time'; // Types -import { TimeRange, TimeSeriesVMs } from '@grafana/ui'; +import { TimeRange, TimeSeriesVMs } from '../../types'; interface GraphProps { timeSeries: TimeSeriesVMs; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index b57b9bcfdb7..0a750cc970f 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -1 +1,2 @@ export { DeleteButton } from './DeleteButton/DeleteButton'; +export { Graph } from './Graph/Graph'; diff --git a/packages/grafana-ui/src/index.ts b/packages/grafana-ui/src/index.ts index 195d95f598d..974d976bbef 100644 --- a/packages/grafana-ui/src/index.ts +++ b/packages/grafana-ui/src/index.ts @@ -1,2 +1,3 @@ export * from './components'; export * from './types'; +export * from './utils'; diff --git a/packages/grafana-ui/src/types/series.ts b/packages/grafana-ui/src/types/series.ts index 6868ff567c9..49662e9872d 100644 --- a/packages/grafana-ui/src/types/series.ts +++ b/packages/grafana-ui/src/types/series.ts @@ -5,7 +5,7 @@ export enum LoadingState { Error = 'Error', } -export type TimeSeriesValue = string | number | null; +export type TimeSeriesValue = number | null; export type TimeSeriesPoints = TimeSeriesValue[][]; @@ -24,9 +24,9 @@ export interface TimeSeriesVM { } export interface TimeSeriesStats { - total: number; - max: number; - min: number; + total: number | null; + max: number | null; + min: number | null; logmin: number; avg: number | null; current: number | null; diff --git a/packages/grafana-ui/src/utils/colors.ts b/packages/grafana-ui/src/utils/colors.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/grafana-ui/src/utils/index.ts b/packages/grafana-ui/src/utils/index.ts new file mode 100644 index 00000000000..4d9b9a4b948 --- /dev/null +++ b/packages/grafana-ui/src/utils/index.ts @@ -0,0 +1 @@ +export * from './processTimeSeries'; diff --git a/packages/grafana-ui/src/utils/processTimeSeries.ts b/packages/grafana-ui/src/utils/processTimeSeries.ts new file mode 100644 index 00000000000..e92aaf0c1a6 --- /dev/null +++ b/packages/grafana-ui/src/utils/processTimeSeries.ts @@ -0,0 +1,174 @@ +// Libraries +import _ from 'lodash'; + +// Types +import { TimeSeries, TimeSeriesVMs, NullValueMode, TimeSeriesValue } from '../types'; + +interface Options { + timeSeries: TimeSeries[]; + nullValueMode: NullValueMode; + colorPalette: string[]; +} + +export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: Options): TimeSeriesVMs { + const vmSeries = timeSeries.map((item, index) => { + const colorIndex = index % colorPalette.length; + const label = item.target; + const result = []; + + // stat defaults + let total = 0; + let max: TimeSeriesValue = -Number.MAX_VALUE; + let min: TimeSeriesValue = Number.MAX_VALUE; + let logmin = Number.MAX_VALUE; + let avg: TimeSeriesValue = null; + let current: TimeSeriesValue = null; + let first: TimeSeriesValue = null; + let delta: TimeSeriesValue = 0; + let diff: TimeSeriesValue = null; + let range: TimeSeriesValue = null; + let timeStep = Number.MAX_VALUE; + let allIsNull = true; + let allIsZero = true; + + const ignoreNulls = nullValueMode === NullValueMode.Ignore; + const nullAsZero = nullValueMode === NullValueMode.AsZero; + + let currentTime: TimeSeriesValue = null; + let currentValue: TimeSeriesValue = null; + let nonNulls = 0; + let previousTime: TimeSeriesValue = null; + let previousValue = 0; + let previousDeltaUp = true; + + for (let i = 0; i < item.datapoints.length; i++) { + currentValue = item.datapoints[i][0]; + currentTime = item.datapoints[i][1]; + + if (typeof currentTime !== 'number') { + continue; + } + + if (typeof currentValue !== 'number') { + continue; + } + + // Due to missing values we could have different timeStep all along the series + // so we have to find the minimum one (could occur with aggregators such as ZimSum) + if (previousTime !== null && currentTime !== null) { + const currentStep = currentTime - previousTime; + if (currentStep < timeStep) { + timeStep = currentStep; + } + } + + previousTime = currentTime; + + if (currentValue === null) { + if (ignoreNulls) { + continue; + } + if (nullAsZero) { + currentValue = 0; + } + } + + if (currentValue !== null) { + if (_.isNumber(currentValue)) { + total += currentValue; + allIsNull = false; + nonNulls++; + } + + if (currentValue > max) { + max = currentValue; + } + + if (currentValue < min) { + min = currentValue; + } + + if (first === null) { + first = currentValue; + } else { + if (previousValue > currentValue) { + // counter reset + previousDeltaUp = false; + if (i === item.datapoints.length - 1) { + // reset on last + delta += currentValue; + } + } else { + if (previousDeltaUp) { + delta += currentValue - previousValue; // normal increment + } else { + delta += currentValue; // account for counter reset + } + previousDeltaUp = true; + } + } + previousValue = currentValue; + + if (currentValue < logmin && currentValue > 0) { + logmin = currentValue; + } + + if (currentValue !== 0) { + allIsZero = false; + } + } + + result.push([currentTime, currentValue]); + } + + if (max === -Number.MAX_VALUE) { + max = null; + } + + if (min === Number.MAX_VALUE) { + min = null; + } + + if (result.length && !allIsNull) { + avg = total / nonNulls; + current = result[result.length - 1][1]; + if (current === null && result.length > 1) { + current = result[result.length - 2][1]; + } + } + + if (max !== null && min !== null) { + range = max - min; + } + + if (current !== null && first !== null) { + diff = current - first; + } + + const count = result.length; + + return { + data: result, + label: label, + color: colorPalette[colorIndex], + stats: { + total, + min, + max, + current, + logmin, + avg, + diff, + delta, + timeStep, + range, + count, + first, + allIsZero, + allIsNull, + }, + }; + }); + + return vmSeries; +} diff --git a/public/app/plugins/panel/graph2/GraphPanel.tsx b/public/app/plugins/panel/graph2/GraphPanel.tsx index 95dc5a9e620..eda200e5e82 100644 --- a/public/app/plugins/panel/graph2/GraphPanel.tsx +++ b/public/app/plugins/panel/graph2/GraphPanel.tsx @@ -1,12 +1,13 @@ // Libraries import _ from 'lodash'; import React, { PureComponent } from 'react'; +import colors from 'app/core/utils/colors'; // Components -import Graph from 'app/viz/Graph'; +import { Graph } from '@grafana/ui'; // Services & Utils -import { getTimeSeriesVMs } from 'app/viz/state/timeSeries'; +import { processTimeSeries } from '@grafana/ui'; // Types import { PanelProps, NullValueMode } from '@grafana/ui'; @@ -23,9 +24,10 @@ export class GraphPanel extends PureComponent { const { timeSeries, timeRange, width, height } = this.props; const { showLines, showBars, showPoints } = this.props.options; - const vmSeries = getTimeSeriesVMs({ + const vmSeries = processTimeSeries({ timeSeries: timeSeries, nullValueMode: NullValueMode.Ignore, + colorPalette: colors, }); return ( diff --git a/yarn.lock b/yarn.lock index ecdc347c4f5..c732e576022 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1029,6 +1029,11 @@ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.4.tgz#cc43ae176a91dcb1504839b0b9d6659386cf0af5" integrity sha512-46jSw0QMerCRkhJZbOwPA0Eb9T1p74HtECsfa0GXdgjkenSGhgvK96w+e2PEPu4GF0/brUK5WQKq/rUQQFyAxA== +"@types/lodash@^4.14.119": + version "4.14.119" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.119.tgz#be847e5f4bc3e35e46d041c394ead8b603ad8b39" + integrity sha512-Z3TNyBL8Vd/M9D9Ms2S3LmFq2sSMzahodD6rCS9V2N44HUMINb75jNkSuwAx7eo2ufqTdfOdtGQpNbieUjPQmw== + "@types/node@*": version "10.11.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.4.tgz#e8bd933c3f78795d580ae41d86590bfc1f4f389d" @@ -1083,7 +1088,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@16.7.6", "@types/react@^16.1.0", "@types/react@^16.7.6": +"@types/react@*", "@types/react@^16.1.0", "@types/react@^16.7.6": version "16.7.6" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.6.tgz#80e4bab0d0731ad3ae51f320c4b08bdca5f03040" integrity sha512-QBUfzftr/8eg/q3ZRgf/GaDP6rTYc7ZNem+g4oZM38C9vXyV8AWRWaTQuW5yCoZTsfHrN7b3DeEiUnqH9SrnpA== @@ -3153,7 +3158,7 @@ caniuse-api@^1.5.2: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-db@1.0.30000772, caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: version "1.0.30000772" resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b" integrity sha1-UarokXaChureSj2DGep21qAbUSs=