Commit a43b7d4a authored by Phil Hughes's avatar Phil Hughes

Merge branch '45738-add-environment-drop-down-to-metrics-dashboard' into 'master'

Add `environment` drop down to Metrics dashboard

Closes #45738

See merge request gitlab-org/gitlab-ce!19833
parents 5c847f1a 786edcb4
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash'; import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service'; import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
...@@ -13,6 +15,7 @@ export default { ...@@ -13,6 +15,7 @@ export default {
Graph, Graph,
GraphGroup, GraphGroup,
EmptyState, EmptyState,
Icon,
}, },
props: { props: {
hasMetrics: { hasMetrics: {
...@@ -80,6 +83,14 @@ export default { ...@@ -80,6 +83,14 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
environmentsEndpoint: {
type: String,
required: true,
},
currentEnvironmentName: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -96,6 +107,7 @@ export default { ...@@ -96,6 +107,7 @@ export default {
this.service = new MonitoringService({ this.service = new MonitoringService({
metricsEndpoint: this.metricsEndpoint, metricsEndpoint: this.metricsEndpoint,
deploymentEndpoint: this.deploymentEndpoint, deploymentEndpoint: this.deploymentEndpoint,
environmentsEndpoint: this.environmentsEndpoint,
}); });
eventHub.$on('toggleAspectRatio', this.toggleAspectRatio); eventHub.$on('toggleAspectRatio', this.toggleAspectRatio);
eventHub.$on('hoverChanged', this.hoverChanged); eventHub.$on('hoverChanged', this.hoverChanged);
...@@ -122,7 +134,11 @@ export default { ...@@ -122,7 +134,11 @@ export default {
this.service this.service
.getDeploymentData() .getDeploymentData()
.then(data => this.store.storeDeploymentData(data)) .then(data => this.store.storeDeploymentData(data))
.catch(() => new Flash('Error getting deployment information.')), .catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
this.service
.getEnvironmentsData()
.then((data) => this.store.storeEnvironmentsData(data))
.catch(() => Flash(s__('Metrics|There was an error getting environments information.'))),
]) ])
.then(() => { .then(() => {
if (this.store.groups.length < 1) { if (this.store.groups.length < 1) {
...@@ -155,8 +171,41 @@ export default { ...@@ -155,8 +171,41 @@ export default {
<template> <template>
<div <div
v-if="!showEmptyState" v-if="!showEmptyState"
class="prometheus-graphs" class="prometheus-graphs prepend-top-10"
> >
<div class="environments d-flex align-items-center">
{{ s__('Metrics|Environment') }}
<div class="dropdown prepend-left-10">
<button
class="dropdown-menu-toggle"
data-toggle="dropdown"
type="button"
>
<span>
{{ currentEnvironmentName }}
</span>
<icon
name="chevron-down"
/>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
<li
v-for="environment in store.environmentsData"
:key="environment.latest.id"
>
<a
:href="environment.latest.metrics_path"
:class="{ 'is-active': environment.latest.name == currentEnvironmentName }"
class="dropdown-item"
>
{{ environment.latest.name }}
</a>
</li>
</ul>
</div>
</div>
</div>
<graph-group <graph-group
v-for="(groupData, index) in store.groups" v-for="(groupData, index) in store.groups"
:key="index" :key="index"
......
import axios from '../../lib/utils/axios_utils'; import axios from '../../lib/utils/axios_utils';
import statusCodes from '../../lib/utils/http_status'; import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils'; import { backOff } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
const MAX_REQUESTS = 3; const MAX_REQUESTS = 3;
...@@ -23,9 +24,10 @@ function backOffRequest(makeRequestCallback) { ...@@ -23,9 +24,10 @@ function backOffRequest(makeRequestCallback) {
} }
export default class MonitoringService { export default class MonitoringService {
constructor({ metricsEndpoint, deploymentEndpoint }) { constructor({ metricsEndpoint, deploymentEndpoint, environmentsEndpoint }) {
this.metricsEndpoint = metricsEndpoint; this.metricsEndpoint = metricsEndpoint;
this.deploymentEndpoint = deploymentEndpoint; this.deploymentEndpoint = deploymentEndpoint;
this.environmentsEndpoint = environmentsEndpoint;
} }
getGraphsData() { getGraphsData() {
...@@ -33,7 +35,7 @@ export default class MonitoringService { ...@@ -33,7 +35,7 @@ export default class MonitoringService {
.then(resp => resp.data) .then(resp => resp.data)
.then((response) => { .then((response) => {
if (!response || !response.data) { if (!response || !response.data) {
throw new Error('Unexpected metrics data response from prometheus endpoint'); throw new Error(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
} }
return response.data; return response.data;
}); });
...@@ -47,9 +49,20 @@ export default class MonitoringService { ...@@ -47,9 +49,20 @@ export default class MonitoringService {
.then(resp => resp.data) .then(resp => resp.data)
.then((response) => { .then((response) => {
if (!response || !response.deployments) { if (!response || !response.deployments) {
throw new Error('Unexpected deployment data response from prometheus endpoint'); throw new Error(s__('Metrics|Unexpected deployment data response from prometheus endpoint'));
} }
return response.deployments; return response.deployments;
}); });
} }
getEnvironmentsData() {
return axios.get(this.environmentsEndpoint)
.then(resp => resp.data)
.then((response) => {
if (!response || !response.environments) {
throw new Error(s__('Metrics|There was an error fetching the environments data, please try again'));
}
return response.environments;
});
}
} }
...@@ -24,6 +24,7 @@ export default class MonitoringStore { ...@@ -24,6 +24,7 @@ export default class MonitoringStore {
constructor() { constructor() {
this.groups = []; this.groups = [];
this.deploymentData = []; this.deploymentData = [];
this.environmentsData = [];
} }
storeMetrics(groups = []) { storeMetrics(groups = []) {
...@@ -37,6 +38,10 @@ export default class MonitoringStore { ...@@ -37,6 +38,10 @@ export default class MonitoringStore {
this.deploymentData = deploymentData; this.deploymentData = deploymentData;
} }
storeEnvironmentsData(environmentsData = []) {
this.environmentsData = environmentsData;
}
getMetricsCount() { getMetricsCount() {
return this.groups.reduce((count, group) => count + group.metrics.length, 0); return this.groups.reduce((count, group) => count + group.metrics.length, 0);
} }
......
...@@ -222,6 +222,23 @@ ...@@ -222,6 +222,23 @@
} }
} }
.prometheus-graphs {
.environments {
.dropdown-menu-toggle {
svg {
position: absolute;
right: 5%;
top: 25%;
}
}
.dropdown-menu-toggle,
.dropdown-menu {
width: 240px;
}
}
}
.environments-actions { .environments-actions {
.external-url, .external-url,
.monitoring-url, .monitoring-url,
......
...@@ -2,15 +2,9 @@ ...@@ -2,15 +2,9 @@
- page_title "Metrics for environment", @environment.name - page_title "Metrics for environment", @environment.name
.prometheus-container{ class: container_class } .prometheus-container{ class: container_class }
.top-area
.row
.col-sm-6
%h3
Environment:
= link_to @environment.name, environment_path(@environment)
#prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'), #prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"clusters-path": project_clusters_path(@project), "clusters-path": project_clusters_path(@project),
"current-environment-name": @environment.name,
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'), "documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
"empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'), "empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
"empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'), "empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
...@@ -18,6 +12,7 @@ ...@@ -18,6 +12,7 @@
"empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect.svg'), "empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect.svg'),
"metrics-endpoint": additional_metrics_project_environment_path(@project, @environment, format: :json), "metrics-endpoint": additional_metrics_project_environment_path(@project, @environment, format: :json),
"deployment-endpoint": project_environment_deployments_path(@project, @environment, format: :json), "deployment-endpoint": project_environment_deployments_path(@project, @environment, format: :json),
"environments-endpoint": project_environments_path(@project, format: :json),
"project-path": project_path(@project), "project-path": project_path(@project),
"tags-path": project_tags_path(@project), "tags-path": project_tags_path(@project),
"has-metrics": "#{@environment.has_metrics?}" } } "has-metrics": "#{@environment.has_metrics?}" } }
---
title: Add environment dropdown for the metrics page
merge_request: 19833
author:
type: changed
...@@ -2,7 +2,7 @@ import Vue from 'vue'; ...@@ -2,7 +2,7 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue'; import Dashboard from '~/monitoring/components/dashboard.vue';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { metricsGroupsAPIResponse, mockApiEndpoint } from './mock_data'; import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data';
describe('Dashboard', () => { describe('Dashboard', () => {
let DashboardComponent; let DashboardComponent;
...@@ -20,6 +20,8 @@ describe('Dashboard', () => { ...@@ -20,6 +20,8 @@ describe('Dashboard', () => {
emptyLoadingSvgPath: '/path/to/loading.svg', emptyLoadingSvgPath: '/path/to/loading.svg',
emptyNoDataSvgPath: '/path/to/no-data.svg', emptyNoDataSvgPath: '/path/to/no-data.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
environmentsEndpoint: '/root/hello-prometheus/environments/35',
currentEnvironmentName: 'production',
}; };
beforeEach(() => { beforeEach(() => {
...@@ -50,7 +52,7 @@ describe('Dashboard', () => { ...@@ -50,7 +52,7 @@ describe('Dashboard', () => {
mock.restore(); mock.restore();
}); });
it('shows up a loading state', (done) => { it('shows up a loading state', done => {
const component = new DashboardComponent({ const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true }, propsData: { ...propsData, hasMetrics: true },
...@@ -62,7 +64,7 @@ describe('Dashboard', () => { ...@@ -62,7 +64,7 @@ describe('Dashboard', () => {
}); });
}); });
it('hides the legend when showLegend is false', (done) => { it('hides the legend when showLegend is false', done => {
const component = new DashboardComponent({ const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showLegend: false }, propsData: { ...propsData, hasMetrics: true, showLegend: false },
...@@ -76,7 +78,7 @@ describe('Dashboard', () => { ...@@ -76,7 +78,7 @@ describe('Dashboard', () => {
}); });
}); });
it('hides the group panels when showPanels is false', (done) => { it('hides the group panels when showPanels is false', done => {
const component = new DashboardComponent({ const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false }, propsData: { ...propsData, hasMetrics: true, showPanels: false },
...@@ -89,5 +91,40 @@ describe('Dashboard', () => { ...@@ -89,5 +91,40 @@ describe('Dashboard', () => {
done(); done();
}); });
}); });
it('renders the dropdown with a number of environments', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
});
component.store.storeEnvironmentsData(environmentData);
setTimeout(() => {
const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul li a');
expect(dropdownMenuEnvironments.length).toEqual(component.store.environmentsData.length);
done();
});
});
it('renders the dropdown with a single is-active element', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
});
component.store.storeEnvironmentsData(environmentData);
setTimeout(() => {
const dropdownIsActiveElement = component.$el.querySelectorAll(
'.dropdown-menu ul li a.is-active',
);
expect(dropdownIsActiveElement.length).toEqual(1);
expect(dropdownIsActiveElement[0].textContent.trim()).toEqual(
component.currentEnvironmentName,
);
done();
});
});
}); });
}); });
...@@ -6542,3 +6542,44 @@ export function convertDatesMultipleSeries(multipleSeries) { ...@@ -6542,3 +6542,44 @@ export function convertDatesMultipleSeries(multipleSeries) {
}); });
return convertedMultiple; return convertedMultiple;
} }
export const environmentData = [
{
name: 'production',
size: 1,
latest: {
id: 34,
name: 'production',
state: 'available',
external_url: 'http://root-autodevops-deploy.my-fake-domain.com',
environment_type: null,
stop_action: false,
metrics_path: '/root/hello-prometheus/environments/34/metrics',
environment_path: '/root/hello-prometheus/environments/34',
stop_path: '/root/hello-prometheus/environments/34/stop',
terminal_path: '/root/hello-prometheus/environments/34/terminal',
folder_path: '/root/hello-prometheus/environments/folders/production',
created_at: '2018-06-29T16:53:38.301Z',
updated_at: '2018-06-29T16:57:09.825Z',
},
},
{
name: 'review',
size: 1,
latest: {
id: 35,
name: 'review/noop-branch',
state: 'available',
external_url: 'http://root-autodevops-deploy-review-noop-branc-die93w.my-fake-domain.com',
environment_type: 'review',
stop_action: true,
metrics_path: '/root/hello-prometheus/environments/35/metrics',
environment_path: '/root/hello-prometheus/environments/35',
stop_path: '/root/hello-prometheus/environments/35/stop',
terminal_path: '/root/hello-prometheus/environments/35/terminal',
folder_path: '/root/hello-prometheus/environments/folders/review',
created_at: '2018-07-03T18:39:41.702Z',
updated_at: '2018-07-03T18:44:54.010Z',
},
},
];
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment