From 739467f1fa4d5d4042b47ff6637a567d1ad6a4a4 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 14 Mar 2022 15:09:32 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../components/navigation_popover.vue | 120 +++++++ .../javascripts/attention_requests/index.js | 73 ++++ app/assets/javascripts/main.js | 6 + .../components/attention_requested_toggle.vue | 5 +- .../javascripts/sidebar/sidebar_bundle.js | 12 +- .../components/mr_widget_related_links.vue | 24 +- .../stores/mr_widget_store.js | 4 +- .../page_bundles/merge_requests.scss | 4 + app/controllers/dashboard_controller.rb | 4 - .../projects/merge_requests_controller.rb | 3 - app/models/users/callout.rb | 4 +- app/presenters/merge_request_presenter.rb | 15 +- .../merge_request_widget_entity.rb | 6 +- app/views/layouts/header/_default.html.haml | 3 +- .../projects/merge_requests/show.html.haml | 3 + doc/administration/postgresql/external.md | 2 +- .../troubleshooting/postgresql.md | 4 + doc/api/graphql/reference/index.md | 2 + doc/user/admin_area/reporting/spamcheck.md | 7 +- .../settings/user_and_ip_rate_limits.md | 2 +- .../project/merge_requests/getting_started.md | 2 +- .../profile_settings_gpg_keys_paste_pub.png | Bin 13008 -> 0 bytes .../repository/gpg_signed_commits/index.md | 335 ++++++++---------- .../repository/x509_signed_commits/index.md | 2 +- lib/gitlab/gon_helper.rb | 1 + locale/gitlab.pot | 21 +- scripts/ingest-reports-to-siem | 11 +- spec/features/users/login_spec.rb | 1 + .../components/navigation_popover_spec.js | 86 +++++ .../mr_widget_related_links_spec.js | 29 +- .../merge_request_presenter_spec.rb | 34 +- .../merge_request_widget_entity_spec.rb | 2 +- 32 files changed, 574 insertions(+), 253 deletions(-) create mode 100644 app/assets/javascripts/attention_requests/components/navigation_popover.vue create mode 100644 app/assets/javascripts/attention_requests/index.js delete mode 100644 doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png create mode 100644 spec/frontend/attention_requests/components/navigation_popover_spec.js diff --git a/app/assets/javascripts/attention_requests/components/navigation_popover.vue b/app/assets/javascripts/attention_requests/components/navigation_popover.vue new file mode 100644 index 00000000000..1542bc9a7e9 --- /dev/null +++ b/app/assets/javascripts/attention_requests/components/navigation_popover.vue @@ -0,0 +1,120 @@ + + + diff --git a/app/assets/javascripts/attention_requests/index.js b/app/assets/javascripts/attention_requests/index.js new file mode 100644 index 00000000000..2a142ab46e5 --- /dev/null +++ b/app/assets/javascripts/attention_requests/index.js @@ -0,0 +1,73 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { __ } from '~/locale'; +import createDefaultClient from '~/lib/graphql'; +import NavigationPopover from './components/navigation_popover.vue'; + +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +export const initTopNavPopover = () => { + const el = document.getElementById('js-need-attention-nav-onboarding'); + + if (!el) return; + + // eslint-disable-next-line no-new + new Vue({ + el, + apolloProvider, + provide: { + observerElSelector: '.user-counter.dropdown', + observerElToggledClass: 'show', + message: [ + __( + '%{strongStart}Need your attention%{strongEnd} are the merge requests that need your help to move forward, as an assignee or reviewer.', + ), + ], + featureName: 'attention_requests_top_nav', + popoverTarget: '#js-need-attention-nav', + }, + render(h) { + return h(NavigationPopover); + }, + }); +}; + +export const initSideNavPopover = () => { + const el = document.getElementById('js-need-attention-sidebar-onboarding'); + + if (!el) return; + + // eslint-disable-next-line no-new + new Vue({ + el, + apolloProvider, + provide: { + observerElSelector: '.js-right-sidebar', + observerElToggledClass: 'right-sidebar-expanded', + message: [ + __( + 'To ask someone to look at a merge request, select %{strongStart}Request attention%{strongEnd}. Select again to remove the request.', + ), + __( + 'Some actions remove attention requests, like a reviewer approving or anyone merging the merge request.', + ), + ], + featureName: 'attention_requests_side_nav', + popoverTarget: '.js-attention-request-toggle', + showAttentionIcon: true, + delay: 500, + popoverCssClass: 'attention-request-sidebar-popover', + }, + render(h) { + return h(NavigationPopover); + }, + }); +}; + +export default () => { + initTopNavPopover(); +}; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index efc425a1972..b3cb93e74f2 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -161,6 +161,12 @@ function deferredInitialisation() { // Adding a helper class to activate animations only after all is rendered setTimeout(() => $body.addClass('page-initialised'), 1000); + + if (window.gon?.features?.mrAttentionRequests) { + import('~/attention_requests') + .then((module) => module.default()) + .catch(() => {}); + } } const $body = $('body'); diff --git a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue index d11bed4f058..6ba88939373 100644 --- a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue +++ b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue @@ -70,7 +70,10 @@ export default { diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 5378dabf638..994e0c23b44 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -82,14 +82,16 @@ export default class MergeRequestStore { const { closing } = links; const mentioned = links.mentioned_but_not_closing; const assignToMe = links.assign_to_closing; + const unassignedCount = links.assign_to_closing_count; - if (closing || mentioned || assignToMe) { + if (closing || mentioned || unassignedCount) { this.relatedLinks = { closing, mentioned, assignToMe, closingCount: links.closing_count, mentionedCount: links.mentioned_count, + unassignedCount: links.assign_to_closing_count, }; } } diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index d2649acd622..34a3d936a67 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -757,3 +757,7 @@ $tabs-holder-z-index: 250; background: linear-gradient(to bottom, rgba(#333, 0), rgba(#333, 1)); } } + +.attention-request-sidebar-popover { + z-index: 999; +} diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 898e7826e3a..f25cc1bbc32 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -20,10 +20,6 @@ class DashboardController < Dashboard::ApplicationController urgency :low, [:merge_requests] - before_action only: [:merge_requests] do - push_frontend_feature_flag(:mr_attention_requests, default_enabled: :yaml) - end - def activity respond_to do |format| format.html diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a7d2fb4a127..5f39a8419c9 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -30,9 +30,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action :set_issuables_index, only: [:index] before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] - before_action only: [:index, :show] do - push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml) - end before_action only: [:show] do push_frontend_feature_flag(:file_identifier_hash) diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb index 800256a613b..0922323e12b 100644 --- a/app/models/users/callout.rb +++ b/app/models/users/callout.rb @@ -46,7 +46,9 @@ module Users storage_enforcement_banner_first_enforcement_threshold: 43, storage_enforcement_banner_second_enforcement_threshold: 44, storage_enforcement_banner_third_enforcement_threshold: 45, - storage_enforcement_banner_fourth_enforcement_threshold: 46 + storage_enforcement_banner_fourth_enforcement_threshold: 46, + attention_requests_top_nav: 47, + attention_requests_side_nav: 48 } validates :feature_name, diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb index 2dc2ecad1b4..6dd3908b21d 100644 --- a/app/presenters/merge_request_presenter.rb +++ b/app/presenters/merge_request_presenter.rb @@ -149,7 +149,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ) end - def assign_to_closing_issues_link + def assign_to_closing_issues_path + assign_related_issues_project_merge_request_path(project, merge_request) + end + + def assign_to_closing_issues_count # rubocop: disable CodeReuse/ServiceClass issues = MergeRequests::AssignIssuesService.new(project: project, current_user: current_user, @@ -157,14 +161,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated merge_request: merge_request, closes_issues: closing_issues }).assignable_issues - path = assign_related_issues_project_merge_request_path(project, merge_request) - if issues.present? - if issues.count > 1 - link_to _('Assign yourself to these issues'), path, method: :post - else - link_to _('Assign yourself to this issue'), path, method: :post - end - end + issues.count # rubocop: enable CodeReuse/ServiceClass end diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index b9c71e6d97b..21ab20747d0 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -104,7 +104,11 @@ class MergeRequestWidgetEntity < Grape::Entity # include them if they are explicitly requested on first load. expose :issues_links, if: -> (_, opts) { opts[:issues_links] } do expose :assign_to_closing do |merge_request| - presenter(merge_request).assign_to_closing_issues_link + presenter(merge_request).assign_to_closing_issues_path + end + + expose :assign_to_closing_count do |merge_request| + presenter(merge_request).assign_to_closing_issues_count end expose :closing do |merge_request| diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 7256b9ed467..512a4185bee 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -79,7 +79,8 @@ %li.dropdown-header = _('Merge requests') - if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml) - %li + %li#js-need-attention-nav + #js-need-attention-nav-onboarding = link_to attention_requested_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do = _('Need your attention') = gl_badge_tag user_merge_requests_counts[:attention_requested_count], { size: :sm, variant: user_merge_requests_counts[:attention_requested_count] == 0 ? :neutral : :warning }, { class: 'merge-request-badge gl-ml-auto js-attention-count' } diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 43bcb75ebbf..008f2588dbd 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -92,5 +92,8 @@ #js-review-bar +- if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml) + #js-need-attention-sidebar-onboarding + = render 'projects/invite_members_modal', project: @project = render 'shared/web_ide_path' diff --git a/doc/administration/postgresql/external.md b/doc/administration/postgresql/external.md index 67c5448a8a0..f4e4c7f8bef 100644 --- a/doc/administration/postgresql/external.md +++ b/doc/administration/postgresql/external.md @@ -21,7 +21,7 @@ If you use a cloud-managed service, or provide your own PostgreSQL instance: 1. If you are using a cloud-managed service, you may need to grant additional roles to your `gitlab` user: - Amazon RDS requires the [`rds_superuser`](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.html#Appendix.PostgreSQL.CommonDBATasks.Roles) role. - - Azure Database for PostgreSQL requires the [`azure_pg_admin`](https://docs.microsoft.com/en-us/azure/postgresql/howto-create-users#how-to-create-additional-admin-users-in-azure-database-for-postgresql) role. + - Azure Database for PostgreSQL requires the [`azure_pg_admin`](https://docs.microsoft.com/en-us/azure/postgresql/howto-create-users#how-to-create-additional-admin-users-in-azure-database-for-postgresql) role. Azure Database for PostgreSQL - Flexible Server requires [allow-listing extensions before they can be installed](https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-extensions#how-to-use-postgresql-extensions). - Google Cloud SQL requires the [`cloudsqlsuperuser`](https://cloud.google.com/sql/docs/postgres/users#default-users) role. This is for the installation of extensions during installation and upgrades. As an alternative, diff --git a/doc/administration/troubleshooting/postgresql.md b/doc/administration/troubleshooting/postgresql.md index 47fd424b1fd..d0ed3c5c12a 100644 --- a/doc/administration/troubleshooting/postgresql.md +++ b/doc/administration/troubleshooting/postgresql.md @@ -107,6 +107,10 @@ This section is for links to information elsewhere in the GitLab documentation. To fix this, see [Backup and restore a non-packaged PostgreSQL database](https://docs.gitlab.com/omnibus/settings/database.html#backup-and-restore-a-non-packaged-postgresql-database). +- Deploying PostgreSQL on Azure Database for PostgreSQL - Flexible Server may result in an error stating `extension "btree_gist" is not allow-listed for "azure_pg_admin" users in Azure Database for PostgreSQL` + + To resolve the above error, [allow-list the extension](https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-extensions#how-to-use-postgresql-extensions) prior to install. + ## Support topics ### Database deadlocks diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 5bda4059579..f26fd31c337 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -18994,6 +18994,8 @@ Name of the feature that the callout is for. | Value | Description | | ----- | ----------- | | `ACTIVE_USER_COUNT_THRESHOLD` | Callout feature name for active_user_count_threshold. | +| `ATTENTION_REQUESTS_SIDE_NAV` | Callout feature name for attention_requests_side_nav. | +| `ATTENTION_REQUESTS_TOP_NAV` | Callout feature name for attention_requests_top_nav. | | `BUY_PIPELINE_MINUTES_NOTIFICATION_DOT` | Callout feature name for buy_pipeline_minutes_notification_dot. | | `CANARY_DEPLOYMENT` | Callout feature name for canary_deployment. | | `CI_DEPRECATION_WARNING_FOR_TYPES_KEYWORD` | Callout feature name for ci_deprecation_warning_for_types_keyword. | diff --git a/doc/user/admin_area/reporting/spamcheck.md b/doc/user/admin_area/reporting/spamcheck.md index 1021e4eb63a..559235fe322 100644 --- a/doc/user/admin_area/reporting/spamcheck.md +++ b/doc/user/admin_area/reporting/spamcheck.md @@ -4,16 +4,17 @@ group: Distribution 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 --- -# Spamcheck anti-spam service **(PREMIUM SELF)** +# Spamcheck anti-spam service **(FREE SELF)** > [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259) in GitLab 14.8. +WARNING: +Spamcheck is available to all tiers, but only on instances using GitLab Enterprise Edition (EE). For [licensing reasons](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259#note_726605397), it is not included in the GitLab Community Edition (CE) package. You can [migrate from CE to EE](../../../update/package/convert_to_ee.md). + [Spamcheck](https://gitlab.com/gitlab-org/spamcheck) is an anti-spam engine developed by GitLab originally to combat rising amount of spam in GitLab.com, and later made public to be used in self-managed GitLab instances. -Spamcheck [contains obfuscated, non-free code](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259#note_726605397), which is only available in GitLab Enterprise Edition. You can [migrate from GitLab Community Edition](../../../update/package/convert_to_ee.md), and use the Free tier. - ## Enable Spamcheck Spamcheck is only available for package-based installations: diff --git a/doc/user/admin_area/settings/user_and_ip_rate_limits.md b/doc/user/admin_area/settings/user_and_ip_rate_limits.md index 88be73c3215..56e240a8d39 100644 --- a/doc/user/admin_area/settings/user_and_ip_rate_limits.md +++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md @@ -107,7 +107,7 @@ attached into the response headers. | `RateLimit-Limit` | `60` | The request quota for the client **each minute**. If the rate limit period set in the admin area is different from 1 minute, the value of this header is adjusted to approximately the nearest 60-minute period. | | `RateLimit-Name` | `throttle_authenticated_web` | Name of the throttle blocking the requests. | | `RateLimit-Observed` | `67` | Number of requests associated to the client in the time window. | -| `RateLimit-Remaining` | `0` | Remaining quota in the time window. The result of `RateLimit-Limit` - `RateLimit-Remaining`. | +| `RateLimit-Remaining` | `0` | Remaining quota in the time window. The result of `RateLimit-Limit` - `RateLimit-Observed`. | | `RateLimit-Reset` | `1609844400` | [Unix time](https://en.wikipedia.org/wiki/Unix_time)-formatted time when the request quota is reset. | | `RateLimit-ResetTime` | `Tue, 05 Jan 2021 11:00:00 GMT` | [RFC2616](https://tools.ietf.org/html/rfc2616#section-3.3.1)-formatted date and time when the request quota is reset. | | `Retry-After` | `30` | Remaining duration **in seconds** until the quota is reset. This is a [standard HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After). | diff --git a/doc/user/project/merge_requests/getting_started.md b/doc/user/project/merge_requests/getting_started.md index 7f061a82d15..fd1751585d5 100644 --- a/doc/user/project/merge_requests/getting_started.md +++ b/doc/user/project/merge_requests/getting_started.md @@ -7,7 +7,7 @@ description: "Getting started with merge requests." # Getting started with merge requests **(FREE)** -A merge request (**MR**) is the basis of GitLab as a code +A merge request (**MR**) is the basis of GitLab as a tool for code collaboration and version control. When working in a Git-based platform, you can use branching diff --git a/doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png b/doc/user/project/repository/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png deleted file mode 100644 index 6e2ff33eebb2dcea924b989c189123fb19fb0db0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13008 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.gpg`. -- Select **View public GPG keys** (**{key}**) in the top right of the user's profile. - -GPG verified tags are not supported yet. - -See the [further reading](#further-reading) section for more details on GPG. - -## How GitLab handles GPG - -GitLab uses its own keyring to verify the GPG signature. It does not access any -public key server. - -For a commit to be verified by GitLab: +For GitLab to consider a commit verified: - The committer must have a GPG public/private key pair. -- The committer's public key must have been uploaded to their GitLab - account. -- One of the emails in the GPG key must match a **verified** email address - used by the committer in GitLab. This address will be part of the public key. - If you want to keep this address private, use the automatically generated +- The committer's public key must be uploaded to their GitLab account. +- One of the email addresses in the GPG public key must match a **verified** email address + used by the committer in GitLab. To keep this address private, use the automatically generated [private commit email address](../../../profile/index.md#use-an-automatically-generated-private-commit-email) GitLab provides in your profile. - The committer's email address must match the verified email address from the GPG key. -## Generating a GPG key +GitLab uses its own keyring to verify the GPG signature. It does not access any +public key server. -If you don't already have a GPG key, the following steps can help you get -started: +GPG verified tags are not supported. -1. [Install GPG](https://www.gnupg.org/download/index.html) for your operating system. +For more details about GPG, refer to the [related topics list](#related-topics). + +## View a user's public GPG key + +To view a user's public GPG key, you can either: + +- Go to `https://gitlab.example.com/.gpg`. GitLab displays the GPG key, + if the user has configured one, or a blank page for users without a configured GPG key. +- Go to the user's profile (such as `https://gitlab.example.com/`). In the top right + of the user's profile, select **View public GPG keys** (**{key}**). + +## Configure commit signing + +To sign commits, you must configure both your local machine and your GitLab account: + +1. [Create a GPG key](#create-a-gpg-key). +1. [Add a GPG key to your account](#add-a-gpg-key-to-your-account). +1. [Associate your GPG key with Git](#associate-your-gpg-key-with-git). +1. [Sign your Git commits](#sign-your-git-commits). + +### Create a GPG key + +If you don't already have a GPG key, create one: + +1. [Install GPG](https://www.gnupg.org/download/) for your operating system. If your operating system has `gpg2` installed, replace `gpg` with `gpg2` in - the following commands. -1. Generate the private/public key pair with the command appropriate for your version - of `gpg`. This command spawns a series of questions: + the commands on this page. +1. To generate your key pair, run the command appropriate for your version of `gpg`: ```shell - # Use this command for the default version of gpg, including + # Use this command for the default version of GPG, including # Gpg4win on Windows, and most macOS versions: gpg --gen-key @@ -62,73 +73,29 @@ started: gpg --full-gen-key ``` -1. The first question is which algorithm can be used. Select the kind you want - or press Enter to choose the default (RSA and RSA): - - ```plaintext - Please select what kind of key you want: - (1) RSA and RSA (default) - (2) DSA and Elgamal - (3) DSA (sign only) - (4) RSA (sign only) - Your selection? 1 - ``` - -1. The next question is key length. We recommend you choose `4096`: - - ```plaintext - RSA keys may be between 1024 and 4096 bits long. - What keysize do you want? (2048) 4096 - Requested keysize is 4096 bits - ``` - -1. Specify the validity period of your key. This is something - subjective, and you can use the default value, which is to never expire: - - ```plaintext - Please specify how long the key should be valid. - 0 = key does not expire - = key expires in n days - w = key expires in n weeks - m = key expires in n months - y = key expires in n years - Key is valid for? (0) 0 - Key does not expire at all - ``` - -1. Confirm that the answers you gave were correct by typing `y`: - - ```plaintext - Is this correct? (y/N) y - ``` - -1. Enter your real name, the email address to be associated with this key - (should match a verified email address you use in GitLab) and an optional - comment (press Enter to skip): - - ```plaintext - GnuPG needs to construct a user ID to identify your key. - - Real name: Mr. Robot - Email address: - Comment: - You selected this USER-ID: - "Mr. Robot " - - Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O - ``` - -1. Pick a strong password when asked and type it twice to confirm. -1. Use the following command to list the private GPG key you just created: +1. Select the algorithm your key should use, or press Enter to select + the default option, `RSA and RSA`. +1. Select the key length, in bits. GitLab recommends 4096-bit keys. +1. Specify the validity period of your key. This value is subjective, and the + default value is no expiration. +1. To confirm your answers, enter `y`. +1. Enter your name. +1. Enter your email address. It must match a + [verified email address](../../../profile/index.md#change-the-email-displayed-on-your-commits) + in your GitLab account. +1. Optional. Enter a comment to display in parentheses after your name. +1. GPG displays the information you've entered so far. Edit the information or press + O (for `Okay`) to continue. +1. Enter a strong password, then enter it again to confirm it. +1. To list your private GPG key, run this command, replacing + `` with the email address you used when you generated the key: ```shell - gpg --list-secret-keys --keyid-format LONG + gpg --list-secret-keys --keyid-format LONG ``` - Replace `` with the email address you entered above. - -1. Copy the GPG key ID that starts with `sec`. In the following example, that's - `30F2B65B9246B6CA`: +1. In the output, identify the `sec` line, and copy the GPG key ID. It begins after + the `/` character. In this example, the key ID is `30F2B65B9246B6CA`: ```plaintext sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC] @@ -137,49 +104,46 @@ started: ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E] ``` -1. Export the public key of that ID (replace your key ID from the previous step): +1. To show the associated public key, run this command, replacing `` with the + GPG key ID from the previous step: ```shell - gpg --armor --export 30F2B65B9246B6CA + gpg --armor --export ``` -1. Finally, copy the public key and [add it in your user settings](#adding-a-gpg-key-to-your-account) +1. Copy the public key, including the `BEGIN PGP PUBLIC KEY BLOCK` and + `END PGP PUBLIC KEY BLOCK` lines. You need this key in the next step. -## Adding a GPG key to your account +### Add a GPG key to your account -NOTE: -After you add a key, you cannot edit it, only remove it. In case the paste -didn't work, you have to remove the offending key and re-add it. - -You can add a GPG key in your user settings: +To add a GPG key to your user settings: +1. Sign in to GitLab. 1. In the top-right corner, select your avatar. 1. Select **Edit profile**. -1. On the left sidebar, select **GPG Keys**. -1. Paste your _public_ key in the **Key** text box. - - ![Paste GPG public key](img/profile_settings_gpg_keys_paste_pub.png) - -1. Select **Add key** to add it to GitLab. You can see the key's fingerprint, the corresponding - email address, and creation date. +1. On the left sidebar, select **GPG Keys** (**{key}**). +1. In **Key**, paste your _public_ key. +1. To add the key to your account, select **Add key**. GitLab shows the key's + fingerprint, email address, and creation date: ![GPG key single page](img/profile_settings_gpg_keys_single_key.png) -## Associating your GPG key with Git +After you add a key, you cannot edit it. Instead, remove the offending key and re-add it. -After you have [created your GPG key](#generating-a-gpg-key) and [added it to -your account](#adding-a-gpg-key-to-your-account), it's time to tell Git which -key to use. +### Associate your GPG key with Git -1. Use the following command to list the private GPG key you just created: +After you [create your GPG key](#create-a-gpg-key) and +[add it to your account](#add-a-gpg-key-to-your-account), you must configure Git +to use this key: + +1. Run this command to list the private GPG key you just created, + replacing `` with the email address for your key: ```shell - gpg --list-secret-keys --keyid-format LONG + gpg --list-secret-keys --keyid-format LONG ``` - Replace `` with the email address you entered above. - -1. Copy the GPG key ID that starts with `sec`. In the following example, that's +1. Copy the GPG private key ID that starts with `sec`. In this example, the private key ID is `30F2B65B9246B6CA`: ```plaintext @@ -189,114 +153,103 @@ key to use. ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E] ``` -1. Tell Git to use that key to sign the commits: +1. Run this command to configure Git to sign your commits with your key, + replacing `` with your GPG key ID: ```shell - git config --global user.signingkey 30F2B65B9246B6CA + git config --global user.signingkey ``` - Replace `30F2B65B9246B6CA` with your GPG key ID. - -1. Optional. If Git is using `gpg` and you get errors like `secret key not available` - or `gpg: signing failed: secret key not available`, run the following command to - change to `gpg2`: +1. Optional. If Git uses `gpg` and you get errors like `secret key not available` + or `gpg: signing failed: secret key not available`, run this command to + use `gpg2` instead: ```shell git config --global gpg.program gpg2 ``` -## Signing commits +### Sign your Git commits -After you have [created your GPG key](#generating-a-gpg-key) and [added it to -your account](#adding-a-gpg-key-to-your-account), you can start signing your -commits: +After you [add your public key to your account](#add-a-gpg-key-to-your-account), +you can sign individual commits manually, or configure Git to default to signed commits: -1. Commit like you used to, the only difference is the addition of the `-S` flag: +- Sign individual Git commits manually: + 1. Add `-S` flag to any commit you want to sign: - ```shell - git commit -S -m "My commit msg" - ``` + ```shell + git commit -S -m "My commit message" + ``` -1. Enter the passphrase of your GPG key when asked. -1. Push to GitLab and check that your commits [are verified](#verifying-commits). + 1. Enter the passphrase of your GPG key when asked. + 1. Push to GitLab and check that your commits [are verified](#verify-commits). +- Sign all Git commits by default by running this command: -If you don't want to type the `-S` flag every time you commit, you can tell Git -to sign your commits automatically: + ```shell + git config --global commit.gpgsign true + ``` -```shell -git config --global commit.gpgsign true -``` +## Verify commits -## Verifying commits +You can review commits for a merge request, or for an entire project: -1. Within a project or [merge request](../../merge_requests/index.md), navigate to - the **Commits** tab. Signed commits show a badge containing either - **Verified** or **Unverified**, depending on the verification status of the GPG - signature. +1. To review commits for a project: + 1. On the top bar, select **Menu > Projects** and find your project. + 1. On the left sidebar, select **Repository > Commits**. +1. To review commits for a merge request: + 1. On the top bar, select **Menu > Projects** and find your project. + 1. On the left sidebar, select **Merge requests**, then select your merge request. + 1. Select **Commits**. +1. Identify the commit you want to review. Signed commits show either a **Verified** + or **Unverified** badge, depending on the verification status of the GPG + signature. Unsigned commits do not display a badge: ![Signed and unsigned commits](img/project_signed_and_unsigned_commits.png) -1. By clicking on the GPG badge, details of the signature are displayed. +1. To display the signature details for a commit, select the GPG badge: ![Signed commit with verified signature](img/project_signed_commit_verified_signature.png) - ![Signed commit with verified signature](img/project_signed_commit_unverified_signature.png) + ![Signed commit with unverified signature](img/project_signed_commit_unverified_signature.png) -## Revoking a GPG key +## Revoke a GPG key -Revoking a key **unverifies** already signed commits. Commits that were -verified by using this key changes to an unverified state. Future commits -stay unverified after you revoke this key. This action should be used -in case your key has been compromised. +If a GPG key becomes compromised, revoke it. Revoking a key changes both future and past commits: + +- Past commits signed by this key are marked as unverified. +- Future commits signed by this key are marked as unverified. To revoke a GPG key: 1. In the top-right corner, select your avatar. 1. Select **Edit profile**. -1. On the left sidebar, select **GPG Keys**. +1. On the left sidebar, select **GPG Keys** (**{key}**). 1. Select **Revoke** next to the GPG key you want to delete. -## Removing a GPG key +## Remove a GPG key -Removing a key **does not unverify** already signed commits. Commits that were -verified by using this key stay verified. Only unpushed commits stay -unverified after you remove this key. To unverify already signed commits, you need -to [revoke the associated GPG key](#revoking-a-gpg-key) from your account. +When you remove a GPG key from your GitLab account: + +- Previous commits signed with this key remain verified. +- Future commits (including any commits created but not yet pushed) that attempt + to use this key are unverified. To remove a GPG key from your account: 1. In the top-right corner, select your avatar. 1. Select **Edit profile**. -1. On the left sidebar, select **GPG Keys**. -1. Select the trash icon (**{remove}**) next to the GPG key you want to delete. +1. On the left sidebar, select **GPG Keys** (**{key}**). +1. Select **Remove** (**{remove}**) next to the GPG key you want to delete. -## Rejecting commits that are not signed **(PREMIUM)** +If you must unverify both future and past commits, +[revoke the associated GPG key](#revoke-a-gpg-key) instead. -You can configure your project to reject commits that aren't GPG-signed -via [push rules](../push_rules.md). +## Related topics -## GPG signing API - -Learn how to [get the GPG signature from a commit via API](../../../../api/commits.md#get-gpg-signature-of-a-commit). - -## Further reading - -For more details about GPG, see: - -- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work) -- [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys) -- [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices) -- [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced) -- [Review existing GPG keys in your instance](../../../admin_area/credentials_inventory.md#review-existing-gpg-keys) - - +- [Sign commits and tags with X.509 certificates](../x509_signed_commits/index.md) +- [Commits API](../../../../api/commits.md) +- GPG resources: + - [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work) + - [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys) + - [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices) + - [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced) + - [Review existing GPG keys in your instance](../../../admin_area/credentials_inventory.md#review-existing-gpg-keys) diff --git a/doc/user/project/repository/x509_signed_commits/index.md b/doc/user/project/repository/x509_signed_commits/index.md index f319c1d8fd3..c9cddad1a91 100644 --- a/doc/user/project/repository/x509_signed_commits/index.md +++ b/doc/user/project/repository/x509_signed_commits/index.md @@ -20,7 +20,7 @@ The main difference is the way GitLab determines whether or not the developer's (A trust store is a repository of trusted security certificates.) Combined with any required intermediate certificates in the signature, the developer's certificate can be chained back to a trusted root certificate. -- For GPG, developers [add their GPG key](../gpg_signed_commits/index.md#adding-a-gpg-key-to-your-account) +- For GPG, developers [add their GPG key](../gpg_signed_commits/index.md#add-a-gpg-key-to-your-account) to their account. GitLab uses its own certificate store and therefore defines the diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index a3c53869789..9f18513f066 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -59,6 +59,7 @@ module Gitlab push_frontend_feature_flag(:sandboxed_mermaid, default_enabled: :yaml) push_frontend_feature_flag(:source_editor_toolbar, default_enabled: :yaml) push_frontend_feature_flag(:gl_avatar_for_all_user_avatars, default_enabled: :yaml) + push_frontend_feature_flag(:mr_attention_requests, default_enabled: :yaml) end # Exposes the state of a feature flag to the frontend code. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index cc11367df43..e037fc64d9e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -984,6 +984,9 @@ msgstr "" msgid "%{strongOpen}Warning:%{strongClose} SAML group links can cause GitLab to automatically remove members from groups." msgstr "" +msgid "%{strongStart}Need your attention%{strongEnd} are the merge requests that need your help to move forward, as an assignee or reviewer." +msgstr "" + msgid "%{strongStart}Tip:%{strongEnd} You can also check out merge requests locally. %{linkStart}Learn more.%{linkEnd}" msgstr "" @@ -4939,12 +4942,6 @@ msgstr "" msgid "Assign to me" msgstr "" -msgid "Assign yourself to these issues" -msgstr "" - -msgid "Assign yourself to this issue" -msgstr "" - msgid "Assigned %{assignee_users_sentence}." msgstr "" @@ -34428,6 +34425,9 @@ msgstr "" msgid "Solution" msgstr "" +msgid "Some actions remove attention requests, like a reviewer approving or anyone merging the merge request." +msgstr "" + msgid "Some changes are not shown" msgstr "" @@ -38468,6 +38468,9 @@ msgstr "" msgid "To add the entry manually, provide the following details to the application on your phone." msgstr "" +msgid "To ask someone to look at a merge request, select %{strongStart}Request attention%{strongEnd}. Select again to remove the request." +msgstr "" + msgid "To confirm, type %{phrase_code}" msgstr "" @@ -44005,6 +44008,12 @@ msgstr "" msgid "mrWidget|Approved by you and others" msgstr "" +msgid "mrWidget|Assign yourself to these issues" +msgstr "" + +msgid "mrWidget|Assign yourself to this issue" +msgstr "" + msgid "mrWidget|Cancel auto-merge" msgstr "" diff --git a/scripts/ingest-reports-to-siem b/scripts/ingest-reports-to-siem index fbd41dc3a8e..86c72e1d7eb 100755 --- a/scripts/ingest-reports-to-siem +++ b/scripts/ingest-reports-to-siem @@ -19,15 +19,22 @@ function getMD5HashFromFile(data) { try { const file = 'gl-dependency-scanning-report.json' const data = fs.readFileSync(file) + + const [filename, fileext] = path.basename(file).split('.') + const uniqueId = process.env['CI_PIPELINE_ID'] && process.env['CI_JOB_ID'] ? + process.env['CI_PIPELINE_ID'] + '-' + process.env['CI_JOB_ID'] : + Date.now() + const key = path.join('package_hunter_test', filename + '-' + uniqueId + '.' + fileext) + const responseData = await s3Client.send( new PutObjectCommand({ Bucket: 'gl-logs-for-panther-test', - Key: path.join('package_hunter_test', path.basename(file)), + Key: key, Body: data, ContentMD5: getMD5HashFromFile(data), }), ) - console.log('Successfully uploaded %s', file) + console.log('Successfully uploaded %s to %s', file, key) } catch (err) { if (err.name === 'CredentialsProviderError' || err.name === 'AuthorizationHeaderMalformed') console.log('Could not upload the report. Are AWS credentials configured in ~/.aws/credentials?') diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 062e79ea91a..4d06415e203 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -797,6 +797,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do context 'when 2FA is required for the user' do before do + stub_feature_flags(mr_attention_requests: false) group = create(:group, require_two_factor_authentication: true) group.add_developer(user) end diff --git a/spec/frontend/attention_requests/components/navigation_popover_spec.js b/spec/frontend/attention_requests/components/navigation_popover_spec.js new file mode 100644 index 00000000000..d0231afbdc4 --- /dev/null +++ b/spec/frontend/attention_requests/components/navigation_popover_spec.js @@ -0,0 +1,86 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlPopover, GlButton, GlSprintf, GlIcon } from '@gitlab/ui'; +import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; +import NavigationPopover from '~/attention_requests/components/navigation_popover.vue'; +import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; + +let wrapper; +let dismiss; + +function createComponent(provideData = {}, shouldShowCallout = true) { + wrapper = shallowMount(NavigationPopover, { + provide: { + message: ['Test'], + observerElSelector: '.js-test', + observerElToggledClass: 'show', + featureName: 'attention_requests', + popoverTarget: '.js-test-popover', + ...provideData, + }, + stubs: { + UserCalloutDismisser: makeMockUserCalloutDismisser({ + dismiss, + shouldShowCallout, + }), + GlSprintf, + }, + }); +} + +describe('Attention requests navigation popover', () => { + beforeEach(() => { + setFixtures('
'); + dismiss = jest.fn(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('hides popover if callout is disabled', () => { + createComponent({}, false); + + expect(wrapper.findComponent(GlPopover).exists()).toBe(false); + }); + + it('shows popover if callout is enabled', () => { + createComponent(); + + expect(wrapper.findComponent(GlPopover).exists()).toBe(true); + }); + + it.each` + isDesktop | device | expectedPlacement + ${true} | ${'desktop'} | ${'left'} + ${false} | ${'mobile'} | ${'bottom'} + `( + 'sets popover position to $expectedPlacement on $device', + ({ isDesktop, expectedPlacement }) => { + jest.spyOn(bp, 'isDesktop').mockReturnValue(isDesktop); + + createComponent(); + + expect(wrapper.findComponent(GlPopover).props('placement')).toBe(expectedPlacement); + }, + ); + + it('calls dismiss when clicking action button', () => { + createComponent(); + + wrapper + .findComponent(GlButton) + .vm.$emit('click', { preventDefault() {}, stopPropagation() {} }); + + expect(dismiss).toHaveBeenCalled(); + }); + + it('shows icon in text', () => { + createComponent({ showAttentionIcon: true, message: ['%{strongStart}Test%{strongEnd}'] }); + + const icon = wrapper.findComponent(GlIcon); + + expect(icon.exists()).toBe(true); + expect(icon.props('name')).toBe('attention'); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js index 6ea8ca10c02..15522f7ac1d 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js @@ -1,3 +1,4 @@ +import { GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import RelatedLinks from '~/vue_merge_request_widget/components/mr_widget_related_links.vue'; @@ -85,13 +86,29 @@ describe('MRWidgetRelatedLinks', () => { expect(content).toContain('Mentions issues #23 and #42'); }); - it('should have assing issues link', () => { - createComponent({ - relatedLinks: { - assignToMe: 'Assign yourself to these issues', - }, + describe('should have correct assign issues link', () => { + it.each([ + [1, 'Assign yourself to this issue'], + [2, 'Assign yourself to these issues'], + ])('when issue count is %s, link displays correct text', (unassignedCount, text) => { + const assignToMe = '/assign'; + + createComponent({ + relatedLinks: { assignToMe, unassignedCount }, + }); + + const glLinkWrapper = wrapper.findComponent(GlLink); + + expect(glLinkWrapper.attributes('href')).toBe(assignToMe); + expect(glLinkWrapper.text()).toBe(text); }); - expect(wrapper.text().trim()).toContain('Assign yourself to these issues'); + it('when no link is present', () => { + createComponent({ + relatedLinks: { assignToMe: '#', unassignedCount: 0 }, + }); + + expect(wrapper.findComponent(GlLink).exists()).toBe(false); + }); }); }); diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index 60296cca058..dbf5af095cb 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -162,10 +162,19 @@ RSpec.describe MergeRequestPresenter do end end - describe '#assign_to_closing_issues_link' do + describe '#assign_to_closing_issues_path' do subject do described_class.new(resource, current_user: user) - .assign_to_closing_issues_link + .assign_to_closing_issues_path + end + + it { is_expected.to match("#{project.full_path}/-/merge_requests/#{resource.iid}/assign_related_issues") } + end + + describe '#assign_to_closing_issues_count' do + subject do + described_class.new(resource, current_user: user) + .assign_to_closing_issues_count end before do @@ -178,33 +187,28 @@ RSpec.describe MergeRequestPresenter do let(:issue) { create(:issue) } let(:assignable_issues) { [issue] } - it 'returns correct link with correct text' do + it 'returns correct count' do is_expected - .to match("#{project.full_path}/-/merge_requests/#{resource.iid}/assign_related_issues") - - is_expected - .to match("Assign yourself to this issue") + .to match(1) end end context 'multiple closing issues' do - let(:issues) { create_list(:issue, 2) } + let(:issues) { build_list(:issue, 2) } let(:assignable_issues) { issues } - it 'returns correct link with correct text' do + it 'returns correct count' do is_expected - .to match("#{project.full_path}/-/merge_requests/#{resource.iid}/assign_related_issues") - - is_expected - .to match("Assign yourself to these issues") + .to match(2) end end context 'no closing issue' do let(:assignable_issues) { [] } - it 'returns correct link with correct text' do - is_expected.to be_nil + it 'returns correct count' do + is_expected + .to match(0) end end end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 1712df6266c..f0779f1c57c 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -59,7 +59,7 @@ RSpec.describe MergeRequestWidgetEntity do data = described_class.new(resource, request: request, issues_links: true).as_json expect(data).to include(:issues_links) - expect(data[:issues_links]).to include(:assign_to_closing, :closing, :mentioned_but_not_closing, :closing_count, :mentioned_count) + expect(data[:issues_links]).to include(:assign_to_closing, :assign_to_closing_count, :closing, :mentioned_but_not_closing, :closing_count, :mentioned_count) end it 'omits issue links by default' do