summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorkushalpandya <kushal@gitlab.com>2017-05-09 19:30:22 +0530
committerkushalpandya <kushal@gitlab.com>2017-05-09 19:30:22 +0530
commitf66006169bf94de749bfdcb9b57ca178f4bf6f68 (patch)
tree4f6bda983f92df7e940f5573b37171e6426b5fa9 /app
parent7ea12d8067357f8e846e26b05a1dc4ab3d5a5a93 (diff)
downloadgitlab-ce-f66006169bf94de749bfdcb9b57ca178f4bf6f68.tar.gz
Add Prometheus memory sparkline to MR widget
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js104
-rw-r--r--app/assets/javascripts/vue_shared/components/memory_graph.js107
-rw-r--r--app/assets/stylesheets/framework/memory_graph.scss8
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss24
-rwxr-xr-xapp/controllers/projects/merge_requests_controller.rb8
6 files changed, 179 insertions, 74 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
index 630e80a7408..3c23b8e472b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
@@ -108,8 +108,6 @@ export default {
</div>
<mr-widget-memory-usage
v-if="deployment.metrics_url"
- :mr="mr"
- :service="service"
:metricsUrl="deployment.metrics_url"
/>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
index 395cc9e91fc..486b13e60af 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
@@ -5,8 +5,6 @@ import MRWidgetService from '../services/mr_widget_service';
export default {
name: 'MemoryUsage',
props: {
- mr: { type: Object, required: true },
- service: { type: Object, required: true },
metricsUrl: { type: String, required: true },
},
data() {
@@ -14,6 +12,7 @@ export default {
// memoryFrom: 0,
// memoryTo: 0,
memoryMetrics: [],
+ deploymentTime: 0,
hasMetrics: false,
loadFailed: false,
loadingMetrics: true,
@@ -23,8 +22,22 @@ export default {
components: {
'mr-memory-graph': MemoryGraph,
},
+ computed: {
+ shouldShowLoading() {
+ return this.loadingMetrics && !this.hasMetrics && !this.loadFailed;
+ },
+ shouldShowMemoryGraph() {
+ return !this.loadingMetrics && this.hasMetrics && !this.loadFailed;
+ },
+ shouldShowLoadFailure() {
+ return !this.loadingMetrics && !this.hasMetrics && this.loadFailed;
+ },
+ shouldShowMetricsUnavailable() {
+ return !this.loadingMetrics && !this.hasMetrics && !this.loadFailed;
+ },
+ },
methods: {
- computeGraphData(metrics) {
+ computeGraphData(metrics, deploymentTime) {
this.loadingMetrics = false;
const { memory_values } = metrics;
// if (memory_previous.length > 0) {
@@ -38,70 +51,73 @@ export default {
if (memory_values.length > 0) {
this.hasMetrics = true;
this.memoryMetrics = memory_values[0].values;
+ this.deploymentTime = deploymentTime;
}
},
- },
- mounted() {
- this.$props.loadingMetrics = true;
- gl.utils.backOff((next, stop) => {
- MRWidgetService.fetchMetrics(this.$props.metricsUrl)
- .then((res) => {
- if (res.status === statusCodes.NO_CONTENT) {
- this.backOffRequestCounter = this.backOffRequestCounter += 1;
- if (this.backOffRequestCounter < 3) {
- next();
+ loadMetrics() {
+ gl.utils.backOff((next, stop) => {
+ MRWidgetService.fetchMetrics(this.metricsUrl)
+ .then((res) => {
+ if (res.status === statusCodes.NO_CONTENT) {
+ this.backOffRequestCounter = this.backOffRequestCounter += 1;
+ /* eslint-disable no-unused-expressions */
+ this.backOffRequestCounter < 3 ? next() : stop(res);
} else {
stop(res);
}
- } else {
- stop(res);
+ })
+ .catch(stop);
+ })
+ .then((res) => {
+ if (res.status === statusCodes.NO_CONTENT) {
+ return res;
}
- })
- .catch(stop);
- })
- .then((res) => {
- if (res.status === statusCodes.NO_CONTENT) {
- return res;
- }
- return res.json();
- })
- .then((res) => {
- this.computeGraphData(res.metrics);
- return res;
- })
- .catch(() => {
- this.$props.loadFailed = true;
- });
+ return res.json();
+ })
+ .then((res) => {
+ this.computeGraphData(res.metrics, res.deployment_time);
+ return res;
+ })
+ .catch(() => {
+ this.loadFailed = true;
+ this.loadingMetrics = false;
+ });
+ },
+ },
+ mounted() {
+ this.loadingMetrics = true;
+ this.loadMetrics();
},
template: `
- <div class="mr-info-list mr-memory-usage">
+ <div class="mr-info-list clearfix mr-memory-usage js-mr-memory-usage">
<div class="legend"></div>
<p
- v-if="loadingMetrics"
- class="usage-info usage-info-loading">
+ v-if="shouldShowLoading"
+ class="usage-info js-usage-info usage-info-loading">
<i
class="fa fa-spinner fa-spin usage-info-load-spinner"
aria-hidden="true" />Loading deployment statistics.
</p>
<p
- v-if="!hasMetrics && !loadingMetrics"
- class="usage-info usage-info-loading">
- Deployment statistics are not available currently.
- </p>
- <p
- v-if="hasMetrics"
- class="usage-info">
+ v-if="shouldShowMemoryGraph"
+ class="usage-info js-usage-info">
Deployment memory usage:
</p>
<p
- v-if="loadFailed"
- class="usage-info">
+ v-if="shouldShowLoadFailure"
+ class="usage-info js-usage-info usage-info-failed">
Failed to load deployment statistics.
</p>
+ <p
+ v-if="shouldShowMetricsUnavailable"
+ class="usage-info js-usage-info usage-info-unavailable">
+ Deployment statistics are not available currently.
+ </p>
<mr-memory-graph
- v-if="hasMetrics"
+ v-if="shouldShowMemoryGraph"
:metrics="memoryMetrics"
+ :deploymentTime="deploymentTime"
height="25"
width="100" />
</div>
diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.js b/app/assets/javascripts/vue_shared/components/memory_graph.js
index 2a605b24339..643b77e04c7 100644
--- a/app/assets/javascripts/vue_shared/components/memory_graph.js
+++ b/app/assets/javascripts/vue_shared/components/memory_graph.js
@@ -2,6 +2,7 @@ export default {
name: 'MemoryGraph',
props: {
metrics: { type: Array, required: true },
+ deploymentTime: { type: Number, required: true },
width: { type: String, required: true },
height: { type: String, required: true },
},
@@ -9,27 +10,105 @@ export default {
return {
pathD: '',
pathViewBox: '',
- // dotX: '',
- // dotY: '',
+ dotX: '',
+ dotY: '',
};
},
+ computed: {
+ getFormattedMedian() {
+ const deployedSince = gl.utils.getTimeago().format(this.deploymentTime * 1000);
+ return `Deployed ${deployedSince}`;
+ },
+ },
+ methods: {
+ /**
+ * Returns metric value index in metrics array
+ * with timestamp closest to matching median
+ */
+ getMedianMetricIndex(median, metrics) {
+ let matchIndex = 0;
+ let timestampDiff = 0;
+ let smallestDiff = 0;
+
+ const metricTimestamps = metrics.map(v => v[0]);
+
+ // Find metric timestamp which is closest to deploymentTime
+ timestampDiff = Math.abs(metricTimestamps[0] - median);
+ metricTimestamps.forEach((timestamp, index) => {
+ if (index === 0) { // Skip first element
+ return;
+ }
+
+ smallestDiff = Math.abs(timestamp - median);
+ if (smallestDiff < timestampDiff) {
+ matchIndex = index;
+ timestampDiff = smallestDiff;
+ }
+ });
+
+ return matchIndex;
+ },
+
+ /**
+ * Get Graph Plotting values to render Line and Dot
+ */
+ getGraphPlotValues(median, metrics) {
+ const renderData = metrics.map(v => v[1]);
+ const medianMetricIndex = this.getMedianMetricIndex(median, metrics);
+ let cx = 0;
+ let cy = 0;
+
+ // Find Maximum and Minimum values from `renderData` array
+ const maxMemory = Math.max.apply(null, renderData);
+ const minMemory = Math.min.apply(null, renderData);
+
+ // Find difference between extreme ends
+ const diff = maxMemory - minMemory;
+ const lineWidth = renderData.length;
+
+ // Iterate over metrics values and perform following
+ // 1. Find x & y co-ords for deploymentTime's memory value
+ // 2. Return line path against maxMemory
+ const linePath = renderData.map((y, x) => {
+ if (medianMetricIndex === x) {
+ cx = x;
+ cy = maxMemory - y;
+ }
+ return `${x} ${maxMemory - y}`;
+ });
+
+ return {
+ pathD: linePath,
+ pathViewBox: {
+ lineWidth,
+ diff,
+ },
+ dotX: cx,
+ dotY: cy,
+ };
+ },
+
+ /**
+ * Render Graph based on provided median and metrics values
+ */
+ renderGraph(median, metrics) {
+ const { pathD, pathViewBox, dotX, dotY } = this.getGraphPlotValues(median, metrics);
+
+ // Set props and update graph on UI.
+ this.pathD = `M ${pathD}`;
+ this.pathViewBox = `0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`;
+ this.dotX = dotX;
+ this.dotY = dotY;
+ },
+ },
mounted() {
- const renderData = this.$props.metrics.map(v => v[1]);
- const maxMemory = Math.max.apply(null, renderData);
- const minMemory = Math.min.apply(null, renderData);
- const diff = maxMemory - minMemory;
- // const cx = 0;
- // const cy = 0;
- const lineWidth = renderData.length;
- const linePath = renderData.map((y, x) => `${x} ${maxMemory - y}`);
- this.pathD = `M ${linePath}`;
- this.pathViewBox = `0 0 ${lineWidth} ${diff}`;
+ this.renderGraph(this.deploymentTime, this.metrics);
},
template: `
<div class="memory-graph-container">
- <svg :width="width" :height="height" xmlns="http://www.w3.org/2000/svg">
+ <svg class="has-tooltip" :title="getFormattedMedian" :width="width" :height="height" xmlns="http://www.w3.org/2000/svg">
<path :d="pathD" :viewBox="pathViewBox" />
- <!--<circle r="0.8" :cx="dotX" :cy="dotY" tranform="translate(0 -1)" /> -->
+ <circle r="1.5" :cx="dotX" :cy="dotY" tranform="translate(0 -1)" />
</svg>
</div>
`,
diff --git a/app/assets/stylesheets/framework/memory_graph.scss b/app/assets/stylesheets/framework/memory_graph.scss
index 8473f2ef094..81cdf6b59e4 100644
--- a/app/assets/stylesheets/framework/memory_graph.scss
+++ b/app/assets/stylesheets/framework/memory_graph.scss
@@ -1,16 +1,22 @@
.memory-graph-container {
svg {
background: $white-light;
+ cursor: pointer;
+
+ &:hover {
+ box-shadow: 0 0 4px $gray-darkest inset;
+ }
}
path {
fill: none;
stroke: $blue-500;
- stroke-width: 1px;
+ stroke-width: 2px;
}
circle {
stroke: $blue-700;
fill: $blue-700;
+ stroke-width: 4px;
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f4488ccd8fe..97019b19667 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -182,8 +182,7 @@
}
&.mr-memory-usage {
- margin-top: 10px;
- margin-bottom: 10px;
+ margin: 5px 0 10px 25px;
}
}
@@ -511,7 +510,12 @@
.mr-info-list.mr-memory-usage {
.legend {
- height: 75%;
+ height: 65%;
+ top: 0;
+
+ @media (max-width: $screen-xs-max) {
+ height: 20px;
+ }
}
p {
@@ -731,13 +735,15 @@
}
.mr-memory-usage {
- p.usage-info-loading {
- margin-bottom: 6px;
+ p.usage-info-loading,
+ p.usage-info-unavailable,
+ p.usage-info-failed {
+ margin-bottom: 5px;
+ }
- .usage-info-load-spinner {
- margin-right: 10px;
- font-size: 16px;
- }
+ p.usage-info-loading .usage-info-load-spinner {
+ margin-right: 10px;
+ font-size: 16px;
}
@media (max-width: $screen-md-min) {
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 44c7eb86855..207fbad7856 100755
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -410,10 +410,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
metrics_url =
if can?(current_user, :read_environment, environment) && environment.has_metrics?
- metrics_namespace_project_environment_path(environment.project.namespace,
- environment.project,
- environment,
- deployment)
+ metrics_namespace_project_environment_deployment_path(environment.project.namespace,
+ environment.project,
+ environment,
+ deployment)
end
{