Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-02-10 18:18:16 +00:00
parent 74d9798736
commit e0277d5393
68 changed files with 1883 additions and 1322 deletions

View File

@ -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

View File

@ -1 +1 @@
1.53.0
1.54.0

View File

@ -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;

View File

@ -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');

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -16,7 +16,15 @@ module Projects
def before_gitaly_call(task, resource)
return unless gc?(task)
# 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

View File

@ -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);

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,
https: DEV_SERVER_HTTPS,
contentBase: false,
// Only print errors to CLI
devMiddleware: {
stats: 'errors-only',
},
host: DEV_SERVER_HOST || 'localhost',
port: DEV_SERVER_PORT || 3808,
https: DEV_SERVER_HTTPS,
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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
26600e01d8b31a4308d0e23564e4d4c52488ec87ad7990a410b7cc0c031f12e7

View File

@ -0,0 +1 @@
51c7ab860b952281bd7f65d68e7a539a8eee57cac3bbdaf439ff5593f5b065ed

View File

@ -0,0 +1 @@
7740d1e71571576a709ae5bfd46f60ea3fb4be3f48cddec2cca53f148096cdd7

View File

@ -0,0 +1 @@
0efe482aa626cf80912feaa1176837253b094fc434f273bee35b5fe3e8ce4243

View File

@ -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);

View File

@ -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`.

View File

@ -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'

View File

@ -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
@ -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,10 +182,20 @@ 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', {
@ -176,7 +203,7 @@ component with a label, use the `label` category:
});
```
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: {

View File

@ -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

View File

@ -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

View File

@ -116,14 +116,18 @@ 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
## Troubleshooting
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.
### Imports from GitHub Enterprise with a self-signed certificate fail
For Omnibus package:
When you import projects from GitHub Enterprise using a self-signed
certificate, the imports fail.
To fix this issue, you must disable SSL verification:
1. Set `verify_ssl` to `false` in the configuration file.
- **For Omnibus installations**
```ruby
gitlab_rails['omniauth_providers'] = [
@ -139,13 +143,7 @@ gitlab_rails['omniauth_providers'] = [
]
```
You must also disable Git SSL verification on the server hosting GitLab.
```ruby
omnibus_gitconfig['system'] = { "http" => ["sslVerify = false"] }
```
For installation from source:
- **For installations from source**
```yaml
- { name: 'github',
@ -157,24 +155,41 @@ For installation from source:
args: { scope: 'user:email' } }
```
You must also disable Git SSL verification on the server hosting GitLab.
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
```
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.
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.
## Troubleshooting
### Signing in using GitHub Enterprise returns a 500 error
### Error 500 when trying to sign in to GitLab via GitHub Enterprise
This error can occur because of a network connectivity issue between your
GitLab instance and GitHub Enterprise.
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:
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
@ -184,19 +199,23 @@ http.verify_mode = 1
response = http.request(Net::HTTP::Get.new(uri.request_uri))
```
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. 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

View File

@ -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.
![Mean time to merge](img/mr_mean_time_to_merge_metric_v13_9.png "Merge Request Analytics - MTTM metric showing the average time it takes from initiating a MR to being merged")
To view **Mean time to merge**:
### Throughput chart
The throughput chart shows the number of merge requests merged per month.
![Throughput chart](img/mr_throughput_chart_v13_3.png "Merge Request Analytics - Throughput chart showing merge requests merged in the past 12 months")
### 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
![Throughput table](img/mr_throughput_table_v13_3.png "Merge Request Analytics - Throughput table listing the 100 merge requests most recently merged")
## 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**.

View File

@ -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).

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 ""

View File

@ -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"

View File

@ -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) }

View File

@ -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', () => {

View File

@ -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(() => {

View File

@ -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(() => {
await waitForPromises();
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
done();
});
});
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(() => {
await waitForPromises();
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy();
done();
});
});
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(() => {
await waitForPromises();
expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics);
done();
});
});
});
});

View File

@ -97,15 +97,15 @@ 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(() => {
await waitForPromises();
expect(createFlash).toHaveBeenCalled();
});
});
it('shows loading icon while updating', async () => {
let resolvePromise;

View File

@ -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(() => {
await waitForPromises();
expect(test.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual(
'&lt;img src=x onerror=alert(document.domain)&gt; foo / bar',
);
done();
});
});
});
@ -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(() => {
await waitForPromises();
expect(createFlash).toHaveBeenCalled();
expect(test.$confirmButton.prop('disabled')).toBeFalsy();
expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
done();
});
});
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(() => {
await waitForPromises();
test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
expect(test.$confirmButton.prop('disabled')).toBeTruthy();
done();
});
});
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(() => {
await waitForPromises();
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);
done();
});
});
});

View File

@ -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({
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(() => {
await waitForPromises();
expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url);
expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time);
done();
});
});
});
});

View File

@ -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(() => {
await waitForPromises();
expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy();
if (mergeRequestWidgetGraphql) {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
} else {
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
}
done();
});
});
});
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,15 +228,15 @@ describe('MRWidgetAutoMergeEnabled', () => {
);
wrapper.vm.removeSourceBranch();
setImmediate(() => {
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,
});
done();
});
});
});
});

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

941
yarn.lock

File diff suppressed because it is too large Load Diff