summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-02-28 09:54:12 +0000
committerFilipa Lacerda <filipa@gitlab.com>2017-02-28 09:54:12 +0000
commit6862e788e8b32196d750152c4657ea95d431e04f (patch)
treeb84746c1943e39ab6540954263b7fef4c4248d51
parent61f65992a05e8fc5ab756ac0ce890d090c6cf4eb (diff)
parent7733f285aca97d444382a59eda0ea3e303539c26 (diff)
downloadgitlab-ce-6862e788e8b32196d750152c4657ea95d431e04f.tar.gz
Merge branch 'master' into add-svg-loader
* master: Fix migration without DOWNTIME clause specified Fix CSS classes fix missing @ symbol Update CHANGELOG.md for 8.16.7 Update CHANGELOG.md for 8.16.7 Update CHANGELOG.md for 8.16.7 Make RuboCop happy Add development fixtures for nested groups Align last column buttons with new environment button Removed jQuery UI draggable ensure webpack dev server proxy connects regardless of request headers Keep consistent in handling indexOf results Replace setInterval with setTimeout to prevent highly frequent requests Add feature specs for three types of user uploads Minor refactoring of Uploaders Fix #27840 - Improve the search bar experience on mobile Fix false positive caused by non-interpolated string use Fix inline comment images by removing wrapper #20890
-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