Commit f6df7d6a authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Added and corrected specs for the additional time series

parent 520ab72b
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import measurements from '../utils/measurements'; import measurements from '../utils/measurements';
import { timeScaleFormat } from '../utils/date_time_formatters'; import { timeScaleFormat } from '../utils/date_time_formatters';
import createTimeSeries from '../utils/multiple_time_series';
import bp from '../../breakpoints'; import bp from '../../breakpoints';
const bisectDate = d3.bisector(d => d.time).left; const bisectDate = d3.bisector(d => d.time).left;
...@@ -63,16 +64,10 @@ ...@@ -63,16 +64,10 @@
}, },
components: { components: {
<<<<<<< HEAD:app/assets/javascripts/monitoring/components/graph.vue
GraphLegend, GraphLegend,
GraphFlag, GraphFlag,
GraphDeployment, GraphDeployment,
=======
monitoringLegends,
monitoringFlag,
monitoringDeployment,
monitoringPaths, monitoringPaths,
>>>>>>> Refactored the monitoring_column component to process all of the time series:app/assets/javascripts/monitoring/components/monitoring_column.vue
}, },
computed: { computed: {
...@@ -152,34 +147,10 @@ ...@@ -152,34 +147,10 @@
}, },
renderAxesPaths() { renderAxesPaths() {
this.timeSeries = this.columnData.queries[0].result.map((timeSeries) => { this.timeSeries = createTimeSeries(this.columnData.queries[0].result,
const timeSeriesScaleX = d3.time.scale() this.graphWidth,
.range([0, this.graphWidth - 70]); this.graphHeight,
this.graphHeightOffset);
const timeSeriesScaleY = d3.scale.linear()
.range([this.graphHeight - this.graphHeightOffset, 0]);
timeSeriesScaleX.domain(d3.extent(timeSeries.values, d => d.time));
timeSeriesScaleY.domain([0, d3.max(timeSeries.values.map(d => d.value))]);
const lineFunction = d3.svg.line()
.x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value));
const areaFunction = d3.svg.area()
.x(d => timeSeriesScaleX(d.time))
.y0(this.graphHeight - this.graphHeightOffset)
.y1(d => timeSeriesScaleY(d.value))
.interpolate('linear');
return {
linePath: lineFunction(timeSeries.values),
areaPath: areaFunction(timeSeries.values),
timeSeriesScaleX,
timeSeriesScaleY,
values: timeSeries.values,
};
});
if (this.timeSeries.length > 4) { if (this.timeSeries.length > 4) {
this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 4) * 20; this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 4) * 20;
...@@ -273,23 +244,6 @@ ...@@ -273,23 +244,6 @@
class="graph-data" class="graph-data"
:viewBox="innerViewBox" :viewBox="innerViewBox"
ref="graphData"> ref="graphData">
<<<<<<< HEAD:app/assets/javascripts/monitoring/components/graph.vue
<path
class="metric-area"
:d="area"
:fill="areaColorRgb"
transform="translate(-5, 20)">
</path>
<path
class="metric-line"
:d="line"
:stroke="lineColorRgb"
fill="none"
stroke-width="2"
transform="translate(-5, 20)">
</path>
<graph-deployment
=======
<monitoring-paths <monitoring-paths
v-for="(path, index) in timeSeries" v-for="(path, index) in timeSeries"
:key="index" :key="index"
...@@ -307,7 +261,6 @@ ...@@ -307,7 +261,6 @@
@mousemove="handleMouseOverGraph($event)"> @mousemove="handleMouseOverGraph($event)">
</rect> </rect>
<monitoring-deployment <monitoring-deployment
>>>>>>> Refactored the monitoring_column component to process all of the time series:app/assets/javascripts/monitoring/components/monitoring_column.vue
:show-deploy-info="showDeployInfo" :show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData" :deployment-data="reducedDeploymentData"
:graph-height="graphHeight" :graph-height="graphHeight"
......
<script> <script>
import { formatRelevantDigits } from '../../lib/utils/number_utils'; import { formatRelevantDigits } from '../../../lib/utils/number_utils';
export default { export default {
props: { props: {
...@@ -89,8 +89,14 @@ ...@@ -89,8 +89,14 @@
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
const bbox = this.$refs.ylabel.getBBox(); const bbox = this.$refs.ylabel.getBBox();
this.metricUsageXPosition = 0;
this.seriesXPosition = 0;
if (this.$refs.legendTitleSvg != null) {
this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width; this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
}
if (this.$refs.seriesTitleSvg != null) {
this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width; this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
}
this.yLabelWidth = bbox.width + 10; // Added some padding this.yLabelWidth = bbox.width + 10; // Added some padding
this.yLabelHeight = bbox.height + 5; this.yLabelHeight = bbox.height + 5;
}); });
......
import d3 from 'd3';
export default function createTimeSeries(seriesData, graphWidth, graphHeight, graphHeightOffset) {
return seriesData.map((timeSeries) => {
const timeSeriesScaleX = d3.time.scale()
.range([0, graphWidth - 70]);
const timeSeriesScaleY = d3.scale.linear()
.range([graphHeight - graphHeightOffset, 0]);
timeSeriesScaleX.domain(d3.extent(timeSeries.values, d => d.time));
timeSeriesScaleY.domain([0, d3.max(timeSeries.values.map(d => d.value))]);
const lineFunction = d3.svg.line()
.x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value));
const areaFunction = d3.svg.area()
.x(d => timeSeriesScaleX(d.time))
.y0(graphHeight - graphHeightOffset)
.y1(d => timeSeriesScaleY(d.value))
.interpolate('linear');
return {
linePath: lineFunction(timeSeries.values),
areaPath: areaFunction(timeSeries.values),
timeSeriesScaleX,
timeSeriesScaleY,
values: timeSeries.values,
};
});
}
...@@ -32,10 +32,6 @@ describe('GraphFlag', () => { ...@@ -32,10 +32,6 @@ describe('GraphFlag', () => {
.toEqual(component.currentXCoordinate); .toEqual(component.currentXCoordinate);
expect(getCoordinate(component, '.selected-metric-line', 'x2')) expect(getCoordinate(component, '.selected-metric-line', 'x2'))
.toEqual(component.currentXCoordinate); .toEqual(component.currentXCoordinate);
expect(getCoordinate(component, '.circle-metric', 'cx'))
.toEqual(component.currentXCoordinate);
expect(getCoordinate(component, '.circle-metric', 'cy'))
.toEqual(component.currentYCoordinate);
}); });
it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => { it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => {
......
import Vue from 'vue'; import Vue from 'vue';
import GraphLegend from '~/monitoring/components/graph/legend.vue'; import GraphLegend from '~/monitoring/components/graph/legend.vue';
import measurements from '~/monitoring/utils/measurements'; import measurements from '~/monitoring/utils/measurements';
import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
const createComponent = (propsData) => { const createComponent = (propsData) => {
const Component = Vue.extend(GraphLegend); const Component = Vue.extend(GraphLegend);
...@@ -10,102 +12,96 @@ const createComponent = (propsData) => { ...@@ -10,102 +12,96 @@ const createComponent = (propsData) => {
}).$mount(); }).$mount();
}; };
function getTextFromNode(component, selector) { const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
return component.$el.querySelector(selector).firstChild.nodeValue.trim();
}
describe('GraphLegend', () => { const defaultValuesComponent = {
describe('Computed props', () => {
it('textTransform', () => {
const component = createComponent({
graphWidth: 500, graphWidth: 500,
graphHeight: 300, graphHeight: 300,
graphHeightOffset: 120,
margin: measurements.large.margin, margin: measurements.large.margin,
measurements: measurements.large, measurements: measurements.large,
areaColorRgb: '#f0f0f0', areaColorRgb: '#f0f0f0',
legendTitle: 'Title', legendTitle: 'Title',
yAxisLabel: 'Values', yAxisLabel: 'Values',
metricUsage: 'Value', metricUsage: 'Value',
}); unitOfDisplay: 'Req/Sec',
currentDataIndex: 0,
};
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result,
defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight,
defaultValuesComponent.graphHeightOffset);
defaultValuesComponent.timeSeries = timeSeries;
function getTextFromNode(component, selector) {
return component.$el.querySelector(selector).firstChild.nodeValue.trim();
}
describe('GraphLegend', () => {
describe('Computed props', () => {
it('textTransform', () => {
const component = createComponent(defaultValuesComponent);
expect(component.textTransform).toContain('translate(15, 120) rotate(-90)'); expect(component.textTransform).toContain('translate(15, 120) rotate(-90)');
}); });
it('xPosition', () => { it('xPosition', () => {
const component = createComponent({ const component = createComponent(defaultValuesComponent);
graphWidth: 500,
graphHeight: 300,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
});
expect(component.xPosition).toEqual(180); expect(component.xPosition).toEqual(180);
}); });
it('yPosition', () => { it('yPosition', () => {
const component = createComponent({ const component = createComponent(defaultValuesComponent);
graphWidth: 500,
graphHeight: 300,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
});
expect(component.yPosition).toEqual(240); expect(component.yPosition).toEqual(240);
}); });
it('rectTransform', () => { it('rectTransform', () => {
const component = createComponent({ const component = createComponent(defaultValuesComponent);
graphWidth: 500,
graphHeight: 300,
margin: measurements.large.margin,
measurements: measurements.large,
areaColorRgb: '#f0f0f0',
legendTitle: 'Title',
yAxisLabel: 'Values',
metricUsage: 'Value',
});
expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)'); expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)');
}); });
}); });
it('has 2 rect-axis-text rect svg elements', () => { describe('methods', () => {
const component = createComponent({ it('translateLegendGroup should only change Y direction', () => {
graphWidth: 500, const component = createComponent(defaultValuesComponent);
graphHeight: 300,
margin: measurements.large.margin, const translatedCoordinate = component.translateLegendGroup(1);
measurements: measurements.large, expect(translatedCoordinate.indexOf('translate(0, ')).not.toEqual(-1);
areaColorRgb: '#f0f0f0', });
legendTitle: 'Title',
yAxisLabel: 'Values', it('formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"', () => {
metricUsage: 'Value', const component = createComponent(defaultValuesComponent);
const formattedMetricUsage = component.formatMetricUsage(timeSeries[0]);
const valueFromSeries = timeSeries[0].values[component.currentDataIndex].value;
expect(formattedMetricUsage.indexOf(component.unitOfDisplay)).not.toEqual(-1);
expect(formattedMetricUsage.indexOf(valueFromSeries)).not.toEqual(-1);
});
}); });
it('has 2 rect-axis-text rect svg elements', () => {
const component = createComponent(defaultValuesComponent);
expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2); expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2);
}); });
it('contains text to signal the usage, title and time', () => { it('contains text to signal the usage, title and time', () => {
const component = createComponent({ const component = createComponent(defaultValuesComponent);
graphWidth: 500, const titles = component.$el.querySelectorAll('.legend-metric-title');
graphHeight: 300,
margin: measurements.large.margin, expect(getTextFromNode(component, '.legend-metric-title')).toEqual(component.legendTitle);
measurements: measurements.large, expect(titles[0].textContent.indexOf('Title')).not.toEqual(-1);
areaColorRgb: '#f0f0f0', expect(titles[1].textContent.indexOf('Series')).not.toEqual(-1);
legendTitle: 'Title', expect(getTextFromNode(component, '.y-label-text')).toEqual(component.yAxisLabel);
yAxisLabel: 'Values',
metricUsage: 'Value',
}); });
expect(getTextFromNode(component, '.text-metric-title')).toEqual(component.legendTitle); it('should contain the same number of legend groups as the timeSeries length', () => {
expect(getTextFromNode(component, '.text-metric-usage')).toEqual(component.metricUsage); const component = createComponent(defaultValuesComponent);
expect(getTextFromNode(component, '.label-axis-text')).toEqual(component.yAxisLabel);
expect(component.$el.querySelectorAll('.legend-group').length).toEqual(component.timeSeries.length);
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import GraphRow from '~/monitoring/components/graph_row.vue'; import GraphRow from '~/monitoring/components/graph_row.vue';
import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins'; import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
import { deploymentData, singleRowMetrics } from './mock_data'; import { deploymentData, convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from './mock_data';
const createComponent = (propsData) => { const createComponent = (propsData) => {
const Component = Vue.extend(GraphRow); const Component = Vue.extend(GraphRow);
...@@ -11,15 +11,15 @@ const createComponent = (propsData) => { ...@@ -11,15 +11,15 @@ const createComponent = (propsData) => {
}).$mount(); }).$mount();
}; };
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
describe('GraphRow', () => { describe('GraphRow', () => {
beforeEach(() => { beforeEach(() => {
spyOn(MonitoringMixins.methods, 'formatDeployments').and.returnValue({}); spyOn(MonitoringMixins.methods, 'formatDeployments').and.returnValue({});
}); });
describe('Computed props', () => { describe('Computed props', () => {
it('bootstrapClass is set to col-md-6 when rowData is higher/equal to 2', () => { it('bootstrapClass is set to col-md-6 when rowData is higher/equal to 2', () => {
const component = createComponent({ const component = createComponent({
rowData: singleRowMetrics, rowData: convertedMetrics,
updateAspectRatio: false, updateAspectRatio: false,
deploymentData, deploymentData,
}); });
...@@ -29,7 +29,7 @@ describe('GraphRow', () => { ...@@ -29,7 +29,7 @@ describe('GraphRow', () => {
it('bootstrapClass is set to col-md-12 when rowData is lower than 2', () => { it('bootstrapClass is set to col-md-12 when rowData is lower than 2', () => {
const component = createComponent({ const component = createComponent({
rowData: [singleRowMetrics[0]], rowData: [convertedMetrics[0]],
updateAspectRatio: false, updateAspectRatio: false,
deploymentData, deploymentData,
}); });
...@@ -40,7 +40,7 @@ describe('GraphRow', () => { ...@@ -40,7 +40,7 @@ describe('GraphRow', () => {
it('has one column', () => { it('has one column', () => {
const component = createComponent({ const component = createComponent({
rowData: singleRowMetrics, rowData: convertedMetrics,
updateAspectRatio: false, updateAspectRatio: false,
deploymentData, deploymentData,
}); });
...@@ -51,7 +51,7 @@ describe('GraphRow', () => { ...@@ -51,7 +51,7 @@ describe('GraphRow', () => {
it('has two columns', () => { it('has two columns', () => {
const component = createComponent({ const component = createComponent({
rowData: singleRowMetrics, rowData: convertedMetrics,
updateAspectRatio: false, updateAspectRatio: false,
deploymentData, deploymentData,
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import _ from 'underscore';
import Graph from '~/monitoring/components/graph.vue'; import Graph from '~/monitoring/components/graph.vue';
import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins'; import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins';
import eventHub from '~/monitoring/event_hub'; import eventHub from '~/monitoring/event_hub';
import { deploymentData, singleRowMetrics } from './mock_data'; import { deploymentData, convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from './mock_data';
const createComponent = (propsData) => { const createComponent = (propsData) => {
const Component = Vue.extend(Graph); const Component = Vue.extend(Graph);
...@@ -13,6 +12,8 @@ const createComponent = (propsData) => { ...@@ -13,6 +12,8 @@ const createComponent = (propsData) => {
}).$mount(); }).$mount();
}; };
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
describe('Graph', () => { describe('Graph', () => {
beforeEach(() => { beforeEach(() => {
spyOn(MonitoringMixins.methods, 'formatDeployments').and.returnValue({}); spyOn(MonitoringMixins.methods, 'formatDeployments').and.returnValue({});
...@@ -20,7 +21,7 @@ describe('Graph', () => { ...@@ -20,7 +21,7 @@ describe('Graph', () => {
it('has a title', () => { it('has a title', () => {
const component = createComponent({ const component = createComponent({
graphData: singleRowMetrics[0], columnData: convertedMetrics[1],
classType: 'col-md-6', classType: 'col-md-6',
updateAspectRatio: false, updateAspectRatio: false,
deploymentData, deploymentData,
...@@ -29,29 +30,10 @@ describe('Graph', () => { ...@@ -29,29 +30,10 @@ describe('Graph', () => {
expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.graphData.title); expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.graphData.title);
}); });
it('creates a path for the line and area of the graph', (done) => {
const component = createComponent({
graphData: singleRowMetrics[0],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
});
Vue.nextTick(() => {
expect(component.area).toBeDefined();
expect(component.line).toBeDefined();
expect(typeof component.area).toEqual('string');
expect(typeof component.line).toEqual('string');
expect(_.isFunction(component.xScale)).toBe(true);
expect(_.isFunction(component.yScale)).toBe(true);
done();
});
});
describe('Computed props', () => { describe('Computed props', () => {
it('axisTransform translates an element Y position depending of its height', () => { it('axisTransform translates an element Y position depending of its height', () => {
const component = createComponent({ const component = createComponent({
graphData: singleRowMetrics[0], columnData: convertedMetrics[1],
classType: 'col-md-6', classType: 'col-md-6',
updateAspectRatio: false, updateAspectRatio: false,
deploymentData, deploymentData,
...@@ -64,7 +46,7 @@ describe('Graph', () => { ...@@ -64,7 +46,7 @@ describe('Graph', () => {
it('outterViewBox gets a width and height property based on the DOM size of the element', () => { it('outterViewBox gets a width and height property based on the DOM size of the element', () => {
const component = createComponent({ const component = createComponent({
graphData: singleRowMetrics[0], columnData: convertedMetrics[1],
classType: 'col-md-6', classType: 'col-md-6',
updateAspectRatio: false, updateAspectRatio: false,
deploymentData, deploymentData,
...@@ -79,7 +61,7 @@ describe('Graph', () => { ...@@ -79,7 +61,7 @@ describe('Graph', () => {
it('sends an event to the eventhub when it has finished resizing', (done) => { it('sends an event to the eventhub when it has finished resizing', (done) => {
const component = createComponent({ const component = createComponent({
graphData: singleRowMetrics[0], columnData: convertedMetrics[1],
classType: 'col-md-6', classType: 'col-md-6',
updateAspectRatio: false, updateAspectRatio: false,
deploymentData, deploymentData,
...@@ -95,7 +77,7 @@ describe('Graph', () => { ...@@ -95,7 +77,7 @@ describe('Graph', () => {
it('has a title for the y-axis and the chart legend that comes from the backend', () => { it('has a title for the y-axis and the chart legend that comes from the backend', () => {
const component = createComponent({ const component = createComponent({
graphData: singleRowMetrics[0], columnData: convertedMetrics[1],
classType: 'col-md-6', classType: 'col-md-6',
updateAspectRatio: false, updateAspectRatio: false,
deploymentData, deploymentData,
......
This source diff could not be displayed because it is too large. You can view the blob instead.
import Vue from 'vue';
import MonitoringPaths from '~/monitoring/components/monitoring_paths.vue';
import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from './mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(MonitoringPaths);
return new Component({
propsData,
}).$mount();
};
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120);
describe('Monitoring Paths', () => {
it('renders two paths to represent a line and the area underneath it', () => {
const component = createComponent({
generatedLinePath: timeSeries[0].linePath,
generatedAreaPath: timeSeries[0].areaPath,
lineColor: '#ccc',
areaColor: '#fff',
});
const metricArea = component.$el.querySelector('.metric-area');
const metricLine = component.$el.querySelector('.metric-line');
expect(metricArea.getAttribute('fill')).toBe('#fff');
expect(metricArea.getAttribute('d')).toBe(timeSeries[0].areaPath);
expect(metricLine.getAttribute('stroke')).toBe('#ccc');
expect(metricLine.getAttribute('d')).toBe(timeSeries[0].linePath);
});
});
import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data';
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries[0].result, 428, 272, 120);
describe('Multiple time series', () => {
it('createTimeSeries returned array contains an object for each element', () => {
expect(typeof timeSeries[0].linePath).toEqual('string');
expect(typeof timeSeries[0].areaPath).toEqual('string');
expect(typeof timeSeries[0].timeSeriesScaleX).toEqual('function');
expect(typeof timeSeries[0].timeSeriesScaleY).toEqual('function');
expect(timeSeries[0].values instanceof Array).toEqual(true);
});
it('createTimeSeries returns an array', () => {
expect(timeSeries instanceof Array).toEqual(true);
expect(timeSeries.length).toEqual(5);
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment