Commit 2e98683c authored by Clement Ho's avatar Clement Ho
Browse files

Merge branch 'master' into ee-dispatcher-dashboard-issues

parents 94aacb23 1b16ffac
0.65.0
0.66.0
......@@ -351,7 +351,7 @@ group :development, :test do
gem 'rubocop', '~> 0.52.0'
gem 'rubocop-rspec', '~> 1.20.1'
gem 'scss_lint', '~> 0.54.0', require: false
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
gem 'flay', '~> 2.8.0', require: false
......
......@@ -724,6 +724,9 @@ GEM
rake
raindrops (0.18.0)
rake (12.3.0)
rb-fsevent (0.10.2)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
......@@ -838,7 +841,11 @@ GEM
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.4.22)
sass (3.5.5)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sass-rails (5.0.6)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
......@@ -848,9 +855,9 @@ GEM
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
scss_lint (0.54.0)
scss_lint (0.56.0)
rake (>= 0.9, < 13)
sass (~> 3.4.20)
sass (~> 3.5.3)
securecompare (1.0.0)
seed-fu (2.3.6)
activerecord (>= 3.1)
......@@ -1200,7 +1207,7 @@ DEPENDENCIES
rugged (~> 0.26.0)
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
scss_lint (~> 0.54.0)
scss_lint (~> 0.56.0)
seed-fu (= 2.3.6)
select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5)
......
......@@ -57,12 +57,12 @@ class GfmAutoComplete {
displayTpl(value) {
if (GfmAutoComplete.isLoading(value)) return GfmAutoComplete.Loading.template;
// eslint-disable-next-line no-template-curly-in-string
let tpl = '<li>/${name}';
let tpl = '<li><span class="name">/${name}</span>';
if (value.aliases.length > 0) {
tpl += ' <small>(or /<%- aliases.join(", /") %>)</small>';
tpl += ' <small class="aliases">(or /<%- aliases.join(", /") %>)</small>';
}
if (value.params.length > 0) {
tpl += ' <small><%- params.join(" ") %></small>';
tpl += ' <small class="params"><%- params.join(" ") %></small>';
}
if (value.description !== '') {
tpl += '<small class="description"><i><%- description %></i></small>';
......
......@@ -141,7 +141,8 @@ export default {
<div
v-if="group.description"
class="description">
{{group.description}}
<span v-html="group.description">
</span>
</div>
</div>
<group-folder
......
......@@ -71,7 +71,7 @@ export default class GroupsStore {
id: rawGroupItem.id,
name: rawGroupItem.name,
fullName: rawGroupItem.full_name,
description: rawGroupItem.description,
description: rawGroupItem.markdown_description,
visibility: rawGroupItem.visibility,
avatarUrl: rawGroupItem.avatar_url,
relativePath: rawGroupItem.relative_path,
......
......@@ -69,8 +69,8 @@
currentFlagPosition: 0,
showFlag: false,
showFlagContent: false,
showDeployInfo: true,
timeSeries: [],
realPixelRatio: 1,
};
},
......@@ -87,10 +87,7 @@
},
innerViewBox() {
if ((this.baseGraphWidth - 150) > 0) {
return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
}
return '0 0 0 0';
return `0 0 ${this.baseGraphWidth - 150} ${this.baseGraphHeight}`;
},
axisTransform() {
......@@ -102,6 +99,10 @@
paddingBottom: `${(Math.ceil(this.baseGraphHeight * 100) / this.baseGraphWidth) || 0}%`,
};
},
deploymentFlagData() {
return this.reducedDeploymentData.find(deployment => deployment.showDeploymentFlag);
},
},
methods: {
......@@ -122,6 +123,10 @@
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
this.baseGraphHeight = this.graphHeight;
this.baseGraphWidth = this.graphWidth;
// pixel offsets inside the svg and outside are not 1:1
this.realPixelRatio = (this.$refs.baseSvg.clientWidth / this.baseGraphWidth);
this.renderAxesPaths();
this.formatDeployments();
},
......@@ -261,6 +266,11 @@
:line-color="path.lineColor"
:area-color="path.areaColor"
/>
<graph-deployment
:deployment-data="reducedDeploymentData"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
/>
<rect
class="prometheus-graph-overlay"
:width="(graphWidth - 70)"
......@@ -269,24 +279,21 @@
ref="graphOverlay"
@mousemove="handleMouseOverGraph($event)">
</rect>
<graph-deployment
:show-deploy-info="showDeployInfo"
:deployment-data="reducedDeploymentData"
:graph-width="graphWidth"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
/>
<graph-flag
v-if="showFlag"
:current-x-coordinate="currentXCoordinate"
:current-data="currentData"
:current-flag-position="currentFlagPosition"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
:show-flag-content="showFlagContent"
/>
</svg>
</svg>
<graph-flag
:real-pixel-ratio="realPixelRatio"
:current-x-coordinate="currentXCoordinate"
:current-data="currentData"
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
:show-flag-content="showFlagContent"
:time-series="timeSeries"
:unit-of-display="unitOfDisplay"
:current-data-index="currentDataIndex"
:legend-title="legendTitle"
:deployment-flag-data="deploymentFlagData"
/>
</div>
</div>
</template>
<script>
import { dateFormatWithName, timeFormat } from '../../utils/date_time_formatters';
import Icon from '../../../vue_shared/components/icon.vue';
export default {
props: {
showDeployInfo: {
type: Boolean,
required: true,
},
deploymentData: {
type: Array,
required: true,
......@@ -20,14 +13,6 @@
type: Number,
required: true,
},
graphWidth: {
type: Number,
required: true,
},
},
components: {
Icon,
},
computed: {
......@@ -37,52 +22,17 @@
},
methods: {
refText(d) {
return d.tag ? d.ref : d.sha.slice(0, 8);
},
formatTime(deploymentTime) {
return timeFormat(deploymentTime);
},
formatDate(deploymentTime) {
return dateFormatWithName(deploymentTime);
},
nameDeploymentClass(deployment) {
return `deploy-info-${deployment.id}`;
},
transformDeploymentGroup(deployment) {
return `translate(${Math.floor(deployment.xPos) + 1}, 20)`;
},
positionFlag(deployment) {
let xPosition = 3;
if (deployment.xPos > (this.graphWidth - 225)) {
xPosition = -142;
}
return xPosition;
},
svgContainerHeight(tag) {
let svgHeight = 80;
if (!tag) {
svgHeight -= 20;
}
return svgHeight;
return `translate(${Math.floor(deployment.xPos) - 5}, 20)`;
},
},
};
</script>
<template>
<g
class="deploy-info"
v-if="showDeployInfo">
<g class="deploy-info">
<g
v-for="(deployment, index) in deploymentData"
:key="index"
:class="nameDeploymentClass(deployment)"
:transform="transformDeploymentGroup(deployment)">
<rect
x="0"
......@@ -99,81 +49,6 @@
:y2="calculatedHeight"
stroke="#000">
</line>
<svg
v-if="deployment.showDeploymentFlag"
class="js-deploy-info-box"
:x="positionFlag(deployment)"
y="0"
width="134"
:height="svgContainerHeight(deployment.tag)">
<rect
class="rect-text-metric deploy-info-rect rect-metric"
x="1"
y="1"
rx="2"
width="132"
:height="svgContainerHeight(deployment.tag) - 2">
</rect>
<text
class="deploy-info-text text-metric-bold"
transform="translate(5, 2)">
Deployed
</text>
<!--The date info-->
<g transform="translate(5, 20)">
<text class="deploy-info-text">
{{formatDate(deployment.time)}}
</text>
<text
class="deploy-info-text text-metric-bold"
x="62">
{{formatTime(deployment.time)}}
</text>
</g>
<line
class="divider-line"
x1="0"
y1="38"
x2="132"
:y2="38"
stroke="#000">
</line>
<!--Commit information-->
<g transform="translate(5, 40)">
<icon
name="commit"
:width="12"
:height="12"
:y="3">
</icon>
<a :xlink:href="deployment.commitUrl">
<text
class="deploy-info-text deploy-info-text-link"
transform="translate(20, 2)">
{{refText(deployment)}}
</text>
</a>
</g>
<!--Tag information-->
<g
transform="translate(5, 55)"
v-if="deployment.tag">
<icon
name="label"
:width="12"
:height="12"
:y="5">
</icon>
<a :xlink:href="deployment.tagUrl">
<text
class="deploy-info-text deploy-info-text-link"
transform="translate(20, 2)"
y="2">
{{deployment.tag}}
</text>
</a>
</g>
</svg>
</g>
<svg
height="0"
......
<script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
import { formatRelevantDigits } from '../../../lib/utils/number_utils';
import Icon from '../../../vue_shared/components/icon.vue';
export default {
props: {
......@@ -7,14 +9,15 @@
type: Number,
required: true,
},
currentFlagPosition: {
type: Number,
required: true,
},
currentData: {
type: Object,
required: true,
},
deploymentFlagData: {
type: Object,
required: false,
default: null,
},
graphHeight: {
type: Number,
required: true,
......@@ -23,71 +26,173 @@
type: Number,
required: true,
},
realPixelRatio: {
type: Number,
required: true,
},
showFlagContent: {
type: Boolean,
required: true,
},
timeSeries: {
type: Array,
required: true,
},
unitOfDisplay: {
type: String,
required: true,
},
currentDataIndex: {
type: Number,
required: true,
},
legendTitle: {
type: String,
required: true,
},
},
data() {
return {
circleColorRgb: '#8fbce8',
};
components: {
Icon,
},
computed: {
formatTime() {
return timeFormat(this.currentData.time);
return this.deploymentFlagData ?
timeFormat(this.deploymentFlagData.time) :
timeFormat(this.currentData.time);
},
formatDate() {
return dateFormat(this.currentData.time);
return this.deploymentFlagData ?
dateFormat(this.deploymentFlagData.time) :
dateFormat(this.currentData.time);
},
cursorStyle() {
const xCoordinate = this.deploymentFlagData ?
this.deploymentFlagData.xPos :
this.currentXCoordinate;
const offsetTop = 20 * this.realPixelRatio;
const offsetLeft = (70 + xCoordinate) * this.realPixelRatio;
const height = (this.graphHeight - this.graphHeightOffset) * this.realPixelRatio;
return {
top: `${offsetTop}px`,
left: `${offsetLeft}px`,
height: `${height}px`,
};
},
flagOrientation() {
if (this.currentXCoordinate * this.realPixelRatio > 120) {
return 'left';
}
return 'right';
},
},
methods: {
seriesMetricValue(series) {
const index = this.deploymentFlagData ?
this.deploymentFlagData.seriesIndex :
this.currentDataIndex;
const value = series.values[index] &&
series.values[index].value;
if (isNaN(value)) {
return '-';
}
return `${formatRelevantDigits(value)}${this.unitOfDisplay}`;
},
calculatedHeight() {
return this.graphHeight - this.graphHeightOffset;
seriesMetricLabel(index, series) {
if (this.timeSeries.length < 2) {
return this.legendTitle;
}
if (series.metricTag) {
return series.metricTag;
}
return `series ${index + 1}`;
},
strokeDashArray(type) {
if (type === 'dashed') return '6, 3';
if (type === 'dotted') return '3, 3';
return null;
},
},
};
</script>
<template>
<g class="mouse-over-flag">
<line
class="selected-metric-line"
:x1="currentXCoordinate"
:y1="0"
:x2="currentXCoordinate"
:y2="calculatedHeight"
transform="translate(-5, 20)">
</line>
<svg
<div
class="prometheus-graph-cursor"
:style="cursorStyle"
>
<div
v-if="showFlagContent"
class="rect-text-metric"
:x="currentFlagPosition"
y="0">
<rect
class="rect-metric"
x="4"
y="1"
rx="2"
width="90"
height="40"
transform="translate(-3, 20)">
</rect>
<text
class="text-metric text-metric-bold"
x="16"
y="35"
transform="translate(-5, 20)">
{{formatTime}}
</text>
<text
class="text-metric"
x="16"
y="15"
transform="translate(-5, 20)">
{{formatDate}}
</text>
</svg>
</g>
class="prometheus-graph-flag popover"
:class="flagOrientation"
>
<div class="arrow"></div>
<div class="popover-title">
<h5 v-if="this.deploymentFlagData">
Deployed
</h5>
{{formatDate}} at
<strong>{{formatTime}}</strong>
</div>
<div
v-if="this.deploymentFlagData"
class="popover-content deploy-meta-content"
>
<div>
<icon
name="commit"
:size="12">
</icon>
<a :href="deploymentFlagData.commitUrl">
{{deploymentFlagData.sha.slice(0, 8)}}
</a>
</div>
<div
v-if="deploymentFlagData.tag">
<icon
name="label"
:size="12">
</icon>
<a :href="deploymentFlagData.tagUrl">
{{deploymentFlagData.ref}}
</a>
</div>
</div>
<div class="popover-content">
<table>
<tr
v-for="(series, index) in timeSeries"
:key="index"
>
<td>
<svg width="15" height="6">
<line
:stroke="series.lineColor"
:stroke-dasharray="strokeDashArray(series.lineStyle)"
stroke-width="4"
x1="0"
x2="15"
y1="2"
y2="2">
</line>
</svg>
</td>
<td>{{seriesMetricLabel(index, series)}}</td>
<td>
<strong>{{seriesMetricValue(series)}}</strong>
</td>
</tr>
</table>
</div>
</div>
</div>
</template>
......@@ -29,15 +29,18 @@ const mixins = {
time.setSeconds(this.timeSeries[0].values[0].time.getSeconds());
if (xPos >= 0) {
const seriesIndex = bisectDate(this.timeSeries[0].values, time, 1);
deploymentDataArray.push({
id: deployment.id,
time,
sha: deployment.sha,
commitUrl: `${this.projectPath}/commit/${deployment.sha}`,
tag: deployment.tag,
tagUrl: `${this.tagsPath}/${deployment.tag}`,
tagUrl: deployment.tag ? `${this.tagsPath}/${deployment.ref.name}` : null,
ref: deployment.ref.name,
xPos,
seriesIndex,
showDeploymentFlag: false,
});
}
......
......@@ -14,7 +14,7 @@ const d3 = {
timeYear,
};
export const dateFormat = d3.time('%b %-d, %Y');
export const dateFormat = d3.time('%a, %b %-d');
export const timeFormat = d3.time('%-I:%M%p');
export const dateFormatWithName = d3.time('%a, %b %-d');
export const bisectDate = d3.bisector(d => d.time).left;
......
......@@ -192,6 +192,17 @@
overflow-y: auto;
overflow-x: hidden;
.name,
small.aliases,
small.params {
float: left;
}
small.aliases,
small.params {
padding: 2px 5px;
}
small.description {
float: right;
padding: 3px 5px;
......@@ -209,6 +220,7 @@
}
ul > li {
@include clearfix;
white-space: nowrap;
}
......
......@@ -408,6 +408,73 @@
}
}
.prometheus-graph-cursor {
position: absolute;
background: $theme-gray-600;
width: 1px;
}
.prometheus-graph-flag {
display: block;
min-width: 160px;
h5 {
padding: 0;
margin: 0;
font-size: 14px;
line-height: 1.2;
}
table {
border-collapse: collapse;
padding: 0;
margin: 0;
}
td {
vertical-align: middle;
+ td {
padding-left: 5px;
vertical-align: top;
}
}
.deploy-meta-content {
border-bottom: 1px solid $white-dark;
svg {
height: 15px;
vertical-align: bottom;
}
}
&.popover {
&.left {
left: auto;
right: 0;
margin-right: 10px;
}
&.right {
left: 0;
right: auto;
margin-left: 10px;
}
> .arrow {
top: 40px;
}
> .popover-title,
> .popover-content {
padding: 5px 8px;
font-size: 12px;
white-space: nowrap;
}
}
}
.prometheus-svg-container {
position: relative;
height: 0;
......
......@@ -86,4 +86,8 @@ class Projects::ApplicationController < ApplicationController
def require_pages_enabled!
not_found unless @project.pages_available?
end
def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user)
end
end
......@@ -4,6 +4,7 @@ class Projects::BoardsController < Projects::ApplicationController
include BoardsResponses
include IssuableCollections
before_action :check_issues_available!
before_action :authorize_read_board!, only: [:index, :show]
before_action :assign_endpoint_vars
......
......@@ -197,10 +197,6 @@ class Projects::IssuesController < Projects::ApplicationController
render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
end
def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user)
end
def render_issue_json
if @issue.valid?
render json: serializer.represent(@issue)
......
......@@ -371,7 +371,7 @@ class Commit
#
# Returns a symbol
def uri_type(path)
entry = @raw.tree.path(path)
entry = @raw.rugged_tree_entry(path)
if entry[:type] == :blob
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project)
blob.image? || blob.video? ? :raw : :blob
......
......@@ -1461,6 +1461,7 @@ class Project < ActiveRecord::Base
import_finish
remove_import_jid
update_project_counter_caches
after_create_default_branch
end
def update_project_counter_caches
......@@ -1474,6 +1475,27 @@ class Project < ActiveRecord::Base
end
end
def after_create_default_branch
return unless default_branch
# Ensure HEAD points to the default branch in case it is not master
change_head(default_branch)
if current_application_settings.default_branch_protection != Gitlab::Access::PROTECTION_NONE && !ProtectedBranch.protected?(self, default_branch)
params = {
name: default_branch,
push_access_levels_attributes: [{
access_level: current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
}],
merge_access_levels_attributes: [{
access_level: current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
}]
}
ProtectedBranches::CreateService.new(self, creator, params).execute(skip_authorization: true)
end
end
def remove_import_jid
return unless import_jid
......
class ProjectTeam
include BulkMemberAccessLoad
prepend EE::ProjectTeam
attr_accessor :project
def initialize(project)
......@@ -40,8 +42,6 @@ class ProjectTeam
end
def add_users(users, access_level, current_user: nil, expires_at: nil)
return false if group_member_lock
ProjectMember.add_users(
project,
users,
......@@ -173,12 +173,4 @@ class ProjectTeam
def group
project.group
end
def group_member_lock
group && group.membership_lock
end
def merge_max!(first_hash, second_hash)
first_hash.merge!(second_hash) { |_key, old, new| old > new ? old : new }
end
end
class GroupChildEntity < Grape::Entity
include ActionView::Helpers::NumberHelper
include RequestAwareEntity
include MarkupHelper
expose :id, :name, :description, :visibility, :full_name,
:created_at, :updated_at, :avatar_url
......@@ -59,6 +60,10 @@ class GroupChildEntity < Grape::Entity
number_with_delimiter(instance.member_count)
end
expose :markdown_description do |instance|
markdown_description
end
private
def membership
......@@ -74,4 +79,8 @@ class GroupChildEntity < Grape::Entity
def type
object.class.name.downcase
end
def markdown_description
markdown_field(object, :description)
end
end
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