Commit 2d00d6d4 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'cluster-monitoring-changes-ce-backport' into 'master'

Cluster monitoring changes CE backport

See merge request gitlab-org/gitlab-ce!17547
parents d12f60a5 226d43b3
...@@ -117,7 +117,10 @@ ...@@ -117,7 +117,10 @@
</script> </script>
<template> <template>
<section class="settings no-animate expanded"> <section
id="cluster-applications"
class="settings no-animate expanded"
>
<div class="settings-header"> <div class="settings-header">
<h4> <h4>
{{ s__('ClusterIntegration|Applications') }} {{ s__('ClusterIntegration|Applications') }}
......
...@@ -7,34 +7,82 @@ ...@@ -7,34 +7,82 @@
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import MonitoringStore from '../stores/monitoring_store'; import MonitoringStore from '../stores/monitoring_store';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
export default { export default {
components: { components: {
Graph, Graph,
GraphGroup, GraphGroup,
EmptyState, EmptyState,
}, },
data() { props: {
const metricsData = document.querySelector('#prometheus-graphs').dataset; hasMetrics: {
const store = new MonitoringStore(); type: Boolean,
required: false,
default: true,
},
showLegend: {
type: Boolean,
required: false,
default: true,
},
showPanels: {
type: Boolean,
required: false,
default: true,
},
forceSmallGraph: {
type: Boolean,
required: false,
default: false,
},
documentationPath: {
type: String,
required: true,
},
settingsPath: {
type: String,
required: true,
},
clustersPath: {
type: String,
required: true,
},
tagsPath: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
metricsEndpoint: {
type: String,
required: true,
},
deploymentEndpoint: {
type: String,
required: false,
default: null,
},
emptyGettingStartedSvgPath: {
type: String,
required: true,
},
emptyLoadingSvgPath: {
type: String,
required: true,
},
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
},
data() {
return { return {
store, store: new MonitoringStore(),
state: 'gettingStarted', state: 'gettingStarted',
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
clustersPath: metricsData.clustersPath,
tagsPath: metricsData.tagsPath,
projectPath: metricsData.projectPath,
metricsEndpoint: metricsData.additionalMetrics,
deploymentEndpoint: metricsData.deploymentEndpoint,
emptyGettingStartedSvgPath: metricsData.emptyGettingStartedSvgPath,
emptyLoadingSvgPath: metricsData.emptyLoadingSvgPath,
emptyUnableToConnectSvgPath: metricsData.emptyUnableToConnectSvgPath,
showEmptyState: true, showEmptyState: true,
updateAspectRatio: false, updateAspectRatio: false,
updatedAspectRatios: 0, updatedAspectRatios: 0,
...@@ -67,6 +115,7 @@ ...@@ -67,6 +115,7 @@
window.addEventListener('resize', this.resizeThrottled, false); window.addEventListener('resize', this.resizeThrottled, false);
} }
}, },
methods: { methods: {
getGraphsData() { getGraphsData() {
this.state = 'loading'; this.state = 'loading';
...@@ -115,6 +164,7 @@ ...@@ -115,6 +164,7 @@
v-for="(groupData, index) in store.groups" v-for="(groupData, index) in store.groups"
:key="index" :key="index"
:name="groupData.group" :name="groupData.group"
:show-panels="showPanels"
> >
<graph <graph
v-for="(graphData, index) in groupData.metrics" v-for="(graphData, index) in groupData.metrics"
...@@ -125,6 +175,8 @@ ...@@ -125,6 +175,8 @@
:deployment-data="store.deploymentData" :deployment-data="store.deploymentData"
:project-path="projectPath" :project-path="projectPath"
:tags-path="tagsPath" :tags-path="tagsPath"
:show-legend="showLegend"
:small-graph="forceSmallGraph"
/> />
</graph-group> </graph-group>
</div> </div>
......
...@@ -52,6 +52,16 @@ ...@@ -52,6 +52,16 @@
type: String, type: String,
required: true, required: true,
}, },
showLegend: {
type: Boolean,
required: false,
default: true,
},
smallGraph: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
...@@ -130,7 +140,7 @@ ...@@ -130,7 +140,7 @@
const breakpointSize = bp.getBreakpointSize(); const breakpointSize = bp.getBreakpointSize();
const query = this.graphData.queries[0]; const query = this.graphData.queries[0];
this.margin = measurements.large.margin; this.margin = measurements.large.margin;
if (breakpointSize === 'xs' || breakpointSize === 'sm') { if (this.smallGraph || breakpointSize === 'xs' || breakpointSize === 'sm') {
this.graphHeight = 300; this.graphHeight = 300;
this.margin = measurements.small.margin; this.margin = measurements.small.margin;
this.measurements = measurements.small; this.measurements = measurements.small;
...@@ -182,7 +192,9 @@ ...@@ -182,7 +192,9 @@
this.graphHeightOffset, this.graphHeightOffset,
); );
if (this.timeSeries.length > 3) { if (!this.showLegend) {
this.baseGraphHeight -= 50;
} else if (this.timeSeries.length > 3) {
this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20; this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
} }
...@@ -255,6 +267,7 @@ ...@@ -255,6 +267,7 @@
:time-series="timeSeries" :time-series="timeSeries"
:unit-of-display="unitOfDisplay" :unit-of-display="unitOfDisplay"
:current-data-index="currentDataIndex" :current-data-index="currentDataIndex"
:show-legend-group="showLegend"
/> />
<svg <svg
class="graph-data" class="graph-data"
......
...@@ -39,6 +39,11 @@ ...@@ -39,6 +39,11 @@
type: Number, type: Number,
required: true, required: true,
}, },
showLegendGroup: {
type: Boolean,
required: false,
default: true,
},
}, },
data() { data() {
return { return {
...@@ -57,8 +62,9 @@ ...@@ -57,8 +62,9 @@
}, },
rectTransform() { rectTransform() {
const yCoordinate = ((this.graphHeight - this.margin.top) / 2) const yCoordinate = (((this.graphHeight - this.margin.top)
+ (this.yLabelWidth / 2) + 10 || 0; + this.measurements.axisLabelLineOffset) / 2)
+ (this.yLabelWidth / 2) || 0;
return `translate(0, ${yCoordinate}) rotate(-90)`; return `translate(0, ${yCoordinate}) rotate(-90)`;
}, },
...@@ -166,39 +172,41 @@ ...@@ -166,39 +172,41 @@
> >
Time Time
</text> </text>
<g <template v-if="showLegendGroup">
class="legend-group" <g
v-for="(series, index) in timeSeries" class="legend-group"
:key="index" v-for="(series, index) in timeSeries"
:transform="translateLegendGroup(index)" :key="index"
> :transform="translateLegendGroup(index)"
<line
:stroke="series.lineColor"
:stroke-width="measurements.legends.height"
:stroke-dasharray="strokeDashArray(series.lineStyle)"
:x1="measurements.legends.offsetX"
:x2="measurements.legends.offsetX + measurements.legends.width"
:y1="graphHeight - measurements.legends.offsetY"
:y2="graphHeight - measurements.legends.offsetY"
/>
<text
v-if="timeSeries.length > 1"
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30"
>
{{ createSeriesString(index, series) }}
</text>
<text
v-else
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30"
> >
{{ legendTitle }} {{ formatMetricUsage(series) }} <line
</text> :stroke="series.lineColor"
</g> :stroke-width="measurements.legends.height"
:stroke-dasharray="strokeDashArray(series.lineStyle)"
:x1="measurements.legends.offsetX"
:x2="measurements.legends.offsetX + measurements.legends.width"
:y1="graphHeight - measurements.legends.offsetY"
:y2="graphHeight - measurements.legends.offsetY"
/>
<text
v-if="timeSeries.length > 1"
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30"
>
{{ createSeriesString(index, series) }}
</text>
<text
v-else
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30"
>
{{ legendTitle }} {{ formatMetricUsage(series) }}
</text>
</g>
</template>
</g> </g>
</template> </template>
...@@ -5,12 +5,20 @@ ...@@ -5,12 +5,20 @@
type: String, type: String,
required: true, required: true,
}, },
showPanels: {
type: Boolean,
required: false,
default: true,
},
}, },
}; };
</script> </script>
<template> <template>
<div class="panel panel-default prometheus-panel"> <div
v-if="showPanels"
class="panel panel-default prometheus-panel"
>
<div class="panel-heading"> <div class="panel-heading">
<h4>{{ name }}</h4> <h4>{{ name }}</h4>
</div> </div>
...@@ -18,4 +26,10 @@ ...@@ -18,4 +26,10 @@
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
<div
v-else
class="prometheus-graph-group"
>
<slot></slot>
</div>
</template> </template>
import Vue from 'vue'; import Vue from 'vue';
import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
import Dashboard from './components/dashboard.vue'; import Dashboard from './components/dashboard.vue';
export default () => new Vue({ export default () => {
el: '#prometheus-graphs', const el = document.getElementById('prometheus-graphs');
render: createElement => createElement(Dashboard),
}); if (el && el.dataset) {
// eslint-disable-next-line no-new
new Vue({
el,
render(createElement) {
return createElement(Dashboard, {
props: {
...el.dataset,
hasMetrics: convertPermissionToBoolean(el.dataset.hasMetrics),
},
});
},
});
}
};
...@@ -40,6 +40,9 @@ export default class MonitoringService { ...@@ -40,6 +40,9 @@ export default class MonitoringService {
} }
getDeploymentData() { getDeploymentData() {
if (!this.deploymentEndpoint) {
return Promise.resolve([]);
}
return backOffRequest(() => axios.get(this.deploymentEndpoint)) return backOffRequest(() => axios.get(this.deploymentEndpoint))
.then(resp => resp.data) .then(resp => resp.data)
.then((response) => { .then((response) => {
......
...@@ -369,7 +369,8 @@ ...@@ -369,7 +369,8 @@
} }
> text { > text {
font-size: 12px; fill: $theme-gray-600;
font-size: 10px;
} }
} }
......
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
"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'),
"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'),
"additional-metrics": 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),
"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?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } } "has-metrics": "#{@environment.has_metrics?}" } }
require 'spec_helper'
describe Projects::EnvironmentsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'environments-project') }
let(:environment) { create(:environment, name: 'production', project: project) }
render_views
before(:all) do
clean_frontend_fixtures('environments/metrics')
end
before do
sign_in(admin)
end
it 'environments/metrics/metrics.html.raw' do |example|
get :metrics,
namespace_id: project.namespace,
project_id: project,
id: environment.id
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
...@@ -5,24 +5,35 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -5,24 +5,35 @@ import axios from '~/lib/utils/axios_utils';
import { metricsGroupsAPIResponse, mockApiEndpoint } from './mock_data'; import { metricsGroupsAPIResponse, mockApiEndpoint } from './mock_data';
describe('Dashboard', () => { describe('Dashboard', () => {
const fixtureName = 'environments/metrics/metrics.html.raw';
let DashboardComponent; let DashboardComponent;
let component;
preloadFixtures(fixtureName); const propsData = {
hasMetrics: false,
documentationPath: '/path/to/docs',
settingsPath: '/path/to/settings',
clustersPath: '/path/to/clusters',
tagsPath: '/path/to/tags',
projectPath: '/path/to/project',
metricsEndpoint: mockApiEndpoint,
deploymentEndpoint: null,
emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
emptyLoadingSvgPath: '/path/to/loading.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
};
beforeEach(() => { beforeEach(() => {
loadFixtures(fixtureName); setFixtures('<div class="prometheus-graphs"></div>');
DashboardComponent = Vue.extend(Dashboard); DashboardComponent = Vue.extend(Dashboard);
}); });
describe('no metrics are available yet', () => { describe('no metrics are available yet', () => {
it('shows a getting started empty state when no metrics are present', () => { it('shows a getting started empty state when no metrics are present', () => {
component = new DashboardComponent({ const component = new DashboardComponent({
el: document.querySelector('#prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData,
}); });
component.$mount(); expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
expect(component.$el.querySelector('#prometheus-graphs')).toBe(null);
expect(component.state).toEqual('gettingStarted'); expect(component.state).toEqual('gettingStarted');
}); });
}); });
...@@ -30,11 +41,8 @@ describe('Dashboard', () => { ...@@ -30,11 +41,8 @@ describe('Dashboard', () => {
describe('requests information to the server', () => { describe('requests information to the server', () => {
let mock; let mock;
beforeEach(() => { beforeEach(() => {
document.querySelector('#prometheus-graphs').setAttribute('data-has-metrics', 'true');
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet(mockApiEndpoint).reply(200, { mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
metricsGroupsAPIResponse,
});
}); });
afterEach(() => { afterEach(() => {
...@@ -42,14 +50,43 @@ describe('Dashboard', () => { ...@@ -42,14 +50,43 @@ describe('Dashboard', () => {
}); });
it('shows up a loading state', (done) => { it('shows up a loading state', (done) => {
component = new DashboardComponent({ const component = new DashboardComponent({
el: document.querySelector('#prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true },
}); });
component.$mount();
Vue.nextTick(() => { Vue.nextTick(() => {
expect(component.state).toEqual('loading'); expect(component.state).toEqual('loading');
done(); done();
}); });
}); });
it('hides the legend when showLegend is false', (done) => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showLegend: false },
});
setTimeout(() => {
expect(component.showEmptyState).toEqual(false);
expect(component.$el.querySelector('.legend-group')).toEqual(null);
expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
done();
});
});
it('hides the group panels when showPanels is false', (done) => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
});
setTimeout(() => {
expect(component.showEmptyState).toEqual(false);
expect(component.$el.querySelector('.prometheus-panel')).toEqual(null);
expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
done();
});
});
}); });
}); });
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