diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-03 03:12:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-03 03:12:53 +0300 |
commit | a77c9fccd057514a8b78a10642779f54a22f3f76 (patch) | |
tree | 5746326ff4e1e25f01af2062454f90bdc3f57d35 | |
parent | a88ffaad9f9f321a71657ac0aca4d947f5bc6a5b (diff) |
Add latest changes from gitlab-org/gitlab@master
35 files changed, 564 insertions, 120 deletions
diff --git a/app/assets/javascripts/profile/components/followers_tab.vue b/app/assets/javascripts/profile/components/followers_tab.vue index 47651c33eb8..5b69f835294 100644 --- a/app/assets/javascripts/profile/components/followers_tab.vue +++ b/app/assets/javascripts/profile/components/followers_tab.vue @@ -1,17 +1,24 @@ <script> -import { GlTab } from '@gitlab/ui'; +import { GlBadge, GlTab } from '@gitlab/ui'; import { s__ } from '~/locale'; export default { i18n: { title: s__('UserProfile|Followers'), }, - components: { GlTab }, + components: { + GlBadge, + GlTab, + }, + inject: ['followers'], }; </script> <template> - <gl-tab :title="$options.i18n.title"> - <!-- placeholder --> + <gl-tab> + <template #title> + <span>{{ $options.i18n.title }}</span> + <gl-badge size="sm" class="gl-ml-2">{{ followers }}</gl-badge> + </template> </gl-tab> </template> diff --git a/app/assets/javascripts/profile/components/following_tab.vue b/app/assets/javascripts/profile/components/following_tab.vue index 6d9631c5e89..d39d15a08f3 100644 --- a/app/assets/javascripts/profile/components/following_tab.vue +++ b/app/assets/javascripts/profile/components/following_tab.vue @@ -1,17 +1,24 @@ <script> -import { GlTab } from '@gitlab/ui'; +import { GlBadge, GlTab } from '@gitlab/ui'; import { s__ } from '~/locale'; export default { i18n: { title: s__('UserProfile|Following'), }, - components: { GlTab }, + components: { + GlBadge, + GlTab, + }, + inject: ['followees'], }; </script> <template> - <gl-tab :title="$options.i18n.title"> - <!-- placeholder --> + <gl-tab> + <template #title> + <span>{{ $options.i18n.title }}</span> + <gl-badge size="sm" class="gl-ml-2">{{ followees }}</gl-badge> + </template> </gl-tab> </template> diff --git a/app/assets/javascripts/profile/index.js b/app/assets/javascripts/profile/index.js index a89331cda96..fe430ccca98 100644 --- a/app/assets/javascripts/profile/index.js +++ b/app/assets/javascripts/profile/index.js @@ -7,11 +7,13 @@ export const initProfileTabs = () => { if (!el) return false; - const { userCalendarPath, utcOffset } = el.dataset; + const { followees, followers, userCalendarPath, utcOffset } = el.dataset; return new Vue({ el, provide: { + followees: parseInt(followers, 10), + followers: parseInt(followees, 10), userCalendarPath, utcOffset, }, diff --git a/app/assets/javascripts/related_issues/constants.js b/app/assets/javascripts/related_issues/constants.js index 2e7fbde78b5..25fc875db65 100644 --- a/app/assets/javascripts/related_issues/constants.js +++ b/app/assets/javascripts/related_issues/constants.js @@ -1,12 +1,5 @@ import { __, sprintf } from '~/locale'; -import { TYPE_EPIC, TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants'; - -export const issuableTypesMap = { - ISSUE: 'issue', - INCIDENT: 'incident', - EPIC: 'epic', - MERGE_REQUEST: 'merge_request', -}; +import { TYPE_EPIC, TYPE_INCIDENT, TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants'; export const linkedIssueTypesMap = { BLOCKS: 'blocks', @@ -37,7 +30,7 @@ export const autoCompleteTextMap = { { emphasisStart: '<', emphasisEnd: '>' }, false, ), - [issuableTypesMap.MERGE_REQUEST]: sprintf( + [TYPE_MERGE_REQUEST]: sprintf( __(' or %{emphasisStart}!merge request id%{emphasisEnd}'), { emphasisStart: '<', emphasisEnd: '>' }, false, @@ -46,7 +39,7 @@ export const autoCompleteTextMap = { false: { [TYPE_ISSUE]: '', [TYPE_EPIC]: '', - [issuableTypesMap.MERGE_REQUEST]: __(' or references'), + [TYPE_MERGE_REQUEST]: __(' or references'), }, }; @@ -54,13 +47,13 @@ export const inputPlaceholderTextMap = { [TYPE_ISSUE]: __('Paste issue link'), [TYPE_INCIDENT]: __('Paste link'), [TYPE_EPIC]: __('Paste epic link'), - [issuableTypesMap.MERGE_REQUEST]: __('Enter merge request URLs'), + [TYPE_MERGE_REQUEST]: __('Enter merge request URLs'), }; export const inputPlaceholderConfidentialTextMap = { [TYPE_ISSUE]: __('Paste confidential issue link'), [TYPE_EPIC]: __('Paste confidential epic link'), - [issuableTypesMap.MERGE_REQUEST]: __('Enter merge request URLs'), + [TYPE_MERGE_REQUEST]: __('Enter merge request URLs'), }; export const relatedIssuesRemoveErrorMap = { diff --git a/app/assets/stylesheets/page_bundles/jira_connect.scss b/app/assets/stylesheets/page_bundles/jira_connect.scss index 0a2b3175aa9..2c54c819543 100644 --- a/app/assets/stylesheets/page_bundles/jira_connect.scss +++ b/app/assets/stylesheets/page_bundles/jira_connect.scss @@ -16,12 +16,12 @@ @import '@gitlab/ui/src/components/base/pagination/pagination'; @import '@gitlab/ui/src/components/base/table/table'; @import '@gitlab/ui/src/components/base/tooltip/tooltip'; -@import '@gitlab/ui/src/components/base/search_box_by_type/search_box_by_type'; @import '@gitlab/ui/src/components/base/form/form_input/form_input'; @import '@gitlab/ui/src/components/base/form/form_radio/form_radio'; @import '@gitlab/ui/src/components/base/form/form_radio_group/form_radio_group'; @import '@gitlab/ui/src/components/base/form/form_checkbox/form_checkbox'; @import '@gitlab/ui/src/components/base/form/form_group/form_group'; +@import '@gitlab/ui/src/components/base/search_box_by_type/search_box_by_type'; $header-height: 40px; diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index b66296d2627..218e13f1e40 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -174,6 +174,8 @@ module UsersHelper def user_profile_tabs_app_data(user) { + followees: user.followees.count, + followers: user.followers.count, user_calendar_path: user_calendar_path(user, :json), utc_offset: local_timezone_instance(user.timezone).now.utc_offset } diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 04236bb988d..6d09dcb647c 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -113,16 +113,6 @@ - if display_public_email?(@user) = render 'middle_dot_divider', stacking: true do = link_to @user.public_email, "mailto:#{@user.public_email}", itemprop: 'email' - .gl-text-gray-900 - = sprite_icon('users', css_class: 'gl-vertical-align-middle gl-text-gray-500') - = render 'middle_dot_divider' do - = link_to user_followers_path do - - count = @user.followers.count - = n_('1 follower', '%{count} followers', count) % { count: count } - = render 'middle_dot_divider' do - = link_to user_following_path, data: { qa_selector: 'following_link' } do - = @user.followees.count - = _('following') - if @user.bio.present? .gl-text-gray-900 .profile-user-bio @@ -166,10 +156,12 @@ %li.js-followers-tab = link_to user_followers_path, data: { target: 'div#followers', action: 'followers', toggle: 'tab', endpoint: user_followers_path(format: :json) } do = s_('UserProfile|Followers') + = gl_badge_tag @user.followers.count, size: :sm - if profile_tab?(:following) %li.js-following-tab - = link_to user_following_path, data: { target: 'div#following', action: 'following', toggle: 'tab', endpoint: user_following_path(format: :json) } do + = link_to user_following_path, data: { target: 'div#following', action: 'following', toggle: 'tab', endpoint: user_following_path(format: :json), qa_selector: 'following_tab' } do = s_('UserProfile|Following') + = gl_badge_tag @user.followees.count, size: :sm - if !profile_tabs.empty? && Feature.enabled?(:profile_tabs_vue, current_user) #js-profile-tabs{ data: user_profile_tabs_app_data(@user) } %div{ class: container_class } diff --git a/config/metrics/counts_28d/20230224095530_bulk_import_entities_group_finished_with_failures.yml b/config/metrics/counts_28d/20230224095530_bulk_import_entities_group_finished_with_failures.yml new file mode 100644 index 00000000000..4304dcd0c25 --- /dev/null +++ b/config/metrics/counts_28d/20230224095530_bulk_import_entities_group_finished_with_failures.yml @@ -0,0 +1,27 @@ +--- +data_category: optional +key_path: usage_activity_by_stage_monthly.manage.group_imports.gitlab_migration_finished_with_failures +description: Count of group entities with finished status and failures in GitLab Migration +product_section: dev +product_stage: manage +product_group: import +product_category: importers +value_type: number +status: active +time_frame: 28d +data_source: database +instrumentation_class: CountBulkImportsEntitiesMetric +options: + source_type: group_entity + status: 2 + has_failures: true +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +milestone: "15.10" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112837" diff --git a/config/metrics/counts_28d/20230224095530_bulk_import_entities_group_finished_without_failures.yml b/config/metrics/counts_28d/20230224095530_bulk_import_entities_group_finished_without_failures.yml new file mode 100644 index 00000000000..376e9bd5baa --- /dev/null +++ b/config/metrics/counts_28d/20230224095530_bulk_import_entities_group_finished_without_failures.yml @@ -0,0 +1,27 @@ +--- +data_category: optional +key_path: usage_activity_by_stage_monthly.manage.group_imports.gitlab_migration_finished_without_failures +description: Count of group entities with finished status and without failures in GitLab Migration +product_section: dev +product_stage: manage +product_group: import +product_category: importers +value_type: number +status: active +time_frame: 28d +data_source: database +instrumentation_class: CountBulkImportsEntitiesMetric +options: + source_type: group_entity + status: 2 + has_failures: false +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +milestone: "15.10" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112837" diff --git a/config/metrics/counts_28d/20230224095530_bulk_import_entities_project_finished_with_failures.yml b/config/metrics/counts_28d/20230224095530_bulk_import_entities_project_finished_with_failures.yml new file mode 100644 index 00000000000..5b0e4db64ad --- /dev/null +++ b/config/metrics/counts_28d/20230224095530_bulk_import_entities_project_finished_with_failures.yml @@ -0,0 +1,27 @@ +--- +data_category: optional +key_path: usage_activity_by_stage_monthly.manage.project_imports.gitlab_migration_finished_with_failures +description: Count of project entities with finished status and failures in GitLab Migration +product_section: dev +product_stage: manage +product_group: import +product_category: importers +value_type: number +status: active +time_frame: 28d +data_source: database +instrumentation_class: CountBulkImportsEntitiesMetric +options: + source_type: project_entity + status: 2 + has_failures: true +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +milestone: "15.10" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112837" diff --git a/config/metrics/counts_28d/20230224095530_bulk_import_entities_project_finished_without_failures.yml b/config/metrics/counts_28d/20230224095530_bulk_import_entities_project_finished_without_failures.yml new file mode 100644 index 00000000000..163a7f0ccd6 --- /dev/null +++ b/config/metrics/counts_28d/20230224095530_bulk_import_entities_project_finished_without_failures.yml @@ -0,0 +1,27 @@ +--- +data_category: optional +key_path: usage_activity_by_stage_monthly.manage.project_imports.gitlab_migration_finished_without_failures +description: Count of project entities with finished status and without failures in GitLab Migration +product_section: dev +product_stage: manage +product_group: import +product_category: importers +value_type: number +status: active +time_frame: 28d +data_source: database +instrumentation_class: CountBulkImportsEntitiesMetric +options: + source_type: project_entity + status: 2 + has_failures: false +performance_indicator_type: [] +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate +milestone: "15.10" +introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112837" diff --git a/db/post_migrate/20230221214519_remove_incorrectly_onboarded_namespaces_from_onboarding_progress.rb b/db/post_migrate/20230221214519_remove_incorrectly_onboarded_namespaces_from_onboarding_progress.rb new file mode 100644 index 00000000000..5672fc42851 --- /dev/null +++ b/db/post_migrate/20230221214519_remove_incorrectly_onboarded_namespaces_from_onboarding_progress.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class RemoveIncorrectlyOnboardedNamespacesFromOnboardingProgress < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + restrict_gitlab_migration gitlab_schema: :gitlab_main + + class OnboardingProgress < MigrationRecord + include EachBatch + + self.table_name = 'onboarding_progresses' + end + + class Project < MigrationRecord + self.table_name = 'projects' + end + + def up + names = ['Learn GitLab', 'Learn GitLab - Ultimate trial'] + + OnboardingProgress.each_batch(of: 500) do |batch| + namespaces_to_keep = Project.where(name: names, namespace_id: batch.select(:namespace_id)).select(:namespace_id) + batch.where.not(namespace_id: namespaces_to_keep).delete_all + end + end + + def down + # no op + end +end diff --git a/db/schema_migrations/20230221214519 b/db/schema_migrations/20230221214519 new file mode 100644 index 00000000000..aa630e64644 --- /dev/null +++ b/db/schema_migrations/20230221214519 @@ -0,0 +1 @@ +b8eccb700af0593b94e26e0fbe8b4c680b9bae47ced111422dc400159a3f6b12
\ No newline at end of file diff --git a/doc/development/snowplow/review_guidelines.md b/doc/development/snowplow/review_guidelines.md index ee2c1eb95df..95e1bbacbe1 100644 --- a/doc/development/snowplow/review_guidelines.md +++ b/doc/development/snowplow/review_guidelines.md @@ -41,3 +41,4 @@ events or touches Snowplow related files. - If needed, check that the events are firing locally using one of the [testing tools](implementation.md#develop-and-test-snowplow) available. - Approve the MR, and relabel the MR with `~"product intelligence::approved"`. +- If the snowplow event mirrors a RedisHLL event, then tag @mdrussell to review if the payload is usable for this purpose. diff --git a/doc/user/group/reporting/git_abuse_rate_limit.md b/doc/user/group/reporting/git_abuse_rate_limit.md index 64a8d6947ef..d96f950edcd 100644 --- a/doc/user/group/reporting/git_abuse_rate_limit.md +++ b/doc/user/group/reporting/git_abuse_rate_limit.md @@ -19,7 +19,7 @@ Git abuse rate limiting does not apply to top-level group owners, [deploy tokens ## Automatic ban notifications -If the `git_abuse_rate_limit_feature_flag` feature flag is enabled, selected users receive an email when a user is about to be banned. +If the `limit_unique_project_downloads_per_namespace_user` feature flag is enabled, selected users receive an email when a user is about to be banned. If automatic banning is disabled, a user is not banned automatically when they exceed the limit. However, notifications are still sent. You can use this setup to determine the correct values of the rate limit settings before enabling automatic banning. diff --git a/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric.rb index 642b67a3b02..ca122ccf6f3 100644 --- a/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric.rb +++ b/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric.rb @@ -23,6 +23,7 @@ module Gitlab scope = super scope = scope.where(source_type: source_type) if source_type.present? scope = scope.where(status: status) if status.present? + scope = scope.where(has_failures: failures) if failures.present? scope end @@ -34,6 +35,10 @@ module Gitlab options[:status] end + def failures + options[:has_failures].to_s + end + def allowed_source_types BulkImports::Entity.source_types.keys.map(&:to_s) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f3f173928e9..37549fd725d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1526,11 +1526,6 @@ msgid_plural "%d deploy keys" msgstr[0] "" msgstr[1] "" -msgid "1 follower" -msgid_plural "%{count} followers" -msgstr[0] "" -msgstr[1] "" - msgid "1 group" msgid_plural "%d groups" msgstr[0] "" @@ -51072,9 +51067,6 @@ msgstr[1] "" msgid "finding is not found or is already attached to a vulnerability" msgstr "" -msgid "following" -msgstr "" - msgid "for" msgstr "" @@ -82,7 +82,8 @@ module QA "jetbrains" => "JetBrains", "vscode" => "VSCode", "registry_with_cdn" => "RegistryWithCDN", - "fips" => "FIPS" + "fips" => "FIPS", + "ci_cd_settings" => "CICDSettings" ) loader.setup diff --git a/qa/qa/page/user/show.rb b/qa/qa/page/user/show.rb index ad2de331ad9..9f5f0fae9bc 100644 --- a/qa/qa/page/user/show.rb +++ b/qa/qa/page/user/show.rb @@ -6,7 +6,7 @@ module QA class Show < Page::Base view 'app/views/users/show.html.haml' do element :follow_user_link - element :following_link + element :following_tab end view 'app/views/shared/users/_user.html.haml' do @@ -21,8 +21,8 @@ module QA click_element(:follow_user_link) end - def click_following_link - click_element(:following_link) + def click_following_tab + click_element(:following_tab) end def click_user_link(username) diff --git a/qa/qa/resource/ci_cd_settings.rb b/qa/qa/resource/ci_cd_settings.rb new file mode 100644 index 00000000000..8240321137b --- /dev/null +++ b/qa/qa/resource/ci_cd_settings.rb @@ -0,0 +1,47 @@ +# rubocop:todo Naming/FileName +# frozen_string_literal: true + +module QA + module Resource + class CICDSettings < QA::Resource::Base + attributes :project_path, + :inbound_job_token_scope_enabled + + attribute :mutation_id do + SecureRandom.hex(6) + end + + def resource_web_url(resource) + super + rescue ResourceURLMissingError + # this particular resource does not expose a web_url property + end + + def api_get_path + '/graphql' + end + + alias_method :api_post_path, :api_get_path + + def api_post_body + <<~GQL + mutation { + projectCiCdSettingsUpdate(input: { + clientMutationId: "#{mutation_id}" + inboundJobTokenScopeEnabled: #{inbound_job_token_scope_enabled} + fullPath: "#{project_path}" + }) + { + ciCdSettings { + inboundJobTokenScopeEnabled + } + errors + } + } + GQL + end + end + end +end + +# rubocop:enable Naming/FileName diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb index 94b383a746d..ac08ecec786 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb @@ -82,7 +82,7 @@ module QA Page::Main::Menu.perform(&:click_user_profile_link) Page::User::Show.perform do |show| - show.click_following_link + show.click_following_tab show.click_user_link(followed_user.username) aggregate_failures do diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb index f95bcc59db1..6252a287fd4 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb @@ -36,6 +36,8 @@ module QA use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: package_project) use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: client_project) when :ci_job_token + package_project_inbound_job_token_disabled + client_project_inbound_job_token_disabled '${CI_JOB_TOKEN}' when :project_deploy_token use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: package_project) @@ -43,10 +45,7 @@ module QA end end - it "pushes and pulls a helm chart", testcase: params[:testcase], quarantine: { - type: :stale, - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391649' - } do + it "pushes and pulls a helm chart", testcase: params[:testcase] do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| helm_upload_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_upload_package.yaml.erb')).result(binding) diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb index 92b0466919c..879bb7022c8 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb @@ -41,11 +41,7 @@ module QA 'using a ci job token' => { authentication_token_type: :ci_job_token, maven_header_name: 'Job-Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347579', - quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/373189', - type: :stale - } + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347579' } } end @@ -57,6 +53,8 @@ module QA use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: package_project) use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: client_project) when :ci_job_token + package_project_inbound_job_token_disabled + client_project_inbound_job_token_disabled '${CI_JOB_TOKEN}' when :project_deploy_token use_ci_variable(name: 'GROUP_DEPLOY_TOKEN', value: group_deploy_token.token, project: package_project) @@ -64,7 +62,7 @@ module QA end end - it 'pushes and pulls a maven package', testcase: params[:testcase], quarantine: params[:quarantine] do + it 'pushes and pulls a maven package', testcase: params[:testcase] do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| gitlab_ci_yaml = ERB.new(read_fixture('package_managers/maven/group/producer', 'gitlab_ci.yaml.erb')).result(binding) diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb index d86ce09c4e1..9a418f11b1b 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb @@ -25,16 +25,15 @@ module QA when :personal_access_token "\"#{personal_access_token}\"" when :ci_job_token + package_project_inbound_job_token_disabled + client_project_inbound_job_token_disabled 'System.getenv("CI_JOB_TOKEN")' when :project_deploy_token "\"#{project_deploy_token.token}\"" end end - it 'pushes and pulls a maven package via gradle', testcase: params[:testcase], quarantine: { - type: :stale, - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391650' - } do + it 'pushes and pulls a maven package via gradle', testcase: params[:testcase] do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| gradle_upload_yaml = ERB.new(read_fixture('package_managers/maven/gradle', 'gradle_upload_package.yaml.erb')).result(binding) diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb index c2cbec3fbb7..48b9fdec2e9 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb @@ -50,6 +50,20 @@ module QA end end + let(:package_project_inbound_job_token_disabled) do + Resource::CICDSettings.fabricate_via_api! do |settings| + settings.project_path = project.full_path + settings.inbound_job_token_scope_enabled = false + end + end + + let(:client_project_inbound_job_token_disabled) do + Resource::CICDSettings.fabricate_via_api! do |settings| + settings.project_path = another_project.full_path + settings.inbound_job_token_scope_enabled = false + end + end + let!(:runner) do Resource::GroupRunner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -79,6 +93,8 @@ module QA use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token.token, project: project) use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token.token, project: another_project) when :ci_job_token + package_project_inbound_job_token_disabled + client_project_inbound_job_token_disabled '${CI_JOB_TOKEN}' when :group_deploy_token use_ci_variable(name: 'GROUP_DEPLOY_TOKEN', value: group_deploy_token.token, project: project) @@ -97,10 +113,7 @@ module QA end end - it 'publishes a nuget package at the project endpoint and installs it from the group endpoint', testcase: params[:testcase], quarantine: { - type: :stale, - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391648' - } do + it 'publishes a nuget package at the project endpoint and installs it from the group endpoint', testcase: params[:testcase] do Flow::Login.sign_in Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do diff --git a/qa/qa/specs/features/shared_contexts/packages_registry_shared_context.rb b/qa/qa/specs/features/shared_contexts/packages_registry_shared_context.rb index a611a801b11..5ab7bb331c0 100644 --- a/qa/qa/specs/features/shared_contexts/packages_registry_shared_context.rb +++ b/qa/qa/specs/features/shared_contexts/packages_registry_shared_context.rb @@ -20,6 +20,20 @@ module QA end end + let(:package_project_inbound_job_token_disabled) do + Resource::CICDSettings.fabricate_via_api! do |settings| + settings.project_path = package_project.full_path + settings.inbound_job_token_scope_enabled = false + end + end + + let(:client_project_inbound_job_token_disabled) do + Resource::CICDSettings.fabricate_via_api! do |settings| + settings.project_path = client_project.full_path + settings.inbound_job_token_scope_enabled = false + end + end + let(:package) do Resource::Package.init do |package| package.name = package_name diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 88b2d918976..9aef3ed7cd6 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -149,7 +149,7 @@ RSpec.describe 'User page', feature_category: :user_profile do end end - context 'follow/unfollow and followers/following' do + context 'follow/unfollow and followers/following', :js do let_it_be(:followee) { create(:user) } let_it_be(:follower) { create(:user) } @@ -159,21 +159,33 @@ RSpec.describe 'User page', feature_category: :user_profile do expect(page).not_to have_button(text: 'Follow', class: 'gl-button') end - it 'shows 0 followers and 0 following' do - subject + shared_examples 'follower tabs with count badges' do + it 'shows 0 followers and 0 following' do + subject + + expect(page).to have_content('Followers 0') + expect(page).to have_content('Following 0') + end + + it 'shows 1 followers and 1 following' do + follower.follow(user) + user.follow(followee) - expect(page).to have_content('0 followers') - expect(page).to have_content('0 following') + subject + + expect(page).to have_content('Followers 1') + expect(page).to have_content('Following 1') + end end - it 'shows 1 followers and 1 following' do - follower.follow(user) - user.follow(followee) + it_behaves_like 'follower tabs with count badges' - subject + context 'with profile_tabs_vue feature flag disabled' do + before_all do + stub_feature_flags(profile_tabs_vue: false) + end - expect(page).to have_content('1 follower') - expect(page).to have_content('1 following') + it_behaves_like 'follower tabs with count badges' end it 'does show button to follow' do diff --git a/spec/frontend/profile/components/followers_tab_spec.js b/spec/frontend/profile/components/followers_tab_spec.js index 4af428c4e0c..9cc5bdea9be 100644 --- a/spec/frontend/profile/components/followers_tab_spec.js +++ b/spec/frontend/profile/components/followers_tab_spec.js @@ -1,4 +1,4 @@ -import { GlTab } from '@gitlab/ui'; +import { GlBadge, GlTab } from '@gitlab/ui'; import { s__ } from '~/locale'; import FollowersTab from '~/profile/components/followers_tab.vue'; @@ -8,12 +8,25 @@ describe('FollowersTab', () => { let wrapper; const createComponent = () => { - wrapper = shallowMountExtended(FollowersTab); + wrapper = shallowMountExtended(FollowersTab, { + provide: { + followers: 2, + }, + }); }; - it('renders `GlTab` and sets `title` prop', () => { + it('renders `GlTab` and sets title', () => { createComponent(); - expect(wrapper.findComponent(GlTab).attributes('title')).toBe(s__('UserProfile|Followers')); + expect(wrapper.findComponent(GlTab).element.textContent).toContain( + s__('UserProfile|Followers'), + ); + }); + + it('renders `GlBadge`, sets size and content', () => { + createComponent(); + + expect(wrapper.findComponent(GlBadge).attributes('size')).toBe('sm'); + expect(wrapper.findComponent(GlBadge).element.textContent).toBe('2'); }); }); diff --git a/spec/frontend/profile/components/following_tab_spec.js b/spec/frontend/profile/components/following_tab_spec.js index 75123274ccb..c9d56360c3e 100644 --- a/spec/frontend/profile/components/following_tab_spec.js +++ b/spec/frontend/profile/components/following_tab_spec.js @@ -1,4 +1,4 @@ -import { GlTab } from '@gitlab/ui'; +import { GlBadge, GlTab } from '@gitlab/ui'; import { s__ } from '~/locale'; import FollowingTab from '~/profile/components/following_tab.vue'; @@ -8,12 +8,25 @@ describe('FollowingTab', () => { let wrapper; const createComponent = () => { - wrapper = shallowMountExtended(FollowingTab); + wrapper = shallowMountExtended(FollowingTab, { + provide: { + followees: 3, + }, + }); }; - it('renders `GlTab` and sets `title` prop', () => { + it('renders `GlTab` and sets title', () => { createComponent(); - expect(wrapper.findComponent(GlTab).attributes('title')).toBe(s__('UserProfile|Following')); + expect(wrapper.findComponent(GlTab).element.textContent).toContain( + s__('UserProfile|Following'), + ); + }); + + it('renders `GlBadge`, sets size and content', () => { + createComponent(); + + expect(wrapper.findComponent(GlBadge).attributes('size')).toBe('sm'); + expect(wrapper.findComponent(GlBadge).element.textContent).toBe('3'); }); }); diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index 83afeb77bad..41161188388 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -482,10 +482,14 @@ RSpec.describe UsersHelper do describe '#user_profile_tabs_app_data' do before do allow(helper).to receive(:user_calendar_path).with(user, :json).and_return('/users/root/calendar.json') + allow(user).to receive_message_chain(:followers, :count).and_return(2) + allow(user).to receive_message_chain(:followees, :count).and_return(3) end it 'returns expected hash' do expect(helper.user_profile_tabs_app_data(user)).to eq({ + followees: 3, + followers: 2, user_calendar_path: '/users/root/calendar.json', utc_offset: 0 }) diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb index ce15d44b1e1..317929f77e6 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitiesMetric do +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitiesMetric, feature_category: :importers do let_it_be(:user) { create(:user) } let_it_be(:bulk_import_projects) do create_list(:bulk_import_entity, 2, source_type: 'project_entity', created_at: 3.weeks.ago, status: 2) @@ -163,4 +163,121 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie options: { status: 2, source_type: 'project_entity' } end end + + context 'with has_failures: true' do + before(:all) do + create_list(:bulk_import_entity, 3, :project_entity, :finished, created_at: 3.weeks.ago, has_failures: true) + create_list(:bulk_import_entity, 2, :project_entity, :finished, created_at: 2.months.ago, has_failures: true) + create_list(:bulk_import_entity, 3, :group_entity, :finished, created_at: 3.weeks.ago, has_failures: true) + create_list(:bulk_import_entity, 2, :group_entity, :finished, created_at: 2.months.ago, has_failures: true) + end + + context 'with all time frame' do + context 'with project entity' do + let(:expected_value) { 5 } + let(:expected_query) do + "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ + "WHERE \"bulk_import_entities\".\"source_type\" = 1 AND \"bulk_import_entities\".\"status\" = 2 " \ + "AND \"bulk_import_entities\".\"has_failures\" = TRUE" + end + + it_behaves_like 'a correct instrumented metric value and query', + time_frame: 'all', + options: { status: 2, source_type: 'project_entity', has_failures: true } + end + + context 'with group entity' do + let(:expected_value) { 5 } + let(:expected_query) do + "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ + "WHERE \"bulk_import_entities\".\"source_type\" = 0 AND \"bulk_import_entities\".\"status\" = 2 " \ + "AND \"bulk_import_entities\".\"has_failures\" = TRUE" + end + + it_behaves_like 'a correct instrumented metric value and query', + time_frame: 'all', + options: { status: 2, source_type: 'group_entity', has_failures: true } + end + end + + context 'for 28d time frame' do + let(:expected_value) { 3 } + let(:start) { 30.days.ago.to_s(:db) } + let(:finish) { 2.days.ago.to_s(:db) } + let(:expected_query) do + "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ + "WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}' " \ + "AND \"bulk_import_entities\".\"source_type\" = 1 AND \"bulk_import_entities\".\"status\" = 2 " \ + "AND \"bulk_import_entities\".\"has_failures\" = TRUE" + end + + it_behaves_like 'a correct instrumented metric value and query', + time_frame: '28d', + options: { status: 2, source_type: 'project_entity', has_failures: true } + end + end + + context 'with has_failures: false' do + context 'with all time frame' do + context 'with project entity' do + let(:expected_value) { 3 } + let(:expected_query) do + "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ + "WHERE \"bulk_import_entities\".\"source_type\" = 1 AND \"bulk_import_entities\".\"status\" = 2 " \ + "AND \"bulk_import_entities\".\"has_failures\" = FALSE" + end + + it_behaves_like 'a correct instrumented metric value and query', + time_frame: 'all', + options: { status: 2, source_type: 'project_entity', has_failures: false } + end + + context 'with group entity' do + let(:expected_value) { 2 } + let(:expected_query) do + "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ + "WHERE \"bulk_import_entities\".\"source_type\" = 0 AND \"bulk_import_entities\".\"status\" = 2 " \ + "AND \"bulk_import_entities\".\"has_failures\" = FALSE" + end + + it_behaves_like 'a correct instrumented metric value and query', + time_frame: 'all', + options: { status: 2, source_type: 'group_entity', has_failures: false } + end + end + + context 'for 28d time frame' do + context 'with project entity' do + let(:expected_value) { 2 } + let(:start) { 30.days.ago.to_s(:db) } + let(:finish) { 2.days.ago.to_s(:db) } + let(:expected_query) do + "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ + "WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}' " \ + "AND \"bulk_import_entities\".\"source_type\" = 1 AND \"bulk_import_entities\".\"status\" = 2 " \ + "AND \"bulk_import_entities\".\"has_failures\" = FALSE" + end + + it_behaves_like 'a correct instrumented metric value and query', + time_frame: '28d', + options: { status: 2, source_type: 'project_entity', has_failures: false } + end + + context 'with group entity' do + let(:expected_value) { 2 } + let(:start) { 30.days.ago.to_s(:db) } + let(:finish) { 2.days.ago.to_s(:db) } + let(:expected_query) do + "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ + "WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}' " \ + "AND \"bulk_import_entities\".\"source_type\" = 0 AND \"bulk_import_entities\".\"status\" = 2 " \ + "AND \"bulk_import_entities\".\"has_failures\" = FALSE" + end + + it_behaves_like 'a correct instrumented metric value and query', + time_frame: '28d', + options: { status: 2, source_type: 'group_entity', has_failures: false } + end + end + end end diff --git a/spec/migrations/20230221214519_remove_incorrectly_onboarded_namespaces_from_onboarding_progress_spec.rb b/spec/migrations/20230221214519_remove_incorrectly_onboarded_namespaces_from_onboarding_progress_spec.rb new file mode 100644 index 00000000000..be49a3e919d --- /dev/null +++ b/spec/migrations/20230221214519_remove_incorrectly_onboarded_namespaces_from_onboarding_progress_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe RemoveIncorrectlyOnboardedNamespacesFromOnboardingProgress, feature_category: :onboarding do + let(:onboarding_progresses) { table(:onboarding_progresses) } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + + # namespace to keep with name Learn Gitlab + let(:namespace1) { namespaces.create!(name: 'namespace1', type: 'Group', path: 'namespace1') } + let!(:onboard_keep_1) { onboarding_progresses.create!(namespace_id: namespace1.id) } + let!(:proj1) do + proj_namespace = namespaces.create!(name: 'proj1', path: 'proj1', type: 'Project', parent_id: namespace1.id) + projects.create!(name: 'project', namespace_id: namespace1.id, project_namespace_id: proj_namespace.id) + end + + let!(:learn_gitlab) do + proj_namespace = namespaces.create!(name: 'projlg1', path: 'projlg1', type: 'Project', parent_id: namespace1.id) + projects.create!(name: 'Learn GitLab', namespace_id: namespace1.id, project_namespace_id: proj_namespace.id) + end + + # namespace to keep with name Learn GitLab - Ultimate trial + let(:namespace2) { namespaces.create!(name: 'namespace2', type: 'Group', path: 'namespace2') } + let!(:onboard_keep_2) { onboarding_progresses.create!(namespace_id: namespace2.id) } + let!(:proj2) do + proj_namespace = namespaces.create!(name: 'proj2', path: 'proj2', type: 'Project', parent_id: namespace2.id) + projects.create!(name: 'project', namespace_id: namespace2.id, project_namespace_id: proj_namespace.id) + end + + let!(:learn_gitlab2) do + proj_namespace = namespaces.create!(name: 'projlg2', path: 'projlg2', type: 'Project', parent_id: namespace2.id) + projects.create!( + name: 'Learn GitLab - Ultimate trial', + namespace_id: namespace2.id, + project_namespace_id: proj_namespace.id + ) + end + + # namespace to remove without learn gitlab project + let(:namespace3) { namespaces.create!(name: 'namespace3', type: 'Group', path: 'namespace3') } + let!(:onboarding_to_delete) { onboarding_progresses.create!(namespace_id: namespace3.id) } + let!(:proj3) do + proj_namespace = namespaces.create!(name: 'proj3', path: 'proj3', type: 'Project', parent_id: namespace3.id) + projects.create!(name: 'project', namespace_id: namespace3.id, project_namespace_id: proj_namespace.id) + end + + # namespace to remove without any projects + let(:namespace4) { namespaces.create!(name: 'namespace4', type: 'Group', path: 'namespace4') } + let!(:onboarding_to_delete_without_project) { onboarding_progresses.create!(namespace_id: namespace4.id) } + + describe '#up' do + it 'deletes the onboarding for namespaces without learn gitlab' do + expect { migrate! }.to change { onboarding_progresses.count }.by(-2) + expect(onboarding_progresses.all).to contain_exactly(onboard_keep_1, onboard_keep_2) + end + end +end diff --git a/spec/support/stub_dot_com_check.rb b/spec/support/stub_dot_com_check.rb index 178cf2577ef..6934b33d111 100644 --- a/spec/support/stub_dot_com_check.rb +++ b/spec/support/stub_dot_com_check.rb @@ -1,17 +1,20 @@ # frozen_string_literal: true RSpec.configure do |config| - config.before(:context, :saas) do - # Ensure Gitlab.com? returns true during context. - # This is needed for let_it_be which is shared across examples, - # therefore the value must be changed in before_all, - # but RSpec prevent stubbing method calls in before_all, - # therefore we have to resort to temporarily swap url value. - @_original_gitlab_url = Gitlab.config.gitlab['url'] - Gitlab.config.gitlab['url'] = Gitlab::Saas.com_url - end - config.after(:context, :saas) do - # Swap back original value - Gitlab.config.gitlab['url'] = @_original_gitlab_url + %i[saas saas_registration].each do |metadata| + config.before(:context, metadata) do + # Ensure Gitlab.com? returns true during context. + # This is needed for let_it_be which is shared across examples, + # therefore the value must be changed in before_all, + # but RSpec prevent stubbing method calls in before_all, + # therefore we have to resort to temporarily swap url value. + @_original_gitlab_url = Gitlab.config.gitlab['url'] + Gitlab.config.gitlab['url'] = Gitlab::Saas.com_url + end + + config.after(:context, metadata) do + # Swap back original value + Gitlab.config.gitlab['url'] = @_original_gitlab_url + end end end diff --git a/spec/tooling/danger/stable_branch_spec.rb b/spec/tooling/danger/stable_branch_spec.rb index a0c628b0add..1bfa62b2379 100644 --- a/spec/tooling/danger/stable_branch_spec.rb +++ b/spec/tooling/danger/stable_branch_spec.rb @@ -92,11 +92,19 @@ RSpec.describe Tooling::Danger::StableBranch, feature_category: :delivery do let(:pipeline_bridges_response) do [ - { 'name' => 'e2e:package-and-test', - 'status' => 'success' } + { + 'name' => 'e2e:package-and-test', + 'status' => 'success', + 'downstream_pipeline' => { + 'id' => '123', + 'status' => package_and_qa_state + } + } ] end + let(:package_and_qa_state) { 'success' } + let(:parsed_response) do [ { 'version' => '15.1.1' }, @@ -176,31 +184,26 @@ RSpec.describe Tooling::Danger::StableBranch, feature_category: :delivery do end context 'when package-and-test job is in manual state' do - described_class::FAILING_PACKAGE_AND_TEST_STATUSES.each do |status| - let(:pipeline_bridges_response) do - [ - { 'name' => 'e2e:package-and-test', - 'status' => status } - ] - end + let(:package_and_qa_state) { 'manual' } - it_behaves_like 'with a failure', described_class::NEEDS_PACKAGE_AND_TEST_MESSAGE - it_behaves_like 'bypassing when flaky test or docs only' - end + it_behaves_like 'with a failure', described_class::NEEDS_PACKAGE_AND_TEST_MESSAGE + it_behaves_like 'bypassing when flaky test or docs only' end context 'when package-and-test job is in a non-successful state' do - let(:pipeline_bridges_response) do - [ - { 'name' => 'e2e:package-and-test', - 'status' => 'running' } - ] - end + let(:package_and_qa_state) { 'running' } it_behaves_like 'with a warning', described_class::WARN_PACKAGE_AND_TEST_MESSAGE it_behaves_like 'bypassing when flaky test or docs only' end + context 'when package-and-test job is canceled' do + let(:package_and_qa_state) { 'canceled' } + + it_behaves_like 'with a failure', described_class::NEEDS_PACKAGE_AND_TEST_MESSAGE + it_behaves_like 'bypassing when flaky test or docs only' + end + context 'when no pipeline is found' do before do allow(gitlab_gem_client).to receive(:mr_json).and_return({}) diff --git a/tooling/danger/stable_branch.rb b/tooling/danger/stable_branch.rb index 7da138d4ec4..aaaf3cbea8c 100644 --- a/tooling/danger/stable_branch.rb +++ b/tooling/danger/stable_branch.rb @@ -46,7 +46,7 @@ module Tooling MSG NEEDS_PACKAGE_AND_TEST_MESSAGE = <<~MSG - The `e2e:package-and-test` job is not present or needs to be automatically triggered. + The `e2e:package-and-test` job is not present, has been canceled, or needs to be automatically triggered. Please ensure the job is present in the latest pipeline, if necessary, retry the `danger-review` job. Read the "QA e2e:package-and-test" section for more details. MSG @@ -89,12 +89,11 @@ module Tooling mr_head_pipeline_id = gitlab.mr_json.dig('head_pipeline', 'id') return unless mr_head_pipeline_id - pipeline_bridges = gitlab.api.pipeline_bridges(helper.mr_target_project_id, mr_head_pipeline_id) - package_and_test_pipeline = pipeline_bridges&.find { |j| j['name'] == 'e2e:package-and-test' } + pipeline = package_and_test_pipeline(mr_head_pipeline_id) - return unless package_and_test_pipeline + return unless pipeline - package_and_test_pipeline['status'] + pipeline['status'] end def stable_target_branch @@ -197,6 +196,17 @@ module Tooling def version_to_minor_string(version) "#{version[:major]}.#{version[:minor]}" end + + def package_and_test_pipeline(mr_head_pipeline_id) + package_and_test_bridge = gitlab + .api + .pipeline_bridges(helper.mr_target_project_id, mr_head_pipeline_id) + &.find { |bridge| bridge['name'] == 'e2e:package-and-test' } + + return unless package_and_test_bridge + + package_and_test_bridge['downstream_pipeline'] + end end end end |