diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-14 06:07:52 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-14 06:07:52 +0300 |
commit | 5e99b288a022aaf47f1d33802f4f96eb8e00084b (patch) | |
tree | 47dcc37acf51fc70b4234cca4354851843d47886 | |
parent | e2d4a6dedbb55154ff9389dbe061fa74ccbae287 (diff) |
Add latest changes from gitlab-org/gitlab@master
33 files changed, 418 insertions, 74 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue index 435b59f91ef..1a25f1cef39 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/alert_management/components/alert_details.vue @@ -1,16 +1,6 @@ <script> import * as Sentry from '@sentry/browser'; -import { - GlAlert, - GlIcon, - GlLoadingIcon, - GlNewDropdown, - GlNewDropdownItem, - GlSprintf, - GlTabs, - GlTab, - GlButton, -} from '@gitlab/ui'; +import { GlAlert, GlIcon, GlLoadingIcon, GlSprintf, GlTabs, GlTab, GlButton } from '@gitlab/ui'; import { s__ } from '~/locale'; import query from '../graphql/queries/details.query.graphql'; import { fetchPolicies } from '~/lib/graphql'; @@ -38,8 +28,6 @@ export default { GlAlert, GlIcon, GlLoadingIcon, - GlNewDropdown, - GlNewDropdownItem, GlSprintf, GlTab, GlTabs, @@ -148,15 +136,6 @@ export default { class="gl-display-flex gl-justify-content-space-between gl-align-items-center" > <h2 data-testid="title">{{ alert.title }}</h2> - <gl-new-dropdown right> - <gl-new-dropdown-item - v-for="(label, field) in $options.statuses" - :key="field" - data-testid="statusDropdownItem" - class="gl-vertical-align-middle" - >{{ label }} - </gl-new-dropdown-item> - </gl-new-dropdown> </div> <gl-tabs v-if="alert" data-testid="alertDetailsTabs"> <gl-tab data-testid="overviewTab" :title="$options.i18n.overviewTitle"> diff --git a/app/controllers/groups/settings/repository_controller.rb b/app/controllers/groups/settings/repository_controller.rb index 6e8c5628d24..4af5e613296 100644 --- a/app/controllers/groups/settings/repository_controller.rb +++ b/app/controllers/groups/settings/repository_controller.rb @@ -46,7 +46,7 @@ module Groups end def deploy_token_params - params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :username) + params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :read_package_registry, :write_package_registry, :username) end end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 3dbf57285b0..2cf5d61ec3a 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -146,7 +146,7 @@ class GroupsController < Groups::ApplicationController export_service = Groups::ImportExport::ExportService.new(group: @group, user: current_user) if export_service.async_execute - redirect_to edit_group_path(@group), notice: _('Group export started.') + redirect_to edit_group_path(@group), notice: _('Group export started. A download link will be sent by email.') else redirect_to edit_group_path(@group), alert: _('Group export could not be started.') end diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index 0aa55dcc5b9..35ca9336613 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -89,7 +89,7 @@ module Projects end def deploy_token_params - params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :username) + params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :read_package_registry, :write_package_registry, :username) end def access_levels_options diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb new file mode 100644 index 00000000000..07812a01202 --- /dev/null +++ b/app/mailers/emails/groups.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Emails + module Groups + def group_was_exported_email(current_user, group) + group_email(current_user, group, _('Group was exported')) + end + + def group_was_not_exported_email(current_user, group, errors) + group_email(current_user, group, _('Group export error'), errors: errors) + end + + def group_email(current_user, group, subj, errors: nil) + @group = group + @errors = errors + mail(to: current_user.notification_email_for(@group), subject: subject(subj)) + end + end +end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 49eacc44519..d9483bab543 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -17,6 +17,7 @@ class Notify < ApplicationMailer include Emails::AutoDevops include Emails::RemoteMirrors include Emails::Releases + include Emails::Groups helper MilestonesHelper helper MergeRequestsHelper diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb index 9848d8ad5c0..0f2e3bb65f9 100644 --- a/app/services/groups/import_export/export_service.rb +++ b/app/services/groups/import_export/export_service.rb @@ -96,6 +96,8 @@ module Groups group_name: @group.name, message: 'Group Import/Export: Export succeeded' ) + + notification_service.group_was_exported(@group, @current_user) end def notify_error @@ -105,6 +107,12 @@ module Groups error: @shared.errors.join(', '), message: 'Group Import/Export: Export failed' ) + + notification_service.group_was_not_exported(@group, @current_user, @shared.errors) + end + + def notification_service + @notification_service ||= NotificationService.new end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 0a8d8e769ec..4c1db03fab8 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -545,6 +545,18 @@ class NotificationService end end + def group_was_exported(group, current_user) + return true unless notifiable?(current_user, :mention, group: group) + + mailer.group_was_exported_email(current_user, group).deliver_later + end + + def group_was_not_exported(group, current_user, errors) + return true unless notifiable?(current_user, :mention, group: group) + + mailer.group_was_not_exported_email(current_user, group, errors).deliver_later + end + protected def new_resource_email(target, method) diff --git a/app/views/groups/settings/_export.html.haml b/app/views/groups/settings/_export.html.haml index 895c2115240..ef7bf562c69 100644 --- a/app/views/groups/settings/_export.html.haml +++ b/app/views/groups/settings/_export.html.haml @@ -17,7 +17,7 @@ %li= _('Projects') %li= _('Runner tokens') %li= _('SAML discovery tokens') - %p= _('Once the exported file is ready you can download it from this page.') + %p= _('Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.') - if group.export_file_exists? = link_to _('Regenerate export'), export_group_path(group), method: :post, class: 'btn btn-default', data: { qa_selector: 'regenerate_export_group_link' } diff --git a/app/views/groups/settings/repository/show.html.haml b/app/views/groups/settings/repository/show.html.haml index 1f1d7779267..ff0c9de4fef 100644 --- a/app/views/groups/settings/repository/show.html.haml +++ b/app/views/groups/settings/repository/show.html.haml @@ -1,6 +1,6 @@ - breadcrumb_title _('Repository Settings') - page_title _('Repository') -- deploy_token_description = s_('DeployTokens|Group deploy tokens allow read-only access to the repositories and registry images within the group.') +- deploy_token_description = s_('DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group.') = render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description diff --git a/app/views/notify/group_was_exported_email.html.haml b/app/views/notify/group_was_exported_email.html.haml new file mode 100644 index 00000000000..a2f34537662 --- /dev/null +++ b/app/views/notify/group_was_exported_email.html.haml @@ -0,0 +1,9 @@ +%p + = _('Group %{group_name} was exported successfully.') % { group_name: @group.name } + +%p + = _('The group export can be downloaded from:') + = link_to download_export_group_url(@group), rel: 'nofollow', download: '' do + #{@group.full_name} export +%p + = _('The download link will expire in 24 hours.') diff --git a/app/views/notify/group_was_exported_email.text.erb b/app/views/notify/group_was_exported_email.text.erb new file mode 100644 index 00000000000..02571459af0 --- /dev/null +++ b/app/views/notify/group_was_exported_email.text.erb @@ -0,0 +1,6 @@ +<%= _('Group %{group_name} was exported successfully.') % { group_name: @group.name } %> + +<%= _('The group export can be downloaded from:') %> +<%= download_export_group_url(@group) %> + +<%= _('The download link will expire in 24 hours.') %> diff --git a/app/views/notify/group_was_not_exported_email.html.haml b/app/views/notify/group_was_not_exported_email.html.haml new file mode 100644 index 00000000000..58fc34d41a3 --- /dev/null +++ b/app/views/notify/group_was_not_exported_email.html.haml @@ -0,0 +1,10 @@ +%p + = _("Group %{group_name} couldn't be exported.") % { group_name: @group.name } + +%p + = _('The errors we encountered were:') + + %ul + - @errors.each do |error| + %li + #{error} diff --git a/app/views/notify/group_was_not_exported_email.text.erb b/app/views/notify/group_was_not_exported_email.text.erb new file mode 100644 index 00000000000..92bd79b7b85 --- /dev/null +++ b/app/views/notify/group_was_not_exported_email.text.erb @@ -0,0 +1,7 @@ +<%= _("Group %{group_name} couldn't be exported.") % { group_name: @group.name } %> + +<%= _('The errors we encountered were:') %> + +<% @errors.each do |error| -%> + - <%= error %> +<% end -%> diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 0373e37818d..760d81136c6 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -22,13 +22,13 @@ .content-list.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } } #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" } = render 'shared/empty_states/priority_labels' - - if @prioritized_labels.present? + - if @prioritized_labels.any? = render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project } - elsif search.present? .nothing-here-block = _('No prioritized labels with such name or description') - - if @labels.present? + - if @labels.any? .other-labels %h5{ class: ('hide' if hide) }= _('Other Labels') .content-list.manage-labels-list.js-other-labels diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml index 193053c8c97..24fc137fd29 100644 --- a/app/views/projects/settings/repository/show.html.haml +++ b/app/views/projects/settings/repository/show.html.haml @@ -1,7 +1,7 @@ - breadcrumb_title _("Repository Settings") - page_title _("Repository") - @content_class = "limit-container-width" unless fluid_layout -- deploy_token_description = s_('DeployTokens|Deploy tokens allow access to your repository and registry images.') +- deploy_token_description = s_('DeployTokens|Deploy tokens allow access to packages, your repository, and registry images.') = render "projects/default_branch/show" = render_if_exists "projects/push_rules/index" diff --git a/app/views/shared/deploy_tokens/_form.html.haml b/app/views/shared/deploy_tokens/_form.html.haml index 5751ed9cb7a..512644518fa 100644 --- a/app/views/shared/deploy_tokens/_form.html.haml +++ b/app/views/shared/deploy_tokens/_form.html.haml @@ -35,5 +35,15 @@ = label_tag ("deploy_token_write_registry"), 'write_registry', class: 'label-bold form-check-label' .text-secondary= s_('DeployTokens|Allows write access to the registry images') + %fieldset.form-group.form-check + = f.check_box :read_package_registry, class: 'form-check-input' + = label_tag ("deploy_token_read_package_registry"), 'read_package_registry', class: 'label-bold form-check-label' + .text-secondary= s_('DeployTokens|Allows read access to the package registry') + + %fieldset.form-group.form-check + = f.check_box :write_package_registry, class: 'form-check-input' + = label_tag ("deploy_token_write_package_registry"), 'write_package_registry', class: 'label-bold form-check-label' + .text-secondary= s_('DeployTokens|Allows write access to the package registry') + .prepend-top-default = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success qa-create-deploy-token' diff --git a/changelogs/unreleased/georgekoltsov-add-group-export-email-nofitication.yml b/changelogs/unreleased/georgekoltsov-add-group-export-email-nofitication.yml new file mode 100644 index 00000000000..ea968288f28 --- /dev/null +++ b/changelogs/unreleased/georgekoltsov-add-group-export-email-nofitication.yml @@ -0,0 +1,5 @@ +--- +title: Add email notification on group export complete +merge_request: 30522 +author: +type: added diff --git a/changelogs/unreleased/package-deploy-tokens-fe.yml b/changelogs/unreleased/package-deploy-tokens-fe.yml new file mode 100644 index 00000000000..9d9557cd06f --- /dev/null +++ b/changelogs/unreleased/package-deploy-tokens-fe.yml @@ -0,0 +1,5 @@ +--- +title: Add read and write package registry scopes to deploy tokens +merge_request: 31267 +author: +type: added diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index 3d09291d9b5..b552f7d88af 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -1,17 +1,26 @@ -# Praefect: High Availability +--- +type: reference +--- -NOTE: **Note:** Praefect is a -[beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga) component that -allows Gitaly to be run in a highly available configuration. While unexpected -data loss is not likely, Praefect is not yet ready for production environments. +# Gitaly Cluster -[Gitaly](index.md) is the service that provides storage for Git repositories in -the GitLab application. Praefect is an optional reverse proxy for Gitaly to -manage multiple Gitaly nodes for high availability. +[Gitaly](index.md), the service that provides storage for Git repositories, can +be run in a clustered configuration to increase fault tolerance. In this +configuration, every Git repository is stored on every Gitaly node in the +cluster. Multiple clusters (or shards), can be configured. -High availability is currently implemented through **asynchronous replication**. -If a Gitaly node becomes unavailable, Praefect will automatically route traffic -to a warm Gitaly replica. +Praefect is a router and transaction manager for Gitaly, and a required +component for running a Gitaly Cluster. + +![Architecture diagram](img/praefect_architecture_v12_10.png) + +Using a Gitaly Cluster increase fault tolerance by: + +- Replicating write operations to warm standby Gitaly nodes. +- Detecting Gitaly node failures. +- Automatically routing Git requests to an available Gitaly node. + +The availability objectives for Gitaly clusters are: - **Recovery Point Objective (RPO):** Less than 1 minute. @@ -35,22 +44,22 @@ The current version supports: - Eventual consistency of the secondary replicas. - Automatic failover from the primary to the secondary. - Reporting of possible data loss if replication queue is non empty. +- Marking the newly promoted primary read only if possible data loss is + detected. Follow the [HA Gitaly epic](https://gitlab.com/groups/gitlab-org/-/epics/1489) for improvements including [horizontally distributing reads](https://gitlab.com/groups/gitlab-org/-/epics/2013). -## Requirements for configuring Gitaly for High Availability +## Requirements for configuring a Gitaly Cluster -A minimum highly available configuration requires: +The minimum recommended configuration for a Gitaly Cluster requires: - 1 highly available load balancer - 1 highly available PostgreSQL server (PostgreSQL 9.6 or newer) - 3 Praefect nodes - 3 Gitaly nodes (1 primary, 2 secondary) -![Architecture diagram](img/praefect_architecture_v12_10.png) - See the [design document](https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/design_ha.md) for implementation details. diff --git a/doc/administration/high_availability/gitaly.md b/doc/administration/high_availability/gitaly.md index f1ebc730e5b..2e6bcabeb06 100644 --- a/doc/administration/high_availability/gitaly.md +++ b/doc/administration/high_availability/gitaly.md @@ -4,12 +4,8 @@ type: reference # Configuring Gitaly for Scaled and High Availability -Gitaly does not yet support full high availability. However, Gitaly is quite -stable and is in use on GitLab.com. Scaled and highly available GitLab environments -should consider using Gitaly on a separate node. - -See the [Gitaly HA Epic](https://gitlab.com/groups/gitlab-org/-/epics/289) to -track plans and progress toward high availability support. +A [Gitaly Cluster](../gitaly/praefect.md) can be used to increase the fault +tolerance of Gitaly in high availability configurations. This document is relevant for [scalable and highly available setups](../reference_architectures/index.md). diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md index a91f3d64a4c..c2fa99e67f4 100644 --- a/doc/administration/high_availability/nfs.md +++ b/doc/administration/high_availability/nfs.md @@ -12,9 +12,6 @@ performance, especially for actions that read or write to Git repositories. See [Filesystem Performance Benchmarking](../operations/filesystem_benchmarking.md) for steps to test filesystem performance. -NOTE: **Note:** [Cloud Object Storage service](object_storage.md) with [Gitaly](gitaly.md) -is recommended over NFS wherever possible for improved performance. - ## NFS Server features ### Required features diff --git a/doc/api/deploy_tokens.md b/doc/api/deploy_tokens.md index 461957847df..6e732a43da0 100644 --- a/doc/api/deploy_tokens.md +++ b/doc/api/deploy_tokens.md @@ -92,7 +92,7 @@ POST /projects/:id/deploy_tokens | `name` | string | yes | New deploy token's name | | `expires_at` | datetime | no | Expiration date for the deploy token. Does not expire if no value is provided. | | `username` | string | no | Username for deploy token. Default is `gitlab+deploy-token-{n}` | -| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, or `write_registry`. | +| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, or `write_package_registry`. | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"name": "My deploy token", "expires_at": "2021-01-01", "username": "custom-user", "scopes": ["read_repository"]}' "https://gitlab.example.com/api/v4/projects/5/deploy_tokens/" @@ -193,7 +193,7 @@ POST /groups/:id/deploy_tokens | `name` | string | yes | New deploy token's name | | `expires_at` | datetime | no | Expiration date for the deploy token. Does not expire if no value is provided. | | `username` | string | no | Username for deploy token. Default is `gitlab+deploy-token-{n}` | -| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, or `write_registry`. | +| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, or `write_package_registry`. | Example request: diff --git a/doc/topics/git/partial_clone.md b/doc/topics/git/partial_clone.md index c776824f433..46318a7f30d 100644 --- a/doc/topics/git/partial_clone.md +++ b/doc/topics/git/partial_clone.md @@ -9,6 +9,8 @@ is a performance optimization that "allows Git to function without having a complete copy of the repository. The goal of this work is to allow Git better handle extremely large repositories." +Git 2.22.0 or later is required. + ## Filter by file size > [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/2553) in GitLab 12.10. @@ -118,7 +120,7 @@ enabled on the Git server: many applications, each in a different subdirectory in the root. Create a file `shiny-app/.filterspec` using the GitLab web interface: - ```.gitignore + ```plaintext # Only the paths listed in the file will be downloaded when performing a # partial clone using `--filter=sparse:oid=shiny-app/.gitfilterspec` @@ -136,7 +138,7 @@ enabled on the Git server: shared-component-b/ ``` -1. *Create a new Git repository and fetch.* Support for `--filter=sparse:oid` +1. **Create a new Git repository and fetch.** Support for `--filter=sparse:oid` using the clone command is incomplete, so we will emulate the clone command by hand, using `git init` and `git fetch`. Follow [issue tracking support for `--filter=sparse:oid`](https://gitlab.com/gitlab-org/git/issues/4) diff --git a/doc/user/group/settings/import_export.md b/doc/user/group/settings/import_export.md index d50327f2a90..a8aec841072 100644 --- a/doc/user/group/settings/import_export.md +++ b/doc/user/group/settings/import_export.md @@ -63,8 +63,11 @@ For more details on the specific data persisted in a group export, see the ![Export group panel](img/export_panel.png) -1. Once the export is generated, you can click **Download export** to download the [exported contents](#exported-contents) -in a compressed tar archive, with contents in JSON format. You can also return to this page to regenerate the export data. +1. Once the export is generated, you should receive an e-mail with a link to the [exported contents](#exported-contents) + in a compressed tar archive, with contents in JSON format. + +1. Alternatively, you can come back to the project settings and download the + file from there by clicking **Download export**, or generate a new file by clicking **Regenerate export**. ## Rate Limits diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md index 44e2d210910..2d42debed68 100644 --- a/doc/user/project/deploy_tokens/index.md +++ b/doc/user/project/deploy_tokens/index.md @@ -4,8 +4,9 @@ > - [Moved](https://gitlab.com/gitlab-org/gitlab/issues/199370) from **Settings > Repository** in GitLab 12.9. > - [Added `write_registry` scope](https://gitlab.com/gitlab-org/gitlab/-/issues/22743) in GitLab 12.10. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29280) from **Settings > CI / CD** in GitLab 12.10.1. +> - [Added package registry scopes](https://gitlab.com/gitlab-org/gitlab/-/issues/213566) from **Settings > CI / CD** in GitLab 13.0. -Deploy tokens allow you to download (`git clone`) or push and pull the container registry images of a project without having a user and a password. +Deploy tokens allow you to download (`git clone`) or push and pull packages and container registry images of a project without having a user and a password. Deploy tokens can be managed by [maintainers only](../../permissions.md). @@ -101,6 +102,22 @@ To push the container registry images, you'll need to: Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply push images to your Container Registry. +### Read or pull packages + +To pull packages in the GitLab package registry, you'll need to: + +1. Create a Deploy Token with `read_package_registry` as a scope. +1. Take note of your `username` and `token`. +1. For the [package type of your choice](./../../packages/index.md), follow the authentication instructions for deploy tokens. + +### Push or upload packages + +To upload packages in the GitLab package registry, you'll need to: + +1. Create a Deploy Token with `write_package_registry` as a scope. +1. Take note of your `username` and `token`. +1. For the [package type of your choice](./../../packages/index.md), follow the authentication instructions for deploy tokens. + ### Group Deploy Token > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/21765) in GitLab 12.9. diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb index f3a08ae970a..0fbbd96cf02 100644 --- a/lib/api/deploy_tokens.rb +++ b/lib/api/deploy_tokens.rb @@ -11,6 +11,8 @@ module API result_hash = Hashie::Mash.new result_hash[:read_registry] = scopes.include?('read_registry') result_hash[:write_registry] = scopes.include?('write_registry') + result_hash[:read_package_registry] = scopes.include?('read_package_registry') + result_hash[:write_package_registry] = scopes.include?('write_package_registry') result_hash[:read_repository] = scopes.include?('read_repository') result_hash end @@ -55,7 +57,7 @@ module API params do requires :name, type: String, desc: "New deploy token's name" requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), - desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", or "write_registry".' + desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".' optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`' end @@ -118,7 +120,7 @@ module API params do requires :name, type: String, desc: 'The name of the deploy token' requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), - desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", or "write_registry".' + desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".' optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`' end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8cc3dc734a2..6aff418ae37 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7128,12 +7128,18 @@ msgstr "" msgid "DeployTokens|Add a deploy token" msgstr "" +msgid "DeployTokens|Allows read access to the package registry" +msgstr "" + msgid "DeployTokens|Allows read-only access to the registry images" msgstr "" msgid "DeployTokens|Allows read-only access to the repository" msgstr "" +msgid "DeployTokens|Allows write access to the package registry" +msgstr "" + msgid "DeployTokens|Allows write access to the registry images" msgstr "" @@ -7155,13 +7161,13 @@ msgstr "" msgid "DeployTokens|Deploy Tokens" msgstr "" -msgid "DeployTokens|Deploy tokens allow access to your repository and registry images." +msgid "DeployTokens|Deploy tokens allow access to packages, your repository, and registry images." msgstr "" msgid "DeployTokens|Expires" msgstr "" -msgid "DeployTokens|Group deploy tokens allow read-only access to the repositories and registry images within the group." +msgid "DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group." msgstr "" msgid "DeployTokens|Name" @@ -10496,6 +10502,12 @@ msgstr "" msgid "Group" msgstr "" +msgid "Group %{group_name} couldn't be exported." +msgstr "" + +msgid "Group %{group_name} was exported successfully." +msgstr "" + msgid "Group %{group_name} was scheduled for deletion." msgstr "" @@ -10547,10 +10559,13 @@ msgstr "" msgid "Group export could not be started." msgstr "" +msgid "Group export error" +msgstr "" + msgid "Group export link has expired. Please generate a new export from your group settings." msgstr "" -msgid "Group export started." +msgid "Group export started. A download link will be sent by email." msgstr "" msgid "Group has been already marked for deletion" @@ -10595,6 +10610,9 @@ msgstr "" msgid "Group variables (inherited)" msgstr "" +msgid "Group was exported" +msgstr "" + msgid "Group was successfully updated." msgstr "" @@ -14492,9 +14510,6 @@ msgstr "" msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source." msgstr "" -msgid "Once the exported file is ready you can download it from this page." -msgstr "" - msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page." msgstr "" @@ -21027,9 +21042,15 @@ msgstr "" msgid "The domain you entered is not allowed." msgstr "" +msgid "The download link will expire in 24 hours." +msgstr "" + msgid "The entered user map is not a valid JSON user map." msgstr "" +msgid "The errors we encountered were:" +msgstr "" + msgid "The file has been successfully created." msgstr "" @@ -21068,6 +21089,9 @@ msgstr "" msgid "The group can be fully restored" msgstr "" +msgid "The group export can be downloaded from:" +msgstr "" + msgid "The group has already been shared with this group" msgstr "" diff --git a/rubocop/cop/performance/ar_exists_and_present_blank.rb b/rubocop/cop/performance/ar_exists_and_present_blank.rb new file mode 100644 index 00000000000..54c2d6bf95a --- /dev/null +++ b/rubocop/cop/performance/ar_exists_and_present_blank.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Performance + class ARExistsAndPresentBlank < RuboCop::Cop::Cop + def message_present(ivar) + "Avoid `#{ivar}.present?`, because it will generate database query 'Select TABLE.*' which is expensive. "\ + "Suggest to use `#{ivar}.any?` to replace `#{ivar}.present?`" + end + + def message_blank(ivar) + "Avoid `#{ivar}.blank?`, because it will generate database query 'Select TABLE.*' which is expensive. "\ + "Suggest to use `#{ivar}.empty?` to replace `#{ivar}.blank?`" + end + + def_node_matcher :exists_match, <<~PATTERN + (send (ivar $_) :exists?) + PATTERN + + def_node_matcher :present_match, <<~PATTERN + (send (ivar $_) :present?) + PATTERN + + def_node_matcher :blank_match, <<~PATTERN + (send (ivar $_) :blank?) + PATTERN + + def file_name(node) + node.location.expression.source_buffer.name + end + + def in_haml_file?(node) + file_name(node).end_with?('.haml.rb') + end + + def on_send(node) + return unless in_haml_file?(node) + + ivar_present = present_match(node) + ivar_blank = blank_match(node) + return unless ivar_present || ivar_blank + + node.each_ancestor(:begin) do |begin_node| + begin_node.each_descendant do |n| + ivar_exists = exists_match(n) + next unless ivar_exists + + add_offense(node, location: :expression, message: message_present(ivar_exists)) if ivar_exists == ivar_present + add_offense(node, location: :expression, message: message_blank(ivar_exists)) if ivar_exists == ivar_blank + end + end + end + end + end + end +end diff --git a/spec/frontend/alert_management/components/alert_management_detail_spec.js b/spec/frontend/alert_management/components/alert_management_detail_spec.js index 5234be0bf07..2a9d66af7df 100644 --- a/spec/frontend/alert_management/components/alert_management_detail_spec.js +++ b/spec/frontend/alert_management/components/alert_management_detail_spec.js @@ -110,10 +110,6 @@ describe('AlertDetails', () => { }); }); - it('renders a status dropdown containing three items', () => { - expect(wrapper.findAll('[data-testid="statusDropdownItem"]').length).toBe(3); - }); - describe('Create issue from alert', () => { describe('createIssueFromAlertEnabled feature flag enabled', () => { it('should display a button that links to new issue page', () => { diff --git a/spec/mailers/emails/groups_spec.rb b/spec/mailers/emails/groups_spec.rb new file mode 100644 index 00000000000..b4746e120e0 --- /dev/null +++ b/spec/mailers/emails/groups_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'email_spec' + +describe Emails::Groups do + include EmailSpec::Matchers + + let(:group) { create(:group) } + let(:user) { create(:user) } + + before do + group.add_owner(user) + end + + describe '#group_was_exported_email' do + subject { Notify.group_was_exported_email(user, group) } + + it 'sends success email' do + expect(subject).to have_subject "#{group.name} | Group was exported" + expect(subject).to have_body_text 'The download link will expire in 24 hours.' + expect(subject).to have_body_text "groups/#{group.path}/-/download_export" + end + end + + describe '#group_was_not_exported_email' do + let(:shared) { Gitlab::ImportExport::Shared.new(group) } + let(:error) { Gitlab::ImportExport::Error.new('Error!') } + + before do + shared.error(error) + end + + subject { Notify.group_was_not_exported_email(user, group, shared.errors) } + + it 'sends failure email' do + expect(subject).to have_subject "#{group.name} | Group export error" + expect(subject).to have_body_text "Group #{group.name} couldn't be exported." + end + end +end diff --git a/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb b/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb new file mode 100644 index 00000000000..ce4fdac56b0 --- /dev/null +++ b/spec/rubocop/cop/performance/ar_exists_and_present_blank_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require_relative '../../../support/helpers/expect_offense' +require_relative '../../../../rubocop/cop/performance/ar_exists_and_present_blank.rb' + +describe RuboCop::Cop::Performance::ARExistsAndPresentBlank do + include CopHelper + include ExpectOffense + + subject(:cop) { described_class.new } + + context 'when it is not haml file' do + it 'does not flag it as an offense' do + expect(subject).to receive(:in_haml_file?).with(anything).at_least(:once).and_return(false) + + expect_no_offenses <<~SOURCE + return unless @users.exists? + show @users if @users.present? + SOURCE + end + end + + context 'when it is haml file' do + before do + expect(subject).to receive(:in_haml_file?).with(anything).at_least(:once).and_return(true) + end + + context 'the same object uses exists? and present?' do + it 'flags it as an offense' do + expect_offense <<~SOURCE + return unless @users.exists? + show @users if @users.present? + ^^^^^^^^^^^^^^^ Avoid `@users.present?`, because it will generate database query 'Select TABLE.*' which is expensive. Suggest to use `@users.any?` to replace `@users.present?` + SOURCE + + expect(cop.offenses.map(&:cop_name)).to contain_exactly('Performance/ARExistsAndPresentBlank') + end + end + + context 'the same object uses exists? and blank?' do + it 'flags it as an offense' do + expect_offense <<~SOURCE + return unless @users.exists? + show @users if @users.blank? + ^^^^^^^^^^^^^ Avoid `@users.blank?`, because it will generate database query 'Select TABLE.*' which is expensive. Suggest to use `@users.empty?` to replace `@users.blank?` + SOURCE + + expect(cop.offenses.map(&:cop_name)).to contain_exactly('Performance/ARExistsAndPresentBlank') + end + end + + context 'the same object uses exists?, blank? and present?' do + it 'flags it as an offense' do + expect_offense <<~SOURCE + return unless @users.exists? + show @users if @users.blank? + ^^^^^^^^^^^^^ Avoid `@users.blank?`, because it will generate database query 'Select TABLE.*' which is expensive. Suggest to use `@users.empty?` to replace `@users.blank?` + show @users if @users.present? + ^^^^^^^^^^^^^^^ Avoid `@users.present?`, because it will generate database query 'Select TABLE.*' which is expensive. Suggest to use `@users.any?` to replace `@users.present?` + SOURCE + + expect(cop.offenses.map(&:cop_name)).to contain_exactly('Performance/ARExistsAndPresentBlank', 'Performance/ARExistsAndPresentBlank') + end + end + + RSpec.shared_examples 'different object uses exists? and present?/blank?' do |another_method| + it 'does not flag it as an offense' do + expect_no_offenses <<~SOURCE + return unless @users.exists? + present @emails if @emails.#{another_method} + SOURCE + end + end + + it_behaves_like 'different object uses exists? and present?/blank?', 'present?' + it_behaves_like 'different object uses exists? and present?/blank?', 'blank?' + + RSpec.shared_examples 'Only using one present?/blank? without exists?' do |non_exists_method| + it 'does not flag it as an offense' do + expect_no_offenses "@users.#{non_exists_method}" + end + end + + it_behaves_like 'Only using one present?/blank? without exists?', 'present?' + it_behaves_like 'Only using one present?/blank? without exists?', 'blank?' + + context 'when using many present?/empty? without exists?' do + it 'does not flag it as an offense' do + expect_no_offenses <<~SOURCE + @user.present? + @user.blank? + @user.present? + @user.blank? + SOURCE + end + end + + context 'when just using exists? without present?/blank?' do + it 'does not flag it as an offense' do + expect_no_offenses '@users.exists?' + + expect_no_offenses <<~SOURCE + @users.exists? + @users.some_other_method? + @users.exists? + SOURCE + end + end + end +end diff --git a/spec/services/groups/import_export/export_service_spec.rb b/spec/services/groups/import_export/export_service_spec.rb index b78e2f0bc40..7bad68b4e00 100644 --- a/spec/services/groups/import_export/export_service_spec.rb +++ b/spec/services/groups/import_export/export_service_spec.rb @@ -71,6 +71,14 @@ describe Groups::ImportExport::ExportService do service.execute end + it 'notifies the user' do + expect_next_instance_of(NotificationService) do |instance| + expect(instance).to receive(:group_was_exported) + end + + service.execute + end + context 'when saver succeeds' do it 'saves the group in the file system' do service.execute @@ -114,16 +122,26 @@ describe Groups::ImportExport::ExportService do context 'when export fails' do context 'when file saver fails' do - it 'removes the remaining exported data' do + before do allow_next_instance_of(Gitlab::ImportExport::Saver) do |saver| allow(saver).to receive(:save).and_return(false) end + end + it 'removes the remaining exported data' do expect { service.execute }.to raise_error(Gitlab::ImportExport::Error) expect(group.import_export_upload).to be_nil expect(File.exist?(shared.archive_path)).to eq(false) end + + it 'notifies the user about failed group export' do + expect_next_instance_of(NotificationService) do |instance| + expect(instance).to receive(:group_was_not_exported) + end + + expect { service.execute }.to raise_error(Gitlab::ImportExport::Error) + end end context 'when file compression fails' do |