diff options
28 files changed, 285 insertions, 311 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index e4d7706f28a..dd9b3c2da91 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -49112bb2b4d8ea8e46e0f0d5e22ba2296a65a1b2 +0c10f5a60848a35049400dd775126b3ad4481663 diff --git a/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue index ec7000120f1..f84ae13180d 100644 --- a/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue +++ b/app/assets/javascripts/pipelines/components/jobs/failed_jobs_table.vue @@ -3,10 +3,11 @@ import { GlButton, GlLink, GlTableLite } from '@gitlab/ui'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { __, s__ } from '~/locale'; import { createAlert } from '~/alert'; +import Tracking from '~/tracking'; import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; import RetryFailedJobMutation from '../../graphql/mutations/retry_failed_job.mutation.graphql'; -import { DEFAULT_FIELDS } from '../../constants'; +import { DEFAULT_FIELDS, TRACKING_CATEGORIES } from '../../constants'; export default { fields: DEFAULT_FIELDS, @@ -20,6 +21,7 @@ export default { directives: { SafeHtml, }, + mixins: [Tracking.mixin()], props: { failedJobs: { type: Array, @@ -28,6 +30,8 @@ export default { }, methods: { async retryJob(id) { + this.track('click_retry', { label: TRACKING_CATEGORIES.failed }); + try { const { data: { diff --git a/app/assets/javascripts/pipelines/components/pipeline_tabs.vue b/app/assets/javascripts/pipelines/components/pipeline_tabs.vue index e14644ae0d5..35dde6379dd 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_tabs.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_tabs.vue @@ -1,12 +1,14 @@ <script> import { GlBadge, GlTabs, GlTab } from '@gitlab/ui'; import { __ } from '~/locale'; +import Tracking from '~/tracking'; import { failedJobsTabName, jobsTabName, needsTabName, pipelineTabName, testReportTabName, + TRACKING_CATEGORIES, } from '../constants'; export default { @@ -31,6 +33,7 @@ export default { GlTab, GlTabs, }, + mixins: [Tracking.mixin()], inject: ['defaultTabValue', 'failedJobsCount', 'totalJobCount', 'testsCount'], data() { return { @@ -56,6 +59,16 @@ export default { this.$router.push({ name: tabName }); }, + failedJobsTabClick() { + this.track('click_tab', { label: TRACKING_CATEGORIES.failed }); + + this.navigateTo(this.$options.tabNames.failures); + }, + testsTabClick() { + this.track('click_tab', { label: TRACKING_CATEGORIES.tests }); + + this.navigateTo(this.$options.tabNames.tests); + }, }, }; </script> @@ -100,7 +113,7 @@ export default { :active="isActive($options.tabNames.failures)" data-testid="failed-jobs-tab" lazy - @click="navigateTo($options.tabNames.failures)" + @click="failedJobsTabClick" > <template #title> <span class="gl-mr-2">{{ $options.i18n.tabs.failedJobsTitle }}</span> @@ -112,7 +125,7 @@ export default { :active="isActive($options.tabNames.tests)" data-testid="tests-tab" lazy - @click="navigateTo($options.tabNames.tests)" + @click="testsTabClick" > <template #title> <span class="gl-mr-2">{{ $options.i18n.tabs.testsTitle }}</span> diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js index a6dd835bb15..d43e29dfae4 100644 --- a/app/assets/javascripts/pipelines/constants.js +++ b/app/assets/javascripts/pipelines/constants.js @@ -109,6 +109,8 @@ export const TRACKING_CATEGORIES = { table: 'pipelines_table_component', tabs: 'pipelines_filter_tabs', search: 'pipelines_filtered_search', + failed: 'pipeline_failed_jobs_tab', + tests: 'pipeline_tests_tab', }; // Pipeline Mini Graph diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue index 1b7fecfde98..27276e55b13 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue @@ -192,7 +192,7 @@ export default { > <gl-button icon="chevron-double-lg-left" - class="gutter-toggle gl-display-block gl-sm-display-none!" + class="gl-ml-auto gl-display-block gl-sm-display-none!" :aria-label="__('Expand sidebar')" @click="handleRightSidebarToggleClick" /> diff --git a/app/services/concerns/merge_requests/error_logger.rb b/app/services/concerns/merge_requests/error_logger.rb new file mode 100644 index 00000000000..c08525bf413 --- /dev/null +++ b/app/services/concerns/merge_requests/error_logger.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module MergeRequests + module ErrorLogger + def log_error(exception:, message:, save_message_on_model: false) + reference = merge_request.to_reference(full: true) + data = { + class: self.class.name, + message: message, + merge_request_id: merge_request.id, + merge_request: reference, + save_message_on_model: save_message_on_model + } + + if exception + Gitlab::ApplicationContext.with_context(user: current_user) do + Gitlab::ErrorTracking.track_exception(exception, data) + end + + data[:"exception.message"] = exception.message + end + + # TODO: Deprecate Gitlab::GitLogger since ErrorTracking should suffice: + # https://gitlab.com/gitlab-org/gitlab/-/issues/216379 + data[:message] = "#{self.class.name} error (#{reference}): #{message}" + Gitlab::GitLogger.error(data) + + merge_request.update(merge_error: message) if save_message_on_model + end + end +end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index aaa91548d19..7074bf5bd29 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -4,6 +4,7 @@ module MergeRequests class BaseService < ::IssuableBaseService extend ::Gitlab::Utils::Override include MergeRequests::AssignsMergeParams + include MergeRequests::ErrorLogger delegate :repository, to: :project @@ -260,32 +261,6 @@ module MergeRequests end end - def log_error(exception:, message:, save_message_on_model: false) - reference = merge_request.to_reference(full: true) - data = { - class: self.class.name, - message: message, - merge_request_id: merge_request.id, - merge_request: reference, - save_message_on_model: save_message_on_model - } - - if exception - Gitlab::ApplicationContext.with_context(user: current_user) do - Gitlab::ErrorTracking.track_exception(exception, data) - end - - data[:"exception.message"] = exception.message - end - - # TODO: Deprecate Gitlab::GitLogger since ErrorTracking should suffice: - # https://gitlab.com/gitlab-org/gitlab/-/issues/216379 - data[:message] = "#{self.class.name} error (#{reference}): #{message}" - Gitlab::GitLogger.error(data) - - merge_request.update(merge_error: message) if save_message_on_model - end - def trigger_merge_request_reviewers_updated(merge_request) GraphqlTriggers.merge_request_reviewers_updated(merge_request) end diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb index 2a3c1e8bc26..fa0a4f808e2 100644 --- a/app/services/merge_requests/merge_base_service.rb +++ b/app/services/merge_requests/merge_base_service.rb @@ -60,8 +60,11 @@ module MergeRequests end def squash_sha! - params[:merge_request] = merge_request - squash_result = ::MergeRequests::SquashService.new(project: project, current_user: current_user, params: params).execute + squash_result = ::MergeRequests::SquashService.new( + merge_request: merge_request, + current_user: current_user, + commit_message: params[:squash_commit_message] + ).execute case squash_result[:status] when :success diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb index f04682bf08a..0b1aefb30d7 100644 --- a/app/services/merge_requests/squash_service.rb +++ b/app/services/merge_requests/squash_service.rb @@ -1,7 +1,17 @@ # frozen_string_literal: true module MergeRequests - class SquashService < MergeRequests::BaseService + class SquashService + include BaseServiceUtility + include MergeRequests::ErrorLogger + + def initialize(merge_request:, current_user:, commit_message:) + @merge_request = merge_request + @target_project = merge_request.target_project + @current_user = current_user + @commit_message = commit_message + end + def execute # If performing a squash would result in no change, then # immediately return a success message without performing a squash @@ -16,6 +26,8 @@ module MergeRequests private + attr_reader :merge_request, :target_project, :current_user, :commit_message + def squash! squash_sha = repository.squash(current_user, merge_request, message) @@ -34,12 +46,8 @@ module MergeRequests target_project.repository end - def merge_request - params[:merge_request] - end - def message - params[:squash_commit_message].presence || merge_request.default_squash_commit_message(user: current_user) + commit_message.presence || merge_request.default_squash_commit_message(user: current_user) end end end diff --git a/app/views/admin/application_settings/_localization.html.haml b/app/views/admin/application_settings/_localization.html.haml index 19d321ca205..4002aa076f7 100644 --- a/app/views/admin/application_settings/_localization.html.haml +++ b/app/views/admin/application_settings/_localization.html.haml @@ -7,7 +7,7 @@ = f.select :first_day_of_week, first_day_of_week_choices, {}, class: 'form-control' .form-text.text-muted = _('Default first day of the week in calendars and date pickers.') - = link_to _('Learn more.'), help_page_path('administration/settings/index.md', anchor: 'default-first-day-of-the-week'), target: '_blank', rel: 'noopener noreferrer' + = link_to _('Learn more.'), help_page_path('administration/settings/index.md', anchor: 'change-the-default-first-day-of-the-week'), target: '_blank', rel: 'noopener noreferrer' .form-group = f.label :time_tracking, _('Time tracking'), class: 'label-bold' diff --git a/app/views/devise/shared/_signup_omniauth_provider_list.haml b/app/views/devise/shared/_signup_omniauth_provider_list.haml index 60c37316c62..e8c82e456ae 100644 --- a/app/views/devise/shared/_signup_omniauth_provider_list.haml +++ b/app/views/devise/shared/_signup_omniauth_provider_list.haml @@ -4,7 +4,7 @@ = _("Register with:") .gl-text-center.gl-ml-auto.gl-mr-auto - providers.each do |provider| - = render Pajamas::ButtonComponent.new(href: omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), method: :post, variant: :default, button_options: { class: "gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider, track_action: "#{provider}_sso", track_label: tracking_label}, id: "oauth-login-#{provider}" }) do + = button_to omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider, track_action: "#{provider}_sso", track_label: tracking_label }, id: "oauth-login-#{provider}" do - if provider_has_icon?(provider) = provider_image_tag(provider) %span.gl-button-text @@ -14,7 +14,7 @@ = _("Create an account using:") .gl-display-flex.gl-justify-content-between.gl-flex-wrap - providers.each do |provider| - = render Pajamas::ButtonComponent.new(href: omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), method: :post, variant: :default, button_options: { class: "gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider, track_action: "#{provider}_sso", track_label: tracking_label}, id: "oauth-login-#{provider}" }) do + = button_to omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider, track_action: "#{provider}_sso", track_label: tracking_label }, id: "oauth-login-#{provider}" do - if provider_has_icon?(provider) = provider_image_tag(provider) %span.gl-button-text diff --git a/app/views/shared/issue_type/_details_header.html.haml b/app/views/shared/issue_type/_details_header.html.haml index 4997d429587..558287480e1 100644 --- a/app/views/shared/issue_type/_details_header.html.haml +++ b/app/views/shared/issue_type/_details_header.html.haml @@ -16,6 +16,6 @@ #js-issuable-header-warnings{ data: { hidden: issue_hidden?(issuable).to_s } } = issuable_meta(issuable, @project) - = render Pajamas::ButtonComponent.new(href: '#', icon: 'chevron-double-lg-left', button_options: { class: 'gl-float-right gl-display-block gl-sm-display-none! gutter-toggle issuable-gutter-toggle js-sidebar-toggle' }) + = render Pajamas::ButtonComponent.new(href: '#', icon: 'chevron-double-lg-left', button_options: { class: 'gl-ml-auto gl-display-block gl-sm-display-none! js-sidebar-toggle' }) .js-issue-header-actions{ data: issue_header_actions_data(@project, issuable, current_user, @issuable_sidebar) } diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md index 1ab45d6ce99..add036ea5ec 100644 --- a/doc/administration/integration/terminal.md +++ b/doc/administration/integration/terminal.md @@ -114,5 +114,5 @@ lifetime in your GitLab instance: 1. On the left sidebar, expand the top-most chevron (**{chevron-down}**). 1. Select **Admin Area**. -1. Select [**Settings > Web terminal**](../../administration/settings/index.md#general). +1. Select **Settings > Web terminal**. 1. Set a `max session time`. diff --git a/doc/administration/settings/index.md b/doc/administration/settings/index.md index 067add353eb..a5746ad26e4 100644 --- a/doc/administration/settings/index.md +++ b/doc/administration/settings/index.md @@ -21,180 +21,8 @@ To access the **Admin Area**: 1. Sign in to your GitLab instance as an administrator. 1. On the left sidebar, expand the top-most chevron (**{chevron-down}**). 1. Select **Admin Area**. -1. Select **Settings**, and the group of settings to view: - - [General](#general) - - [Geo](#geo) - - [CI/CD](#cicd) - - [Integrations](#integrations) - - [Metrics and profiling](#metrics-and-profiling) - - [Network](#network) - - [Preferences](#preferences) - - [Reporting](#reporting) - - [Repository](#repository) - - [Templates](#templates) -### General - -The **General** settings contain: - -- [Visibility and access controls](../settings/visibility_and_access_controls.md) - Set default and - restrict visibility levels. Configure import sources and Git access protocol. -- [Account and limit](../settings/account_and_limit_settings.md) - Set projects and maximum size limits, - session duration, user options, and check feature availability for namespace plan. -- [Diff limits](../diff_limits.md) - Diff content limits. -- [Sign-up restrictions](../settings/sign_up_restrictions.md) - Configure the way a user creates a new account. -- [Sign in restrictions](../settings/sign_in_restrictions.md) - Set requirements for a user to sign in. - Enable mandatory two-factor authentication. -- [Terms of Service and Privacy Policy](../settings/terms.md) - Include a Terms of Service agreement - and Privacy Policy that all users must accept. -- [External Authentication](../../administration/settings/external_authorization.md#configuration) - External Classification Policy Authorization. -- [Web terminal](../integration/terminal.md#limiting-websocket-connection-time) - - Set max session time for web terminal. -- [FLoC](floc.md) - Enable or disable - [Federated Learning of Cohorts (FLoC)](https://en.wikipedia.org/wiki/Federated_Learning_of_Cohorts) tracking. -- [GitLab for Slack app](slack_app.md) - Enable and configure the GitLab for Slack app. - -### CI/CD - -The **CI/CD** settings contain: - -- [Continuous Integration and Deployment](../../administration/settings/continuous_integration.md) - - Auto DevOps, runners and job artifacts. -- [Required pipeline configuration](../../administration/settings/continuous_integration.md#required-pipeline-configuration) - - Set an instance-wide auto included [pipeline configuration](../../ci/yaml/index.md). - This pipeline configuration is run after the project's own configuration. -- [Package Registry](../../administration/settings/continuous_integration.md#package-registry-configuration) - - Settings related to the use and experience of using the GitLab Package Registry. Some - [risks are involved](../../user/packages/container_registry/reduce_container_registry_storage.md#use-with-external-container-registries) - in enabling some of these settings. - -## Security and Compliance settings - -- [License compliance settings](security_and_compliance.md#choose-package-registry-metadata-to-sync): Enable or disable synchronization of package metadata by a registry type. - -### Geo **(PREMIUM SELF)** - -The **Geo** setting contains: - -- [Geo](../geo/index.md) - Replicate your GitLab instance to other - geographical locations. Redirects to **Admin Area > Geo > Settings** are no - longer available at **Admin Area > Settings > Geo** in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/36896). - -### Integrations - -The **Integrations** settings contain: - -- [Elasticsearch](../../integration/advanced_search/elasticsearch.md#enable-advanced-search) - - Elasticsearch integration. Elasticsearch AWS IAM. -- [Kroki](../integration/kroki.md#enable-kroki-in-gitlab) - - Allow rendering of diagrams in AsciiDoc and Markdown documents using [kroki.io](https://kroki.io). -- [Mailgun](../integration/mailgun.md) - Enable your GitLab instance - to receive invite email bounce events from Mailgun, if it is your email provider. -- [PlantUML](../integration/plantuml.md) - Allow rendering of PlantUML - diagrams in documents. -- [Customer experience improvement and third-party offers](../settings/third_party_offers.md) - - Control the display of customer experience improvement content and third-party offers. -- [Snowplow](../../development/internal_analytics/snowplow/index.md) - Configure the Snowplow integration. -- [Google GKE](../../user/project/clusters/add_gke_clusters.md) - Google GKE integration enables - you to provision GKE clusters from GitLab. -- [Amazon EKS](../../user/project/clusters/add_eks_clusters.md) - Amazon EKS integration enables - you to provision EKS clusters from GitLab. - -### Metrics and profiling - -The **Metrics and profiling** settings contain: - -- [Metrics - Prometheus](../monitoring/prometheus/gitlab_metrics.md) - - Enable and configure Prometheus metrics. -- [Metrics - Grafana](../monitoring/performance/grafana_configuration.md#integrate-with-gitlab-ui) - - Enable and configure Grafana. -- [Profiling - Performance bar](../monitoring/performance/performance_bar.md#enable-the-performance-bar-for-non-administrators) - - Enable access to the Performance Bar for non-administrator users in a given group. -- [Usage statistics](../settings/usage_statistics.md) - Enable or disable version check and Service Ping. - -### Network - -The **Network** settings contain: - -- Performance optimization - Various settings that affect GitLab performance, including: - - [Write to `authorized_keys` file](../operations/fast_ssh_key_lookup.md#set-up-fast-lookup). - - [Push event activities limit and bulk push events](../settings/push_event_activities_limit.md). -- [User and IP rate limits](../settings/user_and_ip_rate_limits.md) - Configure limits for web and API requests. - These rate limits can be overridden: - - [Package Registry Rate Limits](../settings/package_registry_rate_limits.md) - Configure specific - limits for Packages API requests that supersede the user and IP rate limits. - - [Git LFS Rate Limits](../settings/git_lfs_rate_limits.md) - Configure specific limits for - Git LFS requests that supersede the user and IP rate limits. - - [Files API Rate Limits](../settings/files_api_rate_limits.md) - Configure specific limits for - Files API requests that supersede the user and IP rate limits. - - [Search rate limits](../instance_limits.md#search-rate-limit) - Configure global search request rate limits for authenticated and unauthenticated users. - - [Deprecated API Rate Limits](../settings/deprecated_api_rate_limits.md) - Configure specific limits - for deprecated API requests that supersede the user and IP rate limits. -- [Outbound requests](../../security/webhooks.md) - Allow requests to the local network from webhooks and integrations, or deny all outbound requests. -- [Protected Paths](../settings/protected_paths.md) - Configure paths to be protected by Rack Attack. -- [Incident Management Limits](../../operations/incident_management/index.md) - Limit the - number of inbound alerts that can be sent to a project. -- [Notes creation limit](../settings/rate_limit_on_notes_creation.md) - Set a rate limit on the note creation requests. -- [Get single user limit](../settings/rate_limit_on_users_api.md) - Set a rate limit on users API endpoint to get a user by ID. -- [Projects API rate limits for unauthenticated requests](../settings/rate_limit_on_projects_api.md) - Set a rate limit on Projects list API endpoint for unauthenticated requests. - -### Preferences - -The **Preferences** settings contain: - -- [Email](../settings/email.md) - Various email settings. -- [What's new](../whats-new.md) - Configure **What's new** drawer and content. -- [Help page](help_page.md) - Help page text and support page URL. -- [Pages](../pages/index.md#custom-domain-verification) - - Size and domain settings for static websites. -- [Polling interval multiplier](../polling.md) - - Configure how frequently the GitLab UI polls for updates. -- [Gitaly timeouts](gitaly_timeouts.md) - Configure Gitaly timeouts. -- Localization: - - [Default first day of the week](../../user/profile/preferences.md). - - [Time tracking](../../user/project/time_tracking.md#limit-displayed-units-to-hours). -- [Sidekiq Job Limits](../settings/sidekiq_job_limits.md) - Limit the size of Sidekiq jobs stored in Redis. - -### Reporting - -The **Reporting** settings contain: - -- Spam and Anti-bot protection: - - Anti-spam services, such as [reCAPTCHA](../../integration/recaptcha.md), - [Akismet](../../integration/akismet.md), or [Spamcheck](../reporting/spamcheck.md). - - [IP address restrictions](../reporting/ip_addr_restrictions.md). -- [Abuse reports](../review_abuse_reports.md) - Set notification email for abuse reports. -- [Git abuse rate limit](../reporting/git_abuse_rate_limit.md) - Configure Git abuse rate limit settings. **(ULTIMATE SELF)** - -### Repository - -The **Repository** settings contain: - -- [Repository's custom initial branch name](../../user/project/repository/branches/default.md#instance-level-custom-initial-branch-name) - - Set a custom branch name for new repositories created in your instance. -- [Repository's initial default branch protection](../../user/project/repository/branches/default.md#instance-level-default-branch-protection) - - Configure the branch protections to apply to every repository's default branch. -- [Repository mirror](visibility_and_access_controls.md#enable-project-mirroring) - - Configure repository mirroring. -- [Repository storage](../repository_storage_types.md) - Configure storage path settings. -- Repository maintenance: - - [Repository checks](../repository_checks.md) - Configure - automatic Git checks on repositories. - - [Housekeeping](../housekeeping.md). Configure automatic - Git housekeeping on repositories. - - [Inactive project deletion](../inactive_project_deletion.md). Configure inactive - project deletion. -- [Repository static objects](../static_objects_external_storage.md) - - Serve repository static objects (for example, archives and blobs) from an external storage (for example, a CDN). - -### Templates **(PREMIUM SELF)** - -The **Templates** settings contain: - -- [Templates](instance_template_repository.md#configuration) - Set instance-wide template repository. -- [Custom project templates](../custom_project_templates.md) - Select the custom project template source group. - -## Default first day of the week +## Change the default first day of the week You can change the [Default first day of the week](../../user/profile/preferences.md) for the entire GitLab instance: @@ -204,7 +32,7 @@ for the entire GitLab instance: 1. Select **Settings > Preferences**. 1. Scroll to the **Localization** section, and select your desired first day of the week. -## Default language +## Change the default language You can change the [Default language](../../user/profile/preferences.md) for the entire GitLab instance: diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 6aedb18c15d..f3c3062e217 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -201,7 +201,8 @@ by a reviewer before passing it to a maintainer as described in the on the line of code in question with the SQL queries so they can give their advice. 1. User-facing changes include both visual changes (regardless of how minor), and changes to the rendered DOM which impact how a screen reader may announce - the content. + the content. Groups that do not have dedicated Product + Designers do not require a Product Designer to approve feature changes, unless the changes are community contributions. 1. End-to-end changes include all files in the `qa` directory. #### Acceptance checklist diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md index a01e35349cb..9c0a64c68b5 100644 --- a/doc/development/integrations/index.md +++ b/doc/development/integrations/index.md @@ -123,6 +123,23 @@ module Integrations end ``` +### Security enhancement features + +#### Masking channel values + +Integrations that [inherit from `Integrations::BaseChatNotification`](#define-the-integration) can hide the +values of their channel input fields. Integrations should hide these values whenever the +fields contain sensitive information such as auth tokens. + +By default, `#mask_configurable_channels?` returns `false`. To mask the channel values, override the `#mask_configurable_channels?` method in the integration to return `true`: + +```ruby +override :mask_configurable_channels? +def mask_configurable_channels? + true +end +``` + ## Define configuration test Optionally, you can define a configuration test of an integration's settings. The test is executed from the integration form's **Test** button, and results are returned to the user. diff --git a/doc/development/sql.md b/doc/development/sql.md index 1e3e5c7d0f6..101ccc239e7 100644 --- a/doc/development/sql.md +++ b/doc/development/sql.md @@ -315,6 +315,30 @@ union = Gitlab::SQL::Union.new([projects, more_projects, ...]) Project.from("(#{union.to_sql}) projects") ``` +The `FromUnion` model concern provides a more convenient method to produce the same result as above: + +```ruby +class Project + include FromUnion + ... +end + +Project.from_union(projects, more_projects, ...) +``` + +`UNION` is common through the codebase, but it's also possible to use the other SQL set operators of `EXCEPT` and `INTERSECT`: + +```ruby +class Project + include FromIntersect + include FromExcept + ... +end + +intersected = Project.from_intersect(all_projects, project_set_1, project_set_2) +excepted = Project.from_except(all_projects, project_set_1, project_set_2) +``` + ### Uneven columns in the `UNION` sub-queries When the `UNION` query has uneven columns in the `SELECT` clauses, the database returns an error. @@ -479,8 +503,8 @@ Simple usage of the `.upsert` method: BuildTrace.upsert( { build_id: build_id, - title: title - }, + title: title + }, unique_by: :build_id ) ``` diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index 1b50bd410c2..825a5e9f49b 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -228,16 +228,13 @@ it('exists', () => { // Bad wrapper.find('.js-foo'); wrapper.find('.btn-primary'); - wrapper.find('.qa-foo-component'); }); ``` -It is recommended to use `kebab-case` for `data-testid` attribute. +You should use `kebab-case` for `data-testid` attribute. It is not recommended that you add `.js-*` classes just for testing purposes. Only do this if there are no other feasible options available. -Do not use `.qa-*` class attributes for any tests other than QA end-to-end testing. - ### Querying for child components When testing Vue components with `@vue/test-utils` another possible approach is querying for child diff --git a/doc/integration/azure.md b/doc/integration/azure.md index ffd6ccc4888..4ef47a0ac51 100644 --- a/doc/integration/azure.md +++ b/doc/integration/azure.md @@ -326,3 +326,33 @@ Alternatively, add the `User.Read.All` application permission. Read [Enable OmniAuth for an existing user](omniauth.md#enable-omniauth-for-an-existing-user) for information on how existing GitLab users can connect to their new Azure AD accounts. + +## Troubleshooting + +### User sign in banner message: Extern UID has already been taken + +When signing in, you might get an error that states `Extern UID has already been taken`. + +To resolve this, use the [Rails console](../administration/operations/rails_console.md#starting-a-rails-console-session) to check if there is an existing user tied to the account: + +1. Find the `extern_uid`: + + ```ruby + id = Identity.where(extern_uid: '<extern_uid>') + ``` + +1. Print the content to find the username attached to that `extern_uid`: + + ```ruby + pp id + ``` + +If the `extern_uid` is attached to an account, you can use the username to sign in. + +If the `extern_uid` is not attached to any username, this might be because of a deletion error resulting in a ghost record. + +Run the following command to delete the identity to release the `extern uid`: + +```ruby + Identity.find('<id>').delete +``` diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md index 17dea99e5ef..166833cc84f 100644 --- a/doc/user/profile/preferences.md +++ b/doc/user/profile/preferences.md @@ -157,7 +157,8 @@ You can choose one of the following options as the first day of the week: - Sunday - Monday -If you select **System Default**, the [instance default](../../administration/settings/index.md#default-first-day-of-the-week) setting is used. +If you select **System Default**, the first day of the week is set to the +[instance default](../../administration/settings/index.md#change-the-default-first-day-of-the-week). ## Time preferences diff --git a/patches/@vue+compat+3.2.47.patch b/patches/@vue+compat+3.2.47.patch new file mode 100644 index 00000000000..20ee26a0308 --- /dev/null +++ b/patches/@vue+compat+3.2.47.patch @@ -0,0 +1,15 @@ +diff --git a/node_modules/@vue/compat/dist/vue.cjs.js b/node_modules/@vue/compat/dist/vue.cjs.js +index 0d10385..d1a5185 100644 +--- a/node_modules/@vue/compat/dist/vue.cjs.js ++++ b/node_modules/@vue/compat/dist/vue.cjs.js +@@ -5877,9 +5877,7 @@ function installCompatInstanceProperties(map) { + const res = {}; + for (const key in i.slots) { + const fn = i.slots[key]; +- if (!fn._ns /* non-scoped slot */) { +- res[key] = fn; +- } ++ res[key] = fn; + } + return res; + }, diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index 5d36f8030b7..ec8aca76250 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -12,44 +12,39 @@ module QA def initialize(name, *options) @name = name @attributes = options.extract_options! - @attributes[:pattern] ||= selector options.each do |option| @attributes[:pattern] = option if option.is_a?(String) || option.is_a?(Regexp) end end - def selector - "qa-#{@name.to_s.tr('_', '-')}" - end - def required? !!@attributes[:required] end def selector_css - %(#{qa_selector}#{additional_selectors},.#{selector}) + [ + %([data-testid="#{name}"]#{additional_selectors}), + %([data-qa-selector="#{name}"]#{additional_selectors}) + ].join(',') end - def expression - if @attributes[:pattern].is_a?(String) - @_regexp ||= Regexp.new(Regexp.escape(@attributes[:pattern])) + def matches?(line) + if expression + !!(line =~ /["']#{name}['"]|#{expression}/) else - @attributes[:pattern] + !!(line =~ /["']#{name}['"]/) end end - def matches?(line) - !!(line =~ /["']#{name}['"]|#{expression}/) - end - private - def qa_selector - [ - %([data-testid="#{name}"]#{additional_selectors}), - %([data-qa-selector="#{name}"]#{additional_selectors}) - ].join(',') + def expression + if @attributes[:pattern].is_a?(String) + @_regexp ||= Regexp.new(Regexp.escape(@attributes[:pattern])) + else + @attributes[:pattern] + end end def additional_selectors diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index da1fd224564..c354d55fb19 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -1,17 +1,10 @@ # frozen_string_literal: true RSpec.describe QA::Page::Element do - describe '#selector' do - it 'transforms element name into QA-specific selector' do - expect(described_class.new(:sign_in_button).selector) - .to eq 'qa-sign-in-button' - end - end - describe '#selector_css' do it 'transforms element name into QA-specific clickable css selector' do expect(described_class.new(:sign_in_button).selector_css) - .to include('.qa-sign-in-button') + .to eq('[data-testid="sign_in_button"],[data-qa-selector="sign_in_button"]') end end @@ -42,10 +35,6 @@ RSpec.describe QA::Page::Element do context 'when pattern is not provided' do subject { described_class.new(:some_name) } - it 'matches when QA specific selector is present' do - expect(subject.matches?('some qa-some-name selector')).to be true - end - it 'does not match if QA selector is not there' do expect(subject.matches?('some_name selector')).to be false end @@ -53,15 +42,18 @@ RSpec.describe QA::Page::Element do it 'matches when element name is specified' do expect(subject.matches?('data:{qa:{selector:"some_name"}}')).to be true end + + it 'matches when element name is specified (single quotes)' do + expect(subject.matches?("data:{qa:{selector:'some_name'}}")).to be true + end end describe 'attributes' do context 'element with no args' do subject { described_class.new(:something) } - it 'defaults pattern to #selector' do - expect(subject.attributes[:pattern]).to eq 'qa-something' - expect(subject.attributes[:pattern]).to eq subject.selector + it 'has no attribute[pattern]' do + expect(subject.attributes[:pattern]).to be(nil) end it 'is not required by default' do @@ -84,11 +76,6 @@ RSpec.describe QA::Page::Element do context 'element with requirement; no pattern' do subject { described_class.new(:something, required: true) } - it 'has an attribute[pattern] of the selector' do - expect(subject.attributes[:pattern]).to eq 'qa-something' - expect(subject.attributes[:pattern]).to eq subject.selector - end - it 'is required' do expect(subject.required?).to be true end @@ -104,10 +91,6 @@ RSpec.describe QA::Page::Element do it 'is required' do expect(subject.required?).to be true end - - it 'has a selector of the name' do - expect(subject.selector).to eq 'qa-something' - end end end @@ -126,18 +109,20 @@ RSpec.describe QA::Page::Element do let(:element) { described_class.new(:my_element, index: 3, another_match: 'something') } let(:required_element) { described_class.new(:my_element, required: true, index: 3) } - it 'matches on additional data-qa properties' do - expect(element.selector_css).to include(%q([data-qa-selector="my_element"][data-qa-index="3"])) + it 'matches on additional data-qa properties translating snake_case to kebab-case' do + expect(element.selector_css) + .to include('[data-testid="my_element"][data-qa-index="3"][data-qa-another-match="something"]') + expect(element.selector_css) + .to include('[data-qa-selector="my_element"][data-qa-index="3"][data-qa-another-match="something"]') end it 'doesnt conflict with element requirement' do + expect(element).not_to be_required + expect(element.selector_css).not_to include(%q(data-qa-required)) + expect(required_element).to be_required expect(required_element.selector_css).not_to include(%q(data-qa-required)) end - - it 'translates snake_case to kebab-case' do - expect(element.selector_css).to include(%q(data-qa-another-match)) - end end end end diff --git a/scripts/utils.sh b/scripts/utils.sh index e19622d07c6..13a051e2b58 100644 --- a/scripts/utils.sh +++ b/scripts/utils.sh @@ -438,8 +438,9 @@ function download_local_gems() { echo "Downloading ${folder_path}" - url=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/repository/archive + url="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/repository/archive" curl -f \ + --create-dirs \ --get \ --header "${private_token_header}" \ --output "${output}" \ diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index f78d50bba24..0e3e3f31783 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -658,21 +658,17 @@ RSpec.describe Projects::MergeRequestsController, feature_category: :code_review let(:message) { 'My custom squash commit message' } it 'passes the same message to SquashService', :sidekiq_inline do - params = { squash: '1', - squash_commit_message: message, - sha: merge_request.diff_head_sha } - expected_squash_params = { squash_commit_message: message, - sha: merge_request.diff_head_sha, - merge_request: merge_request } - - expect_next_instance_of(MergeRequests::SquashService, project: project, current_user: user, params: expected_squash_params) do |squash_service| + expect_next_instance_of(MergeRequests::SquashService, + merge_request: merge_request, + current_user: user, + commit_message: message) do |squash_service| expect(squash_service).to receive(:execute).and_return({ status: :success, squash_sha: SecureRandom.hex(20) }) end - merge_with_sha(params) + merge_with_sha(squash: '1', squash_commit_message: message, sha: merge_request.diff_head_sha) end end diff --git a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js index d5307b87a11..99a178120cc 100644 --- a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js +++ b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js @@ -2,12 +2,14 @@ import { GlButton, GlLink, GlTableLite } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { createAlert } from '~/alert'; import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated import FailedJobsTable from '~/pipelines/components/jobs/failed_jobs_table.vue'; import RetryFailedJobMutation from '~/pipelines/graphql/mutations/retry_failed_job.mutation.graphql'; +import { TRACKING_CATEGORIES } from '~/pipelines/constants'; import { successRetryMutationResponse, failedRetryMutationResponse, @@ -71,7 +73,9 @@ describe('Failed Jobs Table', () => { expect(findFirstFailureMessage().text()).toBe('Job failed'); }); - it('calls the retry failed job mutation correctly', () => { + it('calls the retry failed job mutation and tracks the click', () => { + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + createComponent(successRetryMutationHandler); findRetryButton().trigger('click'); @@ -79,6 +83,12 @@ describe('Failed Jobs Table', () => { expect(successRetryMutationHandler).toHaveBeenCalledWith({ id: mockFailedJobsData[0].id, }); + expect(trackingSpy).toHaveBeenCalledTimes(1); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_retry', { + label: TRACKING_CATEGORIES.failed, + }); + + unmockTracking(); }); it('redirects to the new job after the mutation', async () => { diff --git a/spec/frontend/pipelines/components/pipeline_tabs_spec.js b/spec/frontend/pipelines/components/pipeline_tabs_spec.js index fde13128662..0951e1ffb46 100644 --- a/spec/frontend/pipelines/components/pipeline_tabs_spec.js +++ b/spec/frontend/pipelines/components/pipeline_tabs_spec.js @@ -1,10 +1,14 @@ -import { shallowMount } from '@vue/test-utils'; import { GlTab } from '@gitlab/ui'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import PipelineTabs from '~/pipelines/components/pipeline_tabs.vue'; +import { TRACKING_CATEGORIES } from '~/pipelines/constants'; describe('The Pipeline Tabs', () => { let wrapper; + let trackingSpy; + + const $router = { push: jest.fn() }; const findDagTab = () => wrapper.findByTestId('dag-tab'); const findFailedJobsTab = () => wrapper.findByTestId('failed-jobs-tab'); @@ -24,18 +28,19 @@ describe('The Pipeline Tabs', () => { }; const createComponent = (provide = {}) => { - wrapper = extendedWrapper( - shallowMount(PipelineTabs, { - provide: { - ...defaultProvide, - ...provide, - }, - stubs: { - GlTab, - RouterView: true, - }, - }), - ); + wrapper = shallowMountExtended(PipelineTabs, { + provide: { + ...defaultProvide, + ...provide, + }, + stubs: { + GlTab, + RouterView: true, + }, + mocks: { + $router, + }, + }); }; describe('Tabs', () => { @@ -76,4 +81,34 @@ describe('The Pipeline Tabs', () => { expect(badgeComponent().text()).toBe(badgeText); }); }); + + describe('Tab tracking', () => { + beforeEach(() => { + createComponent(); + + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + }); + + afterEach(() => { + unmockTracking(); + }); + + it('tracks failed jobs tab click', () => { + findFailedJobsTab().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledTimes(1); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', { + label: TRACKING_CATEGORIES.failed, + }); + }); + + it('tracks tests tab click', () => { + findTestsTab().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledTimes(1); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', { + label: TRACKING_CATEGORIES.tests, + }); + }); + }); }); diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb index 1afca466fb5..ecbe2d7e097 100644 --- a/spec/services/merge_requests/squash_service_spec.rb +++ b/spec/services/merge_requests/squash_service_spec.rb @@ -3,16 +3,19 @@ require 'spec_helper' RSpec.describe MergeRequests::SquashService, feature_category: :source_code_management do - let(:service) { described_class.new(project: project, current_user: user, params: { merge_request: merge_request }) } - let(:user) { project.first_owner } - let(:project) { create(:project, :repository) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.first_owner } + + let(:service) { described_class.new(merge_request: merge_request, current_user: user, commit_message: commit_message) } + let(:commit_message) { nil } let(:repository) { project.repository.raw } let(:log_error) { "Failed to squash merge request #{merge_request.to_reference(full: true)}:" } + let(:squash_dir_path) do File.join(Gitlab.config.shared.path, 'tmp/squash', repository.gl_repository, merge_request.id.to_s) end - let(:merge_request_with_one_commit) do + let_it_be(:merge_request_with_one_commit) do create( :merge_request, source_branch: 'feature', source_project: project, @@ -20,7 +23,7 @@ RSpec.describe MergeRequests::SquashService, feature_category: :source_code_mana ) end - let(:merge_request_with_only_new_files) do + let_it_be(:merge_request_with_only_new_files) do create( :merge_request, source_branch: 'video', source_project: project, @@ -28,7 +31,7 @@ RSpec.describe MergeRequests::SquashService, feature_category: :source_code_mana ) end - let(:merge_request_with_large_files) do + let_it_be(:merge_request_with_large_files) do create( :merge_request, source_branch: 'squash-large-files', source_project: project, @@ -66,7 +69,7 @@ RSpec.describe MergeRequests::SquashService, feature_category: :source_code_mana end context 'when squash message matches commit message' do - let(:service) { described_class.new(project: project, current_user: user, params: { merge_request: merge_request, squash_commit_message: merge_request.first_commit.safe_message }) } + let(:commit_message) { merge_request.first_commit.safe_message } it 'returns that commit SHA' do result = service.execute @@ -82,7 +85,7 @@ RSpec.describe MergeRequests::SquashService, feature_category: :source_code_mana end context 'when squash message matches commit message but without trailing new line' do - let(:service) { described_class.new(project: project, current_user: user, params: { merge_request: merge_request, squash_commit_message: merge_request.first_commit.safe_message.strip }) } + let(:commit_message) { merge_request.first_commit.safe_message.strip } it 'returns that commit SHA' do result = service.execute @@ -98,7 +101,7 @@ RSpec.describe MergeRequests::SquashService, feature_category: :source_code_mana end end - context 'the squashed commit' do + describe 'the squashed commit' do let(:squash_sha) { service.execute[:squash_sha] } let(:squash_commit) { project.repository.commit(squash_sha) } @@ -125,7 +128,7 @@ RSpec.describe MergeRequests::SquashService, feature_category: :source_code_mana end context 'if a message was provided' do - let(:service) { described_class.new(project: project, current_user: user, params: { merge_request: merge_request, squash_commit_message: message }) } + let(:commit_message) { message } let(:message) { 'My custom message' } let(:squash_sha) { service.execute[:squash_sha] } @@ -191,7 +194,7 @@ RSpec.describe MergeRequests::SquashService, feature_category: :source_code_mana include_examples 'the squash succeeds' end - context 'git errors' do + describe 'git errors' do let(:merge_request) { merge_request_with_only_new_files } let(:error) { 'A test error' } |