Commit d2f70576 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ce-to-ee' into 'master'

CE upstream: Thursday

See merge request !1293
parents 6cbf3079 75881efc
...@@ -51,3 +51,4 @@ eslint-report.html ...@@ -51,3 +51,4 @@ eslint-report.html
/builds/* /builds/*
/shared/* /shared/*
/.gitlab_workhorse_secret /.gitlab_workhorse_secret
/webpack-report/
...@@ -224,6 +224,25 @@ rake db:seed_fu: ...@@ -224,6 +224,25 @@ rake db:seed_fu:
paths: paths:
- log/development.log - log/development.log
rake gitlab:assets:compile:
stage: test
<<: *dedicated-runner
dependencies: []
variables:
NODE_ENV: "production"
RAILS_ENV: "production"
SETUP_DB: "false"
USE_DB: "false"
SKIP_STORAGE_VALIDATION: "true"
WEBPACK_REPORT: "true"
script:
- bundle exec rake yarn:install gitlab:assets:compile
artifacts:
name: webpack-report
expire_in: 31d
paths:
- webpack-report/
rake karma: rake karma:
cache: cache:
paths: paths:
...@@ -265,7 +284,7 @@ bundler:audit: ...@@ -265,7 +284,7 @@ bundler:audit:
- master@gitlab/gitlabhq - master@gitlab/gitlabhq
- master@gitlab/gitlab-ee - master@gitlab/gitlab-ee
script: script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941" - "bundle exec bundle-audit check --update --ignore OSVDB-115941 CVE-2016-6316 CVE-2016-6317"
migration paths: migration paths:
stage: test stage: test
...@@ -372,6 +391,7 @@ pages: ...@@ -372,6 +391,7 @@ pages:
dependencies: dependencies:
- coverage - coverage
- rake karma - rake karma
- rake gitlab:assets:compile
- lint:javascript:report - lint:javascript:report
script: script:
- mv public/ .public/ - mv public/ .public/
...@@ -379,6 +399,7 @@ pages: ...@@ -379,6 +399,7 @@ pages:
- mv coverage/ public/coverage-ruby/ || true - mv coverage/ public/coverage-ruby/ || true
- mv coverage-javascript/ public/coverage-javascript/ || true - mv coverage-javascript/ public/coverage-javascript/ || true
- mv eslint-report.html public/ || true - mv eslint-report.html public/ || true
- mv webpack-report/ public/webpack-report/ || true
artifacts: artifacts:
paths: paths:
- public - public
......
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 0` # `rubocop --auto-gen-config --exclude-limit 0`
# on 2017-02-22 15:19:47 -0600 using RuboCop version 0.47.1. # on 2017-02-22 13:02:35 -0600 using RuboCop version 0.47.1.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
...@@ -43,7 +43,7 @@ RSpec/ScatteredSetup: ...@@ -43,7 +43,7 @@ RSpec/ScatteredSetup:
RSpec/SingleArgumentMessageChain: RSpec/SingleArgumentMessageChain:
Enabled: false Enabled: false
# Offense count: 169 # Offense count: 172
Rails/FilePath: Rails/FilePath:
Enabled: false Enabled: false
...@@ -64,14 +64,14 @@ Rails/SkipsModelValidations: ...@@ -64,14 +64,14 @@ Rails/SkipsModelValidations:
Security/YAMLLoad: Security/YAMLLoad:
Enabled: false Enabled: false
# Offense count: 55 # Offense count: 48
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles. # Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: percent_q, bare_percent # SupportedStyles: percent_q, bare_percent
Style/BarePercentLiterals: Style/BarePercentLiterals:
Enabled: false Enabled: false
# Offense count: 1304 # Offense count: 1336
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles. # Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: leading, trailing # SupportedStyles: leading, trailing
...@@ -102,7 +102,7 @@ Style/EmptyLiteral: ...@@ -102,7 +102,7 @@ Style/EmptyLiteral:
Style/EmptyMethod: Style/EmptyMethod:
Enabled: false Enabled: false
# Offense count: 201 # Offense count: 203
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
Style/ExtraSpacing: Style/ExtraSpacing:
...@@ -114,7 +114,7 @@ Style/ExtraSpacing: ...@@ -114,7 +114,7 @@ Style/ExtraSpacing:
Style/FormatString: Style/FormatString:
Enabled: false Enabled: false
# Offense count: 316 # Offense count: 318
# Configuration parameters: MinBodyLength. # Configuration parameters: MinBodyLength.
Style/GuardClause: Style/GuardClause:
Enabled: false Enabled: false
...@@ -123,7 +123,7 @@ Style/GuardClause: ...@@ -123,7 +123,7 @@ Style/GuardClause:
Style/IfInsideElse: Style/IfInsideElse:
Enabled: false Enabled: false
# Offense count: 193 # Offense count: 192
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: MaxLineLength. # Configuration parameters: MaxLineLength.
Style/IfUnlessModifier: Style/IfUnlessModifier:
...@@ -181,7 +181,7 @@ Style/NestedParenthesizedCalls: ...@@ -181,7 +181,7 @@ Style/NestedParenthesizedCalls:
Style/Next: Style/Next:
Enabled: false Enabled: false
# Offense count: 30 # Offense count: 31
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles. # Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles.
# SupportedOctalStyles: zero_with_o, zero_only # SupportedOctalStyles: zero_with_o, zero_only
...@@ -200,7 +200,7 @@ Style/NumericPredicate: ...@@ -200,7 +200,7 @@ Style/NumericPredicate:
Style/ParallelAssignment: Style/ParallelAssignment:
Enabled: false Enabled: false
# Offense count: 486 # Offense count: 488
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters. # Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters: Style/PercentLiteralDelimiters:
...@@ -259,7 +259,7 @@ Style/RedundantReturn: ...@@ -259,7 +259,7 @@ Style/RedundantReturn:
Style/RedundantSelf: Style/RedundantSelf:
Enabled: false Enabled: false
# Offense count: 110 # Offense count: 111
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed # SupportedStyles: slashes, percent_r, mixed
...@@ -282,7 +282,7 @@ Style/SelfAssignment: ...@@ -282,7 +282,7 @@ Style/SelfAssignment:
Style/SingleLineMethods: Style/SingleLineMethods:
Enabled: false Enabled: false
# Offense count: 180 # Offense count: 183
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles. # Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: space, no_space # SupportedStyles: space, no_space
...@@ -302,7 +302,7 @@ Style/SpaceBeforeFirstArg: ...@@ -302,7 +302,7 @@ Style/SpaceBeforeFirstArg:
Style/SpaceInLambdaLiteral: Style/SpaceInLambdaLiteral:
Enabled: false Enabled: false
# Offense count: 217 # Offense count: 215
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters. # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters.
# SupportedStyles: space, no_space # SupportedStyles: space, no_space
...@@ -310,7 +310,7 @@ Style/SpaceInLambdaLiteral: ...@@ -310,7 +310,7 @@ Style/SpaceInLambdaLiteral:
Style/SpaceInsideBlockBraces: Style/SpaceInsideBlockBraces:
Enabled: false Enabled: false
# Offense count: 100 # Offense count: 101
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/SpaceInsideParens: Style/SpaceInsideParens:
Enabled: false Enabled: false
...@@ -334,7 +334,7 @@ Style/SpecialGlobalVars: ...@@ -334,7 +334,7 @@ Style/SpecialGlobalVars:
Style/StringLiteralsInInterpolation: Style/StringLiteralsInInterpolation:
Enabled: false Enabled: false
# Offense count: 61 # Offense count: 62
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: IgnoredMethods. # Configuration parameters: IgnoredMethods.
# IgnoredMethods: respond_to, define_method # IgnoredMethods: respond_to, define_method
...@@ -355,13 +355,13 @@ Style/TernaryParentheses: ...@@ -355,13 +355,13 @@ Style/TernaryParentheses:
Style/TrailingCommaInArguments: Style/TrailingCommaInArguments:
Enabled: false Enabled: false
# Offense count: 13 # Offense count: 12
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AllowNamedUnderscoreVariables. # Configuration parameters: AllowNamedUnderscoreVariables.
Style/TrailingUnderscoreVariable: Style/TrailingUnderscoreVariable:
Enabled: false Enabled: false
# Offense count: 89 # Offense count: 83
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/TrailingWhitespace: Style/TrailingWhitespace:
Enabled: false Enabled: false
......
...@@ -209,7 +209,7 @@ gem 'babosa', '~> 1.0.2' ...@@ -209,7 +209,7 @@ gem 'babosa', '~> 1.0.2'
gem 'loofah', '~> 2.0.3' gem 'loofah', '~> 2.0.3'
# Working with license # Working with license
gem 'licensee', '~> 8.0.0' gem 'licensee', '~> 8.7.0'
# Protect against bruteforcing # Protect against bruteforcing
gem 'rack-attack', '~> 4.4.1' gem 'rack-attack', '~> 4.4.1'
......
...@@ -422,8 +422,8 @@ GEM ...@@ -422,8 +422,8 @@ GEM
rubyzip rubyzip
thor thor
xml-simple xml-simple
licensee (8.0.0) licensee (8.7.0)
rugged (>= 0.24b) rugged (~> 0.24)
little-plugger (1.1.4) little-plugger (1.1.4)
logging (2.1.0) logging (2.1.0)
little-plugger (~> 1.1) little-plugger (~> 1.1)
...@@ -937,7 +937,7 @@ DEPENDENCIES ...@@ -937,7 +937,7 @@ DEPENDENCIES
kubeclient (~> 2.2.0) kubeclient (~> 2.2.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder (~> 2.1.0) license_finder (~> 2.1.0)
licensee (~> 8.0.0) licensee (~> 8.7.0)
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.9.1) mail_room (~> 0.9.1)
method_source (~> 0.8) method_source (~> 0.8)
......
8.17.0-ee-pre 8.18.0-ee-pre
<svg width="12" height="15" viewBox="0 0 12 15" xmlns="http://www.w3.org/2000/svg"><path d="M10.267 11.028V5.167c-.028-.728-.318-1.372-.878-1.923-.56-.55-1.194-.85-1.922-.877h-.934V.5l-2.8 2.8 2.8 2.8V4.233h.934a.976.976 0 0 1 .644.29.88.88 0 0 1 .289.644v5.861a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472zM3.733 3.3a1.86 1.86 0 0 0-1.866-1.867 1.86 1.86 0 0 0-.934 3.472v6.123a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472V4.905c.55-.317.933-.914.933-1.605z" fill-rule="nonzero"/></svg>
class AjaxLoadingSpinner {
static init() {
const $elements = $('.js-ajax-loading-spinner');
$elements.on('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
$elements.on('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
}
static ajaxBeforeSend(e) {
e.target.setAttribute('disabled', '');
const iconElement = e.target.querySelector('i');
// get first fa- icon
const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first();
iconElement.dataset.icon = originalIcon;
AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
$(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
}
static ajaxComplete(e) {
e.target.removeAttribute('disabled');
const iconElement = e.target.querySelector('i');
AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
$(e.target).off('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
}
static toggleLoadingIcon(iconElement) {
const classList = iconElement.classList;
classList.toggle(iconElement.dataset.icon);
classList.toggle('fa-spinner');
classList.toggle('fa-spin');
}
}
window.gl = window.gl || {};
gl.AjaxLoadingSpinner = AjaxLoadingSpinner;
/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren, import/newline-after-import, no-multi-spaces, max-len */ /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
/* global Vue */ /* global Vue */
/* global BoardService */ /* global BoardService */
function requireAll(context) { return context.keys().map(context); }
window.Vue = require('vue'); window.Vue = require('vue');
window.Vue.use(require('vue-resource')); window.Vue.use(require('vue-resource'));
requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/)); require('./models/issue');
requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/)); require('./models/label');
requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/)); require('./models/list');
requireAll(require.context('./mixins', true, /^\.\/.*\.(js|es6)$/)); require('./models/milestone');
requireAll(require.context('./filters', true, /^\.\/.*\.(js|es6)$/)); require('./models/user');
require('./stores/boards_store');
require('./stores/modal_store');
require('./services/board_service');
require('./mixins/modal_mixins');
require('./mixins/sortable_default_options');
require('./filters/due_date_filters');
require('./components/board'); require('./components/board');
require('./components/boards_selector'); require('./components/boards_selector');
require('./components/board_sidebar'); require('./components/board_sidebar');
...@@ -94,17 +98,53 @@ $(() => { ...@@ -94,17 +98,53 @@ $(() => {
modal: ModalStore.store, modal: ModalStore.store,
store: Store.state, store: Store.state,
}, },
watch: {
disabled() {
this.updateTooltip();
},
},
computed: { computed: {
disabled() { disabled() {
return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length; return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length;
}, },
tooltipTitle() {
if (this.disabled) {
return 'Please add a list to your board first';
}
return '';
},
},
methods: {
updateTooltip() {
const $tooltip = $(this.$el);
this.$nextTick(() => {
if (this.disabled) {
$tooltip.tooltip();
} else {
$tooltip.tooltip('destroy');
}
});
},
openModal() {
if (!this.disabled) {
this.toggleModal(true);
}
},
},
mounted() {
this.updateTooltip();
}, },
template: ` template: `
<button <button
class="btn btn-create pull-right prepend-left-10 has-tooltip" class="btn btn-create pull-right prepend-left-10"
type="button" type="button"
:disabled="disabled" data-placement="bottom"
@click="toggleModal(true)"> :class="{ 'disabled': disabled }"
:title="tooltipTitle"
:aria-disabled="disabled"
@click="openModal">
Add issues Add issues
</button> </button>
`, `,
......
...@@ -4,10 +4,20 @@ ...@@ -4,10 +4,20 @@
window.Vue = require('vue'); window.Vue = require('vue');
window.Cookies = require('js-cookie'); window.Cookies = require('js-cookie');
require('./svg/icon_branch');
function requireAll(context) { return context.keys().map(context); } require('./svg/icon_build_status');
requireAll(require.context('./svg', false, /^\.\/.*\.(js|es6)$/)); require('./svg/icon_commit');
requireAll(require.context('.', true, /^\.\/(?!cycle_analytics_bundle).*\.(js|es6)$/)); require('./components/stage_code_component');
require('./components/stage_issue_component');
require('./components/stage_plan_component');
require('./components/stage_production_component');
require('./components/stage_review_component');
require('./components/stage_staging_component');
require('./components/stage_test_component');
require('./components/total_time_component');
require('./cycle_analytics_service');
require('./cycle_analytics_store');
require('./default_event_objects');
$(() => { $(() => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed'; const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
......
/* eslint-disable func-names, comma-dangle, new-cap, no-new, import/newline-after-import, no-multi-spaces, max-len */ /* eslint-disable func-names, comma-dangle, new-cap, no-new, max-len */
/* global Vue */ /* global Vue */
/* global ResolveCount */ /* global ResolveCount */
/* global ResolveServiceClass */ /* global ResolveServiceClass */
function requireAll(context) { return context.keys().map(context); }
const Vue = require('vue'); const Vue = require('vue');
requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/)); require('./models/discussion');
requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/)); require('./models/note');
requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/)); require('./stores/comments');
requireAll(require.context('./mixins', false, /^\.\/.*\.(js|es6)$/)); require('./services/resolve');
requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/)); require('./mixins/discussion');
require('./components/comment_resolve_btn');
require('./components/jump_to_discussion');
require('./components/resolve_btn');
require('./components/resolve_count');
require('./components/resolve_discussion_btn');
$(() => { $(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath; const projectPath = document.querySelector('.merge-request').dataset.projectPath;
......
...@@ -110,6 +110,9 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -110,6 +110,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:compare:show': case 'projects:compare:show':
new gl.Diff(); new gl.Diff();
break; break;
case 'projects:branches:index':
gl.AjaxLoadingSpinner.init();
break;
case 'projects:issues:new': case 'projects:issues:new':
case 'projects:issues:edit': case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
......
...@@ -126,13 +126,14 @@ require('./preview_markdown'); ...@@ -126,13 +126,14 @@ require('./preview_markdown');
}; };
pasteText = function(text) { pasteText = function(text) {
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd; var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
var formattedText = text + "\n\n";
caretStart = $(child)[0].selectionStart; caretStart = $(child)[0].selectionStart;
caretEnd = $(child)[0].selectionEnd; caretEnd = $(child)[0].selectionEnd;
textEnd = $(child).val().length; textEnd = $(child).val().length;
beforeSelection = $(child).val().substring(0, caretStart); beforeSelection = $(child).val().substring(0, caretStart);
afterSelection = $(child).val().substring(caretEnd, textEnd); afterSelection = $(child).val().substring(caretEnd, textEnd);
$(child).val(beforeSelection + text + afterSelection); $(child).val(beforeSelection + formattedText + afterSelection);
child.get(0).setSelectionRange(caretStart + text.length, caretEnd + text.length); child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
return form_textarea.trigger("input"); return form_textarea.trigger("input");
}; };
getFilename = function(e) { getFilename = function(e) {
......
...@@ -15,29 +15,29 @@ module.exports = Vue.component('actions-component', { ...@@ -15,29 +15,29 @@ module.exports = Vue.component('actions-component', {
}, },
template: ` template: `
<div class="inline"> <div class="btn-group" role="group">
<div class="dropdown"> <button class="dropdown btn btn-default dropdown-new" data-toggle="dropdown">
<a class="dropdown-new btn btn-default" data-toggle="dropdown"> <span>
<span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span> <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
<i class="fa fa-caret-down"></i> <i class="fa fa-caret-down"></i>
</a> </span>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions"> <li v-for="action in actions">
<a :href="action.play_path" <a :href="action.play_path"
data-method="post" data-method="post"
rel="nofollow" rel="nofollow"
class="js-manual-action-link"> class="js-manual-action-link">
<span class="js-action-play-icon-container" v-html="playIconSvg"></span> <span class="js-action-play-icon-container" v-html="playIconSvg"></span>
<span> <span>
{{action.name}} {{action.name}}
</span> </span>
</a> </a>
</li> </li>
</ul> </ul>
</div> </button>
</div> </div>
`, `,
}); });
...@@ -505,39 +505,26 @@ module.exports = Vue.component('environment-item', { ...@@ -505,39 +505,26 @@ module.exports = Vue.component('environment-item', {
<td class="hidden-xs"> <td class="hidden-xs">
<div v-if="!model.isFolder"> <div v-if="!model.isFolder">
<div v-if="hasManualActions && canCreateDeployment" <div class="btn-group" role="group">
class="inline js-manual-actions-container"> <actions-component v-if="hasManualActions && canCreateDeployment"
<actions-component
:play-icon-svg="playIconSvg" :play-icon-svg="playIconSvg"
:actions="manualActions"> :actions="manualActions">
</actions-component> </actions-component>
</div>
<div v-if="externalURL && canReadEnvironment" <external-url-component v-if="externalURL && canReadEnvironment"
class="inline js-external-url-container">
<external-url-component
:external-url="externalURL"> :external-url="externalURL">
</external-url-component> </external-url-component>
</div>
<div v-if="hasStopAction && canCreateDeployment" <stop-component v-if="hasStopAction && canCreateDeployment"
class="inline js-stop-component-container">
<stop-component
:stop-url="model.stop_path"> :stop-url="model.stop_path">
</stop-component> </stop-component>
</div>
<div v-if="model && model.terminal_path" <terminal-button-component v-if="model && model.terminal_path"
class="inline js-terminal-button-container">
<terminal-button-component
:terminal-icon-svg="terminalIconSvg" :terminal-icon-svg="terminalIconSvg"
:terminal-path="model.terminal_path"> :terminal-path="model.terminal_path">
</terminal-button-component> </terminal-button-component>
</div>
<div v-if="canRetry && canCreateDeployment" <rollback-component v-if="canRetry && canCreateDeployment"
class="inline js-rollback-component-container">
<rollback-component
:is-last-deployment="isLastDeployment" :is-last-deployment="isLastDeployment"
:retry-url="retryUrl"> :retry-url="retryUrl">
</rollback-component> </rollback-component>
......
function requireAll(context) { return context.keys().map(context); } require('./dropdown_hint');
require('./dropdown_non_user');
requireAll(require.context('./', true, /^\.\/(?!filtered_search_bundle).*\.(js|es6)$/)); require('./dropdown_user');
require('./dropdown_utils');
require('./filtered_search_dropdown_manager');
require('./filtered_search_dropdown');
require('./filtered_search_manager');
require('./filtered_search_token_keys');
require('./filtered_search_token_keys_with_weights');
require('./filtered_search_tokenizer');
// require everything else in this directory require('./stat_graph_contributors_graph');
function requireAll(context) { return context.keys().map(context); } require('./stat_graph_contributors_util');
requireAll(require.context('.', false, /^\.\/(?!graphs_bundle).*\.(js|es6)$/)); require('./stat_graph_contributors');
require('./stat_graph');
...@@ -2,9 +2,8 @@ ...@@ -2,9 +2,8 @@
/* global Network */ /* global Network */
/* global ShortcutsNetwork */ /* global ShortcutsNetwork */
// require everything else in this directory require('./branch_graph');
function requireAll(context) { return context.keys().map(context); } require('./network');
requireAll(require.context('.', false, /^\.\/(?!network_bundle).*\.(js|es6)$/));
(function() { (function() {
$(function() { $(function() {
......
(() => { class VersionCheckImage {
class VersionCheckImage { static bindErrorEvent(imageElement) {
static bindErrorEvent(imageElement) { imageElement.off('error').on('error', () => imageElement.hide());
imageElement.off('error').on('error', () => imageElement.hide());
}
} }
}
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.VersionCheckImage = VersionCheckImage; gl.VersionCheckImage = VersionCheckImage;
})();
module.exports = VersionCheckImage;
...@@ -23,7 +23,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s ...@@ -23,7 +23,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
apiScope: 'all', apiScope: 'all',
pageInfo: {}, pageInfo: {},
pagenum: 1, pagenum: 1,
count: { all: 0, running_or_pending: 0 }, count: {},
pageRequest: false, pageRequest: false,
}; };
}, },
......
...@@ -148,11 +148,16 @@ header { ...@@ -148,11 +148,16 @@ header {
} }
.header-logo { .header-logo {
display: inline-block; position: absolute;
margin: 0 8px 0 3px; left: 50%;
position: relative;
top: 7px; top: 7px;
transition-duration: .3s; transition-duration: .3s;
z-index: 999;
#logo {
position: relative;
left: -50%;
}
svg, svg,
img { img {
...@@ -162,6 +167,15 @@ header { ...@@ -162,6 +167,15 @@ header {
&:hover { &:hover {
cursor: pointer; cursor: pointer;
} }
@media (max-width: $screen-xs-max) {
right: 20px;
left: auto;
#logo {
left: auto;
}
}
} }
.title { .title {
...@@ -169,7 +183,7 @@ header { ...@@ -169,7 +183,7 @@ header {
padding-right: 20px; padding-right: 20px;
margin: 0; margin: 0;
font-size: 18px; font-size: 18px;
max-width: 450px; max-width: 385px;
display: inline-block; display: inline-block;
line-height: $header-height; line-height: $header-height;
font-weight: normal; font-weight: normal;
...@@ -179,6 +193,10 @@ header { ...@@ -179,6 +193,10 @@ header {
vertical-align: top; vertical-align: top;
white-space: nowrap; white-space: nowrap;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
max-width: 300px;
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
max-width: 190px; max-width: 190px;
} }
......
...@@ -96,16 +96,6 @@ ul.unstyled-list > li { ...@@ -96,16 +96,6 @@ ul.unstyled-list > li {
border-bottom: none; border-bottom: none;
} }
ul.task-list {
li.task-list-item {
list-style-type: none;
}
ul:not(.task-list) {
padding-left: 1.3em;
}
}
// Generic content list // Generic content list
ul.content-list { ul.content-list {
@include basic-list; @include basic-list;
......
...@@ -76,6 +76,13 @@ ...@@ -76,6 +76,13 @@
#{$property}: $value; #{$property}: $value;
} }
/* http://phrappe.com/css/conditional-css-for-webkit-based-browsers/ */
@mixin on-webkit-only {
@media screen and (-webkit-min-device-pixel-ratio:0) {
@content;
}
}
@mixin keyframes($animation-name) { @mixin keyframes($animation-name) {
@-webkit-keyframes #{$animation-name} { @-webkit-keyframes #{$animation-name} {
@content; @content;
......
@mixin fade($gradient-direction, $gradient-color) { @mixin fade($gradient-direction, $gradient-color) {
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
z-index: 1; z-index: 2;
position: absolute; position: absolute;
bottom: 12px; bottom: 12px;
width: 43px; width: 43px;
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
.fa { .fa {
position: relative; position: relative;
top: 6px; top: 5px;
font-size: 18px; font-size: 18px;
} }
} }
...@@ -79,6 +79,7 @@ ...@@ -79,6 +79,7 @@
} }
&.sub-nav { &.sub-nav {
text-align: center;
background-color: $gray-normal; background-color: $gray-normal;
.container-fluid { .container-fluid {
...@@ -286,6 +287,7 @@ ...@@ -286,6 +287,7 @@
background: $gray-light; background: $gray-light;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
text-align: center;
.container-fluid { .container-fluid {
position: relative; position: relative;
...@@ -351,7 +353,7 @@ ...@@ -351,7 +353,7 @@
right: -5px; right: -5px;
.fa { .fa {
right: -28px; right: -7px;
} }
} }
...@@ -381,7 +383,7 @@ ...@@ -381,7 +383,7 @@
left: 0; left: 0;
.fa { .fa {
left: -4px; left: 10px;
} }
} }
} }
......
...@@ -29,14 +29,16 @@ ...@@ -29,14 +29,16 @@
} }
} }
@media (min-width: $screen-sm-min) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
}
.right-sidebar-collapsed { .right-sidebar-collapsed {
padding-right: 0; padding-right: 0;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
.merge-request-tabs-holder.affix { .merge-request-tabs-holder.affix {
right: $gutter_collapsed_width; right: $gutter_collapsed_width;
} }
...@@ -54,12 +56,6 @@ ...@@ -54,12 +56,6 @@
.right-sidebar-expanded { .right-sidebar-expanded {
padding-right: 0; padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
&:not(.build-sidebar):not(.wiki-sidebar) {
padding-right: $gutter_collapsed_width;
}
}
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
.content-wrapper { .content-wrapper {
padding-right: $gutter_width; padding-right: $gutter_width;
......
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
ul, ul,
ol { ol {
padding: 0; padding: 0;
margin: 3px 0 3px 28px !important; margin: 3px 0 !important;
} }
ul:dir(rtl), ul:dir(rtl),
...@@ -144,6 +144,29 @@ ...@@ -144,6 +144,29 @@
li { li {
line-height: 1.6em; line-height: 1.6em;
margin-left: 25px;
padding-left: 3px;
/* Normalize the bullet position on webkit. */
@include on-webkit-only {
margin-left: 28px;
padding-left: 0;
}
}
ul.task-list {
li.task-list-item {
list-style-type: none;
position: relative;
padding-left: 28px;
margin-left: 0 !important;
input.task-list-item-checkbox {
position: absolute;
left: 8px;
top: 5px;
}
}
} }
a[href*="/uploads/"], a[href*="/uploads/"],
......
...@@ -35,7 +35,6 @@ ...@@ -35,7 +35,6 @@
display: table-cell; display: table-cell;
} }
.environments-name,
.environments-commit, .environments-commit,
.environments-actions { .environments-actions {
width: 20%; width: 20%;
...@@ -45,6 +44,7 @@ ...@@ -45,6 +44,7 @@
width: 10%; width: 10%;
} }
.environments-name,
.environments-deploy, .environments-deploy,
.environments-build { .environments-build {
width: 15%; width: 15%;
...@@ -62,6 +62,22 @@ ...@@ -62,6 +62,22 @@
} }
} }
.btn-group {
> a {
color: $gl-text-color-secondary;
}
svg path {
fill: $gl-text-color-secondary;
}
.dropdown {
outline: none;
}
}
.commit-title { .commit-title {
margin: 0; margin: 0;
} }
......
...@@ -10,6 +10,11 @@ ...@@ -10,6 +10,11 @@
.issue-labels { .issue-labels {
display: inline-block; display: inline-block;
} }
.icon-merge-request-unmerged {
height: 13px;
margin-bottom: 3px;
}
} }
} }
......
...@@ -652,23 +652,29 @@ pre.light-well { ...@@ -652,23 +652,29 @@ pre.light-well {
} }
} }
.container-fluid.project-stats-container {
@media (max-width: $screen-xs-max) {
padding: 12px 0;
}
}
.project-last-commit { .project-last-commit {
background-color: $gray-light;
padding: 12px $gl-padding;
border: 1px solid $border-color;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
margin-top: $gl-padding; margin-top: $gl-padding;
} }
@media (min-width: $screen-sm-min) { &.container-fluid {
border-radius: $border-radius-base; padding-top: 12px;
padding-bottom: 12px;
background-color: $gray-light;
border: 1px solid $border-color;
border-right-width: 0;
border-left-width: 0;
@media (min-width: $screen-sm-min) {
border-right-width: 1px;
border-left-width: 1px;
}
}
&.container-limited {
@media (min-width: 1281px) {
border-radius: $border-radius-base;
}
} }
.ci-status { .ci-status {
......
...@@ -9,24 +9,32 @@ module IssuableCollections ...@@ -9,24 +9,32 @@ module IssuableCollections
private private
def issuable_meta_data(issuable_collection) def issuable_meta_data(issuable_collection, collection_type)
# map has to be used here since using pluck or select will # map has to be used here since using pluck or select will
# throw an error when ordering issuables by priority which inserts # throw an error when ordering issuables by priority which inserts
# a new order into the collection. # a new order into the collection.
# We cannot use reorder to not mess up the paginated collection. # We cannot use reorder to not mess up the paginated collection.
issuable_ids = issuable_collection.map(&:id) issuable_ids = issuable_collection.map(&:id)
issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type) issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type)
issuable_votes_count = AwardEmoji.votes_for_collection(issuable_ids, @collection_type) issuable_votes_count = AwardEmoji.votes_for_collection(issuable_ids, @collection_type)
issuable_merge_requests_count =
if collection_type == 'Issue'
MergeRequestsClosingIssues.count_for_collection(issuable_ids)
else
[]
end
issuable_ids.each_with_object({}) do |id, issuable_meta| issuable_ids.each_with_object({}) do |id, issuable_meta|
downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? } downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? }
upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? } upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? }
notes = issuable_note_count.find { |notes| notes.noteable_id == id } notes = issuable_note_count.find { |notes| notes.noteable_id == id }
merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id }
issuable_meta[id] = Issuable::IssuableMeta.new( issuable_meta[id] = Issuable::IssuableMeta.new(
upvotes.try(:count).to_i, upvotes.try(:count).to_i,
downvotes.try(:count).to_i, downvotes.try(:count).to_i,
notes.try(:count).to_i notes.try(:count).to_i,
merge_requests.try(:last).to_i
) )
end end
end end
......
...@@ -10,7 +10,7 @@ module IssuesAction ...@@ -10,7 +10,7 @@ module IssuesAction
.page(params[:page]) .page(params[:page])
@collection_type = "Issue" @collection_type = "Issue"
@issuable_meta_data = issuable_meta_data(@issues) @issuable_meta_data = issuable_meta_data(@issues, @collection_type)
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -9,7 +9,7 @@ module MergeRequestsAction ...@@ -9,7 +9,7 @@ module MergeRequestsAction
.page(params[:page]) .page(params[:page])
@collection_type = "MergeRequest" @collection_type = "MergeRequest"
@issuable_meta_data = issuable_meta_data(@merge_requests) @issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type)
end end
private private
......
...@@ -17,13 +17,31 @@ module SpammableActions ...@@ -17,13 +17,31 @@ module SpammableActions
private private
def recaptcha_params def recaptcha_check_with_fallback(&fallback)
return {} unless params[:recaptcha_verification] && Gitlab::Recaptcha.load_configurations! && verify_recaptcha if spammable.valid?
redirect_to spammable
elsif render_recaptcha?
if params[:recaptcha_verification]
flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
end
render :verify
else
yield
end
end
def spammable_params
default_params = { request: request }
recaptcha_check = params[:recaptcha_verification] &&
Gitlab::Recaptcha.load_configurations! &&
verify_recaptcha
return default_params unless recaptcha_check
{ { recaptcha_verified: true,
recaptcha_verified: true, spam_log_id: params[:spam_log_id] }.merge(default_params)
spam_log_id: params[:spam_log_id]
}
end end
def spammable def spammable
......
class Dashboard::TodosController < Dashboard::ApplicationController class Dashboard::TodosController < Dashboard::ApplicationController
include ActionView::Helpers::NumberHelper
before_action :find_todos, only: [:index, :destroy_all] before_action :find_todos, only: [:index, :destroy_all]
def index def index
...@@ -48,8 +50,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -48,8 +50,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def todos_counts def todos_counts
{ {
count: current_user.todos_pending_count, count: number_with_delimiter(current_user.todos_pending_count),
done_count: current_user.todos_done_count done_count: number_with_delimiter(current_user.todos_done_count)
} }
end end
end end
...@@ -26,7 +26,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -26,7 +26,7 @@ class Projects::IssuesController < Projects::ApplicationController
@collection_type = "Issue" @collection_type = "Issue"
@issues = issues_collection @issues = issues_collection
@issues = @issues.page(params[:page]) @issues = @issues.page(params[:page])
@issuable_meta_data = issuable_meta_data(@issues) @issuable_meta_data = issuable_meta_data(@issues, @collection_type)
if @issues.out_of_range? && @issues.total_pages != 0 if @issues.out_of_range? && @issues.total_pages != 0
return redirect_to url_for(params.merge(page: @issues.total_pages)) return redirect_to url_for(params.merge(page: @issues.total_pages))
...@@ -99,15 +99,15 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -99,15 +99,15 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def create def create
extra_params = { request: request, create_params = issue_params
merge_request_for_resolving_discussions: merge_request_for_resolving_discussions } .merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
extra_params.merge!(recaptcha_params) .merge(spammable_params)
@issue = Issues::CreateService.new(project, current_user, issue_params.merge(extra_params)).execute @issue = Issues::CreateService.new(project, current_user, create_params).execute
respond_to do |format| respond_to do |format|
format.html do format.html do
html_response_create recaptcha_check_with_fallback { render :new }
end end
format.js do format.js do
@link = @issue.attachment.url.to_js @link = @issue.attachment.url.to_js
...@@ -116,7 +116,9 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -116,7 +116,9 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def update def update
@issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue) update_params = issue_params.merge(spammable_params)
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
if params[:move_to_project_id].to_i > 0 if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id]) new_project = Project.find(params[:move_to_project_id])
...@@ -128,11 +130,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -128,11 +130,7 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
if @issue.valid? recaptcha_check_with_fallback { render :edit }
redirect_to issue_path(@issue)
else
render :edit
end
end end
format.json do format.json do
...@@ -184,20 +182,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -184,20 +182,6 @@ class Projects::IssuesController < Projects::ApplicationController
protected protected
def html_response_create
if @issue.valid?
redirect_to issue_path(@issue)
elsif render_recaptcha?
if params[:recaptcha_verification]
flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
end
render :verify
else
render :new
end
end
def issue def issue
# The Sortable default scope causes performance issues when used with find_by # The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old
......
...@@ -42,7 +42,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -42,7 +42,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@collection_type = "MergeRequest" @collection_type = "MergeRequest"
@merge_requests = merge_requests_collection @merge_requests = merge_requests_collection
@merge_requests = @merge_requests.page(params[:page]) @merge_requests = @merge_requests.page(params[:page])
@issuable_meta_data = issuable_meta_data(@merge_requests) @issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type)
if @merge_requests.out_of_range? && @merge_requests.total_pages != 0 if @merge_requests.out_of_range? && @merge_requests.total_pages != 0
return redirect_to url_for(params.merge(page: @merge_requests.total_pages)) return redirect_to url_for(params.merge(page: @merge_requests.total_pages))
......
...@@ -13,9 +13,15 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -13,9 +13,15 @@ class Projects::PipelinesController < Projects::ApplicationController
.page(params[:page]) .page(params[:page])
.per(30) .per(30)
@running_or_pending_count = PipelinesFinder @running_count = PipelinesFinder
.new(project).execute(scope: 'running').count .new(project).execute(scope: 'running').count
@pending_count = PipelinesFinder
.new(project).execute(scope: 'pending').count
@finished_count = PipelinesFinder
.new(project).execute(scope: 'finished').count
@pipelines_count = PipelinesFinder @pipelines_count = PipelinesFinder
.new(project).execute.count .new(project).execute.count
...@@ -29,7 +35,9 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -29,7 +35,9 @@ class Projects::PipelinesController < Projects::ApplicationController
.represent(@pipelines), .represent(@pipelines),
count: { count: {
all: @pipelines_count, all: @pipelines_count,
running_or_pending: @running_or_pending_count running: @running_count,
pending: @pending_count,
finished: @finished_count,
} }
} }
end end
......
...@@ -38,24 +38,19 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -38,24 +38,19 @@ class Projects::SnippetsController < Projects::ApplicationController
end end
def create def create
create_params = snippet_params.merge(request: request) create_params = snippet_params.merge(spammable_params)
@snippet = CreateSnippetService.new(@project, current_user, create_params).execute @snippet = CreateSnippetService.new(@project, current_user, create_params).execute
if @snippet.valid? recaptcha_check_with_fallback { render :new }
respond_with(@snippet,
location: namespace_project_snippet_path(@project.namespace,
@project, @snippet))
else
render :new
end
end end
def update def update
UpdateSnippetService.new(project, current_user, @snippet, update_params = snippet_params.merge(spammable_params)
snippet_params).execute
respond_with(@snippet, UpdateSnippetService.new(project, current_user, @snippet, update_params).execute
location: namespace_project_snippet_path(@project.namespace,
@project, @snippet)) recaptcha_check_with_fallback { render :edit }
end end
def show def show
......
...@@ -43,16 +43,19 @@ class SnippetsController < ApplicationController ...@@ -43,16 +43,19 @@ class SnippetsController < ApplicationController
end end
def create def create
create_params = snippet_params.merge(request: request) create_params = snippet_params.merge(spammable_params)
@snippet = CreateSnippetService.new(nil, current_user, create_params).execute @snippet = CreateSnippetService.new(nil, current_user, create_params).execute
respond_with @snippet.becomes(Snippet) recaptcha_check_with_fallback { render :new }
end end
def update def update
UpdateSnippetService.new(nil, current_user, @snippet, update_params = snippet_params.merge(spammable_params)
snippet_params).execute
respond_with @snippet.becomes(Snippet) UpdateSnippetService.new(nil, current_user, @snippet, update_params).execute
recaptcha_check_with_fallback { render :edit }
end end
def show def show
......
...@@ -10,7 +10,11 @@ class PipelinesFinder ...@@ -10,7 +10,11 @@ class PipelinesFinder
scoped_pipelines = scoped_pipelines =
case scope case scope
when 'running' when 'running'
pipelines.running_or_pending pipelines.running
when 'pending'
pipelines.pending
when 'finished'
pipelines.finished
when 'branches' when 'branches'
from_ids(ids_for_ref(branches)) from_ids(ids_for_ref(branches))
when 'tags' when 'tags'
......
module EmailsHelper module EmailsHelper
include AppearancesHelper
# Google Actions # Google Actions
# https://developers.google.com/gmail/markup/reference/go-to-action # https://developers.google.com/gmail/markup/reference/go-to-action
def email_action(url) def email_action(url)
...@@ -49,4 +51,19 @@ module EmailsHelper ...@@ -49,4 +51,19 @@ module EmailsHelper
msg = "This link is valid for #{password_reset_token_valid_time}. " msg = "This link is valid for #{password_reset_token_valid_time}. "
msg << "After it expires, you can #{link_tag}." msg << "After it expires, you can #{link_tag}."
end end
def header_logo
if brand_item && brand_item.header_logo?
image_tag(
brand_item.header_logo,
style: 'height: 50px'
)
else
image_tag(
image_url('mailers/gitlab_header_logo.gif'),
size: "55x50",
alt: "GitLab"
)
end
end
end end
...@@ -22,8 +22,8 @@ module Emails ...@@ -22,8 +22,8 @@ module Emails
mail(bcc: recipients, mail(bcc: recipients,
subject: pipeline_subject(status), subject: pipeline_subject(status),
skip_premailer: true) do |format| skip_premailer: true) do |format|
format.html { render layout: false } format.html { render layout: 'mailer' }
format.text format.text { render layout: 'mailer' }
end end
end end
......
...@@ -95,8 +95,11 @@ module Ci ...@@ -95,8 +95,11 @@ module Ci
.select("max(#{quoted_table_name}.id)") .select("max(#{quoted_table_name}.id)")
.group(:ref, :sha) .group(:ref, :sha)
relation = ref ? where(ref: ref) : self if ref
relation.where(id: max_id) where(ref: ref, id: max_id.where(ref: ref))
else
where(id: max_id)
end
end end
def self.latest_status(ref = nil) def self.latest_status(ref = nil)
......
...@@ -16,9 +16,9 @@ module Issuable ...@@ -16,9 +16,9 @@ module Issuable
include TimeTrackable include TimeTrackable
# This object is used to gather issuable meta data for displaying # This object is used to gather issuable meta data for displaying
# upvotes, downvotes and notes count for issues and merge requests # upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# lists avoiding n+1 queries and improving performance. # lists avoiding n+1 queries and improving performance.
IssuableMeta = Struct.new(:upvotes, :downvotes, :notes_count) IssuableMeta = Struct.new(:upvotes, :downvotes, :notes_count, :merge_requests_count)
included do included do
cache_markdown_field :title, pipeline: :single_line cache_markdown_field :title, pipeline: :single_line
......
...@@ -13,7 +13,7 @@ module Spammable ...@@ -13,7 +13,7 @@ module Spammable
attr_accessor :spam attr_accessor :spam
attr_accessor :spam_log attr_accessor :spam_log
after_validation :check_for_spam, on: :create after_validation :check_for_spam, on: [:create, :update]
cattr_accessor :spammable_attrs, instance_accessor: false do cattr_accessor :spammable_attrs, instance_accessor: false do
[] []
......
...@@ -210,7 +210,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -210,7 +210,11 @@ class MergeRequest < ActiveRecord::Base
end end
def diff_size def diff_size
opts = diff_options || {} # The `#diffs` method ends up at an instance of a class inheriting from
# `Gitlab::Diff::FileCollection::Base`, so use those options as defaults
# here too, to get the same diff size without performing highlighting.
#
opts = Gitlab::Diff::FileCollection::Base.default_options.merge(diff_options || {})
raw_diffs(opts).size raw_diffs(opts).size
end end
......
...@@ -4,4 +4,12 @@ class MergeRequestsClosingIssues < ActiveRecord::Base ...@@ -4,4 +4,12 @@ class MergeRequestsClosingIssues < ActiveRecord::Base
validates :merge_request_id, uniqueness: { scope: :issue_id }, presence: true validates :merge_request_id, uniqueness: { scope: :issue_id }, presence: true
validates :issue_id, presence: true validates :issue_id, presence: true
class << self
def count_for_collection(ids)
group(:issue_id).
where(issue_id: ids).
pluck('issue_id', 'COUNT(*) as count')
end
end
end end
...@@ -14,8 +14,4 @@ class ProjectSnippet < Snippet ...@@ -14,8 +14,4 @@ class ProjectSnippet < Snippet
participant :author participant :author
participant :notes_with_associations participant :notes_with_associations
def check_for_spam?
super && project.public?
end
end end
...@@ -63,7 +63,8 @@ module Ci ...@@ -63,7 +63,8 @@ module Ci
private private
def skip_ci? def skip_ci?
pipeline.git_commit_message =~ /\[(ci skip|skip ci)\]/i if pipeline.git_commit_message return false unless pipeline.git_commit_message
pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i
end end
def commit def commit
......
module Ci module Ci
class RetryBuildService < ::BaseService class RetryBuildService < ::BaseService
CLONE_ATTRIBUTES = %i[pipeline ref tag options commands tag_list name CLONE_ATTRIBUTES = %i[pipeline project ref tag options commands name
allow_failure stage stage_idx trigger_request allow_failure stage stage_idx trigger_request
yaml_variables when environment coverage_regex] yaml_variables when environment coverage_regex]
.freeze .freeze
REJECT_ATTRIBUTES = %i[id status user token coverage trace runner REJECT_ATTRIBUTES = %i[id status user token coverage trace runner
artifacts_file artifacts_metadata artifacts_size artifacts_expire_at artifacts_file
artifacts_metadata artifacts_size
created_at updated_at started_at finished_at created_at updated_at started_at finished_at
queued_at erased_by erased_at].freeze queued_at erased_by erased_at].freeze
IGNORE_ATTRIBUTES = %i[trace type lock_version project target_url IGNORE_ATTRIBUTES = %i[type lock_version gl_project_id target_url
deploy job_id description].freeze deploy job_id description].freeze
def execute(build) def execute(build)
......
module Ci module Ci
class RetryPipelineService < ::BaseService class RetryPipelineService < ::BaseService
include Gitlab::OptimisticLocking
def execute(pipeline) def execute(pipeline)
unless can?(current_user, :update_pipeline, pipeline) unless can?(current_user, :update_pipeline, pipeline)
raise Gitlab::Access::AccessDeniedError raise Gitlab::Access::AccessDeniedError
...@@ -12,6 +14,10 @@ module Ci ...@@ -12,6 +14,10 @@ module Ci
.reprocess(build) .reprocess(build)
end end
pipeline.builds.skipped.find_each do |skipped|
retry_optimistic_lock(skipped) { |build| build.process }
end
MergeRequests::AddTodoWhenBuildFailsService MergeRequests::AddTodoWhenBuildFailsService
.new(project, current_user) .new(project, current_user)
.close_all(pipeline) .close_all(pipeline)
......
class CreateSnippetService < BaseService class CreateSnippetService < BaseService
include SpamCheckService
def execute def execute
request = params.delete(:request) filter_spam_check_params
api = params.delete(:api)
snippet = if project snippet = if project
project.snippets.build(params) project.snippets.build(params)
...@@ -15,10 +16,11 @@ class CreateSnippetService < BaseService ...@@ -15,10 +16,11 @@ class CreateSnippetService < BaseService
end end
snippet.author = current_user snippet.author = current_user
snippet.spam = SpamService.new(snippet, request).check(api)
spam_check(snippet, current_user)
if snippet.save if snippet.save
UserAgentDetailService.new(snippet, request).create UserAgentDetailService.new(snippet, @request).create
end end
snippet snippet
......
...@@ -191,14 +191,12 @@ class IssuableBaseService < BaseService ...@@ -191,14 +191,12 @@ class IssuableBaseService < BaseService
# To be overridden by subclasses # To be overridden by subclasses
end end
def after_update(issuable) def before_update(issuable)
# To be overridden by subclasses # To be overridden by subclasses
end end
def update_issuable(issuable, attributes) def after_update(issuable)
issuable.with_transaction_returning_status do # To be overridden by subclasses
issuable.update(attributes.merge(updated_by: current_user))
end
end end
def update(issuable) def update(issuable)
...@@ -212,16 +210,22 @@ class IssuableBaseService < BaseService ...@@ -212,16 +210,22 @@ class IssuableBaseService < BaseService
label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids) label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids)
params[:label_ids] = label_ids if labels_changing?(issuable.label_ids, label_ids) params[:label_ids] = label_ids if labels_changing?(issuable.label_ids, label_ids)
if params.present? && update_issuable(issuable, params) if params.present?
# We do not touch as it will affect a update on updated_at field issuable.assign_attributes(params.merge(updated_by: current_user))
ActiveRecord::Base.no_touching do
handle_common_system_notes(issuable, old_labels: old_labels) before_update(issuable)
end
handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users) if issuable.with_transaction_returning_status { issuable.save }
after_update(issuable) # We do not touch as it will affect a update on updated_at field
issuable.create_new_cross_references!(current_user) ActiveRecord::Base.no_touching do
execute_hooks(issuable, 'update') handle_common_system_notes(issuable, old_labels: old_labels)
end
handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users)
after_update(issuable)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
end
end end
issuable issuable
......
module Issues module Issues
class CreateService < Issues::BaseService class CreateService < Issues::BaseService
include SpamCheckService
def execute def execute
@request = params.delete(:request) filter_spam_check_params
@api = params.delete(:api)
@recaptcha_verified = params.delete(:recaptcha_verified)
@spam_log_id = params.delete(:spam_log_id)
issue_attributes = params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions) issue_attributes = params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions)
@issue = BuildService.new(project, current_user, issue_attributes).execute @issue = BuildService.new(project, current_user, issue_attributes).execute
...@@ -12,14 +11,8 @@ module Issues ...@@ -12,14 +11,8 @@ module Issues
create(@issue) create(@issue)
end end
def before_create(issuable) def before_create(issue)
if @recaptcha_verified spam_check(issue, current_user)
spam_log = current_user.spam_logs.find_by(id: @spam_log_id, title: issuable.title)
spam_log&.update!(recaptcha_verified: true)
else
issuable.spam = spam_service.check(@api)
issuable.spam_log = spam_service.spam_log
end
end end
def after_create(issuable) def after_create(issuable)
...@@ -42,10 +35,6 @@ module Issues ...@@ -42,10 +35,6 @@ module Issues
private private
def spam_service
@spam_service ||= SpamService.new(@issue, @request)
end
def user_agent_detail_service def user_agent_detail_service
UserAgentDetailService.new(@issue, @request) UserAgentDetailService.new(@issue, @request)
end end
......
module Issues module Issues
class UpdateService < Issues::BaseService class UpdateService < Issues::BaseService
include SpamCheckService
def execute(issue) def execute(issue)
filter_spam_check_params
update(issue) update(issue)
end end
def before_update(issue)
spam_check(issue, current_user)
end
def handle_changes(issue, old_labels: [], old_mentioned_users: []) def handle_changes(issue, old_labels: [], old_mentioned_users: [])
if has_changes?(issue, old_labels: old_labels) if has_changes?(issue, old_labels: old_labels)
todo_service.mark_pending_todos_as_done(issue, current_user) todo_service.mark_pending_todos_as_done(issue, current_user)
......
...@@ -2,18 +2,14 @@ module MergeRequests ...@@ -2,18 +2,14 @@ module MergeRequests
class BuildService < MergeRequests::BaseService class BuildService < MergeRequests::BaseService
def execute def execute
self.merge_request = MergeRequest.new(params) self.merge_request = MergeRequest.new(params)
merge_request.can_be_created = true
merge_request.compare_commits = [] merge_request.compare_commits = []
merge_request.source_project = find_source_project merge_request.source_project = find_source_project
merge_request.target_project = find_target_project merge_request.target_project = find_target_project
merge_request.target_branch = find_target_branch merge_request.target_branch = find_target_branch
merge_request.can_be_created = branches_valid? && source_branch_specified? && target_branch_specified?
if branches_specified? && branches_valid? compare_branches if branches_present?
compare_branches assign_title_and_description if merge_request.can_be_created
assign_title_and_description
else
merge_request.can_be_created = false
end
merge_request merge_request
end end
...@@ -37,11 +33,17 @@ module MergeRequests ...@@ -37,11 +33,17 @@ module MergeRequests
target_branch || target_project.default_branch target_branch || target_project.default_branch
end end
def branches_specified? def source_branch_specified?
params[:source_branch] && params[:target_branch] params[:source_branch].present?
end
def target_branch_specified?
params[:target_branch].present?
end end
def branches_valid? def branches_valid?
return false unless source_branch_specified? || target_branch_specified?
validate_branches validate_branches
errors.blank? errors.blank?
end end
...@@ -55,8 +57,10 @@ module MergeRequests ...@@ -55,8 +57,10 @@ module MergeRequests
target_branch target_branch
) )
merge_request.compare_commits = compare.commits if compare
merge_request.compare = compare merge_request.compare_commits = compare.commits
merge_request.compare = compare
end
end end
def validate_branches def validate_branches
......
# SpamCheckService
#
# Provide helper methods for checking if a given spammable object has
# potential spam data.
#
# Dependencies:
# - params with :request
#
module SpamCheckService
def filter_spam_check_params
@request = params.delete(:request)
@api = params.delete(:api)
@recaptcha_verified = params.delete(:recaptcha_verified)
@spam_log_id = params.delete(:spam_log_id)
end
def spam_check(spammable, user)
spam_service = SpamService.new(spammable, @request)
spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do
user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
end
end
end
...@@ -17,15 +17,6 @@ class SpamService ...@@ -17,15 +17,6 @@ class SpamService
end end
end end
def check(api = false)
return false unless request && check_for_spam?
return false unless akismet.is_spam?
create_spam_log(api)
true
end
def mark_as_spam! def mark_as_spam!
return false unless spammable.submittable_as_spam? return false unless spammable.submittable_as_spam?
...@@ -36,8 +27,30 @@ class SpamService ...@@ -36,8 +27,30 @@ class SpamService
end end
end end
def when_recaptcha_verified(recaptcha_verified, api = false)
# In case it's a request which is already verified through recaptcha, yield
# block.
if recaptcha_verified
yield
else
# Otherwise, it goes to Akismet and check if it's a spam. If that's the
# case, it assigns spammable record as "spam" and create a SpamLog record.
spammable.spam = check(api)
spammable.spam_log = spam_log
end
end
private private
def check(api)
return false unless request && check_for_spam?
return false unless akismet.is_spam?
create_spam_log(api)
true
end
def akismet def akismet
@akismet ||= AkismetService.new( @akismet ||= AkismetService.new(
spammable_owner, spammable_owner,
......
class UpdateSnippetService < BaseService class UpdateSnippetService < BaseService
include SpamCheckService
attr_accessor :snippet attr_accessor :snippet
def initialize(project, user, snippet, params) def initialize(project, user, snippet, params)
...@@ -9,7 +11,7 @@ class UpdateSnippetService < BaseService ...@@ -9,7 +11,7 @@ class UpdateSnippetService < BaseService
def execute def execute
# check that user is allowed to set specified visibility_level # check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level] new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != snippet.visibility_level if new_visibility && new_visibility.to_i != snippet.visibility_level
unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(snippet, new_visibility) deny_visibility_level(snippet, new_visibility)
...@@ -17,6 +19,10 @@ class UpdateSnippetService < BaseService ...@@ -17,6 +19,10 @@ class UpdateSnippetService < BaseService
end end
end end
snippet.update_attributes(params) filter_spam_check_params
snippet.assign_attributes(params)
spam_check(snippet, current_user)
snippet.save
end end
end end
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: container_class }
= nav_link(path: 'groups#show', html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'Group Home' do
%span
Home
= nav_link(path: 'groups#activity') do
= link_to activity_group_path(@group), title: 'Activity' do
%span
Activity
= nav_link(path: 'group_members#index') do
= link_to group_group_members_path(@group), title: 'Members' do
%span
Members
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: container_class }
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
= link_to issues_group_path(@group), title: 'List' do
%span
List
= nav_link(path: 'labels#index') do
= link_to group_labels_path(@group), title: 'Labels' do
%span
Labels
= nav_link(path: 'milestones#index') do
= link_to group_milestones_path(@group), title: 'Milestones' do
%span
Milestones
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
- page_title "Activity" - page_title "Activity"
= render 'groups/head'
%section.activities %section.activities
= render 'activities' = render 'activities'
- page_title "Members" - page_title "Members"
= render 'groups/head'
.project-members-page.prepend-top-default .project-members-page.prepend-top-default
%h4 %h4
......
- page_title "Issues" - page_title "Issues"
= render "head_issues"
= content_for :meta_tags do = content_for :meta_tags do
- if current_user - if current_user
= auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@group.name} issues") = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@group.name} issues")
......
- page_title 'Labels' - page_title 'Labels'
= render "groups/head_issues"
.top-area.adjust .top-area.adjust
.nav-text .nav-text
......
- page_title "Milestones" - page_title "Milestones"
= render "groups/head_issues"
.top-area .top-area
= render 'shared/milestones_filter' = render 'shared/milestones_filter'
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
= render 'groups/head'
= render 'groups/home_panel' = render 'groups/home_panel'
......
.page-with-sidebar{ class: page_gutter_class } .page-with-sidebar{ class: page_gutter_class }
- if defined?(nav) && nav - if defined?(nav) && nav
.layout-nav .layout-nav
%div{ class: container_class } .container-fluid
= render "layouts/nav/#{nav}" = render "layouts/nav/#{nav}"
.content-wrapper{ class: "#{layout_nav_class}" } .content-wrapper{ class: "#{layout_nav_class}" }
= yield :sub_nav = yield :sub_nav
......
- humanized_resource_name = spammable.class.model_name.human.downcase
- resource_name = spammable.class.model_name.singular
%h3.page-title
Anti-spam verification
%hr
%p
#{"We detected potential spam in the #{humanized_resource_name}. Please solve the reCAPTCHA to proceed."}
= form_for form do |f|
.recaptcha
- params[resource_name].each do |field, value|
= hidden_field(resource_name, field, value: value)
= hidden_field_tag(:spam_log_id, spammable.spam_log.id)
= hidden_field_tag(:recaptcha_verification, true)
= recaptcha_tags
-# Yields a block with given extra params.
= yield
.row-content-block.footer-block
= f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create'
...@@ -67,12 +67,12 @@ ...@@ -67,12 +67,12 @@
%div %div
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success' = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
%h1.title= title
.header-logo .header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
= brand_header_logo = brand_header_logo
%h1.title= title
= yield :header_content = yield :header_content
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
......
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
%html{ lang: "en" }
%head
%meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/
%meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/
%meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/
%title= message.subject
:css
/* CLIENT-SPECIFIC STYLES */
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; }
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* ANDROID MARGIN HACK */
body { margin:0 !important; }
div[style*="margin: 16px 0"] { margin:0 !important; }
@media only screen and (max-width: 639px) {
body, #body {
min-width: 320px !important;
}
table.wrapper {
width: 100% !important;
min-width: 320px !important;
}
table.wrapper > tbody > tr > td {
border-left: 0 !important;
border-right: 0 !important;
border-radius: 0 !important;
padding-left: 10px !important;
padding-right: 10px !important;
}
}
%body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
%tbody
%tr.line
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  
%tr.header
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
= header_logo
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
%tbody
= yield
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
%div
%a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications
&middot;
%a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help
%div
You're receiving this email because of your account on
= succeed "." do
%a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host
= yield
You're receiving this email because of your account on #{Gitlab.config.gitlab.host}.
Manage all notifications: #{profile_notifications_url}
Help: #{help_url}
...@@ -5,23 +5,11 @@ ...@@ -5,23 +5,11 @@
.fade-right .fade-right
= icon('angle-right') = icon('angle-right')
%ul.nav-links.scrolling-tabs %ul.nav-links.scrolling-tabs
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do = nav_link(path: ['groups#show', 'groups#activity', 'group_members#index'], html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'Home' do = link_to group_path(@group), title: 'Home' do
%span %span
Group Group
= nav_link(path: 'groups#activity') do = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
= link_to activity_group_path(@group), title: 'Activity' do
%span
Activity
= nav_link(controller: [:group, :labels]) do
= link_to group_labels_path(@group), title: 'Labels' do
%span
Labels
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones' do
%span
Milestones
= nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group), title: 'Issues' do = link_to issues_group_path(@group), title: 'Issues' do
%span %span
Issues Issues
...@@ -33,10 +21,6 @@ ...@@ -33,10 +21,6 @@
Merge Requests Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
%span.badge.count= number_with_delimiter(merge_requests.count) %span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do
%span
Members
= nav_link(controller: [:stats]) do = nav_link(controller: [:stats]) do
= link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do = link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
%span %span
......
...@@ -27,7 +27,3 @@ Trace: <%= build.trace_with_state(last_lines: 10)[:text] %> ...@@ -27,7 +27,3 @@ Trace: <%= build.trace_with_state(last_lines: 10)[:text] %>
<% end -%> <% end -%>
<% end -%> <% end -%>
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
Manage all notifications: <%= profile_notifications_url %>
Help: <%= help_url %>
...@@ -18,7 +18,3 @@ Commit Author: <%= commit.author_name %> ...@@ -18,7 +18,3 @@ Commit Author: <%= commit.author_name %>
<% build_count = @pipeline.statuses.latest.size -%> <% build_count = @pipeline.statuses.latest.size -%>
<% stage_count = @pipeline.stages_count -%> <% stage_count = @pipeline.stages_count -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>.
You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>.
Manage all notifications: <%= profile_notifications_url %>
Help: <%= help_url %>
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), = link_to namespace_project_branch_path(@project.namespace, @project, branch.name),
class: "btn btn-remove remove-row #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}", class: "btn btn-remove remove-row js-ajax-loading-spinner #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}",
method: :delete, method: :delete,
data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" }, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" },
remote: true, remote: true,
......
- page_title "Anti-spam verification" - form = [@project.namespace.becomes(Namespace), @project, @issue]
%h3.page-title = render layout: 'layouts/recaptcha_verification', locals: { spammable: @issue, form: form } do
Anti-spam verification = hidden_field_tag(:merge_request_for_resolving_discussions, params[:merge_request_for_resolving_discussions])
%hr
%p
We detected potential spam in the issue description. Please verify that you are not a robot to submit the issue.
= form_for [@project.namespace.becomes(Namespace), @project, @issue] do |f|
.recaptcha
- params[:issue].each do |field, value|
= hidden_field(:issue, field, value: value)
= hidden_field_tag(:merge_request_for_resolving_discussions, params[:merge_request_for_resolving_discussions])
= hidden_field_tag(:spam_log_id, @issue.spam_log.id)
= hidden_field_tag(:recaptcha_verification, true)
= recaptcha_tags
.row-content-block.footer-block
= f.submit "Submit #{@issue.class.model_name.human.downcase}", class: 'btn btn-create'
...@@ -9,23 +9,35 @@ ...@@ -9,23 +9,35 @@
%div{ class: container_class } %div{ class: container_class }
.top-area .top-area
%ul.nav-links %ul.nav-links
%li{ class: active_when(@scope.nil?) }> %li.js-pipelines-tab-all{ class: active_when(@scope.nil?) }>
= link_to project_pipelines_path(@project) do = link_to project_pipelines_path(@project) do
All All
%span.badge.js-totalbuilds-count %span.badge.js-totalbuilds-count
= number_with_delimiter(@pipelines_count) = number_with_delimiter(@pipelines_count)
%li{ class: active_when(@scope == 'running') }> %li.js-pipelines-tab-pending{ class: active_when(@scope == 'pending') }>
= link_to project_pipelines_path(@project, scope: :pending) do
Pending
%span.badge
= number_with_delimiter(@pending_count)
%li.js-pipelines-tab-running{ class: active_when(@scope == 'running') }>
= link_to project_pipelines_path(@project, scope: :running) do = link_to project_pipelines_path(@project, scope: :running) do
Running Running
%span.badge.js-running-count %span.badge.js-running-count
= number_with_delimiter(@running_or_pending_count) = number_with_delimiter(@running_count)
%li.js-pipelines-tab-finished{ class: active_when(@scope == 'finished') }>
= link_to project_pipelines_path(@project, scope: :finished) do
Finished
%span.badge
= number_with_delimiter(@finished_count)
%li{ class: active_when(@scope == 'branches') }> %li.js-pipelines-tab-branches{ class: active_when(@scope == 'branches') }>
= link_to project_pipelines_path(@project, scope: :branches) do = link_to project_pipelines_path(@project, scope: :branches) do
Branches Branches
%li{ class: active_when(@scope == 'tags') }> %li.js-pipelines-tab-tags{ class: active_when(@scope == 'tags') }>
= link_to project_pipelines_path(@project, scope: :tags) do = link_to project_pipelines_path(@project, scope: :tags) do
Tags Tags
......
...@@ -25,3 +25,10 @@ ...@@ -25,3 +25,10 @@
HTML HTML
.col-md-10.code.js-syntax-highlight .col-md-10.code.js-syntax-highlight
= highlight('.html', badge.to_html) = highlight('.html', badge.to_html)
.row
%hr
.row
.col-md-2.text-center
AsciiDoc
.col-md-10.code.js-syntax-highlight
= highlight('.adoc', badge.to_asciidoc)
...@@ -16,70 +16,69 @@ ...@@ -16,70 +16,69 @@
= render "home_panel" = render "home_panel"
- if current_user && can?(current_user, :download_code, @project) - if current_user && can?(current_user, :download_code, @project)
.project-stats-container{ class: container_class } %nav.project-stats{ class: container_class }
%nav.project-stats %ul.nav
%ul.nav %li
%li = link_to project_files_path(@project) do
= link_to project_files_path(@project) do Files (#{storage_counter(@project.statistics.total_repository_size)})
Files (#{storage_counter(@project.statistics.total_repository_size)}) %li
%li = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do #{'Commit'.pluralize(@project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)})
#{'Commit'.pluralize(@project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)}) %li
%li = link_to namespace_project_branches_path(@project.namespace, @project) do
= link_to namespace_project_branches_path(@project.namespace, @project) do #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
#{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) %li
%li = link_to namespace_project_tags_path(@project.namespace, @project) do
= link_to namespace_project_tags_path(@project.namespace, @project) do #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
#{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- if default_project_view != 'readme' && @repository.readme - if default_project_view != 'readme' && @repository.readme
%li %li
= link_to 'Readme', readme_path(@project) = link_to 'Readme', readme_path(@project)
- if @repository.changelog - if @repository.changelog
%li %li
= link_to 'Changelog', changelog_path(@project) = link_to 'Changelog', changelog_path(@project)
- if @repository.license_blob - if @repository.license_blob
%li %li
= link_to license_short_name(@project), license_path(@project) = link_to license_short_name(@project), license_path(@project)
- if @repository.contribution_guide - if @repository.contribution_guide
%li %li
= link_to 'Contribution guide', contribution_guide_path(@project) = link_to 'Contribution guide', contribution_guide_path(@project)
- if @repository.gitlab_ci_yml - if @repository.gitlab_ci_yml
%li %li
= link_to 'CI configuration', ci_configuration_path(@project) = link_to 'CI configuration', ci_configuration_path(@project)
- if current_user && can_push_branch?(@project, @project.default_branch) - if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog - unless @repository.changelog
%li.missing %li.missing
= link_to add_special_file_path(@project, file_name: 'CHANGELOG') do = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog Add Changelog
- unless @repository.license_blob - unless @repository.license_blob
%li.missing %li.missing
= link_to add_special_file_path(@project, file_name: 'LICENSE') do = link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License Add License
- unless @repository.contribution_guide - unless @repository.contribution_guide
%li.missing %li.missing
= link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide Add Contribution guide
- unless @repository.gitlab_ci_yml - unless @repository.gitlab_ci_yml
%li.missing %li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set up CI Set up CI
- if koding_enabled? && @repository.koding_yml.blank? - if koding_enabled? && @repository.koding_yml.blank?
%li.missing %li.missing
= link_to 'Set up Koding', add_koding_stack_path(@project) = link_to 'Set up Koding', add_koding_stack_path(@project)
- if @repository.gitlab_ci_yml.blank? && @project.deployment_service.present? - if @repository.gitlab_ci_yml.blank? && @project.deployment_service.present?
%li.missing %li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', target_branch: 'auto-deploy', context: 'autodeploy') do = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', target_branch: 'auto-deploy', context: 'autodeploy') do
Set up auto deploy Set up auto deploy
- if @repository.commit - if @repository.commit
.project-last-commit .project-last-commit{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
%div{ class: container_class } %div{ class: container_class }
- if @project.archived? - if @project.archived?
......
- form = [@project.namespace.becomes(Namespace), @project, @snippet.becomes(Snippet)]
= render 'layouts/recaptcha_verification', spammable: @snippet, form: form
...@@ -6,5 +6,5 @@ ...@@ -6,5 +6,5 @@
= f.text_field :key, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true = f.text_field :key, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true
.form-group .form-group
= f.label :value, "Value", class: "label-light" = f.label :value, "Value", class: "label-light"
= f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true = f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE"
= f.submit btn_text, class: "btn btn-save" = f.submit btn_text, class: "btn btn-save"
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
- issue_votes = @issuable_meta_data[issuable.id] - issue_votes = @issuable_meta_data[issuable.id]
- upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes - upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes
- issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes') - issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes')
- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count
- if issuable_mr > 0
%li
= image_tag('icon-merge-request-unmerged', class: 'icon-merge-request-unmerged')
= issuable_mr
- if upvotes > 0 - if upvotes > 0
%li %li
......
- form = [@snippet.becomes(Snippet)]
= render 'layouts/recaptcha_verification', spammable: @snippet, form: form
---
title: Align task list checkboxes
merge_request: 6487
author: Jared Deckard <jared.deckard@gmail.com>
---
title: Added AsciiDoc Snippet to CI/CD Badges
merge_request: 9164
author: Jan Christophersen
---
title: Clean-up Groups navigation order
merge_request: 9309
author:
---
title: Add all available statuses to scope filter for project builds endpoint
merge_request:
author: George Andrinopoulos
---
title: Add `copy` backup strategy to combat file changed errors
merge_request: 8728
author:
---
title: Adds Pending and Finished tabs to pipelines page
merge_request:
author:
---
title: Add housekeeping endpoint for Projects API
merge_request: 9421
author:
---
title: Spam check and reCAPTCHA improvements
merge_request:
author:
---
title: test compiling production assets and generate webpack bundle report in CI
merge_request: 9396
author:
---
title: Fixes delimiter removes when todo marked as done
merge_request: 9435
author:
---
title: Document when current coverage configuration option was introduced
merge_request: 9443
author:
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment