Commit 4c6898bd authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into '36860-migrate-issues-author'

# Conflicts:
#   db/schema.rb
parents ae9b7717 063e285e
Pipeline #11468302 passed with stages
in 113 minutes and 57 seconds
......@@ -212,7 +212,6 @@ update-tests-metadata:
flaky-examples-check:
<<: *dedicated-runner
<<: *except-docs
image: ruby:2.3-alpine
services: []
before_script: []
......@@ -227,6 +226,7 @@ flaky-examples-check:
- branches
except:
- master
- /(^docs[\/-].*|.*-docs$)/
artifacts:
expire_in: 30d
paths:
......
......@@ -2,6 +2,7 @@
[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![Overall test coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg)](https://gitlab.com/gitlab-org/gitlab-ce/pipelines)
[![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.svg)](https://gemnasium.com/gitlabhq/gitlabhq)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
[![Gitter](https://badges.gitter.im/gitlabhq/gitlabhq.svg)](https://gitter.im/gitlabhq/gitlabhq?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
......
......@@ -5,7 +5,7 @@ const Api = {
groupPath: '/api/:version/groups/:id.json',
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json?simple=true',
projectsPath: '/api/:version/projects.json',
labelsPath: '/:namespace_path/:project_path/labels',
licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key',
......@@ -58,6 +58,7 @@ const Api = {
const defaults = {
search: query,
per_page: 20,
simple: true,
};
if (gon.current_user_id) {
......
......@@ -2,3 +2,4 @@ import 'underscore';
import './polyfills';
import './jquery';
import './bootstrap';
import './vue';
import Vue from 'vue';
import './vue_resource_interceptor';
if (process.env.NODE_ENV !== 'production') {
Vue.config.productionTip = false;
......
......@@ -21,11 +21,13 @@ const DiffNoteAvatars = Vue.extend({
},
template: `
<div class="diff-comment-avatar-holders"
:class="discussionClassName"
v-show="notesCount !== 0">
<div v-if="!isVisible">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image
v-for="note in notesSubset"
:key="note.id"
class="diff-comment-avatar js-diff-comment-avatar"
@click.native="clickedAvatar($event)"
:img-src="note.authorAvatar"
......@@ -68,7 +70,8 @@ const DiffNoteAvatars = Vue.extend({
});
});
},
destroyed() {
beforeDestroy() {
this.addNoCommentClass();
$(document).off('toggle.comments');
},
watch: {
......@@ -85,6 +88,9 @@ const DiffNoteAvatars = Vue.extend({
},
},
computed: {
discussionClassName() {
return `js-diff-avatars-${this.discussionId}`;
},
notesSubset() {
let notes = [];
......
......@@ -32,6 +32,10 @@ $(() => {
const tmpApp = new tmp().$mount();
$(this).replaceWith(tmpApp.$el);
$(tmpApp.$el).one('remove.vue', () => {
tmpApp.$destroy();
tmpApp.$el.remove();
});
});
const $components = $(COMPONENT_SELECTOR).filter(function () {
......
......@@ -132,6 +132,7 @@ import './project_new';
import './project_select';
import './project_show';
import './project_variables';
import './projects_dropdown';
import './projects_list';
import './syntax_highlight';
import './render_math';
......@@ -249,7 +250,10 @@ $(function () {
// Initialize popovers
$body.popover({
selector: '[data-toggle="popover"]',
trigger: 'focus'
trigger: 'focus',
// set the viewport to the main content, excluding the navigation bar, so
// the navigation can't overlap the popover
viewport: '.page-with-sidebar'
});
$('.trigger-submit').on('change', function () {
return $(this).parents('form').submit();
......
......@@ -3,11 +3,12 @@
import GraphLegend from './graph/legend.vue';
import GraphFlag from './graph/flag.vue';
import GraphDeployment from './graph/deployment.vue';
import monitoringPaths from './monitoring_paths.vue';
import MonitoringMixin from '../mixins/monitoring_mixins';
import eventHub from '../event_hub';
import measurements from '../utils/measurements';
import { formatRelevantDigits } from '../../lib/utils/number_utils';
import { timeScaleFormat } from '../utils/date_time_formatters';
import createTimeSeries from '../utils/multiple_time_series';
import bp from '../../breakpoints';
const bisectDate = d3.bisector(d => d.time).left;
......@@ -36,32 +37,29 @@
data() {
return {
baseGraphHeight: 450,
baseGraphWidth: 600,
graphHeight: 450,
graphWidth: 600,
graphHeightOffset: 120,
xScale: {},
yScale: {},
margin: {},
data: [],
unitOfDisplay: '',
areaColorRgb: '#8fbce8',
lineColorRgb: '#1f78d1',
yAxisLabel: '',
legendTitle: '',
reducedDeploymentData: [],
area: '',
line: '',
measurements: measurements.large,
currentData: {
time: new Date(),
value: 0,
},
currentYCoordinate: 0,
currentDataIndex: 0,
currentXCoordinate: 0,
currentFlagPosition: 0,
metricUsage: '',
showFlag: false,
showDeployInfo: true,
timeSeries: [],
};
},
......@@ -69,16 +67,17 @@
GraphLegend,
GraphFlag,
GraphDeployment,
monitoringPaths,
},
computed: {
outterViewBox() {
return `0 0 ${this.graphWidth} ${this.graphHeight}`;
return `0 0 ${this.baseGraphWidth} ${this.baseGraphHeight}`;
},
innerViewBox() {
if ((this.graphWidth - 150) > 0) {
return `0 0 ${this.graphWidth - 150} ${this.graphHeight}`;
if ((this.baseGraphWidth - 150) > 0) {
return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
}
return '0 0 0 0';
},
......@@ -89,7 +88,7 @@
paddingBottomRootSvg() {
return {
paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`,
};
},
},
......@@ -104,17 +103,16 @@
this.margin = measurements.small.margin;
this.measurements = measurements.small;
}
this.data = query.result[0].values;
this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.graphData.y_label || 'Values';
this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
if (this.data !== undefined) {
this.renderAxesPaths();
this.formatDeployments();
}
this.baseGraphHeight = this.graphHeight;
this.baseGraphWidth = this.graphWidth;
this.renderAxesPaths();
this.formatDeployments();
},
handleMouseOverGraph(e) {
......@@ -123,16 +121,17 @@
point.y = e.clientY;
point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
point.x = point.x += 7;
const timeValueOverlay = this.xScale.invert(point.x);
const overlayIndex = bisectDate(this.data, timeValueOverlay, 1);
const d0 = this.data[overlayIndex - 1];
const d1 = this.data[overlayIndex];
const firstTimeSeries = this.timeSeries[0];
const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x);
const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1);
const d0 = firstTimeSeries.values[overlayIndex - 1];
const d1 = firstTimeSeries.values[overlayIndex];
if (d0 === undefined || d1 === undefined) return;
const evalTime = timeValueOverlay - d0[0] > d1[0] - timeValueOverlay;
this.currentData = evalTime ? d1 : d0;
this.currentXCoordinate = Math.floor(this.xScale(this.currentData.time));
this.currentDataIndex = evalTime ? overlayIndex : (overlayIndex - 1);
this.currentXCoordinate = Math.floor(firstTimeSeries.timeSeriesScaleX(this.currentData.time));
const currentDeployXPos = this.mouseOverDeployInfo(point.x);
this.currentYCoordinate = this.yScale(this.currentData.value);
if (this.currentXCoordinate > (this.graphWidth - 200)) {
this.currentFlagPosition = this.currentXCoordinate - 103;
......@@ -145,17 +144,25 @@
} else {
this.showFlag = true;
}
this.metricUsage = `${formatRelevantDigits(this.currentData.value)} ${this.unitOfDisplay}`;
},
renderAxesPaths() {
this.timeSeries = createTimeSeries(this.graphData.queries[0].result,
this.graphWidth,
this.graphHeight,
this.graphHeightOffset);
if (this.timeSeries.length > 3) {
this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20;
}
const axisXScale = d3.time.scale()
.range([0, this.graphWidth]);
this.yScale = d3.scale.linear()
const axisYScale = d3.scale.linear()
.range([this.graphHeight - this.graphHeightOffset, 0]);
axisXScale.domain(d3.extent(this.data, d => d.time));
this.yScale.domain([0, d3.max(this.data.map(d => d.value))]);
axisXScale.domain(d3.extent(this.timeSeries[0].values, d => d.time));
axisYScale.domain([0, d3.max(this.timeSeries[0].values.map(d => d.value))]);
const xAxis = d3.svg.axis()
.scale(axisXScale)
......@@ -164,7 +171,7 @@
.orient('bottom');
const yAxis = d3.svg.axis()
.scale(this.yScale)
.scale(axisYScale)
.ticks(measurements.yTicks)
.orient('left');
......@@ -180,25 +187,6 @@
.attr('class', 'axis-tick');
} // Avoid adding the class to the first tick, to prevent coloring
}); // This will select all of the ticks once they're rendered
this.xScale = d3.time.scale()
.range([0, this.graphWidth - 70]);
this.xScale.domain(d3.extent(this.data, d => d.time));
const areaFunction = d3.svg.area()
.x(d => this.xScale(d.time))
.y0(this.graphHeight - this.graphHeightOffset)
.y1(d => this.yScale(d.value))
.interpolate('linear');
const lineFunction = d3.svg.line()
.x(d => this.xScale(d.time))
.y(d => this.yScale(d.value));
this.line = lineFunction(this.data);
this.area = areaFunction(this.data);
},
},
......@@ -245,30 +233,25 @@
:graph-height="graphHeight"
:margin="margin"
:measurements="measurements"
:area-color-rgb="areaColorRgb"
:legend-title="legendTitle"
:y-axis-label="yAxisLabel"
:metric-usage="metricUsage"
:time-series="timeSeries"
:unit-of-display="unitOfDisplay"
:current-data-index="currentDataIndex"
/>
<svg
class="graph-data"
:viewBox="innerViewBox"
ref="graphData">
<path
class="metric-area"
:d="area"
:fill="areaColorRgb"
transform="translate(-5, 20)">
</path>
<path
class="metric-line"
:d="line"
:stroke="lineColorRgb"
fill="none"
stroke-width="2"
transform="translate(-5, 20)">
</path>
<graph-deployment
<monitoring-paths
v-for="(path, index) in timeSeries"
:key="index"
:generated-line-path="path.linePath"
:generated-area-path="path.areaPath"
:line-color="path.lineColor"
:area-color="path.areaColor"
/>
<monitoring-deployment
:show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData"
:graph-height="graphHeight"
......@@ -277,7 +260,6 @@
<graph-flag
v-if="showFlag"
:current-x-coordinate="currentXCoordinate"
:current-y-coordinate="currentYCoordinate"
:current-data="currentData"
:current-flag-position="currentFlagPosition"
:graph-height="graphHeight"
......
......@@ -7,10 +7,6 @@
type: Number,
required: true,
},
currentYCoordinate: {
type: Number,
required: true,
},
currentFlagPosition: {
type: Number,
required: true,
......@@ -60,16 +56,7 @@
:y2="calculatedHeight"
transform="translate(-5, 20)">
</line>
<circle
class="circle-metric"
:fill="circleColorRgb"
stroke="#000"
:cx="currentXCoordinate"
:cy="currentYCoordinate"
r="5"
transform="translate(-5, 20)">
</circle>
<svg
<svg
class="rect-text-metric"
:x="currentFlagPosition"
y="0">
......
<script>
import { formatRelevantDigits } from '../../../lib/utils/number_utils';
export default {
props: {
graphWidth: {
......@@ -17,10 +19,6 @@
type: Object,
required: true,
},
areaColorRgb: {
type: String,
required: true,
},
legendTitle: {
type: String,
required: true,
......@@ -29,15 +27,25 @@
type: String,
required: true,
},
metricUsage: {
timeSeries: {
type: Array,
required: true,
},
unitOfDisplay: {
type: String,
required: true,
},
currentDataIndex: {
type: Number,
required: true,
},
},
data() {
return {
yLabelWidth: 0,
yLabelHeight: 0,
seriesXPosition: 0,
metricUsageXPosition: 0,
};
},
computed: {
......@@ -63,10 +71,28 @@
yPosition() {
return ((this.graphHeight - this.margin.top) + this.measurements.axisLabelLineOffset) || 0;
},
},
methods: {
translateLegendGroup(index) {
return `translate(0, ${12 * (index)})`;
},
formatMetricUsage(series) {
return `${formatRelevantDigits(series.values[this.currentDataIndex].value)} ${this.unitOfDisplay}`;
},
},
mounted() {
this.$nextTick(() => {
const bbox = this.$refs.ylabel.getBBox();
this.metricUsageXPosition = 0;
this.seriesXPosition = 0;
if (this.$refs.legendTitleSvg != null) {
this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width;
}
if (this.$refs.seriesTitleSvg != null) {
this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width;
}
this.yLabelWidth = bbox.width + 10; // Added some padding
this.yLabelHeight = bbox.height + 5;
});
......@@ -121,24 +147,33 @@
dy=".35em">
Time
</text>
<rect
:fill="areaColorRgb"
:width="measurements.legends.width"
:height="measurements.legends.height"
x="20"
:y="graphHeight - measurements.legendOffset">
</rect>
<text
class="text-metric-title"
x="50"
:y="graphHeight - 25">
{{legendTitle}}
</text>
<text
class="text-metric-usage"
x="50"
:y="graphHeight - 10">
{{metricUsage}}
</text>
<g class="legend-group"
v-for="(series, index) in timeSeries"
:key="index"
:transform="translateLegendGroup(index)">
<rect
:fill="series.areaColor"
:width="measurements.legends.width"
:height="measurements.legends.height"
x="20"
:y="graphHeight - measurements.legendOffset">
</rect>
<text
v-if="timeSeries.length > 1"
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30">
{{legendTitle}} Series {{index + 1}} {{formatMetricUsage(series)}}
</text>
<text
v-else
class="legend-metric-title"
ref="legendTitleSvg"
x="38"
:y="graphHeight - 30">
{{legendTitle}} {{formatMetricUsage(series)}}
</text>
</g>
</g>
</template>
<script>
export default {
props: {
generatedLinePath: {
type: String,
required: true,
},
generatedAreaPath: {
type: String,
required: true,
},
lineColor: {
type: String,
required: true,
},
areaColor: {
type: String,
required: true,
},
},
};
</script>
<template>
<g>
<path
class="metric-area"
:d="generatedAreaPath"
:fill="areaColor"
transform="translate(-5, 20)">
</path>
<path
class="metric-line"
:d="generatedLinePath"
:stroke="lineColor"
fill="none"
stroke-width="1"
transform="translate(-5, 20)">
</path>
</g>
</template>
......@@ -21,9 +21,9 @@ const mixins = {
formatDeployments() {
this.reducedDeploymentData = this.deploymentData.reduce((deploymentDataArray, deployment) => {
const time = new Date(deployment.created_at);
const xPos = Math.floor(this.xScale(time));
const xPos = Math.floor(this.timeSeries[0].timeSeriesScaleX(time));
time.setSeconds(this.data[0].time.getSeconds());
time.setSeconds(this.timeSeries[0].values[0].time.getSeconds());
if (xPos >= 0) {
deploymentDataArray.push({
......
import _ from 'underscore';
class MonitoringStore {
function sortMetrics(metrics) {
return _.chain(metrics).sortBy('weight').sortBy('title').value();
}
function normalizeMetrics(metrics) {
return metrics.map(metric => ({
...metric,
queries: metric.queries.map(query => ({
...query,
result: query.result.map(result => ({
...result,
values: result.values.map(([timestamp, value]) => ({
time: new Date(timestamp * 1000),
value,
})),
})),
})),
}));
}
function collate(array, rows = 2) {
const collatedArray = [];
let row = [];