Commit 692f8561 authored by Alejandro Rodríguez's avatar Alejandro Rodríguez

Merge remote-tracking branch 'ce/master' into ce-upstream

parents 3b5f3c61 6eeff67c
......@@ -212,6 +212,30 @@ rake brakeman: *exec
rake flay: *exec
license_finder: *exec
rake downtime_check: *exec
<<<<<<< HEAD
=======
rake ee_compat_check:
<<: *exec
only:
- branches@gitlab-org/gitlab-ce
- branches@gitlab/gitlabhq
except:
- master
- tags
- /^[\d-]+-stable(-ee)?$/
allow_failure: yes
cache:
key: "ruby231-ee_compat_check_repo"
paths:
- ee_compat_check/repo/
- vendor/ruby
artifacts:
name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}_${CI_BUILD_REF}"
when: on_failure
expire_in: 10d
paths:
- ee_compat_check/patches/*.patch
>>>>>>> ce/master
rake db:migrate:reset:
stage: test
......
......@@ -147,6 +147,7 @@ gem 'acts-as-taggable-on', '~> 4.0'
gem 'sidekiq', '~> 4.2'
gem 'sidekiq-cron', '~> 0.4.0'
gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4'
# HTTP requests
gem 'httparty', '~> 0.13.3'
......
......@@ -709,6 +709,8 @@ GEM
redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0)
sidekiq-limit_fetch (3.4.0)
sidekiq (>= 4)
simplecov (0.12.0)
docile (~> 1.1.0)
json (>= 1.8, < 3)
......@@ -994,6 +996,7 @@ DEPENDENCIES
shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.2)
sidekiq-cron (~> 0.4.0)
sidekiq-limit_fetch (~> 3.4)
simplecov (= 0.12.0)
slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1)
......
......@@ -112,6 +112,10 @@ GitLab is a Ruby on Rails application that runs on the following software:
For more information please see the [architecture documentation](https://docs.gitlab.com/ce/development/architecture.html).
## UX design
Please adhere to the [UX Guide](doc/development/ux_guide/readme.md) when creating designs and implementing code.
## Third-party applications
There are a lot of [third-party applications integrating with GitLab](https://about.gitlab.com/applications/). These include GUI Git clients, mobile applications and API wrappers for various languages.
......
......@@ -59,11 +59,28 @@
document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch);
window.addEventListener('hashchange', gl.utils.shiftWindow);
// automatically adjust scroll position for hash urls taking the height of the navbar into account
// https://github.com/twitter/bootstrap/issues/1768
window.adjustScroll = function() {
var navbar = document.querySelector('.navbar-gitlab');
var subnav = document.querySelector('.layout-nav');
var fixedTabs = document.querySelector('.js-tabs-affix');
adjustment = 0;
if (navbar) adjustment -= navbar.offsetHeight;
if (subnav) adjustment -= subnav.offsetHeight;
if (fixedTabs) adjustment -= fixedTabs.offsetHeight;
return scrollBy(0, adjustment);
};
window.addEventListener("hashchange", adjustScroll);
window.onload = function () {
// Scroll the window to avoid the topnav bar
// https://github.com/twitter/bootstrap/issues/1768
if (location.hash) {
return setTimeout(gl.utils.shiftWindow, 100);
return setTimeout(adjustScroll, 100);
}
};
......
......@@ -23,6 +23,8 @@ $(() => {
gl.IssueBoardsApp.$destroy(true);
}
Store.create();
gl.IssueBoardsApp = new Vue({
el: $boardApp,
components: {
......@@ -39,16 +41,15 @@ $(() => {
issueLinkBase: $boardApp.dataset.issueLinkBase,
detailIssue: Store.detail
},
init: Store.create.bind(Store),
computed: {
detailIssueVisible () {
return Object.keys(this.detailIssue.issue).length;
}
},
},
created () {
gl.boardService = new BoardService(this.endpoint, this.boardId);
},
ready () {
mounted () {
Store.disabled = this.disabled;
gl.boardService.all()
.then((resp) => {
......@@ -62,6 +63,8 @@ $(() => {
}
});
this.state.lists = _.sortBy(this.state.lists, 'position');
Store.addBlankState();
this.loading = false;
});
......@@ -72,6 +75,9 @@ $(() => {
el: '#js-boards-seach',
data: {
filters: Store.state.filters
},
mounted () {
gl.issueBoards.newListDropdownInit();
}
});
});
......@@ -10,6 +10,7 @@
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.Board = Vue.extend({
template: '#js-board-template',
components: {
'board-list': gl.issueBoards.BoardList,
'board-delete': gl.issueBoards.BoardDelete,
......@@ -24,7 +25,6 @@
return {
detailIssue: Store.detail,
filters: Store.state.filters,
showIssueForm: false
};
},
watch: {
......@@ -58,10 +58,10 @@
},
methods: {
showNewIssueForm() {
this.showIssueForm = !this.showIssueForm;
this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
}
},
ready () {
mounted () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
disabled: this.disabled,
group: 'boards',
......@@ -72,13 +72,9 @@
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
const order = this.sortable.toArray(),
$board = this.$parent.$refs.board[e.oldIndex + 1],
list = $board.list;
$board.$destroy(true);
list = Store.findList('id', parseInt(e.item.dataset.id));
this.$nextTick(() => {
Store.state.lists.splice(e.newIndex, 0, list);
Store.moveList(list, order);
});
}
......@@ -87,8 +83,5 @@
this.sortable = Sortable.create(this.$el.parentNode, options);
},
beforeDestroy () {
Store.state.lists.$remove(this.list);
}
});
})();
......@@ -30,6 +30,8 @@
});
});
Store.state.lists = _.sortBy(Store.state.lists, 'position');
// Save the labels
gl.boardService.generateDefaultLists()
.then((resp) => {
......
......@@ -6,6 +6,7 @@
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardCard = Vue.extend({
template: '#js-board-list-card',
props: {
list: Object,
issue: Object,
......@@ -53,11 +54,6 @@
mouseDown () {
this.showDetail = true;
},
mouseMove () {
if (this.showDetail) {
this.showDetail = false;
}
},
showIssue (e) {
const targetTagName = e.target.tagName.toLowerCase();
......
......@@ -9,6 +9,7 @@
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardList = Vue.extend({
template: '#js-board-list-template',
components: {
'board-card': gl.issueBoards.BoardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue
......@@ -19,20 +20,20 @@
issues: Array,
loading: Boolean,
issueLinkBase: String,
showIssueForm: Boolean
},
data () {
return {
scrollOffset: 250,
filters: Store.state.filters,
showCount: false
showCount: false,
showIssueForm: false
};
},
watch: {
filters: {
handler () {
this.list.loadingMore = false;
this.$els.list.scrollTop = 0;
this.$refs.list.scrollTop = 0;
},
deep: true
},
......@@ -51,15 +52,20 @@
});
}
},
computed: {
orderedIssues () {
return _.sortBy(this.issues, 'priority');
},
},
methods: {
listHeight () {
return this.$els.list.getBoundingClientRect().height;
return this.$refs.list.getBoundingClientRect().height;
},
scrollHeight () {
return this.$els.list.scrollHeight;
return this.$refs.list.scrollHeight;
},
scrollTop () {
return this.$els.list.scrollTop + this.listHeight();
return this.$refs.list.scrollTop + this.listHeight();
},
loadNextPage () {
const getIssues = this.list.nextPage();
......@@ -72,7 +78,7 @@
}
},
},
ready () {
mounted () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
group: 'issues',
sort: false,
......@@ -81,23 +87,27 @@
onStart: (e) => {
const card = this.$refs.issue[e.oldIndex];
card.showDetail = false;
Store.moving.issue = card.issue;
Store.moving.list = card.list;
gl.issueBoards.onStart();
},
onAdd: (e) => {
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
// Add the element back to original list to allow Vue to handle DOM updates
e.from.appendChild(e.item);
this.$nextTick(() => {
// Update the issues once we know the element has been moved
gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue);
});
},
onRemove: (e) => {
this.$refs.issue[e.oldIndex].$destroy(true);
}
});
this.sortable = Sortable.create(this.$els.list, options);
this.sortable = Sortable.create(this.$refs.list, options);
// Scroll event on list to load more
this.$els.list.onscroll = () => {
this.$refs.list.onscroll = () => {
if ((this.scrollTop() > this.scrollHeight() - this.scrollOffset) && !this.list.loadingMore) {
this.loadNextPage();
}
......
......@@ -7,7 +7,6 @@
gl.issueBoards.BoardNewIssue = Vue.extend({
props: {
list: Object,
showIssueForm: Boolean
},
data() {
return {
......@@ -15,11 +14,6 @@
error: false
};
},
watch: {
showIssueForm () {
this.$els.input.focus();
}
},
methods: {
submit(e) {
e.preventDefault();
......@@ -37,28 +31,30 @@
this.list.newIssue(issue)
.then((data) => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$els.submitButton).enable();
$(this.$refs.submitButton).enable();
Store.detail.issue = issue;
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$els.submitButton).enable();
$(this.$refs.submitButton).enable();
// Remove the issue
this.list.removeIssue(issue);
// Show error message
this.error = true;
this.showIssueForm = true;
});
this.cancel();
},
cancel() {
this.showIssueForm = false;
this.title = '';
this.$parent.showIssueForm = false;
}
}
},
mounted() {
this.$refs.input.focus();
},
});
})();
......@@ -41,7 +41,7 @@
this.detail.issue = {};
}
},
ready () {
mounted () {
new IssuableContext(this.currentUser);
new MilestoneSelect();
new gl.DueDateSelectors();
......
/* eslint-disable */
$(() => {
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
const Store = gl.issueBoards.BoardsStore;
$(document).off('created.label').on('created.label', (e, label) => {
......@@ -15,54 +18,58 @@ $(() => {
});
});
$('.js-new-board-list').each(function () {
const $this = $(this);
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
gl.issueBoards.newListDropdownInit = () => {
$('.js-new-board-list').each(function () {
const $this = $(this);
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
$this.glDropdown({
data(term, callback) {
$.get($this.attr('data-labels'))
.then((resp) => {
callback(resp);
});
},
renderRow (label) {
const active = Store.findList('title', label.title),
$li = $('<li />'),
$a = $('<a />', {
class: (active ? `is-active js-board-list-${active.id}` : ''),
text: label.title,
href: '#'
}),
$labelColor = $('<span />', {
class: 'dropdown-label-box',
style: `background-color: ${label.color}`
});
$this.glDropdown({
data(term, callback) {
$.get($this.attr('data-labels'))
.then((resp) => {
callback(resp);
});
},
renderRow (label) {
const active = Store.findList('title', label.title),
$li = $('<li />'),
$a = $('<a />', {
class: (active ? `is-active js-board-list-${active.id}` : ''),
text: label.title,
href: '#'
}),
$labelColor = $('<span />', {
class: 'dropdown-label-box',
style: `background-color: ${label.color}`
});
return $li.append($a.prepend($labelColor));
},
search: {
fields: ['title']
},
filterable: true,
selectable: true,
multiSelect: true,
clicked (label, $el, e) {
e.preventDefault();
return $li.append($a.prepend($labelColor));
},
search: {
fields: ['title']
},
filterable: true,
selectable: true,
multiSelect: true,
clicked (label, $el, e) {
e.preventDefault();
if (!Store.findList('title', label.title)) {
Store.new({
title: label.title,
position: Store.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
if (!Store.findList('title', label.title)) {
Store.new({
title: label.title,
color: label.color
}
});
position: Store.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
title: label.title,
color: label.color
}
});
Store.state.lists = _.sortBy(Store.state.lists, 'position');
}
}
}
});
});
});
});
};
})();
......@@ -23,7 +23,7 @@
fallbackOnBody: true,
ghostClass: 'is-ghost',
filter: '.board-delete, .btn',
delay: gl.issueBoards.touchEnabled ? 100 : 50,
delay: gl.issueBoards.touchEnabled ? 100 : 0,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20,
onStart: gl.issueBoards.onStart,
......
......@@ -42,7 +42,8 @@ class List {
}
destroy () {
gl.issueBoards.BoardsStore.state.lists.$remove(this);
const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this);
gl.issueBoards.BoardsStore.state.lists.splice(index, 1);
gl.issueBoards.BoardsStore.updateNewListDropdown(this.id);
gl.boardService.destroyList(this.id);
......
......@@ -39,6 +39,8 @@
// Remove any new issues from the backlog
// as they will be visible in the new list
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
this.state.lists = _.sortBy(this.state.lists, 'position');
});
this.removeBlankState();
},
......@@ -58,6 +60,8 @@
title: 'Welcome to your Issue Board!',
position: 0
});
this.state.lists = _.sortBy(this.state.lists, 'position');
},
removeBlankState () {
this.removeList('blank');
......
/* eslint-disable */
((w) => {
w.CommentAndResolveBtn = Vue.extend({
(() => {
const CommentAndResolveBtn = Vue.extend({
props: {
discussionId: String,
textareaIsEmpty: Boolean
},
data() {
return {
textareaIsEmpty: true
}
},
computed: {
discussion: function () {
......@@ -35,7 +39,7 @@
}
}
},
ready: function () {
mounted: function () {
const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`);
this.textareaIsEmpty = $textarea.val() === '';
......@@ -47,4 +51,6 @@
$(`#new-discussion-note-form-${this.discussionId} .note-textarea`).off('input.comment-and-resolve-btn');
}
});
Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
})(window);
/* eslint-disable */
((w) => {
w.ResolveBtn = Vue.extend({
(() => {
const ResolveBtn = Vue.extend({
props: {
noteId: Number,
discussionId: String,
......@@ -54,7 +54,7 @@
},
methods: {
updateTooltip: function () {
$(this.$els.button)
$(this.$refs.button)
.tooltip('hide')
.tooltip('fixTitle');
},
......@@ -89,8 +89,8 @@
});
}
},
compiled: function () {
$(this.$els.button).tooltip({
mounted: function () {
$(this.$refs.button).tooltip({
container: 'body'
});
},
......@@ -101,4 +101,6 @@
CommentsStore.create(this.discussionId, this.noteId, this.canResolve, this.resolved, this.resolvedBy);
}
});
})(window);
Vue.component('resolve-btn', ResolveBtn);
})();
......@@ -13,6 +13,9 @@
computed: {
allResolved: function () {
return this.resolvedDiscussionCount === this.discussionCount;
},
resolvedCountText() {
return this.discussionCount === 1 ? 'discussion' : 'discussions';
}
}
});
......
/* eslint-disable */
((w) => {
w.ResolveDiscussionBtn = Vue.extend({
(() => {
const ResolveDiscussionBtn = Vue.extend({
props: {
discussionId: String,
mergeRequestId: Number,
......@@ -54,4 +54,6 @@
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
}
});
})(window);
Vue.component('resolve-discussion-btn', ResolveDiscussionBtn);
})();
......@@ -8,24 +8,35 @@
//= require_directory ./components
$(() => {
window.DiffNotesApp = new Vue({
el: '#diff-notes-app',
components: {
'resolve-btn': ResolveBtn,
'resolve-discussion-btn': ResolveDiscussionBtn,
'comment-and-resolve-btn': CommentAndResolveBtn
},
methods: {
compileComponents: function () {
const $components = $('resolve-btn, resolve-discussion-btn, jump-to-discussion');
if ($components.length) {
$components.each(function () {
DiffNotesApp.$compile($(this).get(0));
});
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn';
window.gl = window.gl || {};
window.gl.diffNoteApps = {};
gl.diffNotesCompileComponents = () => {
const $components = $(COMPONENT_SELECTOR).filter(function () {
return $(this).closest('resolve-count').length !== 1;
});
if ($components) {
$components.each(function () {
const $this = $(this);
const noteId = $this.attr(':note-id');
const tmp = Vue.extend({
template: $this.get(0).outerHTML
});
const tmpApp = new tmp().$mount();
if (noteId) {
gl.diffNoteApps[`note_${noteId}`] = tmpApp;
}
}
$this.replaceWith(tmpApp.$el);
});
}
});
};
gl.diffNotesCompileComponents();
new Vue({
el: '#resolve-count-app',
......
......@@ -36,7 +36,7 @@
this.loadEditor();
}
},
ready() {
mounted() {
if (this.file.loadEditor) {
this.loadEditor();
}
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
global.mergeConflicts.parallelConflictLine = Vue.extend({
props: {
file: Object,
line: Object
},
mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
template: '#parallel-conflict-line'
});
})(window.gl || (window.gl = {}));
......@@ -7,10 +7,22 @@
props: {
file: Object
},
mixins: [global.mergeConflicts.utils],
components: {
'parallel-conflict-line': gl.mergeConflicts.parallelConflictLine
}
mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
template: `
<table>
<tr class="line_holder parallel" v-for="section in file.parallelLines">
<template v-for="line in section">
<td class="diff-line-num header" :class="lineCssClass(line)" v-if="line.isHeader"></td>
<td class="line_content header" :class="lineCssClass(line)" v-if="line.isHeader">
<strong>{{line.richText}}</strong>
<button class="btn" @click="handleSelected(file, line.id, line.section)">{{line.buttonTitle}}</button>
</td>
<td class="diff-line-num old_line" :class="lineCssClass(line)" v-if="!line.isHeader">{{line.lineNumber}}</td>
<td class="line_content parallel" :class="lineCssClass(line)" v-if="!line.isHeader" v-html="line.richText"></td>
</template>
</tr>
</table>
`,
});
})(window.gl || (window.gl = {}));
......@@ -6,7 +6,6 @@
//= require ./mixins/line_conflict_actions
//= require ./components/diff_file_editor
//= require ./components/inline_conflict_lines
//= require ./components/parallel_conflict_line
//= require ./components/parallel_conflict_lines
$(() => {
......@@ -49,7 +48,7 @@ $(() => {
mergeConflictsStore.setLoadingState(false);
this.$nextTick(() => {
$(conflictsEl.querySelectorAll('.js-syntax-highlight')).syntaxHighlight();
$('.js-syntax-highlight').syntaxHighlight();
});
});
},
......
......@@ -130,7 +130,7 @@
MergeRequestTabs.prototype.scrollToElement = function(container) {
var $el, navBarHeight;
if (window.location.hash) {
navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight();
navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + document.querySelector('.js-tabs-affix').offsetHeight;
$el = $(container + " " + window.location.hash + ":not(.match)");
if ($el.length) {
return $.scrollTo(container + " " + window.location.hash + ":not(.match)", {
......@@ -227,8 +227,8 @@
return function(data) {
$('#diffs').html(data.html);
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
......
......@@ -325,8 +325,8 @@
discussionContainer.append(note_html);
}
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
gl.utils.localTimeAgo($('.js-timeago', note_html), false);
......@@ -466,8 +466,8 @@
$note_li.replaceWith($html);
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
};
......@@ -559,11 +559,9 @@
note = $(el);
notes = note.closest(".notes");
if (typeof DiffNotesApp !== "undefined" && DiffNotesApp !== null) {
ref = DiffNotesApp.$refs[noteId];
if (ref) {
ref.$destroy(true);
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
if (gl.diffNoteApps[noteId]) {
gl.diffNoteApps[noteId].$destroy();
}
}
......@@ -643,11 +641,12 @@
form.find('.js-note-target-close').remove();
this.setupNoteForm(form);
if (typeof DiffNotesApp !== 'undefined') {
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
var $commentBtn = form.find('comment-and-resolve-btn');
$commentBtn
.attr(':discussion-id', "'" + dataHolder.data('discussionId') + "'");
DiffNotesApp.$compile($commentBtn.get(0));
gl.diffNotesCompileComponents();
}
form.find(".js-note-text").focus();
......
......@@ -45,15 +45,15 @@
this.content.hide();
this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
this.collapsedContent.show();
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
} else if (this.content) {
this.collapsedContent.hide();
this.content.show();
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
} else {
this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
......@@ -76,8 +76,8 @@
}
_this.collapsedContent.after(_this.content);
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
if (cb) cb();
......
......@@ -71,7 +71,7 @@
display: none;
}
.group-right-buttons {
.group-buttons {
display: none;
}
......
......@@ -165,8 +165,12 @@
}
}
.board-list {
.board-list-component {
height: calc(100% - 49px);
}
.board-list {
height: 100%;
margin-bottom: 0;
padding: 5px;
list-style: none;
......@@ -174,7 +178,7 @@
overflow-x: hidden;
&.is-smaller {
height: calc(100% - 185px);
height: calc(100% - 136px);
}
}
......
......@@ -62,6 +62,8 @@
.ci-status-link {
display: inline-block;
position: relative;
top: 1px;
}
.btn-clipboard,
......
......@@ -15,7 +15,6 @@
}
.group-row {
.stats {
float: right;
line-height: $list-text-height;
......@@ -28,31 +27,14 @@
}
.ldap-group-links {
.form-actions {
margin-bottom: $gl-padding;
}
}
.groups-cover-block {
.container-fluid {
position: relative;
}
.group-right-buttons {
position: absolute;
right: 16px;
.btn {
@include btn-gray;
padding: 3px 10px;
background-color: $background-color;
}
}
.group-avatar {
border: 0;
.group-buttons {
.notification-dropdown {
display: inline-block;
}
}
......@@ -63,7 +45,6 @@
}
.groups-header {
@media (min-width: $screen-sm-min) {
.nav-links {
width: 35%;
......
......@@ -267,20 +267,6 @@
}
}
.issuable-header-btn {
background: $gray-normal;
border: 1px solid $border-gray-normal;
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
&.btn-primary {
@extend .btn-primary;
}
}
a {
&:hover {
color: $md-link-color;
......
......@@ -47,6 +47,7 @@
&.right {
float: right;
padding-right: 0;
a {
color: $gl-gray;
......
......@@ -86,7 +86,8 @@
}
}
.project-home-panel {
.project-home-panel,
.group-home-panel {
padding-top: 24px;
padding-bottom: 24px;
......@@ -94,7 +95,8 @@
border-bottom: 1px solid $border-color;
}
.project-avatar {
.project-avatar,
.group-avatar {
float: none;
margin: 0 auto;
border: none;
......@@ -104,7 +106,8 @@
}
}
.project-title {
.project-title,
.group-title {
margin-top: 10px;
margin-bottom: 10px;
font-size: 24px;
......@@ -118,10 +121,11 @@
}
}
.project-home-desc {
.project-home-desc,
.group-home-desc {
margin-left: auto;
margin-right: auto;
margin-bottom: 15px;
margin-bottom: 0;
max-width: 700px;
> p {
......@@ -141,13 +145,18 @@
}
}
.project-repo-buttons {
font-size: 0;
.project-repo-buttons,
.group-buttons {
margin-top: 15px;
.btn {
@include btn-gray;
padding: 3px 10px;
&:last-child {
margin-left: 0;
}
.fa {
color: $layout-link-gray;
}
......@@ -168,7 +177,8 @@
}
}
.project-repo-btn-group,
.download-button,
.dropdown-toggle,
.notification-dropdown,
.project-dropdown {
margin-left: 10px;
......@@ -474,9 +484,7 @@ a.deploy-project-label {
margin-right: $gl-padding;
}
&.project-repo-buttons-right {
margin-top: 10px;
&.right {
@media (min-width: $screen-md-min) {
float: right;
margin-top: 0;
......
......@@ -133,7 +133,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:elasticsearch_port,
:usage_ping_enabled,
:enabled_git_access_protocol,
<<<<<<< HEAD
:repository_size_limit,
=======
:sidekiq_throttling_enabled,
:sidekiq_throttling_factor,
>>>>>>> ce/master
:housekeeping_enabled,
:housekeeping_bitmaps_enabled,
:housekeeping_incremental_repack_period,
......@@ -142,7 +147,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
repository_storages: [],
restricted_visibility_levels: [],
import_sources: [],
disabled_oauth_sign_in_sources: []
disabled_oauth_sign_in_sources: [],
sidekiq_throttling_queues: []
)
end
end
......@@ -3,7 +3,7 @@ module DiffForPath
def render_diff_for_path(diffs)
diff_file = diffs.diff_files.find do |diff|
diff.old_path == params[:old_path] && diff.new_path == params[:new_path]
diff.file_identifier == params[:file_identifier]
end
return render_404 unless diff_file
......
......@@ -58,7 +58,7 @@ class Groups::MilestonesController < Groups::ApplicationController
def render_new_with_error(empty_project_ids)
@milestone = Milestone.new(milestone_params)
@milestone.errors.add(:project_id, "Please select at least one project.") if empty_project_ids
@milestone.errors.add(:base, "Please select at least one project.") if empty_project_ids
render :new
end
......
......@@ -546,6 +546,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.close
end
labels
define_pipelines_vars
end
......
......@@ -18,7 +18,9 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def create
@pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute(ignore_skip_ci: true, save_on_errors: false)
@pipeline = Ci::CreatePipelineService
.new(project, current_user, create_params)
.execute(ignore_skip_ci: true, save_on_errors: false)
unless @pipeline.persisted?
render 'new'
return
......
......@@ -100,4 +100,8 @@ module ApplicationSettingsHelper
options_for_select(options, @application_setting.repository_storages)
end
def sidekiq_queue_options_for_select
options_for_select(Sidekiq::Queue.all.map(&:name), @application_setting.sidekiq_throttling_queues)
end
end
......@@ -49,7 +49,7 @@ module ProjectsHelper
end
end
def project_title(project, name = nil, url = nil)
def project_title(project)
namespace_link =
if project.group
link_to(simple_sanitize(project.group.name), group_path(project.group))
......@@ -66,10 +66,7 @@ module ProjectsHelper
end
end
full_title = "#{namespace_link} / #{project_link}".html_safe
full_title << ' &middot; '.html_safe << link_to(simple_sanitize(name), url) if name
full_title
"#{namespace_link} / #{project_link}".html_safe
end
def remove_project_message(project)
......
......@@ -19,6 +19,7 @@ class ApplicationSetting < ActiveRecord::Base
serialize :domain_whitelist, Array
serialize :domain_blacklist, Array
serialize :repository_storages
serialize :sidekiq_throttling_queues, Array
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
......@@ -97,6 +98,15 @@ class ApplicationSetting < ActiveRecord::Base
presence: { message: 'Domain blacklist cannot be empty if Blacklist is enabled.' },
if: :domain_blacklist_enabled?
validates :sidekiq_throttling_factor,
numericality: { greater_than: 0, less_than: 1 },
presence: { message: 'Throttling factor cannot be empty if Sidekiq Throttling is enabled.' },
if: :sidekiq_throttling_enabled?
validates :sidekiq_throttling_queues,
presence: { message: 'Queues to throttle cannot be empty if Sidekiq Throttling is enabled.' },
if: :sidekiq_throttling_enabled?
validates :housekeeping_incremental_repack_period,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
......@@ -195,6 +205,7 @@ class ApplicationSetting < ActiveRecord::Base
usage_ping_enabled: true,
repository_storages: ['default'],
user_default_external: false,
sidekiq_throttling_enabled: false,
housekeeping_enabled: true,
housekeeping_bitmaps_enabled: true,
housekeeping_incremental_repack_period: 10,
......
......@@ -1608,6 +1608,10 @@ class Project < ActiveRecord::Base
end
end
def only_allow_merge_if_all_discussions_are_resolved
super || false
end
private
def pushes_since_gc_redis_key
......
......@@ -95,15 +95,17 @@ class Repository
def commit(ref = 'HEAD')
return nil unless exists?
commit =
if ref.is_a?(Gitlab::Git::Commit)
ref
else
Gitlab::Git::Commit.find(raw_repository, ref)
end
commit = ::Commit.new(commit, @project) if commit
commit
rescue Rugged::OdbError
rescue Rugged::OdbError, Rugged::TreeError
nil
end
......@@ -289,6 +291,8 @@ class Repository
def ref_exists?(ref)
rugged.references.exist?(ref)
rescue Rugged::ReferenceError
false
end
def update_ref!(name, newrev, oldrev)
......@@ -296,7 +300,7 @@ class Repository
# offer 'compare and swap' ref updates. Without compare-and-swap we can
# (and have!) accidentally reset the ref to an earlier state, clobbering
# commits. See also https://github.com/libgit2/libgit2/issues/1534.
command = %w[git update-ref --stdin -z]
command = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
_, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin|
stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00")
end
......@@ -327,11 +331,7 @@ class Repository
end
def kept_around?(sha)
begin
ref_exists?(keep_around_ref_name(sha))
rescue Rugged::ReferenceError
false
end
ref_exists?(keep_around_ref_name(sha))
end
def tag_names
......
......@@ -304,6 +304,31 @@
The amount of points to store in a single UDP packet. More points
results in fewer but larger UDP packets being sent.
%fieldset
%legend Background Jobs
%p
These settings require a restart to take effect.
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :sidekiq_throttling_enabled do
= f.check_box :sidekiq_throttling_enabled
Enable Sidekiq Job Throttling
.help-block
Limit the amount of resources slow running jobs are assigned.
.form-group
= f.label :sidekiq_throttling_queues, 'Sidekiq queues to throttle', class: 'control-label col-sm-2'
.col-sm-10
= f.select :sidekiq_throttling_queues, sidekiq_queue_options_for_select, { include_hidden: false }, multiple: true, class: 'select2 select-wide', data: { field: 'sidekiq_throttling_queues' }
.help-block
Choose which queues you wish to throttle.
.form-group
= f.label :sidekiq_throttling_factor, 'Throttling Factor', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :sidekiq_throttling_factor, class: 'form-control', min: '0.01', max: '0.99', step: '0.01'
.help-block
The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive.
%fieldset
%legend Spam and Anti-bot Protection
.form-group
......
......@@ -11,3 +11,6 @@
- if signin_enabled?
%li
= link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab'
- if signin_enabled? && signup_enabled?
%li
= link_to 'Register', '#register-pane', 'data-toggle' => 'tab'
......@@ -34,7 +34,7 @@
= f.label :projects, "Projects", class: "control-label"
.col-sm-10
= f.collection_select :project_ids, @group.projects.non_archived, :id, :name,
{ selected: @group.projects.non_archived.pluck(:id) }, multiple: true, class: 'select2'
{ selected: @group.projects.non_archived.pluck(:id) }, required: true, multiple: true, class: 'select2'
.col-md-6
.form-group
......
......@@ -4,25 +4,23 @@
- if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
.cover-block.groups-cover-block
.group-home-panel.text-center
%div{ class: container_class }
.avatar-container.s70.group-avatar
= image_tag group_icon(@group), class: "avatar s70 avatar-tile"
.group-info
.cover-title
%h1
@#{@group.path}
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, fw: false)
%h1.group-title
@#{@group.path}
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, fw: false)
.group-right-buttons.btn-group
- if current_user
.pull-left.append-right-10= render 'shared/members/access_request_buttons', source: @group
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @group.description.present?
.group-home-desc
= markdown_field(@group, :description)
- if @group.description.present?
.cover-desc.description
= markdown_field(@group, :description)
- if current_user
.group-buttons
= render 'shared/members/access_request_buttons', source: @group
= render 'shared/notifications/button', notification_setting: @notification_setting
%div.groups-header{ class: container_class }
.top-area
......
......@@ -14,7 +14,7 @@
- if can_admin_group
= nav_link(path: 'groups#projects') do
= link_to 'Projects', projects_group_path(@group), title: 'Projects'
- if can_edit || can_leave
- if (can_edit || can_leave) && can_admin_group
%li.divider
- if can_edit
- if ldap_enabled?
......
- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
- header_title project_title(@project, "Builds", project_builds_path(@project))
.top-block.row-content-block.clearfix
.pull-right
......
- @no_container = true
- @content_class = "issue-boards-content"
- page_title "Boards"
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('boards/boards_bundle.js')
= page_specific_javascript_tag('boards/test_utils/simulate_drag.js') if Rails.env.test?
%script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
%script#js-board-list-template{ type: "text/x-template" }= render "projects/boards/components/board_list"
%script#js-board-list-card{ type: "text/x-template" }= render "projects/boards/components/card"
= render "projects/issues/head"
= render 'shared/issuable/filter', type: :boards
#board-app.boards-app{ "v-cloak" => true, data: board_data }
.boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
%board{ "v-cloak" => true,
"v-for" => "list in state.lists",
"ref" => "board",
":list" => "list",
":disabled" => "disabled",
":issue-link-base" => "issueLinkBase",
":key" => "_uid" }
= render "projects/boards/components/sidebar"
%board-blank-state{ "inline-template" => true,
"v-if" => "list.id == 'blank'" }
"v-if" => 'list.id == "blank"' }
.board-blank-state
%p
Add the following default lists to your Issue Board with one click:
......
%board{ "inline-template" => true,
"v-cloak" => true,
"v-for" => "list in state.lists | orderBy 'position'",
"v-ref:board" => true,
":list" => "list",
":disabled" => "disabled",
":issue-link-base" => "issueLinkBase",
"track-by" => "_uid" }
.board{ ":class" => "{ 'is-draggable': !list.preset }",
":data-id" => "list.id" }
.board-inner
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
%span.has-tooltip{ ":title" => "(list.label ? list.label.description : '')",
data: { container: "body", placement: "bottom" } }
{{ list.title }}
.board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" }
%span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" }
{{ list.issuesSize }}
- if can?(current_user, :admin_issue, @project)
%button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button",
"@click" => "showNewIssueForm",
"v-if" => "list.type !== 'done'",
"aria-label" => "Add an issue",
"title" => "Add an issue",
data: { placement: "top", container: "body" } }
= icon("plus")
- if can?(current_user, :admin_list, @project)
%board-delete{ "inline-template" => true,
":list" => "list",
"v-if" => "!list.preset && list.id" }
%button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
= icon("trash")
%board-list{ "inline-template" => true,
"v-if" => "list.type !== 'blank'",
":list" => "list",
":issues" => "list.issues",
":loading" => "list.loading",
":disabled" => "disabled",
":show-issue-form.sync" => "showIssueForm",
":issue-link-base" => "issueLinkBase" }
.board-list-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
- if can? current_user, :create_issue, @project
%board-new-issue{ "inline-template" => true,
.board{ ":class" => '{ "is-draggable": !list.preset }',
":data-id" => "list.id" }
.board-inner
%header.board-header{ ":class" => '{ "has-border": list.label }', ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
%h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' }
%span.has-tooltip{ ":title" => '(list.label ? list.label.description : "")',
data: { container: "body", placement: "bottom" } }
{{ list.title }}
.board-issue-count-holder.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' }
%span.board-issue-count.pull-left{ ":class" => '{ "has-btn": list.type !== "done" }' }
{{ list.issuesSize }}
- if can?(current_user, :admin_issue, @project)
%button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button",
"@click" => "showNewIssueForm",
"v-if" => 'list.type !== "done"',
"aria-label" => "Add an issue",
"title" => "Add an issue",
data: { placement: "top", container: "body" } }
= icon("plus")
- if can?(current_user, :admin_list, @project)
%board-delete{ "inline-template" => true,
":list" => "list",
":show-issue-form.sync" => "showIssueForm",
"v-show" => "list.type !== 'done' && showIssueForm" }
.card.board-new-issue-form
%form{ "@submit" => "submit($event)" }
.flash-container{ "v-if" => "error" }
.flash-alert
An error occured. Please try again.
%label.label-light{ ":for" => "list.id + '-title'" }
Title
%input.form-control{ type: "text",
"v-model" => "title",
"v-el:input" => true,
":id" => "list.id + '-title'" }
.clearfix.prepend-top-10
%button.btn.btn-success.pull-left{ type: "submit",
":disabled" => "title === ''",
"v-el:submit-button" => true }
Submit issue
%button.btn.btn-default.pull-right{ type: "button",
"@click" => "cancel" }
Cancel
%ul.board-list{ "v-el:list" => true,
"v-show" => "!loading",
":data-board" => "list.id",
":class" => "{ 'is-smaller': showIssueForm }" }
= render "projects/boards/components/card"
%li.board-list-count.text-center{ "v-if" => "showCount" }
= icon("spinner spin", "v-show" => "list.loadingMore" )
%span{ "v-if" => "list.issues.length === list.issuesSize" }
Showing all issues
%span{ "v-else" => true }
Showing {{ list.issues.length }} of {{ list.issuesSize }} issues
- if can?(current_user, :admin_list, @project)
= render "projects/boards/components/blank_state"
"v-if" => "!list.preset && list.id" }
%button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
= icon("trash")
%board-list{ "v-if" => 'list.type !== "blank"',
":list" => "list",
":issues" => "list.issues",
":loading" => "list.loading",
":disabled" => "disabled",
":issue-link-base" => "issueLinkBase",
"ref" => "board-list" }
- if can?(current_user, :admin_list, @project)
= render "projects/boards/components/blank_state"
.board-list-component
.board-list-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
- if can? current_user, :create_issue, @project
%board-new-issue{ "inline-template" => true,
":list" => "list",
"v-if" => 'list.type !== "done" && showIssueForm' }
.card.board-new-issue-form
%form{ "@submit" => "submit($event)" }
.flash-container{ "v-if" => "error" }
.flash-alert
An error occured. Please try again.
%label.label-light{ ":for" => 'list.id + "-title"' }
Title
%input.form-control{ type: "text",
"v-model" => "title",
"ref" => "input",
":id" => 'list.id + "-title"' }
.clearfix.prepend-top-10
%button.btn.btn-success.pull-left{ type: "submit",
":disabled" => 'title === ""',
"ref" => "submit-button" }
Submit issue
%button.btn.btn-default.pull-right{ type: "button",
"@click" => "cancel" }
Cancel
%ul.board-list{ "ref" => "list",
"v-show" => "!loading",
":data-board" => "list.id",
":class" => '{ "is-smaller": showIssueForm }' }
%board-card{ "v-for" => "(issue, index) in orderedIssues",
"ref" => "issue",
":index" => "index",
":list" => "list",
":issue" => "issue",
":issue-link-base" => "issueLinkBase",
":disabled" => "disabled",
"key" => "id" }
%li.board-list-count.text-center{ "v-if" => "showCount" }
= icon("spinner spin", "v-show" => "list.loadingMore" )
%span{ "v-if" => "list.issues.length === list.issuesSize" }
Showing all issues
%span{ "v-else" => true }
Showing {{ list.issues.length }} of {{ list.issuesSize }} issues
%board-card{ "inline-template" => true,
"v-for" => "issue in issues | orderBy 'priority'",
"v-ref:issue" => true,
":index" => "$index",
":list" => "list",
":issue" => "issue",
":issue-link-base" => "issueLinkBase",
":disabled" => "disabled",
"track-by" => "id" }
%li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }",
":index" => "index",
"@mousedown" => "mouseDown",
"@mouseMove" => "mouseMove",
"@mouseup" => "showIssue($event)" }
%h4.card-title
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
%a{ ":href" => "issueLinkBase + '/' + issue.id",
":title" => "issue.title" }
{{ issue.title }}
.card-footer
%span.card-number{ "v-if" => "issue.id" }
= precede '#' do
{{ issue.id }}
%a.has-tooltip{ ":href" => "'#{root_path}' + issue.assignee.username",
":title" => "'Assigned to ' + issue.assignee.name",
"v-if" => "issue.assignee",
data: { container: 'body' } }
%img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20 }
%button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels",
type: "button",
"v-if" => "(!list.label || label.id !== list.label.id)",
"@click" => "filterByLabel(label, $event)",
":style" => "{ backgroundColor: label.color, color: label.textColor }",
":title" => "label.description",
data: { container: 'body' } }
{{ label.title }}
%li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }',
":index" => "index",
"@mousedown" => "mouseDown",
"@mouseup" => "showIssue($event)" }
%h4.card-title
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
%a{ ":href" => 'issueLinkBase + "/" + issue.id',
":title" => "issue.title" }
{{ issue.title }}
.card-footer
%span.card-number{ "v-if" => "issue.id" }
= precede '#' do
{{ issue.id }}
%a.has-tooltip{ ":href" => "\"#{root_path}\" + issue.assignee.username",
":title" => '"Assigned to " + issue.assignee.name',
"v-if" => "issue.assignee",
data: { container: 'body' } }
%img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20 }
%button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels",
type: "button",
"v-if" => "(!list.label || label.id !== list.label.id)",
"@click" => "filterByLabel(label, $event)",
":style" => "{ backgroundColor: label.color, color: label.textColor }",
":title" => "label.description",
data: { container: 'body' } }
{{ label.title }}
<<<<<<< HEAD
- @no_container = true
- @content_class = "issue-boards-content"
- page_title "Boards"
......@@ -17,3 +18,6 @@
= icon("spinner spin")
= render "projects/boards/components/board"
= render "projects/boards/components/sidebar"
=======
= render "show"
>>>>>>> ce/master
<<<<<<< HEAD
- @no_container = true
- @content_class = "issue-boards-content"
- page_title "Boards"
......@@ -17,3 +18,6 @@
= icon("spinner spin")
= render "projects/boards/components/board"
= render "projects/boards/components/sidebar"
=======
= render "show"
>>>>>>> ce/master
- @no_container = true
- page_title "#{@build.name} (##{@build.id})", "Builds"
- header_title project_title(@project, "Builds", project_builds_path(@project))
- trace_with_state = @build.trace_with_state
= render "projects/pipelines/head", build_subnav: true
%div{ class: container_class }
......
- if current_user
.dropdown.inline.project-dropdown
.dropdown.inline
%a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
= icon("caret-down")
......
.pipeline-graph-container
.row-content-block.build-content.middle-block.pipeline-actions
.pull-right
.btn.btn-grouped.btn-white.toggle-pipeline-btn
%button.btn.btn-grouped.btn-white.toggle-pipeline-btn
%span.toggle-btn-text Hide
%span pipeline graph
%span.caret
......
......@@ -6,7 +6,7 @@
- note_count = notes.user.count
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
- cache_key.push(commit.status) if commit.status
- cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
......
......@@ -9,7 +9,7 @@
- if !project.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry.
- elsif diff_file.collapsed?
- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path))
- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
.nothing-here-block.diff-collapsed{data: { diff_for_path: url } }
This diff is collapsed.
%a.click-to-expand
......
- header_title project_title(@project, "Environments", project_environments_path(@project))
- page_title "Import in progress"
- page_title @project.forked? ? "Forking in progress" : "Import in progress"
.save-project-loader
.center
%h2
......
......@@ -74,14 +74,15 @@
%span.badge= @merge_request.diff_size
%li#resolve-count-app.line-resolve-all-container.pull-right.prepend-top-10.hidden-xs{ "v-cloak" => true }
%resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
.line-resolve-all{ "v-show" => "discussionCount > 0",
":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
%span.line-resolve-btn.is-disabled{ type: "button",
":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
= render "shared/icons/icon_status_success.svg"
%span.line-resolve-text
{{ resolvedDiscussionCount }}/{{ discussionCount }} {{ discussionCount | pluralize 'discussion' }} resolved
= render "discussions/jump_to_next"
%div
.line-resolve-all{ "v-show" => "discussionCount > 0",
":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
%span.line-resolve-btn.is-disabled{ type: "button",
":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
= render "shared/icons/icon_status_success.svg"
%span.line-resolve-text
{{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
= render "discussions/jump_to_next"
.tab-content#diff-notes-app
#notes.notes.tab-pane.voting_notes
......
......@@ -30,11 +30,8 @@
.diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
= render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines"
.diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
= render partial: "projects/merge_requests/conflicts/components/parallel_conflict_lines"
%parallel-conflict-lines{ ":file" => "file" }
%div{"v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'"}
= render partial: "projects/merge_requests/conflicts/components/diff_file_editor"
= render partial: "projects/merge_requests/conflicts/submit_form"
-# Components
= render partial: 'projects/merge_requests/conflicts/components/parallel_conflict_line'
......@@ -5,11 +5,10 @@
%a {{line.new_line}}
%td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
%a {{line.old_line}}
%td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
{{{line.richText}}}
%td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader", "v-html" => "line.richText"}
%td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%strong {{{line.richText}}}
%strong{"v-html" => "line.richText"}
%button.btn{ "@click" => "handleSelected(file, line.id, line.section)" }
{{line.buttonTitle}}
%script{"id" => 'parallel-conflict-line', "type" => "text/x-template"}
%td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"}
%strong {{line.richText}}
%button.btn{"@click" => "handleSelected(file, line.id, line.section)"}
{{line.buttonTitle}}
%td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
{{line.lineNumber}}
%td.line_content.parallel{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"}
{{{line.richText}}}
%parallel-conflict-lines{"inline-template" => "true", ":file" => "file"}
%table
%tr.line_holder.parallel{"v-for" => "section in file.parallelLines"}
%td{"is"=>"parallel-conflict-line", "v-for" => "line in section", ":line" => "line", ":file" => "file"}
......@@ -32,7 +32,7 @@
"resolved-by" => "#{note.resolved_by.try(:name)}",
"v-show" => "#{can_resolve || note.resolved?}",
"inline-template" => true,
"v-ref:note_#{note.id}" => true }
"ref" => "note_#{note.id}" }
.note-action-button
= icon("spin spinner", "v-show" => "loading")
......@@ -43,7 +43,7 @@
"@click" => "resolve",
":title" => "buttonText",
"v-show" => "!loading",
"v-el:button" => true }
":ref" => "'button'" }
= render "shared/icons/icon_status_success.svg"
......
......@@ -68,8 +68,8 @@
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set Up CI
%li.project-repo-buttons-right
.project-repo-buttons.project-right-buttons
%li.project-repo-buttons.right
.project-right-buttons
- if current_user
= render 'shared/members/access_request_buttons', source: @project
= render "projects/buttons/koding"
......@@ -78,7 +78,8 @@
= render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting
.pull-right
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit
.project-last-commit{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
......
<svg width="20" height="20" class="ci-status-icon-skipped" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Group Copy 31</title><g fill="#5C5C5C" fill-rule="evenodd"><path d="M10 17.857c4.286 0 7.857-3.571 7.857-7.857S14.286 2.143 10 2.143 2.143 5.714 2.143 10 5.714 17.857 10 17.857M10 0c5.571 0 10 4.429 10 10s-4.429 10-10 10S0 15.571 0 10 4.429 0 10 0"/><path d="M10.986 11l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.644-2.644a1.505 1.505 0 0 0 0-2.126l-2.644-2.644a1 1 0 0 0-1.414 1.414L10.986 9H6.4a1 1 0 0 0 0 2h4.586z"/></g></svg>
<svg width="14" height="14" class="ci-status-icon-skipped" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Group Copy 31</title><g fill="#5C5C5C" fill-rule="evenodd"><path d="M10 17.857c4.286 0 7.857-3.571 7.857-7.857S14.286 2.143 10 2.143 2.143 5.714 2.143 10 5.714 17.857 10 17.857M10 0c5.571 0 10 4.429 10 10s-4.429 10-10 10S0 15.571 0 10 4.429 0 10 0"/><path d="M10.986 11l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.644-2.644a1.505 1.505 0 0 0 0-2.126l-2.644-2.644a1 1 0 0 0-1.414 1.414L10.986 9H6.4a1 1 0 0 0 0 2h4.586z"/></g></svg>
- left_align = local_assigns[:left_align]
- if notification_setting
.dropdown.notification-dropdown.pull-right
.dropdown.notification-dropdown
= form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
= hidden_setting_source_input(notification_setting)
= f.hidden_field :level, class: "notification_setting_level"
......
---
title: Fix project records with invalid visibility_level values
merge_request: 7391
author:
---
title: Fix no "Register" tab if ldap auth is enabled (#24038)
merge_request: 7274
author: Luc Didry
---
title: "[Fix] Extra divider issue in dropdown"
merge_request: 7398
author:
---
title: Removed gray button styling from todo buttons in sidebars
merge_request: 7387
author:
---
title: Remove additional padding on right-aligned items in MR widget.
merge_request: 7411
author: Didem Acet
---
title: Fix issue causing Labels not to appear in sidebar on MR page
merge_request: 7416
author: Alex Sanford
---
title: Fix expanding a collapsed diff when converting a symlink to a regular file
merge_request: 6953
author:
---
title: Add api endpoint `/groups/owned`
merge_request: 7103
author: Borja Aparicio
---
title: Fix cache for commit status in commits list to respect branches
merge_request: 7372
author:
---
title: Fix error when using invalid branch name when creating a new pipeline
merge_request: 7324
author:
---
title: Use 'Forking in progress' title when appropriate
merge_request: 7394
author: Philip Karpiak
---
title: Require projects before creating milestone.
merge_request: 7301
author: gfyoung
---
title: Added ability to throttle Sidekiq Jobs
merge_request: 7292
author:
......@@ -29,8 +29,12 @@ Sidekiq.configure_server do |config|
end
Sidekiq::Cron::Job.load_from_hash! cron_jobs
<<<<<<< HEAD
# Gitlab Geo: enable bulk notify job only on primary node
Gitlab::Geo.bulk_notify_job.disable! unless Gitlab::Geo.primary?
=======
Gitlab::SidekiqThrottler.execute!
>>>>>>> ce/master
# Database pool should be at least `sidekiq_concurrency` + 2
# For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md
......
......@@ -5,10 +5,7 @@ class OnlyAllowMergeIfAllDiscussionsAreResolved < ActiveRecord::Migration
disable_ddl_transaction!
def up
add_column_with_default(:projects,
:only_allow_merge_if_all_discussions_are_resolved,
:boolean,
default: false)
add_column :projects, :only_allow_merge_if_all_discussions_are_resolved, :boolean
end
def down
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddSidekiqThrottlingToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
add_column :application_settings, :sidekiq_throttling_enabled, :boolean, default: false
add_column :application_settings, :sidekiq_throttling_queues, :string
add_column :application_settings, :sidekiq_throttling_factor, :decimal
end
end
class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
BATCH_SIZE = 1000
DOWNTIME = false
# This migration is idempotent and there's no sense in throwing away the
# partial result if it's interrupted
disable_ddl_transaction!
def up
projects = Arel::Table.new(:projects)
namespaces = Arel::Table.new(:namespaces)
finder =
projects.
join(namespaces, Arel::Nodes::InnerJoin).
on(projects[:namespace_id].eq(namespaces[:id])).
where(projects[:visibility_level].gt(namespaces[:visibility_level])).
project(projects[:id]).
take(BATCH_SIZE)
# MySQL requires a derived table to perform this query
nested_finder =
projects.
from(finder.as("AS projects_inner")).
project(projects[:id])
valuer =
namespaces.
where(namespaces[:id].eq(projects[:namespace_id])).
project(namespaces[:visibility_level])
# Update matching rows until none remain. The finder contains a limit.
loop do
updater = Arel::UpdateManager.new(ActiveRecord::Base).
table(projects).
set(projects[:visibility_level] => Arel::Nodes::SqlLiteral.new("(#{valuer.to_sql})")).
where(projects[:id].in(nested_finder))
num_updated = connection.exec_update(updater.to_sql, self.class.name, [])
break if num_updated == 0
end
end
def down
# no-op
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20161106185620) do
ActiveRecord::Schema.define(version: 20161109150329) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -107,6 +107,9 @@ ActiveRecord::Schema.define(version: 20161106185620) do
t.text "help_page_text_html"
t.text "shared_runners_text_html"
t.text "after_sign_up_text_html"
t.boolean "sidekiq_throttling_enabled", default: false
t.string "sidekiq_throttling_queues"
t.decimal "sidekiq_throttling_factor"
t.boolean "housekeeping_enabled", default: true, null: false
t.boolean "housekeeping_bitmaps_enabled", default: true, null: false
t.integer "housekeeping_incremental_repack_period", default: 10, null: false
......@@ -1048,8 +1051,12 @@ ActiveRecord::Schema.define(version: 20161106185620) do
t.boolean "repository_read_only"
t.boolean "lfs_enabled"
t.text "description_html"
<<<<<<< HEAD
t.integer "repository_size_limit"
t.boolean "only_allow_merge_if_all_discussions_are_resolved", default: false, null: false
=======
t.boolean "only_allow_merge_if_all_discussions_are_resolved"
>>>>>>> ce/master
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......
# GitLab operations
- [Sidekiq MemoryKiller](operations/sidekiq_memory_killer.md)
- [Sidekiq Job throttling](operations/sidekiq_job_throttling.md)
- [Cleaning up Redis sessions](operations/cleaning_up_redis_sessions.md)
- [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md)
- [Moving repositories to a new location](operations/moving_repositories.md)
# Sidekiq Job throttling
> Note: Introduced with GitLab 8.14
When your GitLab installation needs to handle tens of thousands of background
jobs, it can be convenient to throttle queues that do not need to be executed
immediately, e.g. long running jobs like Pipelines, thus allowing jobs that do
need to be executed immediately to have access to more resources.
In order to accomplish this, you can limit the amount of workers that certain
slow running queues can have available. This is what we call Sidekiq Job
Throttling. Depending on your infrastructure, you might have different slow
running queues, which is why you can choose which queues you want to throttle
and by how much you want to throttle them.
These settings are available in the Application Settings of your GitLab
installation.
![Sidekiq Job Throttling](img/sidekiq_job_throttling.png)
The throttle factor determines the maximum number of workers a queue can run on.
This value gets multiplied by `:concurrency` value set in the Sidekiq settings
and rounded up to the closest full integer.
So, for example, you set the `:concurrency` to 25 and the `Throttling factor` to
0.1, the maximum workers assigned to the selected queues would be 3.
```ruby
queue_limit = (factor * Sidekiq.options[:concurrency]).ceil
```
After enabling the job throttling, you will need to restart your GitLab
instance, in order for the changes to take effect.
\ No newline at end of file
......@@ -26,6 +26,15 @@ GET /groups
You can search for groups by name or path, see below.
=======
## List owned groups
Get a list of groups which are owned by the authenticated user.
```
GET /groups/owned
```
## List a group's projects
Get a list of projects in this group.
......
......@@ -14,7 +14,7 @@
contributing to documentation.
- [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations
- [Testing standards and style guidelines](testing.md)
- [UI guide](ui_guide.md) for building GitLab with existing CSS styles and elements
- [UX guide](ux_guide/README.md) for building GitLab with existing CSS styles and elements
- [Frontend guidelines](frontend.md)
- [SQL guidelines](sql.md) for working with SQL queries
- [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers
......
# Basics
## Contents
* [Responsive](#responsive)
* [Typography](#typography)
* [Icons](#icons)
* [Color](#color)
* [Motion](#motion)
* [Voice and tone](#voice-and-tone)
---
## Responsive
GitLab is a responsive experience that works well across all screen sizes, from mobile devices to large monitors. In order to provide a great user experience, the core functionality (browsing files, creating issues, writing comments, etc.) must be available at all resolutions. However, due to size limitations, some secondary functionality may be hidden on smaller screens. Please keep this functionality limited to rare actions that aren't expected to be needed on small devices.
---
## Typography
### Primary typeface
GitLab's main typeface used throughout the UI is **Source Sans Pro**. We support both the bold and regular weight.
![Source Sans Pro sample](img/sourcesanspro-sample.png)
### Monospace typeface
This is the typeface used for code blocks. GitLab uses the OS default font.
- **Menlo** (Mac)
- **Consolas** (Windows)
- **Liberation Mono** (Linux)
![Monospace font sample](img/monospacefont-sample.png)
---
## Icons
GitLab uses Font Awesome icons throughout our interface.
![Trash icon](img/icon-trash.png)
The trash icon is used for destructive actions that deletes information.
![Edit icon](img/icon-edit.png)
The pencil icon is used for editing content such as comments.
![Notification icon](img/icon-notification.png)
The bell icon is for notifications, such as Todos.
![Subscribe icon](img/icon-subscribe.png)
The eye icon is for subscribing to updates. For example, you can subscribe to a label and get updated on issues with that label.
![RSS icon](img/icon-rss.png)
The standard RSS icon is used for linking to RSS/atom feeds.
![Close icon](img/icon-close.png)
An 'x' is used for closing UI elements such as dropdowns.
![Add icon](img/icon-add.png)
A plus is used when creating new objects, such as issues, projects, etc.
> TODO: update this section, add more general guidance to icon usage and personality, etc.
---
## Color
![Blue](img/color-blue.png)
Blue is used to highlight primary active elements (such as current tab), as well as other organization and managing commands.
![Green](img/color-green.png)
Green is for actions that create new objects.
![Orange](img/color-orange.png)
Orange is used for warnings
![Red](img/color-red.png)
Red is reserved for delete and other destructive commands
![Grey](img/color-grey.png)
Grey, and white (depending on context) is used for netral, secondary elements
> TODO: Establish a perspective for color in terms of our personality and rationalize with Marketing usage.
---
## Motion
Motion is a tool to help convey important relationships, changes or transitions between elements. It should be used sparingly and intentionally, highlighting the right elements at the right moment.
> TODO: Determine a more concrete perspective on motion, create consistent easing/timing curves to follow.
---
## Voice and tone
The copy for GitLab is clear and direct. We strike a clear balance between professional and friendly. We can empathesize with users (such as celebrating completing all Todos), and remain respectful of the importance of the work. We are that trusted, friendly coworker that is helpful and understanding.
# Components
## Contents
* [Tooltips](#tooltips)
* [Anchor links](#anchor-links)
* [Buttons](#buttons)
* [Dropdowns](#dropdowns)
* [Counts](#counts)
* [Lists](#lists)
* [Tables](#tables)
* [Blocks](#blocks)
* [Panels](#panels)
* [Alerts](#alerts)
* [Forms](#forms)
* [File holders](#file-holders)
* [Data formats](#data-formats)
---
## Tooltips
### Usage
A tooltip should only be added if additional information is required.
![Tooltip usage](img/tooltip-usage.png)
### Placement
By default, tooltips should be placed below the element that they refer to. However, if there is not enough space in the viewpoint, the tooltip should be moved to the side as needed.
![Tooltip placement location](img/tooltip-placement.png)
---
## Anchor links
Anchor links are used for navigational actions and lone, secondary commands (such as 'Reset filters' on the Issues List) when deemed appropriate by the UX team.
### States
#### Rest
Primary links are blue in their rest state. Secondary links (such as the time stamp on comments) are a neutral gray color in rest. Details on the main GitLab navigation links can be found on the [features](features.md#navigation) page.
#### Hover
An underline should always be added on hover. A gray link becomes blue on hover.
#### Focus
The focus state should match the hover state.
![Anchor link states ](img/components-anchorlinks.png)
---
## Buttons
Buttons communicate the command that will occur when the user clicks on them.
### Types
#### Primary
Primary buttons communicate the main call to action. There should only be one call to action in any given experience. Visually, primary buttons are conveyed with a full background fill
![Primary button example](img/button-primary.png)
#### Secondary
Secondary buttons are for alternative commands. They should be conveyed by a button with an stroke, and no background fill.
![Secondary button example](img/button-secondary.png)
### Icon and text treatment
Text should be in sentence case, where only the first word is capitalized. "Create issue" is correct, not "Create Issue". Buttons should only contain an icon or a text, not both.
>>>
TODO: Rationalize this. Ensure that we still believe this.
>>>
### Colors
Follow the color guidance on the [basics](basics.md#color) page. The default color treatment is the white/grey button.
---
## Dropdowns
Dropdowns are used to allow users to choose one (or many) options from a list of options. If this list of options is more 20, there should generally be a way to search through and filter the options (see the complex filter dropdowns below.)
>>>
TODO: Will update this section when the new filters UI is implemented.
>>>
![Dropdown states](img/components-dropdown.png)
---
## Counts
A count element is used in navigation contexts where it is helpful to indicate the count, or number of items, in a list. Always use the [`number_with_delimiter`][number_with_delimiter] helper to display counts in the UI.
![Counts example](img/components-counts.png)
[number_with_delimiter]: http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_with_delimiter
---
## Lists
Lists are used where ever there is a single column of information to display. Ths [issues list](https://gitlab.com/gitlab-org/gitlab-ce/issues) is an example of a important list in the GitLab UI.
### Types
Simple list using .content-list
![Simple list](img/components-simplelist.png)
List with avatar, title and description using .content-list
![List with avatar](img/components-listwithavatar.png)
List with hover effect .well-list
![List with hover effect](img/components-listwithhover.png)
List inside panel
![List inside panel](img/components-listinsidepanel.png)
---
## Tables
When the information is too complex for a list, with multiple columns of information, a table can be used. For example, the [pipelines page](https://gitlab.com/gitlab-org/gitlab-ce/pipelines) uses a table.
![Table](img/components-table.png)
---
## Blocks
Blocks are a way to group related information.
### Types
#### Content blocks
Content blocks (`.content-block`) are the basic grouping of content. They are commonly used in [lists](#lists), and are separated by a botton border.
![Content block](img/components-contentblock.png)
#### Row content blocks
A background color can be added to this blocks. For example, items in the [issue list](https://gitlab.com/gitlab-org/gitlab-ce/issues) have a green background if they were created recently. Below is an example of a gray content block with side padding using `.row-content-block`.
![Row content block](img/components-rowcontentblock.png)
#### Cover blocks
Cover blocks are generally used to create a heading element for a page, such as a new project, or a user profile page. Below is a cover block (`.cover-block`) for the profile page with an avatar, name and description.
![Cover block](img/components-coverblock.png)
---
## Panels
>>>
TODO: Catalog how we are currently using panels and rationalize how they relate to alerts
>>>
![Panels](img/components-panels.png)
---
## Alerts
>>>
TODO: Catalog how we are currently using alerts
>>>
![Alerts](img/components-alerts.png)
---
## Forms
There are two options shown below regarding the positioning of labels in forms. Both are options to consider based on context and available size. However, it is important to have a consistent treatment of labels in the same form.
### Types
#### Labels stack vertically
Form (`form`) with label rendered above input.
![Vertical form](img/components-verticalform.png)
#### Labels side-by-side
Horizontal form (`form.horizontal-form`) with label rendered inline with input.
![Horizontal form](img/components-horizontalform.png)
---
## File holders
A file holder (`.file-holder`) is used to show the contents of a file inline on a page of GitLab.
![File Holder component](img/components-fileholder.png)
---
## Data formats
### Dates
#### Exact
Format for exacts dates should be ‘Mon DD, YYYY’, such as the examples below.
![Exact date](img/components-dateexact.png)
#### Relative
This format relates how long since an action has occurred. The exact date can be shown as a tooltip on hover.
![Relative date](img/components-daterelative.png)
### References
Referencing GitLab items depends on a symbol for each type of item. Typing that symbol will invoke a dropdown that allows you to search for and autocomplete the item you were looking for. References are shown as [links](#links) in context, and hovering on them shows the full title or name of the item.
![Hovering on a reference](img/components-referencehover.png)
#### `%` Milestones
![Milestone reference](img/components-referencemilestone.png)
#### `#` Issues
![Issue reference](img/components-referenceissues.png)
#### `!` Merge Requests
![Merge request reference](img/components-referencemrs.png)
#### `~` Labels
![Labels reference](img/components-referencelabels.png)
#### `@` People
![People reference](img/components-referencepeople.png)
> TODO: Open issue: Some commit references use monospace fonts, but others don't. Need to standardize this.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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