diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2016-11-30 12:21:33 +0100 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2016-11-30 12:21:33 +0100 |
commit | 00ca7adca2de8ff05cca3df9eb2df8a67f638cfe (patch) | |
tree | 86764da0f493a7169b2fea59e5618b669cd64d56 /app | |
parent | adb3f3d4e494e8f8d41c1b9e676e395a49cd96b2 (diff) | |
parent | 7e5fa10b665835e3160eee4d333a17fbaef9c113 (diff) | |
download | gitlab-ce-00ca7adca2de8ff05cca3df9eb2df8a67f638cfe.tar.gz |
Merge branch 'master' into fix/rename-mwbs-to-merge-when-pipeline-succeeds
* master: (110 commits)
Rewrite an HTTP link to use HTTPS
Edit /spec/features/profiles/preferences_spec.rb to match changes in 084d90ac
Add blue back to sub nav active
Remove JSX/React eslint plugins.
Fix a transient spec failure
Adds hoverstates for collapsed Issue/Merge Request sidebar
Moved groups above projects
Add StackProf to the Gemfile, along with a utility to get a profile for a spec
Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1
Add a CHANGELOG entry
Alert user when logged in user email is not the same as the invitation
Expose timestamp in build entity used by serializer
Rename `MergeRequest#pipeline` to `head_pipeline`
Remove unnecessary database indexes
CE-specific changes gitlab-org/gitlab-ee#1137
Fixing typo & Clarifying Key name
fix started_at check
fix blob controller spec failure - updated not to use file-path-
fix blob controller spec failure
Merge branch 'jej-use-issuable-finder-instead-of-access-check' into 'security'
...
Conflicts:
app/controllers/projects/merge_requests_controller.rb
lib/api/merge_requests.rb
spec/requests/api/merge_requests_spec.rb
Diffstat (limited to 'app')
111 files changed, 698 insertions, 580 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 index b9675f50e31..0d85e1a4678 100644 --- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 +++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 @@ -10,10 +10,15 @@ }, template: ` <span class="total-time"> - <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template> - <template v-if="time.hours">{{ time.hours }} <span>hr</span></template> - <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template> - <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template> + <template v-if="Object.keys(time).length"> + <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template> + <template v-if="time.hours">{{ time.hours }} <span>hr</span></template> + <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template> + <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template> + </template> + <template v-else> + -- + </template> </span> `, }); diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index c2d4670b7e9..16df4b0b005 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -208,6 +208,9 @@ new gl.ProtectedBranchCreate(); new gl.ProtectedBranchEditList(); break; + case 'projects:variables:index': + new gl.ProjectVariables(); + break; } switch (path.first()) { case 'admin': diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 35e183a9086..84faabf938a 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -181,7 +181,7 @@ <div class="environments-container"> <div class="environments-list-loading text-center" v-if="isLoading"> - <i class="fa fa-spinner spin"></i> + <i class="fa fa-spinner fa-spin"></i> </div> <div class="blank-state blank-state-no-icon" diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js deleted file mode 100644 index fc6c130113d..00000000000 --- a/app/assets/javascripts/extensions/array.js +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */ -Array.prototype.first = function() { - return this[0]; -} - -Array.prototype.last = function() { - return this[this.length-1]; -} diff --git a/app/assets/javascripts/extensions/array.js.es6 b/app/assets/javascripts/extensions/array.js.es6 new file mode 100644 index 00000000000..717566a4715 --- /dev/null +++ b/app/assets/javascripts/extensions/array.js.es6 @@ -0,0 +1,24 @@ +/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */ +Array.prototype.first = function() { + return this[0]; +} + +Array.prototype.last = function() { + return this[this.length-1]; +} + +Array.prototype.find = Array.prototype.find || function(predicate, ...args) { + if (!this) throw new TypeError('Array.prototype.find called on null or undefined'); + if (typeof predicate !== 'function') throw new TypeError('predicate must be a function'); + + const list = Object(this); + const thisArg = args[1]; + let value = {}; + + for (let i = 0; i < list.length; i += 1) { + value = list[i]; + if (predicate.call(thisArg, value, i, list)) return value; + } + + return undefined; +}; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 812d5cde685..f334f35594d 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */ +/* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */ (function() { this.LabelsSelect = (function() { function LabelsSelect() { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 47e7b6f831b..0ca0e255595 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */ +/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */ /*= require autosave */ /*= require autosize */ diff --git a/app/assets/javascripts/project_variables.js.es6 b/app/assets/javascripts/project_variables.js.es6 new file mode 100644 index 00000000000..4ee2e49306d --- /dev/null +++ b/app/assets/javascripts/project_variables.js.es6 @@ -0,0 +1,43 @@ +(() => { + const HIDDEN_VALUE_TEXT = '******'; + + class ProjectVariables { + constructor() { + this.$revealBtn = $('.js-btn-toggle-reveal-values'); + this.$revealBtn.on('click', this.toggleRevealState.bind(this)); + } + + toggleRevealState(e) { + e.preventDefault(); + + const oldStatus = this.$revealBtn.attr('data-status'); + let newStatus = 'hidden'; + let newAction = 'Reveal Values'; + + if (oldStatus === 'hidden') { + newStatus = 'revealed'; + newAction = 'Hide Values'; + } + + this.$revealBtn.attr('data-status', newStatus); + + const $variables = $('.variable-value'); + + $variables.each((_, variable) => { + const $variable = $(variable); + let newText = HIDDEN_VALUE_TEXT; + + if (newStatus === 'revealed') { + newText = $variable.attr('data-value'); + } + + $variable.text(newText); + }); + + this.$revealBtn.text(newAction); + } + } + + window.gl = window.gl || {}; + window.gl.ProjectVariables = ProjectVariables; +})(); diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index ad0d387067f..c0dd1cb3667 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -80,6 +80,7 @@ border-radius: 0; border: none; height: auto; + width: 100%; margin: 0; align-self: center; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 4a9aa0f8717..36f530af685 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -15,7 +15,7 @@ @include btn-default; } -@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border) { +@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border) { background-color: $background; color: $text; border-color: $border; @@ -23,8 +23,14 @@ &:hover, &:focus { background-color: $hover-background; - color: $hover-text; border-color: $hover-border; + color: $hover-text; + } + + &:active { + background-color: $active-background; + border-color: $active-border; + color: $hover-text; } } @@ -82,11 +88,11 @@ } @mixin btn-gray { - @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark); + @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-gray-dark); } @mixin btn-white { - @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active); + @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $gl-text-color); } @mixin btn-with-margin { @@ -139,11 +145,11 @@ &.btn-new, &.btn-create, &.btn-save { - @include btn-outline($white-light, $green-normal, $green-normal, $green-light, $white-light, $green-light); + @include btn-outline($white-light, $border-green-light, $border-green-light, $green-light, $white-light, $border-green-light, $green-normal, $border-green-normal); } &.btn-remove { - @include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light); + @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); } } @@ -165,11 +171,11 @@ } &.btn-close { - @include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light); + @include btn-outline($white-light, $border-orange-light, $border-orange-light, $orange-light, $white-light, $border-orange-light, $orange-normal, $border-orange-normal); } &.btn-spam { - @include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light); + @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); } &.btn-danger, @@ -199,7 +205,7 @@ } .fa-caret-down, - .fa-caret-up { + .fa-chevron-down { margin-left: 5px; } @@ -351,7 +357,7 @@ .btn-inverted { &-secondary { - @include btn-outline($white-light, $blue-normal, $blue-normal, $blue-light, $white-light, $blue-light); + @include btn-outline($white-light, $border-blue-light, $border-blue-light, $blue-light, $white-light, $border-blue-light, $blue-normal, $border-blue-normal); } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 583c17e4a83..6d77aadd753 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -8,6 +8,12 @@ } } +@mixin chevron-active { + .fa-chevron-down { + color: $dropdown-toggle-hover-icon-color; + } +} + .open { .dropdown-menu, .dropdown-menu-nav { @@ -19,53 +25,27 @@ } } + .dropdown-toggle, .dropdown-menu-toggle { + @include chevron-active; border-color: $dropdown-toggle-hover-border-color; - - .fa { - color: $dropdown-toggle-hover-icon-color; - } } } -.dropdown-menu-toggle { - position: relative; - width: 160px; - padding: 6px 20px 6px 10px; +.dropdown-toggle { + padding: 6px 8px 6px 10px; background-color: $dropdown-toggle-bg; color: $dropdown-toggle-color; font-size: 15px; text-align: left; border: 1px solid $border-color; border-radius: $border-radius-base; - text-overflow: ellipsis; white-space: nowrap; - overflow: hidden; - - .fa { - position: absolute; - top: 10px; - right: 8px; - color: $dropdown-toggle-icon-color; - - &.fa-spinner { - font-size: 16px; - margin-top: -8px; - } - } &.no-outline { outline: 0; } - &:hover, { - border-color: $dropdown-toggle-hover-border-color; - - .fa { - color: $dropdown-toggle-hover-icon-color; - } - } - &.large { width: 200px; } @@ -86,6 +66,51 @@ max-width: 100%; padding-right: 25px; } + + .fa { + color: $dropdown-toggle-icon-color; + } + + .fa-chevron-down { + font-size: $dropdown-chevron-size; + position: relative; + top: -3px; + margin-left: 5px; + } + + &:hover { + @include chevron-active; + border-color: $dropdown-toggle-hover-border-color; + } + + &:focus:active { + @include chevron-active; + border-color: $dropdown-toggle-active-border-color; + } +} + +.dropdown-menu-toggle { + @extend .dropdown-toggle; + padding-right: 20px; + position: relative; + width: 160px; + text-overflow: ellipsis; + overflow: hidden; + + .fa { + position: absolute; + + &.fa-spinner { + font-size: 16px; + margin-top: -8px; + } + } + + .fa-chevron-down { + position: absolute; + top: 11px; + right: 8px; + } } .dropdown-menu, diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index ce864c2de5e..1839ffa0976 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -51,19 +51,26 @@ margin-bottom: -1px; font-size: 15px; line-height: 28px; - color: #959494; + color: $note-toolbar-color; border-bottom: 2px solid transparent; &:hover, &:active, &:focus { text-decoration: none; + border-bottom: 2px solid $gray-darkest; + color: $black; + + .badge { + color: $black; + } } } &.active a { border-bottom: 2px solid $link-underline-blue; color: $black; + font-weight: 600; } .badge { @@ -85,14 +92,20 @@ li { + &.active a { + border-bottom: none; + color: $link-underline-blue; + } + a { margin: 0; padding: 11px 10px 9px; - } - &.active a { - border-bottom: none; - color: $link-underline-blue; + &:hover, + &:active, + &:focus { + border-bottom: none; + } } } } @@ -310,37 +323,9 @@ height: 51px; li { - a { padding-top: 10px; } - - a, - i { - color: $layout-link-gray; - } - - &.active { - - a, - i { - color: $black; - } - - svg { - path, - polygon { - fill: $black; - } - } - } - - &:hover { - a, - i { - color: $black; - } - } } } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 750d99ebabe..8a9c279d124 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,67 +12,71 @@ $sidebar-breakpoint: 1024px; /* * Color schema */ +$darken-normal-factor: 7%; +$darken-dark-factor: 10%; +$darken-border-factor: 5%; + $white-light: #fff; -$white-normal: #ededed; -$white-dark: #ececec; +$white-normal: darken($white-light, $darken-normal-factor); +$white-dark: darken($white-light, $darken-dark-factor); $gray-lightest: #fdfdfd; $gray-light: #fafafa; $gray-lighter: #f9f9f9; -$gray-normal: #f5f5f5; -$gray-dark: #ededed; +$gray-normal: darken($gray-light, $darken-normal-factor); +$gray-dark: darken($gray-light, $darken-dark-factor); $gray-darker: #eee; $gray-darkest: #c9c9c9; -$green-light: #38ae67; -$green-normal: #2faa60; -$green-dark: #2ca05b; +$green-light: #3cbd70; +$green-normal: darken($green-light, $darken-normal-factor); +$green-dark: darken($green-light, $darken-dark-factor); $blue-light: #2ea8e5; -$blue-normal: #2d9fd8; -$blue-dark: #2897ce; +$blue-normal: darken($blue-light, $darken-normal-factor); +$blue-dark: darken($blue-light, $darken-dark-factor); $blue-medium-light: #3498cb; -$blue-medium: #2f8ebf; -$blue-medium-dark: #2d86b4; +$blue-medium: darken($blue-medium-light, $darken-normal-factor); +$blue-medium-dark: darken($blue-medium-light, $darken-dark-factor); $blue-light-transparent: rgba(44, 159, 216, 0.05); $orange-light: #fc8a51; -$orange-normal: #e75e40; -$orange-dark: #ce5237; +$orange-normal: darken($orange-light, $darken-normal-factor); +$orange-dark: darken($orange-light, $darken-dark-factor); $red-light: #e52c5a; -$red-normal: #d22852; -$red-dark: darken($red-normal, 5%); +$red-normal: darken($red-light, $darken-normal-factor); +$red-dark: darken($red-light, $darken-dark-factor); $black: #000; $black-transparent: rgba(0, 0, 0, 0.3); -$border-white-light: #f1f2f4; -$border-white-normal: #d6dae2; -$border-white-dark: #c6cacf; +$border-white-light: darken($white-light, $darken-border-factor); +$border-white-normal: darken($white-normal, $darken-border-factor); +$border-white-dark: darken($white-dark, $darken-border-factor); -$border-gray-light: #dcdcdc; -$border-gray-normal: #d7d7d7; -$border-gray-dark: #c6cacf; +$border-gray-light: darken($gray-light, $darken-border-factor); +$border-gray-normal: darken($gray-normal, $darken-border-factor); +$border-gray-dark: darken($gray-dark, $darken-border-factor); $border-green-extra-light: #9adb84; -$border-green-light: #2faa60; -$border-green-normal: #2ca05b; -$border-green-dark: #279654; +$border-green-light: darken($green-light, $darken-border-factor); +$border-green-normal: darken($green-normal, $darken-border-factor); +$border-green-dark: darken($green-dark, $darken-border-factor); -$border-blue-light: #2d9fd8; -$border-blue-normal: #2897ce; -$border-blue-dark: #258dc1; +$border-blue-light: darken($blue-light, $darken-border-factor); +$border-blue-normal: darken($blue-normal, $darken-border-factor); +$border-blue-dark: darken($blue-dark, $darken-border-factor); -$border-orange-light: #fc6d26; -$border-orange-normal: #ce5237; -$border-orange-dark: #c14e35; +$border-orange-light: darken($orange-light, $darken-border-factor); +$border-orange-normal: darken($orange-normal, $darken-border-factor); +$border-orange-dark: darken($orange-dark, $darken-border-factor); -$border-red-light: #d22852; -$border-red-normal: #ca264f; -$border-red-dark: darken($border-red-normal, 5%); +$border-red-light: darken($red-light, $darken-border-factor); +$border-red-normal: darken($red-normal, $darken-border-factor); +$border-red-dark: darken($red-dark, $darken-border-factor); $help-well-bg: $gray-light; $help-well-border: #e5e5e5; @@ -216,7 +220,7 @@ $dropdown-bg: #fff; $dropdown-link-color: #555; $dropdown-link-hover-bg: $row-hover; $dropdown-empty-row-bg: rgba(#000, .04); -$dropdown-border-color: rgba(#000, .1); +$dropdown-border-color: $border-color; $dropdown-shadow-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, .1); $dropdown-header-color: #959494; @@ -225,13 +229,15 @@ $dropdown-input-color: #555; $dropdown-input-focus-border: $focus-border-color; $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4); $dropdown-loading-bg: rgba(#fff, .6); +$dropdown-chevron-size: 10px; $dropdown-toggle-bg: #fff; -$dropdown-toggle-color: #626262; -$dropdown-toggle-border-color: #eaeaea; -$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%); +$dropdown-toggle-color: #5c5c5c; +$dropdown-toggle-border-color: #e5e5e5; +$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 13%); +$dropdown-toggle-active-border-color: darken($dropdown-toggle-border-color, 14%); $dropdown-toggle-icon-color: #c4c4c4; -$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; +$dropdown-toggle-hover-icon-color: darken($dropdown-toggle-icon-color, 7%); /* * Buttons @@ -255,7 +261,7 @@ $search-input-border-color: rgba(#4688f1, .8); $search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-width: 220px; $location-badge-color: #aaa; -$location-badge-bg: $gray-normal; +$location-badge-bg: $dark-background-color; $location-badge-active-bg: #4f91f8; $location-icon-color: #e7e9ed; $location-icon-active-color: #807e7e; diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss index 8d1a6020ca4..8d1a6020ca4 100644 --- a/app/assets/stylesheets/mailers/repository_push_email.scss +++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 773155fe80a..7aad99eee4e 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -132,7 +132,7 @@ display: none; } - .btn-clipboard { + .btn-clipboard:hover { color: $gl-gray; } } @@ -235,6 +235,10 @@ padding-bottom: 10px; color: #999; + &:hover { + color: $gl-gray; + } + span { display: block; margin-top: 0; @@ -244,15 +248,17 @@ display: none; } + .avatar:hover { + border-color: #999; + } + .btn-clipboard { border: none; + color: #999; &:hover { background: transparent; - } - - i { - color: #999; + color: $gl-gray; } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 19a7a97ea0d..0562ee7b178 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -876,3 +876,11 @@ pre.light-well { pointer-events: none; } } + +.variables-table { + table-layout: fixed; + + .variable-key { + width: 30%; + } +} diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index b81842e319b..c2bb8464824 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -112,6 +112,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :koding_enabled, :koding_url, :email_author_in_body, + :html_emails_enabled, :repository_checks_enabled, :metrics_packet_size, :send_user_confirmation_email, diff --git a/app/helpers/lfs_helper.rb b/app/controllers/concerns/lfs_request.rb index 2425c3a8bc8..ed22b1e5470 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -1,5 +1,21 @@ -module LfsHelper - include Gitlab::Routing.url_helpers +# This concern assumes: +# - a `#project` accessor +# - a `#user` accessor +# - a `#authentication_result` accessor +# - a `#can?(object, action, subject)` method +# - a `#ci?` method +# - a `#download_request?` method +# - a `#upload_request?` method +# - a `#has_authentication_ability?(ability)` method +module LfsRequest + extend ActiveSupport::Concern + + included do + before_action :require_lfs_enabled! + before_action :lfs_check_access! + end + + private def require_lfs_enabled! return if Gitlab.config.lfs.enabled @@ -17,35 +33,15 @@ module LfsHelper return if download_request? && lfs_download_access? return if upload_request? && lfs_upload_access? - if project.public? || (user && user.can?(:read_project, project)) - render_lfs_forbidden + if project.public? || can?(user, :read_project, project) + lfs_forbidden! else render_lfs_not_found end end - def lfs_download_access? - return false unless project.lfs_enabled? - - ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? - end - - def objects - @objects ||= (params[:objects] || []).to_a - end - - def user_can_download_code? - has_authentication_ability?(:download_code) && can?(user, :download_code, project) - end - - def build_can_download_code? - has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project) - end - - def lfs_upload_access? - return false unless project.lfs_enabled? - - has_authentication_ability?(:push_code) && can?(user, :push_code, project) + def lfs_forbidden! + render_lfs_forbidden end def render_lfs_forbidden @@ -70,6 +66,30 @@ module LfsHelper ) end + def lfs_download_access? + return false unless project.lfs_enabled? + + ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? + end + + def lfs_upload_access? + return false unless project.lfs_enabled? + + has_authentication_ability?(:push_code) && can?(user, :push_code, project) + end + + def lfs_deploy_token? + authentication_result.lfs_deploy_token?(project) + end + + def user_can_download_code? + has_authentication_ability?(:download_code) && can?(user, :download_code, project) + end + + def build_can_download_code? + has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project) + end + def storage_project @storage_project ||= begin result = project @@ -82,4 +102,8 @@ module LfsHelper result end end + + def objects + @objects ||= (params[:objects] || []).to_a + end end diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb index 3717c49f272..fbf9a026b10 100644 --- a/app/controllers/concerns/toggle_award_emoji.rb +++ b/app/controllers/concerns/toggle_award_emoji.rb @@ -1,11 +1,8 @@ module ToggleAwardEmoji extend ActiveSupport::Concern - included do - before_action :authenticate_user!, only: [:toggle_award_emoji] - end - def toggle_award_emoji + authenticate_user! name = params.require(:name) if awardable.user_can_award?(current_user, name) diff --git a/app/controllers/concerns/workhorse_request.rb b/app/controllers/concerns/workhorse_request.rb new file mode 100644 index 00000000000..43c0f1b173c --- /dev/null +++ b/app/controllers/concerns/workhorse_request.rb @@ -0,0 +1,13 @@ +module WorkhorseRequest + extend ActiveSupport::Concern + + included do + before_action :verify_workhorse_api! + end + + private + + def verify_workhorse_api! + Gitlab::Workhorse.verify_api_request!(request.headers) + end +end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 4b3c71874be..a10cdcce72b 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -6,9 +6,9 @@ class HelpController < ApplicationController def index @help_index = File.read(Rails.root.join('doc', 'README.md')) - # Prefix Markdown links with `help/` unless they already have been - # See http://rubular.com/r/ie2MlpdUMq - @help_index.gsub!(/(\]\()(\/?help\/)?([^\)\(]+\))/, '\1/help/\3') + # Prefix Markdown links with `help/` unless they are external links + # See http://rubular.com/r/MioSrVLK3S + @help_index.gsub!(%r{(\]\()(?!.+://)([^\)\(]+\))}, '\1/help/\2') end def show diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb index f193adb46b4..daa51ae41df 100644 --- a/app/controllers/profiles/avatars_controller.rb +++ b/app/controllers/profiles/avatars_controller.rb @@ -4,7 +4,6 @@ class Profiles::AvatarsController < Profiles::ApplicationController @user.remove_avatar! @user.save - @user.reset_events_cache redirect_to profile_path end diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index ada7db3c552..53788687076 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -20,7 +20,6 @@ class Projects::AvatarsController < Projects::ApplicationController @project.remove_avatar! @project.save - @project.reset_events_cache redirect_to edit_project_path(@project) end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 56ced786311..9940263ae24 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -13,7 +13,6 @@ class Projects::BlobController < Projects::ApplicationController before_action :assign_blob_vars before_action :commit, except: [:new, :create] before_action :blob, except: [:new, :create] - before_action :from_merge_request, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update] before_action :editor_variables, except: [:show, :preview, :diff] before_action :validate_diff_params, only: :diff @@ -39,14 +38,6 @@ class Projects::BlobController < Projects::ApplicationController def update @path = params[:file_path] if params[:file_path].present? - after_edit_path = - if from_merge_request && @target_branch == @ref - diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + - "##{hexdigest(@path)}" - else - namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) - end - create_commit(Files::UpdateService, success_path: after_edit_path, failure_view: :edit, failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) @@ -124,9 +115,14 @@ class Projects::BlobController < Projects::ApplicationController render_404 end - def from_merge_request - # If blob edit was initiated from merge request page - @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) + def after_edit_path + from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid]) + if from_merge_request && @target_branch == @ref + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + + "##{hexdigest(@path)}" + else + namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) + end end def editor_variables diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 6b9f37983c4..89d84809e3a 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -36,7 +36,7 @@ class Projects::BranchesController < Projects::ApplicationController execute(branch_name, ref) if params[:issue_iid] - issue = @project.issues.find_by(iid: params[:issue_iid]) + issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid]) SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue end diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index fd263960b93..ac639ef015b 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -6,7 +6,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController before_action :authorize_read_cycle_analytics! def show - @cycle_analytics = ::CycleAnalytics.new(@project, from: start_date(cycle_analytics_params)) + @cycle_analytics = ::CycleAnalytics.new(@project, current_user, from: start_date(cycle_analytics_params)) stats_values, cycle_analytics_json = generate_cycle_analytics_data diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 3f41916e6d3..8714349e27f 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -18,6 +18,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController private + def download_request? + raise NotImplementedError + end + + def upload_request? + raise NotImplementedError + end + def authenticate_user @authentication_result = Gitlab::Auth::Result.new @@ -130,10 +138,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController authentication_result.ci?(project) end - def lfs_deploy_token? - authentication_result.lfs_deploy_token?(project) - end - def authentication_has_download_access? has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code) end @@ -149,8 +153,4 @@ class Projects::GitHttpClientController < Projects::ApplicationController def authentication_project authentication_result.project end - - def verify_workhorse_api! - Gitlab::Workhorse.verify_api_request!(request.headers) - end end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 13caeb42d40..9184dcccac5 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -1,7 +1,5 @@ -# This file should be identical in GitLab Community Edition and Enterprise Edition - class Projects::GitHttpController < Projects::GitHttpClientController - before_action :verify_workhorse_api! + include WorkhorseRequest # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) @@ -67,14 +65,18 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def render_denied - if user && user.can?(:read_project, project) - render plain: 'Access denied', status: :forbidden + if user && can?(user, :read_project, project) + render plain: access_denied_message, status: :forbidden else # Do not leak information about project existence render_not_found end end + def access_denied_message + 'Access denied' + end + def upload_pack_allowed? return false unless Gitlab.config.gitlab_shell.upload_pack diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb index 2d493276941..440259b643c 100644 --- a/app/controllers/projects/lfs_api_controller.rb +++ b/app/controllers/projects/lfs_api_controller.rb @@ -1,8 +1,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController - include LfsHelper + include LfsRequest - before_action :require_lfs_enabled! - before_action :lfs_check_access!, except: [:deprecated] + skip_before_action :lfs_check_access!, only: [:deprecated] def batch unless objects.present? @@ -31,6 +30,14 @@ class Projects::LfsApiController < Projects::GitHttpClientController private + def download_request? + params[:operation] == 'download' + end + + def upload_request? + params[:operation] == 'upload' + end + def existing_oids @existing_oids ||= begin storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid) @@ -79,12 +86,4 @@ class Projects::LfsApiController < Projects::GitHttpClientController } } end - - def download_request? - params[:operation] == 'download' - end - - def upload_request? - params[:operation] == 'upload' - end end diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb index 9005b104e90..32759672b6c 100644 --- a/app/controllers/projects/lfs_storage_controller.rb +++ b/app/controllers/projects/lfs_storage_controller.rb @@ -1,9 +1,8 @@ class Projects::LfsStorageController < Projects::GitHttpClientController - include LfsHelper + include LfsRequest + include WorkhorseRequest - before_action :require_lfs_enabled! - before_action :lfs_check_access! - before_action :verify_workhorse_api!, only: [:upload_authorize] + skip_before_action :verify_workhorse_api!, only: [:download, :upload_finalize] def download lfs_object = LfsObject.find_by_oid(oid) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 707cc018a12..c7cde9e6a14 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -329,17 +329,18 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) if params[:merge_when_build_succeeds].present? - unless @merge_request.pipeline + unless @merge_request.head_pipeline @status = :failed return end - if @merge_request.pipeline.active? + if @merge_request.head_pipeline.active? MergeRequests::MergeWhenPipelineSucceedsService .new(@project, current_user, merge_params) .execute(@merge_request) + @status = :merge_when_build_succeeds - elsif @merge_request.pipeline.success? + elsif @merge_request.head_pipeline.success? # This can be triggered when a user clicks the auto merge button while # the tests finish at about the same time MergeWorker.perform_async(@merge_request.id, current_user.id, params) @@ -403,7 +404,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_status - pipeline = @merge_request.pipeline + pipeline = @merge_request.head_pipeline + if pipeline status = pipeline.status coverage = pipeline.try(:coverage) @@ -539,7 +541,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_widget_vars - @pipeline = @merge_request.pipeline + @pipeline = @merge_request.head_pipeline end def define_commit_vars @@ -568,8 +570,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_pipelines_vars @pipelines = @merge_request.all_pipelines - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses.relevant if @pipeline.present? + @pipeline = @merge_request.head_pipeline + @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 end def define_new_vars @@ -636,7 +638,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge_when_build_succeeds_active? params[:merge_when_build_succeeds].present? && - @merge_request.pipeline && @merge_request.pipeline.active? + @merge_request.head_pipeline && @merge_request.head_pipeline.active? end def build_merge_request diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb index 5685d0f4e7c..52517381c65 100644 --- a/app/controllers/projects/todos_controller.rb +++ b/app/controllers/projects/todos_controller.rb @@ -16,13 +16,7 @@ class Projects::TodosController < Projects::ApplicationController @issuable ||= begin case params[:issuable_type] when "issue" - issue = @project.issues.find(params[:issuable_id]) - - if can?(current_user, :read_issue, issue) - issue - else - render_404 - end + IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id]) when "merge_request" @project.merge_requests.find(params[:issuable_id]) end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 6297b2db369..9a74e36870b 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -16,14 +16,12 @@ # label_name: string # sort: string # -require_relative 'projects_finder' - class IssuableFinder NONE = '0' attr_accessor :current_user, :params - def initialize(current_user, params) + def initialize(current_user, params = {}) @current_user = current_user @params = params end @@ -43,6 +41,14 @@ class IssuableFinder sort(items) end + def find(*params) + execute.find(*params) + end + + def find_by(*params) + execute.find_by(*params) + end + def group return @group if defined?(@group) diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index 0b7832e6583..a653a6d59c6 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -12,7 +12,7 @@ class NotesFinder when "commit" project.notes.for_commit_id(target_id).non_diff_notes when "issue" - project.issues.visible_to_user(current_user).find(target_id).notes.inc_author + IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author when "merge_request" project.merge_requests.find(target_id).mr_and_commit_notes.inc_author when "snippet", "project_snippet" diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index cbab1fd5967..81e0b6bb5ae 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -43,7 +43,7 @@ module DropdownsHelper default_label = data_attr[:default_label] content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") - output << icon('caret-down') + output << icon('chevron-down') output.html_safe end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 75cd9eece5c..19ab059aea6 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -8,11 +8,7 @@ module GroupsHelper group = Group.find_by(path: group) end - if group && group.avatar.present? - group.avatar.url - else - image_path('no_group_avatar.png') - end + group.try(:avatar_url) || image_path('no_group_avatar.png') end def group_title(group, name = nil, url = nil) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8bebda07787..6584aa3edd5 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -143,6 +143,20 @@ module IssuablesHelper end end + def issuable_filter_params + [ + :search, + :author_id, + :assignee_id, + :milestone_title, + :label_name + ] + end + + def issuable_filter_present? + issuable_filter_params.any? { |k| params.key?(k) } + end + private def assigned_issuables_count(assignee, issuable_type, state) @@ -165,13 +179,9 @@ module IssuablesHelper end end - def issuable_filters_present - params[:search] || params[:author_id] || params[:assignee_id] || params[:milestone_title] || params[:label_name] - end - def issuables_count_for_state(issuable_type, state) issuables_finder = public_send("#{issuable_type}_finder") - + params = issuables_finder.params.merge(state: state) finder = issuables_finder.class.new(issuables_finder.current_user, params) diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 96116e916dd..0d20c9092c4 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -4,6 +4,7 @@ module Emails setup_note_mail(note_id, recipient_id) @commit = @note.noteable + @discussion = @note.to_discussion if @note.diff_note? @target_url = namespace_project_commit_url(*note_target_url_options) mail_answer_thread(@commit, @@ -24,6 +25,7 @@ module Emails setup_note_mail(note_id, recipient_id) @merge_request = @note.noteable + @discussion = @note.to_discussion if @note.diff_note? @target_url = namespace_project_merge_request_url(*note_target_url_options) mail_answer_thread(@merge_request, note_thread_options(recipient_id)) end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4294a10e9e3..fabbf97d4db 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -317,7 +317,7 @@ module Ci def merge_requests @merge_requests ||= project.merge_requests .where(source_branch: self.ref) - .select { |merge_request| merge_request.pipeline.try(:id) == self.id } + .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id } end private diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb index 7fd0905ee81..9dd4d9c6f24 100644 --- a/app/models/concerns/protected_branch_access.rb +++ b/app/models/concerns/protected_branch_access.rb @@ -2,6 +2,9 @@ module ProtectedBranchAccess extend ActiveSupport::Concern included do + belongs_to :protected_branch + delegate :project, to: :protected_branch + scope :master, -> { where(access_level: Gitlab::Access::MASTER) } scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) } end @@ -9,4 +12,10 @@ module ProtectedBranchAccess def humanize self.class.human_access_levels[self.access_level] end + + def check_access(user) + return true if user.is_admin? + + project.team.max_member_access(user.id) >= access_level + end end diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb index cb8e088d21d..ba4ee6fcf9d 100644 --- a/app/models/cycle_analytics.rb +++ b/app/models/cycle_analytics.rb @@ -1,14 +1,15 @@ class CycleAnalytics STAGES = %i[issue plan code test review staging production].freeze - def initialize(project, from:) + def initialize(project, current_user, from:) @project = project + @current_user = current_user @from = from @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil) end def summary - @summary ||= Summary.new(@project, from: @from) + @summary ||= Summary.new(@project, @current_user, from: @from) end def permissions(user:) diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb index b46db449bf3..82f53d17ddd 100644 --- a/app/models/cycle_analytics/summary.rb +++ b/app/models/cycle_analytics/summary.rb @@ -1,12 +1,13 @@ class CycleAnalytics class Summary - def initialize(project, from:) + def initialize(project, current_user, from:) @project = project + @current_user = current_user @from = from end def new_issues - @project.issues.created_after(@from).count + IssuesFinder.new(@current_user, project_id: @project.id).execute.created_after(@from).count end def commits diff --git a/app/models/discussion.rb b/app/models/discussion.rb index de06c13481a..75a85563235 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -25,7 +25,12 @@ class Discussion to: :last_resolved_note, allow_nil: true - delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true + delegate :blob, + :highlighted_diff_lines, + :diff_lines, + + to: :diff_file, + allow_nil: true def self.for_notes(notes) notes.group_by(&:discussion_id).values.map { |notes| new(notes) } @@ -159,10 +164,11 @@ class Discussion end # Returns an array of at most 16 highlighted lines above a diff note - def truncated_diff_lines + def truncated_diff_lines(highlight: true) + lines = highlight ? highlighted_diff_lines : diff_lines prev_lines = [] - highlighted_diff_lines.each do |line| + lines.each do |line| if line.meta? prev_lines.clear else diff --git a/app/models/event.rb b/app/models/event.rb index 21eaca917b8..2662f170765 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -43,12 +43,6 @@ class Event < ActiveRecord::Base scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) } class << self - def reset_event_cache_for(target) - Event.where(target_id: target.id, target_type: target.class.to_s). - order('id DESC').limit(100). - update_all(updated_at: Time.now) - end - # Update Gitlab::ContributionsCalendar#activity_dates if this changes def contributions where("action = ? OR (target_type in (?) AND action in (?))", @@ -353,6 +347,10 @@ class Event < ActiveRecord::Base update_all(last_activity_at: created_at) end + def authored_by?(user) + user ? author_id == user.id : false + end + private def recent_update? diff --git a/app/models/issue.rb b/app/models/issue.rb index dd0cb75f9a8..fbf07040301 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -182,18 +182,6 @@ class Issue < ActiveRecord::Base branches_with_iid - branches_with_merge_request end - # Reset issue events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when an issue is updated - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - # To allow polymorphism with MergeRequest. def source_project project diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index fdf54cc8a7e..64990f8134e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -605,18 +605,6 @@ class MergeRequest < ActiveRecord::Base self.target_project.repository.branch_names.include?(self.target_branch) end - # Reset merge request events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when a merge request is updated - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - def merge_commit_message message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n" message << "#{title}\n\n" @@ -690,7 +678,7 @@ class MergeRequest < ActiveRecord::Base def mergeable_ci_state? return true unless project.only_allow_merge_if_build_succeeds? - !pipeline || pipeline.success? || pipeline.skipped? + !head_pipeline || head_pipeline.success? || head_pipeline.skipped? end def environments @@ -786,14 +774,14 @@ class MergeRequest < ActiveRecord::Base commits.map(&:sha) end - def pipeline + def head_pipeline return unless diff_head_sha && source_project - @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) + @head_pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end def all_pipelines - return unless source_project + return Ci::Pipeline.none unless source_project @all_pipelines ||= source_project.pipelines .where(sha: all_commits_sha, ref: source_branch) diff --git a/app/models/note.rb b/app/models/note.rb index ed4224e3046..5b50ca285c3 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -201,19 +201,6 @@ class Note < ActiveRecord::Base super(noteable_type.to_s.classify.constantize.base_class.to_s) end - # Reset notes events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when a note is updated - # * when a note is removed - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - def editable? !system? end diff --git a/app/models/project.rb b/app/models/project.rb index 9256e9ddd95..f01cb613b85 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -687,9 +687,9 @@ class Project < ActiveRecord::Base self.id end - def get_issue(issue_id) + def get_issue(issue_id, current_user) if default_issues_tracker? - issues.find_by(iid: issue_id) + IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id) else ExternalIssue.new(issue_id, self) end @@ -976,7 +976,6 @@ class Project < ActiveRecord::Base begin gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") send_move_instructions(old_path_with_namespace) - reset_events_cache @old_path_with_namespace = old_path_with_namespace @@ -1043,22 +1042,6 @@ class Project < ActiveRecord::Base attrs end - # Reset events cache related to this project - # - # Since we do cache @event we need to reset cache in special cases: - # * when project was moved - # * when project was renamed - # * when the project avatar changes - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.where(project_id: self.id). - order('id DESC').limit(100). - update_all(updated_at: Time.now) - end - def project_member(user) project_members.find_by(user_id: user) end diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 806b3ccd275..771e3376613 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -1,9 +1,6 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base include ProtectedBranchAccess - belongs_to :protected_branch - delegate :project, to: :protected_branch - validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER] } @@ -13,10 +10,4 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base Gitlab::Access::DEVELOPER => "Developers + Masters" }.with_indifferent_access end - - def check_access(user) - return true if user.is_admin? - - project.team.max_member_access(user.id) >= access_level - end end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 92e9c51d883..14610cb42b7 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,9 +1,6 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base include ProtectedBranchAccess - belongs_to :protected_branch - delegate :project, to: :protected_branch - validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS] } @@ -18,8 +15,7 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base def check_access(user) return false if access_level == Gitlab::Access::NO_ACCESS - return true if user.is_admin? - project.team.max_member_access(user.id) >= access_level + super end end diff --git a/app/models/repository.rb b/app/models/repository.rb index bf136ccdb6c..e2e7d08abac 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -196,18 +196,12 @@ class Repository options = { message: message, tagger: user_to_committer(user) } if message - rugged.tags.create(tag_name, target, options) - tag = find_tag(tag_name) - - GitHooksService.new.execute(user, path_to_repo, oldrev, tag.target, ref) do - # we already created a tag, because we need tag SHA to pass correct - # values to hooks + GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do |service| + raw_tag = rugged.tags.create(tag_name, target, options) + service.newrev = raw_tag.target_id end - tag - rescue GitHooksService::PreReceiveError - rugged.tags.delete(tag_name) - raise + find_tag(tag_name) end def rm_branch(user, branch_name) diff --git a/app/models/user.rb b/app/models/user.rb index 513a19d81d2..b54ce14f0bf 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -445,27 +445,21 @@ class User < ActiveRecord::Base end def refresh_authorized_projects - loop do - begin - Gitlab::Database.serialized_transaction do - project_authorizations.delete_all - - # project_authorizations_union can return multiple records for the same project/user with - # different access_level so we take row with the maximum access_level - project_authorizations.connection.execute <<-SQL - INSERT INTO project_authorizations (user_id, project_id, access_level) - SELECT user_id, project_id, MAX(access_level) AS access_level - FROM (#{project_authorizations_union.to_sql}) sub - GROUP BY user_id, project_id - SQL - - update_column(:authorized_projects_populated, true) unless authorized_projects_populated - end - - break - # In the event of a concurrent modification Rails raises StatementInvalid. - # In this case we want to keep retrying until the transaction succeeds - rescue ActiveRecord::StatementInvalid + transaction do + project_authorizations.delete_all + + # project_authorizations_union can return multiple records for the same + # project/user with different access_level so we take row with the maximum + # access_level + project_authorizations.connection.execute <<-SQL + INSERT INTO project_authorizations (user_id, project_id, access_level) + SELECT user_id, project_id, MAX(access_level) AS access_level + FROM (#{project_authorizations_union.to_sql}) sub + GROUP BY user_id, project_id + SQL + + unless authorized_projects_populated + update_column(:authorized_projects_populated, true) end end end @@ -708,20 +702,6 @@ class User < ActiveRecord::Base project.project_member(self) end - # Reset project events cache related to this user - # - # Since we do cache @event we need to reset cache in special cases: - # * when the user changes their avatar - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.where(author_id: id). - order('id DESC').limit(1000). - update_all(updated_at: Time.now) - end - def full_website_url return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\// diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb index abefcd5cc02..a0db5b8f0f4 100644 --- a/app/serializers/analytics_build_entity.rb +++ b/app/serializers/analytics_build_entity.rb @@ -13,7 +13,7 @@ class AnalyticsBuildEntity < Grape::Entity end expose :duration, as: :total_time do |build| - distance_of_time_as_hash(build.duration.to_f) + build.duration ? distance_of_time_as_hash(build.duration.to_f) : {} end expose :branch do diff --git a/app/serializers/build_entity.rb b/app/serializers/build_entity.rb index cf1c418a88e..b5384e6462b 100644 --- a/app/serializers/build_entity.rb +++ b/app/serializers/build_entity.rb @@ -16,6 +16,9 @@ class BuildEntity < Grape::Entity path_to(:play_namespace_project_build, build) end + expose :created_at + expose :updated_at + private def path_to(route, build) diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb index 918abba8d99..9607ad55a8b 100644 --- a/app/serializers/entity_date_helper.rb +++ b/app/serializers/entity_date_helper.rb @@ -2,6 +2,8 @@ module EntityDateHelper include ActionView::Helpers::DateHelper def interval_in_words(diff) + return 'Not started' unless diff + "#{distance_of_time_in_words(Time.now, diff)} ago" end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index cde856b0186..e3bc9847200 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -45,9 +45,15 @@ module Ci return error('No builds for this pipeline.') end - pipeline.save - pipeline.process! - pipeline + Ci::Pipeline.transaction do + pipeline.save + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end + + pipeline.tap(&:process!) end private diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 8face432d97..2e028c44d8b 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -5,10 +5,7 @@ module Ci def execute(pipeline) @pipeline = pipeline - # This method will ensure that our pipeline does have all builds for all stages created - if created_builds.empty? - create_builds! - end + ensure_created_builds! # TODO, remove me in 9.0 new_builds = stage_indexes_of_created_builds.map do |index| @@ -22,10 +19,6 @@ module Ci private - def create_builds! - Ci::CreatePipelineBuildsService.new(project, current_user).execute(pipeline) - end - def process_stage(index) current_status = status_for_prior_stages(index) @@ -76,5 +69,18 @@ module Ci def created_builds pipeline.builds.created end + + # This method is DEPRECATED and should be removed in 9.0. + # + # We need it to maintain backwards compatibility with previous versions + # when builds were not created within one transaction with the pipeline. + # + def ensure_created_builds! + return if created_builds.any? + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end end end diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb index 172bd85dade..6cd3908d43a 100644 --- a/app/services/git_hooks_service.rb +++ b/app/services/git_hooks_service.rb @@ -1,6 +1,8 @@ class GitHooksService PreReceiveError = Class.new(StandardError) + attr_accessor :oldrev, :newrev, :ref + def execute(user, repo_path, oldrev, newrev, ref) @repo_path = repo_path @user = Gitlab::GlId.gl_id(user) @@ -16,7 +18,7 @@ class GitHooksService end end - yield + yield self run_hook('post-receive') end @@ -25,6 +27,6 @@ class GitHooksService def run_hook(name) hook = Gitlab::Git::Hook.new(name, @repo_path) - hook.trigger(@user, @oldrev, @newrev, @ref) + hook.trigger(@user, oldrev, newrev, ref) end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 575795788de..ce68e433ab8 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -85,14 +85,15 @@ class IssuableBaseService < BaseService def find_or_create_label_ids labels = params.delete(:labels) + return unless labels - params[:label_ids] = labels.split(',').map do |label_name| + params[:label_ids] = labels.split(",").map do |label_name| service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip) label = service.execute - label.id - end + label.try(:id) + end.compact end def process_label_ids(attributes, existing_label_ids: nil) @@ -140,6 +141,7 @@ class IssuableBaseService < BaseService params.delete(:state_event) params[:author] ||= current_user + label_ids = process_label_ids(params) issuable.assign_attributes(params) @@ -184,8 +186,6 @@ class IssuableBaseService < BaseService params[:label_ids] = process_label_ids(params, existing_label_ids: issuable.label_ids) if params.present? && update_issuable(issuable, params) - issuable.reset_events_cache - # We do not touch as it will affect a update on updated_at field ActiveRecord::Base.no_touching do handle_common_system_notes(issuable, old_labels: old_labels) diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb index d622f9edd33..cf4f7606c94 100644 --- a/app/services/labels/find_or_create_service.rb +++ b/app/services/labels/find_or_create_service.rb @@ -22,9 +22,14 @@ module Labels ).execute(skip_authorization: skip_authorization) end + # Only creates the label if current_user can do so, if the label does not exist + # and the user can not create the label, nil is returned def find_or_create_label new_label = available_labels.find_by(title: title) - new_label ||= project.labels.create(params) + + if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, project)) + new_label = project.labels.create(params) + end new_label end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 58f69a41e14..800fd39c424 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -55,7 +55,7 @@ module MergeRequests def pipeline_merge_requests(pipeline) merge_requests_for(pipeline.ref).each do |merge_request| - next unless pipeline == merge_request.pipeline + next unless pipeline == merge_request.head_pipeline yield merge_request end @@ -63,7 +63,7 @@ module MergeRequests def commit_status_merge_requests(commit_status) merge_requests_for(commit_status.ref).each do |merge_request| - pipeline = merge_request.pipeline + pipeline = merge_request.head_pipeline next unless pipeline next unless pipeline.sha == commit_status.sha diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index dd0d738674e..bebfca7537b 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -81,7 +81,7 @@ module MergeRequests commit = commits.first merge_request.title = commit.title merge_request.description ||= commit.description.try(:strip) - elsif iid && (issue = merge_request.target_project.get_issue(iid)) && !issue.try(:confidential?) + elsif iid && issue = merge_request.target_project.get_issue(iid, current_user) case issue when Issue merge_request.title = "Resolve \"#{issue.title}\"" diff --git a/app/services/notes/delete_service.rb b/app/services/notes/delete_service.rb index 7f1b30ec84e..a673e8e9dde 100644 --- a/app/services/notes/delete_service.rb +++ b/app/services/notes/delete_service.rb @@ -2,7 +2,6 @@ module Notes class DeleteService < BaseService def execute(note) note.destroy - note.reset_events_cache end end end diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 1361b1e0300..75a4b3ed826 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -5,7 +5,6 @@ module Notes note.update_attributes(params.merge(updated_by: current_user)) note.create_new_cross_references!(current_user) - note.reset_events_cache if note.previous_changes.include?('note') TodoService.new.update_note(note, current_user) diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 28470f59807..34ec575e808 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -61,9 +61,6 @@ module Projects # Move missing group labels to project Labels::TransferService.new(current_user, old_group, project).execute - # clear project cached events - project.reset_events_cache - # Move uploads Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index 71ff14a3f20..38683fdf6d7 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -3,16 +3,10 @@ class AvatarUploader < CarrierWave::Uploader::Base storage :file - after :store, :reset_events_cache - def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end - def reset_events_cache(file) - model.reset_events_cache if model.is_a?(User) - end - def exists? model.avatar.file && model.avatar.file.exists? end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index ce803f329f9..7accd2529af 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -443,7 +443,16 @@ Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. - + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :html_emails_enabled do + = f.check_box :html_emails_enabled + Enable HTML emails + .help-block + By default GitLab sends emails in HTML and plain text formats so mail + clients can choose what format to use. Disable this option if you only + want to send emails in plain text format. %fieldset %legend Automatic Git repository housekeeping .form-group diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index f7abad54286..48b0fd504f4 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -4,13 +4,13 @@ %ul.nav-links = nav_link(page: [dashboard_projects_path, root_path]) do = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do - Your Projects + Your projects = nav_link(page: starred_dashboard_projects_path) do = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do - Starred Projects + Starred projects = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do - Explore Projects + Explore projects .nav-controls = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index fdea834ff45..4a55aac0df6 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -4,6 +4,18 @@ Welcome to GitLab %p.blank-state-text Code, test, and deploy together + +- if current_user.can_create_group? + .blank-state + .blank-state-icon + = custom_icon("group", size: 50) + %h3.blank-state-title + You can create a group for several dependent projects. + %p.blank-state-text + Groups are the best way to manage projects and members. + = link_to new_group_path, class: "btn btn-new" do + New group + .blank-state .blank-state-icon = custom_icon("project", size: 50) @@ -21,17 +33,6 @@ = link_to new_project_path, class: "btn btn-new" do New project -- if current_user.can_create_group? - .blank-state - .blank-state-icon - = custom_icon("group", size: 50) - %h3.blank-state-title - You can create a group for several dependent projects. - %p.blank-state-text - Groups are the best way to manage projects and members. - = link_to new_group_path, class: "btn btn-new" do - New group - -if publicish_project_count > 0 .blank-state .blank-state-icon diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 472d698486b..62f52086be4 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -50,13 +50,13 @@ data: { data: todo_actions_options }}) .pull-right .dropdown.inline.prepend-left-10 - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li = link_to todos_filter_path(sort: sort_value_priority) do diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 5c318cd3b8b..a0bd14df209 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -3,14 +3,13 @@ .event-item-timestamp #{time_ago_with_tooltip(event.created_at)} - = cache [event, current_application_settings, "v2.2"] do - = author_avatar(event, size: 40) + = author_avatar(event, size: 40) - - if event.created_project? - = render "events/event/created_project", event: event - - elsif event.push? - = render "events/event/push", event: event - - elsif event.commented? - = render "events/event/note", event: event - - else - = render "events/event/common", event: event + - if event.created_project? + = render "events/event/created_project", event: event + - elsif event.push? + = render "events/event/push", event: event + - elsif event.commented? + = render "events/event/note", event: event + - else + = render "events/event/common", event: event diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 44fff49d99c..64ca3c32e01 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -18,7 +18,7 @@ - few_commits.each do |commit| = render "events/commit", commit: commit, project: project, event: event - - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) + - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user) - if event.commits_count > 1 %li.commits-stat - if event.commits_count > 2 @@ -35,12 +35,12 @@ Compare #{from_label}...#{truncate_sha(event.commit_to)} - if create_mr - %span{"data-user-is" => event.author_id, "data-display" => "inline"} + %span or = link_to create_mr_path(project.default_branch, event.ref_name, project) do create a merge request - elsif create_mr - %li.commits-stat{"data-user-is" => event.author_id} + %li.commits-stat = link_to create_mr_path(project.default_branch, event.ref_name, project) do Create Merge Request - elsif event.rm_ref? diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index a1b39d9e1a0..4e5d965ccbe 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -17,13 +17,13 @@ .pull-right .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to explore_groups_path(sort: sort_value_recently_created) do diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 4cff14b096b..5ea154c36b4 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -1,13 +1,13 @@ - if current_user .dropdown - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('globe') %span.light Visibility: - if params[:visibility_level].present? = visibility_level_label(params[:visibility_level].to_i) - else Any - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(visibility_level: nil) do @@ -20,14 +20,14 @@ - if @tags.present? .dropdown - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('tags') %span.light Tags: - if params[:tag].present? = params[:tag] - else Any - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(tag: nil) do diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml index 2fd4859c1c6..882fdf1317d 100644 --- a/app/views/invites/show.html.haml +++ b/app/views/invites/show.html.haml @@ -6,7 +6,7 @@ - if inviter = @member.created_by by = link_to inviter.name, user_url(inviter) - to join + to join - case @member.source - when Project - project = @member.source @@ -20,11 +20,18 @@ = link_to group.name, group_url(group) as #{@member.human_access}. -- if @member.source.users.include?(current_user) +- is_member = @member.source.users.include?(current_user) + +- if is_member %p However, you are already a member of this #{@member.source.is_a?(Group) ? "group" : "project"}. Sign in using a different account to accept the invitation. -- else + +- if @member.invite_email != current_user.email + %p + Note that this invitation was sent to #{mail_to @member.invite_email}, but you are signed in as #{link_to current_user.to_reference, user_url(current_user)} with email #{mail_to current_user.email}. + +- unless is_member .actions = link_to "Accept invitation", accept_invite_url(@token), method: :post, class: "btn btn-success" = link_to "Decline", decline_invite_url(@token), method: :post, class: "btn btn-danger prepend-left-10" diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 757de92d6d4..3e488cf73b9 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -56,5 +56,3 @@ = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') = render 'layouts/bootlint' if Rails.env.development? - - = render 'layouts/user_styles' diff --git a/app/views/layouts/_user_styles.html.haml b/app/views/layouts/_user_styles.html.haml deleted file mode 100644 index b76b3cb5510..00000000000 --- a/app/views/layouts/_user_styles.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -:css - [data-user-is] { - display: none !important; - } - - [data-user-is="#{current_user.try(:id)}"] { - display: block !important; - } - - [data-user-is="#{current_user.try(:id)}"][data-display="inline"] { - display: inline !important; - } - - [data-user-is-not] { - display: block !important; - } - - [data-user-is-not][data-display="inline"] { - display: inline !important; - } - - [data-user-is-not="#{current_user.try(:id)}"] { - display: none !important; - } diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 99a58bbb676..701bcd3ab71 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -70,7 +70,7 @@ %span Issues - if @project.default_issues_tracker? - %span.badge.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count) + %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count) - if project_nav_tab? :merge_requests = nav_link(controller: :merge_requests) do diff --git a/app/views/notify/_note_message.text.erb b/app/views/notify/_note_message.text.erb new file mode 100644 index 00000000000..f82cbc9a3fc --- /dev/null +++ b/app/views/notify/_note_message.text.erb @@ -0,0 +1,5 @@ +<% if current_application_settings.email_author_in_body %> + <%= @note.author_name %> wrote: +<% end -%> + +<%= @note.note %> diff --git a/app/views/notify/_note_mr_or_commit_email.html.haml b/app/views/notify/_note_mr_or_commit_email.html.haml new file mode 100644 index 00000000000..edf8dfe7e9e --- /dev/null +++ b/app/views/notify/_note_mr_or_commit_email.html.haml @@ -0,0 +1,18 @@ += content_for :head do + = stylesheet_link_tag 'mailers/highlighted_diff_email' + +New comment + +- if @discussion && @discussion.diff_file + on + = link_to @note.diff_file.file_path, @target_url, class: 'details' + \: + %table + = render partial: "projects/diffs/line", + collection: @discussion.truncated_diff_lines, + as: :line, + locals: { diff_file: @note.diff_file, + plain: true, + email: true } + += render 'note_message' diff --git a/app/views/notify/_note_mr_or_commit_email.text.erb b/app/views/notify/_note_mr_or_commit_email.text.erb new file mode 100644 index 00000000000..b4fcdf6b1e9 --- /dev/null +++ b/app/views/notify/_note_mr_or_commit_email.text.erb @@ -0,0 +1,8 @@ +<% if @discussion && @discussion.diff_file -%> + on <%= @note.diff_file.file_path -%> +<% end -%>: + +<%= url %> + +<%= render 'simple_diff' if @discussion -%> +<%= render 'note_message' %> diff --git a/app/views/notify/_simple_diff.text.erb b/app/views/notify/_simple_diff.text.erb new file mode 100644 index 00000000000..c28d1cc34d3 --- /dev/null +++ b/app/views/notify/_simple_diff.text.erb @@ -0,0 +1,3 @@ +<% @discussion.truncated_diff_lines(highlight: false).each do |line| %> +> <%= line.text %> +<% end %> diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml index 1d961e4424c..0a650e3b2ca 100644 --- a/app/views/notify/note_commit_email.html.haml +++ b/app/views/notify/note_commit_email.html.haml @@ -1,2 +1,2 @@ -= render 'note_message' - +%p.details + = render 'note_mr_or_commit_email' diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb index aaeaf5fdf73..6aa085a172e 100644 --- a/app/views/notify/note_commit_email.text.erb +++ b/app/views/notify/note_commit_email.text.erb @@ -1,9 +1,2 @@ -New comment for Commit <%= @commit.short_id %> - -<%= url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %> - - -Author: <%= @note.author_name %> - -<%= @note.note %> - +New comment for Commit <%= @commit.short_id -%> +<%= render partial: 'note_mr_or_commit_email', locals: { url: @target_url } %> diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml index ea7e3d199fd..0a650e3b2ca 100644 --- a/app/views/notify/note_merge_request_email.html.haml +++ b/app/views/notify/note_merge_request_email.html.haml @@ -1,7 +1,2 @@ -- if @note.diff_note? && @note.diff_file - %p.details - New comment on diff for - = link_to @note.diff_file.file_path, @target_url - \: - -= render 'note_message' +%p.details + = render 'note_mr_or_commit_email' diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb index 8cdab63829e..2ce64c494cf 100644 --- a/app/views/notify/note_merge_request_email.text.erb +++ b/app/views/notify/note_merge_request_email.text.erb @@ -1,9 +1,2 @@ -New comment for Merge Request <%= @merge_request.to_reference %> - -<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> - - -<%= @note.author_name %> - -<%= @note.note %> - +New comment for Merge Request <%= @merge_request.to_reference -%> +<%= render partial: 'note_mr_or_commit_email', locals: { url: @target_url }%> diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 307c5a11206..25883de257c 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -1,5 +1,5 @@ = content_for :head do - = stylesheet_link_tag 'mailers/repository_push_email' + = stylesheet_link_tag 'mailers/highlighted_diff_email' %h3 #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 2a0352a71b7..a5dcd93f42e 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -27,5 +27,5 @@ = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = hidden_field_tag 'last_commit_sha', @last_commit_sha = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] + = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid] = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 2246316b540..5fd664c7a93 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -12,10 +12,10 @@ = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light = projects_sort_options_hash[@sort] - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_branches_path(sort: sort_value_name) do diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index f5562046953..d5004f6a066 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -116,7 +116,7 @@ .title Stage %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.stage-selection More - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu - @build.pipeline.stages.each do |stage| %li diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 120ba9ffcd2..6c33d80becd 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -9,7 +9,7 @@ = icon('comment') \ - if editable_diff?(diff_file) - - link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {} + - link_opts = @merge_request.persisted? ? { from_merge_request_iid: @merge_request.iid } : {} = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, blob: blob, link_opts: link_opts) diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index a3e4b5b777e..16c96b66714 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -25,7 +25,7 @@ %a{href: "##{line_code}", data: { linenumber: link_text }} %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }< - if email - %pre= diff_line_content(line.text) + %pre= line.text - else = diff_line_content(line.text) diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index abf4f697f86..5ee3979c7e7 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -9,13 +9,13 @@ spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' } .dropdown - %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light sort: - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id] diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index 747bfa554cb..d48923b422a 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -2,12 +2,12 @@ %h2.merge-requests-title = pluralize(@merge_requests.count, 'Related Merge Request') %ul.unstyled-list.related-merge-requests - - has_any_ci = @merge_requests.any?(&:pipeline) + - has_any_ci = @merge_requests.any?(&:head_pipeline) - @merge_requests.each do |merge_request| %li %span.merge-request-ci-status - - if merge_request.pipeline - = render_pipeline_status(merge_request.pipeline) + - if merge_request.head_pipeline + = render_pipeline_status(merge_request.head_pipeline) - elsif has_any_ci = icon('blank fw') %span.merge-request-id diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 9ffcc48eb80..fa189ae62d8 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -15,9 +15,9 @@ = icon('ban') CLOSED - - if merge_request.pipeline + - if merge_request.head_pipeline %li - = render_pipeline_status(merge_request.pipeline) + = render_pipeline_status(merge_request.head_pipeline) - if merge_request.open? && merge_request.broken? %li diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 9c6f562f7db..4a08ed045f4 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -34,10 +34,11 @@ = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do Pipelines %span.badge= @pipelines.size + - if @pipeline.present? %li.builds-tab = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do Builds - %span.badge= @statuses.size + %span.badge= @statuses_count %li.diffs-tab = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do Changes @@ -48,9 +49,10 @@ = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane - # This tab is always loaded via AJAX - - if @pipelines.any? + - if @pipeline.present? #builds.builds.tab-pane = render "projects/merge_requests/show/builds" + - if @pipelines.any? #pipelines.pipelines.tab-pane = render "projects/merge_requests/show/pipelines" @@ -65,5 +67,5 @@ :javascript var merge_request = new MergeRequest({ action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}", - buildsLoaded: "#{@pipelines.any? ? 'true' : 'false'}" + buildsLoaded: "#{@pipeline.present? ? 'true' : 'false'}" }); diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index a497f418c7c..0e2975bd551 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -59,15 +59,16 @@ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do Commits %span.badge= @commits_count - - if @pipeline + - if @pipelines.any? %li.pipelines-tab = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do Pipelines %span.badge= @pipelines.size + - if @pipeline.present? %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do Builds - %span.badge= @statuses.size + %span.badge= @statuses_count %li.diffs-tab = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do Changes diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 18c72ed875c..6d9b91ad0e7 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -1,7 +1,7 @@ - if @pipeline .mr-widget-heading - %w[success success_with_warnings skipped canceled failed running pending].each do |status| - .ci_widget{ class: "ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) } + .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) } = ci_icon_for_status(status) %span Pipeline diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 608fdf1c5f5..a8918c85dde 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -14,7 +14,7 @@ ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", - ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}", + ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}", ci_message: { normal: "Build {{status}} for \"{{title}}\"", preparing: "{{status}} build for \"{{title}}\"" diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index b43b13de4ca..1d39f3a7534 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -12,10 +12,10 @@ = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } .dropdown.inline - %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown'} } %span.light = projects_sort_options_hash[@sort] - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_tags_path(sort: sort_value_name) do diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml index 07cee86ba4c..c7cebf45160 100644 --- a/app/views/projects/variables/_table.html.haml +++ b/app/views/projects/variables/_table.html.haml @@ -12,8 +12,8 @@ - @project.variables.order_key_asc.each do |variable| - if variable.id? %tr - %td= variable.key - %td= variable.value + %td.variable-key= variable.key + %td.variable-value{ "data-value" => variable.value }****** %td = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do %span.sr-only diff --git a/app/views/projects/variables/index.html.haml b/app/views/projects/variables/index.html.haml index 09bb54600af..39303700131 100644 --- a/app/views/projects/variables/index.html.haml +++ b/app/views/projects/variables/index.html.haml @@ -15,3 +15,4 @@ No variables found, add one with the form above. - else = render "table" + %button.btn.btn-info.js-btn-toggle-reveal-values{"data-status" => 'hidden'} Reveal Values diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index ef1c0296d49..938be20c7cf 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -3,7 +3,7 @@ - if params[:project_id].present? = hidden_field_tag :project_id, params[:project_id] .dropdown - %button.dropdown-menu-toggle.btn.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } } + %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } } %span.dropdown-toggle-text Group: - if @group.present? @@ -18,7 +18,7 @@ = dropdown_loading .dropdown.project-filter - %button.dropdown-menu-toggle.btn.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } } + %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } } %span.dropdown-toggle-text Project: - if @project.present? diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index ba25e09d638..0bc851b4256 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -13,8 +13,9 @@ .input-group-addon = root_url = f.text_field :path, placeholder: 'open-source', class: 'form-control', - autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_]+", - required: true, title: 'Please choose a group name with no special characters.' + autofocus: local_assigns[:autofocus] || false, required: true, + pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE, + title: 'Please choose a group name with no special characters.' - if @group.persisted? .alert.alert-warning.prepend-top-10 diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 68e05cb72e1..ede3c7090d7 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -1,11 +1,11 @@ .dropdown.inline.prepend-left-10 - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', data: {toggle: 'dropdown'}} %span.light - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li = link_to page_filter_path(sort: sort_value_priority, label: true) do diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index b7e5e928993..e3503981afe 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -29,9 +29,9 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - - if issuable_filters_present + - if issuable_filter_present? .filter-item.inline.reset-filters - %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters + %a{href: page_filter_path(without: issuable_filter_params)} Reset filters .pull-right - if boards_page diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 9b9ad510444..3d515a05d46 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -16,20 +16,9 @@ = render 'shared/issuable/form/template_selector', issuable: issuable = render 'shared/issuable/form/title', issuable: issuable, form: form -.form-group.detail-page-description - = form.label :description, 'Description', class: 'control-label' - .col-sm-10 += render 'shared/issuable/form/description', issuable: issuable, form: form - = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: form, attr: :description, - classes: 'note-textarea', - placeholder: "Write a comment or drag your files here...", - supports_slash_commands: !issuable.persisted? - = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted? - .clearfix - .error-alert - -- if issuable.is_a?(Issue) +- if issuable.respond_to?(:confidential) .form-group .col-sm-offset-2.col-sm-10 .checkbox @@ -37,38 +26,7 @@ = form.check_box :confidential This issue is confidential and should only be visible to team members with at least Reporter access. -- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) - - has_due_date = issuable.has_attribute?(:due_date) - %hr - .row - %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") } - .form-group.issue-assignee - = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" - .col-sm-10{ class: ("col-lg-8" if has_due_date) } - .issuable-form-select-holder - - if issuable.assignee_id - = form.hidden_field :assignee_id - = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) - .form-group.issue-milestone - = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" - .col-sm-10{ class: ("col-lg-8" if has_due_date) } - .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" - .form-group - - has_labels = @labels && @labels.any? - = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" - = form.hidden_field :label_ids, multiple: true, value: '' - .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } - .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" - - if has_due_date - .col-lg-6 - .form-group - = form.label :due_date, "Due date", class: "control-label" - .col-sm-10 - .issuable-form-select-holder - = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" += render 'shared/issuable/form/metadata', issuable: issuable, form: form - if issuable.can_move?(current_user) %hr diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 1d778bc88de..22b5a6aa11b 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -22,7 +22,7 @@ %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) } = multi_label_name(selected, "Labels") - = icon('caret-down') + = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create } - if show_create && project && can?(current_user, :admin_label, project) diff --git a/app/views/shared/issuable/form/_description.html.haml b/app/views/shared/issuable/form/_description.html.haml new file mode 100644 index 00000000000..dbace9ce401 --- /dev/null +++ b/app/views/shared/issuable/form/_description.html.haml @@ -0,0 +1,15 @@ +- issuable = local_assigns.fetch(:issuable) +- form = local_assigns.fetch(:form) + +.form-group.detail-page-description + = form.label :description, 'Description', class: 'control-label' + .col-sm-10 + + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do + = render 'projects/zen', f: form, attr: :description, + classes: 'note-textarea', + placeholder: "Write a comment or drag your files here...", + supports_slash_commands: !issuable.persisted? + = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted? + .clearfix + .error-alert diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml new file mode 100644 index 00000000000..a47085230b8 --- /dev/null +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -0,0 +1,38 @@ +- issuable = local_assigns.fetch(:issuable) + +- return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) + +- has_due_date = issuable.has_attribute?(:due_date) +- has_labels = @labels && @labels.any? +- form = local_assigns.fetch(:form) + +%hr +.row + %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") } + .form-group.issue-assignee + = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" + .col-sm-10{ class: ("col-lg-8" if has_due_date) } + .issuable-form-select-holder + - if issuable.assignee_id + = form.hidden_field :assignee_id + = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", + placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) + .form-group.issue-milestone + = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" + .col-sm-10{ class: ("col-lg-8" if has_due_date) } + .issuable-form-select-holder + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" + .form-group + - has_labels = @labels && @labels.any? + = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" + = form.hidden_field :label_ids, multiple: true, value: '' + .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } + .issuable-form-select-holder + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" + - if has_due_date + .col-lg-6 + .form-group + = form.label :due_date, "Due date", class: "control-label" + .col-sm-10 + .issuable-form-select-holder + = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb index 331727ba9d8..fccddb70d18 100644 --- a/app/workers/authorized_projects_worker.rb +++ b/app/workers/authorized_projects_worker.rb @@ -2,14 +2,33 @@ class AuthorizedProjectsWorker include Sidekiq::Worker include DedicatedSidekiqQueue + LEASE_TIMEOUT = 1.minute.to_i + def self.bulk_perform_async(args_list) Sidekiq::Client.push_bulk('class' => self, 'args' => args_list) end def perform(user_id) user = User.find_by(id: user_id) - return unless user - user.refresh_authorized_projects + refresh(user) if user + end + + def refresh(user) + lease_key = "refresh_authorized_projects:#{user.id}" + lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT) + + until uuid = lease.try_obtain + # Keep trying until we obtain the lease. If we don't do so we may end up + # not updating the list of authorized projects properly. To prevent + # hammering Redis too much we'll wait for a bit between retries. + sleep(1) + end + + begin + user.refresh_authorized_projects + ensure + Gitlab::ExclusiveLease.cancel(lease_key, uuid) + end end end |