diff options
author | Mike Greiling <mike@pixelcog.com> | 2017-01-28 11:25:15 -0600 |
---|---|---|
committer | Mike Greiling <mike@pixelcog.com> | 2017-01-28 11:25:15 -0600 |
commit | c454dec170da32fc83b8d78c61c3f452c819f0ed (patch) | |
tree | df05c491f06e845a0e8266afcb23962e95160a4a | |
parent | a5f7934420e1d26682700e02aa8fc9333e808f47 (diff) | |
parent | b78d06b78143b16dccc5d5afaa8796473b68bea1 (diff) | |
download | gitlab-ce-c454dec170da32fc83b8d78c61c3f452c819f0ed.tar.gz |
Merge branch 'master' into go-go-gadget-webpack
* master: (33 commits)
Improved code style on the issue_sidebar_spec.rb
add CHAGELOG.md entry for !8831
remove assets:compile step from relative_url docs
update scripts and docs to reference the newly namespaced rake task
namespace assets rake tasks to gitlab:assets:*
correct gzip files if they exist as well
automatically correct CSS urls on assets:precompile
remove hard-coded assets path for ace editor modules
Fixed cancel button in the services form not redirecting back to the integrations settings view
Fix search bar search param encoding
Fix a transient failure in the `Explore::ProjectsController` spec
Fix filtering with multiple words
Fix project name label's for reference in project settings
Fixed merge request tabs extra margin
Don't call `#uniq` on a relation
Move Gitlab::Shell and Gitlab::ShellAdapter files to lib/
Move ApplicationSetting DEFAULTS to `.defaults` instead
Move a begin/rescue clause to ApplicationSetting.expire
Use badge partial as single source of truth instead of having 2 partials doing the same
Changes after review
...
59 files changed, 651 insertions, 135 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6976f24c391..588f5b59e4c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -109,7 +109,7 @@ setup-test-env: script: - npm install - bundle exec rake webpack:compile - - bundle exec rake assets:precompile 2>/dev/null + - bundle exec rake gitlab:assets:compile 2>/dev/null - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' artifacts: expire_in: 7d @@ -280,6 +280,7 @@ group :development, :test do gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' + gem 'rspec_profiling' # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) gem 'minitest', '~> 5.7.0' diff --git a/Gemfile.lock b/Gemfile.lock index b1ef956c32c..b4d03eaddc2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -638,6 +638,11 @@ GEM rspec-retry (0.4.5) rspec-core rspec-support (3.5.0) + rspec_profiling (0.0.4) + activerecord + pg + rails + sqlite3 rubocop (0.46.0) parser (>= 2.3.1.1, < 3.0) powerpack (~> 0.1) @@ -733,6 +738,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + sqlite3 (1.3.11) stackprof (0.2.10) state_machines (0.4.0) state_machines-activemodel (0.4.0) @@ -953,6 +959,7 @@ DEPENDENCIES rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.5.0) rspec-retry (~> 0.4.5) + rspec_profiling rubocop (~> 0.46.0) rubocop-rspec (~> 1.9.1) ruby-fogbugz (~> 0.2.1) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 98042106111..ae81eecfe6a 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -81,7 +81,6 @@ window.ES6Promise.polyfill(); var $sidebarGutterToggle = $('.js-sidebar-toggle'); var $flash = $('.flash-container'); var bootstrapBreakpoint = bp.getBreakpointSize(); - var checkInitialSidebarSize; var fitSidebarForSize; // Set the default path for all cookies to GitLab's root directory @@ -243,19 +242,11 @@ window.ES6Promise.polyfill(); return $document.trigger('breakpoint:change', [bootstrapBreakpoint]); } }; - checkInitialSidebarSize = function () { - bootstrapBreakpoint = bp.getBreakpointSize(); - if (bootstrapBreakpoint === 'xs' || 'sm') { - return $document.trigger('breakpoint:change', [bootstrapBreakpoint]); - } - }; $window.off('resize.app').on('resize.app', function () { return fitSidebarForSize(); }); gl.awardsHandler = new AwardsHandler(); - checkInitialSidebarSize(); new Aside(); - // bind sidebar events new gl.Sidebar(); }); diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index cabeae74ae3..c6fdfbcaa10 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -3,7 +3,7 @@ (function() { this.CommitsList = (function() { - function CommitsList() {} + var CommitsList = {}; CommitsList.timer = null; @@ -20,6 +20,7 @@ }); this.content = $("#commits-list"); this.searchField = $("#commits-search"); + this.lastSearch = this.searchField.val(); return this.initSearch(); }; @@ -37,6 +38,7 @@ var commitsUrl, form, search; form = $(".commits-search-form"); search = CommitsList.searchField.val(); + if (search === CommitsList.lastSearch) return; commitsUrl = form.attr("action") + '?' + form.serialize(); CommitsList.content.fadeTo('fast', 0.5); return $.ajax({ @@ -47,12 +49,16 @@ return CommitsList.content.fadeTo('fast', 1.0); }, success: function(data) { + CommitsList.lastSearch = search; CommitsList.content.html(data.html); return history.replaceState({ page: commitsUrl // Change url so if user reload a page - search results are saved }, document.title, commitsUrl); }, + error: function() { + CommitsList.lastSearch = null; + }, dataType: "json" }); }; diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index eeab10fba17..de3fa116717 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -28,7 +28,12 @@ if (lastToken !== searchToken) { const title = updatedItem.title.toLowerCase(); let value = lastToken.value.toLowerCase(); - value = value.replace(/"(.*?)"/g, str => str.slice(1).slice(0, -1)); + + // Removes the first character if it is a quotation so that we can search + // with multiple words + if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) { + value = value.slice(1); + } // Eg. filterSymbol = ~ for labels const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1; @@ -83,8 +88,9 @@ const selectionStart = input.selectionStart; let inputValue = input.value; // Replace all spaces inside quote marks with underscores + // (will continue to match entire string until an end quote is found if any) // This helps with matching the beginning & end of a token:key - inputValue = inputValue.replace(/("(.*?)"|:\s+)/g, str => str.replace(/\s/g, '_')); + inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_')); // Get the right position for the word selected // Regex matches first space diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 8d62324b79f..029564ffc61 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -196,7 +196,8 @@ }); if (searchToken) { - paths.push(`search=${encodeURIComponent(searchToken)}`); + const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+'); + paths.push(`search=${sanitized}`); } Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`); diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js index 9c53cdee58e..c77fbb6a1c7 100644 --- a/app/assets/javascripts/issuable_context.js +++ b/app/assets/javascripts/issuable_context.js @@ -1,5 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */ /* global UsersSelect */ +/* global Cookies */ +/* global bp */ (function() { this.IssuableContext = (function() { @@ -37,6 +39,13 @@ }, 0); } }); + window.addEventListener('beforeunload', function() { + // collapsed_gutter cookie hides the sidebar + var bpBreakpoint = bp.getBreakpointSize(); + if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') { + Cookies.set('collapsed_gutter', true); + } + }); $(".right-sidebar").niceScroll(); } diff --git a/app/assets/javascripts/lib/ace.js b/app/assets/javascripts/lib/ace.js index 4cdf99cae72..9cdc0309503 100644 --- a/app/assets/javascripts/lib/ace.js +++ b/app/assets/javascripts/lib/ace.js @@ -1,2 +1,3 @@ -/*= require ace-rails-ap */ +/*= require ace/ace */ /*= require ace/ext-searchbox */ +/*= require ./ace/ace_config_paths */ diff --git a/app/assets/javascripts/lib/ace/ace_config_paths.js.erb b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb new file mode 100644 index 00000000000..25e623f0fdc --- /dev/null +++ b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb @@ -0,0 +1,25 @@ +<% +ace_gem_path = Bundler.rubygems.find_name('ace-rails-ap').first.full_gem_path +ace_workers = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/worker-*.js'].sort.map do |file| + File.basename(file, '.js').sub(/^worker-/, '') +end +ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.map do |file| + File.basename(file, '.js').sub(/^mode-/, '') +end +%> + +(function() { + window.gon = window.gon || {}; + var basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace'; + ace.config.set('basePath', basePath); + + // configure paths for all worker modules +<% ace_workers.each do |worker| %> + ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/worker-<%= worker %>.js'); +<% end %> + + // configure paths for all mode modules +<% ace_modes.each do |mode| %> + ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/mode-<%= mode %>.js'); +<% end %> +})(); diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 5e07359b51b..e0e40ad3adb 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -158,7 +158,7 @@ }; Calendar.prototype.renderMonths = function() { - return this.svg.append('g').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { + return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { return date.x; }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) { return function(date) { diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index ef921a8c6a9..1d2d1bfc0d7 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -1,6 +1,7 @@ .calender-block { padding-left: 0; padding-right: 0; + direction: rtl; @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { overflow-x: scroll; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 24a1ce2b84d..2a01bc4d44d 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -71,7 +71,7 @@ header { &:focus, &:active { background-color: $gray-light; - color: darken($gl-text-color-secondary, 30%); + color: $gl-text-color; .todos-pending-count { background: darken($todo-alert-blue, 10%); diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 07cb669a46e..7809d4866f1 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -178,7 +178,7 @@ $count-arrow-border: #dce0e5; $save-project-loader-color: #555; $divergence-graph-bar-bg: #ccc; $divergence-graph-separator-bg: #ccc; -$general-hover-transition-duration: 150ms; +$general-hover-transition-duration: 100ms; $general-hover-transition-curve: linear; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 93cc5a8cf0a..4ef95d27f4f 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -26,10 +26,6 @@ border: 0; } } - - .container-fluid { - @extend .fixed-width-container; - } } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 45ff9f7ff5f..ab68b360f93 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -420,10 +420,6 @@ .merge-request-tabs-holder { background-color: $white-light; - .container-limited { - max-width: $limited-layout-width; - } - &.affix { top: 100px; left: 0; @@ -433,10 +429,26 @@ @media (max-width: $screen-xs-max) { right: 0; } + + .merge-request-tabs-container { + padding-left: $gl-padding; + padding-right: $gl-padding; + } } +} - &:not(.affix) .container-fluid { - padding-left: 0; - padding-right: 0; +.limit-container-width { + .merge-request-tabs-container { + max-width: $limited-layout-width; + margin-left: auto; + margin-right: auto; + } +} + +.limit-container-width:not(.container-limited) { + .merge-request-tabs-holder:not(.affix) { + .merge-request-tabs-container { + max-width: $limited-layout-width - ($gl-padding * 2); + } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 1b0bf4554e6..8b59c20cb65 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -198,7 +198,7 @@ margin: 15px 5px 0 0; input { - height: 27px; + height: 28px; } } @@ -523,7 +523,7 @@ a.deploy-project-label { &:hover, &:focus { - color: darken($notes-light-color, 15%); + color: $gl-text-color; } } diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 6db4e1dc1bc..d7a45bacd35 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -18,15 +18,14 @@ class AutocompleteController < ApplicationController if params[:search].blank? # Include current user if available to filter by "Me" if params[:current_user].present? && current_user + @users = @users.where.not(id: current_user.id) @users = [current_user, *@users] end if params[:author_id].present? author = User.find_by_id(params[:author_id]) - @users = [author, *@users] if author + @users = [author, *@users].uniq if author end - - @users.uniq! end render json: @users, only: [:name, :username, :id], methods: [:avatar_url] diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index e33a58d3771..2df8b071e13 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -13,49 +13,6 @@ class ApplicationSetting < ActiveRecord::Base [\r\n] # any number of newline characters }x - DEFAULTS_CE = { - after_sign_up_text: nil, - akismet_enabled: false, - container_registry_token_expire_delay: 5, - default_branch_protection: Settings.gitlab['default_branch_protection'], - default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], - default_projects_limit: Settings.gitlab['default_projects_limit'], - default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], - disabled_oauth_sign_in_sources: [], - domain_whitelist: Settings.gitlab['domain_whitelist'], - gravatar_enabled: Settings.gravatar['enabled'], - help_page_text: nil, - housekeeping_bitmaps_enabled: true, - housekeeping_enabled: true, - housekeeping_full_repack_period: 50, - housekeeping_gc_period: 200, - housekeeping_incremental_repack_period: 10, - import_sources: Gitlab::ImportSources.values, - koding_enabled: false, - koding_url: nil, - max_artifacts_size: Settings.artifacts['max_size'], - max_attachment_size: Settings.gitlab['max_attachment_size'], - plantuml_enabled: false, - plantuml_url: nil, - recaptcha_enabled: false, - repository_checks_enabled: true, - repository_storages: ['default'], - require_two_factor_authentication: false, - restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], - session_expire_delay: Settings.gitlab['session_expire_delay'], - send_user_confirmation_email: false, - shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], - shared_runners_text: nil, - sidekiq_throttling_enabled: false, - sign_in_text: nil, - signin_enabled: Settings.gitlab['signin_enabled'], - signup_enabled: Settings.gitlab['signup_enabled'], - two_factor_grace_period: 48, - user_default_external: false - } - - DEFAULTS = DEFAULTS_CE - serialize :restricted_visibility_levels serialize :import_sources serialize :disabled_oauth_sign_in_sources, Array @@ -199,14 +156,64 @@ class ApplicationSetting < ActiveRecord::Base def self.expire Rails.cache.delete(CACHE_KEY) + rescue + # Gracefully handle when Redis is not available. For example, + # omnibus may fail here during gitlab:assets:compile. end def self.cached Rails.cache.fetch(CACHE_KEY) end + def self.defaults_ce + { + after_sign_up_text: nil, + akismet_enabled: false, + container_registry_token_expire_delay: 5, + default_branch_protection: Settings.gitlab['default_branch_protection'], + default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], + default_projects_limit: Settings.gitlab['default_projects_limit'], + default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], + disabled_oauth_sign_in_sources: [], + domain_whitelist: Settings.gitlab['domain_whitelist'], + gravatar_enabled: Settings.gravatar['enabled'], + help_page_text: nil, + housekeeping_bitmaps_enabled: true, + housekeeping_enabled: true, + housekeeping_full_repack_period: 50, + housekeeping_gc_period: 200, + housekeeping_incremental_repack_period: 10, + import_sources: Gitlab::ImportSources.values, + koding_enabled: false, + koding_url: nil, + max_artifacts_size: Settings.artifacts['max_size'], + max_attachment_size: Settings.gitlab['max_attachment_size'], + plantuml_enabled: false, + plantuml_url: nil, + recaptcha_enabled: false, + repository_checks_enabled: true, + repository_storages: ['default'], + require_two_factor_authentication: false, + restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], + session_expire_delay: Settings.gitlab['session_expire_delay'], + send_user_confirmation_email: false, + shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], + shared_runners_text: nil, + sidekiq_throttling_enabled: false, + sign_in_text: nil, + signin_enabled: Settings.gitlab['signin_enabled'], + signup_enabled: Settings.gitlab['signup_enabled'], + two_factor_grace_period: 48, + user_default_external: false + } + end + + def self.defaults + defaults_ce + end + def self.create_from_defaults - create(DEFAULTS) + create(defaults) end def home_page_url_column_exist diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index 601fb7f0f3f..c00c7f7407e 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -1,7 +1,8 @@ - status = local_assigns.fetch(:status) +- link = local_assigns.fetch(:link, true) - css_classes = "ci-status ci-#{status.group}" -- if status.has_details? +- if link && status.has_details? = link_to status.details_path, class: css_classes do = custom_icon(status.icon) = status.text diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index b15be0d861d..736b485bf06 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,8 +1,8 @@ .content-block.build-header .header-content - = render 'ci/status/badge', status: @build.detailed_status(current_user) + = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false Build - %strong ##{@build.id} + %strong.js-build-id ##{@build.id} in pipeline = link_to pipeline_path(@build.pipeline) do %strong ##{@build.pipeline.id} diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 114865935d6..84787155168 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -9,7 +9,7 @@ %fieldset.append-bottom-0 .row .form-group.col-md-9 - = f.label :name, class: 'label-light' do + = f.label :name, class: 'label-light', for: 'project_name_edit' do Project name = f.text_field :name, class: "form-control", id: "project_name_edit" diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 2d1671a89df..8ea1a3a45e1 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -19,10 +19,8 @@ = render 'shared/issuable/nav', type: :issues .nav-controls - if current_user - = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10' do + = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do = icon('rss') - %span.icon-label - Subscribe - if can? current_user, :create_issue, @project = link_to new_namespace_project_issue_path(@project.namespace, @project, diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 4a24ec32454..2884f5942f4 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -52,7 +52,7 @@ = render 'award_emoji/awards_block', awardable: @merge_request, inline: true .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } - %div{ class: container_class } + .merge-request-tabs-container %ul.merge-request-tabs.nav-links.no-top.no-bottom %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index fc338dcf887..f1a80f1d5e1 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -17,4 +17,4 @@ - disabled_title = @service.disabled_title = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service), class: "btn #{disabled_class}", title: disabled_title - = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel" + = link_to "Cancel", namespace_project_settings_integrations_path(@project.namespace, @project), class: "btn btn-cancel" diff --git a/changelogs/unreleased/26947-build-status-self-link.yml b/changelogs/unreleased/26947-build-status-self-link.yml new file mode 100644 index 00000000000..15c5821874e --- /dev/null +++ b/changelogs/unreleased/26947-build-status-self-link.yml @@ -0,0 +1,4 @@ +--- +title: Add link verification to badge partial in order to render a badge without a link +merge_request: 8740 +author: diff --git a/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml b/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml new file mode 100644 index 00000000000..6e036923158 --- /dev/null +++ b/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml @@ -0,0 +1,4 @@ +--- +title: Fix filtering with multiple words +merge_request: 8830 +author: diff --git a/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml b/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml new file mode 100644 index 00000000000..2591f161bc5 --- /dev/null +++ b/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml @@ -0,0 +1,4 @@ +--- +title: Fix project name label's for reference in project settings +merge_request: 8795 +author: diff --git a/changelogs/unreleased/contribution-calendar-scroll.yml b/changelogs/unreleased/contribution-calendar-scroll.yml new file mode 100644 index 00000000000..a504d59e61c --- /dev/null +++ b/changelogs/unreleased/contribution-calendar-scroll.yml @@ -0,0 +1,4 @@ +--- +title: contribution calendar scrolls from right to left +merge_request: +author: diff --git a/changelogs/unreleased/fix-cancel-integration-settings.yml b/changelogs/unreleased/fix-cancel-integration-settings.yml new file mode 100644 index 00000000000..294b0aa5db9 --- /dev/null +++ b/changelogs/unreleased/fix-cancel-integration-settings.yml @@ -0,0 +1,4 @@ +--- +title: Fixed services form cancel not redirecting back the integrations settings view +merge_request: 8843 +author: diff --git a/changelogs/unreleased/fix-search-bar-search-param.yml b/changelogs/unreleased/fix-search-bar-search-param.yml new file mode 100644 index 00000000000..4df14d3bf13 --- /dev/null +++ b/changelogs/unreleased/fix-search-bar-search-param.yml @@ -0,0 +1,4 @@ +--- +title: Fix search bar search param encoding +merge_request: 8753 +author: diff --git a/changelogs/unreleased/issuable-sidebar-bug.yml b/changelogs/unreleased/issuable-sidebar-bug.yml new file mode 100644 index 00000000000..4086292eb89 --- /dev/null +++ b/changelogs/unreleased/issuable-sidebar-bug.yml @@ -0,0 +1,4 @@ +--- +title: Fixed Issuable sidebar not closing on smaller/mobile sized screens +merge_request: +author: diff --git a/changelogs/unreleased/mr-tabs-container-offset.yml b/changelogs/unreleased/mr-tabs-container-offset.yml new file mode 100644 index 00000000000..c5df8abfcf2 --- /dev/null +++ b/changelogs/unreleased/mr-tabs-container-offset.yml @@ -0,0 +1,4 @@ +--- +title: Fixed merge requests tab extra margin when fixed to window +merge_request: +author: diff --git a/changelogs/unreleased/relative-url-assets.yml b/changelogs/unreleased/relative-url-assets.yml new file mode 100644 index 00000000000..0877664aca4 --- /dev/null +++ b/changelogs/unreleased/relative-url-assets.yml @@ -0,0 +1,4 @@ +--- +title: allow relative url change without recompiling frontend assets +merge_request: 8831 +author: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 906ec11f012..4f33aad8693 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -419,10 +419,4 @@ if Rails.env.test? end # Force a refresh of application settings at startup -begin - ApplicationSetting.expire - Ci::ApplicationSetting.expire -rescue - # Gracefully handle when Redis is not available. For example, - # omnibus may fail here during assets:precompile. -end +ApplicationSetting.expire diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb index ed88c8ee1b8..2bd159ca7f1 100644 --- a/config/initializers/5_backend.rb +++ b/config/initializers/5_backend.rb @@ -1,9 +1,3 @@ -# GIT over SSH -require_dependency Rails.root.join('lib/gitlab/backend/shell') - -# GitLab shell adapter -require_dependency Rails.root.join('lib/gitlab/backend/shell_adapter') - required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required) current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version) diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb new file mode 100644 index 00000000000..f462e654b2c --- /dev/null +++ b/config/initializers/rspec_profiling.rb @@ -0,0 +1,14 @@ +module RspecProfilingConnection + def establish_connection + ::RspecProfiling::Collectors::PSQL::Result.establish_connection(ENV['RSPEC_PROFILING_POSTGRES_URL']) + end +end + +if Rails.env.test? + RspecProfiling.configure do |config| + if ENV['RSPEC_PROFILING_POSTGRES_URL'] + RspecProfiling::Collectors::PSQL.prepend(RspecProfilingConnection) + config.collector = RspecProfiling::Collectors::PSQL + end + end +end diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index 33b9b28433a..5b6ee354887 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -172,14 +172,14 @@ Omnibus packages. ``` cd /home/git/gitlab -sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production ``` For omnibus versions, the unoptimized assets (JavaScript, CSS) are frozen at the release of upstream GitLab. The omnibus version includes optimized versions of those assets. Unless you are modifying the JavaScript / CSS code on your production machine after installing the package, there should be no reason to redo -rake assets:precompile on the production machine. If you suspect that assets +rake gitlab:assets:compile on the production machine. If you suspect that assets have been corrupted, you should reinstall the omnibus package. ## Tracking Deployments diff --git a/doc/api/commits.md b/doc/api/commits.md index 5c11d0f83bb..53ce381c8ae 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -245,7 +245,7 @@ Example response: ```json [ { - "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files", + "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files", "new_path": "doc/update/5.4-to-6.0.md", "old_path": "doc/update/5.4-to-6.0.md", "a_mode": null, diff --git a/doc/development/performance.md b/doc/development/performance.md index f936a49a2aa..c1f129e576c 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -211,6 +211,41 @@ suite first. See the [StackProf documentation](https://github.com/tmm1/stackprof/blob/master/README.md) for details. +## RSpec profiling + +GitLab's development environment also includes the +[rspec_profiling](https://github.com/foraker/rspec_profiling) gem, which is used +to collect data on spec execution times. This is useful for analyzing the +performance of the test suite itself, or seeing how the performance of a spec +may have changed over time. + +To activate profiling in your local environment, run the following: + +``` +$ export RSPEC_PROFILING=yes +$ rake rspec_profiling:install +``` + +This creates an SQLite3 database in `tmp/rspec_profiling`, into which statistics +are saved every time you run specs with the `RSPEC_PROFILING` environment +variable set. + +Ad-hoc investigation of the collected results can be performed in an interactive +shell: + +``` +$ rake rspec_profiling:console +irb(main):001:0> results.count +=> 231 +irb(main):002:0> results.last.attributes.keys +=> ["id", "commit", "date", "file", "line_number", "description", "time", "status", "exception", "query_count", "query_time", "request_count", "request_time", "created_at", "updated_at"] +irb(main):003:0> results.where(status: "passed").average(:time).to_s +=> "0.211340155844156" +``` +These results can also be placed into a PostgreSQL database by setting the +`RSPEC_PROFILING_POSTGRES_URL` variable. This is used to profile the test suite +when running in the CI environment. + ## Importance of Changes When working on performance improvements, it's important to always ask yourself diff --git a/doc/install/installation.md b/doc/install/installation.md index 3e7674e13ab..425c5d93efb 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -448,7 +448,7 @@ Check if GitLab and its environment are configured correctly: ### Compile Assets - sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production ### Start Your GitLab Instance diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md index 44d2a14f366..713d11b75e4 100644 --- a/doc/install/relative_url.md +++ b/doc/install/relative_url.md @@ -113,14 +113,6 @@ Make sure to follow all steps below: If you are using a custom init script, make sure to edit the above gitlab-workhorse setting as needed. -1. After all the above changes recompile the assets. This is an important task - and will take some time to complete depending on the server resources: - - ``` - cd /home/git/gitlab - sudo -u git -H bundle exec rake assets:clean assets:precompile RAILS_ENV=production - ``` - 1. [Restart GitLab][] for the changes to take effect. ### Disable relative URL in GitLab diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index 54d523b59fd..154a0f817da 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -57,7 +57,7 @@ sudo -u git -H bundle clean sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production # Clean up assets and cache -sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production ``` ### 4. Update gitlab-workhorse to the corresponding version diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 4ebd48a3fc7..e20f5f6f514 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -30,7 +30,7 @@ module Gitlab end def in_memory_application_settings - @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting::DEFAULTS) + @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting.defaults) # In case migrations the application_settings table is not created yet, # we fallback to a simple OpenStruct rescue ActiveRecord::StatementInvalid, ActiveRecord::UnknownAttributeError @@ -38,7 +38,7 @@ module Gitlab end def fake_application_settings - OpenStruct.new(::ApplicationSetting::DEFAULTS) + OpenStruct.new(::ApplicationSetting.defaults) end private diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/shell.rb index 82e194c1af1..82e194c1af1 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/shell.rb diff --git a/lib/gitlab/backend/shell_adapter.rb b/lib/gitlab/shell_adapter.rb index fbe2a7a0d72..fbe2a7a0d72 100644 --- a/lib/gitlab/backend/shell_adapter.rb +++ b/lib/gitlab/shell_adapter.rb diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index f3567f3ef85..e78d0c34a02 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -61,7 +61,7 @@ module Gitlab "Switch to new version" => %W(#{Gitlab.config.git.bin_path} checkout v#{latest_version}), "Install gems" => %W(bundle), "Migrate DB" => %W(bundle exec rake db:migrate), - "Recompile assets" => %W(bundle exec rake assets:clean assets:precompile), + "Recompile assets" => %W(bundle exec rake gitlab:assets:clean gitlab:assets:compile), "Clear cache" => %W(bundle exec rake cache:clear) } end diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh index adea4c7a747..ab46c47d8f5 100755 --- a/lib/support/deploy/deploy.sh +++ b/lib/support/deploy/deploy.sh @@ -31,8 +31,8 @@ echo 'Deploy: Bundle and migrate' sudo -u git -H bundle --without aws development test mysql --deployment sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production -sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production -sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:assets:clean RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production # return stashed changes (if necessary) diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake new file mode 100644 index 00000000000..5d884bf9f66 --- /dev/null +++ b/lib/tasks/gitlab/assets.rake @@ -0,0 +1,47 @@ +namespace :gitlab do + namespace :assets do + desc 'GitLab | Assets | Compile all frontend assets' + task :compile do + Rake::Task['assets:precompile'].invoke + Rake::Task['gitlab:assets:fix_urls'].invoke + end + + desc 'GitLab | Assets | Clean up old compiled frontend assets' + task :clean do + Rake::Task['assets:clean'].invoke + end + + desc 'GitLab | Assets | Remove all compiled frontend assets' + task :purge do + Rake::Task['assets:clobber'].invoke + end + + desc 'GitLab | Assets | Fix all absolute url references in CSS' + task :fix_urls do + css_files = Dir['public/assets/*.css'] + css_files.each do | file | + # replace url(/assets/*) with url(./*) + puts "Fixing #{file}" + system "sed", "-i", "-e", 's/url(\([\"\']\?\)\/assets\//url(\1.\//g', file + + # rewrite the corresponding gzip file (if it exists) + gzip = "#{file}.gz" + if File.exist?(gzip) + puts "Fixing #{gzip}" + + FileUtils.rm(gzip) + mtime = File.stat(file).mtime + + File.open(gzip, 'wb+') do |f| + gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION) + gz.mtime = mtime + gz.write IO.binread(file) + gz.close + + File.utime(mtime, mtime, f.path) + end + end + end + end + end +end diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb index 6128091f543..9dceeca168d 100644 --- a/spec/controllers/explore/projects_controller_spec.rb +++ b/spec/controllers/explore/projects_controller_spec.rb @@ -1,29 +1,26 @@ require 'spec_helper' describe Explore::ProjectsController do - let(:user) { create(:user) } - let(:visibility) { :public } - describe 'GET #trending' do - let!(:project_1) { create(:project, visibility, ci_id: 1) } - let!(:project_2) { create(:project, visibility, ci_id: 2) } - - let!(:trending_project_1) { create(:trending_project, project: project_1) } - let!(:trending_project_2) { create(:trending_project, project: project_2) } + context 'sorting by update date' do + let(:project1) { create(:empty_project, :public, updated_at: 3.days.ago) } + let(:project2) { create(:empty_project, :public, updated_at: 1.day.ago) } - before do - sign_in(user) - end + before do + create(:trending_project, project: project1) + create(:trending_project, project: project2) + end - context 'sorting by update date' do it 'sorts by last updated' do get :trending, sort: 'updated_desc' - expect(assigns(:projects)).to eq [project_2, project_1] + + expect(assigns(:projects)).to eq [project2, project1] end it 'sorts by oldest updated' do get :trending, sort: 'updated_asc' - expect(assigns(:projects)).to eq [project_1, project_2] + + expect(assigns(:projects)).to eq [project1, project2] end end end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index f48a0193545..3f70a6aa75f 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -773,7 +773,7 @@ describe 'Filter issues', js: true, feature: true do describe 'RSS feeds' do it 'updates atom feed link for project issues' do visit namespace_project_issues_path(project.namespace, project, milestone_title: milestone.title, assignee_id: user.id) - link = find('.nav-controls a', text: 'Subscribe') + link = find_link('Subscribe') params = CGI.parse(URI.parse(link[:href]).query) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index bc068b5e7e0..1eb981942ea 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' feature 'Issue Sidebar', feature: true do include WaitForAjax + include MobileHelpers let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } @@ -59,6 +60,23 @@ feature 'Issue Sidebar', feature: true do end end + context 'sidebar', js: true do + it 'changes size when the screen size is smaller' do + sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed' + # Resize the window + resize_screen_sm + # Make sure the sidebar is collapsed + expect(page).to have_css(sidebar_selector) + # Once is collapsed let's open the sidebard and reload + open_issue_sidebar + refresh + expect(page).to have_css(sidebar_selector) + # Restore the window size as it was including the sidebar + restore_window_size + open_issue_sidebar + end + end + context 'creating a new label', js: true do it 'shows option to crate a new label is present' do page.within('.block.labels') do @@ -109,4 +127,11 @@ feature 'Issue Sidebar', feature: true do def visit_issue(project, issue) visit namespace_project_issue_path(project.namespace, project, issue) end + + def open_issue_sidebar + page.within('aside.right-sidebar.right-sidebar-collapsed') do + find('.js-sidebar-toggle').click + sleep 1 + end + end end diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb index 55d5d082c6e..5d0314d5c09 100644 --- a/spec/features/projects/project_settings_spec.rb +++ b/spec/features/projects/project_settings_spec.rb @@ -37,7 +37,7 @@ describe 'Edit Project Settings', feature: true do it 'shows errors for invalid project path/name' do visit edit_namespace_project_path(project.namespace, project) - fill_in 'Project name', with: 'foo&bar' + fill_in 'project_name', with: 'foo&bar' fill_in 'Path', with: 'foo&bar' click_button 'Rename project' @@ -53,7 +53,7 @@ describe 'Edit Project Settings', feature: true do it 'shows error for invalid project name' do visit edit_namespace_project_path(project.namespace, project) - fill_in 'Project name', with: '🚀 foo bar ☁️' + fill_in 'project_name', with: '🚀 foo bar ☁️' click_button 'Rename project' diff --git a/spec/javascripts/commits_spec.js.es6 b/spec/javascripts/commits_spec.js.es6 new file mode 100644 index 00000000000..bb9a9072f3a --- /dev/null +++ b/spec/javascripts/commits_spec.js.es6 @@ -0,0 +1,50 @@ +/* global CommitsList */ + +//= require jquery.endless-scroll +//= require pager +//= require commits + +(() => { + describe('Commits List', () => { + beforeEach(() => { + setFixtures(` + <form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/master"> + <input id="commits-search"> + </form> + <ol id="commits-list"></ol> + `); + }); + + it('should be defined', () => { + expect(CommitsList).toBeDefined(); + }); + + describe('on entering input', () => { + let ajaxSpy; + + beforeEach(() => { + CommitsList.init(25); + CommitsList.searchField.val(''); + spyOn(history, 'replaceState').and.stub(); + ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => { + req.success({ + data: '<li>Result</li>', + }); + }); + }); + + it('should save the last search string', () => { + CommitsList.searchField.val('GitLab'); + CommitsList.filterResults(); + expect(ajaxSpy).toHaveBeenCalled(); + expect(CommitsList.lastSearch).toEqual('GitLab'); + }); + + it('should not make ajax call if the input does not change', () => { + CommitsList.filterResults(); + expect(ajaxSpy).not.toHaveBeenCalled(); + expect(CommitsList.lastSearch).toEqual(''); + }); + }); + }); +})(); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 index 074a46349ab..1e2d7582d5b 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 @@ -64,6 +64,68 @@ require('~/filtered_search/filtered_search_dropdown_manager'); const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); expect(updatedItem.droplab_hidden).toBe(false); }); + + describe('filters multiple word title', () => { + const multipleWordItem = { + title: 'Community Contributions', + }; + + it('should filter with double quote', () => { + input.value = 'label:"'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with double quote and symbol', () => { + input.value = 'label:~"'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with double quote and multiple words', () => { + input.value = 'label:"community con'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with double quote, symbol and multiple words', () => { + input.value = 'label:~"community con'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with single quote', () => { + input.value = 'label:\''; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with single quote and symbol', () => { + input.value = 'label:~\''; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with single quote and multiple words', () => { + input.value = 'label:\'community con'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with single quote, symbol and multiple words', () => { + input.value = 'label:~\'community con'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + }); }); describe('filterHint', () => { @@ -130,5 +192,99 @@ require('~/filtered_search/filtered_search_dropdown_manager'); expect(result).toBe(false); }); }); + + describe('getInputSelectionPosition', () => { + describe('word with trailing spaces', () => { + const value = 'label:none '; + + it('should return selectionStart when cursor is at the trailing space', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 11, + value, + }); + + expect(left).toBe(11); + expect(right).toBe(11); + }); + + it('should return input when cursor is at the start of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 0, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(10); + }); + + it('should return input when cursor is at the middle of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 7, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(10); + }); + + it('should return input when cursor is at the end of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 10, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(10); + }); + }); + + describe('multiple words', () => { + const value = 'label:~"Community Contribution"'; + + it('should return input when cursor is after the first word', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 17, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(31); + }); + + it('should return input when cursor is before the second word', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 18, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(31); + }); + }); + + describe('incomplete multiple words', () => { + const value = 'label:~"Community Contribution'; + + it('should return entire input when cursor is at the start of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 0, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(30); + }); + + it('should return entire input when cursor is at the end of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 30, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(30); + }); + }); + }); }); })(); diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 new file mode 100644 index 00000000000..c8b5c2b36ad --- /dev/null +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 @@ -0,0 +1,68 @@ +/* global Turbolinks */ + +//= require turbolinks +//= require lib/utils/common_utils +//= require filtered_search/filtered_search_token_keys +//= require filtered_search/filtered_search_tokenizer +//= require filtered_search/filtered_search_dropdown_manager +//= require filtered_search/filtered_search_manager + +(() => { + describe('Filtered Search Manager', () => { + describe('search', () => { + let manager; + const defaultParams = '?scope=all&utf8=✓&state=opened'; + + function getInput() { + return document.querySelector('.filtered-search'); + } + + beforeEach(() => { + setFixtures(` + <input type='text' class='filtered-search' /> + `); + + spyOn(gl.FilteredSearchManager.prototype, 'bindEvents').and.callFake(() => {}); + spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {}); + spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {}); + spyOn(gl.utils, 'getParameterByName').and.returnValue(null); + + manager = new gl.FilteredSearchManager(); + }); + + afterEach(() => { + getInput().outerHTML = ''; + }); + + it('should search with a single word', () => { + getInput().value = 'searchTerm'; + + spyOn(Turbolinks, 'visit').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=searchTerm`); + }); + + manager.search(); + }); + + it('should search with multiple words', () => { + getInput().value = 'awesome search terms'; + + spyOn(Turbolinks, 'visit').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`); + }); + + manager.search(); + }); + + it('should search with special characters', () => { + getInput().value = '~!@#$%^&*()_+{}:<>,.?/'; + + spyOn(Turbolinks, 'visit').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`); + }); + + manager.search(); + }); + }); + }); +})(); diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e160c11111b..ab38dac65c5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,6 +9,10 @@ require 'rspec/rails' require 'shoulda/matchers' require 'rspec/retry' +if ENV['RSPEC_PROFILING_POSTGRES_URL'] || ENV['RSPEC_PROFILING'] + require 'rspec_profiling/rspec' +end + if ENV['CI'] && !ENV['NO_KNAPSACK'] require 'knapsack' Knapsack::Adapters::RSpecAdapter.bind diff --git a/spec/support/mobile_helpers.rb b/spec/support/mobile_helpers.rb new file mode 100644 index 00000000000..20d5849bcab --- /dev/null +++ b/spec/support/mobile_helpers.rb @@ -0,0 +1,13 @@ +module MobileHelpers + def resize_screen_sm + resize_window(900, 768) + end + + def restore_window_size + resize_window(1366, 768) + end + + def resize_window(width, height) + page.driver.resize_window width, height + 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 745d0c745bd..44870cfcfb3 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -15,6 +15,36 @@ describe 'projects/builds/show', :view do allow(view).to receive(:can?).and_return(true) end + describe 'build information in header' do + let(:build) do + create(:ci_build, :success, environment: 'staging') + end + + before do + render + end + + it 'shows status name' do + expect(rendered).to have_css('.ci-status.ci-success', text: 'passed') + end + + it 'does not render a link to the build' do + expect(rendered).not_to have_link('passed') + end + + it 'shows build id' do + expect(rendered).to have_css('.js-build-id', text: build.id) + end + + it 'shows a link to the pipeline' do + expect(rendered).to have_link(build.pipeline.id) + end + + it 'shows a link to the commit' do + expect(rendered).to have_link(build.pipeline.short_sha) + end + end + describe 'environment info in build view' do context 'build with latest deployment' do let(:build) do |