diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-10 00:09:33 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-10 00:09:33 +0000 |
commit | 5a120c32fda1c88e38bbce056d6f30f4a2f41bc6 (patch) | |
tree | 8f5ef3c06e5f2f8707da5d32803d73fe10824ea5 | |
parent | b5944525b015e4efb4cd2c1d09ec37566d7691a0 (diff) | |
download | gitlab-ce-5a120c32fda1c88e38bbce056d6f30f4a2f41bc6.tar.gz |
Add latest changes from gitlab-org/gitlab@master
44 files changed, 561 insertions, 142 deletions
diff --git a/app/assets/javascripts/pages/admin/users/components/user_modal_manager.vue b/app/assets/javascripts/pages/admin/users/components/user_modal_manager.vue index 24c9fa4cb3f..1dfea3f1e7b 100644 --- a/app/assets/javascripts/pages/admin/users/components/user_modal_manager.vue +++ b/app/assets/javascripts/pages/admin/users/components/user_modal_manager.vue @@ -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; diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js index 1fd838e704c..ae5db5f5bdc 100644 --- a/app/assets/javascripts/pages/admin/users/index.js +++ b/app/assets/javascripts/pages/admin/users/index.js @@ -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, }, diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 8a9a632003a..f6162f65e21 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -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; } } } diff --git a/app/finders/packages/nuget/package_finder.rb b/app/finders/packages/nuget/package_finder.rb index 8f585f045a1..2f66bd145ee 100644 --- a/app/finders/packages/nuget/package_finder.rb +++ b/app/finders/packages/nuget/package_finder.rb @@ -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 diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index 4200f68d8fd..7bc74e0db74 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -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)) } diff --git a/app/models/packages/rubygems/metadatum.rb b/app/models/packages/rubygems/metadatum.rb new file mode 100644 index 00000000000..42db1f3defc --- /dev/null +++ b/app/models/packages/rubygems/metadatum.rb @@ -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 diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index 31fd3aea94d..224a3cea28d 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -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) } } diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 380348f9a98..c7ec3ab66d7 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -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 } } diff --git a/changelogs/unreleased/241376-max-nuget-packages-returned.yml b/changelogs/unreleased/241376-max-nuget-packages-returned.yml new file mode 100644 index 00000000000..82ad553278c --- /dev/null +++ b/changelogs/unreleased/241376-max-nuget-packages-returned.yml @@ -0,0 +1,5 @@ +--- +title: Update max number of NuGet packages returned +merge_request: 52265 +author: +type: fixed diff --git a/changelogs/unreleased/299263-rubygems-metadata-migration.yml b/changelogs/unreleased/299263-rubygems-metadata-migration.yml new file mode 100644 index 00000000000..be5842a17d6 --- /dev/null +++ b/changelogs/unreleased/299263-rubygems-metadata-migration.yml @@ -0,0 +1,5 @@ +--- +title: Add rubygems metadata table +merge_request: 52639 +author: +type: added diff --git a/changelogs/unreleased/add-missing-known-event-ecs-deploy-template.yml b/changelogs/unreleased/add-missing-known-event-ecs-deploy-template.yml new file mode 100644 index 00000000000..cbb5b48162f --- /dev/null +++ b/changelogs/unreleased/add-missing-known-event-ecs-deploy-template.yml @@ -0,0 +1,5 @@ +--- +title: Fix missing known usage data event +merge_request: 53729 +author: +type: fixed diff --git a/changelogs/unreleased/gl-card-advanced-search.yml b/changelogs/unreleased/gl-card-advanced-search.yml new file mode 100644 index 00000000000..79dc47cd062 --- /dev/null +++ b/changelogs/unreleased/gl-card-advanced-search.yml @@ -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 diff --git a/changelogs/unreleased/peterhegman-fix-assignee-dropdown-checkmark.yml b/changelogs/unreleased/peterhegman-fix-assignee-dropdown-checkmark.yml new file mode 100644 index 00000000000..eb29e0828ae --- /dev/null +++ b/changelogs/unreleased/peterhegman-fix-assignee-dropdown-checkmark.yml @@ -0,0 +1,5 @@ +--- +title: Fix misalignment of assignee dropdown checkmark +merge_request: 53664 +author: +type: fixed diff --git a/db/migrate/20210126233608_add_rubygems_max_file_size_to_plan_limits.rb b/db/migrate/20210126233608_add_rubygems_max_file_size_to_plan_limits.rb new file mode 100644 index 00000000000..e0e7e773d17 --- /dev/null +++ b/db/migrate/20210126233608_add_rubygems_max_file_size_to_plan_limits.rb @@ -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 diff --git a/db/migrate/20210203221631_create_packages_rubygems_metadata.rb b/db/migrate/20210203221631_create_packages_rubygems_metadata.rb new file mode 100644 index 00000000000..f4ad5abf7e5 --- /dev/null +++ b/db/migrate/20210203221631_create_packages_rubygems_metadata.rb @@ -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 diff --git a/db/schema_migrations/20210126233608 b/db/schema_migrations/20210126233608 new file mode 100644 index 00000000000..adab3a3f365 --- /dev/null +++ b/db/schema_migrations/20210126233608 @@ -0,0 +1 @@ +4105ae45742c2eda67fe5c54256732e55555ab7832e4cbf0fcb041599c23bd29
\ No newline at end of file diff --git a/db/schema_migrations/20210203221631 b/db/schema_migrations/20210203221631 new file mode 100644 index 00000000000..ed9efcd2b5f --- /dev/null +++ b/db/schema_migrations/20210203221631 @@ -0,0 +1 @@ +ec6832ba26fca8d8427383cd0189765191a0a7f17bb78d61b900c5b541d5725e
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 8c49262f1e3..2f10967b907 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -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; diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md index 882928a3628..57af1166076 100644 --- a/doc/administration/instance_limits.md +++ b/doc/administration/instance_limits.md @@ -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. diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 1e15a20390d..e2f8485ce63 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -17829,6 +17829,11 @@ enum PackageTypeEnum { Packages from the PyPI package manager """ PYPI + + """ + Packages from the Rubygems package manager + """ + RUBYGEMS } """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 8435194b1bc..6ff2cab1512 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -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 diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index c75671bf797..963b5e9f091 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -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 diff --git a/doc/development/README.md b/doc/development/README.md index d73acdd9dbc..5db4c5438c4 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -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 diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index b878f0fc1ce..6b30a6dab5d 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -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') -``` - -The `help_page_path` contains the path to the document you want to link to with -the following conventions: - -- it is relative to the `doc/` directory in the GitLab repository -- the `.md` extension must be omitted -- it must not end with a slash (`/`) - -Below are some special cases where should be used depending on the context. -You can combine one or more of the following: - -1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path` - method: +- It's relative to the `doc/` directory in the GitLab repository. +- It omits the `.md` extension. +- It doesn't end with a slash (`/`). - ```haml - = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link') - ``` +The help text follows the [Pajamas guidelines](https://design.gitlab.com/usability/helping-users/#formatting-help-content). -1. **Opening links in a new tab.** This should be the default behavior: +Use the following special cases depending on the context, ensuring all links +are inside `_()` so they can be translated: - ```haml - = link_to 'Help page', help_page_path('user/permissions'), target: '_blank' - ``` +- Linking to a doc page. In its most basic form, the HAML code to generate a + link to the `/help` page is: -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_to sprite_icon('question-o'), help_page_path('user/permissions') - ``` + ```haml + = link_to _('Learn more.'), help_page_path('user/permissions'), target: '_blank', rel: 'noopener noreferrer' + ``` -1. **Using a button link.** Useful in places where text would be out of context - with the rest of the page layout: +- Linking to an anchor link. Use `anchor` as part of the `help_page_path` + method: - ```haml - = link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info' - ``` + ```haml + = link_to _('Learn more.'), help_page_path('user/permissions', anchor: 'anchor-link'), target: '_blank', rel: 'noopener noreferrer' + ``` -1. **Using links inline of some text.** +- 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: - ```haml - Description to #{link_to 'Help page', help_page_path('user/permissions')}. - ``` + ```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 } + ``` -1. **Adding a period at the end of the sentence.** Useful when you don't want - the period to be part of the link: +- Using a button link. Useful in places where text would be out of context with + the rest of the page layout: - ```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 diff --git a/doc/development/transient/prevention-patterns.md b/doc/development/transient/prevention-patterns.md new file mode 100644 index 00000000000..7231562415b --- /dev/null +++ b/doc/development/transient/prevention-patterns.md @@ -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. diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md index 2ffe3e7e9fb..1f3a6021588 100644 --- a/doc/operations/incident_management/integrations.md +++ b/doc/operations/incident_management/integrations.md @@ -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 diff --git a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_empty_v13_4.png b/doc/user/application_security/security_dashboard/img/security_center_dashboard_empty_v13_4.png Binary files differindex 5edceb32e5c..5edceb32e5c 100644 --- a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_empty_v13_4.png +++ b/doc/user/application_security/security_dashboard/img/security_center_dashboard_empty_v13_4.png diff --git a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_link_v12_4.png b/doc/user/application_security/security_dashboard/img/security_center_dashboard_link_v12_4.png Binary files differindex e0e80810b08..e0e80810b08 100644 --- a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_link_v12_4.png +++ b/doc/user/application_security/security_dashboard/img/security_center_dashboard_link_v12_4.png diff --git a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png b/doc/user/application_security/security_dashboard/img/security_center_dashboard_v13_4.png Binary files differindex 5379b5c6e5d..5379b5c6e5d 100644 --- a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png +++ b/doc/user/application_security/security_dashboard/img/security_center_dashboard_v13_4.png diff --git a/doc/user/application_security/security_dashboard/img/instance_security_center_settings_v13_4.png b/doc/user/application_security/security_dashboard/img/security_center_settings_v13_4.png Binary files differindex 4223494c294..4223494c294 100644 --- a/doc/user/application_security/security_dashboard/img/instance_security_center_settings_v13_4.png +++ b/doc/user/application_security/security_dashboard/img/security_center_settings_v13_4.png diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md index 08faf9bf885..b08c19bee47 100644 --- a/doc/user/application_security/security_dashboard/index.md +++ b/doc/user/application_security/security_dashboard/index.md @@ -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. diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml index 1828731a504..5cf61acee9e 100644 --- a/lib/gitlab/usage_data_counters/known_events/common.yml +++ b/lib/gitlab/usage_data_counters/known_events/common.yml @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a5b531b90c3..5e94b9865c5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb index 158c5974c6a..7cf37db340d 100644 --- a/spec/factories/packages.rb +++ b/spec/factories/packages.rb @@ -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}" } diff --git a/spec/factories/packages/package_file.rb b/spec/factories/packages/package_file.rb index c328c01ec95..6d8b119040e 100644 --- a/spec/factories/packages/package_file.rb +++ b/spec/factories/packages/package_file.rb @@ -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' } diff --git a/spec/factories/packages/rubygems/metadata.rb b/spec/factories/packages/rubygems/metadata.rb new file mode 100644 index 00000000000..9f03bf80dc3 --- /dev/null +++ b/spec/factories/packages/rubygems/metadata.rb @@ -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 diff --git a/spec/fixtures/packages/rubygems/package-0.0.1.gem b/spec/fixtures/packages/rubygems/package-0.0.1.gem Binary files differnew file mode 100644 index 00000000000..2143ef408ac --- /dev/null +++ b/spec/fixtures/packages/rubygems/package-0.0.1.gem diff --git a/spec/fixtures/packages/rubygems/package.gemspec b/spec/fixtures/packages/rubygems/package.gemspec new file mode 100644 index 00000000000..bb87c47f5dc --- /dev/null +++ b/spec/fixtures/packages/rubygems/package.gemspec @@ -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 diff --git a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js index 6df2efd624d..3669bc40d7e 100644 --- a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js +++ b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js @@ -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', () => { + describe('click handling', () => { + 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(() => { - jest.spyOn(document, 'addEventListener'); - jest.spyOn(document, 'removeEventListener'); + createButtons(); + createComponent(); }); - afterAll(() => { - jest.restoreAllMocks(); + afterEach(() => { + removeButtons(); }); - it('registers global listener on mount', () => { - createComponent(); - expect(document.addEventListener).toHaveBeenCalledWith('click', expect.any(Function)); - }); + it('renders the modal when the button is clicked', async () => { + button.click(); - it('removes global listener on destroy', () => { - createComponent(); - wrapper.destroy(); - expect(document.removeEventListener).toHaveBeenCalledWith('click', expect.any(Function)); + await wrapper.vm.$nextTick(); + + expect(findModal().exists()).toBe(true); }); - }); - describe('click handling', () => { - let node; + it('does not render the modal when a misconfigured button is clicked', async () => { + button.removeAttribute('data-gl-modal-action'); + button.click(); - beforeEach(() => { - node = document.createElement('div'); - document.body.appendChild(node); - }); + await wrapper.vm.$nextTick(); - afterEach(() => { - node.remove(); - node = null; + expect(findModal().exists()).toBe(false); }); - 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('does not render the modal when a button without the selector class is clicked', async () => { + button2.click(); - 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); + await wrapper.vm.$nextTick(); - expect(event.preventDefault).toHaveBeenCalled(); - return wrapper.vm.$nextTick().then(() => { - const modal = wrapper.find({ ref: 'modal' }); - expect(modal.exists()).toBeTruthy(); - expect(modal.vm.showWasCalled).toBeTruthy(); - }); + expect(findModal().exists()).toBe(false); }); }); }); diff --git a/spec/graphql/types/packages/package_type_enum_spec.rb b/spec/graphql/types/packages/package_type_enum_spec.rb index 407d5786f65..ccd91485e4b 100644 --- a/spec/graphql/types/packages/package_type_enum_spec.rb +++ b/spec/graphql/types/packages/package_type_enum_spec.rb @@ -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 diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb index b5ff417e610..cdf03b3c958 100644 --- a/spec/models/packages/package_spec.rb +++ b/spec/models/packages/package_spec.rb @@ -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 diff --git a/spec/models/packages/rubygems/metadatum_spec.rb b/spec/models/packages/rubygems/metadatum_spec.rb new file mode 100644 index 00000000000..e99a07c7731 --- /dev/null +++ b/spec/models/packages/rubygems/metadatum_spec.rb @@ -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 diff --git a/spec/support/shared_examples/controllers/unique_hll_events_examples.rb b/spec/support/shared_examples/controllers/unique_hll_events_examples.rb index c5d65743810..3f69923028c 100644 --- a/spec/support/shared_examples/controllers/unique_hll_events_examples.rb +++ b/spec/support/shared_examples/controllers/unique_hll_events_examples.rb @@ -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 diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index fa307d2a9a6..ab6cd2109cb 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -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 |