Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-30 12:12:25 +00:00
parent 0c918eb567
commit 60273ebb30
124 changed files with 2366 additions and 1583 deletions

View File

@ -35,17 +35,10 @@ workflow:
# they serve no purpose and will run anyway when the changes are merged.
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^release-tools\/\d+\.\d+\.\d+-rc\d+$/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^[\d-]+-stable(-ee)?$/ && $CI_PROJECT_PATH == "gitlab-org/gitlab"'
when: never
# For merged result pipelines, set $QA_IMAGE, since $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is only available for merged result pipelines.
# AND
# For merge requests running exclusively in Ruby 3.0
- if: '($CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3/'
variables:
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
RUBY_VERSION: "3.0"
# For merged result pipelines, set $QA_IMAGE, since $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is only available for merged result pipelines.
- if: '($CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train")'
variables:
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
# For merge requests running exclusively in Ruby 3.0
- if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3/'
variables:
@ -122,11 +115,6 @@ variables:
BUILD_ASSETS_IMAGE: "true" # Set it to "false" to disable assets image building, used in `build-assets-image`
SIMPLECOV: "true"
# For the default QA image, we use $CI_COMMIT_SHA as tag since it's always available and we override it for specific workflow.rules (see above)
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_SHA}"
# Default latest tag for particular branch
QA_IMAGE_BRANCH: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
REGISTRY_HOST: "registry.gitlab.com"
REGISTRY_GROUP: "gitlab-org"

View File

@ -8,9 +8,7 @@
# This image is used by:
# - The `review-qa-*` jobs
# - The downstream `omnibus-gitlab-mirror` pipeline triggered by `package-and-qa` so that it doesn't have to rebuild it again.
# The downstream `omnibus-gitlab-mirror` pipeline itself passes the image name to the `gitlab-qa-mirror` pipeline so that
# it can use it instead of inferring an end-to-end imag from the GitLab image built by the downstream `omnibus-gitlab-mirror` pipeline.
# - The `e2e:package-and-test` child pipeline test stage jobs
# See https://docs.gitlab.com/ee/development/testing_guide/end_to_end/index.html#testing-code-in-merge-requests for more details.
build-qa-image:
extends:
@ -18,8 +16,12 @@ build-qa-image:
- .build-images:rules:build-qa-image
stage: build-images
needs: []
variables:
# Default latest tag for particular branch
QA_IMAGE_BRANCH: ${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}
script:
- !reference [.base-image-build, script]
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$CI_COMMIT_SHA}"
- echo $QA_IMAGE
- echo $QA_IMAGE_BRANCH
- |
@ -35,7 +37,7 @@ build-qa-image:
# This image is used by:
# - The `CNG` pipelines (via the `review-build-cng` job): https://gitlab.com/gitlab-org/build/CNG/-/blob/cfc67136d711e1c8c409bf8e57427a644393da2f/.gitlab-ci.yml#L335
# - The `omnibus-gitlab` pipelines (via the `package-and-qa` job): https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/dfd1ad475868fc84e91ab7b5706aa03e46dc3a86/.gitlab-ci.yml#L130
# - The `omnibus-gitlab` pipelines (via the `e2e:package-and-test` job): https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/dfd1ad475868fc84e91ab7b5706aa03e46dc3a86/.gitlab-ci.yml#L130
build-assets-image:
extends:
- .base-image-build

View File

@ -0,0 +1,499 @@
# E2E tests pipeline loaded dynamically by script: scripts/generate-e2e-pipeline
include:
- local: .gitlab/ci/global.gitlab-ci.yml
- local: .gitlab/ci/package-and-test/rules.gitlab-ci.yml
- project: gitlab-org/quality/pipeline-common
ref: 1.0.1
file:
- /ci/base.gitlab-ci.yml
- /ci/allure-report.yml
- /ci/knapsack-report.yml
# ==========================================
# Templates
# ==========================================
.parallel:
parallel: 5
variables:
QA_KNAPSACK_REPORT_PATH: $CI_PROJECT_DIR/qa/knapsack
.ruby-image:
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3
.bundle-install:
extends: .qa-cache
variables:
RUN_WITH_BUNDLE: "true" # adds 'bundle exec' prefix for gitlab-qa commands in pipeline-common job templates
before_script:
- cd qa && bundle install
.update-script:
script:
- export CURRENT_VERSION="$(cat ../VERSION)"
- export QA_COMMAND="bundle exec gitlab-qa Test::Omnibus::UpdateFromPrevious $RELEASE $CURRENT_VERSION $UPDATE_TYPE -- $QA_RSPEC_TAGS $RSPEC_REPORT_OPTS"
- echo "Running - '$QA_COMMAND'"
- eval "$QA_COMMAND"
.manual-qa:
stage: test
allow_failure: true
needs:
- trigger-omnibus
.qa:
extends:
- .use-docker-in-docker
- .qa-base
- .bundle-install
- .gitlab-qa-report
stage: test
tags:
- e2e
needs:
- trigger-omnibus
- download-knapsack-report
variables:
QA_GENERATE_ALLURE_REPORT: "true"
QA_CAN_TEST_PRAEFECT: "false"
QA_INTERCEPT_REQUESTS: "true"
QA_RUN_TYPE: e2e-package-and-test
TEST_LICENSE_MODE: $QA_TEST_LICENSE_MODE
EE_LICENSE: $QA_EE_LICENSE
GITHUB_ACCESS_TOKEN: $QA_GITHUB_ACCESS_TOKEN
# ==========================================
# Prepare stage
# ==========================================
trigger-omnibus:
extends:
- .ruby-image
- .rules:prepare
stage: .pre
before_script:
- source scripts/utils.sh
- install_gitlab_gem
script:
- ./scripts/trigger-build.rb omnibus
download-knapsack-report:
extends:
- .ruby-image
- .bundle-install
- .rules:prepare
stage: .pre
script:
- bundle exec rake "knapsack:download[test]"
allow_failure: true
artifacts:
paths:
- qa/knapsack/ee-*.json
expire_in: 1 day
# ==========================================
# Test stage
# ==========================================
# ------------------------------------------
# Manual jobs
# ------------------------------------------
# Run manual quarantine job
# this job requires passing QA_SCENARIO variable
# and optionally QA_TESTS to run specific quarantined tests
_ee:quarantine:
extends:
- .qa
- .manual-qa
- .rules:test:quarantine
variables:
QA_RSPEC_TAGS: --tag quarantine
# ------------------------------------------
# FF changes
# ------------------------------------------
# Run specs with feature flags set to the opposite of the default state
ee:instance-parallel-ff-inverse:
extends:
- .qa
- .parallel
variables:
QA_SCENARIO: Test::Instance::Image
QA_KNAPSACK_REPORT_NAME: ee-instance-parallel
GITLAB_QA_OPTS: --set-feature-flags $QA_FEATURE_FLAGS
rules:
- !reference [.rules:test:feature-flags-deleted, rules] # skip job when only change is ff deletion
- !reference [.rules:test:feature-flags-set, rules]
# ------------------------------------------
# Jobs with parallel variant
# ------------------------------------------
ee:instance:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
rules:
- !reference [.rules:test:qa-non-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:instance-parallel:
extends:
- .parallel
- ee:instance
rules:
- !reference [.rules:test:feature-flags-set, rules] # always run instance-parallel to validate ff change
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:praefect:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::Praefect
QA_CAN_TEST_PRAEFECT: "true"
rules:
- !reference [.rules:test:qa-non-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:praefect-parallel:
extends:
- .parallel
- ee:praefect
rules:
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:relative-url:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::RelativeUrl
rules:
- !reference [.rules:test:qa-non-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:relative-url-parallel:
extends:
- .parallel
- ee:relative-url
rules:
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:decomposition-single-db:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
GITLAB_QA_OPTS: --omnibus-config decomposition_single_db
rules:
- !reference [.rules:test:qa-non-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:decomposition-single-db-parallel:
extends:
- .parallel
- ee:decomposition-single-db
rules:
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:decomposition-multiple-db:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db
rules:
- !reference [.rules:test:qa-non-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:decomposition-multiple-db-parallel:
extends:
- .parallel
- ee:decomposition-multiple-db
rules:
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
# ------------------------------------------
# Non parallel jobs
# ------------------------------------------
ee:update-minor:
extends:
- .qa
- .update-script
variables:
UPDATE_TYPE: minor
QA_RSPEC_TAGS: --tag smoke
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::Smoke/
ee:update-major:
extends:
- .qa
- .update-script
variables:
UPDATE_TYPE: major
QA_RSPEC_TAGS: --tag smoke
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::Smoke/
ee:gitaly-cluster:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::GitalyCluster
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::GitalyCluster/
ee:group-saml:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::GroupSAML
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::GroupSAML/
ee:instance-saml:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::InstanceSAML
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::InstanceSAML/
ee:jira:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::Jira
JIRA_ADMIN_USERNAME: $QA_JIRA_ADMIN_USERNAME
JIRA_ADMIN_PASSWORD: $QA_JIRA_ADMIN_PASSWORD
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::Jira/
ee:ldap-no-server:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::LDAPNoServer
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::LDAPNoServer/
ee:ldap-tls:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::LDAPTLS
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::LDAPTLS/
ee:ldap-no-tls:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::LDAPNoTLS
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::LDAPNoTLS/
ee:mtls:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::MTLS
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::MTLS/
ee:mattermost:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::Mattermost
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::Mattermost/
ee:registry:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::Registry
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::Registry/
ee:registry-with-cdn:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::RegistryWithCDN
GCS_CDN_BUCKET_NAME: $QA_GCS_CDN_BUCKET_NAME
GOOGLE_CDN_LB: $QA_GOOGLE_CDN_LB
GOOGLE_CDN_JSON_KEY: $QA_GOOGLE_CDN_JSON_KEY
GOOGLE_CDN_SIGNURL_KEY: $QA_GOOGLE_CDN_SIGNURL_KEY
GOOGLE_CDN_SIGNURL_KEY_NAME: $QA_GOOGLE_CDN_SIGNURL_KEY_NAME
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::RegistryWithCDN/
ee:repository-storage:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::RepositoryStorage
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::RepositoryStorage/
ee:service-ping-disabled:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::ServicePingDisabled
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::ServicePingDisabled/
ee:smtp:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::SMTP
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::SMTP/
ee:cloud-activation:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag cloud_activation
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::CloudActivation/
ee:large-setup:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag can_use_large_setup
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::LargeSetup/
ee:metrics:
extends: .qa
variables:
QA_SCENARIO: Test::Integration::Metrics
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::Metrics/
ee:packages:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag packages
GITLAB_QA_OPTS: --omnibus-config packages
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::Packages/
ee:object-storage:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag object_storage
GITLAB_QA_OPTS: --omnibus-config object_storage
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::ObjectStorage/
ee:object-storage-aws:
extends: ee:object-storage
variables:
AWS_S3_ACCESS_KEY: $QA_AWS_S3_ACCESS_KEY
AWS_S3_BUCKET_NAME: $QA_AWS_S3_BUCKET_NAME
AWS_S3_KEY_ID: $QA_AWS_S3_KEY_ID
AWS_S3_REGION: $QA_AWS_S3_REGION
GITLAB_QA_OPTS: --omnibus-config object_storage_aws
ee:object-storage-gcs:
extends: ee:object-storage
variables:
GCS_BUCKET_NAME: $QA_GCS_BUCKET_NAME
GOOGLE_PROJECT: $QA_GOOGLE_PROJECT
GOOGLE_JSON_KEY: $QA_GOOGLE_JSON_KEY
GOOGLE_CLIENT_EMAIL: $QA_GOOGLE_CLIENT_EMAIL
GITLAB_QA_OPTS: --omnibus-config object_storage_gcs
ee:registry-object-storage-tls:
extends: ee:object-storage-aws
variables:
QA_SCENARIO: Test::Integration::RegistryTLS
QA_RSPEC_TAGS: ""
GITLAB_TLS_CERTIFICATE: $QA_GITLAB_TLS_CERTIFICATE
GITLAB_QA_OPTS: --omnibus-config registry_object_storage
# ==========================================
# Post test stage
# ==========================================
allure-report:
extends:
- .generate-allure-report-base
- .rules:report:allure-report
stage: .post
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_JOB_NAME: e2e-package-and-test
notify-slack:
extends:
- .notify-slack-qa
- .ruby-image
- .bundle-install
- .rules:report:process-results
stage: .post
variables:
ALLURE_JOB_NAME: package-and-qa
SLACK_ICON_EMOJI: ci_failing
STATUS_SYM: ☠️
STATUS: failed
when: on_failure
script:
- bundle exec gitlab-qa-report --prepare-stage-reports "$CI_PROJECT_DIR/gitlab-qa-run-*/**/rspec-*.xml" # generate summary
- !reference [.notify-slack-qa, script]
upload-knapsack-report:
extends:
- .generate-knapsack-report-base
- .ruby-image
- .bundle-install
- .rules:report:process-results
stage: .post
when: always
relate-test-failures:
stage: .post
extends:
- .ruby-image
- .bundle-install
- .rules:report:process-results
variables:
QA_FAILURES_REPORTING_PROJECT: gitlab-org/gitlab
QA_FAILURES_MAX_DIFF_RATIO: "0.15"
GITLAB_QA_ACCESS_TOKEN: $GITLAB_QA_PRODUCTION_ACCESS_TOKEN
when: on_failure
script:
- |
bundle exec gitlab-qa-report \
--relate-failure-issue "gitlab-qa-run-*/**/rspec-*.json" \
--project "$QA_FAILURES_REPORTING_PROJECT" \
--max-diff-ratio "$QA_FAILURES_MAX_DIFF_RATIO"
generate-test-session:
stage: .post
extends:
- .ruby-image
- .bundle-install
- .rules:report:process-results
variables:
QA_TESTCASE_SESSIONS_PROJECT: gitlab-org/quality/testcase-sessions
GITLAB_QA_ACCESS_TOKEN: $QA_TEST_SESSION_TOKEN
when: always
script:
- |
bundle exec gitlab-qa-report \
--generate-test-session "gitlab-qa-run-*/**/rspec-*.json" \
--project "$QA_TESTCASE_SESSIONS_PROJECT"

View File

@ -0,0 +1,99 @@
# Specific specs passed
.specific-specs: &specific-specs
if: $QA_TESTS != ""
# No specific specs passed
.all-specs: &all-specs
if: $QA_TESTS == ""
# FF changes
.feature-flags-set: &feature-flags-set
if: $QA_FEATURE_FLAGS != ""
# Only deleted feature flags
.feature-flags-deleted: &feature-flags-deleted
if: $QA_FEATURE_FLAGS != "" && $QA_FEATURE_FLAGS !~ /enabled|disabled/
# Manually trigger job on ff changes but with default ff state instead of inverted
.feature-flags-set-manual: &feature-flags-set-manual
<<: *feature-flags-set
when: manual
allow_failure: true
# QA framework changes present
.qa-framework-changes: &qa-framework-changes
if: $QA_FRAMEWORK_CHANGES == "true"
# Process test results (notify failure to slack, create test session report, relate test failures)
.process-test-results: &process-test-results
if: $PROCESS_TEST_RESULTS == "true"
# Selective test execution against omnibus instance have following execution scenarios:
# * only e2e spec files changed - runs only changed specs
# * qa framework changes - runs full test suite
# * feature flag changed - runs full test suite with base gitlab instance configuration with both ff states
# * quarantined e2e spec - skips execution of e2e tests by creating a no-op pipeline
# ------------------------------------------
# Prepare
# ------------------------------------------
.rules:prepare:
rules:
- when: always
# ------------------------------------------
# Test
# ------------------------------------------
.rules:test:quarantine:
rules:
- when: manual
variables:
QA_TESTS: ""
.rules:test:feature-flags-set:
rules:
# unset specific specs if pipeline has feature flag changes and run full suite
- <<: *feature-flags-set
variables:
QA_TESTS: ""
.rules:test:feature-flags-deleted:
rules:
- <<: *feature-flags-deleted
when: never
# parallel and non parallel rules are used for jobs that require parallel execution and thus need to switch
# between parallel and non parallel when only certain specs are executed
.rules:test:qa-non-parallel:
rules:
# always run parallel with full suite when framework changes present or ff state changed
- <<: *qa-framework-changes
when: never
- <<: *all-specs
when: never
- <<: *feature-flags-set
when: never
.rules:test:qa-parallel:
rules:
- *qa-framework-changes
- <<: *specific-specs
when: never
- *feature-flags-set-manual
# general qa job rule for jobs without the need to run in parallel
.rules:test:qa:
rules:
- *qa-framework-changes
- *feature-flags-set-manual
# ------------------------------------------
# Report
# ------------------------------------------
.rules:report:allure-report:
rules:
- when: always
.rules:report:process-results:
rules:
- *process-test-results

View File

@ -0,0 +1,14 @@
# no-op pipeline triggered on quarantine only changes
stages:
- qa
no-op:
image: ${GITLAB_DEPENDENCY_PROXY}alpine:latest
stage: qa
variables:
GIT_STRATEGY: none
script:
- echo "Skipping E2E tests because the MR includes only quarantine changes"
rules:
- when: always

View File

@ -1,5 +1,5 @@
.qa-job-base:
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-2.7:bundler-2.3-chrome-103-docker-20.10.14
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}
extends:
- .default-retry
- .qa-cache
@ -57,7 +57,7 @@ qa:selectors-as-if-foss:
- .qa:rules:as-if-foss
- .as-if-foss
update-qa-cache:
qa:update-qa-cache:
extends:
- .qa-job-base
- .qa-cache-push
@ -66,103 +66,48 @@ update-qa-cache:
script:
- echo "Cache has been updated and ready to be uploaded."
populate-qa-tests-var:
populate-e2e-test-vars:
extends:
- .qa-job-base
- .qa:rules:determine-qa-tests
stage: prepare
variables:
ENV_FILE: $CI_PROJECT_DIR/qa_tests_vars.env
COLORIZED_LOGS: "true"
script:
- bundle exec rake "ci:detect_changes[$ENV_FILE]"
artifacts:
expire_in: 1 day
reports:
dotenv: $ENV_FILE
e2e-test-pipeline-generate:
extends:
- .qa:rules:determine-qa-tests
image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: prepare
script:
- export QA_TESTS=$(scripts/determine-qa-tests --files $CHANGES_FILE --labels "$CI_MERGE_REQUEST_LABELS")
- 'echo "QA_TESTS=$QA_TESTS" >> qa_tests_var.env'
- 'echo "QA_TESTS: $QA_TESTS"'
artifacts:
expire_in: 2d
reports:
dotenv: qa_tests_var.env
paths:
- ${CHANGES_FILE}
- qa_tests_var.env
variables:
CHANGES_FILE: tmp/changed_files.txt
when: on_success
needs:
- detect-tests
.package-and-qa-base:
image: ${GITLAB_DEPENDENCY_PROXY}ruby:${RUBY_VERSION}-alpine
stage: qa
retry: 0
before_script:
- source scripts/utils.sh
- install_gitlab_gem
- tooling/bin/find_change_diffs ${CHANGES_DIFFS_DIR}
script:
- 'echo "QA_TESTS: $QA_TESTS"'
- exit_code=0 && tooling/bin/qa/run_qa_check ${CHANGES_DIFFS_DIR} || exit_code=$?
- echo $exit_code
- |
if [ $exit_code -eq 0 ]; then
./scripts/trigger-build.rb omnibus
elif [ $exit_code -eq 1 ]; then
exit 1
else
echo "Downstream jobs will not be triggered because run_qa_check exited with code: $exit_code"
fi
# These jobs often time out, so temporarily use private runners and a long timeout: https://gitlab.com/gitlab-org/gitlab/-/issues/238563
tags:
- prm
timeout: 4h
needs:
- job: build-qa-image
artifacts: false
- job: build-assets-image
artifacts: false
- job: populate-qa-tests-var
- detect-tests
artifacts:
expire_in: 7d
paths:
- ${CHANGES_DIFFS_DIR}/*
- populate-e2e-test-vars
variables:
CHANGES_DIFFS_DIR: tmp/diffs
ALLURE_JOB_NAME: $CI_JOB_NAME
.package-and-qa-ff-base:
PIPELINE_YML: package-and-test.yml
script:
- |
feature_flags=$(scripts/changed-feature-flags --files $CHANGES_DIFFS_DIR --state $QA_FF_STATE)
if [[ $feature_flags ]]; then
export GITLAB_QA_OPTIONS="--set-feature-flags $feature_flags"
echo $GITLAB_QA_OPTIONS
./scripts/trigger-build.rb omnibus
else
echo "No changed feature flag found to test as $QA_FF_STATE."
fi
- scripts/generate-e2e-pipeline $PIPELINE_YML
artifacts:
expire_in: 1 day
paths:
- $PIPELINE_YML
package-and-qa:
e2e:package-and-test:
extends:
- .package-and-qa-base
- .qa:rules:package-and-qa
package-and-qa-ff-enabled:
extends:
- .package-and-qa-base
- .package-and-qa-ff-base
- .qa:rules:package-and-qa:feature-flags
variables:
QA_FF_STATE: "enabled"
package-and-qa-ff-disabled:
extends:
- .package-and-qa-base
- .package-and-qa-ff-base
- .qa:rules:package-and-qa:feature-flags
variables:
QA_FF_STATE: "disabled"
package-and-qa-ff-deleted:
extends:
- .package-and-qa-base
- .package-and-qa-ff-base
- .qa:rules:package-and-qa:feature-flags
variables:
QA_FF_STATE: "deleted"
stage: qa
when: on_success
needs:
- build-assets-image
- build-qa-image
- e2e-test-pipeline-generate
trigger:
strategy: depend
include:
- artifact: package-and-test.yml
job: e2e-test-pipeline-generate

View File

@ -14,7 +14,7 @@ include:
GITLAB_ADMIN_USERNAME: "root"
GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITLAB_QA_ADMIN_ACCESS_TOKEN: "${REVIEW_APPS_ROOT_TOKEN}"
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
GITHUB_ACCESS_TOKEN: "${QA_GITHUB_ACCESS_TOKEN}"
.bundle-base:
extends:

View File

@ -28,29 +28,21 @@ review-app-pipeline-generate:
stage: prepare
extends:
- .review:rules:start-review-app-pipeline
artifacts:
expire_in: 7d
paths:
- ${CHANGES_DIFFS_DIR}/*
- review-app-pipeline.yml
variables:
CHANGES_DIFFS_DIR: tmp/diffs
before_script:
- source scripts/utils.sh
- install_gitlab_gem
- tooling/bin/find_change_diffs ${CHANGES_DIFFS_DIR}
needs:
- populate-e2e-test-vars
script:
- exit_code=0 && tooling/bin/qa/run_qa_check ${CHANGES_DIFFS_DIR} || exit_code=$?
- |
if [ $exit_code -eq 0 ]; then
echo "Review App will use the full pipeline"
cp .gitlab/ci/review-apps/main.gitlab-ci.yml review-app-pipeline.yml
elif [ $exit_code -eq 2 ]; then
if [ "$QA_SKIP_ALL_TESTS" == "true" ]; then
echo "Skip Review App because the MR includes only quarantine changes"
cp .gitlab/ci/review-apps/skip-qa.gitlab-ci.yml review-app-pipeline.yml
else
exit $exit_code
echo "Review App will use the full pipeline"
cp .gitlab/ci/review-apps/main.gitlab-ci.yml review-app-pipeline.yml
fi
artifacts:
expire_in: 7d
paths:
- review-app-pipeline.yml
start-review-app-pipeline:
extends:

View File

@ -940,6 +940,13 @@
when: never
- <<: *if-merge-request-targeting-stable-branch
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request-manual-ff-package-and-qa
changes: *feature-flag-development-config-patterns
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *feature-flag-development-config-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *nodejs-patterns
allow_failure: true
@ -958,24 +965,13 @@
allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule
allow_failure: true
variables:
SKIP_REPORT_IN_ISSUES: "false"
PROCESS_TEST_RESULTS: "true"
- <<: *if-force-ci
when: manual
allow_failure: true
.qa:rules:package-and-qa:feature-flags:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-labels-pipeline-revert
when: never
- <<: *if-dot-com-gitlab-org-and-security-merge-request-manual-ff-package-and-qa
changes: *feature-flag-development-config-patterns
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *feature-flag-development-config-patterns
allow_failure: true
###############
# Rails rules #
###############

View File

@ -42,4 +42,4 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
[Code reviews and Approvals]: (https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#code-reviews-and-approvals)
[Approval Guidelines]: https://docs.gitlab.com/ee/development/code_review.html#approval-guidelines
[Canonical repository]: https://gitlab.com/gitlab-org/gitlab
[`package-and-qa` build]: https://docs.gitlab.com/ee/development/testing_guide/end_to_end/#using-the-package-and-qa-job
[`e2e:package-and-test` job]: https://docs.gitlab.com/ee/development/testing_guide/end_to_end/#using-the-package-and-test-job

View File

@ -1,103 +0,0 @@
<script>
import { GlToggle, GlFormGroup, GlFormInput } from '@gitlab/ui';
import { isEqual } from 'lodash';
import {
DUPLICATES_TOGGLE_LABEL,
DUPLICATES_SETTING_EXCEPTION_TITLE,
DUPLICATES_SETTINGS_EXCEPTION_LEGEND,
} from '~/packages_and_registries/settings/group/constants';
export default {
name: 'DuplicatesSettings',
i18n: {
DUPLICATES_TOGGLE_LABEL,
DUPLICATES_SETTING_EXCEPTION_TITLE,
DUPLICATES_SETTINGS_EXCEPTION_LEGEND,
},
components: {
GlToggle,
GlFormGroup,
GlFormInput,
},
props: {
loading: {
type: Boolean,
required: false,
default: false,
},
duplicatesAllowed: {
type: Boolean,
default: false,
required: false,
},
duplicateExceptionRegex: {
type: String,
default: '',
required: false,
},
duplicateExceptionRegexError: {
type: String,
default: '',
required: false,
},
modelNames: {
type: Object,
required: true,
validator(value) {
return isEqual(Object.keys(value), ['allowed', 'exception']);
},
},
toggleQaSelector: {
type: String,
required: false,
default: null,
},
labelQaSelector: {
type: String,
required: false,
default: null,
},
},
computed: {
isExceptionRegexValid() {
return !this.duplicateExceptionRegexError;
},
},
methods: {
update(type, value) {
this.$emit('update', { [type]: value });
},
},
};
</script>
<template>
<form>
<gl-toggle
:data-qa-selector="toggleQaSelector"
:label="$options.i18n.DUPLICATES_TOGGLE_LABEL"
:value="!duplicatesAllowed"
:disabled="loading"
@change="update(modelNames.allowed, !$event)"
/>
<gl-form-group
v-if="!duplicatesAllowed"
class="gl-mt-4"
:label="$options.i18n.DUPLICATES_SETTING_EXCEPTION_TITLE"
label-size="sm"
:state="isExceptionRegexValid"
:invalid-feedback="duplicateExceptionRegexError"
:description="$options.i18n.DUPLICATES_SETTINGS_EXCEPTION_LEGEND"
label-for="maven-duplicated-settings-regex-input"
>
<gl-form-input
id="maven-duplicated-settings-regex-input"
:disabled="loading"
size="lg"
:value="duplicateExceptionRegex"
@change="update(modelNames.exception, $event)"
/>
</gl-form-group>
</form>
</template>

View File

@ -1,26 +0,0 @@
<script>
import { s__ } from '~/locale';
import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
export default {
name: 'GenericSettings',
components: {
SettingsTitles,
},
i18n: {
title: s__('PackageRegistry|Generic'),
subTitle: s__('PackageRegistry|Settings for Generic packages'),
},
modelNames: {
allowed: 'genericDuplicatesAllowed',
exception: 'genericDuplicateExceptionRegex',
},
};
</script>
<template>
<div>
<settings-titles :title="$options.i18n.title" :sub-title="$options.i18n.subTitle" />
<slot :model-names="$options.modelNames"></slot>
</div>
</template>

View File

@ -1,26 +0,0 @@
<script>
import { s__ } from '~/locale';
import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
export default {
name: 'MavenSettings',
components: {
SettingsTitles,
},
i18n: {
title: s__('PackageRegistry|Maven'),
subTitle: s__('PackageRegistry|Settings for Maven packages'),
},
modelNames: {
allowed: 'mavenDuplicatesAllowed',
exception: 'mavenDuplicateExceptionRegex',
},
};
</script>
<template>
<div>
<settings-titles :title="$options.i18n.title" :sub-title="$options.i18n.subTitle" />
<slot :model-names="$options.modelNames"></slot>
</div>
</template>

View File

@ -1,26 +0,0 @@
<script>
export default {
name: 'SettingsTitle',
props: {
title: {
type: String,
required: true,
},
subTitle: {
type: String,
required: false,
default: '',
},
},
};
</script>
<template>
<div>
<h5 class="gl-border-b-solid gl-border-b-1 gl-border-gray-200 gl-pb-3">
{{ title }}
</h5>
<p v-if="subTitle">{{ subTitle }}</p>
<slot></slot>
</div>
</template>

View File

@ -49,19 +49,28 @@ export default {
<div
:class="[
$options.EXTENSION_ICON_CLASS[iconName],
{ 'mr-widget-extension-icon gl-w-6': !isLoading && level === 1 },
{ 'gl-w-6': !isLoading && level === 1 },
{ 'gl-p-2': isLoading || level === 1 },
]"
class="gl-rounded-full gl-mr-3 gl-relative gl-p-2"
class="gl-mr-3 gl-p-2"
>
<gl-loading-icon v-if="isLoading" size="sm" inline class="gl-display-block" />
<gl-icon
v-else
:name="$options.EXTENSION_ICON_NAMES[iconName]"
:size="size"
:aria-label="iconAriaLabel"
:data-qa-selector="`status_${iconName}_icon`"
class="gl-display-block"
/>
<div
class="gl-rounded-full gl-relative gl-display-flex"
:class="{ 'mr-widget-extension-icon': !isLoading && level === 1 }"
>
<div class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto">
<div class="gl-display-flex gl-m-auto gl-translate-y-n50">
<gl-loading-icon v-if="isLoading" size="md" inline />
<gl-icon
v-else
:name="$options.EXTENSION_ICON_NAMES[iconName]"
:size="size"
:aria-label="iconAriaLabel"
:data-qa-selector="`status_${iconName}_icon`"
class="gl-display-block"
/>
</div>
</div>
</div>
</div>
</template>

View File

@ -1,11 +1,11 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { GlIcon } from '@gitlab/ui';
import StatusIcon from './extensions/status_icon.vue';
export default {
components: {
CiIcon,
GlLoadingIcon,
StatusIcon,
GlIcon,
},
props: {
status: {
@ -17,22 +17,20 @@ export default {
isLoading() {
return this.status === 'loading';
},
statusObj() {
return {
group: this.status,
icon: `status_${this.status}`,
};
},
},
};
</script>
<template>
<div class="gl-display-flex gl-align-self-start">
<div class="square s24 h-auto d-flex-center gl-mr-3">
<div v-if="isLoading" class="mr-widget-icon gl-display-inline-flex">
<gl-loading-icon size="md" class="mr-loading-icon gl-display-inline-flex" />
</div>
<ci-icon v-else :status="statusObj" :size="24" />
<div class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3">
<div class="gl-display-flex gl-m-auto">
<gl-icon v-if="status === 'merged'" name="merge" :size="16" class="gl-text-blue-500" />
<gl-icon
v-else-if="status === 'closed'"
name="merge-request-close"
:size="16"
class="gl-text-red-500"
/>
<status-icon v-else :is-loading="isLoading" :icon-name="status" :level="1" class="gl-m-0!" />
</div>
</div>
</template>

View File

@ -43,7 +43,7 @@ export default {
<slot></slot>
<div
:class="{ 'gl-flex-direction-column-reverse': !actions.length }"
class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto gl-mt-1"
class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto"
>
<slot name="actions">
<actions v-if="actions.length" :tertiary-buttons="actions" />

View File

@ -34,7 +34,7 @@ export default {
<template>
<div class="mr-widget-body media gl-flex-wrap">
<status-icon status="warning" />
<status-icon status="failed" />
<p class="media-body gl-m-0! gl-font-weight-bold gl-text-black-normal!">
{{ failedText }}
</p>

View File

@ -1,22 +1,18 @@
<script>
import StatusIcon from '../mr_widget_status_icon.vue';
import StateContainer from '../state_container.vue';
export default {
name: 'MRWidgetArchived',
components: {
StatusIcon,
StateContainer,
},
};
</script>
<template>
<div class="mr-widget-body media">
<div class="space-children">
<status-icon status="warning" show-disabled-button />
</div>
<div class="media-body">
<span class="gl-ml-0! gl-text-body! bold">
{{ s__('mrWidget|Merge unavailable: merge requests are read-only on archived projects.') }}
</span>
</div>
</div>
<state-container status="failed">
<span class="gl-font-weight-bold">
{{ s__('mrWidget|Merge unavailable: merge requests are read-only on archived projects.') }}
</span>
</state-container>
</template>

View File

@ -1,5 +1,5 @@
<script>
import { GlSkeletonLoader, GlIcon, GlSprintf } from '@gitlab/ui';
import { GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
import autoMergeEnabledQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/auto_merge_enabled.query.graphql';
import createFlash from '~/flash';
@ -28,7 +28,6 @@ export default {
components: {
MrWidgetAuthor,
GlSkeletonLoader,
GlIcon,
GlSprintf,
StateContainer,
},
@ -168,8 +167,5 @@ export default {
</gl-sprintf>
</h4>
</template>
<template v-if="!loading" #icon>
<gl-icon name="status_scheduled" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" />
</template>
</state-container>
</template>

View File

@ -58,8 +58,8 @@ export default {
};
</script>
<template>
<state-container status="warning" :actions="actions">
<span class="bold gl-ml-0!">
<state-container status="failed" :actions="actions">
<span class="gl-font-weight-bold">
<template v-if="mergeError">{{ mergeError }}</template>
{{ s__('mrWidget|This merge request failed to be merged automatically') }}
</span>

View File

@ -1,20 +1,17 @@
<script>
import StatusIcon from '../mr_widget_status_icon.vue';
import StateContainer from '../state_container.vue';
export default {
name: 'MRWidgetChecking',
components: {
StatusIcon,
StateContainer,
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="loading" />
<div class="media-body space-children">
<span class="gl-ml-0! gl-text-body! bold">
{{ s__('mrWidget|Checking if merge request can be merged…') }}
</span>
</div>
</div>
<state-container status="loading">
<span class="gl-font-weight-bold">
{{ s__('mrWidget|Checking if merge request can be merged…') }}
</span>
</state-container>
</template>

View File

@ -20,7 +20,7 @@ export default {
</script>
<template>
<div class="mr-widget-body media">
<status-icon status="warning" />
<status-icon status="closed" />
<div class="media-body">
<mr-widget-author-time
:action-text="s__('mrWidget|Closed by')"

View File

@ -86,7 +86,7 @@ export default {
};
</script>
<template>
<state-container status="warning" :is-loading="isLoading">
<state-container status="failed" :is-loading="isLoading">
<template #loading>
<gl-skeleton-loader :width="334" :height="30">
<rect x="0" y="7" width="150" height="16" rx="4" />

View File

@ -1,16 +1,14 @@
<script>
import { GlButton } from '@gitlab/ui';
import { stripHtml } from '~/lib/utils/text_utility';
import { sprintf, s__, n__ } from '~/locale';
import eventHub from '../../event_hub';
import StatusIcon from '../mr_widget_status_icon.vue';
import StateContainer from '../state_container.vue';
export default {
name: 'MRWidgetFailedToMerge',
components: {
GlButton,
StatusIcon,
StateContainer,
},
props: {
@ -47,6 +45,16 @@ export default {
this.timer,
);
},
actions() {
return [
{
text: s__('mrWidget|Refresh now'),
onClick: () => this.refresh(),
testId: 'merge-request-failed-refresh-button',
dataQaSelector: 'merge_request_error_content',
},
];
},
},
mounted() {
@ -87,30 +95,18 @@ export default {
};
</script>
<template>
<div class="mr-widget-body media">
<template v-if="isRefreshing">
<status-icon status="loading" />
<span class="media-body bold js-refresh-label"> {{ s__('mrWidget|Refreshing now') }} </span>
</template>
<template v-else>
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
<span class="bold">
<span v-if="mr.mergeError" class="has-error-message" data-testid="merge-error">
{{ mergeError }}
</span>
<span v-else> {{ s__('mrWidget|Merge failed.') }} </span>
<span :class="{ 'has-custom-error': mr.mergeError }"> {{ timerText }} </span>
</span>
<gl-button
size="small"
data-testid="merge-request-failed-refresh-button"
data-qa-selector="merge_request_error_content"
@click="refresh"
>
{{ s__('mrWidget|Refresh now') }}
</gl-button>
</div>
</template>
</div>
<state-container v-if="isRefreshing" status="loading">
<span class="gl-font-weight-bold">
{{ s__('mrWidget|Refreshing now') }}
</span>
</state-container>
<state-container v-else status="failed" :actions="actions">
<span class="gl-font-weight-bold">
<span v-if="mr.mergeError" class="has-error-message" data-testid="merge-error">
{{ mergeError }}
</span>
<span v-else> {{ s__('mrWidget|Merge failed.') }} </span>
<span :class="{ 'has-custom-error': mr.mergeError }"> {{ timerText }} </span>
</span>
</state-container>
</template>

View File

@ -1,5 +1,5 @@
<script>
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { GlTooltipDirective } from '@gitlab/ui';
import api from '~/api';
import createFlash from '~/flash';
import { s__, __ } from '~/locale';
@ -16,7 +16,6 @@ export default {
},
components: {
MrWidgetAuthorTime,
GlIcon,
StateContainer,
},
props: {
@ -49,18 +48,6 @@ export default {
const { sourceBranchRemoved, isRemovingSourceBranch } = this.mr;
return !sourceBranchRemoved && (isRemovingSourceBranch || this.isMakingRequest);
},
shouldShowMergedButtons() {
const {
canRevertInCurrentMR,
canCherryPickInCurrentMR,
revertInForkPath,
cherryPickInForkPath,
} = this.mr;
return (
canRevertInCurrentMR || canCherryPickInCurrentMR || revertInForkPath || cherryPickInForkPath
);
},
revertTitle() {
return s__('mrWidget|Revert this merge request in a new merge request');
},
@ -163,10 +150,7 @@ export default {
};
</script>
<template>
<state-container :actions="actions">
<template #icon>
<gl-icon name="merge" :size="24" class="gl-text-blue-500 gl-mr-3 gl-mt-1" />
</template>
<state-container :actions="actions" status="merged">
<mr-widget-author-time
:action-text="s__('mrWidget|Merged by')"
:author="mr.metrics.mergedBy"

View File

@ -71,10 +71,10 @@ export default {
</script>
<template>
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="warning" />
<status-icon :show-disabled-button="true" status="failed" />
<div class="media-body space-children">
<span class="gl-ml-0! gl-text-body! bold js-branch-text" data-testid="widget-content">
<span class="gl-font-weight-bold js-branch-text" data-testid="widget-content">
<gl-sprintf :message="warning">
<template #code="{ content }">
<code>{{ content }}</code>

View File

@ -11,9 +11,9 @@ export default {
<template>
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="success" />
<status-icon status="success" />
<div class="media-body space-children">
<span class="bold">
<span class="gl-font-weight-bold">
{{
s__(`mrWidget|Ready to be merged automatically.
Ask someone with write access to this repository to merge this request`)

View File

@ -12,9 +12,9 @@ export default {
</script>
<template>
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="warning" />
<status-icon status="failed" />
<div class="media-body space-children">
<span class="gl-ml-0! gl-text-body! bold">
<span class="gl-font-weight-bold">
{{
s__(
`mrWidget|Merge blocked: pipeline must succeed. It's waiting for a manual action to continue.`,

View File

@ -81,13 +81,10 @@ export default {
return 'loading';
}
if (!this.canPushToSourceBranch && !this.rebaseInProgress) {
return 'warning';
return 'failed';
}
return 'success';
},
showDisabledButton() {
return ['failed', 'loading'].includes(this.status);
},
fastForwardMergeText() {
return __('Merge blocked: the source branch must be rebased onto the target branch.');
},

View File

@ -26,9 +26,9 @@ export default {
<template>
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="warning" />
<status-icon status="failed" />
<div class="media-body space-children">
<span class="gl-ml-0! gl-text-body! bold">
<span class="gl-font-weight-bold">
<gl-sprintf :message="$options.i18n.failedMessage">
<template #link="{ content }">
<gl-link :href="troubleshootingDocsPath" target="_blank">

View File

@ -22,7 +22,7 @@ export default {
</script>
<template>
<state-container status="warning">
<state-container status="failed">
<span
class="gl-font-weight-bold gl-md-mr-3 gl-flex-grow-1 gl-ml-0! gl-text-body!"
data-qa-selector="head_mismatch_content"

View File

@ -24,7 +24,7 @@ export default {
</script>
<template>
<state-container status="warning">
<state-container status="failed">
<span
class="gl-ml-3 gl-font-weight-bold gl-w-100 gl-flex-grow-1 gl-md-mr-3 gl-ml-0! gl-text-body!"
>

View File

@ -163,7 +163,7 @@ export default {
</script>
<template>
<state-container status="warning">
<state-container status="failed">
<span class="gl-font-weight-bold gl-ml-0! gl-text-body! gl-flex-grow-1">
{{ __("Merge blocked: merge request must be marked as ready. It's still marked as draft.") }}
</span>

View File

@ -140,6 +140,7 @@ export const EXTENSION_ICON_NAMES = {
neutral: 'status-neutral',
error: 'status-alert',
notice: 'status-alert',
scheduled: 'status-scheduled',
severityCritical: 'severity-critical',
severityHigh: 'severity-high',
severityMedium: 'severity-medium',
@ -155,6 +156,7 @@ export const EXTENSION_ICON_CLASS = {
neutral: 'gl-text-gray-400',
error: 'gl-text-red-500',
notice: 'gl-text-gray-500',
scheduled: 'gl-text-blue-500',
severityCritical: 'gl-text-red-800',
severityHigh: 'gl-text-red-600',
severityMedium: 'gl-text-orange-400',

View File

@ -438,7 +438,7 @@ $tabs-holder-z-index: 250;
.mr-widget-body {
&:not(.mr-widget-body-line-height-1) {
line-height: 28px;
line-height: 24px;
}
@include clearfix;
@ -473,12 +473,6 @@ $tabs-holder-z-index: 250;
margin: 0 0 0 10px;
}
.bold {
font-weight: $gl-font-weight-bold;
color: var(--gray-600, $gray-600);
margin-left: 10px;
}
.state-label {
font-weight: $gl-font-weight-bold;
padding-right: 10px;
@ -488,11 +482,6 @@ $tabs-holder-z-index: 250;
color: var(--red-500, $red-500);
}
.spacing,
.bold {
vertical-align: middle;
}
.dropdown-menu {
li a {
padding: 5px;
@ -619,8 +608,8 @@ $tabs-holder-z-index: 250;
.mr-widget-extension-icon::before {
@include gl-content-empty;
@include gl-absolute;
@include gl-left-0;
@include gl-top-0;
@include gl-left-50p;
@include gl-top-half;
@include gl-opacity-3;
@include gl-border-solid;
@include gl-border-4;
@ -628,24 +617,20 @@ $tabs-holder-z-index: 250;
width: 24px;
height: 24px;
transform: translate(-50%, -50%);
}
.mr-widget-extension-icon::after {
@include gl-content-empty;
@include gl-absolute;
@include gl-rounded-full;
@include gl-left-50p;
@include gl-top-half;
top: 4px;
left: 4px;
width: 16px;
height: 16px;
border: 4px solid currentColor;
}
.mr-widget-extension-icon svg {
position: relative;
top: 2px;
left: 2px;
border: 4px solid;
transform: translate(-50%, -50%);
}
.mr-widget-heading {

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Resolvers
module Projects
class BranchRulesResolver < BaseResolver
type Types::Projects::BranchRuleType.connection_type, null: false
alias_method :project, :object
def resolve(**args)
project.protected_branches
end
end
end
end

View File

@ -510,6 +510,12 @@ module Types
resolver: Resolvers::Projects::ForkTargetsResolver,
description: 'Namespaces in which the current user can fork the project into.'
field :branch_rules,
Types::Projects::BranchRuleType.connection_type,
null: true,
description: "Branch rules configured for the project.",
resolver: Resolvers::Projects::BranchRulesResolver
def timelog_categories
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Types
module Projects
class BranchRuleType < BaseObject
graphql_name 'BranchRule'
description 'List of branch rules for a project, grouped by branch name.'
accepts ::ProtectedBranch
authorize :read_protected_branch
field :name,
type: GraphQL::Types::String,
null: false,
description: 'Branch name, with wildcards, for the branch rules.'
field :created_at,
Types::TimeType,
null: false,
description: 'Timestamp of when the branch rule was created.'
field :updated_at,
Types::TimeType,
null: false,
description: 'Timestamp of when the branch rule was last updated.'
end
end
end

View File

@ -1,7 +0,0 @@
# frozen_string_literal: true
module JavascriptHelper
def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js)
end
end

View File

@ -4,6 +4,7 @@ class ProtectedBranchPolicy < BasePolicy
delegate { @subject.project }
rule { can?(:admin_project) }.policy do
enable :read_protected_branch
enable :create_protected_branch
enable :update_protected_branch
enable :destroy_protected_branch

View File

@ -11,14 +11,9 @@
%p
= _('Projects are where you store your code, access issues, wiki and other features of GitLab.')
- else
.blank-state.gl-display-flex.gl-align-items-center.gl-border-1.gl-border-solid.gl-border-gray-100.gl-rounded-base.gl-mb-5
.blank-state-icon
= custom_icon("add_new_project", size: 50)
.blank-state-body.gl-sm-pl-0.gl-pl-6
%h3.gl-font-size-h2.gl-mt-0
= _('Create a project')
%p
= _('If you are added to a project, it will be displayed here.')
= render Pajamas::AlertComponent.new(variant: :info, alert_options: { class: 'gl-mb-5 gl-w-full' }) do |c|
= c.body do
= _("You see projects here when you're added to a group or project.").html_safe
- if current_user.can_create_group?
= link_to new_group_path, class: link_classes do

View File

@ -528,7 +528,7 @@ supported by consolidated configuration form, refer to the following guides:
| Object storage type | Supported by consolidated configuration? |
|---------------------|------------------------------------------|
| [Backups](../raketasks/backup_gitlab.md#uploading-backups-to-a-remote-cloud-storage) | **{dotted-circle}** No |
| [Backups](../raketasks/backup_gitlab.md#upload-backups-to-a-remote-cloud-storage) | **{dotted-circle}** No |
| [Job artifacts](job_artifacts.md#using-object-storage) including archived job logs | **{check-circle}** Yes |
| [LFS objects](lfs/index.md#storing-lfs-objects-in-remote-object-storage) | **{check-circle}** Yes |
| [Uploads](uploads.md#using-object-storage) | **{check-circle}** Yes |

View File

@ -2207,7 +2207,7 @@ on what features you intend to use:
|Object storage type|Supported by consolidated configuration?|
|-------------------|----------------------------------------|
| [Backups](../../raketasks/backup_gitlab.md#uploading-backups-to-a-remote-cloud-storage) | No |
| [Backups](../../raketasks/backup_gitlab.md#upload-backups-to-a-remote-cloud-storage) | No |
| [Job artifacts](../job_artifacts.md#using-object-storage) including archived job logs | Yes |
| [LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage) | Yes |
| [Uploads](../uploads.md#using-object-storage) | Yes |

View File

@ -2211,7 +2211,7 @@ on what features you intend to use:
|Object storage type|Supported by consolidated configuration?|
|-------------------|----------------------------------------|
| [Backups](../../raketasks/backup_gitlab.md#uploading-backups-to-a-remote-cloud-storage) | No |
| [Backups](../../raketasks/backup_gitlab.md#upload-backups-to-a-remote-cloud-storage) | No |
| [Job artifacts](../job_artifacts.md#using-object-storage) including archived job logs | Yes |
| [LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage) | Yes |
| [Uploads](../uploads.md#using-object-storage) | Yes |

View File

@ -922,7 +922,7 @@ on what features you intend to use:
|Object storage type|Supported by consolidated configuration?|
|-------------------|----------------------------------------|
| [Backups](../../raketasks/backup_gitlab.md#uploading-backups-to-a-remote-cloud-storage) | No |
| [Backups](../../raketasks/backup_gitlab.md#upload-backups-to-a-remote-cloud-storage) | No |
| [Job artifacts](../job_artifacts.md#using-object-storage) including archived job logs | Yes |
| [LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage) | Yes |
| [Uploads](../uploads.md#using-object-storage) | Yes |

View File

@ -2148,7 +2148,7 @@ on what features you intend to use:
|Object storage type|Supported by consolidated configuration?|
|-------------------|----------------------------------------|
| [Backups](../../raketasks/backup_gitlab.md#uploading-backups-to-a-remote-cloud-storage) | No |
| [Backups](../../raketasks/backup_gitlab.md#upload-backups-to-a-remote-cloud-storage) | No |
| [Job artifacts](../job_artifacts.md#using-object-storage) including archived job logs | Yes |
| [LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage) | Yes |
| [Uploads](../uploads.md#using-object-storage) | Yes |

View File

@ -2227,7 +2227,7 @@ on what features you intend to use:
|Object storage type|Supported by consolidated configuration?|
|-------------------|----------------------------------------|
| [Backups](../../raketasks/backup_gitlab.md#uploading-backups-to-a-remote-cloud-storage) | No |
| [Backups](../../raketasks/backup_gitlab.md#upload-backups-to-a-remote-cloud-storage) | No |
| [Job artifacts](../job_artifacts.md#using-object-storage) including archived job logs | Yes |
| [LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage) | Yes |
| [Uploads](../uploads.md#using-object-storage) | Yes |

View File

@ -2148,7 +2148,7 @@ on what features you intend to use:
|Object storage type|Supported by consolidated configuration?|
|-------------------|----------------------------------------|
| [Backups](../../raketasks/backup_gitlab.md#uploading-backups-to-a-remote-cloud-storage) | No |
| [Backups](../../raketasks/backup_gitlab.md#upload-backups-to-a-remote-cloud-storage) | No |
| [Job artifacts](../job_artifacts.md#using-object-storage) including archived job logs | Yes |
| [LFS objects](../lfs/index.md#storing-lfs-objects-in-remote-object-storage) | Yes |
| [Uploads](../uploads.md#using-object-storage) | Yes |

View File

@ -6,28 +6,30 @@ info: For assistance with this What's new page, see https://about.gitlab.com/han
# What's new **(FREE)**
With each monthly release, GitLab includes some of the highlights from the last 10
GitLab versions in the **What's new** feature. To access it:
You can view some of the highlights from the last 10
GitLab versions in the **What's new** feature. It lists new features available in different
[GitLab tiers](https://about.gitlab.com/pricing/).
1. In the top navigation bar, select the **{question}** icon.
All users can see the feature list, but the entries might differ depending on the subscription type:
- Features only available on GitLab.com are not shown to self-managed installations.
- Features only available to self-managed installations are not shown on GitLab.com.
NOTE:
For self-managed installations, the updated **What's new** is included
in the first patch release after a new version, such as `13.10.1`.
## Access What's new
To access the **What's new** feature:
1. On the top bar, select the **{question}** icon.
1. Select **What's new** from the menu.
The **What's new** describes new features available in multiple
[GitLab tiers](https://about.gitlab.com/pricing/). While all users can see the
feature list, the feature list is tailored to your subscription type:
## Configure What's new
- Features only available to self-managed installations are not shown on GitLab.com.
- Features only available on GitLab.com are not shown to self-managed installations.
## Self-managed installations
Due to our release post process, the content for **What's new** is not finalized
when a new version (`.0` release) is cut. The updated **What's new** is included
in the first patch release, such as `13.10.1`.
## Configure What's new variant
You can configure the What's new variant:
You can configure **What's new** to display features based on the tier,
or you can hide it. To configure it:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > Preferences**, then expand **What's new**.
@ -39,4 +41,4 @@ You can configure the What's new variant:
| Enable What's new: Current tier only | What's new presents new features for your current subscription tier, while hiding new features not available to your subscription tier. |
| Disable What's new | What's new is disabled and can no longer be viewed. |
1. Save your changes.
1. Select **Save changes**.

View File

@ -58,6 +58,9 @@ Example response:
"started_at": null,
"status": "success",
"tag": false,
"project": {
"ci_job_token_scope_enabled": false
},
"user": {
"id": 1,
"name": "Administrator",
@ -128,6 +131,9 @@ Example response:
"started_at": null,
"status": "success",
"tag": false,
"project": {
"ci_job_token_scope_enabled": false
},
"user": {
"id": 1,
"name": "Administrator",
@ -226,6 +232,9 @@ Example response:
"created_at": "2016-08-11T11:32:24.456Z",
"started_at": null,
"finished_at": "2016-08-11T11:32:35.145Z",
"project": {
"ci_job_token_scope_enabled": false
},
"user": {
"id": 1,
"name": "Administrator",

View File

@ -68,6 +68,9 @@ Example response:
"started_at": "2019-03-25T12:54:50.082Z",
"finished_at": "2019-03-25T18:55:13.216Z",
"duration": 21623.13423,
"project": {
"ci_job_token_scope_enabled": false
},
"user": {
"id": 1,
"name": "Administrator",
@ -180,6 +183,9 @@ Example of response
"started_at": "2019-03-25T12:54:50.082Z",
"finished_at": "2019-03-25T18:55:13.216Z",
"duration": 21623.13423,
"project": {
"ci_job_token_scope_enabled": false
},
"user": {
"id": 1,
"name": "Administrator",

View File

@ -6088,6 +6088,29 @@ The edge type for [`BoardList`](#boardlist).
| <a id="boardlistedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="boardlistedgenode"></a>`node` | [`BoardList`](#boardlist) | The item at the end of the edge. |
#### `BranchRuleConnection`
The connection type for [`BranchRule`](#branchrule).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="branchruleconnectionedges"></a>`edges` | [`[BranchRuleEdge]`](#branchruleedge) | A list of edges. |
| <a id="branchruleconnectionnodes"></a>`nodes` | [`[BranchRule]`](#branchrule) | A list of nodes. |
| <a id="branchruleconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `BranchRuleEdge`
The edge type for [`BranchRule`](#branchrule).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="branchruleedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="branchruleedgenode"></a>`node` | [`BranchRule`](#branchrule) | The item at the end of the edge. |
#### `CiBuildNeedConnection`
The connection type for [`CiBuildNeed`](#cibuildneed).
@ -9985,6 +10008,18 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="branchcommit"></a>`commit` | [`Commit`](#commit) | Commit for the branch. |
| <a id="branchname"></a>`name` | [`String!`](#string) | Name of the branch. |
### `BranchRule`
List of branch rules for a project, grouped by branch name.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="branchrulecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the branch rule was created. |
| <a id="branchrulename"></a>`name` | [`String!`](#string) | Branch name, with wildcards, for the branch rules. |
| <a id="branchruleupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the branch rule was last updated. |
### `BurnupChartDailyTotals`
Represents the total number of issues and their weights for a particular day.
@ -15589,6 +15624,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="pipelinesecurityreportfindingassets"></a>`assets` | [`[AssetType!]`](#assettype) | List of assets associated with the vulnerability. |
| <a id="pipelinesecurityreportfindingconfidence"></a>`confidence` | [`String`](#string) | Type of the security report that found the vulnerability. |
| <a id="pipelinesecurityreportfindingdescription"></a>`description` | [`String`](#string) | Description of the vulnerability finding. |
| <a id="pipelinesecurityreportfindingdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
| <a id="pipelinesecurityreportfindingevidence"></a>`evidence` | [`VulnerabilityEvidence`](#vulnerabilityevidence) | Evidence for the vulnerability. |
| <a id="pipelinesecurityreportfindingfalsepositive"></a>`falsePositive` | [`Boolean`](#boolean) | Indicates whether the vulnerability is a false positive. |
| <a id="pipelinesecurityreportfindingidentifiers"></a>`identifiers` | [`[VulnerabilityIdentifier!]!`](#vulnerabilityidentifier) | Identifiers of the vulnerability finding. |
@ -15618,6 +15654,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectarchived"></a>`archived` | [`Boolean`](#boolean) | Indicates the archived status of the project. |
| <a id="projectautoclosereferencedissues"></a>`autocloseReferencedIssues` | [`Boolean`](#boolean) | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically. |
| <a id="projectavatarurl"></a>`avatarUrl` | [`String`](#string) | URL to avatar image file of the project. |
| <a id="projectbranchrules"></a>`branchRules` | [`BranchRuleConnection`](#branchruleconnection) | Branch rules configured for the project. (see [Connections](#connections)) |
| <a id="projectcicdsettings"></a>`ciCdSettings` | [`ProjectCiCdSetting`](#projectcicdsetting) | CI/CD settings for the project. |
| <a id="projectciconfigpathordefault"></a>`ciConfigPathOrDefault` | [`String!`](#string) | Path of the CI configuration file. |
| <a id="projectcijobtokenscope"></a>`ciJobTokenScope` | [`CiJobTokenScopeType`](#cijobtokenscopetype) | The CI Job Tokens scope of access. |

View File

@ -368,6 +368,9 @@ Example of response
"status": "pending",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/7",
"project": {
"ci_job_token_scope_enabled": false
},
"user": {
"id": 1,
"name": "Administrator",
@ -454,6 +457,9 @@ Example of response
"failure_reason": "script_failure",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/8",
"project": {
"ci_job_token_scope_enabled": false
},
"user": {
"id": 1,
"name": "Administrator",
@ -611,6 +617,9 @@ Example of response
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/8",
"project": {
"ci_job_token_scope_enabled": false
},
"user": {
"id": 1,
"name": "Administrator",
@ -701,6 +710,9 @@ Example of response
"status": "canceled",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/1",
"project": {
"ci_job_token_scope_enabled": false
},
"user": null
}
```
@ -751,6 +763,9 @@ Example of response
"status": "pending",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/1",
"project": {
"ci_job_token_scope_enabled": false
},
"user": null
}
```
@ -806,6 +821,9 @@ Example of response
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/1",
"project": {
"ci_job_token_scope_enabled": false
},
"user": null
}
```
@ -882,6 +900,9 @@ Example response:
"status": "pending",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/1",
"project": {
"ci_job_token_scope_enabled": false
},
"user": null
}
```

View File

@ -653,8 +653,7 @@ on, check out our [self-service framework](geo/framework.md).
### GET:Geo pipeline
As part of the [package-and-qa](testing_guide/end_to_end/index.md#using-the-package-and-qa-job) pipeline, there is an option to manually trigger a job named `GET:Geo`. This
pipeline uses [GET](https://gitlab.com/gitlab-org/gitlab-environment-toolkit) to spin up a
As part of the [e2e:package-and-test](testing_guide/end_to_end/index.md#using-the-package-and-test-job) pipeline, there is an option to manually trigger a job named `GET:Geo`. This pipeline uses [GET](https://gitlab.com/gitlab-org/gitlab-environment-toolkit) to spin up a
[1k](../administration/reference_architectures/1k_users.md) Geo installation,
and run the [`gitlab-qa`](https://gitlab.com/gitlab-org/gitlab-qa) Geo scenario against the instance.
When working on Geo features, it is a good idea to ensure the `qa-geo` job passes in a triggered `GET:Geo pipeline`.
@ -669,7 +668,7 @@ see the [QA documentation](https://gitlab.com/gitlab-org/gitlab/-/tree/master/qa
The pipeline involves the interaction of multiple different projects:
- [GitLab](https://gitlab.com/gitlab-org/gitlab) - The [package-and-qa job](testing_guide/end_to_end/index.md#using-the-package-and-qa-job) is launched from merge requests in this project.
- [GitLab](https://gitlab.com/gitlab-org/gitlab) - The [`e2e:package-and-test` job](testing_guide/end_to_end/index.md#using-the-package-and-test-job) is launched from merge requests in this project.
- [`omnibus-gitlab`](https://gitlab.com/gitlab-org/omnibus-gitlab) - Builds relevant artifacts containing the changes from the triggering merge request pipeline.
- [GET-Configs/Geo](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit-configs/Geo) - Coordinates the lifecycle of a short-lived Geo installation that can be evaluated.
- [GET](https://gitlab.com/gitlab-org/gitlab-environment-toolkit) - Contains the necessary logic for creating and destroying Geo installations. Used by `GET-Configs/Geo`.

View File

@ -8,16 +8,15 @@ info: To determine the technical writer assigned to the Stage/Group associated w
## What is end-to-end testing?
End-to-end testing is a strategy used to check whether your application works
End-to-end (e2e) testing is a strategy used to check whether your application works
as expected across the entire software stack and architecture, including
integration of all micro-services and components that are supposed to work
together.
## How do we test GitLab?
We use [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab) to build GitLab packages and then we
test these packages using the [GitLab QA orchestrator](https://gitlab.com/gitlab-org/gitlab-qa) tool, which is
a black-box testing framework for the API and the UI.
We use [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab) to build GitLab packages and then we test these packages
using the [GitLab QA orchestrator](https://gitlab.com/gitlab-org/gitlab-qa) tool to run the end-to-end tests located in the `qa` directory.
### Testing nightly builds
@ -33,11 +32,9 @@ You can find these pipelines at <https://gitlab.com/gitlab-org/quality/staging/p
### Testing code in merge requests
#### Using the `package-and-qa` job
#### Using the package-and-test job
It is possible to run end-to-end tests for a merge request, eventually being run in
a pipeline in the [`gitlab-org/gitlab-qa-mirror`](https://gitlab.com/gitlab-org/gitlab-qa-mirror) project,
by triggering the `package-and-qa` manual action in the `qa` stage (not
It is possible to run end-to-end tests for a merge request by triggering the `e2e:package-and-test` manual action in the `qa` stage (not
available for forks).
**This runs end-to-end tests against a custom EE (with an Ultimate license)
@ -72,38 +69,30 @@ subgraph "`gitlab-org/gitlab-qa-mirror` pipeline"
```
1. In the [`gitlab-org/gitlab` pipeline](https://gitlab.com/gitlab-org/gitlab):
1. Developer triggers the `package-and-qa` manual action (available once the `build-qa-image` and
1. Developer triggers the `e2e:package-and-test` manual action (available once the `build-qa-image` and
`build-assets-image` jobs are done), that can be found in GitLab merge
requests. This starts a chain of pipelines in multiple projects.
1. The script being executed triggers a pipeline in
requests. This starts a e2e test child pipeline.
1. E2E child pipeline triggers a downstream pipeline in
[`gitlab-org/build/omnibus-gitlab-mirror`](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror)
and polls for the resulting status. We call this a _status attribution_.
1. In the [`gitlab-org/build/omnibus-gitlab-mirror` pipeline](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror):
1. Docker image is being built and pushed to its Container Registry.
1. Finally, the `Trigger:qa-test` job triggers a new end-to-end pipeline in
[`gitlab-org/gitlab-qa-mirror`](https://gitlab.com/gitlab-org/gitlab-qa-mirror/pipelines) and polls for the resulting status.
1. Once Docker images are built and pushed jobs in `test` stage are started
1. In the [`gitlab-org/gitlab-qa-mirror` pipeline](https://gitlab.com/gitlab-org/gitlab-qa-mirror):
1. In the `Test` stage:
1. Container for the Docker image stored in the [`gitlab-org/build/omnibus-gitlab-mirror`](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror) registry is spun-up.
1. End-to-end tests are run with the `gitlab-qa` executable, which spin up a container for the end-to-end image from the [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab) registry.
1. The result of the [`gitlab-org/gitlab-qa-mirror` pipeline](https://gitlab.com/gitlab-org/gitlab-qa-mirror) is being
propagated upstream (through polling from upstream pipelines), through [`gitlab-org/build/omnibus-gitlab-mirror`](https://gitlab.com/gitlab-org/build/omnibus-gitlab-mirror), back to the [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab) merge request.
We plan to [add more specific information](https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/156)
about the tests included in each job/scenario that runs in `gitlab-org/gitlab-qa-mirror`.
NOTE:
You may have noticed that we use `gitlab-org/build/omnibus-gitlab-mirror` instead of
`gitlab-org/omnibus-gitlab`, and `gitlab-org/gitlab-qa-mirror` instead of `gitlab-org/gitlab-qa`.
`gitlab-org/omnibus-gitlab`.
This is due to technical limitations in the GitLab permission model: the ability to run a pipeline
against a protected branch is controlled by the ability to push/merge to this branch.
This means that for developers to be able to trigger a pipeline for the default branch in
`gitlab-org/omnibus-gitlab`/`gitlab-org/gitlab-qa`, they would need to have the
Maintainer role for those projects.
`gitlab-org/omnibus-gitlab`, they would need to have the Maintainer role for this project.
For security reasons and implications, we couldn't open up the default branch to all the Developers.
Hence we created these mirrors where Developers and Maintainers are allowed to push/merge to the default branch.
Hence we created this mirror where Developers and Maintainers are allowed to push/merge to the default branch.
This problem was discovered in <https://gitlab.com/gitlab-org/gitlab-qa/-/issues/63#note_107175160> and the "mirror"
work-around was suggested in <https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4717>.
A feature proposal to segregate access control regarding running pipelines from ability to push/merge was also created at <https://gitlab.com/gitlab-org/gitlab/-/issues/24585>.
@ -231,7 +220,7 @@ a link to the current test report.
Each type of scheduled pipeline generates a static link for the latest test report according to its stage:
- [`master`](https://storage.googleapis.com/gitlab-qa-allure-reports/package-and-qa/master/index.html)
- [`master`](https://storage.googleapis.com/gitlab-qa-allure-reports/e2e-package-and-test/master/index.html)
- [`staging-full`](https://storage.googleapis.com/gitlab-qa-allure-reports/staging-full/master/index.html)
- [`staging-sanity`](https://storage.googleapis.com/gitlab-qa-allure-reports/staging-sanity/master/index.html)
- [`staging-sanity-no-admin`](https://storage.googleapis.com/gitlab-qa-allure-reports/staging-sanity-no-admin/master/index.html)

View File

@ -262,7 +262,7 @@ sudo -u git -H bundle exec rake gitlab:backup:create SKIP=db,uploads RAILS_ENV=p
### Skipping tar creation
NOTE:
It is not possible to skip the tar creation when using [object storage](#uploading-backups-to-a-remote-cloud-storage) for backups.
It is not possible to skip the tar creation when using [object storage](#upload-backups-to-a-remote-cloud-storage) for backups.
The last part of creating a backup is generation of a `.tar` file containing
all the parts. In some cases (for example, if the backup is picked up by other
@ -391,7 +391,7 @@ For example, to back up all repositories for all projects in **Group A** (`group
sudo -u git -H bundle exec rake gitlab:backup:create REPOSITORIES_PATHS=group-a,group-b/project-c
```
### Uploading backups to a remote (cloud) storage
### Upload backups to a remote (cloud) storage
NOTE:
It is not possible to [skip the tar creation](#skipping-tar-creation) when using object storage for backups.
@ -401,7 +401,7 @@ the `.tar` file it creates. In the following example, we use Amazon S3 for
storage, but Fog also lets you use [other storage providers](https://fog.io/storage/).
GitLab also [imports cloud drivers](https://gitlab.com/gitlab-org/gitlab/-/blob/da46c9655962df7d49caef0e2b9f6bbe88462a02/Gemfile#L113)
for AWS, Google, OpenStack Swift, Rackspace, and Aliyun. A local driver is
[also available](#uploading-to-locally-mounted-shares).
[also available](#upload-to-locally-mounted-shares).
[Read more about using object storage with GitLab](../administration/object_storage.md).
@ -722,7 +722,7 @@ Users of GitLab 12.1 and earlier should use the command `gitlab-rake gitlab:back
### Skip uploading backups to remote storage
If you have configured GitLab to [upload backups in a remote storage](#uploading-backups-to-a-remote-cloud-storage),
If you have configured GitLab to [upload backups in a remote storage](#upload-backups-to-a-remote-cloud-storage),
you can use the `SKIP=remote` option to skip uploading your backups to the remote storage.
For Omnibus GitLab packages:
@ -737,23 +737,40 @@ For installations from source:
sudo -u git -H bundle exec rake gitlab:backup:create SKIP=remote RAILS_ENV=production
```
### Uploading to locally mounted shares
### Upload to locally-mounted shares
You may also send backups to a mounted share (for example, `NFS`,`CIFS`, or
`SMB`) by using the Fog [`Local`](https://github.com/fog/fog-local#usage)
storage provider. The directory pointed to by the `local_root` key _must_ be
owned by the `git` user _when mounted_ (mounting with the `uid=` of the `git`
user for `CIFS` and `SMB`) or the user that you are executing the backup tasks
as (for Omnibus packages, this is the `git` user).
You can send backups to a locally-mounted share (for example, `NFS`,`CIFS`, or `SMB`) using the Fog
[`Local`](https://github.com/fog/fog-local#usage) storage provider.
The `backup_upload_remote_directory` _must_ be set in addition to the
`local_root` key. This is the sub directory inside the mounted directory that
backups are copied to, and is created if it does not exist. If the
directory that you want to copy the tarballs to is the root of your mounted
directory, use `.` instead.
To do this, you must set the following configuration keys:
- `backup_upload_remote_directory`: mounted directory that backups are copied to.
- `backup_upload_connection.local_root`: subdirectory of the `backup_upload_remote_directory` directory. It is created if it doesn't exist.
If you want to copy the tarballs to the root of your mounted directory, use `.`.
When mounted, the directory set in the `local_root` key must be owned by either:
- The `git` user. So, mounting with the `uid=` of the `git` user for `CIFS` and `SMB`.
- The user that you are executing the backup tasks as. For Omnibus GitLab, this is the `git` user.
Because file system performance may affect overall GitLab performance,
[GitLab doesn't recommend using cloud-based file systems for storage](../administration/nfs.md#avoid-using-cloud-based-file-systems).
[we don't recommend using cloud-based file systems for storage](../administration/nfs.md#avoid-using-cloud-based-file-systems).
#### Avoid conflicting configuration
Don't set the following configuration keys to the same path:
- `gitlab_rails['backup_path']` (`backup.path` for source installations).
- `gitlab_rails['backup_upload_connection'].local_root` (`backup.upload.connection.local_root` for source installations).
The `backup_path` configuration key sets the local location of the backup file. The `upload` configuration key is
intended for use when the backup file is uploaded to a separate server, perhaps for archival purposes.
If these configuration keys are set to the same location, the upload feature fails because a backup already exists at
the upload location. This failure causes the upload feature to delete the backup because it assumes it's a residual file
remaining after the failed upload attempt.
#### Configure uploads to locally-mounted shares
For Omnibus GitLab packages:
@ -878,7 +895,7 @@ for backups. The next time the backup task runs, backups older than the `backup_
pruned.
This configuration option manages only local files. GitLab doesn't prune old
files stored in a third-party [object storage](#uploading-backups-to-a-remote-cloud-storage)
files stored in a third-party [object storage](#upload-backups-to-a-remote-cloud-storage)
because the user may not have permission to list and delete files. It's
recommended that you configure the appropriate retention policy for your object
storage (for example, [AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/user-guide/create-lifecycle.html)).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -104,18 +104,20 @@ provide to GitLab:
Your slash command can now communicate with your GitLab project.
## Authorizing Mattermost to interact with GitLab
## Connect your GitLab account to Mattermost
The first time a user interacts with the newly created slash commands,
Mattermost triggers an authorization process.
Prerequisite:
![Mattermost bot authorize](img/mattermost_bot_auth.png)
- To run [slash commands](#available-slash-commands), you must have
[permission](../../permissions.md#project-members-permissions) to
perform the action in the GitLab project.
This connects your Mattermost user with your GitLab user. You can
see all authorized chat accounts in your profile's page under **Chat**.
To interact with GitLab using Mattermost slash commands:
When the authorization process is complete, you can start interacting with
GitLab using the Mattermost commands.
1. In a Mattermost chat environment, run your new slash command.
1. Select **connect your GitLab account** to authorize access.
You can see all authorized chat accounts in your Mattermost profile page under **Chat**.
## Available slash commands
@ -123,30 +125,21 @@ The available slash commands for Mattermost are:
| Command | Description | Example |
| ------- | ----------- | ------- |
| <kbd>/&lt;trigger&gt; issue new &lt;title&gt; <kbd>⇧ Shift</kbd>+<kbd>↵ Enter</kbd> &lt;description&gt;</kbd> | Create a new issue in the project that `<trigger>` is tied to. `<description>` is optional. | `/gitlab issue new We need to change the homepage` |
| <kbd>/&lt;trigger&gt; issue show &lt;issue-number&gt;</kbd> | Show the issue with ID `<issue-number>` from the project that `<trigger>` is tied to. | `/gitlab issue show 42` |
| <kbd>/&lt;trigger&gt; deploy &lt;environment&gt; to &lt;environment&gt;</kbd> | Start the CI job that deploys from one environment to another, for example `staging` to `production`. CI/CD must be [properly configured](../../../ci/yaml/index.md). | `/gitlab deploy staging to production` |
| `/<trigger> issue new <title>` <kbd>Shift</kbd>+<kbd>Enter</kbd> `<description>` | Create a new issue in the project that `<trigger>` is tied to. `<description>` is optional. | `/gitlab issue new We need to change the homepage` |
| `/<trigger> issue show <issue-number>` | Show the issue with ID `<issue-number>` from the project that `<trigger>` is tied to. | `/gitlab issue show 42` |
| `/<trigger> deploy <environment> to <environment>` | Start the CI/CD job that deploys from one environment to another (for example, `staging` to `production`). CI/CD must be [properly configured](../../../ci/yaml/index.md). | `/gitlab deploy staging to production` |
| `/<trigger> help` | View a list of available slash commands. | `/gitlab help` |
To see a list of available commands to interact with GitLab, type the
trigger word followed by <kbd>help</kbd>. Example: `/gitlab help`
## Related topics
![Mattermost bot available commands](img/mattermost_bot_available_commands.png)
## Permissions
The permissions to run the [available commands](#available-slash-commands) derive from
the [permissions you have on the project](../../permissions.md#project-members-permissions).
- [Mattermost slash commands](https://developers.mattermost.com/integrate/slash-commands/)
- [Omnibus GitLab Mattermost](../../../integration/mattermost/)
## Troubleshooting
If an event is not being triggered, confirm that the channel you're using is a public one.
Mattermost webhooks do not have access to private channels.
When a Mattermost slash command does not trigger an event in GitLab:
If a private channel is required, you can edit the webhook's channel in Mattermost and
select a private channel. It is not possible to use different channels for
different types of notifications. All events are sent to the specified channel.
## Further reading
- [Mattermost slash commands documentation](https://docs.mattermost.com/developer/slash-commands.html)
- [Omnibus GitLab Mattermost](../../../integration/mattermost/)
- Ensure you're using a public channel.
Mattermost webhooks do not have access to private channels.
- If you require a private channel, edit the webhook channel,
and select a private one. All events are sent to the specified channel.

View File

@ -238,7 +238,6 @@ module API
mount ::API::ImportGithub
mount ::API::Integrations
mount ::API::Integrations::JiraConnect::Subscriptions
mount ::API::Integrations::Slack::Events
mount ::API::Invitations
mount ::API::IssueLinks
mount ::API::Issues

View File

@ -18,6 +18,12 @@ module API
expose :web_url do |job, _options|
Gitlab::Routing.url_helpers.project_job_url(job.project, job)
end
expose :project do
expose :ci_job_token_scope_enabled do |job|
job.project.ci_job_token_scope_enabled?
end
end
end
end
end

View File

@ -1,40 +0,0 @@
# frozen_string_literal: true
# This API endpoint handles all events sent from Slack once a Slack
# workspace has installed the GitLab Slack app.
#
# See https://api.slack.com/apis/connections/events-api.
module API
class Integrations
module Slack
class Events < ::API::Base
feature_category :integrations
before { verify_slack_request! }
helpers do
def verify_slack_request!
unauthorized! unless Request.verify!(request)
end
end
namespace 'integrations/slack' do
post :events do
type = params['type']
raise ArgumentError, "Unable to handle event type: '#{type}'" unless type == 'url_verification'
status :ok
UrlVerification.call(params)
rescue ArgumentError => e
# Track the error, but respond with a `2xx` because we don't want to risk
# Slack rate-limiting, or disabling our app, due to error responses.
# See https://api.slack.com/apis/connections/events-api.
Gitlab::ErrorTracking.track_exception(e)
no_content!
end
end
end
end
end
end

View File

@ -1,22 +0,0 @@
# frozen_string_literal: true
module API
class Integrations
module Slack
class Events
class UrlVerification
# When the GitLab Slack app is first configured to receive Slack events,
# Slack will issue a special request to the endpoint and expect it to respond
# with the `challenge` param.
#
# This must be done in-request, rather than on a queue.
#
# See https://api.slack.com/apis/connections/events-api.
def self.call(params)
{ challenge: params[:challenge] }
end
end
end
end
end
end

View File

@ -1,51 +0,0 @@
# frozen_string_literal: true
module API
class Integrations
module Slack
module Request
VERIFICATION_VERSION = 'v0'
VERIFICATION_TIMESTAMP_HEADER = 'X-Slack-Request-Timestamp'
VERIFICATION_SIGNATURE_HEADER = 'X-Slack-Signature'
VERIFICATION_DELIMITER = ':'
VERIFICATION_HMAC_ALGORITHM = 'sha256'
VERIFICATION_TIMESTAMP_EXPIRY = 1.minute.to_i
# Verify the request by comparing the given request signature in the header
# with a signature value that we compute according to the steps in:
# https://api.slack.com/authentication/verifying-requests-from-slack.
def self.verify!(request)
return false unless Gitlab::CurrentSettings.slack_app_signing_secret
timestamp, signature = request.headers.values_at(
VERIFICATION_TIMESTAMP_HEADER,
VERIFICATION_SIGNATURE_HEADER
)
return false if timestamp.nil? || signature.nil?
return false if Time.current.to_i - timestamp.to_i >= VERIFICATION_TIMESTAMP_EXPIRY
request.body.rewind
basestring = [
VERIFICATION_VERSION,
timestamp,
request.body.read
].join(VERIFICATION_DELIMITER)
hmac_digest = OpenSSL::HMAC.hexdigest(
VERIFICATION_HMAC_ALGORITHM,
Gitlab::CurrentSettings.slack_app_signing_secret,
basestring
)
# Signature will look like: 'v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503'
ActiveSupport::SecurityUtils.secure_compare(
signature,
"#{VERIFICATION_VERSION}=#{hmac_digest}"
)
end
end
end
end
end

View File

@ -19825,9 +19825,6 @@ msgstr ""
msgid "If you add %{codeStart}needs%{codeEnd} to jobs in your pipeline you'll be able to view the %{codeStart}needs%{codeEnd} relationships between jobs in this tab as a %{linkStart}Directed Acyclic Graph (DAG)%{linkEnd}."
msgstr ""
msgid "If you are added to a project, it will be displayed here."
msgstr ""
msgid "If you did not initiate these sign-in attempts, please reach out to your administrator or enable two-factor authentication (2FA) on your account."
msgstr ""
@ -28098,12 +28095,6 @@ msgstr ""
msgid "PackageRegistry|RubyGems"
msgstr ""
msgid "PackageRegistry|Settings for Generic packages"
msgstr ""
msgid "PackageRegistry|Settings for Maven packages"
msgstr ""
msgid "PackageRegistry|Show Composer commands"
msgstr ""
@ -45239,6 +45230,9 @@ msgstr ""
msgid "You need to verify your primary email first before enabling Two-Factor Authentication."
msgstr ""
msgid "You see projects here when you're added to a group or project."
msgstr ""
msgid "You successfully declined the invitation"
msgstr ""
@ -46999,6 +46993,9 @@ msgid_plural "mrWidget|Mentions issues"
msgstr[0] ""
msgstr[1] ""
msgid "mrWidget|Merge blocked: a Jira issue key must be mentioned in the title or description."
msgstr ""
msgid "mrWidget|Merge blocked: all required approvals must be given."
msgstr ""
@ -47134,9 +47131,6 @@ msgstr ""
msgid "mrWidget|To change this default message, edit the template for squash commit messages. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
msgid "mrWidget|To merge, a Jira issue key must be mentioned in the title or description."
msgstr ""
msgid "mrWidget|Users who can write to the source or target branches can resolve the conflicts."
msgstr ""

View File

@ -2,7 +2,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa', '~> 7', require: 'gitlab/qa'
gem 'gitlab-qa', '~> 8', require: 'gitlab/qa'
gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.16.0'
gem 'capybara', '~> 3.35.0'

View File

@ -118,13 +118,14 @@ GEM
gitlab (4.18.0)
httparty (~> 0.18)
terminal-table (>= 1.5.1)
gitlab-qa (7.33.0)
gitlab-qa (8.4.0)
activesupport (~> 6.1)
gitlab (~> 4.18.0)
http (~> 5.0)
nokogiri (~> 1.10)
rainbow (~> 3.0.0)
table_print (= 1.5.7)
zeitwerk (~> 2.4)
google-apis-compute_v1 (0.21.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.4.1)
@ -157,7 +158,7 @@ GEM
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
http (5.0.4)
http (5.1.0)
addressable (~> 2.8)
http-cookie (~> 1.0)
http-form_data (~> 2.2)
@ -170,7 +171,7 @@ GEM
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.10.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
influxdb-client (1.17.0)
@ -193,7 +194,7 @@ GEM
mime-types-data (3.2022.0105)
mini_mime (1.1.0)
mini_portile2 (2.8.0)
minitest (5.15.0)
minitest (5.16.3)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.1.1)
@ -209,7 +210,7 @@ GEM
parallel (1.19.2)
parallel_tests (2.29.0)
parallel
parser (3.0.3.2)
parser (3.1.2.1)
ast (~> 2.4.1)
proc_to_ast (0.1.0)
coderay
@ -290,13 +291,13 @@ GEM
thread_safe (0.3.6)
timecop (0.9.1)
trailblazer-option (0.1.2)
tzinfo (2.0.4)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.1.0)
unicode-display_width (2.2.0)
unparser (0.4.7)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
@ -335,7 +336,7 @@ DEPENDENCIES
deprecation_toolkit (~> 1.5.1)
faker (~> 2.19, >= 2.19.0)
fog-google (~> 1.17)
gitlab-qa (~> 7)
gitlab-qa (~> 8)
influxdb-client (~> 1.17)
knapsack (~> 4.0)
nokogiri (~> 1.12)

View File

@ -1,12 +1,12 @@
# GitLab QA - End-to-end tests for GitLab
This directory contains [end-to-end tests](../../../doc/development/testing_guide/end_to_end/index.md)
This directory contains [end-to-end tests](../doc/development/testing_guide/end_to_end/index.md)
for GitLab. It includes the test framework and the tests themselves.
The tests can be found in `qa/specs/features` (not to be confused with the unit
tests for the test framework, which are in `spec/`).
It is part of the [GitLab QA project](https://gitlab.com/gitlab-org/gitlab-qa).
Tests use [GitLab QA project](https://gitlab.com/gitlab-org/gitlab-qa) for environment orchestration in CI jobs.
## What is it?
@ -46,7 +46,7 @@ Note that tests are using `Chrome` web browser by default so it should be instal
Tests are executed in merge request pipelines as part of the development lifecycle.
- [Review app environment](../doc/development/testing_guide/review_apps.md)
- [package-and-qa](../doc/development/testing_guide/end_to_end/index.md#testing-code-in-merge-requests)
- [e2e:package-and-test](../doc/development/testing_guide/end_to_end/index.md#testing-code-in-merge-requests)
### Logging

View File

@ -0,0 +1,16 @@
# 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

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module QA
module Scenario
module Test
module Instance
class CloudActivation < All
tags :cloud_activation
end
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module QA
module Scenario
module Test
module Instance
class Integrations < All
tags :integrations
end
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module QA
module Scenario
module Test
module Instance
class Jira < All
tags :jira
end
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module QA
module Scenario
module Test
module Instance
class LargeSetup < All
tags :can_use_large_setup
end
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module QA
module Scenario
module Test
module Instance
class Metrics < All
tags :metrics
end
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module QA
module Scenario
module Test
module Instance
class ObjectStorage < All
tags :object_storage
end
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module QA
module Scenario
module Test
module Instance
class Packages < All
tags :packages
end
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module QA
module Scenario
module Test
module Instance
class RepositoryStorage < All
tags :repository_storage
end
end
end
end
end

View File

@ -86,8 +86,7 @@ module QA
File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0
saved_file_msg = total_examples.to_i > 0 ? ". Saved to file: #{filename}" : ''
$stdout.puts "Total examples in #{Runtime::Scenario.klass}: #{total_examples}#{saved_file_msg}"
$stdout.puts total_examples
end
def test_metadata_only(args)

View File

@ -0,0 +1,66 @@
# frozen_string_literal: true
require "yaml"
module QA
module Tools
module Ci
class FfChanges
include Helpers
def initialize(mr_diff)
@mr_diff = mr_diff
end
# Return list of feature flags changed in mr with inverse or deleted state
#
# @return [String]
def fetch
logger.info("Detecting feature flag changes")
ff_toggles = mr_diff.map do |change|
ff_yaml = ff_yaml_for_file(change)
next unless ff_yaml
state = if ff_yaml[:deleted]
"deleted"
else
ff_yaml[:default_enabled] ? 'disabled' : 'enabled'
end
logger.info(" found changes in feature flag '#{ff_yaml[:name]}'")
"#{ff_yaml[:name]}=#{state}"
end.compact
if ff_toggles.empty?
logger.info(" no changes to feature flags detected, skipping!")
return
end
logger.info(" constructed feature flag states: '#{ff_toggles}'")
ff_toggles.join(",")
end
private
attr_reader :mr_diff
# Loads the YAML feature flag definition based on changed files in merge requests.
# The definition is loaded from the definition file itself.
#
# @param [Hash] change mr file change
# @return [Hash] a hash containing the YAML data for the feature flag definition
def ff_yaml_for_file(change)
return unless change[:path] =~ %r{/feature_flags/(development|ops)/.*\.yml}
if change[:deleted_file]
return { name: change[:path].split("/").last.gsub(/\.(yml|yaml)/, ""), deleted: true }
end
YAML.safe_load(
File.read(File.expand_path("../#{change[:path]}", QA::Runtime::Path.qa_root)),
symbolize_names: true
)
end
end
end
end
end

16
qa/qa/tools/ci/helpers.rb Normal file
View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
module QA
module Tools
module Ci
module Helpers
# Logger instance
#
# @return [Logger]
def logger
@logger ||= Gitlab::QA::TestLogger.logger(level: "INFO", source: "CI Tools")
end
end
end
end
end

View File

@ -0,0 +1,97 @@
# frozen_string_literal: true
require 'open3'
module QA
module Tools
module Ci
# Run count commands for scenarios and detect which ones have more than 0 examples to run
#
class NonEmptySuites
include Helpers
# rubocop:disable Layout/LineLength
SCENARIOS = [
{ klass: "Test::Instance::All" },
{ klass: "Test::Instance::Smoke" },
{ klass: "Test::Instance::Reliable" },
{ klass: "Test::Instance::Blocking" },
{ klass: "Test::Instance::CloudActivation" },
{ klass: "Test::Instance::Integrations" },
{ klass: "Test::Instance::Jira" },
{ klass: "Test::Instance::LargeSetup" },
{ klass: "Test::Instance::Metrics" },
{ klass: "Test::Instance::ObjectStorage" },
{ klass: "Test::Instance::Packages" },
{ klass: "Test::Instance::RepositoryStorage" },
{ klass: "Test::Integration::ServicePingDisabled" },
{ klass: "Test::Integration::LDAPNoTLS" },
{ klass: "Test::Integration::LDAPTLS" },
{ klass: "Test::Integration::LDAPNoServer" },
{ klass: "Test::Integration::InstanceSAML" },
{ klass: "Test::Integration::RegistryWithCDN" },
{ klass: "Test::Integration::RegistryTLS" },
{ klass: "Test::Integration::Registry" },
{ klass: "Test::Integration::SMTP" },
{ klass: "QA::EE::Scenario::Test::Integration::Elasticsearch" },
{ klass: "QA::EE::Scenario::Test::Integration::GroupSAML" },
{
klass: "QA::EE::Scenario::Test::Geo",
args: "--primary-address http://dummy1.test --primary-name gitlab-primary --secondary-address http://dummy2.test --secondary-name gitlab-secondary --without-setup"
},
{
klass: "Test::Integration::Mattermost",
args: "--mattermost-address http://mattermost.test"
}
].freeze
# rubocop:enable Layout/LineLength
def initialize(qa_tests)
@qa_tests = qa_tests
end
# Run counts and return runnable scenario list
#
# @return [String]
def fetch
logger.info("Checking for runnable suites")
scenarios = SCENARIOS.each_with_object([]) do |scenario, runnable_scenarios|
logger.info(" fetching runnable specs for '#{scenario[:klass]}'")
out, err, status = run_command(**scenario)
unless status.success?
logger.error(" example count failed!\n#{err}")
next
end
count = out.split("\n").last.to_i
logger.info(" found #{count} examples to run")
runnable_scenarios << scenario[:klass] if count > 0
end
scenarios.join(",")
end
private
attr_reader :qa_tests
# Run scenario count command
#
# @param [String] klass
# @param [String] args
# @return [String]
def run_command(klass:, args: nil)
cmd = ["bundle exec bin/qa"]
cmd << klass
cmd << "--count-examples-only --address http://dummy1.test"
cmd << args if args
cmd << "-- #{qa_tests}" unless qa_tests.blank?
Open3.capture3(cmd.join(" "))
end
end
end
end
end

View File

@ -0,0 +1,116 @@
# frozen_string_literal: true
require "pathname"
module QA
module Tools
module Ci
# Determine specific qa specs or paths to execute based on changes
class QaChanges
include Helpers
QA_PATTERN = %r{^qa/}.freeze
SPEC_PATTERN = %r{^qa/qa/specs/features/}.freeze
def initialize(mr_diff, mr_labels)
@mr_diff = mr_diff
@mr_labels = mr_labels
end
# Specific specs to run
#
# @return [String]
def qa_tests
return if mr_diff.empty?
# make paths relative to qa directory
return changed_files&.map { |path| path.delete_prefix("qa/") }&.join(" ") if only_spec_changes?
return qa_spec_directories_for_devops_stage&.join(" ") if non_qa_changes? && mr_labels.any?
end
# Qa framework changes
#
# @return [Boolean]
def framework_changes?
return false if mr_diff.empty?
return false if only_spec_changes?
changed_files
# TODO: expand pattern to other non spec paths that shouldn't trigger full suite
.select { |file_path| file_path.match?(QA_PATTERN) && !file_path.match?(SPEC_PATTERN) }
.any?
end
def quarantine_changes?
return false if mr_diff.empty?
return false if mr_diff.any? { |change| change[:new_file] || change[:deleted_file] }
files_count = 0
specs_count = 0
quarantine_specs_count = 0
mr_diff.each do |change|
path = change[:path]
next if File.directory?(File.expand_path("../#{path}", QA::Runtime::Path.qa_root))
files_count += 1
next unless path.match?(SPEC_PATTERN) && path.end_with?('_spec.rb')
specs_count += 1
quarantine_specs_count += 1 if change[:diff].match?(/^\+.*,? quarantine:/)
end
return false if specs_count == 0
return true if quarantine_specs_count == specs_count && quarantine_specs_count == files_count
false
end
private
# @return [Array]
attr_reader :mr_diff
# @return [Array]
attr_reader :mr_labels
# Are the changed files only qa specs?
#
# @return [Boolean] whether the changes files are only qa specs
def only_spec_changes?
changed_files.all? { |file_path| file_path =~ SPEC_PATTERN }
end
# Are the changed files only outside the qa directory?
#
# @return [Boolean] whether the changes files are outside of qa directory
def non_qa_changes?
changed_files.none? { |file_path| file_path =~ QA_PATTERN }
end
# Extract devops stage from MR labels
#
# @return [String] a devops stage
def devops_stage_from_mr_labels
mr_labels.find { |label| label =~ /^devops::/ }&.delete_prefix('devops::')
end
# Get qa spec directories for devops stage
#
# @return [Array] qa spec directories
def qa_spec_directories_for_devops_stage
devops_stage = devops_stage_from_mr_labels
return unless devops_stage
Dir.glob("qa/specs/**/*/").select { |dir| dir =~ %r{\d+_#{devops_stage}/$} }
end
# Change files in merge request
#
# @return [Array<String>]
def changed_files
@changed_files ||= mr_diff.map { |change| change[:path] } # rubocop:disable Rails/Pluck
end
end
end
end
end

View File

@ -0,0 +1,8 @@
---
name: bulk_import_projects
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68873
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339941
milestone: '14.3'
type: development
group: group::import
default_enabled: false

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
RSpec.describe QA::Tools::Ci::FfChanges do
subject(:ff_changes) { described_class.new(mr_diff) }
before do
allow(Gitlab::QA::TestLogger).to receive(:logger).and_return(Logger.new(StringIO.new))
end
context "with merge request pipeline" do
let(:deleted_file) { false }
let(:mr_diff) do
[
{
path: "config/feature_flags/development/bulk_import_projects.yml",
deleted_file: deleted_file
}
]
end
before do
allow(File).to receive(:read)
.with(File.expand_path("../#{mr_diff.first[:path]}", QA::Runtime::Path.qa_root))
.and_return(File.read("spec/fixtures/ff/bulk_import_projects.yml"))
end
context "with changed feature flag" do
it "returns inverse ff state option" do
expect(ff_changes.fetch).to eq("bulk_import_projects=enabled")
end
end
context "with deleted feature flag" do
let(:deleted_file) { true }
it "returns deleted ff state option" do
expect(ff_changes.fetch).to eq("bulk_import_projects=deleted")
end
end
end
context "without merge request pipeline" do
let(:mr_diff) { [] }
context "with empty mr diff" do
it "doesn't return any ff options" do
expect(ff_changes.fetch).to be_nil
end
end
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
RSpec.describe QA::Tools::Ci::NonEmptySuites do
let(:non_empty_suites) { described_class.new(nil) }
let(:status) { instance_double(Process::Status, success?: true) }
before do
allow(Gitlab::QA::TestLogger).to receive(:logger).and_return(Logger.new(StringIO.new))
allow(Open3).to receive(:capture3).and_return(["output\n0", "", status])
allow(Open3).to receive(:capture3)
.with("bundle exec bin/qa Test::Instance::All --count-examples-only --address http://dummy1.test")
.and_return(["output\n1", "", status])
end
it "returns runnable test suites" do
expect(non_empty_suites.fetch).to eq("Test::Instance::All")
end
end

View File

@ -0,0 +1,87 @@
# frozen_string_literal: true
RSpec.describe QA::Tools::Ci::QaChanges do
subject(:qa_changes) { described_class.new(mr_diff, mr_labels) }
let(:mr_labels) { [] }
before do
allow(File).to receive(:directory?).and_return(false)
end
context "with spec only changes" do
let(:mr_diff) do
[
{ path: "qa/qa/specs/features/test_spec.rb", diff: "" },
{ path: "qa/qa/specs/features/another_test_spec.rb", diff: "" }
]
end
it ".qa_tests return changed specs" do
expect(qa_changes.qa_tests).to eq(
"qa/specs/features/test_spec.rb qa/specs/features/another_test_spec.rb"
)
end
it ".framework_changes? return false" do
expect(qa_changes.framework_changes?).to eq(false)
end
it ".quarantine_changes? return false" do
expect(qa_changes.quarantine_changes?).to eq(false)
end
end
context "with framework changes" do
let(:mr_diff) { [{ path: "qa/qa.rb" }] }
it ".qa_tests do not return specifix specs" do
expect(qa_changes.qa_tests).to be_nil
end
it ".framework_changes? return true" do
expect(qa_changes.framework_changes?).to eq(true)
end
it ".quarantine_changes? return false" do
expect(qa_changes.quarantine_changes?).to eq(false)
end
end
context "with non qa changes" do
let(:mr_diff) { [{ path: "Gemfile" }] }
it ".framework_changes? return false" do
expect(qa_changes.framework_changes?).to eq(false)
end
it ".quarantine_changes? return false" do
expect(qa_changes.quarantine_changes?).to eq(false)
end
context "without mr labels" do
it ".qa_tests do not return any specific specs" do
expect(qa_changes.qa_tests).to be_nil
end
end
context "with mr label" do
let(:mr_labels) { ["devops::manage"] }
it ".qa_tests return specs for devops stage" do
expect(qa_changes.qa_tests.split(" ")).to include(
"qa/specs/features/browser_ui/1_manage/",
"qa/specs/features/api/1_manage/"
)
end
end
end
context "with quarantine changes" do
let(:mr_diff) { [{ path: "qa/qa/specs/features/test_spec.rb", diff: "+ , quarantine: true" }] }
it ".quarantine_changes? return true" do
expect(qa_changes.quarantine_changes?).to eq(true)
end
end
end

60
qa/tasks/ci.rake Normal file
View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
require_relative "helpers/util"
# rubocop:disable Rails/RakeEnvironment
namespace :ci do
include Task::Helpers::Util
desc "Detect changes and populate test variables for selective test execution and feature flag testing"
task :detect_changes, [:env_file] do |_, args|
env_file = args[:env_file]
abort("ERROR: Path for environment file must be provided") unless env_file
diff = mr_diff
labels = mr_labels
qa_changes = QA::Tools::Ci::QaChanges.new(diff, labels)
logger = qa_changes.logger
logger.info("Analyzing merge request changes")
# skip running tests when only quarantine changes detected
if qa_changes.quarantine_changes?
logger.info(" merge request contains only quarantine changes, e2e test execution will be skipped!")
append_to_file(env_file, <<~TXT)
QA_SKIP_ALL_TESTS=true
TXT
next
end
# run all tests when framework changes detected
if qa_changes.framework_changes?
logger.info(" merge request contains qa framework changes, full test suite will be executed")
append_to_file(env_file, <<~TXT)
QA_FRAMEWORK_CHANGES=true
TXT
end
# detect if any of the test suites would not execute any tests and populate environment variables
tests = qa_changes.qa_tests
if tests
logger.info(" following changed specs detected: '#{tests}'")
else
logger.info(" no specific spec changes detected")
end
# always check all test suites in case a suite is defined but doesn't have any runnable specs
suites = QA::Tools::Ci::NonEmptySuites.new(tests).fetch
append_to_file(env_file, <<~TXT)
QA_TESTS=#{tests}
QA_SUITES=#{suites}
TXT
# check if mr contains feature flag changes
feature_flags = QA::Tools::Ci::FfChanges.new(diff).fetch
append_to_file(env_file, <<~TXT)
QA_FEATURE_FLAGS=#{feature_flags}
TXT
end
end
# rubocop:enable Rails/RakeEnvironment

49
qa/tasks/helpers/util.rb Normal file
View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
module Task
module Helpers
module Util
include ::QA::Support::API
# Append text to file
#
# @param [String] path
# @param [String] text
# @return [void]
def append_to_file(path, text)
File.open(path, "a") { |f| f.write(text) }
end
# Merge request labels
#
# @return [Array]
def mr_labels
ENV["CI_MERGE_REQUEST_LABELS"]&.split(',') || []
end
# Merge request changes
#
# @return [Array<Hash>]
def mr_diff
mr_iid = ENV["CI_MERGE_REQUEST_IID"]
return [] unless mr_iid
gitlab_endpoint = ENV["CI_API_V4_URL"]
gitlab_token = ENV["PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE"]
project_id = ENV["CI_MERGE_REQUEST_PROJECT_ID"]
response = get(
"#{gitlab_endpoint}/projects/#{project_id}/merge_requests/#{mr_iid}/changes",
headers: { "PRIVATE-TOKEN" => gitlab_token }
)
parse_body(response).fetch(:changes, []).map do |change|
{
path: change[:new_path],
**change.slice(:new_file, :deleted_file, :diff)
}
end
end
end
end
end

View File

@ -1,106 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'optparse'
# This script returns end-to-end tests or test directories. The returned value is stored in QA_TESTS
# variable for executing only those tests.
class DetermineQATests # rubocop:disable Gitlab/NamespacedClass
def initialize(options)
@changed_files = options.delete(:changed_files)
@mr_labels = options.delete(:mr_labels) || []
end
def execute
# If only e2e test files have changed, run only those tests
qa_tests = if has_qa_spec_only_changes?
changed_files
# If only non qa files have changed, use the devops MR label to run the files in the related directory
# However, if a feature flag file has changed, do not return any specific test/test directory
elsif has_non_qa_only_changes? && mr_labels.any? && !has_dev_ops_feature_flag_changes?
devops_stage = devops_stage_from_mr_labels
qa_spec_directories_for_devops_stage(devops_stage) if devops_stage
end
trim_path(qa_tests).join(' ') if qa_tests
end
private
attr_reader :changed_files, :mr_labels
# Are the changed files only qa specs?
#
# @return [Boolean] whether the changes files are only qa specs
def has_qa_spec_only_changes?
changed_files.all? { |file_path| file_path =~ %r{^qa/qa/specs/features/} }
end
# Are the changed files only outside the qa directory?
#
# @return [Boolean] whether the changes files are outside of qa directory
def has_non_qa_only_changes?
changed_files.none? { |file_path| file_path =~ %r{^qa/} }
end
# Are the changed files for development and ops feature flags?
#
# @return [Boolean] whether the changes files are for development and ops feature flags
def has_dev_ops_feature_flag_changes?
changed_files.any? { |file_path| file_path =~ %r{/feature_flags/(development|ops)/.*\.yml} }
end
# Remove the leading `qa/` from the file or directory paths
#
# @param [Array] paths Array of file or directory paths
# @return [Array] Array of files or directories with the first occurance of `qa/` removed
def trim_path(paths)
paths.map { |path| path.delete_prefix("qa/") }
end
# Extract devops stage from MR labels
#
# @return [String] a devops stage
def devops_stage_from_mr_labels
mr_labels.find { |label| label =~ /^devops::/ }&.delete_prefix('devops::')
end
# Get qa spec directories for devops stage
#
# @param [String] devops_stage a devops stage
# @return [Array] qa spec directories
def qa_spec_directories_for_devops_stage(devops_stage)
Dir.glob("qa/qa/specs/**/*/").select { |dir| dir =~ %r{\d+_#{devops_stage}/$} }
end
end
if $0 == __FILE__
options = {}
OptionParser.new do |opts|
opts.on("-f", "--files CHANGED_FILES_PATH", String,
"A path to a file containing a list of changed files") do |value|
changed_files_path = value
abort("ERROR: The specified changed files path does not exist") unless File.exist?(changed_files_path)
changed_files = File.read(changed_files_path).split(' ')
abort("ERROR: There are no changed files") if changed_files.empty?
options[:changed_files] = changed_files
end
opts.on("-l", "--labels MR_LABELS", String, "A comma separated list of MR labels") do |value|
options[:mr_labels] = Array(value&.split(',')).compact
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
puts DetermineQATests.new(options).execute
end

33
scripts/generate-e2e-pipeline Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
set -e
# Script to generate e2e test child pipeline
# This is required because environment variables that are generated dynamically are not picked up by rules in child pipelines
pipeline_yml="${1:-package-and-test.yml}"
if [ "$QA_SKIP_ALL_TESTS" == "true" ]; then
echo "Generated no-op child pipeline due to QA_SKIP_ALL_TESTS set to 'true'"
cp .gitlab/ci/package-and-test/skip.gitlab-ci.yml $pipeline_yml
exit
fi
variables=$(cat <<YML
variables:
RELEASE: "${CI_REGISTRY}/gitlab-org/build/omnibus-gitlab-mirror/gitlab-ee:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$CI_COMMIT_SHA}"
SKIP_REPORT_IN_ISSUES: "${SKIP_REPORT_IN_ISSUES:-true}"
COLORIZED_LOGS: "true"
QA_LOG_LEVEL: "info"
QA_TESTS: "$QA_TESTS"
QA_FEATURE_FLAGS: "${QA_FEATURE_FLAGS}"
QA_FRAMEWORK_CHANGES: "${QA_FRAMEWORK_CHANGES:-false}"
QA_SUITES: "$QA_SUITES"
YML
)
echo "$variables" >$pipeline_yml
cat .gitlab/ci/package-and-test/main.gitlab-ci.yml >>$pipeline_yml
echo "Generated e2e:package-and-test pipeline with following variables section:"
echo "$variables"

View File

@ -194,16 +194,12 @@ module Trigger
{
'GITLAB_VERSION' => source_sha,
'IMAGE_TAG' => source_sha,
'QA_IMAGE' => ENV['QA_IMAGE'],
'SKIP_QA_DOCKER' => 'true',
'SKIP_QA_TEST' => 'true',
'ALTERNATIVE_SOURCES' => 'true',
'SECURITY_SOURCES' => Trigger.security? ? 'true' : 'false',
'ee' => Trigger.ee? ? 'true' : 'false',
'QA_BRANCH' => ENV['QA_BRANCH'] || 'master',
'CACHE_UPDATE' => ENV['OMNIBUS_GITLAB_CACHE_UPDATE'],
'GITLAB_QA_OPTIONS' => ENV['GITLAB_QA_OPTIONS'],
'QA_TESTS' => ENV['QA_TESTS'],
'ALLURE_JOB_NAME' => ENV['ALLURE_JOB_NAME']
'CACHE_UPDATE' => ENV['OMNIBUS_GITLAB_CACHE_UPDATE']
}
end
end

View File

@ -20,7 +20,8 @@
"artifacts",
"artifacts_expire_at",
"tag_list",
"runner"
"runner",
"project"
],
"properties": {
"id": { "type": "integer" },
@ -64,6 +65,9 @@
{ "type": "null" },
{ "$ref": "runner.json" }
]
},
"project": {
"ci_job_token_scope_enabled": { "type": "boolean" }
}
},
"additionalProperties":false

View File

@ -1,18 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`settings_titles renders properly 1`] = `
<div>
<h5
class="gl-border-b-solid gl-border-b-1 gl-border-gray-200 gl-pb-3"
>
foo
</h5>
<p>
bar
</p>
</div>
`;

View File

@ -1,143 +0,0 @@
import { GlSprintf, GlToggle, GlFormGroup, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import component from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
import {
DUPLICATES_TOGGLE_LABEL,
DUPLICATES_SETTING_EXCEPTION_TITLE,
DUPLICATES_SETTINGS_EXCEPTION_LEGEND,
} from '~/packages_and_registries/settings/group/constants';
describe('Duplicates Settings', () => {
let wrapper;
const defaultProps = {
duplicatesAllowed: false,
duplicateExceptionRegex: 'foo',
modelNames: {
allowed: 'allowedModel',
exception: 'exceptionModel',
},
};
const mountComponent = (propsData = defaultProps) => {
wrapper = shallowMount(component, {
propsData,
stubs: {
GlSprintf,
},
});
};
afterEach(() => {
wrapper.destroy();
});
const findToggle = () => wrapper.findComponent(GlToggle);
const findInputGroup = () => wrapper.findComponent(GlFormGroup);
const findInput = () => wrapper.findComponent(GlFormInput);
it('has a toggle', () => {
mountComponent();
expect(findToggle().exists()).toBe(true);
expect(findToggle().props()).toMatchObject({
label: DUPLICATES_TOGGLE_LABEL,
value: !defaultProps.duplicatesAllowed,
});
});
it('toggle emits an update event', () => {
mountComponent();
findToggle().vm.$emit('change', false);
expect(wrapper.emitted('update')).toStrictEqual([
[{ [defaultProps.modelNames.allowed]: true }],
]);
});
describe('when the duplicates are disabled', () => {
it('shows a form group with an input field', () => {
mountComponent();
expect(findInputGroup().exists()).toBe(true);
expect(findInputGroup().attributes()).toMatchObject({
'label-for': 'maven-duplicated-settings-regex-input',
label: DUPLICATES_SETTING_EXCEPTION_TITLE,
description: DUPLICATES_SETTINGS_EXCEPTION_LEGEND,
});
});
it('shows an input field', () => {
mountComponent();
expect(findInput().exists()).toBe(true);
expect(findInput().attributes()).toMatchObject({
id: 'maven-duplicated-settings-regex-input',
value: defaultProps.duplicateExceptionRegex,
});
});
it('input change event emits an update event', () => {
mountComponent();
findInput().vm.$emit('change', 'bar');
expect(wrapper.emitted('update')).toStrictEqual([
[{ [defaultProps.modelNames.exception]: 'bar' }],
]);
});
describe('valid state', () => {
it('form group has correct props', () => {
mountComponent();
expect(findInputGroup().attributes()).toMatchObject({
state: 'true',
'invalid-feedback': '',
});
});
});
describe('invalid state', () => {
it('form group has correct props', () => {
const propsWithError = {
...defaultProps,
duplicateExceptionRegexError: 'some error string',
};
mountComponent(propsWithError);
expect(findInputGroup().attributes()).toMatchObject({
'invalid-feedback': propsWithError.duplicateExceptionRegexError,
});
});
});
});
describe('when the duplicates are enabled', () => {
it('hides the form input group', () => {
mountComponent({ ...defaultProps, duplicatesAllowed: true });
expect(findInputGroup().exists()).toBe(false);
});
});
describe('loading', () => {
beforeEach(() => {
mountComponent({ ...defaultProps, loading: true });
});
it('disables the enable toggle', () => {
expect(findToggle().props('disabled')).toBe(true);
});
it('disables the form input', () => {
expect(findInput().attributes('disabled')).toBe('true');
});
});
});

View File

@ -1,54 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
describe('generic_settings', () => {
let wrapper;
const mountComponent = () => {
wrapper = shallowMount(GenericSettings, {
scopedSlots: {
default: '<div data-testid="default-slot">{{props.modelNames}}</div>',
},
});
};
const findSettingsTitle = () => wrapper.findComponent(SettingsTitles);
const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
afterEach(() => {
wrapper.destroy();
});
describe('title component', () => {
it('has a title component', () => {
mountComponent();
expect(findSettingsTitle().exists()).toBe(true);
});
it('passes the correct props', () => {
mountComponent();
expect(findSettingsTitle().props()).toMatchObject({
title: 'Generic',
subTitle: 'Settings for Generic packages',
});
});
});
describe('default slot', () => {
it('accept a default slots', () => {
mountComponent();
expect(findDefaultSlot().exists()).toBe(true);
});
it('binds model names', () => {
mountComponent();
expect(findDefaultSlot().text()).toContain('genericDuplicatesAllowed');
expect(findDefaultSlot().text()).toContain('genericDuplicateExceptionRegex');
});
});
});

View File

@ -1,54 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
describe('maven_settings', () => {
let wrapper;
const mountComponent = () => {
wrapper = shallowMount(MavenSettings, {
scopedSlots: {
default: '<div data-testid="default-slot">{{props.modelNames}}</div>',
},
});
};
const findSettingsTitle = () => wrapper.findComponent(SettingsTitles);
const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
afterEach(() => {
wrapper.destroy();
});
describe('title component', () => {
it('has a title component', () => {
mountComponent();
expect(findSettingsTitle().exists()).toBe(true);
});
it('passes the correct props', () => {
mountComponent();
expect(findSettingsTitle().props()).toMatchObject({
title: 'Maven',
subTitle: 'Settings for Maven packages',
});
});
});
describe('default slot', () => {
it('accept a default slots', () => {
mountComponent();
expect(findDefaultSlot().exists()).toBe(true);
});
it('binds model names', () => {
mountComponent();
expect(findDefaultSlot().text()).toContain('mavenDuplicatesAllowed');
expect(findDefaultSlot().text()).toContain('mavenDuplicateExceptionRegex');
});
});
});

View File

@ -1,35 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
describe('settings_titles', () => {
let wrapper;
const defaultProps = {
title: 'foo',
subTitle: 'bar',
};
const mountComponent = (propsData = defaultProps) => {
wrapper = shallowMount(SettingsTitles, {
propsData,
});
};
const findSubTitle = () => wrapper.find('p');
afterEach(() => {
wrapper.destroy();
});
it('renders properly', () => {
mountComponent();
expect(wrapper.element).toMatchSnapshot();
});
it('does not render the subtitle paragraph when no subtitle is passed', () => {
mountComponent({ title: defaultProps.title });
expect(findSubTitle().exists()).toBe(false);
});
});

View File

@ -1,14 +1,16 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import mrStatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
describe('MR widget status icon component', () => {
let wrapper;
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findStatusIcon = () => wrapper.findComponent(StatusIcon);
const findIcon = () => wrapper.findComponent(GlIcon);
const createWrapper = (props, mountFn = shallowMount) => {
wrapper = mountFn(mrStatusIcon, {
const createWrapper = (props) => {
wrapper = shallowMount(mrStatusIcon, {
propsData: {
...props,
},
@ -17,27 +19,45 @@ describe('MR widget status icon component', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('while loading', () => {
it('renders loading icon', () => {
createWrapper({ status: 'loading' });
expect(findLoadingIcon().exists()).toBe(true);
expect(findStatusIcon().exists()).toBe(true);
expect(findStatusIcon().props().isLoading).toBe(true);
});
});
describe('with status icon', () => {
it('renders success status icon', () => {
createWrapper({ status: 'success' }, mount);
createWrapper({ status: 'success' });
expect(wrapper.find('[data-testid="status_success-icon"]').exists()).toBe(true);
expect(findStatusIcon().exists()).toBe(true);
expect(findStatusIcon().props().iconName).toBe('success');
});
it('renders failed status icon', () => {
createWrapper({ status: 'failed' }, mount);
createWrapper({ status: 'failed' });
expect(wrapper.find('[data-testid="status_failed-icon"]').exists()).toBe(true);
expect(findStatusIcon().exists()).toBe(true);
expect(findStatusIcon().props().iconName).toBe('failed');
});
it('renders merged status icon', () => {
createWrapper({ status: 'merged' });
expect(findIcon().exists()).toBe(true);
expect(findIcon().props().name).toBe('merge');
});
it('renders closed status icon', () => {
createWrapper({ status: 'closed' });
expect(findIcon().exists()).toBe(true);
expect(findIcon().props().name).toBe('merge-request-close');
});
});
});

View File

@ -4,16 +4,41 @@ exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have
<div
class="mr-widget-body media"
>
<svg
aria-hidden="true"
class="gl-text-blue-500 gl-mr-3 gl-mt-1 gl-icon s24"
data-testid="status_scheduled-icon"
role="img"
<div
class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3"
>
<use
href="#status_scheduled"
/>
</svg>
<div
class="gl-display-flex gl-m-auto"
>
<div
class="gl-mr-3 gl-p-2 gl-m-0! gl-text-blue-500 gl-w-6 gl-p-2"
>
<div
class="gl-rounded-full gl-relative gl-display-flex mr-widget-extension-icon"
>
<div
class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto"
>
<div
class="gl-display-flex gl-m-auto gl-translate-y-n50"
>
<svg
aria-label="Scheduled "
class="gl-display-block gl-icon s12"
data-qa-selector="status_scheduled_icon"
data-testid="status-scheduled-icon"
role="img"
>
<use
href="#status-scheduled"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="media-body gl-display-flex"
@ -42,7 +67,7 @@ exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have
</h4>
<div
class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto gl-mt-1"
class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto"
>
<div>
<div
@ -124,16 +149,41 @@ exports[`MRWidgetAutoMergeEnabled when graphql is enabled template should have c
<div
class="mr-widget-body media"
>
<svg
aria-hidden="true"
class="gl-text-blue-500 gl-mr-3 gl-mt-1 gl-icon s24"
data-testid="status_scheduled-icon"
role="img"
<div
class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3"
>
<use
href="#status_scheduled"
/>
</svg>
<div
class="gl-display-flex gl-m-auto"
>
<div
class="gl-mr-3 gl-p-2 gl-m-0! gl-text-blue-500 gl-w-6 gl-p-2"
>
<div
class="gl-rounded-full gl-relative gl-display-flex mr-widget-extension-icon"
>
<div
class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto"
>
<div
class="gl-display-flex gl-m-auto gl-translate-y-n50"
>
<svg
aria-label="Scheduled "
class="gl-display-block gl-icon s12"
data-qa-selector="status_scheduled_icon"
data-testid="status-scheduled-icon"
role="img"
>
<use
href="#status-scheduled"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="media-body gl-display-flex"
@ -162,7 +212,7 @@ exports[`MRWidgetAutoMergeEnabled when graphql is enabled template should have c
</h4>
<div
class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto gl-mt-1"
class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto"
>
<div>
<div

Some files were not shown because too many files have changed in this diff Show More