Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									dc93436903
								
							
						
					
					
						commit
						589ee0e419
					
				|  | @ -0,0 +1,125 @@ | ||||||
|  | import { logError } from '~/lib/logger'; | ||||||
|  | 
 | ||||||
|  | const isSupported = () => Boolean(window.dataLayer) && gon.features?.gitlabGtmDatalayer; | ||||||
|  | 
 | ||||||
|  | const pushEvent = (event, args = {}) => { | ||||||
|  |   if (!window.dataLayer) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     window.dataLayer.push({ | ||||||
|  |       event, | ||||||
|  |       ...args, | ||||||
|  |     }); | ||||||
|  |   } catch (e) { | ||||||
|  |     logError('Unexpected error while pushing to dataLayer', e); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const pushAccountSubmit = (accountType, accountMethod) => | ||||||
|  |   pushEvent('accountSubmit', { accountType, accountMethod }); | ||||||
|  | 
 | ||||||
|  | const trackFormSubmission = (accountType) => { | ||||||
|  |   const form = document.getElementById('new_new_user'); | ||||||
|  |   form.addEventListener('submit', () => { | ||||||
|  |     pushAccountSubmit(accountType, 'form'); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const trackOmniAuthSubmission = (accountType) => { | ||||||
|  |   const links = document.querySelectorAll('.js-oauth-login'); | ||||||
|  |   links.forEach((link) => { | ||||||
|  |     const { provider } = link.dataset; | ||||||
|  |     link.addEventListener('click', () => { | ||||||
|  |       pushAccountSubmit(accountType, provider); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const trackFreeTrialAccountSubmissions = () => { | ||||||
|  |   if (!isSupported()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   trackFormSubmission('freeThirtyDayTrial'); | ||||||
|  |   trackOmniAuthSubmission('freeThirtyDayTrial'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const trackNewRegistrations = () => { | ||||||
|  |   if (!isSupported()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   trackFormSubmission('standardSignUp'); | ||||||
|  |   trackOmniAuthSubmission('standardSignUp'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const trackSaasTrialSubmit = () => { | ||||||
|  |   if (!isSupported()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const form = document.getElementById('new_trial'); | ||||||
|  |   form.addEventListener('submit', () => { | ||||||
|  |     pushEvent('saasTrialSubmit'); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const trackSaasTrialSkip = () => { | ||||||
|  |   if (!isSupported()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const skipLink = document.querySelector('.js-skip-trial'); | ||||||
|  |   skipLink.addEventListener('click', () => { | ||||||
|  |     pushEvent('saasTrialSkip'); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const trackSaasTrialGroup = () => { | ||||||
|  |   if (!isSupported()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const form = document.getElementById('new_group'); | ||||||
|  |   form.addEventListener('submit', () => { | ||||||
|  |     pushEvent('saasTrialGroup'); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const trackSaasTrialProject = () => { | ||||||
|  |   if (!isSupported()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const form = document.getElementById('new_project'); | ||||||
|  |   form.addEventListener('submit', () => { | ||||||
|  |     pushEvent('saasTrialProject'); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const trackSaasTrialProjectImport = () => { | ||||||
|  |   if (!isSupported()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const importButtons = document.querySelectorAll('.js-import-project-btn'); | ||||||
|  |   importButtons.forEach((button) => { | ||||||
|  |     button.addEventListener('click', () => { | ||||||
|  |       const { platform } = button.dataset; | ||||||
|  |       pushEvent('saasTrialProjectImport', { saasProjectImport: platform }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const trackSaasTrialGetStarted = () => { | ||||||
|  |   if (!isSupported()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const getStartedButton = document.querySelector('.js-get-started-btn'); | ||||||
|  |   getStartedButton.addEventListener('click', () => { | ||||||
|  |     pushEvent('saasTrialGetStarted'); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | @ -1,3 +1,5 @@ | ||||||
|  | import { trackNewRegistrations } from '~/google_tag_manager'; | ||||||
|  | 
 | ||||||
| import NoEmojiValidator from '~/emoji/no_emoji_validator'; | import NoEmojiValidator from '~/emoji/no_emoji_validator'; | ||||||
| import LengthValidator from '~/pages/sessions/new/length_validator'; | import LengthValidator from '~/pages/sessions/new/length_validator'; | ||||||
| import UsernameValidator from '~/pages/sessions/new/username_validator'; | import UsernameValidator from '~/pages/sessions/new/username_validator'; | ||||||
|  | @ -5,3 +7,5 @@ import UsernameValidator from '~/pages/sessions/new/username_validator'; | ||||||
| new UsernameValidator(); // eslint-disable-line no-new
 | new UsernameValidator(); // eslint-disable-line no-new
 | ||||||
| new LengthValidator(); // eslint-disable-line no-new
 | new LengthValidator(); // eslint-disable-line no-new
 | ||||||
| new NoEmojiValidator(); // eslint-disable-line no-new
 | new NoEmojiValidator(); // eslint-disable-line no-new
 | ||||||
|  | 
 | ||||||
|  | trackNewRegistrations(); | ||||||
|  |  | ||||||
|  | @ -67,7 +67,7 @@ export default { | ||||||
|       <gl-button |       <gl-button | ||||||
|         class="gl-w-auto! gl-mt-3 gl-text-center! gl-hover-text-white! gl-transition-medium! float-right" |         class="gl-w-auto! gl-mt-3 gl-text-center! gl-hover-text-white! gl-transition-medium! float-right" | ||||||
|         category="primary" |         category="primary" | ||||||
|         variant="success" |         variant="confirm" | ||||||
|         data-qa-selector="commit_with_custom_message_button" |         data-qa-selector="commit_with_custom_message_button" | ||||||
|         @click="onApply" |         @click="onApply" | ||||||
|       > |       > | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ class RegistrationsController < Devise::RegistrationsController | ||||||
| 
 | 
 | ||||||
|   def new |   def new | ||||||
|     @resource = build_resource |     @resource = build_resource | ||||||
|  |     push_frontend_feature_flag(:gitlab_gtm_datalayer, type: :ops) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def create |   def create | ||||||
|  |  | ||||||
|  | @ -177,15 +177,13 @@ module AuthHelper | ||||||
|   def google_tag_manager_enabled? |   def google_tag_manager_enabled? | ||||||
|     return false unless Gitlab.dev_env_or_com? |     return false unless Gitlab.dev_env_or_com? | ||||||
| 
 | 
 | ||||||
|     has_config_key = if Feature.enabled?(:gtm_nonce, type: :ops) |     if Feature.enabled?(:gtm_nonce, type: :ops) | ||||||
|                        extra_config.has_key?('google_tag_manager_nonce_id') && |       extra_config.has_key?('google_tag_manager_nonce_id') && | ||||||
|                           extra_config.google_tag_manager_nonce_id.present? |          extra_config.google_tag_manager_nonce_id.present? | ||||||
|                      else |     else | ||||||
|                        extra_config.has_key?('google_tag_manager_id') && |       extra_config.has_key?('google_tag_manager_id') && | ||||||
|                           extra_config.google_tag_manager_id.present? |          extra_config.google_tag_manager_id.present? | ||||||
|                      end |     end | ||||||
| 
 |  | ||||||
|     has_config_key && !current_user |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def google_tag_manager_id |   def google_tag_manager_id | ||||||
|  |  | ||||||
|  | @ -13,6 +13,10 @@ module TrackingHelper | ||||||
|     } |     } | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def tracking_attrs_data(label, action, property) | ||||||
|  |     tracking_attrs(label, action, property).fetch(:data, {}) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   private |   private | ||||||
| 
 | 
 | ||||||
|   def tracking_enabled? |   def tracking_enabled? | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
|   = _("Create an account using:") |   = _("Create an account using:") | ||||||
| .gl-display-flex.gl-justify-content-between.gl-flex-wrap | .gl-display-flex.gl-justify-content-between.gl-flex-wrap | ||||||
|   - providers.each do |provider| |   - providers.each do |provider| | ||||||
|     = link_to omniauth_authorize_path(:user, provider), method: :post, class: "btn gl-button btn-default gl-w-full gl-mb-3 js-oauth-login #{qa_class_for_provider(provider)}", id: "oauth-login-#{provider}" do |     = link_to omniauth_authorize_path(:user, provider), method: :post, class: "btn gl-button btn-default gl-w-full gl-mb-3 js-oauth-login #{qa_class_for_provider(provider)}", data: { provider: provider }, id: "oauth-login-#{provider}" do | ||||||
|       - if provider_has_icon?(provider) |       - if provider_has_icon?(provider) | ||||||
|         = provider_image_tag(provider) |         = provider_image_tag(provider) | ||||||
|       %span.gl-button-text |       %span.gl-button-text | ||||||
|  |  | ||||||
|  | @ -8,22 +8,22 @@ | ||||||
|     .import-buttons |     .import-buttons | ||||||
|       - if gitlab_project_import_enabled? |       - if gitlab_project_import_enabled? | ||||||
|         .import_gitlab_project.has-tooltip{ data: { container: 'body', qa_selector: 'gitlab_import_button' } } |         .import_gitlab_project.has-tooltip{ data: { container: 'body', qa_selector: 'gitlab_import_button' } } | ||||||
|           = link_to new_import_gitlab_project_path, class: 'gl-button btn-default btn btn_import_gitlab_project', **tracking_attrs(track_label, 'click_button', 'gitlab_export') do |           = link_to new_import_gitlab_project_path, class: 'gl-button btn-default btn btn_import_gitlab_project js-import-project-btn', **tracking_attrs(track_label, 'click_button', 'gitlab_export') do | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = sprite_icon('tanuki') |               = sprite_icon('tanuki') | ||||||
|             = _("GitLab export") |             = _("GitLab export") | ||||||
| 
 | 
 | ||||||
|       - if github_import_enabled? |       - if github_import_enabled? | ||||||
|         %div |         %div | ||||||
|           = link_to new_import_github_path, class: 'gl-button btn-default btn js-import-github', **tracking_attrs(track_label, 'click_button', 'github') do |           = link_to new_import_github_path, class: 'gl-button btn-default btn js-import-github js-import-project-btn', data: { platform: 'github', **tracking_attrs_data(track_label, 'click_button', 'github') } do | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = sprite_icon('github') |               = sprite_icon('github') | ||||||
|             GitHub |             GitHub | ||||||
| 
 | 
 | ||||||
|       - if bitbucket_import_enabled? |       - if bitbucket_import_enabled? | ||||||
|         %div |         %div | ||||||
|           = link_to status_import_bitbucket_path, class: "gl-button btn-default btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}", |           = link_to status_import_bitbucket_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn #{'how_to_import_link' unless bitbucket_import_configured?}", | ||||||
|           **tracking_attrs(track_label, 'click_button', 'bitbucket_cloud') do |           data: { platform: 'bitbucket_cloud', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_cloud') } do | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = sprite_icon('bitbucket') |               = sprite_icon('bitbucket') | ||||||
|             Bitbucket Cloud |             Bitbucket Cloud | ||||||
|  | @ -31,15 +31,14 @@ | ||||||
|             = render 'projects/bitbucket_import_modal' |             = render 'projects/bitbucket_import_modal' | ||||||
|       - if bitbucket_server_import_enabled? |       - if bitbucket_server_import_enabled? | ||||||
|         %div |         %div | ||||||
|           = link_to status_import_bitbucket_server_path, class: "gl-button btn-default btn import_bitbucket", **tracking_attrs(track_label, 'click_button', 'bitbucket_server') do |           = link_to status_import_bitbucket_server_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn", data: { platform: 'bitbucket_server', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_server') } do | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = sprite_icon('bitbucket') |               = sprite_icon('bitbucket') | ||||||
|             Bitbucket Server |             Bitbucket Server | ||||||
|         %div |         %div | ||||||
|       - if gitlab_import_enabled? |       - if gitlab_import_enabled? | ||||||
|         %div |         %div | ||||||
|           = link_to status_import_gitlab_path, class: "gl-button btn-default btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}", |           = link_to status_import_gitlab_path, class: "gl-button btn-default btn import_gitlab js-import-project-btn #{'how_to_import_link' unless gitlab_import_configured?}", data: { platform: 'gitlab_com', **tracking_attrs_data(track_label, 'click_button', 'gitlab_com') } do | ||||||
|           **tracking_attrs(track_label, 'click_button', 'gitlab_com') do |  | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = sprite_icon('tanuki') |               = sprite_icon('tanuki') | ||||||
|             = _("GitLab.com") |             = _("GitLab.com") | ||||||
|  | @ -48,35 +47,35 @@ | ||||||
| 
 | 
 | ||||||
|       - if fogbugz_import_enabled? |       - if fogbugz_import_enabled? | ||||||
|         %div |         %div | ||||||
|           = link_to new_import_fogbugz_path, class: 'gl-button btn-default btn import_fogbugz', **tracking_attrs(track_label, 'click_button', 'fogbugz') do |           = link_to new_import_fogbugz_path, class: 'gl-button btn-default btn import_fogbugz js-import-project-btn', data: { platform: 'fogbugz', **tracking_attrs_data(track_label, 'click_button', 'fogbugz') } do | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = sprite_icon('bug') |               = sprite_icon('bug') | ||||||
|             FogBugz |             FogBugz | ||||||
| 
 | 
 | ||||||
|       - if gitea_import_enabled? |       - if gitea_import_enabled? | ||||||
|         %div |         %div | ||||||
|           = link_to new_import_gitea_path, class: 'gl-button btn-default btn import_gitea', **tracking_attrs(track_label, 'click_button', 'gitea') do |           = link_to new_import_gitea_path, class: 'gl-button btn-default btn import_gitea js-import-project-btn', data: { platform: 'gitea', **tracking_attrs_data(track_label, 'click_button', 'gitea') } do | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = custom_icon('gitea_logo') |               = custom_icon('gitea_logo') | ||||||
|             Gitea |             Gitea | ||||||
| 
 | 
 | ||||||
|       - if git_import_enabled? |       - if git_import_enabled? | ||||||
|         %div |         %div | ||||||
|           %button.gl-button.btn-default.btn.btn-svg.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' }, **tracking_attrs(track_label, 'click_button', 'repo_url') } |           %button.gl-button.btn-default.btn.btn-svg.js-toggle-button.js-import-git-toggle-button.js-import-project-btn{ type: "button", data: { platform: 'repo_url', toggle_open_class: 'active', **tracking_attrs_data(track_label, 'click_button', 'repo_url') } } | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = sprite_icon('link', css_class: 'gl-icon') |               = sprite_icon('link', css_class: 'gl-icon') | ||||||
|             = _('Repo by URL') |             = _('Repo by URL') | ||||||
| 
 | 
 | ||||||
|       - if manifest_import_enabled? |       - if manifest_import_enabled? | ||||||
|         %div |         %div | ||||||
|           = link_to new_import_manifest_path, class: 'gl-button btn-default btn import_manifest', **tracking_attrs(track_label, 'click_button', 'manifest_file') do |           = link_to new_import_manifest_path, class: 'gl-button btn-default btn import_manifest js-import-project-btn', data: { platform: 'manifest_file', **tracking_attrs_data(track_label, 'click_button', 'manifest_file') } do | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = sprite_icon('doc-text') |               = sprite_icon('doc-text') | ||||||
|             Manifest file |             Manifest file | ||||||
| 
 | 
 | ||||||
|       - if phabricator_import_enabled? |       - if phabricator_import_enabled? | ||||||
|         %div |         %div | ||||||
|           = link_to new_import_phabricator_path, class: 'gl-button btn-default btn import_phabricator', data: { track_label: "#{track_label}", track_action: "click_button", track_property: "phabricator" } do |           = link_to new_import_phabricator_path, class: 'gl-button btn-default btn import_phabricator js-import-project-btn', data: { platform: 'phabricator', track_label: "#{track_label}", track_action: "click_button", track_property: "phabricator" } do | ||||||
|             .gl-button-icon |             .gl-button-icon | ||||||
|               = custom_icon('issues') |               = custom_icon('issues') | ||||||
|             = _("Phabricator Tasks") |             = _("Phabricator Tasks") | ||||||
|  |  | ||||||
|  | @ -2,6 +2,10 @@ | ||||||
| - page_title _('Your profile') | - page_title _('Your profile') | ||||||
| - add_page_specific_style 'page_bundles/signup' | - add_page_specific_style 'page_bundles/signup' | ||||||
| - gitlab_experience_text = _('To personalize your GitLab experience, we\'d like to know a bit more about you') | - gitlab_experience_text = _('To personalize your GitLab experience, we\'d like to know a bit more about you') | ||||||
|  | - content_for :page_specific_javascripts do | ||||||
|  |   = render "layouts/google_tag_manager_head" | ||||||
|  |   = render "layouts/one_trust" | ||||||
|  | = render "layouts/google_tag_manager_body" | ||||||
| 
 | 
 | ||||||
| .row.gl-flex-grow-1 | .row.gl-flex-grow-1 | ||||||
|   .d-flex.gl-flex-direction-column.gl-align-items-center.gl-w-full.gl-px-5.gl-pb-5 |   .d-flex.gl-flex-direction-column.gl-align-items-center.gl-w-full.gl-px-5.gl-pb-5 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | --- | ||||||
|  | name: gitlab_gtm_datalayer | ||||||
|  | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76305 | ||||||
|  | rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348932 | ||||||
|  | milestone: '14.6' | ||||||
|  | type: ops | ||||||
|  | group: group::buyer experience | ||||||
|  | default_enabled: false | ||||||
|  | @ -5,7 +5,7 @@ | ||||||
| if defined?(ActiveRecord::Base) && !Gitlab::Runtime.sidekiq? | if defined?(ActiveRecord::Base) && !Gitlab::Runtime.sidekiq? | ||||||
|   Gitlab::Cluster::LifecycleEvents.on_worker_start do |   Gitlab::Cluster::LifecycleEvents.on_worker_start do | ||||||
|     ActiveSupport.on_load(:active_record) do |     ActiveSupport.on_load(:active_record) do | ||||||
|       ActiveRecord::Base.establish_connection |       ActiveRecord::Base.establish_connection # rubocop: disable Database/EstablishConnection | ||||||
| 
 | 
 | ||||||
|       Gitlab::AppLogger.debug("ActiveRecord connection established") |       Gitlab::AppLogger.debug("ActiveRecord connection established") | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -13,6 +13,6 @@ Gitlab.ee do | ||||||
|     # The Geo::TrackingBase model does not yet use connects_to. So, |     # The Geo::TrackingBase model does not yet use connects_to. So, | ||||||
|     # this will not properly support geo: from config/databse.yml |     # this will not properly support geo: from config/databse.yml | ||||||
|     # file yet. This is ACK of the current state and will be fixed. |     # file yet. This is ACK of the current state and will be fixed. | ||||||
|     Geo::TrackingBase.establish_connection(Gitlab::Database.geo_db_config_with_default_pool_size) |     Geo::TrackingBase.establish_connection(Gitlab::Database.geo_db_config_with_default_pool_size) # rubocop: disable Database/EstablishConnection | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -29,48 +29,41 @@ module Gitlab | ||||||
|         'target_branch' => ->(merge_request, _) { merge_request.target_branch.to_s }, |         'target_branch' => ->(merge_request, _) { merge_request.target_branch.to_s }, | ||||||
|         'title' => ->(merge_request, _) { merge_request.title }, |         'title' => ->(merge_request, _) { merge_request.title }, | ||||||
|         'issues' => ->(merge_request, _) do |         'issues' => ->(merge_request, _) do | ||||||
|           return "" if merge_request.visible_closing_issues_for.blank? |           return if merge_request.visible_closing_issues_for.blank? | ||||||
| 
 | 
 | ||||||
|           closes_issues_references = merge_request.visible_closing_issues_for.map do |issue| |           closes_issues_references = merge_request.visible_closing_issues_for.map do |issue| | ||||||
|             issue.to_reference(merge_request.target_project) |             issue.to_reference(merge_request.target_project) | ||||||
|           end |           end | ||||||
|           "Closes #{closes_issues_references.to_sentence}" |           "Closes #{closes_issues_references.to_sentence}" | ||||||
|         end, |         end, | ||||||
|         'description' => ->(merge_request, _) { merge_request.description.presence || '' }, |         'description' => ->(merge_request, _) { merge_request.description }, | ||||||
|         'reference' => ->(merge_request, _) { merge_request.to_reference(full: true) }, |         'reference' => ->(merge_request, _) { merge_request.to_reference(full: true) }, | ||||||
|         'first_commit' => -> (merge_request, _) { merge_request.first_commit&.safe_message&.strip.presence || '' }, |         'first_commit' => -> (merge_request, _) { merge_request.first_commit&.safe_message&.strip }, | ||||||
|         'first_multiline_commit' => -> (merge_request, _) { merge_request.first_multiline_commit&.safe_message&.strip.presence || merge_request.title }, |         'first_multiline_commit' => -> (merge_request, _) { merge_request.first_multiline_commit&.safe_message&.strip.presence || merge_request.title }, | ||||||
|         'url' => ->(merge_request, _) { Gitlab::UrlBuilder.build(merge_request) }, |         'url' => ->(merge_request, _) { Gitlab::UrlBuilder.build(merge_request) }, | ||||||
|         'approved_by' => ->(merge_request, _) { merge_request.approved_by_users.map { |user| "Approved-by: #{user.name} <#{user.commit_email_or_default}>" }.join("\n") }, |         'approved_by' => ->(merge_request, _) { merge_request.approved_by_users.map { |user| "Approved-by: #{user.name} <#{user.commit_email_or_default}>" }.join("\n") }, | ||||||
|         'merged_by' => ->(_, user) { "#{user&.name} <#{user&.commit_email_or_default}>" } |         'merged_by' => ->(_, user) { "#{user&.name} <#{user&.commit_email_or_default}>" } | ||||||
|       }.freeze |       }.freeze | ||||||
| 
 | 
 | ||||||
|       PLACEHOLDERS_REGEX = Regexp.union(PLACEHOLDERS.keys.map do |key| |       PLACEHOLDERS_COMBINED_REGEX = /%{(#{Regexp.union(PLACEHOLDERS.keys)})}/.freeze | ||||||
|         Regexp.new(Regexp.escape(key)) |  | ||||||
|       end).freeze |  | ||||||
| 
 |  | ||||||
|       BLANK_PLACEHOLDERS_REGEXES = (PLACEHOLDERS.map do |key, value| |  | ||||||
|         [key, Regexp.new("[\n\r]+%{#{Regexp.escape(key)}}$")] |  | ||||||
|       end).to_h.freeze |  | ||||||
| 
 | 
 | ||||||
|       def replace_placeholders(message) |       def replace_placeholders(message) | ||||||
|         # convert CRLF to LF |         # Convert CRLF to LF. | ||||||
|         message = message.delete("\r") |         message = message.delete("\r") | ||||||
| 
 | 
 | ||||||
|         # Remove placeholders that correspond to empty values and are the last word in the line |         used_variables = message.scan(PLACEHOLDERS_COMBINED_REGEX).map { |value| value[0] }.uniq | ||||||
|         # along with all whitespace characters preceding them. |         values = used_variables.to_h do |variable_name| | ||||||
|         # This allows us to recreate previous default merge commit message behaviour - we skipped new line character |           ["%{#{variable_name}}", PLACEHOLDERS[variable_name].call(merge_request, current_user)] | ||||||
|         # before empty description and before closed issues when none were present. |  | ||||||
|         PLACEHOLDERS.each do |key, value| |  | ||||||
|           unless value.call(merge_request, current_user).present? |  | ||||||
|             message = message.gsub(BLANK_PLACEHOLDERS_REGEXES[key], '') |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
|  |         names_of_empty_variables = values.filter_map { |name, value| name if value.blank? } | ||||||
| 
 | 
 | ||||||
|         Gitlab::StringPlaceholderReplacer |         # Remove placeholders that correspond to empty values and are the only word in a line | ||||||
|           .replace_string_placeholders(message, PLACEHOLDERS_REGEX) do |key| |         # along with all whitespace characters preceding them. | ||||||
|           PLACEHOLDERS[key].call(merge_request, current_user) |         message = message.gsub(/[\n\r]+#{Regexp.union(names_of_empty_variables)}$/, '') if names_of_empty_variables.present? | ||||||
|         end |         # Substitute all variables with their values. | ||||||
|  |         message = message.gsub(Regexp.union(values.keys), values) if values.present? | ||||||
|  | 
 | ||||||
|  |         message | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -170,7 +170,7 @@ namespace :gitlab do | ||||||
|       # the `ActiveRecord::Base.connection` might be switched to another one |       # the `ActiveRecord::Base.connection` might be switched to another one | ||||||
|       # This is due to `if should_reconnect`: |       # This is due to `if should_reconnect`: | ||||||
|       # https://github.com/rails/rails/blob/a81aeb63a007ede2fe606c50539417dada9030c7/activerecord/lib/active_record/railties/databases.rake#L622 |       # https://github.com/rails/rails/blob/a81aeb63a007ede2fe606c50539417dada9030c7/activerecord/lib/active_record/railties/databases.rake#L622 | ||||||
|       ActiveRecord::Base.establish_connection :main |       ActiveRecord::Base.establish_connection :main # rubocop: disable Database/EstablishConnection | ||||||
| 
 | 
 | ||||||
|       Rake::Task['gitlab:db:create_dynamic_partitions'].invoke |       Rake::Task['gitlab:db:create_dynamic_partitions'].invoke | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -10126,9 +10126,6 @@ msgstr "" | ||||||
| msgid "CreateValueStreamForm|Code stage start" | msgid "CreateValueStreamForm|Code stage start" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "CreateValueStreamForm|Create Value Stream" |  | ||||||
| msgstr "" |  | ||||||
| 
 |  | ||||||
| msgid "CreateValueStreamForm|Create from default template" | msgid "CreateValueStreamForm|Create from default template" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -10138,13 +10135,16 @@ msgstr "" | ||||||
| msgid "CreateValueStreamForm|Create new Value Stream" | msgid "CreateValueStreamForm|Create new Value Stream" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "CreateValueStreamForm|Create value stream" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "CreateValueStreamForm|Default stages" | msgid "CreateValueStreamForm|Default stages" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "CreateValueStreamForm|Default stages can only be hidden or re-ordered" | msgid "CreateValueStreamForm|Default stages can only be hidden or re-ordered" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "CreateValueStreamForm|Edit Value Stream" | msgid "CreateValueStreamForm|Edit value stream" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "CreateValueStreamForm|Editing stage" | msgid "CreateValueStreamForm|Editing stage" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module RuboCop | ||||||
|  |   module Cop | ||||||
|  |     module Database | ||||||
|  |       class EstablishConnection < RuboCop::Cop::Cop | ||||||
|  |         MSG = "Don't establish new database connections, as this slows down " \ | ||||||
|  |           'tests and may result in new connections using an incorrect configuration' | ||||||
|  | 
 | ||||||
|  |         def_node_matcher :establish_connection?, <<~PATTERN | ||||||
|  |           (send (const ...) :establish_connection ...) | ||||||
|  |         PATTERN | ||||||
|  | 
 | ||||||
|  |         def on_send(node) | ||||||
|  |           add_offense(node, location: :expression) if establish_connection?(node) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -12,7 +12,7 @@ module RspecProfiling | ||||||
|         # This disables the automatic creation of the database and |         # This disables the automatic creation of the database and | ||||||
|         # table. In the future, we may want a way to specify the host of |         # table. In the future, we may want a way to specify the host of | ||||||
|         # the database to connect so that we can call #install. |         # the database to connect so that we can call #install. | ||||||
|         Result.establish_connection(results_url) |         Result.establish_connection(results_url) # rubocop: disable Database/EstablishConnection | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def results_url |       def results_url | ||||||
|  |  | ||||||
|  | @ -0,0 +1,250 @@ | ||||||
|  | import { merge } from 'lodash'; | ||||||
|  | import { | ||||||
|  |   trackFreeTrialAccountSubmissions, | ||||||
|  |   trackNewRegistrations, | ||||||
|  |   trackSaasTrialSubmit, | ||||||
|  |   trackSaasTrialSkip, | ||||||
|  |   trackSaasTrialGroup, | ||||||
|  |   trackSaasTrialProject, | ||||||
|  |   trackSaasTrialProjectImport, | ||||||
|  |   trackSaasTrialGetStarted, | ||||||
|  | } from '~/google_tag_manager'; | ||||||
|  | import { setHTMLFixture } from 'helpers/fixtures'; | ||||||
|  | import { logError } from '~/lib/logger'; | ||||||
|  | 
 | ||||||
|  | jest.mock('~/lib/logger'); | ||||||
|  | 
 | ||||||
|  | describe('~/google_tag_manager/index', () => { | ||||||
|  |   let spy; | ||||||
|  | 
 | ||||||
|  |   beforeEach(() => { | ||||||
|  |     spy = jest.fn(); | ||||||
|  | 
 | ||||||
|  |     window.dataLayer = { | ||||||
|  |       push: spy, | ||||||
|  |     }; | ||||||
|  |     window.gon.features = { | ||||||
|  |       gitlabGtmDatalayer: true, | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const createHTML = ({ links = [], forms = [] } = {}) => { | ||||||
|  |     // .foo elements are used to test elements which shouldn't do anything
 | ||||||
|  |     const allLinks = links.concat({ cls: 'foo' }); | ||||||
|  |     const allForms = forms.concat({ cls: 'foo' }); | ||||||
|  | 
 | ||||||
|  |     const el = document.createElement('div'); | ||||||
|  | 
 | ||||||
|  |     allLinks.forEach(({ cls = '', id = '', href = '#', text = 'Hello', attributes = {} }) => { | ||||||
|  |       const a = document.createElement('a'); | ||||||
|  |       a.id = id; | ||||||
|  |       a.href = href || '#'; | ||||||
|  |       a.className = cls; | ||||||
|  |       a.textContent = text; | ||||||
|  | 
 | ||||||
|  |       Object.entries(attributes).forEach(([key, value]) => { | ||||||
|  |         a.setAttribute(key, value); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       el.append(a); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     allForms.forEach(({ cls = '', id = '' }) => { | ||||||
|  |       const form = document.createElement('form'); | ||||||
|  |       form.id = id; | ||||||
|  |       form.className = cls; | ||||||
|  | 
 | ||||||
|  |       el.append(form); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return el.innerHTML; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const triggerEvent = (selector, eventType) => { | ||||||
|  |     const el = document.querySelector(selector); | ||||||
|  | 
 | ||||||
|  |     el.dispatchEvent(new Event(eventType)); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const getSelector = ({ id, cls }) => (id ? `#${id}` : `.${cls}`); | ||||||
|  | 
 | ||||||
|  |   const createTestCase = (subject, { forms = [], links = [] }) => { | ||||||
|  |     const expectedFormEvents = forms.map(({ expectation, ...form }) => ({ | ||||||
|  |       selector: getSelector(form), | ||||||
|  |       trigger: 'submit', | ||||||
|  |       expectation, | ||||||
|  |     })); | ||||||
|  | 
 | ||||||
|  |     const expectedLinkEvents = links.map(({ expectation, ...link }) => ({ | ||||||
|  |       selector: getSelector(link), | ||||||
|  |       trigger: 'click', | ||||||
|  |       expectation, | ||||||
|  |     })); | ||||||
|  | 
 | ||||||
|  |     return [ | ||||||
|  |       subject, | ||||||
|  |       { | ||||||
|  |         forms, | ||||||
|  |         links, | ||||||
|  |         expectedEvents: [...expectedFormEvents, ...expectedLinkEvents], | ||||||
|  |       }, | ||||||
|  |     ]; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const createOmniAuthTestCase = (subject, accountType) => | ||||||
|  |     createTestCase(subject, { | ||||||
|  |       forms: [ | ||||||
|  |         { | ||||||
|  |           id: 'new_new_user', | ||||||
|  |           expectation: { | ||||||
|  |             event: 'accountSubmit', | ||||||
|  |             accountMethod: 'form', | ||||||
|  |             accountType, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |       links: [ | ||||||
|  |         { | ||||||
|  |           // id is needed so that the test selects the right element to trigger
 | ||||||
|  |           id: 'test-0', | ||||||
|  |           cls: 'js-oauth-login', | ||||||
|  |           attributes: { | ||||||
|  |             'data-provider': 'myspace', | ||||||
|  |           }, | ||||||
|  |           expectation: { | ||||||
|  |             event: 'accountSubmit', | ||||||
|  |             accountMethod: 'myspace', | ||||||
|  |             accountType, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           id: 'test-1', | ||||||
|  |           cls: 'js-oauth-login', | ||||||
|  |           attributes: { | ||||||
|  |             'data-provider': 'gitlab', | ||||||
|  |           }, | ||||||
|  |           expectation: { | ||||||
|  |             event: 'accountSubmit', | ||||||
|  |             accountMethod: 'gitlab', | ||||||
|  |             accountType, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |   describe.each([ | ||||||
|  |     createOmniAuthTestCase(trackFreeTrialAccountSubmissions, 'freeThirtyDayTrial'), | ||||||
|  |     createOmniAuthTestCase(trackNewRegistrations, 'standardSignUp'), | ||||||
|  |     createTestCase(trackSaasTrialSubmit, { | ||||||
|  |       forms: [{ id: 'new_trial', expectation: { event: 'saasTrialSubmit' } }], | ||||||
|  |     }), | ||||||
|  |     createTestCase(trackSaasTrialSkip, { | ||||||
|  |       links: [{ cls: 'js-skip-trial', expectation: { event: 'saasTrialSkip' } }], | ||||||
|  |     }), | ||||||
|  |     createTestCase(trackSaasTrialGroup, { | ||||||
|  |       forms: [{ id: 'new_group', expectation: { event: 'saasTrialGroup' } }], | ||||||
|  |     }), | ||||||
|  |     createTestCase(trackSaasTrialProject, { | ||||||
|  |       forms: [{ id: 'new_project', expectation: { event: 'saasTrialProject' } }], | ||||||
|  |     }), | ||||||
|  |     createTestCase(trackSaasTrialProjectImport, { | ||||||
|  |       links: [ | ||||||
|  |         { | ||||||
|  |           id: 'js-test-btn-0', | ||||||
|  |           cls: 'js-import-project-btn', | ||||||
|  |           attributes: { 'data-platform': 'bitbucket' }, | ||||||
|  |           expectation: { event: 'saasTrialProjectImport', saasProjectImport: 'bitbucket' }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           // id is neeeded so we trigger the right element in the test
 | ||||||
|  |           id: 'js-test-btn-1', | ||||||
|  |           cls: 'js-import-project-btn', | ||||||
|  |           attributes: { 'data-platform': 'github' }, | ||||||
|  |           expectation: { event: 'saasTrialProjectImport', saasProjectImport: 'github' }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }), | ||||||
|  |     createTestCase(trackSaasTrialGetStarted, { | ||||||
|  |       links: [ | ||||||
|  |         { | ||||||
|  |           cls: 'js-get-started-btn', | ||||||
|  |           expectation: { event: 'saasTrialGetStarted' }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }), | ||||||
|  |   ])('%p', (subject, { links = [], forms = [], expectedEvents }) => { | ||||||
|  |     beforeEach(() => { | ||||||
|  |       setHTMLFixture(createHTML({ links, forms })); | ||||||
|  | 
 | ||||||
|  |       subject(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it.each(expectedEvents)('when %p', ({ selector, trigger, expectation }) => { | ||||||
|  |       expect(spy).not.toHaveBeenCalled(); | ||||||
|  | 
 | ||||||
|  |       triggerEvent(selector, trigger); | ||||||
|  | 
 | ||||||
|  |       expect(spy).toHaveBeenCalledTimes(1); | ||||||
|  |       expect(spy).toHaveBeenCalledWith(expectation); | ||||||
|  |       expect(logError).not.toHaveBeenCalled(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('when random link is clicked, does nothing', () => { | ||||||
|  |       triggerEvent('a.foo', 'click'); | ||||||
|  | 
 | ||||||
|  |       expect(spy).not.toHaveBeenCalled(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('when random form is submitted, does nothing', () => { | ||||||
|  |       triggerEvent('form.foo', 'submit'); | ||||||
|  | 
 | ||||||
|  |       expect(spy).not.toHaveBeenCalled(); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe.each([ | ||||||
|  |     { dataLayer: null }, | ||||||
|  |     { gon: { features: null } }, | ||||||
|  |     { gon: { features: { gitlabGtmDatalayer: false } } }, | ||||||
|  |   ])('when window %o', (windowAttrs) => { | ||||||
|  |     beforeEach(() => { | ||||||
|  |       merge(window, windowAttrs); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('no ops', () => { | ||||||
|  |       setHTMLFixture(createHTML({ forms: [{ id: 'new_project' }] })); | ||||||
|  | 
 | ||||||
|  |       trackSaasTrialProject(); | ||||||
|  | 
 | ||||||
|  |       triggerEvent('#new_project', 'submit'); | ||||||
|  | 
 | ||||||
|  |       expect(spy).not.toHaveBeenCalled(); | ||||||
|  |       expect(logError).not.toHaveBeenCalled(); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('when window.dataLayer throws error', () => { | ||||||
|  |     const pushError = new Error('test'); | ||||||
|  | 
 | ||||||
|  |     beforeEach(() => { | ||||||
|  |       window.dataLayer = { | ||||||
|  |         push() { | ||||||
|  |           throw pushError; | ||||||
|  |         }, | ||||||
|  |       }; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('logs error', () => { | ||||||
|  |       setHTMLFixture(createHTML({ forms: [{ id: 'new_project' }] })); | ||||||
|  | 
 | ||||||
|  |       trackSaasTrialProject(); | ||||||
|  | 
 | ||||||
|  |       triggerEvent('#new_project', 'submit'); | ||||||
|  | 
 | ||||||
|  |       expect(logError).toHaveBeenCalledWith( | ||||||
|  |         'Unexpected error while pushing to dataLayer', | ||||||
|  |         pushError, | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -312,12 +312,6 @@ RSpec.describe AuthHelper do | ||||||
|           it { is_expected.to be_truthy } |           it { is_expected.to be_truthy } | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         context 'when current user is set' do |  | ||||||
|           let(:user) { instance_double('User') } |  | ||||||
| 
 |  | ||||||
|           it { is_expected.to eq(false) } |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         context 'when no key is set' do |         context 'when no key is set' do | ||||||
|           before do |           before do | ||||||
|             stub_config(extra: {}) |             stub_config(extra: {}) | ||||||
|  |  | ||||||
|  | @ -101,7 +101,7 @@ RSpec.describe Gitlab::Database::BulkUpdate do | ||||||
|     before do |     before do | ||||||
|       configuration_hash = ActiveRecord::Base.connection_db_config.configuration_hash |       configuration_hash = ActiveRecord::Base.connection_db_config.configuration_hash | ||||||
| 
 | 
 | ||||||
|       ActiveRecord::Base.establish_connection( |       ActiveRecord::Base.establish_connection( # rubocop: disable Database/EstablishConnection | ||||||
|         configuration_hash.merge(prepared_statements: prepared_statements) |         configuration_hash.merge(prepared_statements: prepared_statements) | ||||||
|       ) |       ) | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -58,6 +58,19 @@ RSpec.describe Gitlab::MergeRequests::CommitMessageGenerator do | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     context 'when project has commit template with only the title' do | ||||||
|  |       let(:merge_request) do | ||||||
|  |         double(:merge_request, title: 'Fixes', target_project: project, to_reference: '!123', metrics: nil, merge_user: nil) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       let(message_template_name) { '%{title}' } | ||||||
|  | 
 | ||||||
|  |       it 'evaluates only necessary variables' do | ||||||
|  |         expect(result_message).to eq 'Fixes' | ||||||
|  |         expect(merge_request).not_to have_received(:to_reference) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     context 'when project has commit template with closed issues' do |     context 'when project has commit template with closed issues' do | ||||||
|       let(message_template_name) { <<~MSG.rstrip } |       let(message_template_name) { <<~MSG.rstrip } | ||||||
|         Merge branch '%{source_branch}' into '%{target_branch}' |         Merge branch '%{source_branch}' into '%{target_branch}' | ||||||
|  | @ -381,6 +394,57 @@ RSpec.describe Gitlab::MergeRequests::CommitMessageGenerator do | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|  |     context 'when project has commit template with the same variable used twice' do | ||||||
|  |       let(message_template_name) { '%{title} %{title}' } | ||||||
|  | 
 | ||||||
|  |       it 'uses custom template' do | ||||||
|  |         expect(result_message).to eq 'Bugfix Bugfix' | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when project has commit template without any variable' do | ||||||
|  |       let(message_template_name) { 'static text' } | ||||||
|  | 
 | ||||||
|  |       it 'uses custom template' do | ||||||
|  |         expect(result_message).to eq 'static text' | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when project has template with all variables' do | ||||||
|  |       let(message_template_name) { <<~MSG.rstrip } | ||||||
|  |         source_branch:%{source_branch} | ||||||
|  |         target_branch:%{target_branch} | ||||||
|  |         title:%{title} | ||||||
|  |         issues:%{issues} | ||||||
|  |         description:%{description} | ||||||
|  |         first_commit:%{first_commit} | ||||||
|  |         first_multiline_commit:%{first_multiline_commit} | ||||||
|  |         url:%{url} | ||||||
|  |         approved_by:%{approved_by} | ||||||
|  |         merged_by:%{merged_by} | ||||||
|  |       MSG | ||||||
|  | 
 | ||||||
|  |       it 'uses custom template' do | ||||||
|  |         expect(result_message).to eq <<~MSG.rstrip | ||||||
|  |           source_branch:feature | ||||||
|  |           target_branch:master | ||||||
|  |           title:Bugfix | ||||||
|  |           issues: | ||||||
|  |           description:Merge Request Description | ||||||
|  |           Next line | ||||||
|  |           first_commit:Feature added | ||||||
|  | 
 | ||||||
|  |           Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | ||||||
|  |           first_multiline_commit:Feature added | ||||||
|  | 
 | ||||||
|  |           Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | ||||||
|  |           url:#{Gitlab::UrlBuilder.build(merge_request)} | ||||||
|  |           approved_by: | ||||||
|  |           merged_by:#{maintainer.name} <#{maintainer.commit_email_or_default}> | ||||||
|  |         MSG | ||||||
|  |       end | ||||||
|  |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#merge_message' do |   describe '#merge_message' do | ||||||
|  |  | ||||||
|  | @ -1647,13 +1647,10 @@ RSpec.describe MergeRequest, factory_default: :keep do | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'uses template from target project' do |     it 'uses template from target project' do | ||||||
|       subject.title = 'Fix everything' |       request = build(:merge_request, title: 'Fix everything') | ||||||
|       subject.compare_commits = [ |       request.target_project.merge_commit_template = '%{title}' | ||||||
|         double(safe_message: 'Commit message', gitaly_commit?: true, merge_commit?: false, description?: false) |  | ||||||
|       ] |  | ||||||
|       subject.target_project.merge_commit_template = '%{title}' |  | ||||||
| 
 | 
 | ||||||
|       expect(subject.default_merge_commit_message) |       expect(request.default_merge_commit_message) | ||||||
|         .to eq('Fix everything') |         .to eq('Fix everything') | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | require_relative '../../../../rubocop/cop/database/establish_connection' | ||||||
|  | 
 | ||||||
|  | RSpec.describe RuboCop::Cop::Database::EstablishConnection do | ||||||
|  |   subject(:cop) { described_class.new } | ||||||
|  | 
 | ||||||
|  |   it 'flags the use of ActiveRecord::Base.establish_connection' do | ||||||
|  |     expect_offense(<<~CODE) | ||||||
|  |       ActiveRecord::Base.establish_connection | ||||||
|  |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't establish new database [...] | ||||||
|  |     CODE | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it 'flags the use of ActiveRecord::Base.establish_connection with arguments' do | ||||||
|  |     expect_offense(<<~CODE) | ||||||
|  |       ActiveRecord::Base.establish_connection(:foo) | ||||||
|  |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't establish new database [...] | ||||||
|  |     CODE | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it 'flags the use of SomeModel.establish_connection' do | ||||||
|  |     expect_offense(<<~CODE) | ||||||
|  |       SomeModel.establish_connection | ||||||
|  |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't establish new database [...] | ||||||
|  |     CODE | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -67,7 +67,7 @@ module DbCleaner | ||||||
|     # Migrate each database individually |     # Migrate each database individually | ||||||
|     with_reestablished_active_record_base do |     with_reestablished_active_record_base do | ||||||
|       all_connection_classes.each do |connection_class| |       all_connection_classes.each do |connection_class| | ||||||
|         ActiveRecord::Base.establish_connection(connection_class.connection_db_config) |         ActiveRecord::Base.establish_connection(connection_class.connection_db_config) # rubocop: disable Database/EstablishConnection | ||||||
| 
 | 
 | ||||||
|         ActiveRecord::Tasks::DatabaseTasks.migrate |         ActiveRecord::Tasks::DatabaseTasks.migrate | ||||||
|       end |       end | ||||||
|  |  | ||||||
|  | @ -59,7 +59,7 @@ module CycleAnalyticsHelpers | ||||||
|   def save_value_stream(custom_value_stream_name) |   def save_value_stream(custom_value_stream_name) | ||||||
|     fill_in 'create-value-stream-name', with: custom_value_stream_name |     fill_in 'create-value-stream-name', with: custom_value_stream_name | ||||||
| 
 | 
 | ||||||
|     page.find_button(s_('CreateValueStreamForm|Create Value Stream')).click |     page.find_button(s_('CreateValueStreamForm|Create value stream')).click | ||||||
|     wait_for_requests |     wait_for_requests | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,13 +7,13 @@ RSpec.describe 'Database::MultipleDatabases' do | ||||||
|     context 'when doing establish_connection' do |     context 'when doing establish_connection' do | ||||||
|       context 'on ActiveRecord::Base' do |       context 'on ActiveRecord::Base' do | ||||||
|         it 'raises exception' do |         it 'raises exception' do | ||||||
|           expect { ActiveRecord::Base.establish_connection(:main) }.to raise_error /Cannot re-establish/ |           expect { ActiveRecord::Base.establish_connection(:main) }.to raise_error /Cannot re-establish/ # rubocop: disable Database/EstablishConnection | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         context 'when using with_reestablished_active_record_base' do |         context 'when using with_reestablished_active_record_base' do | ||||||
|           it 'does not raise exception' do |           it 'does not raise exception' do | ||||||
|             with_reestablished_active_record_base do |             with_reestablished_active_record_base do | ||||||
|               expect { ActiveRecord::Base.establish_connection(:main) }.not_to raise_error |               expect { ActiveRecord::Base.establish_connection(:main) }.not_to raise_error # rubocop: disable Database/EstablishConnection | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|  | @ -25,13 +25,13 @@ RSpec.describe 'Database::MultipleDatabases' do | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         it 'raises exception' do |         it 'raises exception' do | ||||||
|           expect { Ci::ApplicationRecord.establish_connection(:ci) }.to raise_error /Cannot re-establish/ |           expect { Ci::ApplicationRecord.establish_connection(:ci) }.to raise_error /Cannot re-establish/ # rubocop: disable Database/EstablishConnection | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         context 'when using with_reestablished_active_record_base' do |         context 'when using with_reestablished_active_record_base' do | ||||||
|           it 'does not raise exception' do |           it 'does not raise exception' do | ||||||
|             with_reestablished_active_record_base do |             with_reestablished_active_record_base do | ||||||
|               expect { Ci::ApplicationRecord.establish_connection(:main) }.not_to raise_error |               expect { Ci::ApplicationRecord.establish_connection(:main) }.not_to raise_error # rubocop: disable Database/EstablishConnection | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue