Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0c918eb567
commit
60273ebb30
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 #
|
||||
###############
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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')"
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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`)
|
||||
|
|
|
|||
|
|
@ -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.`,
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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!"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module JavascriptHelper
|
||||
def page_specific_javascript_tag(js)
|
||||
javascript_include_tag asset_path(js)
|
||||
end
|
||||
end
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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**.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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:
|
||||
|
||||

|
||||
- 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>/<trigger> issue new <title> <kbd>⇧ Shift</kbd>+<kbd>↵ Enter</kbd> <description></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>/<trigger> issue show <issue-number></kbd> | Show the issue with ID `<issue-number>` from the project that `<trigger>` is tied to. | `/gitlab issue show 42` |
|
||||
| <kbd>/<trigger> deploy <environment> to <environment></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
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
`;
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue