summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJose Vargas <jvargas@gitlab.com>2018-10-26 13:29:16 -0500
committerJose Vargas <jvargas@gitlab.com>2018-11-26 16:00:24 -0600
commit999cc2262c9fd6450bf70418ecfe8b5aa55762e2 (patch)
treee456edfab6a2732268cb457d8f2ee095303ca1fa
parentdeaf3af7e5f357f3e8d91f7f2d49ad3ce001ba68 (diff)
downloadgitlab-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.vue56
-rw-r--r--app/assets/javascripts/monitoring/utils/multiple_time_series.js32
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;
+}