Merge branch 'jivl-add-empty-state-graphs-null-values' into 'master'
Add empty state for graphs with no values Closes #53018 See merge request gitlab-org/gitlab-ce!22630
This commit is contained in:
commit
a6664ad61d
|
@ -105,6 +105,9 @@ export default {
|
|||
deploymentFlagData() {
|
||||
return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
|
||||
},
|
||||
shouldRenderData() {
|
||||
return this.graphData.queries.filter(s => s.result.length > 0).length > 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
hoverData() {
|
||||
|
@ -120,17 +123,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 +142,15 @@ export default {
|
|||
// pixel offsets inside the svg and outside are not 1:1
|
||||
this.realPixelRatio = svgWidth / this.baseGraphWidth;
|
||||
|
||||
this.renderAxesPaths();
|
||||
this.formatDeployments();
|
||||
// set the legends on the axes
|
||||
const [query] = this.graphData.queries;
|
||||
this.legendTitle = query ? query.label : 'Average';
|
||||
this.unitOfDisplay = query ? query.unit : '';
|
||||
|
||||
if (this.shouldRenderData) {
|
||||
this.renderAxesPaths();
|
||||
this.formatDeployments();
|
||||
}
|
||||
},
|
||||
handleMouseOverGraph(e) {
|
||||
let point = this.$refs.graphData.createSVGPoint();
|
||||
|
@ -266,7 +276,7 @@ export default {
|
|||
:y-axis-label="yAxisLabel"
|
||||
:unit-of-display="unitOfDisplay"
|
||||
/>
|
||||
<svg ref="graphData" :viewBox="innerViewBox" class="graph-data">
|
||||
<svg v-if="shouldRenderData" ref="graphData" :viewBox="innerViewBox" class="graph-data">
|
||||
<slot name="additionalSvgContent" :graphDrawData="graphDrawData" />
|
||||
<graph-path
|
||||
v-for="(path, index) in timeSeries"
|
||||
|
@ -293,8 +303,14 @@ export default {
|
|||
@mousemove="handleMouseOverGraph($event);"
|
||||
/>
|
||||
</svg>
|
||||
<svg v-else :viewBox="innerViewBox" class="js-no-data-to-display">
|
||||
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">
|
||||
{{ s__('Metrics|No data to display') }}
|
||||
</text>
|
||||
</svg>
|
||||
</svg>
|
||||
<graph-flag
|
||||
v-if="shouldRenderData"
|
||||
:real-pixel-ratio="realPixelRatio"
|
||||
:current-x-coordinate="currentXCoordinate"
|
||||
:current-data="currentData"
|
||||
|
|
|
@ -7,10 +7,29 @@ function sortMetrics(metrics) {
|
|||
.value();
|
||||
}
|
||||
|
||||
function checkQueryEmptyData(query) {
|
||||
return {
|
||||
...query,
|
||||
result: query.result.filter(timeSeries => {
|
||||
const newTimeSeries = timeSeries;
|
||||
const hasValue = series =>
|
||||
!Number.isNaN(series.value) && (series.value !== null || series.value !== undefined);
|
||||
const hasNonNullValue = timeSeries.values.find(hasValue);
|
||||
|
||||
newTimeSeries.values = hasNonNullValue ? newTimeSeries.values : [];
|
||||
|
||||
return newTimeSeries.values.length > 0;
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function removeTimeSeriesNoData(queries) {
|
||||
return queries.reduce((series, query) => series.concat(checkQueryEmptyData(query)), []);
|
||||
}
|
||||
|
||||
function normalizeMetrics(metrics) {
|
||||
return metrics.map(metric => ({
|
||||
...metric,
|
||||
queries: metric.queries.map(query => ({
|
||||
return metrics.map(metric => {
|
||||
const queries = metric.queries.map(query => ({
|
||||
...query,
|
||||
result: query.result.map(result => ({
|
||||
...result,
|
||||
|
@ -19,8 +38,13 @@ function normalizeMetrics(metrics) {
|
|||
value: Number(value),
|
||||
})),
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
}));
|
||||
|
||||
return {
|
||||
...metric,
|
||||
queries: removeTimeSeriesNoData(queries),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default class MonitoringStore {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add empty state for graphs with no values
|
||||
merge_request: 22630
|
||||
author:
|
||||
type: fixed
|
|
@ -4067,6 +4067,9 @@ msgstr ""
|
|||
msgid "Metrics|Learn about environments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|No data to display"
|
||||
msgstr ""
|
||||
|
||||
msgid "Metrics|No deployed environments"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
deploymentData,
|
||||
convertDatesMultipleSeries,
|
||||
singleRowMetricsMultipleSeries,
|
||||
queryWithoutData,
|
||||
} from './mock_data';
|
||||
|
||||
const tagsPath = 'http://test.host/frontend-fixtures/environments-project/tags';
|
||||
|
@ -104,4 +105,23 @@ describe('Graph', () => {
|
|||
|
||||
expect(component.currentData).toBe(component.timeSeries[0].values[10]);
|
||||
});
|
||||
|
||||
describe('Without data to display', () => {
|
||||
it('shows a "no data to display" empty state on a graph', done => {
|
||||
const component = createComponent({
|
||||
graphData: queryWithoutData,
|
||||
deploymentData,
|
||||
tagsPath,
|
||||
projectPath,
|
||||
});
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.js-no-data-to-display text').textContent.trim(),
|
||||
).toEqual('No data to display');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ export const metricsGroupsAPIResponse = {
|
|||
queries: [
|
||||
{
|
||||
query_range: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
|
||||
y_label: 'Memory',
|
||||
label: 'Memory',
|
||||
unit: 'MiB',
|
||||
result: [
|
||||
{
|
||||
|
@ -324,12 +324,15 @@ export const metricsGroupsAPIResponse = {
|
|||
],
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: 'CPU usage',
|
||||
weight: 1,
|
||||
queries: [
|
||||
{
|
||||
query_range:
|
||||
'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
|
||||
label: 'Core Usage',
|
||||
unit: 'Cores',
|
||||
result: [
|
||||
{
|
||||
metric: {},
|
||||
|
@ -639,6 +642,39 @@ export const metricsGroupsAPIResponse = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'NGINX',
|
||||
priority: 2,
|
||||
metrics: [
|
||||
{
|
||||
id: 100,
|
||||
title: 'Http Error Rate',
|
||||
weight: 100,
|
||||
queries: [
|
||||
{
|
||||
query_range:
|
||||
'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"nginx-test-8691397-production-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"nginx-test-8691397-production-.*"}[2m])) * 100',
|
||||
label: '5xx errors',
|
||||
unit: '%',
|
||||
result: [
|
||||
{
|
||||
metric: {},
|
||||
values: [
|
||||
[1495700554.925, NaN],
|
||||
[1495700614.925, NaN],
|
||||
[1495700674.925, NaN],
|
||||
[1495700734.925, NaN],
|
||||
[1495700794.925, NaN],
|
||||
[1495700854.925, NaN],
|
||||
[1495700914.925, NaN],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
last_update: '2017-05-25T13:18:34.949Z',
|
||||
};
|
||||
|
@ -6526,6 +6562,21 @@ export const singleRowMetricsMultipleSeries = [
|
|||
},
|
||||
];
|
||||
|
||||
export const queryWithoutData = {
|
||||
title: 'HTTP Error rate',
|
||||
weight: 10,
|
||||
y_label: 'Http Error Rate',
|
||||
queries: [
|
||||
{
|
||||
query_range:
|
||||
'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"nginx-test-8691397-production-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"nginx-test-8691397-production-.*"}[2m])) * 100',
|
||||
label: '5xx errors',
|
||||
unit: '%',
|
||||
result: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export function convertDatesMultipleSeries(multipleSeries) {
|
||||
const convertedMultiple = multipleSeries;
|
||||
multipleSeries.forEach((column, index) => {
|
||||
|
|
|
@ -1,31 +1,35 @@
|
|||
import MonitoringStore from '~/monitoring/stores/monitoring_store';
|
||||
import MonitoringMock, { deploymentData, environmentData } from './mock_data';
|
||||
|
||||
describe('MonitoringStore', function() {
|
||||
this.store = new MonitoringStore();
|
||||
this.store.storeMetrics(MonitoringMock.data);
|
||||
describe('MonitoringStore', () => {
|
||||
const store = new MonitoringStore();
|
||||
store.storeMetrics(MonitoringMock.data);
|
||||
|
||||
it('contains one group that contains two queries sorted by priority', () => {
|
||||
expect(this.store.groups).toBeDefined();
|
||||
expect(this.store.groups.length).toEqual(1);
|
||||
expect(this.store.groups[0].metrics.length).toEqual(2);
|
||||
it('contains two groups that contains, one of which has two queries sorted by priority', () => {
|
||||
expect(store.groups).toBeDefined();
|
||||
expect(store.groups.length).toEqual(2);
|
||||
expect(store.groups[0].metrics.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('gets the metrics count for every group', () => {
|
||||
expect(this.store.getMetricsCount()).toEqual(2);
|
||||
expect(store.getMetricsCount()).toEqual(3);
|
||||
});
|
||||
|
||||
it('contains deployment data', () => {
|
||||
this.store.storeDeploymentData(deploymentData);
|
||||
store.storeDeploymentData(deploymentData);
|
||||
|
||||
expect(this.store.deploymentData).toBeDefined();
|
||||
expect(this.store.deploymentData.length).toEqual(3);
|
||||
expect(typeof this.store.deploymentData[0]).toEqual('object');
|
||||
expect(store.deploymentData).toBeDefined();
|
||||
expect(store.deploymentData.length).toEqual(3);
|
||||
expect(typeof store.deploymentData[0]).toEqual('object');
|
||||
});
|
||||
|
||||
it('only stores environment data that contains deployments', () => {
|
||||
this.store.storeEnvironmentsData(environmentData);
|
||||
store.storeEnvironmentsData(environmentData);
|
||||
|
||||
expect(this.store.environmentsData.length).toEqual(2);
|
||||
expect(store.environmentsData.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('removes the data if all the values from a query are not defined', () => {
|
||||
expect(store.groups[1].metrics[0].queries[0].result.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue