diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-17 12:09:26 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-17 12:09:26 +0000 |
commit | 75a4eaade04ee758bb3b253f27bf1c20c67991f0 (patch) | |
tree | 779b3011793bf35770774dbaa51eec55efd320cd | |
parent | 839e879bcf197a283da8481ddcb15b177172784d (diff) | |
download | gitlab-ce-75a4eaade04ee758bb3b253f27bf1c20c67991f0.tar.gz |
Add latest changes from gitlab-org/gitlab@master
50 files changed, 343 insertions, 363 deletions
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index bb311148627..4323499ef1f 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -522,7 +522,7 @@ export default { :file="file" :reviewed="fileReviews[index]" :is-first-file="index === 0" - :is-last-file="index === diffs.length - 1" + :is-last-file="index === diffFilesLength - 1" :help-page-path="helpPagePath" :can-current-user-fork="canCurrentUserFork" :view-diffs-file-by-file="viewDiffsFileByFile" diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue index 1ae7cf9339d..62e93335a20 100644 --- a/app/assets/javascripts/ide/components/branches/search_list.vue +++ b/app/assets/javascripts/ide/components/branches/search_list.vue @@ -57,7 +57,10 @@ export default { <template> <div> - <label class="dropdown-input pt-3 pb-3 mb-0 border-bottom block position-relative" @click.stop> + <label + class="dropdown-input gl-pt-3 gl-pb-5 gl-mb-0 gl-border-b-1 gl-border-b-solid gl-display-block" + @click.stop + > <input ref="searchInput" v-model="search" diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue index 680e8841a1f..7cb6d4d3dac 100644 --- a/app/assets/javascripts/ide/components/merge_requests/list.vue +++ b/app/assets/javascripts/ide/components/merge_requests/list.vue @@ -75,7 +75,10 @@ export default { <template> <div> - <label class="dropdown-input pt-3 pb-3 mb-0 border-bottom block" @click.stop> + <label + class="dropdown-input gl-pt-3 gl-pb-5 gl-mb-0 gl-border-b-1 gl-border-b-solid gl-display-block" + @click.stop + > <tokened-input v-model="search" :tokens="searchTokens" diff --git a/app/assets/javascripts/ide/components/nav_form.vue b/app/assets/javascripts/ide/components/nav_form.vue index 62bb4841760..98f0504298b 100644 --- a/app/assets/javascripts/ide/components/nav_form.vue +++ b/app/assets/javascripts/ide/components/nav_form.vue @@ -1,13 +1,12 @@ <script> -import Tab from '~/vue_shared/components/tabs/tab.vue'; -import Tabs from '~/vue_shared/components/tabs/tabs'; +import { GlTab, GlTabs } from '@gitlab/ui'; import BranchesSearchList from './branches/search_list.vue'; import MergeRequestSearchList from './merge_requests/list.vue'; export default { components: { - Tabs, - Tab, + GlTab, + GlTabs, BranchesSearchList, MergeRequestSearchList, }, @@ -23,20 +22,14 @@ export default { <template> <div class="ide-nav-form p-0"> - <tabs v-if="showMergeRequests" stop-propagation> - <tab active> - <template #title> - {{ __('Branches') }} - </template> + <gl-tabs v-if="showMergeRequests"> + <gl-tab :title="__('Branches')"> <branches-search-list /> - </tab> - <tab> - <template #title> - {{ __('Merge Requests') }} - </template> + </gl-tab> + <gl-tab :title="__('Merge Requests')"> <merge-request-search-list /> - </tab> - </tabs> + </gl-tab> + </gl-tabs> <branches-search-list v-else /> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/tabs/tab.vue b/app/assets/javascripts/vue_shared/components/tabs/tab.vue deleted file mode 100644 index d24c27cfcc3..00000000000 --- a/app/assets/javascripts/vue_shared/components/tabs/tab.vue +++ /dev/null @@ -1,47 +0,0 @@ -<script> -export default { - props: { - title: { - type: String, - required: false, - default: '', - }, - active: { - type: Boolean, - required: false, - default: false, - }, - }, - data() { - return { - // props can't be updated, so we map it to data where we can - localActive: this.active, - }; - }, - watch: { - active() { - this.localActive = this.active; - }, - }, - created() { - this.isTab = true; - }, - updated() { - if (this.$parent) { - this.$parent.$forceUpdate(); - } - }, -}; -</script> - -<template> - <div - :class="{ - active: localActive, - }" - class="tab-pane" - role="tabpanel" - > - <slot></slot> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/tabs/tabs.js b/app/assets/javascripts/vue_shared/components/tabs/tabs.js deleted file mode 100644 index 233df96a520..00000000000 --- a/app/assets/javascripts/vue_shared/components/tabs/tabs.js +++ /dev/null @@ -1,76 +0,0 @@ -export default { - props: { - stopPropagation: { - type: Boolean, - required: false, - default: false, - }, - }, - data() { - return { - currentIndex: 0, - tabs: [], - }; - }, - mounted() { - this.updateTabs(); - }, - methods: { - updateTabs() { - this.tabs = this.$children.filter((child) => child.isTab); - this.currentIndex = this.tabs.findIndex((tab) => tab.localActive); - }, - setTab(e, index) { - if (this.stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - - this.tabs[this.currentIndex].localActive = false; - this.tabs[index].localActive = true; - - this.currentIndex = index; - }, - }, - render(h) { - const navItems = this.tabs.map((tab, i) => - h( - 'li', - { - key: i, - }, - [ - h( - 'a', - { - class: tab.localActive ? 'active' : null, - attrs: { - href: '#', - }, - on: { - click: (e) => this.setTab(e, i), - }, - }, - tab.$slots.title || tab.title, - ), - ], - ), - ); - const nav = h( - 'ul', - { - class: 'nav-links tab-links', - }, - [navItems], - ); - const content = h( - 'div', - { - class: ['tab-content'], - }, - [this.$slots.default], - ); - - return h('div', {}, [[nav], content]); - }, -}; diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index 7c4d51ab677..009019a45d9 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -1043,8 +1043,7 @@ $ide-commit-header-height: 48px; .input-icon { right: auto; left: 10px; - top: 50%; - transform: translateY(-50%); + top: 1rem; } } diff --git a/app/finders/concerns/packages/finder_helper.rb b/app/finders/concerns/packages/finder_helper.rb index 524e7aa7ff9..30bc0ff7909 100644 --- a/app/finders/concerns/packages/finder_helper.rb +++ b/app/finders/concerns/packages/finder_helper.rb @@ -4,6 +4,9 @@ module Packages module FinderHelper extend ActiveSupport::Concern + InvalidPackageTypeError = Class.new(StandardError) + InvalidStatusError = Class.new(StandardError) + private def packages_visible_to_user(user, within_group:) @@ -25,5 +28,35 @@ module Packages ::Project.in_namespace(namespace_ids) .public_or_visible_to_user(user, ::Gitlab::Access::REPORTER) end + + def package_type + params[:package_type].presence + end + + def filter_by_package_type(packages) + return packages unless package_type + raise InvalidPackageTypeError unless ::Packages::Package.package_types.key?(package_type) + + packages.with_package_type(package_type) + end + + def filter_by_package_name(packages) + return packages unless params[:package_name].present? + + packages.search_by_name(params[:package_name]) + end + + def filter_with_version(packages) + return packages if params[:include_versionless].present? + + packages.has_version + end + + def filter_by_status(packages) + return packages.displayable unless params[:status].present? + raise InvalidStatusError unless Package.statuses.key?(params[:status]) + + packages.with_status(params[:status]) + end end end diff --git a/app/finders/packages/group_packages_finder.rb b/app/finders/packages/group_packages_finder.rb index 860c4068b31..db5161d6e16 100644 --- a/app/finders/packages/group_packages_finder.rb +++ b/app/finders/packages/group_packages_finder.rb @@ -2,9 +2,7 @@ module Packages class GroupPackagesFinder - attr_reader :current_user, :group, :params - - InvalidPackageTypeError = Class.new(StandardError) + include ::Packages::FinderHelper def initialize(current_user, group, params = { exclude_subgroups: false, order_by: 'created_at', sort: 'asc' }) @current_user = current_user @@ -20,6 +18,8 @@ module Packages private + attr_reader :current_user, :group, :params + def packages_for_group_projects packages = ::Packages::Package .including_build_info @@ -32,6 +32,7 @@ module Packages packages = filter_with_version(packages) packages = filter_by_package_type(packages) packages = filter_by_package_name(packages) + packages = filter_by_status(packages) packages end @@ -46,10 +47,6 @@ module Packages .with_feature_available_for_user(:repository, current_user) end - def package_type - params[:package_type].presence - end - def groups return [group] if exclude_subgroups? @@ -59,24 +56,5 @@ module Packages def exclude_subgroups? params[:exclude_subgroups] end - - def filter_by_package_type(packages) - return packages unless package_type - raise InvalidPackageTypeError unless Package.package_types.key?(package_type) - - packages.with_package_type(package_type) - end - - def filter_by_package_name(packages) - return packages unless params[:package_name].present? - - packages.search_by_name(params[:package_name]) - end - - def filter_with_version(packages) - return packages if params[:include_versionless].present? - - packages.has_version - end end end diff --git a/app/finders/packages/packages_finder.rb b/app/finders/packages/packages_finder.rb index 72a63224d2f..bd9e62e3f2a 100644 --- a/app/finders/packages/packages_finder.rb +++ b/app/finders/packages/packages_finder.rb @@ -2,7 +2,7 @@ module Packages class PackagesFinder - attr_reader :params, :project + include ::Packages::FinderHelper def initialize(project, params = {}) @project = project @@ -21,29 +21,14 @@ module Packages packages = filter_with_version(packages) packages = filter_by_package_type(packages) packages = filter_by_package_name(packages) + packages = filter_by_status(packages) packages = order_packages(packages) packages end private - def filter_with_version(packages) - return packages if params[:include_versionless].present? - - packages.has_version - end - - def filter_by_package_type(packages) - return packages unless params[:package_type] - - packages.with_package_type(params[:package_type]) - end - - def filter_by_package_name(packages) - return packages unless params[:package_name] - - packages.search_by_name(params[:package_name]) - end + attr_reader :params, :project def order_packages(packages) packages.sort_by_attribute("#{params[:order_by]}_#{params[:sort]}") diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index 7bc74e0db74..391540634be 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -69,6 +69,8 @@ class Packages::Package < ApplicationRecord composer: 6, generic: 7, golang: 8, debian: 9, rubygems: 10 } + enum status: { default: 0, hidden: 1, processing: 2 } + scope :with_name, ->(name) { where(name: name) } scope :with_name_like, ->(name) { where(arel_table[:name].matches(name)) } scope :with_normalized_pypi_name, ->(name) { where("LOWER(regexp_replace(name, '[-_.]+', '-', 'g')) = ?", name.downcase) } @@ -76,6 +78,8 @@ class Packages::Package < ApplicationRecord scope :with_version, ->(version) { where(version: version) } scope :without_version_like, -> (version) { where.not(arel_table[:version].matches(version)) } scope :with_package_type, ->(package_type) { where(package_type: package_type) } + scope :with_status, ->(status) { where(status: status) } + scope :displayable, -> { with_status(:default) } scope :including_build_info, -> { includes(pipelines: :user) } scope :including_project_route, -> { includes(project: { namespace: :route }) } scope :including_tags, -> { includes(:tags) } diff --git a/app/services/packages/create_package_service.rb b/app/services/packages/create_package_service.rb index fcf252cf971..3dc06497d9f 100644 --- a/app/services/packages/create_package_service.rb +++ b/app/services/packages/create_package_service.rb @@ -9,7 +9,9 @@ module Packages .packages .with_package_type(package_type) .safe_find_or_create_by!(name: name, version: version) do |package| + package.status = params[:status] if params[:status] package.creator = package_creator + add_build_info(package) end end @@ -29,8 +31,9 @@ module Packages { creator: package_creator, name: params[:name], - version: params[:version] - }.merge(attrs) + version: params[:version], + status: params[:status] + }.compact.merge(attrs) end def package_creator diff --git a/app/services/packages/generic/create_package_file_service.rb b/app/services/packages/generic/create_package_file_service.rb index b14b1c193ec..1451a022a39 100644 --- a/app/services/packages/generic/create_package_file_service.rb +++ b/app/services/packages/generic/create_package_file_service.rb @@ -15,13 +15,16 @@ module Packages package_params = { name: params[:package_name], version: params[:package_version], - build: params[:build] + build: params[:build], + status: params[:status] } package = ::Packages::Generic::FindOrCreatePackageService .new(project, current_user, package_params) .execute + package.update_column(:status, params[:status]) if params[:status] && params[:status] != package.status + package.build_infos.safe_find_or_create_by!(pipeline: params[:build].pipeline) if params[:build].present? package end diff --git a/app/services/packages/maven/find_or_create_package_service.rb b/app/services/packages/maven/find_or_create_package_service.rb index 6e0346058e8..4c916d264a7 100644 --- a/app/services/packages/maven/find_or_create_package_service.rb +++ b/app/services/packages/maven/find_or_create_package_service.rb @@ -42,6 +42,7 @@ module Packages package_params = { name: package_name, path: params[:path], + status: params[:status], version: version } diff --git a/changelogs/unreleased/290741-packages-hidden-status.yml b/changelogs/unreleased/290741-packages-hidden-status.yml new file mode 100644 index 00000000000..03c5fd0f4eb --- /dev/null +++ b/changelogs/unreleased/290741-packages-hidden-status.yml @@ -0,0 +1,5 @@ +--- +title: Add status attribute to packages and ability to set 'hidden' for generic packages +merge_request: 53385 +author: +type: added diff --git a/changelogs/unreleased/cngo-convert-tab-to-gl-tab.yml b/changelogs/unreleased/cngo-convert-tab-to-gl-tab.yml new file mode 100644 index 00000000000..f4a843ea6dc --- /dev/null +++ b/changelogs/unreleased/cngo-convert-tab-to-gl-tab.yml @@ -0,0 +1,5 @@ +--- +title: Convert IDE nav form tab to GlTab +merge_request: 54274 +author: +type: changed diff --git a/db/migrate/20210204152257_add_status_to_packages_packages.rb b/db/migrate/20210204152257_add_status_to_packages_packages.rb new file mode 100644 index 00000000000..4fd441048c6 --- /dev/null +++ b/db/migrate/20210204152257_add_status_to_packages_packages.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddStatusToPackagesPackages < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :packages_packages, :status, :smallint, default: 0, null: false + end +end diff --git a/db/migrate/20210209171525_add_status_index_to_packages_packages.rb b/db/migrate/20210209171525_add_status_index_to_packages_packages.rb new file mode 100644 index 00000000000..cb956165d6e --- /dev/null +++ b/db/migrate/20210209171525_add_status_index_to_packages_packages.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddStatusIndexToPackagesPackages < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + INDEX_NAME = 'index_packages_packages_on_project_id_and_status' + + def up + add_concurrent_index :packages_packages, [:project_id, :status], name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :packages_packages, name: INDEX_NAME + end +end diff --git a/db/schema_migrations/20210204152257 b/db/schema_migrations/20210204152257 new file mode 100644 index 00000000000..c98d4637d05 --- /dev/null +++ b/db/schema_migrations/20210204152257 @@ -0,0 +1 @@ +cb9f4b4e627cbc163653fc01c0542ef0ce87139b376c55bbaa4d7ab23e9b8973
\ No newline at end of file diff --git a/db/schema_migrations/20210209171525 b/db/schema_migrations/20210209171525 new file mode 100644 index 00000000000..0c421b99a39 --- /dev/null +++ b/db/schema_migrations/20210209171525 @@ -0,0 +1 @@ +5c661c453922181b350b8551d9a8f9b097e568459a2c2d128e41d9aefb026ab5
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index d582c343e73..97a2850fd5c 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -15244,7 +15244,8 @@ CREATE TABLE packages_packages ( name character varying NOT NULL, version character varying, package_type smallint NOT NULL, - creator_id integer + creator_id integer, + status smallint DEFAULT 0 NOT NULL ); CREATE SEQUENCE packages_packages_id_seq @@ -22886,6 +22887,8 @@ CREATE INDEX index_packages_packages_on_project_id_and_created_at ON packages_pa CREATE INDEX index_packages_packages_on_project_id_and_package_type ON packages_packages USING btree (project_id, package_type); +CREATE INDEX index_packages_packages_on_project_id_and_status ON packages_packages USING btree (project_id, status); + CREATE INDEX index_packages_packages_on_project_id_and_version ON packages_packages USING btree (project_id, version); CREATE INDEX index_packages_project_id_name_partial_for_nuget ON packages_packages USING btree (project_id, name) WHERE (((name)::text <> 'NuGet.Temporary.Package'::text) AND (version IS NOT NULL) AND (package_type = 4)); diff --git a/doc/api/packages.md b/doc/api/packages.md index 516f171e851..112c7ef2e61 100644 --- a/doc/api/packages.md +++ b/doc/api/packages.md @@ -29,6 +29,7 @@ GET /projects/:id/packages | `package_type` | string | no | Filter the returned packages by type. One of `conan`, `maven`, `npm`, `pypi`, `composer`, `nuget`, or `golang`. (_Introduced in GitLab 12.9_) | `package_name` | string | no | Filter the project packages with a fuzzy search by name. (_Introduced in GitLab 12.9_) | `include_versionless` | boolean | no | When set to true, versionless packages are included in the response. (_Introduced in GitLab 13.8_) +| `status` | string | no | Filter the returned packages by status. One of `default` (default), `hidden`, or `processing`. (_Introduced in GitLab 13.9_) ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/packages" @@ -70,6 +71,9 @@ Example response: By default, the `GET` request returns 20 results, because the API is [paginated](README.md#pagination). +Although you can filter packages by status, working with packages that have a `processing` status +can result in malformed data or broken packages. + ### Within a group > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18871) in GitLab 12.5. @@ -90,6 +94,7 @@ GET /groups/:id/packages | `package_type` | string | no | Filter the returned packages by type. One of `conan`, `maven`, `npm`, `pypi`, `composer`, `nuget`, or `golang`. (_Introduced in GitLab 12.9_) | | `package_name` | string | no | Filter the project packages with a fuzzy search by name. (_[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30980) in GitLab 13.0_) | `include_versionless` | boolean | no | When set to true, versionless packages are included in the response. (_Introduced in GitLab 13.8_) +| `status` | string | no | Filter the returned packages by status. One of `default` (default), `hidden`, or `processing`. (_Introduced in GitLab 13.9_) ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/packages?exclude_subgroups=false" @@ -166,6 +171,9 @@ The `_links` object contains the following properties: - `web_path`: The path which you can visit in GitLab and see the details of the package. - `delete_api_path`: The API path to delete the package. Only available if the request user has permission to do so. +Although you can filter packages by status, working with packages that have a `processing` status +can result in malformed data or broken packages. + ## Get a project package > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9667) in GitLab 11.9. diff --git a/doc/user/group/roadmap/index.md b/doc/user/group/roadmap/index.md index e2c01987e36..9b3ae75b39c 100644 --- a/doc/user/group/roadmap/index.md +++ b/doc/user/group/roadmap/index.md @@ -41,7 +41,7 @@ toggle the list of the milestone bars. > - Filtering roadmaps by milestone is enabled on GitLab.com. > - Filtering roadmaps by milestone is recommended for production use. > - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-filtering-roadmaps-by-milestone). **(PREMIUM SELF)** -> - Filtering by epic confidentiality [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218624) in GitLab 13.8. +> - Filtering by epic confidentiality [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218624) in GitLab 13.9. WARNING: Filtering roadmaps by milestone might not be available to you. Check the **version history** note above for details. diff --git a/doc/user/packages/generic_packages/index.md b/doc/user/packages/generic_packages/index.md index 11af10bf8a0..73b84c04b6d 100644 --- a/doc/user/packages/generic_packages/index.md +++ b/doc/user/packages/generic_packages/index.md @@ -49,6 +49,7 @@ PUT /projects/:id/packages/generic/:package_name/:package_version/:file_name | `package_name` | string | yes | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). | `package_version` | string | yes | The package version. It can contain only numbers (`0-9`), and dots (`.`). Must be in the format of `X.Y.Z`, i.e. should match `/\A\d+\.\d+\.\d+\z/` regular expression. | `file_name` | string | yes | The filename. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), or underscores (`_`). +| `status` | string | no | The package status. It can be `default` (default) or `hidden`. Hidden packages do not appear in the UI or [package API list endpoints](../../../api/packages.md). Provide the file context in the request body. diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index 167531fdaec..24d726f4a41 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -7,6 +7,8 @@ module API file_name: API::NO_SLASH_URL_PART_REGEX }.freeze + ALLOWED_STATUSES = %w[default hidden].freeze + feature_category :package_registry before do @@ -35,6 +37,7 @@ module API requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true + optional :status, type: String, values: ALLOWED_STATUSES, desc: 'Package status' end put 'authorize' do @@ -49,6 +52,7 @@ module API requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true + optional :status, type: String, values: ALLOWED_STATUSES, desc: 'Package status' requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' end diff --git a/lib/api/group_packages.rb b/lib/api/group_packages.rb index d482f4d0585..ab4e91ff925 100644 --- a/lib/api/group_packages.rb +++ b/lib/api/group_packages.rb @@ -33,12 +33,14 @@ module API desc: 'Return packages with this name' optional :include_versionless, type: Boolean, desc: 'Returns packages without a version' + optional :status, type: String, values: Packages::Package.statuses.keys, + desc: 'Return packages with specified status' end get ':id/packages' do packages = Packages::GroupPackagesFinder.new( current_user, user_group, - declared(params).slice(:exclude_subgroups, :order_by, :sort, :package_type, :package_name, :include_versionless) + declared(params).slice(:exclude_subgroups, :order_by, :sort, :package_type, :package_name, :include_versionless, :status) ).execute present paginate(packages), with: ::API::Entities::Package, user: current_user, group: true diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb index 32636662987..0fdaa4b2656 100644 --- a/lib/api/project_packages.rb +++ b/lib/api/project_packages.rb @@ -32,11 +32,13 @@ module API desc: 'Return packages with this name' optional :include_versionless, type: Boolean, desc: 'Returns packages without a version' + optional :status, type: String, values: Packages::Package.statuses.keys, + desc: 'Return packages with specified status' end get ':id/packages' do packages = ::Packages::PackagesFinder.new( user_project, - declared_params.slice(:order_by, :sort, :package_type, :package_name, :include_versionless) + declared_params.slice(:order_by, :sort, :package_type, :package_name, :include_versionless, :status) ).execute present paginate(packages), with: ::API::Entities::Package, user: current_user diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb index 5403017d710..10822f943b6 100644 --- a/lib/gitlab/url_blocker.rb +++ b/lib/gitlab/url_blocker.rb @@ -7,9 +7,6 @@ module Gitlab class UrlBlocker BlockedUrlError = Class.new(StandardError) - GETADDRINFO_TIMEOUT_SECONDS = 15 - private_constant :GETADDRINFO_TIMEOUT_SECONDS - class << self # Validates the given url according to the constraints specified by arguments. # @@ -113,7 +110,7 @@ module Gitlab end def get_address_info(uri, dns_rebind_protection) - Addrinfo.getaddrinfo(uri.hostname, get_port(uri), nil, :STREAM, timeout: GETADDRINFO_TIMEOUT_SECONDS).map do |addr| + Addrinfo.getaddrinfo(uri.hostname, get_port(uri), nil, :STREAM).map do |addr| addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr end rescue SocketError diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e2a824b3299..1bcd85ca9e2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18536,6 +18536,9 @@ msgstr "" msgid "Merge request (MR) approvals" msgstr "" +msgid "Merge request approval settings have been updated." +msgstr "" + msgid "Merge request approvals" msgstr "" diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb index 7cf37db340d..2c64abefb01 100644 --- a/spec/factories/packages.rb +++ b/spec/factories/packages.rb @@ -6,6 +6,15 @@ FactoryBot.define do name { 'my/company/app/my-app' } sequence(:version) { |n| "1.#{n}-SNAPSHOT" } package_type { :maven } + status { :default } + + trait :hidden do + status { :hidden } + end + + trait :processing do + status { :processing } + end factory :maven_package do maven_metadatum diff --git a/spec/finders/packages/group_packages_finder_spec.rb b/spec/finders/packages/group_packages_finder_spec.rb index 8dd53b9c3f9..445482a5a96 100644 --- a/spec/finders/packages/group_packages_finder_spec.rb +++ b/spec/finders/packages/group_packages_finder_spec.rb @@ -147,6 +147,7 @@ RSpec.describe Packages::GroupPackagesFinder do end it_behaves_like 'concerning versionless param' + it_behaves_like 'concerning package statuses' end context 'group has package of all types' do diff --git a/spec/finders/packages/packages_finder_spec.rb b/spec/finders/packages/packages_finder_spec.rb index 77a171db144..6e92616bafa 100644 --- a/spec/finders/packages/packages_finder_spec.rb +++ b/spec/finders/packages/packages_finder_spec.rb @@ -82,5 +82,6 @@ RSpec.describe ::Packages::PackagesFinder do end it_behaves_like 'concerning versionless param' + it_behaves_like 'concerning package statuses' end end diff --git a/spec/frontend/vue_shared/components/tabs/tab_spec.js b/spec/frontend/vue_shared/components/tabs/tab_spec.js deleted file mode 100644 index ee0c983c764..00000000000 --- a/spec/frontend/vue_shared/components/tabs/tab_spec.js +++ /dev/null @@ -1,32 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; -import Tab from '~/vue_shared/components/tabs/tab.vue'; - -describe('Tab component', () => { - const Component = Vue.extend(Tab); - let vm; - - beforeEach(() => { - vm = mountComponent(Component); - }); - - it('sets localActive to equal active', (done) => { - vm.active = true; - - vm.$nextTick(() => { - expect(vm.localActive).toBe(true); - - done(); - }); - }); - - it('sets active class', (done) => { - vm.active = true; - - vm.$nextTick(() => { - expect(vm.$el.classList).toContain('active'); - - done(); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/tabs/tabs_spec.js b/spec/frontend/vue_shared/components/tabs/tabs_spec.js deleted file mode 100644 index fe7be5be899..00000000000 --- a/spec/frontend/vue_shared/components/tabs/tabs_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import Vue from 'vue'; -import Tab from '~/vue_shared/components/tabs/tab.vue'; -import Tabs from '~/vue_shared/components/tabs/tabs'; - -describe('Tabs component', () => { - let vm; - - beforeEach(() => { - vm = new Vue({ - components: { - Tabs, - Tab, - }, - render(h) { - return h('div', [ - h('tabs', [ - h('tab', { attrs: { title: 'Testing', active: true } }, 'First tab'), - h('tab', [h('template', { slot: 'title' }, 'Test slot'), 'Second tab']), - ]), - ]); - }, - }).$mount(); - - return vm.$nextTick(); - }); - - describe('tab links', () => { - it('renders links for tabs', () => { - expect(vm.$el.querySelectorAll('a').length).toBe(2); - }); - - it('renders link titles from props', () => { - expect(vm.$el.querySelector('a').textContent).toContain('Testing'); - }); - - it('renders link titles from slot', () => { - expect(vm.$el.querySelectorAll('a')[1].textContent).toContain('Test slot'); - }); - - it('renders active class', () => { - expect(vm.$el.querySelector('a').classList).toContain('active'); - }); - - it('updates active class on click', () => { - vm.$el.querySelectorAll('a')[1].click(); - - return vm.$nextTick(() => { - expect(vm.$el.querySelector('a').classList).not.toContain('active'); - expect(vm.$el.querySelectorAll('a')[1].classList).toContain('active'); - }); - }); - }); - - describe('content', () => { - it('renders content panes', () => { - expect(vm.$el.querySelectorAll('.tab-pane').length).toBe(2); - expect(vm.$el.querySelectorAll('.tab-pane')[0].textContent).toContain('First tab'); - expect(vm.$el.querySelectorAll('.tab-pane')[1].textContent).toContain('Second tab'); - }); - }); -}); diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index 4f274387195..fa01d4e48df 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -160,34 +160,6 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do end end end - - context 'when resolving runs into a timeout' do - let(:import_url) { 'http://example.com' } - - subject { described_class.validate!(import_url, dns_rebind_protection: dns_rebind_protection) } - - before do - stub_env('RSPEC_ALLOW_INVALID_URLS', 'false') - allow(Addrinfo).to receive(:getaddrinfo).and_raise(SocketError) - end - - context 'with dns rebinding enabled' do - let(:dns_rebind_protection) { true } - - it 'raises an error due to DNS timeout' do - expect { subject }.to raise_error(described_class::BlockedUrlError) - end - end - - context 'with dns rebinding disabled' do - let(:dns_rebind_protection) { false } - - it_behaves_like 'validates URI and hostname' do - let(:expected_uri) { import_url } - let(:expected_hostname) { nil } - end - end - end end describe '#blocked_url?' do diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb index cdf03b3c958..6c55d37b95f 100644 --- a/spec/models/packages/package_spec.rb +++ b/spec/models/packages/package_spec.rb @@ -4,6 +4,8 @@ require 'spec_helper' RSpec.describe Packages::Package, type: :model do include SortingHelper + it_behaves_like 'having unique enum values' + describe 'relationships' do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:creator) } @@ -605,6 +607,28 @@ RSpec.describe Packages::Package, type: :model do it { is_expected.to match_array([pypi_package]) } end + + describe '.displayable' do + let_it_be(:hidden_package) { create(:maven_package, :hidden) } + let_it_be(:processing_package) { create(:maven_package, :processing) } + + subject { described_class.displayable } + + it 'does not include hidden packages', :aggregate_failures do + is_expected.not_to include(hidden_package) + is_expected.not_to include(processing_package) + end + end + + describe '.with_status' do + let_it_be(:hidden_package) { create(:maven_package, :hidden) } + + subject { described_class.with_status(:hidden) } + + it 'returns packages with specified status' do + is_expected.to match_array([hidden_package]) + end + end end describe '.select_distinct_name' do diff --git a/spec/requests/api/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb index 430cea17170..a47be1ead9c 100644 --- a/spec/requests/api/generic_packages_spec.rb +++ b/spec/requests/api/generic_packages_spec.rb @@ -281,6 +281,7 @@ RSpec.describe API::GenericPackages do package = project.packages.generic.last expect(package.name).to eq('mypackage') + expect(package.status).to eq('default') expect(package.version).to eq('0.0.1') if should_set_build_info @@ -293,6 +294,39 @@ RSpec.describe API::GenericPackages do expect(package_file.file_name).to eq('myfile.tar.gz') end end + + context 'with a status' do + context 'valid status' do + let(:params) { super().merge(status: 'hidden') } + + it 'assigns the status to the package' do + headers = workhorse_headers.merge(auth_header) + + upload_file(params, headers) + + aggregate_failures do + expect(response).to have_gitlab_http_status(:created) + + package = project.packages.find_by(name: 'mypackage') + expect(package).to be_hidden + end + end + end + + context 'invalid status' do + let(:params) { super().merge(status: 'processing') } + + it 'rejects the package' do + headers = workhorse_headers.merge(auth_header) + + upload_file(params, headers) + + aggregate_failures do + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end + end end context 'when valid personal access token is used' do diff --git a/spec/requests/api/group_packages_spec.rb b/spec/requests/api/group_packages_spec.rb index 26895e473de..792aa2c1f20 100644 --- a/spec/requests/api/group_packages_spec.rb +++ b/spec/requests/api/group_packages_spec.rb @@ -144,6 +144,7 @@ RSpec.describe API::GroupPackages do end it_behaves_like 'with versionless packages' + it_behaves_like 'with status param' it_behaves_like 'does not cause n^2 queries' end end diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb index eb86df36dbb..1f3887cab8a 100644 --- a/spec/requests/api/project_packages_spec.rb +++ b/spec/requests/api/project_packages_spec.rb @@ -120,6 +120,7 @@ RSpec.describe API::ProjectPackages do end it_behaves_like 'with versionless packages' + it_behaves_like 'with status param' it_behaves_like 'does not cause n^2 queries' end end diff --git a/spec/services/packages/composer/create_package_service_spec.rb b/spec/services/packages/composer/create_package_service_spec.rb index d10356cfda7..4f1a46e7e45 100644 --- a/spec/services/packages/composer/create_package_service_spec.rb +++ b/spec/services/packages/composer/create_package_service_spec.rb @@ -43,6 +43,7 @@ RSpec.describe Packages::Composer::CreatePackageService do end it_behaves_like 'assigns build to package' + it_behaves_like 'assigns status to package' end context 'with a tag' do @@ -66,6 +67,7 @@ RSpec.describe Packages::Composer::CreatePackageService do end it_behaves_like 'assigns build to package' + it_behaves_like 'assigns status to package' end end diff --git a/spec/services/packages/conan/create_package_service_spec.rb b/spec/services/packages/conan/create_package_service_spec.rb index ca783475503..6f644f5ef95 100644 --- a/spec/services/packages/conan/create_package_service_spec.rb +++ b/spec/services/packages/conan/create_package_service_spec.rb @@ -31,6 +31,7 @@ RSpec.describe Packages::Conan::CreatePackageService do it_behaves_like 'assigns the package creator' it_behaves_like 'assigns build to package' + it_behaves_like 'assigns status to package' end context 'invalid params' do diff --git a/spec/services/packages/generic/create_package_file_service_spec.rb b/spec/services/packages/generic/create_package_file_service_spec.rb index 816e728c342..10c54369f26 100644 --- a/spec/services/packages/generic/create_package_file_service_spec.rb +++ b/spec/services/packages/generic/create_package_file_service_spec.rb @@ -13,6 +13,8 @@ RSpec.describe Packages::Generic::CreatePackageFileService do let(:temp_file) { Tempfile.new("test") } let(:file) { UploadedFile.new(temp_file.path, sha256: sha256) } let(:package) { create(:generic_package, project: project) } + let(:package_service) { double } + let(:params) do { package_name: 'mypackage', @@ -23,31 +25,34 @@ RSpec.describe Packages::Generic::CreatePackageFileService do } end + let(:package_params) do + { + name: params[:package_name], + version: params[:package_version], + build: params[:build], + status: nil + } + end + subject { described_class.new(project, user, params).execute } before do FileUtils.touch(temp_file) + expect(::Packages::Generic::FindOrCreatePackageService).to receive(:new).with(project, user, package_params).and_return(package_service) + expect(package_service).to receive(:execute).and_return(package) end after do FileUtils.rm_f(temp_file) end - it 'creates package file' do - package_service = double - package_params = { - name: params[:package_name], - version: params[:package_version], - build: params[:build] - } - expect(::Packages::Generic::FindOrCreatePackageService).to receive(:new).with(project, user, package_params).and_return(package_service) - expect(package_service).to receive(:execute).and_return(package) - + it 'creates package file', :aggregate_failures do expect { subject }.to change { package.package_files.count }.by(1) .and change { Packages::PackageFileBuildInfo.count }.by(1) package_file = package.package_files.last aggregate_failures do + expect(package_file.package.status).to eq('default') expect(package_file.package).to eq(package) expect(package_file.file_name).to eq('myfile.tar.gz.1') expect(package_file.size).to eq(file.size) @@ -55,6 +60,21 @@ RSpec.describe Packages::Generic::CreatePackageFileService do end end + context 'with a status' do + let(:params) { super().merge(status: 'hidden') } + let(:package_params) { super().merge(status: 'hidden') } + + it 'updates an existing packages status' do + expect { subject }.to change { package.package_files.count }.by(1) + .and change { Packages::PackageFileBuildInfo.count }.by(1) + + package_file = package.package_files.last + aggregate_failures do + expect(package_file.package.status).to eq('hidden') + end + end + end + it_behaves_like 'assigns build to package file' end end diff --git a/spec/services/packages/maven/find_or_create_package_service_spec.rb b/spec/services/packages/maven/find_or_create_package_service_spec.rb index 191a443a837..2543ab0c669 100644 --- a/spec/services/packages/maven/find_or_create_package_service_spec.rb +++ b/spec/services/packages/maven/find_or_create_package_service_spec.rb @@ -36,10 +36,11 @@ RSpec.describe Packages::Maven::FindOrCreatePackageService do expect(pkg.version).to eq(version) end - context 'with a build' do + context 'with optional attributes' do subject { service.execute.payload[:package] } it_behaves_like 'assigns build to package' + it_behaves_like 'assigns status to package' end end diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb index 6db3777cde8..10fce6c1651 100644 --- a/spec/services/packages/npm/create_package_service_spec.rb +++ b/spec/services/packages/npm/create_package_service_spec.rb @@ -53,6 +53,7 @@ RSpec.describe Packages::Npm::CreatePackageService do let(:params) { super().merge(build: job) } it_behaves_like 'assigns build to package' + it_behaves_like 'assigns status to package' it 'creates a package file build info' do expect { subject }.to change { Packages::PackageFileBuildInfo.count }.by(1) diff --git a/spec/services/packages/nuget/create_package_service_spec.rb b/spec/services/packages/nuget/create_package_service_spec.rb index 5289ad40d61..e338ac36fc3 100644 --- a/spec/services/packages/nuget/create_package_service_spec.rb +++ b/spec/services/packages/nuget/create_package_service_spec.rb @@ -32,5 +32,6 @@ RSpec.describe Packages::Nuget::CreatePackageService do it_behaves_like 'assigns the package creator' it_behaves_like 'assigns build to package' + it_behaves_like 'assigns status to package' end end diff --git a/spec/services/packages/pypi/create_package_service_spec.rb b/spec/services/packages/pypi/create_package_service_spec.rb index 28a727c4a09..a932cf73eb7 100644 --- a/spec/services/packages/pypi/create_package_service_spec.rb +++ b/spec/services/packages/pypi/create_package_service_spec.rb @@ -52,6 +52,7 @@ RSpec.describe Packages::Pypi::CreatePackageService do end it_behaves_like 'assigns build to package' + it_behaves_like 'assigns status to package' context 'with an existing package' do before do diff --git a/spec/support/helpers/dns_helpers.rb b/spec/support/helpers/dns_helpers.rb index 52c708e77a5..1795b0a9ac3 100644 --- a/spec/support/helpers/dns_helpers.rb +++ b/spec/support/helpers/dns_helpers.rb @@ -12,38 +12,19 @@ module DnsHelpers end def stub_all_dns! - allow(Addrinfo).to receive(:getaddrinfo).and_return([]) + allow(Addrinfo).to receive(:getaddrinfo).with(anything, anything, nil, :STREAM).and_return([]) + allow(Addrinfo).to receive(:getaddrinfo).with(anything, anything, nil, :STREAM, anything, anything).and_return([]) end def stub_invalid_dns! - invalid_addresses = %r{ - \A - (?: - foobar\.\w | - (?:\d{1,3}\.){4,}\d{1,3} - ) - \z - }ix - - allow(Addrinfo).to receive(:getaddrinfo) - .with(invalid_addresses, any_args) - .and_raise(SocketError, 'getaddrinfo: Name or service not known') + allow(Addrinfo).to receive(:getaddrinfo).with(/\Afoobar\.\w|(\d{1,3}\.){4,}\d{1,3}\z/i, anything, nil, :STREAM) do + raise SocketError.new("getaddrinfo: Name or service not known") + end end def permit_local_dns! - local_addresses = %r{ - \A - (?: - (?:127|10)\.0\.0\.\d{1,3} | - (?:192\.168|172\.16)\.\d{1,3}\.\d{1,3} | - 0\.0\.0\.0 | - localhost - ) - \z - }ix - - allow(Addrinfo).to receive(:getaddrinfo) - .with(local_addresses, any_args) - .and_call_original + local_addresses = /\A(127|10)\.0\.0\.\d{1,3}|(192\.168|172\.16)\.\d{1,3}\.\d{1,3}|0\.0\.0\.0|localhost\z/i + allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM).and_call_original + allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM, anything, anything, any_args).and_call_original end end diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb index 81872b1c908..473f07dd413 100644 --- a/spec/support/helpers/stub_requests.rb +++ b/spec/support/helpers/stub_requests.rb @@ -18,13 +18,14 @@ module StubRequests end def stub_dns(url, ip_address:, port: 80) - url = URI(url) + url = parse_url(url) socket = Socket.sockaddr_in(port, ip_address) addr = Addrinfo.new(socket) + # See Gitlab::UrlBlocker allow(Addrinfo).to receive(:getaddrinfo) - .with(url.hostname, url.port, any_args) - .and_return([addr]) + .with(url.hostname, url.port, nil, :STREAM) + .and_return([addr]) end def stub_all_dns(url, ip_address:) @@ -33,14 +34,22 @@ module StubRequests socket = Socket.sockaddr_in(port, ip_address) addr = Addrinfo.new(socket) + # See Gitlab::UrlBlocker + allow(Addrinfo).to receive(:getaddrinfo).and_call_original allow(Addrinfo).to receive(:getaddrinfo) - .with(url.hostname, any_args) + .with(url.hostname, anything, nil, :STREAM) .and_return([addr]) end def stubbed_hostname(url, hostname: IP_ADDRESS_STUB) - url = URI(url) + url = parse_url(url) url.hostname = hostname url.to_s end + + private + + def parse_url(url) + url.is_a?(URI) ? url : URI(url) + end end diff --git a/spec/support/shared_examples/finders/packages_shared_examples.rb b/spec/support/shared_examples/finders/packages_shared_examples.rb index 52976565b21..2d4e8d0df1f 100644 --- a/spec/support/shared_examples/finders/packages_shared_examples.rb +++ b/spec/support/shared_examples/finders/packages_shared_examples.rb @@ -17,3 +17,23 @@ RSpec.shared_examples 'concerning versionless param' do it { is_expected.not_to include(versionless_package) } end end + +RSpec.shared_examples 'concerning package statuses' do + let_it_be(:hidden_package) { create(:maven_package, :hidden, project: project) } + + context 'hidden packages' do + it { is_expected.not_to include(hidden_package) } + end + + context 'with status param' do + let(:params) { { status: :hidden } } + + it { is_expected.to match_array([hidden_package]) } + end + + context 'with invalid status param' do + let(:params) { { status: 'invalid_status' } } + + it { expect { subject }.to raise_exception(described_class::InvalidStatusError) } + end +end diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index ab6cd2109cb..4e34c191306 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -40,6 +40,19 @@ RSpec.shared_examples 'assigns the package creator' do end end +RSpec.shared_examples 'assigns status to package' do + context 'with status param' do + let_it_be(:status) { 'hidden' } + let(:params) { super().merge(status: status) } + + it 'assigns the status to the package' do + package = subject + + expect(package.status).to eq(status) + end + end +end + RSpec.shared_examples 'returns packages' do |container_type, user_type| context "for #{user_type}" do before do @@ -263,3 +276,41 @@ RSpec.shared_examples 'with versionless packages' do end end end + +RSpec.shared_examples 'with status param' do + context 'hidden packages' do + let!(:hidden_package) { create(:maven_package, :hidden, project: project) } + + shared_examples 'not including the hidden package' do + it 'does not return the package' do + subject + + expect(json_response.map { |package| package['id'] }).not_to include(hidden_package.id) + end + end + + context 'no status param' do + it_behaves_like 'not including the hidden package' + end + + context 'with hidden status param' do + let(:params) { super().merge(status: 'hidden') } + + it 'returns the package' do + subject + + expect(json_response.map { |package| package['id'] }).to include(hidden_package.id) + end + end + end + + context 'bad status param' do + let(:params) { super().merge(status: 'invalid') } + + it 'returns the package' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + end + end +end |