diff options
77 files changed, 609 insertions, 157 deletions
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 213d5c6521a..1af21715551 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -31,6 +31,7 @@ import initPerformanceBar from './performance_bar'; import initSearchAutocomplete from './search_autocomplete'; import GlFieldErrors from './gl_field_errors'; import initUserPopovers from './user_popovers'; +import { __ } from './locale'; // expose jQuery as global (TODO: remove these) window.jQuery = jQuery; @@ -219,9 +220,9 @@ document.addEventListener('DOMContentLoaded', () => { const ref = xhrObj.status; if (ref === 401) { - Flash('You need to be logged in.'); + Flash(__('You need to be logged in.')); } else if (ref === 404 || ref === 500) { - Flash('Something went wrong on our end.'); + Flash(__('Something went wrong on our end.')); } }); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 509f19e6f00..e5cf43e8289 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -21,6 +21,7 @@ import { localTimeAgo } from './lib/utils/datetime_utility'; import syntaxHighlight from './syntax_highlight'; import Notes from './notes'; import { polyfillSticky } from './lib/utils/sticky'; +import { __ } from './locale'; // MergeRequestTabs // @@ -326,7 +327,7 @@ export default class MergeRequestTabs { }) .catch(() => { this.toggleLoading(false); - flash('An error occurred while fetching this tab.'); + flash(__('An error occurred while fetching this tab.')); }); } @@ -416,7 +417,7 @@ export default class MergeRequestTabs { }) .catch(() => { this.toggleLoading(false); - flash('An error occurred while fetching this tab.'); + flash(__('An error occurred while fetching this tab.')); }); } diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js index f211632cf24..6aaba4e7c74 100644 --- a/app/assets/javascripts/milestone.js +++ b/app/assets/javascripts/milestone.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import axios from './lib/utils/axios_utils'; import flash from './flash'; import { mouseenter, debouncedMouseleave, togglePopover } from './shared/popover'; +import { __ } from './locale'; export default class Milestone { constructor() { @@ -42,7 +43,7 @@ export default class Milestone { $(tabElId).html(data.html); $target.addClass('is-loaded'); }) - .catch(() => flash('Error loading milestone tab')); + .catch(() => flash(__('Error loading milestone tab'))); } } diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 75c18a9b6a0..43949d5cc86 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -56,14 +56,15 @@ export default class MilestoneSelect { const $value = $block.find('.value'); const $loading = $block.find('.block-loading').fadeOut(); selectedMilestoneDefault = showAny ? '' : null; - selectedMilestoneDefault = showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault; + selectedMilestoneDefault = + showNo && defaultNo ? __('No Milestone') : selectedMilestoneDefault; selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; if (issueUpdateURL) { milestoneLinkTemplate = _.template( '<a href="<%- web_url %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>', ); - milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; + milestoneLinkNoneTemplate = `<span class="no-value">${__('None')}</span>`; } return $dropdown.glDropdown({ showMenuAbove: showMenuAbove, @@ -74,28 +75,28 @@ export default class MilestoneSelect { extraOptions.push({ id: null, name: null, - title: 'Any Milestone', + title: __('Any Milestone'), }); } if (showNo) { extraOptions.push({ id: -1, - name: 'No Milestone', - title: 'No Milestone', + name: __('No Milestone'), + title: __('No Milestone'), }); } if (showUpcoming) { extraOptions.push({ id: -2, name: '#upcoming', - title: 'Upcoming', + title: __('Upcoming'), }); } if (showStarted) { extraOptions.push({ id: -3, name: '#started', - title: 'Started', + title: __('Started'), }); } if (extraOptions.length) { diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js index 81ab9d8be4b..b39ad764f01 100644 --- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js +++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import flash from './flash'; import axios from './lib/utils/axios_utils'; +import { __ } from './locale'; /** * In each pipelines table we have a mini pipeline graph for each pipeline. @@ -98,7 +99,7 @@ export default class MiniPipelineGraph { ) { $(button).dropdown('toggle'); } - flash('An error occurred while fetching the builds.', 'alert'); + flash(__('An error occurred while fetching the builds.'), 'alert'); }); } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 2b499e50ea3..2e38b260f5f 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -439,10 +439,6 @@ img.emoji { .min-height-0 { min-height: 0; } -.w-3 { width: #{3 * $grid-size}; } - -.h-3 { width: #{3 * $grid-size}; } - .svg-w-100 { svg { width: 100%; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index a57cd6737f8..1bc597bd4ae 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -472,6 +472,22 @@ background-color: $blue-500; } } + + .canary-badge { + .badge { + font-size: $gl-font-size-small; + line-height: $gl-line-height; + padding: 0 $grid-size; + } + + &:hover { + text-decoration: none; + + .badge { + text-decoration: none; + } + } + } } @include media-breakpoint-down(xs) { diff --git a/app/controllers/concerns/project_unauthorized.rb b/app/controllers/concerns/project_unauthorized.rb index d42363b8b17..7238840440f 100644 --- a/app/controllers/concerns/project_unauthorized.rb +++ b/app/controllers/concerns/project_unauthorized.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true module ProjectUnauthorized - def project_unauthorized_proc - lambda do |project| - if project - label = project.external_authorization_classification_label + module ControllerActions + def self.on_routable_not_found + lambda do |routable| + return unless routable.is_a?(Project) + + label = routable.external_authorization_classification_label rejection_reason = nil unless ::Gitlab::ExternalAuthorization.access_allowed?(current_user, label) @@ -12,9 +14,7 @@ module ProjectUnauthorized rejection_reason ||= _('External authorization denied access to this project') end - if rejection_reason - access_denied!(rejection_reason) - end + access_denied!(rejection_reason) if rejection_reason end end end diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb index 5624eb3aa45..ff9b0332c97 100644 --- a/app/controllers/concerns/routable_actions.rb +++ b/app/controllers/concerns/routable_actions.rb @@ -3,15 +3,13 @@ module RoutableActions extend ActiveSupport::Concern - def find_routable!(routable_klass, requested_full_path, extra_authorization_proc: nil, not_found_or_authorized_proc: nil) + def find_routable!(routable_klass, requested_full_path, extra_authorization_proc: nil) routable = routable_klass.find_by_full_path(requested_full_path, follow_redirects: request.get?) if routable_authorized?(routable, extra_authorization_proc) ensure_canonical_path(routable, requested_full_path) routable else - if not_found_or_authorized_proc - not_found_or_authorized_proc.call(routable) - end + perform_not_found_actions(routable, not_found_actions) route_not_found unless performed? @@ -19,6 +17,18 @@ module RoutableActions end end + def not_found_actions + [ProjectUnauthorized::ControllerActions.on_routable_not_found] + end + + def perform_not_found_actions(routable, actions) + actions.each do |action| + break if performed? + + instance_exec(routable, &action) + end + end + def routable_authorized?(routable, extra_authorization_proc) return false unless routable diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 781eac7f080..80e4f54bbf4 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -3,7 +3,6 @@ class Projects::ApplicationController < ApplicationController include CookiesHelper include RoutableActions - include ProjectUnauthorized include ChecksCollaboration skip_before_action :authenticate_user! @@ -22,7 +21,7 @@ class Projects::ApplicationController < ApplicationController path = File.join(params[:namespace_id], params[:project_id] || params[:id]) auth_proc = ->(project) { !project.pending_delete? } - @project = find_routable!(Project, path, extra_authorization_proc: auth_proc, not_found_or_authorized_proc: project_unauthorized_proc) + @project = find_routable!(Project, path, extra_authorization_proc: auth_proc) end def build_canonical_path(project) diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb index c7b6218d007..2a04b007304 100644 --- a/app/controllers/projects/clusters/applications_controller.rb +++ b/app/controllers/projects/clusters/applications_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Projects::Clusters::ApplicationsController < Clusters::ApplicationsController - include ProjectUnauthorized - prepend_before_action :project private @@ -12,6 +10,6 @@ class Projects::Clusters::ApplicationsController < Clusters::ApplicationsControl end def project - @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), not_found_or_authorized_proc: project_unauthorized_proc) + @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id])) end end diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index feda6deeaa6..cb02581da37 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class Projects::ClustersController < Clusters::ClustersController - include ProjectUnauthorized - prepend_before_action :project before_action :repository @@ -15,7 +13,7 @@ class Projects::ClustersController < Clusters::ClustersController end def project - @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), not_found_or_authorized_proc: project_unauthorized_proc) + @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id])) end def repository diff --git a/app/controllers/projects/serverless/functions_controller.rb b/app/controllers/projects/serverless/functions_controller.rb index 8c3d141c888..79030da64d3 100644 --- a/app/controllers/projects/serverless/functions_controller.rb +++ b/app/controllers/projects/serverless/functions_controller.rb @@ -3,8 +3,6 @@ module Projects module Serverless class FunctionsController < Projects::ApplicationController - include ProjectUnauthorized - before_action :authorize_read_cluster! def index diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 060b09f015c..5d28635232b 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -45,7 +45,7 @@ class UploadsController < ApplicationController when Appearance true else - permission = "read_#{model.class.to_s.underscore}".to_sym + permission = "read_#{model.class.underscore}".to_sym can?(current_user, permission, model) end diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index d90ef8903a7..42732eb93dd 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -21,6 +21,10 @@ module DashboardHelper links.any? { |link| dashboard_nav_link?(link) } end + def has_start_trial? + false + end + private def get_dashboard_nav_links diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 05da5ebdb22..a57ba5f3a4f 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -58,6 +58,14 @@ module NavHelper current_path?('milestones#show') end + def admin_monitoring_nav_links + %w(system_info background_jobs logs health_check requests_profiles) + end + + def group_issues_sub_menu_items + %w(groups#issues labels#index milestones#index boards#index boards#show) + end + private def get_header_links diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 2c43b1a2067..91d15e0e4ea 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -315,6 +315,10 @@ module ProjectsHelper ) % { default_label: default_label } end + def can_import_members? + Ability.allowed?(current_user, :admin_project_member, @project) + end + private def get_project_nav_tabs(project, current_user) diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb index 7aa75ee30e6..cbaf53fced1 100644 --- a/app/mailers/devise_mailer.rb +++ b/app/mailers/devise_mailer.rb @@ -7,6 +7,7 @@ class DeviseMailer < Devise::Mailer layout 'mailer/devise' helper EmailsHelper + helper ApplicationHelper protected diff --git a/app/models/active_session.rb b/app/models/active_session.rb index 1e01f1d17e6..f355b02c428 100644 --- a/app/models/active_session.rb +++ b/app/models/active_session.rb @@ -53,7 +53,7 @@ class ActiveSession def self.list(user) Gitlab::Redis::SharedState.with do |redis| - cleaned_up_lookup_entries(redis, user.id).map do |entry| + cleaned_up_lookup_entries(redis, user).map do |entry| # rubocop:disable Security/MarshalLoad Marshal.load(entry) # rubocop:enable Security/MarshalLoad @@ -78,7 +78,7 @@ class ActiveSession def self.cleanup(user) Gitlab::Redis::SharedState.with do |redis| - cleaned_up_lookup_entries(redis, user.id) + cleaned_up_lookup_entries(redis, user) end end @@ -90,25 +90,52 @@ class ActiveSession "#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user_id}" end - def self.cleaned_up_lookup_entries(redis, user_id) - lookup_key = lookup_key_name(user_id) + def self.list_sessions(user) + sessions_from_ids(session_ids_for_user(user)) + end - session_ids = redis.smembers(lookup_key) + def self.session_ids_for_user(user) + Gitlab::Redis::SharedState.with do |redis| + redis.smembers(lookup_key_name(user.id)) + end + end - entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) } - return [] if entry_keys.empty? + def self.sessions_from_ids(session_ids) + return [] if session_ids.empty? - entries = redis.mget(entry_keys) + Gitlab::Redis::SharedState.with do |redis| + session_keys = session_ids.map { |session_id| "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" } - session_ids_and_entries = session_ids.zip(entries) + redis.mget(session_keys).compact.map do |raw_session| + # rubocop:disable Security/MarshalLoad + Marshal.load(raw_session) + # rubocop:enable Security/MarshalLoad + end + end + end + + def self.raw_active_session_entries(session_ids, user_id) + return [] if session_ids.empty? + + Gitlab::Redis::SharedState.with do |redis| + entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) } + + redis.mget(entry_keys) + end + end + + def self.cleaned_up_lookup_entries(redis, user) + session_ids = session_ids_for_user(user) + entries = raw_active_session_entries(session_ids, user.id) # remove expired keys. # only the single key entries are automatically expired by redis, the # lookup entries in the set need to be removed manually. + session_ids_and_entries = session_ids.zip(entries) session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry| - redis.srem(lookup_key, session_id) + redis.srem(lookup_key_name(user.id), session_id) end - session_ids_and_entries.select { |_session_id, entry| entry }.map { |_session_id, entry| entry } + entries.compact end end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index d1d01368972..0979d03f6e6 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -41,4 +41,8 @@ class ApplicationRecord < ActiveRecord::Base find_or_create_by(*args) end end + + def self.underscore + Gitlab::SafeRequestStore.fetch("model:#{self}:underscore") { self.to_s.underscore } + end end diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index 0a166335b4e..b488bba00e9 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -9,6 +9,6 @@ class AttachmentUploader < GitlabUploader private def dynamic_segment - File.join(model.class.to_s.underscore, mounted_as.to_s, model.id.to_s) + File.join(model.class.underscore, mounted_as.to_s, model.id.to_s) end end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index c0165759203..9af59b0aceb 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -25,6 +25,6 @@ class AvatarUploader < GitlabUploader private def dynamic_segment - File.join(model.class.to_s.underscore, mounted_as.to_s, model.id.to_s) + File.join(model.class.underscore, mounted_as.to_s, model.id.to_s) end end diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb index 272837aa6ce..f4697d898af 100644 --- a/app/uploaders/personal_file_uploader.rb +++ b/app/uploaders/personal_file_uploader.rb @@ -20,7 +20,7 @@ class PersonalFileUploader < FileUploader def self.model_path_segment(model) return 'temp/' unless model - File.join(model.class.to_s.underscore, model.id.to_s) + File.join(model.class.underscore, model.id.to_s) end def object_store diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml index 0f5e97e288a..ac56e354a4d 100644 --- a/app/views/admin/health_check/show.html.haml +++ b/app/views/admin/health_check/show.html.haml @@ -23,7 +23,7 @@ %code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token) %li %code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token) - + = render_if_exists 'admin/health_check/health_check_url' %hr .card .card-header diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index 18a82feb189..8933c5d7227 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -1,4 +1,4 @@ -.blank-state-parent-container +.blank-state-parent-container{ class: ('has-start-trial-container' if has_start_trial?) } .section-container.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" } .container.section-body .row @@ -7,7 +7,12 @@ Welcome to GitLab %p.blank-state-text Code, test, and deploy together - - if current_user.admin? - = render "blank_state_admin_welcome" - - else - = render "blank_state_welcome" + .blank-state-row + %div{ class: ('column-large' if has_start_trial?) } + - if current_user.admin? + = render "blank_state_admin_welcome" + - else + = render "blank_state_welcome" + - if has_start_trial? + .column-small + = render_if_exists "blank_state_ee_trial" diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index aee05b6c81c..b1a9470cf1c 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -2,6 +2,7 @@ - if crowd_enabled? %li.nav-item = link_to "Crowd", "#crowd", class: "nav-link #{active_when(form_based_auth_provider_has_active_class?(:crowd))}", 'data-toggle' => 'tab' + = render_if_exists "devise/shared/kerberos_tab" - @ldap_servers.each_with_index do |server, i| %li.nav-item = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i.zero? && form_based_auth_provider_has_active_class?(:ldapmain))} qa-ldap-tab", 'data-toggle' => 'tab' diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 11e83ddfe64..c357207054b 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -77,3 +77,4 @@ = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') + = render_if_exists 'layouts/snowplow' diff --git a/app/views/layouts/_mailer.html.haml b/app/views/layouts/_mailer.html.haml index e13490ed410..6e8294d6adc 100644 --- a/app/views/layouts/_mailer.html.haml +++ b/app/views/layouts/_mailer.html.haml @@ -64,6 +64,8 @@ %tbody = yield + = render_if_exists 'layouts/mailer/additional_text' + %tr.footer %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } %img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 26a1f1e119c..006334ade07 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -5,6 +5,7 @@ = render 'shared/outdated_browser' .mobile-overlay .alert-wrapper + = render_if_exists "layouts/header/ee_license_banner" = render "layouts/broadcast" = render "layouts/header/read_only_banner" = render "layouts/nav/classification_level_banner" diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 2f3c13aaf6e..f7a561afbb3 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -26,6 +26,9 @@ - if Gitlab::CurrentSettings.sign_in_text.present? = markdown_field(Gitlab::CurrentSettings.current_application_settings, :sign_in_text) + + = render_if_exists 'layouts/devise_help_text' + .col-sm-5.new-session-forms-container = yield diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 724c9976954..1c1d1ea4645 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -18,8 +18,9 @@ %span.logo-text.d-none.d-lg-block.prepend-left-8 = logo_text - if Gitlab.com? - %span.js-canary-badge.badge.badge-pill.green-badge.align-self-center - = _('Next') + = link_to 'https://next.gitlab.com', class: 'label-link js-canary-badge canary-badge bg-transparent', target: :_blank do + %span.color-label.has-tooltip.badge.badge-pill.green-badge + = _('Next') - if current_user = render "layouts/nav/dashboard" diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml index fbec62b02f8..5643a508ddc 100644 --- a/app/views/layouts/header/_help_dropdown.html.haml +++ b/app/views/layouts/header/_help_dropdown.html.haml @@ -8,6 +8,7 @@ = link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback" - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile) = render 'shared/user_dropdown_contributing_link' + = render_if_exists 'shared/user_dropdown_instance_review' - if Gitlab.com? %li.js-canary-link = link_to _("Switch to GitLab Next"), "https://next.gitlab.com/" diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb index f8032f3262b..1a06ea68bcd 100644 --- a/app/views/layouts/mailer.text.erb +++ b/app/views/layouts/mailer.text.erb @@ -4,5 +4,6 @@ -- <%# signature marker %> <%= _("You're receiving this email because of your account on %{host}.") % { host: Gitlab.config.gitlab.host } %> +<%= render_if_exists 'layouts/mailer/additional_text' %> <%= text_footer_message %> diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 5a27237bf76..47710b9e9e5 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -95,3 +95,4 @@ = link_to sherlock_transactions_path, class: 'admin-icon d-none d-lg-block d-xl-block', title: _('Sherlock Transactions'), data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('tachometer fw') + = render_if_exists 'layouts/nav/geo_primary_node_url' diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index 04d67e024ba..83fe871285a 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -48,7 +48,7 @@ %span = _('Gitaly Servers') - = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do + = nav_link(controller: admin_monitoring_nav_links) do = link_to admin_system_info_path do .nav-icon-container = sprite_icon('monitor') @@ -81,6 +81,7 @@ = link_to admin_requests_profiles_path, title: _('Requests Profiles') do %span = _('Requests Profiles') + = render_if_exists 'layouts/nav/ee/admin/new_monitoring_sidebar' = nav_link(controller: :broadcast_messages) do = link_to admin_broadcast_messages_path do @@ -132,6 +133,8 @@ = _('Abuse Reports') %span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all)) + = render_if_exists 'layouts/nav/sidebar/licenses_link' + - if instance_clusters_enabled? = nav_link(controller: :clusters) do = link_to admin_clusters_path do @@ -158,6 +161,10 @@ %strong.fly-out-top-item-name = _('Spam Logs') + = render_if_exists 'layouts/nav/sidebar/push_rules_link' + + = render_if_exists 'layouts/nav/ee/admin/geo_sidebar' + = nav_link(controller: :deploy_keys) do = link_to admin_deploy_keys_path do .nav-icon-container diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index c2116ec63dd..0fc5ebbea7e 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -1,6 +1,5 @@ - issues_count = group_issues_count(state: 'opened') - merge_requests_count = group_merge_requests_count(state: 'opened') -- issues_sub_menu_items = ['groups#issues', 'labels#index', 'milestones#index', 'boards#index', 'boards#show'] .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar-inner-scroll @@ -51,7 +50,7 @@ = render_if_exists "layouts/nav/ee/epic_link", group: @group - if group_sidebar_link?(:issues) - = nav_link(path: issues_sub_menu_items) do + = nav_link(path: group_issues_sub_menu_items) do = link_to issues_group_path(@group) do .nav-icon-container = sprite_icon('issues') diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml index 2061eac917f..7dd33f3c641 100644 --- a/app/views/layouts/nav/sidebar/_profile.html.haml +++ b/app/views/layouts/nav/sidebar/_profile.html.haml @@ -28,6 +28,8 @@ = link_to profile_account_path do %strong.fly-out-top-item-name = _('Account') + + = render_if_exists 'layouts/nav/sidebar/profile_billing_link' = nav_link(controller: 'oauth/applications') do = link_to applications_profile_path do .nav-icon-container @@ -151,4 +153,6 @@ %strong.fly-out-top-item-name = _('Authentication Log') + = render_if_exists 'layouts/nav/sidebar/profile_pipeline_quota_link' + = render 'shared/sidebar_toggle_button' diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 3a0c2b9c284..399305baec1 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -270,6 +270,8 @@ %span= _("Got it!") = sprite_icon('thumb-up') + = render_if_exists 'layouts/nav/sidebar/project_feature_flags_link' + - if project_nav_tab? :container_registry = nav_link(controller: %w[projects/registry/repositories]) do = link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do @@ -283,7 +285,9 @@ %strong.fly-out-top-item-name = _('Registry') - - if project_nav_tab?(:wiki) + = render_if_exists 'layouts/nav/sidebar/project_packages_link' + + - if project_nav_tab? :wiki - wiki_url = project_wiki_path(@project, :home) = nav_link(controller: :wikis) do = link_to wiki_url, class: 'shortcuts-wiki qa-wiki-link' do diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 8dff12c1b7f..de487a94d40 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -31,4 +31,6 @@ adjust your notification settings. = email_action @target_url + + = render_if_exists 'layouts/email_additional_text' = html_footer_message diff --git a/app/views/layouts/notify.text.erb b/app/views/layouts/notify.text.erb index 248916fba63..0ee30c2a6cf 100644 --- a/app/views/layouts/notify.text.erb +++ b/app/views/layouts/notify.text.erb @@ -12,5 +12,6 @@ <% end -%> <%= "You're receiving this email because #{notification_reason_text(@reason)}." %> +<%= render_if_exists 'layouts/mailer/additional_text' %> <%= text_footer_message -%> diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 43f1cd01b67..d270e461ac8 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -1,5 +1,6 @@ - @no_container = true - page_title _('Branches') +- add_to_breadcrumbs(_('Repository'), project_tree_path(@project)) %div{ class: container_class } .top-area.adjust @@ -44,6 +45,8 @@ = link_to new_project_branch_path(@project), class: 'btn btn-success' do = s_('Branches|New branch') + = render_if_exists 'projects/commits/mirror_status' + - if can?(current_user, :admin_project, @project) - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project) .row-content-block diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 0590578c3fe..efabb7f7b19 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -19,4 +19,5 @@ = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input = f.submit _("Add to project"), class: "btn btn-success qa-add-member-button" - = link_to _("Import"), import_project_project_members_path(@project), class: "btn btn-default", title: _("Import members from another project") + - if can_import_members? + = link_to _("Import"), import_project_project_members_path(@project), class: "btn btn-default", title: _("Import members from another project") diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index a2df0347fd6..1e509ea0d1f 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -16,7 +16,12 @@ = ssh_clone_button(project) %li = http_clone_button(project) + = render_if_exists 'shared/kerberos_clone_button', project: project = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' } .input-group-append = clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard") + + = render_if_exists 'shared/geo_modal_button' + += render_if_exists 'shared/geo_modal', project: project diff --git a/changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml b/changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml new file mode 100644 index 00000000000..daeefd3ffd7 --- /dev/null +++ b/changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml @@ -0,0 +1,5 @@ +--- +title: Improve performance of users autocomplete when there are lots of results +merge_request: +author: +type: performance diff --git a/config/karma.config.js b/config/karma.config.js index 83ba46345f2..b2fc3a32816 100644 --- a/config/karma.config.js +++ b/config/karma.config.js @@ -107,6 +107,8 @@ module.exports = function(config) { // chrome cannot run in sandboxed mode inside a docker container unless it is run with // escalated kernel privileges (e.g. docker run --cap-add=CAP_SYS_ADMIN) '--no-sandbox', + // https://bugs.chromium.org/p/chromedriver/issues/detail?id=2870 + '--enable-features=NetworkService,NetworkServiceInProcess', ], }, }, diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md index d0e0e320019..caadec3ac4e 100644 --- a/doc/administration/high_availability/nfs.md +++ b/doc/administration/high_availability/nfs.md @@ -41,7 +41,6 @@ options: NOTE: **Note:** This is only available starting in certain versions of GitLab: - * 11.5.11 * 11.6.11 * 11.7.12 @@ -59,13 +58,13 @@ details. To do this, run the Rake task: ```sh -gitlab-rake gitlab:features:enable_rugged +sudo gitlab-rake gitlab:features:enable_rugged ``` If you need to undo this setting for some reason, run: ```sh -gitlab-rake gitlab:features:disable_rugged +sudo gitlab-rake gitlab:features:disable_rugged ``` ### Known issues diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 6fcc06ea8cd..87c7f371de1 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -181,7 +181,7 @@ Currently gitlab-shell has a boolean return code, preventing GitLab from specify ## Delete existing file in repository -This allows you to delete a single file. For deleting multiple files with a singleh request see the [commits API](commits.html#create-a-commit-with-multiple-files-and-actions). +This allows you to delete a single file. For deleting multiple files with a single request, see the [commits API](commits.html#create-a-commit-with-multiple-files-and-actions). ``` DELETE /projects/:id/repository/files/:file_path diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md index 99a4316ab0d..47d20a4e1c1 100644 --- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md +++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md @@ -9,7 +9,12 @@ You can checkout the [example source](https://gitlab.com/ayufan/python-getting-s This is what the `.gitlab-ci.yml` file looks like for this project: ```yaml +stages: + - test + - deploy + test: + stage: test script: # this configures Django application to use attached postgres database that is run on `postgres` host - export DATABASE_URL=postgres://postgres:@postgres:5432/python-test-app @@ -19,7 +24,7 @@ test: - python manage.py test staging: - type: deploy + stage: deploy script: - apt-get update -qy - apt-get install -y ruby-dev @@ -29,7 +34,7 @@ staging: - master production: - type: deploy + stage: deploy script: - apt-get update -qy - apt-get install -y ruby-dev diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md index 14ea648c00b..0045fa2fb1f 100644 --- a/doc/ci/introduction/index.md +++ b/doc/ci/introduction/index.md @@ -156,7 +156,7 @@ Once you're happy with your implementation: ![GitLab workflow example](img/gitlab_workflow_example_11_9.png) -GitLab CI/CD is capable of a doing a lot more, but this workflow +GitLab CI/CD is capable of doing a lot more, but this workflow exemplifies GitLab's ability to track the entire process, without the need of any external tool to deliver your software. And, most usefully, you can visualize all the steps through diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md index 2902c30c7c0..0de6a93514a 100644 --- a/doc/ci/services/mysql.md +++ b/doc/ci/services/mysql.md @@ -17,8 +17,8 @@ services: variables: # Configure mysql environment variables (https://hub.docker.com/_/mysql/) - MYSQL_DATABASE: el_duderino - MYSQL_ROOT_PASSWORD: mysql_strong_password + MYSQL_DATABASE: "<your_mysql_database>" + MYSQL_ROOT_PASSWORD: "mysql_strong_password" ``` And then configure your application to use the database, for example: diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index dca2d953286..36b88ff12c2 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -337,6 +337,7 @@ In addition, `only` and `except` allow the use of special keywords: | `triggers` | For pipelines created using a trigger token. | | `web` | For pipelines created using **Run pipeline** button in GitLab UI (under your project's **Pipelines**). | | `merge_requests` | When a merge request is created or updated (See [pipelines for merge requests](../merge_request_pipelines/index.md)). | +| `chats` | For jobs created using a [GitLab ChatOps](../chatops/README.md) command. | In the example below, `job` will run only for refs that start with `issue-`, whereas all branches will be skipped: @@ -1446,7 +1447,7 @@ be automatically shown in merge requests. > Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above. -The `sast` report collects [SAST vulnerabilities](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html) +The `sast` report collects [SAST vulnerabilities](https://docs.gitlab.com/ee/user/application_security/sast/index.html) as artifacts. The collected SAST report will be uploaded to GitLab as an artifact and will @@ -1764,9 +1765,6 @@ TIP: **Tip:** Use merging to customize and override included CI/CD configurations with local definitions. -Recursive includes are not supported. Your external files should not use the -`include` keyword as it will be ignored. - NOTE: **Note:** Using YAML aliases across different YAML files sourced by `include` is not supported. You must only refer to aliases in the same file. Instead diff --git a/doc/development/README.md b/doc/development/README.md index 83a1145c020..cfbbd5aaeaa 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -44,6 +44,7 @@ description: 'Learn how to contribute to GitLab.' - [`Gemfile` guidelines](gemfile.md) - [Pry debugging](pry_debugging.md) - [Sidekiq debugging](sidekiq_debugging.md) +- [Accessing session data](session.md) - [Gotchas](gotchas.md) to avoid - [Avoid modules with instance variables](module_with_instance_variables.md) if possible - [How to dump production data to staging](db_dump.md) diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 1e59b59b312..13de1f55f23 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -16,9 +16,18 @@ This document is designed to be consumed by systems adminstrators and GitLab Sup When deployed, GitLab should be considered the amalgamation of the below processes. When troubleshooting or debugging, be as specific as possible as to which component you are referencing. That should increase clarity and reduce confusion. +### Layers + +GitLab can be considered to have two layers from a process perspective: + +- **Monitoring**: Anything from this layer is not required to deliver GitLab the application, but will allow administrators more insight into their infrastructure and what the service as a whole is doing. +- **Core**: Any process that is vital for the delivery of GitLab as a platform. If any of these processes halt there will be a GitLab outage. For the Core layer, you can further divide into: + - **Processors**: These processes are responsible for actually performing operations and presenting the service. + - **Data**: These services store/expose structured data for the GitLab service. + ### GitLab Process Descriptions -As of this writing, a fresh GitLab 11.3.0 install will show the following processes with `gitlab-ctl status`: +As of this writing, a fresh GitLab 11.3.0 install with default settings will show the following processes with `gitlab-ctl status`: ``` run: alertmanager: (pid 30829) 14207s; run: log: (pid 13906) 2432044s @@ -37,86 +46,77 @@ run: sidekiq: (pid 30953) 14205s; run: log: (pid 13810) 2432047s run: unicorn: (pid 30960) 14204s; run: log: (pid 13809) 2432047s ``` -### Layers - -GitLab can be considered to have two layers from a process perspective: - -- **Monitoring**: Anything from this layer is not required to deliver GitLab the application, but will allow administrators more insight into their infrastructure and what the service as a whole is doing. -- **Core**: Any process that is vital for the delivery of GitLab as a platform. If any of these processes halt there will be a GitLab outage. For the Core layer, you can further divide into: - - **Processors**: These processes are responsible for actually performing operations and presenting the service. - - **Data**: These services store/expose structured data for the GitLab service. - -### alertmanager +#### alertmanager - Omnibus configuration options - Layer: Monitoring -[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue gitlab-ce#45740](https://gitlab.com/gitlab-org/gitlab-ce/issues/45740) about what we will be alerting on. +[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by Prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue gitlab-ce#45740](https://gitlab.com/gitlab-org/gitlab-ce/issues/45740) about what we will be alerting on. -### gitaly +#### gitaly - [Omnibus configuration options](https://gitlab.com/gitlab-org/gitaly/tree/master/doc/configuration) - Layer: Core Service (Data) -Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab (Think GitLab.com or High Availability Deployments). As of 11.3.0, this service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly). +Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab (think GitLab.com or High Availability Deployments). As of 11.3.0, this service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly). -### gitlab-monitor +#### gitlab-monitor - Omnibus configuration options - Layer: Monitoring -GitLab Monitor is a process designed in house that allows us to export metrics about GitLab application internals to prometheus. You can read more [in the project's readme](https://gitlab.com/gitlab-org/gitlab-monitor) +GitLab Monitor is a process designed in house that allows us to export metrics about GitLab application internals to Prometheus. You can read more [in the project's readme](https://gitlab.com/gitlab-org/gitlab-monitor). -### gitlab-workhorse +#### gitlab-workhorse - Omnibus configuration options - Layer: Core Service (Processor) -[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) is a program designed at GitLab to help alleviate pressure from unicorn. You can read more about the [historical reasons for developing](https://about.gitlab.com/2016/04/12/a-brief-history-of-gitlab-workhorse/). It's designed to act as a smart reverse proxy to help speed up GitLab as a whole. +[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) is a program designed at GitLab to help alleviate pressure from Unicorn. You can read more about the [historical reasons for developing](https://about.gitlab.com/2016/04/12/a-brief-history-of-gitlab-workhorse/). It's designed to act as a smart reverse proxy to help speed up GitLab as a whole. -### logrotate +#### logrotate - [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate) - Layer: Core Service -GitLab is comprised of a large number of services that all log. We started bundling our own logrotate as of 7.4 to make sure we were logging responsibly. This is just a packaged version of the common opensource offering. +GitLab is comprised of a large number of services that all log. We started bundling our own logrotate as of 7.4 to make sure we were logging responsibly. This is just a packaged version of the common open source offering. -### nginx +#### nginx - [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/nginx.html) - Layer: Core Service (Processor) Nginx as an ingress port for all HTTP requests and routes them to the approriate sub-systems within GitLab. We are bundling an unmodified version of the popular open source webserver. -### node-exporter +#### node-exporter - [Omnibus configuration options](https://docs.gitlab.com/ee/administration/monitoring/prometheus/node_exporter.html) - Layer: Monitoring -[Node Exporter](https://github.com/prometheus/node_exporter) is a Prometheus tool that gives us metrics on the underlying machine. (Think CPU/Disk/Load) It's just a packaged version of the common open source offering from the Prometheus project. +[Node Exporter](https://github.com/prometheus/node_exporter) is a Prometheus tool that gives us metrics on the underlying machine (think CPU/Disk/Load). It's just a packaged version of the common open source offering from the Prometheus project. -### postgres-exporter +#### postgres-exporter - [Omnibus configuration options](https://docs.gitlab.com/ee/administration/monitoring/prometheus/postgres_exporter.html) - Layer: Monitoring -[Postgres-exporter](https://github.com/wrouesnel/postgres_exporter) is the community provided Prometheus exporter that will deliver data about Postgres to prometheus for use in Grafana Dashboards. +[Postgres-exporter](https://github.com/wrouesnel/postgres_exporter) is the community provided Prometheus exporter that will deliver data about Postgres to Prometheus for use in Grafana Dashboards. -### postgresql +#### postgresql - [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/database.html) - Layer: Core Service (Data) GitLab packages the popular Database to provide storage for Application meta data and user information. -### prometheus +#### prometheus - [Omnibus configuration options](https://docs.gitlab.com/ee/administration/monitoring/prometheus/) - Layer: Monitoring Prometheus is a time-series tool that helps GitLab administrators expose metrics about the individual processes used to provide GitLab the service. -### redis +#### redis - [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/redis.html) - Layer: Core Service (Data) @@ -125,40 +125,42 @@ Redis is packaged to provide a place to store: - session data - temporary cache information -- background job queues. +- background job queues -### redis-exporter +#### redis-exporter - [Omnibus configuration options](https://docs.gitlab.com/ee/administration/monitoring/prometheus/redis_exporter.html) - Layer: Monitoring -[Redis Exporter](https://github.com/oliver006/redis_exporter) is designed to give specific metrics about the Redis process to Prometheus so that we can graph these metrics in Graphana. +[Redis Exporter](https://github.com/oliver006/redis_exporter) is designed to give specific metrics about the Redis process to Prometheus so that we can graph these metrics in Grafana. -### sidekiq +#### sidekiq - Omnibus configuration options - Layer: Core Service (Processor) Sidekiq is a Ruby background job processor that pulls jobs from the redis queue and processes them. Background jobs allow GitLab to provide a faster request/response cycle by moving work into the background. -### unicorn +#### unicorn - [Omnibus configuration options](https://docs.gitlab.com/omnibus/settings/unicorn.html) - Layer: Core Service (Processor) [Unicorn](https://bogomips.org/unicorn/) is a Ruby application server that is used to run the core Rails Application that provides the user facing features in GitLab. Often process output you will see this as `bundle` or `config.ru` depending on the GitLab version. -### Additional Processes +#### Additional Processes + +The following processes will be running if corresponding feature is enabled. -### GitLab Pages +##### gitlab-pages TODO -### Mattermost +##### mattermost TODO -### Registry +##### registry The registry is what users use to store their own Docker images. The bundled registry uses nginx as a load balancer and GitLab as an authentication manager. @@ -181,10 +183,10 @@ It's important to understand the distinction as some processes are used in both ### GitLab Web HTTP Request Cycle -When making a request to an HTTP Endpoint (Think `/users/sign_in`) the request will take the following path through the GitLab Service: +When making a request to an HTTP Endpoint (think `/users/sign_in`) the request will take the following path through the GitLab Service: -- nginx - Acts as our first line reverse proxy -- gitlab-workhorse - This determines if it needs to go to the Rails application or somewhere else to reduce load on unicorn. +- nginx - Acts as our first line reverse proxy. +- gitlab-workhorse - This determines if it needs to go to the Rails application or somewhere else to reduce load on Unicorn. - unicorn - Since this is a web request, and it needs to access the application it will go to Unicorn. - Postgres/Gitaly/Redis - Depending on the type of request, it may hit these services to store or retrieve data. @@ -200,7 +202,7 @@ TODO ## System Layout -When referring to `~git` in the pictures it means the home directory of the git user which is typically /home/git. +When referring to `~git` in the pictures it means the home directory of the git user which is typically `/home/git`. GitLab is primarily installed within the `/home/git` user home directory as `git` user. Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable). diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md index 7d52cac5f7e..bf248b7f8af 100644 --- a/doc/development/fe_guide/vuex.md +++ b/doc/development/fe_guide/vuex.md @@ -186,7 +186,7 @@ Remember that actions only describe that something happened, they don't describe state.users.push(user); }, [types.REQUEST_ADD_USER_ERROR](state, error) { - state.isAddingUser = true; + state.isAddingUser = false; state.errorAddingUser = error; }, }; @@ -231,7 +231,7 @@ The store should be included in the main component of your application: ```javascript // app.vue - import store from 'store'; // it will include the index.js file + import store from './store'; // it will include the index.js file export default { name: 'application', diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md index b9dc3797e5b..cf78b792ffd 100644 --- a/doc/development/go_guide/index.md +++ b/doc/development/go_guide/index.md @@ -40,7 +40,7 @@ of possible security breaches in our code: - SQL injections Remember to run -[SAST](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html) +[SAST](https://docs.gitlab.com/ee/user/application_security/sast/index) **[ULTIMATE]** on your project (or at least the [gosec analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/gosec)), and to follow our [Security @@ -96,7 +96,7 @@ dependency should be argued in the merge request, as per our [Approval Guidelines](../code_review.md#approval-guidelines). Both [License Management](https://docs.gitlab.com/ee/user/project/merge_requests/license_management.html) **[ULTIMATE]** and [Dependency -Scanning](https://docs.gitlab.com/ee/user/project/merge_requests/dependency_scanning.html) +Scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index) **[ULTIMATE]** should be activated on all projects to ensure new dependencies security status and license compatibility. diff --git a/doc/development/session.md b/doc/development/session.md new file mode 100644 index 00000000000..9edce3dbda0 --- /dev/null +++ b/doc/development/session.md @@ -0,0 +1,65 @@ +# Accessing session data + +Session data in GitLab is stored in Redis and can be accessed in a variety of ways. + +During a web request, for example: + +- Rails provides access to the session from within controllers through [`ActionDispatch::Session`](https://guides.rubyonrails.org/action_controller_overview.html#session). +- Outside of controllers, it is possible to access the session through `Gitlab::Session`. + +Outside of a web request it is still possible to access sessions stored in Redis. For example: + +- Session IDs and contents can be [looked up directly in Redis](#redis). +- Data about the UserAgent associated with the session can be accessed through `ActiveSession`. + +When storing values in a session it is best to: + +- Use simple primitives and avoid storing objects to avoid marshaling complications. +- Clean up after unneeded variables to keep memory usage in Redis down. + +## Gitlab::Session + +Sometimes you might want to persist data in the session instead of another store like the database. `Gitlab::Session` lets you access this without passing the session around extensively. For example, you could access it from within a policy without having to pass the session through to each place permissions are checked from. + +The session has a hash-like interface, just like when using it from a controller. There is also `NamespacedSessionStore` for storing key-value data in a hash. + +```ruby +# Lookup a value stored in the current session +Gitlab::Session.current[:my_feature] + +# Modify the current session stored in redis +Gitlab::Session.current[:my_feature] = value + +# Store key-value data namespaced under a key +Gitlab::NamespacedSessionStore.new(:my_feature)[some_key] = value + +# Set the session for a block of code, such as for tests +Gitlab::Session.with_session(my_feature: value) do + # Code that uses Session.current[:my_feature] +end +``` + +## Redis + +Session data can be accessed directly through Redis. This can let you check up on a browser session when debugging. + +```ruby +# Get a list of sessions +session_ids = Gitlab::Redis::SharedState.with do |redis| + redis.smembers("#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user.id}") +end + +# Retrieve a specific session +session_data = Gitlab::Redis::SharedState.with { |redis| redis.get("#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}") } +Marshal.load(session_data) +``` + +## Getting device information with ActiveSession + +The [**Active Sessions** page on a user's profile](../user/profile/active_sessions.md) displays information about the device used to access each session. The methods used there to list sessions can also be useful for development. + +```ruby +# Get list of sessions for a given user +# Includes session_id and data from the UserAgent +ActiveSession.list(user) +``` diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md index 7cef664bc98..b03bb6d98e8 100644 --- a/doc/integration/elasticsearch.md +++ b/doc/integration/elasticsearch.md @@ -85,7 +85,7 @@ To build and install the indexer, run: ```sh git clone https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer.git -cd /gitlab-elasticsearch-indexer +cd gitlab-elasticsearch-indexer make sudo make install ``` diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index f90bff662e2..c2dff21b028 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -67,7 +67,7 @@ sudo -u git -H bundle exec rake gettext:pack RAILS_ENV=production sudo -u git -H bundle exec rake gettext:po_to_json RAILS_ENV=production # Clean up assets and cache -sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production NODE_ENV=production +sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096" ``` ### 4. Update gitlab-workhorse to the corresponding version diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md index 5118726cb0a..82a86f3f343 100644 --- a/doc/update/upgrading_from_source.md +++ b/doc/update/upgrading_from_source.md @@ -25,13 +25,7 @@ This section contains all the steps necessary to upgrade Community Edition or Enterprise Edition, regardless of the version you are upgrading to. Version specific guidelines (should there be any) are covered separately. -### 1. Stop server - -```bash -sudo service gitlab stop -``` - -### 2. Backup +### 1. Backup NOTE: If you installed GitLab from source, make sure `rsync` is installed. @@ -41,6 +35,12 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` +### 2. Stop server + +```bash +sudo service gitlab stop +``` + ### 3. Update Ruby NOTE: Beginning in GitLab 11.6, we only support Ruby 2.5 or higher, and dropped @@ -314,7 +314,7 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production # Update node dependencies and recompile assets -sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production +sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096" # Clean up cache sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index 904c9e8fefe..f3b7d7fd471 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -138,7 +138,7 @@ variables: #### Customizing the DAST settings -The SAST settings can be changed through environment variables by using the +The DAST settings can be changed through environment variables by using the [`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. These variables are documented in the [DAST README](https://gitlab.com/gitlab-org/security-products/dast#settings). diff --git a/doc/user/group/custom_project_templates.md b/doc/user/group/custom_project_templates.md index 8e101407ac0..f67325272a6 100644 --- a/doc/user/group/custom_project_templates.md +++ b/doc/user/group/custom_project_templates.md @@ -1,4 +1,4 @@ -# Custom group-level project templates **[PREMIUM ONLY]** +# Custom group-level project templates **[PREMIUM]** > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6861) in [GitLab Premium](https://about.gitlab.com/pricing) 11.6. diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml index 9d99d04d263..876f53c66ba 100644 --- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml @@ -338,7 +338,6 @@ rollout 100%: image_tag=${CI_APPLICATION_TAG:-$CI_COMMIT_TAG} fi - replicas="1" service_enabled="true" postgres_enabled="$POSTGRES_ENABLED" diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb index 356e6445e0e..72c44114001 100644 --- a/lib/gitlab/sentry.rb +++ b/lib/gitlab/sentry.rb @@ -10,7 +10,7 @@ module Gitlab def self.context(current_user = nil) return unless enabled? - Raven.tags_context(locale: I18n.locale) + Raven.tags_context(default_tags) if current_user Raven.user_context( @@ -44,16 +44,19 @@ module Gitlab extra[:issue_url] = issue_url if issue_url context # Make sure we've set everything we know in the context - tags = { - Labkit::Correlation::CorrelationId::LOG_KEY.to_sym => Labkit::Correlation::CorrelationId.current_id - } - - Raven.capture_exception(exception, tags: tags, extra: extra) + Raven.capture_exception(exception, tags: default_tags, extra: extra) end end def self.should_raise_for_dev? Rails.env.development? || Rails.env.test? end + + def self.default_tags + { + Labkit::Correlation::CorrelationId::LOG_KEY.to_sym => Labkit::Correlation::CorrelationId.current_id, + locale: I18n.locale + } + end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 71ec8bcb9ba..fdb6adfbd51 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -868,6 +868,9 @@ msgstr "" msgid "An error occurred while fetching the board lists. Please try again." msgstr "" +msgid "An error occurred while fetching the builds." +msgstr "" + msgid "An error occurred while fetching the job log." msgstr "" @@ -883,6 +886,9 @@ msgstr "" msgid "An error occurred while fetching the releases. Please try again." msgstr "" +msgid "An error occurred while fetching this tab." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" @@ -970,6 +976,9 @@ msgstr "" msgid "Any" msgstr "" +msgid "Any Milestone" +msgstr "" + msgid "Any encrypted tokens" msgstr "" @@ -3905,6 +3914,9 @@ msgstr "" msgid "Error loading merge requests." msgstr "" +msgid "Error loading milestone tab" +msgstr "" + msgid "Error loading project data. Please try again." msgstr "" @@ -11149,6 +11161,9 @@ msgstr "" msgid "You need permission." msgstr "" +msgid "You need to be logged in." +msgstr "" + msgid "You need to register a two-factor authentication app before you can set up a U2F device." msgstr "" diff --git a/spec/controllers/concerns/project_unauthorized_spec.rb b/spec/controllers/concerns/project_unauthorized_spec.rb index 57ac00cf4dd..5834b1ef37f 100644 --- a/spec/controllers/concerns/project_unauthorized_spec.rb +++ b/spec/controllers/concerns/project_unauthorized_spec.rb @@ -12,7 +12,7 @@ describe ProjectUnauthorized do render_views - describe '#project_unauthorized_proc' do + describe '.on_routable_not_found' do controller(::Projects::ApplicationController) do def show head :ok diff --git a/spec/controllers/concerns/routable_actions_spec.rb b/spec/controllers/concerns/routable_actions_spec.rb new file mode 100644 index 00000000000..59d48c68b9c --- /dev/null +++ b/spec/controllers/concerns/routable_actions_spec.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe RoutableActions do + controller(::ApplicationController) do + include RoutableActions # rubocop:disable RSpec/DescribedClass + + before_action :routable + + def routable + @klass = params[:type].constantize + @routable = find_routable!(params[:type].constantize, params[:id]) + end + + def show + head :ok + end + end + + def get_routable(routable) + get :show, params: { id: routable.full_path, type: routable.class } + end + + describe '#find_routable!' do + context 'when signed in' do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'with a project' do + let(:routable) { create(:project) } + + context 'when authorized' do + before do + routable.add_guest(user) + end + + it 'returns the project' do + get_routable(routable) + + expect(assigns[:routable]).to be_a(Project) + end + + it 'allows access' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(200) + end + end + + it 'prevents access when not authorized' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'with a group' do + let(:routable) { create(:group, :private) } + + context 'when authorized' do + before do + routable.add_guest(user) + end + + it 'returns the group' do + get_routable(routable) + + expect(assigns[:routable]).to be_a(Group) + end + + it 'allows access' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(200) + end + end + + it 'prevents access when not authorized' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'with a user' do + let(:routable) { user } + + it 'allows access when authorized' do + get_routable(routable) + + expect(response).to have_gitlab_http_status(200) + end + + it 'prevents access when unauthorized' do + allow(subject).to receive(:can?).and_return(false) + + get_routable(user) + + expect(response).to have_gitlab_http_status(404) + end + end + end + + context 'when not signed in' do + it 'redirects to sign in for private resouces' do + routable = create(:project, :private) + + get_routable(routable) + + expect(response).to have_gitlab_http_status(302) + expect(response.location).to end_with('/users/sign_in') + end + end + end + + describe '#perform_not_found_actions' do + let(:routable) { create(:project) } + + before do + sign_in(create(:user)) + end + + it 'performs multiple checks' do + last_check_called = false + checks = [proc {}, proc { last_check_called = true }] + allow(subject).to receive(:not_found_actions).and_return(checks) + + get_routable(routable) + + expect(last_check_called).to eq(true) + end + + it 'performs checks in the context of the controller' do + check = lambda { |routable| redirect_to routable } + allow(subject).to receive(:not_found_actions).and_return([check]) + + get_routable(routable) + + expect(response.location).to end_with(routable.to_param) + end + + it 'skips checks once one has resulted in a render/redirect' do + first_check = proc { render plain: 'first' } + second_check = proc { render plain: 'second' } + allow(subject).to receive(:not_found_actions).and_return([first_check, second_check]) + + get_routable(routable) + + expect(response.body).to eq('first') + end + end +end diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb index 8408578a7db..a3ce08f736c 100644 --- a/spec/controllers/concerns/send_file_upload_spec.rb +++ b/spec/controllers/concerns/send_file_upload_spec.rb @@ -1,3 +1,4 @@ +# coding: utf-8 # frozen_string_literal: true require 'spec_helper' @@ -13,7 +14,7 @@ describe SendFileUpload do # user/:id def dynamic_segment - File.join(model.class.to_s.underscore, model.id.to_s) + File.join(model.class.underscore, model.id.to_s) end end end diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index 7256f785e1f..426abdc2a6c 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -13,7 +13,7 @@ FactoryBot.define do end # this needs to comply with RecordsUpload::Concern#upload_path - path { File.join("uploads/-/system", model.class.to_s.underscore, mount_point.to_s, 'avatar.jpg') } + path { File.join("uploads/-/system", model.class.underscore, mount_point.to_s, 'avatar.jpg') } trait :personal_snippet_upload do uploader "PersonalFileUploader" diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb index 7ba24ba2956..023238ee0ae 100644 --- a/spec/helpers/dashboard_helper_spec.rb +++ b/spec/helpers/dashboard_helper_spec.rb @@ -21,4 +21,10 @@ describe DashboardHelper do expect(helper.dashboard_nav_links).not_to include(:activity, :milestones) end end + + describe '.has_start_trial?' do + subject { helper.has_start_trial? } + + it { is_expected.to eq(false) } + end end diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb index e840c927d59..979d89812f5 100644 --- a/spec/helpers/nav_helper_spec.rb +++ b/spec/helpers/nav_helper_spec.rb @@ -50,4 +50,16 @@ describe NavHelper do expect(helper.header_links).to contain_exactly(:sign_in, :search) end end + + context '.admin_monitoring_nav_links' do + subject { helper.admin_monitoring_nav_links } + + it { is_expected.to all(be_a(String)) } + end + + context '.group_issues_sub_menu_items' do + subject { helper.group_issues_sub_menu_items } + + it { is_expected.to all(be_a(String)) } + end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 554cb861563..83271aa24a3 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -799,4 +799,24 @@ describe ProjectsHelper do it { is_expected.to eq(result) } end end + + describe '#can_import_members?' do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:owner) { project.owner } + + before do + helper.instance_variable_set(:@project, project) + end + + it 'returns false if user cannot admin_project_member' do + allow(helper).to receive(:current_user) { user } + expect(helper.can_import_members?).to eq false + end + + it 'returns true if user can admin_project_member' do + allow(helper).to receive(:current_user) { owner } + expect(helper.can_import_members?).to eq true + end + end end diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb index ae522a588ee..af8b059b984 100644 --- a/spec/lib/gitlab/sentry_spec.rb +++ b/spec/lib/gitlab/sentry_spec.rb @@ -2,12 +2,15 @@ require 'spec_helper' describe Gitlab::Sentry do describe '.context' do - it 'adds the locale to the tags' do + it 'adds the expected tags' do expect(described_class).to receive(:enabled?).and_return(true) + allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid') described_class.context(nil) expect(Raven.tags_context[:locale].to_s).to eq(I18n.locale.to_s) + expect(Raven.tags_context[Labkit::Correlation::CorrelationId::LOG_KEY.to_sym].to_s) + .to eq('cid') end end diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index b523f393ece..2762eaeccd3 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -88,6 +88,52 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do end end + describe '.list_sessions' do + it 'uses the ActiveSession lookup to return original sessions' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ _csrf_token: 'abcd' })) + + redis.sadd( + "session:lookup:user:gitlab:#{user.id}", + %w[ + 6919a6f1bb119dd7396fadc38fd18d0d + 59822c7d9fcdfa03725eff41782ad97d + ] + ) + end + + expect(ActiveSession.list_sessions(user)).to eq [{ _csrf_token: 'abcd' }] + end + end + + describe '.session_ids_for_user' do + it 'uses the user lookup table to return session ids' do + session_ids = ['59822c7d9fcdfa03725eff41782ad97d'] + + Gitlab::Redis::SharedState.with do |redis| + redis.sadd("session:lookup:user:gitlab:#{user.id}", session_ids) + end + + expect(ActiveSession.session_ids_for_user(user)).to eq(session_ids) + end + end + + describe '.sessions_from_ids' do + it 'uses the ActiveSession lookup to return original sessions' do + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:gitlab:6919a6f1bb119dd7396fadc38fd18d0d", Marshal.dump({ _csrf_token: 'abcd' })) + end + + expect(ActiveSession.sessions_from_ids(['6919a6f1bb119dd7396fadc38fd18d0d'])).to eq [{ _csrf_token: 'abcd' }] + end + + it 'avoids a redis lookup for an empty array' do + expect(Gitlab::Redis::SharedState).not_to receive(:with) + + expect(ActiveSession.sessions_from_ids([])).to eq([]) + end + end + describe '.set' do it 'sets a new redis entry for the user session and a lookup entry' do ActiveSession.set(user, request) diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb index cc90a998d3f..74573d0941c 100644 --- a/spec/models/application_record_spec.rb +++ b/spec/models/application_record_spec.rb @@ -52,4 +52,10 @@ describe ApplicationRecord do expect { Suggestion.find_or_create_by!(note: nil) }.to raise_error(ActiveRecord::RecordInvalid) end end + + describe '.underscore' do + it 'returns the underscored value of the class as a string' do + expect(MergeRequest.underscore).to eq('merge_request') + end + end end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 25a312cb734..ed907841bd8 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -247,9 +247,8 @@ describe API::Helpers do exception = RuntimeError.new('test error') allow(exception).to receive(:backtrace).and_return(caller) - expect(Raven).to receive(:capture_exception).with(exception, tags: { - correlation_id: 'new-correlation-id' - }, extra: {}) + expect(Raven).to receive(:capture_exception).with(exception, tags: + a_hash_including(correlation_id: 'new-correlation-id'), extra: {}) Labkit::Correlation::CorrelationId.use_id('new-correlation-id') do handle_api_exception(exception) diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb index 38f30a14409..8a139fafac2 100644 --- a/spec/support/helpers/features/notes_helpers.rb +++ b/spec/support/helpers/features/notes_helpers.rb @@ -25,12 +25,10 @@ module Spec page.within('.js-main-target-form') do filled_text = fill_in('note[note]', with: text) - begin - # Dismiss quick action prompt if it appears - filled_text.parent.send_keys(:escape) - rescue Selenium::WebDriver::Error::ElementNotInteractableError - # It's fine if we can't escape when there's no prompt. - end + # Wait for quick action prompt to load and then dismiss it with ESC + # because it may block the Preview button + wait_for_requests + filled_text.send_keys(:escape) click_on('Preview') diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index a62830c35f1..6bad5d49b1c 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -12,7 +12,7 @@ class Implementation < GitlabUploader # user/:id def dynamic_segment - File.join(model.class.to_s.underscore, model.id.to_s) + File.join(model.class.underscore, model.id.to_s) end end |