WIP all the refactors and in teh darkness bind them

pull/799/head
Andrew Watkins 2017-01-23 21:14:39 -08:00
parent 9cb38ae4ba
commit 73d6d624a3
2 changed files with 252 additions and 157 deletions

View File

@ -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);
});

View File

@ -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. <measurement>.<field>)
// const labels = []; // all of the effective field names (i.e. <measurement>.<field>)
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: "<measurement>",
* columns: ["time", "<field name 1>", "<field name 2>", ...],
* values: [<time>, <value of field 1>, <value of field 2>, ...],
* },
* }
*/
s.series.forEach(parseSeries);
const sortedLabels = _.sortBy(labels, 'label')
const timeSeries = cells.reduce((acc, cell) => {
let existingRowIndex = acc.findIndex(({time}) => cell.time === time)
if (existingRowIndex === -1) {
acc.push({
time: cell.time,
values: Array(sortedLabels.length).fill(null),
})
existingRowIndex = acc.length - 1
}
function parseSeries(series) {
/*
* series looks like:
* {
* name: "<measurement>",
* columns: ["time", "<field name 1>", "<field name 2>", ...],
* values: [
* [<time1>, <value of field 1 @ time1>, <value of field 2 @ time1>, ...],
* [<time2>, <value of field 1 @ time2>, <value of field 2 @ time2>, ...],
* ]
* }
*/
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 (<tag key>).
const tags = Object.keys(series.tags || {}).map((key) => {
return `[${key}=${series.tags[key]}]`;
}).sort().join('');
const values = acc[existingRowIndex].values
const labelIndex = sortedLabels.findIndex(({label}) => label === cell.label)
values[labelIndex] = cell.value
acc[existingRowIndex].values = values
const c = columns.slice(1).sort();
let previousColumnLength = 0;
return acc
}, [])
if (c.length != previousColumnLength) {
previousColumnLength = c.length;
}
const sortedTimeSeries = _.sortBy(timeSeries, 'time')
c.forEach((fieldName) => {
let effectiveFieldName = `${measurementName}.${fieldName}${tags}`;
// If there are duplicate effectiveFieldNames identify them by their queryIndex
if (effectiveFieldName in dygraphSeries) {
effectiveFieldName = `${effectiveFieldName}-${queryIndex}`;
}
// 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] = c.indexOf(fieldName) + previousColumnLength;
labels.push(effectiveFieldName);
const {light, heavy} = STROKE_WIDTH;
const dygraphSeriesStyles = {
strokeWidth: queryIndex === activeQueryIndex ? heavy : light,
};
if (!isInDataExplorer) {
dygraphSeriesStyles.axis = queryIndex === 0 ? 'y' : 'y2';
}
dygraphSeries[effectiveFieldName] = dygraphSeriesStyles;
});
(series.values || []).forEach(parseRow);
function parseRow(row) {
/**
* row looks like:
* [<time1>, <value of field 1 @ time1>, <value of field 2 @ time1>, ...]
*/
const date = row[0];
const dateString = date.toString();
row.forEach((value, index) => {
if (index === 0) {
// index 0 in a row is always the timestamp
if (!dateToFieldValue[dateString]) {
dateToFieldValue[dateString] = {};
dates[dateString] = date;
}
return;
}
const fieldName = columns[index];
let effectiveFieldName = `${measurementName}.${fieldName}${tags}`;
// If there are duplicate effectiveFieldNames identify them by their queryIndex
if (effectiveFieldName in dateToFieldValue[dateString]) {
effectiveFieldName = `${effectiveFieldName}-${queryIndex}`;
}
dateToFieldValue[dateString][effectiveFieldName] = value;
});
}
}
});
function buildTimeSeries() {
const allDates = Object.keys(dateToFieldValue);
allDates.sort((a, b) => a - b);
const rowLength = labels.length + 1;
return allDates.map((date) => {
const row = new Array(rowLength);
row.fill(null);
row[0] = new Date(dates[date]);
const fieldsForRow = dateToFieldValue[date];
Object.keys(fieldsForRow).forEach((effectiveFieldName) => {
row[fieldToIndex[effectiveFieldName]] = fieldsForRow[effectiveFieldName];
});
return row;
});
const timeSeriesToDygraph = {
timeSeries: sortedTimeSeries.map(({time, values}) => ([new Date(time), ...values])),
labels: ["time", ...sortedLabels.map(({label}) => label)],
}
return {
labels: ['time', ...labels.sort()],
timeSeries: buildTimeSeries(),
dygraphSeries,
};
// console.log("MY CAT LOVES LABELS: ", labels)
// console.log("MY CAT HATES SORTED LABELS: ", sortedLabels)
console.log("sorted term serrrrries: ", JSON.stringify(timeSeriesToDygraph, null, 2))
return timeSeriesToDygraph;
// timeSeriesToDygraph , {labels: [], timeSeries: []}
// 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;
// }
// }
//
// /**
// * response looks like:
// * {
// * results: [
// * { series: [...] },
// * { series: [...] },
// * ]
// * }
// */
// response.results.forEach(parseResult);
//
// function parseResult(s) {
// /*
// * s looks like:
// * {
// * series: [
// * {
// * name: "<measurement>",
// * columns: ["time", "<field name 1>", "<field name 2>", ...],
// * values: [<time>, <value of field 1>, <value of field 2>, ...],
// * },
// * }
// */
// s.series.forEach(parseSeries);
// }
//
// function parseSeries(series) {
// /*
// * series looks like:
// * {
// * name: "<measurement>",
// * columns: ["time", "<field name 1>", "<field name 2>", ...],
// * values: [
// * [<time1>, <value of field 1 @ time1>, <value of field 2 @ time1>, ...],
// * [<time2>, <value of field 1 @ time2>, <value of field 2 @ time2>, ...],
// * ]
// * }
// */
// 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 (<tag key>).
// const tags = Object.keys(series.tags || {}).map((key) => {
// return `[${key}=${series.tags[key]}]`;
// }).sort().join('');
//
// 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
// if (effectiveFieldName in dygraphSeries) {
// effectiveFieldName = `${effectiveFieldName}-${queryIndex}`;
// }
//
// // 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] = c.indexOf(fieldName);
// labels.push(effectiveFieldName);
//
// const {light, heavy} = STROKE_WIDTH;
//
// const dygraphSeriesStyles = {
// strokeWidth: queryIndex === activeQueryIndex ? heavy : light,
// };
//
// if (!isInDataExplorer) {
// dygraphSeriesStyles.axis = queryIndex === 0 ? 'y' : 'y2';
// }
//
// dygraphSeries[effectiveFieldName] = dygraphSeriesStyles;
// });
//
// (series.values || []).forEach(parseRow);
//
//
//
// function parseRow(row) {
// /**
// * row looks like:
// * [<time1>, <value of field 1 @ time1>, <value of field 2 @ time1>, ...]
// */
// const date = row[0];
// const dateString = date.toString();
// row.forEach((value, index) => {
// if (index === 0) {
// // index 0 in a row is always the timestamp
// if (!dateToFieldValue[dateString]) {
// dateToFieldValue[dateString] = {};
// dates[dateString] = date;
// }
// return;
// }
//
// const fieldName = columns[index];
// let effectiveFieldName = `${measurementName}.${fieldName}${tags}`;
//
// // If there are duplicate effectiveFieldNames identify them by their queryIndex
// if (effectiveFieldName in dateToFieldValue[dateString]) {
// effectiveFieldName = `${effectiveFieldName}-${queryIndex}`;
// }
//
// dateToFieldValue[dateString][effectiveFieldName] = value;
// });
// }
// }
// });
//
// function buildTimeSeries() {
// const allDates = Object.keys(dateToFieldValue);
// allDates.sort((a, b) => a - b);
// const rowLength = labels.length + 1;
// return allDates.map((date) => {
// const row = new Array(rowLength);
//
// row.fill(null);
// row[0] = new Date(dates[date]);
//
// const fieldsForRow = dateToFieldValue[date];
//
// Object.keys(fieldsForRow).forEach((effectiveFieldName) => {
// row[fieldToIndex[effectiveFieldName]] = fieldsForRow[effectiveFieldName];
// });
//
// return row;
// });
// }
}
function isEmpty(resp) {