diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 26ce20e976d..4bbc8719c56 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -215,18 +215,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/administration/audit_events.md @eread
/doc/administration/audit_reports.md @eread
/doc/administration/auditor_users.md @axil
-/doc/administration/auth/atlassian.md @eread
-/doc/administration/auth/authentiq.md @eread
-/doc/administration/auth/cognito.md @eread
-/doc/administration/auth/crowd.md @eread
-/doc/administration/auth/index.md @eread
-/doc/administration/auth/ldap/google_secure_ldap.md @eread
-/doc/administration/auth/jwt.md @eread
-/doc/administration/auth/ldap/ldap-troubleshooting.md @eread
-/doc/administration/auth/ldap/ldap_synchronization.md @eread
-/doc/administration/auth/ldap/index.md @eread
-/doc/administration/auth/oidc.md @eread
-/doc/administration/auth/smartcard.md @eread
+/doc/administration/auth/ @eread
/doc/administration/cicd.md @marcel.amirault
/doc/administration/clusters/kas.md @sselhorn
/doc/administration/compliance.md @eread
@@ -269,8 +258,8 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/administration/operations/sidekiq_memory_killer.md @marcel.amirault
/doc/administration/package_information/ @axil
/doc/administration/packages/ @ngaskill
-/doc/administration/pages/index.md @rdickenson
-/doc/administration/pages/source.md @rdickenson
+/doc/administration/pages/index.md @aqualls
+/doc/administration/pages/source.md @aqualls
/doc/administration/polling.md @axil
/doc/administration/postgresql/ @aqualls
/doc/administration/pseudonymizer.md @axil
@@ -291,6 +280,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/administration/smime_signing_email.md @axil
/doc/administration/snippets/index.md @aqualls
/doc/administration/static_objects_external_storage.md @aqualls
+/doc/administration/system_hooks.md @kpaizee
/doc/administration/terraform_state.md @sselhorn
/doc/administration/timezone.md @axil
/doc/administration/troubleshooting/ @axil
@@ -340,6 +330,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/api/graphql/ @kpaizee
/doc/api/graphql/custom_emoji.md @msedlakjakubowski
/doc/api/graphql/sample_issue_boards.md @msedlakjakubowski
+/doc/api/group_access_tokens.md @eread
/doc/api/group_activity_analytics.md @fneill
/doc/api/group_badges.md @eread
/doc/api/group_boards.md @msedlakjakubowski
@@ -369,6 +360,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/api/keys.md @aqualls
/doc/api/labels.md @msedlakjakubowski
/doc/api/license.md @kpaizee
+/doc/api/linked_epics.md @msedlakjakubowski
/doc/api/lint.md @marcel.amirault
/doc/api/managed_licenses.md @kpaizee
/doc/api/markdown.md @aqualls
@@ -387,13 +379,15 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/api/openapi/openapi_interactive.md @kpaizee
/doc/api/packages.md @ngaskill
/doc/api/packages/ @ngaskill
-/doc/api/pages_domains.md @rdickenson
-/doc/api/pages.md @rdickenson
+/doc/api/pages_domains.md @aqualls
+/doc/api/pages.md @aqualls
/doc/api/personal_access_tokens.md @eread
/doc/api/pipeline_schedules.md @marcel.amirault
/doc/api/pipeline_triggers.md @marcel.amirault
/doc/api/pipelines.md @marcel.amirault
+/doc/api/scim.md @eread
/doc/api/plan_limits.md @eread
+/doc/api/project_access_tokens.md @eread
/doc/api/project_aliases.md @aqualls
/doc/api/project_badges.md @aqualls
/doc/api/project_clusters.md @sselhorn
@@ -422,7 +416,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/api/resource_state_events.md @msedlakjakubowski
/doc/api/resource_weight_events.md @msedlakjakubowski
/doc/api/runners.md @sselhorn
-/doc/api/scim.md @eread
+/doc/api/plan_limits.md @eread
/doc/api/search.md @aqualls
/doc/api/secure_files.md @marcel.amirault
/doc/api/settings.md @eread
@@ -509,6 +503,8 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/auto_devops.md @sselhorn
/doc/development/avoiding_downtime_in_migrations.md @aqualls
/doc/development/backend/create_source_code_be/index.md @aqualls
+/doc/development/backend/ruby_style_guide.md @marcia
+/doc/development/background_migrations.md @marcia
/doc/development/build_test_package.md @axil
/doc/development/bulk_import.md @ngaskill
/doc/development/cascading_settings.md @eread
@@ -518,11 +514,15 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/cicd/schema.md @marcel.amirault
/doc/development/cicd/templates.md @marcel.amirault
/doc/development/code_intelligence/index.md @aqualls
+/doc/development/contributing/ @marcia
+/doc/development/contributing/merge_request_workflow.md @aqualls
/doc/development/creating_enums.md @aqualls
/doc/development/database_debugging.md @aqualls
/doc/development/database_query_comments.md @aqualls
/doc/development/database_review.md @aqualls
+/doc/development/database/client_side_connection_pool.md @marcia
/doc/development/database/ @aqualls
+/doc/development/database/multiple_databases.md @marcia
/doc/development/db_dump.md @aqualls
/doc/development/developing_with_solargraph.md @aqualls
/doc/development/distributed_tracing.md @ngaskill
@@ -538,7 +538,12 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/experiment_guide/ @kpaizee
/doc/development/export_csv.md @ngaskill
/doc/development/fe_guide/content_editor.md @aqualls
+/doc/development/fe_guide/dark_mode.md @marcia
+/doc/development/fe_guide/graphql.md @marcia
/doc/development/fe_guide/source_editor.md @aqualls
+/doc/development/feature_categorization/index.md @marcia
+/doc/development/feature_flags/controls.md @marcia
+/doc/development/feature_flags/index.md @marcia
/doc/development/filtering_by_label.md @msedlakjakubowski
/doc/development/foreign_keys.md @aqualls
/doc/development/geo.md @axil
@@ -551,12 +556,15 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/i18n/ @ngaskill
/doc/development/image_scaling.md @marcel.amirault
/doc/development/import_export.md @ngaskill
+/doc/development/index.md @marcia
/doc/development/insert_into_tables_in_batches.md @aqualls
-/doc/development/integrations/jenkins.md @kpaizee
+/doc/development/integrations/ @kpaizee
+/doc/development/integrations/codesandbox.md @marcia
/doc/development/integrations/jira_connect.md @kpaizee
/doc/development/integrations/secure_partner_integration.md @rdickenson
/doc/development/integrations/secure.md @ngaskill
/doc/development/internal_api/ @aqualls
+/doc/development/internal_users.md @marcia
/doc/development/issuable-like-models.md @msedlakjakubowski
/doc/development/issue_types.md @msedlakjakubowski
/doc/development/iterating_tables_in_batches.md @aqualls
@@ -565,17 +573,24 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/licensed_feature_availability.md @sselhorn
/doc/development/maintenance_mode.md @axil
/doc/development/new_fe_guide/modules/widget_extensions.md @aqualls
+/doc/development/new_fe_guide/tips.md @marcia
/doc/development/omnibus.md @axil
/doc/development/ordering_table_columns.md @aqualls
/doc/development/packages.md @ngaskill
+/doc/development/permissions.md @eread
+/doc/development/policies.md @eread
/doc/development/product_qualified_lead_guide/index.md @kpaizee
/doc/development/query_performance.md @aqualls
/doc/development/real_time.md @msedlakjakubowski
+/doc/development/secure_coding_guidelines.md @marcia
/doc/development/serializing_data.md @aqualls
/doc/development/service_ping/ @fneill
/doc/development/snowplow/ @fneill
+/doc/development/spam_protection_and_captcha/ @eread
/doc/development/sql.md @aqualls
/doc/development/swapping_tables.md @aqualls
+/doc/development/testing_guide/best_practices.md @marcia
+/doc/development/testing_guide/end_to_end/best_practices.md @marcia
/doc/development/understanding_explain_plans.md @aqualls
/doc/development/value_stream_analytics.md @fneill
/doc/development/value_stream_analytics/value_stream_analytics_aggregated_backend.md @fneill
@@ -583,6 +598,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/wikis.md @aqualls
/doc/development/work_items.md @msedlakjakubowski
/doc/development/work_items_widgets.md @msedlakjakubowski
+/doc/development/workspace/index.md @marcia
/doc/downgrade_ee_to_ce/index.md @axil
/doc/gitlab-basics/add-file.md @aqualls
/doc/gitlab-basics/command-line-commands.md @aqualls
@@ -610,6 +626,8 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/raketasks/generate_sample_prometheus_data.md @ngaskill
/doc/raketasks/migrate_snippets.md @aqualls
/doc/raketasks/spdx.md @rdickenson
+/doc/raketasks/user_management.md @axil
+/doc/raketasks/web_hooks.md @axil
/doc/raketasks/x509_signatures.md @aqualls
/doc/security/ @eread
/doc/ssh/index.md @eread
@@ -632,6 +650,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/admin_area/geo_nodes.md @axil
/doc/user/admin_area/labels.md @msedlakjakubowski
/doc/user/admin_area/license.md @kpaizee
+/doc/user/admin_area/license_file.md @sselhorn
/doc/user/admin_area/merge_requests_approvals.md @aqualls
/doc/user/admin_area/moderate_users.md @eread
/doc/user/admin_area/monitoring/background_migrations.md @aqualls
@@ -657,8 +676,8 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/admin_area/settings/rate_limit_on_users_api.md @eread
/doc/user/admin_area/settings/third_party_offers.md @fneill
/doc/user/admin_area/settings/visibility_and_access_controls.md @aqualls
-/doc/user/analytics/ci_cd_analytics.md @rdickenson
/doc/user/analytics/ @fneill
+/doc/user/analytics/ci_cd_analytics.md @rdickenson
/doc/user/application_security/ @rdickenson
/doc/user/application_security/cluster_image_scanning/index.md @ngaskill
/doc/user/application_security/container_scanning/index.md @ngaskill
@@ -676,15 +695,18 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/compliance/license_compliance/index.md @rdickenson
/doc/user/crm/index.md @msedlakjakubowski
/doc/user/discussions/index.md @aqualls
+/doc/user/feature_flags.md @marcia
+/doc/user/group/index.md @eread
/doc/user/group/clusters/index.md @sselhorn
/doc/user/group/contribution_analytics/index.md @fneill
/doc/user/group/custom_project_templates.md @ngaskill
/doc/user/group/devops_adoption/index.md @fneill
/doc/user/group/epics/epic_boards.md @msedlakjakubowski
/doc/user/group/epics/index.md @msedlakjakubowski
+/doc/user/group/epics/linked_epics.md @msedlakjakubowski
/doc/user/group/epics/manage_epics.md @msedlakjakubowski
-/doc/user/group/import/index.md @ngaskill
/doc/user/group/index.md @eread
+/doc/user/group/import/index.md @ngaskill
/doc/user/group/insights/index.md @fneill
/doc/user/group/issues_analytics/index.md @msedlakjakubowski
/doc/user/group/iterations/index.md @msedlakjakubowski
@@ -694,6 +716,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/group/saml_sso/group_managed_accounts.md @eread
/doc/user/group/saml_sso/index.md @eread
/doc/user/group/saml_sso/scim_setup.md @eread
+/doc/user/group/settings/group_access_tokens.md @eread
/doc/user/group/settings/import_export.md @ngaskill
/doc/user/group/subgroups/index.md @eread
/doc/user/group/value_stream_analytics/index.md @fneill
@@ -704,11 +727,13 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/infrastructure/clusters/manage/management_project_applications/falco.md @ngaskill
/doc/user/infrastructure/clusters/manage/management_project_applications/fluentd.md @ngaskill
/doc/user/infrastructure/iac/ @sselhorn
-/doc/user/infrastructure/index.md @ssehorn
+/doc/user/infrastructure/index.md @sselhorn
/doc/user/markdown.md @aqualls
/doc/user/packages/ @ngaskill
/doc/user/packages/infrastructure_registry/index.md @sselhorn
/doc/user/packages/terraform_module_registry/index.md @sselhorn
+/doc/user/packages/workflows/project_registry.md @ngaskill
+/doc/user/packages/workflows/working_with_monorepos.md @ngaskill
/doc/user/permissions.md @eread
/doc/user/profile/ @eread
/doc/user/profile/notifications.md @msedlakjakubowski
@@ -734,6 +759,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/project/integrations/prometheus.md @ngaskill
/doc/user/project/issue_board.md @msedlakjakubowski
/doc/user/project/issues/ @msedlakjakubowski
+/doc/user/project/issues/csv_import.md @ngaskill
/doc/user/project/labels.md @msedlakjakubowski
/doc/user/project/members/index.md @eread
/doc/user/project/members/share_project_with_groups.md @fneill
@@ -748,7 +774,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/project/merge_requests/test_coverage_visualization.md @eread
/doc/user/project/merge_requests/testing_and_reports_in_merge_requests.md @eread
/doc/user/project/milestones/ @msedlakjakubowski
-/doc/user/project/pages/ @rdickenson
+/doc/user/project/pages/ @aqualls
/doc/user/project/protected_branches.md @aqualls
/doc/user/project/protected_tags.md @aqualls
/doc/user/project/push_options.md @aqualls
@@ -768,6 +794,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/project/wiki/group.md @aqualls
/doc/user/project/wiki/index.md @aqualls
/doc/user/project/working_with_projects.md @fneill
+/doc/user/public_access.md @fneill
/doc/user/reserved_names.md @fneill
/doc/user/search/advanced_search.md @rdickenson
/doc/user/search/index.md @aqualls
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
index db0ee0cf00e..6babbca58c3 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
@@ -93,9 +93,9 @@ export default {
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 5dc9718d7a4..b896e2543ff 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -26,6 +26,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to do |format|
format.html do
+ @milestone_states = Milestone.states_count(@project)
# We need to show group milestones in the JSON response
# so that people can filter by and assign group milestones,
# but we don't need to show them on the project milestones page itself.
diff --git a/app/helpers/timeboxes_helper.rb b/app/helpers/timeboxes_helper.rb
index eca40572735..c81fbcbfd11 100644
--- a/app/helpers/timeboxes_helper.rb
+++ b/app/helpers/timeboxes_helper.rb
@@ -63,21 +63,6 @@ module TimeboxesHelper
issues.size
end
- # Returns count of milestones for different states
- # Uses explicit hash keys as the 'opened' state URL params differs from the db value
- # and we need to add the total
- # rubocop: disable CodeReuse/ActiveRecord
- def milestone_counts(milestones)
- counts = milestones.reorder(nil).group(:state).count
-
- {
- opened: counts['active'] || 0,
- closed: counts['closed'] || 0,
- all: counts.values.sum || 0
- }
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def milestone_progress_tooltip_text(milestone)
has_issues = milestone.total_issues_count > 0
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 872099f98fc..2bbca851dcc 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -12,16 +12,29 @@
path: '-/milestones/new', label: 'New milestone',
include_groups: true, type: :milestones
-.top-area
- = render 'shared/milestones_filter', counts: @milestone_states
- .nav-controls
- = render 'shared/milestones/search_form'
+- if @milestone_states.any? { |name, count| count > 0 }
+ .top-area
+ = render 'shared/milestones_filter', counts: @milestone_states
+ .nav-controls
+ = render 'shared/milestones/search_form'
-- if @milestones.blank?
- = render 'shared/empty_states/milestones'
+ - if @milestones.blank?
+ = render 'shared/empty_states/milestones_tab', active_tab: params[:state] do
+ - if current_user
+ .page-title-controls
+ = render 'shared/new_project_item_select',
+ path: '-/milestones/new', label: 'New milestone',
+ include_groups: true, type: :milestones
+ - else
+ .milestones
+ %ul.content-list
+ - @milestones.each do |milestone|
+ = render 'milestone', milestone: milestone
+ = paginate @milestones, theme: 'gitlab'
- else
- .milestones
- %ul.content-list
- - @milestones.each do |milestone|
- = render 'milestone', milestone: milestone
- = paginate @milestones, theme: 'gitlab'
+ = render 'shared/empty_states/milestones' do
+ - if current_user
+ .page-title-controls
+ = render 'shared/new_project_item_select',
+ path: '-/milestones/new', label: 'New milestone',
+ include_groups: true, type: :milestones
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 1c7427fef87..048606e2db4 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,23 +1,32 @@
- page_title _("Milestones")
- add_page_specific_style 'page_bundles/milestone'
-.top-area
- = render 'shared/milestones_filter', counts: @milestone_states
+- if @milestone_states.any? { |name, count| count > 0 }
+ .top-area
+ = render 'shared/milestones_filter', counts: @milestone_states
- .nav-controls
- = render 'shared/milestones/search_form'
- = render 'shared/milestones_sort_dropdown'
- - if can?(current_user, :admin_milestone, @group)
- = link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
+ .nav-controls
+ = render 'shared/milestones/search_form'
+ = render 'shared/milestones_sort_dropdown'
+ - if can?(current_user, :admin_milestone, @group)
+ = link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
-- if @milestones.blank?
- = render 'shared/empty_states/milestones'
+ - if @milestones.blank?
+ = render 'shared/empty_states/milestones_tab', learn_more_path: help_page_path('user/group/milestones') do
+ - if can?(current_user, :admin_milestone, @group)
+ .text-center
+ = link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
+ - else
+ .milestones
+ %ul.content-list
+ - @milestones.each do |milestone|
+ - if milestone.project_milestone?
+ = render 'projects/milestones/milestone', milestone: milestone
+ - else
+ = render 'milestone', milestone: milestone
+ = paginate @milestones, theme: "gitlab"
- else
- .milestones
- %ul.content-list
- - @milestones.each do |milestone|
- - if milestone.project_milestone?
- = render 'projects/milestones/milestone', milestone: milestone
- - else
- = render 'milestone', milestone: milestone
- = paginate @milestones, theme: "gitlab"
+ = render 'shared/empty_states/milestones', learn_more_path: help_page_path('user/group/milestones') do
+ - if can?(current_user, :admin_milestone, @group)
+ .text-center
+ = link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 059ef53c42b..154a92e6ec8 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,24 +1,36 @@
- page_title _('Milestones')
- add_page_specific_style 'page_bundles/milestone'
-.top-area
- = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
+- if @milestone_states.any? { |name, count| count > 0 }
+ .top-area
+ = render 'shared/milestones_filter', counts: @milestone_states
- .nav-controls
- = render 'shared/milestones/search_form'
- = render 'shared/milestones_sort_dropdown'
- - if can?(current_user, :admin_milestone, @project)
- = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
- = _('New milestone')
+ .nav-controls
+ = render 'shared/milestones/search_form'
+ = render 'shared/milestones_sort_dropdown'
+ - if can?(current_user, :admin_milestone, @project)
+ = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
+ = _('New milestone')
-- if @milestones.blank?
- = render 'shared/empty_states/milestones'
+ - if @milestones.blank?
+ = render 'shared/empty_states/milestones_tab' do
+ - if can?(current_user, :admin_milestone, @project)
+ .text-center
+ = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
+ = _('New milestone')
+
+ - else
+ .milestones
+ #js-delete-milestone-modal
+ #promote-milestone-modal
+
+ %ul.content-list
+ = render @milestones
+
+ = paginate @milestones, theme: 'gitlab'
- else
- .milestones
- #js-delete-milestone-modal
- #promote-milestone-modal
-
- %ul.content-list
- = render @milestones
-
- = paginate @milestones, theme: 'gitlab'
+ = render 'shared/empty_states/milestones' do
+ - if can?(current_user, :admin_milestone, @project)
+ .text-center
+ = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
+ = _('New milestone')
diff --git a/app/views/shared/empty_states/_milestones.html.haml b/app/views/shared/empty_states/_milestones.html.haml
index c22869fb7e6..90697795d62 100644
--- a/app/views/shared/empty_states/_milestones.html.haml
+++ b/app/views/shared/empty_states/_milestones.html.haml
@@ -1,7 +1,13 @@
+- learn_more_path = local_assigns.fetch(:learn_more_path, help_page_path('user/project/milestones/index'))
+- learn_more_link = link_to _('Learn more.'), learn_more_path
+
.row.empty-state
.col-12
.svg-content
= image_tag 'illustrations/milestone_burndown_chart.svg'
.col-12
.text-content
- %h4.text-center= _('No milestones to show')
+ %h4= s_('Milestones|Use milestones to track issues and merge requests over a fixed period of time')
+ %p.state-description
+ = s_('Milestones|Organize issues and merge requests into a cohesive group, and set an optional start and due dates. %{learn_more_link}').html_safe % { learn_more_link: learn_more_link }
+ = yield
diff --git a/app/views/shared/empty_states/_milestones_tab.html.haml b/app/views/shared/empty_states/_milestones_tab.html.haml
new file mode 100644
index 00000000000..f6760b0a3f4
--- /dev/null
+++ b/app/views/shared/empty_states/_milestones_tab.html.haml
@@ -0,0 +1,17 @@
+- learn_more_path = local_assigns.fetch(:learn_more_path, help_page_path('user/project/milestones/index'))
+- learn_more_link = link_to _('Learn more.'), learn_more_path
+- closed_tab_selected = params[:state] == 'closed'
+
+.row.empty-state
+ .col-12
+ .svg-content
+ = image_tag 'illustrations/milestone_burndown_chart.svg'
+ .col-12
+ .text-content
+ - if closed_tab_selected
+ %h4.text-center= s_('Milestones|There are no closed milestones')
+ - else
+ %h4.text-center= s_('Milestones|There are no open milestones')
+ %p.state-description
+ = s_('Milestones|Create a milestone to better track your issues and merge requests. %{learn_more_link}').html_safe % { learn_more_link: learn_more_link }
+ = yield
diff --git a/doc/administration/auditor_users.md b/doc/administration/auditor_users.md
index 7c89f34ce61..b3304fd1cbd 100644
--- a/doc/administration/auditor_users.md
+++ b/doc/administration/auditor_users.md
@@ -61,8 +61,7 @@ To create an Auditor user:
1. On the left sidebar, select **Overview > Users**.
1. Create a new user or edit an existing one, and in the **Access** section
select Auditor.
-1. Select **Create user** or **Save changes** if you created a new user or
- edited an existing one respectively.
+1. If you created a user, select **Create user**. For an existing user, select **Save changes**.
To revoke Auditor permissions from a user, make them a Regular user by
following the previous steps.
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index d7a06994b6c..8a3af72e46e 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -619,7 +619,7 @@ Note the following:
environment variable so that the Gitaly certificate is trusted. For example:
```shell
- sudo SSL_CERT_DIR=/etc/gitlab/trusted_certs /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes
+ sudo SSL_CERT_DIR=/etc/gitlab/trusted-certs /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes
```
- You can configure Praefect servers with both an unencrypted listening address
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index f7e4149cd7a..fb8615d1c96 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
description: 'Learn how to administer GitLab Pages.'
---
@@ -1393,15 +1393,15 @@ in all of your GitLab Pages instances.
Connections will time out when using a Network Load Balancer with client IP preservation enabled and [the request is looped back to the source server](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-troubleshooting.html#loopback-timeout).
This can happen to GitLab instances with multiple servers
-running both the core GitLab application and GitLab Pages. This can also happen when a single
+running both the core GitLab application and GitLab Pages. This can also happen when a single
container is running both the core GitLab application and GitLab Pages.
AWS [recommends using an IP target type](https://aws.amazon.com/premiumsupport/knowledge-center/target-connection-fails-load-balancer/)
to resolve this issue.
-Turning off [client IP preservation](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#client-ip-preservation)
-may resolve this issue when the core GitLab application and GitLab Pages run on the same host or
-container.
+Turning off [client IP preservation](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#client-ip-preservation)
+may resolve this issue when the core GitLab application and GitLab Pages run on the same host or
+container.
### 500 error with `securecookie: failed to generate random iv` and `Failed to save the session`
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index c4b1756d8a1..6c148387d7d 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/administration/sidekiq_health_check.md b/doc/administration/sidekiq_health_check.md
index 2ed736bac2c..ae02711f46f 100644
--- a/doc/administration/sidekiq_health_check.md
+++ b/doc/administration/sidekiq_health_check.md
@@ -21,7 +21,7 @@ The readiness probe checks whether the Sidekiq workers are ready to process jobs
GET /readiness
```
-Assuming you set up Sidekiq's address and port to be `localhost` and `8092` respectively,
+If you set Sidekiq's address as `localhost` and port as `8092`,
here's an example request:
```shell
@@ -44,7 +44,7 @@ Checks whether the Sidekiq cluster is running.
GET /liveness
```
-Assuming you set up Sidekiq's address and port to be `localhost` and `8092` respectively,
+If you set Sidekiq's address as `localhost` and port as `8092`,
here's an example request:
```shell
diff --git a/doc/api/group_access_tokens.md b/doc/api/group_access_tokens.md
index 45366885c5c..0d1878ebf39 100644
--- a/doc/api/group_access_tokens.md
+++ b/doc/api/group_access_tokens.md
@@ -20,7 +20,7 @@ GET groups/:id/access_tokens
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer or string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
+| `id` | integer or string | yes | ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
```shell
curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/groups//access_tokens"
@@ -44,6 +44,41 @@ curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/a
]
```
+## Get a group access token
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82714) in GitLab 14.10.
+
+Get a [group access token](../user/group/settings/group_access_tokens.md) by ID.
+
+```plaintext
+GET groups/:id/access_tokens/:token_id
+```
+
+| Attribute | Type | required | Description |
+|-----------|---------|----------|---------------------|
+| `id` | integer or string | yes | ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
+| `token_id` | integer or string | yes | ID of the group access token |
+
+```shell
+curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/groups//access_tokens/"
+```
+
+```json
+{
+ "user_id" : 141,
+ "scopes" : [
+ "api"
+ ],
+ "name" : "token",
+ "expires_at" : "2021-01-31",
+ "id" : 42,
+ "active" : true,
+ "created_at" : "2021-01-20T22:11:48.151Z",
+ "revoked" : false,
+ "access_level": 40
+}
+```
+
## Create a group access token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77236) in GitLab 14.7.
@@ -56,11 +91,11 @@ POST groups/:id/access_tokens
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer or string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
-| `name` | String | yes | The name of the group access token |
+| `id` | integer or string | yes | ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
+| `name` | String | yes | Name of the group access token |
| `scopes` | `Array[String]` | yes | [List of scopes](../user/group/settings/group_access_tokens.md#scopes-for-a-group-access-token) |
| `access_level` | Integer | no | A valid access level. Default value is 40 (Maintainer). Other allowed values are 10 (Guest), 20 (Reporter), and 30 (Developer). |
-| `expires_at` | Date | no | The token expires at midnight UTC on that date |
+| `expires_at` | Date | no | Token expires at midnight UTC on that date |
```shell
curl --request POST --header "PRIVATE-TOKEN: " \
@@ -99,8 +134,8 @@ DELETE groups/:id/access_tokens/:token_id
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer or string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
-| `token_id` | integer or string | yes | The ID of the group access token |
+| `id` | integer or string | yes | ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
+| `token_id` | integer or string | yes | ID of the group access token |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/groups//access_tokens/"
diff --git a/doc/api/pages.md b/doc/api/pages.md
index 7316d225dbc..57982188858 100644
--- a/doc/api/pages.md
+++ b/doc/api/pages.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/api/pages_domains.md b/doc/api/pages_domains.md
index c1f81ffa361..610fd97c810 100644
--- a/doc/api/pages_domains.md
+++ b/doc/api/pages_domains.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/api/project_access_tokens.md b/doc/api/project_access_tokens.md
index f6eced4f08a..cee0dd523c0 100644
--- a/doc/api/project_access_tokens.md
+++ b/doc/api/project_access_tokens.md
@@ -20,7 +20,7 @@ GET projects/:id/access_tokens
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer or string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
+| `id` | integer or string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
```shell
curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/projects//access_tokens"
@@ -44,6 +44,42 @@ curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/a
]
```
+## Get a project access token
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82714) in GitLab 14.10.
+
+Get a [project access token](../user/project/settings/project_access_tokens.md) by ID.
+
+```plaintext
+GET projects/:id/access_tokens/:token_id
+```
+
+| Attribute | Type | required | Description |
+|-----------|---------|----------|---------------------|
+| `id` | integer or string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
+| `token_id` | integer or string | yes | ID of the project access token |
+
+```shell
+curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/projects//access_tokens/"
+```
+
+```json
+{
+ "user_id" : 141,
+ "scopes" : [
+ "api"
+ ],
+ "name" : "token",
+ "expires_at" : "2021-01-31",
+ "id" : 42,
+ "active" : true,
+ "created_at" : "2021-01-20T22:11:48.151Z",
+ "revoked" : false,
+ "access_level": 40,
+ "last_used_at": "2022-03-15T11:05:42.437Z"
+}
+```
+
## Create a project access token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55408) in GitLab 13.10.
@@ -56,11 +92,11 @@ POST projects/:id/access_tokens
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer or string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
-| `name` | String | yes | The name of the project access token |
+| `id` | integer or string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
+| `name` | String | yes | Name of the project access token |
| `scopes` | `Array[String]` | yes | [List of scopes](../user/project/settings/project_access_tokens.md#scopes-for-a-project-access-token) |
| `access_level` | Integer | no | A valid access level. Default value is 40 (Maintainer). Other allowed values are 10 (Guest), 20 (Reporter), and 30 (Developer). |
-| `expires_at` | Date | no | The token expires at midnight UTC on that date |
+| `expires_at` | Date | no | Token expires at midnight UTC on that date |
```shell
curl --request POST --header "PRIVATE-TOKEN: " \
@@ -99,8 +135,8 @@ DELETE projects/:id/access_tokens/:token_id
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
-| `id` | integer or string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
-| `token_id` | integer or string | yes | The ID of the project access token |
+| `id` | integer or string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
+| `token_id` | integer or string | yes | ID of the project access token |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/projects//access_tokens/"
diff --git a/doc/ci/environments/deployment_approvals.md b/doc/ci/environments/deployment_approvals.md
index 45af78aa036..0f7bd2c14a3 100644
--- a/doc/ci/environments/deployment_approvals.md
+++ b/doc/ci/environments/deployment_approvals.md
@@ -15,7 +15,7 @@ This feature is in a [Beta](../../policy/alpha-beta-support.md#beta-features) st
It may be useful to require additional approvals before deploying to certain protected environments (for example, production). This pre-deployment approval requirement is useful to accommodate testing, security, or compliance processes that must happen before each deployment.
-When a protected environment requires one or more approvals, all deployments to that environment become blocked and wait for the required approvals before running.
+When a protected environment requires one or more approvals, all deployments to that environment become blocked and wait for the required approvals from the `Allowed to Deploy` list before running.
NOTE:
See the [epic](https://gitlab.com/groups/gitlab-org/-/epics/6832) for planned features.
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index c38c6586c3a..9954a3cc3e4 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -729,6 +729,19 @@ Do not use **Reporter permissions**. A user who is assigned the Reporter role ha
Use title case for **Repository Mirroring**.
+## respectively
+
+Avoid **respectively** and be more precise instead.
+
+Use:
+
+- To create a user, select **Create user**. For an existing user, select **Save changes**.
+
+Instead of:
+
+- Select **Create user** or **Save changes** if you created a new user or
+ edited an existing one respectively.
+
## roles
Do not use **roles** and [**permissions**](#permissions) interchangeably. Each user is assigned a role. Each role includes a set of permissions.
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 95bf835147d..7aae09258b5 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -730,6 +730,11 @@ Refer to the documentation for your SAML Identity Provider for information on ho
The [Generated passwords for users created through integrated authentication](../security/passwords_for_integrated_authentication_methods.md) guide provides an overview of how GitLab generates and sets passwords for users created via SAML.
+## Link SAML identity for an existing user
+
+A user can manually link their SAML identity to an existing GitLab account by following the steps in
+[Enable OmniAuth for an existing user](omniauth.md#enable-omniauth-for-an-existing-user).
+
## Configuring Group SAML on a self-managed GitLab instance **(PREMIUM SELF)**
For information on the GitLab.com implementation, please see the [SAML SSO for GitLab.com groups page](../user/group/saml_sso).
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
index 3491346f7d9..5433e02b210 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
@@ -1,7 +1,7 @@
---
type: concepts
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
index 4d8919090a2..d970c0f9ef4 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
@@ -1,7 +1,7 @@
---
disqus_identifier: 'https://docs.gitlab.com/ee/user/project/pages/getting_started_part_three.html'
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
index f09aea3b02a..dd5e8c85757 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
@@ -1,8 +1,8 @@
---
type: reference
description: "Automatic Let's Encrypt SSL certificates for GitLab Pages."
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
index 21f2dd51f70..0c848a24dec 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
@@ -1,7 +1,7 @@
---
type: concepts
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/getting_started/pages_ci_cd_template.md b/doc/user/project/pages/getting_started/pages_ci_cd_template.md
index 25382778b1d..510f9332e7b 100644
--- a/doc/user/project/pages/getting_started/pages_ci_cd_template.md
+++ b/doc/user/project/pages/getting_started/pages_ci_cd_template.md
@@ -1,7 +1,7 @@
---
type: reference, howto
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/getting_started/pages_forked_sample_project.md b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
index b43af2f0efe..71ed3134c1e 100644
--- a/doc/user/project/pages/getting_started/pages_forked_sample_project.md
+++ b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
@@ -1,7 +1,7 @@
---
type: reference, howto
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/getting_started/pages_from_scratch.md b/doc/user/project/pages/getting_started/pages_from_scratch.md
index e0c10e27ec3..f08afc924f3 100644
--- a/doc/user/project/pages/getting_started/pages_from_scratch.md
+++ b/doc/user/project/pages/getting_started/pages_from_scratch.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/getting_started/pages_new_project_template.md b/doc/user/project/pages/getting_started/pages_new_project_template.md
index cee10675a62..b32d71a4887 100644
--- a/doc/user/project/pages/getting_started/pages_new_project_template.md
+++ b/doc/user/project/pages/getting_started/pages_new_project_template.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 5773dd1f2c0..54b843945cd 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 82b1a824f7a..af49522efe2 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -1,7 +1,7 @@
---
description: 'Learn how to use GitLab Pages to deploy a static website at no additional cost.'
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index e4bc58854ef..f274338c2fd 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/pages_access_control.md b/doc/user/project/pages/pages_access_control.md
index 002b234f561..9b747e04973 100644
--- a/doc/user/project/pages/pages_access_control.md
+++ b/doc/user/project/pages/pages_access_control.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/pages/redirects.md b/doc/user/project/pages/redirects.md
index cdae1d2f837..1db404f4888 100644
--- a/doc/user/project/pages/redirects.md
+++ b/doc/user/project/pages/redirects.md
@@ -1,6 +1,6 @@
---
-stage: Release
-group: Release
+stage: Create
+group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/lib/api/resource_access_tokens.rb b/lib/api/resource_access_tokens.rb
index e52f8fd9111..2ba109b7092 100644
--- a/lib/api/resource_access_tokens.rb
+++ b/lib/api/resource_access_tokens.rb
@@ -27,6 +27,28 @@ module API
present paginate(tokens), with: Entities::ResourceAccessToken, resource: resource
end
+ desc 'Get an access token for the specified resource by ID' do
+ detail 'This feature was introduced in GitLab 14.10.'
+ end
+ params do
+ requires :id, type: String, desc: "The #{source_type} ID"
+ requires :token_id, type: String, desc: "The ID of the token"
+ end
+ get ":id/access_tokens/:token_id" do
+ resource = find_source(source_type, params[:id])
+
+ next unauthorized! unless current_user.can?(:read_resource_access_tokens, resource)
+
+ token = find_token(resource, params[:token_id])
+
+ if token.nil?
+ next not_found!("Could not find #{source_type} access token with token_id: #{params[:token_id]}")
+ end
+
+ resource.members.load
+ present token, with: Entities::ResourceAccessToken, resource: resource
+ end
+
desc 'Revoke a resource access token' do
detail 'This feature was introduced in GitLab 13.9.'
end
diff --git a/lib/gitlab/database/migrations/runner.rb b/lib/gitlab/database/migrations/runner.rb
index 02645a0d452..b7a1259514d 100644
--- a/lib/gitlab/database/migrations/runner.rb
+++ b/lib/gitlab/database/migrations/runner.rb
@@ -17,6 +17,10 @@ module Gitlab
Runner.new(direction: :down, migrations: migrations_for_down, result_dir: BASE_RESULT_DIR.join('down'))
end
+ def background_migrations
+ TestBackgroundRunner.new(result_dir: BASE_RESULT_DIR.join('background_migrations'))
+ end
+
def migration_context
@migration_context ||= ApplicationRecord.connection.migration_context
end
diff --git a/lib/gitlab/database/migrations/test_background_runner.rb b/lib/gitlab/database/migrations/test_background_runner.rb
index 821d68c06c9..74e54d62e05 100644
--- a/lib/gitlab/database/migrations/test_background_runner.rb
+++ b/lib/gitlab/database/migrations/test_background_runner.rb
@@ -4,12 +4,10 @@ module Gitlab
module Database
module Migrations
class TestBackgroundRunner
- # TODO - build a rake task to call this method, and support it in the gitlab-com-database-testing project.
- # Until then, we will inject a migration with a very high timestamp during database testing
- # that calls this class to run jobs
- # See https://gitlab.com/gitlab-org/database-team/gitlab-com-database-testing/-/issues/41 for details
+ attr_reader :result_dir
- def initialize
+ def initialize(result_dir:)
+ @result_dir = result_dir
@job_coordinator = Gitlab::BackgroundMigration.coordinator_for_database(Gitlab::Database::MAIN_DATABASE_NAME)
end
@@ -24,18 +22,30 @@ module Gitlab
# without .to_f, we do integer division
# For example, 3.minutes / 2 == 1.minute whereas 3.minutes / 2.to_f == (1.minute + 30.seconds)
duration_per_migration_type = for_duration / jobs_to_run.count.to_f
- jobs_to_run.each do |_migration_name, jobs|
+ jobs_to_run.each do |migration_name, jobs|
run_until = duration_per_migration_type.from_now
- jobs.shuffle.each do |j|
- break if run_until <= Time.current
- run_job(j)
- end
+ run_jobs_for_migration(migration_name: migration_name, jobs: jobs, run_until: run_until)
end
end
private
+ def run_jobs_for_migration(migration_name:, jobs:, run_until:)
+ per_background_migration_result_dir = File.join(@result_dir, migration_name)
+
+ instrumentation = Instrumentation.new(result_dir: per_background_migration_result_dir)
+ batch_names = (1..).each.lazy.map { |i| "batch_#{i}"}
+
+ jobs.shuffle.each do |j|
+ break if run_until <= Time.current
+
+ instrumentation.observe(version: nil, name: batch_names.next, connection: ActiveRecord::Migration.connection) do
+ run_job(j)
+ end
+ end
+ end
+
def run_job(job)
Gitlab::BackgroundMigration.perform(job.args[0], job.args[1])
end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index a579172f8b8..5c6be3d9859 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -307,6 +307,13 @@ namespace :gitlab do
task down: :environment do
Gitlab::Database::Migrations::Runner.down.run
end
+
+ desc 'Sample traditional background migrations with instrumentation'
+ task :sample_background_migrations, [:duration_s] => [:environment] do |_t, args|
+ duration = args[:duration_s]&.to_i&.seconds || 30.minutes # Default of 30 minutes
+
+ Gitlab::Database::Migrations::Runner.background_migrations.run_jobs(for_duration: duration)
+ end
end
desc 'Run all pending batched migrations'
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2327099e79d..ae968dbc06a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -24091,6 +24091,9 @@ msgstr ""
msgid "Milestones|Completed Issues (closed)"
msgstr ""
+msgid "Milestones|Create a milestone to better track your issues and merge requests. %{learn_more_link}"
+msgstr ""
+
msgid "Milestones|Delete milestone"
msgstr ""
@@ -24109,6 +24112,9 @@ msgstr ""
msgid "Milestones|Ongoing Issues (open and assigned)"
msgstr ""
+msgid "Milestones|Organize issues and merge requests into a cohesive group, and set an optional start and due dates. %{learn_more_link}"
+msgstr ""
+
msgid "Milestones|Project Milestone"
msgstr ""
@@ -24127,12 +24133,21 @@ msgstr ""
msgid "Milestones|Reopen Milestone"
msgstr ""
+msgid "Milestones|There are no closed milestones"
+msgstr ""
+
+msgid "Milestones|There are no open milestones"
+msgstr ""
+
msgid "Milestones|This action cannot be reversed."
msgstr ""
msgid "Milestones|Unstarted Issues (open and unassigned)"
msgstr ""
+msgid "Milestones|Use milestones to track issues and merge requests over a fixed period of time"
+msgstr ""
+
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
@@ -25208,9 +25223,6 @@ msgstr ""
msgid "No milestone"
msgstr ""
-msgid "No milestones to show"
-msgstr ""
-
msgid "No namespace"
msgstr ""
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index 4edf27e8fa4..42eaa8358a1 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -66,7 +66,7 @@ RSpec.describe 'Group milestones' do
context 'when no milestones' do
it 'renders no milestones text' do
visit group_milestones_path(group)
- expect(page).to have_content('No milestones to show')
+ expect(page).to have_content('Use milestones to track issues and merge requests')
end
end
diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb
index ede9faed876..40626407642 100644
--- a/spec/features/milestones/user_deletes_milestone_spec.rb
+++ b/spec/features/milestones/user_deletes_milestone_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe "User deletes milestone", :js do
click_button("Delete")
click_button("Delete milestone")
- expect(page).to have_content("No milestones to show")
+ expect(page).to have_content("Use milestones to track issues and merge requests over a fixed period of time")
visit(activity_project_path(project))
diff --git a/spec/fixtures/api/schemas/public_api/v4/resource_access_token.json b/spec/fixtures/api/schemas/public_api/v4/resource_access_token.json
new file mode 100644
index 00000000000..3636c970e83
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/resource_access_token.json
@@ -0,0 +1,31 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "user_id",
+ "active",
+ "created_at",
+ "expires_at",
+ "revoked",
+ "access_level",
+ "scopes",
+ "last_used_at"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "user_id": { "type": "integer" },
+ "active": { "type": "boolean" },
+ "created_at": { "type": "string", "format": "date-time" },
+ "expires_at": { "type": ["string", "null"], "format": "date" },
+ "revoked": { "type": "boolean" },
+ "access_level": { "type": "integer" },
+ "scopes": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "last_used_at": { "type": ["string", "null"], "format": "date-time" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/resource_access_tokens.json b/spec/fixtures/api/schemas/public_api/v4/resource_access_tokens.json
new file mode 100644
index 00000000000..1bf013b8bca
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/resource_access_tokens.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "resource_access_token.json" }
+}
diff --git a/spec/helpers/timeboxes_helper_spec.rb b/spec/helpers/timeboxes_helper_spec.rb
index 1b9442c0a09..e31f2df7372 100644
--- a/spec/helpers/timeboxes_helper_spec.rb
+++ b/spec/helpers/timeboxes_helper_spec.rb
@@ -24,34 +24,6 @@ RSpec.describe TimeboxesHelper do
end
end
- describe '#milestone_counts' do
- let(:project) { create(:project) }
- let(:counts) { helper.milestone_counts(project.milestones) }
-
- context 'when there are milestones' do
- it 'returns the correct counts' do
- create_list(:active_milestone, 2, project: project)
- create(:closed_milestone, project: project)
-
- expect(counts).to eq(opened: 2, closed: 1, all: 3)
- end
- end
-
- context 'when there are only milestones of one type' do
- it 'returns the correct counts' do
- create_list(:active_milestone, 2, project: project)
-
- expect(counts).to eq(opened: 2, closed: 0, all: 2)
- end
- end
-
- context 'when there are no milestones' do
- it 'returns the correct counts' do
- expect(counts).to eq(opened: 0, closed: 0, all: 0)
- end
- end
- end
-
describe "#group_milestone_route" do
let(:group) { build_stubbed(:group) }
let(:subgroup) { build_stubbed(:group, parent: group, name: "Test Subgrp") }
diff --git a/spec/lib/gitlab/database/migrations/runner_spec.rb b/spec/lib/gitlab/database/migrations/runner_spec.rb
index 84482e6b450..8b1ccf05eb1 100644
--- a/spec/lib/gitlab/database/migrations/runner_spec.rb
+++ b/spec/lib/gitlab/database/migrations/runner_spec.rb
@@ -124,4 +124,16 @@ RSpec.describe Gitlab::Database::Migrations::Runner do
expect(metadata).to match('version' => described_class::SCHEMA_VERSION)
end
end
+
+ describe '.background_migrations' do
+ it 'is a TestBackgroundRunner' do
+ expect(described_class.background_migrations).to be_a(Gitlab::Database::Migrations::TestBackgroundRunner)
+ end
+
+ it 'is configured with a result dir of /background_migrations' do
+ runner = described_class.background_migrations
+
+ expect(runner.result_dir).to eq(described_class::BASE_RESULT_DIR.join( 'background_migrations'))
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/migrations/test_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_background_runner_spec.rb
index c6fe88a7c2d..9407efad91f 100644
--- a/spec/lib/gitlab/database/migrations/test_background_runner_spec.rb
+++ b/spec/lib/gitlab/database/migrations/test_background_runner_spec.rb
@@ -11,11 +11,17 @@ RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do
Sidekiq::Testing.disable! { ex.run }
end
+ let(:result_dir) { Dir.mktmpdir }
+
+ after do
+ FileUtils.rm_rf(result_dir)
+ end
+
context 'without jobs to run' do
it 'returns immediately' do
- runner = described_class.new
+ runner = described_class.new(result_dir: result_dir)
expect(runner).not_to receive(:run_job)
- described_class.new.run_jobs(for_duration: 1.second)
+ described_class.new(result_dir: result_dir).run_jobs(for_duration: 1.second)
end
end
@@ -30,7 +36,7 @@ RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do
context 'finding pending background jobs' do
it 'finds all the migrations' do
- expect(described_class.new.traditional_background_migrations.to_a.size).to eq(5)
+ expect(described_class.new(result_dir: result_dir).traditional_background_migrations.to_a.size).to eq(5)
end
end
@@ -53,12 +59,28 @@ RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do
end
end
+ def expect_recorded_migration_runs(migrations_to_runs)
+ migrations_to_runs.each do |migration, runs|
+ path = File.join(result_dir, migration.name.demodulize)
+ num_subdirs = Pathname(path).children.count(&:directory?)
+ expect(num_subdirs).to eq(runs)
+ end
+ end
+
+ def expect_migration_runs(migrations_to_run_counts)
+ expect_migration_call_counts(migrations_to_run_counts)
+
+ yield
+
+ expect_recorded_migration_runs(migrations_to_run_counts)
+ end
+
it 'runs the migration class correctly' do
calls = []
define_background_migration(migration_name) do |i|
calls << i
end
- described_class.new.run_jobs(for_duration: 1.second) # Any time would work here as we do not advance time
+ described_class.new(result_dir: result_dir).run_jobs(for_duration: 1.second) # Any time would work here as we do not advance time
expect(calls).to contain_exactly(1, 2, 3, 4, 5)
end
@@ -67,9 +89,9 @@ RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do
travel(1.minute)
end
- expect_migration_call_counts(migration => 3)
-
- described_class.new.run_jobs(for_duration: 3.minutes)
+ expect_migration_runs(migration => 3) do
+ described_class.new(result_dir: result_dir).run_jobs(for_duration: 3.minutes)
+ end
end
context 'with multiple migrations to run' do
@@ -90,12 +112,12 @@ RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do
travel(2.minutes)
end
- expect_migration_call_counts(
+ expect_migration_runs(
migration => 2, # 1 minute jobs for 90 seconds, can finish the first and start the second
other_migration => 1 # 2 minute jobs for 90 seconds, past deadline after a single job
- )
-
- described_class.new.run_jobs(for_duration: 3.minutes)
+ ) do
+ described_class.new(result_dir: result_dir).run_jobs(for_duration: 3.minutes)
+ end
end
it 'does not give leftover time to extra migrations' do
@@ -107,12 +129,13 @@ RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do
other_migration = define_background_migration(other_migration_name) do
travel(1.minute)
end
- expect_migration_call_counts(
+
+ expect_migration_runs(
migration => 5,
other_migration => 2
- )
-
- described_class.new.run_jobs(for_duration: 3.minutes)
+ ) do
+ described_class.new(result_dir: result_dir).run_jobs(for_duration: 3.minutes)
+ end
end
end
end
diff --git a/spec/requests/api/resource_access_tokens_spec.rb b/spec/requests/api/resource_access_tokens_spec.rb
index 7e3e682767f..369a8c1b0ab 100644
--- a/spec/requests/api/resource_access_tokens_spec.rb
+++ b/spec/requests/api/resource_access_tokens_spec.rb
@@ -29,6 +29,8 @@ RSpec.describe API::ResourceAccessTokens do
token_ids = json_response.map { |token| token['id'] }
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response).to match_response_schema('public_api/v4/resource_access_tokens')
expect(token_ids).to match_array(access_tokens.pluck(:id))
end
@@ -131,6 +133,103 @@ RSpec.describe API::ResourceAccessTokens do
end
end
+ context "GET #{source_type}s/:id/access_tokens/:token_id" do
+ subject(:get_token) { get api("/#{source_type}s/#{resource_id}/access_tokens/#{token_id}", user) }
+
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+ let_it_be(:token) { create(:personal_access_token, user: project_bot) }
+ let_it_be(:resource_id) { resource.id }
+ let_it_be(:token_id) { token.id }
+
+ before do
+ if source_type == 'project'
+ resource.add_maintainer(project_bot)
+ else
+ resource.add_owner(project_bot)
+ end
+ end
+
+ context "when the user has valid permissions" do
+ it "gets the #{source_type} access token from the #{source_type}" do
+ get_token
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/resource_access_token')
+
+ expect(json_response["name"]).to eq(token.name)
+ expect(json_response["scopes"]).to eq(token.scopes)
+
+ if source_type == 'project'
+ expect(json_response["access_level"]).to eq(resource.team.max_member_access(token.user.id))
+ else
+ expect(json_response["access_level"]).to eq(resource.max_member_access_for_user(token.user))
+ end
+
+ expect(json_response["expires_at"]).to eq(token.expires_at.to_date.iso8601)
+ end
+
+ context "when using #{source_type} access token to GET other #{source_type} access token" do
+ let_it_be(:other_project_bot) { create(:user, :project_bot) }
+ let_it_be(:other_token) { create(:personal_access_token, user: other_project_bot) }
+ let_it_be(:token_id) { other_token.id }
+
+ before do
+ resource.add_maintainer(other_project_bot)
+ end
+
+ it "gets the #{source_type} access token from the #{source_type}" do
+ get_token
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/resource_access_token')
+
+ expect(json_response["name"]).to eq(other_token.name)
+ expect(json_response["scopes"]).to eq(other_token.scopes)
+
+ if source_type == 'project'
+ expect(json_response["access_level"]).to eq(resource.team.max_member_access(other_token.user.id))
+ else
+ expect(json_response["access_level"]).to eq(resource.max_member_access_for_user(other_token.user))
+ end
+
+ expect(json_response["expires_at"]).to eq(other_token.expires_at.to_date.iso8601)
+ end
+ end
+
+ context "when attempting to get a non-existent #{source_type} access token" do
+ let_it_be(:token_id) { non_existing_record_id }
+
+ it "does not get the token, and returns 404" do
+ get_token
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to include("Could not find #{source_type} access token with token_id: #{token_id}")
+ end
+ end
+
+ context "when attempting to get a token that does not belong to the specified #{source_type}" do
+ let_it_be(:resource_id) { other_resource.id }
+
+ it "does not get the token, and returns 404" do
+ get_token
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(response.body).to include("Could not find #{source_type} access token with token_id: #{token_id}")
+ end
+ end
+ end
+
+ context "when the user does not have valid permissions" do
+ let_it_be(:user) { user_non_priviledged }
+
+ it "returns 401" do
+ get_token
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
context "DELETE #{source_type}s/:id/access_tokens/:token_id", :sidekiq_inline do
subject(:delete_token) { delete api("/#{source_type}s/#{resource_id}/access_tokens/#{token_id}", user) }
diff --git a/spec/support/fips.rb b/spec/support/fips.rb
new file mode 100644
index 00000000000..52ef555dd87
--- /dev/null
+++ b/spec/support/fips.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+# rubocop: disable RSpec/EnvAssignment
+
+RSpec.configure do |config|
+ config.around(:each, :fips_mode) do |example|
+ prior_value = ENV["FIPS_MODE"]
+ ENV["FIPS_MODE"] = "true"
+
+ example.run
+
+ ENV["FIPS_MODE"] = prior_value
+ end
+end
+
+# rubocop: enable RSpec/EnvAssignment
diff --git a/spec/support/shared_examples/views/milestone_shared_examples.rb b/spec/support/shared_examples/views/milestone_shared_examples.rb
new file mode 100644
index 00000000000..b6f4d0db0e9
--- /dev/null
+++ b/spec/support/shared_examples/views/milestone_shared_examples.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'milestone empty states' do
+ include Devise::Test::ControllerHelpers
+
+ let_it_be(:user) { build(:user) }
+ let(:empty_state) { 'Use milestones to track issues and merge requests over a fixed period of time' }
+
+ before do
+ assign(:projects, [])
+ allow(view).to receive(:current_user).and_return(user)
+ end
+
+ context 'with no milestones' do
+ before do
+ assign(:milestones, [])
+ assign(:milestone_states, { opened: 0, closed: 0, all: 0 })
+ render
+ end
+
+ it 'shows empty state' do
+ expect(rendered).to have_content(empty_state)
+ end
+
+ it 'does not show tabs or searchbar' do
+ expect(rendered).not_to have_link('Open')
+ expect(rendered).not_to have_link('Closed')
+ expect(rendered).not_to have_link('All')
+ end
+ end
+
+ context 'with no open milestones' do
+ before do
+ allow(view).to receive(:milestone_path).and_return("/milestones/1")
+ assign(:milestones, [])
+ assign(:milestone_states, { opened: 0, closed: 1, all: 1 })
+ end
+
+ it 'shows tabs and searchbar', :aggregate_failures do
+ render
+
+ expect(rendered).not_to have_content(empty_state)
+ expect(rendered).to have_link('Open')
+ expect(rendered).to have_link('Closed')
+ expect(rendered).to have_link('All')
+ end
+
+ it 'shows empty state' do
+ render
+
+ expect(rendered).to have_content('There are no open milestones')
+ end
+ end
+
+ context 'with no closed milestones' do
+ before do
+ allow(view).to receive(:milestone_path).and_return("/milestones/1")
+ allow(view).to receive(:params).and_return(state: 'closed')
+ assign(:milestones, [])
+ assign(:milestone_states, { opened: 1, closed: 0, all: 1 })
+ end
+
+ it 'shows tabs and searchbar', :aggregate_failures do
+ render
+
+ expect(rendered).not_to have_content(empty_state)
+ expect(rendered).to have_link('Open')
+ expect(rendered).to have_link('Closed')
+ expect(rendered).to have_link('All')
+ end
+
+ it 'shows empty state on closed milestones' do
+ render
+
+ expect(rendered).to have_content('There are no closed milestones')
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index 3c7dbe35b7a..0134e2c28a3 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -538,6 +538,20 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
subject
end
end
+
+ describe '#sample_background_migrations' do
+ it 'delegates to the migration runner with a default sample duration' do
+ expect(::Gitlab::Database::Migrations::Runner).to receive_message_chain(:background_migrations, :run_jobs).with(for_duration: 30.minutes)
+
+ run_rake_task('gitlab:db:migration_testing:sample_background_migrations')
+ end
+
+ it 'delegates to the migration runner with a configured sample duration' do
+ expect(::Gitlab::Database::Migrations::Runner).to receive_message_chain(:background_migrations, :run_jobs).with(for_duration: 100.seconds)
+
+ run_rake_task('gitlab:db:migration_testing:sample_background_migrations', '[100]')
+ end
+ end
end
describe '#execute_batched_migrations' do
diff --git a/spec/views/dashboard/milestones/index.html.haml_spec.rb b/spec/views/dashboard/milestones/index.html.haml_spec.rb
new file mode 100644
index 00000000000..738d31bfa3f
--- /dev/null
+++ b/spec/views/dashboard/milestones/index.html.haml_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'dashboard/milestones/index.html.haml' do
+ it_behaves_like 'milestone empty states'
+end
diff --git a/spec/views/groups/milestones/index.html.haml_spec.rb b/spec/views/groups/milestones/index.html.haml_spec.rb
new file mode 100644
index 00000000000..40f175ebdc3
--- /dev/null
+++ b/spec/views/groups/milestones/index.html.haml_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'groups/milestones/index.html.haml' do
+ it_behaves_like 'milestone empty states'
+end
diff --git a/spec/views/projects/milestones/index.html.haml_spec.rb b/spec/views/projects/milestones/index.html.haml_spec.rb
new file mode 100644
index 00000000000..f8117a71310
--- /dev/null
+++ b/spec/views/projects/milestones/index.html.haml_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'projects/milestones/index.html.haml' do
+ it_behaves_like 'milestone empty states'
+end