Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
74d9798736
commit
e0277d5393
|
|
@ -453,6 +453,106 @@
|
|||
.feature-flag-development-config-patterns: &feature-flag-development-config-patterns
|
||||
- "{,ee/}config/feature_flags/{development,ops}/*.yml"
|
||||
|
||||
##################
|
||||
# Conditions set #
|
||||
##################
|
||||
.strict-ee-only-rules:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
|
||||
.as-if-jh-default-exclusion-rules:
|
||||
rules:
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-stable-branch-refs
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-as-if-jh
|
||||
allow_failure: true
|
||||
|
||||
.rails:rules:minimal-default-rules:
|
||||
rules:
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
|
||||
|
||||
.rails:rules:ee-and-foss-default-rules:
|
||||
rules:
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
|
||||
.rails:rules:as-if-foss-migration-unit-integration:minimal-default-rules:
|
||||
rules:
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
|
||||
.rails:rules:unit-integration:minimal-default-rules:
|
||||
rules:
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- !reference [".rails:rules:as-if-foss-migration-unit-integration:minimal-default-rules", rules]
|
||||
|
||||
.rails:rules:system-default-rules:
|
||||
rules:
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *workhorse-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
|
||||
.rails:rules:system:minimal-default-rules:
|
||||
rules:
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *workhorse-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
|
||||
.rails:rules:previous-failed-tests-default-rules:
|
||||
rules:
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
|
||||
################
|
||||
# Shared rules #
|
||||
################
|
||||
|
|
@ -516,7 +616,6 @@
|
|||
rules:
|
||||
- <<: *if-default-refs
|
||||
changes: *code-backstage-patterns
|
||||
when: on_success
|
||||
|
||||
.dev-fixtures:rules:ee-only:
|
||||
rules:
|
||||
|
|
@ -524,7 +623,6 @@
|
|||
when: never
|
||||
- <<: *if-default-refs
|
||||
changes: *code-backstage-patterns
|
||||
when: on_success
|
||||
|
||||
##############
|
||||
# Docs rules #
|
||||
|
|
@ -540,7 +638,6 @@
|
|||
rules:
|
||||
- <<: *if-default-refs
|
||||
changes: *docs-patterns
|
||||
when: on_success
|
||||
|
||||
.docs:rules:deprecations-and-removals:
|
||||
rules:
|
||||
|
|
@ -550,14 +647,12 @@
|
|||
##################
|
||||
# GraphQL rules #
|
||||
##################
|
||||
|
||||
.graphql:rules:graphql-verify:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-default-refs
|
||||
changes: *code-backstage-qa-patterns
|
||||
when: on_success
|
||||
|
||||
##################
|
||||
# Frontend rules #
|
||||
|
|
@ -589,18 +684,8 @@
|
|||
|
||||
.frontend:rules:compile-test-assets-as-if-jh:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-stable-branch-refs
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-as-if-jh
|
||||
allow_failure: true
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- !reference [".as-if-jh-default-exclusion-rules", rules]
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
allow_failure: true
|
||||
- changes: *code-backstage-qa-patterns
|
||||
|
|
@ -616,10 +701,7 @@
|
|||
|
||||
.frontend:rules:default-frontend-jobs-as-if-foss:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
|
|
@ -631,18 +713,8 @@
|
|||
|
||||
.frontend:rules:default-frontend-jobs-as-if-jh:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-stable-branch-refs
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-as-if-jh
|
||||
allow_failure: true
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- !reference [".as-if-jh-default-exclusion-rules", rules]
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
allow_failure: true
|
||||
- <<: *if-merge-request
|
||||
|
|
@ -670,12 +742,7 @@
|
|||
|
||||
.frontend:rules:jest:minimal:
|
||||
rules:
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request-labels-run-all-jest
|
||||
when: never
|
||||
- <<: *if-default-refs
|
||||
|
|
@ -689,10 +756,7 @@
|
|||
|
||||
.frontend:rules:eslint-as-if-foss:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
# We already have `static-analysis as-if-foss` which already runs `lint:eslint:all` if the `pipeline:run-as-if-foss` label is set.
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
when: never
|
||||
|
|
@ -740,7 +804,6 @@
|
|||
rules:
|
||||
- <<: *if-default-refs
|
||||
changes: *code-patterns
|
||||
when: on_success
|
||||
|
||||
###############
|
||||
# Pages rules #
|
||||
|
|
@ -756,14 +819,10 @@
|
|||
rules:
|
||||
- <<: *if-default-refs
|
||||
changes: *code-qa-patterns
|
||||
when: on_success
|
||||
|
||||
.qa:rules:as-if-foss:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-qa-patterns
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
|
|
@ -835,20 +894,8 @@
|
|||
|
||||
.rails:rules:ee-and-foss-migration:minimal:
|
||||
rules:
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:unit-integration:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request
|
||||
changes: *db-patterns
|
||||
when: never
|
||||
|
|
@ -869,110 +916,37 @@
|
|||
|
||||
.rails:rules:ee-and-foss-unit:
|
||||
rules:
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- !reference [".rails:rules:ee-and-foss-default-rules", rules]
|
||||
- changes: *backend-patterns
|
||||
|
||||
.rails:rules:ee-and-foss-unit:minimal:
|
||||
rules:
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:unit-integration:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request
|
||||
changes: *backend-patterns
|
||||
|
||||
.rails:rules:ee-and-foss-integration:
|
||||
rules:
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- !reference [".rails:rules:ee-and-foss-default-rules", rules]
|
||||
- changes: *backend-patterns
|
||||
|
||||
.rails:rules:ee-and-foss-integration:minimal:
|
||||
rules:
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:unit-integration:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request
|
||||
changes: *backend-patterns
|
||||
|
||||
.rails:rules:ee-and-foss-system:
|
||||
rules:
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *workhorse-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- !reference [".rails:rules:system-default-rules", rules]
|
||||
- changes: *code-backstage-patterns
|
||||
|
||||
.rails:rules:ee-and-foss-system:minimal:
|
||||
rules:
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *workhorse-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:system:minimal-default-rules", rules]
|
||||
|
||||
.rails:rules:ee-and-foss-fast_spec_helper:
|
||||
rules:
|
||||
|
|
@ -1011,20 +985,8 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:unit-integration:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request
|
||||
changes: *db-patterns
|
||||
when: never
|
||||
|
|
@ -1033,37 +995,15 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- !reference [".rails:rules:ee-and-foss-default-rules", rules]
|
||||
- changes: *backend-patterns
|
||||
|
||||
.rails:rules:ee-only-unit:minimal:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:unit-integration:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request
|
||||
changes: *backend-patterns
|
||||
|
||||
|
|
@ -1071,37 +1011,15 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- !reference [".rails:rules:ee-and-foss-default-rules", rules]
|
||||
- changes: *backend-patterns
|
||||
|
||||
.rails:rules:ee-only-integration:minimal:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:unit-integration:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request
|
||||
changes: *backend-patterns
|
||||
|
||||
|
|
@ -1109,44 +1027,15 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *workhorse-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- !reference [".rails:rules:system-default-rules", rules]
|
||||
- changes: *code-backstage-patterns
|
||||
|
||||
.rails:rules:ee-only-system:minimal:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *workhorse-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:system:minimal-default-rules", rules]
|
||||
|
||||
.rails:rules:as-if-foss-migration:
|
||||
rules:
|
||||
|
|
@ -1172,18 +1061,8 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:as-if-foss-migration-unit-integration:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
changes: *db-patterns
|
||||
when: never
|
||||
|
|
@ -1192,17 +1071,7 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- !reference [".rails:rules:ee-and-foss-default-rules", rules]
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
changes: *backend-patterns
|
||||
|
||||
|
|
@ -1210,18 +1079,8 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:as-if-foss-migration-unit-integration:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
changes: *backend-patterns
|
||||
|
||||
|
|
@ -1229,17 +1088,7 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- !reference [".rails:rules:ee-and-foss-default-rules", rules]
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
changes: *backend-patterns
|
||||
|
||||
|
|
@ -1247,18 +1096,8 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- !reference [".rails:rules:as-if-foss-migration-unit-integration:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
changes: *backend-patterns
|
||||
|
||||
|
|
@ -1266,19 +1105,7 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *workhorse-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- !reference [".rails:rules:system-default-rules", rules]
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
changes: *code-backstage-patterns
|
||||
|
||||
|
|
@ -1286,12 +1113,7 @@
|
|||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-merge-request-approved
|
||||
when: never
|
||||
- <<: *if-automated-merge-request
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- !reference [".rails:rules:minimal-default-rules", rules]
|
||||
- <<: *if-merge-request
|
||||
changes: *core-backend-patterns
|
||||
when: never
|
||||
|
|
@ -1306,18 +1128,8 @@
|
|||
|
||||
.rails:rules:as-if-jh-rspec:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-stable-branch-refs
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-as-if-jh
|
||||
allow_failure: true
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- !reference [".as-if-jh-default-exclusion-rules", rules]
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
allow_failure: true
|
||||
|
|
@ -1345,19 +1157,11 @@
|
|||
|
||||
.rails:rules:detect-previous-failed-tests:
|
||||
rules:
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- !reference [".rails:rules:previous-failed-tests-default-rules", rules]
|
||||
|
||||
.rails:rules:rerun-previous-failed-tests:
|
||||
rules:
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- !reference [".rails:rules:previous-failed-tests-default-rules", rules]
|
||||
|
||||
.rails:rules:rspec-foss-impact:
|
||||
rules:
|
||||
|
|
@ -1418,19 +1222,21 @@
|
|||
- <<: *if-merge-request
|
||||
changes: *backend-patterns
|
||||
|
||||
.rails:rules:default-branch-schedule-nightly--code-backstage:
|
||||
.rails:rules:default-branch-schedule-nightly--code-backstage-default-rules:
|
||||
rules:
|
||||
- <<: *if-default-branch-schedule-nightly
|
||||
- <<: *if-merge-request
|
||||
changes: [".gitlab/ci/rails.gitlab-ci.yml"]
|
||||
|
||||
.rails:rules:default-branch-schedule-nightly--code-backstage:
|
||||
rules:
|
||||
- !reference [".rails:rules:default-branch-schedule-nightly--code-backstage-default-rules", rules]
|
||||
|
||||
.rails:rules:default-branch-schedule-nightly--code-backstage-ee-only:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-default-branch-schedule-nightly
|
||||
- <<: *if-merge-request
|
||||
changes: [".gitlab/ci/rails.gitlab-ci.yml"]
|
||||
- !reference [".rails:rules:default-branch-schedule-nightly--code-backstage-default-rules", rules]
|
||||
|
||||
.rails:rules:rspec-feature-flags:
|
||||
rules:
|
||||
|
|
@ -1737,7 +1543,6 @@
|
|||
when: never
|
||||
- <<: *if-default-branch-or-tag
|
||||
changes: *code-backstage-qa-patterns
|
||||
when: on_success
|
||||
|
||||
.setup:rules:dont-interrupt-me:
|
||||
rules:
|
||||
|
|
@ -1752,7 +1557,6 @@
|
|||
rules:
|
||||
- <<: *if-default-refs
|
||||
changes: *code-backstage-patterns
|
||||
when: on_success
|
||||
|
||||
.setup:rules:no-ee-check:
|
||||
rules:
|
||||
|
|
@ -1760,7 +1564,6 @@
|
|||
when: never
|
||||
- <<: *if-default-refs
|
||||
changes: *code-backstage-patterns
|
||||
when: on_success
|
||||
|
||||
.setup:rules:no-jh-check:
|
||||
rules:
|
||||
|
|
@ -1768,7 +1571,6 @@
|
|||
when: never
|
||||
- <<: *if-default-refs
|
||||
changes: *code-backstage-patterns
|
||||
when: on_success
|
||||
|
||||
.setup:rules:verify-tests-yml:
|
||||
rules:
|
||||
|
|
@ -1776,7 +1578,6 @@
|
|||
when: never
|
||||
- <<: *if-default-refs
|
||||
changes: *code-backstage-patterns
|
||||
when: on_success
|
||||
|
||||
.setup:rules:generate-frontend-fixtures-mapping:
|
||||
rules:
|
||||
|
|
@ -1790,18 +1591,8 @@
|
|||
|
||||
.setup:rules:add-jh-folder:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-stable-branch-refs
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-as-if-jh
|
||||
allow_failure: true
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
- !reference [".as-if-jh-default-exclusion-rules", rules]
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
allow_failure: true
|
||||
- changes: *code-backstage-qa-patterns
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
1.53.0
|
||||
1.54.0
|
||||
|
|
|
|||
|
|
@ -275,6 +275,8 @@ class GfmAutoComplete {
|
|||
UNASSIGN_REVIEWER: '/unassign_reviewer',
|
||||
REASSIGN: '/reassign',
|
||||
CC: '/cc',
|
||||
ATTENTION: '/attention',
|
||||
REMOVE_ATTENTION: '/remove_attention',
|
||||
};
|
||||
let assignees = [];
|
||||
let reviewers = [];
|
||||
|
|
@ -353,6 +355,23 @@ class GfmAutoComplete {
|
|||
} else if (command === MEMBER_COMMAND.UNASSIGN_REVIEWER) {
|
||||
// Only include members which are not assigned as a reviewer to Issuable currently
|
||||
return data.filter((member) => reviewers.includes(member.search));
|
||||
} else if (
|
||||
command === MEMBER_COMMAND.ATTENTION ||
|
||||
command === MEMBER_COMMAND.REMOVE_ATTENTION
|
||||
) {
|
||||
const attentionUsers = [
|
||||
...(SidebarMediator.singleton?.store?.assignees || []),
|
||||
...(SidebarMediator.singleton?.store?.reviewers || []),
|
||||
];
|
||||
const attentionRequested = command === MEMBER_COMMAND.REMOVE_ATTENTION;
|
||||
|
||||
return data.filter((member) =>
|
||||
attentionUsers.find(
|
||||
(u) =>
|
||||
createMemberSearchString(u).includes(member.search) &&
|
||||
u.attention_requested === attentionRequested,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return data;
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ export const COVERAGE_FUZZING_CONFIG_HELP_PATH = helpPagePath(
|
|||
|
||||
export const CORPUS_MANAGEMENT_NAME = __('Corpus Management');
|
||||
export const CORPUS_MANAGEMENT_DESCRIPTION = s__(
|
||||
'SecurityConfiguration|Manage corpus files used as mutation sources in coverage fuzzing.',
|
||||
'SecurityConfiguration|Manage corpus files used as seed inputs with coverage-guided fuzzing.',
|
||||
);
|
||||
export const CORPUS_MANAGEMENT_CONFIG_TEXT = s__('SecurityConfiguration|Manage corpus');
|
||||
|
||||
|
|
|
|||
|
|
@ -501,6 +501,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
.can_push_to_branch?(@merge_request.source_branch)
|
||||
|
||||
access_denied! unless access_check
|
||||
|
||||
access_denied! unless merge_request.permits_force_push?
|
||||
end
|
||||
|
||||
def merge_access_check
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ module Ci
|
|||
#
|
||||
# ref - The name (or names) of the branch(es)/tag(s) to limit the list of
|
||||
# pipelines to.
|
||||
# sha - The commit SHA (or mutliple SHAs) to limit the list of pipelines to.
|
||||
# sha - The commit SHA (or multiple SHAs) to limit the list of pipelines to.
|
||||
# limit - This limits a backlog search, default to 100.
|
||||
def self.newest_first(ref: nil, sha: nil, limit: 100)
|
||||
relation = order(id: :desc)
|
||||
|
|
|
|||
|
|
@ -471,6 +471,12 @@ class MergeRequest < ApplicationRecord
|
|||
rebase_jid.present? && Gitlab::SidekiqStatus.running?(rebase_jid)
|
||||
end
|
||||
|
||||
def permits_force_push?
|
||||
return true unless ProtectedBranch.protected?(source_project, source_branch)
|
||||
|
||||
ProtectedBranch.allow_force_push?(source_project, source_branch)
|
||||
end
|
||||
|
||||
# Use this method whenever you need to make sure the head_pipeline is synced with the
|
||||
# branch head commit, for example checking if a merge request can be merged.
|
||||
# For more information check: https://gitlab.com/gitlab-org/gitlab-foss/issues/40004
|
||||
|
|
|
|||
|
|
@ -2779,6 +2779,12 @@ class Project < ApplicationRecord
|
|||
end
|
||||
|
||||
def save_topics
|
||||
topic_ids_before = self.topic_ids
|
||||
update_topics
|
||||
Projects::Topic.update_non_private_projects_counter(topic_ids_before, self.topic_ids, visibility_level_previously_was, visibility_level)
|
||||
end
|
||||
|
||||
def update_topics
|
||||
return if @topic_list.nil?
|
||||
|
||||
@topic_list = @topic_list.split(',') if @topic_list.instance_of?(String)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,29 @@ module Projects
|
|||
def search(query)
|
||||
fuzzy_search(query, [:name])
|
||||
end
|
||||
|
||||
def update_non_private_projects_counter(ids_before, ids_after, project_visibility_level_before, project_visibility_level_after)
|
||||
project_visibility_level_before ||= project_visibility_level_after
|
||||
|
||||
topics_to_decrement = []
|
||||
topics_to_increment = []
|
||||
topic_ids_removed = ids_before - ids_after
|
||||
topic_ids_retained = ids_before & ids_after
|
||||
topic_ids_added = ids_after - ids_before
|
||||
|
||||
if project_visibility_level_before > Gitlab::VisibilityLevel::PRIVATE
|
||||
topics_to_decrement += topic_ids_removed
|
||||
topics_to_decrement += topic_ids_retained if project_visibility_level_after == Gitlab::VisibilityLevel::PRIVATE
|
||||
end
|
||||
|
||||
if project_visibility_level_after > Gitlab::VisibilityLevel::PRIVATE
|
||||
topics_to_increment += topic_ids_added
|
||||
topics_to_increment += topic_ids_retained if project_visibility_level_before == Gitlab::VisibilityLevel::PRIVATE
|
||||
end
|
||||
|
||||
where(id: topics_to_increment).update_counters(non_private_projects_count: 1) unless topics_to_increment.empty?
|
||||
where(id: topics_to_decrement).where('non_private_projects_count > 0').update_counters(non_private_projects_count: -1) unless topics_to_decrement.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -246,7 +246,9 @@ module MergeRequests
|
|||
def remove_all_attention_requests(merge_request)
|
||||
return unless merge_request.attention_requested_enabled?
|
||||
|
||||
::MergeRequests::BulkRemoveAttentionRequestedService.new(project: merge_request.project, current_user: current_user, merge_request: merge_request).execute
|
||||
users = merge_request.reviewers + merge_request.assignees
|
||||
|
||||
::MergeRequests::BulkRemoveAttentionRequestedService.new(project: merge_request.project, current_user: current_user, merge_request: merge_request, users: users.uniq).execute
|
||||
end
|
||||
|
||||
def remove_attention_requested(merge_request, user)
|
||||
|
|
|
|||
|
|
@ -3,20 +3,24 @@
|
|||
module MergeRequests
|
||||
class BulkRemoveAttentionRequestedService < MergeRequests::BaseService
|
||||
attr_accessor :merge_request
|
||||
attr_accessor :users
|
||||
|
||||
def initialize(project:, current_user:, merge_request:)
|
||||
def initialize(project:, current_user:, merge_request:, users:)
|
||||
super(project: project, current_user: current_user)
|
||||
|
||||
@merge_request = merge_request
|
||||
@users = users
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def execute
|
||||
return error("Invalid permissions") unless can?(current_user, :update_merge_request, merge_request)
|
||||
|
||||
merge_request.merge_request_assignees.update_all(state: :reviewed)
|
||||
merge_request.merge_request_reviewers.update_all(state: :reviewed)
|
||||
merge_request.merge_request_assignees.where(user_id: users).update_all(state: :reviewed)
|
||||
merge_request.merge_request_reviewers.where(user_id: users).update_all(state: :reviewed)
|
||||
|
||||
success
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
= _("If you get a lot of false alarms from repository checks, you can clear all repository check information from the database.")
|
||||
- clear_repository_checks_link = _('Clear all repository checks')
|
||||
- clear_repository_checks_message = _('This clears repository check states for all projects in the database and cannot be undone. Are you sure?')
|
||||
= link_to clear_repository_checks_link, clear_repository_check_states_admin_application_settings_path, data: { confirm: clear_repository_checks_message }, method: :put, class: "gl-button btn btn-sm btn-danger gl-mt-3"
|
||||
= link_to clear_repository_checks_link, clear_repository_check_states_admin_application_settings_path, data: { confirm: clear_repository_checks_message, confirm_btn_variant: 'danger' }, aria: { label: _('Clear repository checks') }, method: :put, class: "gl-button btn btn-sm btn-danger gl-mt-3"
|
||||
|
||||
.sub-section
|
||||
%h4= _("Housekeeping")
|
||||
|
|
|
|||
|
|
@ -282,6 +282,15 @@
|
|||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: cronjob:container_registry_migration_observer
|
||||
:worker_name: ContainerRegistry::Migration::ObserverWorker
|
||||
:feature_category: :container_registry
|
||||
:has_external_dependencies:
|
||||
:urgency: :low
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: cronjob:database_batched_background_migration
|
||||
:worker_name: Database::BatchedBackgroundMigrationWorker
|
||||
:feature_category: :database
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ContainerRegistry
|
||||
module Migration
|
||||
class ObserverWorker
|
||||
include ApplicationWorker
|
||||
# This worker does not perform work scoped to a context
|
||||
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
|
||||
|
||||
COUNT_BATCH_SIZE = 50000
|
||||
|
||||
data_consistency :sticky
|
||||
feature_category :container_registry
|
||||
urgency :low
|
||||
deduplicate :until_executed, including_scheduled: true
|
||||
idempotent!
|
||||
|
||||
def perform
|
||||
return unless ::ContainerRegistry::Migration.enabled?
|
||||
|
||||
use_replica_if_available do
|
||||
ContainerRepository::MIGRATION_STATES.each do |state|
|
||||
relation = ContainerRepository.with_migration_state(state)
|
||||
count = ::Gitlab::Database::BatchCount.batch_count(
|
||||
relation, batch_size: COUNT_BATCH_SIZE
|
||||
)
|
||||
name = "#{state}_count".to_sym
|
||||
log_extra_metadata_on_done(name, count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def use_replica_if_available(&block)
|
||||
::Gitlab::Database::LoadBalancing::Session.current.use_replicas_for_read_queries(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -16,7 +16,15 @@ module Projects
|
|||
def before_gitaly_call(task, resource)
|
||||
return unless gc?(task)
|
||||
|
||||
::Projects::GitDeduplicationService.new(resource).execute
|
||||
# Don't block garbage collection if we can't fetch into an object pool
|
||||
# due to some gRPC error because we don't want to accumulate cruft.
|
||||
# See https://gitlab.com/gitlab-org/gitaly/-/issues/4022.
|
||||
begin
|
||||
::Projects::GitDeduplicationService.new(resource).execute
|
||||
rescue Gitlab::Git::CommandTimedOut, GRPC::Internal => e
|
||||
Gitlab::ErrorTracking.track_exception(e)
|
||||
end
|
||||
|
||||
cleanup_orphan_lfs_file_references(resource)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ const path = require('path');
|
|||
const { History, HistoryWithTTL } = require('./history');
|
||||
const log = require('./log');
|
||||
|
||||
const onRequestEntryPoint = (app, callback) => {
|
||||
app.use((req, res, next) => {
|
||||
const onRequestEntryPoint = (callback) => {
|
||||
return (req, res, next) => {
|
||||
const fileName = path.basename(req.url);
|
||||
|
||||
/**
|
||||
|
|
@ -20,7 +20,7 @@ const onRequestEntryPoint = (app, callback) => {
|
|||
}
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -40,7 +40,9 @@ class NoopCompiler {
|
|||
logStatus() {}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
setupMiddleware() {}
|
||||
createMiddleware() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -55,8 +57,8 @@ class HistoryOnlyCompiler extends NoopCompiler {
|
|||
this.history = new History(historyFilePath);
|
||||
}
|
||||
|
||||
setupMiddleware(app) {
|
||||
onRequestEntryPoint(app, (entryPoint) => {
|
||||
createMiddleware() {
|
||||
return onRequestEntryPoint((entryPoint) => {
|
||||
this.history.onRequestEntryPoint(entryPoint);
|
||||
});
|
||||
}
|
||||
|
|
@ -92,16 +94,16 @@ class IncrementalWebpackCompiler {
|
|||
log(`Currently compiling route entrypoints: ${this.history.size} of ${totalCount}`);
|
||||
}
|
||||
|
||||
setupMiddleware(app, server) {
|
||||
onRequestEntryPoint(app, (entryPoint) => {
|
||||
createMiddleware(devServer) {
|
||||
return onRequestEntryPoint((entryPoint) => {
|
||||
const wasVisitedRecently = this.history.onRequestEntryPoint(entryPoint);
|
||||
if (!wasVisitedRecently) {
|
||||
log(`Have not visited ${entryPoint} recently. Adding to compilation.`);
|
||||
|
||||
setTimeout(() => {
|
||||
server.middleware.invalidate(() => {
|
||||
if (server.sockets) {
|
||||
server.sockWrite(server.sockets, 'content-changed');
|
||||
devServer.invalidate(() => {
|
||||
if (devServer.sockets) {
|
||||
devServer.sendMessage(devServer.webSocketServer.clients, 'static-changed');
|
||||
}
|
||||
});
|
||||
}, TIMEOUT);
|
||||
|
|
|
|||
|
|
@ -542,6 +542,9 @@ Settings.cron_jobs['container_expiration_policy_worker']['job_class'] = 'Contain
|
|||
Settings.cron_jobs['container_registry_migration_guard_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['container_registry_migration_guard_worker']['cron'] ||= '*/10 * * * *'
|
||||
Settings.cron_jobs['container_registry_migration_guard_worker']['job_class'] = 'ContainerRegistry::Migration::GuardWorker'
|
||||
Settings.cron_jobs['container_registry_migration_observer_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['container_registry_migration_observer_worker']['cron'] ||= '*/30 * * * *'
|
||||
Settings.cron_jobs['container_registry_migration_observer_worker']['job_class'] = 'ContainerRegistry::Migration::ObserverWorker'
|
||||
Settings.cron_jobs['image_ttl_group_policy_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['image_ttl_group_policy_worker']['cron'] ||= '40 0 * * *'
|
||||
Settings.cron_jobs['image_ttl_group_policy_worker']['job_class'] = 'DependencyProxy::ImageTtlGroupPolicyWorker'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.quickactions.i_quickactions_attention_monthly
|
||||
description: Count of MAU using the `/attention` quick action
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.8'
|
||||
introduced_by_url:
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_quickactions_attention
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.quickactions.i_quickactions_remove_attention_monthly
|
||||
description: Count of MAU using the `/remove_attention` quick action
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.8'
|
||||
introduced_by_url:
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_quickactions_remove_attention
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.quickactions.i_quickactions_attention_weekly
|
||||
description: Count of WAU using the `/attention` quick action
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.8'
|
||||
introduced_by_url:
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_quickactions_attention
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
key_path: redis_hll_counters.quickactions.i_quickactions_remove_attention_weekly
|
||||
description: Count of WAU using the `/remove_attention` quick action
|
||||
product_section: dev
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.8'
|
||||
introduced_by_url:
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_quickactions_remove_attention
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -38,11 +38,10 @@ const SUPPORTED_BROWSERS_HASH = crypto
|
|||
const VENDOR_DLL = process.env.WEBPACK_VENDOR_DLL && process.env.WEBPACK_VENDOR_DLL !== 'false';
|
||||
const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
|
||||
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
|
||||
const IS_DEV_SERVER = process.env.WEBPACK_DEV_SERVER === 'true';
|
||||
const IS_DEV_SERVER = process.env.WEBPACK_SERVE === 'true';
|
||||
|
||||
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
|
||||
const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
|
||||
const { DEV_SERVER_PUBLIC_ADDR } = process.env;
|
||||
const { DEV_SERVER_HOST, DEV_SERVER_PUBLIC_ADDR } = process.env;
|
||||
const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10);
|
||||
const DEV_SERVER_ALLOWED_HOSTS =
|
||||
process.env.DEV_SERVER_ALLOWED_HOSTS && process.env.DEV_SERVER_ALLOWED_HOSTS.split(',');
|
||||
const DEV_SERVER_HTTPS = process.env.DEV_SERVER_HTTPS && process.env.DEV_SERVER_HTTPS !== 'false';
|
||||
|
|
@ -654,9 +653,6 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
|
||||
// enable HMR only in webpack-dev-server
|
||||
DEV_SERVER_LIVERELOAD && new webpack.HotModuleReplacementPlugin(),
|
||||
|
||||
// optionally generate webpack bundle analysis
|
||||
WEBPACK_REPORT &&
|
||||
new BundleAnalyzerPlugin({
|
||||
|
|
@ -689,19 +685,38 @@ module.exports = {
|
|||
*/
|
||||
new webpack.IgnorePlugin(/moment/, /pikaday/),
|
||||
].filter(Boolean),
|
||||
|
||||
devServer: {
|
||||
before(app, server) {
|
||||
incrementalCompiler.setupMiddleware(app, server);
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
if (!devServer) {
|
||||
throw new Error('webpack-dev-server is not defined');
|
||||
}
|
||||
|
||||
const incrementalCompilerMiddleware = incrementalCompiler.createMiddleware(devServer);
|
||||
|
||||
if (incrementalCompilerMiddleware) {
|
||||
middlewares.unshift(incrementalCompilerMiddleware);
|
||||
}
|
||||
|
||||
return middlewares;
|
||||
},
|
||||
host: DEV_SERVER_HOST,
|
||||
port: DEV_SERVER_PORT,
|
||||
public: DEV_SERVER_PUBLIC_ADDR,
|
||||
allowedHosts: DEV_SERVER_ALLOWED_HOSTS,
|
||||
// Only print errors to CLI
|
||||
devMiddleware: {
|
||||
stats: 'errors-only',
|
||||
},
|
||||
host: DEV_SERVER_HOST || 'localhost',
|
||||
port: DEV_SERVER_PORT || 3808,
|
||||
https: DEV_SERVER_HTTPS,
|
||||
contentBase: false,
|
||||
stats: 'errors-only',
|
||||
hot: DEV_SERVER_LIVERELOAD,
|
||||
inline: DEV_SERVER_LIVERELOAD,
|
||||
// The following settings are mainly needed for HMR support in gitpod.
|
||||
// Per default only local hosts are allowed, but here we could
|
||||
// allow different hosts (e.g. ['.gitpod'], all of gitpod),
|
||||
// as the webpack server will run on a different subdomain than
|
||||
// the rails application
|
||||
...(DEV_SERVER_ALLOWED_HOSTS ? { allowedHosts: DEV_SERVER_ALLOWED_HOSTS } : {}),
|
||||
client: {
|
||||
...(DEV_SERVER_PUBLIC_ADDR ? { webSocketURL: DEV_SERVER_PUBLIC_ADDR } : {}),
|
||||
},
|
||||
},
|
||||
|
||||
devtool: NO_SOURCEMAPS ? false : devtool,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTopicsNonPrivateProjectsCount < Gitlab::Database::Migration[1.0]
|
||||
def up
|
||||
add_column :topics, :non_private_projects_count, :bigint, null: false, default: 0
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :topics, :non_private_projects_count
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTopicsNonPrivateProjectsCountIndex < Gitlab::Database::Migration[1.0]
|
||||
INDEX_NAME = 'index_topics_non_private_projects_count'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :topics, [:non_private_projects_count, :id], order: { non_private_projects_count: :desc }, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :topics, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTmpIndexOnIdAndMigrationStateToContainerRepositories < Gitlab::Database::Migration[1.0]
|
||||
INDEX_NAME = 'tmp_index_container_repositories_on_id_migration_state'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
# Temporary index to be removed https://gitlab.com/gitlab-org/gitlab/-/issues/351783
|
||||
def up
|
||||
add_concurrent_index :container_repositories, [:id, :migration_state], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :container_repositories, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SchedulePopulateTopicsNonPrivateProjectsCount < Gitlab::Database::Migration[1.0]
|
||||
MIGRATION = 'PopulateTopicsNonPrivateProjectsCount'
|
||||
BATCH_SIZE = 10_000
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
define_batchable_model('topics'),
|
||||
MIGRATION,
|
||||
DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
track_jobs: true
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
26600e01d8b31a4308d0e23564e4d4c52488ec87ad7990a410b7cc0c031f12e7
|
||||
|
|
@ -0,0 +1 @@
|
|||
51c7ab860b952281bd7f65d68e7a539a8eee57cac3bbdaf439ff5593f5b065ed
|
||||
|
|
@ -0,0 +1 @@
|
|||
7740d1e71571576a709ae5bfd46f60ea3fb4be3f48cddec2cca53f148096cdd7
|
||||
|
|
@ -0,0 +1 @@
|
|||
0efe482aa626cf80912feaa1176837253b094fc434f273bee35b5fe3e8ce4243
|
||||
|
|
@ -20272,6 +20272,7 @@ CREATE TABLE topics (
|
|||
avatar text,
|
||||
description text,
|
||||
total_projects_count bigint DEFAULT 0 NOT NULL,
|
||||
non_private_projects_count bigint DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT check_26753fb43a CHECK ((char_length(avatar) <= 255)),
|
||||
CONSTRAINT check_5d1a07c8c8 CHECK ((char_length(description) <= 1024)),
|
||||
CONSTRAINT check_7a90d4c757 CHECK ((char_length(name) <= 255))
|
||||
|
|
@ -27935,6 +27936,8 @@ CREATE UNIQUE INDEX index_token_with_ivs_on_hashed_plaintext_token ON token_with
|
|||
|
||||
CREATE UNIQUE INDEX index_token_with_ivs_on_hashed_token ON token_with_ivs USING btree (hashed_token);
|
||||
|
||||
CREATE INDEX index_topics_non_private_projects_count ON topics USING btree (non_private_projects_count DESC, id);
|
||||
|
||||
CREATE UNIQUE INDEX index_topics_on_name ON topics USING btree (name);
|
||||
|
||||
CREATE INDEX index_topics_on_name_trigram ON topics USING gin (name gin_trgm_ops);
|
||||
|
|
@ -28317,6 +28320,8 @@ CREATE INDEX tmp_idx_deduplicate_vulnerability_occurrences ON vulnerability_occu
|
|||
|
||||
CREATE INDEX tmp_idx_vulnerability_occurrences_on_id_where_report_type_7_99 ON vulnerability_occurrences USING btree (id) WHERE (report_type = ANY (ARRAY[7, 99]));
|
||||
|
||||
CREATE INDEX tmp_index_container_repositories_on_id_migration_state ON container_repositories USING btree (id, migration_state);
|
||||
|
||||
CREATE INDEX tmp_index_for_namespace_id_migration_on_routes ON routes USING btree (id) WHERE ((namespace_id IS NULL) AND ((source_type)::text = 'Namespace'::text));
|
||||
|
||||
CREATE INDEX tmp_index_members_on_state ON members USING btree (state) WHERE (state = 2);
|
||||
|
|
|
|||
|
|
@ -990,7 +990,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/approve
|
|||
| `approval_password` | string | no | Current user's password. Required if [**Require user password to approve**](../user/project/merge_requests/approvals/settings.md#require-user-password-to-approve) is enabled in the project settings. |
|
||||
|
||||
The `sha` parameter works in the same way as
|
||||
when [accepting a merge request](merge_requests.md#accept-mr): if it is passed, then it must
|
||||
when [accepting a merge request](merge_requests.md#merge-a-merge-request): if it is passed, then it must
|
||||
match the current HEAD of the merge request for the approval to be added. If it
|
||||
does not match, the response code is `409`.
|
||||
|
||||
|
|
|
|||
|
|
@ -1454,9 +1454,9 @@ DELETE /projects/:id/merge_requests/:merge_request_iid
|
|||
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/4/merge_requests/85"
|
||||
```
|
||||
|
||||
## Accept MR
|
||||
## Merge a merge request
|
||||
|
||||
Merge changes submitted with MR using this API.
|
||||
Accept and merge changes submitted with MR using this API.
|
||||
|
||||
If a merge request is unable to be accepted (such as Draft, Closed, Pipeline Pending Completion, or Failed while requiring Success) - you receive a `405` and the error message 'Method Not Allowed'
|
||||
|
||||
|
|
|
|||
|
|
@ -18,21 +18,27 @@ to track custom events.
|
|||
|
||||
For the recommended frontend tracking implementation, see [Usage recommendations](#usage-recommendations).
|
||||
|
||||
Tracking implementations must have an `action` and a `category`. You can provide additional
|
||||
categories from the [structured event taxonomy](index.md#structured-event-taxonomy) with an `extra` object
|
||||
that accepts key-value pairs.
|
||||
Structured events and page views include the [`gitlab_standard`](schemas.md#gitlab_standard)
|
||||
context, using the `window.gl.snowplowStandardContext` object which includes
|
||||
[default data](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/views/layouts/_snowplow.html.haml)
|
||||
as base. This object can be modified for any subsequent structured event fired,
|
||||
although it's not recommended.
|
||||
|
||||
| Field | Type | Default value | Description |
|
||||
Tracking implementations must have an `action` and a `category`. You can provide additional
|
||||
properties from the [structured event taxonomy](index.md#structured-event-taxonomy), in
|
||||
addition to an `extra` object that accepts key-value pairs.
|
||||
|
||||
| Property | Type | Default value | Description |
|
||||
|:-----------|:-------|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `category` | string | `document.body.dataset.page` | Page or subsection of a page in which events are captured. |
|
||||
| `action` | string | generic | Action the user is taking. Clicks must be `click` and activations must be `activate`. For example, focusing a form field is `activate_form_input`, and clicking a button is `click_button`. |
|
||||
| `data` | object | `{}` | Additional data such as `label`, `property`, `value`, `context` as described in [Structured event taxonomy](index.md#structured-event-taxonomy), and `extra` (key-value pairs object). |
|
||||
| `action` | string | `'generic'` | Action the user is taking. Clicks must be `click` and activations must be `activate`. For example, focusing a form field is `activate_form_input`, and clicking a button is `click_button`. |
|
||||
| `data` | object | `{}` | Additional data such as `label`, `property`, `value` as described in [Structured event taxonomy](index.md#structured-event-taxonomy), `context` for custom contexts, and `extra` (key-value pairs object). |
|
||||
|
||||
### Usage recommendations
|
||||
|
||||
- Use [data attributes](#implement-data-attribute-tracking) on HTML elements that emit `click`, `show.bs.dropdown`, or `hide.bs.dropdown` events.
|
||||
- Use the [Vue mixin](#implement-vue-component-tracking) for tracking custom events, or if the supported events for data attributes are not propagating.
|
||||
- Use the [tracking class](#implement-raw-javascript-tracking) when tracking raw JavaScript files.
|
||||
- Use the [Vue mixin](#implement-vue-component-tracking) for tracking custom events, or if the supported events for data attributes are not propagating. For example, clickable components that don't emit `click`.
|
||||
- Use the [tracking class](#implement-raw-javascript-tracking) when tracking in vanilla JavaScript files.
|
||||
|
||||
### Implement data attribute tracking
|
||||
|
||||
|
|
@ -41,7 +47,10 @@ To implement tracking for HAML or Vue templates, add a [`data-track` attribute](
|
|||
The following example shows `data-track-*` attributes assigned to a button:
|
||||
|
||||
```haml
|
||||
%button.btn{ data: { track_action: "click_button", track_label: "template_preview", track_property: "my-template" } }
|
||||
%button.btn{ data: { track: { action: "click_button", label: "template_preview", property: "my-template" } } }
|
||||
|
||||
// or
|
||||
// %button.btn{ data: { track_action: "click_button", track_label: "template_preview", track_property: "my-template" } }
|
||||
```
|
||||
|
||||
```html
|
||||
|
|
@ -62,7 +71,7 @@ The following example shows `data-track-*` attributes assigned to a button:
|
|||
| `data-track-property` | false | Any additional property of the element, or object being acted on. |
|
||||
| `data-track-value` | false | Describes a numeric value (decimal) directly related to the event. This could be the value of an input. For example, `10` when clicking `internal` visibility. If omitted, this is the element's `value` property or `undefined`. For checkboxes, the default value is the element's checked attribute or `0` when unchecked. |
|
||||
| `data-track-extra` | false | A key-value pair object passed as a valid JSON string. This attribute is added to the `extra` property in our [`gitlab_standard`](schemas.md#gitlab_standard) schema. |
|
||||
| `data-track-context` | false | The `context` as described in our [Structured event taxonomy](index.md#structured-event-taxonomy). |
|
||||
| `data-track-context` | false | To append a custom context object, passed as a valid JSON string. |
|
||||
|
||||
#### Event listeners
|
||||
|
||||
|
|
@ -74,12 +83,19 @@ If click events stop propagating, you must implement listeners and [Vue componen
|
|||
|
||||
#### Helper methods
|
||||
|
||||
Use the following Ruby helper:
|
||||
You can use the following Ruby helper:
|
||||
|
||||
```ruby
|
||||
tracking_attrs(label, action, property) # { data: { track_label... } }
|
||||
```
|
||||
|
||||
You can also use it on HAML templates:
|
||||
|
||||
```haml
|
||||
%button{ **tracking_attrs('main_navigation', 'click_button', 'navigation') }
|
||||
|
||||
// When adding additional data
|
||||
// %button{ data: { platform: "...", **tracking_attrs('main_navigation', 'click_button', 'navigation') } }
|
||||
```
|
||||
|
||||
If you use the GitLab helper method [`nav_link`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/helpers/tab_helper.rb#L76), you must wrap `html_options` under the `html_options` keyword argument. If you
|
||||
|
|
@ -91,7 +107,7 @@ use the `ActionView` helper method [`link_to`](https://api.rubyonrails.org/v5.2.
|
|||
track_action: "click_button" })
|
||||
|
||||
# Good
|
||||
= nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { data: { track_label:
|
||||
= nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { data: { track_label:
|
||||
"explore_groups", track_action: "click_button" } })
|
||||
|
||||
# Good (other helpers)
|
||||
|
|
@ -101,63 +117,64 @@ track_action: "click_button" })
|
|||
|
||||
### Implement Vue component tracking
|
||||
|
||||
For custom event tracking, use a Vue `mixin` in components. Vue `mixin` exposes the `Tracking.event`
|
||||
static method and the `track` method. You can specify tracking options in `data` or `computed`.
|
||||
These options override any defaults and allow the values to be dynamic from props or based on state.
|
||||
For custom event tracking, use the [Vue mixin](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/tracking/tracking.js#L207). It exposes `Tracking.event` as the `track` method.
|
||||
You can specify tracking options by creating a `tracking` data object or
|
||||
computed property, and as a second parameter: `this.track('click_button', opts)`.
|
||||
These options override any defaults and allow the values to be dynamic from props or based on state:
|
||||
|
||||
Several default options are passed when an event is tracked from the component:
|
||||
|
||||
- `category`: If you don't specify, by default `document.body.dataset.page` is used.
|
||||
- `label`
|
||||
- `property`
|
||||
- `value`
|
||||
| Property | Type | Default | Example |
|
||||
| -- | -- | -- | -- |
|
||||
| `category` | string | `document.body.dataset.page` | `'code_quality_walkthrough'` |
|
||||
| `label` | string | `''` | `'process_start_button'` |
|
||||
| `property` | string | `''` | `'asc'` or `'desc'` |
|
||||
| `value` | integer | `undefined` | `0`, `1`, `500` |
|
||||
| `extra` | object | `{}` | `{ selectedVariant: this.variant }` |
|
||||
|
||||
To implement Vue component tracking:
|
||||
|
||||
1. Import the `Tracking` library and request a `mixin`:
|
||||
1. Import the `Tracking` library and call the `mixin` method:
|
||||
|
||||
```javascript
|
||||
import Tracking from '~/tracking';
|
||||
const trackingMixin = Tracking.mixin;
|
||||
|
||||
const trackingMixin = Tracking.mixin();
|
||||
|
||||
// Optionally provide default properties
|
||||
// const trackingMixin = Tracking.mixin({ label: 'right_sidebar' });
|
||||
```
|
||||
|
||||
1. Provide categories to track the event from the component. For example, to track all events in a
|
||||
component with a label, use the `label` category:
|
||||
|
||||
```javascript
|
||||
import Tracking from '~/tracking';
|
||||
const trackingMixin = Tracking.mixin({ label: 'right_sidebar' });
|
||||
```
|
||||
|
||||
1. In the component, declare the Vue `mixin`:
|
||||
1. Use the mixin in the component:
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
mixins: [trackingMixin],
|
||||
// ...[component implementation]...
|
||||
// Or
|
||||
// mixins: [Tracking.mixin()],
|
||||
// mixins: [Tracking.mixin({ label: 'right_sidebar' })],
|
||||
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
tracking: {
|
||||
label: 'left_sidebar',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
1. To receive event data as a tracking object or computed property:
|
||||
- Declare it in the `data` function. Use a `tracking` object when default event properties are dynamic or provided at runtime:
|
||||
1. You can specify tracking options in by creating a `tracking` data object
|
||||
or computed property:
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
name: 'RightSidebar',
|
||||
|
||||
mixins: [Tracking.mixin()],
|
||||
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
variant: '',
|
||||
tracking: {
|
||||
label: 'right_sidebar',
|
||||
// category: '',
|
||||
// property: '',
|
||||
// value: '',
|
||||
// experiment: '',
|
||||
|
|
@ -165,18 +182,28 @@ component with a label, use the `label` category:
|
|||
},
|
||||
};
|
||||
},
|
||||
|
||||
// Or
|
||||
// computed: {
|
||||
// tracking() {
|
||||
// return {
|
||||
// property: this.variant,
|
||||
// extra: { expanded: this.expanded },
|
||||
// };
|
||||
// },
|
||||
// },
|
||||
};
|
||||
```
|
||||
|
||||
- Declare it in the event data in the `track` function. This object merges with any previously provided options:
|
||||
1. Call the `track` method. Tracking options can be passed as the second parameter:
|
||||
|
||||
```javascript
|
||||
this.track('click_button', {
|
||||
label: 'right_sidebar',
|
||||
});
|
||||
```
|
||||
```javascript
|
||||
this.track('click_button', {
|
||||
label: 'right_sidebar',
|
||||
});
|
||||
```
|
||||
|
||||
1. Optional. Use the `track` method in a template:
|
||||
Or use the `track` method in the template:
|
||||
|
||||
```html
|
||||
<template>
|
||||
|
|
@ -185,55 +212,67 @@ component with a label, use the `label` category:
|
|||
|
||||
<div v-if="expanded">
|
||||
<p>Hello world!</p>
|
||||
<button @click="track('click_action')">Track another event</button>
|
||||
<button @click="track('click_button')">Track another event</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
The following example shows an implementation of Vue component tracking:
|
||||
#### Testing example
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
name: 'RightSidebar',
|
||||
mixins: [Tracking.mixin({ label: 'right_sidebar' })],
|
||||
name: 'CountDropdown',
|
||||
|
||||
mixins: [Tracking.mixin({ label: 'count_dropdown' })],
|
||||
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
variant: 'counter',
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
this.expanded = !this.expanded;
|
||||
// Additional data will be merged, like `value` below
|
||||
this.track('click_toggle', { value: Number(this.expanded) });
|
||||
}
|
||||
}
|
||||
handleChange({ target }) {
|
||||
const { variant } = this;
|
||||
|
||||
this.count = Number(target.value);
|
||||
|
||||
this.track('change_value', {
|
||||
value: this.count,
|
||||
extra: { variant }
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### Testing example
|
||||
|
||||
```javascript
|
||||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
// mockTracking(category, documentOverride, spyMethod)
|
||||
|
||||
describe('RightSidebar.vue', () => {
|
||||
describe('CountDropdown.vue', () => {
|
||||
let trackingSpy;
|
||||
let wrapper;
|
||||
|
||||
...
|
||||
|
||||
beforeEach(() => {
|
||||
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||
});
|
||||
|
||||
const findToggle = () => wrapper.find('[data-testid="toggle"]');
|
||||
const findDropdown = () => wrapper.find('[data-testid="dropdown"]');
|
||||
|
||||
it('tracks turning off toggle', () => {
|
||||
findToggle().trigger('click');
|
||||
it('tracks change event', () => {
|
||||
const dropdown = findDropdown();
|
||||
dropdown.element.value = 30;
|
||||
dropdown.trigger('change');
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_toggle', {
|
||||
label: 'right_sidebar',
|
||||
value: 0,
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'change_value', {
|
||||
value: 30,
|
||||
label: 'count_dropdown',
|
||||
extra: { variant: 'counter' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -241,7 +280,8 @@ describe('RightSidebar.vue', () => {
|
|||
|
||||
### Implement raw JavaScript tracking
|
||||
|
||||
To call custom event tracking and instrumentation directly from the JavaScript file, call the `Tracking.event` static function.
|
||||
To track from a vanilla JavaScript file, use the `Tracking.event` static function
|
||||
(calls [`dispatchSnowplowEvent`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/tracking/dispatch_snowplow_event.js)).
|
||||
|
||||
The following example demonstrates tracking a click on a button by manually calling `Tracking.event`.
|
||||
|
||||
|
|
@ -251,7 +291,7 @@ import Tracking from '~/tracking';
|
|||
const button = document.getElementById('create_from_template_button');
|
||||
|
||||
button.addEventListener('click', () => {
|
||||
Tracking.event('dashboard:projects:index', 'click_button', {
|
||||
Tracking.event(undefined, 'click_button', {
|
||||
label: 'create_from_template',
|
||||
property: 'template_preview',
|
||||
extra: {
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ sequenceDiagram
|
|||
GitLab.com Snowplow Collector ->> GitLab.com Snowplow Collector: Write to disk
|
||||
end
|
||||
GitLab.com Snowplow Collector ->> S3 Bucket: Kinesis Firehose
|
||||
Note over GitLab.com Snowplow Collector, S3 Bucket: Pseudonymization
|
||||
S3 Bucket->>Snowflake DW: Import data
|
||||
Snowflake DW->>Snowflake DW: Transform data using dbt
|
||||
Snowflake DW->>Sisense Dashboards: Data available for querying
|
||||
|
|
|
|||
|
|
@ -12,19 +12,23 @@ This page provides Snowplow schema reference for GitLab events.
|
|||
|
||||
We are including the [`gitlab_standard` schema](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_standard/jsonschema/) with every event. See [Standardize Snowplow Schema](https://gitlab.com/groups/gitlab-org/-/epics/5218) for details.
|
||||
|
||||
The [`StandardContext`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/tracking/standard_context.rb) class represents this schema in the application.
|
||||
The [`StandardContext`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/tracking/standard_context.rb)
|
||||
class represents this schema in the application. Some properties are automatically populated for [frontend](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/views/layouts/_snowplow.html.haml)
|
||||
events.
|
||||
|
||||
| Field Name | Required | Type | Description |
|
||||
|----------------|---------------------|-----------------------|---------------------------------------------------------------------------------------------|
|
||||
| `project_id` | **{dotted-circle}** | integer | |
|
||||
| `namespace_id` | **{dotted-circle}** | integer | |
|
||||
| `user_id` | **{dotted-circle}** | integer | User database record ID attribute. This file undergoes a pseudonymization process at the collector level. |
|
||||
| `context_generated_at` | **{dotted-circle}** | string (date time format) | Timestamp indicating when context was generated. |
|
||||
| `environment` | **{check-circle}** | string (max 32 chars) | Name of the source environment, such as `production` or `staging` |
|
||||
| `source` | **{check-circle}** | string (max 32 chars) | Name of the source application, such as `gitlab-rails` or `gitlab-javascript` |
|
||||
| `plan` | **{dotted-circle}** | string (max 32 chars) | Name of the plan for the namespace, such as `free`, `premium`, or `ultimate`. Automatically picked from the `namespace`. |
|
||||
| `google_analytics_id` | **{dotted-circle}** | string (max 32 chars) | Google Analytics ID, present when set from our marketing sites. |
|
||||
| `extra` | **{dotted-circle}** | JSON | Any additional data associated with the event, in the form of key-value pairs |
|
||||
| Field Name | Required | Default value | Type | Description |
|
||||
|----------------|:-------------------:|-----------------------|--|---------------------------------------------------------------------------------------------|
|
||||
| `project_id` | **{dotted-circle}** | Current project ID * | integer | |
|
||||
| `namespace_id` | **{dotted-circle}** | Current group/namespace ID * | integer | |
|
||||
| `user_id` | **{dotted-circle}** | Current user ID * | integer | User database record ID attribute. This file undergoes a pseudonymization process at the collector level. |
|
||||
| `context_generated_at` | **{dotted-circle}** | Current timestamp | string (date time format) | Timestamp indicating when context was generated. |
|
||||
| `environment` | **{check-circle}** | Current environment | string (max 32 chars) | Name of the source environment, such as `production` or `staging` |
|
||||
| `source` | **{check-circle}** | Event source | string (max 32 chars) | Name of the source application, such as `gitlab-rails` or `gitlab-javascript` |
|
||||
| `plan` | **{dotted-circle}** | Current namespace plan * | string (max 32 chars) | Name of the plan for the namespace, such as `free`, `premium`, or `ultimate`. Automatically picked from the `namespace`. |
|
||||
| `google_analytics_id` | **{dotted-circle}** | GA ID value * | string (max 32 chars) | Google Analytics ID, present when set from our marketing sites. |
|
||||
| `extra` | **{dotted-circle}** | | JSON | Any additional data associated with the event, in the form of key-value pairs |
|
||||
|
||||
_\* Default value present for frontend events only_
|
||||
|
||||
## Default Schema
|
||||
|
||||
|
|
|
|||
|
|
@ -116,87 +116,106 @@ your website could enable the covert redirect attack.
|
|||
|
||||
1. Select the icon. Sign in to GitHub and authorize the GitLab application.
|
||||
|
||||
## GitHub Enterprise with self-signed Certificate
|
||||
|
||||
If you are attempting to import projects from GitHub Enterprise with a self-signed
|
||||
certificate and the imports are failing, you must disable SSL verification.
|
||||
It should be disabled by adding `verify_ssl` to `false` in the provider configuration
|
||||
and changing the global Git `sslVerify` option to `false` in the GitLab server.
|
||||
|
||||
For Omnibus package:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['omniauth_providers'] = [
|
||||
{
|
||||
name: "github",
|
||||
# label: "Provider name", # optional label for login button, defaults to "GitHub"
|
||||
app_id: "YOUR_APP_ID",
|
||||
app_secret: "YOUR_APP_SECRET",
|
||||
url: "https://github.example.com/",
|
||||
verify_ssl: false,
|
||||
args: { scope: "user:email" }
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
You must also disable Git SSL verification on the server hosting GitLab.
|
||||
|
||||
```ruby
|
||||
omnibus_gitconfig['system'] = { "http" => ["sslVerify = false"] }
|
||||
```
|
||||
|
||||
For installation from source:
|
||||
|
||||
```yaml
|
||||
- { name: 'github',
|
||||
# label: 'Provider name', # optional label for login button, defaults to "GitHub"
|
||||
app_id: 'YOUR_APP_ID',
|
||||
app_secret: 'YOUR_APP_SECRET',
|
||||
url: "https://github.example.com/",
|
||||
verify_ssl: false,
|
||||
args: { scope: 'user:email' } }
|
||||
```
|
||||
|
||||
You must also disable Git SSL verification on the server hosting GitLab.
|
||||
|
||||
```shell
|
||||
git config --global http.sslVerify false
|
||||
```
|
||||
|
||||
For the changes to take effect, [reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) if you installed
|
||||
via Omnibus, or [restart GitLab](../administration/restart_gitlab.md#installations-from-source) if you installed from source.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error 500 when trying to sign in to GitLab via GitHub Enterprise
|
||||
### Imports from GitHub Enterprise with a self-signed certificate fail
|
||||
|
||||
Check the [`production.log`](../administration/logs.md#productionlog)
|
||||
on your GitLab server to obtain further details. If you are getting the error like
|
||||
`Faraday::ConnectionFailed (execution expired)` in the log, there may be a connectivity issue
|
||||
between your GitLab instance and GitHub Enterprise. To verify it, [start the rails console](../administration/operations/rails_console.md#starting-a-rails-console-session)
|
||||
and run the commands below replacing `<github_url>` with the URL of your GitHub Enterprise instance:
|
||||
When you import projects from GitHub Enterprise using a self-signed
|
||||
certificate, the imports fail.
|
||||
|
||||
```ruby
|
||||
uri = URI.parse("https://<github_url>") # replace `GitHub-URL` with the real one here
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = true
|
||||
http.verify_mode = 1
|
||||
response = http.request(Net::HTTP::Get.new(uri.request_uri))
|
||||
```
|
||||
To fix this issue, you must disable SSL verification:
|
||||
|
||||
If you are getting a similar `execution expired` error, it confirms the theory about the
|
||||
network connectivity. In that case, make sure that the GitLab server is able to reach your
|
||||
GitHub enterprise instance.
|
||||
1. Set `verify_ssl` to `false` in the configuration file.
|
||||
|
||||
- **For Omnibus installations**
|
||||
|
||||
```ruby
|
||||
gitlab_rails['omniauth_providers'] = [
|
||||
{
|
||||
name: "github",
|
||||
# label: "Provider name", # optional label for login button, defaults to "GitHub"
|
||||
app_id: "YOUR_APP_ID",
|
||||
app_secret: "YOUR_APP_SECRET",
|
||||
url: "https://github.example.com/",
|
||||
verify_ssl: false,
|
||||
args: { scope: "user:email" }
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
- **For installations from source**
|
||||
|
||||
```yaml
|
||||
- { name: 'github',
|
||||
# label: 'Provider name', # optional label for login button, defaults to "GitHub"
|
||||
app_id: 'YOUR_APP_ID',
|
||||
app_secret: 'YOUR_APP_SECRET',
|
||||
url: "https://github.example.com/",
|
||||
verify_ssl: false,
|
||||
args: { scope: 'user:email' } }
|
||||
```
|
||||
|
||||
1. Change the global Git `sslVerify` option to `false` on the GitLab server.
|
||||
|
||||
- **For Omnibus installations**
|
||||
|
||||
```ruby
|
||||
omnibus_gitconfig['system'] = { "http" => ["sslVerify = false"] }
|
||||
```
|
||||
|
||||
- **For installations from source**
|
||||
|
||||
```shell
|
||||
git config --global http.sslVerify false
|
||||
```
|
||||
|
||||
1. [Reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure)
|
||||
if you installed using Omnibus, or [restart GitLab](../administration/restart_gitlab.md#installations-from-source)
|
||||
if you installed from source.
|
||||
|
||||
### Signing in using GitHub Enterprise returns a 500 error
|
||||
|
||||
This error can occur because of a network connectivity issue between your
|
||||
GitLab instance and GitHub Enterprise.
|
||||
|
||||
To check for a connectivity issue:
|
||||
|
||||
1. Go to the [`production.log`](../administration/logs.md#productionlog)
|
||||
on your GitLab server and look for the following error:
|
||||
|
||||
``` plaintext
|
||||
Faraday::ConnectionFailed (execution expired)
|
||||
```
|
||||
|
||||
1. [Start the rails console](../administration/operations/rails_console.md#starting-a-rails-console-session)
|
||||
and run the following commands. Replace `<github_url>` with the URL of your
|
||||
GitHub Enterprise instance:
|
||||
|
||||
```ruby
|
||||
uri = URI.parse("https://<github_url>") # replace `GitHub-URL` with the real one here
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = true
|
||||
http.verify_mode = 1
|
||||
response = http.request(Net::HTTP::Get.new(uri.request_uri))
|
||||
```
|
||||
|
||||
1. If a similar `execution expired` error is returned, this confirms the error is
|
||||
caused by a connectivity issue. Make sure the GitLab server can reach
|
||||
your GitHub Enterprise instance.
|
||||
|
||||
### Signing in using your GitHub account without a pre-existing GitLab account is not allowed
|
||||
|
||||
If you're getting the message `Signing in using your GitHub account without a pre-existing
|
||||
GitLab account is not allowed. Create a GitLab account first, and then connect it to your
|
||||
GitHub account` when signing in, in GitLab:
|
||||
When you sign in to GitLab, you get the following error:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
```plaintext
|
||||
Signing in using your GitHub account without a pre-existing
|
||||
GitLab account is not allowed. Create a GitLab account first,
|
||||
and then connect it to your GitHub account
|
||||
```
|
||||
|
||||
To fix this issue, you must activate GitHub sign-in in GitLab:
|
||||
|
||||
1. On the top bar, in the top right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. On the left sidebar, select **Account**.
|
||||
1. In the **Social sign-in** section, select **Connect to GitHub**.
|
||||
|
||||
After that, you should be able to sign in via GitHub successfully.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
|
|
@ -1,119 +1,66 @@
|
|||
---
|
||||
description: "Merge Request Analytics help you understand the efficiency of your code review process, and the productivity of your team." # Up to ~200 chars long. They will be displayed in Google Search snippets. It may help to write the page intro first, and then reuse it here.
|
||||
description: "Merge request analytics help you understand the efficiency of your code review process, and the productivity of your team." # Up to ~200 chars long. They will be displayed in Google Search snippets. It may help to write the page intro first, and then reuse it here.
|
||||
stage: Manage
|
||||
group: Optimize
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Merge Request Analytics **(PREMIUM)**
|
||||
# Merge request analytics **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229045) in GitLab 13.3.
|
||||
> - Moved to GitLab Premium in 13.9.
|
||||
|
||||
Merge Request Analytics helps you understand the efficiency of your code review process, and the productivity of your team.
|
||||
Use merge request analytics to view:
|
||||
|
||||
## Overview
|
||||
- The number of merge requests your organization merged per month.
|
||||
- The average time between merge request creation and merge.
|
||||
- Information about each merged merge request.
|
||||
|
||||
Merge Request Analytics displays information that will help you evaluate the efficiency and productivity of your merge request process.
|
||||
You can use merge request analytics to identify:
|
||||
|
||||
The Throughput chart shows the number of merge requests merged, by month. Merge request throughput is
|
||||
a common measure of productivity in software engineering. Although imperfect, the average throughput can
|
||||
be a meaningful benchmark of your team's overall productivity.
|
||||
- Low or high productivity months.
|
||||
- Efficiency and productivity of your merge request process.
|
||||
- Efficiency of your code review process.
|
||||
|
||||
To access Merge Request Analytics:
|
||||
## View merge request analytics
|
||||
|
||||
You must have at least the Reporter role to view merge request analytics.
|
||||
|
||||
To view merge request analytics:
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Analytics > Merge request**.
|
||||
|
||||
## Use cases
|
||||
## View merge requests merged per month
|
||||
|
||||
This feature is designed for [development team leaders](https://about.gitlab.com/handbook/marketing/strategic-marketing/roles-personas/#delaney-development-team-lead)
|
||||
and others who want to understand broad patterns in code review and productivity.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232651) in GitLab 13.3.
|
||||
> - Filtering [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229266) in GitLab 13.4
|
||||
|
||||
You can use Merge Request Analytics to expose when your team is most and least productive, and
|
||||
identify improvements that might substantially accelerate your development cycle.
|
||||
To view the number of merge requests merged per month:
|
||||
|
||||
Merge Request Analytics could be used when:
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Analytics > Merge request**.
|
||||
1. Optional. Filter results:
|
||||
1. Select the filter bar.
|
||||
1. Select a parameter.
|
||||
1. Select a value or enter text to refine the results.
|
||||
1. To adjust the date range:
|
||||
- In the **From** field, select a start date.
|
||||
- In the **To** field, select an end date.
|
||||
|
||||
- You want to know if you were more productive this month than last month, or 12 months ago.
|
||||
- You want to drill into low- or high-productivity months to understand the work that took place.
|
||||
The **Throughput** chart shows the number of merge requests merged per month.
|
||||
|
||||
## Visualizations and data
|
||||
The table shows up to 20 merge requests per page, and includes
|
||||
information about each merge request.
|
||||
|
||||
The following visualizations and data are available, representing all merge requests that were merged in the given date range.
|
||||
|
||||
### Mean time to merge
|
||||
## View average time between merge request creation and merge
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229389) in GitLab 13.9.
|
||||
|
||||
The mean time to merge (MTTM) metric shows the average time between when a merge request is created,
|
||||
and when it is merged. To view how the MTTM changes over time, compare MTTM across different date ranges.
|
||||
Use the number in **Mean time to merge** to view the average time between when a merge request is
|
||||
created and when it's merged.
|
||||
|
||||

|
||||
To view **Mean time to merge**:
|
||||
|
||||
### Throughput chart
|
||||
|
||||
The throughput chart shows the number of merge requests merged per month.
|
||||
|
||||

|
||||
|
||||
### Throughput table
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232651) in GitLab 13.3.
|
||||
|
||||
The Throughput table displays the most recent merge requests merged in the date range. The
|
||||
table displays up to 20 merge requests at a time. If there are more than 20 merge requests,
|
||||
you can paginate to them. For each merge request, you can review the following data:
|
||||
|
||||
- Title (as a link to the merge request itself)
|
||||
- ID
|
||||
- Pipeline status
|
||||
- Label count
|
||||
- Comment count
|
||||
- Approval count (if approved)
|
||||
- Date merged
|
||||
- Time to merge
|
||||
- Milestone
|
||||
- Commit count
|
||||
- Pipeline count
|
||||
- Line change counts
|
||||
- Assignees
|
||||
|
||||

|
||||
|
||||
## Filter the data
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229266) in GitLab 13.4
|
||||
|
||||
You can filter the data that is presented on the page based on the following parameters:
|
||||
|
||||
- Author
|
||||
- Assignee
|
||||
- Label
|
||||
- Milestone
|
||||
- Source branch
|
||||
- Target branch
|
||||
|
||||
To filter results:
|
||||
|
||||
1. Select the filter bar.
|
||||
1. Select a parameter to filter by.
|
||||
1. Select a value from the autocompleted results, or enter search text to refine the results.
|
||||
1. Press Enter.
|
||||
|
||||
## Date range
|
||||
|
||||
The date range is set to the past 12 months by default. You can modify the date range by changing the "From" and/or "To" values that appear alongside the filter bar. After changing either value, the data displayed on the page will update automatically.
|
||||
|
||||
## Tip: Bookmark preferred settings
|
||||
|
||||
You can bookmark preferred filters and date ranges. After you have applied a change to the
|
||||
filter bar or the date range, you'll see that information in the URL. You can create a
|
||||
bookmark for those preferred settings in your browser.
|
||||
|
||||
## Permissions
|
||||
|
||||
The **Merge Request Analytics** feature can be accessed only:
|
||||
|
||||
- On [GitLab Premium](https://about.gitlab.com/pricing/) and above.
|
||||
- By users with at least the Reporter role.
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Analytics > Merge request**.
|
||||
|
|
|
|||
|
|
@ -5,107 +5,30 @@ group: Import
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Import your project from Bitbucket Server to GitLab **(FREE)**
|
||||
# Import your project from Bitbucket Server **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/20164) in GitLab 11.2.
|
||||
|
||||
NOTE:
|
||||
The Bitbucket Server importer does not work with [Bitbucket Cloud](https://bitbucket.org).
|
||||
Use the [Bitbucket Cloud importer](bitbucket.md) for that.
|
||||
This process is different than [importing from Bitbucket Cloud](bitbucket.md).
|
||||
|
||||
Import your projects from Bitbucket Server to GitLab with minimal effort.
|
||||
From Bitbucket Server, you can import:
|
||||
|
||||
The Bitbucket importer can import:
|
||||
|
||||
- Repository description (GitLab 11.2+)
|
||||
- Git repository data (GitLab 11.2+)
|
||||
- Pull requests (GitLab 11.2+)
|
||||
- Pull request comments (GitLab 11.2+)
|
||||
- Repository description
|
||||
- Git repository data
|
||||
- Pull requests
|
||||
- Pull request comments
|
||||
|
||||
When importing, repository public access is retained. If a repository is private in Bitbucket, it's
|
||||
created as private in GitLab as well.
|
||||
|
||||
## Limitations
|
||||
|
||||
- GitLab doesn't allow comments on arbitrary lines of code, so any Bitbucket comments out of bounds
|
||||
are inserted as comments in the merge request.
|
||||
- Bitbucket Server allows multiple levels of threading. GitLab import collapses this into one thread
|
||||
and quote part of the original comment.
|
||||
- Declined pull requests have unreachable commits, which prevents the GitLab importer from
|
||||
generating a proper diff. These pull requests show up as empty changes.
|
||||
- Pull request approvals are not imported.
|
||||
- Attachments in Markdown are not imported.
|
||||
- Task lists are not imported.
|
||||
- Emoji reactions are not imported.
|
||||
- Project filtering does not support fuzzy search (only `starts with` or `full match strings` are
|
||||
supported).
|
||||
|
||||
## How it works
|
||||
|
||||
The Bitbucket Server importer works as follows:
|
||||
|
||||
1. The user is prompted to enter the URL, username, and password (or personal access token) to log in to Bitbucket.
|
||||
These credentials are preserved only as long as the importer is running.
|
||||
1. The importer attempts to list all the current repositories on the Bitbucket Server.
|
||||
1. Upon selection, the importer clones the repository and import pull requests and comments.
|
||||
|
||||
### User assignment
|
||||
|
||||
When issues/pull requests are being imported, the Bitbucket importer tries to
|
||||
find the author's email address with a confirmed email address in the GitLab
|
||||
user database. If no such user is available, the project creator is set as
|
||||
the author. The importer appends a note in the comment to mark the original
|
||||
creator.
|
||||
|
||||
The importer creates any new namespaces (groups) if they don't exist or in
|
||||
the case the namespace is taken, the repository is imported under the user's
|
||||
namespace that started the import process.
|
||||
|
||||
#### User assignment by username
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218609) in GitLab 13.4.
|
||||
> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default.
|
||||
> - It's disabled on GitLab.com.
|
||||
> - It's not recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to enable it.
|
||||
|
||||
WARNING:
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
|
||||
If you've enabled this feature, the importer tries to find a user in the GitLab user database with
|
||||
the author's:
|
||||
|
||||
- `username`
|
||||
- `slug`
|
||||
- `displayName`
|
||||
|
||||
If the user is not found by any of these properties, the project creator is set as the author.
|
||||
|
||||
##### Enable or disable User assignment by username
|
||||
|
||||
User assignment by username is under development and not ready for production use. It is
|
||||
deployed behind a feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
|
||||
can enable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:bitbucket_server_user_mapping_by_username)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:bitbucket_server_user_mapping_by_username)
|
||||
```
|
||||
|
||||
## Import your Bitbucket repositories
|
||||
|
||||
Prerequisite:
|
||||
Prerequisites:
|
||||
|
||||
- An administrator must have enabled the importer in
|
||||
**Admin > Application Settings > Visibility and access controls > Import sources**.
|
||||
- An administrator must have enabled the **Bitbucket Server** in
|
||||
**Admin > Settings > General > Visibility and access controls > Import sources**.
|
||||
- Review the importer's [limitations](#limitations).
|
||||
|
||||
To import your Bitbucket repositories:
|
||||
|
||||
|
|
@ -115,28 +38,77 @@ To import your Bitbucket repositories:
|
|||
1. Select **Import project**.
|
||||
1. Select **Bitbucket Server**.
|
||||
1. Log in to Bitbucket and grant GitLab access to your Bitbucket account.
|
||||
1. Select the projects that you'd like to import or import all projects.
|
||||
You can filter projects by name and select the namespace
|
||||
each project will be imported for.
|
||||
1. Select the projects to import, or import all projects. You can filter projects by name and select
|
||||
the namespace for which to import each project.
|
||||
|
||||
## Automate group and project import **(PREMIUM)**
|
||||
## Limitations
|
||||
|
||||
For information on automating user, group, and project import API calls, see
|
||||
[Automate group and project import](index.md#automate-group-and-project-import).
|
||||
- GitLab doesn't allow comments on arbitrary lines of code. Any out-of-bounds Bitbucket comments are
|
||||
inserted as comments in the merge request.
|
||||
- Bitbucket Server allows multiple threading levels. The importer collapses this into one thread and
|
||||
quotes part of the original comment.
|
||||
- Declined pull requests have unreachable commits. This prevents the importer from generating a
|
||||
proper diff. These pull requests show up as empty changes.
|
||||
- Project filtering doesn't support fuzzy search. Only starts with or full match strings are
|
||||
supported.
|
||||
|
||||
The following aren't imported:
|
||||
|
||||
- Pull request approvals
|
||||
- Attachments in Markdown
|
||||
- Task lists
|
||||
- Emoji reactions
|
||||
|
||||
## User assignment
|
||||
|
||||
When issues and pull requests are importing, the importer tries to find the author's email address
|
||||
with a confirmed email address in the GitLab user database. If no such user is available, the
|
||||
project creator is set as the author. The importer appends a note in the comment to mark the
|
||||
original creator.
|
||||
|
||||
The importer creates any new namespaces (groups) if they don't exist. If the namespace is taken, the
|
||||
repository imports under the namespace of the user who started the import process.
|
||||
|
||||
### User assignment by username
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218609) in GitLab 13.4 [with a flag](../../../administration/feature_flags.md) named `bitbucket_server_user_mapping_by_username`. Disabled by default.
|
||||
> - Not recommended for production use.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab and GitLab.com, by default this feature is not available. To make it
|
||||
available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md)
|
||||
named `bitbucket_server_user_mapping_by_username`. This feature is not ready for production use.
|
||||
|
||||
With this feature enabled, the importer tries to find a user in the GitLab user database with the
|
||||
author's:
|
||||
|
||||
- `username`
|
||||
- `slug`
|
||||
- `displayName`
|
||||
|
||||
If no user matches these properties, the project creator is set as the author.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### General
|
||||
|
||||
If the GUI-based import tool does not work, you can try to:
|
||||
|
||||
- Use the [GitLab Import API](../../../api/import.md#import-repository-from-bitbucket-server)
|
||||
Bitbucket Server endpoint.
|
||||
- Set up [repository mirroring](../repository/mirror/index.md).
|
||||
It provides verbose error output.
|
||||
|
||||
See the [troubleshooting section](bitbucket.md#troubleshooting)
|
||||
for Bitbucket Cloud.
|
||||
|
||||
### LFS objects not imported
|
||||
|
||||
If the project import completes but LFS objects can't be downloaded or cloned, you may be using a
|
||||
password or personal access token containing special characters. For more information, see
|
||||
[this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/337769).
|
||||
|
||||
### General
|
||||
## Related topics
|
||||
|
||||
If the GUI-based import tool does not work, you can try to:
|
||||
|
||||
- Use the [GitLab Import API](../../../api/import.md#import-repository-from-bitbucket-server) Bitbucket server endpoint.
|
||||
- Set up [Repository Mirroring](../repository/mirror/index.md), which provides verbose error output.
|
||||
|
||||
See the [troubleshooting](bitbucket.md#troubleshooting) section for [Bitbucket](bitbucket.md).
|
||||
For information on automating user, group, and project import API calls, see
|
||||
[Automate group and project import](index.md#automate-group-and-project-import).
|
||||
|
|
|
|||
|
|
@ -117,6 +117,9 @@ module API
|
|||
|
||||
forbidden!('Cannot push to source branch') unless
|
||||
user_access.can_push_to_branch?(merge_request.source_branch)
|
||||
|
||||
forbidden!('Source branch is protected from force push') unless
|
||||
merge_request.permits_force_push?
|
||||
end
|
||||
|
||||
params :merge_requests_params do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# The class to populates the non private projects counter of topics
|
||||
class PopulateTopicsNonPrivateProjectsCount
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
# Temporary AR model for topics
|
||||
class Topic < ActiveRecord::Base
|
||||
include EachBatch
|
||||
|
||||
self.table_name = 'topics'
|
||||
end
|
||||
|
||||
def perform(start_id, stop_id)
|
||||
Topic.where(id: start_id..stop_id).each_batch(of: SUB_BATCH_SIZE) do |batch|
|
||||
ActiveRecord::Base.connection.execute(<<~SQL)
|
||||
WITH batched_relation AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (#{batch.select(:id).limit(SUB_BATCH_SIZE).to_sql})
|
||||
UPDATE topics
|
||||
SET non_private_projects_count = (
|
||||
SELECT COUNT(*)
|
||||
FROM project_topics
|
||||
INNER JOIN projects
|
||||
ON project_topics.project_id = projects.id
|
||||
WHERE project_topics.topic_id = batched_relation.id
|
||||
AND projects.visibility_level > 0
|
||||
)
|
||||
FROM batched_relation
|
||||
WHERE topics.id = batched_relation.id
|
||||
SQL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -221,6 +221,7 @@ security-code-scan-sast:
|
|||
image:
|
||||
name: "$SAST_ANALYZER_IMAGE"
|
||||
variables:
|
||||
SAST_ANALYZER_IMAGE_TAG: '2'
|
||||
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/security-code-scan:$SAST_ANALYZER_IMAGE_TAG"
|
||||
rules:
|
||||
- if: $SAST_DISABLED
|
||||
|
|
@ -230,8 +231,6 @@ security-code-scan-sast:
|
|||
# This rule shim will be removed in %15.0,
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/350935
|
||||
- if: $CI_COMMIT_BRANCH && $CI_SERVER_VERSION_MAJOR == '14'
|
||||
variables:
|
||||
SAST_ANALYZER_IMAGE_TAG: '2'
|
||||
exists:
|
||||
- '**/*.csproj'
|
||||
- '**/*.vbproj'
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ module Gitlab
|
|||
# updating the timestamp.
|
||||
project.touch(:last_repository_updated_at) # rubocop: disable Rails/SkipsModelValidations
|
||||
|
||||
project.repository.fetch_remote(project.import_url, refmap: Gitlab::GithubImport.refmap, forced: false)
|
||||
project.repository.fetch_remote(project.import_url, refmap: Gitlab::GithubImport.refmap, forced: true)
|
||||
|
||||
pname = project.path_with_namespace
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@ module Gitlab
|
|||
access_check.can_push_to_branch?(merge_request.source_branch)
|
||||
end
|
||||
command :rebase do
|
||||
unless quick_action_target.permits_force_push?
|
||||
@execution_message[:rebase] = _('This merge request branch is protected from force push.')
|
||||
next
|
||||
end
|
||||
|
||||
if quick_action_target.cannot_be_merged?
|
||||
@execution_message[:rebase] = _('This merge request cannot be rebased while there are conflicts.')
|
||||
next
|
||||
|
|
@ -249,6 +254,76 @@ module Gitlab
|
|||
@updates[:reviewer_ids] = []
|
||||
end
|
||||
end
|
||||
|
||||
desc do
|
||||
if quick_action_target.allows_multiple_reviewers?
|
||||
_('Request attention from assignee(s) or reviewer(s)')
|
||||
else
|
||||
_('Request attention from assignee or reviewer')
|
||||
end
|
||||
end
|
||||
explanation do |users|
|
||||
_('Request attention from %{users_sentence}.') % { users_sentence: reviewer_users_sentence(users) }
|
||||
end
|
||||
execution_message do |users = nil|
|
||||
if users.blank?
|
||||
_("Failed to request attention because no user was found.")
|
||||
else
|
||||
_('Requested attention from %{users_sentence}.') % { users_sentence: reviewer_users_sentence(users) }
|
||||
end
|
||||
end
|
||||
params do
|
||||
quick_action_target.allows_multiple_reviewers? ? '@user1 @user2' : '@user'
|
||||
end
|
||||
types MergeRequest
|
||||
condition do
|
||||
Feature.enabled?(:mr_attention_requests, project, default_enabled: :yaml) &&
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
|
||||
end
|
||||
parse_params do |attention_param|
|
||||
extract_users(attention_param)
|
||||
end
|
||||
command :attention do |users|
|
||||
next if users.empty?
|
||||
|
||||
users.each do |user|
|
||||
::MergeRequests::ToggleAttentionRequestedService.new(project: quick_action_target.project, merge_request: quick_action_target, current_user: current_user, user: user).execute
|
||||
end
|
||||
end
|
||||
|
||||
desc do
|
||||
if quick_action_target.allows_multiple_reviewers?
|
||||
_('Remove attention request(s)')
|
||||
else
|
||||
_('Remove attention request')
|
||||
end
|
||||
end
|
||||
explanation do |users|
|
||||
_('Removes attention from %{users_sentence}.') % { users_sentence: reviewer_users_sentence(users) }
|
||||
end
|
||||
execution_message do |users = nil|
|
||||
if users.blank?
|
||||
_("Failed to remove attention because no user was found.")
|
||||
else
|
||||
_('Removed attention from %{users_sentence}.') % { users_sentence: reviewer_users_sentence(users) }
|
||||
end
|
||||
end
|
||||
params do
|
||||
quick_action_target.allows_multiple_reviewers? ? '@user1 @user2' : '@user'
|
||||
end
|
||||
types MergeRequest
|
||||
condition do
|
||||
Feature.enabled?(:mr_attention_requests, project, default_enabled: :yaml) &&
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
|
||||
end
|
||||
parse_params do |attention_param|
|
||||
extract_users(attention_param)
|
||||
end
|
||||
command :remove_attention do |users|
|
||||
next if users.empty?
|
||||
|
||||
::MergeRequests::BulkRemoveAttentionRequestedService.new(project: quick_action_target.project, merge_request: quick_action_target, current_user: current_user, users: users).execute
|
||||
end
|
||||
end
|
||||
|
||||
def reviewer_users_sentence(users)
|
||||
|
|
|
|||
|
|
@ -295,3 +295,11 @@
|
|||
category: quickactions
|
||||
redis_slot: quickactions
|
||||
aggregation: weekly
|
||||
- name: i_quickactions_attention
|
||||
category: quickactions
|
||||
redis_slot: quickactions
|
||||
aggregation: weekly
|
||||
- name: i_quickactions_remove_attention
|
||||
category: quickactions
|
||||
redis_slot: quickactions
|
||||
aggregation: weekly
|
||||
|
|
|
|||
|
|
@ -7405,6 +7405,9 @@ msgstr ""
|
|||
msgid "Clear recent searches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clear repository checks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clear search"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9896,10 +9899,10 @@ msgstr ""
|
|||
msgid "CorpusManagement|Actions"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Corpus are used in fuzz testing as mutation source to Improve future testing."
|
||||
msgid "CorpusManagement|Corpus file"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Corpus file"
|
||||
msgid "CorpusManagement|Corpus files are used in coverage-guided fuzz testing as seed inputs to improve testing."
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Corpus files must be in *.zip format. Maximum 5 GB"
|
||||
|
|
@ -15023,6 +15026,9 @@ msgstr ""
|
|||
msgid "Failed to remove a to-do item for the design."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to remove attention because no user was found."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to remove mirror."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -15035,6 +15041,9 @@ msgstr ""
|
|||
msgid "Failed to remove user key."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to request attention because no user was found."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to reset key. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30016,6 +30025,9 @@ msgstr ""
|
|||
msgid "Remove attention request"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove attention request(s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove avatar"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30154,6 +30166,9 @@ msgstr ""
|
|||
msgid "Removed an issue from an epic."
|
||||
msgstr ""
|
||||
|
||||
msgid "Removed attention from %{users_sentence}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Removed attention request from @%{username}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30205,6 +30220,9 @@ msgstr ""
|
|||
msgid "Removes an issue from an epic."
|
||||
msgstr ""
|
||||
|
||||
msgid "Removes attention from %{users_sentence}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Removes parent epic %{epic_ref}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30605,6 +30623,15 @@ msgstr ""
|
|||
msgid "Request attention"
|
||||
msgstr ""
|
||||
|
||||
msgid "Request attention from %{users_sentence}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Request attention from assignee or reviewer"
|
||||
msgstr ""
|
||||
|
||||
msgid "Request attention from assignee(s) or reviewer(s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Request attention to review"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30629,6 +30656,9 @@ msgstr ""
|
|||
msgid "Requested %{time_ago}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Requested attention from %{users_sentence}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Requested attention from @%{username}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31973,7 +32003,7 @@ msgstr ""
|
|||
msgid "SecurityConfiguration|Manage corpus"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityConfiguration|Manage corpus files used as mutation sources in coverage fuzzing."
|
||||
msgid "SecurityConfiguration|Manage corpus files used as seed inputs with coverage-guided fuzzing."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityConfiguration|Manage profiles for use by DAST scans."
|
||||
|
|
@ -37134,6 +37164,9 @@ msgstr ""
|
|||
msgid "This means you can not push code until you create an empty repository or import existing one."
|
||||
msgstr ""
|
||||
|
||||
msgid "This merge request branch is protected from force push."
|
||||
msgstr ""
|
||||
|
||||
msgid "This merge request cannot be rebased while there are conflicts."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@
|
|||
"web-vitals": "^0.2.4",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-stats-plugin": "^0.3.1",
|
||||
"worker-loader": "^2.0.0",
|
||||
"xterm": "3.14.5",
|
||||
|
|
@ -257,7 +257,7 @@
|
|||
"stylelint": "^14.3.0",
|
||||
"timezone-mock": "^1.0.8",
|
||||
"vue-jest": "4.0.1",
|
||||
"webpack-dev-server": "^3.11.3",
|
||||
"webpack-dev-server": "4.7.4",
|
||||
"xhr-mock": "^2.5.1",
|
||||
"yarn-check-webpack-plugin": "^1.2.0",
|
||||
"yarn-deduplicate": "^3.1.0"
|
||||
|
|
|
|||
|
|
@ -2078,6 +2078,20 @@ RSpec.describe Projects::MergeRequestsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when source branch is protected from force push' do
|
||||
before do
|
||||
create(:protected_branch, project: project, name: merge_request.source_branch, allow_force_push: false)
|
||||
end
|
||||
|
||||
it 'returns 404' do
|
||||
expect_rebase_worker_for(user).never
|
||||
|
||||
post_rebase
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a forked project' do
|
||||
let(:forked_project) { fork_project(project, fork_owner, repository: true) }
|
||||
let(:fork_owner) { create(:user) }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import $ from 'jquery';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import '~/lib/utils/common_utils';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { addDelimiter } from '~/lib/utils/text_utility';
|
||||
|
|
@ -71,7 +72,7 @@ describe('Todos', () => {
|
|||
describe('on done todo click', () => {
|
||||
let onToggleSpy;
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach(() => {
|
||||
const el = document.querySelector('.js-done-todo');
|
||||
const path = el.dataset.href;
|
||||
|
||||
|
|
@ -86,7 +87,7 @@ describe('Todos', () => {
|
|||
el.click();
|
||||
|
||||
// Wait for axios and HTML to udpate
|
||||
setImmediate(done);
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
it('dispatches todo:toggle', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import $ from 'jquery';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import ProjectFindFile from '~/projects/project_find_file';
|
||||
|
|
@ -53,7 +54,7 @@ describe('ProjectFindFile', () => {
|
|||
{ path: 'folde?rC/fil#F.txt', escaped: 'folde%3FrC/fil%23F.txt' },
|
||||
];
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach(() => {
|
||||
// Create a mock adapter for stubbing axios API requests
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
|
|
@ -64,7 +65,7 @@ describe('ProjectFindFile', () => {
|
|||
);
|
||||
getProjectFindFileInstance(); // This triggers a load / axios call + subsequent render in the constructor
|
||||
|
||||
setImmediate(done);
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import PANEL_STATE from '~/prometheus_metrics/constants';
|
||||
import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
|
||||
|
|
@ -132,7 +133,7 @@ describe('PrometheusMetrics', () => {
|
|||
mock.restore();
|
||||
});
|
||||
|
||||
it('should show loader animation while response is being loaded and hide it when request is complete', (done) => {
|
||||
it('should show loader animation while response is being loaded and hide it when request is complete', async () => {
|
||||
mockSuccess();
|
||||
|
||||
prometheusMetrics.loadActiveMetrics();
|
||||
|
|
@ -140,34 +141,31 @@ describe('PrometheusMetrics', () => {
|
|||
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeFalsy();
|
||||
expect(axios.get).toHaveBeenCalledWith(prometheusMetrics.activeMetricsEndpoint);
|
||||
|
||||
setImmediate(() => {
|
||||
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show empty state if response failed to load', (done) => {
|
||||
it('should show empty state if response failed to load', async () => {
|
||||
mockError();
|
||||
|
||||
prometheusMetrics.loadActiveMetrics();
|
||||
|
||||
setImmediate(() => {
|
||||
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
|
||||
expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
|
||||
expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should populate metrics list once response is loaded', (done) => {
|
||||
it('should populate metrics list once response is loaded', async () => {
|
||||
jest.spyOn(prometheusMetrics, 'populateActiveMetrics').mockImplementation();
|
||||
mockSuccess();
|
||||
|
||||
prometheusMetrics.loadActiveMetrics();
|
||||
|
||||
setImmediate(() => {
|
||||
expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics);
|
||||
done();
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -97,14 +97,14 @@ describe('SidebarSeverity', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('shows error alert when severity update fails ', () => {
|
||||
it('shows error alert when severity update fails ', async () => {
|
||||
const errorMsg = 'Something went wrong';
|
||||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValueOnce(errorMsg);
|
||||
findCriticalSeverityDropdownItem().vm.$emit('click');
|
||||
|
||||
setImmediate(() => {
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows loading icon while updating', async () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import $ from 'jquery';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
|
||||
|
|
@ -77,15 +78,14 @@ describe('SidebarMoveIssue', () => {
|
|||
expect(test.sidebarMoveIssue.$dropdownToggle.data('deprecatedJQueryDropdown')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('escapes html from project name', (done) => {
|
||||
it('escapes html from project name', async () => {
|
||||
test.$toggleButton.dropdown('toggle');
|
||||
|
||||
setImmediate(() => {
|
||||
expect(test.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual(
|
||||
'<img src=x onerror=alert(document.domain)> foo / bar',
|
||||
);
|
||||
done();
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(test.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual(
|
||||
'<img src=x onerror=alert(document.domain)> foo / bar',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -101,20 +101,20 @@ describe('SidebarMoveIssue', () => {
|
|||
expect(test.$confirmButton.hasClass('is-loading')).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove loading state from confirm button on failure', (done) => {
|
||||
it('should remove loading state from confirm button on failure', async () => {
|
||||
jest.spyOn(test.mediator, 'moveIssue').mockReturnValue(Promise.reject());
|
||||
test.mediator.setMoveToProjectId(7);
|
||||
|
||||
test.sidebarMoveIssue.onConfirmClicked();
|
||||
|
||||
expect(test.mediator.moveIssue).toHaveBeenCalled();
|
||||
|
||||
// Wait for the move issue request to fail
|
||||
setImmediate(() => {
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(test.$confirmButton.prop('disabled')).toBeFalsy();
|
||||
expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
|
||||
done();
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(createFlash).toHaveBeenCalled();
|
||||
expect(test.$confirmButton.prop('disabled')).toBeFalsy();
|
||||
expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
|
||||
});
|
||||
|
||||
it('should not move the issue with id=0', () => {
|
||||
|
|
@ -127,35 +127,33 @@ describe('SidebarMoveIssue', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should set moveToProjectId on dropdown item "No project" click', (done) => {
|
||||
it('should set moveToProjectId on dropdown item "No project" click', async () => {
|
||||
jest.spyOn(test.mediator, 'setMoveToProjectId').mockImplementation(() => {});
|
||||
|
||||
// Open the dropdown
|
||||
test.$toggleButton.dropdown('toggle');
|
||||
|
||||
// Wait for the autocomplete request to finish
|
||||
setImmediate(() => {
|
||||
test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
|
||||
await waitForPromises();
|
||||
|
||||
expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
|
||||
expect(test.$confirmButton.prop('disabled')).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
|
||||
|
||||
expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
|
||||
expect(test.$confirmButton.prop('disabled')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should set moveToProjectId on dropdown item click', (done) => {
|
||||
it('should set moveToProjectId on dropdown item click', async () => {
|
||||
jest.spyOn(test.mediator, 'setMoveToProjectId').mockImplementation(() => {});
|
||||
|
||||
// Open the dropdown
|
||||
test.$toggleButton.dropdown('toggle');
|
||||
|
||||
// Wait for the autocomplete request to finish
|
||||
setImmediate(() => {
|
||||
test.$content.find('.js-move-issue-dropdown-item').eq(1).trigger('click');
|
||||
await waitForPromises();
|
||||
|
||||
expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(20);
|
||||
expect(test.$confirmButton.attr('disabled')).toBe(undefined);
|
||||
done();
|
||||
});
|
||||
test.$content.find('.js-move-issue-dropdown-item').eq(1).trigger('click');
|
||||
|
||||
expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(20);
|
||||
expect(test.$confirmButton.attr('disabled')).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
|
||||
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
|
||||
|
||||
|
|
@ -152,23 +153,18 @@ describe('MemoryUsage', () => {
|
|||
});
|
||||
|
||||
describe('loadMetrics', () => {
|
||||
const returnServicePromise = () =>
|
||||
new Promise((resolve) => {
|
||||
resolve({
|
||||
data: metricsMockData,
|
||||
});
|
||||
it('should load metrics data using MRWidgetService', async () => {
|
||||
jest.spyOn(MRWidgetService, 'fetchMetrics').mockResolvedValue({
|
||||
data: metricsMockData,
|
||||
});
|
||||
|
||||
it('should load metrics data using MRWidgetService', (done) => {
|
||||
jest.spyOn(MRWidgetService, 'fetchMetrics').mockReturnValue(returnServicePromise(true));
|
||||
jest.spyOn(vm, 'computeGraphData').mockImplementation(() => {});
|
||||
|
||||
vm.loadMetrics();
|
||||
setImmediate(() => {
|
||||
expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url);
|
||||
expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time);
|
||||
done();
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url);
|
||||
expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
|
|||
import { nextTick } from 'vue';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue';
|
||||
import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
|
|
@ -185,7 +186,7 @@ describe('MRWidgetAutoMergeEnabled', () => {
|
|||
|
||||
describe('methods', () => {
|
||||
describe('cancelAutomaticMerge', () => {
|
||||
it('should set flag and call service then tell main component to update the widget with data', (done) => {
|
||||
it('should set flag and call service then tell main component to update the widget with data', async () => {
|
||||
factory({
|
||||
...defaultMrProps(),
|
||||
});
|
||||
|
|
@ -201,20 +202,20 @@ describe('MRWidgetAutoMergeEnabled', () => {
|
|||
);
|
||||
|
||||
wrapper.vm.cancelAutomaticMerge();
|
||||
setImmediate(() => {
|
||||
expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy();
|
||||
if (mergeRequestWidgetGraphql) {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
|
||||
} else {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy();
|
||||
if (mergeRequestWidgetGraphql) {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
|
||||
} else {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeSourceBranch', () => {
|
||||
it('should set flag and call service then request main component to update the widget', (done) => {
|
||||
it('should set flag and call service then request main component to update the widget', async () => {
|
||||
factory({
|
||||
...defaultMrProps(),
|
||||
});
|
||||
|
|
@ -227,14 +228,14 @@ describe('MRWidgetAutoMergeEnabled', () => {
|
|||
);
|
||||
|
||||
wrapper.vm.removeSourceBranch();
|
||||
setImmediate(() => {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
|
||||
expect(wrapper.vm.service.merge).toHaveBeenCalledWith({
|
||||
sha,
|
||||
auto_merge_strategy: MWPS_MERGE_STRATEGY,
|
||||
should_remove_source_branch: true,
|
||||
});
|
||||
done();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
|
||||
expect(wrapper.vm.service.merge).toHaveBeenCalledWith({
|
||||
sha,
|
||||
auto_merge_strategy: MWPS_MERGE_STRATEGY,
|
||||
should_remove_source_branch: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::PopulateTopicsNonPrivateProjectsCount, schema: 20220125122640 do
|
||||
it 'correctly populates the non private projects counters' do
|
||||
namespaces = table(:namespaces)
|
||||
projects = table(:projects)
|
||||
topics = table(:topics)
|
||||
project_topics = table(:project_topics)
|
||||
|
||||
group = namespaces.create!(name: 'group', path: 'group')
|
||||
project_public = projects.create!(namespace_id: group.id, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||
project_internal = projects.create!(namespace_id: group.id, visibility_level: Gitlab::VisibilityLevel::INTERNAL)
|
||||
project_private = projects.create!(namespace_id: group.id, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
|
||||
topic_1 = topics.create!(name: 'Topic1')
|
||||
topic_2 = topics.create!(name: 'Topic2')
|
||||
topic_3 = topics.create!(name: 'Topic3')
|
||||
topic_4 = topics.create!(name: 'Topic4')
|
||||
topic_5 = topics.create!(name: 'Topic5')
|
||||
topic_6 = topics.create!(name: 'Topic6')
|
||||
topic_7 = topics.create!(name: 'Topic7')
|
||||
topic_8 = topics.create!(name: 'Topic8')
|
||||
|
||||
project_topics.create!(topic_id: topic_1.id, project_id: project_public.id)
|
||||
project_topics.create!(topic_id: topic_2.id, project_id: project_internal.id)
|
||||
project_topics.create!(topic_id: topic_3.id, project_id: project_private.id)
|
||||
project_topics.create!(topic_id: topic_4.id, project_id: project_public.id)
|
||||
project_topics.create!(topic_id: topic_4.id, project_id: project_internal.id)
|
||||
project_topics.create!(topic_id: topic_5.id, project_id: project_public.id)
|
||||
project_topics.create!(topic_id: topic_5.id, project_id: project_private.id)
|
||||
project_topics.create!(topic_id: topic_6.id, project_id: project_internal.id)
|
||||
project_topics.create!(topic_id: topic_6.id, project_id: project_private.id)
|
||||
project_topics.create!(topic_id: topic_7.id, project_id: project_public.id)
|
||||
project_topics.create!(topic_id: topic_7.id, project_id: project_internal.id)
|
||||
project_topics.create!(topic_id: topic_7.id, project_id: project_private.id)
|
||||
project_topics.create!(topic_id: topic_8.id, project_id: project_public.id)
|
||||
|
||||
subject.perform(topic_1.id, topic_7.id)
|
||||
|
||||
expect(topic_1.reload.non_private_projects_count).to eq(1)
|
||||
expect(topic_2.reload.non_private_projects_count).to eq(1)
|
||||
expect(topic_3.reload.non_private_projects_count).to eq(0)
|
||||
expect(topic_4.reload.non_private_projects_count).to eq(2)
|
||||
expect(topic_5.reload.non_private_projects_count).to eq(1)
|
||||
expect(topic_6.reload.non_private_projects_count).to eq(1)
|
||||
expect(topic_7.reload.non_private_projects_count).to eq(2)
|
||||
expect(topic_8.reload.non_private_projects_count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
|
@ -164,7 +164,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsImporter do
|
|||
|
||||
expect(project.repository)
|
||||
.to receive(:fetch_remote)
|
||||
.with(url, forced: false, refmap: Gitlab::GithubImport.refmap)
|
||||
.with(url, forced: true, refmap: Gitlab::GithubImport.refmap)
|
||||
|
||||
freeze_time do
|
||||
importer.update_repository
|
||||
|
|
|
|||
|
|
@ -1538,6 +1538,42 @@ RSpec.describe MergeRequest, factory_default: :keep do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#permits_force_push?' do
|
||||
let_it_be(:merge_request) { build_stubbed(:merge_request) }
|
||||
|
||||
subject { merge_request.permits_force_push? }
|
||||
|
||||
context 'when source branch is not protected' do
|
||||
before do
|
||||
allow(ProtectedBranch).to receive(:protected?).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when source branch is protected' do
|
||||
before do
|
||||
allow(ProtectedBranch).to receive(:protected?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when force push is not allowed' do
|
||||
before do
|
||||
allow(ProtectedBranch).to receive(:allow_force_push?) { false }
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when force push is allowed' do
|
||||
before do
|
||||
allow(ProtectedBranch).to receive(:allow_force_push?) { true }
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_remove_source_branch?' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:merge_request, reload: true) { create(:merge_request, :simple) }
|
||||
|
|
|
|||
|
|
@ -7418,6 +7418,67 @@ RSpec.describe Project, factory_default: :keep do
|
|||
expect(project.reload.topics.map(&:name)).to eq(%w[topic1 topic2 topic3])
|
||||
end
|
||||
end
|
||||
|
||||
context 'public topics counter' do
|
||||
let_it_be(:topic_1) { create(:topic, name: 't1') }
|
||||
let_it_be(:topic_2) { create(:topic, name: 't2') }
|
||||
let_it_be(:topic_3) { create(:topic, name: 't3') }
|
||||
|
||||
let(:private) { Gitlab::VisibilityLevel::PRIVATE }
|
||||
let(:internal) { Gitlab::VisibilityLevel::INTERNAL }
|
||||
let(:public) { Gitlab::VisibilityLevel::PUBLIC }
|
||||
|
||||
subject do
|
||||
project_updates = {
|
||||
visibility_level: new_visibility,
|
||||
topic_list: new_topic_list
|
||||
}.compact
|
||||
|
||||
project.update!(project_updates)
|
||||
end
|
||||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
# rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
|
||||
where(:initial_visibility, :new_visibility, :new_topic_list, :expected_count_changes) do
|
||||
ref(:private) | nil | 't2, t3' | [0, 0, 0]
|
||||
ref(:internal) | nil | 't2, t3' | [-1, 0, 1]
|
||||
ref(:public) | nil | 't2, t3' | [-1, 0, 1]
|
||||
ref(:private) | ref(:public) | nil | [1, 1, 0]
|
||||
ref(:private) | ref(:internal) | nil | [1, 1, 0]
|
||||
ref(:private) | ref(:private) | nil | [0, 0, 0]
|
||||
ref(:internal) | ref(:public) | nil | [0, 0, 0]
|
||||
ref(:internal) | ref(:internal) | nil | [0, 0, 0]
|
||||
ref(:internal) | ref(:private) | nil | [-1, -1, 0]
|
||||
ref(:public) | ref(:public) | nil | [0, 0, 0]
|
||||
ref(:public) | ref(:internal) | nil | [0, 0, 0]
|
||||
ref(:public) | ref(:private) | nil | [-1, -1, 0]
|
||||
ref(:private) | ref(:public) | 't2, t3' | [0, 1, 1]
|
||||
ref(:private) | ref(:internal) | 't2, t3' | [0, 1, 1]
|
||||
ref(:private) | ref(:private) | 't2, t3' | [0, 0, 0]
|
||||
ref(:internal) | ref(:public) | 't2, t3' | [-1, 0, 1]
|
||||
ref(:internal) | ref(:internal) | 't2, t3' | [-1, 0, 1]
|
||||
ref(:internal) | ref(:private) | 't2, t3' | [-1, -1, 0]
|
||||
ref(:public) | ref(:public) | 't2, t3' | [-1, 0, 1]
|
||||
ref(:public) | ref(:internal) | 't2, t3' | [-1, 0, 1]
|
||||
ref(:public) | ref(:private) | 't2, t3' | [-1, -1, 0]
|
||||
end
|
||||
# rubocop:enable Lint/BinaryOperatorWithIdenticalOperands
|
||||
|
||||
with_them do
|
||||
it 'increments or decrements counters of topics' do
|
||||
project.reload.update!(
|
||||
visibility_level: initial_visibility,
|
||||
topic_list: [topic_1.name, topic_2.name]
|
||||
)
|
||||
|
||||
expect { subject }
|
||||
.to change { topic_1.reload.non_private_projects_count }.by(expected_count_changes[0])
|
||||
.and change { topic_2.reload.non_private_projects_count }.by(expected_count_changes[1])
|
||||
.and change { topic_3.reload.non_private_projects_count }.by(expected_count_changes[2])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'all_runners' do
|
||||
|
|
|
|||
|
|
@ -3344,6 +3344,18 @@ RSpec.describe API::MergeRequests do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when merge request branch does not allow force push' do
|
||||
before do
|
||||
create(:protected_branch, project: project, name: merge_request.source_branch, allow_force_push: false)
|
||||
end
|
||||
|
||||
it 'returns 403' do
|
||||
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/rebase", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns 403 if the user cannot push to the branch' do
|
||||
guest = create(:user)
|
||||
project.add_guest(guest)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ RSpec.describe MergeRequests::BulkRemoveAttentionRequestedService do
|
|||
let(:reviewer) { merge_request.find_reviewer(user) }
|
||||
let(:assignee) { merge_request.find_assignee(assignee_user) }
|
||||
let(:project) { merge_request.project }
|
||||
let(:service) { described_class.new(project: project, current_user: current_user, merge_request: merge_request) }
|
||||
let(:service) { described_class.new(project: project, current_user: current_user, merge_request: merge_request, users: [user, assignee_user]) }
|
||||
let(:result) { service.execute }
|
||||
|
||||
before do
|
||||
|
|
@ -20,7 +20,7 @@ RSpec.describe MergeRequests::BulkRemoveAttentionRequestedService do
|
|||
|
||||
describe '#execute' do
|
||||
context 'invalid permissions' do
|
||||
let(:service) { described_class.new(project: project, current_user: create(:user), merge_request: merge_request) }
|
||||
let(:service) { described_class.new(project: project, current_user: create(:user), merge_request: merge_request, users: [user]) }
|
||||
|
||||
it 'returns an error' do
|
||||
expect(result[:status]).to eq :error
|
||||
|
|
|
|||
|
|
@ -701,6 +701,27 @@ RSpec.describe QuickActions::InterpretService do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'attention command' do
|
||||
it 'updates reviewers attention status' do
|
||||
_, _, message = service.execute(content, issuable)
|
||||
|
||||
expect(message).to eq("Requested attention from #{developer.to_reference}.")
|
||||
|
||||
reviewer.reload
|
||||
|
||||
expect(reviewer).to be_attention_requested
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'remove attention command' do
|
||||
it 'updates reviewers attention status' do
|
||||
_, _, message = service.execute(content, issuable)
|
||||
|
||||
expect(message).to eq("Removed attention from #{developer.to_reference}.")
|
||||
expect(reviewer).not_to be_attention_requested
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'reopen command' do
|
||||
let(:content) { '/reopen' }
|
||||
let(:issuable) { issue }
|
||||
|
|
@ -2283,6 +2304,82 @@ RSpec.describe QuickActions::InterpretService do
|
|||
expect(message).to eq('One or more contacts were successfully removed.')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'attention command' do
|
||||
let(:issuable) { create(:merge_request, reviewers: [developer], source_project: project) }
|
||||
let(:reviewer) { issuable.merge_request_reviewers.find_by(user_id: developer.id) }
|
||||
let(:content) { "/attention @#{developer.username}" }
|
||||
|
||||
context 'with one user' do
|
||||
before do
|
||||
reviewer.update!(state: :reviewed)
|
||||
end
|
||||
|
||||
it_behaves_like 'attention command'
|
||||
end
|
||||
|
||||
context 'with no user' do
|
||||
let(:content) { "/attention" }
|
||||
|
||||
it_behaves_like 'failed command', 'Failed to request attention because no user was found.'
|
||||
end
|
||||
|
||||
context 'with incorrect permissions' do
|
||||
let(:service) { described_class.new(project, create(:user)) }
|
||||
|
||||
it_behaves_like 'failed command', 'Could not apply attention command.'
|
||||
end
|
||||
|
||||
context 'with feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(mr_attention_requests: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'failed command', 'Could not apply attention command.'
|
||||
end
|
||||
|
||||
context 'with an issue instead of a merge request' do
|
||||
let(:issuable) { issue }
|
||||
|
||||
it_behaves_like 'failed command', 'Could not apply attention command.'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'remove attention command' do
|
||||
let(:issuable) { create(:merge_request, reviewers: [developer], source_project: project) }
|
||||
let(:reviewer) { issuable.merge_request_reviewers.find_by(user_id: developer.id) }
|
||||
let(:content) { "/remove_attention @#{developer.username}" }
|
||||
|
||||
context 'with one user' do
|
||||
it_behaves_like 'remove attention command'
|
||||
end
|
||||
|
||||
context 'with no user' do
|
||||
let(:content) { "/remove_attention" }
|
||||
|
||||
it_behaves_like 'failed command', 'Failed to remove attention because no user was found.'
|
||||
end
|
||||
|
||||
context 'with incorrect permissions' do
|
||||
let(:service) { described_class.new(project, create(:user)) }
|
||||
|
||||
it_behaves_like 'failed command', 'Could not apply remove_attention command.'
|
||||
end
|
||||
|
||||
context 'with feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(mr_attention_requests: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'failed command', 'Could not apply remove_attention command.'
|
||||
end
|
||||
|
||||
context 'with an issue instead of a merge request' do
|
||||
let(:issuable) { issue }
|
||||
|
||||
it_behaves_like 'failed command', 'Could not apply remove_attention command.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#explain' do
|
||||
|
|
|
|||
|
|
@ -73,6 +73,16 @@ RSpec.shared_examples 'rebase quick action' do
|
|||
expect(page).to have_content 'This merge request cannot be rebased while there are conflicts.'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the merge request branch is protected from force push' do
|
||||
let!(:protected_branch) { create(:protected_branch, project: project, name: merge_request.source_branch, allow_force_push: false) }
|
||||
|
||||
it 'does not rebase the MR' do
|
||||
add_note("/rebase")
|
||||
|
||||
expect(page).to have_content 'This merge request branch is protected from force push.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the current user cannot rebase the MR' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ContainerRegistry::Migration::ObserverWorker, :aggregate_failures do
|
||||
let(:worker) { described_class.new }
|
||||
|
||||
describe '#perform' do
|
||||
subject { worker.perform }
|
||||
|
||||
context 'when the migration feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(container_registry_migration_phase2_enabled: false)
|
||||
end
|
||||
|
||||
it 'does nothing' do
|
||||
expect(worker).not_to receive(:log_extra_metadata_on_done)
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the migration is enabled' do
|
||||
before do
|
||||
create_list(:container_repository, 3)
|
||||
create(:container_repository, :pre_importing)
|
||||
create(:container_repository, :pre_import_done)
|
||||
create_list(:container_repository, 2, :importing)
|
||||
create(:container_repository, :import_aborted)
|
||||
# batch_count is not allowed within a transaction but
|
||||
# all rspec tests run inside of a transaction.
|
||||
# This mocks the false positive.
|
||||
allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) # rubocop:disable Database/MultipleDatabases
|
||||
end
|
||||
|
||||
it 'logs all the counts' do
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:default_count, 3)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:pre_importing_count, 1)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:pre_import_done_count, 1)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:importing_count, 2)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:import_done_count, 0)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:import_aborted_count, 1)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:import_skipped_count, 0)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'with load balancing enabled', :db_load_balancing do
|
||||
it 'uses the replica' do
|
||||
expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_replicas_for_read_queries).and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -32,6 +32,21 @@ RSpec.describe Projects::GitGarbageCollectWorker do
|
|||
|
||||
subject.perform(*params)
|
||||
end
|
||||
|
||||
context 'when deduplication service runs into a GRPC internal error' do
|
||||
before do
|
||||
allow_next_instance_of(::Projects::GitDeduplicationService) do |instance|
|
||||
expect(instance).to receive(:execute).and_raise(GRPC::Internal)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'can collect git garbage' do
|
||||
let(:resource) { project }
|
||||
let(:statistics_service_klass) { Projects::UpdateStatisticsService }
|
||||
let(:statistics_keys) { [:repository_size, :lfs_objects_size] }
|
||||
let(:expected_default_lease) { "projects:#{resource.id}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'LFS object garbage collection' do
|
||||
|
|
|
|||
Loading…
Reference in New Issue