diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index c93ef2287a8..3512f059911 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -92,8 +92,7 @@
@include transition(background-color, border-color, color, box-shadow);
}
-.dropdown-menu-toggle,
-.header-user-avatar {
+.dropdown-menu-toggle {
@include transition(border-color);
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 0ae88f579e6..e791a0dbbbd 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -810,14 +810,6 @@
.navbar-gitlab {
li.dropdown {
position: static;
-
- &.user-counter {
- margin-left: 8px !important;
-
- > a {
- padding: 0 4px !important;
- }
- }
}
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 9608e294d30..2265d425e1f 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -98,12 +98,6 @@
.container-fluid {
padding: 0;
- .user-counter {
- svg {
- margin-right: 3px;
- }
- }
-
.navbar-nav {
@include media-breakpoint-down(xs) {
display: flex;
@@ -120,12 +114,6 @@
}
.nav > li {
- &.header-user {
- @include media-breakpoint-down(xs) {
- padding-left: 10px;
- }
- }
-
> a {
will-change: color;
margin: 4px 0;
@@ -137,38 +125,11 @@
padding: 0;
}
}
-
- &.header-user-dropdown-toggle {
- margin-left: 2px;
-
- .header-user-avatar {
- margin-right: 0;
- }
- }
}
.header-new-dropdown-toggle {
margin-right: 0;
}
-
- .impersonated-user,
- .impersonated-user:hover {
- margin-right: 1px;
- background-color: $white;
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- }
-
- .impersonation-btn,
- .impersonation-btn:hover {
- background-color: $white;
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
-
- svg {
- color: $orange-500;
- }
- }
}
}
}
@@ -223,10 +184,6 @@
top: -1px;
font-size: 10px;
}
-
- .impersonation i {
- color: $red-500;
- }
}
.caret-down,
@@ -238,7 +195,6 @@
fill: currentColor;
}
-.header-user .dropdown-menu,
.header-new .dropdown-menu {
margin-top: $dropdown-vertical-offset;
}
@@ -304,44 +260,6 @@
}
}
}
-
- .header-user-dropdown-toggle {
- text-align: center;
- }
-
- .header-user-avatar {
- float: none;
- }
-}
-
-.header-user {
- &.show .dropdown-menu {
- margin-top: 4px;
- color: var(--gl-text-color, $gl-text-color);
- left: auto;
- max-height: $dropdown-max-height-lg;
-
- .user-status {
- max-width: 240px;
- }
-
- svg {
- vertical-align: text-top;
- }
-
- a.ci-minutes-emoji gl-emoji,
- a.trial-link gl-emoji {
- font-size: $gl-font-size;
- vertical-align: baseline;
- }
- }
-}
-
-.header-user-avatar {
- float: left;
- margin-right: 5px;
- border-radius: 50%;
- border: 1px solid $gray-normal;
}
.notification-dot {
diff --git a/app/controllers/explore/catalog_controller.rb b/app/controllers/explore/catalog_controller.rb
index 50846c21b1b..0c78c1fbdb8 100644
--- a/app/controllers/explore/catalog_controller.rb
+++ b/app/controllers/explore/catalog_controller.rb
@@ -3,7 +3,6 @@
module Explore
class CatalogController < Explore::ApplicationController
feature_category :pipeline_composition
- before_action :check_feature_flag
before_action :check_resource_access, only: :show
def show; end
@@ -14,10 +13,6 @@ module Explore
private
- def check_feature_flag
- render_404 unless Feature.enabled?(:global_ci_catalog, current_user)
- end
-
def check_resource_access
render_404 unless catalog_resource.present?
end
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 7059e2a0371..4c93c738484 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -41,6 +41,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:color_scheme_id,
:diffs_deletion_color,
:diffs_addition_color,
+ :home_organization_id,
:layout,
:dashboard,
:project_view,
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 7371902a6bd..7851e2ac80b 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -52,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController
push_frontend_feature_flag(:blob_blame_info, @project)
push_frontend_feature_flag(:highlight_js_worker, @project)
push_frontend_feature_flag(:explain_code_chat, current_user)
+ push_frontend_feature_flag(:encoding_logs_tree)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
end
diff --git a/app/controllers/projects/service_desk_controller.rb b/app/controllers/projects/service_desk_controller.rb
index 70cb439c4f3..a53e8859ee6 100644
--- a/app/controllers/projects/service_desk_controller.rb
+++ b/app/controllers/projects/service_desk_controller.rb
@@ -29,7 +29,13 @@ class Projects::ServiceDeskController < Projects::ApplicationController
end
def allowed_update_attributes
- %i[issue_template_key outgoing_name project_key add_external_participants_from_cc]
+ %i[
+ issue_template_key
+ outgoing_name
+ project_key
+ reopen_issue_on_external_participant_note
+ add_external_participants_from_cc
+ ]
end
def service_desk_attributes
@@ -42,6 +48,7 @@ class Projects::ServiceDeskController < Projects::ApplicationController
template_file_missing: service_desk_settings&.issue_template_missing?,
outgoing_name: service_desk_settings&.outgoing_name,
project_key: service_desk_settings&.project_key,
+ reopen_issue_on_external_participant_note: service_desk_settings&.reopen_issue_on_external_participant_note,
add_external_participants_from_cc: service_desk_settings&.add_external_participants_from_cc
}
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index cfcc27edf3e..1bbf272e8f9 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -22,6 +22,7 @@ class Projects::TreeController < Projects::ApplicationController
push_frontend_feature_flag(:blob_blame_info, @project)
push_frontend_feature_flag(:highlight_js_worker, @project)
push_frontend_feature_flag(:explain_code_chat, current_user)
+ push_frontend_feature_flag(:encoding_logs_tree)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index b8b79192d3f..12decbbfeee 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -43,6 +43,7 @@ class ProjectsController < Projects::ApplicationController
push_frontend_feature_flag(:remove_monitor_metrics, @project)
push_frontend_feature_flag(:explain_code_chat, current_user)
push_frontend_feature_flag(:issue_email_participants, @project)
+ push_frontend_feature_flag(:encoding_logs_tree)
# TODO: We need to remove the FF eventually when we rollout page_specific_styles
push_frontend_feature_flag(:page_specific_styles, current_user)
push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks)
diff --git a/app/graphql/resolvers/namespaces/work_item_state_counts_resolver.rb b/app/graphql/resolvers/namespaces/work_item_state_counts_resolver.rb
new file mode 100644
index 00000000000..099b509f77f
--- /dev/null
+++ b/app/graphql/resolvers/namespaces/work_item_state_counts_resolver.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Namespaces
+ class WorkItemStateCountsResolver < WorkItemsResolver
+ type Types::WorkItemStateCountsType, null: true
+
+ def ready?(**args)
+ # The search filter is not supported for work times at the namespace level.
+ # See https://gitlab.com/gitlab-org/gitlab/-/work_items/393126
+ if args[:search]
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ 'Searching is not available for work items at the namespace level yet'
+ end
+
+ super
+ end
+
+ def resolve(**args)
+ return if resource_parent.nil?
+
+ Gitlab::IssuablesCountForState.new(
+ finder(args),
+ resource_parent,
+ store_in_redis_cache: true
+ )
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/work_item_state_counts_resolver.rb b/app/graphql/resolvers/work_item_state_counts_resolver.rb
new file mode 100644
index 00000000000..93551a57694
--- /dev/null
+++ b/app/graphql/resolvers/work_item_state_counts_resolver.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class WorkItemStateCountsResolver < WorkItemsResolver
+ type Types::WorkItemStateCountsType, null: true
+
+ def resolve(**args)
+ return if resource_parent.nil?
+
+ work_item_finder = finder(prepare_finder_params(args))
+ work_item_finder.parent_param = resource_parent
+
+ Gitlab::IssuablesCountForState.new(work_item_finder, resource_parent)
+ end
+ end
+end
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index a4eba3c63ae..7234948033b 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -275,6 +275,14 @@ module Types
description: 'Find a work item by IID directly associated with the group. Returns `null` if the ' \
'`namespace_level_work_items` feature flag is disabled.'
+ field :work_item_state_counts,
+ Types::WorkItemStateCountsType,
+ null: true,
+ alpha: { milestone: '16.7' },
+ description: 'Counts of work items by state for the namespace. Returns `null` if the ' \
+ '`namespace_level_work_items` feature flag is disabled.',
+ resolver: Resolvers::Namespaces::WorkItemStateCountsResolver
+
field :autocomplete_users,
null: true,
resolver: Resolvers::AutocompleteUsersResolver,
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index befac90ef42..8e84605cb05 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -254,6 +254,13 @@ module Types
extras: [:lookahead],
resolver: Resolvers::WorkItemsResolver
+ field :work_item_state_counts,
+ Types::WorkItemStateCountsType,
+ null: true,
+ alpha: { milestone: '16.7' },
+ description: 'Counts of work items by state for the project.',
+ resolver: Resolvers::WorkItemStateCountsResolver
+
field :issue_status_counts,
Types::IssueStatusCountsType,
null: true,
diff --git a/app/graphql/types/work_item_state_counts_type.rb b/app/graphql/types/work_item_state_counts_type.rb
new file mode 100644
index 00000000000..a5fdf542464
--- /dev/null
+++ b/app/graphql/types/work_item_state_counts_type.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes -- Parent node applies authorization
+ class WorkItemStateCountsType < BaseObject
+ graphql_name 'WorkItemStateCountsType'
+ description 'Represents total number of work items for the represented states'
+
+ field :all,
+ GraphQL::Types::Int,
+ null: true,
+ description: 'Number of work items for the project or group.'
+
+ field :closed,
+ GraphQL::Types::Int,
+ null: true,
+ description: 'Number of work items with state CLOSED for the project or group.'
+
+ field :opened,
+ GraphQL::Types::Int,
+ null: true,
+ description: 'Number of work items with state OPENED for the project or group.'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 3c64a685ba3..49230e558a8 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -315,8 +315,8 @@ module ApplicationHelper
class_names << 'issue-boards-page gl-overflow-auto' if current_controller?(:boards)
class_names << 'epic-boards-page gl-overflow-auto' if current_controller?(:epic_boards)
class_names << 'with-performance-bar' if performance_bar_enabled?
- class_names << 'with-header' if !show_super_sidebar? || !current_user
- class_names << 'with-top-bar' if show_super_sidebar? && !@hide_top_bar_padding
+ class_names << 'with-header' unless current_user
+ class_names << 'with-top-bar' unless @hide_top_bar_padding
class_names << system_message_class
class_names
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index d19b0a9a43a..3756584e3b3 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -3,18 +3,6 @@
module DashboardHelper
include IconsHelper
- def assigned_issues_dashboard_path
- issues_dashboard_path(assignee_username: current_user.username)
- end
-
- def assigned_mrs_dashboard_path
- merge_requests_dashboard_path(assignee_username: current_user.username)
- end
-
- def reviewer_mrs_dashboard_path
- merge_requests_dashboard_path(reviewer_username: current_user.username)
- end
-
def has_start_trial?
false
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 2cc83a936d8..e02b4fa0410 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -149,16 +149,6 @@ module IssuablesHelper
end
end
- def assigned_open_issues_count_text
- count = assigned_issuables_count(:issues)
-
- if count > User::MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT - 1
- "#{count - 1}+"
- else
- count.to_s
- end
- end
-
def issuable_reference(issuable)
@show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 4b561914f2f..cb9a270253f 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -40,14 +40,6 @@ module NavHelper
end
end
- def user_dropdown_class
- class_names = []
- class_names << 'header-user-dropdown-toggle'
- class_names << 'impersonated-user' if session[:impersonator_id]
-
- class_names
- end
-
def page_has_markdown?
current_path?('projects/merge_requests#show') ||
current_path?('projects/merge_requests/conflicts#show') ||
diff --git a/app/helpers/organizations/organization_helper.rb b/app/helpers/organizations/organization_helper.rb
index 533d5409ab7..d0dd9dc5aea 100644
--- a/app/helpers/organizations/organization_helper.rb
+++ b/app/helpers/organizations/organization_helper.rb
@@ -51,8 +51,7 @@ module Organizations
def home_organization_setting_app_data
{
- # TODO: use real setting - https://gitlab.com/gitlab-org/gitlab/-/issues/428668
- initial_selection: 1
+ initial_selection: current_user.home_organization_id
}.to_json
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index d053aeb7bfe..fc4d69dcdbc 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -5,10 +5,6 @@ module TodosHelper
@todos_pending_count ||= current_user.todos_pending_count
end
- def todos_count_format(count)
- count > 99 ? '99+' : count.to_s
- end
-
def todos_done_count
@todos_done_count ||= current_user.todos_done_count
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 4d73db8caac..d589c55af02 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -758,6 +758,11 @@ class Issue < ApplicationRecord
Gitlab::HookData::IssueBuilder.new(self).build
end
+ override :gfm_reference
+ def gfm_reference(from = nil)
+ "#{work_item_type_with_default.name.underscore} #{to_reference(from)}"
+ end
+
private
def project_level_readable_by?(user)
diff --git a/app/models/user.rb b/app/models/user.rb
index fd15ee035ea..d7880579092 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -400,6 +400,7 @@ class User < MainClusterwide::ApplicationRecord
:pinned_nav_items, :pinned_nav_items=,
:achievements_enabled, :achievements_enabled=,
:enabled_following, :enabled_following=,
+ :home_organization, :home_organization_id, :home_organization_id=,
to: :user_preference
delegate :path, to: :namespace, allow_nil: true, prefix: true
@@ -613,6 +614,10 @@ class User < MainClusterwide::ApplicationRecord
strip_attributes! :name
+ def user_belongs_to_organization?(organization)
+ organization_users.exists?(organization: organization)
+ end
+
def preferred_language
read_attribute('preferred_language').presence || Gitlab::CurrentSettings.default_preferred_language
end
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index aef221623e9..b8c4c8597a4 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -10,6 +10,7 @@ class UserPreference < MainClusterwide::ApplicationRecord
TIME_DISPLAY_FORMATS = { system: 0, non_iso_format: 1, iso_format: 2 }.freeze
belongs_to :user
+ belongs_to :home_organization, class_name: "Organizations::Organization", optional: true
scope :with_user, -> { joins(:user) }
scope :gitpod_enabled, -> { where(gitpod_enabled: true) }
@@ -30,6 +31,8 @@ class UserPreference < MainClusterwide::ApplicationRecord
validates :time_display_format, inclusion: { in: TIME_DISPLAY_FORMATS.values }, presence: true
+ validate :user_belongs_to_home_organization, if: :home_organization_changed?
+
# 2023-06-22 is after 16.1 release and during 16.2 release https://docs.gitlab.com/ee/development/database/avoiding_downtime_in_migrations.html#ignoring-the-column-release-m
ignore_columns :use_legacy_web_ide, remove_with: '16.2', remove_after: '2023-06-22'
@@ -52,6 +55,16 @@ class UserPreference < MainClusterwide::ApplicationRecord
end
end
+ def user_belongs_to_home_organization
+ # If we don't ignore the default organization id below then all users need to have their corresponding entry
+ # with default organization id as organization id in the `organization_users` table.
+ # Otherwise, the user won't be able to the default organization as the home organization.
+ return if home_organization_id == Organizations::Organization::DEFAULT_ORGANIZATION_ID
+ return if user.user_belongs_to_organization?(home_organization_id)
+
+ errors.add(:user, _("is not part of the given organization"))
+ end
+
def set_notes_filter(filter_id, issuable)
# No need to update the column if the value is already set.
if filter_id && NOTES_FILTERS.value?(filter_id)
diff --git a/app/services/groups/participants_service.rb b/app/services/groups/participants_service.rb
index a2238264295..7b68b435f14 100644
--- a/app/services/groups/participants_service.rb
+++ b/app/services/groups/participants_service.rb
@@ -12,8 +12,8 @@ module Groups
noteable_owner +
participants_in_noteable +
all_members +
- groups +
- group_hierarchy_users
+ group_hierarchy_users +
+ groups
render_participants_as_hash(participants.uniq)
end
diff --git a/app/services/merge_requests/create_ref_service.rb b/app/services/merge_requests/create_ref_service.rb
index eae6845335a..1e5e127072e 100644
--- a/app/services/merge_requests/create_ref_service.rb
+++ b/app/services/merge_requests/create_ref_service.rb
@@ -35,8 +35,6 @@ module MergeRequests
result = maybe_rebase!(**result)
result = maybe_merge!(**result)
- update_merge_request!(merge_request, result)
-
ServiceResponse.success(payload: result)
rescue CreateRefError => error
ServiceResponse.error(message: error.message)
@@ -118,10 +116,6 @@ module MergeRequests
).compact
end
- def update_merge_request!(merge_request, result)
- # overridden in EE
- end
-
def safe_gitaly_operation
yield
rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError, ArgumentError => error
diff --git a/app/services/namespaces/in_product_marketing_emails_service.rb b/app/services/namespaces/in_product_marketing_emails_service.rb
deleted file mode 100644
index 14e670126c6..00000000000
--- a/app/services/namespaces/in_product_marketing_emails_service.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-module Namespaces
- class InProductMarketingEmailsService
- TRACKS = {
- create: {
- interval_days: [1, 5, 10],
- completed_actions: [:created],
- incomplete_actions: [:git_write]
- },
- team_short: {
- interval_days: [1],
- completed_actions: [:git_write],
- incomplete_actions: [:user_added]
- },
- trial_short: {
- interval_days: [2],
- completed_actions: [:git_write],
- incomplete_actions: [:trial_started]
- },
- admin_verify: {
- interval_days: [3],
- completed_actions: [:git_write],
- incomplete_actions: [:pipeline_created]
- },
- verify: {
- interval_days: [4, 8, 13],
- completed_actions: [:git_write],
- incomplete_actions: [:pipeline_created]
- },
- trial: {
- interval_days: [1, 5, 10],
- completed_actions: [:git_write, :pipeline_created],
- incomplete_actions: [:trial_started]
- },
- team: {
- interval_days: [1, 5, 10],
- completed_actions: [:git_write, :pipeline_created, :trial_started],
- incomplete_actions: [:user_added]
- }
- }.freeze
-
- def self.email_count_for_track(track)
- interval_days = TRACKS.dig(track.to_sym, :interval_days)
- interval_days&.count || 0
- end
- end
-end
-
-Namespaces::InProductMarketingEmailsService.prepend_mod
diff --git a/app/services/users/in_product_marketing_email_records.rb b/app/services/users/in_product_marketing_email_records.rb
deleted file mode 100644
index fcb252536b3..00000000000
--- a/app/services/users/in_product_marketing_email_records.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module Users
- class InProductMarketingEmailRecords
- attr_reader :records
-
- def initialize
- @records = []
- end
-
- def save!
- Users::InProductMarketingEmail.bulk_insert!(@records)
- @records = []
- end
-
- def add(user, track: nil, series: nil)
- @records << Users::InProductMarketingEmail.new(
- user: user,
- track: track,
- series: series,
- created_at: Time.zone.now,
- updated_at: Time.zone.now
- )
- end
- end
-end
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 2c97df90110..5f038ac467d 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -62,7 +62,7 @@
= yield :page_specific_javascripts
- = webpack_bundle_tag 'super_sidebar' if show_super_sidebar?
+ = webpack_bundle_tag 'super_sidebar'
= webpack_controller_bundle_tags
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
deleted file mode 100644
index 75de13d4862..00000000000
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ /dev/null
@@ -1,51 +0,0 @@
-- return unless current_user
-
-%ul
- %li.current-user
- - if current_user_menu?(:profile)
- = link_to current_user, class: 'gl-line-height-20!', data: { user: current_user.username, testid: 'user-profile-link', track_action: "click_link", track_label: "user_profile", track_property: "navigation_top" } do
- = render 'layouts/header/current_user_dropdown_item'
- - else
- .gl-py-3.gl-px-4
- = render 'layouts/header/current_user_dropdown_item'
- %li.divider
- - if can?(current_user, :update_user_status, current_user)
- %li
- = render Pajamas::ButtonComponent.new(button_options: { class: 'menu-item js-set-status-modal-trigger' }) do
- - if current_user.status&.busy? || current_user.status&.customized?
- = s_('SetStatusModal|Edit status')
- - else
- = s_('SetStatusModal|Set status')
- = dispensable_render_if_exists 'layouts/header/start_trial'
- - if current_user_menu?(:settings)
- %li
- = link_to s_("CurrentUser|Edit profile"), profile_path, data: { testid: 'edit_profile_link', track_action: "click_link", track_label: "user_edit_profile", track_property: "navigation_top" }
- %li
- = link_to s_("CurrentUser|Preferences"), profile_preferences_path, data: { track_action: "click_link", track_label: "user_preferences", track_property: "navigation_top" }
- = render_if_exists 'layouts/header/buy_pipeline_minutes', project: @project, namespace: @group
-
- - if current_user_menu?(:help)
- %li.divider.d-md-none
- %li.d-md-none
- = link_to _("Help"), help_path, data: {track_action: 'click_link', track_label: 'help', track_property: 'navigation_top'}
- %li.d-md-none
- = link_to _("Support"), support_url, data: {track_action: 'click_link', track_label: 'support', track_property: 'navigation_top'}
- %li.d-md-none
- = render 'shared/help_dropdown_forum_link'
- %li.d-md-none
- = link_to _("Submit feedback"), Gitlab::Utils.append_path(promo_url, "submit-feedback"), data: {track_action: 'click_link', track_label: 'submit_feedback', track_property: 'navigation_top'}
- - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
- %li.d-md-none
- = render 'shared/user_dropdown_contributing_link'
- = render 'shared/user_dropdown_instance_review'
- - if Gitlab.com_but_not_canary?
- %li.d-md-none
- = link_to _("Switch to GitLab Next"), Gitlab::Saas.canary_toggle_com_url, data: { track_action: "click_link", track_label: "switch_to_canary", track_property: "navigation_top" }
-
- %li.divider
- .js-new-nav-toggle{ data: { enabled: show_super_sidebar?.to_s, endpoint: profile_preferences_path} }
-
- - if current_user_menu?(:sign_out)
- %li.divider
- %li
- = link_to _("Sign out"), destroy_user_session_path, method: :post, class: "sign-out-link", data: { testid: 'sign_out_link', track_action: "click_link", track_label: "user_sign_out", track_property: "navigation_top" }
diff --git a/app/views/layouts/header/_current_user_dropdown_item.html.haml b/app/views/layouts/header/_current_user_dropdown_item.html.haml
deleted file mode 100644
index fa0a6364a15..00000000000
--- a/app/views/layouts/header/_current_user_dropdown_item.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-.gl-font-weight-bold
- = current_user.name
- - if current_user.status&.busy?
- = render Pajamas::BadgeComponent.new(s_('UserProfile|Busy'), size: 'sm', variant: 'warning')
-= current_user.to_reference
-- if current_user.status
- .user-status.d-flex.align-items-center.gl-mt-2.gl-mr-0.gl-font-sm.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
- - if current_user.status.customized?
- .user-status-emoji.d-flex.align-items-center
- = emoji_icon current_user.status.emoji
- %span.user-status-message.str-truncated
- = current_user.status.message_html.html_safe
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index dbfd8a99fbb..7f7758d4081 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,6 +1,3 @@
-- has_impersonation_link = header_link?(:admin_impersonation)
-- user_status_data = user_status_properties(current_user)
-
%header.navbar.navbar-gitlab.navbar-expand-sm.js-navbar.legacy-top-bar{ data: { testid: 'navbar' } }
%a.gl-sr-only.gl-accessibility{ href: "#content-body" } Skip to content
.container-fluid
@@ -12,64 +9,6 @@
%ul.nav.navbar-nav.gl-w-full.gl-align-items-center.gl-justify-content-end
- if current_user
= render 'layouts/header/new_dropdown', class: 'gl-display-none gl-sm-display-block gl-white-space-nowrap gl-text-right'
- - if header_link?(:issues)
- = nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do
- = link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues js-prefetch-document', aria: { label: _('Issues') },
- data: { testid: 'issues_shortcut_button', toggle: 'tooltip', placement: 'bottom',
- track_label: 'main_navigation',
- track_action: 'click_issues_link',
- track_property: 'navigation_top',
- container: 'body' } do
- = sprite_icon('issues')
- - issues_count = assigned_issuables_count(:issues)
- = gl_badge_tag({ size: :sm, variant: :success }, { class: "gl-ml-n2 #{'gl-display-none' if issues_count == 0}", "aria-label": n_("%d assigned issue", "%d assigned issues", issues_count) % issues_count }) do
- = assigned_open_issues_count_text
- - if header_link?(:merge_requests)
- = nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter dropdown" }) do
- - top_level_link = assigned_mrs_dashboard_path
- = link_to top_level_link, class: 'dashboard-shortcuts-merge_requests has-tooltip', title: _('Merge requests'), aria: { label: _('Merge requests') },
- data: { testid: 'merge_requests_shortcut_button',
- toggle: "dropdown",
- placement: 'bottom',
- track_label: 'merge_requests_menu',
- track_action: 'click_dropdown',
- track_property: 'navigation_top',
- container: 'body' } do
- = sprite_icon('git-merge')
- = gl_badge_tag({ size: :sm, variant: :warning }, { class: "js-merge-requests-count gl-ml-n2 #{'gl-display-none' if user_merge_requests_counts[:total] == 0}", "aria-label": n_("%d merge request", "%d merge requests", user_merge_requests_counts[:total]) % user_merge_requests_counts[:total] }) do
- = number_with_delimiter(user_merge_requests_counts[:total])
- = sprite_icon('chevron-down', css_class: 'caret-down gl-mx-0!')
- .dropdown-menu.dropdown-menu-right
- %ul
- %li.dropdown-header
- = _('Merge requests')
- %li
- = link_to assigned_mrs_dashboard_path,
- class: 'gl-display-flex! gl-align-items-center js-prefetch-document',
- data: {track_action: 'click_link', track_label: 'merge_requests_assigned', track_property: 'navigation_top'} do
- = _('Assigned')
- = gl_badge_tag({ variant: :neutral, size: :sm }, { class: "js-assigned-mr-count gl-ml-auto" }) do
- = user_merge_requests_counts[:assigned]
- %li
- = link_to reviewer_mrs_dashboard_path,
- class: 'dashboard-shortcuts-review_requests gl-display-flex! gl-align-items-center js-prefetch-document',
- data: {track_action: 'click_link', track_label: 'merge_requests_to_review', track_property: 'navigation_top'} do
- = _('Review requests')
- = gl_badge_tag({ variant: :neutral, size: :sm }, { class: "js-reviewer-mr-count gl-ml-auto" }) do
- = user_merge_requests_counts[:review_requested]
- - if header_link?(:todos)
- = nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do
- = link_to dashboard_todos_path, title: _('To-Do List'), aria: { label: _('To-Do List') }, class: 'shortcuts-todos js-prefetch-document',
- data: { testid: 'todos-shortcut-button', toggle: 'tooltip', placement: 'bottom',
- track_label: 'main_navigation',
- track_action: 'click_to_do_link',
- track_property: 'navigation_top',
- container: 'body' } do
- = sprite_icon('todo-done')
- -# The todos' counter badge's visibility is being toggled by adding or removing the .hidden class in Js.
- -# We'll eventually migrate to .gl-display-none: https://gitlab.com/gitlab-org/gitlab/-/issues/351792.
- = gl_badge_tag({ size: :sm, variant: :info }, { class: "js-todos-count gl-ml-n2 #{'hidden' if todos_pending_count == 0}", "aria-label": _("Todos count") }) do
- = todos_count_format(todos_pending_count)
%li.nav-item.header-help.dropdown.d-none.d-md-block
= link_to help_path, class: 'header-help-dropdown-toggle gl-relative', data: { toggle: "dropdown", track_action: 'click_question_mark_link', track_label: 'main_navigation', track_property: 'navigation_top' } do
%span.gl-sr-only
@@ -79,27 +18,6 @@
= sprite_icon('chevron-down', css_class: 'caret-down')
.dropdown-menu.dropdown-menu-right
= render 'layouts/header/help_dropdown'
- - if header_link?(:user_dropdown)
- %li.nav-item.header-user.js-nav-user-dropdown.dropdown{ data: { testid: 'user-dropdown' }, class: ('mr-0' if has_impersonation_link) }
- = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown", track_label: "profile_dropdown", track_action: "click_dropdown", track_property: "navigation_top" } do
- = render Pajamas::AvatarComponent.new(current_user, size: 24, class: 'header-user-avatar', avatar_options: { data: { testid: 'user-avatar-content' } })
- = render_if_exists 'layouts/header/user_notification_dot', project: project, namespace: group
- = sprite_icon('chevron-down', css_class: 'caret-down')
- .dropdown-menu.dropdown-menu-right
- = render 'layouts/header/current_user_dropdown'
- - if has_impersonation_link
- %li.nav-item.impersonation.ml-0
- = render Pajamas::ButtonComponent.new(href: admin_impersonation_path, icon: 'incognito', button_options: { title: _('Stop impersonation'), class: 'impersonation-btn', aria: { label: _('Stop impersonation') }, data: { method: :delete, toggle: 'tooltip', placement: 'bottom', container: 'body', testid: 'stop_impersonation_btn' } })
- - if header_link?(:sign_in)
- - if allow_signup?
- %li.nav-item
- = render Pajamas::ButtonComponent.new(href: new_user_registration_path) do
- = _('Register')
- %li.nav-item{ class: 'gl-flex-grow-0! gl-flex-basis-half!' }
- = link_to _('Sign in'), new_session_path(:user, redirect_to_referer: 'yes')
- if display_whats_new?
#whats-new-app{ data: { version_digest: whats_new_version_digest } }
-
-- if can?(current_user, :update_user_status, current_user)
- .js-set-status-modal-wrapper{ data: user_status_data }
diff --git a/app/views/layouts/help.html.haml b/app/views/layouts/help.html.haml
index 68426f71879..89467efcc6e 100644
--- a/app/views/layouts/help.html.haml
+++ b/app/views/layouts/help.html.haml
@@ -1,7 +1,6 @@
- @breadcrumb_title = _("Help")
- page_title _("Help")
- header_title _("Help"), help_path
-- if show_super_sidebar?
- - @force_desktop_expanded_sidebar = true
+- @force_desktop_expanded_sidebar = true
= render template: "layouts/application"
diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml
index 885fda10744..06192469833 100644
--- a/app/views/layouts/search.html.haml
+++ b/app/views/layouts/search.html.haml
@@ -1,7 +1,6 @@
- page_title _("Search")
- header_title _("Search"), search_path
- add_page_specific_style 'page_bundles/search'
-- if show_super_sidebar?
- - @force_desktop_expanded_sidebar = true
+- @force_desktop_expanded_sidebar = true
= render template: "layouts/application"
diff --git a/app/views/projects/_service_desk_settings.html.haml b/app/views/projects/_service_desk_settings.html.haml
index 115788e7764..d6b28e94645 100644
--- a/app/views/projects/_service_desk_settings.html.haml
+++ b/app/views/projects/_service_desk_settings.html.haml
@@ -18,6 +18,7 @@
selected_file_template_project_id: "#{@project.service_desk_setting&.file_template_project_id}",
outgoing_name: "#{@project.service_desk_setting&.outgoing_name}",
project_key: "#{@project.service_desk_setting&.project_key}",
+ reopen_issue_on_external_participant_note: "#{@project.service_desk_setting&.reopen_issue_on_external_participant_note}",
add_external_participants_from_cc: "#{@project.service_desk_setting&.add_external_participants_from_cc}",
templates: available_service_desk_templates_for(@project),
public_project: "#{@project.public?}",
diff --git a/app/views/search/_results_list.html.haml b/app/views/search/_results_list.html.haml
index fb96672cf99..dad352e376b 100644
--- a/app/views/search/_results_list.html.haml
+++ b/app/views/search/_results_list.html.haml
@@ -8,9 +8,7 @@
- elsif @search_objects.blank?
= render partial: "search/results/empty"
- else
- - statusBarClass = !show_super_sidebar? ? 'gl-lg-pl-5' : ''
-
- .section{ class: statusBarClass }
+ .section
- if @scope == 'commits'
%ul.content-list.commit-list
= render partial: "search/results/commit", collection: @search_objects
diff --git a/app/views/search/_results_status.html.haml b/app/views/search/_results_status.html.haml
index 8417b66eb34..3c42126e480 100644
--- a/app/views/search/_results_status.html.haml
+++ b/app/views/search/_results_status.html.haml
@@ -1,7 +1,4 @@
-- statusBarClass = !show_super_sidebar? ? 'gl-lg-pl-5' : ''
-- statusBarClass = statusBarClass + ' gl-lg-display-none' if @search_objects.to_a.empty?
-
-.section{ class: statusBarClass }
+.section{ class: ('gl-lg-display-none' if @search_objects.to_a.empty?) }
.search-results-status
.gl-display-flex.gl-flex-direction-column
.gl-p-5.gl-display-flex.gl-flex-wrap
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 4ddeca64c7d..af3177e4422 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -16,8 +16,8 @@
- page_description(_("%{count} %{scope} for term '%{term}'") % { count: @search_results.formatted_count(@scope), scope: @scope, term: @search_term })
- page_card_attributes("Namespace" => @group&.full_path, "Project" => @project&.full_path)
-#js-search-topbar{ data: { "group-initial-json": group_attributes.to_json, "project-initial-json": project_attributes.to_json, "default-branch-name": @project&.default_branch } }
+#js-search-topbar{ data: { "default-branch-name": @project&.default_branch } }
.results.gl-lg-display-flex.gl-mt-0
- #js-search-sidebar{ data: { navigation_json: search_navigation_json, search_type: search_service.search_type } }
+ #js-search-sidebar{ data: { navigation_json: search_navigation_json, search_type: search_service.search_type, group_initial_json: group_attributes.to_json, project_initial_json: project_attributes.to_json, } }
- if @search_term
= render 'search/results'
diff --git a/app/views/shared/_ci_catalog_badge.html.haml b/app/views/shared/_ci_catalog_badge.html.haml
index 18e0cb37d7d..7f8f4f6143b 100644
--- a/app/views/shared/_ci_catalog_badge.html.haml
+++ b/app/views/shared/_ci_catalog_badge.html.haml
@@ -1,2 +1 @@
-- current_href = Feature.enabled?(:global_ci_catalog, @user) ? href : nil
-= render Pajamas::BadgeComponent.new(s_('CiCatalog|CI/CD catalog resource'), variant: 'info', icon: 'catalog-checkmark', class: css_class, href: current_href)
+= render Pajamas::BadgeComponent.new(s_('CiCatalog|CI/CD catalog resource'), variant: 'info', icon: 'catalog-checkmark', class: css_class, href: href)
diff --git a/app/views/shared/runners/_runner_description.html.haml b/app/views/shared/runners/_runner_description.html.haml
index dc689303f77..6f918ae8103 100644
--- a/app/views/shared/runners/_runner_description.html.haml
+++ b/app/views/shared/runners/_runner_description.html.haml
@@ -19,4 +19,4 @@
%p
= s_("Runners|Tags control which type of jobs a runner can handle. By tagging a runner, you make sure shared runners only handle the jobs they are equipped to run.")
- = link_to _("Learn more."), help_page_path("ci/runners/configure_runners", anchor: "use-tags-to-control-which-jobs-a-runner-can-run"), target: '_blank'
+ = link_to _("Learn more."), help_page_path("ci/runners/configure_runners", anchor: "how-the-runner-uses-tags"), target: '_blank'
diff --git a/config/feature_flags/development/global_ci_catalog.yml b/config/feature_flags/development/encoding_logs_tree.yml
similarity index 62%
rename from config/feature_flags/development/global_ci_catalog.yml
rename to config/feature_flags/development/encoding_logs_tree.yml
index cf61406112b..881774f7edb 100644
--- a/config/feature_flags/development/global_ci_catalog.yml
+++ b/config/feature_flags/development/encoding_logs_tree.yml
@@ -1,8 +1,8 @@
---
-name: global_ci_catalog
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133885
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/427940
-milestone: '16.6'
+name: encoding_logs_tree
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136323
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432559
+milestone: '16.7'
type: development
-group: group::pipeline authoring
+group: group::source code
default_enabled: false
diff --git a/config/feature_flags/development/product_analytics_beta_optin.yml b/config/feature_flags/development/product_analytics_beta_optin.yml
new file mode 100644
index 00000000000..f49afc9458d
--- /dev/null
+++ b/config/feature_flags/development/product_analytics_beta_optin.yml
@@ -0,0 +1,8 @@
+---
+name: product_analytics_beta_optin
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138469
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433344
+milestone: '16.7'
+type: development
+group: group::product analytics
+default_enabled: false
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 77503814158..2598a8d4ac4 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -187,6 +187,8 @@
- 1
- - compliance_management_pending_status_check
- 1
+- - compliance_management_standards_adherence_export_mailer
+ - 1
- - compliance_management_standards_gitlab_at_least_two_approvals
- 1
- - compliance_management_standards_gitlab_base
diff --git a/db/docs/batched_background_migrations/backfill_missing_vulnerability_dismissal_details.yml b/db/docs/batched_background_migrations/backfill_missing_vulnerability_dismissal_details.yml
index 0c66dbb74fb..33a46c3f930 100644
--- a/db/docs/batched_background_migrations/backfill_missing_vulnerability_dismissal_details.yml
+++ b/db/docs/batched_background_migrations/backfill_missing_vulnerability_dismissal_details.yml
@@ -4,3 +4,4 @@ description: Backfill missing vulnerability dimissal information as a result of
feature_category: vulnerability_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126253
milestone: '16.2'
+finalized_by: '20231207220935'
diff --git a/db/docs/batched_background_migrations/backfill_root_storage_statistics_fork_storage_sizes.yml b/db/docs/batched_background_migrations/backfill_root_storage_statistics_fork_storage_sizes.yml
index ad1764d611a..5120baf69db 100644
--- a/db/docs/batched_background_migrations/backfill_root_storage_statistics_fork_storage_sizes.yml
+++ b/db/docs/batched_background_migrations/backfill_root_storage_statistics_fork_storage_sizes.yml
@@ -1,6 +1,9 @@
---
migration_job_name: BackfillRootStorageStatisticsForkStorageSizes
-description: Backfill the public_forks_storage_size, internal_forks_storage_size, and private_forks_storage_size columns on the namespace_root_storage_statistics table
+description: Backfill the public_forks_storage_size, internal_forks_storage_size,
+ and private_forks_storage_size columns on the namespace_root_storage_statistics
+ table
feature_category: consumables_cost_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120916
milestone: '16.1'
+finalized_by: '20231207221036'
diff --git a/db/migrate/20231115081652_add_home_organization_id_to_user_preferences.rb b/db/migrate/20231115081652_add_home_organization_id_to_user_preferences.rb
new file mode 100644
index 00000000000..1b137d56575
--- /dev/null
+++ b/db/migrate/20231115081652_add_home_organization_id_to_user_preferences.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddHomeOrganizationIdToUserPreferences < Gitlab::Database::Migration[2.2]
+ enable_lock_retries!
+ milestone '16.7'
+
+ def change
+ add_column(:user_preferences, :home_organization_id, :bigint, null: true)
+ end
+end
diff --git a/db/migrate/20231122072428_add_home_organization_id_index_to_user_preferences.rb b/db/migrate/20231122072428_add_home_organization_id_index_to_user_preferences.rb
new file mode 100644
index 00000000000..41bad39ae73
--- /dev/null
+++ b/db/migrate/20231122072428_add_home_organization_id_index_to_user_preferences.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddHomeOrganizationIdIndexToUserPreferences < Gitlab::Database::Migration[2.2]
+ disable_ddl_transaction!
+ milestone '16.7'
+
+ INDEX = 'index_user_preferences_on_home_organization_id'
+
+ def up
+ add_concurrent_index(:user_preferences, :home_organization_id, name: INDEX)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:user_preferences, name: INDEX)
+ end
+end
diff --git a/db/migrate/20231201095326_add_home_organization_id_fk_to_user_preferences.rb b/db/migrate/20231201095326_add_home_organization_id_fk_to_user_preferences.rb
new file mode 100644
index 00000000000..7eb2c0116d1
--- /dev/null
+++ b/db/migrate/20231201095326_add_home_organization_id_fk_to_user_preferences.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddHomeOrganizationIdFkToUserPreferences < Gitlab::Database::Migration[2.2]
+ disable_ddl_transaction!
+ milestone '16.7'
+
+ def up
+ add_concurrent_foreign_key(:user_preferences, :organizations, column: :home_organization_id, on_delete: :nullify)
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key_if_exists :user_preferences, column: :home_organization_id
+ end
+ end
+end
diff --git a/db/post_migrate/20231207220935_finalize_backfill_missing_vulnerability_dismissal_details.rb b/db/post_migrate/20231207220935_finalize_backfill_missing_vulnerability_dismissal_details.rb
new file mode 100644
index 00000000000..9fa1b2e597f
--- /dev/null
+++ b/db/post_migrate/20231207220935_finalize_backfill_missing_vulnerability_dismissal_details.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class FinalizeBackfillMissingVulnerabilityDismissalDetails < Gitlab::Database::Migration[2.2]
+ milestone '16.7'
+
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: 'BackfillMissingVulnerabilityDismissalDetails',
+ table_name: :vulnerabilities,
+ column_name: :id,
+ job_arguments: [],
+ finalize: true
+ )
+ end
+
+ def down; end
+end
diff --git a/db/post_migrate/20231207221036_finalize_backfill_root_storage_statistics_fork_storage_sizes.rb b/db/post_migrate/20231207221036_finalize_backfill_root_storage_statistics_fork_storage_sizes.rb
new file mode 100644
index 00000000000..0284e4bd8b9
--- /dev/null
+++ b/db/post_migrate/20231207221036_finalize_backfill_root_storage_statistics_fork_storage_sizes.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class FinalizeBackfillRootStorageStatisticsForkStorageSizes < Gitlab::Database::Migration[2.2]
+ milestone '16.7'
+
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: 'BackfillRootStorageStatisticsForkStorageSizes',
+ table_name: :namespace_root_storage_statistics,
+ column_name: :namespace_id,
+ job_arguments: [],
+ finalize: true
+ )
+ end
+
+ def down; end
+end
diff --git a/db/schema_migrations/20231115081652 b/db/schema_migrations/20231115081652
new file mode 100644
index 00000000000..7674d897a70
--- /dev/null
+++ b/db/schema_migrations/20231115081652
@@ -0,0 +1 @@
+624bef2f1f8ebd81bdc49c0007a72d77bad34db215dfee01101d976000964e28
\ No newline at end of file
diff --git a/db/schema_migrations/20231122072428 b/db/schema_migrations/20231122072428
new file mode 100644
index 00000000000..507842fd15c
--- /dev/null
+++ b/db/schema_migrations/20231122072428
@@ -0,0 +1 @@
+4d4539b21d0f9ea2ade7a1223953f2aea36c4432e5f3b042266e98d71f6a9a48
\ No newline at end of file
diff --git a/db/schema_migrations/20231201095326 b/db/schema_migrations/20231201095326
new file mode 100644
index 00000000000..dd826218b84
--- /dev/null
+++ b/db/schema_migrations/20231201095326
@@ -0,0 +1 @@
+7078ee3b40cd12e32c0d8f2cc1e55e19b4352dac8d2c708b617a2ff03e979c3a
\ No newline at end of file
diff --git a/db/schema_migrations/20231207220935 b/db/schema_migrations/20231207220935
new file mode 100644
index 00000000000..16e78f79f43
--- /dev/null
+++ b/db/schema_migrations/20231207220935
@@ -0,0 +1 @@
+644dfd3c7371feff5431900510e25fc2dc0c661c7ee9142bd26431c10d929416
\ No newline at end of file
diff --git a/db/schema_migrations/20231207221036 b/db/schema_migrations/20231207221036
new file mode 100644
index 00000000000..210bc7a0e93
--- /dev/null
+++ b/db/schema_migrations/20231207221036
@@ -0,0 +1 @@
+5afbc4c287ce349c58ab70e2c8b44c833f075fe114cf2af2a29aaf4247053d82
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index dc6e8c4cef9..1f2ed208630 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -24663,6 +24663,7 @@ CREATE TABLE user_preferences (
enabled_zoekt boolean DEFAULT true NOT NULL,
keyboard_shortcuts_enabled boolean DEFAULT true NOT NULL,
time_display_format smallint DEFAULT 0 NOT NULL,
+ home_organization_id bigint,
CONSTRAINT check_89bf269f41 CHECK ((char_length(diffs_deletion_color) <= 7)),
CONSTRAINT check_d07ccd35f7 CHECK ((char_length(diffs_addition_color) <= 7))
);
@@ -34896,6 +34897,8 @@ CREATE INDEX index_user_phone_validations_on_dial_code_phone_number ON user_phon
CREATE INDEX index_user_preferences_on_gitpod_enabled ON user_preferences USING btree (gitpod_enabled);
+CREATE INDEX index_user_preferences_on_home_organization_id ON user_preferences USING btree (home_organization_id);
+
CREATE UNIQUE INDEX index_user_preferences_on_user_id ON user_preferences USING btree (user_id);
CREATE INDEX index_user_project_callouts_on_project_id ON user_project_callouts USING btree (project_id);
@@ -38158,6 +38161,9 @@ ALTER TABLE ONLY gitlab_subscriptions
ALTER TABLE ONLY abuse_events
ADD CONSTRAINT fk_e5ce49c215 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
+ALTER TABLE ONLY user_preferences
+ ADD CONSTRAINT fk_e5e029c10b FOREIGN KEY (home_organization_id) REFERENCES organizations(id) ON DELETE SET NULL;
+
ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_e719a85f8a FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
diff --git a/doc/administration/gitaly/configure_gitaly.md b/doc/administration/gitaly/configure_gitaly.md
index 0333e2439e3..d89413b2cf4 100644
--- a/doc/administration/gitaly/configure_gitaly.md
+++ b/doc/administration/gitaly/configure_gitaly.md
@@ -1249,8 +1249,15 @@ By default, Gitaly doesn't sign commits made using GitLab UI. For example, commi
- Web IDE.
- Merge requests.
-You can configure Gitaly to sign commits made with the GitLab UI. The commits show as unverified and signed by an unknown
-user. Support for improvements is proposed in [issue 19185](https://gitlab.com/gitlab-org/gitlab/-/issues/19185).
+You can configure Gitaly to sign commits made with the GitLab UI.
+
+By default, Gitaly sets the author of a commit as the committer. In this case,
+it is harder to [Verify commits locally](../../user/project/repository/signed_commits/ssh.md#verify-commits-locally)
+because the signature belongs to neither the author nor the committer of the commit.
+
+You can configure Gitaly to reflect that a commit has been committed by your instance by
+setting `committer_email` and `committer_name`. For example, on GitLab.com these configuration options are
+set to `noreply@gitlab.com` and `GitLab`.
Configure Gitaly to sign commits made with the GitLab UI in one of two ways:
@@ -1281,7 +1288,10 @@ Configure Gitaly to sign commits made with the GitLab UI in one of two ways:
# ...
git: {
# ...
+ committer_name: 'Your Instance',
+ committer_email: 'noreply@yourinstance.com',
signing_key: '/etc/gitlab/gitaly/signing_key.gpg',
+ # ...
},
}
```
@@ -1310,6 +1320,8 @@ Configure Gitaly to sign commits made with the GitLab UI in one of two ways:
```toml
[git]
+ committer_name = "Your Instance"
+ committer_email = "noreply@yourinstance.com"
signing_key = "/etc/gitlab/gitaly/signing_key.gpg"
```
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index 38e1e68d8f9..259a0c961d2 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -435,7 +435,7 @@ You can change the maximum time a job can run before it times out:
- At the project-level in the [project's CI/CD settings](../ci/pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run)
for a given project. This limit must be between 10 minutes and 1 month.
-- At the [runner level](../ci/runners/configure_runners.md#set-maximum-job-timeout-for-a-runner).
+- At the [runner level](../ci/runners/configure_runners.md#set-the-maximum-job-timeout).
This limit must be 10 minutes or longer.
### Maximum number of deployment jobs in a pipeline
diff --git a/doc/administration/operations/rails_console.md b/doc/administration/operations/rails_console.md
index da7e35c7ebe..f15fdd32c90 100644
--- a/doc/administration/operations/rails_console.md
+++ b/doc/administration/operations/rails_console.md
@@ -108,13 +108,24 @@ Notify.test_email(u.email, "Test email for #{u.name}", 'Test email').deliver_now
## Disable database statement timeout
You can disable the PostgreSQL statement timeout for the current Rails console
-session by running:
+session.
+
+In GitLab 15.11 and earlier, to disable the database statement timeout, run:
```ruby
ActiveRecord::Base.connection.execute('SET statement_timeout TO 0')
```
-This change only affects the current Rails console session and is
+In GitLab 16.0 and later, [GitLab uses two database connections by default](../../update/versions/gitlab_16_changes.md#1600). To disable the database statement timeout, run:
+
+```ruby
+ActiveRecord::Base.connection.execute('SET statement_timeout TO 0')
+Ci::ApplicationRecord.connection.execute('SET statement_timeout TO 0')
+```
+
+Instances running GitLab 16.0 and later reconfigured to use a single database connection should disable the database statement timeout using the code for GitLab 15.11 and earlier.
+
+Disabling the database statement timeout affects only the current Rails console session and is
not persisted in the GitLab production environment or in the next Rails
console session.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index e88d60aebe7..ddbaf540dbd 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -109,8 +109,7 @@ Prerequisites:
If you need support for namespace in the URL path to remove the requirement for wildcard DNS:
1. Enable the GitLab Pages flag for this feature by adding
- `gitlab_pages["namespace_in_path"] = true` to `gitlab.rb`. For more information,
- see [Use environment variables](#use-environment-variables).
+ `gitlab_pages["namespace_in_path"] = true` to `/etc/gitlab/gitlab.rb`.
1. In your DNS provider, add entries for `example.com` and `projects.example.com`.
In both lines, replace `example.com` with your domain name, and `192.0.0.0` with
the IPv4 version of your IP address. The entries look like this:
@@ -214,8 +213,7 @@ Prerequisites:
pages_nginx['enable'] = true
- # Set this feature flag to enable this feature
- # For more information, see https://docs.gitlab.com/ee/administration/pages/index.html#use-environment-variables
+ # Set this flag to enable this feature
gitlab_pages["namespace_in_path"] = true
```
@@ -304,8 +302,7 @@ daemon doesn't listen to the outside world:
pages_nginx['enable'] = true
pages_nginx['redirect_http_to_https'] = true
- # Set this feature flag to enable this feature
- # For more information, see https://docs.gitlab.com/ee/administration/pages/index.html#use-environment-variables
+ # Set this flag to enable this feature
gitlab_pages["namespace_in_path"] = true
```
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index b97bc58c3d6..9884e6e8f34 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -20281,6 +20281,31 @@ Returns [`WorkItem`](#workitem).
| ---- | ---- | ----------- |
| `iid` | [`String!`](#string) | IID of the work item. |
+##### `Group.workItemStateCounts`
+
+Counts of work items by state for the namespace. Returns `null` if the `namespace_level_work_items` feature flag is disabled.
+
+WARNING:
+**Introduced** in 16.7.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `authorUsername` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.9. This feature is an Experiment. It can be changed or removed at any time. Filter work items by author username. |
+| `iid` | [`String`](#string) | IID of the work item. For example, "1". |
+| `iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
+| `in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
+| `requirementLegacyWidget` **{warning-solid}** | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in 15.9. Use work item IID filter instead. |
+| `search` | [`String`](#string) | Search query for title or description. |
+| `sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
+| `state` | [`IssuableState`](#issuablestate) | Current state of the work item. |
+| `statusWidget` | [`StatusFilterInput`](#statusfilterinput) | Input for status widget filter. Ignored if `work_items_mvc_2` is disabled. |
+| `types` | [`[IssueType!]`](#issuetype) | Filter work items by the given work item types. |
+
##### `Group.workItemTypes`
Work item types available to the group.
@@ -25587,6 +25612,31 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| `severity` | [`[VulnerabilitySeverity!]`](#vulnerabilityseverity) | Filter vulnerabilities by severity. |
| `state` | [`[VulnerabilityState!]`](#vulnerabilitystate) | Filter vulnerabilities by state. |
+##### `Project.workItemStateCounts`
+
+Counts of work items by state for the project.
+
+WARNING:
+**Introduced** in 16.7.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `authorUsername` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.9. This feature is an Experiment. It can be changed or removed at any time. Filter work items by author username. |
+| `iid` | [`String`](#string) | IID of the work item. For example, "1". |
+| `iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
+| `in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
+| `requirementLegacyWidget` **{warning-solid}** | [`RequirementLegacyFilterInput`](#requirementlegacyfilterinput) | **Deprecated** in 15.9. Use work item IID filter instead. |
+| `search` | [`String`](#string) | Search query for title or description. |
+| `sort` | [`WorkItemSort`](#workitemsort) | Sort work items by criteria. |
+| `state` | [`IssuableState`](#issuablestate) | Current state of the work item. |
+| `statusWidget` | [`StatusFilterInput`](#statusfilterinput) | Input for status widget filter. Ignored if `work_items_mvc_2` is disabled. |
+| `types` | [`[IssueType!]`](#issuetype) | Filter work items by the given work item types. |
+
##### `Project.workItemTypes`
Work item types available to the project.
@@ -28684,6 +28734,18 @@ Check permissions for the current user on a work item.
| `setWorkItemMetadata` | [`Boolean!`](#boolean) | If `true`, the user can perform `set_work_item_metadata` on this resource. |
| `updateWorkItem` | [`Boolean!`](#boolean) | If `true`, the user can perform `update_work_item` on this resource. |
+### `WorkItemStateCountsType`
+
+Represents total number of work items for the represented states.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `all` | [`Int`](#int) | Number of work items for the project or group. |
+| `closed` | [`Int`](#int) | Number of work items with state CLOSED for the project or group. |
+| `opened` | [`Int`](#int) | Number of work items with state OPENED for the project or group. |
+
### `WorkItemType`
#### Fields
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 7eba4882a4a..681f4901b47 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -15,13 +15,8 @@ request on that project results in a `404` status code.
By default, `GET` requests return 20 results at a time because the API results
are paginated.
-
Read more on [pagination](rest/index.md#pagination).
-WARNING:
-The `reference` attribute in responses is deprecated in favor of `references`.
-[Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20354) in GitLab 12.6.
-
NOTE:
The `references.relative` attribute is relative to the group or project of the issue being requested.
When an issue is fetched from its project, the `relative` format is the same as the `short` format.
@@ -29,9 +24,7 @@ When requested across groups or projects, it's expected to be the same as the `f
## List issues
-> - The `due_date` property was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233420) in GitLab 13.3.
-> - The `weight` property moved to GitLab Premium in 13.9.
-> - The `due_date` filters `any`, `today`, and `tomorrow` were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78460) in GitLab 14.8.
+> The `due_date` filters `any`, `today`, and `tomorrow` were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78460) in GitLab 14.8.
Get all issues the authenticated user has access to. By default it
returns only issues created by the current user. To get all issues,
@@ -54,38 +47,42 @@ GET /issues?state=closed
GET /issues?state=opened
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|---------------------------------|---------------| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. |
-| `assignee_username` | string array | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In GitLab CE, the `assignee_username` array should only contain a single value. Otherwise, an invalid parameter error is returned. |
-| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
-| `author_username` | string | no | Return issues created by the given `username`. Similar to `author_id` and mutually exclusive with `author_id`. |
-| `confidential` | boolean | no | Filter confidential or public issues. |
-| `created_after` | datetime | no | Return issues created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `created_before` | datetime | no | Return issues created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `due_date` | string | no | Return issues that have no due date, are overdue, or whose due date is this week, this month, or between two weeks ago and next month. Accepts: `0` (no due date), `any`, `today`, `tomorrow`, `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`. |
-| `epic_id` **(PREMIUM ALL)** | integer | no | Return issues associated with the given epic ID. `None` returns issues that are not associated with an epic. `Any` returns issues that are associated with an epic. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46887) in GitLab 13.6)_
-| `health_status` **(ULTIMATE ALL)** | string | no | Return issues with the specified `health_status`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370721) in GitLab 15.4)._ In [GitLab 15.5 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/370721), `None` returns issues with no health status assigned, and `Any` returns issues with a health status assigned.
-| `iids[]` | integer array | no | Return only the issues having the given `iid` |
-| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
-| `issue_type` | string | no | Filter to a given type of issue. One of `issue`, `incident`, or `test_case`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/260375) in GitLab 13.12)_ |
-| `iteration_id` **(PREMIUM ALL)** | integer | no | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
-| `iteration_title` **(PREMIUM ALL)** | string | no | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
-| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
-| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. Using `None` or `Any` will be [deprecated in the future](https://gitlab.com/gitlab-org/gitlab/-/issues/336044). Please use `milestone_id` attribute instead. `milestone` and `milestone_id` are mutually exclusive. |
-| `milestone_id` | string | no | Returns issues assigned to milestones with a given timebox value (`None`, `Any`, `Upcoming`, and `Started`). `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. `Upcoming` lists all issues assigned to milestones due in the future. `Started` lists all issues assigned to open, started milestones. `milestone` and `milestone_id` are mutually exclusive. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335939) in GitLab 14.3)_ |
-| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
-| `non_archived` | boolean | no | Return issues only from non-archived projects. If `false`, the response returns issues from both archived and non-archived projects. Default is `true`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/197170) in GitLab 13.0)_ |
-| `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `assignee_id`, `assignee_username`, `author_id`, `author_username`, `iids`, `iteration_id`, `iteration_title`, `labels`, `milestone`, `milestone_id` and `weight`. |
-| `order_by` | string | no | Return issues ordered by `created_at`, `due_date`, `label_priority`, `milestone_due`, `popularity`, `priority`, `relative_position`, `title`, `updated_at`, or `weight` fields. Default is `created_at`. |
-| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`. |
-| `search` | string | no | Search issues against their `title` and `description` |
-| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
-| `state` | string | no | Return `all` issues or just those that are `opened` or `closed` |
-| `updated_after` | datetime | no | Return issues updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `updated_before` | datetime | no | Return issues updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `weight` **(PREMIUM ALL)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
-| `with_labels_details` | boolean | no | If `true`, the response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. The `description_html` attribute was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) in GitLab 12.7|
+| `assignee_id` | integer | No | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. |
+| `assignee_username` | string array | No | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In GitLab CE, the `assignee_username` array should only contain a single value. Otherwise, an invalid parameter error is returned. |
+| `author_id` | integer | No | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
+| `author_username` | string | No | Return issues created by the given `username`. Similar to `author_id` and mutually exclusive with `author_id`. |
+| `confidential` | boolean | No | Filter confidential or public issues. |
+| `created_after` | datetime | No | Return issues created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `created_before` | datetime | No | Return issues created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `due_date` | string | No | Return issues that have no due date, are overdue, or whose due date is this week, this month, or between two weeks ago and next month. Accepts: `0` (no due date), `any`, `today`, `tomorrow`, `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`. |
+| `epic_id` **(PREMIUM ALL)** | integer | No | Return issues associated with the given epic ID. `None` returns issues that are not associated with an epic. `Any` returns issues that are associated with an epic. |
+| `health_status` **(ULTIMATE ALL)** | string | No | Return issues with the specified `health_status`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370721) in GitLab 15.4)._ In [GitLab 15.5 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/370721), `None` returns issues with no health status assigned, and `Any` returns issues with a health status assigned.
+| `iids[]` | integer array | No | Return only the issues having the given `iid`. |
+| `in` | string | No | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description`. |
+| `issue_type` | string | No | Filter to a given type of issue. One of `issue`, `incident`, or `test_case`. |
+| `iteration_id` **(PREMIUM ALL)** | integer | No | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. |
+| `iteration_title` **(PREMIUM ALL)** | string | No | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. |
+| `labels` | string | No | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
+| `milestone_id` | string | No | Returns issues assigned to milestones with a given timebox value (`None`, `Any`, `Upcoming`, and `Started`). `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. `Upcoming` lists all issues assigned to milestones due in the future. `Started` lists all issues assigned to open, started milestones. `milestone` and `milestone_id` are mutually exclusive. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335939) in GitLab 14.3)_ |
+| `milestone` | string | No | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. Using `None` or `Any` will be [deprecated in the future](https://gitlab.com/gitlab-org/gitlab/-/issues/336044). Use `milestone_id` attribute instead. `milestone` and `milestone_id` are mutually exclusive. |
+| `my_reaction_emoji` | string | No | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
+| `non_archived` | boolean | No | Return issues only from non-archived projects. If `false`, the response returns issues from both archived and non-archived projects. Default is `true`. |
+| `not` | Hash | No | Return issues that do not match the parameters supplied. Accepts: `assignee_id`, `assignee_username`, `author_id`, `author_username`, `iids`, `iteration_id`, `iteration_title`, `labels`, `milestone`, `milestone_id` and `weight`. |
+| `order_by` | string | No | Return issues ordered by `created_at`, `due_date`, `label_priority`, `milestone_due`, `popularity`, `priority`, `relative_position`, `title`, `updated_at`, or `weight` fields. Default is `created_at`. |
+| `scope` | string | No | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`. |
+| `search` | string | No | Search issues against their `title` and `description`. |
+| `sort` | string | No | Return issues sorted in `asc` or `desc` order. Default is `desc`. |
+| `state` | string | No | Return `all` issues or just those that are `opened` or `closed`. |
+| `updated_after` | datetime | No | Return issues updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `updated_before` | datetime | No | Return issues updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `weight` **(PREMIUM ALL)** | integer | No | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
+| `with_labels_details` | boolean | No | If `true`, the response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -255,17 +252,15 @@ to the GitLab EE API.
WARNING:
The `epic_iid` attribute is deprecated and [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5.
-Please use `iid` of the `epic` attribute instead.
+Use `iid` of the `epic` attribute instead.
## List group issues
-> - The `due_date` property was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233420) in GitLab 13.3.
-> - The `weight` property moved to GitLab Premium in 13.9.
-> - The `due_date` filters `any`, `today`, and `tomorrow` were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78460) in GitLab 14.8.
+> The `due_date` filters `any`, `today`, and `tomorrow` were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78460) in GitLab 14.8.
Get a list of a group's issues.
-If the group is private, credentials need to be provided for authorization.
+If the group is private, you must provide credentials to authorize.
The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
```plaintext
@@ -285,36 +280,40 @@ GET /groups/:id/issues?state=closed
GET /groups/:id/issues?state=opened
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
-| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. |
-| `assignee_username` | string array | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In GitLab CE, the `assignee_username` array should only contain a single value. Otherwise, an invalid parameter error is returned. |
-| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
-| `author_username` | string | no | Return issues created by the given `username`. Similar to `author_id` and mutually exclusive with `author_id`. |
-| `confidential` | boolean | no | Filter confidential or public issues. |
-| `created_after` | datetime | no | Return issues created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `created_before` | datetime | no | Return issues created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `due_date` | string | no | Return issues that have no due date, are overdue, or whose due date is this week, this month, or between two weeks ago and next month. Accepts: `0` (no due date), `any`, `today`, `tomorrow`, `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`. |
-| `epic_id` **(PREMIUM ALL)** | integer | no | Return issues associated with the given epic ID. `None` returns issues that are not associated with an epic. `Any` returns issues that are associated with an epic. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46887) in GitLab 13.6)_
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `iids[]` | integer array | no | Return only the issues having the given `iid` |
-| `issue_type` | string | no | Filter to a given type of issue. One of `issue`, `incident`, or `test_case`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/260375) in GitLab 13.12)_ |
-| `iteration_id` **(PREMIUM ALL)** | integer | no | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
-| `iteration_title` **(PREMIUM ALL)** | string | no | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
-| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
-| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
-| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
-| `non_archived` | boolean | no | Return issues from non archived projects. Default is true. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23785) in GitLab 12.8)_ |
-| `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji`, `search`, `in` |
-| `order_by` | string | no | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
-| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `all`. |
-| `search` | string | no | Search group issues against their `title` and `description` |
-| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
-| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
-| `updated_after` | datetime | no | Return issues updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `updated_before` | datetime | no | Return issues updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `weight` **(PREMIUM ALL)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
-| `with_labels_details` | boolean | no | If `true`, the response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. The `description_html` attribute was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) in GitLab 12.7 |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `assignee_id` | integer | No | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. |
+| `assignee_username` | string array | No | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In GitLab CE, the `assignee_username` array should only contain a single value. Otherwise, an invalid parameter error is returned. |
+| `author_id` | integer | No | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
+| `author_username` | string | No | Return issues created by the given `username`. Similar to `author_id` and mutually exclusive with `author_id`. |
+| `confidential` | boolean | No | Filter confidential or public issues. |
+| `created_after` | datetime | No | Return issues created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `created_before` | datetime | No | Return issues created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `due_date` | string | No | Return issues that have no due date, are overdue, or whose due date is this week, this month, or between two weeks ago and next month. Accepts: `0` (no due date), `any`, `today`, `tomorrow`, `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`. |
+| `epic_id` **(PREMIUM ALL)** | integer | No | Return issues associated with the given epic ID. `None` returns issues that are not associated with an epic. `Any` returns issues that are associated with an epic. |
+| `iids[]` | integer array | No | Return only the issues having the given `iid`. |
+| `issue_type` | string | No | Filter to a given type of issue. One of `issue`, `incident`, or `test_case`. |
+| `iteration_id` **(PREMIUM ALL)** | integer | No | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. |
+| `iteration_title` **(PREMIUM ALL)** | string | No | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. |
+| `labels` | string | No | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
+| `milestone` | string | No | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
+| `my_reaction_emoji` | string | No | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
+| `non_archived` | boolean | No | Return issues from non archived projects. Default is true. |
+| `not` | Hash | No | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji`, `search`, `in`. |
+| `order_by` | string | No | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
+| `scope` | string | No | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `all`. |
+| `search` | string | No | Search group issues against their `title` and `description`. |
+| `sort` | string | No | Return issues sorted in `asc` or `desc` order. Default is `desc`. |
+| `state` | string | No | Return all issues or just those that are `opened` or `closed`. |
+| `updated_after` | datetime | No | Return issues updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `updated_before` | datetime | No | Return issues updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `weight` **(PREMIUM ALL)** | integer | No | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
+| `with_labels_details` | boolean | No | If `true`, the response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -460,13 +459,11 @@ The `assignee` column is deprecated. We now show it as a single-sized array `ass
WARNING:
The `epic_iid` attribute is deprecated and [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5.
-Please use `iid` of the `epic` attribute instead.
+Use `iid` of the `epic` attribute instead.
## List project issues
-> - The `due_date` property was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233420) in GitLab 13.3.
-> - The `weight` property moved to GitLab Premium in 13.9.
-> - The `due_date` filters `any`, `today`, and `tomorrow` were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78460) in GitLab 14.8.
+> The `due_date` filters `any`, `today`, and `tomorrow` were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78460) in GitLab 14.8.
Get a list of a project's issues.
@@ -490,35 +487,39 @@ GET /projects/:id/issues?state=closed
GET /projects/:id/issues?state=opened
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
-| `assignee_id` | integer | no | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. |
-| `assignee_username` | string array | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In GitLab CE, the `assignee_username` array should only contain a single value. Otherwise, an invalid parameter error is returned. |
-| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
-| `author_username` | string | no | Return issues created by the given `username`. Similar to `author_id` and mutually exclusive with `author_id`. |
-| `confidential` | boolean | no | Filter confidential or public issues. |
-| `created_after` | datetime | no | Return issues created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `created_before` | datetime | no | Return issues created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `due_date` | string | no | Return issues that have no due date, are overdue, or whose due date is this week, this month, or between two weeks ago and next month. Accepts: `0` (no due date), `any`, `today`, `tomorrow`, `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`. |
-| `epic_id` **(PREMIUM ALL)** | integer | no | Return issues associated with the given epic ID. `None` returns issues that are not associated with an epic. `Any` returns issues that are associated with an epic. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46887) in GitLab 13.6)_
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `iids[]` | integer array | no | Return only the issues having the given `iid` |
-| `issue_type` | string | no | Filter to a given type of issue. One of `issue`, `incident`, or `test_case`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/260375) in GitLab 13.12)_ |
-| `iteration_id` **(PREMIUM ALL)** | integer | no | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
-| `iteration_title` **(PREMIUM ALL)** | string | no | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)_ |
-| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
-| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
-| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
-| `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji`, `search`, `in` |
-| `order_by` | string | no | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
-| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `all`. |
-| `search` | string | no | Search project issues against their `title` and `description` |
-| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
-| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
-| `updated_after` | datetime | no | Return issues updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `updated_before` | datetime | no | Return issues updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`) |
-| `weight` **(PREMIUM ALL)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
-| `with_labels_details` | boolean | no | If `true`, the response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. `description_html` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21413) in GitLab 12.7 |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `assignee_id` | integer | No | Return issues assigned to the given user `id`. Mutually exclusive with `assignee_username`. `None` returns unassigned issues. `Any` returns issues with an assignee. |
+| `assignee_username` | string array | No | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In GitLab CE, the `assignee_username` array should only contain a single value. Otherwise, an invalid parameter error is returned. |
+| `author_id` | integer | No | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
+| `author_username` | string | No | Return issues created by the given `username`. Similar to `author_id` and mutually exclusive with `author_id`. |
+| `confidential` | boolean | No | Filter confidential or public issues. |
+| `created_after` | datetime | No | Return issues created on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `created_before` | datetime | No | Return issues created on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `due_date` | string | No | Return issues that have no due date, are overdue, or whose due date is this week, this month, or between two weeks ago and next month. Accepts: `0` (no due date), `any`, `today`, `tomorrow`, `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`. |
+| `epic_id` **(PREMIUM ALL)** | integer | No | Return issues associated with the given epic ID. `None` returns issues that are not associated with an epic. `Any` returns issues that are associated with an epic. |
+| `iids[]` | integer array | No | Return only the issues having the given `iid`. |
+| `issue_type` | string | No | Filter to a given type of issue. One of `issue`, `incident`, or `test_case`. |
+| `iteration_id` **(PREMIUM ALL)** | integer | No | Return issues assigned to the given iteration ID. `None` returns issues that do not belong to an iteration. `Any` returns issues that belong to an iteration. Mutually exclusive with `iteration_title`. |
+| `iteration_title` **(PREMIUM ALL)** | string | No | Return issues assigned to the iteration with the given title. Similar to `iteration_id` and mutually exclusive with `iteration_id`. |
+| `labels` | string | No | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
+| `milestone` | string | No | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
+| `my_reaction_emoji` | string | No | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. |
+| `not` | Hash | No | Return issues that do not match the parameters supplied. Accepts: `labels`, `milestone`, `author_id`, `author_username`, `assignee_id`, `assignee_username`, `my_reaction_emoji`, `search`, `in`. |
+| `order_by` | string | No | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at`. |
+| `scope` | string | No | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `all`. |
+| `search` | string | No | Search project issues against their `title` and `description`. |
+| `sort` | string | No | Return issues sorted in `asc` or `desc` order. Default is `desc`. |
+| `state` | string | No | Return all issues or just those that are `opened` or `closed`. |
+| `updated_after` | datetime | No | Return issues updated on or after the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `updated_before` | datetime | No | Return issues updated on or before the given time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| `weight` **(PREMIUM ALL)** | integer | No | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
+| `with_labels_details` | boolean | No | If `true`, the response returns more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -671,11 +672,13 @@ The `assignee` column is deprecated. We now show it as a single-sized array `ass
WARNING:
The `epic_iid` attribute is deprecated and [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5.
-Please use `iid` of the `epic` attribute instead.
+Use `iid` of the `epic` attribute instead.
## Single issue
-Only for administrators. Get a single issue.
+Only for administrators.
+
+Get a single issue.
The preferred way to do this is by using [personal access tokens](../user/profile/personal_access_tokens.md).
@@ -683,9 +686,13 @@ The preferred way to do this is by using [personal access tokens](../user/profil
GET /issues/:id
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer | yes | The ID of the issue |
+| `id` | integer | Yes | The ID of the issue. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -835,7 +842,7 @@ to the GitLab EE API.
WARNING:
The `epic_iid` attribute is deprecated, and [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5.
-Please use `iid` of the `epic` attribute instead.
+Use `iid` of the `epic` attribute instead.
## Single project issue
@@ -848,10 +855,14 @@ The preferred way to do this, is by using [personal access tokens](../user/profi
GET /projects/:id/issues/:issue_iid
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -994,37 +1005,39 @@ The `assignee` column is deprecated. We now show it as a single-sized array `ass
WARNING:
The `epic_iid` attribute is deprecated and [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5.
-Please use `iid` of the `epic` attribute instead.
+Use `iid` of the `epic` attribute instead.
## New issue
-> The `weight` property moved to GitLab Premium in 13.9.
-
Creates a new project issue.
```plaintext
POST /projects/:id/issues
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------------------------------------|----------------|----------|--------------|
-| `assignee_id` | integer | no | The ID of the user to assign the issue to. Only appears on GitLab Free. |
-| `assignee_ids` **(PREMIUM ALL)** | integer array | no | The IDs of the users to assign the issue to. |
-| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. |
-| `created_at` | string | no | When the issue was created. Date time string, ISO 8601 formatted, for example `2016-03-11T03:45:40Z`. Requires administrator or project/group owner rights. |
-| `description` | string | no | The description of an issue. Limited to 1,048,576 characters. |
-| `discussion_to_resolve` | string | no | The ID of a discussion to resolve. This fills out the issue with a default description and mark the discussion as resolved. Use in combination with `merge_request_to_resolve_discussions_of`. |
-| `due_date` | string | no | The due date. Date time string in the format `YYYY-MM-DD`, for example `2016-03-11` |
-| `epic_id` **(PREMIUM ALL)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
-| `epic_iid` **(PREMIUM ALL)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5) |
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `iid` | integer/string | no | The internal ID of the project's issue (requires administrator or project owner rights) |
-| `issue_type` | string | no | The type of issue. One of `issue`, `incident`, or `test_case`. Default is `issue`. |
-| `labels` | string | no | Comma-separated label names for an issue |
-| `merge_request_to_resolve_discussions_of` | integer | no | The IID of a merge request in which to resolve all issues. This fills out the issue with a default description and mark all discussions as resolved. When passing a description or title, these values take precedence over the default values.|
-| `milestone_id` | integer | no | The global ID of a milestone to assign issue. To find the `milestone_id` associated with a milestone, view an issue with the milestone assigned and [use the API](#single-project-issue) to retrieve the issue's details. |
-| `title` | string | yes | The title of an issue |
-| `weight` **(PREMIUM ALL)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `assignee_id` | integer | No | The ID of the user to assign the issue to. Only appears on GitLab Free. |
+| `assignee_ids` **(PREMIUM ALL)** | integer array | No | The IDs of the users to assign the issue to. |
+| `confidential` | boolean | No | Set an issue to be confidential. Default is `false`. |
+| `created_at` | string | No | When the issue was created. Date time string, ISO 8601 formatted, for example `2016-03-11T03:45:40Z`. Requires administrator or project/group owner rights. |
+| `description` | string | No | The description of an issue. Limited to 1,048,576 characters. |
+| `discussion_to_resolve` | string | No | The ID of a discussion to resolve. This fills out the issue with a default description and mark the discussion as resolved. Use in combination with `merge_request_to_resolve_discussions_of`. |
+| `due_date` | string | No | The due date. Date time string in the format `YYYY-MM-DD`, for example `2016-03-11`. |
+| `epic_id` **(PREMIUM ALL)** | integer | No | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
+| `epic_iid` **(PREMIUM ALL)** | integer | No | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5). |
+| `iid` | integer/string | No | The internal ID of the project's issue (requires administrator or project owner rights). |
+| `issue_type` | string | No | The type of issue. One of `issue`, `incident`, or `test_case`. Default is `issue`. |
+| `labels` | string | No | Comma-separated label names for an issue. |
+| `merge_request_to_resolve_discussions_of` | integer | No | The IID of a merge request in which to resolve all issues. This fills out the issue with a default description and mark all discussions as resolved. When passing a description or title, these values take precedence over the default values.|
+| `milestone_id` | integer | No | The global ID of a milestone to assign issue. To find the `milestone_id` associated with a milestone, view an issue with the milestone assigned and [use the API](#single-project-issue) to retrieve the issue's details. |
+| `title` | string | Yes | The title of an issue. |
+| `weight` **(PREMIUM ALL)** | integer | No | The weight of the issue. Valid values are greater than or equal to 0. |
+
+Example request:
```shell
curl --request POST \
@@ -1144,19 +1157,16 @@ The `assignee` column is deprecated. We now show it as a single-sized array `ass
WARNING:
The `epic_iid` attribute is deprecated and [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5.
-Please use `iid` of the `epic` attribute instead.
+Use `iid` of the `epic` attribute instead.
-## Rate limits
+### Rate limits
To help avoid abuse, users can be limited to a specific number of `Create` requests per minute.
See [Issues rate limits](../administration/settings/rate_limit_on_issues_creation.md).
-## Edit issue
+## Edit an issue
-> The `weight` property moved to GitLab Premium in 13.9.
-
-Updates an existing project issue. This call is also used to mark an issue as
-closed.
+Updates an existing project issue. This request is also used to close or reopen an issue (with `state_event`).
At least one of the following parameters is required for the request to be successful:
@@ -1177,26 +1187,30 @@ At least one of the following parameters is required for the request to be succe
PUT /projects/:id/issues/:issue_iid
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|----------------|---------|----------|------------------------------------------------------------------------------------------------------------|
-| `add_labels` | string | no | Comma-separated label names to add to an issue. |
-| `assignee_ids` | integer array | no | The ID of the users to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. |
-| `confidential` | boolean | no | Updates an issue to be confidential |
-| `description` | string | no | The description of an issue. Limited to 1,048,576 characters. |
-| `discussion_locked` | boolean | no | Flag indicating if the issue's discussion is locked. If the discussion is locked only project members can add or edit comments. |
-| `due_date` | string | no | The due date. Date time string in the format `YYYY-MM-DD`, for example `2016-03-11` |
-| `epic_id` **(PREMIUM ALL)** | integer | no | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
-| `epic_iid` **(PREMIUM ALL)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5) |
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
-| `issue_type` | string | no | Updates the type of issue. One of `issue`, `incident`, or `test_case`. |
-| `labels` | string | no | Comma-separated label names for an issue. Set to an empty string to unassign all labels. |
-| `milestone_id` | integer | no | The global ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.|
-| `remove_labels`| string | no | Comma-separated label names to remove from an issue. |
-| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
-| `title` | string | no | The title of an issue |
-| `updated_at` | string | no | When the issue was updated. Date time string, ISO 8601 formatted, for example `2016-03-11T03:45:40Z` (requires administrator or project owner rights). Empty string or null values are not accepted.|
-| `weight` **(PREMIUM ALL)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. 0 |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+| `add_labels` | string | No | Comma-separated label names to add to an issue. |
+| `assignee_ids` | integer array | No | The ID of the users to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. |
+| `confidential` | boolean | No | Updates an issue to be confidential. |
+| `description` | string | No | The description of an issue. Limited to 1,048,576 characters. |
+| `discussion_locked` | boolean | No | Flag indicating if the issue's discussion is locked. If the discussion is locked only project members can add or edit comments. |
+| `due_date` | string | No | The due date. Date time string in the format `YYYY-MM-DD`, for example `2016-03-11`. |
+| `epic_id` **(PREMIUM ALL)** | integer | No | ID of the epic to add the issue to. Valid values are greater than or equal to 0. |
+| `epic_iid` **(PREMIUM ALL)** | integer | No | IID of the epic to add the issue to. Valid values are greater than or equal to 0. (deprecated, [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5). |
+| `issue_type` | string | No | Updates the type of issue. One of `issue`, `incident`, or `test_case`. |
+| `labels` | string | No | Comma-separated label names for an issue. Set to an empty string to unassign all labels. |
+| `milestone_id` | integer | No | The global ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.|
+| `remove_labels`| string | No | Comma-separated label names to remove from an issue. |
+| `state_event` | string | No | The state event of an issue. To close the issue, use `close`, and to reopen it, use `reopen`. |
+| `title` | string | No | The title of an issue. |
+| `updated_at` | string | No | When the issue was updated. Date time string, ISO 8601 formatted, for example `2016-03-11T03:45:40Z` (requires administrator or project owner rights). Empty string or null values are not accepted.|
+| `weight` **(PREMIUM ALL)** | integer | No | The weight of the issue. Valid values are greater than or equal to 0. |
+
+Example request:
```shell
curl --request PUT \
@@ -1320,23 +1334,29 @@ Issues created by users on GitLab Ultimate include the `health_status` property:
WARNING:
The `epic_iid` attribute is deprecated and [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5.
-Please use `iid` of the `epic` attribute instead.
+Use `iid` of the `epic` attribute instead.
WARNING:
`assignee` column is deprecated. We now show it as a single-sized array `assignees` to conform to the GitLab EE API.
## Delete an issue
-Only for administrators and project owners. Deletes an issue.
+Only for administrators and project owners.
+
+Deletes an issue.
```plaintext
DELETE /projects/:id/issues/:issue_iid
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --request DELETE \
@@ -1344,22 +1364,26 @@ curl --request DELETE \
--url "https://gitlab.example.com/api/v4/projects/4/issues/85"
```
+If successful, returns [`204 No Content`](rest/index.md#status-codes).
+
## Reorder an issue
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211864) in GitLab 13.2.
-
-Reorders an issue, you can see the results when sorting issues manually
+Reorders an issue. You can see the results when [sorting issues manually](../user/project/issues/sorting_issue_lists.md#manual-sorting).
```plaintext
PUT /projects/:id/issues/:issue_iid/reorder
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of the project's issue |
-| `move_after_id` | integer | no | The global ID of a project's issue that should be placed after this issue |
-| `move_before_id` | integer | no | The global ID of a project's issue that should be placed before this issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of the project's issue. |
+| `move_after_id` | integer | No | The global ID of a project's issue that should be placed after this issue. |
+| `move_before_id` | integer | No | The global ID of a project's issue that should be placed before this issue. |
+
+Example request:
```shell
curl --request PUT \
@@ -1380,11 +1404,15 @@ project, it's then assigned to the issue being moved.
POST /projects/:id/issues/:issue_iid/move
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-----------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
-| `to_project_id` | integer | yes | The ID of the new project |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+| `to_project_id` | integer | Yes | The ID of the new project. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -1514,20 +1542,22 @@ The `assignee` column is deprecated. We now show it as a single-sized array `ass
WARNING:
The `epic_iid` attribute is deprecated and [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5.
-Please use `iid` of the `epic` attribute instead.
+Use `iid` of the `epic` attribute instead.
## Clone an issue
-Clone the issue to given project. If the user has insufficient permissions,
-an error message with status code `400` is returned.
-
+Clone the issue to given project.
Copies as much data as possible as long as the target project contains equivalent
-criteria such as labels or milestones.
+criteria, such as labels or milestones.
+
+If you have insufficient permissions, an error message with status code `400` is returned.
```plaintext
POST /projects/:id/issues/:issue_iid/clone
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
| --------------- | -------------- | ---------------------- | --------------------------------- |
| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
@@ -1535,6 +1565,8 @@ POST /projects/:id/issues/:issue_iid/clone
| `to_project_id` | integer | Yes | ID of the new project. |
| `with_notes` | boolean | No | Clone the issue with [notes](notes.md). Default is `false`. |
+Example request:
+
```shell
curl --request POST \
--header "PRIVATE-TOKEN: " \
@@ -1626,7 +1658,11 @@ Example response:
}
```
-## Subscribe to an issue
+## Notifications
+
+The following requests are related to [email notifications](../user/profile/notifications.md) for issues.
+
+### Subscribe to an issue
Subscribes the authenticated user to an issue to receive notifications.
If the user is already subscribed to the issue, the status code `304`
@@ -1636,10 +1672,14 @@ is returned.
POST /projects/:id/issues/:issue_iid/subscribe
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --request POST \
@@ -1769,9 +1809,9 @@ The `assignee` column is deprecated. We now show it as a single-sized array `ass
WARNING:
The `epic_iid` attribute is deprecated and [scheduled for removal](https://gitlab.com/gitlab-org/gitlab/-/issues/35157) in API version 5.
-Please use `iid` of the `epic` attribute instead.
+Use `iid` of the `epic` attribute instead.
-## Unsubscribe from an issue
+### Unsubscribe from an issue
Unsubscribes the authenticated user from the issue to not receive notifications
from it. If the user is not subscribed to the issue, the
@@ -1781,10 +1821,14 @@ status code `304` is returned.
POST /projects/:id/issues/:issue_iid/unsubscribe
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --request POST \
@@ -1857,10 +1901,14 @@ returned.
POST /projects/:id/issues/:issue_iid/todo
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --request POST \
@@ -1982,9 +2030,9 @@ Supported attributes:
| Attribute | Type | Required | Description |
| :---------- | :------------- | :------- | :---------- |
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
-| `body` | String | yes | The content of a note. Must contain `/promote` at the start of a new line. |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+| `body` | String | Yes | The content of a note. Must contain `/promote` at the start of a new line. If the note only contains `/promote`, promotes the issue, but doesn't add a comment. Otherwise, the other lines form a comment.|
Example request:
@@ -2024,7 +2072,11 @@ Example response:
}
```
-## Set a time estimate for an issue
+## Time tracking
+
+The following requests are related to [time tracking](../user/project/time_tracking.md) on issues.
+
+### Set a time estimate for an issue
Sets an estimated time of work for this issue.
@@ -2032,11 +2084,15 @@ Sets an estimated time of work for this issue.
POST /projects/:id/issues/:issue_iid/time_estimate
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|------------------------------------------|
-| `duration` | string | yes | The duration in human format. e.g: `3h30m` |
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `duration` | string | Yes | The duration in human-readable format. For example: `3h30m`. |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --request POST \
@@ -2055,7 +2111,7 @@ Example response:
}
```
-## Reset the time estimate for an issue
+### Reset the time estimate for an issue
Resets the estimated time for this issue to 0 seconds.
@@ -2063,10 +2119,14 @@ Resets the estimated time for this issue to 0 seconds.
POST /projects/:id/issues/:issue_iid/reset_time_estimate
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --request POST \
@@ -2085,20 +2145,24 @@ Example response:
}
```
-## Add spent time for an issue
+### Add spent time for an issue
-Adds spent time for this issue
+Adds spent time for this issue.
```plaintext
POST /projects/:id/issues/:issue_iid/add_spent_time
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|------------------------------------------|
-| `duration` | string | yes | The duration in human format. e.g: `3h30m` |
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
-| `summary` | string | no | A summary of how the time was spent |
+| `duration` | string | Yes | The duration in human-readable format. For example: `3h30m` |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+| `summary` | string | No | A summary of how the time was spent. |
+
+Example request:
```shell
curl --request POST \
@@ -2117,7 +2181,7 @@ Example response:
}
```
-## Reset spent time for an issue
+### Reset spent time for an issue
Resets the total spent time for this issue to 0 seconds.
@@ -2125,10 +2189,14 @@ Resets the total spent time for this issue to 0 seconds.
POST /projects/:id/issues/:issue_iid/reset_spent_time
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --request POST \
@@ -2147,19 +2215,25 @@ Example response:
}
```
-## Get time tracking stats
+### Get time tracking stats
-If the project is private or the issue is confidential, you need to provide credentials to authorize.
+Gets time tracking stats for an issue in human-readable format (for example, `1h30m`) and in number of seconds.
+
+If the project is private or the issue is confidential, you must provide credentials to authorize.
The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
```plaintext
GET /projects/:id/issues/:issue_iid/time_stats
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -2177,9 +2251,13 @@ Example response:
}
```
-## List merge requests related to issue
+## Merge requests
-Get all the merge requests that are related to the issue.
+The following requests are related to relationships between issues and merge requests.
+
+### List merge requests related to issue
+
+Gets all the merge requests that are related to the issue.
If the project is private or the issue is confidential, you need to provide credentials to authorize.
The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
@@ -2188,10 +2266,14 @@ The preferred way to do this, is by using [personal access tokens](../user/profi
GET /projects/:id/issues/:issue_iid/related_merge_requests
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -2338,9 +2420,9 @@ Example response:
]
```
-## List merge requests that close a particular issue on merge
+### List merge requests that close a particular issue on merge
-Get all merge requests that close a particular issue when merged.
+Gets all merge requests that close a particular issue when merged.
If the project is private or the issue is confidential, you need to provide credentials to authorize.
The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
@@ -2349,10 +2431,14 @@ The preferred way to do this, is by using [personal access tokens](../user/profi
GET /projects/:id/issues/:issue_iid/closed_by
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
| ----------- | ---------------| -------- | ---------------------------------- |
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project issue. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -2418,7 +2504,9 @@ Example response:
]
```
-## Participants on issues
+## List participants in an issue
+
+Lists users that are participants in the issue.
If the project is private or the issue is confidential, you need to provide credentials to authorize.
The preferred way to do this, is by using [personal access tokens](../user/profile/personal_access_tokens.md).
@@ -2427,10 +2515,14 @@ The preferred way to do this, is by using [personal access tokens](../user/profi
GET /projects/:id/issues/:issue_iid/participants
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -2462,20 +2554,27 @@ Example response:
## Comments on issues
-Comments are done via the [notes](notes.md) resource.
+Interact with comments using the [Notes API](notes.md).
## Get user agent details
Available only for administrators.
+Gets the user agent string and IP of the user who created the issue.
+Used for spam tracking.
+
```plaintext
GET /projects/:id/issues/:issue_iid/user_agent_detail
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -2494,24 +2593,36 @@ Example response:
## List issue state events
-To track which state was set, who did it, and when it happened, check out
+To track which state was set, who did it, and when it happened, use
[Resource state events API](resource_state_events.md#issues).
-## Upload metric image
+## Incidents
-Available only for Incident issues.
+The following requests are available only for [incidents](../operations/incident_management/incidents.md).
+
+### Upload metric image
+
+Available only for [incidents](../operations/incident_management/incidents.md).
+
+Uploads a screenshot of metric charts to show in the incident's **Metrics** tab.
+When you upload an image, you can associate the image with text or a link to the original graph.
+If you add a URL, you can access the original graph by selecting the hyperlink above the uploaded image.
```plaintext
POST /projects/:id/issues/:issue_iid/metric_images
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
-| `file` | file | yes | The image file to be uploaded |
-| `url` | string | no | The URL to view more metric information |
-| `url_text` | string | no | A description of the image or URL |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+| `file` | file | Yes | The image file to be uploaded. |
+| `url` | string | No | The URL to view more metric information. |
+| `url_text` | string | No | A description of the image or URL. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -2534,18 +2645,24 @@ Example response:
}
```
-## List metric images
+### List metric images
-Available only for Incident issues.
+Available only for [incidents](../operations/incident_management/incidents.md).
+
+Lists screenshots of metric charts shown in the incident's **Metrics** tab.
```plaintext
GET /projects/:id/issues/:issue_iid/metric_images
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -2573,21 +2690,27 @@ Example response:
]
```
-## Update metric image
+### Update metric image
-Available only for Incident issues.
+Available only for [incidents](../operations/incident_management/incidents.md).
+
+Edits attributes of a screenshot of metric charts shown in the incident's **Metrics** tab.
```plaintext
PUT /projects/:id/issues/:issue_iid/metric_images/:image_id
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
-| `image_id` | integer | yes | The ID of the image |
-| `url` | string | no | The URL to view more metric information |
-| `url_text` | string | no | A description of the image or URL |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+| `image_id` | integer | Yes | The ID of the image. |
+| `url` | string | No | The URL to view more metric information. |
+| `url_text` | string | No | A description of the image or URL. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
@@ -2610,19 +2733,25 @@ Example response:
}
```
-## Delete metric image
+### Delete metric image
-Available only for Incident issues.
+Available only for [incidents](../operations/incident_management/incidents.md).
+
+Delete a screenshot of metric charts shown in the incident's **Metrics** tab.
```plaintext
DELETE /projects/:id/issues/:issue_iid/metric_images/:image_id
```
+Supported attributes:
+
| Attribute | Type | Required | Description |
|-------------|---------|----------|--------------------------------------|
-| `id` | integer/string | yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of a project's issue |
-| `image_id` | integer | yes | The ID of the image |
+| `id` | integer/string | Yes | The global ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `issue_iid` | integer | Yes | The internal ID of a project's issue. |
+| `image_id` | integer | Yes | The ID of the image. |
+
+Example request:
```shell
curl --header "PRIVATE-TOKEN: " \
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index f958cb1db74..d332af418e5 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -45,7 +45,7 @@ can't link to files outside it.
To ensure maximum availability of the cache, do one or more of the following:
-- [Tag your runners](../runners/configure_runners.md#use-tags-to-control-which-jobs-a-runner-can-run) and use the tag on jobs
+- [Tag your runners](../runners/configure_runners.md#control-jobs-that-a-runner-can-run) and use the tag on jobs
that share the cache.
- [Use runners that are only available to a particular project](../runners/runners_scope.md#prevent-a-project-runner-from-being-enabled-for-other-projects).
- [Use a `key`](../yaml/index.md#cachekey) that fits your workflow. For example,
diff --git a/doc/ci/components/index.md b/doc/ci/components/index.md
index 6f1365561dc..f052fbf98b6 100644
--- a/doc/ci/components/index.md
+++ b/doc/ci/components/index.md
@@ -216,6 +216,17 @@ If you disable [catalog resource setting](#set-a-component-project-as-a-catalog-
the component project and all versions are removed from the catalog. To publish it again,
you must re-enable the setting and release a new version.
+### Unpublish a component project
+
+To remove a component project from the catalog, turn off the [**CI/CD Catalog resource**](#set-a-component-project-as-a-catalog-resource) toggle.
+in the project settings.
+
+WARNING:
+This action destroys the metadata about the component project and its versions published
+in the catalog. The project and its repository still exist, but are not visible in the catalog.
+
+To publish the component project in the catalog again, you need to [publish a new release](#publish-a-new-release).
+
## Best practices
This section describes some best practices for creating high quality component projects.
diff --git a/doc/ci/migration/github_actions.md b/doc/ci/migration/github_actions.md
index edd04743707..ec55f129c1e 100644
--- a/doc/ci/migration/github_actions.md
+++ b/doc/ci/migration/github_actions.md
@@ -466,7 +466,7 @@ Some key details about runners:
- Runners can be [configured](../runners/runners_scope.md) to be shared across an instance,
a group, or dedicated to a single project.
-- You can use the [`tags` keyword](../runners/configure_runners.md#use-tags-to-control-which-jobs-a-runner-can-run)
+- You can use the [`tags` keyword](../runners/configure_runners.md#control-jobs-that-a-runner-can-run)
for finer control, and associate runners with specific jobs. For example, you can use a tag for jobs that
require dedicated, more powerful, or specific hardware.
- GitLab has [autoscaling for runners](https://docs.gitlab.com/runner/configuration/autoscale.html).
diff --git a/doc/ci/migration/jenkins.md b/doc/ci/migration/jenkins.md
index 12f3901436d..f430b1ac7b9 100644
--- a/doc/ci/migration/jenkins.md
+++ b/doc/ci/migration/jenkins.md
@@ -482,7 +482,7 @@ Some key details about runners:
- Runners can be [configured](../runners/runners_scope.md) to be shared across an instance,
a group, or dedicated to a single project.
-- You can use the [`tags` keyword](../runners/configure_runners.md#use-tags-to-control-which-jobs-a-runner-can-run)
+- You can use the [`tags` keyword](../runners/configure_runners.md#control-jobs-that-a-runner-can-run)
for finer control, and associate runners with specific jobs. For example, you can use a tag for jobs that
require dedicated, more powerful, or specific hardware.
- GitLab has [autoscaling for runners](https://docs.gitlab.com/runner/configuration/autoscale.html).
diff --git a/doc/ci/pipelines/settings.md b/doc/ci/pipelines/settings.md
index 6cd38c64e8e..22ff98fe449 100644
--- a/doc/ci/pipelines/settings.md
+++ b/doc/ci/pipelines/settings.md
@@ -225,7 +225,7 @@ You can define how long a job can run before it times out.
Jobs that exceed the timeout are marked as failed.
-You can override this value [for individual runners](../runners/configure_runners.md#set-maximum-job-timeout-for-a-runner).
+You can override this value [for individual runners](../runners/configure_runners.md#set-the-maximum-job-timeout).
## Pipeline badges
diff --git a/doc/ci/runners/configure_runners.md b/doc/ci/runners/configure_runners.md
index 3f8d35957ef..eef78b70155 100644
--- a/doc/ci/runners/configure_runners.md
+++ b/doc/ci/runners/configure_runners.md
@@ -11,53 +11,79 @@ If you have installed your own runners, you can configure and secure them in Git
If you need to configure runners on the machine where you installed GitLab Runner, see
[the GitLab Runner documentation](https://docs.gitlab.com/runner/configuration/).
-## Manually clear the runner cache
+## Set the maximum job timeout
-Read [clearing the cache](../caching/index.md#clearing-the-cache).
+You can specify a maximum job timeout for each runner to prevent projects
+with longer job timeouts from using the runner. The maximum job timeout is
+used of it is shorter than the job timeout defined in the project.
-## Set maximum job timeout for a runner
+### For a shared runner
-For each runner, you can specify a *maximum job timeout*. This timeout,
-if smaller than the [project defined timeout](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run), takes precedence.
+Prerequisites:
-This feature can be used to prevent your shared runner from being overwhelmed
-by a project that has jobs with a long timeout (for example, one week).
+- You must be an administrator.
-On GitLab.com, you cannot override the job timeout for shared runners and must use the [project defined timeout](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run).
+On GitLab.com, you cannot override the job timeout for shared runners and must use the [project defined timeout](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run) instead.
+
+To set the maximum job timeout:
+
+1. On the left sidebar, at the bottom, select **Admin Area**.
+1. Select **CI/CD > Runners**.
+1. To the right of the runner, you want to edit, select **Edit** (**{pencil}**).
+1. In the **Maximum job timeout** field, enter a value in seconds. The minimum amount is 600 seconds (10 minutes).
+1. Select **Save changes**.
+
+### For a group runner
+
+Prerequisites:
+
+- You must have the Owner role for the group.
+
+To set the maximum job timeout:
+
+1. On the left sidebar, select **Search or go to** and find your group.
+1. Select **Build > Runners**.
+1. To the right of the runner you want to edit, select **Edit** (**{pencil}**).
+1. In the **Maximum job timeout** field, enter a value in seconds. The minimum amount is 600 seconds (10 minutes).
+1. Select **Save changes**.
+
+### For a project runner
+
+Prerequisites:
+
+- You must have the Owner role for the project.
To set the maximum job timeout:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > CI/CD**.
1. Expand **Runners**.
-1. Select your project runner to edit the settings.
-1. Enter a value under **Maximum job timeout**. Must be 10 minutes or more. If not
- defined, the [project's job timeout setting](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run)
- is used.
+1. To the right of the runner you want to edit, select **Edit** (**{pencil}**).
+1. In the **Maximum job timeout** field, enter a value in seconds. The minimum amount is 600 seconds (10 minutes). If not defined, the [job timeout for the project](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run) is used instead.
1. Select **Save changes**.
-How this feature works:
+## How maximum job timeout works
**Example 1 - Runner timeout bigger than project timeout**
-1. You set the _maximum job timeout_ for a runner to 24 hours
-1. You set the _CI/CD Timeout_ for a project to **2 hours**
-1. You start a job
-1. The job, if running longer, times out after **2 hours**
+1. You set the _maximum job timeout_ for a runner to 24 hours.
+1. You set the _CI/CD Timeout_ for a project to **2 hours**.
+1. You start a job.
+1. The job, if running longer, times out after **2 hours**.
**Example 2 - Runner timeout not configured**
-1. You remove the _maximum job timeout_ configuration from a runner
-1. You set the _CI/CD Timeout_ for a project to **2 hours**
-1. You start a job
-1. The job, if running longer, times out after **2 hours**
+1. You remove the _maximum job timeout_ configuration from a runner.
+1. You set the _CI/CD Timeout_ for a project to **2 hours**.
+1. You start a job.
+1. The job, if running longer, times out after **2 hours**.
**Example 3 - Runner timeout smaller than project timeout**
-1. You set the _maximum job timeout_ for a runner to **30 minutes**
-1. You set the _CI/CD Timeout_ for a project to 2 hours
-1. You start a job
-1. The job, if running longer, times out after **30 minutes**
+1. You set the _maximum job timeout_ for a runner to **30 minutes**.
+1. You set the _CI/CD Timeout_ for a project to 2 hours.
+1. You start a job.
+1. The job, if running longer, times out after **30 minutes**.
## Set `script` and `after_script` timeouts
@@ -66,7 +92,7 @@ How this feature works:
To control the amount of time `script` and `after_script` runs before it terminates, you can set specify a timeout.
For example, you can specify a timeout to terminate a long-running `script` early, so that artifacts and caches can still be uploaded
-before the [job timeout](#set-maximum-job-timeout-for-a-runner) is exceeded.
+before the job timeout is exceeded.
- To set a timeout for `script`, use the job variable `RUNNER_SCRIPT_TIMEOUT`.
- To set a timeout for `after_script`, and override the default of 5 minutes, use the job variable `RUNNER_AFTER_SCRIPT_TIMEOUT`.
@@ -105,26 +131,11 @@ of shared runners on large GitLab instances. This ensures that you
control access to your GitLab instances and secure [runner executors](https://docs.gitlab.com/runner/executors/).
If certain executors run a job, the file system, the code the runner executes,
-and the runner authentication token may be exposed. This means that anyone that runs jobs
+and the runner authentication token may be exposed. This means that anyone who runs jobs
on a _shared runner_ can access another user's code that runs on the runner.
Users with access to the runner authentication token can use it to create a clone of
a runner and submit false jobs in a vector attack. For more information, see [Security Considerations](https://docs.gitlab.com/runner/security/).
-### Prevent runners from revealing sensitive information
-
-To ensure runners don't reveal sensitive information, you can configure them to only run jobs
-on [protected branches](../../user/project/protected_branches.md), or jobs that have [protected tags](../../user/project/protected_tags.md).
-
-To prevent runners from revealing sensitive information:
-
-1. On the left sidebar, select **Search or go to** and find your project.
-1. Select **Settings > CI/CD**.
-1. Expand **Runners**.
-1. Find the runner you want to protect or unprotect. Make sure the runner is enabled.
-1. Select **Edit** (**{pencil}**).
-1. Select the **Protected** checkbox.
-1. Select **Save changes**.
-
### Using shared runners in forked projects
When a project is forked, the job settings related to jobs are copied. If you have shared runners
@@ -179,37 +190,129 @@ To reset the runner authentication token:
- [Create a project runner](runners_scope.md#create-a-project-runner-with-a-runner-authentication-token).
1. Optional. To verify that the previous runner authentication token has been revoked, use the [Runners API](../../api/runners.md#verify-authentication-for-a-registered-runner).
-## Use tags to control which jobs a runner can run
+### Automatically rotate runner authentication tokens
-You can use [tags](../yaml/index.md#tags) to ensure that runners only run the jobs they are equipped
-to run. For example, you can specify the `rails` tag for runners that have the dependencies to run
+You can specify an interval for runner authentication tokens to rotate.
+This rotation helps ensure the security of the tokens assigned to your runners.
+
+Prerequisites:
+
+- Runners must use [GitLab Runner 15.3 or later](https://docs.gitlab.com/runner/#gitlab-runner-versions).
+- You must be an administrator.
+
+To automatically rotate runner authentication tokens:
+
+1. On the left sidebar, at the bottom, select **Admin Area**.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **Continuous Integration and Deployment**
+1. Set a **Runners expiration** time for runners, leave empty for no expiration.
+1. Select **Save changes**.
+
+Before the interval expires, runners automatically request a new runner authentication token.
+
+## Prevent runners from revealing sensitive information
+
+To ensure runners don't reveal sensitive information, you can configure them to only run jobs
+on [protected branches](../../user/project/protected_branches.md), or jobs that have [protected tags](../../user/project/protected_tags.md).
+
+### For a shared runner
+
+Prerequisites:
+
+- You must be an administrator.
+
+1. On the left sidebar, at the bottom, select **Admin Area**.
+1. Select **CI/CD > Runners**.
+1. To the right of the runner you want to protect, select **Edit** (**{pencil}**).
+1. Select the **Protected** checkbox.
+1. Select **Save changes**.
+
+### For a group runner
+
+Prerequisites:
+
+- You must have the Owner role for the group.
+
+1. On the left sidebar, select **Search or go to** and find your group.
+1. Select **Build > Runners**.
+1. To the right of the runner you want to protect, select **Edit** (**{pencil}**).
+1. Select the **Protected** checkbox.
+1. Select **Save changes**.
+
+### For a project runner
+
+Prerequisites:
+
+- You must have the Owner role for the project.
+
+1. On the left sidebar, select **Search or go to** and find your project.
+1. Select **Settings > CI/CD**.
+1. Expand **Runners**.
+1. To the right of the runner you want to protect, select **Edit** (**{pencil}**).
+1. Select the **Protected** checkbox.
+1. Select **Save changes**.
+
+## Control jobs that a runner can run
+
+You can use [tags](../yaml/index.md#tags) to control the jobs a runner can run.
+For example, you can specify the `rails` tag for runners that have the dependencies to run
Rails test suites.
GitLab CI/CD tags are different to Git tags. GitLab CI/CD tags are associated with runners.
Git tags are associated with commits.
-### Set a runner to run untagged jobs
+### For a shared runner
-When you [register a runner](https://docs.gitlab.com/runner/register/), its default behavior is to **only pick**
-[tagged jobs](../yaml/index.md#tags).
-To change this, you must have the Owner role for the project.
+Prerequisites:
-To make a runner pick untagged jobs:
+- You must be an administrator.
+
+To set the maximum job timeout:
+
+1. On the left sidebar, at the bottom, select **Admin Area**.
+1. Select **CI/CD > Runners**.
+1. To the right of the runner you want to edit, select **Edit** (**{pencil}**).
+1. Set the runner to run tagged or untagged jobs:
+ - To run tagged jobs, in the **Tags** field, enter the job tags separated with a comma. For example, `macos`, `rails`.
+ - To run untagged jobs, select the **Run untagged jobs** checkbox.
+1. Select **Save changes**.
+
+### For a group runner
+
+Prerequisites:
+
+- You must have the Owner role for the group.
+
+To set the maximum job timeout:
+
+1. On the left sidebar, select **Search or go to** and find your group.
+1. Select **Build > Runners**.
+1. To the right of the runner you want to edit, select **Edit** (**{pencil}**).
+1. Set the runner to run tagged or untagged jobs:
+ - To run tagged jobs, in the **Tags** field, enter the job tags separated with a comma. For example, `macos`, `ruby`.
+ - To run untagged jobs, select the **Run untagged jobs** checkbox.
+1. Select **Save changes**.
+
+### For a project runner
+
+Prerequisites:
+
+- You must have the Owner role for the project.
+
+To set a runner to run tagged jobs:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Settings > CI/CD**.
1. Expand **Runners**.
-1. Find the runner you want to pick untagged jobs and make sure it's enabled.
-1. Select **Edit** (**{pencil}**).
-1. Select the **Run untagged jobs** checkbox.
+1. To the right of the runner you want to edit, select **Edit** (**{pencil}**).
+1. Set the runner to run tagged or untagged jobs:
+ - To run tagged jobs, in the **Tags** field, enter the job tags separated with a comma. For example, `macos`, `ruby`.
+ - To run untagged jobs, select the **Run untagged jobs** checkbox.
1. Select **Save changes**.
-NOTE:
-The runner tags list cannot be empty when it's not allowed to pick untagged jobs.
+### How the runner uses tags
-Below are some example scenarios of different variations.
-
-### Runner runs only tagged jobs
+#### Runner runs only tagged jobs
The following examples illustrate the potential impact of the runner being set
to run only tagged jobs.
@@ -229,7 +332,7 @@ Example 3:
1. The runner is configured to run only tagged jobs and has the `docker` tag.
1. A job that has no tags defined is executed and stuck.
-### Runner is allowed to run untagged jobs
+#### Runner is allowed to run untagged jobs
The following examples illustrate the potential impact of the runner being set
to run tagged and untagged jobs.
@@ -246,7 +349,7 @@ Example 2:
1. A job that has no tags defined is executed and run.
1. A second job that has a `docker` tag defined is stuck.
-### A runner and a job have multiple tags
+#### A runner and a job have multiple tags
The selection logic that matches the job and runner is based on the list of `tags`
defined in the job.
@@ -273,7 +376,9 @@ Example 3:
You can use tags to run different jobs on different platforms. For
example, if you have an OS X runner with tag `osx` and a Windows runner with tag
-`windows`, you can run a job on each platform:
+`windows`, you can run a job on each platform.
+
+Update the `tags` field in the `config.toml`:
```yaml
windows job:
@@ -295,7 +400,7 @@ osx job:
> Introduced in [GitLab 14.1](https://gitlab.com/gitlab-org/gitlab/-/issues/35742).
-You can use [CI/CD variables](../variables/index.md) with `tags` for dynamic runner selection:
+In the `.gitlab-ci.yml` file, use [CI/CD variables](../variables/index.md) with `tags` for dynamic runner selection:
```yaml
variables:
@@ -928,22 +1033,3 @@ No manual intervention should be required, and no running jobs should be affecte
If you need to manually update the runner authentication token, you can run a
command to [reset the token](https://docs.gitlab.com/runner/commands/#gitlab-runner-reset-token).
-
-### Automatically rotate runner authentication tokens
-
-You can specify an interval for runner authentication tokens to rotate.
-This rotation helps ensure the security of the tokens assigned to your runners.
-
-Prerequisites:
-
-- Ensure your runners are using [GitLab Runner 15.3 or later](https://docs.gitlab.com/runner/#gitlab-runner-versions).
-
-To automatically rotate runner authentication tokens:
-
-1. On the left sidebar, at the bottom, select **Admin Area**.
-1. On the left sidebar, select **Settings > CI/CD**.
-1. Expand **Continuous Integration and Deployment**
-1. Set a **Runners expiration** time for runners, leave empty for no expiration.
-1. Select **Save**.
-
-Before the interval expires, runners automatically request a new runner authentication token.
diff --git a/doc/ci/runners/index.md b/doc/ci/runners/index.md
index d6f5d9d0230..1df93ed8896 100644
--- a/doc/ci/runners/index.md
+++ b/doc/ci/runners/index.md
@@ -20,7 +20,7 @@ For more information about the cost factor applied to the machine type based on
The number of minutes you can use on these runners depends on the [maximum number of units of compute](../pipelines/cicd_minutes.md)
in your [subscription plan](https://about.gitlab.com/pricing/).
-[Untagged](../../ci/runners/configure_runners.md#use-tags-to-control-which-jobs-a-runner-can-run) jobs automatically run in containers
+[Untagged](../../ci/runners/configure_runners.md#control-jobs-that-a-runner-can-run) jobs automatically run in containers
on the `small` Linux runners.
The objective is to make 90% of CI/CD jobs start executing in 120 seconds or less. The error rate should be less than 0.5%.
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index 6d53d2fd7b1..8d64c82ce46 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -4568,7 +4568,7 @@ In this example, only runners with *both* the `ruby` and `postgres` tags can run
**Related topics**:
-- [Use tags to control which jobs a runner can run](../runners/configure_runners.md#use-tags-to-control-which-jobs-a-runner-can-run).
+- [Use tags to control which jobs a runner can run](../runners/configure_runners.md#control-jobs-that-a-runner-can-run).
- [Select different runner tags for each parallel matrix job](../jobs/job_control.md#select-different-runner-tags-for-each-parallel-matrix-job).
### `timeout`
@@ -4579,7 +4579,7 @@ Use `timeout` to configure a timeout for a specific job. If the job runs for lon
than the timeout, the job fails.
The job-level timeout can be longer than the [project-level timeout](../pipelines/settings.md#set-a-limit-for-how-long-jobs-can-run),
-but can't be longer than the [runner's timeout](../runners/configure_runners.md#set-maximum-job-timeout-for-a-runner).
+but can't be longer than the [runner's timeout](../runners/configure_runners.md#set-the-maximum-job-timeout).
**Keyword type**: Job keyword. You can use it only as part of a job or in the
[`default` section](#default).
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 5beff3af5b4..6a312f76388 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -38,8 +38,8 @@ For more information on:
gitlab_rails['omniauth_block_auto_created_users'] = false
```
-1. Optional. You can automatically link SAML users with existing GitLab users if their
- email addresses match by adding the following setting in `/etc/gitlab/gitlab.rb`:
+1. Optional. You should automatically link a first-time SAML sign-in with existing GitLab users if their
+ email addresses match. To do this, add the following setting in `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['omniauth_auto_link_saml_user'] = true
@@ -728,6 +728,9 @@ On GitLab.com, Microsoft Azure/Entra ID attributes are introduced
[with a flag](../administration/feature_flags.md) named `saml_microsoft_attribute_names`.
On GitLab.com, this feature is unavailable but can be configured by GitLab.com administrators only.
+NOTE:
+The attributes are case-sensitive.
+
| Field | Supported default keys |
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Email (required)| `email`, `mail`, `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`, `http://schemas.microsoft.com/ws/2008/06/identity/claims/emailaddress` |
diff --git a/doc/tutorials/create_register_first_runner/index.md b/doc/tutorials/create_register_first_runner/index.md
index 4e247026ca6..5b57d8f35ea 100644
--- a/doc/tutorials/create_register_first_runner/index.md
+++ b/doc/tutorials/create_register_first_runner/index.md
@@ -90,7 +90,7 @@ To create a project runner:
1. Expand the **Runners** section.
1. Select **New project runner**.
1. Select your operating system.
-1. In the **Tags** section, select the **Run untagged** checkbox. [Tags](../../ci/runners/configure_runners.md#use-tags-to-control-which-jobs-a-runner-can-run) specify which jobs
+1. In the **Tags** section, select the **Run untagged** checkbox. [Tags](../../ci/runners/configure_runners.md#control-jobs-that-a-runner-can-run) specify which jobs
the runner can run and are optional.
1. Select **Create runner**.
1. Follow the on-screen instructions to register the runner from the command line. When prompted:
diff --git a/doc/update/versions/gitlab_16_changes.md b/doc/update/versions/gitlab_16_changes.md
index 20886645692..b055b9e85a5 100644
--- a/doc/update/versions/gitlab_16_changes.md
+++ b/doc/update/versions/gitlab_16_changes.md
@@ -541,6 +541,10 @@ by this issue.
[throw errors on startup](../../install/docker.md#threaderror-cant-create-thread-operation-not-permitted).
- Starting with 16.0, GitLab self-managed installations now have two database connections by default, instead of one. This change doubles the number of PostgreSQL connections. It makes self-managed versions of GitLab behave similarly to GitLab.com, and is a step toward enabling a separate database for CI features for self-managed versions of GitLab. Before upgrading to 16.0, determine if you need to [increase max connections for PostgreSQL](https://docs.gitlab.com/omnibus/settings/database.html#configuring-multiple-database-connections).
- This change applies to installation methods with Linux packages (Omnibus), GitLab Helm chart, GitLab Operator, GitLab Docker images, and self-compiled installations.
+ - The second database connection can be disabled:
+ - [Linux package and Docker installations](https://docs.gitlab.com/omnibus/settings/database.html#configuring-multiple-database-connections).
+ - [Helm chart and GitLab Operator installations](https://docs.gitlab.com/charts/charts/globals.html#configure-multiple-database-connections).
+ - [Self-compiled installations](../../install/installation.md#configure-gitlab-db-settings).
- Container registry using Azure storage might be empty with zero tags. You can fix this by following the [breaking change instructions](../deprecations.md#azure-storage-driver-defaults-to-the-correct-root-prefix).
### Linux package installations
diff --git a/doc/user/application_security/dependency_list/index.md b/doc/user/application_security/dependency_list/index.md
index d263fcde77f..7f4806a89f1 100644
--- a/doc/user/application_security/dependency_list/index.md
+++ b/doc/user/application_security/dependency_list/index.md
@@ -61,14 +61,38 @@ Details of each dependency are listed, sorted by decreasing severity of vulnerab

-### Vulnerabilities
+## Filter dependency list
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/422356) in GitLab 16.7 [with a flag](../../../administration/feature_flags.md) named `group_level_dependencies_filtering`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `group_level_dependencies_filtering`.
+On GitLab.com, this feature is available but can be configured by GitLab.com administrators only.
+
+In the group-level dependency list you can filter by:
+
+- Project
+- License
+
+To filter the dependency list:
+
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or group.
+1. Select **Secure > Dependency list**.
+1. Select the filter bar.
+1. Select a filter, then from the dropdown list select one or more criteria.
+ To close the dropdown list, select outside of it. To add more filters, repeat this step.
+1. To apply the selected filters, press Enter.
+
+The dependency list shows only dependencies that match your filters.
+
+## Vulnerabilities
If a dependency has known vulnerabilities, view them by selecting the arrow next to the
dependency's name or the badge that indicates how many known vulnerabilities exist. For each
vulnerability, its severity and description appears below it. To view more details of a vulnerability,
select the vulnerability's description. The [vulnerability's details](../vulnerabilities) page is opened.
-### Dependency paths
+## Dependency paths
The dependency list shows the path between a dependency and a top-level dependency it's connected
to, if any. Multiple paths may connect a transient dependency to top-level
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index ddbbf932cfd..6f1ab305782 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -179,6 +179,9 @@ To set up OneLogin as your identity provider:
### Configure assertions
+NOTE:
+The attributes are case-sensitive.
+
At minimum, you must configure the following assertions:
1. [NameID](#manage-user-saml-identity).
diff --git a/doc/user/project/service_desk/configure.md b/doc/user/project/service_desk/configure.md
index 9acf4b4909b..681e41f35c0 100644
--- a/doc/user/project/service_desk/configure.md
+++ b/doc/user/project/service_desk/configure.md
@@ -155,6 +155,26 @@ To edit the custom email display name:
1. Below **Email display name**, enter a new name.
1. Select **Save changes**.
+## Reopen issues when an external participant comments
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8549) in GitLab 16.7
+
+You can configure GitLab to reopen closed issues when an external participant adds
+a new comment on an issue by email. This also adds an internal comment that mentions
+the assignees of the issue and creates to-do items for them.
+
+Prerequisites:
+
+- You must have at least the Maintainer role for the project.
+
+To enable this setting:
+
+1. On the left sidebar, select **Search or go to** and find your project.
+1. Select **Settings > General**.
+1. Expand **Service Desk**.
+1. Select the **Reopen issues on a new note from an external participant** checkbox.
+1. Select **Save changes**.
+
## Custom email address **(BETA)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/329990) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `service_desk_custom_email`. Disabled by default.
diff --git a/doc/user/project/service_desk/index.md b/doc/user/project/service_desk/index.md
index c5562c7ce4b..9ab69c4bdb8 100644
--- a/doc/user/project/service_desk/index.md
+++ b/doc/user/project/service_desk/index.md
@@ -46,6 +46,7 @@ Meanwhile:
- [Customize emails sent to the requester](configure.md#customize-emails-sent-to-the-requester)
- [Use a custom template for Service Desk tickets](configure.md#use-a-custom-template-for-service-desk-tickets)
- [Support Bot user](configure.md#support-bot-user)
+ - [Reopen issues when an external participant comments](configure.md#reopen-issues-when-an-external-participant-comments)
- [Custom email address (Beta)](configure.md#custom-email-address)
- [Use an additional Service Desk alias email](configure.md#use-an-additional-service-desk-alias-email)
- [Configure email ingestion in multi-node environments](configure.md#configure-email-ingestion-in-multi-node-environments)
diff --git a/lib/gitlab/ci/config/interpolation/text_interpolator.rb b/lib/gitlab/ci/config/interpolation/text_interpolator.rb
new file mode 100644
index 00000000000..5c4953f8bbe
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/text_interpolator.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ ##
+ # Performs CI config file interpolation and either returns the interpolated result or interpolation errors.
+ #
+ class TextInterpolator
+ attr_reader :errors
+
+ def initialize(config, input_args, variables)
+ @config = config
+ @input_args = input_args.to_h
+ @variables = variables
+ @errors = []
+ @interpolated = false
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def to_result
+ @result
+ end
+
+ def error_message
+ # Interpolator can have multiple error messages, like: ["interpolation interrupted by errors", "unknown
+ # interpolation key: `abc`"] ?
+ #
+ # We are joining them together into a single one, because only one error can be surfaced when an external
+ # file gets included and is invalid. The limit to three error messages combined is more than required.
+ #
+ errors.first(3).join(', ')
+ end
+
+ def interpolate!
+ return errors.push(config.error) unless config.valid?
+
+ if inputs_without_header?
+ return errors.push(
+ _('Given inputs not defined in the `spec` section of the included configuration file'))
+ end
+
+ return @result ||= config.content unless config.has_header?
+
+ return errors.concat(header.errors) unless header.valid?
+ return errors.concat(inputs.errors) unless inputs.valid?
+ return errors.concat(context.errors) unless context.valid?
+ return errors.concat(template.errors) unless template.valid?
+
+ @interpolated = true
+
+ @result ||= template.interpolated
+ end
+
+ def interpolated?
+ @interpolated
+ end
+
+ private
+
+ attr_reader :config, :input_args, :variables
+
+ def inputs_without_header?
+ input_args.any? && !config.has_header?
+ end
+
+ def header
+ @header ||= Header::Root.new(config.header).tap do |header|
+ header.key = 'header'
+
+ header.compose!
+ end
+ end
+
+ def content
+ @content ||= config.content
+ end
+
+ def spec
+ @spec ||= header.inputs_value
+ end
+
+ def inputs
+ @inputs ||= Inputs.new(spec, input_args)
+ end
+
+ def context
+ @context ||= Context.new({ inputs: inputs.to_hash }, variables: variables)
+ end
+
+ def template
+ @template ||= TextTemplate.new(content, context)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/text_template.rb b/lib/gitlab/ci/config/interpolation/text_template.rb
new file mode 100644
index 00000000000..e1f5d368e88
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/text_template.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ class TextTemplate
+ MAX_BLOCKS = 10_000
+
+ def initialize(content, ctx)
+ @content = content
+ @ctx = Interpolation::Context.fabricate(ctx)
+ @errors = []
+ @blocks = {}
+
+ interpolate! if valid?
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def errors
+ @errors + ctx.errors + blocks.values.flat_map(&:errors)
+ end
+
+ def interpolated
+ @result if valid?
+ end
+
+ private
+
+ attr_reader :blocks, :content, :ctx
+
+ def interpolate!
+ return @errors.push('config too large') if content.bytesize > max_total_yaml_size_bytes
+
+ @result = Interpolation::Block.match(content) do |matched, data|
+ block = (blocks[matched] ||= Interpolation::Block.new(matched, data, ctx))
+
+ break @errors.push('too many interpolation blocks') if blocks.count > MAX_BLOCKS
+ break unless block.valid?
+
+ if block.value.is_a?(String)
+ block.value
+ else
+ block.value.to_json
+ end
+ end
+ end
+
+ def max_total_yaml_size_bytes
+ Gitlab::CurrentSettings.current_application_settings.ci_max_total_yaml_size_bytes
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/content_security_policy/directives.rb b/lib/gitlab/content_security_policy/directives.rb
index e293e5653c7..3df0ec76839 100644
--- a/lib/gitlab/content_security_policy/directives.rb
+++ b/lib/gitlab/content_security_policy/directives.rb
@@ -12,11 +12,11 @@ module Gitlab
end
def self.frame_src
- "https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com https://www.googletagmanager.com/ns.html"
+ "https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://www.googletagmanager.com/ns.html"
end
def self.script_src
- "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net https://apis.google.com"
+ "'strict-dynamic' 'self' 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net"
end
def self.style_src
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index edeee343edc..9d5323ee9bf 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -56,7 +56,6 @@ module Gitlab
gon.dot_com = Gitlab.com?
gon.uf_error_prefix = ::Gitlab::Utils::ErrorMessage::UF_ERROR_PREFIX
gon.pat_prefix = Gitlab::CurrentSettings.current_application_settings.personal_access_token_prefix
- gon.use_new_navigation = NavHelper.show_super_sidebar?(current_user)
gon.keyboard_shortcuts_enabled = current_user ? current_user.keyboard_shortcuts_enabled : true
gon.diagramsnet_url = Gitlab::CurrentSettings.diagramsnet_url if Gitlab::CurrentSettings.diagramsnet_enabled
@@ -81,6 +80,7 @@ module Gitlab
# To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/399248
push_frontend_feature_flag(:remove_monitor_metrics)
push_frontend_feature_flag(:custom_emoji)
+ push_frontend_feature_flag(:encoding_logs_tree)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/lib/gitlab/issuables_count_for_state.rb b/lib/gitlab/issuables_count_for_state.rb
index f11dd520d2d..13909ca2ce3 100644
--- a/lib/gitlab/issuables_count_for_state.rb
+++ b/lib/gitlab/issuables_count_for_state.rb
@@ -124,7 +124,7 @@ module Gitlab
def cache_issues_count?
@store_in_redis_cache &&
- finder.instance_of?(IssuesFinder) &&
+ finder.class <= IssuesFinder &&
parent_group.present? &&
!params_include_filters?
end
@@ -134,7 +134,7 @@ module Gitlab
end
def redis_cache_key
- ['group', parent_group&.id, 'issues']
+ ['group', parent_group&.id, finder.klass.model_name.plural]
end
def cache_options
@@ -143,8 +143,8 @@ module Gitlab
def params_include_filters?
non_filtering_params = %i[
- scope state sort group_id include_subgroups
- attempt_group_search_optimizations non_archived issue_types
+ scope state sort group_id include_subgroups namespace_id
+ attempt_group_search_optimizations non_archived issue_types lookahead
]
finder.params.except(*non_filtering_params).values.any?
diff --git a/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric.rb b/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric.rb
deleted file mode 100644
index b1a2de29fd7..00000000000
--- a/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Usage
- module Metrics
- module Instrumentations
- class InProductMarketingEmailCtaClickedMetric < DatabaseMetric
- operation :count
-
- def initialize(metric_definition)
- super
-
- unless track.in?(allowed_track)
- raise ArgumentError, "track '#{track}' must be one of: #{allowed_track.join(', ')}"
- end
-
- return if series.in?(allowed_series)
-
- raise ArgumentError, "series '#{series}' must be one of: #{allowed_series.join(', ')}"
- end
-
- relation { Users::InProductMarketingEmail }
-
- private
-
- def relation
- scope = super.where.not(cta_clicked_at: nil)
- scope = scope.where(series: series)
- scope.where(track: track)
- end
-
- def track
- options[:track]
- end
-
- def series
- options[:series]
- end
-
- def allowed_track
- Users::InProductMarketingEmail::ACTIVE_TRACKS.keys
- end
-
- def allowed_series
- @allowed_series ||= begin
- series_amount = Namespaces::InProductMarketingEmailsService.email_count_for_track(track)
- 0.upto(series_amount - 1).to_a
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric.rb b/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric.rb
deleted file mode 100644
index 50dec606d9b..00000000000
--- a/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Usage
- module Metrics
- module Instrumentations
- class InProductMarketingEmailSentMetric < DatabaseMetric
- operation :count
-
- def initialize(metric_definition)
- super
-
- unless track.in?(allowed_track)
- raise ArgumentError, "track '#{track}' must be one of: #{allowed_track.join(', ')}"
- end
-
- return if series.in?(allowed_series)
-
- raise ArgumentError, "series '#{series}' must be one of: #{allowed_series.join(', ')}"
- end
-
- relation { Users::InProductMarketingEmail }
-
- private
-
- def relation
- scope = super
- scope = scope.where(series: series)
- scope.where(track: track)
- end
-
- def track
- options[:track]
- end
-
- def series
- options[:series]
- end
-
- def allowed_track
- Users::InProductMarketingEmail::ACTIVE_TRACKS.keys
- end
-
- def allowed_series
- @allowed_series ||= begin
- series_amount = Namespaces::InProductMarketingEmailsService.email_count_for_track(track)
- 0.upto(series_amount - 1).to_a
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage_data_queries.rb b/lib/gitlab/usage_data_queries.rb
index 534a08cad9a..8310c464a59 100644
--- a/lib/gitlab/usage_data_queries.rb
+++ b/lib/gitlab/usage_data_queries.rb
@@ -75,16 +75,6 @@ module Gitlab
}
end
- # rubocop: disable CodeReuse/ActiveRecord
- def sent_in_product_marketing_email_count(sent_emails, track, series)
- count(Users::InProductMarketingEmail.where(track: track, series: series))
- end
-
- def clicked_in_product_marketing_email_count(clicked_emails, track, series)
- count(Users::InProductMarketingEmail.where(track: track, series: series).where.not(cta_clicked_at: nil))
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def stage_manage_events(time_period)
# rubocop: disable CodeReuse/ActiveRecord
# rubocop: disable UsageData/LargeTable
diff --git a/lib/sidebars/explore/menus/catalog_menu.rb b/lib/sidebars/explore/menus/catalog_menu.rb
index 2d8e8bba08b..61578147326 100644
--- a/lib/sidebars/explore/menus/catalog_menu.rb
+++ b/lib/sidebars/explore/menus/catalog_menu.rb
@@ -21,7 +21,7 @@ module Sidebars
override :render?
def render?
- Feature.enabled?(:global_ci_catalog, current_user)
+ true
end
override :active_routes
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 80d2c8a3647..c5b6b246687 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -167,11 +167,6 @@ msgid_plural "%d artifacts"
msgstr[0] ""
msgstr[1] ""
-msgid "%d assigned issue"
-msgid_plural "%d assigned issues"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d author"
msgid_plural "%d authors"
msgstr[0] ""
@@ -12732,6 +12727,9 @@ msgstr ""
msgid "ComplianceReport|Update result"
msgstr ""
+msgid "ComplianceStandardsAdherence| Standards adherence export"
+msgstr ""
+
msgid "ComplianceStandardsAdherence|A rule is configured to prevent author approved merge requests."
msgstr ""
@@ -12819,6 +12817,12 @@ msgstr ""
msgid "ComplianceStandardsAdherence|View details (fix available)"
msgstr ""
+msgid "ComplianceStandardsAdherence|Your standards adherence CSV export for the group \"%{group_name}\" is attached to this email."
+msgstr ""
+
+msgid "ComplianceStandardsAdherence|Your standards adherence CSV export for the group %{group_link} is attached to this email."
+msgstr ""
+
msgid "ComplianceViolations| Violations export"
msgstr ""
@@ -44811,6 +44815,9 @@ msgstr ""
msgid "ServiceDesk|Read timeout"
msgstr ""
+msgid "ServiceDesk|Reopen issues when an external participant comments"
+msgstr ""
+
msgid "ServiceDesk|Reset custom email"
msgstr ""
@@ -44883,6 +44890,9 @@ msgstr ""
msgid "ServiceDesk|The verification email wasn't received in time. There is a 30 minutes timeframe for verification emails to appear in your instance's Service Desk. Make sure that you have set up email forwarding correctly."
msgstr ""
+msgid "ServiceDesk|This also adds an internal comment that mentions the assignees of the issue."
+msgstr ""
+
msgid "ServiceDesk|This issue has been reopened because it received a new comment from an external participant."
msgstr ""
@@ -46881,9 +46891,6 @@ msgstr ""
msgid "Stop impersonating"
msgstr ""
-msgid "Stop impersonation"
-msgstr ""
-
msgid "Stop this environment"
msgstr ""
@@ -50625,9 +50632,6 @@ msgstr ""
msgid "Today"
msgstr ""
-msgid "Todos count"
-msgstr ""
-
msgid "Todos|Added"
msgstr ""
@@ -57633,6 +57637,9 @@ msgstr ""
msgid "is not one of"
msgstr ""
+msgid "is not part of the given organization"
+msgstr ""
+
msgid "is not valid. The iteration group has to match the iteration cadence group."
msgstr ""
diff --git a/rubocop/cop/database/avoid_using_pluck_without_limit.rb b/rubocop/cop/database/avoid_using_pluck_without_limit.rb
new file mode 100644
index 00000000000..8f1ea251dcb
--- /dev/null
+++ b/rubocop/cop/database/avoid_using_pluck_without_limit.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'rubocop/cop/mixin/active_record_helper'
+require_relative '../../code_reuse_helpers'
+
+module RuboCop
+ module Cop
+ module Database
+ # Checks the use of .pluck(:attribute) without setting a limit.
+ #
+ # @example
+ #
+ # # bad
+ # def all
+ # Project.where(user_id: User.pluck(:id))
+ # end
+ #
+ # # good
+ # def all(limit)
+ # Project.where(user_id: User.limit(limit).pluck(:id))
+ # end
+ class AvoidUsingPluckWithoutLimit < RuboCop::Cop::Base
+ include RuboCop::Cop::ActiveRecordHelper
+ include RuboCop::CodeReuseHelpers
+
+ MSG = 'Avoid using `pluck` without defining a proper `limit`. ' \
+ 'Querying with too much values inside an `IN` clause can result in database performance degradation. ' \
+ 'See https://gitlab.com/gitlab-com/gl-infra/production/-/issues/17168'
+
+ RESTRICT_ON_SEND = %i[pluck].freeze
+
+ def_node_matcher :pluck_with_limit?, <<~PATTERN
+ (send (send _ :limit _) ...)
+ PATTERN
+
+ def on_send(node)
+ return unless should_scan?(node)
+
+ return if pluck_with_limit?(node)
+
+ add_offense(node.loc.selector)
+ end
+
+ private
+
+ # It limits the check to ActiveRecord, Models, Finders and Service classes
+ def should_scan?(node)
+ inherit_active_record_base?(node) || in_model?(node) || in_finder?(node) || in_service_class?(node)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index aaf169cd42b..505f5b2d417 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -3,10 +3,12 @@
require 'spec_helper'
RSpec.describe Profiles::PreferencesController do
+ let_it_be(:home_organization) { create(:organization) }
let(:user) { create(:user) }
before do
sign_in(user)
+ create(:organization_user, organization: home_organization, user: user)
allow(subject).to receive(:current_user).and_return(user)
end
@@ -28,6 +30,7 @@ RSpec.describe Profiles::PreferencesController do
params.reverse_merge!(
color_scheme_id: '1',
dashboard: 'stars',
+ home_organization_id: home_organization.id,
theme_id: '1'
)
@@ -49,6 +52,7 @@ RSpec.describe Profiles::PreferencesController do
diffs_deletion_color: '#123456',
diffs_addition_color: '#abcdef',
dashboard: 'stars',
+ home_organization_id: home_organization.id.to_s,
theme_id: '2',
first_day_of_week: '1',
preferred_language: 'jp',
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index db726f5652f..4da7dcba763 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -254,6 +254,24 @@ FactoryBot.define do
end
end
+ trait :pipeline_refs do
+ transient do
+ object_format { Repository::FORMAT_SHA1 }
+ pipeline_count { 10 }
+ end
+
+ after :create do |project, evaluator|
+ raise "Failed to create repository!" unless project.repository.exists? || project.create_repository(object_format: evaluator.object_format)
+
+ project.repository.create_file(project.creator, "README.md", "Test", message: "Test file", branch_name: project.default_branch || 'master')
+
+ evaluator.pipeline_count.times do |x|
+ project.repository.create_ref(project.repository.head_commit.id, "refs/pipelines/#{x}")
+ project.repository.create_ref(project.repository.head_commit.id, "refs/head/foo-#{x}")
+ end
+ end
+ end
+
# A catalog resource repository with a file structure set up for ci components.
trait :catalog_resource_with_components do
small_repo
diff --git a/spec/factories/user_preferences.rb b/spec/factories/user_preferences.rb
index 19059a93625..2cfdd12d44b 100644
--- a/spec/factories/user_preferences.rb
+++ b/spec/factories/user_preferences.rb
@@ -3,6 +3,7 @@
FactoryBot.define do
factory :user_preference do
user
+ home_organization { association(:organization, :default) }
trait :only_comments do
issue_notes_filter { UserPreference::NOTES_FILTERS[:only_comments] }
diff --git a/spec/features/explore/navbar_spec.rb b/spec/features/explore/navbar_spec.rb
index c172760eb2c..f8fe9bc9af3 100644
--- a/spec/features/explore/navbar_spec.rb
+++ b/spec/features/explore/navbar_spec.rb
@@ -7,19 +7,7 @@ RSpec.describe '"Explore" navbar', :js, feature_category: :navigation do
it_behaves_like 'verified navigation bar' do
before do
- stub_feature_flags(global_ci_catalog: false)
visit explore_projects_path
end
end
-
- context "with 'global_ci_catalog' enabled" do
- include_context '"Explore" navbar structure with global_ci_catalog FF'
-
- it_behaves_like 'verified navigation bar', global_ci_catalog: true do
- before do
- stub_feature_flags(global_ci_catalog: true)
- visit explore_projects_path
- end
- end
- end
end
diff --git a/spec/features/user_sorts_things_spec.rb b/spec/features/user_sorts_things_spec.rb
index 8b2ee9f1ced..c53ad961d84 100644
--- a/spec/features/user_sorts_things_spec.rb
+++ b/spec/features/user_sorts_things_spec.rb
@@ -11,13 +11,13 @@ RSpec.describe "User sorts things", :js do
include DashboardHelper
let_it_be(:project) { create(:project_empty_repo, :public) }
- let_it_be(:current_user) { create(:user) } # Using `current_user` instead of just `user` because of the hardoced call in `assigned_mrs_dashboard_path` which is used below.
- let_it_be(:issue) { create(:issue, project: project, author: current_user) }
- let_it_be(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: current_user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:issue) { create(:issue, project: project, author: user) }
+ let_it_be(:merge_request) { create(:merge_request, target_project: project, source_project: project, author: user) }
before do
- project.add_developer(current_user)
- sign_in(current_user)
+ project.add_developer(user)
+ sign_in(user)
end
it "issues -> project home page -> issues", feature_category: :team_planning do
@@ -40,7 +40,7 @@ RSpec.describe "User sorts things", :js do
pajamas_sort_by sort_option, from: s_('SortOptions|Created date')
- visit(assigned_mrs_dashboard_path)
+ visit(merge_requests_dashboard_path(assignee_username: user.username))
expect(find(".issues-filters")).to have_content(sort_option)
end
diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js
index 376575a8acb..a9bfc0003bf 100644
--- a/spec/frontend/persistent_user_callout_spec.js
+++ b/spec/frontend/persistent_user_callout_spec.js
@@ -24,6 +24,7 @@ describe('PersistentUserCallout', () => {
>
+ A Link
`;
@@ -65,6 +66,8 @@ describe('PersistentUserCallout', () => {
return fixture;
}
+ useMockLocationHelper();
+
describe('dismiss', () => {
const buttons = {};
let mockAxios;
@@ -178,8 +181,6 @@ describe('PersistentUserCallout', () => {
let mockAxios;
let persistentUserCallout;
- useMockLocationHelper();
-
beforeEach(() => {
const fixture = createFollowLinkFixture();
const container = fixture.querySelector('.container');
@@ -222,6 +223,53 @@ describe('PersistentUserCallout', () => {
});
});
+ describe('dismiss and follow links', () => {
+ let link;
+ let mockAxios;
+ let persistentUserCallout;
+
+ beforeEach(() => {
+ const fixture = createFixture();
+ const container = fixture.querySelector('.container');
+ link = fixture.querySelector('.js-close-and-follow-link');
+ mockAxios = new MockAdapter(axios);
+
+ persistentUserCallout = new PersistentUserCallout(container);
+ jest.spyOn(persistentUserCallout.container, 'remove').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ mockAxios.restore();
+ });
+
+ it('uses a link to trigger callout and defers following until callout is finished', async () => {
+ const { href } = link;
+ mockAxios.onPost(dismissEndpoint).replyOnce(HTTP_STATUS_OK);
+
+ link.click();
+
+ await waitForPromises();
+
+ expect(window.location.assign).toHaveBeenCalledWith(href);
+ expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
+ expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
+ });
+
+ it('invokes Flash when the dismiss request fails', async () => {
+ mockAxios.onPost(dismissEndpoint).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+
+ link.click();
+
+ await waitForPromises();
+
+ expect(window.location.assign).not.toHaveBeenCalled();
+ expect(createAlert).toHaveBeenCalledWith({
+ message:
+ 'An error occurred while acknowledging the notification. Refresh the page and try again.',
+ });
+ });
+ });
+
describe('factory', () => {
it('returns an instance of PersistentUserCallout with the provided container property', () => {
const fixture = createFixture();
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
index fca3a14d82f..185a85cdb80 100644
--- a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
@@ -22,6 +22,7 @@ describe('ServiceDeskRoot', () => {
isIssueTrackerEnabled: true,
outgoingName: 'GitLab Support Bot',
projectKey: 'key',
+ reopenIssueOnExternalParticipantNote: true,
addExternalParticipantsFromCc: true,
selectedTemplate: 'Bug',
selectedFileTemplateProjectId: 42,
@@ -60,6 +61,8 @@ describe('ServiceDeskRoot', () => {
incomingEmail: provideData.initialIncomingEmail,
initialOutgoingName: provideData.outgoingName,
initialProjectKey: provideData.projectKey,
+ initialReopenIssueOnExternalParticipantNote:
+ provideData.reopenIssueOnExternalParticipantNote,
initialAddExternalParticipantsFromCc: provideData.addExternalParticipantsFromCc,
initialSelectedTemplate: provideData.selectedTemplate,
initialSelectedFileTemplateProjectId: provideData.selectedFileTemplateProjectId,
@@ -146,6 +149,7 @@ describe('ServiceDeskRoot', () => {
selectedTemplate: 'Bug',
outgoingName: 'GitLab Support Bot',
projectKey: 'key',
+ reopenIssueOnExternalParticipantNote: true,
addExternalParticipantsFromCc: true,
};
@@ -160,6 +164,7 @@ describe('ServiceDeskRoot', () => {
outgoing_name: 'GitLab Support Bot',
project_key: 'key',
service_desk_enabled: true,
+ reopen_issue_on_external_participant_note: true,
add_external_participants_from_cc: true,
});
});
@@ -179,6 +184,7 @@ describe('ServiceDeskRoot', () => {
selectedTemplate: 'Bug',
outgoingName: 'GitLab Support Bot',
projectKey: 'key',
+ reopen_issue_on_external_participant_note: true,
addExternalParticipantsFromCc: true,
};
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
index 6449f9bb68e..f7bdb2455e9 100644
--- a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
@@ -1,4 +1,4 @@
-import { GlButton, GlDropdown, GlFormCheckbox, GlLoadingIcon, GlToggle, GlAlert } from '@gitlab/ui';
+import { GlButton, GlDropdown, GlLoadingIcon, GlToggle, GlAlert } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -19,7 +19,10 @@ describe('ServiceDeskSetting', () => {
const findSuffixFormGroup = () => wrapper.findByTestId('suffix-form-group');
const findIssueTrackerInfo = () => wrapper.findComponent(GlAlert);
const findIssueHelpLink = () => wrapper.findByTestId('issue-help-page');
- const findAddExternalParticipantsFromCcCheckbox = () => wrapper.findComponent(GlFormCheckbox);
+ const findReopenIssueOnExternalParticipantNoteCheckbox = () =>
+ wrapper.findByTestId('reopen-issue-on-external-participant-note');
+ const findAddExternalParticipantsFromCcCheckbox = () =>
+ wrapper.findByTestId('add-external-participants-from-cc');
const createComponent = ({ props = {}, provide = {} } = {}) =>
extendedWrapper(
@@ -212,6 +215,27 @@ describe('ServiceDeskSetting', () => {
});
});
+ describe('reopen issue on external participant note checkbox', () => {
+ it('is rendered', () => {
+ wrapper = createComponent();
+ expect(findReopenIssueOnExternalParticipantNoteCheckbox().exists()).toBe(true);
+ });
+
+ it('forwards false as initial value to the checkbox', () => {
+ wrapper = createComponent({ props: { initialReopenIssueOnExternalParticipantNote: false } });
+ expect(findReopenIssueOnExternalParticipantNoteCheckbox().find('input').element.checked).toBe(
+ false,
+ );
+ });
+
+ it('forwards true as initial value to the checkbox', () => {
+ wrapper = createComponent({ props: { initialReopenIssueOnExternalParticipantNote: true } });
+ expect(findReopenIssueOnExternalParticipantNoteCheckbox().find('input').element.checked).toBe(
+ true,
+ );
+ });
+ });
+
describe('add external participants from cc checkbox', () => {
it('is rendered', () => {
wrapper = createComponent();
@@ -249,7 +273,8 @@ describe('ServiceDeskSetting', () => {
initialSelectedFileTemplateProjectId: 42,
initialOutgoingName: 'GitLab Support Bot',
initialProjectKey: 'key',
- initialAddExternalParticipantsFromCc: false,
+ initialReopenIssueOnExternalParticipantNote: true,
+ initialAddExternalParticipantsFromCc: true,
},
});
@@ -262,7 +287,8 @@ describe('ServiceDeskSetting', () => {
fileTemplateProjectId: 42,
outgoingName: 'GitLab Support Bot',
projectKey: 'key',
- addExternalParticipantsFromCc: false,
+ reopenIssueOnExternalParticipantNote: true,
+ addExternalParticipantsFromCc: true,
};
expect(wrapper.emitted('save')[0]).toEqual([payload]);
@@ -288,6 +314,10 @@ describe('ServiceDeskSetting', () => {
expect(findButton().exists()).toBe(false);
});
+ it('does not render reopen issue on external participant note checkbox', () => {
+ expect(findReopenIssueOnExternalParticipantNoteCheckbox().exists()).toBe(false);
+ });
+
it('does not render add external participants from cc checkbox', () => {
expect(findAddExternalParticipantsFromCcCheckbox().exists()).toBe(false);
});
diff --git a/spec/frontend/repository/commits_service_spec.js b/spec/frontend/repository/commits_service_spec.js
index 5fb683bd370..d779abcbfd6 100644
--- a/spec/frontend/repository/commits_service_spec.js
+++ b/spec/frontend/repository/commits_service_spec.js
@@ -14,7 +14,7 @@ describe('commits service', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
-
+ window.gon.features = { encodingLogsTree: true };
mock.onGet(url).reply(HTTP_STATUS_OK, [], {});
jest.spyOn(axios, 'get');
@@ -48,14 +48,27 @@ describe('commits service', () => {
});
it('encodes the path and ref', async () => {
- const encodedRef = encodeURIComponent(refWithSpecialCharMock);
- const encodedUrl = `/some-project/-/refs/${encodedRef}/logs_tree/with%20%24peci%40l%20ch%40rs%2F`;
+ const encodedRef = encodeURI(refWithSpecialCharMock);
+ const encodedUrl = `/some-project/-/refs/${encodedRef}/logs_tree/with%20$peci@l%20ch@rs/`;
await requestCommits(1, 'some-project', 'with $peci@l ch@rs/', refWithSpecialCharMock);
expect(axios.get).toHaveBeenCalledWith(encodedUrl, expect.anything());
});
+ describe('when encodingLogsTree FF is off', () => {
+ beforeEach(() => {
+ window.gon.features = {};
+ });
+
+ it('encodes the path and ref with encodeURIComponent', async () => {
+ const encodedRef = encodeURIComponent(refWithSpecialCharMock);
+ const encodedUrl = `/some-project/-/refs/${encodedRef}/logs_tree/with%20%24peci%40l%20ch%40rs%2F`;
+ await requestCommits(1, 'some-project', 'with $peci@l ch@rs/', refWithSpecialCharMock);
+ expect(axios.get).toHaveBeenCalledWith(encodedUrl, expect.anything());
+ });
+ });
+
it('calls axios get once per batch', async () => {
await Promise.all([requestCommits(0), requestCommits(1), requestCommits(23)]);
diff --git a/spec/frontend/search/sidebar/components/all_scopes_start_filters_spec.js b/spec/frontend/search/sidebar/components/all_scopes_start_filters_spec.js
new file mode 100644
index 00000000000..cd43214ed38
--- /dev/null
+++ b/spec/frontend/search/sidebar/components/all_scopes_start_filters_spec.js
@@ -0,0 +1,28 @@
+import { shallowMount } from '@vue/test-utils';
+import GroupFilter from '~/search/sidebar/components/group_filter.vue';
+import ProjectFilter from '~/search/sidebar/components/project_filter.vue';
+import AllScopesStartFilters from '~/search/sidebar/components/all_scopes_start_filters.vue';
+
+describe('GlobalSearch AllScopesStartFilters', () => {
+ let wrapper;
+
+ const findGroupFilter = () => wrapper.findComponent(GroupFilter);
+ const findProjectFilter = () => wrapper.findComponent(ProjectFilter);
+
+ const createComponent = () => {
+ wrapper = shallowMount(AllScopesStartFilters);
+ };
+
+ describe('Renders correctly', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+ it('renders ArchivedFilter', () => {
+ expect(findGroupFilter().exists()).toBe(true);
+ });
+
+ it('renders FiltersTemplate', () => {
+ expect(findProjectFilter().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js
index f607d40253f..3ff6bbf7666 100644
--- a/spec/frontend/search/sidebar/components/app_spec.js
+++ b/spec/frontend/search/sidebar/components/app_spec.js
@@ -20,6 +20,7 @@ import MilestonesFilters from '~/search/sidebar/components/milestones_filters.vu
import WikiBlobsFilters from '~/search/sidebar/components/wiki_blobs_filters.vue';
import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue';
import DomElementListener from '~/vue_shared/components/dom_element_listener.vue';
+import AllScopesStartFilters from '~/search/sidebar/components/all_scopes_start_filters.vue';
jest.mock('~/super_sidebar/super_sidebar_collapsed_state_manager');
@@ -57,6 +58,7 @@ describe('GlobalSearchSidebar', () => {
const findWikiBlobsFilters = () => wrapper.findComponent(WikiBlobsFilters);
const findScopeSidebarNavigation = () => wrapper.findComponent(ScopeSidebarNavigation);
const findDomElementListener = () => wrapper.findComponent(DomElementListener);
+ const findAllScopesStartFilters = () => wrapper.findComponent(AllScopesStartFilters);
describe('renders properly', () => {
describe('always', () => {
@@ -70,31 +72,50 @@ describe('GlobalSearchSidebar', () => {
});
describe.each`
- scope | filter | searchType | isShown
- ${'issues'} | ${findIssuesFilters} | ${SEARCH_TYPE_BASIC} | ${true}
- ${'merge_requests'} | ${findMergeRequestsFilters} | ${SEARCH_TYPE_BASIC} | ${true}
- ${'projects'} | ${findProjectsFilters} | ${SEARCH_TYPE_BASIC} | ${true}
- ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_BASIC} | ${false}
- ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_ADVANCED} | ${true}
- ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_ZOEKT} | ${false}
- ${'notes'} | ${findNotesFilters} | ${SEARCH_TYPE_BASIC} | ${true}
- ${'notes'} | ${findNotesFilters} | ${SEARCH_TYPE_ADVANCED} | ${true}
- ${'commits'} | ${findCommitsFilters} | ${SEARCH_TYPE_BASIC} | ${true}
- ${'commits'} | ${findCommitsFilters} | ${SEARCH_TYPE_ADVANCED} | ${true}
- ${'milestones'} | ${findMilestonesFilters} | ${SEARCH_TYPE_BASIC} | ${true}
- ${'milestones'} | ${findMilestonesFilters} | ${SEARCH_TYPE_ADVANCED} | ${true}
- ${'wiki_blobs'} | ${findWikiBlobsFilters} | ${SEARCH_TYPE_BASIC} | ${true}
- ${'wiki_blobs'} | ${findWikiBlobsFilters} | ${SEARCH_TYPE_ADVANCED} | ${true}
- `('with sidebar $scope scope:', ({ scope, filter, searchType, isShown }) => {
+ scope | filter
+ ${'issues'} | ${findIssuesFilters}
+ ${'issues'} | ${findAllScopesStartFilters}
+ ${'merge_requests'} | ${findMergeRequestsFilters}
+ ${'merge_requests'} | ${findAllScopesStartFilters}
+ ${'projects'} | ${findProjectsFilters}
+ ${'projects'} | ${findAllScopesStartFilters}
+ ${'blobs'} | ${findAllScopesStartFilters}
+ ${'notes'} | ${findNotesFilters}
+ ${'notes'} | ${findAllScopesStartFilters}
+ ${'commits'} | ${findCommitsFilters}
+ ${'commits'} | ${findAllScopesStartFilters}
+ ${'milestones'} | ${findMilestonesFilters}
+ ${'milestones'} | ${findAllScopesStartFilters}
+ ${'wiki_blobs'} | ${findWikiBlobsFilters}
+ ${'wiki_blobs'} | ${findAllScopesStartFilters}
+ `('with sidebar scope: $scope', ({ scope, filter }) => {
+ describe.each([SEARCH_TYPE_BASIC, SEARCH_TYPE_ADVANCED])(
+ 'with search_type %s',
+ (searchType) => {
+ beforeEach(() => {
+ getterSpies.currentScope = jest.fn(() => scope);
+ createComponent({ urlQuery: { scope }, searchType });
+ });
+
+ it(`renders correctly ${filter.name.replace('find', '')}`, () => {
+ expect(filter().exists()).toBe(true);
+ });
+ },
+ );
+ });
+
+ describe.each`
+ scope | filter | searchType | isShown
+ ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_BASIC} | ${false}
+ ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_ADVANCED} | ${true}
+ ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_ZOEKT} | ${false}
+ `('sidebar blobs scope:', ({ scope, filter, searchType, isShown }) => {
beforeEach(() => {
getterSpies.currentScope = jest.fn(() => scope);
createComponent({ urlQuery: { scope }, searchType });
});
- it(`renders correctly filter ${filter.name.replace(
- 'find',
- '',
- )} when search_type ${searchType}`, () => {
+ it(`renders correctly filter BlobsFilters when search_type ${searchType}`, () => {
expect(filter().exists()).toBe(isShown);
});
});
diff --git a/spec/frontend/search/topbar/components/group_filter_spec.js b/spec/frontend/search/sidebar/components/group_filter_spec.js
similarity index 92%
rename from spec/frontend/search/topbar/components/group_filter_spec.js
rename to spec/frontend/search/sidebar/components/group_filter_spec.js
index b360c7134cd..a90a8a38267 100644
--- a/spec/frontend/search/topbar/components/group_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/group_filter_spec.js
@@ -6,9 +6,9 @@ import Vuex from 'vuex';
import { MOCK_GROUP, MOCK_QUERY, CURRENT_SCOPE } from 'jest/search/mock_data';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { GROUPS_LOCAL_STORAGE_KEY } from '~/search/store/constants';
-import GroupFilter from '~/search/topbar/components/group_filter.vue';
-import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue';
-import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/topbar/constants';
+import GroupFilter from '~/search/sidebar/components/group_filter.vue';
+import SearchableDropdown from '~/search/sidebar/components/searchable_dropdown.vue';
+import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/sidebar/constants';
Vue.use(Vuex);
@@ -137,7 +137,7 @@ describe('GroupFilter', () => {
describe('when initialData is set', () => {
beforeEach(() => {
- createComponent({}, { groupInitialJson: { ...MOCK_GROUP } });
+ createComponent({ groupInitialJson: { ...MOCK_GROUP } }, {});
});
it('sets selectedGroup to ANY_OPTION', () => {
@@ -162,8 +162,9 @@ describe('GroupFilter', () => {
createComponent(
{
query: { ...MOCK_QUERY, nav_source: navSource },
+ groupInitialJson: { ...initialData },
},
- { groupInitialJson: { ...initialData } },
+ {},
);
});
diff --git a/spec/frontend/search/topbar/components/project_filter_spec.js b/spec/frontend/search/sidebar/components/project_filter_spec.js
similarity index 92%
rename from spec/frontend/search/topbar/components/project_filter_spec.js
rename to spec/frontend/search/sidebar/components/project_filter_spec.js
index 9aeb2362279..817ec77380f 100644
--- a/spec/frontend/search/topbar/components/project_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/project_filter_spec.js
@@ -6,9 +6,9 @@ import Vuex from 'vuex';
import { MOCK_PROJECT, MOCK_QUERY, CURRENT_SCOPE } from 'jest/search/mock_data';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { PROJECTS_LOCAL_STORAGE_KEY } from '~/search/store/constants';
-import ProjectFilter from '~/search/topbar/components/project_filter.vue';
-import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue';
-import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/topbar/constants';
+import ProjectFilter from '~/search/sidebar/components/project_filter.vue';
+import SearchableDropdown from '~/search/sidebar/components/searchable_dropdown.vue';
+import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/sidebar/constants';
Vue.use(Vuex);
@@ -36,6 +36,7 @@ describe('ProjectFilter', () => {
const store = new Vuex.Store({
state: {
query: MOCK_QUERY,
+ projectInitialJson: MOCK_PROJECT,
...initialState,
},
actions: actionSpies,
@@ -130,7 +131,7 @@ describe('ProjectFilter', () => {
describe('selectedProject', () => {
describe('when initialData is null', () => {
beforeEach(() => {
- createComponent({}, { projectInitialJson: ANY_OPTION });
+ createComponent({ projectInitialJson: ANY_OPTION }, {});
});
it('sets selectedProject to ANY_OPTION', () => {
@@ -164,8 +165,9 @@ describe('ProjectFilter', () => {
createComponent(
{
query: { ...MOCK_QUERY, nav_source: navSource },
+ projectInitialJson: { ...initialData },
},
- { projectInitialJson: { ...initialData } },
+ {},
);
});
diff --git a/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js
index d85942b9634..44c243d15f7 100644
--- a/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js
+++ b/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js
@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
+import sidebarEventHub from '~/super_sidebar/event_hub';
import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue';
import NavItem from '~/super_sidebar/components/nav_item.vue';
import { MOCK_QUERY, MOCK_NAVIGATION, MOCK_NAVIGATION_ITEMS } from '../../mock_data';
@@ -49,6 +50,7 @@ describe('ScopeSidebarNavigation', () => {
describe('scope navigation', () => {
beforeEach(() => {
+ jest.spyOn(sidebarEventHub, '$emit');
createComponent({ urlQuery: { ...MOCK_QUERY, search: 'test' } });
});
@@ -71,6 +73,11 @@ describe('ScopeSidebarNavigation', () => {
expect(findNavItems().at(linkAtPosition).findComponent('a').attributes('href')).toBe(link);
});
+
+ it('always emits toggle-menu-header event', () => {
+ expect(sidebarEventHub.$emit).toHaveBeenCalledWith('toggle-menu-header', false);
+ expect(sidebarEventHub.$emit).toHaveBeenCalledTimes(1);
+ });
});
describe('scope navigation sets proper state with url scope set', () => {
diff --git a/spec/frontend/search/topbar/components/searchable_dropdown_spec.js b/spec/frontend/search/sidebar/components/searchable_dropdown_spec.js
similarity index 95%
rename from spec/frontend/search/topbar/components/searchable_dropdown_spec.js
rename to spec/frontend/search/sidebar/components/searchable_dropdown_spec.js
index 1d4ccbf66a6..c8f157e4fe4 100644
--- a/spec/frontend/search/topbar/components/searchable_dropdown_spec.js
+++ b/spec/frontend/search/sidebar/components/searchable_dropdown_spec.js
@@ -6,8 +6,8 @@ import Vue from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import { MOCK_GROUPS, MOCK_QUERY } from 'jest/search/mock_data';
-import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue';
-import { ANY_OPTION, GROUP_DATA } from '~/search/topbar/constants';
+import SearchableDropdown from '~/search/sidebar/components/searchable_dropdown.vue';
+import { ANY_OPTION, GROUP_DATA } from '~/search/sidebar/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
Vue.use(Vuex);
diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js
index c4411f9f245..ce1df3caabc 100644
--- a/spec/frontend/search/topbar/components/app_spec.js
+++ b/spec/frontend/search/topbar/components/app_spec.js
@@ -1,16 +1,14 @@
import { GlSearchBoxByType, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import { stubComponent } from 'helpers/stub_component';
import GlobalSearchTopbar from '~/search/topbar/components/app.vue';
-import GroupFilter from '~/search/topbar/components/group_filter.vue';
-import ProjectFilter from '~/search/topbar/components/project_filter.vue';
import MarkdownDrawer from '~/vue_shared/components/markdown_drawer/markdown_drawer.vue';
import SearchTypeIndicator from '~/search/topbar/components/search_type_indicator.vue';
-
+import { ENTER_KEY } from '~/lib/utils/keys';
import {
SYNTAX_OPTIONS_ADVANCED_DOCUMENT,
SYNTAX_OPTIONS_ZOEKT_DOCUMENT,
@@ -44,8 +42,6 @@ describe('GlobalSearchTopbar', () => {
};
const findGlSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
- const findGroupFilter = () => wrapper.findComponent(GroupFilter);
- const findProjectFilter = () => wrapper.findComponent(ProjectFilter);
const findSyntaxOptionButton = () => wrapper.findComponent(GlButton);
const findSyntaxOptionDrawer = () => wrapper.findComponent(MarkdownDrawer);
const findSearchTypeIndicator = () => wrapper.findComponent(SearchTypeIndicator);
@@ -63,27 +59,6 @@ describe('GlobalSearchTopbar', () => {
expect(findSearchTypeIndicator().exists()).toBe(true);
});
- describe.each`
- snippets | showFilters
- ${null} | ${true}
- ${{ query: { snippets: '' } }} | ${true}
- ${{ query: { snippets: false } }} | ${true}
- ${{ query: { snippets: true } }} | ${false}
- ${{ query: { snippets: 'false' } }} | ${true}
- ${{ query: { snippets: 'true' } }} | ${false}
- `('topbar filters', ({ snippets, showFilters }) => {
- beforeEach(() => {
- createComponent(snippets);
- });
-
- it(`does${showFilters ? '' : ' not'} render when snippets is ${JSON.stringify(
- snippets,
- )}`, () => {
- expect(findGroupFilter().exists()).toBe(showFilters);
- expect(findProjectFilter().exists()).toBe(showFilters);
- });
- });
-
describe.each`
searchType | showSyntaxOptions
${'basic'} | ${false}
@@ -167,9 +142,10 @@ describe('GlobalSearchTopbar', () => {
createComponent();
});
- it('clicking search button inside search box calls applyQuery', () => {
- findGlSearchBox().vm.$emit('submit', { preventDefault: () => {} });
+ it('clicking search button inside search box calls applyQuery', async () => {
+ await nextTick();
+ findGlSearchBox().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
expect(actionSpies.applyQuery).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
index 7996aa92002..9718cb7ad15 100644
--- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js
+++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
@@ -1,5 +1,6 @@
import { nextTick } from 'vue';
import { GlBreakpointInstance as bp, breakpoints } from '@gitlab/ui/dist/utils';
+import sidebarEventHub from '~/super_sidebar/event_hub';
import ExtraInfo from 'jh_else_ce/super_sidebar/components/extra_info.vue';
import { Mousetrap } from '~/lib/mousetrap';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -60,6 +61,7 @@ describe('SuperSidebar component', () => {
const findTrialStatusPopover = () => wrapper.findByTestId(trialStatusPopoverStubTestId);
const findSidebarMenu = () => wrapper.findComponent(SidebarMenu);
const findAdminLink = () => wrapper.findByTestId('sidebar-admin-link');
+ const findContextHeader = () => wrapper.findComponent('#super-sidebar-context-header');
let trackingSpy = null;
const createWrapper = ({
@@ -216,6 +218,15 @@ describe('SuperSidebar component', () => {
expect(wrapper.text()).toContain('Your work');
});
+ it('handles event toggle-menu-header correctly', async () => {
+ createWrapper();
+
+ sidebarEventHub.$emit('toggle-menu-header', false);
+
+ await nextTick();
+ expect(findContextHeader().exists()).toBe(false);
+ });
+
describe('item access tracking', () => {
it('does not track anything if logged out', () => {
createWrapper({ sidebarData: loggedOutSidebarData });
diff --git a/spec/graphql/types/work_item_state_counts_type_spec.rb b/spec/graphql/types/work_item_state_counts_type_spec.rb
new file mode 100644
index 00000000000..bab2e124222
--- /dev/null
+++ b/spec/graphql/types/work_item_state_counts_type_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['WorkItemStateCountsType'], feature_category: :portfolio_management do
+ specify { expect(described_class.graphql_name).to eq('WorkItemStateCountsType') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[all opened closed]
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index d67d07a4f1e..2445689bf9f 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -701,55 +701,35 @@ RSpec.describe ApplicationHelper do
end
describe 'with-header' do
- using RSpec::Parameterized::TableSyntax
-
- before do
- allow(helper).to receive(:show_super_sidebar?).and_return(show_super_sidebar)
- allow(helper).to receive(:current_user).and_return(current_user)
- end
-
- where(:show_super_sidebar, :current_user) do
- true | nil
- false | ref(:user)
- false | nil
- end
-
- with_them do
- it { is_expected.to include('with-header') }
- end
-
- context 'when with-header should not be shown' do
- let(:show_super_sidebar) { true }
- let(:current_user) { user }
+ context 'when current_user' do
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
it { is_expected.not_to include('with-header') }
end
+
+ context 'when no current_user' do
+ before do
+ allow(helper).to receive(:current_user).and_return(nil)
+ end
+
+ it { is_expected.to include('with-header') }
+ end
end
describe 'with-top-bar' do
- context 'when show_super_sidebar? is true' do
- context 'when @hide_top_bar_padding is false' do
- before do
- allow(helper).to receive(:show_super_sidebar?).and_return(true)
- helper.instance_variable_set(:@hide_top_bar_padding, false)
- end
-
- it { is_expected.to include('with-top-bar') }
+ context 'when @hide_top_bar_padding is false' do
+ before do
+ helper.instance_variable_set(:@hide_top_bar_padding, false)
end
- context 'when @hide_top_bar_padding is true' do
- before do
- allow(helper).to receive(:show_super_sidebar?).and_return(true)
- helper.instance_variable_set(:@hide_top_bar_padding, true)
- end
-
- it { is_expected.not_to include('with-top-bar') }
- end
+ it { is_expected.to include('with-top-bar') }
end
- context 'when show_super_sidebar? is false' do
+ context 'when @hide_top_bar_padding is true' do
before do
- allow(helper).to receive(:show_super_sidebar?).and_return(false)
+ helper.instance_variable_set(:@hide_top_bar_padding, true)
end
it { is_expected.not_to include('with-top-bar') }
diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb
index 92a1ea87f86..d52b3c9abb3 100644
--- a/spec/helpers/dashboard_helper_spec.rb
+++ b/spec/helpers/dashboard_helper_spec.rb
@@ -75,10 +75,4 @@ RSpec.describe DashboardHelper do
it { is_expected.to eq(false) }
end
-
- describe '#reviewer_mrs_dashboard_path' do
- subject { helper.reviewer_mrs_dashboard_path }
-
- it { is_expected.to eq(merge_requests_dashboard_path(reviewer_username: user.username)) }
- end
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 525a1928474..f2e88bc311d 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -121,27 +121,6 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do
end
end
- describe '#assigned_open_issues_count_text', feature_category: :team_planning do
- let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project).tap { |p| p.add_developer(user) } }
-
- subject { helper.assigned_open_issues_count_text }
-
- before do
- allow(helper).to receive(:current_user).and_return(user)
- end
-
- context 'when assigned issues count is over MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT' do
- before do
- stub_const('User::MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT', 2)
- end
-
- let_it_be(:issues) { create_list(:issue, 3, project: project, assignees: [user]) }
-
- it { is_expected.to eq '1+' }
- end
- end
-
describe '#issuables_state_counter_text' do
let_it_be(:user) { create(:user) }
diff --git a/spec/helpers/organizations/organization_helper_spec.rb b/spec/helpers/organizations/organization_helper_spec.rb
index 86a7a88797c..594013d515b 100644
--- a/spec/helpers/organizations/organization_helper_spec.rb
+++ b/spec/helpers/organizations/organization_helper_spec.rb
@@ -94,9 +94,12 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
describe '#home_organization_setting_app_data' do
it 'returns expected json' do
+ current_user = build_stubbed(:user)
+ allow(helper).to receive(:current_user).and_return(current_user)
+
expect(Gitlab::Json.parse(helper.home_organization_setting_app_data)).to eq(
{
- 'initial_selection' => 1
+ 'initial_selection' => current_user.user_preference.home_organization_id
}
)
end
diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb
index 4680a43058d..bffb240dae4 100644
--- a/spec/helpers/todos_helper_spec.rb
+++ b/spec/helpers/todos_helper_spec.rb
@@ -48,19 +48,6 @@ RSpec.describe TodosHelper do
create(:todo, target: project, action: Todo::MEMBER_ACCESS_REQUESTED)
end
- describe '#todos_count_format' do
- it 'shows fuzzy count for 100 or more items' do
- expect(helper.todos_count_format(100)).to eq '99+'
- expect(helper.todos_count_format(1000)).to eq '99+'
- end
-
- it 'shows exact count for 99 or fewer items' do
- expect(helper.todos_count_format(99)).to eq '99'
- expect(helper.todos_count_format(50)).to eq '50'
- expect(helper.todos_count_format(1)).to eq '1'
- end
- end
-
describe '#todo_target_name' do
context 'when given a design' do
let(:todo) { design_todo }
diff --git a/spec/lib/gitlab/ci/config/interpolation/text_interpolator_spec.rb b/spec/lib/gitlab/ci/config/interpolation/text_interpolator_spec.rb
new file mode 100644
index 00000000000..70858c0fff8
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/text_interpolator_spec.rb
@@ -0,0 +1,221 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::TextInterpolator, feature_category: :pipeline_composition do
+ let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(config: [header, content]) }
+
+ subject(:interpolator) { described_class.new(result, arguments, []) }
+
+ context 'when input data is valid' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'correctly interpolates the config' do
+ interpolator.interpolate!
+
+ expect(interpolator).to be_interpolated
+ expect(interpolator).to be_valid
+ expect(interpolator.to_result).to eq("test: 'deploy gitlab.com'")
+ end
+ end
+
+ context 'when config has a syntax error' do
+ let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(error: 'Invalid configuration format') }
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'surfaces an error about invalid config' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('Invalid configuration format')
+ end
+ end
+
+ context 'when spec header is missing but inputs are specified' do
+ let(:header) { nil }
+ let(:content) { "test: 'echo'" }
+ let(:arguments) { { foo: 'bar' } }
+
+ it 'surfaces an error about invalid inputs' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq(
+ 'Given inputs not defined in the `spec` section of the included configuration file'
+ )
+ end
+ end
+
+ context 'when spec header is invalid' do
+ let(:header) do
+ { spec: { arguments: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'surfaces an error about invalid header' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('header:spec config contains unknown keys: arguments')
+ end
+ end
+
+ context 'when provided interpolation argument is invalid' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]]'"
+ end
+
+ let(:arguments) do
+ { website: ['gitlab.com'] }
+ end
+
+ it 'returns an error about the invalid argument' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('`website` input: provided value is not a string')
+ end
+ end
+
+ context 'when interpolation block is invalid' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.abc ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'returns an error about the invalid block' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('unknown interpolation key: `abc`')
+ end
+ end
+
+ context 'when multiple interpolation blocks are invalid' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.something.abc ]] $[[ inputs.cde ]] $[[ efg ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'stops execution after the first invalid block' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('unknown interpolation key: `something`')
+ end
+ end
+
+ context 'when there are many invalid arguments' do
+ let(:header) do
+ { spec: { inputs: {
+ allow_failure: { type: 'boolean' },
+ image: nil,
+ parallel: { type: 'number' },
+ website: nil
+ } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]] $[[ inputs.parallel ]] $[[ inputs.allow_failure ]] $[[ inputs.image ]]'"
+ end
+
+ let(:arguments) do
+ { allow_failure: 'no', parallel: 'yes', website: 8 }
+ end
+
+ it 'reports a maximum of 3 errors in the error message' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq(
+ '`allow_failure` input: provided value is not a boolean, ' \
+ '`image` input: required value has not been provided, ' \
+ '`parallel` input: provided value is not a number'
+ )
+ expect(interpolator.errors).to contain_exactly(
+ '`allow_failure` input: provided value is not a boolean',
+ '`image` input: required value has not been provided',
+ '`parallel` input: provided value is not a number',
+ '`website` input: provided value is not a string'
+ )
+ end
+ end
+
+ describe '#to_result' do
+ context 'when interpolation is not used' do
+ let(:result) do
+ ::Gitlab::Ci::Config::Yaml::Result.new(config: content)
+ end
+
+ let(:content) do
+ "test: 'deploy production'"
+ end
+
+ let(:arguments) { nil }
+
+ it 'returns original content' do
+ interpolator.interpolate!
+
+ expect(interpolator.to_result).to eq(content)
+ end
+ end
+
+ context 'when interpolation is available' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'correctly interpolates content' do
+ interpolator.interpolate!
+
+ expect(interpolator.to_result).to eq("test: 'deploy gitlab.com'")
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/text_template_spec.rb b/spec/lib/gitlab/ci/config/interpolation/text_template_spec.rb
new file mode 100644
index 00000000000..a2f98fc0d5d
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/text_template_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::TextTemplate, feature_category: :pipeline_composition do
+ subject(:template) { described_class.new(config, ctx) }
+
+ let(:config) do
+ <<~CFG
+ test:
+ spec:
+ env: $[[ inputs.env ]]
+
+ $[[ inputs.key ]]:
+ name: $[[ inputs.key ]]
+ script: my-value
+ parallel: $[[ inputs.parallel ]]
+ CFG
+ end
+
+ let(:ctx) do
+ { inputs: { env: 'dev', key: 'abc', parallel: 6 } }
+ end
+
+ it 'interpolates the values properly' do
+ expect(template.interpolated).to eq <<~RESULT
+ test:
+ spec:
+ env: dev
+
+ abc:
+ name: abc
+ script: my-value
+ parallel: 6
+ RESULT
+ end
+
+ context 'when the config has an unknown interpolation key' do
+ let(:config) { '$[[ xxx.yyy ]]: abc' }
+
+ it 'does not interpolate the config' do
+ expect(template).not_to be_valid
+ expect(template.interpolated).to be_nil
+ expect(template.errors).to contain_exactly('unknown interpolation key: `xxx`')
+ end
+ end
+
+ context 'when template consists of nested arrays with hashes and values' do
+ let(:config) do
+ <<~CFG
+ test:
+ - a-$[[ inputs.key ]]-b
+ - c-$[[ inputs.key ]]-d:
+ d-$[[ inputs.key ]]-e
+ val: 1
+ CFG
+ end
+
+ it 'performs a valid interpolation' do
+ result = <<~RESULT
+ test:
+ - a-abc-b
+ - c-abc-d:
+ d-abc-e
+ val: 1
+ RESULT
+
+ expect(template).to be_valid
+ expect(template.interpolated).to eq result
+ end
+ end
+
+ context 'when template contains symbols that need interpolation' do
+ subject(:template) do
+ described_class.new("'$[[ inputs.key ]]': 'cde'", ctx)
+ end
+
+ it 'performs a valid interpolation' do
+ expect(template).to be_valid
+ expect(template.interpolated).to eq("'abc': 'cde'")
+ end
+ end
+
+ context 'when template is too large' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 1)
+ end
+
+ it 'returns an error' do
+ expect(template.interpolated).to be_nil
+ expect(template.errors).to contain_exactly('config too large')
+ end
+ end
+
+ context 'when there are too many interpolation blocks' do
+ before do
+ stub_const("#{described_class}::MAX_BLOCKS", 1)
+ end
+
+ it 'returns an error' do
+ expect(template.interpolated).to be_nil
+ expect(template.errors).to contain_exactly('too many interpolation blocks')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/issuables_count_for_state_spec.rb b/spec/lib/gitlab/issuables_count_for_state_spec.rb
index cc4ebba863d..e85dc890cbf 100644
--- a/spec/lib/gitlab/issuables_count_for_state_spec.rb
+++ b/spec/lib/gitlab/issuables_count_for_state_spec.rb
@@ -72,7 +72,6 @@ RSpec.describe Gitlab::IssuablesCountForState do
let_it_be(:group) { create(:group) }
let(:cache_options) { { expires_in: 1.hour } }
- let(:cache_key) { ['group', group.id, 'issues'] }
let(:threshold) { described_class::THRESHOLD }
let(:states_count) { { opened: 1, closed: 1, all: 2 } }
let(:params) { {} }
@@ -95,9 +94,7 @@ RSpec.describe Gitlab::IssuablesCountForState do
end
end
- context 'with Issues' do
- let(:finder) { IssuesFinder.new(user, params) }
-
+ shared_examples 'calculating counts for issuables' do
it 'returns -1 for the requested state' do
allow(finder).to receive(:count_by_state).and_raise(ActiveRecord::QueryCanceled)
expect(Rails.cache).not_to receive(:write)
@@ -162,6 +159,20 @@ RSpec.describe Gitlab::IssuablesCountForState do
end
end
+ context 'with Issues' do
+ let(:finder) { IssuesFinder.new(user, params) }
+ let(:cache_key) { ['group', group.id, 'issues'] }
+
+ it_behaves_like 'calculating counts for issuables'
+ end
+
+ context 'with Work Items' do
+ let(:finder) { ::WorkItems::WorkItemsFinder.new(user, params) }
+ let(:cache_key) { ['group', group.id, 'work_items'] }
+
+ it_behaves_like 'calculating counts for issuables'
+ end
+
context 'with Merge Requests' do
let(:finder) { MergeRequestsFinder.new(user, params) }
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric_spec.rb
deleted file mode 100644
index 91ad81c4291..00000000000
--- a/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_cta_clicked_metric_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Usage::Metrics::Instrumentations::InProductMarketingEmailCtaClickedMetric do
- using RSpec::Parameterized::TableSyntax
-
- let(:email_attributes) { { cta_clicked_at: Date.yesterday, track: 'verify', series: 0 } }
- let(:options) { { track: 'verify', series: 0 } }
- let(:expected_value) { 2 }
- let(:expected_query) do
- 'SELECT COUNT("in_product_marketing_emails"."id") FROM "in_product_marketing_emails" ' \
- 'WHERE "in_product_marketing_emails"."cta_clicked_at" IS NOT NULL ' \
- 'AND "in_product_marketing_emails"."series" = 0 ' \
- 'AND "in_product_marketing_emails"."track" = 1'
- end
-
- before do
- create_list :in_product_marketing_email, 2, email_attributes
-
- create :in_product_marketing_email, email_attributes.merge(cta_clicked_at: nil)
- create :in_product_marketing_email, email_attributes.merge(track: 'team')
- create :in_product_marketing_email, email_attributes.merge(series: 1)
- end
-
- it_behaves_like 'a correct instrumented metric value and query', {
- options: { track: 'verify', series: 0 },
- time_frame: 'all'
- }
-
- where(:options_key, :valid_value, :invalid_value) do
- :track | 'admin_verify' | 'invite_team'
- :series | 1 | 5
- end
-
- with_them do
- it "raises an exception if option is not present" do
- expect do
- described_class.new(options: options.except(options_key), time_frame: 'all')
- end.to raise_error(ArgumentError, %r{#{options_key} .* must be one of})
- end
-
- it "raises an exception if option has invalid value" do
- expect do
- options[options_key] = invalid_value
- described_class.new(options: options, time_frame: 'all')
- end.to raise_error(ArgumentError, %r{#{options_key} .* must be one of})
- end
-
- it "doesn't raise exceptions if option has valid value" do
- options[options_key] = valid_value
- described_class.new(options: options, time_frame: 'all')
- end
- end
-end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric_spec.rb
deleted file mode 100644
index 3c51368f396..00000000000
--- a/spec/lib/gitlab/usage/metrics/instrumentations/in_product_marketing_email_sent_metric_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Usage::Metrics::Instrumentations::InProductMarketingEmailSentMetric do
- using RSpec::Parameterized::TableSyntax
-
- let(:email_attributes) { { track: 'verify', series: 0 } }
- let(:expected_value) { 2 }
- let(:expected_query) do
- 'SELECT COUNT("in_product_marketing_emails"."id") FROM "in_product_marketing_emails" ' \
- 'WHERE "in_product_marketing_emails"."series" = 0 ' \
- 'AND "in_product_marketing_emails"."track" = 1'
- end
-
- before do
- create_list :in_product_marketing_email, 2, email_attributes
-
- create :in_product_marketing_email, email_attributes.merge(track: 'team')
- create :in_product_marketing_email, email_attributes.merge(series: 1)
- end
-
- it_behaves_like 'a correct instrumented metric value and query', {
- options: { track: 'verify', series: 0 },
- time_frame: 'all'
- }
-
- where(:options_key, :valid_value, :invalid_value) do
- :track | 'admin_verify' | 'invite_team'
- :series | 1 | 5
- end
-
- with_them do
- it "raises an exception if option is not present" do
- expect do
- described_class.new(options: email_attributes.except(options_key), time_frame: 'all')
- end.to raise_error(ArgumentError, %r{#{options_key} .* must be one of})
- end
-
- it "raises an exception if option has invalid value" do
- expect do
- email_attributes[options_key] = invalid_value
- described_class.new(options: email_attributes, time_frame: 'all')
- end.to raise_error(ArgumentError, %r{#{options_key} .* must be one of})
- end
-
- it "doesn't raise exceptions if option has valid value" do
- email_attributes[options_key] = valid_value
- described_class.new(options: email_attributes, time_frame: 'all')
- end
- end
-end
diff --git a/spec/lib/gitlab/usage_data_queries_spec.rb b/spec/lib/gitlab/usage_data_queries_spec.rb
index 6d30947167c..68af9cd9cfc 100644
--- a/spec/lib/gitlab/usage_data_queries_spec.rb
+++ b/spec/lib/gitlab/usage_data_queries_spec.rb
@@ -106,25 +106,4 @@ RSpec.describe Gitlab::UsageDataQueries do
expect(described_class.maximum_id(Project)).to eq(nil)
end
end
-
- describe 'sent_in_product_marketing_email_count' do
- it 'returns sql query that returns correct value' do
- expect(described_class.sent_in_product_marketing_email_count(nil, 0, 0)).to eq(
- 'SELECT COUNT("in_product_marketing_emails"."id") ' \
- 'FROM "in_product_marketing_emails" ' \
- 'WHERE "in_product_marketing_emails"."track" = 0 AND "in_product_marketing_emails"."series" = 0'
- )
- end
- end
-
- describe 'clicked_in_product_marketing_email_count' do
- it 'returns sql query that returns correct value' do
- expect(described_class.clicked_in_product_marketing_email_count(nil, 0, 0)).to eq(
- 'SELECT COUNT("in_product_marketing_emails"."id") ' \
- 'FROM "in_product_marketing_emails" ' \
- 'WHERE "in_product_marketing_emails"."track" = 0 AND "in_product_marketing_emails"."series" = 0 ' \
- 'AND "in_product_marketing_emails"."cta_clicked_at" IS NOT NULL'
- )
- end
- end
end
diff --git a/spec/lib/sidebars/explore/menus/catalog_menu_spec.rb b/spec/lib/sidebars/explore/menus/catalog_menu_spec.rb
index 2c4c4c48eae..543f9b26a66 100644
--- a/spec/lib/sidebars/explore/menus/catalog_menu_spec.rb
+++ b/spec/lib/sidebars/explore/menus/catalog_menu_spec.rb
@@ -10,31 +10,19 @@ RSpec.describe Sidebars::Explore::Menus::CatalogMenu, feature_category: :navigat
subject { described_class.new(context) }
- context 'when `global_ci_catalog` is enabled`' do
- it 'renders' do
- expect(subject.render?).to be(true)
- end
-
- it 'renders the correct link' do
- expect(subject.link).to match "explore/catalog"
- end
-
- it 'renders the correct title' do
- expect(subject.title).to eq "CI/CD Catalog"
- end
-
- it 'renders the correct icon' do
- expect(subject.sprite_icon).to eq "catalog-checkmark"
- end
+ it 'renders' do
+ expect(subject.render?).to be(true)
end
- context 'when `global_ci_catalog` FF is disabled' do
- before do
- stub_feature_flags(global_ci_catalog: false)
- end
+ it 'renders the correct link' do
+ expect(subject.link).to match "explore/catalog"
+ end
- it 'does not render' do
- expect(subject.render?).to be(false)
- end
+ it 'renders the correct title' do
+ expect(subject.title).to eq "CI/CD Catalog"
+ end
+
+ it 'renders the correct icon' do
+ expect(subject.sprite_icon).to eq "catalog-checkmark"
end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index aa538de9f02..4aa687b5135 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -2200,4 +2200,21 @@ RSpec.describe Issue, feature_category: :team_planning do
end
end
end
+
+ describe '#gfm_reference' do
+ where(:issue_type, :expected_name) do
+ :issue | 'issue'
+ :incident | 'incident'
+ :test_case | 'test case'
+ :task | 'task'
+ end
+
+ with_them do
+ it 'uses the issue type as the reference name' do
+ issue = create(:issue, issue_type, project: reusable_project)
+
+ expect(issue.gfm_reference).to eq("#{expected_name} #{issue.to_reference}")
+ end
+ end
+ end
end
diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb
index 343576de4d3..ee3fbb97e47 100644
--- a/spec/models/user_preference_spec.rb
+++ b/spec/models/user_preference_spec.rb
@@ -64,6 +64,10 @@ RSpec.describe UserPreference, feature_category: :user_profile do
end
end
+ describe 'associations' do
+ it { is_expected.to belong_to(:home_organization).class_name('Organizations::Organization').optional }
+ end
+
describe 'notes filters global keys' do
it 'contains expected values' do
expect(UserPreference::NOTES_FILTERS.keys).to match_array([:all_notes, :only_comments, :only_activity])
@@ -291,4 +295,30 @@ RSpec.describe UserPreference, feature_category: :user_profile do
expect(pref.read_attribute(:render_whitespace_in_code)).to eq(true)
end
end
+
+ describe '#user_belongs_to_home_organization' do
+ let_it_be(:organization) { create(:organization) }
+
+ context 'when user is an organization user' do
+ before do
+ create(:organization_user, organization: organization, user: user)
+ end
+
+ it 'does not add any validation errors' do
+ user_preference.home_organization = organization
+
+ expect(user_preference).to be_valid
+ expect(user_preference.errors).to be_empty
+ end
+ end
+
+ context 'when user is not an organization user' do
+ it 'adds a validation error' do
+ user_preference.home_organization = organization
+
+ expect(user_preference).to be_invalid
+ expect(user_preference.errors.messages[:user].first).to eq(_("is not part of the given organization"))
+ end
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 6a21ab644bc..641ab947388 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -95,6 +95,10 @@ RSpec.describe User, feature_category: :user_profile do
it { is_expected.to delegate_method(:achievements_enabled).to(:user_preference) }
it { is_expected.to delegate_method(:achievements_enabled=).to(:user_preference).with_arguments(:args) }
+ it { is_expected.to delegate_method(:home_organization).to(:user_preference) }
+ it { is_expected.to delegate_method(:home_organization_id).to(:user_preference) }
+ it { is_expected.to delegate_method(:home_organization_id=).to(:user_preference).with_arguments(:args) }
+
it { is_expected.to delegate_method(:job_title).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:job_title=).to(:user_detail).with_arguments(:args).allow_nil }
@@ -1441,6 +1445,25 @@ RSpec.describe User, feature_category: :user_profile do
end
end
+ describe '#user_belongs_to_organization?' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:organization) { create(:organization) }
+
+ subject { user.user_belongs_to_organization?(organization) }
+
+ context 'when user is an organization user' do
+ before do
+ create(:organization_user, organization: organization, user: user)
+ end
+
+ it { is_expected.to eq true }
+ end
+
+ context 'when user is not an organization user' do
+ it { is_expected.to eq false }
+ end
+ end
+
context 'strip attributes' do
context 'name' do
let(:user) { described_class.new(name: ' John Smith ') }
diff --git a/spec/models/users/in_product_marketing_email_spec.rb b/spec/models/users/in_product_marketing_email_spec.rb
index d333a51ae3b..b1642383e42 100644
--- a/spec/models/users/in_product_marketing_email_spec.rb
+++ b/spec/models/users/in_product_marketing_email_spec.rb
@@ -134,15 +134,4 @@ RSpec.describe Users::InProductMarketingEmail, type: :model, feature_category: :
end
end
end
-
- describe '.ACTIVE_TRACKS' do
- it 'has an entry for every track' do
- tracks = Namespaces::InProductMarketingEmailsService::TRACKS.keys
- expect(tracks).to match_array(described_class::ACTIVE_TRACKS.keys.map(&:to_sym))
- end
-
- it 'does not include INACTIVE_TRACK_NAMES' do
- expect(described_class::ACTIVE_TRACKS.keys).not_to include(*described_class::INACTIVE_TRACK_NAMES)
- end
- end
end
diff --git a/spec/requests/api/graphql/group/work_item_state_counts_spec.rb b/spec/requests/api/graphql/group/work_item_state_counts_spec.rb
new file mode 100644
index 00000000000..2ae623c39f2
--- /dev/null
+++ b/spec/requests/api/graphql/group/work_item_state_counts_spec.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'request_store'
+
+RSpec.describe 'getting Work Item counts by state', feature_category: :portfolio_management do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:work_item_opened1) { create(:work_item, namespace: group) }
+ let_it_be(:work_item_opened2) { create(:work_item, namespace: group, author: current_user) }
+ let_it_be(:work_item_closed1) { create(:work_item, :closed, namespace: group) }
+ let_it_be(:work_item_closed2) { create(:work_item, :closed, namespace: group) }
+
+ let(:params) { {} }
+
+ subject(:query_counts) { post_graphql(query, current_user: current_user) }
+
+ context 'with work items count data' do
+ let(:work_item_counts) { graphql_data.dig('group', 'workItemStateCounts') }
+
+ context 'with group permissions' do
+ before_all do
+ group.add_developer(current_user)
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ query_counts
+ end
+ end
+
+ it 'returns the correct counts for each state' do
+ query_counts
+
+ expect(work_item_counts).to eq(
+ 'all' => 4,
+ 'opened' => 2,
+ 'closed' => 2
+ )
+ end
+
+ context 'when filters are provided' do
+ context 'when filtering by author username' do
+ let(:params) { { 'authorUsername' => current_user.username } }
+
+ it 'returns the correct counts for each state' do
+ query_counts
+
+ expect(work_item_counts).to eq(
+ 'all' => 1,
+ 'opened' => 1,
+ 'closed' => 0
+ )
+ end
+ end
+
+ context 'when filtering by search' do
+ let(:params) { { search: 'foo', in: [:TITLE] } }
+
+ it 'returns an error for filters that are not supported' do
+ query_counts
+
+ expect(graphql_errors).to contain_exactly(
+ hash_including('message' => 'Searching is not available for work items at the namespace level yet')
+ )
+ end
+ end
+ end
+
+ context 'when the namespace_level_work_items feature flag is disabled' do
+ before do
+ stub_feature_flags(namespace_level_work_items: false)
+ end
+
+ it 'does not return work item counts' do
+ query_counts
+
+ expect_graphql_errors_to_be_empty
+ expect(work_item_counts).to be_nil
+ end
+ end
+ end
+
+ context 'without group permissions' do
+ it 'does not return work item counts' do
+ query_counts
+
+ expect_graphql_errors_to_be_empty
+ expect(work_item_counts).to be_nil
+ end
+ end
+ end
+
+ def query(args: params)
+ fields = <<~QUERY
+ #{all_graphql_fields_for('WorkItemStateCountsType'.classify)}
+ QUERY
+
+ graphql_query_for(
+ 'group',
+ { 'fullPath' => group.full_path },
+ query_graphql_field('workItemStateCounts', args, fields)
+ )
+ end
+end
diff --git a/spec/requests/api/graphql/project/work_item_state_counts_spec.rb b/spec/requests/api/graphql/project/work_item_state_counts_spec.rb
new file mode 100644
index 00000000000..d13204a36b7
--- /dev/null
+++ b/spec/requests/api/graphql/project/work_item_state_counts_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting Work Item counts by state', feature_category: :portfolio_management do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:project) { create(:project, :repository, :private, group: group) }
+ let_it_be(:work_item_opened1) { create(:work_item, project: project, title: 'Foo') }
+ let_it_be(:work_item_opened2) { create(:work_item, project: project, author: current_user) }
+ let_it_be(:work_item_closed) { create(:work_item, :closed, project: project, description: 'Bar') }
+
+ let(:params) { {} }
+
+ subject(:query_counts) { post_graphql(query, current_user: current_user) }
+
+ context 'with work items count data' do
+ let(:work_item_counts) { graphql_data.dig('project', 'workItemStateCounts') }
+
+ context 'with project permissions' do
+ before_all do
+ group.add_developer(current_user)
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ query_counts
+ end
+ end
+
+ it 'returns the correct counts for each state' do
+ query_counts
+
+ expect(work_item_counts).to eq(
+ 'all' => 3,
+ 'opened' => 2,
+ 'closed' => 1
+ )
+ end
+
+ context 'when other work items are present in the group' do
+ it 'only returns counts for work items in the current project' do
+ other_project = create(:project, :repository, group: group)
+ create(:work_item, project: other_project)
+ query_counts
+
+ expect(work_item_counts).to eq(
+ 'all' => 3,
+ 'opened' => 2,
+ 'closed' => 1
+ )
+ end
+ end
+
+ context 'when filters are provided' do
+ context 'when filtering by author username' do
+ let(:params) { { 'authorUsername' => current_user.username } }
+
+ it 'returns the correct counts for each status' do
+ query_counts
+
+ expect(work_item_counts).to eq(
+ 'all' => 1,
+ 'opened' => 1,
+ 'closed' => 0
+ )
+ end
+ end
+
+ context 'when searching in title' do
+ let(:params) { { search: 'Foo', in: [:TITLE] } }
+
+ it 'returns the correct counts for each status' do
+ query_counts
+
+ expect(work_item_counts).to eq(
+ 'all' => 1,
+ 'opened' => 1,
+ 'closed' => 0
+ )
+ end
+ end
+
+ context 'when searching in description' do
+ let(:params) { { search: 'Bar', in: [:DESCRIPTION] } }
+
+ it 'returns the correct counts for each status' do
+ query_counts
+
+ expect(work_item_counts).to eq(
+ 'all' => 1,
+ 'opened' => 0,
+ 'closed' => 1
+ )
+ end
+ end
+ end
+ end
+
+ context 'without project permissions' do
+ it 'does not return work item counts' do
+ query_counts
+
+ expect_graphql_errors_to_be_empty
+ expect(work_item_counts).to be_nil
+ end
+ end
+ end
+
+ def query(args: params)
+ fields = <<~QUERY
+ #{all_graphql_fields_for('WorkItemStateCountsType'.classify)}
+ QUERY
+
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('workItemStateCounts', args, fields)
+ )
+ end
+end
diff --git a/spec/requests/explore/catalog_controller_spec.rb b/spec/requests/explore/catalog_controller_spec.rb
index 1361e207779..051379c9641 100644
--- a/spec/requests/explore/catalog_controller_spec.rb
+++ b/spec/requests/explore/catalog_controller_spec.rb
@@ -23,28 +23,10 @@ RSpec.describe Explore::CatalogController, feature_category: :pipeline_compositi
end
end
- context 'with FF `global_ci_catalog`' do
- before do
- stub_feature_flags(global_ci_catalog: true)
- end
+ it 'responds with 200' do
+ get path
- it 'responds with 200' do
- get path
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'without FF `global_ci_catalog`' do
- before do
- stub_feature_flags(global_ci_catalog: false)
- end
-
- it 'responds with 404' do
- get path
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
+ expect(response).to have_gitlab_http_status(:ok)
end
end
diff --git a/spec/requests/projects/service_desk_controller_spec.rb b/spec/requests/projects/service_desk_controller_spec.rb
index 7d881d8ea62..1a8104dd669 100644
--- a/spec/requests/projects/service_desk_controller_spec.rb
+++ b/spec/requests/projects/service_desk_controller_spec.rb
@@ -78,24 +78,25 @@ RSpec.describe Projects::ServiceDeskController, feature_category: :service_desk
expect(response).to have_gitlab_http_status(:ok)
end
- it 'sets issue_template_key' do
- put project_service_desk_path(project, format: :json), params: { issue_template_key: 'service_desk' }
+ it 'sets attributes', :aggregate_failures do
+ put project_service_desk_path(project, format: :json), params: {
+ issue_template_key: 'service_desk',
+ reopen_issue_on_external_participant_note: true,
+ add_external_participants_from_cc: true
+ }
settings = project.service_desk_setting
expect(settings).to be_present
- expect(settings.issue_template_key).to eq('service_desk')
- expect(json_response['template_file_missing']).to eq(false)
- expect(json_response['issue_template_key']).to eq('service_desk')
- end
-
- it 'sets add_external_participants_from_cc' do
- put project_service_desk_path(project, format: :json), params: { add_external_participants_from_cc: true }
- project.reset
-
- settings = project.service_desk_setting
- expect(settings).to be_present
- expect(settings.add_external_participants_from_cc).to eq(true)
- expect(json_response['add_external_participants_from_cc']).to eq(true)
+ expect(settings).to have_attributes(
+ issue_template_key: 'service_desk',
+ reopen_issue_on_external_participant_note: true,
+ add_external_participants_from_cc: true
+ )
+ expect(json_response).to include(
+ 'issue_template_key' => 'service_desk',
+ 'reopen_issue_on_external_participant_note' => true,
+ 'add_external_participants_from_cc' => true
+ )
end
it 'returns an error when update of service desk settings fails' do
diff --git a/spec/rubocop/cop/database/avoid_using_pluck_without_limit_spec.rb b/spec/rubocop/cop/database/avoid_using_pluck_without_limit_spec.rb
new file mode 100644
index 00000000000..801cf449726
--- /dev/null
+++ b/spec/rubocop/cop/database/avoid_using_pluck_without_limit_spec.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+require 'rubocop_spec_helper'
+require_relative '../../../../rubocop/cop/database/avoid_using_pluck_without_limit'
+
+RSpec.describe RuboCop::Cop::Database::AvoidUsingPluckWithoutLimit, feature_category: :database do
+ context 'when using pluck without a limit' do
+ it 'flags the use of pluck as a model scope' do
+ expect_offense(<<~RUBY)
+ class MyModel < ApplicationRecord
+ scope :all_users, -> { where(user_id: User.pluck(:id)) }
+ ^^^^^ #{described_class::MSG}
+ end
+ RUBY
+ end
+
+ it 'flags the use of pluck as a regular method' do
+ expect_offense(<<~RUBY)
+ class MyModel < ApplicationRecord
+ def all
+ self.pluck(:id)
+ ^^^^^ #{described_class::MSG}
+ end
+ end
+ RUBY
+ end
+
+ it 'flags the use of pluck inside where' do
+ expect_offense(<<~RUBY)
+ class MyModel < ApplicationRecord
+ def all_projects
+ Project.where(id: self.pluck(:id))
+ ^^^^^ #{described_class::MSG}
+ end
+ end
+ RUBY
+ end
+
+ it 'flags the use of pluck inside a model class method' do
+ allow(cop).to receive(:in_model?).and_return(true)
+
+ expect_offense(<<~RUBY)
+ class MyClass < Model
+ def all_users
+ User.where(id: self.pluck(:id))
+ ^^^^^ #{described_class::MSG}
+ end
+ end
+ RUBY
+ end
+
+ it 'flags the use of pluck inside a finder' do
+ allow(cop).to receive(:in_finder?).and_return(true)
+
+ expect_offense(<<~RUBY)
+ class MyFinder
+ def find(path)
+ Project.where(path: path).pluck(:id)
+ ^^^^^ #{described_class::MSG}
+ end
+ end
+ RUBY
+ end
+
+ it 'flags the use of pluck inside a service' do
+ allow(cop).to receive(:in_service_class?).and_return(true)
+
+ expect_offense(<<~RUBY)
+ class MyService
+ def delete_all(project)
+ delete(project.for_scan_result_policy_read(scan_result_policy_reads.pluck(:id)))
+ ^^^^^ #{described_class::MSG}
+ end
+ end
+ RUBY
+ end
+ end
+
+ context 'when using pluck with a limit' do
+ it 'does not flags the use of pluck as a model scope' do
+ expect_no_offenses(<<~RUBY)
+ class MyModel < ApplicationRecord
+ scope :all_users, ->(limit) { where(user_id: User.limit(limit).pluck(:id)) }
+ end
+ RUBY
+ end
+
+ it 'does not flags the use of pluck as a regular method' do
+ expect_no_offenses(<<~RUBY)
+ class MyModel < ApplicationRecord
+ def all(limit)
+ self.limit(limit).pluck(:id)
+ end
+ end
+ RUBY
+ end
+
+ it 'does not flags the use of pluck inside where' do
+ expect_no_offenses(<<~RUBY)
+ class MyModel < ApplicationRecord
+ def all_projects(limit)
+ Project.where(id: self.limit(limit).pluck(:id))
+ end
+ end
+ RUBY
+ end
+
+ it 'does not flags the use of pluck inside a model class method' do
+ allow(cop).to receive(:in_model?).and_return(true)
+
+ expect_no_offenses(<<~RUBY)
+ class MyClass < Model
+ def all_users
+ User.where(id: self.limit(100).pluck(:id))
+ end
+ end
+ RUBY
+ end
+
+ it 'does not flags the use of pluck inside a finder' do
+ allow(cop).to receive(:in_finder?).and_return(true)
+
+ expect_no_offenses(<<~RUBY)
+ class MyFinder
+ def find(path)
+ Project.where(path: path).limit(100).pluck(:id)
+ end
+ end
+ RUBY
+ end
+
+ it 'flags the use of pluck inside a service' do
+ allow(cop).to receive(:in_service_class?).and_return(true)
+
+ expect_no_offenses(<<~RUBY)
+ class MyService
+ def delete_all(project)
+ delete(project.for_scan_result_policy_read(scan_result_policy_reads.limit(100).pluck(:id)))
+ end
+ end
+ RUBY
+ end
+ end
+end
diff --git a/spec/services/groups/participants_service_spec.rb b/spec/services/groups/participants_service_spec.rb
index 8359bf1670f..e934921317d 100644
--- a/spec/services/groups/participants_service_spec.rb
+++ b/spec/services/groups/participants_service_spec.rb
@@ -22,6 +22,12 @@ RSpec.describe Groups::ParticipantsService, feature_category: :groups_and_projec
stub_feature_flags(disable_all_mention: false)
end
+ it 'returns results in correct order' do
+ expect(service_result.pluck(:username)).to eq([
+ 'all', developer.username, parent_group.full_path, subgroup.full_path
+ ])
+ end
+
it 'includes `All Group Members`' do
group.add_developer(create(:user))
diff --git a/spec/services/service_desk_settings/update_service_spec.rb b/spec/services/service_desk_settings/update_service_spec.rb
index 321f5576941..2c310bad247 100644
--- a/spec/services/service_desk_settings/update_service_spec.rb
+++ b/spec/services/service_desk_settings/update_service_spec.rb
@@ -13,7 +13,14 @@ RSpec.describe ServiceDeskSettings::UpdateService, :aggregate_failures, feature_
let_it_be(:user) { create(:user) }
context 'with valid params' do
- let(:params) { { outgoing_name: 'some name', project_key: 'foo', add_external_participants_from_cc: true } }
+ let(:params) do
+ {
+ outgoing_name: 'some name',
+ project_key: 'foo',
+ reopen_issue_on_external_participant_note: true,
+ add_external_participants_from_cc: true
+ }
+ end
it 'updates service desk settings' do
response = described_class.new(settings.project, user, params).execute
@@ -22,6 +29,7 @@ RSpec.describe ServiceDeskSettings::UpdateService, :aggregate_failures, feature_
expect(settings.reset).to have_attributes(
outgoing_name: 'some name',
project_key: 'foo',
+ reopen_issue_on_external_participant_note: true,
add_external_participants_from_cc: true
)
end
diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb
index ca6feb6fde2..0ba20ee5be1 100644
--- a/spec/services/system_notes/issuables_service_spec.rb
+++ b/spec/services/system_notes/issuables_service_spec.rb
@@ -387,7 +387,7 @@ RSpec.describe ::SystemNotes::IssuablesService, feature_category: :team_planning
describe 'note_body' do
context 'cross-project' do
let(:project2) { create(:project, :repository) }
- let(:mentioned_in) { create(:issue, project: project2) }
+ let(:mentioned_in) { create(:issue, :task, project: project2) }
context 'from Commit' do
let(:mentioned_in) { project2.repository.commit }
@@ -399,7 +399,7 @@ RSpec.describe ::SystemNotes::IssuablesService, feature_category: :team_planning
context 'from non-Commit' do
it 'references the mentioning object' do
- expect(subject.note).to eq "mentioned in issue #{mentioned_in.to_reference(project)}"
+ expect(subject.note).to eq "mentioned in task #{mentioned_in.to_reference(project)}"
end
end
end
diff --git a/spec/services/users/in_product_marketing_email_records_spec.rb b/spec/services/users/in_product_marketing_email_records_spec.rb
deleted file mode 100644
index d214560b2a6..00000000000
--- a/spec/services/users/in_product_marketing_email_records_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Users::InProductMarketingEmailRecords, feature_category: :onboarding do
- let_it_be(:user) { create :user }
-
- subject(:records) { described_class.new }
-
- it 'initializes records' do
- expect(subject.records).to match_array []
- end
-
- describe '#save!' do
- before do
- allow(Users::InProductMarketingEmail).to receive(:bulk_insert!)
-
- records.add(user, track: :team_short, series: 0)
- records.add(user, track: :create, series: 1)
- end
-
- it 'bulk inserts added records' do
- expect(Users::InProductMarketingEmail).to receive(:bulk_insert!).with(records.records)
- records.save!
- end
-
- it 'resets its records' do
- records.save!
- expect(records.records).to match_array []
- end
- end
-
- describe '#add' do
- it 'adds a Users::InProductMarketingEmail record to its records', :aggregate_failures do
- freeze_time do
- records.add(user, track: :team_short, series: 0)
- records.add(user, track: :create, series: 1)
-
- first, second = records.records
-
- expect(first).to be_a Users::InProductMarketingEmail
- expect(first.track.to_sym).to eq :team_short
- expect(first.series).to eq 0
- expect(first.created_at).to eq Time.zone.now
- expect(first.updated_at).to eq Time.zone.now
-
- expect(second).to be_a Users::InProductMarketingEmail
- expect(second.track.to_sym).to eq :create
- expect(second.series).to eq 1
- expect(second.created_at).to eq Time.zone.now
- expect(second.updated_at).to eq Time.zone.now
- end
- end
- end
-end
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 2892edbe85c..d68331b855a 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -2899,7 +2899,6 @@
- './ee/spec/views/groups/settings/reporting/show.html.haml_spec.rb'
- './ee/spec/views/layouts/application.html.haml_spec.rb'
- './ee/spec/views/layouts/checkout.html.haml_spec.rb'
-- './ee/spec/views/layouts/header/_current_user_dropdown.html.haml_spec.rb'
- './ee/spec/views/layouts/header/_ee_subscribable_banner.html.haml_spec.rb'
- './ee/spec/views/layouts/header/_read_only_banner.html.haml_spec.rb'
- './ee/spec/views/operations/environments.html.haml_spec.rb'
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index a5ccce27aa5..2adb9e410a7 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -225,29 +225,6 @@ RSpec.shared_context 'dashboard navbar structure' do
end
RSpec.shared_context '"Explore" navbar structure' do
- let(:structure) do
- [
- {
- nav_item: _("Projects"),
- nav_sub_items: []
- },
- {
- nav_item: _("Groups"),
- nav_sub_items: []
- },
- {
- nav_item: _("Topics"),
- nav_sub_items: []
- },
- {
- nav_item: _("Snippets"),
- nav_sub_items: []
- }
- ]
- end
-end
-
-RSpec.shared_context '"Explore" navbar structure with global_ci_catalog FF' do
let(:structure) do
[
{
diff --git a/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
index 97c4b275a6e..bdc2bac0573 100644
--- a/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
+++ b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
@@ -124,23 +124,11 @@ RSpec.shared_examples 'every metric definition' do
Gitlab::Usage::Metrics::Instrumentations::UniqueUsersAllImportsMetric::IMPORTS_METRICS
end
- let(:removed_classes) do
- [
- Gitlab::Usage::Metrics::Instrumentations::InProductMarketingEmailCtaClickedMetric,
- Gitlab::Usage::Metrics::Instrumentations::InProductMarketingEmailSentMetric
- ].freeze
- end
-
- def metric_not_used?(constant)
- parent_metric_classes.include?(constant) ||
- ignored_classes.include?(constant) ||
- removed_classes.include?(constant)
- end
-
def assert_uses_all_nested_classes(parent_module)
parent_module.constants(false).each do |const_name|
constant = parent_module.const_get(const_name, false)
- next if metric_not_used?(constant)
+ next if parent_metric_classes.include?(constant) ||
+ ignored_classes.include?(constant)
case constant
when Class