Merge branch 'add-error-empty-states' into 'master'
Introduced empty/error UX states to environments monitoring. Closes #29212 See merge request !10271
This commit is contained in:
commit
15429ea554
10 changed files with 213 additions and 36 deletions
|
@ -6,7 +6,10 @@ import statusCodes from '~/lib/utils/http_status';
|
|||
import { formatRelevantDigits } from '~/lib/utils/number_utils';
|
||||
import '../flash';
|
||||
|
||||
const prometheusContainer = '.prometheus-container';
|
||||
const prometheusParentGraphContainer = '.prometheus-graphs';
|
||||
const prometheusGraphsContainer = '.prometheus-graph';
|
||||
const prometheusStatesContainer = '.prometheus-state';
|
||||
const metricsEndpoint = 'metrics.json';
|
||||
const timeFormat = d3.time.format('%H:%M');
|
||||
const dayFormat = d3.time.format('%b %e, %a');
|
||||
|
@ -14,19 +17,30 @@ const bisectDate = d3.bisector(d => d.time).left;
|
|||
const extraAddedWidthParent = 100;
|
||||
|
||||
class PrometheusGraph {
|
||||
|
||||
constructor() {
|
||||
this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
|
||||
this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
|
||||
const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
|
||||
extraAddedWidthParent;
|
||||
this.originalWidth = parentContainerWidth;
|
||||
this.originalHeight = 330;
|
||||
this.width = parentContainerWidth - this.margin.left - this.margin.right;
|
||||
this.height = this.originalHeight - this.margin.top - this.margin.bottom;
|
||||
this.backOffRequestCounter = 0;
|
||||
this.configureGraph();
|
||||
this.init();
|
||||
const $prometheusContainer = $(prometheusContainer);
|
||||
const hasMetrics = $prometheusContainer.data('has-metrics');
|
||||
this.docLink = $prometheusContainer.data('doc-link');
|
||||
this.integrationLink = $prometheusContainer.data('prometheus-integration');
|
||||
|
||||
$(document).ajaxError(() => {});
|
||||
|
||||
if (hasMetrics) {
|
||||
this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
|
||||
this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
|
||||
const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
|
||||
extraAddedWidthParent;
|
||||
this.originalWidth = parentContainerWidth;
|
||||
this.originalHeight = 330;
|
||||
this.width = parentContainerWidth - this.margin.left - this.margin.right;
|
||||
this.height = this.originalHeight - this.margin.top - this.margin.bottom;
|
||||
this.backOffRequestCounter = 0;
|
||||
this.configureGraph();
|
||||
this.init();
|
||||
} else {
|
||||
this.state = '.js-getting-started';
|
||||
this.updateState();
|
||||
}
|
||||
}
|
||||
|
||||
createGraph() {
|
||||
|
@ -40,8 +54,19 @@ class PrometheusGraph {
|
|||
|
||||
init() {
|
||||
this.getData().then((metricsResponse) => {
|
||||
if (Object.keys(metricsResponse).length === 0) {
|
||||
new Flash('Empty metrics', 'alert');
|
||||
let enoughData = true;
|
||||
Object.keys(metricsResponse.metrics).forEach((key) => {
|
||||
let currentKey;
|
||||
if (key === 'cpu_values' || key === 'memory_values') {
|
||||
currentKey = metricsResponse.metrics[key];
|
||||
if (Object.keys(currentKey).length === 0) {
|
||||
enoughData = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!enoughData) {
|
||||
this.state = '.js-loading';
|
||||
this.updateState();
|
||||
} else {
|
||||
this.transformData(metricsResponse);
|
||||
this.createGraph();
|
||||
|
@ -345,14 +370,17 @@ class PrometheusGraph {
|
|||
}
|
||||
return resp.metrics;
|
||||
})
|
||||
.catch(() => new Flash('An error occurred while fetching metrics.', 'alert'));
|
||||
.catch(() => {
|
||||
this.state = '.js-unable-to-connect';
|
||||
this.updateState();
|
||||
});
|
||||
}
|
||||
|
||||
transformData(metricsResponse) {
|
||||
Object.keys(metricsResponse.metrics).forEach((key) => {
|
||||
if (key === 'cpu_values' || key === 'memory_values') {
|
||||
const metricValues = (metricsResponse.metrics[key])[0];
|
||||
if (typeof metricValues !== 'undefined') {
|
||||
if (metricValues !== undefined) {
|
||||
this.graphSpecificProperties[key].data = metricValues.values.map(metric => ({
|
||||
time: new Date(metric[0] * 1000),
|
||||
value: metric[1],
|
||||
|
@ -361,6 +389,13 @@ class PrometheusGraph {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateState() {
|
||||
const $statesContainer = $(prometheusStatesContainer);
|
||||
$(prometheusParentGraphContainer).hide();
|
||||
$(`${this.state}`, $statesContainer).removeClass('hidden');
|
||||
$(prometheusStatesContainer).show();
|
||||
}
|
||||
}
|
||||
|
||||
export default PrometheusGraph;
|
||||
|
|
|
@ -233,6 +233,15 @@
|
|||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.prometheus-state {
|
||||
margin-top: 10px;
|
||||
display: none;
|
||||
|
||||
.state-button-section {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.environments-actions {
|
||||
.external-url,
|
||||
.monitoring-url,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- environment = local_assigns.fetch(:environment)
|
||||
|
||||
- return unless environment.has_metrics? && can?(current_user, :read_environment, environment)
|
||||
- return unless can?(current_user, :read_environment, environment)
|
||||
|
||||
= link_to environment_metrics_path(environment), title: 'See metrics', class: 'btn metrics-button' do
|
||||
= icon('area-chart')
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
= page_specific_javascript_bundle_tag('monitoring')
|
||||
= render "projects/pipelines/head"
|
||||
|
||||
%div{ class: container_class }
|
||||
.prometheus-container{ class: container_class, 'data-has-metrics': "#{@environment.has_metrics?}" }
|
||||
.top-area
|
||||
.row
|
||||
.col-sm-6
|
||||
|
@ -16,13 +16,68 @@
|
|||
.col-sm-6
|
||||
.nav-controls
|
||||
= render 'projects/deployments/actions', deployment: @environment.last_deployment
|
||||
.row
|
||||
.col-sm-12
|
||||
%h4
|
||||
CPU utilization
|
||||
%svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
|
||||
.row
|
||||
.col-sm-12
|
||||
%h4
|
||||
Memory usage
|
||||
%svg.prometheus-graph{ 'graph-type' => 'memory_values' }
|
||||
.prometheus-state
|
||||
.js-getting-started.hidden
|
||||
.row
|
||||
.col-md-4.col-md-offset-4.state-svg
|
||||
= render "shared/empty_states/monitoring/getting_started.svg"
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
%h4.text-center.state-title
|
||||
Get started with performance monitoring
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
.description-text.text-center.state-description
|
||||
Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.
|
||||
= link_to help_page_path('administration/monitoring/prometheus/index.md') do
|
||||
Learn more about performance monitoring
|
||||
.row.state-button-section
|
||||
.col-md-4.col-md-offset-4.text-center.state-button
|
||||
= link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus'), class: 'btn btn-success' do
|
||||
Configure Prometheus
|
||||
.js-loading.hidden
|
||||
.row
|
||||
.col-md-4.col-md-offset-4.state-svg
|
||||
= render "shared/empty_states/monitoring/loading.svg"
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
%h4.text-center.state-title
|
||||
Waiting for performance data
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
.description-text.text-center.state-description
|
||||
Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.
|
||||
.row.state-button-section
|
||||
.col-md-4.col-md-offset-4.text-center.state-button
|
||||
= link_to help_page_path('administration/monitoring/prometheus/index.md'), class: 'btn btn-success' do
|
||||
View documentation
|
||||
.js-unable-to-connect.hidden
|
||||
.row
|
||||
.col-md-4.col-md-offset-4.state-svg
|
||||
= render "shared/empty_states/monitoring/unable_to_connect.svg"
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
%h4.text-center.state-title
|
||||
Unable to connect to Prometheus server
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
.description-text.text-center.state-description
|
||||
Ensure connectivity is available from the GitLab server to the
|
||||
= link_to edit_namespace_project_service_path(@project.namespace, @project, 'prometheus') do
|
||||
Prometheus server
|
||||
.row.state-button-section
|
||||
.col-md-4.col-md-offset-4.text-center.state-button
|
||||
= link_to help_page_path('administration/monitoring/prometheus/index.md'), class:'btn btn-success' do
|
||||
View documentation
|
||||
|
||||
.prometheus-graphs
|
||||
.row
|
||||
.col-sm-12
|
||||
%h4
|
||||
CPU utilization
|
||||
%svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
|
||||
.row
|
||||
.col-sm-12
|
||||
%h4
|
||||
Memory usage
|
||||
%svg.prometheus-graph{ 'graph-type' => 'memory_values' }
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406 305" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="0" width="159.8" height="127.81" x=".196" y="5" rx="10"/><rect id="2" width="160" height="128" x=".666" y=".41" rx="10"/><rect id="4" width="160.19" height="128.19" x=".339" y=".59" rx="10"/><mask id="1" width="159.8" height="127.81" x="0" y="0" fill="#fff"><use xlink:href="#0"/></mask><mask id="3" width="160" height="128" x="0" y="0" fill="#fff"><use xlink:href="#2"/></mask><mask id="5" width="160.19" height="128.19" x="0" y="0" fill="#fff"><use xlink:href="#4"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(12 3)"><rect width="160" height="128" x="122.08" y="146.08" fill="#f9f9f9" transform="matrix(.99619.08716-.08716.99619 19.08-16.813)" rx="10"/><g transform="matrix(.96593.25882-.25882.96593 227.1 57.47)"><rect width="159.8" height="127.81" x="1.64" y="10.06" fill="#f9f9f9" rx="8"/><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#1)" xlink:href="#0"/><g transform="translate(24.368 36.951)"><path fill="#d2caea" fill-rule="nonzero" d="m71.785 44.2c.761.296 1.625.099 2.184-.496l35.956-38.34c.756-.806.715-2.071-.091-2.827-.806-.756-2.071-.715-2.827.091l-35.03 37.36-41.888-16.285c-.749-.291-1.6-.106-2.16.471l-26.368 27.16c-.769.793-.751 2.059.042 2.828.793.769 2.059.751 2.828-.042l25.444-26.21 41.911 16.294"/><g fill="#fff"><circle cx="5.716" cy="5.104" r="5" stroke="#6b4fbb" stroke-width="4" transform="translate(65.917 34.945)"/><g stroke="#fb722e"><ellipse cx="4.632" cy="50.05" stroke-width="3.2" rx="4" ry="3.999"/><g stroke-width="4"><ellipse cx="29.632" cy="27.05" rx="4" ry="3.999"/><ellipse cx="107.63" cy="4.048" rx="4" ry="3.999"/></g></g></g></g></g><rect width="160.19" height="128.19" x="36.28" y="86.74" fill="#f9f9f9" transform="matrix(.99619-.08716.08716.99619-12.703 10.717)" rx="10"/><g transform="matrix(.99619.08716-.08716.99619 126.61 137.8)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#3)" xlink:href="#2"/><path fill="#6b4fbb" stroke="#6b4fbb" stroke-width="3.2" d="m84.67 28.41c18.225 0 33 15.07 33 33.651h-33v-33.651" stroke-linecap="round" stroke-linejoin="round"/><path fill="#d2caea" fill-rule="nonzero" d="m78.67 66.41h30c1.105 0 2 .895 2 2 0 18.778-15.222 34-34 34-18.778 0-34-15.222-34-34 0-18.778 15.222-34 34-34 1.105 0 2 .895 2 2v30m-32 2c0 16.569 13.431 30 30 30 15.896 0 28.905-12.364 29.934-28h-29.934c-1.105 0-2-.895-2-2v-29.934c-15.636 1.029-28 14.04-28 29.934"/></g><g transform="matrix(.99619-.08716.08716.99619 30 88.03)"><use fill="#fff" stroke="#eee" stroke-width="8" mask="url(#5)" xlink:href="#4"/><g transform="translate(42 34)"><path fill="#fef0ea" d="m0 13.391c0-.768.628-1.391 1.4-1.391h9.2c.773 0 1.4.626 1.4 1.391v49.609h-12v-49.609"/><path fill="#fb722e" d="m66 21.406c0-.777.628-1.406 1.4-1.406h9.2c.773 0 1.4.624 1.4 1.406v41.594h-12v-41.594"/><path fill="#6b4fbb" d="m22 1.404c0-.776.628-1.404 1.4-1.404h9.2c.773 0 1.4.624 1.4 1.404v61.6h-12v-61.6"/><path fill="#d2caea" d="m44 39.4c0-.772.628-1.398 1.4-1.398h9.2c.773 0 1.4.618 1.4 1.398v23.602h-12v-23.602"/></g></g><g fill="#fee8dc"><path d="m6.226 94.95l-2.84.631c-1.075.239-1.752-.445-1.515-1.515l.631-2.84-.631-2.84c-.239-1.075.445-1.752 1.515-1.515l2.84.631 2.84-.631c1.075-.239 1.752.445 1.515 1.515l-.631 2.84.631 2.84c.239 1.075-.445 1.752-1.515 1.515l-2.84-.631" transform="matrix(.70711.70711-.70711.70711 66.33 22.317)"/><path d="m312.78 53.43l-3.634.807c-1.296.288-2.115-.52-1.825-1.825l.807-3.634-.807-3.634c-.288-1.296.52-2.115 1.825-1.825l3.634.807 3.634-.807c1.296-.288 2.115.52 1.825 1.825l-.807 3.634.807 3.634c.288 1.296-.52 2.115-1.825 1.825l-3.634-.807" transform="matrix(.70711.70711-.70711.70711 126.1-206.88)"/></g><path fill="#e1dcf1" d="m124.78 12.43l-3.617.804c-1.306.29-2.129-.53-1.839-1.839l.804-3.617-.804-3.617c-.29-1.306.53-2.129 1.839-1.839l3.617.804 3.617-.804c1.306-.29 2.129.53 1.839 1.839l-.804 3.617.804 3.617c.29 1.306-.53 2.129-1.839 1.839l-3.617-.804" transform="matrix(.70711-.70711.70711.70711 31.05 90.51)"/><path fill="#d2caea" d="m374.78 244.43l-3.617.804c-1.306.29-2.129-.53-1.839-1.839l.804-3.617-.804-3.617c-.29-1.306.53-2.129 1.839-1.839l3.617.804 3.617-.804c1.306-.29 2.129.53 1.839 1.839l-.804 3.617.804 3.617c.29 1.306-.53 2.129-1.839 1.839l-3.617-.804" transform="matrix(.70711-.70711.70711.70711-59.779 335.24)"/></g></svg>
|
After Width: | Height: | Size: 4.3 KiB |
1
app/views/shared/empty_states/monitoring/_loading.svg
Normal file
1
app/views/shared/empty_states/monitoring/_loading.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.1 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.6 KiB |
4
changelogs/unreleased/add-error-empty-states.yml
Normal file
4
changelogs/unreleased/add-error-empty-states.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Introduced error/empty states for the environments performance metrics
|
||||
merge_request: 10271
|
||||
author:
|
|
@ -1,12 +1,62 @@
|
|||
%div
|
||||
.prometheus-container{ 'data-has-metrics': "false", 'data-doc-link': '/help/administration/monitoring/prometheus/index.md', 'data-prometheus-integration': '/root/hello-prometheus/services/prometheus/edit' }
|
||||
.top-area
|
||||
.row
|
||||
.col-sm-6
|
||||
%h3.page-title
|
||||
Metrics for environment
|
||||
.row
|
||||
.col-sm-12
|
||||
%svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
|
||||
.row
|
||||
.col-sm-12
|
||||
%svg.prometheus-graph{ 'graph-type' => 'memory_values' }
|
||||
.prometheus-state
|
||||
.js-getting-started.hidden
|
||||
.row
|
||||
.col-md-4.col-md-offset-4.state-svg
|
||||
%svg
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
%h4.text-center.state-title
|
||||
Get started with performance monitoring
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
.description-text.text-center.state-description
|
||||
Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments. Learn more about performance monitoring
|
||||
.row.state-button-section
|
||||
.col-md-4.col-md-offset-4.text-center.state-button
|
||||
%a.btn.btn-success
|
||||
Configure Prometheus
|
||||
.js-loading.hidden
|
||||
.row
|
||||
.col-md-4.col-md-offset-4.state-svg
|
||||
%svg
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
%h4.text-center.state-title
|
||||
Waiting for performance data
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
.description-text.text-center.state-description
|
||||
Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.
|
||||
.row.state-button-section
|
||||
.col-md-4.col-md-offset-4.text-center.state-button
|
||||
%a.btn.btn-success
|
||||
View documentation
|
||||
.js-unable-to-connect.hidden
|
||||
.row
|
||||
.col-md-4.col-md-offset-4.state-svg
|
||||
%svg
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
%h4.text-center.state-title
|
||||
Unable to connect to Prometheus server
|
||||
.row
|
||||
.col-md-6.col-md-offset-3
|
||||
.description-text.text-center.state-description
|
||||
Ensure connectivity is available from the GitLab server to the Prometheus server
|
||||
.row.state-button-section
|
||||
.col-md-4.col-md-offset-4.text-center.state-button
|
||||
%a.btn.btn-success
|
||||
View documentation
|
||||
.prometheus-graphs
|
||||
.row
|
||||
.col-sm-12
|
||||
%svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
|
||||
.row
|
||||
.col-sm-12
|
||||
%svg.prometheus-graph{ 'graph-type' => 'memory_values' }
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'jquery';
|
||||
import '~/lib/utils/common_utils';
|
||||
import PrometheusGraph from '~/monitoring/prometheus_graph';
|
||||
import { prometheusMockData } from './prometheus_mock_data';
|
||||
|
||||
|
@ -12,6 +11,7 @@ describe('PrometheusGraph', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
loadFixtures(fixtureName);
|
||||
$('.prometheus-container').data('has-metrics', 'true');
|
||||
this.prometheusGraph = new PrometheusGraph();
|
||||
const self = this;
|
||||
const fakeInit = (metricsResponse) => {
|
||||
|
@ -75,3 +75,24 @@ describe('PrometheusGraph', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PrometheusGraphs UX states', () => {
|
||||
const fixtureName = 'static/environments/metrics.html.raw';
|
||||
preloadFixtures(fixtureName);
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures(fixtureName);
|
||||
this.prometheusGraph = new PrometheusGraph();
|
||||
});
|
||||
|
||||
it('shows a specified state', () => {
|
||||
this.prometheusGraph.state = '.js-getting-started';
|
||||
this.prometheusGraph.updateState();
|
||||
const $state = $('.js-getting-started');
|
||||
expect($state).toBeDefined();
|
||||
expect($('.state-title', $state)).toBeDefined();
|
||||
expect($('.state-svg', $state)).toBeDefined();
|
||||
expect($('.state-description', $state)).toBeDefined();
|
||||
expect($('.state-button', $state)).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue