From a2ef93ba41020a20f1262da20e7facbaacd154cf Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 30 Jul 2021 21:10:15 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- Gemfile | 2 +- Gemfile.lock | 4 +- .../mailgun/permanent_failures_controller.rb | 65 ----- app/mailers/emails/members.rb | 6 +- app/services/members/mailgun.rb | 8 - .../mailgun/process_webhook_service.rb | 39 --- .../application_settings/_mailgun.html.haml | 2 + .../application_settings/network.html.haml | 3 +- .../nav/sidebar/_group_menus.html.haml | 17 -- .../development/mailgun_events_receiver.yml | 8 + .../20210216180424_i_search_total_monthly.yml | 4 + ...431_search_total_unique_counts_monthly.yml | 6 + ...h_unique_visits_for_any_target_monthly.yml | 6 + .../20210216180422_i_search_total_weekly.yml | 4 + ...0429_search_total_unique_counts_weekly.yml | 6 + config/routes.rb | 1 - config/routes/members.rb | 7 - ...2928_add_invite_email_success_to_member.rb | 6 +- db/structure.sql | 3 +- doc/administration/integration/mailgun.md | 41 --- doc/api/graphql/removed_items.md | 4 +- doc/api/settings.md | 2 +- doc/api/users.md | 24 +- .../documentation/styleguide/word_list.md | 4 + doc/raketasks/backup_restore.md | 2 +- doc/user/admin_area/settings/help_page.md | 2 +- .../rate_limit_on_issues_creation_v13_1.png | Bin 13479 -> 0 bytes .../rate_limit_on_issues_creation_v14_2.png | Bin 0 -> 29368 bytes doc/user/admin_area/settings/index.md | 1 - .../settings/rate_limit_on_issues_creation.md | 2 +- doc/user/project/code_owners.md | 271 ++++++++---------- lib/api/user_counts.rb | 6 +- lib/sidebars/groups/menus/ci_cd_menu.rb | 51 ++++ lib/sidebars/groups/panel.rb | 1 + locale/gitlab.pot | 6 +- .../application_experiment_spec.rb | 5 + ..._project_readme_content_experiment_spec.rb | 6 +- spec/features/admin/admin_settings_spec.rb | 31 +- .../import_export/safe_model_attributes.yml | 1 - .../sidebars/groups/menus/ci_cd_menu_spec.rb | 40 +++ spec/mailers/notify_spec.rb | 8 +- spec/requests/api/user_counts_spec.rb | 38 ++- .../members/mailgun/permanent_failure_spec.rb | 128 --------- .../mailgun/process_webhook_service_spec.rb | 42 --- .../nav/sidebar/_group.html.haml_spec.rb | 23 +- 45 files changed, 371 insertions(+), 565 deletions(-) delete mode 100644 app/controllers/members/mailgun/permanent_failures_controller.rb delete mode 100644 app/services/members/mailgun.rb delete mode 100644 app/services/members/mailgun/process_webhook_service.rb create mode 100644 config/feature_flags/development/mailgun_events_receiver.yml delete mode 100644 config/routes/members.rb delete mode 100644 doc/administration/integration/mailgun.md delete mode 100644 doc/user/admin_area/settings/img/rate_limit_on_issues_creation_v13_1.png create mode 100644 doc/user/admin_area/settings/img/rate_limit_on_issues_creation_v14_2.png create mode 100644 lib/sidebars/groups/menus/ci_cd_menu.rb create mode 100644 spec/lib/sidebars/groups/menus/ci_cd_menu_spec.rb delete mode 100644 spec/requests/members/mailgun/permanent_failure_spec.rb delete mode 100644 spec/services/members/mailgun/process_webhook_service_spec.rb diff --git a/Gemfile b/Gemfile index e4901223f95..9b1076baf87 100644 --- a/Gemfile +++ b/Gemfile @@ -488,7 +488,7 @@ gem 'flipper', '~> 0.21.0' gem 'flipper-active_record', '~> 0.21.0' gem 'flipper-active_support_cache_store', '~> 0.21.0' gem 'unleash', '~> 3.2.2' -gem 'gitlab-experiment', '~> 0.6.2' +gem 'gitlab-experiment', '~> 0.6.3' # Structured logging gem 'lograge', '~> 0.5' diff --git a/Gemfile.lock b/Gemfile.lock index 38dc22cd566..999bed3eb4d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -463,7 +463,7 @@ GEM gitlab-dangerfiles (2.3.0) danger (>= 8.3.1) danger-gitlab (>= 8.0.0) - gitlab-experiment (0.6.2) + gitlab-experiment (0.6.3) activesupport (>= 3.0) request_store (>= 1.0) scientist (~> 1.6, >= 1.6.0) @@ -1468,7 +1468,7 @@ DEPENDENCIES github-markup (~> 1.7.0) gitlab-chronic (~> 0.10.5) gitlab-dangerfiles (~> 2.3.0) - gitlab-experiment (~> 0.6.2) + gitlab-experiment (~> 0.6.3) gitlab-fog-azure-rm (~> 1.1.1) gitlab-labkit (~> 0.21.0) gitlab-license (~> 2.0) diff --git a/app/controllers/members/mailgun/permanent_failures_controller.rb b/app/controllers/members/mailgun/permanent_failures_controller.rb deleted file mode 100644 index 685faa34694..00000000000 --- a/app/controllers/members/mailgun/permanent_failures_controller.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -module Members - module Mailgun - class PermanentFailuresController < ApplicationController - respond_to :json - - skip_before_action :authenticate_user! - skip_before_action :verify_authenticity_token - - before_action :ensure_feature_enabled! - before_action :authenticate_signature! - before_action :validate_invite_email! - - feature_category :authentication_and_authorization - - def create - webhook_processor.execute - - head :ok - end - - private - - def ensure_feature_enabled! - render_406 unless Gitlab::CurrentSettings.mailgun_events_enabled? - end - - def authenticate_signature! - access_denied! unless valid_signature? - end - - def valid_signature? - return false if Gitlab::CurrentSettings.mailgun_signing_key.blank? - - # per this guide: https://documentation.mailgun.com/en/latest/user_manual.html#webhooks - digest = OpenSSL::Digest.new('SHA256') - data = [params.dig(:signature, :timestamp), params.dig(:signature, :token)].join - - hmac_digest = OpenSSL::HMAC.hexdigest(digest, Gitlab::CurrentSettings.mailgun_signing_key, data) - - ActiveSupport::SecurityUtils.secure_compare(params.dig(:signature, :signature), hmac_digest) - end - - def validate_invite_email! - # permanent_failures webhook does not provide a way to filter failures, so we'll get them all on this endpoint - # and we only care about our invite_emails - render_406 unless payload[:tags]&.include?(::Members::Mailgun::INVITE_EMAIL_TAG) - end - - def webhook_processor - ::Members::Mailgun::ProcessWebhookService.new(payload) - end - - def payload - @payload ||= params.permit!['event-data'] - end - - def render_406 - # failure to stop retries per https://documentation.mailgun.com/en/latest/user_manual.html#webhooks - head :not_acceptable - end - end - end -end diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index 5940f8e5a33..fe2d2891547 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -154,10 +154,10 @@ module Emails end def invite_email_headers - if Gitlab::CurrentSettings.mailgun_events_enabled? + if Gitlab.dev_env_or_com? { - 'X-Mailgun-Tag' => ::Members::Mailgun::INVITE_EMAIL_TAG, - 'X-Mailgun-Variables' => { ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY => @token }.to_json + 'X-Mailgun-Tag' => 'invite_email', + 'X-Mailgun-Variables' => { 'invite_token' => @token }.to_json } else {} diff --git a/app/services/members/mailgun.rb b/app/services/members/mailgun.rb deleted file mode 100644 index 43fb5a14ef1..00000000000 --- a/app/services/members/mailgun.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -module Members - module Mailgun - INVITE_EMAIL_TAG = 'invite_email' - INVITE_EMAIL_TOKEN_KEY = :invite_token - end -end diff --git a/app/services/members/mailgun/process_webhook_service.rb b/app/services/members/mailgun/process_webhook_service.rb deleted file mode 100644 index e359a83ad42..00000000000 --- a/app/services/members/mailgun/process_webhook_service.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -module Members - module Mailgun - class ProcessWebhookService - ProcessWebhookServiceError = Class.new(StandardError) - - def initialize(payload) - @payload = payload - end - - def execute - @member = Member.find_by_invite_token(invite_token) - update_member_and_log if member - rescue ProcessWebhookServiceError => e - Gitlab::ErrorTracking.track_exception(e) - end - - private - - attr_reader :payload, :member - - def update_member_and_log - log_update_event if member.update(invite_email_success: false) - end - - def log_update_event - Gitlab::AppLogger.info "UPDATED MEMBER INVITE_EMAIL_SUCCESS: member_id: #{member.id}" - end - - def invite_token - # may want to validate schema in some way using ::JSONSchemer.schema(SCHEMA_PATH).valid?(message) if this - # gets more complex - payload.dig('user-variables', ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY) || - raise(ProcessWebhookServiceError, "Failed to receive #{::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY} in user-variables: #{payload}") - end - end - end -end diff --git a/app/views/admin/application_settings/_mailgun.html.haml b/app/views/admin/application_settings/_mailgun.html.haml index 40b4d5cac6d..6204f7df5dc 100644 --- a/app/views/admin/application_settings/_mailgun.html.haml +++ b/app/views/admin/application_settings/_mailgun.html.haml @@ -1,3 +1,5 @@ +- return unless Feature.enabled?(:mailgun_events_receiver) + - expanded = integration_expanded?('mailgun_') %section.settings.as-mailgun.no-animate#js-mailgun-settings{ class: ('expanded' if expanded) } .settings-header diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml index 72a27e4523f..500b7b95189 100644 --- a/app/views/admin/application_settings/network.html.haml +++ b/app/views/admin/application_settings/network.html.haml @@ -68,7 +68,8 @@ %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded_by_default? ? _('Collapse') : _('Expand') %p - = _('Configure limit for issues created per minute by web and API requests.') + = _('Limit the number of issues per minute a user can create through web and API requests.') + = link_to _('Learn more.'), help_page_path('user/admin_area/settings/rate_limit_on_issues_creation.md'), target: '_blank', rel: 'noopener noreferrer' .settings-content = render 'issue_limits' diff --git a/app/views/layouts/nav/sidebar/_group_menus.html.haml b/app/views/layouts/nav/sidebar/_group_menus.html.haml index 40d994bdf36..42114287cdf 100644 --- a/app/views/layouts/nav/sidebar/_group_menus.html.haml +++ b/app/views/layouts/nav/sidebar/_group_menus.html.haml @@ -1,20 +1,3 @@ -- if group_sidebar_link?(:runners) - = nav_link(path: 'groups/runners#index') do - = link_to group_runners_path(@group), title: _('CI/CD'), class: 'has-sub-items' do - .nav-icon-container - = sprite_icon('rocket') - %span.nav-item-name - = _('CI/CD') - %ul.sidebar-sub-level-items - = nav_link(path: 'groups/runners#index', html_options: { class: "fly-out-top-item" } ) do - = link_to group_runners_path(@group), title: _('CI/CD') do - %strong.fly-out-top-item-name - = _('CI/CD') - %li.divider.fly-out-top-item - = nav_link(path: 'groups/runners#index') do - = link_to group_runners_path(@group), title: s_('Runners|Runners') do - %span= s_('Runners|Runners') - - if group_sidebar_link?(:kubernetes) = nav_link(controller: [:clusters]) do = link_to group_clusters_path(@group) do diff --git a/config/feature_flags/development/mailgun_events_receiver.yml b/config/feature_flags/development/mailgun_events_receiver.yml new file mode 100644 index 00000000000..119d8d34f21 --- /dev/null +++ b/config/feature_flags/development/mailgun_events_receiver.yml @@ -0,0 +1,8 @@ +--- +name: mailgun_events_receiver +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64249 +rollout_issue_url: +milestone: '14.1' +type: development +group: group::expansion +default_enabled: false diff --git a/config/metrics/counts_28d/20210216180424_i_search_total_monthly.yml b/config/metrics/counts_28d/20210216180424_i_search_total_monthly.yml index d678e6c3c91..dfec960830d 100644 --- a/config/metrics/counts_28d/20210216180424_i_search_total_monthly.yml +++ b/config/metrics/counts_28d/20210216180424_i_search_total_monthly.yml @@ -10,6 +10,10 @@ value_type: number status: data_available time_frame: 28d data_source: redis_hll +instrumentation_class: RedisHLLMetric +options: + events: + - i_search_total distribution: - ce - ee diff --git a/config/metrics/counts_28d/20210216180431_search_total_unique_counts_monthly.yml b/config/metrics/counts_28d/20210216180431_search_total_unique_counts_monthly.yml index ec39054bffb..2bb0d4edc0d 100644 --- a/config/metrics/counts_28d/20210216180431_search_total_unique_counts_monthly.yml +++ b/config/metrics/counts_28d/20210216180431_search_total_unique_counts_monthly.yml @@ -10,6 +10,12 @@ value_type: number status: data_available time_frame: 28d data_source: redis_hll +instrumentation_class: RedisHLLMetric +options: + events: + - i_search_total + - i_search_advanced + - i_search_paid distribution: - ce - ee diff --git a/config/metrics/counts_28d/20210216183922_search_unique_visits_for_any_target_monthly.yml b/config/metrics/counts_28d/20210216183922_search_unique_visits_for_any_target_monthly.yml index ef051f7031e..15939ae2a9d 100644 --- a/config/metrics/counts_28d/20210216183922_search_unique_visits_for_any_target_monthly.yml +++ b/config/metrics/counts_28d/20210216183922_search_unique_visits_for_any_target_monthly.yml @@ -10,6 +10,12 @@ value_type: number status: data_available time_frame: 28d data_source: redis_hll +instrumentation_class: RedisHLLMetric +options: + events: + - i_search_total + - i_search_advanced + - i_search_paid distribution: - ce - ee diff --git a/config/metrics/counts_7d/20210216180422_i_search_total_weekly.yml b/config/metrics/counts_7d/20210216180422_i_search_total_weekly.yml index af0ba376863..c88b9ad8bb8 100644 --- a/config/metrics/counts_7d/20210216180422_i_search_total_weekly.yml +++ b/config/metrics/counts_7d/20210216180422_i_search_total_weekly.yml @@ -10,6 +10,10 @@ value_type: number status: data_available time_frame: 7d data_source: redis_hll +instrumentation_class: RedisHLLMetric +options: + events: + - i_search_total distribution: - ee - ce diff --git a/config/metrics/counts_7d/20210216180429_search_total_unique_counts_weekly.yml b/config/metrics/counts_7d/20210216180429_search_total_unique_counts_weekly.yml index fb889331ead..440e99d642c 100644 --- a/config/metrics/counts_7d/20210216180429_search_total_unique_counts_weekly.yml +++ b/config/metrics/counts_7d/20210216180429_search_total_unique_counts_weekly.yml @@ -10,6 +10,12 @@ value_type: number status: data_available time_frame: 7d data_source: redis_hll +instrumentation_class: RedisHLLMetric +options: + events: + - i_search_total + - i_search_advanced + - i_search_paid distribution: - ee - ce diff --git a/config/routes.rb b/config/routes.rb index ff979d7da10..a4404f9d3a8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -221,7 +221,6 @@ Rails.application.routes.draw do draw :snippets draw :profile - draw :members # Product analytics collector match '/collector/i', to: ProductAnalytics::CollectorApp.new, via: :all diff --git a/config/routes/members.rb b/config/routes/members.rb deleted file mode 100644 index e84f0987171..00000000000 --- a/config/routes/members.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -namespace :members do - namespace :mailgun do - resources :permanent_failures, only: [:create] - end -end diff --git a/db/migrate/20210719192928_add_invite_email_success_to_member.rb b/db/migrate/20210719192928_add_invite_email_success_to_member.rb index 40feb13a564..ad629483a82 100644 --- a/db/migrate/20210719192928_add_invite_email_success_to_member.rb +++ b/db/migrate/20210719192928_add_invite_email_success_to_member.rb @@ -2,12 +2,10 @@ class AddInviteEmailSuccessToMember < ActiveRecord::Migration[6.1] def up - unless column_exists?(:members, :invite_email_success) - add_column :members, :invite_email_success, :boolean, null: false, default: true - end + # no-op end def down - remove_column :members, :invite_email_success + # no-op end end diff --git a/db/structure.sql b/db/structure.sql index d6620c1b78e..1fcc8804893 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14758,8 +14758,7 @@ CREATE TABLE members ( requested_at timestamp without time zone, expires_at date, ldap boolean DEFAULT false NOT NULL, - override boolean DEFAULT false NOT NULL, - invite_email_success boolean DEFAULT true NOT NULL + override boolean DEFAULT false NOT NULL ); CREATE SEQUENCE members_id_seq diff --git a/doc/administration/integration/mailgun.md b/doc/administration/integration/mailgun.md deleted file mode 100644 index 6486cc9de04..00000000000 --- a/doc/administration/integration/mailgun.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -stage: Growth -group: Expansion -info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: reference, howto ---- - -# Mailgun and GitLab **(FREE SELF)** - -When you use Mailgun to send emails for your GitLab instance and [Mailgun](https://www.mailgun.com/) -integration is enabled and configured in GitLab, you can receive their webhook for -permanent invite email failures. To set up the integration, you must: - -1. [Configure your Mailgun domain](#configure-your-mailgun-domain). -1. [Enable Mailgun integration](#enable-mailgun-integration). - -After completing the integration, Mailgun `permanent_failure` webhooks are sent to your GitLab instance. - -## Configure your Mailgun domain - -Before you can enable Mailgun in GitLab, set up your own Mailgun permanent failure endpoint to receive the webhooks. - -Using the [Mailgun webhook guide](https://www.mailgun.com/blog/a-guide-to-using-mailguns-webhooks/): - -1. Add a webhook with the **Event type** set to **Permanent Failure**. -1. Fill in the URL of your instance and include the `/-/members/mailgun/permanent_failures` path. - - Example: `https://myinstance.gitlab.com/-/members/mailgun/permanent_failures` - -## Enable Mailgun integration - -After configuring your Mailgun domain for the permanent failures endpoint, -you're ready to enable the Mailgun integration: - -1. Sign in to GitLab as an [Administrator](../../user/permissions.md) user. -1. On the top bar, select **Menu >** **{admin}** **Admin**. -1. In the left sidebar, go to **Settings > General** and expand the **Mailgun** section. -1. Select the **Enable Mailgun** check box. -1. Enter the Mailgun HTTP webhook signing key as described in - [the Mailgun documentation](https://documentation.mailgun.com/en/latest/user_manual.html#webhooks) and - shown in the [API security](https://app.mailgun.com/app/account/security/api_keys) section for your Mailgun account. -1. Select **Save changes**. diff --git a/doc/api/graphql/removed_items.md b/doc/api/graphql/removed_items.md index d8fc6cb35f8..1c425d5f1d5 100644 --- a/doc/api/graphql/removed_items.md +++ b/doc/api/graphql/removed_items.md @@ -41,8 +41,8 @@ Fields removed in [GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/merge_req Fields removed in [GitLab 13.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44866): | Field name | GraphQL type | Deprecated in | Use instead | -| -------------------- | -------------------- | ------------- | -------------------------- | -| `date` | `Timelog` **(STARTER)** | 12.10 | `spentAt` | +|----------------------|--------------------------|---------------|----------------------------| +| `date` | `Timelog` | 12.10 | `spentAt` | | `designs` | `Issue`, `EpicIssue` | 12.2 | `designCollection` | | `latestPipeline` | `Commit` | 12.5 | `pipelines` | | `mergeCommitMessage` | `MergeRequest` | 11.8 | `latestMergeCommitMessage` | diff --git a/doc/api/settings.md b/doc/api/settings.md index 671a9c008fc..e3366cf176c 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -328,7 +328,7 @@ listed in the descriptions of the relevant settings. | `issues_create_limit` | integer | no | Max number of issue creation requests per minute per user. Disabled by default.| | `keep_latest_artifact` | boolean | no | Prevent the deletion of the artifacts from the most recent successful jobs, regardless of the expiry time. Enabled by default. | | `local_markdown_version` | integer | no | Increase this value when any cached Markdown should be invalidated. | -| `mailgun_signing_key` | string | no | The Mailgun HTTP webhook signing key for receiving events from webhook. | +| `mailgun_signing_key` | string | no | The Mailgun HTTP webhook signing key for receiving events from webhook | | `mailgun_events_enabled` | boolean | no | Enable Mailgun event receiver. | | `maintenance_mode_message` | string | no | **(PREMIUM)** Message displayed when instance is in maintenance mode. | | `maintenance_mode` | boolean | no | **(PREMIUM)** When instance is in maintenance mode, non-administrative users can sign in with read-only access and make read-only API requests. | diff --git a/doc/api/users.md b/doc/api/users.md index beed816f387..e389759a9ba 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -434,7 +434,7 @@ Parameters: | `email` | Yes | Email | | `extern_uid` | No | External UID | | `external` | No | Flags the user as external - true or false (default) | -| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user (purchased in addition to the minutes included in the plan) **(STARTER)** | +| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user (purchased in addition to the minutes included in the plan) **(PREMIUM)** | | `force_random_password` | No | Set user password to a random value - true or false (default) | | `group_id_for_saml` | No | ID of group where SAML has been configured | | `linkedin` | No | LinkedIn | @@ -447,7 +447,7 @@ Parameters: | `projects_limit` | No | Number of projects user can create | | `provider` | No | External provider name | | `reset_password` | No | Send user password reset link - true or false(default) | -| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` **(STARTER)** | +| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` **(PREMIUM)** | | `skip_confirmation` | No | Skip confirmation - true or false (default) | | `skype` | No | Skype ID | | `theme_id` | No | The GitLab theme for the user (see [the user preference docs](../user/profile/preferences.md#navigation-theme) for more information) | @@ -476,7 +476,7 @@ Parameters: | `email` | No | Email | | `extern_uid` | No | External UID | | `external` | No | Flags the user as external - true or false (default) | -| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user (purchased in addition to the minutes included in the plan) **(STARTER)** | +| `extra_shared_runners_minutes_limit` | No | Extra pipeline minutes quota for this user (purchased in addition to the minutes included in the plan) **(PREMIUM)** | | `group_id_for_saml` | No | ID of group where SAML has been configured | | `id` | Yes | The ID of the user | | `linkedin` | No | LinkedIn | @@ -489,7 +489,7 @@ Parameters: | `projects_limit` | No | Limit projects each user can create | | `provider` | No | External provider name | | `public_email` | No | The public email of the user (must be already verified) | -| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` **(STARTER)** | +| `shared_runners_minutes_limit` | No | Pipeline minutes quota for this user (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` **(PREMIUM)** | | `skip_reconfirmation` | No | Skip reconfirmation - true or false (default) | | `skype` | No | Skype ID | | `theme_id` | No | The GitLab theme for the user (see [the user preference docs](../user/profile/preferences.md#navigation-theme) for more information) | @@ -859,9 +859,13 @@ Example response: Get the counts (same as in top right menu) of the currently signed in user. -| Attribute | Type | Description | -| ---------------- | ------ | ------------------------------------------------------------ | -| `merge_requests` | number | Merge requests that are active and assigned to current user. | +| Attribute | Type | Description | +| --------------------------------- | ------ | ---------------------------------------------------------------------------- | +| `assigned_issues` | number | Number of issues that are open and assigned to the current user. [Added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66909) in GitLab 14.2. | +| `assigned_merge_requests` | number | Number of merge requests that are active and assigned to the current user. [Added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50026) in GitLab 13.8. | +| `merge_requests` | number | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50026) in GitLab 13.8. Equivalent to and replaced by `assigned_merge_requests`. | +| `review_requested_merge_requests` | number | Number of merge requests that the current user has been requested to review. [Added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50026) in GitLab 13.8. | +| `todos` | number | Number of pending to-do items for current user. [Added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66909) in GitLab 14.2. | ```plaintext GET /user_counts @@ -875,7 +879,11 @@ Example response: ```json { - "merge_requests": 4 + "merge_requests": 4, + "assigned_issues": 15, + "assigned_merge_requests": 11, + "review_requested_merge_requests": 0, + "todos": 1 } ``` diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md index b3c0fb19565..e34f494d0be 100644 --- a/doc/development/documentation/styleguide/word_list.md +++ b/doc/development/documentation/styleguide/word_list.md @@ -108,6 +108,10 @@ Try to avoid. Be as specific as you can. Do not use **and so on** as a replaceme - Avoid: You can update objects, like merge requests, issues, etc. - Use instead: You can update objects, like merge requests and issues. +## foo + +Do not use in product documentation. You can use it in our API and contributor documentation, but try to use a clearer and more meaningful example instead. + ## future tense When possible, use present tense instead. For example, use `after you execute this command, GitLab displays the result` instead of `after you execute this command, GitLab will display the result`. ([Vale](../testing.md#vale) rule: [`FutureTense.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FutureTense.yml)) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 7fc19566a52..250abf2fa60 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -1481,7 +1481,7 @@ If this happens, examine the following: > - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-or-enable-gitaly-backup). There can be -[risks when disabling released features](../user/feature_flags.md#risks-when-disabling-released-features). +[risks when disabling released features](../administration/feature_flags.md#risks-when-disabling-released-features). Refer to this feature's version history for more details. `gitaly-backup` is used by the backup Rake task to create and restore repository backups from Gitaly. diff --git a/doc/user/admin_area/settings/help_page.md b/doc/user/admin_area/settings/help_page.md index f79c9e58162..6b6987634d9 100644 --- a/doc/user/admin_area/settings/help_page.md +++ b/doc/user/admin_area/settings/help_page.md @@ -67,7 +67,7 @@ You can specify a custom URL to which users are directed when they: > - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-gitlab-documentation-link-redirects). **(FREE SELF)** This in-development feature might not be available for your use. There can be -[risks when enabling features still in development](../../feature_flags.md#risks-when-enabling-features-still-in-development). +[risks when enabling features still in development](../../../administration/feature_flags.md#risks-when-enabling-features-still-in-development). Refer to this feature's version history for more details. Documentation links go to the `/help` section on the instance by default, but you can diff --git a/doc/user/admin_area/settings/img/rate_limit_on_issues_creation_v13_1.png b/doc/user/admin_area/settings/img/rate_limit_on_issues_creation_v13_1.png deleted file mode 100644 index 9edc916c7fb10ff1b674580a310c6c297926c1bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13479 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 TnlDpuy}DOXR98SeH4gY+os{R| diff --git a/doc/user/admin_area/settings/img/rate_limit_on_issues_creation_v14_2.png b/doc/user/admin_area/settings/img/rate_limit_on_issues_creation_v14_2.png new file mode 100644 index 0000000000000000000000000000000000000000..63f4d5a4a1a0266d8ea56da46907351af3ca0e90 GIT binary patch literal 29368 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 - Code Owners for merge request approvals was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4418) in GitLab Premium 11.9. > - Moved to GitLab Premium in 13.9. -Code Owners define who owns specific files or folders in a repository. +Code Owners define who owns specific files or directories in a repository. - The users you define as Code Owners are displayed in the UI when you browse directories. - You can set your merge requests so they must be approved by Code Owners before merge. @@ -23,7 +23,7 @@ If you don't want to use Code Owners for approvals, you can ## Set up Code Owners You can use Code Owners to specify users or [shared groups](members/share_project_with_groups.md) -that are responsible for specific files and folders in a repository. +that are responsible for specific files and directories in a repository. To set up Code Owners: @@ -41,24 +41,65 @@ To set up Code Owners: filename @username1 @username2 # Code Owners for a directory - foldername @username1 @username2 + directoryname/ @username1 @username2 # All group members as Code Owners for a file filename @groupname - # All group members as Code Owners for a folder - foldername @groupname + # All group members as Code Owners for a directory + directoryname/ @groupname ``` -The Code Owners are now displayed in the UI. +The Code Owners are now displayed in the UI. They apply to the current branch only. Next steps: - [Add Code Owners as merge request approvers](merge_requests/approvals/rules.md#code-owners-as-eligible-approvers). - Set up [Code Owner approval on a protected branch](protected_branches.md#require-code-owner-approval-on-a-protected-branch). -NOTE: -The Code Owners apply to the current branch only. +## Groups as Code Owners + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53182) in GitLab 12.1. +> - Group and subgroup hierarchy support was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32432) in GitLab 13.0. + +You can use members of groups and subgroups as Code Owners for a project. + +For example, if you have these groups: + +- **Group X** (`group-x`) with **Project A** in it. +- **Subgroup Y** (`group-x/subgroup-y`), which belongs to **Group X**, with **Project B** in it. + +The eligible Code Owners: + +- For **Project A** are the members of **Group X** only, because **Project A** doesn't belong to **Subgroup Y**. +- For **Project B** are the members of both **Group X** and **Subgroup Y**. + +![Eligible Code Owners](img/code_owners_members_v13_4.png) + +You can [invite](members/share_project_with_groups.md) **Subgroup Y** to **Project A** +so that their members also become eligible Code Owners. + +![Invite subgroup members to become eligible Code Owners](img/code_owners_invite_members_v13_4.png) + +If you do not invite **Subgroup Y** to **Project A**, but make them Code Owners, their approval +of the merge request becomes optional. + +### Add a group as a Code Owner + +To set a group as a Code Owner: + +In the `CODEOWNERS` file, enter text that follows one of these patterns: + +```plaintext +# All group members as Code Owners for a file +file.md @group-x + +# All subgroup members as Code Owners for a file +file.md @group-x/subgroup-y + +# All group and subgroup members as Code Owners for a file +file.md @group-x @group-x/subgroup-y +``` ## When a file matches multiple `CODEOWNERS` entries @@ -74,83 +115,24 @@ README.md @user1 *.md @user2 ``` -The user that would show for `README.md` would be `@user2`. +The Code Owner for `README.md` would be `@user2`. -## Groups as Code Owners +If you use sections, the last user _for each section_ is used. -> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53182) in GitLab 12.1. -> - Group and subgroup hierarchy support was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32432) in GitLab 13.0. +Only one CODEOWNERS pattern can match per file path. -Groups and subgroups members are inherited as eligible Code Owners to a -project, as long as the hierarchy is respected. - -For example, consider a given group called "Group X" (slug `group-x`) and a -"Subgroup Y" (slug `group-x/subgroup-y`) that belongs to the Group X, and -suppose you have a project called "Project A" within the group and a -"Project B" within the subgroup. - -The eligible Code Owners to Project B are both the members of the Group X and -the Subgroup Y. The eligible Code Owners to the Project A are just the -members of the Group X, given that Project A doesn't belong to the Subgroup Y: - -![Eligible Code Owners](img/code_owners_members_v13_4.png) - -But you have the option to [invite](members/share_project_with_groups.md) -the Subgroup Y to the Project A so that their members also become eligible -Code Owners: - -NOTE: -If you do not invite Subgroup Y to Project A, but make them Code Owners, their approval -of the merge request becomes optional. - -![Invite subgroup members to become eligible Code Owners](img/code_owners_invite_members_v13_4.png) - -After being invited, any member (`@user`) of the group or subgroup can be set -as Code Owner to files of the Project A or B, and the entire Group X -(`@group-x`) or Subgroup Y (`@group-x/subgroup-y`), as follows: - -```plaintext -# A member of the group or subgroup as Code Owner to a file -file.md @user - -# All group members as Code Owners to a file -file.md @group-x - -# All subgroup members as Code Owners to a file -file.md @group-x/subgroup-y - -# All group and subgroup members as Code Owners to a file -file.md @group-x @group-x/subgroup-y -``` - -### Code Owners sections **(PREMIUM)** +### Organize Code Owners by putting them into sections > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12137) in GitLab Premium 13.2 behind a feature flag, enabled by default. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42389) in GitLab 13.4. -Code Owner rules can be grouped into named sections. This allows for better -organization of broader categories of Code Owner rules to be applied. -Additionally, the usual guidance that only the last pattern matching the file is -applied is expanded such that the last pattern matching _for each section_ is -applied. +You can organize Code Owners by putting them into named sections. -For example, in a large organization, independent teams may have a common interest -in parts of the application, for instance, a payment processing company may have -"development", "security", and "compliance" teams looking after common parts of -the codebase. All three teams may need to approve changes. Although approval rules -make this possible, they apply to every merge request. Also, while Code Owners are -applied based on which files are changed, only one CODEOWNERS pattern can match per -file path. +You can use sections for shared directories, so that multiple +teams can be reviewers. -Using `CODEOWNERS` sections allows multiple teams that only need to approve certain -changes, to set their own independent patterns by specifying discrete sections in the -`CODEOWNERS` file. The section rules may be used for shared paths so that multiple -teams can be added as reviewers. - -Sections can be added to `CODEOWNERS` files as a new line with the name of the -section inside square brackets. Every entry following is assigned to that -section. The following example would create two Code Owner rules for the "README -Owners" section: +To add a section to the `CODEOWNERS` file, enter a section name in brackets, +followed by the files or directories, and users, groups, or subgroups: ```plaintext [README Owners] @@ -158,43 +140,41 @@ README.md @user1 @user2 internal/README.md @user2 ``` -Multiple sections can be used, even with matching file or directory patterns. -Reusing the same section name groups the results together under the same -section, with the most specific rule or last matching pattern being used. For -example, consider the following entries in a `CODEOWNERS` file: - -```plaintext -[Documentation] -ee/docs @gl-docs -docs @gl-docs - -[Database] -README.md @gl-database -model/db @gl-database - -[DOCUMENTATION] -README.md @gl-docs -``` - -This results in three entries under the "Documentation" section header, and two -entries under "Database". Case is not considered when combining sections, so in -this example, entries defined under the sections "Documentation" and -"DOCUMENTATION" would be combined into one, using the case of the first instance -of the section encountered in the file. - -When assigned to a section, each Code Owner rule displayed in merge requests -widget is sorted under a "section" label. In the screenshot below, we can see -the rules for "Groups" and "Documentation" sections: +Each Code Owner in the merge request widget is listed under a label. +The following image shows a **Groups** and **Documentation** section: ![MR widget - Sectional Code Owners](img/sectional_code_owners_v13.2.png) -#### Optional Code Owners sections **(PREMIUM)** +### Sections with duplicate names + +If multiple sections have the same name, they are combined. +Also, section headings are not case-sensitive. For example: + +```plaintext +[Documentation] +ee/docs/ @docs +docs/ @docs + +[Database] +README.md @database +model/db/ @database + +[DOCUMENTATION] +README.md @docs +``` + +This code results in three entries under the **Documentation** section header, and two +entries under **Database**. The entries defined under the sections **Documentation** and +**DOCUMENTATION** are combined, using the case of the first section. + +### Make a Code Owners section optional > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232995) in GitLab Premium 13.8 behind a feature flag, enabled by default. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53227) in GitLab 13.9. -To make a certain section optional, add a Code Owners section prepended with the -caret `^` character. Approvals from owners listed in the section are **not** required. For example: +You can make a section optional, so that approval from the Code Owners in that section is optional. + +Put a caret `^` character before the Code Owners section name. For example: ```plaintext [Documentation] @@ -211,83 +191,64 @@ The optional Code Owners section displays in merge requests under the **Approval ![MR widget - Optional Code Owners sections](img/optional_code_owners_sections_v13_8.png) -If a section is duplicated in the file, and one of them is marked as optional and the other isn't, the requirement prevails. - -For example, the Code Owners of the "Documentation" section below is still required to approve merge requests: - -```plaintext -[Documentation] -*.md @root - -[Ruby] -*.rb @root - -^[Go] -*.go @root - -^[Documentation] -*.txt @root -``` +If a section is duplicated in the file, and one of them is marked as optional and the other isn't, the section is required. Optional sections in the `CODEOWNERS` file are treated as optional only when changes are submitted by using merge requests. If a change is submitted directly to the protected branch, approval from Code Owners is still required, even if the -section is marked as optional. We plan to change this behavior in a -[future release](https://gitlab.com/gitlab-org/gitlab/-/issues/297638), -and allow direct pushes to the protected branch for sections marked as optional. +section is marked as optional. [An issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/297638) +to allow direct pushes to the protected branch for sections marked as optional. ## Example `CODEOWNERS` file ```plaintext -# This is an example of a CODEOWNERS file -# lines starting with a `#` will be ignored. +# This is an example of a CODEOWNERS file. +# Lines that start with `#` are ignored. # app/ @commented-rule -# We can specify a default match using wildcards: +# Specify a default Code Owner by using a wildcard: * @default-codeowner -# We can also specify "multiple tab or space" separated codeowners: +# Specify multiple Code Owners by using a tab or space: * @multiple @code @owners # Rules defined later in the file take precedence over the rules # defined before. -# This will match all files for which the file name ends in `.rb` +# For example, for all files with a filename ending in `.rb`: *.rb @ruby-owner -# Files with a `#` can still be accessed by escaping the pound sign +# Files with a `#` can still be accessed by escaping the pound sign: \#file_with_pound.rb @owner-file-with-pound -# Multiple codeowners can be specified, separated by spaces or tabs +# Specify multiple Code Owners separated by spaces or tabs. # In the following case the CODEOWNERS file from the root of the repo -# has 3 Code Owners (@multiple @code @owners) +# has 3 Code Owners (@multiple @code @owners): CODEOWNERS @multiple @code @owners -# Both usernames or email addresses can be used to match -# users. Everything else will be ignored. For example this will -# specify `@legal` and a user with email `janedoe@gitlab.com` as the -# owner for the LICENSE file +# You can use both usernames or email addresses to match +# users. Everything else is ignored. For example, this code +# specifies the `@legal` and a user with email `janedoe@gitlab.com` as the +# owner for the LICENSE file: LICENSE @legal this_does_not_match janedoe@gitlab.com -# Group names can be used to match groups and nested groups to specify -# them as owners for a file +# Use group names to match groups, and nested groups to specify +# them as owners for a file: README @group @group/with-nested/subgroup -# Ending a path in a `/` will specify the Code Owners for every file -# nested in that directory, on any level +# End a path in a `/` to specify the Code Owners for every file +# nested in that directory, on any level: /docs/ @all-docs -# Ending a path in `/*` will specify Code Owners for every file in -# that directory, but not nested deeper. This will match -# `docs/index.md` but not `docs/projects/index.md` +# End a path in `/*` to specify Code Owners for every file in +# a directory, but not nested deeper. This code matches +# `docs/index.md` but not `docs/projects/index.md`: /docs/* @root-docs -# This will make a `lib` directory nested anywhere in the repository -# match +# This code makes matches a `lib` directory nested anywhere in the repository: lib/ @lib-owner -# This will only match a `config` directory in the root of the -# repository +# This code match only a `config` directory in the root of the repository: /config/ @config-owner # If the path contains spaces, escape them like this: @@ -295,14 +256,14 @@ path\ with\ spaces/ @space-owner # Code Owners section: [Documentation] -ee/docs @gl-docs -docs @gl-docs +ee/docs @docs +docs @docs [Database] -README.md @gl-database -model/db @gl-database +README.md @database +model/db @database -# This section will be joined with the [Documentation] section previously defined: +# This section is combined with the previously defined [Documentation] section: [DOCUMENTATION] -README.md @gl-docs +README.md @docs ``` diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb index 31c923a219a..634dd0f2179 100644 --- a/lib/api/user_counts.rb +++ b/lib/api/user_counts.rb @@ -6,15 +6,17 @@ module API resource :user_counts do desc 'Return the user specific counts' do - detail 'Open MR Count' + detail 'Assigned open issues, assigned MRs and pending todos count' end get do unauthorized! unless current_user { merge_requests: current_user.assigned_open_merge_requests_count, # @deprecated + assigned_issues: current_user.assigned_open_issues_count, assigned_merge_requests: current_user.assigned_open_merge_requests_count, - review_requested_merge_requests: current_user.review_requested_open_merge_requests_count + review_requested_merge_requests: current_user.review_requested_open_merge_requests_count, + todos: current_user.todos_pending_count } end end diff --git a/lib/sidebars/groups/menus/ci_cd_menu.rb b/lib/sidebars/groups/menus/ci_cd_menu.rb new file mode 100644 index 00000000000..e870bbf5ebc --- /dev/null +++ b/lib/sidebars/groups/menus/ci_cd_menu.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Sidebars + module Groups + module Menus + class CiCdMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + add_item(runners_menu_item) + + true + end + + override :link + def link + renderable_items.first.link + end + + override :title + def title + _('CI/CD') + end + + override :sprite_icon + def sprite_icon + 'rocket' + end + + private + + def runners_menu_item + return ::Sidebars::NilMenuItem.new(item_id: :runners) unless show_runners? + + ::Sidebars::MenuItem.new( + title: _('Runners'), + link: group_runners_path(context.group), + active_routes: { path: 'groups/runners#index' }, + item_id: :runners + ) + end + + # TODO Proper policies, such as `read_group_runners`, should be implemented per + # See https://gitlab.com/gitlab-org/gitlab/-/issues/334802 + def show_runners? + can?(context.current_user, :admin_group, context.group) && + Feature.enabled?(:runner_list_group_view_vue_ui, context.group, default_enabled: :yaml) + end + end + end + end +end diff --git a/lib/sidebars/groups/panel.rb b/lib/sidebars/groups/panel.rb index 8201ad0f180..398cbd73b2a 100644 --- a/lib/sidebars/groups/panel.rb +++ b/lib/sidebars/groups/panel.rb @@ -10,6 +10,7 @@ module Sidebars add_menu(Sidebars::Groups::Menus::GroupInformationMenu.new(context)) add_menu(Sidebars::Groups::Menus::IssuesMenu.new(context)) add_menu(Sidebars::Groups::Menus::MergeRequestsMenu.new(context)) + add_menu(Sidebars::Groups::Menus::CiCdMenu.new(context)) end override :render_raw_menus_partial diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c2c89adee51..d59bc11fc65 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8371,9 +8371,6 @@ msgstr "" msgid "Configure existing installation" msgstr "" -msgid "Configure limit for issues created per minute by web and API requests." -msgstr "" - msgid "Configure limit for notes created per minute by web and API requests." msgstr "" @@ -19741,6 +19738,9 @@ msgstr "" msgid "Limit the number of concurrent operations this secondary node can run in the background." msgstr "" +msgid "Limit the number of issues per minute a user can create through web and API requests." +msgstr "" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "" diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb index 4966e508f7e..b288d074133 100644 --- a/spec/experiments/application_experiment_spec.rb +++ b/spec/experiments/application_experiment_spec.rb @@ -236,6 +236,11 @@ RSpec.describe ApplicationExperiment, :experiment do expect(subject.process_redirect_url(url)).to be_nil end end + + it "generates the correct urls based on where the engine was mounted" do + url = Rails.application.routes.url_helpers.experiment_redirect_url(subject, url: 'https://docs.gitlab.com') + expect(url).to include("/-/experiment/namespaced%2Fstub:#{subject.context.key}?https://docs.gitlab.com") + end end context "when resolving variants" do diff --git a/spec/experiments/new_project_readme_content_experiment_spec.rb b/spec/experiments/new_project_readme_content_experiment_spec.rb index f76b8a1ded1..a6a81580a29 100644 --- a/spec/experiments/new_project_readme_content_experiment_spec.rb +++ b/spec/experiments/new_project_readme_content_experiment_spec.rb @@ -30,9 +30,9 @@ RSpec.describe NewProjectReadmeContentExperiment, :experiment do end it "renders redirect URLs" do - expect(markdown).to include( - Rails.application.routes.url_helpers.experiment_redirect_url(subject, url: initial_url) - ) + url = Rails.application.routes.url_helpers.experiment_redirect_url(subject, url: initial_url) + expect(url).to include("/-/experiment/#{subject.to_param}?") + expect(markdown).to include(url) end end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index cc65e5753c0..fe4c532060b 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -269,7 +269,10 @@ RSpec.describe 'Admin updates settings' do end context 'Integrations page' do + let(:mailgun_events_receiver_enabled) { true } + before do + stub_feature_flags(mailgun_events_receiver: mailgun_events_receiver_enabled) visit general_admin_application_settings_path end @@ -283,16 +286,26 @@ RSpec.describe 'Admin updates settings' do expect(current_settings.hide_third_party_offers).to be true end - it 'enabling Mailgun events', :aggregate_failures do - page.within('.as-mailgun') do - check 'Enable Mailgun event receiver' - fill_in 'Mailgun HTTP webhook signing key', with: 'MAILGUN_SIGNING_KEY' - click_button 'Save changes' - end + context 'when mailgun_events_receiver feature flag is enabled' do + it 'enabling Mailgun events', :aggregate_failures do + page.within('.as-mailgun') do + check 'Enable Mailgun event receiver' + fill_in 'Mailgun HTTP webhook signing key', with: 'MAILGUN_SIGNING_KEY' + click_button 'Save changes' + end - expect(page).to have_content 'Application settings saved successfully' - expect(current_settings.mailgun_events_enabled).to be true - expect(current_settings.mailgun_signing_key).to eq 'MAILGUN_SIGNING_KEY' + expect(page).to have_content 'Application settings saved successfully' + expect(current_settings.mailgun_events_enabled).to be true + expect(current_settings.mailgun_signing_key).to eq 'MAILGUN_SIGNING_KEY' + end + end + + context 'when mailgun_events_receiver feature flag is disabled' do + let(:mailgun_events_receiver_enabled) { false } + + it 'does not have mailgun' do + expect(page).not_to have_selector('.as-mailgun') + end end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 10162ade48b..77d126e012e 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -167,7 +167,6 @@ ProjectMember: - expires_at - ldap - override -- invite_email_success User: - id - username diff --git a/spec/lib/sidebars/groups/menus/ci_cd_menu_spec.rb b/spec/lib/sidebars/groups/menus/ci_cd_menu_spec.rb new file mode 100644 index 00000000000..1ba89af1b02 --- /dev/null +++ b/spec/lib/sidebars/groups/menus/ci_cd_menu_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Groups::Menus::CiCdMenu do + let_it_be(:owner) { create(:user) } + let_it_be(:root_group) do + build(:group, :private).tap do |g| + g.add_owner(owner) + end + end + + let(:group) { root_group } + let(:user) { owner } + let(:context) { Sidebars::Groups::Context.new(current_user: user, container: group) } + + describe 'Menu Items' do + subject { described_class.new(context).renderable_items.index { |e| e.item_id == item_id } } + + describe 'Runners' do + let(:item_id) { :runners } + + specify { is_expected.not_to be_nil } + + describe 'when feature flag :runner_list_group_view_vue_ui is disabled' do + before do + stub_feature_flags(runner_list_group_view_vue_ui: false) + end + + specify { is_expected.to be_nil } + end + + describe 'when the user does not have access' do + let(:user) { nil } + + specify { is_expected.to be_nil } + end + end + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index d00260a3bcd..240abfc5c53 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -832,15 +832,15 @@ RSpec.describe Notify do end end - context 'when mailgun events are enabled' do + context 'when on gitlab.com' do before do - stub_application_setting(mailgun_events_enabled: true) + allow(Gitlab).to receive(:dev_env_or_com?).and_return(true) end it 'has custom headers' do aggregate_failures do - expect(subject).to have_header('X-Mailgun-Tag', ::Members::Mailgun::INVITE_EMAIL_TAG) - expect(subject).to have_header('X-Mailgun-Variables', { ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY => project_member.invite_token }.to_json) + expect(subject).to have_header('X-Mailgun-Tag', 'invite_email') + expect(subject).to have_header('X-Mailgun-Variables', { 'invite_token' => project_member.invite_token }.to_json) end end end diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb index 94e25d647fc..ab2aa87d1b7 100644 --- a/spec/requests/api/user_counts_spec.rb +++ b/spec/requests/api/user_counts_spec.rb @@ -3,8 +3,10 @@ require 'spec_helper' RSpec.describe API::UserCounts do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + let_it_be(:todo) { create(:todo, :pending, user: user, project: project) } let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, title: "Test") } @@ -18,22 +20,36 @@ RSpec.describe API::UserCounts do end context 'when authenticated' do - it 'returns open counts for current user' do + it 'returns assigned issue counts for current_user' do get api('/user_counts', user) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_a Hash - expect(json_response['merge_requests']).to eq(1) + expect(json_response['assigned_issues']).to eq(1) end - it 'updates the mr count when a new mr is assigned' do - create(:merge_request, source_project: project, author: user, assignees: [user]) + context 'merge requests' do + it 'returns assigned MR counts for current user' do + get api('/user_counts', user) + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_a Hash + expect(json_response['merge_requests']).to eq(1) + end + + it 'updates the mr count when a new mr is assigned' do + create(:merge_request, source_project: project, author: user, assignees: [user]) + + get api('/user_counts', user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_a Hash + expect(json_response['merge_requests']).to eq(2) + end + end + + it 'returns pending todo counts for current_user' do get api('/user_counts', user) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_a Hash - expect(json_response['merge_requests']).to eq(2) + expect(json_response['todos']).to eq(1) end end end diff --git a/spec/requests/members/mailgun/permanent_failure_spec.rb b/spec/requests/members/mailgun/permanent_failure_spec.rb deleted file mode 100644 index e47aedf8e94..00000000000 --- a/spec/requests/members/mailgun/permanent_failure_spec.rb +++ /dev/null @@ -1,128 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'receive a permanent failure' do - describe 'POST /members/mailgun/permanent_failures', :aggregate_failures do - let_it_be(:member) { create(:project_member, :invited) } - - let(:raw_invite_token) { member.raw_invite_token } - let(:mailgun_events) { true } - let(:mailgun_signing_key) { 'abc123' } - - subject(:post_request) { post members_mailgun_permanent_failures_path(standard_params) } - - before do - stub_application_setting(mailgun_events_enabled: mailgun_events, mailgun_signing_key: mailgun_signing_key) - end - - it 'marks the member invite email success as false' do - expect { post_request }.to change { member.reload.invite_email_success }.from(true).to(false) - - expect(response).to have_gitlab_http_status(:ok) - end - - context 'when the change to a member is not made' do - context 'with incorrect signing key' do - context 'with incorrect signing key' do - let(:mailgun_signing_key) { '_foobar_' } - - it 'does not change member status and responds as not_found' do - expect { post_request }.not_to change { member.reload.invite_email_success } - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - context 'with nil signing key' do - let(:mailgun_signing_key) { nil } - - it 'does not change member status and responds as not_found' do - expect { post_request }.not_to change { member.reload.invite_email_success } - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - context 'when the feature is not enabled' do - let(:mailgun_events) { false } - - it 'does not change member status and responds as expected' do - expect { post_request }.not_to change { member.reload.invite_email_success } - - expect(response).to have_gitlab_http_status(:not_acceptable) - end - end - - context 'when it is not an invite email' do - before do - stub_const('::Members::Mailgun::INVITE_EMAIL_TAG', '_foobar_') - end - - it 'does not change member status and responds as expected' do - expect { post_request }.not_to change { member.reload.invite_email_success } - - expect(response).to have_gitlab_http_status(:not_acceptable) - end - end - end - - def standard_params - { - "signature": { - "timestamp": "1625056677", - "token": "eb944d0ace7227667a1b97d2d07276ae51d2b849ed2cfa68f3", - "signature": "9790cc6686eb70f0b1f869180d906870cdfd496d27fee81da0aa86b9e539e790" - }, - "event-data": { - "severity": "permanent", - "tags": ["invite_email"], - "timestamp": 1521233195.375624, - "storage": { - "url": "_anything_", - "key": "_anything_" - }, - "log-level": "error", - "id": "_anything_", - "campaigns": [], - "reason": "suppress-bounce", - "user-variables": { - "invite_token": raw_invite_token - }, - "flags": { - "is-routed": false, - "is-authenticated": true, - "is-system-test": false, - "is-test-mode": false - }, - "recipient-domain": "example.com", - "envelope": { - "sender": "bob@mg.gitlab.com", - "transport": "smtp", - "targets": "alice@example.com" - }, - "message": { - "headers": { - "to": "Alice ", - "message-id": "20130503192659.13651.20287@mg.gitlab.com", - "from": "Bob ", - "subject": "Test permanent_fail webhook" - }, - "attachments": [], - "size": 111 - }, - "recipient": "alice@example.com", - "event": "failed", - "delivery-status": { - "attempt-no": 1, - "message": "", - "code": 605, - "description": "Not delivering to previously bounced address", - "session-seconds": 0 - } - } - } - end - end -end diff --git a/spec/services/members/mailgun/process_webhook_service_spec.rb b/spec/services/members/mailgun/process_webhook_service_spec.rb deleted file mode 100644 index d6a21183395..00000000000 --- a/spec/services/members/mailgun/process_webhook_service_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Members::Mailgun::ProcessWebhookService do - describe '#execute', :aggregate_failures do - let_it_be(:member) { create(:project_member, :invited) } - - let(:raw_invite_token) { member.raw_invite_token } - let(:payload) { { 'user-variables' => { ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY => raw_invite_token } } } - - subject(:service) { described_class.new(payload).execute } - - it 'marks the member invite email success as false' do - expect(Gitlab::AppLogger).to receive(:info).with(/^UPDATED MEMBER INVITE_EMAIL_SUCCESS/).and_call_original - - expect { service }.to change { member.reload.invite_email_success }.from(true).to(false) - end - - context 'when member can not be found' do - let(:raw_invite_token) { '_foobar_' } - - it 'does not change member status' do - expect(Gitlab::AppLogger).not_to receive(:info).with(/^UPDATED MEMBER INVITE_EMAIL_SUCCESS/) - - expect { service }.not_to change { member.reload.invite_email_success } - end - end - - context 'when invite token is not found in payload' do - let(:payload) { {} } - - it 'does not change member status and logs an error' do - expect(Gitlab::AppLogger).not_to receive(:info).with(/^UPDATED MEMBER INVITE_EMAIL_SUCCESS/) - expect(Gitlab::ErrorTracking).to receive(:track_exception).with( - an_instance_of(described_class::ProcessWebhookServiceError)) - - expect { service }.not_to change { member.reload.invite_email_success } - end - end - end -end diff --git a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb index c00c3efe6d6..fc62fbda2cc 100644 --- a/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb @@ -3,10 +3,17 @@ require 'spec_helper' RSpec.describe 'layouts/nav/sidebar/_group' do - let_it_be(:group) { create(:group) } + let_it_be(:owner) { create(:user) } + let_it_be(:group) do + create(:group).tap do |g| + g.add_owner(owner) + end + end before do assign(:group, group) + + allow(view).to receive(:current_user).and_return(owner) end it_behaves_like 'has nav sidebar' @@ -79,4 +86,18 @@ RSpec.describe 'layouts/nav/sidebar/_group' do expect(rendered).to have_css('span.badge.badge-pill.merge_counter.js-merge-counter') end end + + describe 'CI/CD' do + it 'has a default link to the runners list path' do + render + + expect(rendered).to have_link('CI/CD', href: group_runners_path(group)) + end + + it 'has a link to the runners list page' do + render + + expect(rendered).to have_link('Runners', href: group_runners_path(group)) + end + end end