diff --git a/web/ui/react-app/src/pages/graph/DataTable.test.tsx b/web/ui/react-app/src/pages/graph/DataTable.test.tsx index d0e099e694..ab730fd4af 100755 --- a/web/ui/react-app/src/pages/graph/DataTable.test.tsx +++ b/web/ui/react-app/src/pages/graph/DataTable.test.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import DataTable, { DataTableProps } from './DataTable'; +import HistogramString, { HistogramStringProps } from './DataTable'; import { Alert, Table } from 'reactstrap'; import SeriesName from './SeriesName'; @@ -71,7 +72,81 @@ describe('DataTable', () => { const table = dataTable.find(Table); table.find('tr').forEach((row, idx) => { expect(row.find(SeriesName)).toHaveLength(1); - expect(row.find('td').at(1).text()).toEqual(`${idx}`); + expect(row.find('td').at(1).text()).toEqual(`${idx} `); + }); + }); + }); + + describe('when resultType is a vector with histograms', () => { + const dataTableProps: DataTableProps = { + data: { + resultType: 'vector', + result: [ + { + metric: { + __name__: 'metric_name_1', + label1: 'value_1', + labeln: 'value_n', + }, + histogram: [ + 1572098246.599, + { + "count": "10", + "sum": "3.3", + "buckets": [ + [ 1, "-1", "-0.5", "2"], + [ 3, "-0.5", "0.5", "3"], + [ 0, "0.5", "1", "5"], + ] + } + ], + }, + { + metric: { + __name__: 'metric_name_2', + label1: 'value_1', + labeln: 'value_n', + }, + histogram: [ + 1572098247.599, + { + "count": "5", + "sum": "1.11", + "buckets": [ + [ 0, "0.5", "1", "2"], + [ 0, "1", "2", "3"], + ] + } + ], + }, + { + metric: { + __name__: 'metric_name_2', + label1: 'value_1', + labeln: 'value_n', + }, + + }, + ], + }, + useLocalTime: false, + }; + const dataTable = shallow(); + + it('renders a table', () => { + const table = dataTable.find(Table); + expect(table.prop('hover')).toBe(true); + expect(table.prop('size')).toEqual('sm'); + expect(table.prop('className')).toEqual('data-table'); + expect(table.find('tbody')).toHaveLength(1); + }); + + it('renders rows', () => { + const table = dataTable.find(Table); + table.find('tr').forEach((row, idx) => { + expect(row.find(SeriesName)).toHaveLength(1); + // TODO(beorn7): This doesn't actually test the rendoring yet. Need to trigger it somehow. + expect(row.find('td').at(1).text()).toEqual(` `); }); }); }); @@ -239,7 +314,7 @@ describe('DataTable', () => { expect(table.find('tr')).toHaveLength(3); const row = rows.at(0); expect(row.text()).toEqual( - `9 @1572097950.9310 @1572097965.93111 @1572097980.92912 @1572097995.93113 @1572098010.93214 @1572098025.93315 @1572098040.9316 @1572098055.9317 @1572098070.9318 @1572098085.93619 @1572098100.93620 @1572098115.93321 @1572098130.93222 @1572098145.93223 @1572098160.93324 @1572098175.93425 @1572098190.93726 @1572098205.93427 @1572098220.93328 @1572098235.934` + `9 @1572097950.9310 @1572097965.93111 @1572097980.92912 @1572097995.93113 @1572098010.93214 @1572098025.93315 @1572098040.9316 @1572098055.9317 @1572098070.9318 @1572098085.93619 @1572098100.93620 @1572098115.93321 @1572098130.93222 @1572098145.93223 @1572098160.93324 @1572098175.93425 @1572098190.93726 @1572098205.93427 @1572098220.93328 @1572098235.934 ` ); }); }); diff --git a/web/ui/react-app/src/pages/graph/DataTable.tsx b/web/ui/react-app/src/pages/graph/DataTable.tsx index eed818afcd..7deb8d7608 100644 --- a/web/ui/react-app/src/pages/graph/DataTable.tsx +++ b/web/ui/react-app/src/pages/graph/DataTable.tsx @@ -3,7 +3,7 @@ import React, { FC, ReactNode } from 'react'; import { Alert, Table } from 'reactstrap'; import SeriesName from './SeriesName'; -import { Metric } from '../../types/types'; +import { Metric, Histogram } from '../../types/types'; import moment from 'moment'; @@ -31,15 +31,18 @@ export interface DataTableProps { interface InstantSample { metric: Metric; - value: SampleValue; + value?: SampleValue; + histogram?: SampleHistogram; } interface RangeSamples { metric: Metric; - values: SampleValue[]; + values?: SampleValue[]; + histograms?: SampleHistogram[]; } type SampleValue = [number, string]; +type SampleHistogram = [number, Histogram]; const limitSeries = (series: S[]): S[] => { const maxSeries = 10000; @@ -71,7 +74,9 @@ const DataTable: FC = ({ data, useLocalTime }) => { - {s.value[1]} + + {s.value && s.value[1]} + ); }); @@ -79,21 +84,36 @@ const DataTable: FC = ({ data, useLocalTime }) => { break; case 'matrix': rows = (limitSeries(data.result) as RangeSamples[]).map((s, seriesIdx) => { - const valuesAndTimes = s.values.map((v, valIdx) => { - const printedDatetime = moment.unix(v[0]).toISOString(useLocalTime); - return ( - - {v[1]} @{{v[0]}} -
-
- ); - }); + const valuesAndTimes = s.values + ? s.values.map((v, valIdx) => { + const printedDatetime = moment.unix(v[0]).toISOString(useLocalTime); + return ( + + {v[1]} @{{v[0]}} +
+
+ ); + }) + : []; + const histogramsAndTimes = s.histograms + ? s.histograms.map((h, hisIdx) => { + const printedDatetime = moment.unix(h[0]).toISOString(useLocalTime); + return ( + + @{{h[0]}} +
+
+ ); + }) + : []; return ( - {valuesAndTimes} + + {valuesAndTimes} {histogramsAndTimes} + ); }); @@ -139,4 +159,27 @@ const DataTable: FC = ({ data, useLocalTime }) => { ); }; +export interface HistogramStringProps { + h?: Histogram; +} + +export const HistogramString: FC = ({ h }) => { + if (!h) { + return <>; + } + const buckets: string[] = []; + + for (const bucket of h.buckets) { + const left = bucket[0] === 3 || bucket[0] === 1 ? '[' : '('; + const right = bucket[0] === 3 || bucket[0] === 0 ? ']' : ')'; + buckets.push(left + bucket[1] + ',' + bucket[2] + right + ':' + bucket[3] + ' '); + } + + return ( + <> + {'{'} count:{h.count} sum:{h.sum} {buckets} {'}'} + + ); +}; + export default DataTable; diff --git a/web/ui/react-app/src/pages/graph/Graph.tsx b/web/ui/react-app/src/pages/graph/Graph.tsx index 91a3ea05e3..cfa9fef169 100644 --- a/web/ui/react-app/src/pages/graph/Graph.tsx +++ b/web/ui/react-app/src/pages/graph/Graph.tsx @@ -3,7 +3,7 @@ import React, { PureComponent } from 'react'; import ReactResizeDetector from 'react-resize-detector'; import { Legend } from './Legend'; -import { Metric, ExemplarData, QueryParams } from '../../types/types'; +import { Metric, Histogram, ExemplarData, QueryParams } from '../../types/types'; import { isPresent } from '../../utils'; import { normalizeData, getOptions, toHoverColor } from './GraphHelpers'; import { Button } from 'reactstrap'; @@ -20,7 +20,7 @@ require('jquery.flot.tooltip'); export interface GraphProps { data: { resultType: string; - result: Array<{ metric: Metric; values: [number, string][] }>; + result: Array<{ metric: Metric; values?: [number, string][]; histograms?: [number, Histogram][] }>; }; exemplars: ExemplarData; stacked: boolean; diff --git a/web/ui/react-app/src/pages/graph/GraphHelpers.ts b/web/ui/react-app/src/pages/graph/GraphHelpers.ts index d28bedcdd1..f77383f568 100644 --- a/web/ui/react-app/src/pages/graph/GraphHelpers.ts +++ b/web/ui/react-app/src/pages/graph/GraphHelpers.ts @@ -189,17 +189,22 @@ export const normalizeData = ({ queryParams, data, exemplars, stacked }: GraphPr const deviation = stdDeviation(sum, values); return { - series: data.result.map(({ values, metric }, index) => { + series: data.result.map(({ values, histograms, metric }, index) => { // Insert nulls for all missing steps. const data = []; - let pos = 0; + let valuePos = 0; + let histogramPos = 0; for (let t = startTime; t <= endTime; t += resolution) { // Allow for floating point inaccuracy. - const currentValue = values[pos]; - if (values.length > pos && currentValue[0] < t + resolution / 100) { + const currentValue = values && values[valuePos]; + const currentHistogram = histograms && histograms[histogramPos]; + if (currentValue && values.length > valuePos && currentValue[0] < t + resolution / 100) { data.push([currentValue[0] * 1000, parseValue(currentValue[1])]); - pos++; + valuePos++; + } else if (currentHistogram && histograms.length > histogramPos && currentHistogram[0] < t + resolution / 100) { + data.push([currentHistogram[0] * 1000, parseValue(currentHistogram[1].sum)]); + histogramPos++; } else { data.push([t * 1000, null]); } diff --git a/web/ui/react-app/src/types/types.ts b/web/ui/react-app/src/types/types.ts index 69da46ce41..e87f247e72 100644 --- a/web/ui/react-app/src/types/types.ts +++ b/web/ui/react-app/src/types/types.ts @@ -4,6 +4,12 @@ export interface Metric { [key: string]: string; } +export interface Histogram { + count: string; + sum: string; + buckets: [number, string, string, string][]; +} + export interface Exemplar { labels: { [key: string]: string }; value: string;