提交 5e99b288 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 e2d4a6de
<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">
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
# 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
......@@ -17,6 +17,7 @@ class Notify < ApplicationMailer
include Emails::AutoDevops
include Emails::RemoteMirrors
include Emails::Releases
include Emails::Groups
helper MilestonesHelper
helper MergeRequestsHelper
......
......@@ -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
......
......@@ -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)
......
......@@ -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' }
......
- 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
%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.')
<%= _('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.') %>
%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}
<%= _("Group %{group_name} couldn't be exported.") % { group_name: @group.name } %>
<%= _('The errors we encountered were:') %>
<% @errors.each do |error| -%>
- <%= error %>
<% end -%>
......@@ -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
......
- 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"
......
......@@ -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'
---
title: Add email notification on group export complete
merge_request: 30522
author:
type: added
---
title: Add read and write package registry scopes to deploy tokens
merge_request: 31267
author:
type: added
# 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.
......
......@@ -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).
......
......@@ -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
......
......@@ -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:
......
......@@ -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)
......
......@@ -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
......
......@@ -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.
......
......@@ -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
......
......@@ -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 ""
......
# 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
......@@ -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', () => {
......
# 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
# 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
......@@ -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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册