Add latest changes from gitlab-org/gitlab@master
|
|
@ -12,6 +12,10 @@ export default {
|
|||
required: true,
|
||||
type: String,
|
||||
},
|
||||
selector: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -34,22 +38,24 @@ export default {
|
|||
},
|
||||
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleClick);
|
||||
},
|
||||
/*
|
||||
* Here we're looking for every button that needs to launch a modal
|
||||
* on click, and then attaching a click event handler to show the modal
|
||||
* if it's correctly configured.
|
||||
*
|
||||
* TODO: Replace this with integrated modal components https://gitlab.com/gitlab-org/gitlab/-/issues/320922
|
||||
*/
|
||||
document.querySelectorAll(this.selector).forEach((button) => {
|
||||
button.addEventListener('click', (e) => {
|
||||
if (!button.dataset.glModalAction) return;
|
||||
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.handleClick);
|
||||
e.preventDefault();
|
||||
this.show(button.dataset);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick(e) {
|
||||
const { glModalAction: action } = e.target.dataset;
|
||||
if (!action) return;
|
||||
|
||||
this.show(e.target.dataset);
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
show(modalData) {
|
||||
const { glModalAction: requestedAction } = modalData;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { initAdminUsersApp, initCohortsEmptyState } from '~/admin/users';
|
|||
import initTabs from '~/admin/users/tabs';
|
||||
import ModalManager from './components/user_modal_manager.vue';
|
||||
|
||||
const CONFIRM_DELETE_BUTTON_SELECTOR = '.js-delete-user-modal-button';
|
||||
const MODAL_TEXTS_CONTAINER_SELECTOR = '#js-modal-texts';
|
||||
const MODAL_MANAGER_SELECTOR = '#js-delete-user-modal';
|
||||
|
||||
|
|
@ -50,6 +51,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
return h(ModalManager, {
|
||||
ref: 'manager',
|
||||
props: {
|
||||
selector: CONFIRM_DELETE_BUTTON_SELECTOR,
|
||||
modalConfiguration,
|
||||
csrfToken: csrf.token,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -525,7 +525,7 @@
|
|||
|
||||
&.is-active {
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: url(asset_path('checkmark.png')) no-repeat 14px 8px;
|
||||
background: url(asset_path('checkmark.png')) no-repeat 14px center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module Packages
|
|||
class PackageFinder
|
||||
include ::Packages::FinderHelper
|
||||
|
||||
MAX_PACKAGES_COUNT = 50
|
||||
MAX_PACKAGES_COUNT = 300
|
||||
|
||||
def initialize(current_user, project_or_group, package_name:, package_version: nil, limit: MAX_PACKAGES_COUNT)
|
||||
@current_user = current_user
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class Packages::Package < ApplicationRecord
|
|||
has_one :maven_metadatum, inverse_of: :package, class_name: 'Packages::Maven::Metadatum'
|
||||
has_one :nuget_metadatum, inverse_of: :package, class_name: 'Packages::Nuget::Metadatum'
|
||||
has_one :composer_metadatum, inverse_of: :package, class_name: 'Packages::Composer::Metadatum'
|
||||
has_one :rubygems_metadatum, inverse_of: :package, class_name: 'Packages::Rubygems::Metadatum'
|
||||
has_many :build_infos, inverse_of: :package
|
||||
has_many :pipelines, through: :build_infos
|
||||
has_one :debian_publication, inverse_of: :package, class_name: 'Packages::Debian::Publication'
|
||||
|
|
@ -64,7 +65,9 @@ class Packages::Package < ApplicationRecord
|
|||
if: :debian_package?
|
||||
validate :forbidden_debian_changes, if: :debian?
|
||||
|
||||
enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5, composer: 6, generic: 7, golang: 8, debian: 9 }
|
||||
enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5,
|
||||
composer: 6, generic: 7, golang: 8, debian: 9,
|
||||
rubygems: 10 }
|
||||
|
||||
scope :with_name, ->(name) { where(name: name) }
|
||||
scope :with_name_like, ->(name) { where(arel_table[:name].matches(name)) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Packages
|
||||
module Rubygems
|
||||
class Metadatum < ApplicationRecord
|
||||
self.table_name = 'packages_rubygems_metadata'
|
||||
self.primary_key = :package_id
|
||||
|
||||
belongs_to :package, -> { where(package_type: :rubygems) }, inverse_of: :rubygems_metadatum
|
||||
|
||||
validates :package, presence: true
|
||||
|
||||
validate :rubygems_package_type
|
||||
|
||||
private
|
||||
|
||||
def rubygems_package_type
|
||||
unless package&.rubygems?
|
||||
errors.add(:base, _('Package type must be RubyGems'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -59,13 +59,13 @@
|
|||
%li.divider
|
||||
- if user.can_be_removed?
|
||||
%li
|
||||
%button.delete-user-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete',
|
||||
%button.js-delete-user-modal-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete',
|
||||
delete_user_url: admin_user_path(user),
|
||||
block_user_url: block_admin_user_path(user),
|
||||
username: sanitize_name(user.name) } }
|
||||
= s_('AdminUsers|Delete user')
|
||||
%li
|
||||
%button.delete-user-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
|
||||
%button.js-delete-user-modal-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
|
||||
delete_user_url: admin_user_path(user, hard_delete: true),
|
||||
block_user_url: block_admin_user_path(user),
|
||||
username: sanitize_name(user.name) } }
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@
|
|||
%p Deleting a user has the following effects:
|
||||
= render 'users/deletion_guidance', user: @user
|
||||
%br
|
||||
%button.delete-user-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete',
|
||||
%button.js-delete-user-modal-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete',
|
||||
delete_user_url: admin_user_path(@user),
|
||||
block_user_url: block_admin_user_path(@user),
|
||||
username: sanitize_name(@user.name) } }
|
||||
|
|
@ -235,7 +235,7 @@
|
|||
the user, and projects in them, will also be removed. Commits
|
||||
to other projects are unaffected.
|
||||
%br
|
||||
%button.delete-user-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
|
||||
%button.js-delete-user-modal-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
|
||||
delete_user_url: admin_user_path(@user, hard_delete: true),
|
||||
block_user_url: block_admin_user_path(@user),
|
||||
username: @user.name } }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update max number of NuGet packages returned
|
||||
merge_request: 52265
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add rubygems metadata table
|
||||
merge_request: 52639
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix missing known usage data event
|
||||
merge_request: 53729
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move the sub-section to gl-card in advanced search settings in admin
|
||||
merge_request: 52585
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix misalignment of assignee dropdown checkmark
|
||||
merge_request: 53664
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddRubygemsMaxFileSizeToPlanLimits < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :plan_limits, :rubygems_max_file_size, :bigint, default: 3.gigabytes, null: false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreatePackagesRubygemsMetadata < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
create_table_with_constraints :packages_rubygems_metadata, id: false do |t|
|
||||
t.timestamps_with_timezone
|
||||
t.references :package, primary_key: true, index: false, default: nil, null: false, foreign_key: { to_table: :packages_packages, on_delete: :cascade }, type: :bigint
|
||||
t.text :authors
|
||||
t.text :files
|
||||
t.text :summary
|
||||
|
||||
t.text :description
|
||||
t.text :email
|
||||
t.text :homepage
|
||||
t.text :licenses
|
||||
t.text :metadata
|
||||
|
||||
t.text :author
|
||||
t.text :bindir
|
||||
t.text :cert_chain
|
||||
t.text :executables
|
||||
t.text :extensions
|
||||
t.text :extra_rdoc_files
|
||||
t.text :platform
|
||||
t.text :post_install_message
|
||||
t.text :rdoc_options
|
||||
t.text :require_paths
|
||||
t.text :required_ruby_version
|
||||
t.text :required_rubygems_version
|
||||
t.text :requirements
|
||||
t.text :rubygems_version
|
||||
t.text :signing_key
|
||||
|
||||
t.text_limit :authors, 255
|
||||
t.text_limit :files, 255
|
||||
t.text_limit :summary, 1024
|
||||
|
||||
t.text_limit :description, 1024
|
||||
t.text_limit :email, 255
|
||||
t.text_limit :homepage, 255
|
||||
t.text_limit :licenses, 255
|
||||
t.text_limit :metadata, 255
|
||||
|
||||
t.text_limit :author, 255
|
||||
t.text_limit :bindir, 255
|
||||
t.text_limit :cert_chain, 255
|
||||
t.text_limit :executables, 255
|
||||
t.text_limit :extensions, 255
|
||||
t.text_limit :extra_rdoc_files, 255
|
||||
t.text_limit :platform, 255
|
||||
t.text_limit :post_install_message, 255
|
||||
t.text_limit :rdoc_options, 255
|
||||
t.text_limit :require_paths, 255
|
||||
t.text_limit :required_ruby_version, 255
|
||||
t.text_limit :required_rubygems_version, 255
|
||||
t.text_limit :requirements, 255
|
||||
t.text_limit :rubygems_version, 255
|
||||
t.text_limit :signing_key, 255
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :packages_rubygems_metadata
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
4105ae45742c2eda67fe5c54256732e55555ab7832e4cbf0fcb041599c23bd29
|
||||
|
|
@ -0,0 +1 @@
|
|||
ec6832ba26fca8d8427383cd0189765191a0a7f17bb78d61b900c5b541d5725e
|
||||
|
|
@ -15256,6 +15256,58 @@ CREATE TABLE packages_pypi_metadata (
|
|||
CONSTRAINT check_379019d5da CHECK ((char_length(required_python) <= 255))
|
||||
);
|
||||
|
||||
CREATE TABLE packages_rubygems_metadata (
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
package_id bigint NOT NULL,
|
||||
authors text,
|
||||
files text,
|
||||
summary text,
|
||||
description text,
|
||||
email text,
|
||||
homepage text,
|
||||
licenses text,
|
||||
metadata text,
|
||||
author text,
|
||||
bindir text,
|
||||
cert_chain text,
|
||||
executables text,
|
||||
extensions text,
|
||||
extra_rdoc_files text,
|
||||
platform text,
|
||||
post_install_message text,
|
||||
rdoc_options text,
|
||||
require_paths text,
|
||||
required_ruby_version text,
|
||||
required_rubygems_version text,
|
||||
requirements text,
|
||||
rubygems_version text,
|
||||
signing_key text,
|
||||
CONSTRAINT check_0154a18c82 CHECK ((char_length(description) <= 1024)),
|
||||
CONSTRAINT check_22814c771b CHECK ((char_length(email) <= 255)),
|
||||
CONSTRAINT check_242293030e CHECK ((char_length(extensions) <= 255)),
|
||||
CONSTRAINT check_27619a7922 CHECK ((char_length(rubygems_version) <= 255)),
|
||||
CONSTRAINT check_3d1b6f3a39 CHECK ((char_length(post_install_message) <= 255)),
|
||||
CONSTRAINT check_545f7606f9 CHECK ((char_length(required_rubygems_version) <= 255)),
|
||||
CONSTRAINT check_5988451714 CHECK ((char_length(executables) <= 255)),
|
||||
CONSTRAINT check_5f9c84ea17 CHECK ((char_length(platform) <= 255)),
|
||||
CONSTRAINT check_64f1cecf05 CHECK ((char_length(requirements) <= 255)),
|
||||
CONSTRAINT check_6ac7043c50 CHECK ((char_length(extra_rdoc_files) <= 255)),
|
||||
CONSTRAINT check_6ff3abe325 CHECK ((char_length(cert_chain) <= 255)),
|
||||
CONSTRAINT check_7cb01436df CHECK ((char_length(licenses) <= 255)),
|
||||
CONSTRAINT check_8be21d92e7 CHECK ((char_length(summary) <= 1024)),
|
||||
CONSTRAINT check_946cb96acb CHECK ((char_length(homepage) <= 255)),
|
||||
CONSTRAINT check_9824fc9efc CHECK ((char_length(bindir) <= 255)),
|
||||
CONSTRAINT check_994b68eb64 CHECK ((char_length(authors) <= 255)),
|
||||
CONSTRAINT check_9d42fa48ae CHECK ((char_length(signing_key) <= 255)),
|
||||
CONSTRAINT check_b0f4f8c853 CHECK ((char_length(files) <= 255)),
|
||||
CONSTRAINT check_b7b296b420 CHECK ((char_length(author) <= 255)),
|
||||
CONSTRAINT check_bf16b21a47 CHECK ((char_length(rdoc_options) <= 255)),
|
||||
CONSTRAINT check_ca641a3354 CHECK ((char_length(required_ruby_version) <= 255)),
|
||||
CONSTRAINT check_ea02f4800f CHECK ((char_length(metadata) <= 255)),
|
||||
CONSTRAINT check_f76bad1a9a CHECK ((char_length(require_paths) <= 255))
|
||||
);
|
||||
|
||||
CREATE TABLE packages_tags (
|
||||
id bigint NOT NULL,
|
||||
package_id integer NOT NULL,
|
||||
|
|
@ -15467,7 +15519,8 @@ CREATE TABLE plan_limits (
|
|||
project_feature_flags integer DEFAULT 200 NOT NULL,
|
||||
ci_max_artifact_size_api_fuzzing integer DEFAULT 0 NOT NULL,
|
||||
ci_pipeline_deployments integer DEFAULT 500 NOT NULL,
|
||||
pull_mirror_interval_seconds integer DEFAULT 300 NOT NULL
|
||||
pull_mirror_interval_seconds integer DEFAULT 300 NOT NULL,
|
||||
rubygems_max_file_size bigint DEFAULT '3221225472'::bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE plan_limits_id_seq
|
||||
|
|
@ -20511,6 +20564,9 @@ ALTER TABLE ONLY packages_packages
|
|||
ALTER TABLE ONLY packages_pypi_metadata
|
||||
ADD CONSTRAINT packages_pypi_metadata_pkey PRIMARY KEY (package_id);
|
||||
|
||||
ALTER TABLE ONLY packages_rubygems_metadata
|
||||
ADD CONSTRAINT packages_rubygems_metadata_pkey PRIMARY KEY (package_id);
|
||||
|
||||
ALTER TABLE ONLY packages_tags
|
||||
ADD CONSTRAINT packages_tags_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -25523,6 +25579,9 @@ ALTER TABLE ONLY scim_identities
|
|||
ALTER TABLE ONLY packages_debian_project_distributions
|
||||
ADD CONSTRAINT fk_rails_94b95e1f84 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY packages_rubygems_metadata
|
||||
ADD CONSTRAINT fk_rails_95a3f5ce78 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY packages_pypi_metadata
|
||||
ADD CONSTRAINT fk_rails_9698717cdd FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -612,3 +612,7 @@ Plan.default.actual_limits.update!(generic_packages_max_file_size: 100.megabytes
|
|||
```
|
||||
|
||||
Set the limit to `0` to allow any file size.
|
||||
|
||||
### Package versions returned
|
||||
|
||||
When asking for versions of a given NuGet package name, the GitLab Package Registry returns a maximum of 300 versions.
|
||||
|
|
|
|||
|
|
@ -17829,6 +17829,11 @@ enum PackageTypeEnum {
|
|||
Packages from the PyPI package manager
|
||||
"""
|
||||
PYPI
|
||||
|
||||
"""
|
||||
Packages from the Rubygems package manager
|
||||
"""
|
||||
RUBYGEMS
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -52437,6 +52437,12 @@
|
|||
"description": "Packages from the Debian package manager",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "RUBYGEMS",
|
||||
"description": "Packages from the Rubygems package manager",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
|
|
|
|||
|
|
@ -5138,6 +5138,7 @@ Rotation length unit of an on-call rotation.
|
|||
| `NPM` | Packages from the NPM package manager |
|
||||
| `NUGET` | Packages from the Nuget package manager |
|
||||
| `PYPI` | Packages from the PyPI package manager |
|
||||
| `RUBYGEMS` | Packages from the Rubygems package manager |
|
||||
|
||||
### PipelineConfigSourceEnum
|
||||
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ See [database guidelines](database/index.md).
|
|||
- [Compatibility with multiple versions of the application running at the same time](multi_version_compatibility.md)
|
||||
- [Features inside `.gitlab/`](features_inside_dot_gitlab.md)
|
||||
- [Dashboards for stage groups](stage_group_dashboards.md)
|
||||
- [Preventing transient bugs](transient/prevention-patterns.md)
|
||||
|
||||
## Other GitLab Development Kit (GDK) guides
|
||||
|
||||
|
|
|
|||
|
|
@ -328,68 +328,51 @@ with GitLab 11.4. Meaning, it's available only with `/help` from GitLab
|
|||
|
||||
### Linking to `/help`
|
||||
|
||||
When you're building a new feature, you may need to link the documentation
|
||||
from GitLab, the application. This is normally done in files inside the
|
||||
`app/views/` directory with the help of the `help_page_path` helper method.
|
||||
When you're building a new feature, you may need to link to the documentation
|
||||
from the GitLab application. This is normally done in files inside the
|
||||
`app/views/` directory, with the help of the `help_page_path` helper method.
|
||||
|
||||
In its simplest form, the HAML code to generate a link to the `/help` page is:
|
||||
The `help_page_path` contains the path to the document you want to link to,
|
||||
with the following conventions:
|
||||
|
||||
```haml
|
||||
= link_to 'Help page', help_page_path('user/permissions')
|
||||
```
|
||||
- It's relative to the `doc/` directory in the GitLab repository.
|
||||
- It omits the `.md` extension.
|
||||
- It doesn't end with a slash (`/`).
|
||||
|
||||
The `help_page_path` contains the path to the document you want to link to with
|
||||
the following conventions:
|
||||
The help text follows the [Pajamas guidelines](https://design.gitlab.com/usability/helping-users/#formatting-help-content).
|
||||
|
||||
- it is relative to the `doc/` directory in the GitLab repository
|
||||
- the `.md` extension must be omitted
|
||||
- it must not end with a slash (`/`)
|
||||
Use the following special cases depending on the context, ensuring all links
|
||||
are inside `_()` so they can be translated:
|
||||
|
||||
Below are some special cases where should be used depending on the context.
|
||||
You can combine one or more of the following:
|
||||
- Linking to a doc page. In its most basic form, the HAML code to generate a
|
||||
link to the `/help` page is:
|
||||
|
||||
1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path`
|
||||
method:
|
||||
```haml
|
||||
= link_to _('Learn more.'), help_page_path('user/permissions'), target: '_blank', rel: 'noopener noreferrer'
|
||||
```
|
||||
|
||||
```haml
|
||||
= link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
|
||||
```
|
||||
- Linking to an anchor link. Use `anchor` as part of the `help_page_path`
|
||||
method:
|
||||
|
||||
1. **Opening links in a new tab.** This should be the default behavior:
|
||||
```haml
|
||||
= link_to _('Learn more.'), help_page_path('user/permissions', anchor: 'anchor-link'), target: '_blank', rel: 'noopener noreferrer'
|
||||
```
|
||||
|
||||
```haml
|
||||
= link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
|
||||
```
|
||||
- Using links inline of some text. First, define the link, and then use it. In
|
||||
this example, `link_start` is the name of the variable that contains the
|
||||
link:
|
||||
|
||||
1. **Using a question icon.** Usually used in settings where a long
|
||||
description cannot be used, like near checkboxes. You can basically use
|
||||
any GitLab SVG icon, but prefer the `question-o`:
|
||||
```haml
|
||||
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/permissions') }
|
||||
%p= _("This is a text describing the option/feature in a sentence. %{link_start}Learn more.%{link_end}").html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||
```
|
||||
|
||||
```haml
|
||||
= link_to sprite_icon('question-o'), help_page_path('user/permissions')
|
||||
```
|
||||
- Using a button link. Useful in places where text would be out of context with
|
||||
the rest of the page layout:
|
||||
|
||||
1. **Using a button link.** Useful in places where text would be out of context
|
||||
with the rest of the page layout:
|
||||
|
||||
```haml
|
||||
= link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info'
|
||||
```
|
||||
|
||||
1. **Using links inline of some text.**
|
||||
|
||||
```haml
|
||||
Description to #{link_to 'Help page', help_page_path('user/permissions')}.
|
||||
```
|
||||
|
||||
1. **Adding a period at the end of the sentence.** Useful when you don't want
|
||||
the period to be part of the link:
|
||||
|
||||
```haml
|
||||
= succeed '.' do
|
||||
Learn more in the
|
||||
= link_to 'Help page', help_page_path('user/permissions')
|
||||
```
|
||||
```haml
|
||||
= link_to _('Learn more.'), help_page_path('user/permissions'), class: 'btn btn-info', target: '_blank', rel: 'noopener noreferrer'
|
||||
```
|
||||
|
||||
#### Linking to `/help` in JavaScript
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Preventing Transient Bugs
|
||||
|
||||
This page will cover architectural patterns and tips for developers to follow to prevent [transient bugs.](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#transient-bugs)
|
||||
|
||||
## Frontend
|
||||
|
||||
### Don't rely on response order
|
||||
|
||||
When working with multiple requests, it's easy to assume the order of the responses will match the order in which they are triggered.
|
||||
|
||||
That's not always the case and can cause bugs that only happen if the order is switched.
|
||||
|
||||
**Example:**
|
||||
|
||||
- `diffs_metadata.json` (lighter)
|
||||
- `diffs_batch.json` (heavier)
|
||||
|
||||
If your feature requires data from both, ensure that the two have finished loading before working on it.
|
||||
|
||||
### Simulate slower connections when testing manually
|
||||
|
||||
Add a network condition template to your browser's dev tools to enable you to toggle between a slow and a fast connection.
|
||||
|
||||
**Example:**
|
||||
|
||||
- Turtle:
|
||||
- Down: 50kb/s
|
||||
- Up: 20kb/s
|
||||
- Latency: 10000ms
|
||||
|
||||
### Collapsed elements
|
||||
|
||||
When setting event listeners, if not possible to use event delegation, ensure all relevant event listeners are set for expanded content.
|
||||
|
||||
Including when that expanded content is:
|
||||
|
||||
- **Invisible** (`display: none;`). Some JavaScript requires the element to be visible to work properly (eg.: when taking measurements).
|
||||
- **Dynamic content** (AJAX/DOM manipulation).
|
||||
|
||||
### Using assertions to detect transient bugs caused by unmet conditions
|
||||
|
||||
Transient bugs happen in the context of code that executes under the assumption
|
||||
that the application’s state meets one or more conditions. We may write a feature
|
||||
that assumes a server-side API response always include a group of attributes or that
|
||||
an operation only executes when the application has successfully transitioned to a new
|
||||
state.
|
||||
|
||||
Transient bugs are difficult to debug because there isn’t any mechanism that alerts
|
||||
the user or the developer about unsatisfied conditions. These conditions are usually
|
||||
not expressed explicitly in the code. A useful debugging technique for such situations
|
||||
is placing assertions to make any assumption explicit. They can help detect the cause
|
||||
which unmet condition causes the bug.
|
||||
|
||||
#### Asserting pre-conditions on state mutations
|
||||
|
||||
A common scenario that leads to transient bugs is when there is a polling service
|
||||
that should mutate state only if a user operation is completed. We can use
|
||||
assertions to make this pre-condition explicit:
|
||||
|
||||
```javascript
|
||||
// This action is called by a polling service. It assumes that all pre-conditions
|
||||
// are satisfied by the time the action is dispatched.
|
||||
export const updateMergeableStatus = ({ commit }, payload) => {
|
||||
commit(types.SET_MERGEABLE_STATUS, payload);
|
||||
};
|
||||
|
||||
// We can make any pre-condition explicit by adding an assertion
|
||||
export const updateMergeableStatus = ({ state, commit }, payload) => {
|
||||
console.assert(
|
||||
state.isResolvingDiscussion === true,
|
||||
'Resolve discussion request must be completed before updating mergeable status'
|
||||
);
|
||||
commit(types.SET_MERGEABLE_STATUS, payload);
|
||||
};
|
||||
```
|
||||
|
||||
#### Asserting API contracts
|
||||
|
||||
Another useful way of using assertions is to detect if the response payload returned
|
||||
by the server-side endpoint satisfies the API contract.
|
||||
|
||||
#### Related reading
|
||||
|
||||
[Debug it!](https://pragprog.com/titles/pbdp/debug-it/) explores techniques to diagnose
|
||||
and fix non-determinstic bugs and write software that is easier to debug.
|
||||
|
||||
## Backend
|
||||
|
||||
### Sidekiq jobs with locks
|
||||
|
||||
When dealing with asynchronous work via Sidekiq, it is possible to have 2 jobs with the same arguments
|
||||
getting worked on at the same time. If not handled correctly, this can result in an outdated or inaccurate state.
|
||||
|
||||
For instance, consider a worker that updates a state of an object. Before the worker updates the state
|
||||
(for example, `#update_state`) of the object, it needs to check what the appropriate state should be
|
||||
(for example, `#check_state`).
|
||||
|
||||
When there are 2 jobs being worked on at the same time, it is possible that the order of operations will go like:
|
||||
|
||||
1. (Worker A) Calls `#check_state`
|
||||
1. (Worker B) Calls `#check_state`
|
||||
1. (Worker B) Calls `#update_state`
|
||||
1. (Worker A) Calls `#update_state`
|
||||
|
||||
In this example, `Worker B` is meant to set the updated status. But `Worker A` calls `#update_state` a little too late.
|
||||
|
||||
This can be avoided by utilizing either database locks or `Gitlab::ExclusiveLease`. This way, jobs will be
|
||||
worked on one at a time. This also allows them to be marked as [idempotent](../sidekiq_style_guide.md#idempotent-jobs).
|
||||
|
||||
### Retry mechanism handling
|
||||
|
||||
There are times that an object/record will be on a failed state which can be rechecked.
|
||||
|
||||
If an object is in a state that can be rechecked, ensure that appropriate messaging is shown to the user
|
||||
so they know what to do. Also, make sure that the retry functionality will be able to reset the state
|
||||
correctly when triggered.
|
||||
|
||||
### Error Logging
|
||||
|
||||
Error logging doesn't necessarily directly prevents transient bugs but it can help to debug them.
|
||||
|
||||
When coding, sometimes we expect some exceptions to be raised and we rescue them.
|
||||
|
||||
Logging whenever we rescue an error helps in case it's causing transient bugs that a user may see.
|
||||
While investigating a bug report, it may require the engineer to look into logs of when it happened.
|
||||
Seeing an error being logged can be a signal of something that went wrong which can be handled differently.
|
||||
|
|
@ -6,8 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Alert integrations **(FREE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.8.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13203) in GitLab Ultimate 12.4.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42640) to GitLab Free in 12.8.
|
||||
|
||||
GitLab can accept alerts from any source via a webhook receiver. This can be configured
|
||||
generically or, in GitLab versions 13.1 and greater, you can configure
|
||||
|
|
@ -16,7 +16,7 @@ to use this endpoint.
|
|||
|
||||
## Integrations list
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/245331) in [GitLab Core](https://about.gitlab.com/pricing/) 13.5.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/245331) in GitLab Free 13.5.
|
||||
|
||||
With Maintainer or higher [permissions](../../user/permissions.md), you can view
|
||||
the list of configured alerts integrations by navigating to
|
||||
|
|
@ -45,7 +45,7 @@ receive alert payloads in JSON format. You can always
|
|||
|
||||
### HTTP Endpoints **PREMIUM**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4442) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.6.
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4442) in GitLab Premium 13.6.
|
||||
|
||||
In [GitLab Premium](https://about.gitlab.com/pricing/), you can create multiple
|
||||
unique HTTP endpoints to receive alerts from any external source in JSON format,
|
||||
|
|
@ -140,7 +140,7 @@ Example payload:
|
|||
|
||||
## Triggering test alerts
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Core in 13.2.
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Free in 13.2.
|
||||
|
||||
After a [project maintainer or owner](../../user/permissions.md)
|
||||
configures an integration, you can trigger a test
|
||||
|
|
@ -156,7 +156,7 @@ GitLab displays an error or success message, depending on the outcome of your te
|
|||
|
||||
## Automatic grouping of identical alerts **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214557) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214557) in GitLab Premium 13.2.
|
||||
|
||||
In GitLab versions 13.2 and greater, GitLab groups alerts based on their
|
||||
payload. When an incoming alert contains the same payload as another alert
|
||||
|
|
@ -170,7 +170,7 @@ If the existing alert is already `resolved`, GitLab creates a new alert instead.
|
|||
|
||||
## Link to your Opsgenie Alerts
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab Premium 13.2.
|
||||
|
||||
WARNING:
|
||||
We are building deeper integration with Opsgenie and other alerting tools through
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
|
@ -9,11 +9,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
GitLab provides a comprehensive set of features for viewing and managing vulnerabilities:
|
||||
|
||||
- Security dashboards: An overview of the security status in your instance, [groups](#group-security-dashboard), and
|
||||
- Security dashboards: An overview of the security status in your personal [Security Center](#security-center), [groups](#group-security-dashboard), and
|
||||
[projects](#project-security-dashboard).
|
||||
- [Vulnerability reports](../vulnerability_report/index.md): Detailed lists of all vulnerabilities for the instance, group, project, or
|
||||
- [Vulnerability reports](../vulnerability_report/index.md): Detailed lists of all vulnerabilities for the Security Center, group, project, or
|
||||
pipeline. This is where you triage and manage vulnerabilities.
|
||||
- [Security Center](#instance-security-center): A dedicated area for vulnerability management at the instance level. This
|
||||
- [Security Center](#security-center): A dedicated area for personalized vulnerability management. This
|
||||
includes a security dashboard, vulnerability report, and settings.
|
||||
|
||||
You can also drill down into a vulnerability and get extra information on the
|
||||
|
|
@ -111,28 +111,28 @@ vulnerabilities are excluded.
|
|||
|
||||
Navigate to the group's [vulnerability report](../vulnerability_report/index.md) to view the vulnerabilities found.
|
||||
|
||||
## Instance Security Center
|
||||
## Security Center
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3426) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.4.
|
||||
|
||||
The Security Center is where you manage vulnerabilities for your instance. It displays the
|
||||
vulnerabilities present in the default branches of all the projects you configure. It includes the
|
||||
following:
|
||||
The Security Center is personal space where you manage vulnerabilities across all your projects. It
|
||||
displays the vulnerabilities present in the default branches of all the projects you configure. It includes
|
||||
the following:
|
||||
|
||||
- The [group security dashboard's](#group-security-dashboard) features.
|
||||
- A [vulnerability report](../vulnerability_report/index.md).
|
||||
- A dedicated settings area to configure which projects to display.
|
||||
|
||||

|
||||

|
||||
|
||||
You can access the Instance Security Center from the menu
|
||||
You can access the Security Center from the menu
|
||||
bar at the top of the page. Under **More**, select **Security**.
|
||||
|
||||

|
||||

|
||||
|
||||
The dashboard and vulnerability report are empty before you add projects.
|
||||
|
||||

|
||||

|
||||
|
||||
### Adding projects to the Security Center
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ To add projects to the Security Center:
|
|||
1. Search for and add one or more projects using the **Search your projects** field.
|
||||
1. Click the **Add projects** button.
|
||||
|
||||

|
||||

|
||||
|
||||
After you add projects, the security dashboard and vulnerability report display the vulnerabilities
|
||||
found in those projects' default branches.
|
||||
|
|
|
|||
|
|
@ -603,6 +603,11 @@
|
|||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_aws_deploy_ecs
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_auto_devops_build
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
|
|
|
|||
|
|
@ -20862,6 +20862,9 @@ msgstr ""
|
|||
msgid "Package type must be PyPi"
|
||||
msgstr ""
|
||||
|
||||
msgid "Package type must be RubyGems"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|%{boldStart}Allow duplicates%{boldEnd} - Packages with the same name and version are accepted."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,23 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
factory :rubygems_package do
|
||||
sequence(:name) { |n| "my_gem_#{n}" }
|
||||
sequence(:version) { |n| "1.#{n}" }
|
||||
package_type { :rubygems }
|
||||
|
||||
after :create do |package|
|
||||
create :package_file, :gem, package: package
|
||||
create :package_file, :gemspec, package: package
|
||||
end
|
||||
|
||||
trait(:with_metadatum) do
|
||||
after :build do |pkg|
|
||||
pkg.rubygems_metadatum = build(:rubygems_metadatum)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
factory :debian_package do
|
||||
sequence(:name) { |n| "package-#{n}" }
|
||||
sequence(:version) { |n| "1.0-#{n}" }
|
||||
|
|
|
|||
|
|
@ -221,6 +221,22 @@ FactoryBot.define do
|
|||
size { 300.kilobytes }
|
||||
end
|
||||
|
||||
trait(:gem) do
|
||||
package
|
||||
file_fixture { 'spec/fixtures/packages/rubygems/package-0.0.1.gem' }
|
||||
file_name { 'package-0.0.1.gem' }
|
||||
file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' }
|
||||
size { 4.kilobytes }
|
||||
end
|
||||
|
||||
trait(:gemspec) do
|
||||
package
|
||||
file_fixture { 'spec/fixtures/packages/rubygems/package.gemspec' }
|
||||
file_name { 'package.gemspec' }
|
||||
file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' }
|
||||
size { 242.bytes }
|
||||
end
|
||||
|
||||
trait(:pypi) do
|
||||
package
|
||||
file_fixture { 'spec/fixtures/packages/pypi/sample-project.tar.gz' }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :rubygems_metadatum, class: 'Packages::Rubygems::Metadatum' do
|
||||
package { association(:rubygems_package) }
|
||||
authors { FFaker::Name.name }
|
||||
email { FFaker::Internet.email }
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{package}
|
||||
s.authors = ["Tanuki Steve"]
|
||||
s.version = "0.0.1"
|
||||
s.date = %q{2011-09-29}
|
||||
s.summary = %q{package is the best}
|
||||
s.files = [
|
||||
"lib/package.rb"
|
||||
]
|
||||
s.required_ruby_version = '>= 2.7.0'
|
||||
s.rubygems_version = '>= 1.8.11'
|
||||
s.require_paths = ["lib"]
|
||||
end
|
||||
|
|
@ -3,6 +3,8 @@ import UserModalManager from '~/pages/admin/users/components/user_modal_manager.
|
|||
import ModalStub from './stubs/modal_stub';
|
||||
|
||||
describe('Users admin page Modal Manager', () => {
|
||||
let wrapper;
|
||||
|
||||
const modalConfiguration = {
|
||||
action1: {
|
||||
title: 'action1',
|
||||
|
|
@ -14,11 +16,12 @@ describe('Users admin page Modal Manager', () => {
|
|||
},
|
||||
};
|
||||
|
||||
let wrapper;
|
||||
const findModal = () => wrapper.find({ ref: 'modal' });
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = mount(UserModalManager, {
|
||||
propsData: {
|
||||
selector: '.js-delete-user-modal-button',
|
||||
modalConfiguration,
|
||||
csrfToken: 'dummyCSRF',
|
||||
...props,
|
||||
|
|
@ -37,7 +40,7 @@ describe('Users admin page Modal Manager', () => {
|
|||
describe('render behavior', () => {
|
||||
it('does not renders modal when initialized', () => {
|
||||
createComponent();
|
||||
expect(wrapper.find({ ref: 'modal' }).exists()).toBeFalsy();
|
||||
expect(findModal().exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('throws if action has no proper configuration', () => {
|
||||
|
|
@ -55,7 +58,7 @@ describe('Users admin page Modal Manager', () => {
|
|||
});
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
const modal = wrapper.find({ ref: 'modal' });
|
||||
const modal = findModal();
|
||||
expect(modal.exists()).toBeTruthy();
|
||||
expect(modal.vm.$attrs.csrfToken).toEqual('dummyCSRF');
|
||||
expect(modal.vm.$attrs.extraProp).toEqual('extraPropValue');
|
||||
|
|
@ -64,68 +67,60 @@ describe('Users admin page Modal Manager', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('global listener', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(document, 'addEventListener');
|
||||
jest.spyOn(document, 'removeEventListener');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('registers global listener on mount', () => {
|
||||
createComponent();
|
||||
expect(document.addEventListener).toHaveBeenCalledWith('click', expect.any(Function));
|
||||
});
|
||||
|
||||
it('removes global listener on destroy', () => {
|
||||
createComponent();
|
||||
wrapper.destroy();
|
||||
expect(document.removeEventListener).toHaveBeenCalledWith('click', expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe('click handling', () => {
|
||||
let node;
|
||||
let button;
|
||||
let button2;
|
||||
|
||||
const createButtons = () => {
|
||||
button = document.createElement('button');
|
||||
button2 = document.createElement('button');
|
||||
button.setAttribute('class', 'js-delete-user-modal-button');
|
||||
button.setAttribute('data-username', 'foo');
|
||||
button.setAttribute('data-gl-modal-action', 'action1');
|
||||
button.setAttribute('data-block-user-url', '/block');
|
||||
button.setAttribute('data-delete-user-url', '/delete');
|
||||
document.body.appendChild(button);
|
||||
document.body.appendChild(button2);
|
||||
};
|
||||
const removeButtons = () => {
|
||||
button.remove();
|
||||
button = null;
|
||||
button2.remove();
|
||||
button2 = null;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
node = document.createElement('div');
|
||||
document.body.appendChild(node);
|
||||
createButtons();
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
node.remove();
|
||||
node = null;
|
||||
removeButtons();
|
||||
});
|
||||
|
||||
it('ignores wrong clicks', () => {
|
||||
createComponent();
|
||||
const event = new window.MouseEvent('click', {
|
||||
bubbles: true,
|
||||
cancellable: true,
|
||||
});
|
||||
jest.spyOn(event, 'preventDefault');
|
||||
node.dispatchEvent(event);
|
||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||
it('renders the modal when the button is clicked', async () => {
|
||||
button.click();
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(findModal().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('captures click with glModalAction', () => {
|
||||
createComponent();
|
||||
node.dataset.glModalAction = 'action1';
|
||||
const event = new window.MouseEvent('click', {
|
||||
bubbles: true,
|
||||
cancellable: true,
|
||||
});
|
||||
jest.spyOn(event, 'preventDefault');
|
||||
node.dispatchEvent(event);
|
||||
it('does not render the modal when a misconfigured button is clicked', async () => {
|
||||
button.removeAttribute('data-gl-modal-action');
|
||||
button.click();
|
||||
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
const modal = wrapper.find({ ref: 'modal' });
|
||||
expect(modal.exists()).toBeTruthy();
|
||||
expect(modal.vm.showWasCalled).toBeTruthy();
|
||||
});
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(findModal().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render the modal when a button without the selector class is clicked', async () => {
|
||||
button2.click();
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(findModal().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
|
||||
it 'exposes all package types' do
|
||||
expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN])
|
||||
expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ RSpec.describe Packages::Package, type: :model do
|
|||
it { is_expected.to have_one(:debian_publication).inverse_of(:package).class_name('Packages::Debian::Publication') }
|
||||
it { is_expected.to have_one(:debian_distribution).through(:debian_publication).source(:distribution).inverse_of(:packages).class_name('Packages::Debian::ProjectDistribution') }
|
||||
it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) }
|
||||
it { is_expected.to have_one(:rubygems_metadatum).inverse_of(:package) }
|
||||
end
|
||||
|
||||
describe '.with_composer_target' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Rubygems::Metadatum, type: :model do
|
||||
describe 'relationships' do
|
||||
it { is_expected.to belong_to(:package) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:package) }
|
||||
|
||||
describe '#rubygems_package_type' do
|
||||
it 'will not allow a package with a different package_type' do
|
||||
package = build('conan_package')
|
||||
rubygems_metadatum = build('rubygems_metadatum', package: package)
|
||||
|
||||
expect(rubygems_metadatum).not_to be_valid
|
||||
expect(rubygems_metadatum.errors.to_a).to include('Package type must be RubyGems')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
RSpec.shared_examples 'tracking unique hll events' do |feature_flag|
|
||||
it 'tracks unique event' do
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(target_id, values: expected_type)
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter).to(
|
||||
receive(:track_event)
|
||||
.with(target_id, values: expected_type)
|
||||
.and_call_original # we call original to trigger additional validations; otherwise the method is stubbed
|
||||
)
|
||||
|
||||
request
|
||||
end
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false|
|
|||
let_it_be(:package7) { create(:generic_package, project: project) }
|
||||
let_it_be(:package8) { create(:golang_package, project: project) }
|
||||
let_it_be(:package9) { create(:debian_package, project: project) }
|
||||
let_it_be(:package9) { create(:rubygems_package, project: project) }
|
||||
|
||||
Packages::Package.package_types.keys.each do |package_type|
|
||||
context "for package type #{package_type}" do
|
||||
|
|
|
|||