diff options
10 files changed, 421 insertions, 299 deletions
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index 04d546fafa0..9bf32d8a2da 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -3,6 +3,7 @@ import { scaleLinear, scaleTime } from 'd3-scale'; import { axisLeft, axisBottom } from 'd3-axis'; import { max, extent } from 'd3-array'; import { select } from 'd3-selection'; +import GraphAxis from './graph/axis.vue'; import GraphLegend from './graph/legend.vue'; import GraphFlag from './graph/flag.vue'; import GraphDeployment from './graph/deployment.vue'; @@ -18,10 +19,11 @@ const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select } export default { components: { - GraphLegend, + GraphAxis, GraphFlag, GraphDeployment, GraphPath, + GraphLegend, }, mixins: [MonitoringMixin], props: { @@ -138,7 +140,7 @@ export default { this.legendTitle = query.label || 'Average'; this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right; this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom; - this.baseGraphHeight = this.graphHeight; + this.baseGraphHeight = this.graphHeight - 50; this.baseGraphWidth = this.graphWidth; // pixel offsets inside the svg and outside are not 1:1 @@ -177,14 +179,10 @@ export default { this.graphHeightOffset, ); - if (!this.showLegend) { - this.baseGraphHeight -= 50; - } else if (this.timeSeries.length > 3) { - this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20; - } - - const axisXScale = d3.scaleTime().range([0, this.graphWidth - 70]); - const axisYScale = d3.scaleLinear().range([this.graphHeight - this.graphHeightOffset, 0]); + const axisXScale = d3.scaleTime() + .range([0, this.graphWidth - 70]); + const axisYScale = d3.scaleLinear() + .range([this.graphHeight - this.graphHeightOffset, 0]); const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []); axisXScale.domain(d3.extent(allValues, d => d.time)); @@ -251,17 +249,12 @@ export default { class="y-axis" transform="translate(70, 20)" /> - <graph-legend + <graph-axis :graph-width="graphWidth" :graph-height="graphHeight" :margin="margin" :measurements="measurements" - :legend-title="legendTitle" :y-axis-label="yAxisLabel" - :time-series="timeSeries" - :unit-of-display="unitOfDisplay" - :current-data-index="currentDataIndex" - :show-legend-group="showLegend" /> <svg class="graph-data" @@ -306,5 +299,12 @@ export default { :deployment-flag-data="deploymentFlagData" /> </div> + <graph-legend + v-if="showLegend" + :legend-title="legendTitle" + :time-series="timeSeries" + :current-data-index="currentDataIndex" + :unit-of-display="unitOfDisplay" + /> </div> </template> diff --git a/app/assets/javascripts/monitoring/components/graph/axis.vue b/app/assets/javascripts/monitoring/components/graph/axis.vue new file mode 100644 index 00000000000..ae397fc39e2 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/graph/axis.vue @@ -0,0 +1,127 @@ +<script> +export default { + props: { + graphWidth: { + type: Number, + required: true, + }, + graphHeight: { + type: Number, + required: true, + }, + margin: { + type: Object, + required: true, + }, + measurements: { + type: Object, + required: true, + }, + yAxisLabel: { + type: String, + required: true, + }, + }, + data() { + return { + yLabelWidth: 0, + yLabelHeight: 0, + }; + }, + computed: { + textTransform() { + const yCoordinate = + (this.graphHeight - + this.margin.top + + this.measurements.axisLabelLineOffset) / + 2 || 0; + + return `translate(15, ${yCoordinate}) rotate(-90)`; + }, + + rectTransform() { + const yCoordinate = + (this.graphHeight - + this.margin.top + + this.measurements.axisLabelLineOffset) / + 2 + + this.yLabelWidth / 2 || 0; + + return `translate(0, ${yCoordinate}) rotate(-90)`; + }, + + xPosition() { + return ( + (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - + this.margin.right || 0 + ); + }, + + yPosition() { + return ( + this.graphHeight - + this.margin.top + + this.measurements.axisLabelLineOffset || 0 + ); + }, + }, + mounted() { + this.$nextTick(() => { + const bbox = this.$refs.ylabel.getBBox(); + this.yLabelWidth = bbox.width + 10; // Added some padding + this.yLabelHeight = bbox.height + 5; + }); + }, +}; +</script> +<template> + <g class="axis-label-container"> + <line + class="label-x-axis-line" + stroke="#000000" + stroke-width="1" + x1="10" + :y1="yPosition" + :x2="graphWidth + 20" + :y2="yPosition" + /> + <line + class="label-y-axis-line" + stroke="#000000" + stroke-width="1" + x1="10" + y1="0" + :x2="10" + :y2="yPosition" + /> + <rect + class="rect-axis-text" + :transform="rectTransform" + :width="yLabelWidth" + :height="yLabelHeight" + /> + <text + class="label-axis-text y-label-text" + text-anchor="middle" + :transform="textTransform" + ref="ylabel" + > + {{ yAxisLabel }} + </text> + <rect + class="rect-axis-text" + :x="xPosition + 60" + :y="graphHeight - 80" + width="35" + height="50" + /> + <text + class="label-axis-text x-label-text" + :x="xPosition + 60" + :y="yPosition" + dy=".35em" + > + Time + </text> + </g> +</template> diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue index 906c7c51f52..8e6a830f08c 100644 --- a/app/assets/javascripts/monitoring/components/graph/flag.vue +++ b/app/assets/javascripts/monitoring/components/graph/flag.vue @@ -160,7 +160,7 @@ export default { </div> </div> <div class="popover-content"> - <table> + <table class="prometheus-table"> <tr v-for="(series, index) in timeSeries" :key="index" diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue index a7a058a9203..ac1c6e34cc4 100644 --- a/app/assets/javascripts/monitoring/components/graph/legend.vue +++ b/app/assets/javascripts/monitoring/components/graph/legend.vue @@ -1,112 +1,50 @@ <script> -import { formatRelevantDigits } from '../../../lib/utils/number_utils'; +import { formatRelevantDigits } from '~/lib/utils/number_utils'; export default { props: { - graphWidth: { - type: Number, - required: true, - }, - graphHeight: { - type: Number, - required: true, - }, - margin: { - type: Object, - required: true, - }, - measurements: { - type: Object, - required: true, - }, legendTitle: { type: String, required: true, }, - yAxisLabel: { - type: String, - required: true, - }, timeSeries: { type: Array, required: true, }, - unitOfDisplay: { - type: String, - required: true, - }, currentDataIndex: { type: Number, required: true, }, - showLegendGroup: { - type: Boolean, - required: false, - default: true, - }, - }, - data() { - return { - yLabelWidth: 0, - yLabelHeight: 0, - seriesXPosition: 0, - metricUsageXPosition: 0, - }; - }, - computed: { - textTransform() { - const yCoordinate = - (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 || 0; - - return `translate(15, ${yCoordinate}) rotate(-90)`; - }, - rectTransform() { - const yCoordinate = - (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 + - this.yLabelWidth / 2 || 0; - - return `translate(0, ${yCoordinate}) rotate(-90)`; - }, - xPosition() { - return (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - this.margin.right || 0; - }, - yPosition() { - return this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset || 0; + unitOfDisplay: { + type: String, + required: true, }, }, - mounted() { - this.$nextTick(() => { - 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; - } - if (this.$refs.seriesTitleSvg != null) { - this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width; - } - this.yLabelWidth = bbox.width + 10; // Added some padding - this.yLabelHeight = bbox.height + 5; - }); - }, methods: { - translateLegendGroup(index) { - return `translate(0, ${12 * index})`; - }, formatMetricUsage(series) { const value = - series.values[this.currentDataIndex] && series.values[this.currentDataIndex].value; + series.values[this.currentDataIndex] && + series.values[this.currentDataIndex].value; if (isNaN(value)) { return '-'; } return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`; }, + createSeriesString(index, series) { if (series.metricTag) { return `${series.metricTag} ${this.formatMetricUsage(series)}`; } - return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`; + return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage( + series, + )}`; + }, + + summaryMetrics(series) { + return `Avg: ${formatRelevantDigits(series.average)} ${this.unitOfDisplay}, + Max: ${formatRelevantDigits(series.max)} ${this.unitOfDisplay}`; }, + strokeDashArray(type) { if (type === 'dashed') return '6, 3'; if (type === 'dotted') return '3, 3'; @@ -116,89 +54,38 @@ export default { }; </script> <template> - <g class="axis-label-container"> - <line - class="label-x-axis-line" - stroke="#000000" - stroke-width="1" - x1="10" - :y1="yPosition" - :x2="graphWidth + 20" - :y2="yPosition" - /> - <line - class="label-y-axis-line" - stroke="#000000" - stroke-width="1" - x1="10" - y1="0" - :x2="10" - :y2="yPosition" - /> - <rect - class="rect-axis-text" - :transform="rectTransform" - :width="yLabelWidth" - :height="yLabelHeight" - /> - <text - class="label-axis-text y-label-text" - text-anchor="middle" - :transform="textTransform" - ref="ylabel" - > - {{ yAxisLabel }} - </text> - <rect - class="rect-axis-text" - :x="xPosition + 60" - :y="graphHeight - 80" - width="35" - height="50" - /> - <text - class="label-axis-text x-label-text" - :x="xPosition + 60" - :y="yPosition" - dy=".35em" - > - Time - </text> - <template v-if="showLegendGroup"> - <g - class="legend-group" + <div class="prometheus-graph-legends prepend-left-10"> + <table class="prometheus-table"> + <tr v-for="(series, index) in timeSeries" :key="index" - :transform="translateLegendGroup(index)" > - <line - :stroke="series.lineColor" - :stroke-width="measurements.legends.height" - :stroke-dasharray="strokeDashArray(series.lineStyle)" - :x1="measurements.legends.offsetX" - :x2="measurements.legends.offsetX + measurements.legends.width" - :y1="graphHeight - measurements.legends.offsetY" - :y2="graphHeight - measurements.legends.offsetY" - /> - <text - v-if="timeSeries.length > 1" + <td> + <svg + width="15" + height="6" + > + <line + :stroke-dasharray="strokeDashArray(series.lineStyle)" + :stroke="series.lineColor" + stroke-width="4" + :x1="0" + :x2="15" + :y1="2" + :y2="2" + /> + </svg> + </td> + <td class="legend-metric-title" - ref="legendTitleSvg" - x="38" - :y="graphHeight - 30" - > - {{ createSeriesString(index, series) }} - </text> - <text - v-else - class="legend-metric-title" - ref="legendTitleSvg" - x="38" - :y="graphHeight - 30" + v-if="timeSeries.length > 1" > - {{ legendTitle }} {{ formatMetricUsage(series) }} - </text> - </g> - </template> - </g> + {{ createSeriesString(index, series) }}, {{ summaryMetrics(series) }} + </td> + <td v-else> + {{ legendTitle }} {{ formatMetricUsage(series) }}, {{ summaryMetrics(series) }} + </td> + </tr> + </table> + </div> </template> diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js index b5b8e3c255d..95db31c7a2a 100644 --- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js +++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js @@ -1,10 +1,20 @@ import _ from 'underscore'; import { scaleLinear, scaleTime } from 'd3-scale'; import { line, area, curveLinear } from 'd3-shape'; -import { extent, max } from 'd3-array'; +import { extent, max, sum } from 'd3-array'; import { timeMinute } from 'd3-time'; -const d3 = { scaleLinear, scaleTime, line, area, curveLinear, extent, max, timeMinute }; +const d3 = { + scaleLinear, + scaleTime, + line, + area, + curveLinear, + extent, + max, + timeMinute, + sum, +}; const defaultColorPalette = { blue: ['#1f78d1', '#8fbce8'], @@ -18,7 +28,15 @@ const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple']; const defaultStyleOrder = ['solid', 'dashed', 'dotted']; -function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) { +function queryTimeSeries( + query, + graphWidth, + graphHeight, + graphHeightOffset, + xDom, + yDom, + lineStyle, +) { let usedColors = []; function pickColor(name) { @@ -42,11 +60,14 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom let metricTag = ''; let lineColor = ''; let areaColor = ''; + const timeSeriesValues = timeSeries.values.map(d => d.value); + const maximumValue = d3.max(timeSeriesValues); + const accum = d3.sum(timeSeriesValues); - const timeSeriesScaleX = d3.scaleTime() - .range([0, graphWidth - 70]); + const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]); - const timeSeriesScaleY = d3.scaleLinear() + const timeSeriesScaleY = d3 + .scaleLinear() .range([graphHeight - graphHeightOffset, 0]); timeSeriesScaleX.domain(xDom); @@ -55,28 +76,35 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom const defined = d => !isNaN(d.value) && d.value != null; - const lineFunction = d3.line() + const lineFunction = d3 + .line() .defined(defined) .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate .x(d => timeSeriesScaleX(d.time)) .y(d => timeSeriesScaleY(d.value)); - const areaFunction = d3.area() + const areaFunction = d3 + .area() .defined(defined) .curve(d3.curveLinear) .x(d => timeSeriesScaleX(d.time)) .y0(graphHeight - graphHeightOffset) .y1(d => timeSeriesScaleY(d.value)); - const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]]; - const seriesCustomizationData = query.series != null && + const timeSeriesMetricLabel = + timeSeries.metric[Object.keys(timeSeries.metric)[0]]; + const seriesCustomizationData = + query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel }); if (seriesCustomizationData) { metricTag = seriesCustomizationData.value || timeSeriesMetricLabel; [lineColor, areaColor] = pickColor(seriesCustomizationData.color); } else { - metricTag = timeSeriesMetricLabel || query.label || `series ${timeSeriesNumber + 1}`; + metricTag = + timeSeriesMetricLabel || + query.label || + `series ${timeSeriesNumber + 1}`; [lineColor, areaColor] = pickColor(); } @@ -89,6 +117,8 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom areaPath: areaFunction(timeSeries.values), timeSeriesScaleX, values: timeSeries.values, + max: maximumValue, + average: accum / timeSeries.values.length, lineStyle, lineColor, areaColor, @@ -97,10 +127,22 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom }); } -export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) { - const allValues = queries.reduce((allQueryResults, query) => allQueryResults.concat( - query.result.reduce((allResults, result) => allResults.concat(result.values), []), - ), []); +export default function createTimeSeries( + queries, + graphWidth, + graphHeight, + graphHeightOffset, +) { + const allValues = queries.reduce( + (allQueryResults, query) => + allQueryResults.concat( + query.result.reduce( + (allResults, result) => allResults.concat(result.values), + [], + ), + ), + [], + ); const xDom = d3.extent(allValues, d => d.time); const yDom = [0, d3.max(allValues.map(d => d.value))]; @@ -108,7 +150,15 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph return queries.reduce((series, query, index) => { const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length]; return series.concat( - queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle), + queryTimeSeries( + query, + graphWidth, + graphHeight, + graphHeightOffset, + xDom, + yDom, + lineStyle, + ), ); }, []); } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 58700661142..9016dd3374f 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -273,21 +273,6 @@ line-height: 1.2; } - table { - border-collapse: collapse; - padding: 0; - margin: 0; - } - - td { - vertical-align: middle; - - + td { - padding-left: 5px; - vertical-align: top; - } - } - .deploy-meta-content { border-bottom: 1px solid $white-dark; @@ -323,6 +308,21 @@ } } +.prometheus-table { + border-collapse: collapse; + padding: 0; + margin: 0; + + td { + vertical-align: middle; + + + td { + padding-left: 5px; + vertical-align: top; + } + } +} + .prometheus-svg-container { position: relative; height: 0; @@ -330,8 +330,7 @@ padding: 0; padding-bottom: 100%; - .text-metric-usage, - .legend-metric-title { + .text-metric-usage { fill: $black; font-weight: $gl-font-weight-normal; font-size: 12px; @@ -374,10 +373,6 @@ } } - .text-metric-title { - font-size: 12px; - } - .y-label-text, .x-label-text { fill: $gray-darkest; diff --git a/changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml b/changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml new file mode 100644 index 00000000000..c5cdbcf7b40 --- /dev/null +++ b/changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml @@ -0,0 +1,5 @@ +--- +title: Add average and maximum summary statistics to the prometheus dashboard +merge_request: 17921 +author: +type: changed diff --git a/spec/javascripts/monitoring/graph/axis_spec.js b/spec/javascripts/monitoring/graph/axis_spec.js new file mode 100644 index 00000000000..a251a72cfcf --- /dev/null +++ b/spec/javascripts/monitoring/graph/axis_spec.js @@ -0,0 +1,70 @@ +import Vue from 'vue'; +import GraphAxis from '~/monitoring/components/graph/axis.vue'; +import measurements from '~/monitoring/utils/measurements'; + +const createComponent = propsData => { + const Component = Vue.extend(GraphAxis); + + return new Component({ + propsData, + }).$mount(); +}; + +const defaultValuesComponent = { + graphWidth: 500, + graphHeight: 300, + graphHeightOffset: 120, + margin: measurements.large.margin, + measurements: measurements.large, + yAxisLabel: 'Values', +}; + +function getTextFromNode(component, selector) { + return component.$el.querySelector(selector).firstChild.nodeValue.trim(); +} + +describe('Axis', () => { + describe('Computed props', () => { + it('textTransform', () => { + const component = createComponent(defaultValuesComponent); + + expect(component.textTransform).toContain( + 'translate(15, 120) rotate(-90)', + ); + }); + + it('xPosition', () => { + const component = createComponent(defaultValuesComponent); + + expect(component.xPosition).toEqual(180); + }); + + it('yPosition', () => { + const component = createComponent(defaultValuesComponent); + + expect(component.yPosition).toEqual(240); + }); + + it('rectTransform', () => { + const component = createComponent(defaultValuesComponent); + + expect(component.rectTransform).toContain( + 'translate(0, 120) rotate(-90)', + ); + }); + }); + + it('has 2 rect-axis-text rect svg elements', () => { + const component = createComponent(defaultValuesComponent); + + expect(component.$el.querySelectorAll('.rect-axis-text').length).toEqual(2); + }); + + it('contains text to signal the usage, title and time with multiple time series', () => { + const component = createComponent(defaultValuesComponent); + + expect(getTextFromNode(component, '.y-label-text')).toEqual( + component.yAxisLabel, + ); + }); +}); diff --git a/spec/javascripts/monitoring/graph/legend_spec.js b/spec/javascripts/monitoring/graph/legend_spec.js index 145c8db28d5..6c085ebba89 100644 --- a/spec/javascripts/monitoring/graph/legend_spec.js +++ b/spec/javascripts/monitoring/graph/legend_spec.js @@ -1,106 +1,85 @@ import Vue from 'vue'; import GraphLegend from '~/monitoring/components/graph/legend.vue'; -import measurements from '~/monitoring/utils/measurements'; import createTimeSeries from '~/monitoring/utils/multiple_time_series'; -import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data'; - -const createComponent = (propsData) => { - const Component = Vue.extend(GraphLegend); - - return new Component({ - propsData, - }).$mount(); -}; - -const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); - -const defaultValuesComponent = { - graphWidth: 500, - graphHeight: 300, - graphHeightOffset: 120, - margin: measurements.large.margin, - measurements: measurements.large, - areaColorRgb: '#f0f0f0', - legendTitle: 'Title', - yAxisLabel: 'Values', - metricUsage: 'Value', - unitOfDisplay: 'Req/Sec', - currentDataIndex: 0, -}; - -const timeSeries = createTimeSeries(convertedMetrics[0].queries, - defaultValuesComponent.graphWidth, defaultValuesComponent.graphHeight, - defaultValuesComponent.graphHeightOffset); +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { + singleRowMetricsMultipleSeries, + convertDatesMultipleSeries, +} from '../mock_data'; -defaultValuesComponent.timeSeries = timeSeries; - -function getTextFromNode(component, selector) { - return component.$el.querySelector(selector).firstChild.nodeValue.trim(); -} +const convertedMetrics = convertDatesMultipleSeries( + singleRowMetricsMultipleSeries, +); -describe('GraphLegend', () => { - describe('Computed props', () => { - it('textTransform', () => { - const component = createComponent(defaultValuesComponent); +const defaultValuesComponent = {}; - expect(component.textTransform).toContain('translate(15, 120) rotate(-90)'); - }); +const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); - it('xPosition', () => { - const component = createComponent(defaultValuesComponent); +defaultValuesComponent.timeSeries = timeSeries; - expect(component.xPosition).toEqual(180); - }); +describe('Legend Component', () => { + let vm; + let Legend; - it('yPosition', () => { - const component = createComponent(defaultValuesComponent); + beforeEach(() => { + Legend = Vue.extend(GraphLegend); + }); - expect(component.yPosition).toEqual(240); + describe('Methods', () => { + beforeEach(() => { + vm = mountComponent(Legend, { + legendTitle: 'legend', + timeSeries, + currentDataIndex: 0, + unitOfDisplay: 'Req/Sec', + }); }); - it('rectTransform', () => { - const component = createComponent(defaultValuesComponent); + it('formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"', () => { + const formattedMetricUsage = vm.formatMetricUsage(timeSeries[0]); + const valueFromSeries = timeSeries[0].values[vm.currentDataIndex].value; - expect(component.rectTransform).toContain('translate(0, 120) rotate(-90)'); + expect(formattedMetricUsage.indexOf(vm.unitOfDisplay)).not.toEqual(-1); + expect(formattedMetricUsage.indexOf(valueFromSeries)).not.toEqual(-1); }); - }); - describe('methods', () => { - it('translateLegendGroup should only change Y direction', () => { - const component = createComponent(defaultValuesComponent); + it('strokeDashArray', () => { + const dashedArray = vm.strokeDashArray('dashed'); + const dottedArray = vm.strokeDashArray('dotted'); - const translatedCoordinate = component.translateLegendGroup(1); - expect(translatedCoordinate.indexOf('translate(0, ')).not.toEqual(-1); + expect(dashedArray).toEqual('6, 3'); + expect(dottedArray).toEqual('3, 3'); }); - it('formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"', () => { - const component = createComponent(defaultValuesComponent); + it('summaryMetrics gets the average and max of a series', () => { + const summary = vm.summaryMetrics(timeSeries[0]); - 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); + expect(summary.indexOf('Max')).not.toEqual(-1); + expect(summary.indexOf('Avg')).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); - }); - - it('contains text to signal the usage, title and time with multiple time series', () => { - const component = createComponent(defaultValuesComponent); - const titles = component.$el.querySelectorAll('.legend-metric-title'); + describe('View', () => { + beforeEach(() => { + vm = mountComponent(Legend, { + legendTitle: 'legend', + timeSeries, + currentDataIndex: 0, + unitOfDisplay: 'Req/Sec', + }); + }); - expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1); - expect(titles[1].textContent.indexOf('2xx')).not.toEqual(-1); - expect(getTextFromNode(component, '.y-label-text')).toEqual(component.yAxisLabel); - }); + it('should render the usage, title and time with multiple time series', () => { + const titles = vm.$el.querySelectorAll('.legend-metric-title'); - it('should contain the same number of legend groups as the timeSeries length', () => { - const component = createComponent(defaultValuesComponent); + expect(titles[0].textContent.indexOf('1xx')).not.toEqual(-1); + expect(titles[1].textContent.indexOf('2xx')).not.toEqual(-1); + }); - expect(component.$el.querySelectorAll('.legend-group').length).toEqual(component.timeSeries.length); + it('should container the same number of rows in the table as time series', () => { + expect(vm.$el.querySelectorAll('.prometheus-table tr').length).toEqual( + vm.timeSeries.length, + ); + }); }); }); diff --git a/spec/javascripts/monitoring/graph_spec.js b/spec/javascripts/monitoring/graph_spec.js index b1d69752bad..1213c80ba3a 100644 --- a/spec/javascripts/monitoring/graph_spec.js +++ b/spec/javascripts/monitoring/graph_spec.js @@ -2,11 +2,15 @@ import Vue from 'vue'; import Graph from '~/monitoring/components/graph.vue'; import MonitoringMixins from '~/monitoring/mixins/monitoring_mixins'; import eventHub from '~/monitoring/event_hub'; -import { deploymentData, convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from './mock_data'; +import { + deploymentData, + convertDatesMultipleSeries, + singleRowMetricsMultipleSeries, +} from './mock_data'; const tagsPath = 'http://test.host/frontend-fixtures/environments-project/tags'; const projectPath = 'http://test.host/frontend-fixtures/environments-project'; -const createComponent = (propsData) => { +const createComponent = propsData => { const Component = Vue.extend(Graph); return new Component({ @@ -14,7 +18,9 @@ const createComponent = (propsData) => { }).$mount(); }; -const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); +const convertedMetrics = convertDatesMultipleSeries( + singleRowMetricsMultipleSeries, +); describe('Graph', () => { beforeEach(() => { @@ -31,7 +37,9 @@ describe('Graph', () => { projectPath, }); - expect(component.$el.querySelector('.text-center').innerText.trim()).toBe(component.graphData.title); + expect(component.$el.querySelector('.text-center').innerText.trim()).toBe( + component.graphData.title, + ); }); describe('Computed props', () => { @@ -46,8 +54,9 @@ describe('Graph', () => { }); const transformedHeight = `${component.graphHeight - 100}`; - expect(component.axisTransform.indexOf(transformedHeight)) - .not.toEqual(-1); + expect(component.axisTransform.indexOf(transformedHeight)).not.toEqual( + -1, + ); }); it('outerViewBox gets a width and height property based on the DOM size of the element', () => { @@ -63,11 +72,11 @@ describe('Graph', () => { const viewBoxArray = component.outerViewBox.split(' '); expect(typeof component.outerViewBox).toEqual('string'); expect(viewBoxArray[2]).toEqual(component.graphWidth.toString()); - expect(viewBoxArray[3]).toEqual(component.graphHeight.toString()); + expect(viewBoxArray[3]).toEqual((component.graphHeight - 50).toString()); }); }); - 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({ graphData: convertedMetrics[1], classType: 'col-md-6', |