diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-11 18:10:00 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-11 18:10:00 +0300 |
commit | d06e4d855aed9148571c7fd95fe02f208a017dd3 (patch) | |
tree | 83a1fe4bc449255b426f6b87fa4a37732a5c1fa1 | |
parent | 320d8adff14c100cd8a6798880b7eeff8e137f15 (diff) |
Add latest changes from gitlab-org/gitlab@master
70 files changed, 758 insertions, 169 deletions
diff --git a/app/assets/javascripts/integrations/index/components/integrations_table.vue b/app/assets/javascripts/integrations/index/components/integrations_table.vue index 37d622a941e..fc5048490a7 100644 --- a/app/assets/javascripts/integrations/index/components/integrations_table.vue +++ b/app/assets/javascripts/integrations/index/components/integrations_table.vue @@ -1,5 +1,12 @@ <script> -import { GlAvatarLabeled, GlAvatarLink, GlIcon, GlTable, GlTooltipDirective } from '@gitlab/ui'; +import { + GlAvatarLabeled, + GlAvatarLink, + GlButton, + GlIcon, + GlTable, + GlTooltipDirective, +} from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -8,6 +15,7 @@ export default { components: { GlAvatarLabeled, GlAvatarLink, + GlButton, GlIcon, GlTable, TimeAgoTooltip, @@ -55,13 +63,7 @@ export default { { key: 'title', label: __('Integration'), - thClass: 'gl-w-quarter gl-xs-w-full', - }, - { - key: 'description', - label: __('Description'), - thClass: 'gl-display-none d-sm-table-cell', - tdClass: 'gl-display-none d-sm-table-cell', + thClass: 'd-sm-table-cell', }, ); @@ -69,11 +71,17 @@ export default { fields.push({ key: 'updated_at', label: this.showUpdatedAt ? __('Last updated') : '', - thClass: 'gl-w-20 gl-text-right', - tdClass: 'gl-text-right', + thClass: 'gl-display-none d-sm-table-cell gl-text-right', + tdClass: 'gl-text-right gl-display-none d-sm-table-cell gl-vertical-align-middle!', }); } + fields.push({ + key: 'edit_path', + label: '', + thClass: 'gl-w-15', + }); + return fields; }, filteredIntegrations() { @@ -113,6 +121,7 @@ export default { > <gl-avatar-labeled :label="item.title" + :sub-label="item.description" :entity-id="item.id" :entity-name="item.title" :src="item.icon" @@ -126,5 +135,11 @@ export default { <template #cell(updated_at)="{ item }"> <time-ago-tooltip v-if="showUpdatedAt && item.updated_at" :time="item.updated_at" /> </template> + + <template #cell(edit_path)="{ item }"> + <gl-button :href="item.edit_path"> + {{ __('Configure') }} + </gl-button> + </template> </gl-table> </template> diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index 72351f8ba0c..47701d0490a 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -9,5 +9,6 @@ @import './pages/notes'; @import './pages/pipelines'; @import './pages/profile'; +@import './pages/projects'; @import './pages/registry'; @import './pages/settings'; diff --git a/app/assets/stylesheets/page_bundles/projects.scss b/app/assets/stylesheets/pages/projects.scss index 68139c55f5a..9ce470dbcf2 100644 --- a/app/assets/stylesheets/page_bundles/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -1,5 +1,3 @@ -@import 'mixins_and_variables_and_functions'; - .new_project, .edit-project, .import-project { @@ -8,7 +6,7 @@ } .project-path .form-control { - border-radius: $gl-border-radius-base; + border-radius: $border-radius-base; } .input-group { @@ -49,7 +47,7 @@ } + .btn-default { - border-radius: 0 $gl-border-radius-base $gl-border-radius-base 0; + border-radius: 0 $border-radius-base $border-radius-base 0; } } } diff --git a/app/finders/ci/jobs_finder.rb b/app/finders/ci/jobs_finder.rb index b35637d0e4f..efacd8143bc 100644 --- a/app/finders/ci/jobs_finder.rb +++ b/app/finders/ci/jobs_finder.rb @@ -16,9 +16,7 @@ module Ci def execute builds = init_collection.order_id_desc - builds = filter_by_with_artifacts(builds) - builds = filter_by_runner_types(builds) - filter_by_scope(builds) + filter_builds(builds) rescue Gitlab::Access::AccessDeniedError type.none end @@ -59,6 +57,13 @@ module Ci params[:include_retried] ? jobs_scope : jobs_scope.latest end + # Overriden in EE + def filter_builds(builds) + builds = filter_by_with_artifacts(builds) + builds = filter_by_runner_types(builds) + filter_by_scope(builds) + end + def filter_by_scope(builds) return filter_by_statuses!(builds) if params[:scope].is_a?(Array) @@ -80,16 +85,15 @@ module Ci builds.with_runner_type(params[:runner_type]) end + # Overriden in EE def use_runner_type_filter? params[:runner_type].present? && Feature.enabled?(:admin_jobs_filter_runner_type, project, type: :ops) end def filter_by_with_artifacts(builds) - if params[:with_artifacts] - builds.with_any_artifacts - else - builds - end + return builds.with_any_artifacts if params[:with_artifacts] + + builds end def filter_by_statuses!(builds) @@ -111,3 +115,5 @@ module Ci end end end + +Ci::JobsFinder.prepend_mod diff --git a/app/finders/groups/accepting_group_transfers_finder.rb b/app/finders/groups/accepting_group_transfers_finder.rb index c95318d0098..ddf1ed2662a 100644 --- a/app/finders/groups/accepting_group_transfers_finder.rb +++ b/app/finders/groups/accepting_group_transfers_finder.rb @@ -14,7 +14,13 @@ module Groups return Group.none unless can_transfer_group? items = find_all_groups - items = by_search(items) + + if exact_matches_first_enabled? + # Search will perform an ORDER BY to ensure exact matches are returned first. + return by_search(items, exact_matches_first: true) if params[:search].present? + else + items = by_search(items) + end sort(items) end @@ -60,5 +66,9 @@ module Groups def can_transfer_group? Ability.allowed?(current_user, :admin_group, group_to_be_transferred) end + + def exact_matches_first_enabled? + Feature.enabled?(:exact_matches_first_group_transfer, group_to_be_transferred) + end end end diff --git a/app/finders/groups/base.rb b/app/finders/groups/base.rb index 9d2f9f60a63..26d2ad85fd4 100644 --- a/app/finders/groups/base.rb +++ b/app/finders/groups/base.rb @@ -8,10 +8,10 @@ module Groups items.reorder(Group.arel_table[:path].asc, Group.arel_table[:id].asc) # rubocop: disable CodeReuse/ActiveRecord end - def by_search(items) + def by_search(items, exact_matches_first: false) return items if params[:search].blank? - items.search(params[:search], include_parents: true) + items.search(params[:search], include_parents: true, exact_matches_first: exact_matches_first) end end end diff --git a/app/finders/packages/npm/packages_for_user_finder.rb b/app/finders/packages/npm/packages_for_user_finder.rb new file mode 100644 index 00000000000..f42e49f9184 --- /dev/null +++ b/app/finders/packages/npm/packages_for_user_finder.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Packages + module Npm + class PackagesForUserFinder < ::Packages::GroupOrProjectPackageFinder + def execute + packages + end + + private + + def packages + base.npm + .with_name(@params[:package_name]) + end + end + end +end diff --git a/app/graphql/resolvers/ci/all_jobs_resolver.rb b/app/graphql/resolvers/ci/all_jobs_resolver.rb index 85b0f8da877..3012a7defa6 100644 --- a/app/graphql/resolvers/ci/all_jobs_resolver.rb +++ b/app/graphql/resolvers/ci/all_jobs_resolver.rb @@ -17,15 +17,18 @@ module Resolvers description: 'Filter jobs by runner type if ' \ 'feature flag `:admin_jobs_filter_runner_type` is enabled.' - def resolve_with_lookahead(statuses: nil, runner_types: nil) - jobs = ::Ci::JobsFinder.new(current_user: current_user, -params: { scope: statuses, runner_type: runner_types }).execute + def resolve_with_lookahead(**args) + jobs = ::Ci::JobsFinder.new(current_user: current_user, params: params_data(args)).execute apply_lookahead(jobs) end private + def params_data(args) + { scope: args[:statuses], runner_type: args[:runner_types] } + end + def preloads { previous_stage_jobs_or_needs: [:needs, :pipeline], @@ -55,3 +58,5 @@ params: { scope: statuses, runner_type: runner_types }).execute end end end + +Resolvers::Ci::AllJobsResolver.prepend_mod diff --git a/app/graphql/types/ci/job_failure_reason_enum.rb b/app/graphql/types/ci/job_failure_reason_enum.rb new file mode 100644 index 00000000000..3b9c13536d6 --- /dev/null +++ b/app/graphql/types/ci/job_failure_reason_enum.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module Ci + class JobFailureReasonEnum < BaseEnum + graphql_name 'CiJobFailureReason' + + ::Enums::Ci::CommitStatus.failure_reasons.each_key do |reason| + value reason.to_s.upcase, + description: "A job that failed due to #{reason.to_s.tr('_', ' ')}.", + value: reason + end + end + end +end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index ced855c7287..e9d5d940458 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -233,16 +233,26 @@ class Namespace < ApplicationRecord # query - The search query as a String. # # Returns an ActiveRecord::Relation. - def search(query, include_parents: false, use_minimum_char_limit: true) + def search(query, include_parents: false, use_minimum_char_limit: true, exact_matches_first: false) if include_parents - without_project_namespaces + route_columns = [Route.arel_table[:path], Route.arel_table[:name]] + namespaces = without_project_namespaces .where(id: Route.for_routable_type(Namespace.name) .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046") - .fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name]], + .fuzzy_search(query, route_columns, use_minimum_char_limit: use_minimum_char_limit) .select(:source_id)) + + if exact_matches_first + namespaces = namespaces + .joins(:route) + .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046") + .order(exact_matches_first_sql(query, route_columns)) + end + + namespaces else - without_project_namespaces.fuzzy_search(query, [:path, :name], use_minimum_char_limit: use_minimum_char_limit) + without_project_namespaces.fuzzy_search(query, [:path, :name], use_minimum_char_limit: use_minimum_char_limit, exact_matches_first: exact_matches_first) end end diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 412d8e64e89..31ec4935f64 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,6 +1,5 @@ - page_title _('Projects') - add_page_specific_style 'page_bundles/search' -- add_page_specific_style 'page_bundles/projects' - params[:visibility_level] ||= [] .top-area.gl-flex-direction-column-reverse diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 6be5aa003fc..85dce00752b 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,5 +1,4 @@ - add_page_specific_style 'page_bundles/members' -- add_page_specific_style 'page_bundles/projects' - add_to_breadcrumbs _("Projects"), admin_projects_path - breadcrumb_title @project.full_name - page_title @project.full_name, _("Projects") diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index c74a9f4cbe6..140bc6e06c3 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -5,7 +5,6 @@ - page_title _("Projects") - add_page_specific_style 'page_bundles/dashboard_projects' -- add_page_specific_style 'page_bundles/projects' = render "projects/last_push" - if show_projects?(@projects, params) diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 76758769d01..22e9f9f5071 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -1,6 +1,5 @@ - breadcrumb_title _("Projects") - page_title _("Projects") -- add_page_specific_style 'page_bundles/projects' - @force_desktop_expanded_sidebar = true = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card js-search-settings-section' }, header_options: { class: 'gl-new-card-header gl-display-flex' }, body_options: { class: 'gl-new-card-body' }) do |c| diff --git a/app/views/layouts/header/_super_sidebar_logged_out.haml b/app/views/layouts/header/_super_sidebar_logged_out.haml index 67322aced74..31dfdfb2bb3 100644 --- a/app/views/layouts/header/_super_sidebar_logged_out.haml +++ b/app/views/layouts/header/_super_sidebar_logged_out.haml @@ -44,4 +44,4 @@ - if allow_signup? %li = render Pajamas::ButtonComponent.new(href: new_user_registration_path, variant: :confirm) do - = _('Register') + = Gitlab.com? ? _('Get free trial') : _('Register') diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml index 7e5dd0d37c9..29e30c4434f 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -1,7 +1,6 @@ - page_title _("Settings") - nav "project" - add_page_specific_style 'page_bundles/settings' -- add_page_specific_style 'page_bundles/projects' - enable_search_settings locals: { container_class: 'gl-my-5' } diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index bd0f4577a32..e2cad2fb3d7 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -1,6 +1,5 @@ - page_title _("Blame"), @blob.path, @ref - add_page_specific_style 'page_bundles/tree' -- add_page_specific_style 'page_bundles/projects' - blame_streaming_url = blame_pages_streaming_url(@id, @project) - if @blame_mode.streaming? && @blame_pagination.total_extra_pages > 0 diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 2d9b7ada015..543bdaf46df 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -2,7 +2,6 @@ - project = @project.present(current_user: current_user) - ref = local_assigns[:ref] || @ref - expanded = params[:expanded].present? -- add_page_specific_style 'page_bundles/projects' -# If the blob has a RichViewer we preload the content except for GeoJSON since it is handled by Vue - if blob.rich_viewer && blob.extension != 'geojson' - add_page_startup_api_call local_assigns.fetch(:viewer_url) { url_for(safe_params.merge(viewer: blob.rich_viewer.type, format: :json)) } diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index be2bf43cbb9..1034f06f722 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -1,7 +1,6 @@ - breadcrumb_title _("Commits") - add_page_specific_style 'page_bundles/tree' - add_page_specific_style 'page_bundles/merge_request' -- add_page_specific_style 'page_bundles/projects' - page_title _("Commits"), @ref = content_for :meta_tags do diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml index 2c53b060c11..541b8c1147d 100644 --- a/app/views/projects/find_file/show.html.haml +++ b/app/views/projects/find_file/show.html.haml @@ -1,6 +1,5 @@ - page_title _("Find File"), @ref - add_page_specific_style 'page_bundles/tree' -- add_page_specific_style 'page_bundles/projects' .file-finder-holder.tree-holder.clearfix.js-file-finder.gl-pt-4{ 'data-file-find-url': "#{escape_javascript(project_files_path(@project, @ref, format: :json))}", 'data-find-tree-url': escape_javascript(project_tree_path(@project, @ref)), 'data-blob-url-template': escape_javascript(project_blob_path(@project, @ref)) } .nav-block.gl-xs-mr-0 diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 76e20dcc105..59a21cecd39 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -3,7 +3,6 @@ - page_title _('New Project') - header_title _("Projects"), dashboard_projects_path - add_page_specific_style 'page_bundles/new_namespace' -- add_page_specific_style 'page_bundles/projects' .project-edit-container .project-edit-errors diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index c44499d69b5..a4ed19c2fc9 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -1,5 +1,3 @@ -- add_page_specific_style 'page_bundles/projects' - .tree-ref-container.gl-display-flex.gl-flex-wrap.gl-gap-2.mb-2.mb-md-0 .tree-ref-holder.gl-max-w-26{ data: { qa_selector: 'ref_dropdown_container' } } #js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } } diff --git a/config/application.rb b/config/application.rb index 3f095ee0e10..7bce5afa1af 100644 --- a/config/application.rb +++ b/config/application.rb @@ -339,7 +339,6 @@ module Gitlab config.assets.precompile << "page_bundles/profile_two_factor_auth.css" config.assets.precompile << "page_bundles/profiles/preferences.css" config.assets.precompile << "page_bundles/project.css" - config.assets.precompile << "page_bundles/projects.css" config.assets.precompile << "page_bundles/projects_edit.css" config.assets.precompile << "page_bundles/promotions.css" config.assets.precompile << "page_bundles/releases.css" diff --git a/config/feature_flags/development/exact_matches_first_group_transfer.yml b/config/feature_flags/development/exact_matches_first_group_transfer.yml new file mode 100644 index 00000000000..f5701c78177 --- /dev/null +++ b/config/feature_flags/development/exact_matches_first_group_transfer.yml @@ -0,0 +1,8 @@ +--- +name: exact_matches_first_group_transfer +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130773 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/424242 +milestone: '16.4' +type: development +group: group::project management +default_enabled: false diff --git a/config/feature_flags/development/loose_foreign_keys_batch_load_using_union.yml b/config/feature_flags/development/loose_foreign_keys_batch_load_using_union.yml index 0b0ed16c1cd..4e4a1e262b0 100644 --- a/config/feature_flags/development/loose_foreign_keys_batch_load_using_union.yml +++ b/config/feature_flags/development/loose_foreign_keys_batch_load_using_union.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/421422 milestone: '16.3' type: development group: group::tenant scale -default_enabled: false +default_enabled: true diff --git a/db/migrate/20230901170145_update_vulnerability_reads_trigger_to_set_has_merge_request.rb b/db/migrate/20230901170145_update_vulnerability_reads_trigger_to_set_has_merge_request.rb new file mode 100644 index 00000000000..7b5a4645f85 --- /dev/null +++ b/db/migrate/20230901170145_update_vulnerability_reads_trigger_to_set_has_merge_request.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +class UpdateVulnerabilityReadsTriggerToSetHasMergeRequest < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def up + execute(<<~SQL) + CREATE OR REPLACE FUNCTION insert_or_update_vulnerability_reads() + RETURNS trigger + LANGUAGE plpgsql + AS $$ + DECLARE + severity smallint; + state smallint; + report_type smallint; + resolved_on_default_branch boolean; + present_on_default_branch boolean; + namespace_id bigint; + has_issues boolean; + has_merge_request boolean; + BEGIN + IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN + RETURN NULL; + END IF; + + IF (TG_OP = 'UPDATE' AND OLD.vulnerability_id IS NOT NULL AND NEW.vulnerability_id IS NOT NULL) THEN + RETURN NULL; + END IF; + + SELECT + vulnerabilities.severity, vulnerabilities.state, vulnerabilities.report_type, vulnerabilities.resolved_on_default_branch, vulnerabilities.present_on_default_branch + INTO + severity, state, report_type, resolved_on_default_branch, present_on_default_branch + FROM + vulnerabilities + WHERE + vulnerabilities.id = NEW.vulnerability_id; + + IF present_on_default_branch IS NOT true THEN + RETURN NULL; + END IF; + + SELECT + projects.namespace_id + INTO + namespace_id + FROM + projects + WHERE + projects.id = NEW.project_id; + + SELECT + EXISTS (SELECT 1 FROM vulnerability_issue_links WHERE vulnerability_issue_links.vulnerability_id = NEW.vulnerability_id) + INTO + has_issues; + + SELECT + EXISTS (SELECT 1 FROM vulnerability_merge_request_links WHERE vulnerability_merge_request_links.vulnerability_id = NEW.vulnerability_id) + INTO + has_merge_request; + + INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues, has_merge_request) + VALUES (NEW.vulnerability_id, namespace_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id', CAST(NEW.location->'kubernetes_resource'->>'agent_id' AS bigint), has_issues, has_merge_request) + ON CONFLICT(vulnerability_id) DO NOTHING; + RETURN NULL; + END + $$ + SQL + + execute(<<~SQL) + CREATE OR REPLACE FUNCTION insert_vulnerability_reads_from_vulnerability() + RETURNS trigger + LANGUAGE plpgsql + AS $$ + DECLARE + scanner_id bigint; + uuid uuid; + location_image text; + cluster_agent_id text; + casted_cluster_agent_id bigint; + namespace_id bigint; + has_issues boolean; + has_merge_request boolean; + BEGIN + SELECT + v_o.scanner_id, v_o.uuid, v_o.location->>'image', v_o.location->'kubernetes_resource'->>'agent_id', CAST(v_o.location->'kubernetes_resource'->>'agent_id' AS bigint), projects.namespace_id + INTO + scanner_id, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, namespace_id + FROM + vulnerability_occurrences v_o + INNER JOIN projects ON projects.id = v_o.project_id + WHERE + v_o.vulnerability_id = NEW.id + LIMIT 1; + + SELECT + EXISTS (SELECT 1 FROM vulnerability_issue_links WHERE vulnerability_issue_links.vulnerability_id = NEW.id) + INTO + has_issues; + + SELECT + EXISTS (SELECT 1 FROM vulnerability_merge_request_links WHERE vulnerability_merge_request_links.vulnerability_id = NEW.id) + INTO + has_merge_request; + + INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues, has_merge_request) + VALUES (NEW.id, namespace_id, NEW.project_id, scanner_id, NEW.report_type, NEW.severity, NEW.state, NEW.resolved_on_default_branch, uuid::uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues, has_merge_request) + ON CONFLICT(vulnerability_id) DO NOTHING; + RETURN NULL; + END + $$ + SQL + end + + def down + execute(<<~SQL) + CREATE OR REPLACE FUNCTION insert_or_update_vulnerability_reads() + RETURNS trigger + LANGUAGE plpgsql + AS $$ + DECLARE + severity smallint; + state smallint; + report_type smallint; + resolved_on_default_branch boolean; + present_on_default_branch boolean; + namespace_id bigint; + has_issues boolean; + BEGIN + IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN + RETURN NULL; + END IF; + + IF (TG_OP = 'UPDATE' AND OLD.vulnerability_id IS NOT NULL AND NEW.vulnerability_id IS NOT NULL) THEN + RETURN NULL; + END IF; + + SELECT + vulnerabilities.severity, vulnerabilities.state, vulnerabilities.report_type, vulnerabilities.resolved_on_default_branch, vulnerabilities.present_on_default_branch + INTO + severity, state, report_type, resolved_on_default_branch, present_on_default_branch + FROM + vulnerabilities + WHERE + vulnerabilities.id = NEW.vulnerability_id; + + IF present_on_default_branch IS NOT true THEN + RETURN NULL; + END IF; + + SELECT + projects.namespace_id + INTO + namespace_id + FROM + projects + WHERE + projects.id = NEW.project_id; + + SELECT + EXISTS (SELECT 1 FROM vulnerability_issue_links WHERE vulnerability_issue_links.vulnerability_id = NEW.vulnerability_id) + INTO + has_issues; + + INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues) + VALUES (NEW.vulnerability_id, namespace_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id', CAST(NEW.location->'kubernetes_resource'->>'agent_id' AS bigint), has_issues) + ON CONFLICT(vulnerability_id) DO NOTHING; + RETURN NULL; + END + $$ + SQL + + execute(<<~SQL) + CREATE OR REPLACE FUNCTION insert_vulnerability_reads_from_vulnerability() + RETURNS trigger + LANGUAGE plpgsql + AS $$ + DECLARE + scanner_id bigint; + uuid uuid; + location_image text; + cluster_agent_id text; + casted_cluster_agent_id bigint; + namespace_id bigint; + has_issues boolean; + BEGIN + SELECT + v_o.scanner_id, v_o.uuid, v_o.location->>'image', v_o.location->'kubernetes_resource'->>'agent_id', CAST(v_o.location->'kubernetes_resource'->>'agent_id' AS bigint), projects.namespace_id + INTO + scanner_id, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, namespace_id + FROM + vulnerability_occurrences v_o + INNER JOIN projects ON projects.id = v_o.project_id + WHERE + v_o.vulnerability_id = NEW.id + LIMIT 1; + + SELECT + EXISTS (SELECT 1 FROM vulnerability_issue_links WHERE vulnerability_issue_links.vulnerability_id = NEW.id) + INTO + has_issues; + + INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues) + VALUES (NEW.id, namespace_id, NEW.project_id, scanner_id, NEW.report_type, NEW.severity, NEW.state, NEW.resolved_on_default_branch, uuid::uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues) + ON CONFLICT(vulnerability_id) DO NOTHING; + RETURN NULL; + END + $$ + SQL + end +end diff --git a/db/migrate/20230905061815_add_foreign_key_for_ci_pipeline_messages_pipeline_id_bigint.rb b/db/migrate/20230905061815_add_foreign_key_for_ci_pipeline_messages_pipeline_id_bigint.rb new file mode 100644 index 00000000000..72ff8399a16 --- /dev/null +++ b/db/migrate/20230905061815_add_foreign_key_for_ci_pipeline_messages_pipeline_id_bigint.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddForeignKeyForCiPipelineMessagesPipelineIdBigint < Gitlab::Database::Migration[2.1] + TABLE_NAME = :ci_pipeline_messages + REFERENCING_TABLE_NAME = :ci_pipelines + COLUMN_NAME = :pipeline_id_convert_to_bigint + + disable_ddl_transaction! + + def up + add_concurrent_foreign_key( + TABLE_NAME, REFERENCING_TABLE_NAME, + column: COLUMN_NAME, on_delete: :cascade, validate: false, reverse_lock_order: true + ) + end + + def down + remove_foreign_key_if_exists TABLE_NAME, column: COLUMN_NAME + end +end diff --git a/db/migrate/20230907162613_add_force_full_reconciliation_to_workspaces.rb b/db/migrate/20230907162613_add_force_full_reconciliation_to_workspaces.rb new file mode 100644 index 00000000000..bd0b9b9bf69 --- /dev/null +++ b/db/migrate/20230907162613_add_force_full_reconciliation_to_workspaces.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddForceFullReconciliationToWorkspaces < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def change + add_column :workspaces, :force_full_reconciliation, :boolean, default: false, null: false + end +end diff --git a/db/post_migrate/20230903064537_add_ci_job_artifacts_file_final_path_index.rb b/db/post_migrate/20230903064537_add_ci_job_artifacts_file_final_path_index.rb new file mode 100644 index 00000000000..e1e5694f4d4 --- /dev/null +++ b/db/post_migrate/20230903064537_add_ci_job_artifacts_file_final_path_index.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddCiJobArtifactsFileFinalPathIndex < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + INDEX_NAME = 'index_ci_job_artifacts_on_file_final_path' + WHERE_CLAUSE = 'file_final_path IS NOT NULL' + + # TODO: Index to be created synchronously in https://gitlab.com/gitlab-org/gitlab/-/issues/423990 + def up + prepare_async_index :ci_job_artifacts, :file_final_path, name: INDEX_NAME, where: WHERE_CLAUSE + end + + def down + unprepare_async_index :ci_job_artifacts, :file_final_path, name: INDEX_NAME, where: WHERE_CLAUSE + end +end diff --git a/db/post_migrate/20230905071915_prepare_async_foreign_key_for_ci_pipeline_messages_pipeline_id_bigint.rb b/db/post_migrate/20230905071915_prepare_async_foreign_key_for_ci_pipeline_messages_pipeline_id_bigint.rb new file mode 100644 index 00000000000..ce576704d4c --- /dev/null +++ b/db/post_migrate/20230905071915_prepare_async_foreign_key_for_ci_pipeline_messages_pipeline_id_bigint.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class PrepareAsyncForeignKeyForCiPipelineMessagesPipelineIdBigint < Gitlab::Database::Migration[2.1] + TABLE_NAME = :ci_pipeline_messages + COLUMN_NAME = :pipeline_id_convert_to_bigint + FK_NAME = :fk_0946fea681 + + def up + prepare_async_foreign_key_validation TABLE_NAME, COLUMN_NAME, name: FK_NAME + end + + def down + unprepare_async_foreign_key_validation TABLE_NAME, COLUMN_NAME, name: FK_NAME + end +end diff --git a/db/post_migrate/20230906181457_add_index_to_violations_on_target_proj_id.rb b/db/post_migrate/20230906181457_add_index_to_violations_on_target_proj_id.rb new file mode 100644 index 00000000000..13c76a3c2de --- /dev/null +++ b/db/post_migrate/20230906181457_add_index_to_violations_on_target_proj_id.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddIndexToViolationsOnTargetProjId < Gitlab::Database::Migration[2.1] + TABLE_NAME = 'merge_requests_compliance_violations' + # Use funciton based naming as suggested in docs: + # https://docs.gitlab.com/ee/development/migration_style_guide.html#truncate-long-index-names + INDEX_NAME = 'i_compliance_violations_for_export' + + def up + prepare_async_index TABLE_NAME, [:target_project_id, :id], name: INDEX_NAME + end + + def down + unprepare_async_index TABLE_NAME, INDEX_NAME + end +end diff --git a/db/schema_migrations/20230901170145 b/db/schema_migrations/20230901170145 new file mode 100644 index 00000000000..ae389df1a50 --- /dev/null +++ b/db/schema_migrations/20230901170145 @@ -0,0 +1 @@ +86b17eb7dd562a935cea98c9a1e0815110dea9c6994f0c2fa9db5b37d5a3af27
\ No newline at end of file diff --git a/db/schema_migrations/20230903064537 b/db/schema_migrations/20230903064537 new file mode 100644 index 00000000000..763aa7ff01c --- /dev/null +++ b/db/schema_migrations/20230903064537 @@ -0,0 +1 @@ +6eda15f0921c135f38f3d35edcdb8deefd8e1735abf537c94dd8c6475600060b
\ No newline at end of file diff --git a/db/schema_migrations/20230905061815 b/db/schema_migrations/20230905061815 new file mode 100644 index 00000000000..12ea99e3e4a --- /dev/null +++ b/db/schema_migrations/20230905061815 @@ -0,0 +1 @@ +c32510034870dea5f26ab8fd64b034919355038a2e24f38bdd7c9051059346ec
\ No newline at end of file diff --git a/db/schema_migrations/20230905071915 b/db/schema_migrations/20230905071915 new file mode 100644 index 00000000000..8b4985f2141 --- /dev/null +++ b/db/schema_migrations/20230905071915 @@ -0,0 +1 @@ +c3be3211b1b7a08cb93ca79b569a4ee4412fe42066573c938fd920d9aee9163a
\ No newline at end of file diff --git a/db/schema_migrations/20230906181457 b/db/schema_migrations/20230906181457 new file mode 100644 index 00000000000..65e99ba4f0f --- /dev/null +++ b/db/schema_migrations/20230906181457 @@ -0,0 +1 @@ +183a4dd3ea67df81f38744550919d10d76b0a9e44eaf1cb949211d938b8c8f56
\ No newline at end of file diff --git a/db/schema_migrations/20230907162613 b/db/schema_migrations/20230907162613 new file mode 100644 index 00000000000..f3442502b6e --- /dev/null +++ b/db/schema_migrations/20230907162613 @@ -0,0 +1 @@ +bad1a624184b8f0bfe57dbc36b4ec8478edeaa2dc6366eb5e5d31cdc7c0c8595
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 75fb3c6ef28..6148a375c48 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -82,6 +82,7 @@ DECLARE present_on_default_branch boolean; namespace_id bigint; has_issues boolean; + has_merge_request boolean; BEGIN IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN RETURN NULL; @@ -118,8 +119,13 @@ BEGIN INTO has_issues; - INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues) - VALUES (NEW.vulnerability_id, namespace_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id', CAST(NEW.location->'kubernetes_resource'->>'agent_id' AS bigint), has_issues) + SELECT + EXISTS (SELECT 1 FROM vulnerability_merge_request_links WHERE vulnerability_merge_request_links.vulnerability_id = NEW.vulnerability_id) + INTO + has_merge_request; + + INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues, has_merge_request) + VALUES (NEW.vulnerability_id, namespace_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id', CAST(NEW.location->'kubernetes_resource'->>'agent_id' AS bigint), has_issues, has_merge_request) ON CONFLICT(vulnerability_id) DO NOTHING; RETURN NULL; END @@ -147,6 +153,7 @@ DECLARE casted_cluster_agent_id bigint; namespace_id bigint; has_issues boolean; + has_merge_request boolean; BEGIN SELECT v_o.scanner_id, v_o.uuid, v_o.location->>'image', v_o.location->'kubernetes_resource'->>'agent_id', CAST(v_o.location->'kubernetes_resource'->>'agent_id' AS bigint), projects.namespace_id @@ -164,8 +171,13 @@ BEGIN INTO has_issues; - INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues) - VALUES (NEW.id, namespace_id, NEW.project_id, scanner_id, NEW.report_type, NEW.severity, NEW.state, NEW.resolved_on_default_branch, uuid::uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues) + SELECT + EXISTS (SELECT 1 FROM vulnerability_merge_request_links WHERE vulnerability_merge_request_links.vulnerability_id = NEW.id) + INTO + has_merge_request; + + INSERT INTO vulnerability_reads (vulnerability_id, namespace_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues, has_merge_request) + VALUES (NEW.id, namespace_id, NEW.project_id, scanner_id, NEW.report_type, NEW.severity, NEW.state, NEW.resolved_on_default_branch, uuid::uuid, location_image, cluster_agent_id, casted_cluster_agent_id, has_issues, has_merge_request) ON CONFLICT(vulnerability_id) DO NOTHING; RETURN NULL; END @@ -25240,6 +25252,7 @@ CREATE TABLE workspaces ( deployment_resource_version text, personal_access_token_id bigint, config_version integer DEFAULT 1 NOT NULL, + force_full_reconciliation boolean DEFAULT false NOT NULL, CONSTRAINT check_15543fb0fa CHECK ((char_length(name) <= 64)), CONSTRAINT check_157d5f955c CHECK ((char_length(namespace) <= 64)), CONSTRAINT check_2b401b0034 CHECK ((char_length(deployment_resource_version) <= 64)), @@ -36185,6 +36198,9 @@ ALTER TABLE ONLY user_interacted_projects ALTER TABLE ONLY merge_request_assignment_events ADD CONSTRAINT fk_08f7602bfd FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE; +ALTER TABLE ONLY ci_pipeline_messages + ADD CONSTRAINT fk_0946fea681 FOREIGN KEY (pipeline_id_convert_to_bigint) REFERENCES ci_pipelines(id) ON DELETE CASCADE NOT VALID; + ALTER TABLE ONLY remote_development_agent_configs ADD CONSTRAINT fk_0a3c0ada56 FOREIGN KEY (cluster_agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE; diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index c28597d84c3..03b49cb1075 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -462,6 +462,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="queryjobsfailurereason"></a>`failureReason` **{warning-solid}** | [`CiJobFailureReason`](#cijobfailurereason) | **Introduced** in 16.4. This feature is an Experiment. It can be changed or removed at any time. Filter jobs by failure reason. Currently only `RUNNER_SYSTEM_FAILURE` together with `runnerTypes: INSTANCE_TYPE` is supported. | | <a id="queryjobsrunnertypes"></a>`runnerTypes` **{warning-solid}** | [`[CiRunnerType!]`](#cirunnertype) | **Introduced** in 16.4. This feature is an Experiment. It can be changed or removed at any time. Filter jobs by runner type if feature flag `:admin_jobs_filter_runner_type` is enabled. | | <a id="queryjobsstatuses"></a>`statuses` | [`[CiJobStatus!]`](#cijobstatus) | Filter jobs by status. | @@ -26308,6 +26309,47 @@ Values for sorting inherited variables. | <a id="cigroupvariablessortkey_asc"></a>`KEY_ASC` | Key by ascending order. | | <a id="cigroupvariablessortkey_desc"></a>`KEY_DESC` | Key by descending order. | +### `CiJobFailureReason` + +| Value | Description | +| ----- | ----------- | +| <a id="cijobfailurereasonapi_failure"></a>`API_FAILURE` | A job that failed due to api failure. | +| <a id="cijobfailurereasonarchived_failure"></a>`ARCHIVED_FAILURE` | A job that failed due to archived failure. | +| <a id="cijobfailurereasonbridge_pipeline_is_child_pipeline"></a>`BRIDGE_PIPELINE_IS_CHILD_PIPELINE` | A job that failed due to bridge pipeline is child pipeline. | +| <a id="cijobfailurereasonbuilds_disabled"></a>`BUILDS_DISABLED` | A job that failed due to builds disabled. | +| <a id="cijobfailurereasonci_quota_exceeded"></a>`CI_QUOTA_EXCEEDED` | A job that failed due to ci quota exceeded. | +| <a id="cijobfailurereasondata_integrity_failure"></a>`DATA_INTEGRITY_FAILURE` | A job that failed due to data integrity failure. | +| <a id="cijobfailurereasondeployment_rejected"></a>`DEPLOYMENT_REJECTED` | A job that failed due to deployment rejected. | +| <a id="cijobfailurereasondownstream_bridge_project_not_found"></a>`DOWNSTREAM_BRIDGE_PROJECT_NOT_FOUND` | A job that failed due to downstream bridge project not found. | +| <a id="cijobfailurereasondownstream_pipeline_creation_failed"></a>`DOWNSTREAM_PIPELINE_CREATION_FAILED` | A job that failed due to downstream pipeline creation failed. | +| <a id="cijobfailurereasonenvironment_creation_failure"></a>`ENVIRONMENT_CREATION_FAILURE` | A job that failed due to environment creation failure. | +| <a id="cijobfailurereasonfailed_outdated_deployment_job"></a>`FAILED_OUTDATED_DEPLOYMENT_JOB` | A job that failed due to failed outdated deployment job. | +| <a id="cijobfailurereasonforward_deployment_failure"></a>`FORWARD_DEPLOYMENT_FAILURE` | A job that failed due to forward deployment failure. | +| <a id="cijobfailurereasoninsufficient_bridge_permissions"></a>`INSUFFICIENT_BRIDGE_PERMISSIONS` | A job that failed due to insufficient bridge permissions. | +| <a id="cijobfailurereasoninsufficient_upstream_permissions"></a>`INSUFFICIENT_UPSTREAM_PERMISSIONS` | A job that failed due to insufficient upstream permissions. | +| <a id="cijobfailurereasoninvalid_bridge_trigger"></a>`INVALID_BRIDGE_TRIGGER` | A job that failed due to invalid bridge trigger. | +| <a id="cijobfailurereasonip_restriction_failure"></a>`IP_RESTRICTION_FAILURE` | A job that failed due to ip restriction failure. | +| <a id="cijobfailurereasonjob_execution_timeout"></a>`JOB_EXECUTION_TIMEOUT` | A job that failed due to job execution timeout. | +| <a id="cijobfailurereasonmissing_dependency_failure"></a>`MISSING_DEPENDENCY_FAILURE` | A job that failed due to missing dependency failure. | +| <a id="cijobfailurereasonno_matching_runner"></a>`NO_MATCHING_RUNNER` | A job that failed due to no matching runner. | +| <a id="cijobfailurereasonpipeline_loop_detected"></a>`PIPELINE_LOOP_DETECTED` | A job that failed due to pipeline loop detected. | +| <a id="cijobfailurereasonproject_deleted"></a>`PROJECT_DELETED` | A job that failed due to project deleted. | +| <a id="cijobfailurereasonprotected_environment_failure"></a>`PROTECTED_ENVIRONMENT_FAILURE` | A job that failed due to protected environment failure. | +| <a id="cijobfailurereasonreached_max_descendant_pipelines_depth"></a>`REACHED_MAX_DESCENDANT_PIPELINES_DEPTH` | A job that failed due to reached max descendant pipelines depth. | +| <a id="cijobfailurereasonreached_max_pipeline_hierarchy_size"></a>`REACHED_MAX_PIPELINE_HIERARCHY_SIZE` | A job that failed due to reached max pipeline hierarchy size. | +| <a id="cijobfailurereasonrunner_system_failure"></a>`RUNNER_SYSTEM_FAILURE` | A job that failed due to runner system failure. | +| <a id="cijobfailurereasonrunner_unsupported"></a>`RUNNER_UNSUPPORTED` | A job that failed due to runner unsupported. | +| <a id="cijobfailurereasonscheduler_failure"></a>`SCHEDULER_FAILURE` | A job that failed due to scheduler failure. | +| <a id="cijobfailurereasonscript_failure"></a>`SCRIPT_FAILURE` | A job that failed due to script failure. | +| <a id="cijobfailurereasonsecrets_provider_not_found"></a>`SECRETS_PROVIDER_NOT_FOUND` | A job that failed due to secrets provider not found. | +| <a id="cijobfailurereasonstale_schedule"></a>`STALE_SCHEDULE` | A job that failed due to stale schedule. | +| <a id="cijobfailurereasonstuck_or_timeout_failure"></a>`STUCK_OR_TIMEOUT_FAILURE` | A job that failed due to stuck or timeout failure. | +| <a id="cijobfailurereasontrace_size_exceeded"></a>`TRACE_SIZE_EXCEEDED` | A job that failed due to trace size exceeded. | +| <a id="cijobfailurereasonunknown_failure"></a>`UNKNOWN_FAILURE` | A job that failed due to unknown failure. | +| <a id="cijobfailurereasonunmet_prerequisites"></a>`UNMET_PREREQUISITES` | A job that failed due to unmet prerequisites. | +| <a id="cijobfailurereasonupstream_bridge_project_not_found"></a>`UPSTREAM_BRIDGE_PROJECT_NOT_FOUND` | A job that failed due to upstream bridge project not found. | +| <a id="cijobfailurereasonuser_blocked"></a>`USER_BLOCKED` | A job that failed due to user blocked. | + ### `CiJobKind` | Value | Description | diff --git a/doc/architecture/blueprints/cells/diagrams/cells-and-fulfillment.drawio.png b/doc/architecture/blueprints/cells/diagrams/cells-and-fulfillment.drawio.png Binary files differindex c5fff9dbca5..fc32c694ddc 100644 --- a/doc/architecture/blueprints/cells/diagrams/cells-and-fulfillment.drawio.png +++ b/doc/architecture/blueprints/cells/diagrams/cells-and-fulfillment.drawio.png diff --git a/doc/architecture/blueprints/cells/diagrams/term-cell.drawio.png b/doc/architecture/blueprints/cells/diagrams/term-cell.drawio.png Binary files differindex 84a6d6d1745..639026c801f 100644 --- a/doc/architecture/blueprints/cells/diagrams/term-cell.drawio.png +++ b/doc/architecture/blueprints/cells/diagrams/term-cell.drawio.png diff --git a/doc/architecture/blueprints/cells/diagrams/term-cluster.drawio.png b/doc/architecture/blueprints/cells/diagrams/term-cluster.drawio.png Binary files differindex a6fd790ba5e..c5e3a0f7c71 100644 --- a/doc/architecture/blueprints/cells/diagrams/term-cluster.drawio.png +++ b/doc/architecture/blueprints/cells/diagrams/term-cluster.drawio.png diff --git a/doc/architecture/blueprints/cells/diagrams/term-organization.drawio.png b/doc/architecture/blueprints/cells/diagrams/term-organization.drawio.png Binary files differindex f1cb7cd92fe..9bfdba43309 100644 --- a/doc/architecture/blueprints/cells/diagrams/term-organization.drawio.png +++ b/doc/architecture/blueprints/cells/diagrams/term-organization.drawio.png diff --git a/doc/architecture/blueprints/cells/diagrams/term-top-level-group.drawio.png b/doc/architecture/blueprints/cells/diagrams/term-top-level-group.drawio.png Binary files differindex f5535409945..c8f6393f9fc 100644 --- a/doc/architecture/blueprints/cells/diagrams/term-top-level-group.drawio.png +++ b/doc/architecture/blueprints/cells/diagrams/term-top-level-group.drawio.png diff --git a/doc/architecture/blueprints/code_search_with_zoekt/diagrams/sharding_proposal_2023-08.drawio.png b/doc/architecture/blueprints/code_search_with_zoekt/diagrams/sharding_proposal_2023-08.drawio.png Binary files differindex 0927a144454..c45745c9dd2 100644 --- a/doc/architecture/blueprints/code_search_with_zoekt/diagrams/sharding_proposal_2023-08.drawio.png +++ b/doc/architecture/blueprints/code_search_with_zoekt/diagrams/sharding_proposal_2023-08.drawio.png diff --git a/doc/architecture/blueprints/gitaly_adaptive_concurrency_limit/adaptive_concurrency_limit_flow.png b/doc/architecture/blueprints/gitaly_adaptive_concurrency_limit/adaptive_concurrency_limit_flow.png Binary files differindex 0475a32e933..ce6bb1a8dfc 100644 --- a/doc/architecture/blueprints/gitaly_adaptive_concurrency_limit/adaptive_concurrency_limit_flow.png +++ b/doc/architecture/blueprints/gitaly_adaptive_concurrency_limit/adaptive_concurrency_limit_flow.png diff --git a/doc/ci/migration/examples/img/maven-freestyle-plugin.png b/doc/ci/migration/examples/img/maven-freestyle-plugin.png Binary files differindex ab3ece9bf5f..f6d6de2b8c0 100644 --- a/doc/ci/migration/examples/img/maven-freestyle-plugin.png +++ b/doc/ci/migration/examples/img/maven-freestyle-plugin.png diff --git a/doc/ci/migration/examples/img/maven-freestyle-shell.png b/doc/ci/migration/examples/img/maven-freestyle-shell.png Binary files differindex f60d5320cb9..1bb213582cc 100644 --- a/doc/ci/migration/examples/img/maven-freestyle-shell.png +++ b/doc/ci/migration/examples/img/maven-freestyle-shell.png diff --git a/doc/operations/img/tracing_details_v16_3.png b/doc/operations/img/tracing_details_v16_3.png Binary files differindex b6d0e89c6ec..2b371228cec 100644 --- a/doc/operations/img/tracing_details_v16_3.png +++ b/doc/operations/img/tracing_details_v16_3.png diff --git a/doc/user/analytics/img/issues_closed_analytics_v16_4.png b/doc/user/analytics/img/issues_closed_analytics_v16_4.png Binary files differindex e3002928b68..5e1fe4eaa8c 100644 --- a/doc/user/analytics/img/issues_closed_analytics_v16_4.png +++ b/doc/user/analytics/img/issues_closed_analytics_v16_4.png diff --git a/doc/user/analytics/index.md b/doc/user/analytics/index.md index 023c1cd81fc..8142f390c3b 100644 --- a/doc/user/analytics/index.md +++ b/doc/user/analytics/index.md @@ -7,6 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Analyze GitLab usage **(FREE ALL)** GitLab provides different types of analytics insights at the instance, group, and project level. +These insights appear on the left sidebar, under [**Analyze**](../project/settings/index.md#remove-project-analytics-from-the-left-sidebar). ## Instance-level analytics diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index 9152f01fffb..6d996446b76 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -225,7 +225,7 @@ or [purchase additional storage](https://about.gitlab.com/pricing/licensing-faq/ NOTE: `git push` and GitLab project imports are limited to 5 GB per request through -Cloudflare. Git LFS and imports other than a file upload are not affected by +Cloudflare. Imports other than a file upload are not affected by this limit. Repository limits apply to both public and private projects. ## Default import sources diff --git a/doc/user/group/issues_analytics/img/issues_closed_analytics_v16_4.png b/doc/user/group/issues_analytics/img/issues_closed_analytics_v16_4.png Binary files differindex e3002928b68..5e1fe4eaa8c 100644 --- a/doc/user/group/issues_analytics/img/issues_closed_analytics_v16_4.png +++ b/doc/user/group/issues_analytics/img/issues_closed_analytics_v16_4.png diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md index 973a689a554..277c43a14cd 100644 --- a/doc/user/project/working_with_projects.md +++ b/doc/user/project/working_with_projects.md @@ -420,6 +420,12 @@ download starts, the `insteadOf` configuration sends the traffic to the secondar When working with projects, you might encounter the following issues, or require alternate methods to complete specific tasks. +### `An error occurred while fetching commit data` + +When you visit a project, the message `An error occurred while fetching commit data` might be displayed +if you use an ad blocker in your browser. The solution is to disable your ad blocker +for the GitLab instance you are trying to access. + ### Find projects using an SQL query While in [a Rails console session](../../administration/operations/rails_console.md#starting-a-rails-console-session), you can find and store an array of projects based on a SQL query: diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb index a045a3d4828..4278510e999 100644 --- a/lib/api/concerns/packages/npm_endpoints.rb +++ b/lib/api/concerns/packages/npm_endpoints.rb @@ -197,7 +197,7 @@ module API route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true get '*package_name', format: false, requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do package_name = params[:package_name] - packages = + available_packages = if Feature.enabled?(:npm_allow_packages_in_multiple_projects) finder_for_endpoint_scope(package_name).execute else @@ -205,7 +205,8 @@ module API .execute end - redirect_request = project_or_nil.blank? || packages.empty? + # In order to redirect a request, packages should not exist (without taking the user into account). + redirect_request = project_or_nil.blank? || available_packages.empty? redirect_registry_request( forward_to_registry: redirect_request, @@ -213,9 +214,25 @@ module API target: project_or_nil, package_name: package_name ) do - authorize_read_package!(project) + if endpoint_scope == :project || Feature.disabled?(:npm_allow_packages_in_multiple_projects) + authorize_read_package!(project) + elsif Feature.enabled?(:npm_allow_packages_in_multiple_projects) + available_packages_to_user = ::Packages::Npm::PackagesForUserFinder.new( + current_user, + group_or_namespace, + package_name: params[:package_name] + ).execute + + if available_packages.any? && available_packages_to_user.empty? + forbidden! if current_user + + not_found!('Packages') + end + + available_packages = available_packages_to_user + end - not_found!('Packages') if packages.empty? + not_found!('Packages') if available_packages.empty? if endpoint_scope == :project && Feature.enabled?(:npm_metadata_cache, project) if metadata_cache&.file&.exists? @@ -228,7 +245,7 @@ module API enqueue_sync_metadata_cache_worker(project, package_name) end - metadata = generate_metadata_service(packages).execute.payload + metadata = generate_metadata_service(available_packages).execute.payload present metadata, with: ::API::Entities::NpmPackage end end diff --git a/lib/api/npm_group_packages.rb b/lib/api/npm_group_packages.rb index 1aa3135b186..f2b8e1840a1 100644 --- a/lib/api/npm_group_packages.rb +++ b/lib/api/npm_group_packages.rb @@ -11,6 +11,10 @@ module API def endpoint_scope :group end + + def group_or_namespace + group + end end params do diff --git a/lib/api/npm_instance_packages.rb b/lib/api/npm_instance_packages.rb index ea92818e76c..1805edceb2c 100644 --- a/lib/api/npm_instance_packages.rb +++ b/lib/api/npm_instance_packages.rb @@ -10,6 +10,10 @@ module API def endpoint_scope :instance end + + def group_or_namespace + top_namespace_from(params[:package_name]) + end end namespace 'packages/npm' do diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb index dbcca2c1a33..9fedb2d174f 100644 --- a/lib/gitlab/sql/pattern.rb +++ b/lib/gitlab/sql/pattern.rb @@ -9,12 +9,28 @@ module Gitlab REGEX_QUOTED_TERM = /(?<=\A| )"[^"]+"(?= |\z)/ class_methods do - def fuzzy_search(query, columns, use_minimum_char_limit: true) + def fuzzy_search(query, columns, use_minimum_char_limit: true, exact_matches_first: false) matches = columns.map do |col| fuzzy_arel_match(col, query, use_minimum_char_limit: use_minimum_char_limit) end.compact.reduce(:or) - where(matches) + matches = where(matches) + + return matches unless exact_matches_first + + matches.order(exact_matches_first_sql(query, columns)) + end + + def exact_matches_first_sql(query, columns) + cases_sql = columns.map do |column| + arel_column = column.is_a?(Arel::Attributes::Attribute) ? column : arel_table[column] + match_sql = arel_column.matches(sanitize_sql_like(query)).to_sql + "WHEN #{match_sql} THEN 1" + end + + cases_sql << "ELSE 2" + + Arel.sql("CASE\n#{cases_sql.join("\n")}\nEND") end def to_pattern(query, use_minimum_char_limit: true) diff --git a/lib/gitlab/utils/override.rb b/lib/gitlab/utils/override.rb index 10370811bb5..9bc37746349 100644 --- a/lib/gitlab/utils/override.rb +++ b/lib/gitlab/utils/override.rb @@ -3,6 +3,9 @@ require 'gitlab/utils/all' require_relative '../environment' +# See https://docs.gitlab.com/ee/development/utilities.html#override for +# more information + module Gitlab module Utils module Override diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6856142418c..a92f8978c2b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -12341,6 +12341,9 @@ msgstr "" msgid "Configuration help" msgstr "" +msgid "Configure" +msgstr "" + msgid "Configure %{italic_start}What's new%{italic_end} drawer and content." msgstr "" @@ -21271,6 +21274,9 @@ msgstr "" msgid "Get a support subscription" msgstr "" +msgid "Get free trial" +msgstr "" + msgid "Get more information about troubleshooting pipelines" msgstr "" diff --git a/spec/finders/ci/jobs_finder_spec.rb b/spec/finders/ci/jobs_finder_spec.rb index e86ac65df61..57046baafab 100644 --- a/spec/finders/ci/jobs_finder_spec.rb +++ b/spec/finders/ci/jobs_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::JobsFinder, '#execute', feature_category: :runner_fleet do +RSpec.describe Ci::JobsFinder, '#execute', feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:user, :admin) } let_it_be(:project) { create(:project, :private, public_builds: false) } @@ -13,8 +13,8 @@ RSpec.describe Ci::JobsFinder, '#execute', feature_category: :runner_fleet do let(:params) { {} } - context 'when project, pipeline or runner are blank' do - subject { described_class.new(current_user: current_user, params: params).execute } + context 'when project, pipeline, and runner are blank' do + subject(:finder_execute) { described_class.new(current_user: current_user, params: params).execute } context 'with admin' do let(:current_user) { admin } @@ -278,28 +278,30 @@ RSpec.describe Ci::JobsFinder, '#execute', feature_category: :runner_fleet do let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) } let_it_be(:job_4) { create(:ci_build, :success, runner: runner) } - subject { described_class.new(current_user: user, runner: runner, params: params).execute } + subject(:execute) { described_class.new(current_user: user, runner: runner, params: params).execute } - context 'with admin and admin mode enabled', :enable_admin_mode do + context 'when current user is an admin' do let(:user) { admin } - it 'returns jobs for the specified project' do - expect(subject).to match_array([job_4]) - end + context 'when admin mode is enabled', :enable_admin_mode do + it 'returns jobs for the specified project' do + expect(subject).to contain_exactly job_4 + end - context "with params" do - using RSpec::Parameterized::TableSyntax + context 'with params' do + using RSpec::Parameterized::TableSyntax - where(:param_runner_type, :param_scope, :expected_jobs) do - 'project_type' | 'success' | lazy { [job_4] } - 'instance_type' | nil | lazy { [] } - nil | 'pending' | lazy { [] } - end + where(:param_runner_type, :param_scope, :expected_jobs) do + 'project_type' | 'success' | lazy { [job_4] } + 'instance_type' | nil | lazy { [] } + nil | 'pending' | lazy { [] } + end - with_them do - let(:params) { { runner_type: param_runner_type, scope: param_scope } } + with_them do + let(:params) { { runner_type: param_runner_type, scope: param_scope } } - it { is_expected.to match_array(expected_jobs) } + it { is_expected.to match_array(expected_jobs) } + end end end end diff --git a/spec/finders/groups/accepting_group_transfers_finder_spec.rb b/spec/finders/groups/accepting_group_transfers_finder_spec.rb index 18407dd0196..2a61150acf9 100644 --- a/spec/finders/groups/accepting_group_transfers_finder_spec.rb +++ b/spec/finders/groups/accepting_group_transfers_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::AcceptingGroupTransfersFinder do +RSpec.describe Groups::AcceptingGroupTransfersFinder, feature_category: :groups_and_projects do let_it_be(:current_user) { create(:user) } let_it_be(:great_grandparent_group) do @@ -119,6 +119,37 @@ RSpec.describe Groups::AcceptingGroupTransfersFinder do expect(result).to contain_exactly(great_grandparent_group) end end + + context 'on searching with multiple matches' do + let(:params) { { search: 'great-grandparent-group' } } + let(:other_groups) { [] } + + before do + 2.times do + # app/finders/group/base.rb adds an ORDER BY path, so create a group with 1 in the front. + group = create(:group, parent: great_grandparent_group, path: "1-#{SecureRandom.hex}") + group.add_owner(current_user) + other_groups << group + end + end + + it 'prioritizes exact matches first' do + expect(result.first).to eq(great_grandparent_group) + expect(result[1..]).to match_array(other_groups) + end + + context 'when exact_matches_first_group_transfer feature flag is disabled' do + let(:expected_groups) { other_groups + [great_grandparent_group] } + + before do + stub_feature_flags(exact_matches_first_group_transfer: false) + end + + it 'returns matching groups sorted by namespace path' do + expect(result).to match_array(expected_groups.sort_by(&:path)) + end + end + end end end end diff --git a/spec/finders/packages/npm/packages_for_user_finder_spec.rb b/spec/finders/packages/npm/packages_for_user_finder_spec.rb new file mode 100644 index 00000000000..e2dc21e1008 --- /dev/null +++ b/spec/finders/packages/npm/packages_for_user_finder_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Packages::Npm::PackagesForUserFinder, feature_category: :package_registry do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:project2) { create(:project, group: group) } + let_it_be(:package) { create(:npm_package, project: project) } + let_it_be(:package_name) { package.name } + let_it_be(:package_with_diff_name) { create(:npm_package, project: project) } + let_it_be(:package_with_diff_project) { create(:npm_package, name: package_name, project: project2) } + let_it_be(:maven_package) { create(:maven_package, name: package_name, project: project) } + + let(:finder) { described_class.new(user, project_or_group, package_name: package_name) } + + describe '#execute' do + subject { finder.execute } + + shared_examples 'searches for packages' do + it { is_expected.to contain_exactly(package) } + end + + context 'with a project' do + let(:project_or_group) { project } + + it_behaves_like 'searches for packages' + end + + context 'with a group' do + let(:project_or_group) { group } + + before_all do + project.add_reporter(user) + end + + it_behaves_like 'searches for packages' + end + end +end diff --git a/spec/graphql/resolvers/ci/all_jobs_resolver_spec.rb b/spec/graphql/resolvers/ci/all_jobs_resolver_spec.rb index 933abf31470..6b9e3a484b1 100644 --- a/spec/graphql/resolvers/ci/all_jobs_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/all_jobs_resolver_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Resolvers::Ci::AllJobsResolver, feature_category: :continuous_integration do include GraphqlHelpers + let_it_be(:instance_runner) { create(:ci_runner, :instance) } let_it_be(:successful_job) { create(:ci_build, :success, name: 'successful_job') } let_it_be(:successful_job_two) { create(:ci_build, :success, name: 'successful_job_two') } let_it_be(:failed_job) { create(:ci_build, :failed, name: 'failed_job') } @@ -12,11 +13,11 @@ RSpec.describe Resolvers::Ci::AllJobsResolver, feature_category: :continuous_int let(:args) { {} } - subject { resolve_jobs(args) } - describe '#resolve' do - context 'with admin' do - let(:current_user) { create(:admin) } + subject(:request) { resolve_jobs(args) } + + context 'when current user is an admin' do + let_it_be(:current_user) { create(:admin) } shared_examples 'executes as admin' do context "with argument `statuses`" do @@ -40,8 +41,7 @@ RSpec.describe Resolvers::Ci::AllJobsResolver, feature_category: :continuous_int context "with argument `runner_types`" do let_it_be(:successful_job_with_instance_runner) do - create(:ci_build, :success, name: 'successful_job_with_instance_runner', - runner: create(:ci_runner, :instance)) + create(:ci_build, :success, name: 'successful_job_with_instance_runner', runner: instance_runner) end context 'with feature flag :admin_jobs_filter_runner_type enabled' do @@ -80,7 +80,7 @@ RSpec.describe Resolvers::Ci::AllJobsResolver, feature_category: :continuous_int :ci_build, :success, name: 'successful_job_with_instance_runner', - runner: create(:ci_runner, :instance) + runner: instance_runner ) end @@ -132,7 +132,9 @@ RSpec.describe Resolvers::Ci::AllJobsResolver, feature_category: :continuous_int end context 'with unauthorized user' do - let(:current_user) { nil } + let_it_be(:unauth_user) { create(:user) } + + let(:current_user) { unauth_user } it { is_expected.to be_empty } end diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb index a34ddf8773c..7bd2ddf2889 100644 --- a/spec/lib/gitlab/sql/pattern_spec.rb +++ b/spec/lib/gitlab/sql/pattern_spec.rb @@ -9,36 +9,44 @@ RSpec.describe Gitlab::SQL::Pattern do let_it_be(:issue1) { create(:issue, title: 'noise foo noise', description: 'noise bar noise') } let_it_be(:issue2) { create(:issue, title: 'noise baz noise', description: 'noise foo noise') } let_it_be(:issue3) { create(:issue, title: 'Oh', description: 'Ah') } + let_it_be(:issue4) { create(:issue, title: 'beep beep', description: 'beep beep') } + let_it_be(:issue5) { create(:issue, title: 'beep', description: 'beep') } - subject(:fuzzy_search) { Issue.fuzzy_search(query, columns) } + subject(:fuzzy_search) { Issue.fuzzy_search(query, columns, exact_matches_first: exact_matches_first) } - where(:query, :columns, :expected) do - 'foo' | [Issue.arel_table[:title]] | %i[issue1] + where(:query, :columns, :exact_matches_first, :expected) do + 'foo' | [Issue.arel_table[:title]] | false | %i[issue1] - 'foo' | %i[title] | %i[issue1] - 'foo' | %w[title] | %i[issue1] - 'foo' | %i[description] | %i[issue2] - 'foo' | %i[title description] | %i[issue1 issue2] - 'bar' | %i[title description] | %i[issue1] - 'baz' | %i[title description] | %i[issue2] - 'qux' | %i[title description] | [] + 'foo' | %i[title] | false | %i[issue1] + 'foo' | %w[title] | false | %i[issue1] + 'foo' | %i[description] | false | %i[issue2] + 'foo' | %i[title description] | false | %i[issue1 issue2] + 'bar' | %i[title description] | false | %i[issue1] + 'baz' | %i[title description] | false | %i[issue2] + 'qux' | %i[title description] | false | [] - 'oh' | %i[title description] | %i[issue3] - 'OH' | %i[title description] | %i[issue3] - 'ah' | %i[title description] | %i[issue3] - 'AH' | %i[title description] | %i[issue3] - 'oh' | %i[title] | %i[issue3] - 'ah' | %i[description] | %i[issue3] + 'oh' | %i[title description] | false | %i[issue3] + 'OH' | %i[title description] | false | %i[issue3] + 'ah' | %i[title description] | false | %i[issue3] + 'AH' | %i[title description] | false | %i[issue3] + 'oh' | %i[title] | false | %i[issue3] + 'ah' | %i[description] | false | %i[issue3] - '' | %i[title] | %i[issue1 issue2 issue3] - %w[a b] | %i[title] | %i[issue1 issue2 issue3] + '' | %i[title] | false | %i[issue1 issue2 issue3 issue4 issue5] + %w[a b] | %i[title] | false | %i[issue1 issue2 issue3 issue4 issue5] + + 'beep' | %i[title] | true | %i[issue5 issue4] end with_them do let(:expected_issues) { expected.map { |sym| send(sym) } } it 'finds the expected issues' do - expect(fuzzy_search).to match_array(expected_issues) + if exact_matches_first + expect(fuzzy_search).to eq(expected_issues) + else + expect(fuzzy_search).to match_array(expected_issues) + end end end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index a03dac83113..37d967cbc7c 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -1079,17 +1079,30 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do end it 'defaults use_minimum_char_limit to true' do - expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: true).once + expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: true, exact_matches_first: false).once described_class.search('my namespace') end it 'passes use_minimum_char_limit if it is set' do - expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: false).once + expect(described_class).to receive(:fuzzy_search).with(anything, anything, use_minimum_char_limit: false, exact_matches_first: false).once described_class.search('my namespace', use_minimum_char_limit: false) end + context 'with multiple matching namespaces' do + let_it_be(:first_group) { create(:group, name: 'some name', path: 'z-path') } + let_it_be(:second_group) { create(:group, name: 'some name too', path: 'a-path') } + + it 'returns exact matches first' do + expect(described_class.search('some name', exact_matches_first: true).to_a).to eq([first_group, second_group]) + end + + it 'returns exact matches first when parents are included' do + expect(described_class.search('some name', include_parents: true, exact_matches_first: true).to_a).to eq([first_group, second_group]) + end + end + context 'with project namespaces' do let_it_be(:project) { create(:project, namespace: parent_group, path: 'some-new-path') } let_it_be(:project_namespace) { project.project_namespace } diff --git a/spec/requests/api/npm_group_packages_spec.rb b/spec/requests/api/npm_group_packages_spec.rb index 6968e30e2b0..7fba75b0630 100644 --- a/spec/requests/api/npm_group_packages_spec.rb +++ b/spec/requests/api/npm_group_packages_spec.rb @@ -11,43 +11,12 @@ RSpec.describe API::NpmGroupPackages, feature_category: :package_registry do let(:url) { api("/groups/#{group.id}/-/packages/npm/#{package_name}") } it_behaves_like 'handling get metadata requests', scope: :group - - context 'with a duplicate package name in another project' do + it_behaves_like 'rejects invalid package names' do subject { get(url) } - - before do - group.add_developer(user) - end - - let_it_be(:project2) { create(:project, :public, namespace: namespace) } - let_it_be(:package2) do - create(:npm_package, - project: project2, - name: "@#{group.path}/scoped_package", - version: '1.2.0') - end - - it_behaves_like 'rejects invalid package names' - - it 'includes all matching package versions in the response' do - subject - - expect(json_response['versions'].keys).to match_array([package.version, package2.version]) - end - - context 'with the feature flag disabled' do - before do - stub_feature_flags(npm_allow_packages_in_multiple_projects: false) - end - - it 'returns matching package versions from only one project' do - subject - - expect(json_response['versions'].keys).to match_array([package2.version]) - end - end end + it_behaves_like 'handling get metadata requests for packages in multiple projects' + context 'with mixed group and project visibilities' do subject { get(url, headers: headers) } diff --git a/spec/requests/api/npm_instance_packages_spec.rb b/spec/requests/api/npm_instance_packages_spec.rb index 4f965d86d66..7b74a052860 100644 --- a/spec/requests/api/npm_instance_packages_spec.rb +++ b/spec/requests/api/npm_instance_packages_spec.rb @@ -17,34 +17,7 @@ RSpec.describe API::NpmInstancePackages, feature_category: :package_registry do it_behaves_like 'handling get metadata requests', scope: :instance it_behaves_like 'rejects invalid package names' - - context 'with a duplicate package name in another project' do - let_it_be(:project2) { create(:project, :public, namespace: namespace) } - let_it_be(:package2) do - create(:npm_package, - project: project2, - name: "@#{group.path}/scoped_package", - version: '1.2.0') - end - - it 'includes all matching package versions in the response' do - subject - - expect(json_response['versions'].keys).to match_array([package.version, package2.version]) - end - - context 'with the feature flag disabled' do - before do - stub_feature_flags(npm_allow_packages_in_multiple_projects: false) - end - - it 'returns matching package versions from only one project' do - subject - - expect(json_response['versions'].keys).to match_array([package2.version]) - end - end - end + it_behaves_like 'handling get metadata requests for packages in multiple projects' context 'when metadata cache exists' do let_it_be(:npm_metadata_cache) { create(:npm_metadata_cache, package_name: package.name, project_id: project.id) } diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb index db547fd7838..5f043cdd996 100644 --- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb @@ -876,3 +876,67 @@ RSpec.shared_examples 'rejects invalid package names' do expect(Gitlab::Json.parse(response.body)).to eq({ 'error' => 'package_name should be a valid file path' }) end end + +RSpec.shared_examples 'handling get metadata requests for packages in multiple projects' do + let_it_be(:project2) { create(:project, namespace: namespace) } + let_it_be(:package2) do + create(:npm_package, + project: project2, + name: "@#{group.path}/scoped_package", + version: '1.2.0') + end + + let(:headers) { build_token_auth_header(personal_access_token.token) } + + subject { get(url, headers: headers) } + + before_all do + project.update!(visibility: 'private') + + group.add_guest(user) + project.add_reporter(user) + project2.add_reporter(user) + end + + it 'includes all matching package versions in the response' do + subject + + expect(json_response['versions'].keys).to match_array([package.version, package2.version]) + end + + context 'with the feature flag disabled' do + before do + stub_feature_flags(npm_allow_packages_in_multiple_projects: false) + end + + it 'returns matching package versions from only one project' do + subject + + expect(json_response['versions'].keys).to match_array([package2.version]) + end + end + + context 'with limited access to the project with the last package version' do + before_all do + project2.add_guest(user) + end + + it 'includes matching package versions from authorized projects in the response' do + subject + + expect(json_response['versions'].keys).to contain_exactly(package.version) + end + end + + context 'with limited access to the project with the first package version' do + before do + project.add_guest(user) + end + + it 'includes matching package versions from authorized projects in the response' do + subject + + expect(json_response['versions'].keys).to contain_exactly(package2.version) + end + end +end diff --git a/spec/views/layouts/header/_super_sidebar_logged_out.html.haml_spec.rb b/spec/views/layouts/header/_super_sidebar_logged_out.html.haml_spec.rb index 89a03d72a90..f81e8c5badf 100644 --- a/spec/views/layouts/header/_super_sidebar_logged_out.html.haml_spec.rb +++ b/spec/views/layouts/header/_super_sidebar_logged_out.html.haml_spec.rb @@ -19,6 +19,10 @@ RSpec.describe 'layouts/header/_super_sidebar_logged_out', feature_category: :na expect(rendered).to have_content('Pricing') expect(rendered).to have_content('Contact Sales') end + + it 'renders the free trial button' do + expect(rendered).to have_content('Get free trial') + end end context 'on self-managed' do |