diff options
Diffstat (limited to 'app')
71 files changed, 955 insertions, 755 deletions
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 43a679501a7..fea6f41d5e9 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -174,9 +174,7 @@ $body.tooltip({ selector: '.has-tooltip, [data-toggle="tooltip"]', placement: function(_, el) { - var $el; - $el = $(el); - return $el.data('placement') || 'bottom'; + return $(el).data('placement') || 'bottom'; } }); $('.trigger-submit').on('change', function() { @@ -286,42 +284,9 @@ gl.awardsHandler = new AwardsHandler(); checkInitialSidebarSize(); new Aside(); - if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') { - $.cookie('pin_nav', 'false', { - path: gon.relative_url_root || '/', - expires: 365 * 10 - }); - $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned'); - $('.navbar-fixed-top').removeClass('header-pinned-nav'); - } - $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) { - var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText; - e.preventDefault(); - $pinBtn = $(e.currentTarget); - $page = $('.page-with-sidebar'); - $topNav = $('.navbar-fixed-top'); - $tooltip = $("#" + ($pinBtn.attr('aria-describedby'))); - doPinNav = !$page.is('.page-sidebar-pinned'); - tooltipText = 'Pin navigation'; - $(this).toggleClass('is-active'); - if (doPinNav) { - $page.addClass('page-sidebar-pinned'); - $topNav.addClass('header-pinned-nav'); - } else { - $tooltip.remove(); - $page.removeClass('page-sidebar-pinned').toggleClass('page-sidebar-collapsed page-sidebar-expanded'); - $topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded'); - } - $.cookie('pin_nav', doPinNav, { - path: gon.relative_url_root || '/', - expires: 365 * 10 - }); - if ($.cookie('pin_nav') === 'true' || doPinNav) { - tooltipText = 'Unpin navigation'; - } - $tooltip.find('.tooltip-inner').text(tooltipText); - return $pinBtn.attr('title', tooltipText).tooltip('fixTitle'); - }); + + // bind sidebar events + new gl.Sidebar(); // Custom time ago gl.utils.shortTimeAgo($('.js-short-timeago')); diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index ad12cb906e1..5ea18ea8b7a 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -255,12 +255,12 @@ }; AwardsHandler.prototype.animateEmoji = function($emoji) { - var className; - className = 'pulse animated'; + var className = 'pulse animated once short'; $emoji.addClass(className); - return setTimeout((function() { - return $emoji.removeClass(className); - }), 321); + + $emoji.on('webkitAnimationEnd animationEnd', function() { + $(this).removeClass(className); + }); }; AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) { diff --git a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 index f9f9f7999d4..b5ff3a81ed5 100644 --- a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 +++ b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 @@ -1,10 +1,7 @@ -Vue.http.interceptors.push((request, next) => { +Vue.http.interceptors.push((request, next) => { Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; - Vue.nextTick(() => { - setTimeout(() => { - Vue.activeResources--; - }, 500); + next(function (response) { + Vue.activeResources--; }); - next(); }); diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 0d7d29bb0d0..4d066f13646 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -54,12 +54,14 @@ } Build.prototype.getInitialBuildTrace = function() { + var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'] + return $.ajax({ url: this.build_url, dataType: 'json', success: function(build_data) { $('.js-build-output').html(build_data.trace_html); - if (build_data.status === 'success' || build_data.status === 'failed') { + if (removeRefreshStatuses.indexOf(build_data.status) >= 0) { return $('.js-build-refresh').remove(); } } diff --git a/app/assets/javascripts/build_variables.js.es6 b/app/assets/javascripts/build_variables.js.es6 new file mode 100644 index 00000000000..8d3e29794a1 --- /dev/null +++ b/app/assets/javascripts/build_variables.js.es6 @@ -0,0 +1,6 @@ +$(function(){ + $('.reveal-variables').off('click').on('click',function(){ + $('.js-build').toggle().niceScroll(); + $(this).hide(); + }); +}); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 38cdc7b9fba..179d3bc38a5 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -23,6 +23,7 @@ case 'projects:boards:show': shortcut_handler = new ShortcutsNavigation(); break; + case 'projects:merge_requests:index': case 'projects:issues:index': Issuable.init(); new IssuableBulkActions(); @@ -93,10 +94,6 @@ break; case "projects:merge_requests:conflicts": window.mcui = new MergeConflictResolver() - case 'projects:merge_requests:index': - shortcut_handler = new ShortcutsNavigation(); - Issuable.init(); - break; case 'dashboard:activity': new Activities(); break; diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js.es6 index d0305c6c6a1..82c14ef0157 100644 --- a/app/assets/javascripts/issuable.js +++ b/app/assets/javascripts/issuable.js.es6 @@ -8,6 +8,7 @@ Issuable.initTemplates(); Issuable.initSearch(); Issuable.initChecks(); + Issuable.initResetFilters(); return Issuable.initLabelFilterRemove(); }, initTemplates: function() { @@ -55,6 +56,17 @@ return Turbolinks.visit(issuesUrl); }; })(this), + initResetFilters: function() { + $('.reset-filters').on('click', function(e) { + e.preventDefault(); + const target = e.target; + const $form = $(target).parents('.js-filter-form'); + const baseIssuesUrl = target.href; + + $form.attr('action', baseIssuesUrl); + Turbolinks.visit(baseIssuesUrl); + }); + }, initChecks: function() { this.issuableBulkActions = $('.bulk-update').data('bulkActions'); $('.check_all_issues').off('click').on('click', function() { @@ -64,19 +76,22 @@ return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this)); }, checkChanged: function() { - var checked_issues, ids; - checked_issues = $('.selected_issue:checked'); - if (checked_issues.length > 0) { - ids = $.map(checked_issues, function(value) { + const $checkedIssues = $('.selected_issue:checked'); + const $updateIssuesIds = $('#update_issuable_ids'); + const $issuesOtherFilters = $('.issues-other-filters'); + const $issuesBulkUpdate = $('.issues_bulk_update'); + + if ($checkedIssues.length > 0) { + let ids = $.map($checkedIssues, function(value) { return $(value).data('id'); }); - $('#update_issues_ids').val(ids); - $('.issues-other-filters').hide(); - $('.issues_bulk_update').show(); + $updateIssuesIds.val(ids); + $issuesOtherFilters.hide(); + $issuesBulkUpdate.show(); } else { - $('#update_issues_ids').val([]); - $('.issues_bulk_update').hide(); - $('.issues-other-filters').show(); + $updateIssuesIds.val([]); + $issuesBulkUpdate.hide(); + $issuesOtherFilters.show(); this.issuableBulkActions.willUpdateLabels = false; } return true; diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js index 98d3358ba92..8ca90490426 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js +++ b/app/assets/javascripts/issues-bulk-assignment.js @@ -5,7 +5,7 @@ if (opts == null) { opts = {}; } - this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issues-list .issue'); + this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li'); this.form.data('bulkActions', this); this.willUpdateLabels = false; this.bindEvents(); @@ -106,7 +106,7 @@ state_event: this.form.find('input[name="update[state_event]"]').val(), assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), - issues_ids: this.form.find('input[name="update[issues_ids]"]').val(), + issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), add_label_ids: [], remove_label_ids: [] diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index d0d5cad813a..ebe9ff2b8c4 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -331,7 +331,12 @@ form.find(".js-md-write-button").click(); form.find(".js-note-text").val("").trigger("input"); form.find(".js-note-text").data("autosave").reset(); - return this.updateTargetButtons(e); + + var event = document.createEvent('Event'); + event.initEvent('autosize:update', true, false); + form.find('.js-autosize')[0].dispatchEvent(event); + + this.updateTargetButtons(e); }; Notes.prototype.reenableTargetFormSubmitButton = function() { diff --git a/app/assets/javascripts/sidebar.js b/app/assets/javascripts/sidebar.js deleted file mode 100644 index bd0c1194b36..00000000000 --- a/app/assets/javascripts/sidebar.js +++ /dev/null @@ -1,41 +0,0 @@ -(function() { - var collapsed, expanded, toggleSidebar; - - collapsed = 'page-sidebar-collapsed'; - - expanded = 'page-sidebar-expanded'; - - toggleSidebar = function() { - $('.page-with-sidebar').toggleClass(collapsed + " " + expanded); - $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded"); - if ($.cookie('pin_nav') === 'true') { - $('.navbar-fixed-top').toggleClass('header-pinned-nav'); - $('.page-with-sidebar').toggleClass('page-sidebar-pinned'); - } - return setTimeout((function() { - var niceScrollBars; - niceScrollBars = $('.nav-sidebar').niceScroll(); - return niceScrollBars.updateScrollBar(); - }), 300); - }; - - $(document).off('click', 'body').on('click', 'body', function(e) { - var $nav, $target, $toggle, pageExpanded; - if ($.cookie('pin_nav') !== 'true') { - $target = $(e.target); - $nav = $target.closest('.sidebar-wrapper'); - pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded'); - $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle'); - if ($nav.length === 0 && pageExpanded && $toggle.length === 0) { - $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded'); - return $('.navbar-fixed-top').toggleClass('header-collapsed header-expanded'); - } - } - }); - - $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', function(e) { - e.preventDefault(); - return toggleSidebar(); - }); - -}).call(this); diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6 new file mode 100644 index 00000000000..755fac8107b --- /dev/null +++ b/app/assets/javascripts/sidebar.js.es6 @@ -0,0 +1,93 @@ +((global) => { + let singleton; + + const pinnedStateCookie = 'pin_nav'; + const sidebarBreakpoint = 1024; + + const pageSelector = '.page-with-sidebar'; + const navbarSelector = '.navbar-fixed-top'; + const sidebarWrapperSelector = '.sidebar-wrapper'; + const sidebarContentSelector = '.nav-sidebar'; + + const pinnedToggleSelector = '.js-nav-pin'; + const sidebarToggleSelector = '.toggle-nav-collapse, .side-nav-toggle'; + + const pinnedPageClass = 'page-sidebar-pinned'; + const expandedPageClass = 'page-sidebar-expanded'; + + const pinnedNavbarClass = 'header-sidebar-pinned'; + const expandedNavbarClass = 'header-sidebar-expanded'; + + class Sidebar { + constructor() { + if (!singleton) { + singleton = this; + singleton.init(); + } + return singleton; + } + + init() { + this.isPinned = $.cookie(pinnedStateCookie) === 'true'; + this.isExpanded = ( + window.innerWidth >= sidebarBreakpoint && + $(pageSelector).hasClass(expandedPageClass) + ); + $(document) + .on('click', sidebarToggleSelector, () => this.toggleSidebar()) + .on('click', pinnedToggleSelector, () => this.togglePinnedState()) + .on('click', 'html, body', (e) => this.handleClickEvent(e)) + .on('page:change', () => this.renderState()); + this.renderState(); + } + + handleClickEvent(e) { + if (this.isExpanded && (!this.isPinned || window.innerWidth < sidebarBreakpoint)) { + const $target = $(e.target); + const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0; + const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0; + if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) { + this.toggleSidebar(); + } + } + } + + toggleSidebar() { + this.isExpanded = !this.isExpanded; + this.renderState(); + } + + togglePinnedState() { + this.isPinned = !this.isPinned; + if (!this.isPinned) { + this.isExpanded = false; + } + $.cookie(pinnedStateCookie, this.isPinned ? 'true' : 'false', { + path: gon.relative_url_root || '/', + expires: 3650 + }); + this.renderState(); + } + + renderState() { + $(pageSelector) + .toggleClass(pinnedPageClass, this.isPinned && this.isExpanded) + .toggleClass(expandedPageClass, this.isExpanded); + $(navbarSelector) + .toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded) + .toggleClass(expandedNavbarClass, this.isExpanded); + + const $pinnedToggle = $(pinnedToggleSelector); + const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation'; + const tooltipState = $pinnedToggle.attr('aria-describedby') && this.isExpanded ? 'show' : 'hide'; + $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState); + + if (this.isExpanded) { + setTimeout(() => $(sidebarContentSelector).niceScroll().updateScrollBar(), 200); + } + } + } + + global.Sidebar = Sidebar; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/snippets_list.js.es6 b/app/assets/javascripts/snippets_list.js.es6 new file mode 100644 index 00000000000..6f0996c0d2a --- /dev/null +++ b/app/assets/javascripts/snippets_list.js.es6 @@ -0,0 +1,11 @@ +(global => { + global.gl = global.gl || {}; + + gl.SnippetsList = function() { + var $holder = $('.snippets-list-holder'); + + $holder.find('.pagination').on('ajax:success', (e, data) => { + $holder.replaceWith(data.html); + }); + } +})(window); diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 74ecf4f4cf9..90cf551b32e 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -52,8 +52,22 @@ this.initTooltips(); } + // Add extra padding for the last month label if it is also the last column + Calendar.prototype.getExtraWidthPadding = function(group) { + var extraWidthPadding = 0; + var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth(); + var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth(); + + if (lastColMonth != secondLastColMonth) { + extraWidthPadding = 3; + } + + return extraWidthPadding; + } + Calendar.prototype.renderSvg = function(group) { - return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', (group + 1) * this.daySizeWithSpace).attr('height', 167).attr('class', 'contrib-calendar'); + var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); + return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar'); }; Calendar.prototype.renderDays = function() { diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 1fec61bdba1..1e9a45c19b8 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -8,65 +8,44 @@ // Copyright (c) 2016 Daniel Eden .animated { - -webkit-animation-duration: 1s; - animation-duration: 1s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} - -.animated.infinite { - -webkit-animation-iteration-count: infinite; - animation-iteration-count: infinite; -} + @include webkit-prefix(animation-duration, 1s); + @include webkit-prefix(animation-fill-mode, both); -.animated.hinge { - -webkit-animation-duration: 2s; - animation-duration: 2s; -} + &.infinite { + @include webkit-prefix(animation-iteration-count, infinite); + } -.animated.flipOutX, -.animated.flipOutY, -.animated.bounceIn, -.animated.bounceOut { - -webkit-animation-duration: .75s; - animation-duration: .75s; -} + &.once { + @include webkit-prefix(animation-iteration-count, 1); + } -@-webkit-keyframes pulse { - from { - -webkit-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); + &.hinge { + @include webkit-prefix(animation-duration, 2s); } - 50% { - -webkit-transform: scale3d(1.05, 1.05, 1.05); - transform: scale3d(1.05, 1.05, 1.05); + &.flipOutX, + &.flipOutY, + &.bounceIn, + &.bounceOut { + @include webkit-prefix(animation-duration, .75s); } - to { - -webkit-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); + &.short { + @include webkit-prefix(animation-duration, 321ms); + @include webkit-prefix(animation-fill-mode, none); } } -@keyframes pulse { - from { - -webkit-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); +@include keyframes(pulse) { + from, to { + @include webkit-prefix(transform, scale3d(1, 1, 1)); } 50% { - -webkit-transform: scale3d(1.05, 1.05, 1.05); - transform: scale3d(1.05, 1.05, 1.05); - } - - to { - -webkit-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); + @include webkit-prefix(transform, scale3d(1.05, 1.05, 1.05)); } } .pulse { - -webkit-animation-name: pulse; - animation-name: pulse; + @include webkit-prefix(animation-name, pulse); } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 7ce203d2ec7..f5223207f3a 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -249,6 +249,10 @@ > .controls { float: right; } + + .new-branch { + margin-top: 3px; + } } .content-block-small { diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index e3be45ba1dc..76a3c083697 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -63,7 +63,7 @@ &.image_file { background: #eee; text-align: center; - + img { padding: 20px; max-width: 80%; @@ -94,7 +94,6 @@ &.blame { table { border: none; - box-shadow: none; margin: 0; } tr { @@ -108,19 +107,10 @@ border-right: none; } } - img.avatar { - border: 0 none; - float: none; - margin: 0; - padding: 0; - } td.blame-commit { + padding: 0 10px; + min-width: 400px; background: $gray-light; - min-width: 350px; - - .commit-author-link { - color: #888; - } } td.line-numbers { float: none; @@ -133,12 +123,6 @@ } td.lines { padding: 0; - code { - font-family: $monospace_font; - } - pre { - margin: 0; - } } } diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 9209347f9bc..19827943385 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -1,6 +1,10 @@ .filter-item { margin-right: 6px; vertical-align: top; + + &.reset-filters { + padding: 7px; + } } @media (min-width: $screen-sm-min) { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 1036219172e..d4a030f7f7a 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -77,10 +77,6 @@ header { } } - &.header-collapsed { - padding: 0 16px; - } - .side-nav-toggle { position: absolute; left: -10px; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 00f92cef9a4..1ec08cdef23 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -85,3 +85,13 @@ #{'-webkit-' + $property}: $value; #{$property}: $value; } + +@mixin keyframes($animation-name) { + @-webkit-keyframes #{$animation-name} { + @content; + } + + @keyframes #{$animation-name} { + @content; + } +} diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 9efbaf54e90..553768b2e68 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -138,7 +138,7 @@ } li a { - padding: 16px 10px 11px; + padding: 16px 15px 11px; } /* Small devices (phones, tablets, 768px and lower) */ diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 015fe3debf9..3b7de4b57bb 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -1,6 +1,5 @@ .page-with-sidebar { - padding-top: $header-height; - padding-bottom: 25px; + padding: $header-height 0 25px; transition: padding $sidebar-transition-duration; &.page-sidebar-pinned { @@ -15,6 +14,7 @@ bottom: 0; left: 0; height: 100%; + width: 0; overflow: hidden; transition: width $sidebar-transition-duration; @include box-shadow(2px 0 16px 0 $black-transparent); @@ -128,10 +128,8 @@ .fa { transition: transform .15s; - } - &.is-active { - .fa { + .page-sidebar-pinned & { transform: rotate(90deg); } } @@ -152,14 +150,6 @@ } } -.page-sidebar-collapsed { - padding-left: 0; - - .sidebar-wrapper { - width: 0; - } -} - .page-sidebar-expanded { .sidebar-wrapper { width: $sidebar_width; @@ -175,7 +165,7 @@ } } -header.header-pinned-nav { +header.header-sidebar-pinned { @media (min-width: $sidebar-breakpoint) { padding-left: ($sidebar_width + $gl-padding); diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index e4a3ca3a9ea..9f563a4de35 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -101,6 +101,7 @@ $gl-icon-color: $gl-placeholder-color; $gl-grayish-blue: #7f8fa4; $gl-gray: $gl-text-color; $gl-gray-dark: #313236; +$gl-gray-light: $gl-placeholder-color; $gl-header-color: $gl-title-color; /* diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 5faedfedd66..9282e0ae03b 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -93,11 +93,8 @@ } .award-control { - margin-right: 5px; - margin-bottom: 5px; - padding-left: 5px; - padding-right: 5px; - line-height: 20px; + margin: 3px 5px 3px 0; + padding: 6px 5px; outline: 0; &:hover, diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 23255f34710..c879074c7fe 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -48,12 +48,6 @@ margin-bottom: 10px; } } - - .page-sidebar-collapsed { - .scroll-controls { - left: 70px; - } - } } .build-header { @@ -115,6 +109,16 @@ width: 100%; } + .js-build-variable { + color: $code-color; + } + + .js-build-value { + padding: 2px 4px; + color: $black; + background-color: $white-light; + } + .build-sidebar-header { padding: 0 $gl-padding $gl-padding; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index b369f5c0805..dc57a837155 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -18,8 +18,7 @@ } .commit-row-title { - line-height: 1; - margin-bottom: 7px; + line-height: 1.35; .notes_count { float: right; @@ -43,6 +42,7 @@ border: 1px solid $border-gray-dark; border-radius: $border-radius-default; margin-left: 5px; + line-height: 1; &:hover { background-color: darken($gray-light, 10%); @@ -136,7 +136,7 @@ .commit-row-info { color: $gl-gray; - line-height: 1; + line-height: 1.35; a { color: $gl-gray; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 46c4a11aa2e..63845e6b9ba 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -404,3 +404,18 @@ margin-bottom: $gl-padding; } } + +.issuable-list { + li { + .issue-check { + float: left; + padding-right: $gl-padding; + margin-bottom: 10px; + min-width: 15px; + + .selected_issue { + vertical-align: text-top; + } + } + } +} diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 910700b0206..7a26b7ad497 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -7,17 +7,6 @@ margin-bottom: 2px; } - .issue-check { - float: left; - padding-right: 8px; - margin-bottom: 10px; - min-width: 15px; - - .selected_issue { - vertical-align: text-top; - } - } - .issue-labels { display: inline-block; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7fdd79fa8b9..2a44b95de64 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -375,7 +375,7 @@ } } -.mr-version-switch { +.mr-version-controls { background: $background-color; padding: $gl-btn-padding; color: $gl-placeholder-color; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index ee5d9de66d8..2d66ab25da6 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -490,6 +490,6 @@ .ci-status-icon-created { svg { - fill: $table-text-gray; + fill: $gray-darkest; } } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index cdd38442550..1778c069706 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -15,16 +15,6 @@ vertical-align: top; } - .last-commit { - max-width: 506px; - - .last-commit-content { - @include str-truncated; - width: calc(100% - 140px); - margin-left: 3px; - } - } - .tree-table { margin-bottom: 0; @@ -36,6 +26,15 @@ line-height: 21px; } + .last-commit { + @include str-truncated(60%); + } + + .commit-history-link-spacer { + margin: 0 10px; + color: $table-border-color; + } + &:hover { td { background-color: $row-hover; @@ -91,11 +90,17 @@ } } - .tree_commit { - color: $gl-gray; + .tree-time-ago { + min-width: 135px; + color: $gl-gray-light; + } + + .tree-commit { + max-width: 320px; + color: $gl-gray-light; .tree-commit-link { - color: $gl-gray; + color: $gl-gray-light; &:hover { text-decoration: underline; diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index a7af3cb8345..e06d12cfce1 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -7,19 +7,14 @@ module Ci def create @content = params[:content] + @error = Ci::GitlabCiYamlProcessor.validation_message(@content) + @status = @error.blank? - if @content.blank? - @status = false - @error = "Please provide content of .gitlab-ci.yml" - else + if @error.blank? @config_processor = Ci::GitlabCiYamlProcessor.new(@content) @stages = @config_processor.stages @builds = @config_processor.builds - @status = true end - rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e - @error = e.message - @status = false rescue @error = 'Undefined error' @status = false diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index f40b62446e5..77b4efffd7f 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -3,6 +3,7 @@ module IssuableActions included do before_action :authorize_destroy_issuable!, only: :destroy + before_action :authorize_admin_issuable!, only: :bulk_update end def destroy @@ -13,11 +14,41 @@ module IssuableActions redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]) end + def bulk_update + result = Issuable::BulkUpdateService.new(project, current_user, bulk_update_params).execute(resource_name) + quantity = result[:count] + + render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" } + end + private def authorize_destroy_issuable! - unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable) + unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable) return access_denied! end end + + def authorize_admin_issuable! + unless can?(current_user, :"admin_#{resource_name}", @project) + return access_denied! + end + end + + def bulk_update_params + params.require(:update).permit( + :issuable_ids, + :assignee_id, + :milestone_id, + :state_event, + :subscription_event, + label_ids: [], + add_label_ids: [], + remove_label_ids: [] + ) + end + + def resource_name + @resource_name ||= controller_name.singularize + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 72d2d361878..de02e28e384 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -20,9 +20,6 @@ class Projects::IssuesController < Projects::ApplicationController # Allow modify issue before_action :authorize_update_issue!, only: [:edit, :update] - # Allow issues bulk update - before_action :authorize_admin_issues!, only: [:bulk_update] - respond_to :html def index @@ -168,16 +165,6 @@ class Projects::IssuesController < Projects::ApplicationController end end - def bulk_update - result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute - - respond_to do |format| - format.json do - render json: { notice: "#{result[:count]} issues updated" } - end - end - end - protected def issue @@ -237,17 +224,4 @@ class Projects::IssuesController < Projects::ApplicationController :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [] ) end - - def bulk_update_params - params.require(:update).permit( - :issues_ids, - :assignee_id, - :milestone_id, - :state_event, - :subscription_event, - label_ids: [], - add_label_ids: [], - remove_label_ids: [] - ) - end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 4f9ca0097a1..8895cb955bd 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -90,16 +90,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.merge_request_diff end + @merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff + @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } + + if params[:start_sha].present? + @start_sha = params[:start_sha] + @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha } + + unless @start_version + render_404 + end + end + respond_to do |format| format.html { define_discussion_vars } format.json do - unless @merge_request_diff.latest? - # Disable comments if browsing older version of the diff - @diff_notes_disabled = true + if @start_sha + compared_diff_version + else + original_diff_version end - @diffs = @merge_request_diff.diffs(diff_options) - render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } end end @@ -529,4 +540,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute end + + def compared_diff_version + @diff_notes_disabled = true + @diffs = @merge_request_diff.compare_with(@start_sha).diffs(diff_options) + end + + def original_diff_version + @diff_notes_disabled = !@merge_request_diff.latest? + @diffs = @merge_request_diff.diffs(diff_options) + end end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index b0c72cfe4b4..371cc3787fb 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -7,11 +7,10 @@ class Projects::PipelinesController < Projects::ApplicationController def index @scope = params[:scope] - all_pipelines = project.pipelines - @pipelines_count = all_pipelines.count - @running_or_pending_count = all_pipelines.running_or_pending.count - @pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope) - @pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30) + @pipelines = PipelinesFinder.new(project).execute(scope: @scope).page(params[:page]).per(30) + + @running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count + @pipelines_count = PipelinesFinder.new(project).execute.count end def new diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb index 641fbf838f1..32aea75486d 100644 --- a/app/finders/pipelines_finder.rb +++ b/app/finders/pipelines_finder.rb @@ -1,30 +1,34 @@ class PipelinesFinder - attr_reader :project + attr_reader :project, :pipelines def initialize(project) @project = project + @pipelines = project.pipelines end - def execute(pipelines, scope) - case scope - when 'running' - pipelines.running_or_pending - when 'branches' - from_ids(pipelines, ids_for_ref(pipelines, branches)) - when 'tags' - from_ids(pipelines, ids_for_ref(pipelines, tags)) - else - pipelines - end + def execute(scope: nil) + scoped_pipelines = + case scope + when 'running' + pipelines.running_or_pending + when 'branches' + from_ids(ids_for_ref(branches)) + when 'tags' + from_ids(ids_for_ref(tags)) + else + pipelines + end + + scoped_pipelines.order(id: :desc) end private - def ids_for_ref(pipelines, refs) + def ids_for_ref(refs) pipelines.where(ref: refs).group(:ref).select('max(id)') end - def from_ids(pipelines, ids) + def from_ids(ids) pipelines.unscoped.where(id: ids) end diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index aa8acbe7567..df41473543b 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -14,7 +14,8 @@ module AvatarsHelper avatar_icon(options[:user] || options[:user_email], avatar_size), class: "avatar has-tooltip hidden-xs s#{avatar_size}", alt: "#{user_name}'s avatar", - title: user_name + title: user_name, + data: { container: 'body' } ) if options[:user] diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb index 09684955233..8ab394384f3 100644 --- a/app/helpers/git_helper.rb +++ b/app/helpers/git_helper.rb @@ -2,4 +2,8 @@ module GitHelper def strip_gpg_signature(text) text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "") end + + def short_sha(text) + Commit.truncate_sha(text) + end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index a9e175c3f5c..8abe7865fed 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -100,4 +100,14 @@ module MergeRequestsHelper def merge_request_button_visibility(merge_request, closed) return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork? end + + def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil) + diffs_namespace_project_merge_request_path( + project.namespace, project, merge_request, + diff_id: merge_request_diff.id, start_sha: start_sha) + end + + def version_index(merge_request_diff) + @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff) + end end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 2b0ff6c0d00..df87fac132d 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -1,21 +1,7 @@ module NavHelper - def nav_menu_collapsed? - cookies[:collapsed_nav] == 'true' - end - - def nav_sidebar_class - if nav_menu_collapsed? - "sidebar-collapsed" - else - "sidebar-expanded" - end - end - def page_sidebar_class if pinned_nav? "page-sidebar-expanded page-sidebar-pinned" - else - "page-sidebar-collapsed" end end @@ -26,7 +12,6 @@ module NavHelper current_path?('merge_requests#builds') || current_path?('merge_requests#conflicts') || current_path?('merge_requests#pipelines') || - current_path?('issues#show') if cookies[:collapsed_gutter] == 'true' "page-gutter right-sidebar-collapsed" @@ -43,9 +28,7 @@ module NavHelper class_name << " with-horizontal-nav" if defined?(nav) && nav if pinned_nav? - class_name << " header-expanded header-pinned-nav" - else - class_name << " header-collapsed" + class_name << " header-sidebar-expanded header-sidebar-pinned" end class_name diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 4c685b97c03..16a8e52a4ca 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -129,6 +129,19 @@ module ProjectsHelper current_user.recent_push(project_ids) end + def project_feature_access_select(field) + # Don't show option "everyone with access" if project is private + options = project_feature_options + + if @project.private? + options.delete('Everyone with access') + highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED + end + + options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) + content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe + end + private def get_project_nav_tabs(project, current_user) @@ -422,15 +435,4 @@ module ProjectsHelper 'Everyone with access' => ProjectFeature::ENABLED } end - - def project_feature_access_select(field) - # Don't show option "everyone with access" if project is private - options = project_feature_options - level = @project.project_feature.public_send(field) - - options.delete('Everyone with access') if @project.private? && level != ProjectFeature::ENABLED - - options = options_for_select(options, selected: @project.project_feature.public_send(field) || ProjectFeature::ENABLED) - content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe - end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 4549c2e5bb6..e523c46e879 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -7,8 +7,10 @@ module SearchHelper projects_autocomplete(term) ].flatten + search_pattern = Regexp.new(Regexp.escape(term), "i") + generic_results = project_autocomplete + default_autocomplete + help_autocomplete - generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") } + generic_results.select! { |result| result[:label] =~ search_pattern } [ resources_results, diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 49f05f881a2..ed5d4b13b7e 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -2,7 +2,7 @@ module Ci class Runner < ActiveRecord::Base extend Ci::Model - LAST_CONTACT_TIME = 5.minutes.ago + LAST_CONTACT_TIME = 2.hours.ago AVAILABLE_SCOPES = %w[specific shared active paused online] FORM_EDITABLE = %i[description tag_list active run_untagged locked] diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 4442cefc7e9..559b3075905 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -13,6 +13,11 @@ class DiffNote < Note validate :positions_complete validate :verify_supported + # Keep this scope in sync with the logic in `#resolvable?` + scope :resolvable, -> { user.where(noteable_type: 'MergeRequest') } + scope :resolved, -> { resolvable.where.not(resolved_at: nil) } + scope :unresolved, -> { resolvable.where(resolved_at: nil) } + after_initialize :ensure_original_discussion_id before_validation :set_original_position, :update_position, on: :create before_validation :set_line_code, :set_original_discussion_id @@ -25,6 +30,16 @@ class DiffNote < Note def build_discussion_id(noteable_type, noteable_id, position) [super(noteable_type, noteable_id), *position.key].join("-") end + + # This method must be kept in sync with `#resolve!` + def resolve!(current_user) + unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id) + end + + # This method must be kept in sync with `#unresolve!` + def unresolve! + resolved.update_all(resolved_at: nil, resolved_by_id: nil) + end end def new_diff_note? @@ -73,6 +88,7 @@ class DiffNote < Note self.position.diff_refs == diff_refs end + # If you update this method remember to also update the scope `resolvable` def resolvable? !system? && for_merge_request? end @@ -83,6 +99,7 @@ class DiffNote < Note self.resolved_at.present? end + # If you update this method remember to also update `.resolve!` def resolve!(current_user) return unless resolvable? return if resolved? @@ -92,6 +109,7 @@ class DiffNote < Note save! end + # If you update this method remember to also update `.unresolve!` def unresolve! return unless resolvable? return unless resolved? diff --git a/app/models/discussion.rb b/app/models/discussion.rb index 9676bc03470..de06c13481a 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -1,7 +1,7 @@ class Discussion NUMBER_OF_TRUNCATED_DIFF_LINES = 16 - attr_reader :first_note, :last_note, :notes + attr_reader :notes delegate :created_at, :project, @@ -36,8 +36,6 @@ class Discussion end def initialize(notes) - @first_note = notes.first - @last_note = notes.last @notes = notes end @@ -70,17 +68,25 @@ class Discussion end def resolvable? - return @resolvable if defined?(@resolvable) + return @resolvable if @resolvable.present? @resolvable = diff_discussion? && notes.any?(&:resolvable?) end def resolved? - return @resolved if defined?(@resolved) + return @resolved if @resolved.present? @resolved = resolvable? && notes.none?(&:to_be_resolved?) end + def first_note + @first_note ||= @notes.first + end + + def last_note + @last_note ||= @notes.last + end + def resolved_notes notes.select(&:resolved?) end @@ -100,17 +106,13 @@ class Discussion def resolve!(current_user) return unless resolvable? - notes.each do |note| - note.resolve!(current_user) if note.resolvable? - end + update { |notes| notes.resolve!(current_user) } end def unresolve! return unless resolvable? - notes.each do |note| - note.unresolve! if note.resolvable? - end + update { |notes| notes.unresolve! } end def for_target?(target) @@ -118,7 +120,7 @@ class Discussion end def active? - return @active if defined?(@active) + return @active if @active.present? @active = first_note.active? end @@ -174,4 +176,17 @@ class Discussion prev_lines end + + private + + def update + notes_relation = DiffNote.where(id: notes.map(&:id)).fresh + yield(notes_relation) + + # Set the notes array to the updated notes + @notes = notes_relation.to_a + + # Reset the memoized values + @last_resolved_note = @resolvable = @resolved = @first_note = @last_note = nil + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 445179a4487..18c583add88 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -152,6 +152,10 @@ class MergeRequestDiff < ActiveRecord::Base self == merge_request.merge_request_diff end + def compare_with(sha) + CompareService.new.execute(project, head_commit_sha, project, sha) + end + private def dump_commits(commits) diff --git a/app/models/repository.rb b/app/models/repository.rb index 414b82516bc..7b7090b8a73 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -757,7 +757,7 @@ class Repository end def commit_dir(user, path, message, branch) - commit_with_hooks(user, branch) do |ref| + update_branch_with_hooks(user, branch) do |ref| committer = user_to_committer(user) options = {} options[:committer] = committer @@ -774,7 +774,7 @@ class Repository end def commit_file(user, path, content, message, branch, update) - commit_with_hooks(user, branch) do |ref| + update_branch_with_hooks(user, branch) do |ref| committer = user_to_committer(user) options = {} options[:committer] = committer @@ -796,7 +796,7 @@ class Repository end def update_file(user, path, content, branch:, previous_path:, message:) - commit_with_hooks(user, branch) do |ref| + update_branch_with_hooks(user, branch) do |ref| committer = user_to_committer(user) options = {} options[:committer] = committer @@ -823,7 +823,7 @@ class Repository end def remove_file(user, path, message, branch) - commit_with_hooks(user, branch) do |ref| + update_branch_with_hooks(user, branch) do |ref| committer = user_to_committer(user) options = {} options[:committer] = committer @@ -871,7 +871,7 @@ class Repository merge_index = rugged.merge_commits(our_commit, their_commit) return false if merge_index.conflicts? - commit_with_hooks(user, merge_request.target_branch) do + update_branch_with_hooks(user, merge_request.target_branch) do actual_options = options.merge( parents: [our_commit, their_commit], tree: merge_index.write_tree(rugged), @@ -889,7 +889,7 @@ class Repository return false unless revert_tree_id - commit_with_hooks(user, base_branch) do + update_branch_with_hooks(user, base_branch) do committer = user_to_committer(user) source_sha = Rugged::Commit.create(rugged, message: commit.revert_message, @@ -906,7 +906,7 @@ class Repository return false unless cherry_pick_tree_id - commit_with_hooks(user, base_branch) do + update_branch_with_hooks(user, base_branch) do committer = user_to_committer(user) source_sha = Rugged::Commit.create(rugged, message: commit.message, @@ -922,7 +922,7 @@ class Repository end def resolve_conflicts(user, branch, params) - commit_with_hooks(user, branch) do + update_branch_with_hooks(user, branch) do committer = user_to_committer(user) Rugged::Commit.create(rugged, params.merge(author: committer, committer: committer)) @@ -1026,7 +1026,7 @@ class Repository Gitlab::Popen.popen(args, path_to_repo) end - def commit_with_hooks(current_user, branch) + def update_branch_with_hooks(current_user, branch) update_autocrlf_option ref = Gitlab::Git::BRANCH_REF_PREFIX + branch @@ -1040,7 +1040,11 @@ class Repository raise CommitError.new('Failed to create commit') end - oldrev = rugged.lookup(newrev).parent_ids.first || Gitlab::Git::BLANK_SHA + if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil? + oldrev = Gitlab::Git::BLANK_SHA + else + oldrev = rugged.merge_base(newrev, target_branch.target.sha) + end GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do update_ref!(ref, newrev, oldrev) diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb new file mode 100644 index 00000000000..60891cbb255 --- /dev/null +++ b/app/services/issuable/bulk_update_service.rb @@ -0,0 +1,26 @@ +module Issuable + class BulkUpdateService < IssuableBaseService + def execute(type) + model_class = type.classify.constantize + update_class = type.classify.pluralize.constantize::UpdateService + + ids = params.delete(:issuable_ids).split(",") + items = model_class.where(id: ids) + + %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key| + params.delete(key) unless params[key].present? + end + + items.each do |issuable| + next unless can?(current_user, :"update_#{type}", issuable) + + update_class.new(issuable.project, current_user, params).execute(issuable) + end + + { + count: items.count, + success: !items.count.zero? + } + end + end +end diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb deleted file mode 100644 index 7e19a73f71a..00000000000 --- a/app/services/issues/bulk_update_service.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Issues - class BulkUpdateService < BaseService - def execute - issues_ids = params.delete(:issues_ids).split(",") - issue_params = params - - %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key| - issue_params.delete(key) unless issue_params[key].present? - end - - issues = Issue.where(id: issues_ids) - - issues.each do |issue| - next unless can?(current_user, :update_issue, issue) - - Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue) - end - - { - count: issues.count, - success: !issues.count.zero? - } - end - end -end diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index ce4536ebdc6..16c16cec137 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -7,277 +7,278 @@ Keyboard Shortcuts %small = link_to '(Show all)', '#', class: 'js-more-help-button' - .modal-body.shortcuts-cheatsheet - .col-lg-4 - %table.shortcut-mappings - %tbody - %tr - %th - %th Global Shortcuts - %tr - %td.shortcut - .key s - %td Focus Search - %tr - %td.shortcut - .key f - %td Focus Filter - %tr - %td.shortcut - .key ? - %td Show/hide this dialog - %tr - %td.shortcut - - if browser.platform.mac? - .key ⌘ shift p - - else - .key ctrl shift p - %td Toggle Markdown preview - %tr - %td.shortcut - .key - %i.fa.fa-arrow-up - %td Edit last comment (when focused on an empty textarea) - %tbody - %tr - %th - %th Project Files browsing - %tr - %td.shortcut - .key - %i.fa.fa-arrow-up - %td Move selection up - %tr - %td.shortcut - .key - %i.fa.fa-arrow-down - %td Move selection down - %tr - %td.shortcut - .key enter - %td Open Selection - %tbody - %tr - %th - %th Finding Project File - %tr - %td.shortcut - .key - %i.fa.fa-arrow-up - %td Move selection up - %tr - %td.shortcut - .key - %i.fa.fa-arrow-down - %td Move selection down - %tr - %td.shortcut - .key enter - %td Open Selection - %tr - %td.shortcut - .key esc - %td Go back + .modal-body + .row + .col-lg-4 + %table.shortcut-mappings + %tbody + %tr + %th + %th Global Shortcuts + %tr + %td.shortcut + .key s + %td Focus Search + %tr + %td.shortcut + .key f + %td Focus Filter + %tr + %td.shortcut + .key ? + %td Show/hide this dialog + %tr + %td.shortcut + - if browser.platform.mac? + .key ⌘ shift p + - else + .key ctrl shift p + %td Toggle Markdown preview + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Edit last comment (when focused on an empty textarea) + %tbody + %tr + %th + %th Project Files browsing + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Move selection up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + %td Move selection down + %tr + %td.shortcut + .key enter + %td Open Selection + %tbody + %tr + %th + %th Finding Project File + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Move selection up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + %td Move selection down + %tr + %td.shortcut + .key enter + %td Open Selection + %tr + %td.shortcut + .key esc + %td Go back - .col-lg-4 - %table.shortcut-mappings - %tbody{ class: 'hidden-shortcut project', style: 'display:none' } - %tr - %th - %th Global Dashboard - %tr - %td.shortcut - .key g - .key a - %td - Go to the activity feed - %tr - %td.shortcut - .key g - .key p - %td - Go to projects - %tr - %td.shortcut - .key g - .key i - %td - Go to issues - %tr - %td.shortcut - .key g - .key m - %td - Go to merge requests - %tbody - %tr - %th - %th Project - %tr - %td.shortcut - .key g - .key p - %td - Go to the project's home page - %tr - %td.shortcut - .key g - .key e - %td - Go to the project's activity feed - %tr - %td.shortcut - .key g - .key f - %td - Go to files - %tr - %td.shortcut - .key g - .key c - %td - Go to commits - %tr - %td.shortcut - .key g - .key b - %td - Go to builds - %tr - %td.shortcut - .key g - .key n - %td - Go to network graph - %tr - %td.shortcut - .key g - .key g - %td - Go to graphs - %tr - %td.shortcut - .key g - .key i - %td - Go to issues - %tr - %td.shortcut - .key g - .key m - %td - Go to merge requests - %tr - %td.shortcut - .key g - .key s - %td - Go to snippets - %tr - %td.shortcut - .key t - %td Go to finding file - %tr - %td.shortcut - .key i - %td New issue - .col-lg-4 - %table.shortcut-mappings - %tbody{ class: 'hidden-shortcut network', style: 'display:none' } - %tr - %th - %th Network Graph - %tr - %td.shortcut - .key - %i.fa.fa-arrow-left - \/ - .key h - %td Scroll left - %tr - %td.shortcut - .key - %i.fa.fa-arrow-right - \/ - .key l - %td Scroll right - %tr - %td.shortcut - .key - %i.fa.fa-arrow-up - \/ - .key k - %td Scroll up - %tr - %td.shortcut - .key - %i.fa.fa-arrow-down - \/ - .key j - %td Scroll down - %tr - %td.shortcut - .key - shift - %i.fa.fa-arrow-up - \/ - .key - shift k - %td Scroll to top - %tr - %td.shortcut - .key - shift - %i.fa.fa-arrow-down - \/ - .key - shift j - %td Scroll to bottom - %tbody{ class: 'hidden-shortcut issues', style: 'display:none' } - %tr - %th - %th Issues - %tr - %td.shortcut - .key a - %td Change assignee - %tr - %td.shortcut - .key m - %td Change milestone - %tr - %td.shortcut - .key r - %td Reply (quoting selected text) - %tr - %td.shortcut - .key e - %td Edit issue - %tr - %td.shortcut - .key l - %td Change Label - %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' } - %tr - %th - %th Merge Requests - %tr - %td.shortcut - .key a - %td Change assignee - %tr - %td.shortcut - .key m - %td Change milestone - %tr - %td.shortcut - .key r - %td Reply (quoting selected text) - %tr - %td.shortcut - .key e - %td Edit merge request - %tr - %td.shortcut - .key l - %td Change Label + .col-lg-4 + %table.shortcut-mappings + %tbody{ class: 'hidden-shortcut project', style: 'display:none' } + %tr + %th + %th Global Dashboard + %tr + %td.shortcut + .key g + .key a + %td + Go to the activity feed + %tr + %td.shortcut + .key g + .key p + %td + Go to projects + %tr + %td.shortcut + .key g + .key i + %td + Go to issues + %tr + %td.shortcut + .key g + .key m + %td + Go to merge requests + %tbody + %tr + %th + %th Project + %tr + %td.shortcut + .key g + .key p + %td + Go to the project's home page + %tr + %td.shortcut + .key g + .key e + %td + Go to the project's activity feed + %tr + %td.shortcut + .key g + .key f + %td + Go to files + %tr + %td.shortcut + .key g + .key c + %td + Go to commits + %tr + %td.shortcut + .key g + .key b + %td + Go to builds + %tr + %td.shortcut + .key g + .key n + %td + Go to network graph + %tr + %td.shortcut + .key g + .key g + %td + Go to graphs + %tr + %td.shortcut + .key g + .key i + %td + Go to issues + %tr + %td.shortcut + .key g + .key m + %td + Go to merge requests + %tr + %td.shortcut + .key g + .key s + %td + Go to snippets + %tr + %td.shortcut + .key t + %td Go to finding file + %tr + %td.shortcut + .key i + %td New issue + .col-lg-4 + %table.shortcut-mappings + %tbody{ class: 'hidden-shortcut network', style: 'display:none' } + %tr + %th + %th Network Graph + %tr + %td.shortcut + .key + %i.fa.fa-arrow-left + \/ + .key h + %td Scroll left + %tr + %td.shortcut + .key + %i.fa.fa-arrow-right + \/ + .key l + %td Scroll right + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + \/ + .key k + %td Scroll up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + \/ + .key j + %td Scroll down + %tr + %td.shortcut + .key + shift + %i.fa.fa-arrow-up + \/ + .key + shift k + %td Scroll to top + %tr + %td.shortcut + .key + shift + %i.fa.fa-arrow-down + \/ + .key + shift j + %td Scroll to bottom + %tbody{ class: 'hidden-shortcut issues', style: 'display:none' } + %tr + %th + %th Issues + %tr + %td.shortcut + .key a + %td Change assignee + %tr + %td.shortcut + .key m + %td Change milestone + %tr + %td.shortcut + .key r + %td Reply (quoting selected text) + %tr + %td.shortcut + .key e + %td Edit issue + %tr + %td.shortcut + .key l + %td Change Label + %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' } + %tr + %th + %th Merge Requests + %tr + %td.shortcut + .key a + %td Change assignee + %tr + %td.shortcut + .key m + %td Change milestone + %tr + %td.shortcut + .key r + %td Reply (quoting selected text) + %tr + %td.shortcut + .key e + %td Edit merge request + %tr + %td.shortcut + .key l + %td Change Label diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index bf50633af24..4f7839a881f 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,5 +1,5 @@ .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } - .sidebar-wrapper.nicescroll{ class: nav_sidebar_class } + .sidebar-wrapper.nicescroll .sidebar-action-buttons = link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do %span.sr-only Toggle navigation diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 377665b096f..5a98e258b22 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -11,7 +11,7 @@ %small= number_to_human_size @blob.size .file-actions = render "projects/blob/actions" - .file-content.blame.code.js-syntax-highlight + .table-responsive.file-content.blame.code.js-syntax-highlight %table - current_line = 1 - @blame_groups.each do |blame_group| @@ -19,6 +19,7 @@ %td.blame-commit .commit - commit = blame_group[:commit] + = author_avatar(commit, size: 36) .commit-row-title %strong = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 5ce36a475a9..56306b05934 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -90,12 +90,13 @@ - if @build.trigger_request.variables %p - %span.build-light-text Variables: + %button.btn.group.btn-group-justified.reveal-variables Reveal Variables - @build.trigger_request.variables.each do |key, value| - %code - #{key}=#{value} + .hide.js-build + .js-build-variable= key + .js-build-value= value .block .title diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 79b14819865..851d4c06990 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -1,7 +1,7 @@ %li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } } - - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) + - if @bulk_edit .issue-check - = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue" + = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue" .issue-title.title %span.issue-title-text diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index f34f3c05737..a2c31c0b4c5 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,4 +1,4 @@ -%ul.content-list.issues-list +%ul.content-list.issues-list.issuable-list = render @issues - if @issues.blank? %li diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 33556a1a2b3..c56b6cc11f5 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -1,6 +1,6 @@ - if can?(current_user, :push_code, @project) .pull-right - #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)} + #new-branch.new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)} = link_to '#', class: 'checking btn btn-grouped', disabled: 'disabled' do = icon('spinner spin') Checking branches diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 1a87045aa60..8da9f2100e9 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,4 +1,6 @@ - @no_container = true +- @bulk_edit = can?(current_user, :admin_issue, @project) + - page_title "Issues" - new_issue_email = @project.new_issue_address(current_user) = render "projects/issues/head" diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 5029b365f93..31f8d0aeb5b 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -1,4 +1,8 @@ %li{ class: mr_css_classes(merge_request) } + - if @bulk_edit + .issue-check + = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue" + .merge-request-title.title %span.merge-request-title-text = link_to merge_request.title, merge_request_path(merge_request) diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml index 446887774a4..fe82f751f53 100644 --- a/app/views/projects/merge_requests/_merge_requests.html.haml +++ b/app/views/projects/merge_requests/_merge_requests.html.haml @@ -1,4 +1,4 @@ -%ul.content-list.mr-list +%ul.content-list.mr-list.issuable-list = render @merge_requests - if @merge_requests.blank? %li diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 9d8b4cc56be..4b4d418e8ec 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -83,7 +83,7 @@ .tab-content#diff-notes-app #notes.notes.tab-pane.voting_notes - .content-block.content-block-small.oneline-block + .content-block.content-block-small = render 'award_emoji/awards_block', awardable: @merge_request, inline: true .row diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index ace275c689b..144b3a9c8c8 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,4 +1,6 @@ - @no_container = true +- @bulk_edit = can?(current_user, :admin_merge_request, @project) + - page_title "Merge Requests" = render "projects/issues/head" = render 'projects/last_push' diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index 571e4dd1b1a..f1d5441f9dd 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -12,7 +12,7 @@ %pre.dark#merge-info-1 - if @merge_request.for_fork? :preserve - git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch} + git fetch #{h default_url_to_repo(@merge_request.source_project)} #{h @merge_request.source_branch} git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD - else :preserve diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 2da70ce7137..00287f2d245 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -1,31 +1,60 @@ -- merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff - -- if merge_request_diffs.size > 1 - .mr-version-switch - Version: - %span.dropdown.inline +- if @merge_request_diffs.size > 1 + .mr-version-controls + Changes between + %span.dropdown.inline.mr-version-dropdown %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } - %strong.monospace< + %strong - if @merge_request_diff.latest? - #{"latest"} + latest version - else - #{@merge_request_diff.head_commit.short_id} + version #{version_index(@merge_request_diff)} %span.caret %ul.dropdown-menu.dropdown-menu-selectable - - merge_request_diffs.each do |merge_request_diff| + - @merge_request_diffs.each do |merge_request_diff| %li - = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do - %strong.monospace - #{merge_request_diff.head_commit.short_id} - %br + = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do + %strong + - if merge_request_diff.latest? + latest version + - else + version #{version_index(merge_request_diff)} + .monospace #{short_sha(merge_request_diff.head_commit_sha)} %small #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, = time_ago_with_tooltip(merge_request_diff.created_at) - - unless @merge_request_diff.latest? - %span.prepend-left-default + - if @merge_request_diff.base_commit_sha + and + %span.dropdown.inline.mr-version-compare-dropdown + %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } + %strong + - if @start_sha + version #{version_index(@start_version)} + - else + #{@merge_request.target_branch} + %span.caret + %ul.dropdown-menu.dropdown-menu-selectable + - @comparable_diffs.each do |merge_request_diff| + %li + = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do + %strong + - if merge_request_diff.latest? + latest version + - else + version #{version_index(merge_request_diff)} + .monospace #{short_sha(merge_request_diff.head_commit_sha)} + %small + = time_ago_with_tooltip(merge_request_diff.created_at) + %li + = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do + %strong + #{@merge_request.target_branch} (base) + .monospace #{short_sha(@merge_request_diff.base_commit_sha)} + + - unless @merge_request_diff.latest? && !@start_sha + .prepend-top-10 = icon('info-circle') - This version is not the latest one. Comments are disabled - .pull-right - %span.monospace - #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} + - if @start_sha + Comments are disabled because you're comparing two versions of this merge request. + - else + Comments are disabled because you're viewing an old version of this merge request. diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml index 8ee2aef0e61..1141168f037 100644 --- a/app/views/projects/refs/logs_tree.js.haml +++ b/app/views/projects/refs/logs_tree.js.haml @@ -5,8 +5,8 @@ :plain var row = $("table.table_#{@hex_path} tr.file_#{hexdigest(file_name)}"); - row.find("td.tree_time_ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}'); - row.find("td.tree_commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}'); + row.find("td.tree-time-ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}'); + row.find("td.tree-commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}'); - if @more_log_url :plain diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index a3a4dba3fa4..ee417b58cbf 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -4,6 +4,6 @@ - file_name = blob_item.name = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do %span.str-truncated= file_name - %td.tree_time_ago.cgray - = render 'projects/tree/spinner' - %td.hidden-xs.tree_commit + %td.hidden-xs.tree-commit + %td.tree-time-ago.cgray.text-right + = render 'projects/tree/spinner'
\ No newline at end of file diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index ca5d2d7722a..0f7d629ab98 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -4,20 +4,20 @@ %thead %tr %th Name - %th Last Update - %th.hidden-xs.last-commit - Last Commit - .last-commit-content.hidden-sm + %th.hidden-xs + .pull-left Last Commit + .last-commit.hidden-sm.pull-left + %i.fa.fa-angle-right %small.light = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" – = time_ago_with_tooltip(@commit.committed_date) - – = @commit.full_title - = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right' - + %small.commit-history-link-spacer | + = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'commit-history-link' + %th.text-right Last Update - if @path.present? %tr.tree-item %td.tree-item-file-name diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index 9577696fc0d..1ccef6d52ab 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -4,6 +4,6 @@ - path = flatten_tree(tree_item) = link_to namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)), title: path do %span.str-truncated= path - %td.tree_time_ago.cgray - = render 'projects/tree/spinner' - %td.hidden-xs.tree_commit + %td.hidden-xs.tree-commit + %td.tree-time-ago.text-right + = render 'projects/tree/spinner'
\ No newline at end of file diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml index 7f3de47d7df..f6e0b0a7c8a 100644 --- a/app/views/projects/triggers/index.html.haml +++ b/app/views/projects/triggers/index.html.haml @@ -4,65 +4,89 @@ .col-lg-3 %h4.prepend-top-0 = page_title - %p - Triggers can force a specific branch or tag to rebuild with an API call. + %p.prepend-top-20 + Triggers can force a specific branch or tag to get rebuilt with an API call. + %p.append-bottom-0 + = succeed '.' do + Learn more in the + = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank' .col-lg-9 - %h5.prepend-top-0 - Your triggers - - if @triggers.any? - .table-responsive - %table.table - %thead - %th Token - %th Last used - %th - = render partial: 'trigger', collection: @triggers, as: :trigger - - else - %p.settings-message.text-center.append-bottom-default - No triggers have been created yet. Add one using the button below. + .panel.panel-default + .panel-heading + %h4.panel-title + Manage your project's triggers + .panel-body + - if @triggers.any? + .table-responsive + %table.table + %thead + %th + %strong Token + %th + %strong Last used + %th + = render partial: 'trigger', collection: @triggers, as: :trigger + - else + %p.settings-message.text-center.append-bottom-default + No triggers have been created yet. Add one using the button below. - = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f| - = f.submit "Add Trigger", class: 'btn btn-success' + = form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create') do |f| + = f.submit "Add trigger", class: 'btn btn-success' - %h5.prepend-top-default - Use CURL + .panel-footer - %p.light - Copy the token above, set your branch or tag name, and that reference will be rebuilt. + %p + In the following examples, you can see the exact API call you need to + make in order to rebuild a specific + %code ref + (branch or tag) with a trigger token. + %p + All you need to do is replace the + %code TOKEN + and + %code REF_NAME + with the trigger token and the branch or tag name respectively. - %pre - :plain - curl -X POST \ - -F token=TOKEN \ - -F ref=REF_NAME \ - #{builds_trigger_url(@project.id)} - %h5.prepend-top-default - Use .gitlab-ci.yml + %h5.prepend-top-default + Use cURL - %p.light - In the - %code .gitlab-ci.yml - of the dependent project, include the following snippet. - The project will rebuild at the end of the build. + %p.light + Copy one of the tokens above, set your branch or tag name, and that + reference will be rebuilt. - %pre - :plain - trigger: - type: deploy - script: - - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}" - %h5.prepend-top-default - Pass build variables + %pre + :plain + curl -X POST \ + -F token=TOKEN \ + -F ref=REF_NAME \ + #{builds_trigger_url(@project.id)} + %h5.prepend-top-default + Use .gitlab-ci.yml - %p.light - Add - %code variables[VARIABLE]=VALUE - to an API request. Variable values can be used to distinguish between triggered builds and normal builds. + %p.light + In the + %code .gitlab-ci.yml + of another project, include the following snippet. + The project will be rebuilt at the end of the build. - %pre.append-bottom-0 - :plain - curl -X POST \ - -F token=TOKEN \ - -F "ref=REF_NAME" \ - -F "variables[RUN_NIGHTLY_BUILD]=true" \ - #{builds_trigger_url(@project.id)} + %pre + :plain + trigger_build: + stage: deploy + script: + - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}" + %h5.prepend-top-default + Pass build variables + + %p.light + Add + %code variables[VARIABLE]=VALUE + to an API request. Variable values can be used to distinguish between triggered builds and normal builds. + + %pre.append-bottom-0 + :plain + curl -X POST \ + -F token=TOKEN \ + -F "ref=REF_NAME" \ + -F "variables[RUN_NIGHTLY_BUILD]=true" \ + #{builds_trigger_url(@project.id)} diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 0f4f744a71f..93c4d5c3d30 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -1,9 +1,11 @@ +- boards_page = controller.controller_name == 'boards' + .issues-filters .issues-details-filters.row-content-block.second-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do - if params[:issue_search].present? = hidden_field_tag :issue_search, params[:issue_search] - - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) + - if @bulk_edit .check-all-holder = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left" @@ -26,8 +28,11 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown" + .filter-item.inline.reset-filters + %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search])} Reset filters + .pull-right - - if controller.controller_name == 'boards' + - if boards_page #js-boards-seach.issue-boards-search %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" } - if can?(current_user, :admin_list, @project) @@ -42,9 +47,9 @@ - else = render 'shared/sort_dropdown' - - if controller.controller_name == 'issues' + - if @bulk_edit .issues_bulk_update.hide - = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post, class: 'bulk-update' do + = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update' do .filter-item.inline = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do %ul @@ -67,10 +72,10 @@ %li %a{href: "#", data: {id: "unsubscribe"}} Unsubscribe - = hidden_field_tag 'update[issues_ids]', [] + = hidden_field_tag 'update[issuable_ids]', [] = hidden_field_tag :state_event, params[:state_event] .filter-item.inline - = button_tag "Update issues", class: "btn update_selected_issues btn-save" + = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save" - if !@labels.nil? .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) } diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 80a3e731e1d..7be4a471579 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -1,7 +1,11 @@ -%ul.content-list - = render partial: 'shared/snippets/snippet', collection: @snippets - - if @snippets.empty? - %li - .nothing-here-block Nothing here. +.snippets-list-holder + %ul.content-list + = render partial: 'shared/snippets/snippet', collection: @snippets + - if @snippets.empty? + %li + .nothing-here-block Nothing here. -= paginate @snippets, theme: 'gitlab' + = paginate @snippets, theme: 'gitlab', remote: true + +:javascript + gl.SnippetsList(); diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb new file mode 100644 index 00000000000..5883cafe1d1 --- /dev/null +++ b/app/workers/prune_old_events_worker.rb @@ -0,0 +1,17 @@ +class PruneOldEventsWorker + include Sidekiq::Worker + + def perform + # Contribution calendar shows maximum 12 months of events. + # Double nested query is used because MySQL doesn't allow DELETE subqueries + # on the same table. + Event.unscoped.where( + '(id IN (SELECT id FROM (?) ids_to_remove))', + Event.unscoped.where( + 'created_at < ?', + (12.months + 1.day).ago). + select(:id). + limit(10_000)). + delete_all + end +end |