diff options
47 files changed, 778 insertions, 415 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 04ed04b1f9d..7810021a883 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -493,7 +493,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/ci/unit_test_reports.md @marcel.amirault /doc/ci/variables/ @marcel.amirault /doc/ci/yaml/ @marcel.amirault -/doc/development/adding_database_indexes.md @aqualls /doc/development/application_limits.md @axil /doc/development/approval_rules.md @aqualls /doc/development/audit_event_guide/index.md @eread @@ -512,12 +511,8 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/development/code_intelligence/index.md @aqualls /doc/development/contributing/ @sselhorn /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/ @aqualls -/doc/development/db_dump.md @aqualls /doc/development/developing_with_solargraph.md @aqualls /doc/development/diffs.md @aqualls /doc/development/distributed_tracing.md @msedlakjakubowski @@ -538,7 +533,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/development/feature_flags/controls.md @sselhorn /doc/development/feature_flags/index.md @sselhorn /doc/development/filtering_by_label.md @msedlakjakubowski -/doc/development/foreign_keys.md @aqualls /doc/development/geo.md @axil /doc/development/geo/framework.md @axil /doc/development/git_object_deduplication.md @eread @@ -552,7 +546,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/development/image_scaling.md @sselhorn /doc/development/import_export.md @eread /doc/development/index.md @sselhorn -/doc/development/insert_into_tables_in_batches.md @aqualls /doc/development/integrations/ @kpaizee /doc/development/integrations/codesandbox.md @sselhorn /doc/development/integrations/secure_partner_integration.md @rdickenson @@ -561,7 +554,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/development/internal_users.md @sselhorn /doc/development/issuable-like-models.md @msedlakjakubowski /doc/development/issue_types.md @msedlakjakubowski -/doc/development/iterating_tables_in_batches.md @aqualls /doc/development/kubernetes.md @sselhorn /doc/development/lfs.md @aqualls /doc/development/ee_features.md @fneill @@ -570,30 +562,22 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/development/new_fe_guide/modules/widget_extensions.md @aqualls /doc/development/new_fe_guide/tips.md @sselhorn /doc/development/omnibus.md @axil -/doc/development/ordering_table_columns.md @aqualls /doc/development/packages/ @claytoncornell /doc/development/permissions.md @eread /doc/development/policies.md @eread /doc/development/product_qualified_lead_guide/index.md @kpaizee /doc/development/project_templates.md @fneill /doc/development/prometheus_metrics.md @msedlakjakubowski -/doc/development/query_performance.md @aqualls -/doc/development/query_recorder.md @aqualls /doc/development/real_time.md @msedlakjakubowski /doc/development/secure_coding_guidelines.md @sselhorn -/doc/development/serializing_data.md @aqualls /doc/development/service_ping/ @claytoncornell -/doc/development/single_table_inheritance.md @aqualls /doc/development/snowplow/ @claytoncornell /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 @sselhorn /doc/development/testing_guide/end_to_end/best_practices.md @sselhorn -/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 -/doc/development/verifying_database_capabilities.md @aqualls /doc/development/wikis.md @aqualls /doc/development/work_items_widgets.md @msedlakjakubowski /doc/development/work_items.md @msedlakjakubowski diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 549289450a4..f947042ba51 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -84,89 +84,6 @@ border-bottom: 1px solid $white-dark; padding: 11px 0; margin-bottom: 11px; - - &.no-bottom-space { - border-bottom: 0; - margin-bottom: 0; - } -} - -.cover-block { - text-align: center; - background: $gray-light; - padding-top: 44px; - position: relative; - - .avatar-holder { - .avatar, - .identicon { - margin: 0 auto; - float: none; - } - - .identicon { - border-radius: 50%; - } - } - - .cover-title { - color: $gl-text-color; - font-size: 23px; - - h1 { - color: $gl-text-color; - margin-bottom: 6px; - font-size: 23px; - } - - .visibility-icon { - display: inline-block; - margin-left: 5px; - font-size: 18px; - color: color('gray'); - } - - p { - padding: 0 $gl-padding; - color: $gl-text-color; - } - } - - .cover-controls { - @include media-breakpoint-up(sm) { - position: absolute; - top: 1rem; - right: 1.25rem; - } - - &.left { - @include media-breakpoint-up(sm) { - left: 1.25rem; - right: auto; - } - } - } - - &.user-cover-block { - padding: 24px 0 0; - - .nav-links { - width: 100%; - float: none; - - &.scrolling-tabs { - float: none; - } - } - - li:first-child { - margin-left: auto; - } - - li:last-child { - margin-right: auto; - } - } } .content-block { diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index 1fa03d66f32..b1e5ca50a8b 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -1,30 +1,3 @@ -.calendar-block { - padding-left: 0; - padding-right: 0; - border-top: 0; - - @media (min-width: map-get($grid-breakpoints, sm)) and (max-width: map-get($grid-breakpoints, sm)) { - overflow-x: auto; - } -} - -.user-calendar-activities { - direction: ltr; - - .str-truncated { - max-width: 70%; - } -} - -.user-calendar { - text-align: center; - min-height: 172px; - - .calendar { - display: inline-block; - } -} - .user-contrib-cell { &:hover { cursor: pointer; @@ -42,18 +15,6 @@ } } -.user-contrib-text { - font-size: 12px; - fill: $calendar-user-contrib-text; -} - -.calendar-hint { - font-size: 12px; - direction: ltr; - margin-top: -23px; - float: right; -} - .pika-single.gitlab-theme { .pika-label { color: $gl-text-color-secondary; diff --git a/app/assets/stylesheets/page_bundles/profile.scss b/app/assets/stylesheets/page_bundles/profile.scss new file mode 100644 index 00000000000..59b8823c113 --- /dev/null +++ b/app/assets/stylesheets/page_bundles/profile.scss @@ -0,0 +1,212 @@ +@import 'mixins_and_variables_and_functions'; + +.calendar-block { + padding-left: 0; + padding-right: 0; + border-top: 0; + + @media (min-width: map-get($grid-breakpoints, sm)) and (max-width: map-get($grid-breakpoints, sm)) { + overflow-x: auto; + } +} + +.calendar-hint { + font-size: 12px; + direction: ltr; + margin-top: -23px; + float: right; +} + +.cover-block { + text-align: center; + background: var(--gray-50, $gray-light); + padding-top: 44px; + position: relative; + + .avatar-holder { + .avatar, + .identicon { + margin: 0 auto; + float: none; + } + + .identicon { + border-radius: 50%; + } + } + + .cover-title { + color: var(--gl-text-color, $gl-text-color); + font-size: 23px; + + h1 { + color: var(--gl-text-color, $gl-text-color); + margin-bottom: 6px; + font-size: 23px; + } + + .visibility-icon { + display: inline-block; + margin-left: 5px; + font-size: 18px; + color: color('gray'); + } + + p { + padding: 0 $gl-padding; + color: var(--gl-text-color, $gl-text-color); + } + } + + .cover-controls { + @include media-breakpoint-up(sm) { + position: absolute; + top: 1rem; + right: 1.25rem; + } + + &.left { + @include media-breakpoint-up(sm) { + left: 1.25rem; + right: auto; + } + } + } + + &.user-cover-block { + padding: 24px 0 0; + + .nav-links { + width: 100%; + float: none; + + &.scrolling-tabs { + float: none; + } + } + + li:first-child { + margin-left: auto; + } + + li:last-child { + margin-right: auto; + } + } +} + +// Middle dot divider between each element in a list of items. +.middle-dot-divider { + @include middle-dot-divider; +} + +.middle-dot-divider-sm { + @include media-breakpoint-up(sm) { + @include middle-dot-divider; + } +} + +.profile-user-bio { + // Limits the width of the user bio for readability. + max-width: 600px; + margin: 10px auto; +} + +.user-calendar { + text-align: center; + min-height: 172px; + + .calendar { + display: inline-block; + } +} + +.user-calendar-activities { + direction: ltr; + + .str-truncated { + max-width: 70%; + } +} + +.user-contrib-text { + font-size: 12px; + fill: $calendar-user-contrib-text; +} + +.user-profile { + .profile-header { + margin: 0 $gl-padding; + + &.with-no-profile-tabs { + margin-bottom: $gl-padding-24; + } + + .avatar-holder { + width: 90px; + margin: 0 auto 10px; + } + } + + .user-profile-nav { + font-size: 0; + } + + .fade-right { + right: 0; + } + + .fade-left { + left: 0; + } + + .activities-block { + .event-item { + padding-left: 40px; + } + + .gl-label-scoped { + --label-inset-border: inset 0 0 0 1px currentColor; + } + + @include media-breakpoint-up(lg) { + margin-right: 5px; + } + } + + .projects-block { + @include media-breakpoint-up(lg) { + margin-left: 5px; + } + } + + @include media-breakpoint-down(xs) { + .cover-block { + padding-top: 20px; + } + + .user-profile-nav { + a { + margin-right: 0; + } + } + + .activities-block { + .event-item { + padding-left: 0; + } + } + } +} + +.linkedin-icon { + color: $linkedin; +} + +.skype-icon { + color: $skype; +} + +.twitter-icon { + color: $twitter; +} diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 812cc6ab4e6..951e31ef768 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -29,23 +29,6 @@ } } -// Middle dot divider between each element in a list of items. -.middle-dot-divider { - @include middle-dot-divider; -} - -.middle-dot-divider-sm { - @include media-breakpoint-up(sm) { - @include middle-dot-divider; - } -} - -.profile-user-bio { - // Limits the width of the user bio for readability. - max-width: 600px; - margin: 10px auto; -} - .user-avatar-button { .file-name { display: inline-block; @@ -156,71 +139,6 @@ } } -.user-profile { - .profile-header { - margin: 0 $gl-padding; - - &.with-no-profile-tabs { - margin-bottom: $gl-padding-24; - } - - .avatar-holder { - width: 90px; - margin: 0 auto 10px; - } - } - - .user-profile-nav { - font-size: 0; - } - - .fade-right { - right: 0; - } - - .fade-left { - left: 0; - } - - .activities-block { - .event-item { - padding-left: 40px; - } - - .gl-label-scoped { - --label-inset-border: inset 0 0 0 1px currentColor; - } - - @include media-breakpoint-up(lg) { - margin-right: 5px; - } - } - - .projects-block { - @include media-breakpoint-up(lg) { - margin-left: 5px; - } - } - - @include media-breakpoint-down(xs) { - .cover-block { - padding-top: 20px; - } - - .user-profile-nav { - a { - margin-right: 0; - } - } - - .activities-block { - .event-item { - padding-left: 0; - } - } - } -} - table.u2f-registrations { th:not(:last-child), td:not(:last-child) { @@ -366,15 +284,3 @@ table.u2f-registrations { .gitlab-slack-slack-logo { transform: scale(200%); // Slack logo SVG is scaled down 50% and has empty space around it } - -.skype-icon { - color: $skype; -} - -.linkedin-icon { - color: $linkedin; -} - -.twitter-icon { - color: $twitter; -} diff --git a/app/components/pajamas/checkbox_component.rb b/app/components/pajamas/checkbox_component.rb index ae78d0453f8..d9987b7653c 100644 --- a/app/components/pajamas/checkbox_component.rb +++ b/app/components/pajamas/checkbox_component.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true # Renders a Pajamas compliant checkbox element -# Must be used in an instance of `ActionView::Helpers::FormBuilder` +# An instance of `ActionView::Helpers::FormBuilder` must be passed as the `form` argument. +# The easiest way to use this component is by using the `gitlab_ui_checkbox_component` helper. +# See https://docs.gitlab.com/ee/development/fe_guide/haml.html#gitlab_ui_checkbox_component +# To use a checkbox without an instance of `ActionView::Helpers::FormBuilder` use `CheckboxTagComponent`. module Pajamas class CheckboxComponent < Pajamas::Component include Pajamas::Concerns::CheckboxRadioLabelWithHelpText @@ -31,6 +34,8 @@ module Pajamas @value = checked_value if checkbox_options[:multiple] end + private + attr_reader( :form, :method, @@ -43,8 +48,6 @@ module Pajamas :value ) - private - def label_content label? ? label : label_argument end diff --git a/app/components/pajamas/checkbox_tag_component.html.haml b/app/components/pajamas/checkbox_tag_component.html.haml new file mode 100644 index 00000000000..ad02c966fad --- /dev/null +++ b/app/components/pajamas/checkbox_tag_component.html.haml @@ -0,0 +1,6 @@ +.gl-form-checkbox.custom-control.custom-checkbox + = check_box_tag(name, + value, + checked, + formatted_input_options) + = render_label_tag_with_help_text diff --git a/app/components/pajamas/checkbox_tag_component.rb b/app/components/pajamas/checkbox_tag_component.rb new file mode 100644 index 00000000000..45e88588059 --- /dev/null +++ b/app/components/pajamas/checkbox_tag_component.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Renders a Pajamas compliant checkbox element +module Pajamas + class CheckboxTagComponent < Pajamas::Component + include Pajamas::Concerns::CheckboxRadioLabelWithHelpText + include Pajamas::Concerns::CheckboxRadioOptions + + renders_one :label + renders_one :help_text + + def initialize( + name:, + label_options: {}, + checkbox_options: {}, + value: '1', + checked: false + ) + @name = name + @label_options = label_options + @input_options = checkbox_options + @value = value + @checked = checked + end + + private + + attr_reader( + :name, + :label_options, + :input_options, + :value, + :checked + ) + + def label_content + label + end + + def help_text_content + help_text + end + end +end diff --git a/app/components/pajamas/concerns/checkbox_radio_label_with_help_text.rb b/app/components/pajamas/concerns/checkbox_radio_label_with_help_text.rb index 4ece904fb85..298ed200101 100644 --- a/app/components/pajamas/concerns/checkbox_radio_label_with_help_text.rb +++ b/app/components/pajamas/concerns/checkbox_radio_label_with_help_text.rb @@ -7,6 +7,10 @@ module Pajamas form.label(method, formatted_label_options) { label_entry } end + def render_label_tag_with_help_text + label_tag(name, formatted_label_options) { label_entry } + end + private def label_entry diff --git a/app/components/pajamas/radio_component.rb b/app/components/pajamas/radio_component.rb index 52a761b9d7d..7a3d95c8565 100644 --- a/app/components/pajamas/radio_component.rb +++ b/app/components/pajamas/radio_component.rb @@ -28,6 +28,8 @@ module Pajamas @value = value end + private + attr_reader( :form, :method, @@ -38,8 +40,6 @@ module Pajamas :value ) - private - def label_content label? ? label : label_argument end diff --git a/app/services/ci/runners/process_runner_version_update_service.rb b/app/services/ci/runners/process_runner_version_update_service.rb index ed591a9ab3e..c8a5e42ccab 100644 --- a/app/services/ci/runners/process_runner_version_update_service.rb +++ b/app/services/ci/runners/process_runner_version_update_service.rb @@ -20,7 +20,7 @@ module Ci private def upgrade_check_service - Gitlab::Ci::RunnerUpgradeCheck.instance + @runner_upgrade_check ||= Gitlab::Ci::RunnerUpgradeCheck.new(::Gitlab::VERSION) end end end diff --git a/app/services/ci/runners/reconcile_existing_runner_versions_service.rb b/app/services/ci/runners/reconcile_existing_runner_versions_service.rb index 18b959513a8..da9be7d7207 100644 --- a/app/services/ci/runners/reconcile_existing_runner_versions_service.rb +++ b/app/services/ci/runners/reconcile_existing_runner_versions_service.rb @@ -22,7 +22,7 @@ module Ci private def upgrade_check - Gitlab::Ci::RunnerUpgradeCheck.instance + @runner_upgrade_check ||= Gitlab::Ci::RunnerUpgradeCheck.new(::Gitlab::VERSION) end # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/services/merge_requests/approval_service.rb b/app/services/merge_requests/approval_service.rb index f2651370305..be6ab9050d9 100644 --- a/app/services/merge_requests/approval_service.rb +++ b/app/services/merge_requests/approval_service.rb @@ -25,9 +25,9 @@ module MergeRequests ) else create_event(merge_request) + stream_audit_event(merge_request) end - stream_audit_event(merge_request) create_approval_note(merge_request) mark_pending_todos_as_done(merge_request) execute_approval_hooks(merge_request, current_user) diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index 992b46c1f7b..3c7d358bde9 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -63,18 +63,20 @@ = s_('ProjectsNew|Project Configuration') .form-group - .form-check.gl-mb-3 - = check_box_tag 'project[initialize_with_readme]', '1', true, class: 'form-check-input', data: { qa_selector: 'initialize_with_readme_checkbox', track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_readme' } - = label_tag 'project[initialize_with_readme]', s_('ProjectsNew|Initialize repository with a README'), class: 'form-check-label' - .form-text.text-muted + = render Pajamas::CheckboxTagComponent.new(name: 'project[initialize_with_readme]', + checked: true, + checkbox_options: { data: { qa_selector: 'initialize_with_readme_checkbox', track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_readme' } }) do |c| + = c.label do + = s_('ProjectsNew|Initialize repository with a README') + = c.help_text do = s_('ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.') .form-group - .form-check.gl-mb-3 - = check_box_tag 'project[initialize_with_sast]', '1', false, class: 'form-check-input', data: { qa_selector: 'initialize_with_sast_checkbox', track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_sast' } - = label_tag 'project[initialize_with_sast]', class: 'form-check-label' do + = render Pajamas::CheckboxTagComponent.new(name: 'project[initialize_with_sast]', + checkbox_options: { data: { qa_selector: 'initialize_with_sast_checkbox', track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_sast' } }) do |c| + = c.label do = s_('ProjectsNew|Enable Static Application Security Testing (SAST)') - .form-text.text-muted + = c.help_text do = s_('ProjectsNew|Analyze your source code for known security vulnerabilities.') = link_to _('Learn more.'), help_page_path('user/application_security/sast/index'), target: '_blank', rel: 'noopener noreferrer', data: { track_action: 'followed' } diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index adb5cceb5d6..95186b85838 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,7 +1,7 @@ - add_to_breadcrumbs _("Compare Revisions"), project_compare_index_path(@project) - page_title "#{params[:from]}...#{params[:to]}" -.sub-header-block.no-bottom-space +.sub-header-block.gl-border-b-0.gl-mb-0 .js-signature-container{ data: { 'signatures-path' => signatures_namespace_project_compare_index_path } } #js-compare-selector{ data: project_compare_selector_data(@project, @merge_request, params) } diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 0eb243137b6..25070138128 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -4,6 +4,7 @@ - page_title user_display_name(@user) - page_description @user.bio unless @user.blocked? || !@user.confirmed? - page_itemtype 'http://schema.org/Person' +- add_page_specific_style 'page_bundles/profile' - link_classes = "flex-grow-1 mx-1 " = content_for :meta_tags do diff --git a/config/application.rb b/config/application.rb index d5e7cd69b37..d28967f2966 100644 --- a/config/application.rb +++ b/config/application.rb @@ -290,6 +290,7 @@ module Gitlab config.assets.precompile << "page_bundles/pipelines.css" config.assets.precompile << "page_bundles/pipeline_editor.css" config.assets.precompile << "page_bundles/productivity_analytics.css" + config.assets.precompile << "page_bundles/profile.css" config.assets.precompile << "page_bundles/profile_two_factor_auth.css" config.assets.precompile << "page_bundles/project.css" config.assets.precompile << "page_bundles/projects_edit.css" diff --git a/config/feature_flags/development/incremental_repository_backup.yml b/config/feature_flags/development/incremental_repository_backup.yml deleted file mode 100644 index 2b980abd403..00000000000 --- a/config/feature_flags/development/incremental_repository_backup.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: incremental_repository_backup -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79589 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/355945 -milestone: '14.9' -type: development -group: group::gitaly -default_enabled: true diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 6d27b9de54c..80a452eec44 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -287,6 +287,8 @@ - 1 - - merge_requests_resolve_todos - 1 +- - merge_requests_stream_approval_audit_event + - 1 - - merge_requests_sync_code_owner_approval_rules - 1 - - merge_requests_update_head_pipeline diff --git a/doc/development/fe_guide/view_component.md b/doc/development/fe_guide/view_component.md index 35df9db2452..2e373e6933b 100644 --- a/doc/development/fe_guide/view_component.md +++ b/doc/development/fe_guide/view_component.md @@ -153,6 +153,39 @@ If you want to add custom attributes to any of these or the card itself, use the For the full list of options, see its [source](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/components/pajamas/card_component.rb). +#### Checkbox tag + +The `Pajamas::CheckboxTagComponent` follows the [Pajamas Checkbox](https://design.gitlab.com/components/checkbox) specification. + +The `name` argument and `label` slot are required. + +For example: + +```haml += render Pajamas::CheckboxTagComponent.new(name: 'project[initialize_with_sast]', + checkbox_options: { data: { qa_selector: 'initialize_with_sast_checkbox', track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_sast' } }) do |c| + = c.label do + = s_('ProjectsNew|Enable Static Application Security Testing (SAST)') + = c.help_text do + = s_('ProjectsNew|Analyze your source code for known security vulnerabilities.') + = link_to _('Learn more.'), help_page_path('user/application_security/sast/index'), target: '_blank', rel: 'noopener noreferrer', data: { track_action: 'followed' } +``` + +For the full list of options, see its +[source](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/components/pajamas/checkbox_tag_component.rb). + +#### Checkbox + +The `Pajamas::CheckboxComponent` follows the [Pajamas Checkbox](https://design.gitlab.com/components/checkbox) specification. + +NOTE: +`Pajamas::CheckboxComponent` is used internally by the [GitLab UI form builder](haml.md#use-the-gitlab-ui-form-builder) and requires an instance of [ActionView::Helpers::FormBuilder](https://api.rubyonrails.org/v6.1.0/classes/ActionView/Helpers/FormBuilder.html) to be passed as the `form` argument. +It is preferred to use the [gitlab_ui_checkbox_component](haml.md#gitlab_ui_checkbox_component) method to render this ViewComponent. +To use a checkbox without an instance of [ActionView::Helpers::FormBuilder](https://api.rubyonrails.org/v6.1.0/classes/ActionView/Helpers/FormBuilder.html) use [CheckboxTagComponent](#checkbox-tag). + +For the full list of options, see its +[source](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/components/pajamas/checkbox_component.rb). + #### Toggle The `Pajamas::ToggleComponent` follows the [Pajamas Toggle](https://design.gitlab.com/components/toggle) specification. diff --git a/doc/development/single_table_inheritance.md b/doc/development/single_table_inheritance.md new file mode 100644 index 00000000000..da8d48f2a42 --- /dev/null +++ b/doc/development/single_table_inheritance.md @@ -0,0 +1,11 @@ +--- +redirect_to: 'database/single_table_inheritance.md' +remove_date: '2022-11-06' +--- + +This document was moved to [another location](database/single_table_inheritance.md). + +<!-- This redirect file can be deleted after <2022-11-06>. --> +<!-- Redirects that point to other docs in the same project expire in three months. --> +<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. --> +<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html --> diff --git a/doc/development/testing_guide/contract/index.md b/doc/development/testing_guide/contract/index.md index 8e12eea2874..f61f842e167 100644 --- a/doc/development/testing_guide/contract/index.md +++ b/doc/development/testing_guide/contract/index.md @@ -28,14 +28,14 @@ Before running the consumer tests, go to `spec/contracts/consumer` and run `npm ### Run the provider tests -Before running the provider tests, make sure your GDK (GitLab Development Kit) is fully set up and running. You can follow the setup instructions detailed in the [GDK repository](https://gitlab.com/gitlab-org/gitlab-development-kit/-/tree/main). To run the provider tests, you use Rake tasks that are defined in [`./lib/tasks/contracts.rake`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/tasks/contracts.rake). To get a list of all the Rake tasks related to the provider tests, run `bundle exec rake -T contracts`. For example: +Before running the provider tests, make sure your GDK (GitLab Development Kit) is fully set up and running. You can follow the setup instructions detailed in the [GDK repository](https://gitlab.com/gitlab-org/gitlab-development-kit/-/tree/main). To run the provider tests, you use Rake tasks that can be found in [`./lib/tasks/contracts`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/tasks/contracts). To get a list of all the Rake tasks related to the provider tests, run `bundle exec rake -T contracts`. For example: ```shell $ bundle exec rake -T contracts -rake contracts:mr:pact:verify:diffs # Verify provider against the consumer pacts for diffs -rake contracts:mr:pact:verify:discussions # Verify provider against the consumer pacts for discussions -rake contracts:mr:pact:verify:metadata # Verify provider against the consumer pacts for metadata -rake contracts:mr:test:merge_request[contract_mr] # Run all merge request contract tests +rake contracts:merge_requests:pact:verify:diffs_batch # Verify provider against the consumer pacts for diffs_batch +rake contracts:merge_requests:pact:verify:diffs_metadata # Verify provider against the consumer pacts for diffs_metadata +rake contracts:merge_requests:pact:verify:discussions # Verify provider against the consumer pacts for discussions +rake contracts:merge_requests:test:merge_requests[contract_merge_requests] # Run all merge request contract tests ``` ## Test suite folder structure and naming conventions diff --git a/lib/backup/gitaly_backup.rb b/lib/backup/gitaly_backup.rb index a995f308c2b..b777b581ae1 100644 --- a/lib/backup/gitaly_backup.rb +++ b/lib/backup/gitaly_backup.rb @@ -32,15 +32,12 @@ module Backup raise Error, "unknown backup type: #{type}" end - args = [] + args = ['-layout', 'pointer'] args += ['-parallel', @max_parallelism.to_s] if @max_parallelism args += ['-parallel-storage', @storage_parallelism.to_s] if @storage_parallelism - if Feature.enabled?(:incremental_repository_backup) - args += ['-layout', 'pointer'] - if type == :create - args += ['-incremental'] if incremental? - args += ['-id', backup_id] if backup_id - end + if type == :create + args += ['-incremental'] if incremental? + args += ['-id', backup_id] if backup_id end @input_stream, stdout, @thread = Open3.popen2(build_env, bin_path, command, '-path', backup_repos_path, *args) diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 18d9124fbd2..a95311e0ecc 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -34,11 +34,7 @@ module Backup def initialize(progress, definitions: nil) @progress = progress - - @incremental = Feature.feature_flags_available? && - Feature.enabled?(:incremental_repository_backup) && - Gitlab::Utils.to_boolean(ENV['INCREMENTAL'], default: false) - + @incremental = Gitlab::Utils.to_boolean(ENV['INCREMENTAL'], default: false) @definitions = definitions end diff --git a/lib/gitlab/ci/runner_releases.rb b/lib/gitlab/ci/runner_releases.rb index 8773ecbf09e..31a537f1b61 100644 --- a/lib/gitlab/ci/runner_releases.rb +++ b/lib/gitlab/ci/runner_releases.rb @@ -74,7 +74,7 @@ module Gitlab releases = response.parsed_response .map { |release| parse_runner_release(release) } .select(&:valid?) - .sort! + .sort return if releases.empty? && response.parsed_response.present? diff --git a/lib/gitlab/ci/runner_upgrade_check.rb b/lib/gitlab/ci/runner_upgrade_check.rb index 646d1420ffb..03130addd6a 100644 --- a/lib/gitlab/ci/runner_upgrade_check.rb +++ b/lib/gitlab/ci/runner_upgrade_check.rb @@ -3,7 +3,10 @@ module Gitlab module Ci class RunnerUpgradeCheck - include Singleton + def initialize(gitlab_version, runner_releases_store = nil) + @gitlab_version = ::Gitlab::VersionInfo.parse(gitlab_version, parse_suffix: true) + @releases_store = runner_releases_store + end def check_runner_upgrade_suggestion(runner_version) check_runner_upgrade_suggestions(runner_version).first @@ -11,12 +14,8 @@ module Gitlab private - def gitlab_version - @gitlab_version ||= ::Gitlab::VersionInfo.parse(::Gitlab::VERSION, parse_suffix: true) - end - def runner_releases_store - RunnerReleases.instance + @releases_store ||= RunnerReleases.instance end def add_suggestion(suggestions, runner_version, version, status) @@ -54,12 +53,12 @@ module Gitlab # Consider the edge case of pre-release runner versions that get registered, but are never published. # In this case, suggest the latest compatible runner version - latest_release = runner_releases_store.releases_by_minor.values.select { |v| v < gitlab_version }.max + latest_release = runner_releases_store.releases_by_minor.values.select { |v| v < @gitlab_version }.max add_suggestion(suggestions, runner_version, latest_release, :recommended) end def add_available_runner_release(runner_version, suggestions) - available_version = runner_releases_store.releases_by_minor[gitlab_version.without_patch] + available_version = runner_releases_store.releases_by_minor[@gitlab_version.without_patch] unless suggestions.include?(available_version) add_suggestion(suggestions, runner_version, available_version, :available) end @@ -76,12 +75,12 @@ module Gitlab outside_window = minor_releases_with_index.count - runner_minor_version_index > 3 if outside_window - recommended_version = runner_releases_store.releases_by_minor[gitlab_version.without_patch] + recommended_version = runner_releases_store.releases_by_minor[@gitlab_version.without_patch] return add_suggestion(suggestions, runner_version, recommended_version, :recommended) end else # If unknown runner version, then recommend the latest version for the GitLab instance - return add_recommended_runner_release_update(gitlab_version, suggestions) + return add_recommended_runner_release_update(@gitlab_version, suggestions) end false diff --git a/lib/tasks/contracts/merge_requests.rake b/lib/tasks/contracts/merge_requests.rake index 05ed9c30495..2fdd2beb4ee 100644 --- a/lib/tasks/contracts/merge_requests.rake +++ b/lib/tasks/contracts/merge_requests.rake @@ -33,9 +33,9 @@ namespace :contracts do end desc 'Run all merge request contract tests' - task 'test:merge_requests', :contract_mr do |_t, arg| + task 'test:merge_requests', :contract_merge_requests do |_t, arg| errors = %w[diffs_batch diffs_metadata discussions].each_with_object([]) do |task, err| - Rake::Task["contracts:mr:pact:verify:#{task}"].execute + Rake::Task["contracts:merge_requests:pact:verify:#{task}"].execute rescue StandardError, SystemExit err << "contracts:merge_requests:pact:verify:#{task}" end diff --git a/lib/tasks/contracts/pipeline_schedules.rake b/lib/tasks/contracts/pipeline_schedules.rake new file mode 100644 index 00000000000..b6d331448e2 --- /dev/null +++ b/lib/tasks/contracts/pipeline_schedules.rake @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +return if Rails.env.production? + +require 'pact/tasks/verification_task' + +contracts = File.expand_path('../../../spec/contracts/contracts/project/pipeline_schedule', __dir__) +provider = File.expand_path('../../../provider', contracts) + +# rubocop:disable Rails/RakeEnvironment +namespace :contracts do + namespace :pipeline_schedules do + Pact::VerificationTask.new(:update_pipeline_schedule) do |pact| + pact.uri( + "#{contracts}/edit/pipelineschedules#edit-put_edit_a_pipeline_schedule.json", + pact_helper: "#{provider}/pact_helpers/project/pipeline_schedule/update_pipeline_schedule_helper.rb" + ) + end + + desc 'Run all pipeline schedule contract tests' + task 'test:pipeline_schedules', :contract_pipeline_schedules do |_t, arg| + errors = %w[ + update_pipeline_schedule + ].each_with_object([]) do |task, err| + Rake::Task["contracts:pipeline_schedules:pact:verify:#{task}"].execute + rescue StandardError, SystemExit + err << "contracts:pipeline_schedule:pact:verify:#{task}" + end + + raise StandardError, "Errors in tasks #{errors.join(', ')}" unless errors.empty? + end + end +end +# rubocop:enable Rails/RakeEnvironment diff --git a/lib/tasks/contracts/pipelines.rake b/lib/tasks/contracts/pipelines.rake index 522e1f0399e..75955822242 100644 --- a/lib/tasks/contracts/pipelines.rake +++ b/lib/tasks/contracts/pipelines.rake @@ -39,7 +39,7 @@ namespace :contracts do end desc 'Run all pipeline contract tests' - task 'test:pipelines', :contract_mr do |_t, arg| + task 'test:pipelines', :contract_pipelines do |_t, arg| errors = %w[ create_a_new_pipeline get_list_project_pipelines diff --git a/spec/components/pajamas/checkbox_component_spec.rb b/spec/components/pajamas/checkbox_component_spec.rb index d79c537a30e..3d50509ef10 100644 --- a/spec/components/pajamas/checkbox_component_spec.rb +++ b/spec/components/pajamas/checkbox_component_spec.rb @@ -8,12 +8,6 @@ RSpec.describe Pajamas::CheckboxComponent, :aggregate_failures, type: :component let_it_be(:label) { "Show one file at a time on merge request's Changes tab" } let_it_be(:help_text) { 'Instead of all the files changed, show only one file at a time.' } - RSpec.shared_examples 'it renders unchecked checkbox with value of `1`' do - it 'renders unchecked checkbox with value of `1`' do - expect(page).to have_unchecked_field(label, with: '1') - end - end - context 'with default options' do before do fake_form_for do |form| diff --git a/spec/components/pajamas/checkbox_tag_component_spec.rb b/spec/components/pajamas/checkbox_tag_component_spec.rb new file mode 100644 index 00000000000..bca7a6005d5 --- /dev/null +++ b/spec/components/pajamas/checkbox_tag_component_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true +require "spec_helper" + +RSpec.describe Pajamas::CheckboxTagComponent, :aggregate_failures, type: :component do + let_it_be(:name) { :view_diffs_file_by_file } + let_it_be(:label) { "Show one file at a time on merge request's Changes tab" } + let_it_be(:help_text) { 'Instead of all the files changed, show only one file at a time.' } + + context 'with default options' do + before do + render_inline(described_class.new(name: name)) do |c| + c.label { label } + end + end + + include_examples 'it renders unchecked checkbox with value of `1`' + include_examples 'it does not render help text' + end + + context 'with custom options' do + let_it_be(:value) { 'yes' } + let_it_be(:checkbox_options) { { class: 'checkbox-foo-bar', checked: true } } + let_it_be(:label_options) { { class: 'label-foo-bar' } } + + before do + render_inline( + described_class.new( + name: name, + value: value, + checked: true, + checkbox_options: checkbox_options, + label_options: label_options + ) + ) do |c| + c.label { label } + end + end + + it 'renders checked checkbox with value of `yes`' do + expect(page).to have_checked_field(label, with: value, class: checkbox_options[:class]) + end + + it 'adds CSS class to label' do + expect(page).to have_selector('label.label-foo-bar') + end + end + + context 'with `help_text` slot' do + before do + render_inline(described_class.new(name: name)) do |c| + c.label { label } + c.help_text { help_text } + end + end + + include_examples 'it renders unchecked checkbox with value of `1`' + include_examples 'it renders help text' + end +end diff --git a/spec/components/pajamas/concerns/checkbox_radio_label_with_help_text_spec.rb b/spec/components/pajamas/concerns/checkbox_radio_label_with_help_text_spec.rb index 7a792592b3c..4994abcfb93 100644 --- a/spec/components/pajamas/concerns/checkbox_radio_label_with_help_text_spec.rb +++ b/spec/components/pajamas/concerns/checkbox_radio_label_with_help_text_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Pajamas::Concerns::CheckboxRadioLabelWithHelpText do attr_reader( :form, :method, + :name, :label_argument, :help_text_argument, :label_options, @@ -16,8 +17,9 @@ RSpec.describe Pajamas::Concerns::CheckboxRadioLabelWithHelpText do ) def initialize( - form:, - method:, + form: nil, + method: nil, + name: nil, label: nil, help_text: nil, label_options: {}, @@ -26,6 +28,7 @@ RSpec.describe Pajamas::Concerns::CheckboxRadioLabelWithHelpText do ) @form = form @method = method + @name = name @label_argument = label @help_text_argument = help_text @label_options = label_options @@ -46,19 +49,25 @@ RSpec.describe Pajamas::Concerns::CheckboxRadioLabelWithHelpText do end include Pajamas::Concerns::CheckboxRadioLabelWithHelpText + include ActionView::Context include ActionView::Helpers::TagHelper + include ActionView::Helpers::FormTagHelper end end - let_it_be(:method) { 'username' } + let_it_be(:method_or_name) { 'username' } let_it_be(:label_options) { { class: 'foo-bar' } } let_it_be(:value) { 'Foo bar' } + let_it_be(:expected_label_entry) { '<span>Label argument</span>' } + let_it_be(:expected_label_with_help_text_entry) do + '<span>Label argument</span><p class="help-text" data-testid="pajamas-component-help-text">Help text argument</p>' + end describe '#render_label_with_help_text' do it 'calls `#format_options` with correct arguments' do allow(form).to receive(:label) - component = component_class.new(form: form, method: method, label_options: label_options, value: value) + component = component_class.new(form: form, method: method_or_name, label_options: label_options, value: value) expect(component).to receive(:format_options).with( options: label_options, @@ -73,16 +82,13 @@ RSpec.describe Pajamas::Concerns::CheckboxRadioLabelWithHelpText do it 'calls `form.label` with `label` and `help_text` arguments used in the block' do component = component_class.new( form: form, - method: method, + method: method_or_name, label: 'Label argument', help_text: 'Help text argument' ) - expected_label_entry = '<span>Label argument</span><p class="help-text"' \ - ' data-testid="pajamas-component-help-text">Help text argument</p>' - - expect(form).to receive(:label).with(method, {}) do |&block| - expect(block.call).to eq(expected_label_entry) + expect(form).to receive(:label).with(method_or_name, {}) do |&block| + expect(block.call).to eq(expected_label_with_help_text_entry) end component.render_label_with_help_text @@ -93,13 +99,11 @@ RSpec.describe Pajamas::Concerns::CheckboxRadioLabelWithHelpText do it 'calls `form.label` with `label` argument used in the block' do component = component_class.new( form: form, - method: method, + method: method_or_name, label: 'Label argument' ) - expected_label_entry = '<span>Label argument</span>' - - expect(form).to receive(:label).with(method, {}) do |&block| + expect(form).to receive(:label).with(method_or_name, {}) do |&block| expect(block.call).to eq(expected_label_entry) end @@ -107,4 +111,49 @@ RSpec.describe Pajamas::Concerns::CheckboxRadioLabelWithHelpText do end end end + + describe '#render_label_tag_with_help_text' do + it 'calls `#format_options` with correct arguments' do + component = component_class.new(name: method_or_name, label_options: label_options, value: value) + + expect(component).to receive(:format_options).with( + options: label_options, + css_classes: ['custom-control-label'], + additional_options: { value: value } + ) + + component.render_label_tag_with_help_text + end + + context 'when `help_text` argument is passed' do + it 'calls `label_tag` with `label` and `help_text` arguments used in the block' do + component = component_class.new( + name: method_or_name, + label: 'Label argument', + help_text: 'Help text argument' + ) + + expect(component).to receive(:label_tag).with(method_or_name, {}) do |&block| + expect(block.call).to eq(expected_label_with_help_text_entry) + end + + component.render_label_tag_with_help_text + end + end + + context 'when `help_text` argument is not passed' do + it 'calls `label_tag` with `label` argument used in the block' do + component = component_class.new( + name: method_or_name, + label: 'Label argument' + ) + + expect(component).to receive(:label_tag).with(method_or_name, {}) do |&block| + expect(block.call).to eq(expected_label_entry) + end + + component.render_label_tag_with_help_text + end + end + end end diff --git a/spec/contracts/consumer/fixtures/project/pipeline_schedule/update_pipeline_schedule.fixture.js b/spec/contracts/consumer/fixtures/project/pipeline_schedule/update_pipeline_schedule.fixture.js new file mode 100644 index 00000000000..acdc94d5c6e --- /dev/null +++ b/spec/contracts/consumer/fixtures/project/pipeline_schedule/update_pipeline_schedule.fixture.js @@ -0,0 +1,48 @@ +/* eslint-disable @gitlab/require-i18n-strings */ + +import { Matchers } from '@pact-foundation/pact'; +import { REDIRECT_HTML } from '../../../helpers/common_regex_patterns'; + +const body = Matchers.term({ + matcher: REDIRECT_HTML, + generate: + '<html><body>You are being <a href="http://example.org/gitlab-org/gitlab-qa/-/pipelines/5">redirected</a>.</body></html>', +}); + +const UpdatePipelineSchedule = { + success: { + status: 302, + headers: { + 'Content-Type': 'text/html; charset=utf-8', + }, + body, + }, + + scenario: { + state: 'a project with a pipeline schedule exists', + uponReceiving: 'a request to edit a pipeline schedule', + }, + + request: { + withRequest: { + method: 'PUT', + path: '/gitlab-org/gitlab-qa/-/pipeline_schedules/25', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json; charset=utf-8', + }, + body: { + schedule: { + description: 'bar', + cron: '0 1 * * *', + cron_timezone: 'UTC', + active: true, + }, + }, + }, + }, +}; + +export { UpdatePipelineSchedule }; + +/* eslint-enable @gitlab/require-i18n-strings */ diff --git a/spec/contracts/consumer/helpers/common_regex_patterns.js b/spec/contracts/consumer/helpers/common_regex_patterns.js index 2fbb4ea49f8..78dfeb7748f 100644 --- a/spec/contracts/consumer/helpers/common_regex_patterns.js +++ b/spec/contracts/consumer/helpers/common_regex_patterns.js @@ -3,7 +3,7 @@ */ export const URL = '^(http|https)://[a-z0-9]+([-.]{1}[a-z0-9]+)*.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?$'; export const URL_PATH = '^/[a-zA-Z0-9#-=?_]+$'; -export const REDIRECT_HTML = 'You are being <a href=\\"(.)+/pipelines/[0-9]+\\">redirected</a>.'; +export const REDIRECT_HTML = 'You are being <a href=\\"(.)+\\">redirected</a>.'; // Pipelines export const PIPELINE_GROUPS = diff --git a/spec/contracts/consumer/resources/api/pipeline_schedules.js b/spec/contracts/consumer/resources/api/pipeline_schedules.js new file mode 100644 index 00000000000..ad04e59b9cd --- /dev/null +++ b/spec/contracts/consumer/resources/api/pipeline_schedules.js @@ -0,0 +1,26 @@ +import axios from 'axios'; + +export async function updatePipelineSchedule(endpoint) { + const { url } = endpoint; + + return axios({ + method: 'PUT', + baseURL: url, + url: '/gitlab-org/gitlab-qa/-/pipeline_schedules/25', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json; charset=utf-8', + }, + data: { + schedule: { + description: 'bar', + cron: '0 1 * * *', + cron_timezone: 'UTC', + active: true, + }, + }, + validateStatus: (status) => { + return status === 302; + }, + }); +} diff --git a/spec/contracts/consumer/specs/project/pipeline_schedule/edit.spec.js b/spec/contracts/consumer/specs/project/pipeline_schedule/edit.spec.js new file mode 100644 index 00000000000..7d89825bcd4 --- /dev/null +++ b/spec/contracts/consumer/specs/project/pipeline_schedule/edit.spec.js @@ -0,0 +1,45 @@ +/* eslint-disable @gitlab/require-i18n-strings */ + +import { pactWith } from 'jest-pact'; + +import { UpdatePipelineSchedule } from '../../../fixtures/project/pipeline_schedule/update_pipeline_schedule.fixture'; +import { updatePipelineSchedule } from '../../../resources/api/pipeline_schedules'; + +const CONSUMER_NAME = 'PipelineSchedules#edit'; +const CONSUMER_LOG = '../logs/consumer.log'; +const CONTRACT_DIR = '../contracts/project/pipeline_schedule/edit'; +const PROVIDER_NAME = 'PUT Edit a pipeline schedule'; + +// API endpoint: /pipelines.json +pactWith( + { + consumer: CONSUMER_NAME, + provider: PROVIDER_NAME, + log: CONSUMER_LOG, + dir: CONTRACT_DIR, + }, + + (provider) => { + describe(PROVIDER_NAME, () => { + beforeEach(() => { + const interaction = { + ...UpdatePipelineSchedule.scenario, + ...UpdatePipelineSchedule.request, + willRespondWith: UpdatePipelineSchedule.success, + }; + + provider.addInteraction(interaction); + }); + + it('returns a successful body', async () => { + const pipelineSchedule = await updatePipelineSchedule({ + url: provider.mockService.baseUrl, + }); + + expect(pipelineSchedule.status).toEqual(UpdatePipelineSchedule.success.status); + }); + }); + }, +); + +/* eslint-enable @gitlab/require-i18n-strings */ diff --git a/spec/contracts/contracts/project/pipeline_schedule/edit/pipelineschedules#edit-put_edit_a_pipeline_schedule.json b/spec/contracts/contracts/project/pipeline_schedule/edit/pipelineschedules#edit-put_edit_a_pipeline_schedule.json new file mode 100644 index 00000000000..e0dd68dc230 --- /dev/null +++ b/spec/contracts/contracts/project/pipeline_schedule/edit/pipelineschedules#edit-put_edit_a_pipeline_schedule.json @@ -0,0 +1,48 @@ +{ + "consumer": { + "name": "PipelineSchedules#edit" + }, + "provider": { + "name": "PUT Edit a pipeline schedule" + }, + "interactions": [ + { + "description": "a request to edit a pipeline schedule", + "providerState": "a project with a pipeline schedule exists", + "request": { + "method": "PUT", + "path": "/gitlab-org/gitlab-qa/-/pipeline_schedules/25", + "headers": { + "Accept": "*/*", + "Content-Type": "application/json; charset=utf-8" + }, + "body": { + "schedule": { + "description": "bar", + "cron": "0 1 * * *", + "cron_timezone": "UTC", + "active": true + } + } + }, + "response": { + "status": 302, + "headers": { + "Content-Type": "text/html; charset=utf-8" + }, + "body": "<html><body>You are being <a href=\"http://example.org/gitlab-org/gitlab-qa/-/pipelines/5\">redirected</a>.</body></html>", + "matchingRules": { + "$.body": { + "match": "regex", + "regex": "You are being <a href=\\\"(.)+\\\">redirected<\\/a>." + } + } + } + } + ], + "metadata": { + "pactSpecification": { + "version": "2.0.0" + } + } +}
\ No newline at end of file diff --git a/spec/contracts/provider/pact_helpers/project/pipeline_schedule/update_pipeline_schedule_helper.rb b/spec/contracts/provider/pact_helpers/project/pipeline_schedule/update_pipeline_schedule_helper.rb new file mode 100644 index 00000000000..a83aa9524dc --- /dev/null +++ b/spec/contracts/provider/pact_helpers/project/pipeline_schedule/update_pipeline_schedule_helper.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative '../../../spec_helper' +require_relative '../../../states/project/pipeline_schedule/edit_state' + +module Provider + module CreateNewPipelineHelper + Pact.service_provider "PUT Edit a pipeline schedule" do + app { Environments::Test.app } + + honours_pact_with 'PipelineSchedule#edit' do + pact_uri '../contracts/project/pipeline_schedule/edit/pipelineschedules#edit-put_edit_a_pipeline_schedule.json' + end + end + end +end diff --git a/spec/contracts/provider/states/project/pipeline_schedule/edit_state.rb b/spec/contracts/provider/states/project/pipeline_schedule/edit_state.rb new file mode 100644 index 00000000000..4ee714f15f3 --- /dev/null +++ b/spec/contracts/provider/states/project/pipeline_schedule/edit_state.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +Pact.provider_states_for "PipelineSchedules#edit" do + provider_state "a project with a pipeline schedule exists" do + set_up do + user = User.find_by(name: Provider::UsersHelper::CONTRACT_USER_NAME) + namespace = create(:namespace, name: 'gitlab-org') + project = create(:project, :repository, name: 'gitlab-qa', namespace: namespace, creator: user) + + project.add_maintainer(user) + + create(:ci_pipeline_schedule, id: 25, project: project, owner: user) + end + end +end diff --git a/spec/frontend/fixtures/runner.rb b/spec/frontend/fixtures/runner.rb index 1df0e4bef54..b523650dda5 100644 --- a/spec/frontend/fixtures/runner.rb +++ b/spec/frontend/fixtures/runner.rb @@ -28,9 +28,10 @@ RSpec.describe 'Runner (JavaScript fixtures)' do end before do - allow(Gitlab::Ci::RunnerUpgradeCheck.instance) - .to receive(:check_runner_upgrade_suggestion) - .and_return([nil, :not_available]) + allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance| + allow(instance).to receive(:check_runner_upgrade_suggestion) + .and_return([nil, :not_available]) + end end describe 'as admin', GraphQL::Query do diff --git a/spec/lib/backup/gitaly_backup_spec.rb b/spec/lib/backup/gitaly_backup_spec.rb index 3a9c4dfe3fb..d427e41026e 100644 --- a/spec/lib/backup/gitaly_backup_spec.rb +++ b/spec/lib/backup/gitaly_backup_spec.rb @@ -73,7 +73,7 @@ RSpec.describe Backup::GitalyBackup do let(:max_parallelism) { 3 } it 'passes parallel option through' do - expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-parallel', '3', '-layout', 'pointer', '-id', backup_id).and_call_original + expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-layout', 'pointer', '-parallel', '3', '-id', backup_id).and_call_original subject.start(:create, destination, backup_id: backup_id) subject.finish! @@ -84,7 +84,7 @@ RSpec.describe Backup::GitalyBackup do let(:storage_parallelism) { 3 } it 'passes parallel option through' do - expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-parallel-storage', '3', '-layout', 'pointer', '-id', backup_id).and_call_original + expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-layout', 'pointer', '-parallel-storage', '3', '-id', backup_id).and_call_original subject.start(:create, destination, backup_id: backup_id) subject.finish! @@ -103,36 +103,6 @@ RSpec.describe Backup::GitalyBackup do expect { subject.start(:create, destination, backup_id: backup_id) }.to raise_error(::Backup::Error, 'gitaly-backup binary not found and gitaly_backup_path is not configured') end - - context 'feature flag incremental_repository_backup disabled' do - before do - stub_feature_flags(incremental_repository_backup: false) - end - - it 'creates repository bundles', :aggregate_failures do - # Add data to the wiki, design repositories, and snippets, so they will be included in the dump. - create(:wiki_page, container: project) - create(:design, :with_file, issue: create(:issue, project: project)) - project_snippet = create(:project_snippet, :repository, project: project) - personal_snippet = create(:personal_snippet, :repository, author: project.first_owner) - - expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything).and_call_original - - subject.start(:create, destination, backup_id: backup_id) - subject.enqueue(project, Gitlab::GlRepository::PROJECT) - subject.enqueue(project, Gitlab::GlRepository::WIKI) - subject.enqueue(project, Gitlab::GlRepository::DESIGN) - subject.enqueue(personal_snippet, Gitlab::GlRepository::SNIPPET) - subject.enqueue(project_snippet, Gitlab::GlRepository::SNIPPET) - subject.finish! - - expect(File).to exist(File.join(destination, project.disk_path + '.bundle')) - expect(File).to exist(File.join(destination, project.disk_path + '.wiki.bundle')) - expect(File).to exist(File.join(destination, project.disk_path + '.design.bundle')) - expect(File).to exist(File.join(destination, personal_snippet.disk_path + '.bundle')) - expect(File).to exist(File.join(destination, project_snippet.disk_path + '.bundle')) - end - end end context 'hashed storage' do @@ -208,7 +178,7 @@ RSpec.describe Backup::GitalyBackup do let(:max_parallelism) { 3 } it 'passes parallel option through' do - expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-parallel', '3', '-layout', 'pointer').and_call_original + expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-parallel', '3').and_call_original subject.start(:restore, destination, backup_id: backup_id) subject.finish! @@ -219,45 +189,13 @@ RSpec.describe Backup::GitalyBackup do let(:storage_parallelism) { 3 } it 'passes parallel option through' do - expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-parallel-storage', '3', '-layout', 'pointer').and_call_original + expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-parallel-storage', '3').and_call_original subject.start(:restore, destination, backup_id: backup_id) subject.finish! end end - context 'feature flag incremental_repository_backup disabled' do - before do - stub_feature_flags(incremental_repository_backup: false) - end - - it 'restores from repository bundles', :aggregate_failures do - copy_bundle_to_backup_path('project_repo.bundle', project.disk_path + '.bundle') - copy_bundle_to_backup_path('wiki_repo.bundle', project.disk_path + '.wiki.bundle') - copy_bundle_to_backup_path('design_repo.bundle', project.disk_path + '.design.bundle') - copy_bundle_to_backup_path('personal_snippet_repo.bundle', personal_snippet.disk_path + '.bundle') - copy_bundle_to_backup_path('project_snippet_repo.bundle', project_snippet.disk_path + '.bundle') - - expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything).and_call_original - - subject.start(:restore, destination, backup_id: backup_id) - subject.enqueue(project, Gitlab::GlRepository::PROJECT) - subject.enqueue(project, Gitlab::GlRepository::WIKI) - subject.enqueue(project, Gitlab::GlRepository::DESIGN) - subject.enqueue(personal_snippet, Gitlab::GlRepository::SNIPPET) - subject.enqueue(project_snippet, Gitlab::GlRepository::SNIPPET) - subject.finish! - - collect_commit_shas = -> (repo) { repo.commits('master', limit: 10).map(&:sha) } - - expect(collect_commit_shas.call(project.repository)).to match_array(['393a7d860a5a4c3cc736d7eb00604e3472bb95ec']) - expect(collect_commit_shas.call(project.wiki.repository)).to match_array(['c74b9948d0088d703ee1fafeddd9ed9add2901ea']) - expect(collect_commit_shas.call(project.design_repository)).to match_array(['c3cd4d7bd73a51a0f22045c3a4c871c435dc959d']) - expect(collect_commit_shas.call(personal_snippet.repository)).to match_array(['3b3c067a3bc1d1b695b51e2be30c0f8cf698a06e']) - expect(collect_commit_shas.call(project_snippet.repository)).to match_array(['6e44ba56a4748be361a841e759c20e421a1651a1']) - end - end - it 'raises when the exit code not zero' do expect(subject).to receive(:bin_path).and_return(Gitlab::Utils.which('false')) diff --git a/spec/lib/gitlab/ci/runner_releases_spec.rb b/spec/lib/gitlab/ci/runner_releases_spec.rb index 1cc582cf972..b47e94400c5 100644 --- a/spec/lib/gitlab/ci/runner_releases_spec.rb +++ b/spec/lib/gitlab/ci/runner_releases_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::RunnerReleases do subject { described_class.instance } - let(:runner_releases_url) { 'the release API URL' } + let(:runner_releases_url) { 'http://testurl.com/runner_public_releases' } def releases subject.releases @@ -18,7 +18,7 @@ RSpec.describe Gitlab::Ci::RunnerReleases do before do subject.reset_backoff! - stub_application_setting(public_runner_releases_url: runner_releases_url) + allow(subject).to receive(:runner_releases_url).and_return(runner_releases_url) end describe 'caching behavior', :use_clean_rails_memory_store_caching do diff --git a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb index 191579607b1..55c3834bfa7 100644 --- a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb +++ b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb @@ -5,24 +5,20 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do using RSpec::Parameterized::TableSyntax + subject(:instance) { described_class.new(gitlab_version, runner_releases) } + describe '#check_runner_upgrade_suggestion' do - subject(:result) { described_class.instance.check_runner_upgrade_suggestion(runner_version) } + subject(:result) { instance.check_runner_upgrade_suggestion(runner_version) } let(:gitlab_version) { '14.1.1' } let(:parsed_runner_version) { ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true) } - - before do - allow(described_class.instance).to receive(:gitlab_version) - .and_return(::Gitlab::VersionInfo.parse(gitlab_version)) - end + let(:runner_releases) { instance_double(Gitlab::Ci::RunnerReleases) } context 'with failing Gitlab::Ci::RunnerReleases request' do let(:runner_version) { '14.1.123' } - let(:runner_releases_double) { instance_double(Gitlab::Ci::RunnerReleases) } before do - allow(Gitlab::Ci::RunnerReleases).to receive(:instance).and_return(runner_releases_double) - allow(runner_releases_double).to receive(:releases).and_return(nil) + allow(runner_releases).to receive(:releases).and_return(nil) end it 'returns :error' do @@ -31,10 +27,13 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do end context 'with available_runner_releases configured' do - before do - url = ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url + let(:runner_releases) { Gitlab::Ci::RunnerReleases.instance } + let(:runner_releases_url) do + ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url + end - WebMock.stub_request(:get, url).to_return( + before do + WebMock.stub_request(:get, runner_releases_url).to_return( body: available_runner_releases.map { |v| { name: v } }.to_json, status: 200, headers: { 'Content-Type' => 'application/json' } diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb index 65492876489..749f6839cb5 100644 --- a/spec/requests/api/graphql/ci/runners_spec.rb +++ b/spec/requests/api/graphql/ci/runners_spec.rb @@ -37,7 +37,9 @@ RSpec.describe 'Query.runners' do end before do - allow(Gitlab::Ci::RunnerUpgradeCheck.instance).to receive(:check_runner_upgrade_suggestion) + allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance| + allow(instance).to receive(:check_runner_upgrade_suggestion) + end post_graphql(query, current_user: current_user) end diff --git a/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb b/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb index 64b735b2153..47a7ae4af4d 100644 --- a/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb +++ b/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute' do + include RunnerReleasesHelper + subject(:execute) { described_class.new.execute } let_it_be(:runner_14_0_1) { create(:ci_runner, version: '14.0.1') } @@ -11,12 +13,12 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute' end context 'with RunnerUpgradeCheck recommending 14.0.2' do + let(:upgrade_check) { instance_double(::Gitlab::Ci::RunnerUpgradeCheck) } + before do stub_const('Ci::Runners::ReconcileExistingRunnerVersionsService::VERSION_BATCH_SIZE', 1) - allow(::Gitlab::Ci::RunnerUpgradeCheck.instance) - .to receive(:check_runner_upgrade_suggestion) - .and_return([::Gitlab::VersionInfo.new(14, 0, 2), :recommended]) + allow(::Gitlab::Ci::RunnerUpgradeCheck).to receive(:new).and_return(upgrade_check).once end context 'with runner with new version' do @@ -25,8 +27,9 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute' let!(:runner_14_0_0) { create(:ci_runner, version: '14.0.0') } before do - allow(::Gitlab::Ci::RunnerUpgradeCheck.instance) - .to receive(:check_runner_upgrade_suggestion) + allow(upgrade_check).to receive(:check_runner_upgrade_suggestion) + .and_return([::Gitlab::VersionInfo.new(14, 0, 2), :recommended]) + allow(upgrade_check).to receive(:check_runner_upgrade_suggestion) .with('14.0.2') .and_return([::Gitlab::VersionInfo.new(14, 0, 2), :not_available]) .once @@ -58,8 +61,7 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute' let!(:runner_version_14_0_2) { create(:ci_runner_version, version: '14.0.2', status: :not_available) } before do - allow(::Gitlab::Ci::RunnerUpgradeCheck.instance) - .to receive(:check_runner_upgrade_suggestion) + allow(upgrade_check).to receive(:check_runner_upgrade_suggestion) .and_return([::Gitlab::VersionInfo.new(14, 0, 2), :not_available]) end @@ -80,8 +82,7 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute' context 'with no runner version changes' do before do - allow(::Gitlab::Ci::RunnerUpgradeCheck.instance) - .to receive(:check_runner_upgrade_suggestion) + allow(upgrade_check).to receive(:check_runner_upgrade_suggestion) .and_return([::Gitlab::VersionInfo.new(14, 0, 1), :not_available]) end @@ -100,8 +101,7 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute' context 'with failing version check' do before do - allow(::Gitlab::Ci::RunnerUpgradeCheck.instance) - .to receive(:check_runner_upgrade_suggestion) + allow(upgrade_check).to receive(:check_runner_upgrade_suggestion) .and_return([::Gitlab::VersionInfo.new(14, 0, 1), :error]) end @@ -120,18 +120,8 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute' end context 'integration testing with Gitlab::Ci::RunnerUpgradeCheck' do - let(:available_runner_releases) do - %w[14.0.0 14.0.1] - end - before do - url = ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url - - WebMock.stub_request(:get, url).to_return( - body: available_runner_releases.map { |v| { name: v } }.to_json, - status: 200, - headers: { 'Content-Type' => 'application/json' } - ) + stub_runner_releases(%w[14.0.0 14.0.1]) end it 'does not modify ci_runner_versions entries', :aggregate_failures do diff --git a/spec/support/helpers/runner_releases_helper.rb b/spec/support/helpers/runner_releases_helper.rb new file mode 100644 index 00000000000..ab16a705425 --- /dev/null +++ b/spec/support/helpers/runner_releases_helper.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module RunnerReleasesHelper + def stub_runner_releases(available_runner_releases, gitlab_version: nil) + # We stub the behavior of RunnerReleases so that we don't need to rely on flaky global settings + available_runner_releases = available_runner_releases + .map { |v| ::Gitlab::VersionInfo.parse(v, parse_suffix: true) } + .sort + releases_by_minor = available_runner_releases + .group_by(&:without_patch) + .transform_values(&:max) + + runner_releases_double = instance_double(Gitlab::Ci::RunnerReleases) + allow(::Gitlab::Ci::RunnerUpgradeCheck).to receive(:new).and_wrap_original do |method, *_original_args| + gitlab_version ||= available_runner_releases.max + method.call(gitlab_version, runner_releases_double) + end + + allow(runner_releases_double).to receive(:releases).and_return(available_runner_releases) + allow(runner_releases_double).to receive(:releases_by_minor).and_return(releases_by_minor) + end +end diff --git a/spec/support/shared_examples/components/pajamas_shared_examples.rb b/spec/support/shared_examples/components/pajamas_shared_examples.rb index 955b64152ef..bcf7df24fd9 100644 --- a/spec/support/shared_examples/components/pajamas_shared_examples.rb +++ b/spec/support/shared_examples/components/pajamas_shared_examples.rb @@ -11,3 +11,9 @@ RSpec.shared_examples 'it does not render help text' do expect(page).not_to have_css('[data-testid="pajamas-component-help-text"]') end end + +RSpec.shared_examples 'it renders unchecked checkbox with value of `1`' do + it 'renders unchecked checkbox with value of `1`' do + expect(page).to have_unchecked_field(label, with: '1') + end +end |