diff options
21 files changed, 364 insertions, 222 deletions
diff --git a/.gitlab/ci/package-and-test/variables.gitlab-ci.yml b/.gitlab/ci/package-and-test/variables.gitlab-ci.yml index 36bb2f8d5f6..324092c80f4 100644 --- a/.gitlab/ci/package-and-test/variables.gitlab-ci.yml +++ b/.gitlab/ci/package-and-test/variables.gitlab-ci.yml @@ -4,7 +4,6 @@ variables: RELEASE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/build/omnibus-gitlab-mirror/gitlab-ee:${CI_COMMIT_SHA}" SKIP_REPORT_IN_ISSUES: "true" OMNIBUS_GITLAB_CACHE_UPDATE: "false" - COLORIZED_LOGS: "true" QA_LOG_LEVEL: "info" QA_TESTS: "" QA_FEATURE_FLAGS: "" diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml index 16bc8d39d15..7bb5fa258c2 100644 --- a/.gitlab/ci/review-apps/main.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml @@ -11,6 +11,7 @@ stages: include: - local: .gitlab/ci/global.gitlab-ci.yml - local: .gitlab/ci/rules.gitlab-ci.yml + - local: .gitlab/ci/review-apps/rules.gitlab-ci.yml - local: .gitlab/ci/review-apps/qa.gitlab-ci.yml - local: .gitlab/ci/review-apps/dast.gitlab-ci.yml @@ -97,6 +98,7 @@ review-deploy: - export GITALY_VERSION=$(<GITALY_SERVER_VERSION) - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION) - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt + - echo "QA_GITLAB_URL=${CI_ENVIRONMENT_URL}" > environment.env - *base-before_script script: - check_kube_domain @@ -112,6 +114,8 @@ review-deploy: artifacts: paths: - environment_url.txt + reports: + dotenv: environment.env expire_in: 7 days when: always diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml index e664e556068..ee094a131f7 100644 --- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml @@ -1,6 +1,6 @@ include: - project: gitlab-org/quality/pipeline-common - ref: 0.13.0 + ref: 1.2.2 file: - /ci/allure-report.yml - /ci/knapsack-report.yml @@ -8,7 +8,7 @@ include: .test-variables: variables: QA_GENERATE_ALLURE_REPORT: "true" - COLORIZED_LOGS: "true" + QA_CAN_TEST_PRAEFECT: "false" GITLAB_USERNAME: "root" GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}" GITLAB_ADMIN_USERNAME: "root" @@ -21,49 +21,31 @@ include: - .qa-cache image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3 before_script: - - export QA_GITLAB_URL="$(cat environment_url.txt)" - cd qa && bundle install .review-qa-base: + image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-git-2.33-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 extends: - .use-docker-in-docker - .bundle-base - .test-variables - image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-git-2.33-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 stage: qa needs: - review-deploy - download-knapsack-report variables: - DOCKER_HOST: tcp://docker:2376 - DOCKER_TLS_CERTDIR: /certs - DOCKER_CERT_PATH: /certs/client - DOCKER_TLS_VERIFY: 1 GIT_LFS_SKIP_SMUDGE: 1 WD_INSTALL_DIR: /usr/local/bin - before_script: - - !reference [.bundle-base, before_script] + RSPEC_REPORT_OPTS: --force-color --order random --format documentation --format RspecJunitFormatter --out tmp/rspec.xml script: - export EE_LICENSE="$(cat $REVIEW_APPS_EE_LICENSE_FILE)" - - qa_run_status=0 + - QA_COMMAND="bundle exec bin/qa ${QA_SCENARIO} ${QA_GITLAB_URL} -- ${QA_TESTS} ${RSPEC_REPORT_OPTS}" + - echo "Running - '${QA_COMMAND}'" + - eval "$QA_COMMAND" + after_script: - | - bundle exec rake "knapsack:rspec[\ - ${RSPEC_TAGS} \ - --tag ~orchestrated \ - --tag ~transient \ - --tag ~skip_signup_disabled \ - --tag ~requires_git_protocol_v2 \ - --tag ~requires_praefect \ - --force-color \ - --order random \ - --format documentation \ - --format RspecJunitFormatter --out tmp/rspec.xml \ - ]" || qa_run_status=$? - - if [ ${qa_run_status} -ne 0 ]; then - release_sha=$(echo "${CI_COMMIT_SHA}" | cut -c1-11); - echo "Errors can be found at https://sentry.gitlab.net/gitlab/gitlab-review-apps/releases/${release_sha}/all-events/."; - fi - - exit ${qa_run_status} + echo "Sentry errors for the current review-app test run can be found via following url:" + echo "https://sentry.gitlab.net/gitlab/gitlab-review-apps/releases/$(echo "${CI_COMMIT_SHA}" | cut -c1-11)/all-events/." artifacts: paths: - qa/tmp @@ -72,20 +54,11 @@ include: expire_in: 7 days when: always -.allure-report-base: - extends: .generate-allure-report-base - stage: post-qa - variables: - GITLAB_AUTH_TOKEN: $GITLAB_QA_MR_ALLURE_REPORT_TOKEN - ALLURE_PROJECT_PATH: $CI_PROJECT_PATH - ALLURE_MERGE_REQUEST_IID: $CI_MERGE_REQUEST_IID - ALLURE_RESULTS_GLOB: qa/tmp/allure-results/* - # Store knapsack report as artifact so the same report is reused across all jobs download-knapsack-report: extends: - .bundle-base - - .review:rules:review-qa-reliable + - .rules:qa-framework-changes-or-review-scenarios stage: prepare script: - bundle exec rake "knapsack:download[qa]" @@ -98,30 +71,39 @@ download-knapsack-report: review-qa-smoke: extends: - .review-qa-base - - .review:rules:review-qa-smoke - retry: 1 + - .rules:qa-smoke variables: + QA_SCENARIO: Test::Instance::Smoke QA_RUN_TYPE: review-qa-smoke - RSPEC_TAGS: --tag smoke + retry: 1 -review-qa-reliable: +review-qa-blocking: extends: - .review-qa-base - - .review:rules:review-qa-reliable + - .rules:qa-blocking + variables: + QA_SCENARIO: Test::Instance::ReviewBlocking + QA_RUN_TYPE: review-qa-blocking retry: 1 +review-qa-blocking-parallel: + extends: + - review-qa-blocking + - .rules:qa-blocking-parallel parallel: 10 - variables: - QA_RUN_TYPE: review-qa-reliable - RSPEC_TAGS: --tag reliable --tag sanity_feature_flags -review-qa-all: +review-qa-non-blocking: extends: - .review-qa-base - - .review:rules:review-qa-all - parallel: 5 + - .rules:qa-non-blocking variables: - QA_RUN_TYPE: review-qa-all - RSPEC_TAGS: --tag ~reliable --tag ~smoke --tag ~sanity_feature_flags + QA_SCENARIO: Test::Instance::ReviewNonBlocking + QA_RUN_TYPE: review-qa-non-blocking + allow_failure: true +review-qa-non-blocking-parallel: + extends: + - review-qa-non-blocking + - .rules:qa-non-blocking-parallel + parallel: 5 review-performance: extends: @@ -149,27 +131,20 @@ review-performance: performance: performance.json expire_in: 31d -# Generate single report for both smoke and reliable test jobs -# Both job types are essentially the same: -# * always executed -# * always blocking -allure-report-qa-blocking: +e2e-test-report: extends: - - .allure-report-base - - .review:rules:review-qa-blocking-report - needs: - - review-qa-smoke - - review-qa-reliable - variables: - ALLURE_JOB_NAME: e2e-review-qa-blocking - -allure-report-qa-all: - extends: - - .allure-report-base - - .review:rules:review-qa-all-report - needs: ["review-qa-all"] + - .generate-allure-report-base + - .rules:qa-framework-changes-or-review-scenarios + stage: post-qa variables: - ALLURE_JOB_NAME: e2e-review-qa-all + ALLURE_JOB_NAME: e2e-review-qa + ALLURE_PROJECT_PATH: $CI_PROJECT_PATH + ALLURE_RESULTS_GLOB: qa/tmp/allure-results/* + ALLURE_MERGE_REQUEST_IID: $CI_MERGE_REQUEST_IID + GITLAB_AUTH_TOKEN: $GITLAB_QA_MR_ALLURE_REPORT_TOKEN + GIT_STRATEGY: none + allow_failure: true + when: always upload-knapsack-report: extends: @@ -182,13 +157,13 @@ upload-knapsack-report: delete-test-resources: extends: - .bundle-base - - .review:rules:review-qa-cleanup + - .rules:qa-framework-changes-or-review-scenarios stage: post-qa variables: QA_TEST_RESOURCES_FILE_PATTERN: $CI_PROJECT_DIR/qa/tmp/test-resources-*.json GITLAB_QA_ACCESS_TOKEN: $REVIEW_APPS_ROOT_TOKEN - COLORIZED_LOGS: "true" script: - export GITLAB_ADDRESS="$QA_GITLAB_URL" - bundle exec rake "test_resources:delete[$QA_TEST_RESOURCES_FILE_PATTERN]" allow_failure: true + when: always diff --git a/.gitlab/ci/review-apps/rules.gitlab-ci.yml b/.gitlab/ci/review-apps/rules.gitlab-ci.yml new file mode 100644 index 00000000000..86e46a953af --- /dev/null +++ b/.gitlab/ci/review-apps/rules.gitlab-ci.yml @@ -0,0 +1,80 @@ +# Specific specs passed +.specific-specs: &specific-specs + if: $QA_TESTS != "" + +# No specific specs passed +.all-specs: &all-specs + if: $QA_TESTS == "" + +# No specific specs in mr pipeline +.all-specs-mr: &all-specs-mr + if: $CI_MERGE_REQUEST_IID && $QA_TESTS == "" + when: manual + +# Triggered by change pattern +.app-changes: &app-changes + if: $APP_CHANGE_TRIGGER == "true" + +# QA framework changes present +.qa-framework-changes: &qa-framework-changes + if: $QA_FRAMEWORK_CHANGES == "true" + +.never-when-qa-framework-changes-or-no-specific-specs: + - <<: *qa-framework-changes + when: never + - <<: *all-specs + when: never + +.never-when-specific-specs-always-when-qa-framework-changes: + - <<: *specific-specs + when: never + - *qa-framework-changes + +# ------------------------------------------ +# Test +# ------------------------------------------ +.rules:qa-smoke: + rules: + # always trigger smoke suite if review pipeline got triggered by specific changes in application code + - <<: *app-changes + variables: + QA_TESTS: "" # unset QA_TESTS even if specific tests were inferred from stage label + - *qa-framework-changes + - if: $QA_SUITES =~ /Test::Instance::Smoke/ + +.rules:qa-blocking: + rules: + - <<: *app-changes + when: never + - !reference [.never-when-qa-framework-changes-or-no-specific-specs] + - if: $QA_SUITES =~ /Test::Instance::ReviewBlocking/ +.rules:qa-blocking-parallel: + rules: + # always trigger blocking suite if review pipeline got triggered by specific changes in application code + - <<: *app-changes + variables: + QA_TESTS: "" # unset QA_TESTS even if specific tests were inferred from stage label + - !reference [.never-when-specific-specs-always-when-qa-framework-changes] + - if: $QA_SUITES =~ /Test::Instance::ReviewBlocking/ + +.rules:qa-non-blocking: + rules: + - !reference [.never-when-qa-framework-changes-or-no-specific-specs] + - if: $QA_SUITES =~ /Test::Instance::ReviewNonBlocking/ +.rules:qa-non-blocking-parallel: + rules: + - !reference [.never-when-specific-specs-always-when-qa-framework-changes] + - *all-specs-mr # set full suite to manual when no specific specs passed in mr + - if: $QA_SUITES =~ /Test::Instance::ReviewNonBlocking/ + +# ------------------------------------------ +# Prepare/Report +# ------------------------------------------ +# if no framework changes or QA_SUITES do not contain review scenarios, pipeline will not have e2e test jobs +# so we need to skip knapsack, allure and test resource deletion jobs as well +.rules:qa-framework-changes-or-review-scenarios: + rules: + - *qa-framework-changes + - if: $QA_SUITES =~ /Test::Instance::Smoke/ + - if: $QA_SUITES =~ /Test::Instance::ReviewBlocking/ + - if: $QA_SUITES =~ /Test::Instance::ReviewNonBlocking/ diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index bb6ebaa42d5..30e779936d3 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -1667,6 +1667,9 @@ ################ # Review rules # ################ +.review-change-pattern: &review-change-pattern + APP_CHANGE_TRIGGER: "true" + .review:rules:start-review-app-pipeline: rules: - <<: *if-not-ee @@ -1678,12 +1681,16 @@ changes: *ci-review-patterns - <<: *if-dot-com-gitlab-org-merge-request changes: *frontend-build-patterns + variables: *review-change-pattern - <<: *if-dot-com-gitlab-org-merge-request changes: *controllers-patterns + variables: *review-change-pattern - <<: *if-dot-com-gitlab-org-merge-request changes: *models-patterns + variables: *review-change-pattern - <<: *if-dot-com-gitlab-org-merge-request changes: *lib-gitlab-patterns + variables: *review-change-pattern - <<: *if-dot-com-gitlab-org-merge-request changes: *qa-patterns - <<: *if-dot-com-gitlab-org-merge-request @@ -1717,49 +1724,6 @@ rules: - when: on_success -.review:rules:review-qa-smoke: - rules: - - when: on_success - -# If the needed job isn't allowed to fail, we need to use `when: always` in -# order to keep the job always running after it. -# -# If the needed job is allowed to fail, we need to use both -# `when: on_success` and `when: on_failure` in order to keep -# the job always running after it. -# Not that if the needed job has `when: on_success` we can use `when: always` -# for the depending job. -# -# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76756 - -.review:rules:review-qa-reliable: - rules: - - when: on_success - -# Since `review-qa-reliable` isn't allowed to fail, we need to use `when: always`for `review-qa-reliable-report`. -.review:rules:review-qa-blocking-report: - rules: - - when: always - -.review:rules:review-qa-all: - rules: - - <<: *if-dot-com-gitlab-org-merge-request - changes: *code-patterns - when: manual - allow_failure: true # manual jobs needs to be allowed to fail, otherwise they block the pipeline - - when: on_success - allow_failure: true - -# Since `review-qa-all` is allowed to fail (and potentially manual), we need to use `when: on_success` and `when: on_failure` for `review-qa-all-report`. -.review:rules:review-qa-all-report: - rules: - - when: on_success - - when: on_failure - -.review:rules:review-qa-cleanup: - rules: - - when: always - .review:rules:review-cleanup: rules: - <<: *if-not-ee diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js index c2892fb8dac..ee5d6a22fc3 100644 --- a/app/assets/javascripts/user_popovers.js +++ b/app/assets/javascripts/user_popovers.js @@ -46,6 +46,7 @@ const populateUserInfo = (user) => { pronouns: userData.pronouns, localTime: userData.local_time, isFollowed: userData.is_followed, + state: userData.state, loaded: true, }); } diff --git a/app/assets/javascripts/vue_shared/components/user_popover/constants.js b/app/assets/javascripts/vue_shared/components/user_popover/constants.js index 1d49aefd297..bcbe72b4b4f 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/constants.js +++ b/app/assets/javascripts/vue_shared/components/user_popover/constants.js @@ -1 +1,14 @@ +import { __ } from '~/locale'; + export const USER_POPOVER_DELAY = 200; +export const I18N_ERROR_FOLLOW = __( + 'An error occurred while trying to follow this user, please try again.', +); +export const I18N_ERROR_UNFOLLOW = __( + 'An error occurred while trying to unfollow this user, please try again.', +); +export const I18N_USER_BLOCKED = __('User is blocked'); +export const I18N_USER_BUSY = __('Busy'); +export const I18N_USER_LEARN = __('Learn more about %{name}'); +export const I18N_USER_FOLLOW = __('Follow'); +export const I18N_USER_UNFOLLOW = __('Unfollow'); diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index 2b9804796ae..4b39a8e45bb 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -9,23 +9,31 @@ import { GlButton, GlAvatarLabeled, } from '@gitlab/ui'; -import { __ } from '~/locale'; import { glEmojiTag } from '~/emoji'; import createFlash from '~/flash'; import { followUser, unfollowUser } from '~/rest_api'; import { isUserBusy } from '~/set_status_modal/utils'; import Tracking from '~/tracking'; -import { USER_POPOVER_DELAY } from './constants'; +import { + I18N_ERROR_FOLLOW, + I18N_ERROR_UNFOLLOW, + I18N_USER_BLOCKED, + I18N_USER_BUSY, + I18N_USER_LEARN, + I18N_USER_FOLLOW, + I18N_USER_UNFOLLOW, + USER_POPOVER_DELAY, +} from './constants'; const MAX_SKELETON_LINES = 4; export default { name: 'UserPopover', maxSkeletonLines: MAX_SKELETON_LINES, + I18N_USER_BLOCKED, + I18N_USER_BUSY, + I18N_USER_LEARN, USER_POPOVER_DELAY, - i18n: { - busy: __('Busy'), - }, components: { GlIcon, GlLink, @@ -94,7 +102,7 @@ export default { toggleFollowButtonText() { if (this.toggleFollowLoading) return null; - return this.user?.isFollowed ? __('Unfollow') : __('Follow'); + return this.user?.isFollowed ? I18N_USER_UNFOLLOW : I18N_USER_FOLLOW; }, toggleFollowButtonVariant() { return this.user?.isFollowed ? 'default' : 'confirm'; @@ -102,6 +110,9 @@ export default { hasPronouns() { return Boolean(this.user?.pronouns?.trim()); }, + isBlocked() { + return this.user?.state === 'blocked'; + }, isBusy() { return isUserBusy(this.availabilityStatus); }, @@ -129,7 +140,7 @@ export default { this.$emit('follow'); } catch (error) { createFlash({ - message: __('An error occurred while trying to follow this user, please try again.'), + message: I18N_ERROR_FOLLOW, error, captureError: true, }); @@ -149,7 +160,7 @@ export default { this.$emit('unfollow'); } catch (error) { createFlash({ - message: __('An error occurred while trying to unfollow this user, please try again.'), + message: I18N_ERROR_UNFOLLOW, error, captureError: true, }); @@ -189,16 +200,21 @@ export default { :label="user.name" :sub-label="username" > - <gl-button - v-if="shouldRenderToggleFollowButton" - class="gl-mt-3 gl-align-self-start" - :variant="toggleFollowButtonVariant" - :loading="toggleFollowLoading" - size="small" - data-testid="toggle-follow-button" - @click="toggleFollow" - >{{ toggleFollowButtonText }}</gl-button - > + <template v-if="isBlocked"> + <span class="gl-mt-4 gl-font-style-italic">{{ $options.I18N_USER_BLOCKED }}</span> + </template> + <template v-else> + <gl-button + v-if="shouldRenderToggleFollowButton" + class="gl-mt-3 gl-align-self-start" + :variant="toggleFollowButtonVariant" + :loading="toggleFollowLoading" + size="small" + data-testid="toggle-follow-button" + @click="toggleFollow" + >{{ toggleFollowButtonText }}</gl-button + > + </template> <template #meta> <span @@ -208,7 +224,7 @@ export default { >({{ user.pronouns }})</span > <span v-if="isBusy" class="gl-text-gray-500 gl-font-sm gl-font-weight-normal gl-p-1" - >({{ $options.i18n.busy }})</span + >({{ $options.I18N_USER_BUSY }})</span > </template> </gl-avatar-labeled> @@ -223,39 +239,41 @@ export default { /> </template> <template v-else> - <div class="gl-text-gray-500"> - <div v-if="user.bio" class="gl-display-flex gl-mb-2"> - <gl-icon name="profile" class="gl-flex-shrink-0" /> - <span ref="bio" class="gl-ml-2">{{ user.bio }}</span> + <template v-if="!isBlocked"> + <div class="gl-text-gray-500"> + <div v-if="user.bio" class="gl-display-flex gl-mb-2"> + <gl-icon name="profile" class="gl-flex-shrink-0" /> + <span ref="bio" class="gl-ml-2">{{ user.bio }}</span> + </div> + <div v-if="user.workInformation" class="gl-display-flex gl-mb-2"> + <gl-icon name="work" class="gl-flex-shrink-0" /> + <span ref="workInformation" class="gl-ml-2">{{ user.workInformation }}</span> + </div> + <div v-if="user.location" class="gl-display-flex gl-mb-2"> + <gl-icon name="location" class="gl-flex-shrink-0" /> + <span class="gl-ml-2">{{ user.location }}</span> + </div> + <div + v-if="user.localTime && !user.bot" + class="gl-display-flex gl-mb-2" + data-testid="user-popover-local-time" + > + <gl-icon name="clock" class="gl-flex-shrink-0" /> + <span class="gl-ml-2">{{ user.localTime }}</span> + </div> </div> - <div v-if="user.workInformation" class="gl-display-flex gl-mb-2"> - <gl-icon name="work" class="gl-flex-shrink-0" /> - <span ref="workInformation" class="gl-ml-2">{{ user.workInformation }}</span> + <div v-if="statusHtml" class="gl-mb-2" data-testid="user-popover-status"> + <span v-safe-html:[$options.safeHtmlConfig]="statusHtml"></span> </div> - <div v-if="user.location" class="gl-display-flex gl-mb-2"> - <gl-icon name="location" class="gl-flex-shrink-0" /> - <span class="gl-ml-2">{{ user.location }}</span> + <div v-if="user.bot && user.websiteUrl" class="gl-text-blue-500"> + <gl-icon name="question" /> + <gl-link data-testid="user-popover-bot-docs-link" :href="user.websiteUrl"> + <gl-sprintf :message="$options.I18N_USER_LEARN"> + <template #name>{{ user.name }}</template> + </gl-sprintf> + </gl-link> </div> - <div - v-if="user.localTime && !user.bot" - class="gl-display-flex gl-mb-2" - data-testid="user-popover-local-time" - > - <gl-icon name="clock" class="gl-flex-shrink-0" /> - <span class="gl-ml-2">{{ user.localTime }}</span> - </div> - </div> - <div v-if="statusHtml" class="gl-mb-2" data-testid="user-popover-status"> - <span v-safe-html:[$options.safeHtmlConfig]="statusHtml"></span> - </div> - <div v-if="user.bot && user.websiteUrl" class="gl-text-blue-500"> - <gl-icon name="question" /> - <gl-link data-testid="user-popover-bot-docs-link" :href="user.websiteUrl"> - <gl-sprintf :message="__('Learn more about %{username}')"> - <template #username>{{ user.name }}</template> - </gl-sprintf> - </gl-link> - </div> + </template> </template> </div> </gl-popover> diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 93806e6de8e..ee219914513 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -2,29 +2,30 @@ = stylesheet_link_tag 'mailers/highlighted_diff_email' %h3 - #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} - at #{link_to(@message.project_name_with_namespace, project_url(@message.project))} + = s_('Notify|%{author_name} %{action_name} %{ref_type} %{ref_name} at %{project_link}').html_safe % {author_name: @message.author_name, action_name: @message.action_name, ref_type: @message.ref_type, ref_name: @message.ref_name, project_link: link_to(@message.project_name_with_namespace, strip_tags(project_url(@message.project)))} - if @message.compare - if @message.reverse_compare? %p - %strong WARNING: - The push did not contain any new commits, but force pushed to delete the commits and changes below. + %strong + = _('WARNING:') + = s_('Notify|The push did not contain any new commits, but force pushed to delete the commits and changes below.') %h4 - = @message.reverse_compare? ? "Deleted commits:" : "Commits:" + = @message.reverse_compare? ? _("Deleted commits:") : _("Commits:") %ul - @message.commits.each do |commit| %li %strong= link_to(commit.short_id, project_commit_url(@message.project, commit)) %div - %span by #{commit.author_name} - %i at #{commit.committed_date.to_s(:iso8601)} + = html_escape(s_('Notify|%{committed_by_start} by %{author_name} %{committed_by_end} %{committed_at_start} at %{committed_date} %{committed_at_end}')) % {committed_by_start: '<span>'.html_safe, author_name: commit.author_name, committed_by_end: '</span>'.html_safe, committed_at_start: '<i>'.html_safe, committed_date: commit.committed_date.to_s(:iso8601), committed_at_end: '</i>'.html_safe} %pre.commit-message = commit.safe_message - %h4 #{pluralize @message.diffs_count, "changed file"}: + %h4 + - changed_files = n_('%d changed file', '%d changed files', @message.diffs_count) % @message.diffs_count + = s_('Notify|%{changed_files}:') % {changed_files: changed_files} %ul - @message.diffs.each do |diff_file| @@ -47,9 +48,11 @@ - unless @message.disable_diffs? - if @message.compare_timeout - %h5 The diff was not included because it is too large. + %h5 + = s_('Notify|The diff was not included because it is too large.') - else - %h4 Changes: + %h4 + = _('Changes:') - @message.diffs.each do |diff_file| - file_hash = hexdigest(diff_file.file_path) %li{ id: file_hash } @@ -57,7 +60,7 @@ - if diff_file.deleted_file? %strong< = diff_file.old_path - deleted + = s_('deleted') - elsif diff_file.renamed_file? %strong< = diff_file.old_path @@ -68,7 +71,7 @@ %strong< = diff_file.new_path - if diff_file.too_large? - The diff for this file was not included because it is too large. + = s_('Notify|The diff for this file was not included because it is too large.') - else %hr - blob = diff_file.blob @@ -76,5 +79,5 @@ %table.code.white = render partial: "projects/diffs/email_line", collection: diff_file.highlighted_diff_lines, as: :line, locals: { diff_file: diff_file } - else - No preview for this file type + = s_('Notify|No preview for this file type') %br diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index d55cf3202a6..293aa3b53bf 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -79,11 +79,11 @@ module Gitlab @action_name ||= case @action when :create - 'pushed new' + s_('Notify|pushed new') when :delete - 'deleted' + s_('Notify|deleted') else - 'pushed to' + s_('Notify|pushed to') end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6eb58c5e575..e432108c84d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7723,6 +7723,9 @@ msgstr "" msgid "Changes to the title have not been saved" msgstr "" +msgid "Changes:" +msgstr "" + msgid "Changing any setting here requires an application restart" msgstr "" @@ -9620,6 +9623,9 @@ msgstr "" msgid "Commits you select appear here. Go to the first tab and select commits to add to this merge request." msgstr "" +msgid "Commits:" +msgstr "" + msgid "Commits|An error occurred while fetching merge requests data." msgstr "" @@ -12717,6 +12723,9 @@ msgstr "" msgid "Deleted chat nickname: %{chat_name}!" msgstr "" +msgid "Deleted commits:" +msgstr "" + msgid "Deleted projects cannot be restored!" msgstr "" @@ -23423,7 +23432,7 @@ msgstr "" msgid "Learn more" msgstr "" -msgid "Learn more about %{username}" +msgid "Learn more about %{name}" msgstr "" msgid "Learn more about Auto DevOps" @@ -27039,12 +27048,21 @@ msgstr "" msgid "Notify|%{author_link}'s issue %{issue_reference_link} is due soon." msgstr "" +msgid "Notify|%{author_name} %{action_name} %{ref_type} %{ref_name} at %{project_link}" +msgstr "" + +msgid "Notify|%{changed_files}:" +msgstr "" + msgid "Notify|%{commit_link} in %{mr_link}" msgstr "" msgid "Notify|%{commits_text} from branch `%{target_branch}`" msgstr "" +msgid "Notify|%{committed_by_start} by %{author_name} %{committed_by_end} %{committed_at_start} at %{committed_date} %{committed_at_end}" +msgstr "" + msgid "Notify|%{invite_email}, now known as %{user_name}, has accepted your invitation to join the %{target_name} %{target_model_name}." msgstr "" @@ -27171,6 +27189,9 @@ msgstr "" msgid "Notify|New issue: %{project_issue_url}" msgstr "" +msgid "Notify|No preview for this file type" +msgstr "" + msgid "Notify|Pipeline %{pipeline_link} triggered by" msgstr "" @@ -27180,6 +27201,15 @@ msgstr "" msgid "Notify|The Auto DevOps pipeline failed for pipeline %{pipeline_link} and has been disabled for %{project_link}. In order to use the Auto DevOps pipeline with your project, please review the %{supported_langs_link}, adjust your project accordingly, and turn on the Auto DevOps pipeline within your %{settings_link}." msgstr "" +msgid "Notify|The diff for this file was not included because it is too large." +msgstr "" + +msgid "Notify|The diff was not included because it is too large." +msgstr "" + +msgid "Notify|The push did not contain any new commits, but force pushed to delete the commits and changes below." +msgstr "" + msgid "Notify|This issue is due on: %{issue_due_date}" msgstr "" @@ -27204,6 +27234,15 @@ msgstr "" msgid "Notify|currently supported languages" msgstr "" +msgid "Notify|deleted" +msgstr "" + +msgid "Notify|pushed new" +msgstr "" + +msgid "Notify|pushed to" +msgstr "" + msgid "Notify|successfully completed %{jobs} in %{stages}." msgstr "" @@ -43051,6 +43090,9 @@ msgstr "" msgid "User identity was successfully updated." msgstr "" +msgid "User is blocked" +msgstr "" + msgid "User is not allowed to resolve thread" msgstr "" @@ -44194,6 +44236,9 @@ msgstr "" msgid "Vulnerability|View training" msgstr "" +msgid "WARNING:" +msgstr "" + msgid "WARNING: This snippet contains hidden files which might be used to mask malicious behavior. Exercise caution if cloning and executing code from this snippet." msgstr "" diff --git a/qa/qa/scenario/test/instance/blocking.rb b/qa/qa/scenario/test/instance/blocking.rb deleted file mode 100644 index 0f03100f3d5..00000000000 --- a/qa/qa/scenario/test/instance/blocking.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module QA - module Scenario - module Test - module Instance - class Blocking < Template - include Bootable - include SharedAttributes - - tags :reliable, :smoke - end - end - end - end -end diff --git a/qa/qa/scenario/test/instance/review_blocking.rb b/qa/qa/scenario/test/instance/review_blocking.rb new file mode 100644 index 00000000000..cb1b6c9cf10 --- /dev/null +++ b/qa/qa/scenario/test/instance/review_blocking.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Instance + class ReviewBlocking < All + tags :reliable, + :sanity_feature_flags, + :"~orchestrated", + :"~skip_signup_disabled" + end + end + end + end +end diff --git a/qa/qa/scenario/test/instance/review_non_blocking.rb b/qa/qa/scenario/test/instance/review_non_blocking.rb new file mode 100644 index 00000000000..e295171eb0b --- /dev/null +++ b/qa/qa/scenario/test/instance/review_non_blocking.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Instance + class ReviewNonBlocking < All + tags :"~reliable", + :"~smoke", + :"~skip_signup_disabled", + *Specs::Runner::DEFAULT_SKIPPED_TAGS.map { |tag| :"~#{tag}" } + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index ed271533f87..7e4a391c390 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -64,7 +64,7 @@ module QA it( 'comments on an issue with an attachment', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347946', - except: { job: 'review-qa-smoke' } + except: { job: 'review-qa-*' } ) do Page::Project::Issue::Show.perform do |show| show.comment('See attached image for scale', attachment: file_to_attach) diff --git a/qa/qa/tools/ci/non_empty_suites.rb b/qa/qa/tools/ci/non_empty_suites.rb index cdf06da6763..687c11a3e62 100644 --- a/qa/qa/tools/ci/non_empty_suites.rb +++ b/qa/qa/tools/ci/non_empty_suites.rb @@ -15,7 +15,8 @@ module QA { klass: "Test::Instance::All" }, { klass: "Test::Instance::Smoke" }, { klass: "Test::Instance::Reliable" }, - { klass: "Test::Instance::Blocking" }, + { klass: "Test::Instance::ReviewBlocking" }, + { klass: "Test::Instance::ReviewNonBlocking" }, { klass: "Test::Instance::CloudActivation" }, { klass: "Test::Instance::Integrations" }, { klass: "Test::Instance::Jira" }, diff --git a/scripts/generate-e2e-pipeline b/scripts/generate-e2e-pipeline index 8f5046ef32c..f541ae6665c 100755 --- a/scripts/generate-e2e-pipeline +++ b/scripts/generate-e2e-pipeline @@ -21,6 +21,7 @@ fi variables=$(cat <<YML variables: GITLAB_VERSION: "$(cat VERSION)" + COLORIZED_LOGS: "true" QA_TESTS: "$QA_TESTS" QA_FEATURE_FLAGS: "${QA_FEATURE_FLAGS}" QA_FRAMEWORK_CHANGES: "${QA_FRAMEWORK_CHANGES:-false}" @@ -29,13 +30,10 @@ YML ) echo "Using .gitlab/ci/review-apps/main.gitlab-ci.yml and .gitlab/ci/package-and-test/main.gitlab-ci.yml" - cp .gitlab/ci/review-apps/main.gitlab-ci.yml "$REVIEW_PIPELINE_YML" echo "$variables" >>"$REVIEW_PIPELINE_YML" -echo "Successfully generated review-app pipeline with following variables section:" -echo "$variables" - cp .gitlab/ci/package-and-test/main.gitlab-ci.yml "$OMNIBUS_PIPELINE_YML" echo "$variables" >>"$OMNIBUS_PIPELINE_YML" -echo "Successfully generated package-and-test pipeline with following variables section:" + +echo "Successfully generated review-app and package-and-test pipeline with following variables section:" echo "$variables" diff --git a/spec/fast_spec_helper.rb b/spec/fast_spec_helper.rb index 722c1e66f34..db4d9125e6e 100644 --- a/spec/fast_spec_helper.rb +++ b/spec/fast_spec_helper.rb @@ -32,13 +32,6 @@ end ActiveSupport::XmlMini.backend = 'Nokogiri' RSpec.configure do |config| - unless ENV['CI'] - # Allow running `:focus` examples locally, - # falling back to all tests when there is no `:focus` example. - config.filter_run focus: true - config.run_all_when_everything_filtered = true - end - # Makes diffs show entire non-truncated values. config.before(:each, unlimited_max_formatted_output_length: true) do |_example| config.expect_with :rspec do |c| diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js index ce9b7c5f061..6d48000beb0 100644 --- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js @@ -1,8 +1,15 @@ import { GlSkeletonLoader, GlIcon } from '@gitlab/ui'; import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; +import { sprintf } from '~/locale'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { AVAILABILITY_STATUS } from '~/set_status_modal/constants'; import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue'; +import { + I18N_USER_BLOCKED, + I18N_USER_LEARN, + I18N_USER_FOLLOW, + I18N_USER_UNFOLLOW, +} from '~/vue_shared/components/user_popover/constants'; import axios from '~/lib/utils/axios_utils'; import createFlash from '~/flash'; import { followUser, unfollowUser } from '~/api/user_api'; @@ -310,7 +317,9 @@ describe('User Popover Component', () => { const securityBotDocsLink = findSecurityBotDocsLink(); expect(securityBotDocsLink.exists()).toBe(true); expect(securityBotDocsLink.attributes('href')).toBe(SECURITY_BOT_USER.websiteUrl); - expect(securityBotDocsLink.text()).toBe('Learn more about GitLab Security Bot'); + expect(securityBotDocsLink.text()).toBe( + sprintf(I18N_USER_LEARN, { name: SECURITY_BOT_USER.name }), + ); }); it("does not show a link to the bot's documentation if there is no website_url", () => { @@ -320,9 +329,10 @@ describe('User Popover Component', () => { }); it("doesn't escape user's name", () => { - createWrapper({ user: { ...SECURITY_BOT_USER, name: '%<>\';"' } }); + const name = '%<>\';"'; + createWrapper({ user: { ...SECURITY_BOT_USER, name } }); const securityBotDocsLink = findSecurityBotDocsLink(); - expect(securityBotDocsLink.text()).toBe('Learn more about %<>\';"'); + expect(securityBotDocsLink.text()).toBe(sprintf(I18N_USER_LEARN, { name }, false)); }); it('does not display local time', () => { @@ -336,7 +346,7 @@ describe('User Popover Component', () => { beforeEach(() => createWrapper()); it('renders the Follow button with the correct variant', () => { - expect(findToggleFollowButton().text()).toBe('Follow'); + expect(findToggleFollowButton().text()).toBe(I18N_USER_FOLLOW); expect(findToggleFollowButton().props('variant')).toBe('confirm'); }); @@ -387,7 +397,7 @@ describe('User Popover Component', () => { beforeEach(() => createWrapper({ user: { ...DEFAULT_PROPS.user, isFollowed: true } })); it('renders the Unfollow button with the correct variant', () => { - expect(findToggleFollowButton().text()).toBe('Unfollow'); + expect(findToggleFollowButton().text()).toBe(I18N_USER_UNFOLLOW); expect(findToggleFollowButton().props('variant')).toBe('default'); }); @@ -441,6 +451,25 @@ describe('User Popover Component', () => { }); }); + describe('when the user is blocked', () => { + const bio = 'My super interesting bio'; + const status = 'My status'; + beforeEach(() => + createWrapper({ + user: { ...DEFAULT_PROPS.user, state: 'blocked', bio, status: { message_html: status } }, + }), + ); + + it('renders warning', () => { + expect(wrapper.text()).toContain(I18N_USER_BLOCKED); + }); + + it("doesn't show other information", () => { + expect(wrapper.text()).not.toContain(bio); + expect(wrapper.text()).not.toContain(status); + }); + }); + describe('when API does not support `isFollowed`', () => { beforeEach(() => { const user = { diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 18820eefe31..a40f19ecf7c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -36,6 +36,7 @@ require 'rspec-parameterized' require 'shoulda/matchers' require 'test_prof/recipes/rspec/let_it_be' require 'test_prof/factory_default' +require 'test_prof/factory_prof/nate_heckler' require 'parslet/rig/rspec' rspec_profiling_is_configured = @@ -109,11 +110,6 @@ RSpec.configure do |config| warn `curl -s -o log/goroutines.log http://localhost:9236/debug/pprof/goroutine?debug=2` end end - else - # Allow running `:focus` examples locally, - # falling back to all tests when there is no `:focus` example. - config.filter_run focus: true - config.run_all_when_everything_filtered = true end # Attempt to troubleshoot https://gitlab.com/gitlab-org/gitlab/-/issues/351531 diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb index 2d37501f7c6..6795d2f6d2a 100644 --- a/spec/support/rspec.rb +++ b/spec/support/rspec.rb @@ -14,6 +14,13 @@ RSpec.configure do |config| # Re-run failures locally with `--only-failures` config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', './spec/examples.txt') + unless ENV['CI'] + # Allow running `:focus` examples locally, + # falling back to all tests when there is no `:focus` example. + config.filter_run focus: true + config.run_all_when_everything_filtered = true + end + config.mock_with :rspec do |mocks| mocks.verify_doubled_constant_names = true end |