From 5316a9bca92df51b192db5c444784f951fbc83c9 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 23 Mar 2022 03:07:30 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/CODEOWNERS | 71 ++++++++----- .../source_viewer/components/chunk.vue | 2 +- .../projects/milestones_controller.rb | 1 + app/helpers/timeboxes_helper.rb | 15 --- .../dashboard/milestones/index.html.haml | 35 ++++--- app/views/groups/milestones/index.html.haml | 43 ++++---- app/views/projects/milestones/index.html.haml | 48 +++++---- .../shared/empty_states/_milestones.html.haml | 8 +- .../empty_states/_milestones_tab.html.haml | 17 ++++ doc/administration/auditor_users.md | 3 +- doc/administration/gitaly/praefect.md | 2 +- doc/administration/pages/index.md | 12 +-- doc/administration/pages/source.md | 4 +- doc/administration/sidekiq_health_check.md | 4 +- doc/api/group_access_tokens.md | 47 +++++++-- doc/api/pages.md | 4 +- doc/api/pages_domains.md | 4 +- doc/api/project_access_tokens.md | 48 +++++++-- doc/ci/environments/deployment_approvals.md | 2 +- .../documentation/styleguide/word_list.md | 13 +++ doc/integration/saml.md | 5 + .../dns_concepts.md | 4 +- .../index.md | 4 +- .../lets_encrypt_integration.md | 4 +- .../ssl_tls_concepts.md | 4 +- .../getting_started/pages_ci_cd_template.md | 4 +- .../pages_forked_sample_project.md | 4 +- .../getting_started/pages_from_scratch.md | 4 +- .../pages_new_project_template.md | 4 +- .../project/pages/getting_started_part_one.md | 4 +- doc/user/project/pages/index.md | 4 +- doc/user/project/pages/introduction.md | 4 +- .../project/pages/pages_access_control.md | 4 +- doc/user/project/pages/redirects.md | 4 +- lib/api/resource_access_tokens.rb | 22 +++++ lib/gitlab/database/migrations/runner.rb | 4 + .../migrations/test_background_runner.rb | 30 ++++-- lib/tasks/gitlab/db.rake | 7 ++ locale/gitlab.pot | 18 +++- spec/features/groups/milestone_spec.rb | 2 +- .../milestones/user_deletes_milestone_spec.rb | 2 +- .../public_api/v4/resource_access_token.json | 31 ++++++ .../public_api/v4/resource_access_tokens.json | 4 + spec/helpers/timeboxes_helper_spec.rb | 28 ------ .../gitlab/database/migrations/runner_spec.rb | 12 +++ .../migrations/test_background_runner_spec.rb | 53 +++++++--- .../api/resource_access_tokens_spec.rb | 99 +++++++++++++++++++ spec/support/fips.rb | 15 +++ .../views/milestone_shared_examples.rb | 78 +++++++++++++++ spec/tasks/gitlab/db_rake_spec.rb | 14 +++ .../milestones/index.html.haml_spec.rb | 7 ++ .../groups/milestones/index.html.haml_spec.rb | 7 ++ .../milestones/index.html.haml_spec.rb | 7 ++ 53 files changed, 681 insertions(+), 199 deletions(-) create mode 100644 app/views/shared/empty_states/_milestones_tab.html.haml create mode 100644 spec/fixtures/api/schemas/public_api/v4/resource_access_token.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/resource_access_tokens.json create mode 100644 spec/support/fips.rb create mode 100644 spec/support/shared_examples/views/milestone_shared_examples.rb create mode 100644 spec/views/dashboard/milestones/index.html.haml_spec.rb create mode 100644 spec/views/groups/milestones/index.html.haml_spec.rb create mode 100644 spec/views/projects/milestones/index.html.haml_spec.rb 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