From 005f834f536b81e6becbe2cf3de97c3797f25ab1 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 23 Jan 2017 12:00:00 -0800 Subject: [PATCH 01/28] Introduce Talk alert config --- ui/src/kapacitor/components/AlertOutputs.js | 5 ++ ui/src/kapacitor/components/TalkConfig.js | 61 +++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 ui/src/kapacitor/components/TalkConfig.js diff --git a/ui/src/kapacitor/components/AlertOutputs.js b/ui/src/kapacitor/components/AlertOutputs.js index 6e45bfa39..a2137503b 100644 --- a/ui/src/kapacitor/components/AlertOutputs.js +++ b/ui/src/kapacitor/components/AlertOutputs.js @@ -7,6 +7,7 @@ import PagerDutyConfig from './PagerDutyConfig'; import SensuConfig from './SensuConfig'; import SlackConfig from './SlackConfig'; import SMTPConfig from './SMTPConfig'; +import TalkConfig from './TalkConfig'; import TelegramConfig from './TelegramConfig'; import VictorOpsConfig from './VictorOpsConfig'; @@ -114,6 +115,7 @@ const AlertOutputs = React.createClass({ + @@ -166,6 +168,9 @@ const AlertOutputs = React.createClass({ case 'sensu': { return ; } + case 'talk': { + return ; + } } }, }); diff --git a/ui/src/kapacitor/components/TalkConfig.js b/ui/src/kapacitor/components/TalkConfig.js new file mode 100644 index 000000000..595adfac6 --- /dev/null +++ b/ui/src/kapacitor/components/TalkConfig.js @@ -0,0 +1,61 @@ +import React, {PropTypes} from 'react'; + +const { + bool, + string, + shape, + func, +} = PropTypes; + +const TalkConfig = React.createClass({ + propTypes: { + config: shape({ + options: shape({ + url: bool.isRequired, + author_name: string.isRequired, + }).isRequired, + }).isRequired, + onSave: func.isRequired, + }, + + handleSaveAlert(e) { + e.preventDefault(); + + const properties = { + url: this.url.value, + author_name: this.author.value, + }; + + this.props.onSave(properties); + }, + + render() { + const {url, author_name: author} = this.props.config.options; + + return ( +
+

Talk Alert

+
+

Have alerts sent to Talk.

+
+
+ + this.url = r} defaultValue={url || ''}> + +
+ +
+ + this.author = r} defaultValue={author || ''}> +
+ +
+ +
+
+
+ ); + }, +}); + +export default TalkConfig; From 9cb38ae4ba37d43de276cdc29930f8da2d4c067b Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 23 Jan 2017 16:08:45 -0800 Subject: [PATCH 02/28] WIP Fix label ordering bug --- ui/spec/utils/timeSeriesToDygraphSpec.js | 65 ++++++++++++------------ ui/src/utils/timeSeriesToDygraph.js | 14 +++-- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/ui/spec/utils/timeSeriesToDygraphSpec.js b/ui/spec/utils/timeSeriesToDygraphSpec.js index c071889c8..0b5396be1 100644 --- a/ui/spec/utils/timeSeriesToDygraphSpec.js +++ b/ui/spec/utils/timeSeriesToDygraphSpec.js @@ -322,45 +322,35 @@ describe('timeSeriesToDygraph', () => { expect(dygraphSeries["m2.f2"].strokeWidth).to.be.above(dygraphSeries["m1.f1"].strokeWidth); }); - it('parses a raw InfluxDB response into a dygraph friendly data format', () => { + it('parses labels alphabetically with the correct field values for multiple series', () => { const influxResponse = [ { "response": { "results": [ - { - "series": [ - { - "name":"mb", - "columns": ["time","f1"], - "values": [[1000, 1],[2000, 2]], - }, - ] - }, { "series": [ { "name":"ma", - "columns": ["time","f1"], - "values": [[1000, 1],[2000, 2]], + "columns": ["time","fa","fc","fb"], + "values": [ + [1000, 20, 10, 10], + [2000, 30, 15, 9], + [3000, 40, 20, 8], + ], }, ] }, { "series": [ { - "name":"mc", - "columns": ["time","f2"], - "values": [[2000, 3],[4000, 4]], - }, - ] - }, - { - "series": [ - { - "name":"mc", - "columns": ["time","f1"], - "values": [[2000, 3],[4000, 4]], + "name":"mb", + "columns": ["time","fa","fc","fb"], + "values": [ + [1000, 200, 100, 100], + [2000, 300, 150, 90], + [3000, 400, 200, 80], + ], }, ] }, @@ -371,14 +361,25 @@ describe('timeSeriesToDygraph', () => { const actual = timeSeriesToDygraph(influxResponse); - const expected = [ - 'time', - `ma.f1`, - `mb.f1`, - `mc.f1`, - `mc.f2`, - ]; + const expected = { + labels: [ + 'time', + `ma.fa`, + `ma.fb`, + `ma.fc`, + `mb.fa`, + `mb.fb`, + `mb.fc`, + ], + timeSeries: [ + [new Date(1000), 20, 10, 10], + [new Date(2000), 30, 9, 15], + [new Date(3000), 40, 8, 20], + ], + }; - expect(actual.labels).to.deep.equal(expected); + console.log(actual.timeSeries); + expect(actual.labels).to.deep.equal(expected.labels); + expect(actual.timeSeries).to.deep.equal(expected.timeSeries); }); }); diff --git a/ui/src/utils/timeSeriesToDygraph.js b/ui/src/utils/timeSeriesToDygraph.js index a5815c202..19bb31404 100644 --- a/ui/src/utils/timeSeriesToDygraph.js +++ b/ui/src/utils/timeSeriesToDygraph.js @@ -79,14 +79,20 @@ export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInData */ const measurementName = series.name; const columns = series.columns; - // Tags are only included in an influxdb response under certain circumstances, e.g. // when a query is using GROUP BY (). const tags = Object.keys(series.tags || {}).map((key) => { return `[${key}=${series.tags[key]}]`; }).sort().join(''); - columns.slice(1).forEach((fieldName) => { + const c = columns.slice(1).sort(); + let previousColumnLength = 0; + + if (c.length != previousColumnLength) { + previousColumnLength = c.length; + } + + c.forEach((fieldName) => { let effectiveFieldName = `${measurementName}.${fieldName}${tags}`; // If there are duplicate effectiveFieldNames identify them by their queryIndex @@ -96,7 +102,7 @@ export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInData // Given a field name, identify which column in the timeSeries result should hold the field's value // ex given this timeSeries [Date, 10, 20, 30] field index at 2 would correspond to value 20 - fieldToIndex[effectiveFieldName] = labels.length + 1; + fieldToIndex[effectiveFieldName] = c.indexOf(fieldName) + previousColumnLength; labels.push(effectiveFieldName); const {light, heavy} = STROKE_WIDTH; @@ -156,6 +162,7 @@ export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInData row[0] = new Date(dates[date]); const fieldsForRow = dateToFieldValue[date]; + Object.keys(fieldsForRow).forEach((effectiveFieldName) => { row[fieldToIndex[effectiveFieldName]] = fieldsForRow[effectiveFieldName]; }); @@ -178,4 +185,3 @@ function isEmpty(resp) { function hasError(resp) { return !!resp.results[0].error; } - From 73d6d624a3d564d851f55d4640aa87511948cd30 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 23 Jan 2017 21:14:39 -0800 Subject: [PATCH 03/28] WIP all the refactors and in teh darkness bind them --- ui/spec/utils/timeSeriesToDygraphSpec.js | 51 ++-- ui/src/utils/timeSeriesToDygraph.js | 358 ++++++++++++++--------- 2 files changed, 252 insertions(+), 157 deletions(-) diff --git a/ui/spec/utils/timeSeriesToDygraphSpec.js b/ui/spec/utils/timeSeriesToDygraphSpec.js index 0b5396be1..7221ae800 100644 --- a/ui/spec/utils/timeSeriesToDygraphSpec.js +++ b/ui/spec/utils/timeSeriesToDygraphSpec.js @@ -322,12 +322,35 @@ describe('timeSeriesToDygraph', () => { expect(dygraphSeries["m2.f2"].strokeWidth).to.be.above(dygraphSeries["m1.f1"].strokeWidth); }); - it('parses labels alphabetically with the correct field values for multiple series', () => { + it.only('parses labels alphabetically with the correct field values for multiple series', () => { const influxResponse = [ { "response": { "results": [ + { + "series": [ + { + "name":"mb", + "columns": ["time","fa"], + "values": [ + [1000, 200], + [2000, 300], + [4000, 400], + ], + }, + { + "name":"mc", + "columns": ["time","fa"], + "values": [ + [1000, 400], + [2000, 600], + [3000, 800], + [5000, 1000], + ], + }, + ] + }, { "series": [ { @@ -341,19 +364,6 @@ describe('timeSeriesToDygraph', () => { }, ] }, - { - "series": [ - { - "name":"mb", - "columns": ["time","fa","fc","fb"], - "values": [ - [1000, 200, 100, 100], - [2000, 300, 150, 90], - [3000, 400, 200, 80], - ], - }, - ] - }, ], }, } @@ -368,17 +378,18 @@ describe('timeSeriesToDygraph', () => { `ma.fb`, `ma.fc`, `mb.fa`, - `mb.fb`, - `mb.fc`, + `mc.fa`, ], timeSeries: [ - [new Date(1000), 20, 10, 10], - [new Date(2000), 30, 9, 15], - [new Date(3000), 40, 8, 20], + [new Date(1000), 20, 10, 10, 200, 400], + [new Date(2000), 30, 9, 15, 300, 600], + [new Date(3000), 40, 8, 20, null, 800], + [new Date(4000), null, null, null, 400, null], + [new Date(5000), null, null, null, null, 1000], ], }; - console.log(actual.timeSeries); + // console.log(actual.timeSeries); expect(actual.labels).to.deep.equal(expected.labels); expect(actual.timeSeries).to.deep.equal(expected.timeSeries); }); diff --git a/ui/src/utils/timeSeriesToDygraph.js b/ui/src/utils/timeSeriesToDygraph.js index 19bb31404..206ae2c39 100644 --- a/ui/src/utils/timeSeriesToDygraph.js +++ b/ui/src/utils/timeSeriesToDygraph.js @@ -1,3 +1,4 @@ +import _ from 'lodash'; import {STROKE_WIDTH} from 'src/shared/constants'; /** * Accepts an array of raw influxdb responses and returns a format @@ -7,7 +8,7 @@ import {STROKE_WIDTH} from 'src/shared/constants'; // activeQueryIndex is an optional argument that indicated which query's series // we want highlighted. export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInDataExplorer) { - const labels = []; // all of the effective field names (i.e. .) + // const labels = []; // all of the effective field names (i.e. .) const fieldToIndex = {}; // see parseSeries const dates = {}; // map of date as string to date value to minimize string coercion const dygraphSeries = {}; // dygraphSeries is a graph legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'}; @@ -30,152 +31,235 @@ export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInData */ const dateToFieldValue = {}; - raw.forEach(({response}, queryIndex) => { - // If a response is an empty result set or a query returned an error - // from InfluxDB, don't try and parse. - if (response.results.length) { - if (isEmpty(response) || hasError(response)) { - return; - } + const results = raw.reduce((acc, response) => { + return [...acc, ..._.get(response, 'response.results', [])] + }, []) + + const serieses = results.reduce((acc, result, index) => { + return [...acc, ...result.series.map((item) => ({...item, index}))]; + }, []) + + const cells = serieses.reduce((acc, {name, columns, values, index}) => { + const rows = values.map((values) => ({ + name, + columns, + values, + index, + })) + console.log("HI IM Rerws: ", rows) + + rows.forEach(({values: vals, columns: cols, name: n, index: seriesIndex}) => { + const [time, ...rowValues] = vals + rowValues.forEach((value, i) => { + const column = cols[i + 1] + acc.push({ + label: `${n}.${column}`, + value, + time, + seriesIndex, + }) + }) + }) + + return acc + }, []) + + console.log("HI IM CELLS: ", cells) + + const labels = cells.reduce((acc, cell) => { + const existingLabel = acc.find(({label, seriesIndex}) => cell.label === label && cell.seriesIndex === seriesIndex) + + if (!existingLabel) { + acc.push({ + label: cell.label, + seriesIndex: cell.seriesIndex, + }) } - /** - * response looks like: - * { - * results: [ - * { series: [...] }, - * { series: [...] }, - * ] - * } - */ - response.results.forEach(parseResult); + return acc + }, []) - function parseResult(s) { - /* - * s looks like: - * { - * series: [ - * { - * name: "", - * columns: ["time", "", "", ...], - * values: [