diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-21 15:11:26 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-21 15:11:26 +0300 |
commit | eac99f198f2834788c38108bcee3a2c567fba9e0 (patch) | |
tree | 427b3e304f49b62a207f345d2ebb9ded47ee63d4 | |
parent | 59ffff96dfdb114c112d14cedc5dfd9553cf568b (diff) |
Add latest changes from gitlab-org/gitlab@master
51 files changed, 663 insertions, 239 deletions
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml index 9ceeab04049..6c5694293f8 100644 --- a/.rubocop_todo/rspec/missing_feature_category.yml +++ b/.rubocop_todo/rspec/missing_feature_category.yml @@ -4904,7 +4904,6 @@ RSpec/MissingFeatureCategory: - 'spec/lib/gitlab/i18n/metadata_entry_spec.rb' - 'spec/lib/gitlab/i18n/po_linter_spec.rb' - 'spec/lib/gitlab/i18n/translation_entry_spec.rb' - - 'spec/lib/gitlab/i18n_spec.rb' - 'spec/lib/gitlab/identifier_spec.rb' - 'spec/lib/gitlab/import/database_helpers_spec.rb' - 'spec/lib/gitlab/import/import_failure_service_spec.rb' diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue index 749fae0c426..09ee6e50881 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -1,18 +1,18 @@ <script> import { GlButton, - GlButtonGroup, GlLabel, GlTooltip, GlIcon, GlSprintf, GlTooltipDirective, + GlDisclosureDropdown, } from '@gitlab/ui'; import { mapActions, mapGetters, mapState } from 'vuex'; import { isListDraggable } from '~/boards/boards_util'; import { isScopedLabel, parseBoolean } from '~/lib/utils/common_utils'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; -import { n__, s__, __ } from '~/locale'; +import { n__, s__ } from '~/locale'; import sidebarEventHub from '~/sidebar/event_hub'; import Tracking from '~/tracking'; import { formatDate } from '~/lib/utils/datetime_utility'; @@ -25,14 +25,15 @@ import ItemCount from './item_count.vue'; export default { i18n: { - newIssue: __('New issue'), - newEpic: s__('Boards|New epic'), - listSettings: __('List settings'), + newIssue: s__('Boards|Create new issue'), + listActions: s__('Boards|List actions'), + newEpic: s__('Boards|Create new epic'), + listSettings: s__('Boards|Edit list settings'), expand: s__('Boards|Expand'), collapse: s__('Boards|Collapse'), }, components: { - GlButtonGroup, + GlDisclosureDropdown, GlButton, GlLabel, GlTooltip, @@ -111,7 +112,10 @@ export default { }, showListHeaderActions() { if (this.isLoggedIn) { - return this.isNewIssueShown || this.isNewEpicShown || this.isSettingsShown; + return ( + (this.isNewIssueShown || this.isNewEpicShown || this.isSettingsShown) && + !this.list.collapsed + ); } return false; }, @@ -162,6 +166,50 @@ export default { canShowTotalWeight() { return this.weightFeatureAvailable && !this.isLoading; }, + actionListItems() { + const items = []; + + if (this.isNewIssueShown) { + const newIssueText = this.$options.i18n.newIssue; + items.push({ + text: newIssueText, + action: this.showNewIssueForm, + extraAttrs: { + 'data-testid': 'newIssueBtn', + title: newIssueText, + 'aria-label': newIssueText, + }, + }); + } + + if (this.isNewEpicShown) { + const newEpicText = this.$options.i18n.newEpic; + items.push({ + text: newEpicText, + action: this.showNewEpicForm, + extraAttrs: { + 'data-testid': 'newEpicBtn', + title: newEpicText, + 'aria-label': newEpicText, + }, + }); + } + + if (this.isSettingsShown) { + const listSettingsText = this.$options.i18n.listSettings; + items.push({ + text: listSettingsText, + action: this.openSidebarSettings, + extraAttrs: { + 'data-testid': 'settingsBtn', + title: listSettingsText, + 'aria-label': listSettingsText, + }, + }); + } + + return items; + }, }, apollo: { boardList: { @@ -188,6 +236,9 @@ export default { }, methods: { ...mapActions(['updateList', 'setActiveId', 'toggleListCollapsed']), + closeListActions() { + this.$refs.headerListActions?.close(); + }, openSidebarSettings() { if (this.activeId === inactiveId) { sidebarEventHub.$emit('sidebar.closeAll'); @@ -196,11 +247,12 @@ export default { this.setActiveId({ id: this.list.id, sidebarType: LIST }); this.track('click_button', { label: 'list_settings' }); + + this.closeListActions(); }, showScopedLabels(label) { return this.scopedLabelsAvailable && isScopedLabel(label); }, - showNewIssueForm() { if (this.isSwimlanesOn) { eventHub.$emit('open-unassigned-lane'); @@ -210,9 +262,13 @@ export default { } else { eventHub.$emit(`${toggleFormEventPrefix.issue}${this.list.id}`); } + + this.closeListActions(); }, showNewEpicForm() { eventHub.$emit(`${toggleFormEventPrefix.epic}${this.list.id}`); + + this.closeListActions(); }, toggleExpanded() { const collapsed = !this.list.collapsed; @@ -407,44 +463,24 @@ export default { <!-- EE end --> </span> </div> - <gl-button-group v-if="showListHeaderActions" class="board-list-button-group gl-pl-2"> - <gl-button - v-if="isNewIssueShown" - v-show="!list.collapsed" - ref="newIssueBtn" - v-gl-tooltip.hover - :aria-label="$options.i18n.newIssue" - :title="$options.i18n.newIssue" - class="no-drag" - size="small" - icon="plus" - @click="showNewIssueForm" - /> - - <gl-button - v-if="isNewEpicShown" - v-show="!list.collapsed" - v-gl-tooltip.hover - :aria-label="$options.i18n.newEpic" - :title="$options.i18n.newEpic" - class="no-drag" - size="small" - icon="plus" - @click="showNewEpicForm" - /> - - <gl-button - v-if="isSettingsShown" - ref="settingsBtn" - v-gl-tooltip.hover - :aria-label="$options.i18n.listSettings" - class="no-drag" - size="small" - :title="$options.i18n.listSettings" - icon="settings" - @click="openSidebarSettings" - /> - </gl-button-group> + <gl-disclosure-dropdown + v-if="showListHeaderActions" + ref="headerListActions" + v-gl-tooltip.hover.top="{ + title: $options.i18n.listActions, + boundary: 'viewport', + }" + data-testid="header-list-actions" + class="gl-py-2 gl-ml-3" + :aria-label="$options.i18n.listActions" + :title="$options.i18n.listActions" + category="tertiary" + icon="ellipsis_v" + :text-sr-only="true" + :items="actionListItems" + no-caret + placement="right" + /> </h3> </header> </template> diff --git a/app/assets/javascripts/projects/commit/components/form_modal.vue b/app/assets/javascripts/projects/commit/components/form_modal.vue index f78afef1c17..05e526d51b1 100644 --- a/app/assets/javascripts/projects/commit/components/form_modal.vue +++ b/app/assets/javascripts/projects/commit/components/form_modal.vue @@ -85,7 +85,6 @@ export default { ]), }, mounted() { - this.setSelectedProject(this.targetProjectId); eventHub.$on(this.openModal, this.show); }, methods: { diff --git a/app/controllers/repositories/git_http_controller.rb b/app/controllers/repositories/git_http_controller.rb index bd3461d8331..018d98f2786 100644 --- a/app/controllers/repositories/git_http_controller.rb +++ b/app/controllers/repositories/git_http_controller.rb @@ -8,6 +8,7 @@ module Repositories prepend_before_action :deny_head_requests, only: [:info_refs] rescue_from Gitlab::GitAccess::ForbiddenError, with: :render_403_with_exception + rescue_from JWT::DecodeError, with: :render_403_with_exception rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404_with_exception rescue_from Gitlab::GitAccessProject::CreationError, with: :render_422_with_exception rescue_from Gitlab::GitAccess::TimeoutError, with: :render_503_with_exception diff --git a/config/initializers_before_autoloader/001_fast_gettext.rb b/config/initializers_before_autoloader/001_fast_gettext.rb index 3d54ed8f32f..01aec95ce53 100644 --- a/config/initializers_before_autoloader/001_fast_gettext.rb +++ b/config/initializers_before_autoloader/001_fast_gettext.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require_relative '../../lib/gitlab/i18n' +require_relative '../../lib/gitlab/i18n/pluralization' Gitlab::I18n.setup(domain: 'gitlab', default_locale: :en) diff --git a/db/post_migrate/20230214181633_finalize_ci_build_needs_big_int_conversion.rb b/db/post_migrate/20230214181633_finalize_ci_build_needs_big_int_conversion.rb new file mode 100644 index 00000000000..11870b9c540 --- /dev/null +++ b/db/post_migrate/20230214181633_finalize_ci_build_needs_big_int_conversion.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class FinalizeCiBuildNeedsBigIntConversion < Gitlab::Database::Migration[2.1] + restrict_gitlab_migration gitlab_schema: :gitlab_ci + + TABLE_NAME = 'ci_build_needs' + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: TABLE_NAME, + column_name: 'id', + job_arguments: [['id'], ['id_convert_to_bigint']] + ) + end + + def down; end +end diff --git a/db/post_migrate/20230221010522_prepare_async_foreign_key_validation_for_ci_sources_pipelines.rb b/db/post_migrate/20230221010522_prepare_async_foreign_key_validation_for_ci_sources_pipelines.rb new file mode 100644 index 00000000000..e25bbca5c80 --- /dev/null +++ b/db/post_migrate/20230221010522_prepare_async_foreign_key_validation_for_ci_sources_pipelines.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class PrepareAsyncForeignKeyValidationForCiSourcesPipelines < Gitlab::Database::Migration[2.1] + TABLE_NAME = :ci_sources_pipelines + COLUMN_NAMES = [:source_partition_id, :source_job_id] + FOREIGN_KEY_NAME = :fk_be5624bf37_p + + def up + prepare_async_foreign_key_validation(TABLE_NAME, COLUMN_NAMES, name: FOREIGN_KEY_NAME) + end + + def down + unprepare_async_foreign_key_validation(TABLE_NAME, COLUMN_NAMES, name: FOREIGN_KEY_NAME) + end +end diff --git a/db/post_migrate/20230221011750_prepare_async_foreign_key_validation_for_ci_job_variables.rb b/db/post_migrate/20230221011750_prepare_async_foreign_key_validation_for_ci_job_variables.rb new file mode 100644 index 00000000000..973c4c7316d --- /dev/null +++ b/db/post_migrate/20230221011750_prepare_async_foreign_key_validation_for_ci_job_variables.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class PrepareAsyncForeignKeyValidationForCiJobVariables < Gitlab::Database::Migration[2.1] + TABLE_NAME = :ci_job_variables + COLUMN_NAMES = [:partition_id, :job_id] + FOREIGN_KEY_NAME = :fk_rails_fbf3b34792_p + + def up + prepare_async_foreign_key_validation(TABLE_NAME, COLUMN_NAMES, name: FOREIGN_KEY_NAME) + end + + def down + unprepare_async_foreign_key_validation(TABLE_NAME, COLUMN_NAMES, name: FOREIGN_KEY_NAME) + end +end diff --git a/db/schema_migrations/20230214181633 b/db/schema_migrations/20230214181633 new file mode 100644 index 00000000000..76d4cf10b5c --- /dev/null +++ b/db/schema_migrations/20230214181633 @@ -0,0 +1 @@ +a8c815d1d85a6690755623b53e15e5fb73f7e6be6a24ead3532f21d21c1de20f
\ No newline at end of file diff --git a/db/schema_migrations/20230221010522 b/db/schema_migrations/20230221010522 new file mode 100644 index 00000000000..57727695f5e --- /dev/null +++ b/db/schema_migrations/20230221010522 @@ -0,0 +1 @@ +474f2b46179134270dc65c2b32ef9acfb01edb976f5efefe9ab49352d3fc390f
\ No newline at end of file diff --git a/db/schema_migrations/20230221011750 b/db/schema_migrations/20230221011750 new file mode 100644 index 00000000000..9f43d53a1b2 --- /dev/null +++ b/db/schema_migrations/20230221011750 @@ -0,0 +1 @@ +cca7c1c232fa56e85bb0fc120a5920f3cb75d0fea2657ed2e81a4fe69349134a
\ No newline at end of file diff --git a/doc/administration/geo/replication/container_registry.md b/doc/administration/geo/replication/container_registry.md index 88ca8781dc3..fea3af3613d 100644 --- a/doc/administration/geo/replication/container_registry.md +++ b/doc/administration/geo/replication/container_registry.md @@ -7,7 +7,7 @@ type: howto # Container Registry for a secondary site **(PREMIUM SELF)** -You can set up a Container Registry on your **secondary** Geo site that mirrors the one on the **primary** Geo site. +You can set up a Container Registry on your **secondary** Geo site that mirrors the one on the **primary** Geo site. NOTE: The Container Registry replication is used only for disaster recovery purposes. We do not recommend diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md index 403f8525d39..5940c1f5580 100644 --- a/doc/administration/geo/replication/troubleshooting.md +++ b/doc/administration/geo/replication/troubleshooting.md @@ -1353,7 +1353,7 @@ If you have installed GitLab using the Linux package (Omnibus) and have configur - `15.6.0`-`15.6.3` - `15.7.0`-`15.7.1` -This is due to [a bug introduced in the included version of cURL](https://github.com/curl/curl/issues/10122) shipped with Omnibus GitLab 15.4.6 and later. You are encouraged to upgrade to a later version where this has been [fixed](https://about.gitlab.com/releases/2023/01/09/security-release-gitlab-15-7-2-released/). +This is due to [a bug introduced in the included version of cURL](https://github.com/curl/curl/issues/10122) shipped with Omnibus GitLab 15.4.6 and later. You are encouraged to upgrade to a later version where this has been [fixed](https://about.gitlab.com/releases/2023/01/09/security-release-gitlab-15-7-2-released/). The bug causes all wildcard domains (`.example.com`) to be ignored except for the last on in the `no_proxy` environment variable list. Therefore, if for any reason you cannot upgrade to a newer version, you can work around the issue by moving your wildcard domain to the end of the list: @@ -1368,7 +1368,7 @@ The bug causes all wildcard domains (`.example.com`) to be ignored except for th ```shell sudo gitlab-ctl reconfigure - ``` + ``` You can have only one wildcard domain in the `no_proxy` list. diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index ed55e7d7ff3..9d15d4bbb46 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -163,6 +163,9 @@ The following metrics are available: | `gitlab_diffs_render_real_duration_seconds` | Histogram | 15.8 | Duration in seconds spent on serializing and rendering diffs on diffs batch request | `controller`, `action` | | `gitlab_memwd_violations_total` | Counter | 15.9 | Total number of times a Ruby process violated a memory threshold | | | `gitlab_memwd_violations_handled_total` | Counter | 15.9 | Total number of times Ruby process memory violations were handled | | +| `gitlab_sli_rails_request_apdex_total` | Counter | 14.4 | Total number of request Apdex measurements. For more information, see [Rails request SLIs](../../../development/application_slis/rails_request.md) | `endpoint_id`, `feature_category`, `request_urgency` | +| `gitlab_sli_rails_request_apdex_success_total` | Counter | 14.4 | Total number of successful requests that met the target duration for their urgency. Divide by `gitlab_sli_rails_requests_apdex_total` to get a success ratio | `endpoint_id`, `feature_category`, `request_urgency` | +| `gitlab_sli_rails_request_error_total` | Counter | 15.7 | Total number of request error measurements. For more information, see [Rails request SLIs](../../../development/application_slis/rails_request.md) | `endpoint_id`, `feature_category`, `request_urgency`, `error` | ## Metrics controlled by a feature flag @@ -320,8 +323,6 @@ configuration option in `gitlab.yml`. These metrics are served from the | `geo_container_repositories_synced` | Gauge | 15.4 | Number of container repositories synced on secondary | `url` | | `geo_container_repositories_failed` | Gauge | 15.4 | Number of syncable container repositories failed to sync on secondary | `url` | | `geo_container_repositories_registry` | Gauge | 15.4 | Number of container repositories in the registry | `url` | -| `gitlab_sli:rails_request_apdex:total` | Counter | 14.4 | The number of request-apdex measurements, [more information the development documentation](../../../development/application_slis/rails_request_apdex.md) | `endpoint_id`, `feature_category`, `request_urgency` | -| `gitlab_sli:rails_request_apdex:success_total` | Counter | 14.4 | The number of successful requests that met the target duration for their urgency. Divide by `gitlab_sli:rails_requests_apdex:total` to get a success ratio | `endpoint_id`, `feature_category`, `request_urgency` | | `geo_ci_secure_files` | Gauge | 15.3 | Number of secure files on primary | `url` | | `geo_ci_secure_files_checksum_total` | Gauge | 15.3 | Number of secure files tried to checksum on primary | `url` | | `geo_ci_secure_files_checksummed` | Gauge | 15.3 | Number of secure files successfully checksummed on primary | `url` | diff --git a/doc/administration/object_storage.md b/doc/administration/object_storage.md index 4b3e251d407..aaa5f7b90b0 100644 --- a/doc/administration/object_storage.md +++ b/doc/administration/object_storage.md @@ -912,9 +912,9 @@ When this is used, GitLab fetches temporary credentials each time an S3 bucket is accessed, so no hard-coded values are needed in the configuration. -To use an Amazon instance profile, GitLab must be able to connect to the -[instance metadata endpoint](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html). -If GitLab is [configured to use an Internet proxy](https://docs.gitlab.com/omnibus/settings/environment-variables.html), the endpoint IP +To use an Amazon instance profile, GitLab must be able to connect to the +[instance metadata endpoint](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html). +If GitLab is [configured to use an Internet proxy](https://docs.gitlab.com/omnibus/settings/environment-variables.html), the endpoint IP address must be added to the `no_proxy` list. #### Encrypted S3 buckets diff --git a/doc/ci/runners/saas/macos_saas_runner.md b/doc/ci/runners/saas/macos_saas_runner.md index 96e294ff828..21120732fbe 100644 --- a/doc/ci/runners/saas/macos_saas_runner.md +++ b/doc/ci/runners/saas/macos_saas_runner.md @@ -84,4 +84,4 @@ In SaaS runners on macOS, the objective is to make 90% of CI jobs start executin - If the VM image does not include the specific software version you need for your job, then the job execution time will increase as the required software needs to be fetched and installed. - At this time, it is not possible to bring your own OS image. -- The keychain for user `gitlab` is not publicly available. You must create a keychain instead. +- The keychain for user `gitlab` is not publicly available. You must create a keychain instead. diff --git a/doc/development/application_slis/index.md b/doc/development/application_slis/index.md index bd4587333e0..f48088a6e08 100644 --- a/doc/development/application_slis/index.md +++ b/doc/development/application_slis/index.md @@ -24,7 +24,7 @@ to be emitted from the rails application: ## Existing SLIs -1. [`rails_request_apdex`](rails_request_apdex.md) +1. [`rails_request`](rails_request.md) 1. `global_search_apdex` 1. `global_search_error_rate` 1. `global_search_indexing_apdex` diff --git a/doc/development/application_slis/rails_request_apdex.md b/doc/development/application_slis/rails_request.md index dc9d67b0a2b..fa22b5f6aca 100644 --- a/doc/development/application_slis/rails_request_apdex.md +++ b/doc/development/application_slis/rails_request.md @@ -4,7 +4,7 @@ group: Scalability info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- -# Rails request Apdex SLI +# Rails request SLIs (service level indicators) > [Introduced](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/525) in GitLab 14.4 @@ -12,21 +12,33 @@ NOTE: This SLI is used for service monitoring. But not for [error budgets for stage groups](../stage_group_observability/index.md#error-budget) by default. You can [opt in](#error-budget-attribution-and-ownership). -The request Apdex SLI (Service Level Indicator) is [an SLI defined in the application](index.md). -It measures the duration of successful requests as an indicator for +The request Apdex SLI and the error rate SLI are [SLIs defined in the application](index.md). + +The request Apdex measures the duration of successful requests as an indicator for application performance. This includes the REST and GraphQL API, and the -regular controller endpoints. It consists of these counters: +regular controller endpoints. + +The error rate measures unsuccessful requests as an indicator for +server misbehavior. This includes the REST API, and the +regular controller endpoints. -1. `gitlab_sli:rails_request_apdex:total`: This counter gets +1. `gitlab_sli_rails_request_apdex_total`: This counter gets incremented for every request that did not result in a response with a `5xx` status code. It ensures slow failures are not counted twice, because the request is already counted in the error SLI. -1. `gitlab_sli:rails_request_apdex:success_total`: This counter gets +1. `gitlab_sli_rails_request_apdex_success_total`: This counter gets incremented for every successful request that performed faster than the [defined target duration depending on the endpoint's urgency](#adjusting-request-urgency). -Both these counters are labeled with: +1. `gitlab_sli_rails_request_error_total`: This counter gets + incremented for every request that resulted in a response + with a `5xx` status code. + +1. `gitlab_sli_rails_request_total`: This counter gets + incremented for every request. + +These counters are labeled with: 1. `endpoint_id`: The identification of the Rails Controller or the Grape-API endpoint. diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md index 01bfdae5999..87308b68582 100644 --- a/doc/development/contributing/merge_request_workflow.md +++ b/doc/development/contributing/merge_request_workflow.md @@ -13,7 +13,7 @@ for community contributions have the [`Seeking community contributions`](issue_w label, but you are free to contribute to any issue you want. If an issue is marked for the current milestone at any time, even -when you are working on it, a GitLab team member may take over the merge request to ensure the work is finished before the release date. +when you are working on it, a GitLab team member may take over the merge request to ensure the work is finished before the release date. If a contributor is no longer actively working on a submitted merge request, we can: diff --git a/doc/development/stage_group_observability/index.md b/doc/development/stage_group_observability/index.md index b275b0bfec2..d549123968c 100644 --- a/doc/development/stage_group_observability/index.md +++ b/doc/development/stage_group_observability/index.md @@ -68,11 +68,11 @@ component can have two indicators: and [Web](https://gitlab.com/gitlab-com/runbooks/-/blob/f22f40b2c2eab37d85e23ccac45e658b2c914445/metrics-catalog/services/web.jsonnet#L154) services, that threshold is **5 seconds** when not opted in to the - [`rails_requests` SLI](../application_slis/rails_request_apdex.md). + [`rails_request` SLI](../application_slis/rails_request.md). We've made this target configurable in [this project](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/525). - To learn how to customize the request Apdex, see - [Rails request Apdex SLI](../application_slis/rails_request_apdex.md). + To customize the request Apdex, see + [Rails request SLIs](../application_slis/rails_request.md). This new Apdex measurement is not part of the error budget until you [opt in](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1451). diff --git a/doc/integration/partner_marketplace.md b/doc/integration/partner_marketplace.md index 57c409824a5..a1e55561a7a 100644 --- a/doc/integration/partner_marketplace.md +++ b/doc/integration/partner_marketplace.md @@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Marketplace partner integration GitLab supports automation for selected distribution marketplaces to process sales of GitLab products to authorized -channel partners. Marketplace partners can use the GitLab Marketplace APIs to integrate their systems with GitLab to +channel partners. Marketplace partners can use the GitLab Marketplace APIs to integrate their systems with GitLab to sell GitLab subscriptions on their site. This document's target audience is third-party developers for Marketplace partners. diff --git a/doc/operations/error_tracking.md b/doc/operations/error_tracking.md index cb46b08c279..b52a2599ce9 100644 --- a/doc/operations/error_tracking.md +++ b/doc/operations/error_tracking.md @@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w WARNING: This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/389991) -for use in GitLab 15.9, and is planned for removal in GitLab 16.0. We are replacing this feature with functionality in the [GitLab Observability UI](https://gitlab.com/gitlab-org/opstrace/opstrace-ui). Please also reference our direction for [Observability](https://about.gitlab.com/direction/monitor/observability/) and [data visualization](https://about.gitlab.com/direction/monitor/observability/data-visualization/). +for use in GitLab 15.9, and is planned for removal in GitLab 16.0. We are replacing this feature with functionality in the [GitLab Observability UI](https://gitlab.com/gitlab-org/opstrace/opstrace-ui). Please also reference our direction for [Observability](https://about.gitlab.com/direction/monitor/observability/) and [data visualization](https://about.gitlab.com/direction/monitor/observability/data-visualization/). Error Tracking allows developers to discover and view errors generated by their application. Because error information is surfaced where the code is being developed, efficiency and awareness are increased. diff --git a/doc/operations/feature_flags.md b/doc/operations/feature_flags.md index 68fc0fb9499..4da7a57d327 100644 --- a/doc/operations/feature_flags.md +++ b/doc/operations/feature_flags.md @@ -151,7 +151,7 @@ Enables the feature for a list of target users. It is implemented using the Unleash UserIDs (`userWithId`) activation [strategy](https://docs.getunleash.io/reference/activation-strategies#userids). Enter user IDs as a comma-separated list of values (for example, -`user@example.com, user2@example.com`, or `username1,username2,username3`, and so on). +`user@example.com, user2@example.com`, or `username1,username2,username3`, and so on). User IDs are identifiers for your application users. They do not need to be GitLab users. WARNING: diff --git a/doc/update/index.md b/doc/update/index.md index e6b7dc69b24..77c19097aed 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -520,10 +520,10 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap - GitLab Runner 15.7.0 introduced a breaking change that impacts CI/CD jobs: [Correctly handle expansion of job file variables](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3613). Previously, job-defined variables that referred to [file type variables](../ci/variables/index.md#use-file-type-cicd-variables) - were expanded to the value of the file variable (its content). This behavior did not + were expanded to the value of the file variable (its content). This behavior did not respect the typical rules of shell variable expansion. There was also the potential - that secrets or sensitive information could leak if the file variable and its - contents printed. For example, if they were printed in an echo output. For more information, + that secrets or sensitive information could leak if the file variable and its + contents printed. For example, if they were printed in an echo output. For more information, see [Understanding the file type variable expansion change in GitLab 15.7](https://about.gitlab.com/blog/2023/02/13/impact-of-the-file-type-variable-change-15-7/). - Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.0 - 15.6.6 and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.6.7, 15.7.3, or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover. - Due to [a bug introduced in GitLab 15.4](https://gitlab.com/gitlab-org/gitlab/-/issues/390155), if one or more Git repositories in Gitaly Cluster is [unavailable](../administration/gitaly/recovery.md#unavailable-repositories), then [Repository checks](../administration/repository_checks.md#repository-checks) and [Geo replication and verification](../administration/geo/index.md) stop running for all project or project wiki repositories in the affected Gitaly Cluster. The bug was fixed by [reverting the change in GitLab 15.9.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110823). Before upgrading to this version, check if you have any "unavailable" repositories. See [the bug issue](https://gitlab.com/gitlab-org/gitlab/-/issues/390155) for more information. diff --git a/doc/user/infrastructure/iac/terraform_state.md b/doc/user/infrastructure/iac/terraform_state.md index 93a82023480..ad4863c226f 100644 --- a/doc/user/infrastructure/iac/terraform_state.md +++ b/doc/user/infrastructure/iac/terraform_state.md @@ -58,7 +58,7 @@ WARNING: Like any other job artifact, Terraform plan data is viewable by anyone with the Guest role on the repository. Neither Terraform nor GitLab encrypts the plan file by default. If your Terraform plan includes sensitive data, like passwords, access tokens, or certificates, you should -encrypt plan output or modify the project visibility settings. We also strongly recommend that you **disable** +encrypt plan output or modify the project visibility settings. We also strongly recommend that you **disable** [public pipelines](../../../ci/pipelines/settings.md#change-pipeline-visibility-for-non-project-members-in-public-projects) by setting the artifact's public flag to false (`public: false`). This setting ensures artifacts are accessible only to GitLab Administrators and project members with the Reporter role and above. diff --git a/doc/user/packages/npm_registry/index.md b/doc/user/packages/npm_registry/index.md index 11e3d0e5131..7577a9446c8 100644 --- a/doc/user/packages/npm_registry/index.md +++ b/doc/user/packages/npm_registry/index.md @@ -266,7 +266,7 @@ The GitLab npm repository supports the following commands for the npm CLI (`npm` ### `404 Not Found` errors are happening on `npm install` or `yarn` -Using `CI_JOB_TOKEN` to install npm packages with dependencies in another project gives you 404 Not Found errors. A fix for this problem is proposed in [issue 352962](https://gitlab.com/gitlab-org/gitlab/-/issues/352962). +Using `CI_JOB_TOKEN` to install npm packages with dependencies in another project gives you 404 Not Found errors. A fix for this problem is proposed in [issue 352962](https://gitlab.com/gitlab-org/gitlab/-/issues/352962). As a workaround, you can: diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 8e736b6d83e..aa292ca18a3 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -469,7 +469,7 @@ subscriptions. > - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110810) in GitLab 15.9. Custom roles allow group members who are assigned the Owner role to create roles -specific to the needs of their organization. +specific to the needs of their organization. <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> For a demo of the custom roles feature, see [[Demo] Ultimate Guest can view code on private repositories via custom role](https://www.youtube.com/watch?v=46cp_-Rtxps). diff --git a/lib/api/draft_notes.rb b/lib/api/draft_notes.rb index 842180652c4..7305a1702ab 100644 --- a/lib/api/draft_notes.rb +++ b/lib/api/draft_notes.rb @@ -30,6 +30,18 @@ module API .new(merge_request(params: params), current_user) .execute(get_draft_note(params: params)) end + + def authorize_create_note!(params:) + access_denied! unless can?(current_user, :create_note, merge_request(params: params)) + end + + def draft_note_params + { + note: params[:note], + commit_id: params[:commit_id] == 'undefined' ? nil : params[:commit_id], + resolve_discussion: params[:resolve_discussion] || false + } + end end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -71,6 +83,36 @@ module API end end + desc "Create a new draft note" do + success Entities::DraftNote + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + end + params do + requires :id, type: String, desc: "The ID of a project." + requires :merge_request_iid, type: Integer, desc: "The ID of a merge request." + requires :note, type: String, desc: 'The content of a note.' + optional :in_reply_to_discussion_id, type: Integer, desc: 'The ID of a discussion the draft note replies to.' + optional :commit_id, type: String, desc: 'The sha of a commit to associate the draft note to.' + optional :resolve_discussion, type: Boolean, desc: 'The associated discussion should be resolved.' + end + post ":id/merge_requests/:merge_request_iid/draft_notes", feature_category: :code_review_workflow do + authorize_create_note!(params: params) + + create_params = draft_note_params.merge(in_reply_to_discussion_id: params[:in_reply_to_discussion_id]) + create_service = ::DraftNotes::CreateService.new(merge_request(params: params), current_user, create_params) + + draft_note = create_service.execute + + if draft_note.persisted? + present draft_note, with: Entities::DraftNote + else + render_validation_error!(draft_note) + end + end + desc "Delete a draft note" do success Entities::DraftNote failure [ diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 8fe5868ca57..971737e21af 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -118,12 +118,17 @@ module Gitlab end def setup(domain:, default_locale:) + custom_pluralization setup_repositories(domain) setup_default_locale(default_locale) end private + def custom_pluralization + Gitlab::I18n::Pluralization.install_on(FastGettext) + end + def setup_repositories(domain) translation_repositories = [ (po_repository(domain, 'jh/locale') if Gitlab.jh?), diff --git a/lib/gitlab/i18n/pluralization.rb b/lib/gitlab/i18n/pluralization.rb new file mode 100644 index 00000000000..5d4a05f1fd1 --- /dev/null +++ b/lib/gitlab/i18n/pluralization.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module Gitlab + module I18n + # Pluralization formulas per locale used by FastGettext via: + # `FastGettext.pluralisation_rule.call(count)`. + module Pluralization + # rubocop:disable all + MAP = { + "bg" => ->(n) { (n != 1) }, + "cs_CZ" => ->(n) { (n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3 }, + "da_DK" => ->(n) { (n != 1) }, + "de" => ->(n) { (n != 1) }, + "en" => ->(n) { (n != 1) }, + "eo" => ->(n) { (n != 1) }, + "es" => ->(n) { (n != 1) }, + "fil_PH" => ->(n) { (n > 1) }, + "fr" => ->(n) { (n > 1) }, + "gl_ES" => ->(n) { (n != 1) }, + "id_ID" => ->(n) { 0 }, + "it" => ->(n) { (n != 1) }, + "ja" => ->(n) { 0 }, + "ko" => ->(n) { 0 }, + "nb_NO" => ->(n) { (n != 1) }, + "nl_NL" => ->(n) { (n != 1) }, + "pl_PL" => ->(n) { (n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3) }, + "pt_BR" => ->(n) { (n != 1) }, + "ro_RO" => ->(n) { (n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2) }, + "ru" => ->(n) { ((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3)) }, + "si_LK" => ->(n) { (n != 1) }, + "tr_TR" => ->(n) { (n != 1) }, + "uk" => ->(n) { ((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3)) }, + "zh_CN" => ->(n) { 0 }, + "zh_HK" => ->(n) { 0 }, + "zh_TW" => ->(n) { 0 } + }.freeze + # rubocop:enable + + NOT_FOUND_ERROR = lambda do |locale| + po = File.expand_path("../../../locale/#{locale}/gitlab.po", __dir__) + + forms = File.read(po)[/Plural-Forms:.*; plural=(.*?);\\n/, 1] if File.exist?(po) + suggestion = <<~TEXT if forms + Add the following line to #{__FILE__}: + + MAP = { + ... + "#{locale}" => ->(n) { #{forms} }, + ... + }.freeze + + This rule was extracted from #{po}. + TEXT + + raise ArgumentError, <<~MESSAGE + Missing pluralization rule for locale #{locale.inspect}. + + #{suggestion} + MESSAGE + end + + def self.call(count) + locale = FastGettext.locale + + MAP.fetch(locale, &NOT_FOUND_ERROR).call(count) + end + + def self.install_on(klass) + klass.extend(FastGettextClassMethods) + end + + module FastGettextClassMethods + # FastGettext allows to set the rule via + # `FastGettext.pluralisation_rule=` which is on thread-level. + # + # Because we are patching FastGettext at boot time per thread values + # won't work so we have to override the method implementation. + # + # `FastGettext.pluralisation_rule=` has now no effect. + def pluralisation_rule + Gitlab::I18n::Pluralization + end + end + end + end +end diff --git a/lib/gitlab/instrumentation/redis_base.rb b/lib/gitlab/instrumentation/redis_base.rb index de24132a28e..3ee8768d509 100644 --- a/lib/gitlab/instrumentation/redis_base.rb +++ b/lib/gitlab/instrumentation/redis_base.rb @@ -129,6 +129,10 @@ module Gitlab @request_latency_histogram.observe({ storage: storage_key }, duration) end + def log_exception(ex) + ::Gitlab::ErrorTracking.log_exception(ex, storage: storage_key) + end + private def request_count_key diff --git a/lib/gitlab/instrumentation/redis_interceptor.rb b/lib/gitlab/instrumentation/redis_interceptor.rb index 35dd7cbfeb8..82531883810 100644 --- a/lib/gitlab/instrumentation/redis_interceptor.rb +++ b/lib/gitlab/instrumentation/redis_interceptor.rb @@ -41,6 +41,7 @@ module Gitlab yield rescue ::Redis::BaseError => ex instrumentation_class.instance_count_exception(ex) + instrumentation_class.log_exception(ex) raise ex ensure duration = Gitlab::Metrics::System.monotonic_time - start diff --git a/lib/gitlab/redis/cache.rb b/lib/gitlab/redis/cache.rb index 647573e59fe..ba3af3e7a6f 100644 --- a/lib/gitlab/redis/cache.rb +++ b/lib/gitlab/redis/cache.rb @@ -2,16 +2,6 @@ module Gitlab module Redis - # Match signature in - # https://github.com/rails/rails/blob/v6.1.7.2/activesupport/lib/active_support/cache/redis_cache_store.rb#L59 - ERROR_HANDLER = ->(method:, returning:, exception:) do - Gitlab::ErrorTracking.log_exception( - exception, - method: method, - returning: returning.inspect - ) - end - class Cache < ::Gitlab::Redis::Wrapper CACHE_NAMESPACE = 'cache:gitlab' @@ -22,8 +12,7 @@ module Gitlab redis: pool, compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')), namespace: CACHE_NAMESPACE, - expires_in: default_ttl_seconds, - error_handler: ::Gitlab::Redis::ERROR_HANDLER + expires_in: default_ttl_seconds } end diff --git a/lib/gitlab/redis/rate_limiting.rb b/lib/gitlab/redis/rate_limiting.rb index 12710bafbea..3a9fb63a495 100644 --- a/lib/gitlab/redis/rate_limiting.rb +++ b/lib/gitlab/redis/rate_limiting.rb @@ -10,11 +10,7 @@ module Gitlab end def cache_store - @cache_store ||= ActiveSupport::Cache::RedisCacheStore.new( - redis: pool, - namespace: Cache::CACHE_NAMESPACE, - error_handler: ::Gitlab::Redis::ERROR_HANDLER - ) + @cache_store ||= ActiveSupport::Cache::RedisCacheStore.new(redis: pool, namespace: Cache::CACHE_NAMESPACE) end private diff --git a/lib/gitlab/redis/repository_cache.rb b/lib/gitlab/redis/repository_cache.rb index 6c7bc8c41d5..d8d434ae062 100644 --- a/lib/gitlab/redis/repository_cache.rb +++ b/lib/gitlab/redis/repository_cache.rb @@ -14,8 +14,7 @@ module Gitlab redis: pool, compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')), namespace: Cache::CACHE_NAMESPACE, - expires_in: Cache.default_ttl_seconds, - error_handler: ::Gitlab::Redis::ERROR_HANDLER + expires_in: Cache.default_ttl_seconds ) end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1b34e6e24d5..24f81b76e96 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6958,15 +6958,27 @@ msgstr[1] "" msgid "Boards|Collapse" msgstr "" +msgid "Boards|Create new epic" +msgstr "" + +msgid "Boards|Create new issue" +msgstr "" + msgid "Boards|Edit board" msgstr "" +msgid "Boards|Edit list settings" +msgstr "" + msgid "Boards|Expand" msgstr "" msgid "Boards|Failed to fetch blocking %{issuableType}s" msgstr "" +msgid "Boards|List actions" +msgstr "" + msgid "Boards|Move card" msgstr "" @@ -6979,9 +6991,6 @@ msgstr "" msgid "Boards|New board" msgstr "" -msgid "Boards|New epic" -msgstr "" - msgid "Boards|Retrieving blocking %{issuableType}s" msgstr "" @@ -32458,18 +32467,33 @@ msgstr "" msgid "ProductAnalytics|All pages" msgstr "" +msgid "ProductAnalytics|All sessions compared" +msgstr "" + msgid "ProductAnalytics|An error occured while loading the %{panelTitle} panel." msgstr "" msgid "ProductAnalytics|An error occurred while fetching data. Refresh the page to try again." msgstr "" +msgid "ProductAnalytics|Analytics dashboards" +msgstr "" + msgid "ProductAnalytics|Any Click on elements" msgstr "" msgid "ProductAnalytics|Audience" msgstr "" +msgid "ProductAnalytics|Average Session Duration" +msgstr "" + +msgid "ProductAnalytics|Average duration in minutes" +msgstr "" + +msgid "ProductAnalytics|Average per User" +msgstr "" + msgid "ProductAnalytics|Browser" msgstr "" @@ -32494,6 +32518,9 @@ msgstr "" msgid "ProductAnalytics|Compares all events against each other" msgstr "" +msgid "ProductAnalytics|Compares all user sessions against each other" +msgstr "" + msgid "ProductAnalytics|Compares click events against each other" msgstr "" @@ -32542,6 +32569,12 @@ msgstr "" msgid "ProductAnalytics|Host" msgstr "" +msgid "ProductAnalytics|How many sessions a user has" +msgstr "" + +msgid "ProductAnalytics|How often sesions are repeated" +msgstr "" + msgid "ProductAnalytics|Language" msgstr "" @@ -32554,6 +32587,12 @@ msgstr "" msgid "ProductAnalytics|Measure all or specific Page Views" msgstr "" +msgid "ProductAnalytics|Measure all sessions" +msgstr "" + +msgid "ProductAnalytics|Measure by unique users" +msgstr "" + msgid "ProductAnalytics|Measuring" msgstr "" @@ -32587,15 +32626,18 @@ msgstr "" msgid "ProductAnalytics|Panel" msgstr "" -msgid "ProductAnalytics|Product analytics dashboards" +msgid "ProductAnalytics|Referer" msgstr "" -msgid "ProductAnalytics|Referer" +msgid "ProductAnalytics|Repeat Visit percentage" msgstr "" msgid "ProductAnalytics|Resulting Data" msgstr "" +msgid "ProductAnalytics|Sessions" +msgstr "" + msgid "ProductAnalytics|Single Statistic" msgstr "" @@ -32608,6 +32650,12 @@ msgstr "" msgid "ProductAnalytics|URL" msgstr "" +msgid "ProductAnalytics|Unique Users" +msgstr "" + +msgid "ProductAnalytics|User Sessions" +msgstr "" + msgid "ProductAnalytics|User activity" msgstr "" diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb index da62acb1fda..93857435c9d 100644 --- a/spec/controllers/repositories/git_http_controller_spec.rb +++ b/spec/controllers/repositories/git_http_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Repositories::GitHttpController do +RSpec.describe Repositories::GitHttpController, feature_category: :source_code_management do let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:personal_snippet) { create(:personal_snippet, :public, :repository) } let_it_be(:project_snippet) { create(:project_snippet, :public, :repository, project: project) } @@ -14,7 +14,7 @@ RSpec.describe Repositories::GitHttpController do request.headers.merge! auth_env(user.username, user.password, nil) end - context 'when Gitaly is unavailable' do + context 'when Gitaly is unavailable', :use_clean_rails_redis_caching do it 'responds with a 503 message' do expect(Gitlab::GitalyClient).to receive(:call).and_raise(GRPC::Unavailable) diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 87896b60f87..1ea6e079104 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -591,7 +591,9 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do def remove_list page.within(find('.board:nth-child(2)')) do - find('button[title="List settings"]').click + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button('Edit list settings') end page.within(find('.js-board-settings-sidebar')) do diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index d597c57ac1c..6753f0ea009 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -32,18 +32,23 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d end it 'displays new issue button' do - expect(first('.board')).to have_button('New issue', count: 1) + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + expect(first('.board')).to have_button('Create new issue', count: 1) end it 'does not display new issue button in closed list' do page.within('.board:nth-child(3)') do - expect(page).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(page).not_to have_button('Create new issue') end end it 'shows form when clicking button' do page.within(first('.board')) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' expect(page).to have_selector('.board-new-issue-form') end @@ -51,7 +56,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d it 'hides form when clicking cancel' do page.within(first('.board')) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' expect(page).to have_selector('.board-new-issue-form') @@ -63,7 +70,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d it 'creates new issue, places it on top of the list, and opens sidebar' do page.within(first('.board')) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' end page.within(first('.board-new-issue-form')) do @@ -91,7 +100,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d it 'successfuly loads labels to be added to newly created issue' do page.within(first('.board')) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' end page.within(first('.board-new-issue-form')) do @@ -121,7 +132,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d wait_for_all_requests page.within('.board:nth-child(2)') do - click_button('New issue') + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button('Create new issue') page.within(first('.board-new-issue-form')) do find('.form-control').set('new issue') @@ -144,12 +157,14 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d end it 'does not display new issue button in open list' do - expect(first('.board')).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(first('.board')).not_to have_button('Create new issue') end it 'does not display new issue button in label list' do page.within('.board:nth-child(2)') do - expect(page).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(page).not_to have_button('Create new issue') end end end @@ -173,7 +188,8 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d context 'when backlog does not exist' do it 'does not display new issue button in label list' do page.within('.board.is-draggable') do - expect(page).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(page).not_to have_button('Create new issue') end end end @@ -182,12 +198,14 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d let_it_be(:backlog_list) { create(:backlog_list, board: group_board) } it 'does not display new issue button in open list' do - expect(first('.board')).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(first('.board')).not_to have_button('Create new issue') end it 'does not display new issue button in label list' do page.within('.board.is-draggable') do - expect(page).not_to have_button('New issue') + expect(page).not_to have_selector("[data-testid='header-list-actions']") + expect(page).not_to have_button('Create new issue') end end end @@ -205,7 +223,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d context 'when backlog does not exist' do it 'display new issue button in label list' do - expect(board_list_header).to have_button('New issue') + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + expect(board_list_header).to have_button('Create new issue') end end @@ -214,7 +234,9 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d before do page.within(board_list_header) do - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + click_button 'Create new issue' end project_select_dropdown.click diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb index c451a97bed5..8acf3ffe441 100644 --- a/spec/features/groups/board_spec.rb +++ b/spec/features/groups/board_spec.rb @@ -25,8 +25,10 @@ RSpec.describe 'Group Boards', feature_category: :team_planning do it 'adds an issue to the backlog' do page.within(find('.board', match: :first)) do - issue_title = 'New Issue' - click_button 'New issue' + dropdown = first("[data-testid='header-list-actions']") + dropdown.click + issue_title = 'Create new issue' + click_button issue_title wait_for_requests diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js index 9e65e900440..f8865bb6feb 100644 --- a/spec/frontend/boards/components/board_list_header_spec.js +++ b/spec/frontend/boards/components/board_list_header_spec.js @@ -1,10 +1,9 @@ -import { shallowMount } from '@vue/test-utils'; +import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import Vuex from 'vuex'; import createMockApollo from 'helpers/mock_apollo_helper'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; - +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { boardListQueryResponse, mockLabelList } from 'jest/boards/mock_data'; import BoardListHeader from '~/boards/components/board_list_header.vue'; import { ListType } from '~/boards/constants'; @@ -64,31 +63,33 @@ describe('Board List Header Component', () => { fakeApollo = createMockApollo([[listQuery, listQueryHandler]]); - wrapper = extendedWrapper( - shallowMount(BoardListHeader, { - apolloProvider: fakeApollo, - store, - propsData: { - list: listMock, - }, - provide: { - boardId, - weightFeatureAvailable: false, - currentUserId, - isEpicBoard: false, - disabled: false, - ...injectedProps, - }, - }), - ); + wrapper = shallowMountExtended(BoardListHeader, { + apolloProvider: fakeApollo, + store, + propsData: { + list: listMock, + }, + provide: { + boardId, + weightFeatureAvailable: false, + currentUserId, + isEpicBoard: false, + disabled: false, + ...injectedProps, + }, + stubs: { + GlDisclosureDropdown, + GlDisclosureDropdownItem, + }, + }); }; + const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); const isCollapsed = () => wrapper.vm.list.collapsed; - - const findAddIssueButton = () => wrapper.findComponent({ ref: 'newIssueBtn' }); const findTitle = () => wrapper.find('.board-title'); const findCaret = () => wrapper.findByTestId('board-title-caret'); - const findSettingsButton = () => wrapper.findComponent({ ref: 'settingsBtn' }); + const findNewIssueButton = () => wrapper.findByTestId('newIssueBtn'); + const findSettingsButton = () => wrapper.findByTestId('settingsBtn'); describe('Add issue button', () => { const hasNoAddButton = [ListType.closed]; @@ -100,59 +101,49 @@ describe('Board List Header Component', () => { ListType.assignee, ]; - it.each(hasNoAddButton)('does not render when List Type is `%s`', (listType) => { + it.each(hasNoAddButton)('does not render dropdown when List Type is `%s`', (listType) => { createComponent({ listType }); - expect(findAddIssueButton().exists()).toBe(false); + expect(findDropdown().exists()).toBe(false); }); it.each(hasAddButton)('does render when List Type is `%s`', (listType) => { createComponent({ listType }); - expect(findAddIssueButton().exists()).toBe(true); + expect(findDropdown().exists()).toBe(true); + expect(findNewIssueButton().exists()).toBe(true); }); - it('has a test for each list type', () => { - createComponent(); - - Object.values(ListType).forEach((value) => { - expect([...hasAddButton, ...hasNoAddButton]).toContain(value); - }); - }); - - it('does not render when logged out', () => { + it('does not render dropdown when logged out', () => { createComponent({ currentUserId: null, }); - expect(findAddIssueButton().exists()).toBe(false); + expect(findDropdown().exists()).toBe(false); }); }); describe('Settings Button', () => { - describe('with disabled=true', () => { - const hasSettings = [ - ListType.assignee, - ListType.milestone, - ListType.iteration, - ListType.label, - ]; - const hasNoSettings = [ListType.backlog, ListType.closed]; - - it.each(hasSettings)('does render for List Type `%s` when disabled=true', (listType) => { - createComponent({ listType, injectedProps: { disabled: true } }); - - expect(findSettingsButton().exists()).toBe(true); - }); + const hasSettings = [ListType.assignee, ListType.milestone, ListType.iteration, ListType.label]; - it.each(hasNoSettings)( - 'does not render for List Type `%s` when disabled=true', - (listType) => { - createComponent({ listType }); + it.each(hasSettings)('does render for List Type `%s`', (listType) => { + createComponent({ listType }); - expect(findSettingsButton().exists()).toBe(false); - }, - ); + expect(findDropdown().exists()).toBe(true); + expect(findSettingsButton().exists()).toBe(true); + }); + + it('does not render dropdown when ListType `closed`', () => { + createComponent({ listType: ListType.closed }); + + expect(findDropdown().exists()).toBe(false); + }); + + it('renders dropdown but not the Settings button when ListType `backlog`', () => { + createComponent({ listType: ListType.backlog }); + + expect(findDropdown().exists()).toBe(true); + expect(findSettingsButton().exists()).toBe(false); }); }); diff --git a/spec/lib/gitlab/i18n/pluralization_spec.rb b/spec/lib/gitlab/i18n/pluralization_spec.rb new file mode 100644 index 00000000000..857562d549c --- /dev/null +++ b/spec/lib/gitlab/i18n/pluralization_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rspec-parameterized' +require 'gettext_i18n_rails' + +RSpec.describe Gitlab::I18n::Pluralization, feature_category: :internationalization do + describe '.call' do + subject(:rule) { described_class.call(1) } + + context 'with available locales' do + around do |example| + Gitlab::I18n.with_locale(locale, &example) + end + + where(:locale) do + Gitlab::I18n.available_locales + end + + with_them do + it 'supports pluralization' do + expect(rule).not_to be_nil + end + end + + context 'with missing rules' do + let(:locale) { "pl_PL" } + + before do + stub_const("#{described_class}::MAP", described_class::MAP.except(locale)) + end + + it 'raises an ArgumentError' do + expect { rule }.to raise_error(ArgumentError, + /Missing pluralization rule for locale "#{locale}"/ + ) + end + end + end + end + + describe '.install_on' do + let(:mod) { Module.new } + + before do + described_class.install_on(mod) + end + + it 'adds pluralisation_rule method' do + expect(mod.pluralisation_rule).to eq(described_class) + end + end +end diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb index b752d89bf0d..ee92831922d 100644 --- a/spec/lib/gitlab/i18n_spec.rb +++ b/spec/lib/gitlab/i18n_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::I18n do +RSpec.describe Gitlab::I18n, feature_category: :internationalization do let(:user) { create(:user, preferred_language: :es) } describe '.selectable_locales' do @@ -47,4 +47,19 @@ RSpec.describe Gitlab::I18n do expect(::I18n.locale).to eq(:en) end end + + describe '.pluralisation_rule' do + context 'when overridden' do + before do + # Internally, FastGettext sets + # Thread.current[:fast_gettext_pluralisation_rule]. + # Our patch patches `FastGettext.pluralisation_rule` instead. + FastGettext.pluralisation_rule = :something + end + + it 'returns custom definition regardless' do + expect(FastGettext.pluralisation_rule).to eq(Gitlab::I18n::Pluralization) + end + end + end end diff --git a/spec/lib/gitlab/instrumentation/redis_base_spec.rb b/spec/lib/gitlab/instrumentation/redis_base_spec.rb index 656e6ffba05..426997f6e86 100644 --- a/spec/lib/gitlab/instrumentation/redis_base_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_base_spec.rb @@ -210,4 +210,16 @@ RSpec.describe Gitlab::Instrumentation::RedisBase, :request_store do end end end + + describe '.log_exception' do + it 'logs exception with storage details' do + expect(::Gitlab::ErrorTracking).to receive(:log_exception) + .with( + an_instance_of(StandardError), + storage: instrumentation_class_a.storage_key + ) + + instrumentation_class_a.log_exception(StandardError.new) + end + end end diff --git a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb index 187a6ff1739..63fdad0cab2 100644 --- a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb @@ -67,6 +67,8 @@ RSpec.describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_sh it 'counts exceptions' do expect(instrumentation_class).to receive(:instance_count_exception) .with(instance_of(Redis::CommandError)).and_call_original + expect(instrumentation_class).to receive(:log_exception) + .with(instance_of(Redis::CommandError)).and_call_original expect(instrumentation_class).to receive(:instance_count_request).and_call_original expect do diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb index 64615c4d9ad..82ff8a26199 100644 --- a/spec/lib/gitlab/redis/cache_spec.rb +++ b/spec/lib/gitlab/redis/cache_spec.rb @@ -26,22 +26,5 @@ RSpec.describe Gitlab::Redis::Cache do expect(described_class.active_support_config[:expires_in]).to eq(1.day) end - - context 'when encountering an error' do - let(:cache) { ActiveSupport::Cache::RedisCacheStore.new(**described_class.active_support_config) } - - subject { cache.read('x') } - - before do - described_class.with do |redis| - allow(redis).to receive(:get).and_raise(::Redis::CommandError) - end - end - - it 'logs error' do - expect(::Gitlab::ErrorTracking).to receive(:log_exception) - subject - end - end end end diff --git a/spec/lib/gitlab/redis/rate_limiting_spec.rb b/spec/lib/gitlab/redis/rate_limiting_spec.rb index d82228426f0..e79c070df93 100644 --- a/spec/lib/gitlab/redis/rate_limiting_spec.rb +++ b/spec/lib/gitlab/redis/rate_limiting_spec.rb @@ -4,21 +4,4 @@ require 'spec_helper' RSpec.describe Gitlab::Redis::RateLimiting do include_examples "redis_new_instance_shared_examples", 'rate_limiting', Gitlab::Redis::Cache - - describe '.cache_store' do - context 'when encountering an error' do - subject { described_class.cache_store.read('x') } - - before do - described_class.with do |redis| - allow(redis).to receive(:get).and_raise(::Redis::CommandError) - end - end - - it 'logs error' do - expect(::Gitlab::ErrorTracking).to receive(:log_exception) - subject - end - end - end end diff --git a/spec/lib/gitlab/redis/repository_cache_spec.rb b/spec/lib/gitlab/redis/repository_cache_spec.rb index 2c167a6eb62..8cdc4580f9e 100644 --- a/spec/lib/gitlab/redis/repository_cache_spec.rb +++ b/spec/lib/gitlab/redis/repository_cache_spec.rb @@ -17,20 +17,5 @@ RSpec.describe Gitlab::Redis::RepositoryCache, feature_category: :scalability do it 'has a default ttl of 8 hours' do expect(described_class.cache_store.options[:expires_in]).to eq(8.hours) end - - context 'when encountering an error' do - subject { described_class.cache_store.read('x') } - - before do - described_class.with do |redis| - allow(redis).to receive(:get).and_raise(::Redis::CommandError) - end - end - - it 'logs error' do - expect(::Gitlab::ErrorTracking).to receive(:log_exception) - subject - end - end end end diff --git a/spec/requests/api/draft_notes_spec.rb b/spec/requests/api/draft_notes_spec.rb index e8f519e004d..56b0c834010 100644 --- a/spec/requests/api/draft_notes_spec.rb +++ b/spec/requests/api/draft_notes_spec.rb @@ -8,6 +8,11 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do let_it_be(:project) { create(:project, :public) } let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } + let_it_be(:private_project) { create(:project, :private) } + let_it_be(:private_merge_request) do + create(:merge_request, source_project: private_project, target_project: private_project) + end + let_it_be(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) } let!(:draft_note_by_current_user) { create(:draft_note, merge_request: merge_request, author: user) } let!(:draft_note_by_random_user) { create(:draft_note, merge_request: merge_request) } @@ -120,6 +125,97 @@ RSpec.describe API::DraftNotes, feature_category: :code_review_workflow do end end + def create_draft_note(params = {}, url = api_stub) + post api("#{url}/draft_notes", user), params: params + end + + describe "Create a new draft note" do + let(:basic_create_params) do + { + note: "Example body string" + } + end + + context "when creating a new draft note" do + context "with required params" do + it "returns 201 Created status" do + create_draft_note(basic_create_params) + + expect(response).to have_gitlab_http_status(:created) + end + + it "creates a new draft note with the submitted params" do + expect { create_draft_note(basic_create_params) }.to change { DraftNote.count }.by(1) + + expect(json_response["note"]).to eq(basic_create_params[:note]) + expect(json_response["merge_request_id"]).to eq(merge_request.id) + expect(json_response["author_id"]).to eq(user.id) + end + end + + context "without required params" do + it "returns 400 Bad Request status" do + create_draft_note({}) + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context "when providing a non-existing commit_id" do + it "returns a 400 Bad Request" do + create_draft_note( + basic_create_params.merge( + commit_id: 'bad SHA' + ) + ) + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context "when targeting a merge request the user doesn't have access to" do + it "returns a 404 Not Found" do + create_draft_note( + basic_create_params, + "/projects/#{private_project.id}/merge_requests/#{private_merge_request.iid}" + ) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context "when attempting to resolve a disscussion" do + context "when providing a non-existant ID" do + it "returns a 400 Bad Request" do + create_draft_note( + basic_create_params.merge( + resolve_discussion: true, + in_reply_to_discussion_id: non_existing_record_id + ) + ) + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context "when not providing an ID" do + it "returns a 400 Bad Request" do + create_draft_note(basic_create_params.merge(resolve_discussion: true)) + + expect(response).to have_gitlab_http_status(:bad_request) + end + + it "returns a validation error message" do + create_draft_note(basic_create_params.merge(resolve_discussion: true)) + + expect(response.body) + .to eq("{\"message\":{\"base\":[\"User is not allowed to resolve thread\"]}}") + end + end + end + end + end + describe "Publishing a draft note" do let(:publish_draft_note) do put api( diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 02b99eba8ce..eaac2a77353 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -472,10 +472,11 @@ RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do end context 'when the request is not from gitlab-workhorse' do - it 'raises an exception' do - expect do - get("/#{project.full_path}.git/info/refs?service=git-upload-pack") - end.to raise_error(JWT::DecodeError) + it 'responds with 403 Forbidden' do + get("/#{project.full_path}.git/info/refs?service=git-upload-pack") + + expect(response).to have_gitlab_http_status(:forbidden) + expect(response.body).to eq('Nil JSON web token') end end @@ -1112,10 +1113,11 @@ RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do end context 'when the request is not from gitlab-workhorse' do - it 'raises an exception' do - expect do - get("/#{project.full_path}.git/info/refs?service=git-upload-pack") - end.to raise_error(JWT::DecodeError) + it 'responds with 403 Forbidden' do + get("/#{project.full_path}.git/info/refs?service=git-upload-pack") + + expect(response).to have_gitlab_http_status(:forbidden) + expect(response.body).to eq('Nil JSON web token') end end diff --git a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb index cc28a79b4ca..39c300d547d 100644 --- a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb @@ -87,14 +87,20 @@ RSpec.shared_examples Repositories::GitHttpController do end describe 'POST #git_upload_pack' do - before do + it 'returns 200' do allow(controller).to receive(:verify_workhorse_api!).and_return(true) - end - it 'returns 200' do post :git_upload_pack, params: params expect(response).to have_gitlab_http_status(:ok) end + + context 'when JWT token is not provided' do + it 'returns 403' do + post :git_upload_pack, params: params + + expect(response).to have_gitlab_http_status(:forbidden) + end + end end end |