summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md6
-rw-r--r--app/assets/javascripts/application.js2
-rw-r--r--app/assets/javascripts/build.js35
-rw-r--r--app/assets/javascripts/commit/image_file.js81
-rw-r--r--app/assets/javascripts/environments/components/environment_item.js.es628
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es66
-rw-r--r--app/assets/javascripts/gl_dropdown.js6
-rw-r--r--app/assets/javascripts/issuable.js.es62
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es64
-rw-r--r--app/assets/javascripts/new_branch_form.js2
-rw-r--r--app/assets/javascripts/project.js2
-rw-r--r--app/assets/stylesheets/framework/filters.scss46
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss4
-rw-r--r--app/helpers/builds_helper.rb7
-rw-r--r--app/uploaders/artifact_uploader.rb4
-rw-r--r--app/uploaders/attachment_uploader.rb2
-rw-r--r--app/uploaders/avatar_uploader.rb2
-rw-r--r--app/uploaders/file_uploader.rb25
-rw-r--r--app/uploaders/gitlab_uploader.rb10
-rw-r--r--app/uploaders/uploader_helper.rb6
-rw-r--r--app/views/projects/builds/_header.html.haml9
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml2
-rw-r--r--changelogs/unreleased/25920-create-issue-from-failing-build.yml4
-rw-r--r--changelogs/unreleased/27840-improve-search-bar-experience.yml4
-rw-r--r--changelogs/unreleased/28212-avoid-dos-on-build-trace.yml4
-rw-r--r--changelogs/unreleased/28723-consistent-handling-indexof.yml4
-rw-r--r--changelogs/unreleased/fix-mr-size-with-over-100-files.yml4
-rw-r--r--db/fixtures/development/19_nested_groups.rb69
-rw-r--r--db/migrate/20160610201627_migrate_users_notification_level.rb2
-rw-r--r--lib/banzai/filter/image_link_filter.rb9
-rw-r--r--lib/gitlab/middleware/webpack_proxy.rb12
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/issues_spec.rb32
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb26
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_profile_spec.rb24
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb22
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb10
-rw-r--r--spec/support/dropzone_helper.rb37
-rw-r--r--spec/support/update_invalid_issuable.rb2
-rw-r--r--spec/uploaders/attachment_uploader_spec.rb7
-rw-r--r--spec/uploaders/avatar_uploader_spec.rb7
-rw-r--r--spec/uploaders/file_uploader_spec.rb46
-rw-r--r--spec/uploaders/uploader_helper_spec.rb35
-rw-r--r--spec/views/projects/builds/show.html.haml_spec.rb42
44 files changed, 508 insertions, 187 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f5b101ad6b..c039335c46d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -182,6 +182,12 @@ entry.
- Remove deprecated GitlabCiService.
- Requeue pending deletion projects.
+## 8.16.7 (2017-02-27)
+
+- No changes.
+- No changes.
+- Fix MR changes tab size count when there are over 100 files in the diff.
+
## 8.16.6 (2017-02-17)
- API: Fix file downloading. !0 (8267)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 53d8d313e39..c51860d1604 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -7,8 +7,6 @@
/* global Aside */
window.$ = window.jQuery = require('jquery');
-require('jquery-ui/ui/draggable');
-require('jquery-ui/ui/sortable');
require('jquery-ujs');
require('vendor/jquery.endless-scroll');
require('vendor/jquery.highlight');
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 8fa1aceddff..6e6e9b18686 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -7,7 +7,7 @@
var DOWN_BUILD_TRACE = '#down-build-trace';
this.Build = (function() {
- Build.interval = null;
+ Build.timeout = null;
Build.state = null;
@@ -31,7 +31,7 @@
this.$scrollBottomBtn = $('#scroll-bottom');
this.$buildRefreshAnimation = $('.js-build-refresh');
- clearInterval(Build.interval);
+ clearTimeout(Build.timeout);
// Init breakpoint checker
this.bp = Breakpoints.get();
@@ -52,17 +52,7 @@
this.getInitialBuildTrace();
this.initScrollButtonAffix();
}
- if (this.buildStatus === "running" || this.buildStatus === "pending") {
- Build.interval = setInterval((function(_this) {
- // Check for new build output if user still watching build page
- // Only valid for runnig build when output changes during time
- return function() {
- if (_this.location() === _this.pageUrl) {
- return _this.getBuildTrace();
- }
- };
- })(this), 4000);
- }
+ this.invokeBuildTrace();
}
Build.prototype.initSidebar = function() {
@@ -75,6 +65,22 @@
return window.location.href.split("#")[0];
};
+ Build.prototype.invokeBuildTrace = function() {
+ var continueRefreshStatuses = ['running', 'pending'];
+ // Continue to update build trace when build is running or pending
+ if (continueRefreshStatuses.indexOf(this.buildStatus) !== -1) {
+ // Check for new build output if user still watching build page
+ // Only valid for runnig build when output changes during time
+ Build.timeout = setTimeout((function(_this) {
+ return function() {
+ if (_this.location() === _this.pageUrl) {
+ return _this.getBuildTrace();
+ }
+ };
+ })(this), 4000);
+ }
+ };
+
Build.prototype.getInitialBuildTrace = function() {
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'];
@@ -86,7 +92,7 @@
if (window.location.hash === DOWN_BUILD_TRACE) {
$("html,body").scrollTop(this.$buildTrace.height());
}
- if (removeRefreshStatuses.indexOf(buildData.status) >= 0) {
+ if (removeRefreshStatuses.indexOf(buildData.status) !== -1) {
this.$buildRefreshAnimation.remove();
return this.initScrollMonitor();
}
@@ -105,6 +111,7 @@
if (log.state) {
_this.state = log.state;
}
+ _this.invokeBuildTrace();
if (log.status === "running") {
if (log.append) {
$('.js-build-output').append(log.html);
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 49bb64a3472..17d14dc1e79 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -52,6 +52,30 @@
return this.views[viewMode].call(this);
};
+ ImageFile.prototype.initDraggable = function($el, padding, callback) {
+ var dragging = false;
+ var $body = $('body');
+ var $offsetEl = $el.parent();
+
+ $el.off('mousedown').on('mousedown', function() {
+ dragging = true;
+ $body.css('user-select', 'none');
+ });
+
+ $body.off('mouseup').off('mousemove').on('mouseup', function() {
+ dragging = false;
+ $body.css('user-select', '');
+ })
+ .on('mousemove', function(e) {
+ var left;
+ if (!dragging) return;
+
+ left = e.pageX - ($offsetEl.offset().left + padding);
+
+ callback(e, left);
+ });
+ };
+
prepareFrames = function(view) {
var maxHeight, maxWidth;
maxWidth = 0;
@@ -96,26 +120,30 @@
maxHeight = 0;
return $('.swipe.view', this.file).each((function(_this) {
return function(index, view) {
- var ref;
+ var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
- $('.swipe-frame', view).css({
+ $swipeFrame = $('.swipe-frame', view);
+ $swipeWrap = $('.swipe-wrap', view);
+ $swipeBar = $('.swipe-bar', view);
+
+ $swipeFrame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
- $('.swipe-wrap', view).css({
+ $swipeWrap.css({
width: maxWidth + 1,
height: maxHeight + 2
});
- return $('.swipe-bar', view).css({
+ $swipeBar.css({
left: 0
- }).draggable({
- axis: 'x',
- containment: 'parent',
- drag: function(event) {
- return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
- },
- stop: function(event) {
- return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
+ });
+
+ wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
+
+ _this.initDraggable($swipeBar, wrapPadding, function(e, left) {
+ if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) {
+ $swipeWrap.width((maxWidth + 1) - left);
+ $swipeBar.css('left', left);
}
});
};
@@ -128,9 +156,14 @@
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
return $('.onion-skin.view', this.file).each((function(_this) {
return function(index, view) {
- var ref;
+ var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
- $('.onion-skin-frame', view).css({
+ $frame = $('.onion-skin-frame', view);
+ $frameAdded = $('.frame.added', view);
+ $track = $('.drag-track', view);
+ $dragger = $('.dragger', $track);
+
+ $frame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
@@ -138,16 +171,18 @@
width: maxWidth + 1,
height: maxHeight + 2
});
- return $('.dragger', view).css({
+ $dragger.css({
left: dragTrackWidth
- }).draggable({
- axis: 'x',
- containment: 'parent',
- drag: function(event) {
- return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
- },
- stop: function(event) {
- return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
+ });
+
+ framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
+
+ _this.initDraggable($dragger, framePadding, function(e, left) {
+ var opacity = left / dragTrackWidth;
+
+ if (opacity >= 0 && opacity <= 1) {
+ $dragger.css('left', left);
+ $frameAdded.css('opacity', opacity);
}
});
};
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6
index 6a91982ffa7..08579d0e826 100644
--- a/app/assets/javascripts/environments/components/environment_item.js.es6
+++ b/app/assets/javascripts/environments/components/environment_item.js.es6
@@ -486,25 +486,23 @@ module.exports = Vue.component('environment-item', {
</span>
</td>
- <td class="hidden-xs">
- <div v-if="!model.isFolder">
- <div class="btn-group" role="group">
- <actions-component v-if="hasManualActions && canCreateDeployment"
- :actions="manualActions"/>
+ <td class="hidden-xs environments-actions">
+ <div v-if="!model.isFolder" class="btn-group" role="group">
+ <actions-component v-if="hasManualActions && canCreateDeployment"
+ :actions="manualActions"/>
- <external-url-component v-if="externalURL && canReadEnvironment"
- :external-url="externalURL"/>
+ <external-url-component v-if="externalURL && canReadEnvironment"
+ :external-url="externalURL"/>
- <stop-component v-if="hasStopAction && canCreateDeployment"
- :stop-url="model.stop_path"/>
+ <stop-component v-if="hasStopAction && canCreateDeployment"
+ :stop-url="model.stop_path"/>
- <terminal-button-component v-if="model && model.terminal_path"
- :terminal-path="model.terminal_path"/>
+ <terminal-button-component v-if="model && model.terminal_path"
+ :terminal-path="model.terminal_path"/>
- <rollback-component v-if="canRetry && canCreateDeployment"
- :is-last-deployment="isLastDeployment"
- :retry-url="retryUrl"/>
- </div>
+ <rollback-component v-if="canRetry && canCreateDeployment"
+ :is-last-deployment="isLastDeployment"
+ :retry-url="retryUrl"/>
</div>
</td>
</tr>
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
index fbc72a3001a..dd565da507e 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
@@ -48,7 +48,11 @@
}
setOffset(offset = 0) {
- this.dropdown.style.left = `${offset}px`;
+ if (window.innerWidth > 480) {
+ this.dropdown.style.left = `${offset}px`;
+ } else {
+ this.dropdown.style.left = '0px';
+ }
}
renderContent(forceShowList = false) {
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index a01662e2f9e..9e6ed06054b 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -63,7 +63,7 @@
}
GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
- return BLUR_KEYCODES.indexOf(keyCode) >= 0;
+ return BLUR_KEYCODES.indexOf(keyCode) !== -1;
};
GitLabDropdownFilter.prototype.filter = function(search_text) {
@@ -605,7 +605,7 @@
var occurrences;
occurrences = fuzzaldrinPlus.match(text, term);
return text.split('').map(function(character, i) {
- if (indexOf.call(occurrences, i) >= 0) {
+ if (indexOf.call(occurrences, i) !== -1) {
return "<b>" + character + "</b>";
} else {
return character;
@@ -748,7 +748,7 @@
return function(e) {
var $listItems, PREV_INDEX, currentKeyCode;
currentKeyCode = e.which;
- if (ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0) {
+ if (ARROW_KEY_CODES.indexOf(currentKeyCode) !== -1) {
e.preventDefault();
e.stopImmediatePropagation();
PREV_INDEX = currentIndex;
diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6
index 8df86f68218..3bfce32768a 100644
--- a/app/assets/javascripts/issuable.js.es6
+++ b/app/assets/javascripts/issuable.js.es6
@@ -116,7 +116,7 @@
formData = $.param(formData);
formAction = form.attr('action');
issuesUrl = formAction;
- issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
+ issuesUrl += "" + (formAction.indexOf('?') === -1 ? '?' : '&');
issuesUrl += formData;
return gl.utils.visitUrl(issuesUrl);
};
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index 88f08bbaa34..00c6c050612 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -83,7 +83,7 @@ require('./smart_interval');
return function() {
var page;
page = $('body').data('page').split(':').last();
- if (allowedPages.indexOf(page) < 0) {
+ if (allowedPages.indexOf(page) === -1) {
return _this.clearEventListeners();
}
};
@@ -233,7 +233,7 @@ require('./smart_interval');
}
$('.ci_widget').hide();
allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"];
- if (indexOf.call(allowed_states, state) >= 0) {
+ if (indexOf.call(allowed_states, state) !== -1) {
$('.ci_widget.ci-' + state).show();
switch (state) {
case "failed":
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index 3f678b93f73..5828f460a23 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -81,7 +81,7 @@
var errorMessage, errors, formatter, unique, validator;
this.branchNameError.empty();
unique = function(values, value) {
- if (indexOf.call(values, value) < 0) {
+ if (indexOf.call(values, value) === -1) {
values.push(value);
}
return values;
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 7c03c8b72d4..db7ceaa2421 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -116,7 +116,7 @@
if ($('input[name="ref"]').length) {
var $form = $dropdown.closest('form');
var action = $form.attr('action');
- var divider = action.indexOf('?') < 0 ? '?' : '&';
+ var divider = action.indexOf('?') === -1 ? '?' : '&';
gl.utils.visitUrl(action + '' + divider + '' + $form.serialize());
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index e3da467a27c..d2be8dc7a39 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -26,6 +26,11 @@
.filtered-search-container {
display: -webkit-flex;
display: flex;
+
+ @media (max-width: $screen-xs-min) {
+ -webkit-flex-direction: column;
+ flex-direction: column;
+ }
}
.filtered-search-input-container {
@@ -34,6 +39,20 @@
position: relative;
width: 100%;
+ @media (max-width: $screen-xs-min) {
+ -webkit-flex: 1 1 100%;
+ flex: 1 1 100%;
+ margin-bottom: 10px;
+
+ .dropdown-menu {
+ width: auto;
+ left: 0;
+ right: 0;
+ max-width: none;
+ min-width: 100%;
+ }
+ }
+
.form-control {
padding-left: 25px;
padding-right: 25px;
@@ -79,6 +98,31 @@
overflow: auto;
}
+@media (max-width: $screen-xs-min) {
+ .issues-details-filters {
+ padding: 0 0 10px;
+ background-color: $white-light;
+ border-top: 0;
+ }
+
+ .filter-dropdown-container {
+ .dropdown-toggle,
+ .dropdown {
+ width: 100%;
+ }
+
+ .dropdown {
+ margin-left: 0;
+ }
+
+ .fa-chevron-down {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ }
+ }
+}
+
%filter-dropdown-item-btn-hover {
background-color: $dropdown-hover-color;
color: $white-light;
@@ -148,4 +192,4 @@
.filter-dropdown-loading {
padding: 8px 16px;
-}
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 08b3206f31e..f4707f71208 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -128,6 +128,10 @@
padding: 10px 8px;
}
+ td.environments-actions {
+ padding-right: 0;
+ }
+
td.stage-cell {
padding: 10px 0;
}
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index ff937b5ebd2..5ac3e66bb1f 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -15,4 +15,11 @@ module BuildsHelper
log_state: @build.trace_with_state[:state].to_s
}
end
+
+ def build_failed_issue_options
+ {
+ title: "Build Failed ##{@build.id}",
+ description: namespace_project_build_url(@project.namespace, @project, @build)
+ }
+ end
end
diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb
index 86f317dcd18..e84944ed411 100644
--- a/app/uploaders/artifact_uploader.rb
+++ b/app/uploaders/artifact_uploader.rb
@@ -27,10 +27,6 @@ class ArtifactUploader < GitlabUploader
File.join(self.class.artifacts_cache_path, @build.artifacts_path)
end
- def file_storage?
- self.class.storage == CarrierWave::Storage::File
- end
-
def filename
file.try(:filename)
end
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index cfcb877cc3e..6aa1f5a8c50 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -4,6 +4,6 @@ class AttachmentUploader < GitlabUploader
storage :file
def store_dir
- "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ "#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index 265cea2d2c6..b4c393c6f2c 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -4,7 +4,7 @@ class AvatarUploader < GitlabUploader
storage :file
def store_dir
- "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ "#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def exists?
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 23b7318827c..0d2edaeff3b 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -4,15 +4,12 @@ class FileUploader < GitlabUploader
storage :file
- attr_accessor :project, :secret
+ attr_accessor :project
+ attr_reader :secret
def initialize(project, secret = nil)
@project = project
- @secret = secret || self.class.generate_secret
- end
-
- def base_dir
- "uploads"
+ @secret = secret || generate_secret
end
def store_dir
@@ -23,10 +20,6 @@ class FileUploader < GitlabUploader
File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end
- def secure_url
- File.join("/uploads", @secret, file.filename)
- end
-
def to_markdown
to_h[:markdown]
end
@@ -35,17 +28,23 @@ class FileUploader < GitlabUploader
filename = image_or_video? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]")
- markdown = "[#{escaped_filename}](#{self.secure_url})"
+ markdown = "[#{escaped_filename}](#{secure_url})"
markdown.prepend("!") if image_or_video? || dangerous?
{
alt: filename,
- url: self.secure_url,
+ url: secure_url,
markdown: markdown
}
end
- def self.generate_secret
+ private
+
+ def generate_secret
SecureRandom.hex
end
+
+ def secure_url
+ File.join('/uploads', @secret, file.filename)
+ end
end
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index 02d7c601d6c..bd7de4ed562 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -1,4 +1,14 @@
class GitlabUploader < CarrierWave::Uploader::Base
+ def self.base_dir
+ 'uploads'
+ end
+
+ delegate :base_dir, to: :class
+
+ def file_storage?
+ self.class.storage == CarrierWave::Storage::File
+ end
+
# Reduce disk IO
def move_to_cache
true
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
index bee311583ea..7635c20ab3a 100644
--- a/app/uploaders/uploader_helper.rb
+++ b/app/uploaders/uploader_helper.rb
@@ -27,6 +27,8 @@ module UploaderHelper
extension_match?(DANGEROUS_EXT)
end
+ private
+
def extension_match?(extensions)
return false unless file
@@ -40,8 +42,4 @@ module UploaderHelper
extensions.include?(extension.downcase)
end
-
- def file_storage?
- self.class.storage == CarrierWave::Storage::File
- end
end
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index 27e81c2bec3..7eb17e887e7 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -1,4 +1,4 @@
-.content-block.build-header
+.content-block.build-header.top-area
.header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false
Job
@@ -16,7 +16,10 @@
- if @build.user
= render "user"
= time_ago_with_tooltip(@build.created_at)
- - if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post
+ .nav-controls
+ - if can?(current_user, :create_issue, @project) && @build.failed?
+ = link_to "New issue", new_namespace_project_issue_path(@project.namespace, @project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
+ - if can?(current_user, :update_build, @build) && @build.retryable?
+ = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 8e04b50bb8a..62f09cc2dc1 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -82,7 +82,7 @@
%span.dropdown-label-box{ style: 'background: {{color}}' }
%span.label-title.js-data-value
{{title}}
- .pull-right
+ .pull-right.filter-dropdown-container
= render 'shared/sort_dropdown'
- if @bulk_edit
diff --git a/changelogs/unreleased/25920-create-issue-from-failing-build.yml b/changelogs/unreleased/25920-create-issue-from-failing-build.yml
new file mode 100644
index 00000000000..580d1074aa7
--- /dev/null
+++ b/changelogs/unreleased/25920-create-issue-from-failing-build.yml
@@ -0,0 +1,4 @@
+---
+title: Add button to create issue for failing build
+merge_request: 9391
+author: Alex Sanford
diff --git a/changelogs/unreleased/27840-improve-search-bar-experience.yml b/changelogs/unreleased/27840-improve-search-bar-experience.yml
new file mode 100644
index 00000000000..87b1f0c5572
--- /dev/null
+++ b/changelogs/unreleased/27840-improve-search-bar-experience.yml
@@ -0,0 +1,4 @@
+---
+title: Enhanced filter issues layout for better mobile experiance
+merge_request: 9280
+author: Pratik Borsadiya
diff --git a/changelogs/unreleased/28212-avoid-dos-on-build-trace.yml b/changelogs/unreleased/28212-avoid-dos-on-build-trace.yml
new file mode 100644
index 00000000000..800e0389c86
--- /dev/null
+++ b/changelogs/unreleased/28212-avoid-dos-on-build-trace.yml
@@ -0,0 +1,4 @@
+---
+title: Replace setInterval with setTimeout to prevent highly frequent requests
+merge_request: 9271
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/28723-consistent-handling-indexof.yml b/changelogs/unreleased/28723-consistent-handling-indexof.yml
new file mode 100644
index 00000000000..95d6181d5fa
--- /dev/null
+++ b/changelogs/unreleased/28723-consistent-handling-indexof.yml
@@ -0,0 +1,4 @@
+---
+title: Keep consistent in handling indexOf results
+merge_request: 9531
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/fix-mr-size-with-over-100-files.yml b/changelogs/unreleased/fix-mr-size-with-over-100-files.yml
deleted file mode 100644
index eecf3c99a75..00000000000
--- a/changelogs/unreleased/fix-mr-size-with-over-100-files.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix MR changes tab size count when there are over 100 files in the diff
-merge_request:
-author:
diff --git a/db/fixtures/development/19_nested_groups.rb b/db/fixtures/development/19_nested_groups.rb
new file mode 100644
index 00000000000..d8dddc3fee9
--- /dev/null
+++ b/db/fixtures/development/19_nested_groups.rb
@@ -0,0 +1,69 @@
+require './spec/support/sidekiq'
+
+def create_group_with_parents(user, full_path)
+ parent_path = nil
+ group = nil
+
+ until full_path.blank?
+ path, _, full_path = full_path.partition('/')
+
+ if parent_path
+ parent = Group.find_by_full_path(parent_path)
+
+ parent_path += '/'
+ parent_path += path
+
+ group = Groups::CreateService.new(user, path: path, parent_id: parent.id).execute
+ else
+ parent_path = path
+
+ group = Group.find_by_full_path(parent_path) ||
+ Groups::CreateService.new(user, path: path).execute
+ end
+ end
+
+ group
+end
+
+Sidekiq::Testing.inline! do
+ Gitlab::Seeder.quiet do
+ project_urls = [
+ 'https://android.googlesource.com/platform/hardware/broadcom/libbt.git',
+ 'https://android.googlesource.com/platform/hardware/broadcom/wlan.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/bootloader/intel/edison-u-boot.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/broadcom.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/freescale.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/imagination.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/intel.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.1.git',
+ 'https://android.googlesource.com/platform/hardware/bsp/kernel/common/v4.4.git'
+ ]
+
+ user = User.admins.first
+
+ project_urls.each_with_index do |url, i|
+ full_path = url.sub('https://android.googlesource.com/', '')
+ full_path = full_path.sub(/\.git\z/, '')
+ full_path, _, project_path = full_path.rpartition('/')
+ group = Group.find_by_full_path(full_path) || create_group_with_parents(user, full_path)
+
+ params = {
+ import_url: url,
+ namespace_id: group.id,
+ path: project_path,
+ name: project_path,
+ description: FFaker::Lorem.sentence,
+ visibility_level: Gitlab::VisibilityLevel.values.sample
+ }
+
+ project = Projects::CreateService.new(user, params).execute
+ project.send(:_run_after_commit_queue)
+
+ if project.valid?
+ print '.'
+ else
+ print 'F'
+ end
+ end
+ end
+end
diff --git a/db/migrate/20160610201627_migrate_users_notification_level.rb b/db/migrate/20160610201627_migrate_users_notification_level.rb
index ce4f00e25fa..cd8b505de9f 100644
--- a/db/migrate/20160610201627_migrate_users_notification_level.rb
+++ b/db/migrate/20160610201627_migrate_users_notification_level.rb
@@ -1,4 +1,6 @@
class MigrateUsersNotificationLevel < ActiveRecord::Migration
+ DOWNTIME = false
+
# Migrates only users who changed their default notification level :participating
# creating a new record on notification settings table
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
index f0fb6084a35..651b55523c0 100644
--- a/lib/banzai/filter/image_link_filter.rb
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -8,11 +8,6 @@ module Banzai
# of the anchor, and then replace the img with the link-wrapped version.
def call
doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img|
- div = doc.document.create_element(
- 'div',
- class: 'image-container'
- )
-
link = doc.document.create_element(
'a',
class: 'no-attachment-icon',
@@ -22,9 +17,7 @@ module Banzai
link.children = img.clone
- div.children = link
-
- img.replace(div)
+ img.replace(link)
end
doc
diff --git a/lib/gitlab/middleware/webpack_proxy.rb b/lib/gitlab/middleware/webpack_proxy.rb
index 3fe32adeade..6105d165810 100644
--- a/lib/gitlab/middleware/webpack_proxy.rb
+++ b/lib/gitlab/middleware/webpack_proxy.rb
@@ -8,16 +8,16 @@ module Gitlab
@proxy_host = opts.fetch(:proxy_host, 'localhost')
@proxy_port = opts.fetch(:proxy_port, 3808)
@proxy_path = opts[:proxy_path] if opts[:proxy_path]
- super(app, opts)
+
+ super(app, backend: "http://#{@proxy_host}:#{@proxy_port}", **opts)
end
def perform_request(env)
- unless @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}")
- return @app.call(env)
+ if @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}")
+ super(env)
+ else
+ @app.call(env)
end
-
- env['HTTP_HOST'] = "#{@proxy_host}:#{@proxy_port}"
- super(env)
end
end
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index f8c3ccb416b..b740e191f48 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -61,7 +61,7 @@ describe "User Feed", feature: true do
end
it 'has XHTML summaries in merge request descriptions' do
- expect(body).to match /Here is the fix: <\/p><div[^>]*><a[^>]*><img[^>]*\/><\/a><\/div>/
+ expect(body).to match /Here is the fix: <a[^>]*><img[^>]*\/><\/a>/
end
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 1e0db4a0499..1c8267b1593 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe 'Issues', feature: true do
+ include DropzoneHelper
include IssueHelpers
include SortingHelper
include WaitForAjax
@@ -570,19 +571,13 @@ describe 'Issues', feature: true do
end
it 'uploads file when dragging into textarea' do
- drop_in_dropzone test_image_file
-
- # Wait for the file to upload
- sleep 1
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
expect(page.find_field("issue_description").value).to have_content 'banana_sample'
end
it 'adds double newline to end of attachment markdown' do
- drop_in_dropzone test_image_file
-
- # Wait for the file to upload
- sleep 1
+ dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
expect(page.find_field("issue_description").value).to match /\n\n$/
end
@@ -665,25 +660,4 @@ describe 'Issues', feature: true do
end
end
end
-
- def drop_in_dropzone(file_path)
- # Generate a fake input selector
- page.execute_script <<-JS
- var fakeFileInput = window.$('<input/>').attr(
- {id: 'fakeFileInput', type: 'file'}
- ).appendTo('body');
- JS
- # Attach the file to the fake input selector with Capybara
- attach_file("fakeFileInput", file_path)
- # Add the file to a fileList array and trigger the fake drop event
- page.execute_script <<-JS
- var fileList = [$('#fakeFileInput')[0].files[0]];
- var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
- $('.div-dropzone')[0].dropzone.listeners[0].events.drop(e);
- JS
- end
-
- def test_image_file
- File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
- end
end
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
new file mode 100644
index 00000000000..f88a515f7fc
--- /dev/null
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+feature 'User uploads avatar to group', feature: true do
+ scenario 'they see the new avatar' do
+ user = create(:user)
+ group = create(:group)
+ group.add_owner(user)
+ login_as(user)
+
+ visit edit_group_path(group)
+ attach_file(
+ 'group_avatar',
+ Rails.root.join('spec', 'fixtures', 'dk.png'),
+ visible: false
+ )
+
+ click_button 'Save group'
+
+ visit group_path(group)
+
+ expect(page).to have_selector(%Q(img[src$="/uploads/group/avatar/#{group.id}/dk.png"]))
+
+ # Cheating here to verify something that isn't user-facing, but is important
+ expect(group.reload.avatar.file).to exist
+ end
+end
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
new file mode 100644
index 00000000000..0dfd29045e5
--- /dev/null
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+feature 'User uploads avatar to profile', feature: true do
+ scenario 'they see their new avatar' do
+ user = create(:user)
+ login_as(user)
+
+ visit profile_path
+ attach_file(
+ 'user_avatar',
+ Rails.root.join('spec', 'fixtures', 'dk.png'),
+ visible: false
+ )
+
+ click_button 'Update profile settings'
+
+ visit user_path(user)
+
+ expect(page).to have_selector(%Q(img[src$="/uploads/user/avatar/#{user.id}/dk.png"]))
+
+ # Cheating here to verify something that isn't user-facing, but is important
+ expect(user.reload.avatar.file).to exist
+ end
+end
diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb
new file mode 100644
index 00000000000..0c160dd74b4
--- /dev/null
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+feature 'User uploads file to note', feature: true do
+ include DropzoneHelper
+
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, creator: user, namespace: user.namespace) }
+
+ scenario 'they see the attached file', js: true do
+ issue = create(:issue, project: project, author: user)
+
+ login_as(user)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+
+ dropzone_file(Rails.root.join('spec', 'fixtures', 'dk.png'))
+ click_button 'Comment'
+ wait_for_ajax
+
+ expect(find('a.no-attachment-icon img[alt="dk"]')['src'])
+ .to match(%r{/#{project.full_path}/uploads/\h{32}/dk\.png$})
+ end
+end
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index a2a1ed58d1b..294558b3db2 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -13,8 +13,8 @@ describe Banzai::Filter::ImageLinkFilter, lib: true do
end
it 'does not wrap a duplicate link' do
- exp = act = %q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>)
- expect(filter(act).to_html).to eq exp
+ doc = filter(%Q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>))
+ expect(doc.to_html).to match /^<a href="\/whatever"><img[^>]*><\/a>$/
end
it 'works with external images' do
@@ -22,8 +22,8 @@ describe Banzai::Filter::ImageLinkFilter, lib: true do
expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
end
- it 'wraps the image with a link and a div' do
- doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
- expect(doc.to_html).to include('<div class="image-container">')
+ it 'works with inline images' do
+ doc = filter(%Q(<p>test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline</p>))
+ expect(doc.to_html).to match /^<p>test <a[^>]*><img[^>]*><\/a> inline<\/p>$/
end
end
diff --git a/spec/support/dropzone_helper.rb b/spec/support/dropzone_helper.rb
new file mode 100644
index 00000000000..984ec7d2741
--- /dev/null
+++ b/spec/support/dropzone_helper.rb
@@ -0,0 +1,37 @@
+module DropzoneHelper
+ # Provides a way to perform `attach_file` for a Dropzone-based file input
+ #
+ # This is accomplished by creating a standard HTML file input on the page,
+ # performing `attach_file` on that field, and then triggering the appropriate
+ # Dropzone events to perform the actual upload.
+ #
+ # This method waits for the upload to complete before returning.
+ def dropzone_file(file_path)
+ # Generate a fake file input that Capybara can attach to
+ page.execute_script <<-JS.strip_heredoc
+ var fakeFileInput = window.$('<input/>').attr(
+ {id: 'fakeFileInput', type: 'file'}
+ ).appendTo('body');
+
+ window._dropzoneComplete = false;
+ JS
+
+ # Attach the file to the fake input selector with Capybara
+ attach_file('fakeFileInput', file_path)
+
+ # Manually trigger a Dropzone "drop" event with the fake input's file list
+ page.execute_script <<-JS.strip_heredoc
+ var fileList = [$('#fakeFileInput')[0].files[0]];
+ var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
+
+ var dropzone = $('.div-dropzone')[0].dropzone;
+ dropzone.on('queuecomplete', function() {
+ window._dropzoneComplete = true;
+ });
+ dropzone.listeners[0].events.drop(e);
+ JS
+
+ # Wait until Dropzone's fired `queuecomplete`
+ loop until page.evaluate_script('window._dropzoneComplete === true')
+ end
+end
diff --git a/spec/support/update_invalid_issuable.rb b/spec/support/update_invalid_issuable.rb
index f984ac7bfa7..365c34448ac 100644
--- a/spec/support/update_invalid_issuable.rb
+++ b/spec/support/update_invalid_issuable.rb
@@ -33,7 +33,7 @@ shared_examples 'update invalid issuable' do |klass|
end
it 'renders json error message when format is json' do
- params.merge!(format: "json")
+ params[:format] = "json"
put :update, params
diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb
index 6098be5cd45..ea714fb08f0 100644
--- a/spec/uploaders/attachment_uploader_spec.rb
+++ b/spec/uploaders/attachment_uploader_spec.rb
@@ -1,18 +1,17 @@
require 'spec_helper'
describe AttachmentUploader do
- let(:issue) { build(:issue) }
- subject { described_class.new(issue) }
+ let(:uploader) { described_class.new(build_stubbed(:user)) }
describe '#move_to_cache' do
it 'is true' do
- expect(subject.move_to_cache).to eq(true)
+ expect(uploader.move_to_cache).to eq(true)
end
end
describe '#move_to_store' do
it 'is true' do
- expect(subject.move_to_store).to eq(true)
+ expect(uploader.move_to_store).to eq(true)
end
end
end
diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb
index 76f5a4b42ed..c4d558805ab 100644
--- a/spec/uploaders/avatar_uploader_spec.rb
+++ b/spec/uploaders/avatar_uploader_spec.rb
@@ -1,18 +1,17 @@
require 'spec_helper'
describe AvatarUploader do
- let(:user) { build(:user) }
- subject { described_class.new(user) }
+ let(:uploader) { described_class.new(build_stubbed(:user)) }
describe '#move_to_cache' do
it 'is false' do
- expect(subject.move_to_cache).to eq(false)
+ expect(uploader.move_to_cache).to eq(false)
end
end
describe '#move_to_store' do
it 'is false' do
- expect(subject.move_to_store).to eq(false)
+ expect(uploader.move_to_store).to eq(false)
end
end
end
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index 6a712e33c96..b0f5be55c33 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -1,57 +1,35 @@
require 'spec_helper'
describe FileUploader do
- let(:project) { create(:project) }
+ let(:uploader) { described_class.new(build_stubbed(:project)) }
- before do
- @previous_enable_processing = FileUploader.enable_processing
- FileUploader.enable_processing = false
- @uploader = FileUploader.new(project)
- end
-
- after do
- FileUploader.enable_processing = @previous_enable_processing
- @uploader.remove!
- end
+ describe 'initialize' do
+ it 'generates a secret if none is provided' do
+ expect(SecureRandom).to receive(:hex).and_return('secret')
- describe '#image_or_video?' do
- context 'given an image file' do
- before do
- @uploader.store!(fixture_file_upload(Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')))
- end
+ uploader = described_class.new(double)
- it 'detects an image based on file extension' do
- expect(@uploader.image_or_video?).to be true
- end
+ expect(uploader.secret).to eq 'secret'
end
- context 'given an video file' do
- before do
- video_file = fixture_file_upload(Rails.root.join('spec', 'fixtures', 'video_sample.mp4'))
- @uploader.store!(video_file)
- end
-
- it 'detects a video based on file extension' do
- expect(@uploader.image_or_video?).to be true
- end
- end
+ it 'accepts a secret parameter' do
+ expect(SecureRandom).not_to receive(:hex)
- it 'does not return image_or_video? for other types' do
- @uploader.store!(fixture_file_upload(Rails.root.join('spec', 'fixtures', 'doc_sample.txt')))
+ uploader = described_class.new(double, 'secret')
- expect(@uploader.image_or_video?).to be false
+ expect(uploader.secret).to eq 'secret'
end
end
describe '#move_to_cache' do
it 'is true' do
- expect(@uploader.move_to_cache).to eq(true)
+ expect(uploader.move_to_cache).to eq(true)
end
end
describe '#move_to_store' do
it 'is true' do
- expect(@uploader.move_to_store).to eq(true)
+ expect(uploader.move_to_store).to eq(true)
end
end
end
diff --git a/spec/uploaders/uploader_helper_spec.rb b/spec/uploaders/uploader_helper_spec.rb
new file mode 100644
index 00000000000..e9efd13b9aa
--- /dev/null
+++ b/spec/uploaders/uploader_helper_spec.rb
@@ -0,0 +1,35 @@
+require 'rails_helper'
+
+describe UploaderHelper do
+ class ExampleUploader < CarrierWave::Uploader::Base
+ include UploaderHelper
+
+ storage :file
+ end
+
+ def upload_fixture(filename)
+ fixture_file_upload(Rails.root.join('spec', 'fixtures', filename))
+ end
+
+ describe '#image_or_video?' do
+ let(:uploader) { ExampleUploader.new }
+
+ it 'returns true for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).to be_image_or_video
+ end
+
+ it 'it returns true for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).to be_image_or_video
+ end
+
+ it 'returns false for other extensions' do
+ uploader.store!(upload_fixture('doc_sample.txt'))
+
+ expect(uploader).not_to be_image_or_video
+ end
+ end
+end
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb
index b6f6e7b7a2b..ec78ac30593 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -209,6 +209,10 @@ describe 'projects/builds/show', :view do
it 'does not show retry button' do
expect(rendered).not_to have_link('Retry')
end
+
+ it 'does not show New issue button' do
+ expect(rendered).not_to have_link('New issue')
+ end
end
context 'when job is not running' do
@@ -220,6 +224,23 @@ describe 'projects/builds/show', :view do
it 'shows retry button' do
expect(rendered).to have_link('Retry')
end
+
+ context 'if build passed' do
+ it 'does not show New issue button' do
+ expect(rendered).not_to have_link('New issue')
+ end
+ end
+
+ context 'if build failed' do
+ before do
+ build.status = 'failed'
+ render
+ end
+
+ it 'shows New issue button' do
+ expect(rendered).to have_link('New issue')
+ end
+ end
end
describe 'commit title in sidebar' do
@@ -248,4 +269,25 @@ describe 'projects/builds/show', :view do
expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_2')
end
end
+
+ describe 'New issue button' do
+ before do
+ build.status = 'failed'
+ render
+ end
+
+ it 'links to issues/new with the title and description filled in' do
+ title = "Build Failed ##{build.id}"
+ build_url = namespace_project_build_url(project.namespace, project, build)
+ href = new_namespace_project_issue_path(
+ project.namespace,
+ project,
+ issue: {
+ title: title,
+ description: build_url
+ }
+ )
+ expect(rendered).to have_link('New issue', href: href)
+ end
+ end
end