diff options
| author | Jose Vargas <jvargas@gitlab.com> | 2018-10-26 13:29:16 -0500 |
|---|---|---|
| committer | Jose Vargas <jvargas@gitlab.com> | 2018-11-26 16:00:24 -0600 |
| commit | 999cc2262c9fd6450bf70418ecfe8b5aa55762e2 (patch) | |
| tree | e456edfab6a2732268cb457d8f2ee095303ca1fa | |
| parent | deaf3af7e5f357f3e8d91f7f2d49ad3ce001ba68 (diff) | |
| download | gitlab-ce-999cc2262c9fd6450bf70418ecfe8b5aa55762e2.tar.gz | |
Add empty state for graphs with no values
This adds a new "empty" state for graphs, whose time series contain
all of their data as either `null`, `undefined` or `NaN`. If only a set
amount of time series do not contain data but the rest does, the
remaining time series will render accordingly
| -rw-r--r-- | app/assets/javascripts/monitoring/components/graph.vue | 56 | ||||
| -rw-r--r-- | app/assets/javascripts/monitoring/utils/multiple_time_series.js | 32 |
2 files changed, 80 insertions, 8 deletions
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index 815063237fc..9f5045a4960 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -13,8 +13,9 @@ import MonitoringMixin from '../mixins/monitoring_mixins'; import eventHub from '../event_hub'; import measurements from '../utils/measurements'; import { bisectDate, timeScaleFormat } from '../utils/date_time_formatters'; -import createTimeSeries from '../utils/multiple_time_series'; +import createTimeSeries, { removeTimeSeriesNoData } from '../utils/multiple_time_series'; import bp from '../../breakpoints'; +import { s__ } from '~/locale'; const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select }; @@ -85,6 +86,7 @@ export default { graphDrawData: {}, realPixelRatio: 1, seriesUnderMouse: [], + renderEmptyState: false, }; }, computed: { @@ -105,6 +107,9 @@ export default { deploymentFlagData() { return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag); }, + noDataToDisplayMsg() { + return s__('Metrics|No data to display'); + }, }, watch: { hoverData() { @@ -120,17 +125,17 @@ export default { }, draw() { const breakpointSize = bp.getBreakpointSize(); - const query = this.graphData.queries[0]; const svgWidth = this.$refs.baseSvg.getBoundingClientRect().width; + this.margin = measurements.large.margin; + if (this.smallGraph || breakpointSize === 'xs' || breakpointSize === 'sm') { this.graphHeight = 300; this.margin = measurements.small.margin; this.measurements = measurements.small; } - this.unitOfDisplay = query.unit || ''; + this.yAxisLabel = this.graphData.y_label || 'Values'; - this.legendTitle = query.label || 'Average'; this.graphWidth = svgWidth - this.margin.left - this.margin.right; this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom; this.baseGraphHeight = this.graphHeight - 50; @@ -139,8 +144,20 @@ export default { // pixel offsets inside the svg and outside are not 1:1 this.realPixelRatio = svgWidth / this.baseGraphWidth; - this.renderAxesPaths(); - this.formatDeployments(); + // verify the data + this.graphData.queries = removeTimeSeriesNoData(this.graphData.queries); + const queryLengthOfData = this.graphData.queries.filter(s => s.result.length > 0).length; + + const [query] = this.graphData.queries; + this.legendTitle = query ? query.label : 'Average'; + this.unitOfDisplay = query ? query.unit : ''; + + if (queryLengthOfData > 0) { + this.renderAxesPaths(); + this.formatDeployments(); + } else { + this.renderEmptyState = true; + } }, handleMouseOverGraph(e) { let point = this.$refs.graphData.createSVGPoint(); @@ -266,8 +283,16 @@ export default { :y-axis-label="yAxisLabel" :unit-of-display="unitOfDisplay" /> - <svg ref="graphData" :viewBox="innerViewBox" class="graph-data"> - <slot name="additionalSvgContent" :graphDrawData="graphDrawData" /> + <svg + v-if="!renderEmptyState" + ref="graphData" + :viewBox="innerViewBox" + class="graph-data" + > + <slot + name="additionalSvgContent" + :graphDrawData="graphDrawData" + /> <graph-path v-for="(path, index) in timeSeries" :key="index" @@ -293,8 +318,23 @@ export default { @mousemove="handleMouseOverGraph($event);" /> </svg> + <svg + v-else + :viewBox="innerViewBox" + class="js-no-data-empty-state" + > + <text + x="50%" + y="50%" + alignment-baseline="middle" + text-anchor="middle" + > + {{ noDataToDisplayMsg }} + </text> + </svg> </svg> <graph-flag + v-if="!renderEmptyState" :real-pixel-ratio="realPixelRatio" :current-x-coordinate="currentXCoordinate" :current-data="currentData" diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js index bb24a1acdb3..72cf91afc7c 100644 --- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js +++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js @@ -217,3 +217,35 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph graphDrawData, }; } + +function checkQueryEmptyData(query) { + return { + ...query, + result: query.result.filter(timeSeries => { + const newTimeSeries = timeSeries; + const emptyValues = timeSeries.values.filter( + val => Number.isNaN(val.value) || val.value === null || val.value === undefined, + ); + + if (emptyValues.length === timeSeries.values.length) { + newTimeSeries.values = []; + } + + return newTimeSeries; + }), + }; +} + +export function removeTimeSeriesNoData(queries) { + const timeSeries = queries.reduce((series, query) => { + let checkedQuery = checkQueryEmptyData(query); + checkedQuery = { + ...checkedQuery, + result: checkedQuery.result.filter(c => c.values.length > 0), + }; + + return series.concat(checkedQuery); + }, []); + + return timeSeries; +} |
