Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-18 18:09:09 +00:00
parent dfda8b7e77
commit c8deb6a801
49 changed files with 896 additions and 196 deletions

View file

@ -1 +1 @@
a88ceb12912277ab1e07ca325792df40550e6bcc
6f0bfe7c326aeabcb5c3bc35bd3994160d20d6c3

View file

@ -0,0 +1,55 @@
const DATA_ATTR_REGEX_PATTERN = 'data-user-internal-regex-pattern';
const DATA_ATTR_REGEX_OPTIONS = 'data-user-internal-regex-options';
export const ID_USER_EXTERNAL = 'user_external';
export const ID_WARNING = 'warning_external_automatically_set';
export const ID_USER_EMAIL = 'user_email';
const getAttributeValue = (attr) => document.querySelector(`[${attr}]`)?.getAttribute(attr);
const getRegexPattern = () => getAttributeValue(DATA_ATTR_REGEX_PATTERN);
const getRegexOptions = () => getAttributeValue(DATA_ATTR_REGEX_OPTIONS);
export const setupInternalUserRegexHandler = () => {
const regexPattern = getRegexPattern();
if (!regexPattern) {
return;
}
const regexOptions = getRegexOptions();
const elExternal = document.getElementById(ID_USER_EXTERNAL);
const elWarningMessage = document.getElementById(ID_WARNING);
const elUserEmail = document.getElementById(ID_USER_EMAIL);
const isEmailInternal = (email) => {
const regex = new RegExp(regexPattern, regexOptions);
return regex.test(email);
};
const setExternalCheckbox = (email) => {
const isChecked = elExternal.checked;
if (isEmailInternal(email)) {
if (isChecked) {
elExternal.checked = false;
elWarningMessage.classList.remove('hidden');
}
} else if (!isChecked) {
elExternal.checked = true;
elWarningMessage.classList.add('hidden');
}
};
const setupListeners = () => {
elUserEmail.addEventListener('input', (event) => {
setExternalCheckbox(event.target.value);
});
elExternal.addEventListener('change', () => {
elWarningMessage.classList.add('hidden');
});
};
setupListeners();
};

View file

@ -1,49 +1,3 @@
import $ from 'jquery';
import { setupInternalUserRegexHandler } from '~/admin/users/new';
export default class UserInternalRegexHandler {
constructor() {
this.regexPattern = $('[data-user-internal-regex-pattern]').data('user-internal-regex-pattern');
if (this.regexPattern && this.regexPattern !== '') {
this.regexOptions = $('[data-user-internal-regex-options]').data(
'user-internal-regex-options',
);
this.external = $('#user_external');
this.warningMessage = $('#warning_external_automatically_set');
this.addListenerToEmailField();
this.addListenerToUserExternalCheckbox();
}
}
addListenerToEmailField() {
$('#user_email').on('input', (event) => {
this.setExternalCheckbox(event.currentTarget.value);
});
}
addListenerToUserExternalCheckbox() {
this.external.on('click', () => {
this.warningMessage.addClass('hidden');
});
}
isEmailInternal(email) {
const regex = new RegExp(this.regexPattern, this.regexOptions);
return regex.test(email);
}
setExternalCheckbox(email) {
const isChecked = this.external.prop('checked');
if (this.isEmailInternal(email)) {
if (isChecked) {
this.external.prop('checked', false);
this.warningMessage.removeClass('hidden');
}
} else if (!isChecked) {
this.external.prop('checked', true);
this.warningMessage.addClass('hidden');
}
}
}
// eslint-disable-next-line no-new
new UserInternalRegexHandler();
setupInternalUserRegexHandler();

View file

@ -10,6 +10,7 @@ class Groups::BoardsController < Groups::ApplicationController
before_action do
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false)
push_frontend_feature_flag(:boards_filtered_search, group)
push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
end
feature_category :boards

View file

@ -9,6 +9,7 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :assign_endpoint_vars
before_action do
push_frontend_feature_flag(:add_issues_button)
push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
end
feature_category :boards

View file

@ -9,6 +9,7 @@ module Mutations
ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'
field_class ::Types::BaseField
argument_class ::Types::BaseArgument
field :errors, [GraphQL::STRING_TYPE],
null: false,

View file

@ -4,8 +4,10 @@ module Types
class BaseArgument < GraphQL::Schema::Argument
include GitlabStyleDeprecations
attr_reader :deprecation
def initialize(*args, **kwargs, &block)
kwargs = gitlab_deprecation(kwargs)
@deprecation = gitlab_deprecation(kwargs)
super(*args, **kwargs, &block)
end

View file

@ -26,7 +26,7 @@ module Types
def value(*args, **kwargs, &block)
enum[args[0].downcase] = kwargs[:value] || args[0]
kwargs = gitlab_deprecation(kwargs)
gitlab_deprecation(kwargs)
super(*args, **kwargs, &block)
end

View file

@ -8,6 +8,8 @@ module Types
DEFAULT_COMPLEXITY = 1
attr_reader :deprecation
def initialize(**kwargs, &block)
@calls_gitaly = !!kwargs.delete(:calls_gitaly)
@constant_complexity = kwargs[:complexity].is_a?(Integer) && kwargs[:complexity] > 0
@ -16,7 +18,7 @@ module Types
kwargs[:complexity] = field_complexity(kwargs[:resolver_class], kwargs[:complexity])
@feature_flag = kwargs[:feature_flag]
kwargs = check_feature_flag(kwargs)
kwargs = gitlab_deprecation(kwargs)
@deprecation = gitlab_deprecation(kwargs)
super(**kwargs, &block)

View file

@ -7,25 +7,21 @@ module GitlabStyleDeprecations
private
# Mutate the arguments, returns the deprecation
def gitlab_deprecation(kwargs)
if kwargs[:deprecation_reason].present?
raise ArgumentError, 'Use `deprecated` property instead of `deprecation_reason`. ' \
'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-arguments-and-enum-values'
end
deprecation = kwargs.delete(:deprecated)
return kwargs unless deprecation
deprecation = ::Gitlab::Graphql::Deprecation.parse(kwargs.delete(:deprecated))
return unless deprecation
milestone, reason = deprecation.values_at(:milestone, :reason).map(&:presence)
raise ArgumentError, "Bad deprecation. #{deprecation.errors.full_messages.to_sentence}" unless deprecation.valid?
raise ArgumentError, 'Please provide a `milestone` within `deprecated`' unless milestone
raise ArgumentError, 'Please provide a `reason` within `deprecated`' unless reason
raise ArgumentError, '`milestone` must be a `String`' unless milestone.is_a?(String)
kwargs[:deprecation_reason] = deprecation.deprecation_reason
kwargs[:description] = deprecation.edit_description(kwargs[:description])
deprecated_in = "Deprecated in #{milestone}"
kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}."
kwargs[:description] += " #{deprecated_in}: #{reason}." if kwargs[:description]
kwargs
deprecation
end
end

View file

@ -29,6 +29,7 @@ class Packages::Package < ApplicationRecord
delegate :recipe, :recipe_path, to: :conan_metadatum, prefix: :conan
delegate :codename, :suite, to: :debian_distribution, prefix: :debian_distribution
delegate :target_sha, to: :composer_metadatum, prefix: :composer
validates :project, presence: true
validates :name, presence: true

View file

@ -28,4 +28,4 @@
= _("When merge requests and commits in the default branch close, any issues they reference also close.")
= link_to sprite_icon('question-o'), help_page_path('user/project/issues/managing_issues.md', anchor: 'disabling-automatic-issue-closing'), target: '_blank'
= f.submit _('Save changes'), class: "gl-button btn btn-success"
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -0,0 +1,5 @@
---
title: Support for --prefer-source option for Composer registry
merge_request: 56693
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Updated MR Approvals to specify settings section
merge_request: 54985
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Move from btn-success to btn-confirm in default_branch directory
merge_request: 56330
author: Yogi (@yo)
type: changed

View file

@ -0,0 +1,5 @@
---
title: In admin new user page, fix external checkbox warning hide with keyboard interaction
merge_request: 56896
author:
type: fixed

View file

@ -0,0 +1,8 @@
---
name: swimlanes_buffered_rendering
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56614
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/324994
milestone: '13.11'
type: development
group: group::product planning
default_enabled: false

View file

@ -672,7 +672,7 @@ For each Patroni instance on the secondary site:
## Migrating from repmgr to Patroni
1. Before migrating, it is recommended that there is no replication lag between the primary and secondary sites and that replication is paused. In GitLab 13.2 and later, you can pause and resume replication with `gitlab-ctl geo-replication-pause` and `gitlab-ctl geo-replication-resume` on a Geo secondary database node.
1. Follow the [instructions to migrate repmgr to Patroni](../../postgresql/replication_and_failover.md#switching-from-repmgr-to-patroni). When configuring Patroni on each primary site database node, add `patroni['replicaton_slots'] = { '<slot_name>' => 'physical' }`
1. Follow the [instructions to migrate repmgr to Patroni](../../postgresql/replication_and_failover.md#switching-from-repmgr-to-patroni). When configuring Patroni on each primary site database node, add `patroni['replication_slots'] = { '<slot_name>' => 'physical' }`
to `gitlab.rb` where `<slot_name>` is the name of the replication slot for your Geo secondary. This will ensure that Patroni recognizes the replication slot as permanent and will not drop it upon restarting.
1. If database replication to the secondary was paused before migration, resume replication once Patroni is confirmed working on the primary.

View file

@ -97,6 +97,39 @@ The following settings are:
| `remote_directory` | The bucket name where Terraform state files are stored | |
| `connection` | Various connection options described below | |
### Migrate to object storage
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247042) in GitLab 13.9.
To migrate Terraform state files to object storage, follow the instructions below.
- For Omnibus package installations:
```shell
gitlab-rake gitlab:terraform_states:migrate
```
- For source installations:
```shell
sudo -u git -H bundle exec rake gitlab:terraform_states:migrate RAILS_ENV=production
```
For GitLab 13.8 and earlier versions, you can use a workaround for the Rake task:
1. Open the GitLab [Rails console](operations/rails_console.md).
1. Run the following commands:
```ruby
Terraform::StateUploader.alias_method(:upload, :model)
Terraform::StateVersion.where(file_store: ::ObjectStorage::Store::LOCAL). find_each(batch_size: 10) do |terraform_state_version|
puts "Migrating: #{terraform_state_version.inspect}"
terraform_state_version.file.migrate!(::ObjectStorage::Store::REMOTE)
end
```
### S3-compatible connection settings
See [the available connection settings for different providers](object_storage.md#connection-settings).

View file

@ -203,7 +203,7 @@ To do this:
```rails
u = User.find_by_email('email_of_user_doing_search')
s = SearchService.new(u, {:search => 'search_term'})
pp s.search_objects.class.name
pp s.search_objects.class
```
The output from the last command is the key here. If it shows:
@ -217,7 +217,9 @@ The output from the last command is the key here. If it shows:
If all the settings look correct and it is still not using Elasticsearch for the search function, it is best to escalate to GitLab support. This could be a bug/issue.
Moving past that, it is best to attempt the same search using the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) and compare the results from what you see in GitLab.
Moving past that, it is best to attempt the same [search via the Rails console](../../integration/elasticsearch.md#i-indexed-all-the-repositories-but-i-cant-get-any-hits-for-my-search-term-in-the-ui)
or the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html),
and compare the results from what you see in GitLab.
If the results:

View file

@ -3210,7 +3210,8 @@ test:
```
With this configuration, GitLab adds a link **artifact 1** to the relevant merge request
that points to `file1.txt`.
that points to `file1.txt`. To access the link, select **View exposed artifact**
below the pipeline graph in the merge request overview.
An example that matches an entire directory:

View file

@ -91,6 +91,14 @@ Since Elasticsearch can read and use indices created in the previous major versi
The only thing worth noting is that if you have created your current index before GitLab 13.0, you might want to reindex from scratch (which will implicitly create an alias) in order to use some features, for example [Zero downtime reindexing](#zero-downtime-reindexing). Once you do that, you'll be able to perform zero-downtime reindexing and will benefit from any future features that make use of the alias.
If you are unsure when your current index was created,
you can check whether it was created after GitLab 13.0 by using the
[Elasticsearch cat aliases API](https://www.elastic.co/guide/en/elasticsearch/reference/7.11/cat-alias.html).
If the list of aliases returned contains an entry for `gitlab-production` that points to an index
named `gitlab-production-<numerical timestamp>`, your index was created after GitLab 13.0.
If the `gitlab-production` alias is missing, you'll need to reindex from scratch to use
features such as Zero-downtime reindexing.
## Elasticsearch repository indexer
For indexing Git repository data, GitLab uses an [indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer).
@ -936,3 +944,10 @@ sudo gitlab-rake gitlab:elastic:index
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:elastic:index
```
### How does Advanced Search handle private projects?
Advanced Search will store all the projects in the same Elasticsearch indexes,
however searches will only surface results that can be viewed by the user.
Advanced Search will honor all permission checks in the application by
filtering out projects that a user does not have access to at search time.

View file

@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# GitLab Kubernetes Agent **(PREMIUM SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223061) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
> - It's disabled on GitLab.com. Rolling this feature out to GitLab.com is [planned](https://gitlab.com/groups/gitlab-org/-/epics/3834).
> - [In GitLab 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/300960), KAS became available on GitLab.com under `wss://kas.gitlab.com` through an Early Adopter Program.
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
@ -85,7 +85,7 @@ component, `agentk`.
Upgrade your agent installations together with GitLab upgrades. To decide which version of `agentk`to install follow:
1. Open the [GITLAB_KAS_VERSION](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_KAS_VERSION) file from the GitLab Repository, which contains the latest `agentk` version associated with the `master` branch.
1. Open the [`GITLAB_KAS_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_KAS_VERSION) file from the GitLab Repository, which contains the latest `agentk` version associated with the `master` branch.
1. Change the `master` branch and select the Git tag associated with your version. For instance, you could change it to GitLab [v13.5.3-ee release](https://gitlab.com/gitlab-org/gitlab/-/blob/v13.5.3-ee/GITLAB_KAS_VERSION)
The available `agentk` and `kas` versions can be found in
@ -96,20 +96,21 @@ The available `agentk` and `kas` versions can be found in
[Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3834) in GitLab 13.10,
the GitLab Kubernetes Agent Server (KAS) is available on GitLab.com under `wss://kas.gitlab.com`.
If you are a GitLab.com user, skip this step and directly
[set up the configuration repository](#define-a-configuration-repository)
[set up the configuration repository](#define-a-configuration-repository)
for your agent.
The GitLab Kubernetes Agent Server (KAS) can be deployed using [Omnibus
GitLab](https://docs.gitlab.com/omnibus/) or the [GitLab
chart](https://gitlab.com/gitlab-org/charts/gitlab). If you don't already have
The GitLab Kubernetes Agent Server (KAS) can be installed through Omnibus GitLab or
through the GitLab Helm Chart. If you don't already have
GitLab installed, please refer to our [installation
documentation](https://docs.gitlab.com/ee/install/README.html).
You can install the KAS within GitLab as explained below according to your GitLab installation method.
You can also opt to use an [external KAS](#use-an-external-kas-installation).
#### Install with Omnibus
#### Install KAS with Omnibus
When using the [Omnibus GitLab](https://docs.gitlab.com/omnibus/) package:
For [Omnibus](https://docs.gitlab.com/omnibus/) package installations:
1. Edit `/etc/gitlab/gitlab.rb`:
1. Edit `/etc/gitlab/gitlab.rb` to enable the Kubernetes Agent Server:
```plaintext
gitlab_kas['enable'] = true
@ -121,9 +122,9 @@ To configure any additional options related to GitLab Kubernetes Agent Server,
refer to the **Enable GitLab KAS** section of the
[`gitlab.rb.template`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/master/files/gitlab-config-template/gitlab.rb.template).
#### Install with the Helm chart
#### Install KAS with GitLab Helm Chart
When installing or upgrading the GitLab Helm chart, consider the following Helm v3 example.
For GitLab [Helm Chart](https://gitlab.com/gitlab-org/charts/gitlab) installations, consider the following Helm v3 example.
If you're using Helm v2, you must modify this example. See our [notes regarding deploy with Helm](https://docs.gitlab.com/charts/installation/deployment.html#deploy-using-helm).
You must set `global.kas.enabled=true` for the KAS to be properly installed and configured:
@ -150,6 +151,29 @@ gitlab:
For details, read [Using the GitLab-KAS chart](https://docs.gitlab.com/charts/charts/gitlab/kas/).
#### Use an external KAS installation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299850) in GitLab 13.10.
Besides installing KAS with GitLab, you can opt to configure GitLab to use an external KAS.
For GitLab instances installed through the GitLab Helm Chart, see [how to configure your external KAS](https://docs.gitlab.com/charts/charts/globals.html#external-kas).
For GitLab instances installed through Omnibus packages:
1. Edit `/etc/gitlab/gitlab.rb` adding the paths to your external KAS:
```ruby
gitlab_kas['enable'] = false
gitlab_kas['api_secret_key'] = 'Your shared secret between GitLab and KAS'
gitlab_rails['gitlab_kas_enabled'] = true
gitlab_rails['gitlab_kas_external_url'] = 'wss://kas.gitlab.example.com' # User-facing URL for the in-cluster agentk
gitlab_rails['gitlab_kas_internal_url'] = 'grpc://kas.internal.gitlab.example.com' # Internal URL for the GitLab backend
```
1. [Reconfigure GitLab](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
### Define a configuration repository
Next, you need a GitLab repository to contain your Agent configuration. The minimal

View file

@ -121,7 +121,7 @@ NOTE:
## IP range
GitLab.com is using the IP range `34.74.90.64/28` for traffic from its Web/API
GitLab.com uses the IP ranges `34.74.90.64/28` and `34.74.226.0/24` for traffic from its Web/API
fleet. This whole range is solely allocated to GitLab. You can expect connections from webhooks or repository mirroring to come
from those IPs and allow them.

View file

@ -274,8 +274,21 @@ To install a package:
composer update
```
Or to install the single package:
```shell
composer req <package-name>:<package-version>
```
If successful, you should see output indicating that the package installed successfully.
You can also install from source (by pulling the Git repository directly) using the
`--prefer-source` option:
```shell
composer update --prefer-source
```
WARNING:
Never commit the `auth.json` file to your repository. To install packages from a CI/CD job,
consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your personal access token

View file

@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
# Deploy Tokens
# Deploy tokens
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17894) in GitLab 10.7.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/199370) from **Settings > Repository** in GitLab 12.9.
@ -23,15 +23,15 @@ Deploy tokens cannot be used with the GitLab API.
If you have a key pair, you might want to use [deploy keys](../../project/deploy_keys/index.md)
instead.
## Creating a Deploy Token
## Creating a Deploy token
You can create as many deploy tokens as you need from the settings of your
project. Alternatively, you can also create [group-scoped deploy tokens](#group-deploy-token).
1. Sign in to your GitLab account.
1. Go to the project (or group) you want to create Deploy Tokens for.
1. Go to the project (or group) you want to create deploy tokens for.
1. Go to **Settings > Repository**.
1. Click on "Expand" on **Deploy Tokens** section.
1. Expand the **Deploy tokens** section.
1. Choose a name, expiry date (optional), and username (optional) for the token.
1. Choose the [desired scopes](#limiting-scopes-of-a-deploy-token).
1. Select **Create deploy token**.
@ -46,8 +46,8 @@ Deploy tokens expire at midnight UTC on the date you define.
## Revoking a deploy token
At any time, you can revoke any deploy token by just clicking the respective
**Revoke** button under the 'Active deploy tokens' area.
To revoke a deploy token, under the **Active deploy tokens** area,
select the respective **Revoke** button.
## Limiting scopes of a deploy token
@ -75,11 +75,11 @@ username to be used when creating the deploy token.
### Git clone a repository
To download a repository using a Deploy Token, you just need to:
To download a repository using a deploy token:
1. Create a Deploy Token with `read_repository` as a scope.
1. Create a deploy token with `read_repository` as a scope.
1. Take note of your `username` and `token`.
1. `git clone` the project using the Deploy Token:
1. `git clone` the project using the deploy token:
```shell
git clone https://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git
@ -91,7 +91,7 @@ Replace `<username>` and `<deploy_token>` with the proper values.
To read the container registry images, you must:
1. Create a Deploy Token with `read_registry` as a scope.
1. Create a deploy token with `read_registry` as a scope.
1. Take note of your `username` and `token`.
1. Sign in to the GitLab Container Registry using the deploy token:
@ -108,7 +108,7 @@ pull images from your Container Registry.
To push the container registry images, you must:
1. Create a Deploy Token with `write_registry` as a scope.
1. Create a deploy token with `write_registry` as a scope.
1. Take note of your `username` and `token`.
1. Sign in to the GitLab Container Registry using the deploy token:
@ -125,7 +125,7 @@ push images to your Container Registry.
To pull packages in the GitLab package registry, you must:
1. Create a Deploy Token with `read_package_registry` as a scope.
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.
@ -152,12 +152,12 @@ Example response:
To upload packages in the GitLab package registry, you must:
1. Create a Deploy Token with `write_package_registry` as a scope.
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
### Group deploy token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21765) in GitLab 12.9.
@ -167,7 +167,7 @@ belong either to the specific group or to one of its subgroups.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [Group Deploy Tokens](https://youtu.be/8kxTJvaD9ks).
The Group Deploy Tokens UI is now accessible under **Settings > Repository**,
The Group deploy tokens UI is now accessible under **Settings > Repository**,
not **Settings > CI/CD** as indicated in the video.
To use a group deploy token:
@ -179,12 +179,12 @@ To use a group deploy token:
The scopes applied to a group deploy token (such as `read_repository`)
apply consistently when cloning the repository of related projects.
### GitLab Deploy Token
### GitLab deploy token
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18414) in GitLab 10.8.
There's a special case when it comes to Deploy Tokens. If a user creates one
named `gitlab-deploy-token`, the username and token of the Deploy Token is
There's a special case when it comes to deploy tokens. If a user creates one
named `gitlab-deploy-token`, the username and token of the deploy token is
automatically exposed to the CI/CD jobs as CI/CD variables: `CI_DEPLOY_USER`
and `CI_DEPLOY_PASSWORD`, respectively.

View file

@ -53,7 +53,7 @@ be merged, and optionally which users should do the approving. Approvals can be
If no approval rules are defined, any user can approve a merge request. However, the default
minimum number of required approvers can still be set in the
[project settings for merge request approvals](#merge-request-approvals-project-settings).
[settings for merge request approvals](#approval-settings).
You can opt to define one single rule to approve a merge request among the available rules
or choose more than one with [multiple approval rules](#multiple-approval-rules).
@ -278,9 +278,9 @@ else blocking it. Note that the merge request could still be blocked by other co
such as merge conflicts, [pending discussions](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved),
or a [failed CI/CD pipeline](merge_when_pipeline_succeeds.md).
### Merge request approvals project settings
### Approval settings
The project settings for Merge request approvals are found by going to
The settings for Merge request approvals are found by going to
**Settings > General** and expanding **Merge request approvals**.
#### Prevent overriding default approvals

View file

@ -28,20 +28,34 @@ module Gitlab
def package_metadata(package)
json = package.composer_metadatum.composer_json
json.merge('dist' => package_dist(package), 'uid' => package.id, 'version' => package.version)
json.merge(
'dist' => package_dist(package),
'source' => package_source(package),
'uid' => package.id,
'version' => package.version
)
end
def package_dist(package)
sha = package.composer_metadatum.target_sha
archive_api_path = api_v4_projects_packages_composer_archives_package_name_path({ id: package.project_id, package_name: package.name, format: '.zip' }, true)
{
'type' => 'zip',
'url' => expose_url(archive_api_path) + "?sha=#{sha}",
'reference' => sha,
'url' => expose_url(archive_api_path) + "?sha=#{package.composer_target_sha}",
'reference' => package.composer_target_sha,
'shasum' => ''
}
end
def package_source(package)
git_url = package.project.http_url_to_repo
{
'type' => 'git',
'url' => git_url,
'reference' => package.composer_target_sha
}
end
end
end
end

View file

@ -0,0 +1,116 @@
# frozen_string_literal: true
module Gitlab
module Graphql
class Deprecation
REASONS = {
renamed: 'This was renamed.',
discouraged: 'Use of this is not recommended.'
}.freeze
include ActiveModel::Validations
validates :milestone, presence: true, format: { with: /\A\d+\.\d+\z/, message: 'must be milestone-ish' }
validates :reason, presence: true
validates :reason,
format: { with: /.*[^.]\z/, message: 'must not end with a period' },
if: :reason_is_string?
validate :milestone_is_string
validate :reason_known_or_string
def self.parse(options)
new(**options) if options
end
def initialize(reason: nil, milestone: nil, replacement: nil)
@reason = reason.presence
@milestone = milestone.presence
@replacement = replacement.presence
end
def ==(other)
return false unless other.is_a?(self.class)
[reason_text, milestone, replacement] == [:reason_text, :milestone, :replacement].map do |attr|
other.send(attr) # rubocop: disable GitlabSecurity/PublicSend
end
end
alias_method :eql, :==
def markdown(context: :inline)
parts = [
"#{deprecated_in(format: :markdown)}.",
reason_text,
replacement.then { |r| "Use: `#{r}`." if r }
].compact
case context
when :block
['WARNING:', *parts].join("\n")
when :inline
parts.join(' ')
end
end
def edit_description(original_description)
@original_description = original_description
return unless original_description
original_description + description_suffix
end
def original_description
return unless @original_description
return @original_description if @original_description.ends_with?('.')
"#{@original_description}."
end
def deprecation_reason
[
reason_text,
replacement && "Please use `#{replacement}`.",
"#{deprecated_in}."
].compact.join(' ')
end
private
attr_reader :reason, :milestone, :replacement
def milestone_is_string
return if milestone.is_a?(String)
errors.add(:milestone, 'must be a string')
end
def reason_known_or_string
return if REASONS.key?(reason)
return if reason_is_string?
errors.add(:reason, 'must be a known reason or a string')
end
def reason_is_string?
reason.is_a?(String)
end
def reason_text
@reason_text ||= REASONS[reason] || "#{reason.to_s.strip}."
end
def description_suffix
" #{deprecated_in}: #{reason_text}"
end
def deprecated_in(format: :plain)
case format
when :plain
"Deprecated in #{milestone}"
when :markdown
"**Deprecated** in #{milestone}"
end
end
end
end
end

View file

@ -3844,6 +3844,9 @@ msgstr ""
msgid "Approval rules reset to project defaults"
msgstr ""
msgid "Approval settings"
msgstr ""
msgid "ApprovalRuleRemove|%d member"
msgid_plural "ApprovalRuleRemove|%d members"
msgstr[0] ""
@ -13893,6 +13896,9 @@ msgstr ""
msgid "Geo|In sync"
msgstr ""
msgid "Geo|Internal URL"
msgstr ""
msgid "Geo|Last repository check run"
msgstr ""
@ -13917,9 +13923,6 @@ msgstr ""
msgid "Geo|Next sync scheduled at"
msgstr ""
msgid "Geo|Node Details"
msgstr ""
msgid "Geo|Node name can't be blank"
msgstr ""
@ -13938,6 +13941,9 @@ msgstr ""
msgid "Geo|Please refer to Geo Troubleshooting."
msgstr ""
msgid "Geo|Primary Details"
msgstr ""
msgid "Geo|Primary node"
msgstr ""
@ -13986,6 +13992,9 @@ msgstr ""
msgid "Geo|Review replication status, and resynchronize and reverify items with the primary node."
msgstr ""
msgid "Geo|Secondary Details"
msgstr ""
msgid "Geo|Secondary node"
msgstr ""
@ -14214,6 +14223,9 @@ msgstr ""
msgid "GitLab uses %{jaeger_link} to monitor distributed systems."
msgstr ""
msgid "GitLab version"
msgstr ""
msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""

View file

@ -96,6 +96,7 @@ module QA
autoload :ProjectSnippet, 'qa/resource/project_snippet'
autoload :Design, 'qa/resource/design'
autoload :RegistryRepository, 'qa/resource/registry_repository'
autoload :Package, 'qa/resource/package'
module KubernetesCluster
autoload :Base, 'qa/resource/kubernetes_cluster/base'

52
qa/qa/resource/package.rb Normal file
View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'securerandom'
module QA
module Resource
class Package < Base
attr_accessor :name
attribute :project do
Project.fabricate_via_api! do |resource|
resource.name = 'project-with-package'
resource.description = 'Project with Package'
end
end
attribute :id do
packages = project.packages
return unless (this_package = packages&.find { |package| package[:name] == "#{project.path_with_namespace}/#{name}" }) # rubocop:disable Cop/AvoidReturnFromBlocks
this_package[:id]
end
def fabricate!
end
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
super
end
def remove_via_api!
packages = project.packages
if packages && !packages.empty?
QA::Runtime::Logger.debug("Deleting package '#{name}' from '#{project.path_with_namespace}' via API")
super
end
end
def api_delete_path
"/projects/#{project.id}/packages/#{id}"
end
def api_get_path
"/projects/#{project.id}/packages"
end
end
end
end

View file

@ -155,6 +155,10 @@ module QA
"#{api_get_path}/registry/repositories"
end
def api_packages_path
"#{api_get_path}/packages"
end
def api_commits_path
"#{api_get_path}/repository/commits"
end
@ -262,7 +266,11 @@ module QA
def registry_repositories
response = get Runtime::API::Request.new(api_client, "#{api_registry_repositories_path}").url
parse_body(response)
end
def packages
response = get Runtime::API::Request.new(api_client, "#{api_packages_path}").url
parse_body(response)
end

View file

@ -1,18 +1,25 @@
# frozen_string_literal: true
require 'securerandom'
module QA
RSpec.describe 'Package', :orchestrated, :packages do
describe 'Composer Repository' do
include Runtime::Fixtures
let(:package_name) { 'my_package' }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'composer-package-project'
end
end
let(:package) do
Resource::Package.new.tap do |package|
package.name = "my_package-#{SecureRandom.hex(4)}"
package.project = project
end
end
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = "qa-runner-#{Time.now.to_i}"
@ -30,7 +37,7 @@ module QA
let(:composer_json_file) do
<<~EOF
{
"name": "#{project.path_with_namespace}/#{package_name}",
"name": "#{project.path_with_namespace}/#{package.name}",
"description": "Library XY",
"type": "library",
"license": "GPL-3.0-only",
@ -94,14 +101,15 @@ module QA
after do
runner.remove_via_api!
package.remove_via_api!
end
it 'publishes a composer package and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1088' do
Page::Project::Menu.perform(&:click_packages_link)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package_name)
index.click_package(package_name)
expect(index).to have_package(package.name)
index.click_package(package.name)
end
Page::Project::Packages::Show.perform(&:click_delete)
@ -109,7 +117,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
aggregate_failures 'package deletion' do
expect(index).to have_content("Package deleted successfully")
expect(index).not_to have_package(package_name)
expect(index).not_to have_package(package.name)
end
end
end

View file

@ -5,14 +5,19 @@ module QA
describe 'Conan Repository' do
include Runtime::Fixtures
let(:package_name) { 'conantest' }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'conan-package-project'
end
end
let(:package) do
Resource::Package.new.tap do |package|
package.name = 'conantest'
package.project = project
end
end
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = "qa-runner-#{Time.now.to_i}"
@ -29,6 +34,7 @@ module QA
after do
runner.remove_via_api!
package.remove_via_api!
end
it 'publishes, installs, and deletes a Conan package', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1077' do
@ -47,10 +53,10 @@ module QA
stage: deploy
script:
- "conan remote add gitlab #{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/conan"
- "conan new #{package_name}/0.1 -t"
- "conan new #{package.name}/0.1 -t"
- "conan create . mycompany/stable"
- "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload #{package_name}/0.1@mycompany/stable --all --remote=gitlab"
- "conan install conantest/0.1@mycompany/stable --remote=gitlab"
- "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload #{package.name}/0.1@mycompany/stable --all --remote=gitlab"
- "conan install #{package.name}/0.1@mycompany/stable --remote=gitlab"
tags:
- "runner-for-#{project.name}"
YAML
@ -71,15 +77,15 @@ module QA
Page::Project::Menu.perform(&:click_packages_link)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package_name)
index.click_package(package_name)
expect(index).to have_package(package.name)
index.click_package(package.name)
end
Page::Project::Packages::Show.perform(&:click_delete)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_content("Package deleted successfully")
expect(index).not_to have_package(package_name)
expect(index).not_to have_package(package.name)
end
end
end

View file

@ -3,14 +3,19 @@
module QA
RSpec.describe 'Package', :orchestrated, :packages do
describe 'Generic Repository' do
let(:package_name) { 'my_package' }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'generic-package-project'
end
end
let(:package) do
Resource::Package.new.tap do |package|
package.name = "my_package"
package.project = project
end
end
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = "qa-runner-#{Time.now.to_i}"
@ -90,14 +95,15 @@ module QA
after do
runner.remove_via_api!
package.remove_via_api!
end
it 'uploads a generic package, downloads and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1108' do
Page::Project::Menu.perform(&:click_packages_link)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package_name)
index.click_package(package_name)
expect(index).to have_package(package.name)
index.click_package(package.name)
end
Page::Project::Packages::Show.perform(&:click_delete)
@ -105,7 +111,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
aggregate_failures 'package deletion' do
expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
expect(index).to have_no_package(package.name)
end
end
end

View file

@ -23,6 +23,13 @@ module QA
end
end
let(:package) do
Resource::Package.new.tap do |package|
package.name = package_name
package.project = project
end
end
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = "qa-runner-#{Time.now.to_i}"
@ -39,6 +46,7 @@ module QA
after do
runner.remove_via_api!
package.remove_via_api!
end
it 'publishes a maven package via gradle', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1074' do

View file

@ -31,6 +31,13 @@ module QA
end
end
let(:package) do
Resource::Package.new.tap do |package|
package.name = package_name
package.project = project
end
end
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = "qa-runner-#{Time.now.to_i}"

View file

@ -5,8 +5,7 @@ module QA
describe 'npm registry' do
include Runtime::Fixtures
let(:registry_scope) { project.group.sandbox.path }
let(:package_name) { "@#{registry_scope}/#{project.name}" }
let!(:registry_scope) { project.group.sandbox.path }
let(:auth_token) do
unless Page::Main::Menu.perform(&:signed_in?)
Flow::Login.sign_in
@ -15,12 +14,23 @@ module QA
Resource::PersonalAccessToken.fabricate!.token
end
let(:project) do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'npm-registry-project'
end
end
let(:package) do
Resource::Package.new.tap do |package|
package.name = "@#{registry_scope}/#{project.name}"
package.project = project
end
end
after do
package.remove_via_api!
end
it 'publishes an npm package and then deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/944' do
uri = URI.parse(Runtime::Scenario.gitlab_address)
gitlab_host_with_port = "#{uri.host}:#{uri.port}"
@ -29,7 +39,7 @@ module QA
file_path: 'package.json',
content: <<~JSON
{
"name": "#{package_name}",
"name": "#{package.name}",
"version": "1.0.0",
"description": "Example package for GitLab npm registry",
"publishConfig": {
@ -56,20 +66,20 @@ module QA
Page::Project::Menu.perform(&:click_packages_link)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package_name)
expect(index).to have_package(package.name)
index.click_package(package_name)
index.click_package(package.name)
end
Page::Project::Packages::Show.perform do |show|
expect(show).to have_package_info(package_name, "1.0.0")
expect(show).to have_package_info(package.name, "1.0.0")
show.click_delete
end
Page::Project::Packages::Index.perform do |index|
expect(index).to have_content("Package deleted successfully")
expect(index).not_to have_package(package_name)
expect(index).not_to have_package(package.name)
end
end
end

View file

@ -6,8 +6,6 @@ module QA
RSpec.describe 'Package', :orchestrated, :packages do
describe 'NuGet Repository' do
include Runtime::Fixtures
let(:package_name) { "dotnetcore-#{SecureRandom.hex(8)}" }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'nuget-package-project'
@ -15,6 +13,13 @@ module QA
end
end
let(:package) do
Resource::Package.new.tap do |package|
package.name = "dotnetcore-#{SecureRandom.hex(8)}"
package.project = project
end
end
let(:another_project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'nuget-package-install-project'
@ -43,6 +48,7 @@ module QA
after do
runner.remove_via_api!
another_runner.remove_via_api!
package.remove_via_api!
end
it 'publishes a nuget package at the project level, installs and deletes it at the group level', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1073' do
@ -66,7 +72,7 @@ module QA
script:
- dotnet restore -p:Configuration=Release
- dotnet build -c Release
- dotnet pack -c Release -p:PackageID=#{package_name}
- dotnet pack -c Release -p:PackageID=#{package.name}
- dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text
- dotnet nuget push "bin/Release/*.nupkg" --source gitlab
only:
@ -127,7 +133,7 @@ module QA
script:
- dotnet nuget locals all --clear
- dotnet nuget add source "$CI_SERVER_URL/api/v4/groups/#{another_project.group.id}/-/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text
- "dotnet add otherdotnet.csproj package #{package_name} --version 1.0.0"
- "dotnet add otherdotnet.csproj package #{package.name} --version 1.0.0"
only:
- "#{another_project.default_branch}"
tags:
@ -153,15 +159,15 @@ module QA
Page::Group::Menu.perform(&:go_to_group_packages)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package_name)
index.click_package(package_name)
expect(index).to have_package(package.name)
index.click_package(package.name)
end
Page::Project::Packages::Show.perform(&:click_delete)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_content("Package deleted successfully")
expect(index).not_to have_package(package_name)
expect(index).not_to have_package(package.name)
end
end
end

View file

@ -4,15 +4,19 @@ module QA
RSpec.describe 'Package', :orchestrated, :packages do
describe 'PyPI Repository' do
include Runtime::Fixtures
let(:package_name) { 'mypypipackage' }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'pypi-package-project'
end
end
let(:package) do
Resource::Package.new.tap do |package|
package.name = 'mypypipackage'
package.project = project
end
end
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = "qa-runner-#{Time.now.to_i}"
@ -87,6 +91,7 @@ module QA
after do
runner.remove_via_api!
package.remove_via_api!
project&.remove_via_api!
end
@ -94,8 +99,8 @@ module QA
Page::Project::Menu.perform(&:click_packages_link)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package_name)
index.click_package(package_name)
expect(index).to have_package(package.name)
index.click_package(package.name)
end
Page::Project::Packages::Show.perform(&:click_delete)
@ -103,7 +108,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
aggregate_failures do
expect(index).to have_content("Package deleted successfully")
expect(index).not_to have_package(package_name)
expect(index).not_to have_package(package.name)
end
end
end
@ -127,8 +132,8 @@ module QA
Page::Project::Menu.perform(&:click_packages_link)
Page::Project::Packages::Index.perform do |index|
index.wait_for_package_replication(package_name)
expect(index).to have_package(package_name)
index.wait_for_package_replication(package.name)
expect(index).to have_package(package.name)
end
end
end

View file

@ -16,6 +16,15 @@ RSpec.describe Groups::BoardsController do
expect { list_boards }.to change(group.boards, :count).by(1)
end
it 'pushes swimlanes_buffered_rendering feature flag' do
allow(controller).to receive(:push_frontend_feature_flag).and_call_original
expect(controller).to receive(:push_frontend_feature_flag)
.with(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
list_boards
end
context 'when format is HTML' do
it 'renders template' do
list_boards
@ -98,6 +107,15 @@ RSpec.describe Groups::BoardsController do
describe 'GET show' do
let!(:board) { create(:board, group: group) }
it 'pushes swimlanes_buffered_rendering feature flag' do
allow(controller).to receive(:push_frontend_feature_flag).and_call_original
expect(controller).to receive(:push_frontend_feature_flag)
.with(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
read_board board: board
end
context 'when format is HTML' do
it 'renders template' do
expect { read_board board: board }.to change(BoardGroupRecentVisit, :count).by(1)

View file

@ -22,6 +22,15 @@ RSpec.describe Projects::BoardsController do
expect(assigns(:boards_endpoint)).to eq project_boards_path(project)
end
it 'pushes swimlanes_buffered_rendering feature flag' do
allow(controller).to receive(:push_frontend_feature_flag).and_call_original
expect(controller).to receive(:push_frontend_feature_flag)
.with(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
list_boards
end
context 'when format is HTML' do
it 'renders template' do
list_boards
@ -116,6 +125,15 @@ RSpec.describe Projects::BoardsController do
describe 'GET show' do
let!(:board) { create(:board, project: project) }
it 'pushes swimlanes_buffered_rendering feature flag' do
allow(controller).to receive(:push_frontend_feature_flag).and_call_original
expect(controller).to receive(:push_frontend_feature_flag)
.with(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
read_board board: board
end
it 'sets boards_endpoint instance variable to a boards path' do
read_board board: board

View file

@ -0,0 +1,76 @@
import {
setupInternalUserRegexHandler,
ID_USER_EMAIL,
ID_USER_EXTERNAL,
ID_WARNING,
} from '~/admin/users/new';
describe('admin/users/new', () => {
const FIXTURE = 'admin/users/new_with_internal_user_regex.html';
let elExternal;
let elUserEmail;
let elWarningMessage;
beforeEach(() => {
loadFixtures(FIXTURE);
setupInternalUserRegexHandler();
elExternal = document.getElementById(ID_USER_EXTERNAL);
elUserEmail = document.getElementById(ID_USER_EMAIL);
elWarningMessage = document.getElementById(ID_WARNING);
elExternal.checked = true;
});
const changeEmail = (val) => {
elUserEmail.value = val;
elUserEmail.dispatchEvent(new Event('input'));
};
const hasHiddenWarning = () => elWarningMessage.classList.contains('hidden');
describe('Behaviour of userExternal checkbox', () => {
it('hides warning by default', () => {
expect(hasHiddenWarning()).toBe(true);
});
describe('when matches email as internal', () => {
beforeEach(() => {
changeEmail('test@');
});
it('has external unchecked', () => {
expect(elExternal.checked).toBe(false);
});
it('shows warning', () => {
expect(hasHiddenWarning()).toBe(false);
});
describe('when external is checked again', () => {
beforeEach(() => {
elExternal.dispatchEvent(new Event('change'));
});
it('hides warning', () => {
expect(hasHiddenWarning()).toBe(true);
});
});
});
describe('when matches emails as external', () => {
beforeEach(() => {
changeEmail('test.ext@');
});
it('has external checked', () => {
expect(elExternal.checked).toBe(true);
});
it('hides warning', () => {
expect(hasHiddenWarning()).toBe(true);
});
});
});
});

View file

@ -1,41 +0,0 @@
import $ from 'jquery';
import UserInternalRegexHandler from '~/pages/admin/users/new/index';
describe('UserInternalRegexHandler', () => {
const FIXTURE = 'admin/users/new_with_internal_user_regex.html';
let $userExternal;
let $userEmail;
let $warningMessage;
beforeEach(() => {
loadFixtures(FIXTURE);
// eslint-disable-next-line no-new
new UserInternalRegexHandler();
$userExternal = $('#user_external');
$userEmail = $('#user_email');
$warningMessage = $('#warning_external_automatically_set');
if (!$userExternal.prop('checked')) $userExternal.prop('checked', 'checked');
});
describe('Behaviour of userExternal checkbox when', () => {
it('matches email as internal', (done) => {
expect($warningMessage.hasClass('hidden')).toBeTruthy();
$userEmail.val('test@').trigger('input');
expect($userExternal.prop('checked')).toBeFalsy();
expect($warningMessage.hasClass('hidden')).toBeFalsy();
done();
});
it('matches email as external', (done) => {
expect($warningMessage.hasClass('hidden')).toBeTruthy();
$userEmail.val('test.ext@').trigger('input');
expect($userExternal.prop('checked')).toBeTruthy();
expect($warningMessage.hasClass('hidden')).toBeTruthy();
done();
});
});
});

View file

@ -27,6 +27,11 @@ RSpec.describe Gitlab::Composer::VersionIndex do
'type' => 'zip',
'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}"
},
'source' => {
'reference' => branch.target,
'type' => 'git',
'url' => project.http_url_to_repo
},
'name' => package.name,
'uid' => package.id,
'version' => package.version

View file

@ -0,0 +1,213 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'active_model'
RSpec.describe ::Gitlab::Graphql::Deprecation do
let(:options) { {} }
subject(:deprecation) { described_class.parse(options) }
describe '.parse' do
context 'with nil' do
let(:options) { nil }
it 'parses to nil' do
expect(deprecation).to be_nil
end
end
context 'with empty options' do
let(:options) { {} }
it 'parses to an empty deprecation' do
expect(deprecation).to eq(described_class.new)
end
end
context 'with defined options' do
let(:options) { { reason: :renamed, milestone: '10.10' } }
it 'assigns the properties' do
expect(deprecation).to eq(described_class.new(reason: 'This was renamed', milestone: '10.10'))
end
end
end
describe 'validations' do
let(:options) { { reason: :renamed, milestone: '10.10' } }
it { is_expected.to be_valid }
context 'when the milestone is absent' do
before do
options.delete(:milestone)
end
it { is_expected.not_to be_valid }
end
context 'when the milestone is not milestone-ish' do
before do
options[:milestone] = 'next year'
end
it { is_expected.not_to be_valid }
end
context 'when the milestone is not a string' do
before do
options[:milestone] = 10.01
end
it { is_expected.not_to be_valid }
end
context 'when the reason is absent' do
before do
options.delete(:reason)
end
it { is_expected.not_to be_valid }
end
context 'when the reason is not a known reason' do
before do
options[:reason] = :not_stylish_enough
end
it { is_expected.not_to be_valid }
end
context 'when the reason is a string' do
before do
options[:reason] = 'not stylish enough'
end
it { is_expected.to be_valid }
end
context 'when the reason is a string ending with a period' do
before do
options[:reason] = 'not stylish enough.'
end
it { is_expected.not_to be_valid }
end
end
describe '#deprecation_reason' do
context 'when there is a replacement' do
let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } }
it 'renders as reason-replacement-milestone' do
expect(deprecation.deprecation_reason).to eq('This was renamed. Please use `X.y`. Deprecated in 10.10.')
end
end
context 'when there is no replacement' do
let(:options) { { reason: :renamed, milestone: '10.10' } }
it 'renders as reason-milestone' do
expect(deprecation.deprecation_reason).to eq('This was renamed. Deprecated in 10.10.')
end
end
describe 'processing of reason' do
described_class::REASONS.each_key do |known_reason|
context "when the reason is a known reason such as #{known_reason.inspect}" do
let(:options) { { reason: known_reason } }
it 'renders the reason_text correctly' do
expect(deprecation.deprecation_reason).to start_with(described_class::REASONS[known_reason])
end
end
end
context 'when the reason is any other string' do
let(:options) { { reason: 'unhelpful' } }
it 'appends a period' do
expect(deprecation.deprecation_reason).to start_with('unhelpful.')
end
end
end
end
describe '#edit_description' do
let(:options) { { reason: :renamed, milestone: '10.10' } }
it 'appends milestone:reason with a leading space if there is a description' do
desc = deprecation.edit_description('Some description.')
expect(desc).to eq('Some description. Deprecated in 10.10: This was renamed.')
end
it 'returns nil if there is no description' do
desc = deprecation.edit_description(nil)
expect(desc).to be_nil
end
end
describe '#original_description' do
it 'records the description passed to it' do
deprecation.edit_description('Some description.')
expect(deprecation.original_description).to eq('Some description.')
end
end
describe '#markdown' do
context 'when there is a replacement' do
let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } }
context 'when the context is :inline' do
it 'renders on one line' do
expectation = '**Deprecated** in 10.10. This was renamed. Use: `X.y`.'
expect(deprecation.markdown).to eq(expectation)
expect(deprecation.markdown(context: :inline)).to eq(expectation)
end
end
context 'when the context is :block' do
it 'renders a warning note' do
expectation = <<~MD.chomp
WARNING:
**Deprecated** in 10.10.
This was renamed.
Use: `X.y`.
MD
expect(deprecation.markdown(context: :block)).to eq(expectation)
end
end
end
context 'when there is no replacement' do
let(:options) { { reason: 'Removed', milestone: '10.10' } }
context 'when the context is :inline' do
it 'renders on one line' do
expectation = '**Deprecated** in 10.10. Removed.'
expect(deprecation.markdown).to eq(expectation)
expect(deprecation.markdown(context: :inline)).to eq(expectation)
end
end
context 'when the context is :block' do
it 'renders a warning note' do
expectation = <<~MD.chomp
WARNING:
**Deprecated** in 10.10.
Removed.
MD
expect(deprecation.markdown(context: :block)).to eq(expectation)
end
end
end
end
end

View file

@ -9,8 +9,8 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
let_it_be(:json) { { 'name' => package_name } }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) }
let_it_be(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
let_it_be(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
let!(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
let!(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
let(:branch) { project.repository.find_branch('master') }
@ -28,6 +28,11 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
'type' => 'zip',
'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}"
},
'source' => {
'reference' => branch.target,
'type' => 'git',
'url' => "http://localhost/#{group.path}/#{project.path}.git"
},
'name' => package.name,
'uid' => package.id,
'version' => package.version

View file

@ -13,18 +13,18 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
it 'raises an error if a required property is missing', :aggregate_failures do
expect { subject(deprecated: { milestone: '1.10' }) }.to raise_error(
ArgumentError,
'Please provide a `reason` within `deprecated`'
include("Reason can't be blank")
)
expect { subject(deprecated: { reason: 'Deprecation reason' }) }.to raise_error(
ArgumentError,
'Please provide a `milestone` within `deprecated`'
include("Milestone can't be blank")
)
end
it 'raises an error if milestone is not a String', :aggregate_failures do
expect { subject(deprecated: { milestone: 1.10, reason: 'Deprecation reason' }) }.to raise_error(
ArgumentError,
'`milestone` must be a `String`'
include("Milestone must be a string")
)
end
end
@ -49,4 +49,22 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
expect(deprecable.description).to be_nil
end
it 'adds information about the replacement if provided' do
deprecable = subject(deprecated: { milestone: '1.10', reason: :renamed, replacement: 'Foo.bar' })
expect(deprecable.deprecation_reason).to include 'Please use `Foo.bar`'
end
it 'supports named reasons: renamed' do
deprecable = subject(deprecated: { milestone: '1.10', reason: :renamed })
expect(deprecable.deprecation_reason).to include 'This was renamed.'
end
it 'supports named reasons: discouraged' do
deprecable = subject(deprecated: { milestone: '1.10', reason: :discouraged })
expect(deprecable.deprecation_reason).to include 'Use of this is not recommended.'
end
end