diff options
13 files changed, 148 insertions, 46 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bebb0a6f6ee..f17a5f77214 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -150,8 +150,6 @@ variables: FLAKY_RSPEC_SUITE_REPORT_PATH: rspec/flaky/report-suite.json FRONTEND_FIXTURES_MAPPING_PATH: crystalball/frontend_fixtures_mapping.json GITLAB_WORKHORSE_FOLDER: "gitlab-workhorse" - JUNIT_RESULT_FILE: rspec/junit_rspec.xml - JUNIT_RETRY_FILE: rspec/junit_rspec-retry.xml KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/report-master.json RSPEC_CHANGED_FILES_PATH: rspec/changed_files.txt RSPEC_FOSS_IMPACT_PIPELINE_TEMPLATE_YML: .gitlab/ci/rails/rspec-foss-impact.gitlab-ci.yml.erb diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml index 98cc4cc44c8..618c4e3b654 100644 --- a/.gitlab/ci/rails/shared.gitlab-ci.yml +++ b/.gitlab/ci/rails/shared.gitlab-ci.yml @@ -92,7 +92,7 @@ include: - tmp/capybara/ - log/*.log reports: - junit: ${JUNIT_RESULT_FILE} + junit: "rspec/rspec-*.xml" .rspec-base-migration: script: diff --git a/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue b/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue index 4a7c12e5e51..266950e2769 100644 --- a/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue +++ b/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue @@ -60,11 +60,11 @@ export default { }; }, computed: { - drawerOffsetTop() { + getDrawerHeaderHeight() { // avoid calculating this in advance because it causes layout thrashing // https://gitlab.com/gitlab-org/gitlab/-/issues/331172#note_1269378396 if (!this.showDrawer) return '0'; - return getContentWrapperHeight('.content-wrapper'); + return getContentWrapperHeight(); }, }, mounted() { @@ -81,7 +81,7 @@ export default { </script> <template> <gl-drawer - :header-height="drawerOffsetTop" + :header-height="getDrawerHeaderHeight" :z-index="300" :open="showDrawer && mounted" @close="closeDrawer" diff --git a/app/assets/javascripts/admin/abuse_report/components/report_actions.vue b/app/assets/javascripts/admin/abuse_report/components/report_actions.vue index bb6e022da54..25b22afbf84 100644 --- a/app/assets/javascripts/admin/abuse_report/components/report_actions.vue +++ b/app/assets/javascripts/admin/abuse_report/components/report_actions.vue @@ -56,9 +56,9 @@ export default { }; }, computed: { - drawerOffsetTop() { + getDrawerHeaderHeight() { if (!this.showActionsDrawer || gon.use_new_navigation) return '0'; - return getContentWrapperHeight('.content-wrapper'); + return getContentWrapperHeight(); }, isFormValid() { return Object.values(this.validationState).every(Boolean); @@ -124,7 +124,7 @@ export default { </gl-button> <gl-drawer :open="showActionsDrawer" - :header-height="drawerOffsetTop" + :header-height="getDrawerHeaderHeight" :z-index="$options.DRAWER_Z_INDEX" @close="toggleActionsDrawer" > diff --git a/app/assets/javascripts/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue b/app/assets/javascripts/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue index 52c5f5f9b8e..b3befcf1cbc 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue @@ -3,7 +3,6 @@ import { GlDrawer } from '@gitlab/ui'; import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; import { getContentWrapperHeight } from '~/lib/utils/dom_utils'; import { __ } from '~/locale'; -import { DRAWER_CONTAINER_CLASS } from '../job_assistant_drawer/constants'; import FirstPipelineCard from './cards/first_pipeline_card.vue'; import GettingStartedCard from './cards/getting_started_card.vue'; import PipelineConfigReferenceCard from './cards/pipeline_config_reference_card.vue'; @@ -36,8 +35,8 @@ export default { }, }, computed: { - drawerHeightOffset() { - return getContentWrapperHeight(DRAWER_CONTAINER_CLASS); + getDrawerHeaderHeight() { + return getContentWrapperHeight(); }, }, methods: { @@ -49,7 +48,7 @@ export default { </script> <template> <gl-drawer - :header-height="drawerHeightOffset" + :header-height="getDrawerHeaderHeight" :open="isVisible" :z-index="zIndex" @close="closeDrawer" diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/constants.js b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/constants.js index e93a9e84302..e650b88af5f 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/constants.js +++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/constants.js @@ -1,7 +1,5 @@ import { __, s__ } from '~/locale'; -export const DRAWER_CONTAINER_CLASS = '.content-wrapper'; - export const JOB_RULES_WHEN = { onSuccess: { value: 'on_success', diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue index a97b9ed8d5a..9eeaac32e95 100644 --- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue +++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue @@ -6,7 +6,7 @@ import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; import { getContentWrapperHeight } from '~/lib/utils/dom_utils'; import eventHub, { SCROLL_EDITOR_TO_BOTTOM } from '~/ci/pipeline_editor/event_hub'; import getRunnerTags from '../../graphql/queries/runner_tags.query.graphql'; -import { DRAWER_CONTAINER_CLASS, JOB_TEMPLATE, JOB_RULES_WHEN, i18n } from './constants'; +import { JOB_TEMPLATE, JOB_RULES_WHEN, i18n } from './constants'; import { removeEmptyObj, trimFields, validateEmptyValue, validateStartIn } from './utils'; import JobSetupItem from './accordion_items/job_setup_item.vue'; import ImageItem from './accordion_items/image_item.vue'; @@ -79,8 +79,8 @@ export default { }; }); }, - drawerHeightOffset() { - return getContentWrapperHeight(DRAWER_CONTAINER_CLASS); + getDrawerHeaderHeight() { + return getContentWrapperHeight(); }, isJobValid() { return this.isNameValid && this.isScriptValid && this.isStartValid; @@ -173,7 +173,7 @@ export default { <template> <gl-drawer class="job-assistant-drawer" - :header-height="drawerHeightOffset" + :header-height="getDrawerHeaderHeight" :open="isVisible" :z-index="zIndex" @close="closeDrawer" diff --git a/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue b/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue index ff182c61ccf..9cf2572c924 100644 --- a/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue +++ b/app/assets/javascripts/ci/runner/components/registration/platforms_drawer.vue @@ -42,8 +42,8 @@ export default { }; }, computed: { - drawerHeightOffset() { - return getContentWrapperHeight('.content-wrapper'); + getDrawerHeaderHeight() { + return getContentWrapperHeight(); }, architectureOptions() { return platformArchitectures({ platform: this.selectedPlatform }); @@ -86,7 +86,7 @@ export default { <template> <gl-drawer :open="open" - :header-height="drawerHeightOffset" + :header-height="getDrawerHeaderHeight" :z-index="$options.DRAWER_Z_INDEX" data-testid="runner-platforms-drawer" @close="onClose" diff --git a/app/assets/javascripts/diffs/components/shared/findings_drawer.vue b/app/assets/javascripts/diffs/components/shared/findings_drawer.vue index da880c6f3ca..2cffe928d7b 100644 --- a/app/assets/javascripts/diffs/components/shared/findings_drawer.vue +++ b/app/assets/javascripts/diffs/components/shared/findings_drawer.vue @@ -30,8 +30,8 @@ export default { ALLOWED_ATTR: ['href', 'rel'], }, computed: { - drawerOffsetTop() { - return getContentWrapperHeight('.content-wrapper'); + getDrawerHeaderHeight() { + return getContentWrapperHeight(); }, }, DRAWER_Z_INDEX, @@ -47,7 +47,7 @@ export default { </script> <template> <gl-drawer - :header-height="drawerOffsetTop" + :header-height="getDrawerHeaderHeight" :z-index="$options.DRAWER_Z_INDEX" class="findings-drawer" :open="Object.keys(drawer).length !== 0" diff --git a/app/views/admin/sessions/_signin_box.html.haml b/app/views/admin/sessions/_signin_box.html.haml index 15005bb9224..70cad880293 100644 --- a/app/views/admin/sessions/_signin_box.html.haml +++ b/app/views/admin/sessions/_signin_box.html.haml @@ -2,7 +2,7 @@ - if crowd_enabled? .login-box.tab-pane{ id: "crowd", role: 'tabpanel', class: active_when(form_based_auth_provider_has_active_class?(:crowd)) } .login-body - = render 'devise/sessions/new_crowd' + = render 'devise/sessions/new_crowd', render_remember_me: false, submit_message: _('Enter admin mode') - ldap_servers.each_with_index do |server, i| .login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain)) } diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index 293e287371a..983b8160b69 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,13 +1,16 @@ -= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'gl-show-field-errors') do - .form-group.gl-px-5.gl-pt-5 - = label_tag :username, _('Username or email') - = text_field_tag :username, nil, { class: "form-control top", title: _("This field is required."), autofocus: "autofocus", required: true } - .form-group.gl-px-5 - = label_tag :password - = password_field_tag :password, nil, { class: 'form-control gl-form-input js-password', data: { id: 'password', name: 'password' } } - - if remember_me_enabled? - .remember-me.gl-px-5 - %label{ for: "remember_me" } - = check_box_tag :remember_me, '1', false, id: 'remember_me' - %span= _('Remember me') - = submit_tag _("Sign in"), class: "gl-button btn-confirm btn gl-px-5" +- render_remember_me = remember_me_enabled? && local_assigns.fetch(:render_remember_me, true) +- submit_message = local_assigns.fetch(:submit_message, _('Sign in')) + += gitlab_ui_form_for(:crowd, url: omniauth_authorize_path(:user, :crowd), html: { class: 'gl-p-5 gl-show-field-errors', aria: { live: 'assertive' }, data: { testid: 'new_crowd_user' }}) do |f| + .form-group + = f.label :username, _('Username or email') + = f.text_field :username, name: :username, autocomplete: :username, class: 'form-control gl-form-input', title: _('This field is required.'), autofocus: 'autofocus', required: true + .form-group + = f.label :password, _('Password') + = f.password_field :password, name: :password, autocomplete: :current_password, class: 'form-control gl-form-input js-password', data: { id: 'password', name: 'password' } + + - if render_remember_me + = f.gitlab_ui_checkbox_component :remember_me, _('Remember me'), checkbox_options: { name: :remember_me, autocomplete: 'off' } + + = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true) do + = submit_message diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh index 4e73bf48021..8be98cb6346 100644 --- a/scripts/rspec_helpers.sh +++ b/scripts/rspec_helpers.sh @@ -97,15 +97,16 @@ function retrieve_failed_tests() { function rspec_args() { local rspec_opts="${1}" - local junit_report_file="${2:-${JUNIT_RESULT_FILE}}" + local json_report_file="${2:-rspec/rspec-${CI_JOB_ID}.json}" + local junit_report_file="${3:-rspec/rspec-${CI_JOB_ID}.xml}" - echo "-Ispec -rspec_helper --color --failure-exit-code 1 --error-exit-code 2 --format documentation --format RspecJunitFormatter --out ${junit_report_file} ${rspec_opts}" + echo "-Ispec -rspec_helper --color --failure-exit-code 1 --error-exit-code 2 --format documentation --format Support::Formatters::JsonFormatter --out ${json_report_file} --format RspecJunitFormatter --out ${junit_report_file} ${rspec_opts}" } function rspec_simple_job() { export NO_KNAPSACK="1" - local rspec_cmd="bin/rspec $(rspec_args "${1}" "${2}")" + local rspec_cmd="bin/rspec $(rspec_args "${1}" "${2}" "${3}")" echoinfo "Running RSpec command: ${rspec_cmd}" eval "${rspec_cmd}" @@ -114,7 +115,7 @@ function rspec_simple_job() { function rspec_simple_job_with_retry () { local rspec_run_status=0 - rspec_simple_job "${1}" "${2}" || rspec_run_status=$? + rspec_simple_job "${1}" "${2}" "${3}" || rspec_run_status=$? handle_retry_rspec_in_new_process $rspec_run_status } @@ -265,13 +266,16 @@ function retry_failed_rspec_examples() { local default_knapsack_pattern="{,ee/,jh/}spec/{,**/}*_spec.rb" local knapsack_test_file_pattern="${KNAPSACK_TEST_FILE_PATTERN:-$default_knapsack_pattern}" + local json_retry_file="rspec/rspec-retry-${CI_JOB_ID}.json" + local junit_retry_file="rspec/rspec-retry-${CI_JOB_ID}.xml" # Retry only the tests that failed on first try - rspec_simple_job "--only-failures --pattern \"${knapsack_test_file_pattern}\"" "${JUNIT_RETRY_FILE}" + rspec_simple_job "--only-failures --pattern \"${knapsack_test_file_pattern}\"" "${json_retry_file}" "${junit_retry_file}" rspec_run_status=$? - # Merge the JUnit report from retry into the first-try report - junit_merge "${JUNIT_RETRY_FILE}" "${JUNIT_RESULT_FILE}" --update-only + # Merge the reports from retry into the first-try report + scripts/merge-reports "rspec/rspec-${CI_JOB_ID}.json" "${json_retry_file}" + junit_merge "${junit_retry_file}" "rspec/rspec-${CI_JOB_ID}.xml" --update-only if [[ $rspec_run_status -eq 0 ]]; then # The test is flaky because it succeeded after being retried. diff --git a/spec/support/formatters/json_formatter.rb b/spec/support/formatters/json_formatter.rb new file mode 100644 index 00000000000..9ca1a538293 --- /dev/null +++ b/spec/support/formatters/json_formatter.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'rspec/core/formatters' + +module Support + module Formatters + class JsonFormatter < RSpec::Core::Formatters::JsonFormatter + QA_SUPPORT_LOGLINKING_CONST = 'QA::Support::Loglinking' + + RSpec::Core::Formatters.register self, :message, :dump_summary, :stop, :seed, :close + + def dump_profile(profile) + # We don't currently use the profile info. This overrides the base + # implementation so that it's not included. + end + + def stop(example_notification) + # Based on https://github.com/rspec/rspec-core/blob/main/lib/rspec/core/formatters/json_formatter.rb#L35 + # But modified to include full details of multiple exceptions and to provide output similar to + # https://github.com/sj26/rspec_junit_formatter + @output_hash[:examples] = example_notification.notifications.map do |notification| + format_example(notification.example).tap do |hash| + e = notification.example.exception + if e + exceptions = e.respond_to?(:all_exceptions) ? e.all_exceptions : [e] + hash[:exceptions] = exceptions.map do |exception| + hash = { + class: exception.class.name, + message: exception.message, + message_lines: strip_ansi_codes(notification.message_lines), + backtrace: notification.formatted_backtrace + } + + if loglinking + hash.merge!( + correlation_id: exception.message[match_data_after(loglinking::CORRELATION_ID_TITLE)], + sentry_url: exception.message[match_data_after(loglinking::SENTRY_URL_TITLE)], + kibana_discover_url: exception.message[match_data_after(loglinking::KIBANA_DISCOVER_URL_TITLE)], + kibana_dashboard_url: exception.message[match_data_after(loglinking::KIBANA_DASHBOARD_URL_TITLE)] + ) + end + + hash + end + end + end + end + end + + private + + def loglinking + return @loglinking if defined?(@loglinking) + + @loglinking = Object.const_defined?(QA_SUPPORT_LOGLINKING_CONST) && + Object.const_get(QA_SUPPORT_LOGLINKING_CONST, false) + end + + def format_example(example) + file_path, line_number = location_including_shared_examples(example.metadata) + + { + id: example.id, + description: example.description, + full_description: example.full_description, + status: example.execution_result.status.to_s, + file_path: file_path, + line_number: line_number.to_i, + run_time: example.execution_result.run_time, + pending_message: example.execution_result.pending_message, + testcase: example.metadata[:testcase], + quarantine: example.metadata[:quarantine], + screenshot: example.metadata[:screenshot], + product_group: example.metadata[:product_group], + feature_category: example.metadata[:feature_category], + ci_job_url: ENV['CI_JOB_URL'] + } + end + + def location_including_shared_examples(metadata) + if metadata[:shared_group_inclusion_backtrace].empty? + [metadata[:file_path], metadata[:line_number]] + else + # If there are nested shared examples, the outermost location is last in the array + metadata[:shared_group_inclusion_backtrace].last.formatted_inclusion_location.split(':') + end + end + + def strip_ansi_codes(strings) + # The code below is from https://github.com/piotrmurach/pastel/blob/master/lib/pastel/color.rb + modified = Array(strings).map { |string| string.dup.gsub(/\x1b\[{1,2}[0-9;:?]*m/m, '') } + modified.size == 1 ? modified[0] : modified + end + + def match_data_after(title) + /(?<=#{title} ).*/ + end + end + end +end |