Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a5efa544eb
commit
bd15a45eeb
|
|
@ -5,7 +5,7 @@
|
||||||
needs: []
|
needs: []
|
||||||
|
|
||||||
.qa-preflight-job:
|
.qa-preflight-job:
|
||||||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:bundler-${BUNDLER_VERSION}-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}
|
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}
|
||||||
extends:
|
extends:
|
||||||
- .preflight-job-base
|
- .preflight-job-base
|
||||||
- .qa-cache
|
- .qa-cache
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ stages:
|
||||||
.ruby-image:
|
.ruby-image:
|
||||||
# Because this pipeline template can be included directly in other projects,
|
# Because this pipeline template can be included directly in other projects,
|
||||||
# image path and registry needs to be defined explicitly
|
# image path and registry needs to be defined explicitly
|
||||||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:bundler-${BUNDLER_VERSION}
|
image: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}"
|
||||||
|
|
||||||
.bundler-variables:
|
.bundler-variables:
|
||||||
variables:
|
variables:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
.qa-job-base:
|
.qa-job-base:
|
||||||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:bundler-${BUNDLER_VERSION}-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}
|
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}
|
||||||
extends:
|
extends:
|
||||||
- .default-retry
|
- .default-retry
|
||||||
- .qa-cache
|
- .qa-cache
|
||||||
|
|
@ -31,7 +31,6 @@
|
||||||
- RUBY_VERSION_DEFAULT
|
- RUBY_VERSION_DEFAULT
|
||||||
- RUBY_VERSION_NEXT
|
- RUBY_VERSION_NEXT
|
||||||
- RUBY_VERSION
|
- RUBY_VERSION
|
||||||
- BUNDLER_VERSION
|
|
||||||
- DOCKER_VERSION
|
- DOCKER_VERSION
|
||||||
- BUILD_OS
|
- BUILD_OS
|
||||||
- OS_VERSION
|
- OS_VERSION
|
||||||
|
|
|
||||||
|
|
@ -99,8 +99,6 @@ start-review-app-pipeline:
|
||||||
- OS_VERSION
|
- OS_VERSION
|
||||||
- DOCKER_VERSION
|
- DOCKER_VERSION
|
||||||
- CHROME_VERSION
|
- CHROME_VERSION
|
||||||
- BUNDLER_VERSION
|
|
||||||
|
|
||||||
# These variables are set in the pipeline schedules.
|
# These variables are set in the pipeline schedules.
|
||||||
# They need to be explicitly passed on to the child pipeline.
|
# They need to be explicitly passed on to the child pipeline.
|
||||||
# https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#pass-cicd-variables-to-a-downstream-pipeline-by-using-the-variables-keyword
|
# https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#pass-cicd-variables-to-a-downstream-pipeline-by-using-the-variables-keyword
|
||||||
|
|
|
||||||
|
|
@ -180,8 +180,8 @@
|
||||||
# Changes patterns #
|
# Changes patterns #
|
||||||
####################
|
####################
|
||||||
.ci-patterns: &ci-patterns
|
.ci-patterns: &ci-patterns
|
||||||
- ".gitlab-ci.yml"
|
- "{,jh/}.gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- "{,jh/}.gitlab/ci/**/*"
|
||||||
- "scripts/rspec_helpers.sh"
|
- "scripts/rspec_helpers.sh"
|
||||||
|
|
||||||
.ci-build-images-patterns: &ci-build-images-patterns
|
.ci-build-images-patterns: &ci-build-images-patterns
|
||||||
|
|
@ -340,8 +340,8 @@
|
||||||
- "{,ee/,jh/}{bin,config,db,elastic,gems,generator_templates,lib}/**/*"
|
- "{,ee/,jh/}{bin,config,db,elastic,gems,generator_templates,lib}/**/*"
|
||||||
- "{,ee/,jh/}spec/**/*"
|
- "{,ee/,jh/}spec/**/*"
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- "{,jh/}.gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- "{,jh/}.gitlab/ci/**/*"
|
||||||
- "*_VERSION"
|
- "*_VERSION"
|
||||||
- "scripts/rspec_helpers.sh"
|
- "scripts/rspec_helpers.sh"
|
||||||
# Mapped patterns (see tests.yml)
|
# Mapped patterns (see tests.yml)
|
||||||
|
|
@ -455,8 +455,8 @@
|
||||||
# Auto-generated files
|
# Auto-generated files
|
||||||
- "doc/api/graphql/reference/*"
|
- "doc/api/graphql/reference/*"
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- "{,jh/}.gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- "{,jh/}.gitlab/ci/**/*"
|
||||||
# Mapped patterns (see tests.yml)
|
# Mapped patterns (see tests.yml)
|
||||||
- "data/whats_new/*.yml"
|
- "data/whats_new/*.yml"
|
||||||
- "doc/index.md"
|
- "doc/index.md"
|
||||||
|
|
@ -481,8 +481,8 @@
|
||||||
# Auto-generated files
|
# Auto-generated files
|
||||||
- "doc/api/graphql/reference/*"
|
- "doc/api/graphql/reference/*"
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- "{,jh/}.gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- "{,jh/}.gitlab/ci/**/*"
|
||||||
# Mapped patterns (see tests.yml)
|
# Mapped patterns (see tests.yml)
|
||||||
- "data/whats_new/*.yml"
|
- "data/whats_new/*.yml"
|
||||||
- "doc/index.md"
|
- "doc/index.md"
|
||||||
|
|
@ -514,8 +514,8 @@
|
||||||
# Auto-generated files
|
# Auto-generated files
|
||||||
- "doc/api/graphql/reference/*"
|
- "doc/api/graphql/reference/*"
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- "{,jh/}.gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- "{,jh/}.gitlab/ci/**/*"
|
||||||
# Mapped patterns (see tests.yml)
|
# Mapped patterns (see tests.yml)
|
||||||
- "data/whats_new/*.yml"
|
- "data/whats_new/*.yml"
|
||||||
- "doc/index.md"
|
- "doc/index.md"
|
||||||
|
|
@ -543,8 +543,8 @@
|
||||||
# Auto-generated files
|
# Auto-generated files
|
||||||
- "doc/api/graphql/reference/*"
|
- "doc/api/graphql/reference/*"
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- "{,jh/}.gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- "{,jh/}.gitlab/ci/**/*"
|
||||||
# Backstage changes
|
# Backstage changes
|
||||||
- "Dangerfile"
|
- "Dangerfile"
|
||||||
- "danger/**/*"
|
- "danger/**/*"
|
||||||
|
|
@ -582,8 +582,8 @@
|
||||||
# Auto-generated files
|
# Auto-generated files
|
||||||
- "doc/api/graphql/reference/*"
|
- "doc/api/graphql/reference/*"
|
||||||
# CI changes
|
# CI changes
|
||||||
- ".gitlab-ci.yml"
|
- "{,jh/}.gitlab-ci.yml"
|
||||||
- ".gitlab/ci/**/*"
|
- "{,jh/}.gitlab/ci/**/*"
|
||||||
# Mapped patterns (see tests.yml)
|
# Mapped patterns (see tests.yml)
|
||||||
- "data/whats_new/*.yml"
|
- "data/whats_new/*.yml"
|
||||||
- "doc/index.md"
|
- "doc/index.md"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ workflow:
|
||||||
- when: always
|
- when: always
|
||||||
|
|
||||||
.cng-base:
|
.cng-base:
|
||||||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:bundler-${BUNDLER_VERSION}-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-kubectl-1.23-helm-3.14-kind-0.20
|
image: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-kubectl-1.23-helm-3.14-kind-0.20"
|
||||||
stage: test
|
stage: test
|
||||||
extends:
|
extends:
|
||||||
- .qa-cache
|
- .qa-cache
|
||||||
|
|
@ -109,7 +109,6 @@ download-knapsack-report:
|
||||||
cng-instance:
|
cng-instance:
|
||||||
extends: .cng-base
|
extends: .cng-base
|
||||||
variables:
|
variables:
|
||||||
QA_SCENARIO: Test::Instance::All
|
|
||||||
DEPLOYMENT_TYPE: kind
|
DEPLOYMENT_TYPE: kind
|
||||||
parallel: 5
|
parallel: 5
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
|
@ -118,8 +117,8 @@ cng-instance:
|
||||||
cng-qa-min-redis-version:
|
cng-qa-min-redis-version:
|
||||||
extends: .cng-base
|
extends: .cng-base
|
||||||
variables:
|
variables:
|
||||||
QA_SCENARIO: Test::Instance::Smoke
|
|
||||||
DEPLOYMENT_TYPE: kind
|
DEPLOYMENT_TYPE: kind
|
||||||
|
QA_RSPEC_TAGS: --tag health_check
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
- |
|
||||||
redis_version=$(awk -F "=" "/MIN_REDIS_VERSION =/ {print \$2}" $CI_PROJECT_DIR/lib/system_check/app/redis_version_check.rb | sed "s/['\" ]//g")
|
redis_version=$(awk -F "=" "/MIN_REDIS_VERSION =/ {print \$2}" $CI_PROJECT_DIR/lib/system_check/app/redis_version_check.rb | sed "s/['\" ]//g")
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ include:
|
||||||
- mv $CI_BUILDS_DIR/*.log $CI_PROJECT_DIR/
|
- mv $CI_BUILDS_DIR/*.log $CI_PROJECT_DIR/
|
||||||
|
|
||||||
.gdk-qa-base:
|
.gdk-qa-base:
|
||||||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:bundler-${BUNDLER_VERSION}-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23
|
image: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23"
|
||||||
extends:
|
extends:
|
||||||
- .qa-cache
|
- .qa-cache
|
||||||
- .default-retry
|
- .default-retry
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ variables:
|
||||||
CHROME_VERSION: "123"
|
CHROME_VERSION: "123"
|
||||||
DOCKER_VERSION: "24.0.5"
|
DOCKER_VERSION: "24.0.5"
|
||||||
RUBYGEMS_VERSION: "3.4"
|
RUBYGEMS_VERSION: "3.4"
|
||||||
BUNDLER_VERSION: "2.5"
|
|
||||||
GO_VERSION: "1.22"
|
GO_VERSION: "1.22"
|
||||||
NODE_VERSION: "20.12"
|
NODE_VERSION: "20.12"
|
||||||
RUST_VERSION: "1.73"
|
RUST_VERSION: "1.73"
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import {
|
||||||
ISSUABLE_CHANGE_LABEL,
|
ISSUABLE_CHANGE_LABEL,
|
||||||
ISSUABLE_COMMENT_OR_REPLY,
|
ISSUABLE_COMMENT_OR_REPLY,
|
||||||
ISSUABLE_EDIT_DESCRIPTION,
|
ISSUABLE_EDIT_DESCRIPTION,
|
||||||
MR_COPY_SOURCE_BRANCH_NAME,
|
|
||||||
ISSUABLE_COPY_REF,
|
ISSUABLE_COPY_REF,
|
||||||
} from './keybindings';
|
} from './keybindings';
|
||||||
|
|
||||||
|
|
@ -43,7 +42,6 @@ export default class ShortcutsIssuable {
|
||||||
[ISSUABLE_CHANGE_LABEL, () => ShortcutsIssuable.openSidebarDropdown('labels')],
|
[ISSUABLE_CHANGE_LABEL, () => ShortcutsIssuable.openSidebarDropdown('labels')],
|
||||||
[ISSUABLE_COMMENT_OR_REPLY, ShortcutsIssuable.replyWithSelectedText],
|
[ISSUABLE_COMMENT_OR_REPLY, ShortcutsIssuable.replyWithSelectedText],
|
||||||
[ISSUABLE_EDIT_DESCRIPTION, ShortcutsIssuable.editIssue],
|
[ISSUABLE_EDIT_DESCRIPTION, ShortcutsIssuable.editIssue],
|
||||||
[MR_COPY_SOURCE_BRANCH_NAME, () => this.copyBranchName()],
|
|
||||||
[ISSUABLE_COPY_REF, () => this.copyIssuableRef()],
|
[ISSUABLE_COPY_REF, () => this.copyIssuableRef()],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -166,17 +164,6 @@ export default class ShortcutsIssuable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyBranchName() {
|
|
||||||
const button = document.querySelector('.js-source-branch-copy');
|
|
||||||
const branchName = button?.dataset.clipboardText;
|
|
||||||
|
|
||||||
if (branchName) {
|
|
||||||
this.branchInMemoryButton.dataset.clipboardText = branchName;
|
|
||||||
|
|
||||||
this.branchInMemoryButton.dispatchEvent(new CustomEvent('click'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async copyIssuableRef() {
|
async copyIssuableRef() {
|
||||||
const refButton = document.querySelector('.js-copy-reference');
|
const refButton = document.querySelector('.js-copy-reference');
|
||||||
const copiedRef = refButton?.dataset.clipboardText;
|
const copiedRef = refButton?.dataset.clipboardText;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import {
|
||||||
OPERATOR_AFTER,
|
OPERATOR_AFTER,
|
||||||
OPERATOR_BEFORE,
|
OPERATOR_BEFORE,
|
||||||
TOKEN_TYPE_ASSIGNEE,
|
TOKEN_TYPE_ASSIGNEE,
|
||||||
TOKEN_TYPE_MR_ASSIGNEE,
|
|
||||||
TOKEN_TYPE_AUTHOR,
|
TOKEN_TYPE_AUTHOR,
|
||||||
TOKEN_TYPE_CONFIDENTIAL,
|
TOKEN_TYPE_CONFIDENTIAL,
|
||||||
TOKEN_TYPE_CONTACT,
|
TOKEN_TYPE_CONTACT,
|
||||||
|
|
@ -215,26 +214,6 @@ export const filtersMap = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[TOKEN_TYPE_MR_ASSIGNEE]: {
|
|
||||||
[API_PARAM]: {
|
|
||||||
[NORMAL_FILTER]: 'assigneeUsername',
|
|
||||||
[SPECIAL_FILTER]: 'assigneeWildcardId',
|
|
||||||
[ALTERNATIVE_FILTER]: 'assigneeId',
|
|
||||||
},
|
|
||||||
[URL_PARAM]: {
|
|
||||||
[OPERATOR_IS]: {
|
|
||||||
[NORMAL_FILTER]: 'mr_assignee_username',
|
|
||||||
[SPECIAL_FILTER]: 'mr_assignee_id',
|
|
||||||
[ALTERNATIVE_FILTER]: 'mr_assignee_username',
|
|
||||||
},
|
|
||||||
[OPERATOR_NOT]: {
|
|
||||||
[NORMAL_FILTER]: 'not[mr_assignee_username]',
|
|
||||||
},
|
|
||||||
[OPERATOR_OR]: {
|
|
||||||
[NORMAL_FILTER]: 'or[mr_assignee_username]',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[TOKEN_TYPE_ASSIGNEE]: {
|
[TOKEN_TYPE_ASSIGNEE]: {
|
||||||
[API_PARAM]: {
|
[API_PARAM]: {
|
||||||
[NORMAL_FILTER]: 'assigneeUsernames',
|
[NORMAL_FILTER]: 'assigneeUsernames',
|
||||||
|
|
|
||||||
|
|
@ -160,14 +160,14 @@ export default {
|
||||||
@disappear="setStickyHeaderVisible(true)"
|
@disappear="setStickyHeaderVisible(true)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="issue-sticky-header merge-request-sticky-header gl-fixed gl-bg-white gl-hidden md:gl-flex gl-flex-direction-column gl-justify-content-end gl-border-b"
|
class="issue-sticky-header merge-request-sticky-header gl-fixed gl-bg-white gl-hidden md:gl-flex gl-flex-col gl-justify-end gl-border-b"
|
||||||
:class="{ 'gl-invisible': !isStickyHeaderVisible }"
|
:class="{ 'gl-invisible': !isStickyHeaderVisible }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="issue-sticky-header-text gl-display-flex gl-flex-direction-column gl-align-items-center gl-mx-auto gl-w-full"
|
class="issue-sticky-header-text gl-flex gl-flex-col gl-items-center gl-mx-auto gl-w-full"
|
||||||
:class="{ 'container-limited': !isFluidLayout }"
|
:class="{ 'container-limited': !isFluidLayout }"
|
||||||
>
|
>
|
||||||
<div class="gl-w-full gl-display-flex gl-align-items-center gl-gap-2">
|
<div class="gl-w-full gl-flex gl-items-center gl-gap-2">
|
||||||
<status-badge :issuable-type="$options.TYPE_MERGE_REQUEST" :state="badgeState.state" />
|
<status-badge :issuable-type="$options.TYPE_MERGE_REQUEST" :state="badgeState.state" />
|
||||||
<imported-badge v-if="isImported" :importable-type="$options.TYPE_MERGE_REQUEST" />
|
<imported-badge v-if="isImported" :importable-type="$options.TYPE_MERGE_REQUEST" />
|
||||||
<a
|
<a
|
||||||
|
|
@ -175,22 +175,23 @@ export default {
|
||||||
href="#top"
|
href="#top"
|
||||||
class="gl-hidden lg:gl-block gl-font-bold gl-overflow-hidden gl-whitespace-nowrap gl-text-overflow-ellipsis gl-my-0 gl-ml-1 gl-mr-2 gl-text-black-normal"
|
class="gl-hidden lg:gl-block gl-font-bold gl-overflow-hidden gl-whitespace-nowrap gl-text-overflow-ellipsis gl-my-0 gl-ml-1 gl-mr-2 gl-text-black-normal"
|
||||||
></a>
|
></a>
|
||||||
<div class="gl-display-flex gl-align-items-center">
|
<div class="gl-flex gl-items-center">
|
||||||
<gl-sprintf :message="__('%{source} %{copyButton} into %{target}')">
|
<gl-sprintf :message="__('%{source} %{copyButton} into %{target}')">
|
||||||
<template #copyButton>
|
<template #copyButton>
|
||||||
<clipboard-button
|
<clipboard-button
|
||||||
v-gl-tooltip.bottom.html="copySourceBranchTooltip"
|
v-gl-tooltip.bottom.html="copySourceBranchTooltip"
|
||||||
|
:title="copySourceBranchTooltip"
|
||||||
:text="getNoteableData.source_branch"
|
:text="getNoteableData.source_branch"
|
||||||
size="small"
|
size="small"
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
class="gl-m-0! gl-mx-1! js-source-branch-copy gl-align-self-center"
|
class="gl-mx-1"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #source>
|
<template #source>
|
||||||
<gl-link
|
<gl-link
|
||||||
:title="getNoteableData.source_branch"
|
:title="getNoteableData.source_branch"
|
||||||
:href="getNoteableData.source_branch_path"
|
:href="getNoteableData.source_branch_path"
|
||||||
class="gl-text-blue-500! gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-text-truncate gl-max-w-26"
|
class="gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-mt-2 gl-truncate gl-max-w-26"
|
||||||
data-testid="source-branch"
|
data-testid="source-branch"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|
@ -208,7 +209,7 @@ export default {
|
||||||
<gl-link
|
<gl-link
|
||||||
:title="getNoteableData.target_branch"
|
:title="getNoteableData.target_branch"
|
||||||
:href="getNoteableData.target_branch_path"
|
:href="getNoteableData.target_branch_path"
|
||||||
class="gl-text-blue-500! gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-text-truncate gl-max-w-26 gl-ml-2"
|
class="gl-text-blue-500! gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-px-2 gl-mt-2 gl-truncate gl-max-w-26 gl-ml-2"
|
||||||
>
|
>
|
||||||
{{ getNoteableData.target_branch }}
|
{{ getNoteableData.target_branch }}
|
||||||
</gl-link>
|
</gl-link>
|
||||||
|
|
@ -216,21 +217,16 @@ export default {
|
||||||
</gl-sprintf>
|
</gl-sprintf>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gl-w-full gl-display-flex">
|
<div class="gl-w-full gl-flex">
|
||||||
<ul
|
<ul
|
||||||
class="merge-request-tabs nav-tabs nav nav-links gl-display-flex gl-flex-nowrap gl-m-0 gl-p-0 gl-border-b-0"
|
class="merge-request-tabs nav-tabs nav nav-links gl-flex gl-flex-nowrap gl-m-0 gl-p-0 gl-border-b-0"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
:key="tab[0]"
|
:key="tab[0]"
|
||||||
:class="{ active: activeTab === tab[0] }"
|
:class="{ active: activeTab === tab[0] }"
|
||||||
>
|
>
|
||||||
<gl-link
|
<gl-link :href="tab[2]" :data-action="tab[0]" class="!gl-py-4" @click="visitTab">
|
||||||
:href="tab[2]"
|
|
||||||
:data-action="tab[0]"
|
|
||||||
class="!gl-outline-none gl-py-4!"
|
|
||||||
@click="visitTab"
|
|
||||||
>
|
|
||||||
{{ tab[1] }}
|
{{ tab[1] }}
|
||||||
<gl-badge variant="muted" size="sm">
|
<gl-badge variant="muted" size="sm">
|
||||||
<template v-if="index === 0 && discussionCounter !== 0">
|
<template v-if="index === 0 && discussionCounter !== 0">
|
||||||
|
|
@ -243,12 +239,9 @@ export default {
|
||||||
</gl-link>
|
</gl-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="gl-hidden lg:gl-flex gl-align-items-center gl-ml-auto">
|
<div class="gl-hidden lg:gl-flex gl-items-center gl-ml-auto">
|
||||||
<discussion-counter :blocks-merge="blocksMerge" hide-options />
|
<discussion-counter :blocks-merge="blocksMerge" hide-options />
|
||||||
<div
|
<div v-if="isSignedIn" :class="{ 'gl-flex gl-gap-3': isNotificationsTodosButtons }">
|
||||||
v-if="isSignedIn"
|
|
||||||
:class="{ 'gl-display-flex gl-gap-3': isNotificationsTodosButtons }"
|
|
||||||
>
|
|
||||||
<todo-widget
|
<todo-widget
|
||||||
:issuable-id="issuableId"
|
:issuable-id="issuableId"
|
||||||
:issuable-iid="issuableIid"
|
:issuable-iid="issuableIid"
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import {
|
||||||
TOKEN_TITLE_SOURCE_BRANCH,
|
TOKEN_TITLE_SOURCE_BRANCH,
|
||||||
TOKEN_TYPE_SOURCE_BRANCH,
|
TOKEN_TYPE_SOURCE_BRANCH,
|
||||||
TOKEN_TITLE_ASSIGNEE,
|
TOKEN_TITLE_ASSIGNEE,
|
||||||
TOKEN_TYPE_MR_ASSIGNEE,
|
TOKEN_TYPE_ASSIGNEE,
|
||||||
TOKEN_TITLE_MILESTONE,
|
TOKEN_TITLE_MILESTONE,
|
||||||
TOKEN_TYPE_MILESTONE,
|
TOKEN_TYPE_MILESTONE,
|
||||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||||
|
|
@ -178,7 +178,7 @@ export default {
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: TOKEN_TYPE_MR_ASSIGNEE,
|
type: TOKEN_TYPE_ASSIGNEE,
|
||||||
title: TOKEN_TITLE_ASSIGNEE,
|
title: TOKEN_TITLE_ASSIGNEE,
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
token: UserToken,
|
token: UserToken,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ query getMergeRequests(
|
||||||
$fullPath: ID!
|
$fullPath: ID!
|
||||||
$sort: MergeRequestSort
|
$sort: MergeRequestSort
|
||||||
$state: MergeRequestState
|
$state: MergeRequestState
|
||||||
$assigneeUsername: String
|
$assigneeUsernames: String
|
||||||
|
$assigneeWildcardId: AssigneeWildcardId
|
||||||
$authorUsername: String
|
$authorUsername: String
|
||||||
$draft: Boolean
|
$draft: Boolean
|
||||||
$milestoneTitle: String
|
$milestoneTitle: String
|
||||||
|
|
@ -24,7 +25,8 @@ query getMergeRequests(
|
||||||
mergeRequests(
|
mergeRequests(
|
||||||
sort: $sort
|
sort: $sort
|
||||||
state: $state
|
state: $state
|
||||||
assigneeUsername: $assigneeUsername
|
assigneeUsername: $assigneeUsernames
|
||||||
|
assigneeWildcardId: $assigneeWildcardId
|
||||||
authorUsername: $authorUsername
|
authorUsername: $authorUsername
|
||||||
draft: $draft
|
draft: $draft
|
||||||
milestoneTitle: $milestoneTitle
|
milestoneTitle: $milestoneTitle
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
query getMergeRequestsCount(
|
query getMergeRequestsCount(
|
||||||
$fullPath: ID!
|
$fullPath: ID!
|
||||||
|
$assigneeWildcardId: AssigneeWildcardId
|
||||||
|
$assigneeUsernames: String
|
||||||
$milestoneTitle: String
|
$milestoneTitle: String
|
||||||
$milestoneWildcardId: MilestoneWildcardId
|
$milestoneWildcardId: MilestoneWildcardId
|
||||||
) {
|
) {
|
||||||
|
|
@ -7,6 +9,8 @@ query getMergeRequestsCount(
|
||||||
id
|
id
|
||||||
openedMergeRequests: mergeRequests(
|
openedMergeRequests: mergeRequests(
|
||||||
state: opened
|
state: opened
|
||||||
|
assigneeUsername: $assigneeUsernames
|
||||||
|
assigneeWildcardId: $assigneeWildcardId
|
||||||
milestoneTitle: $milestoneTitle
|
milestoneTitle: $milestoneTitle
|
||||||
milestoneWildcardId: $milestoneWildcardId
|
milestoneWildcardId: $milestoneWildcardId
|
||||||
) {
|
) {
|
||||||
|
|
@ -14,6 +18,8 @@ query getMergeRequestsCount(
|
||||||
}
|
}
|
||||||
mergedMergeRequests: mergeRequests(
|
mergedMergeRequests: mergeRequests(
|
||||||
state: merged
|
state: merged
|
||||||
|
assigneeUsername: $assigneeUsernames
|
||||||
|
assigneeWildcardId: $assigneeWildcardId
|
||||||
milestoneTitle: $milestoneTitle
|
milestoneTitle: $milestoneTitle
|
||||||
milestoneWildcardId: $milestoneWildcardId
|
milestoneWildcardId: $milestoneWildcardId
|
||||||
) {
|
) {
|
||||||
|
|
@ -21,6 +27,8 @@ query getMergeRequestsCount(
|
||||||
}
|
}
|
||||||
closedMergeRequests: mergeRequests(
|
closedMergeRequests: mergeRequests(
|
||||||
state: closed
|
state: closed
|
||||||
|
assigneeUsername: $assigneeUsernames
|
||||||
|
assigneeWildcardId: $assigneeWildcardId
|
||||||
milestoneTitle: $milestoneTitle
|
milestoneTitle: $milestoneTitle
|
||||||
milestoneWildcardId: $milestoneWildcardId
|
milestoneWildcardId: $milestoneWildcardId
|
||||||
) {
|
) {
|
||||||
|
|
@ -28,6 +36,8 @@ query getMergeRequestsCount(
|
||||||
}
|
}
|
||||||
allMergeRequests: mergeRequests(
|
allMergeRequests: mergeRequests(
|
||||||
state: all
|
state: all
|
||||||
|
assigneeUsername: $assigneeUsernames
|
||||||
|
assigneeWildcardId: $assigneeWildcardId
|
||||||
milestoneTitle: $milestoneTitle
|
milestoneTitle: $milestoneTitle
|
||||||
milestoneWildcardId: $milestoneWildcardId
|
milestoneWildcardId: $milestoneWildcardId
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -39,20 +39,20 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="revision-card gl-flex-basis-half">
|
<div class="revision-card gl-flex-basis-half gl-min-w-0">
|
||||||
<h2 class="gl-font-base gl-mt-0">
|
<h2 class="gl-font-base gl-mt-0">
|
||||||
{{ revisionText }}
|
{{ revisionText }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="sm:gl-flex gl-align-items-center gl-gap-3">
|
<div class="gl-flex gl-flex-direction-column gl-sm-flex-direction-row gl-gap-3">
|
||||||
<repo-dropdown
|
<repo-dropdown
|
||||||
class="gl-sm-w-half"
|
class="gl-flex-basis-half gl-min-w-0 gl-max-w-full"
|
||||||
:params-name="paramsName"
|
:params-name="paramsName"
|
||||||
:projects="projects"
|
:projects="projects"
|
||||||
:selected-project="selectedProject"
|
:selected-project="selectedProject"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
/>
|
/>
|
||||||
<revision-dropdown
|
<revision-dropdown
|
||||||
class="gl-sm-w-half gl-mt-3 gl-sm-mt-0"
|
class="gl-flex-basis-half gl-min-w-0 gl-max-w-full"
|
||||||
:refs-project-path="refsProjectPath"
|
:refs-project-path="refsProjectPath"
|
||||||
:params-name="paramsName"
|
:params-name="paramsName"
|
||||||
:params-branch="paramsBranch"
|
:params-branch="paramsBranch"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlCollapsibleListbox, GlSprintf } from '@gitlab/ui';
|
import { GlCollapsibleListbox, GlSprintf } from '@gitlab/ui';
|
||||||
|
import { InternalEvents } from '~/tracking';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
|
import { EVENT_CLICK_COMMANDS_SUB_MENU_IN_COMMAND_PALETTE } from '~/super_sidebar/components/global_search/tracking_constants';
|
||||||
|
|
||||||
|
const trackingMixin = InternalEvents.mixin();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CommandsOverviewDropdown',
|
name: 'CommandsOverviewDropdown',
|
||||||
components: { GlCollapsibleListbox, GlSprintf },
|
components: { GlCollapsibleListbox, GlSprintf },
|
||||||
|
mixins: [trackingMixin],
|
||||||
i18n: {
|
i18n: {
|
||||||
header: s__('GlobalSearch|I’m looking for'),
|
header: s__("GlobalSearch|I'm looking for"),
|
||||||
button: s__('GlobalSearch|Commands %{link1Start}⌘%{link1End} %{link2Start}k%{link2End}'),
|
button: s__('GlobalSearch|Commands %{link1Start}⌘%{link1End} %{link2Start}k%{link2End}'),
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -26,6 +31,7 @@ export default {
|
||||||
this.$refs.commandsDropdown.close();
|
this.$refs.commandsDropdown.close();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
EVENT_CLICK_COMMANDS_SUB_MENU_IN_COMMAND_PALETTE,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -37,6 +43,7 @@ export default {
|
||||||
:header-text="$options.i18n.header"
|
:header-text="$options.i18n.header"
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
@select="emitSelected"
|
@select="emitSelected"
|
||||||
|
@shown="trackEvent($options.EVENT_CLICK_COMMANDS_SUB_MENU_IN_COMMAND_PALETTE)"
|
||||||
>
|
>
|
||||||
<template #toggle>
|
<template #toggle>
|
||||||
<button class="gl-border-0 gl-rounded-base">
|
<button class="gl-border-0 gl-rounded-base">
|
||||||
|
|
|
||||||
|
|
@ -80,3 +80,6 @@ export const OVERLAY_PROJECT = s__('GlobalSearch|Go to project %{kbdStart}↵%{k
|
||||||
export const OVERLAY_FILE = s__('GlobalSearch|Go to file %{kbdStart}↵%{kbdEnd}');
|
export const OVERLAY_FILE = s__('GlobalSearch|Go to file %{kbdStart}↵%{kbdEnd}');
|
||||||
|
|
||||||
export const OVERLAY_GOTO = s__('GlobalSearch|Go to %{kbdStart}↵%{kbdEnd}');
|
export const OVERLAY_GOTO = s__('GlobalSearch|Go to %{kbdStart}↵%{kbdEnd}');
|
||||||
|
|
||||||
|
export const FREQUENTLY_VISITED_PROJECTS_HANDLE = 'FREQUENTLY_VISITED_PROJECTS_HANDLE';
|
||||||
|
export const FREQUENTLY_VISITED_GROUPS_HANDLE = 'FREQUENTLY_VISITED_GROUPS_HANDLE';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
import currentUserFrecentGroupsQuery from '~/super_sidebar/graphql/queries/current_user_frecent_groups.query.graphql';
|
import currentUserFrecentGroupsQuery from '~/super_sidebar/graphql/queries/current_user_frecent_groups.query.graphql';
|
||||||
|
import { FREQUENTLY_VISITED_GROUPS_HANDLE } from '~/super_sidebar/components/global_search/command_palette/constants';
|
||||||
import FrequentItems from './frequent_items.vue';
|
import FrequentItems from './frequent_items.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -19,6 +20,7 @@ export default {
|
||||||
viewAllText: s__('Navigation|View all my groups'),
|
viewAllText: s__('Navigation|View all my groups'),
|
||||||
emptyStateText: s__('Navigation|Groups you visit often will appear here.'),
|
emptyStateText: s__('Navigation|Groups you visit often will appear here.'),
|
||||||
},
|
},
|
||||||
|
FREQUENTLY_VISITED_GROUPS_HANDLE,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -33,5 +35,6 @@ export default {
|
||||||
:view-all-items-path="groupsPath"
|
:view-all-items-path="groupsPath"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
|
@action="$emit('action', $options.FREQUENTLY_VISITED_GROUPS_HANDLE)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ export default {
|
||||||
:key="item.forDropdown.id"
|
:key="item.forDropdown.id"
|
||||||
:item="item.forDropdown"
|
:item="item.forDropdown"
|
||||||
class="show-on-focus-or-hover--context show-hover-layover"
|
class="show-on-focus-or-hover--context show-hover-layover"
|
||||||
|
@action="$emit('action')"
|
||||||
>
|
>
|
||||||
<template #list-item><frequent-item :item="item.forRenderer" /></template>
|
<template #list-item><frequent-item :item="item.forRenderer" /></template>
|
||||||
</gl-disclosure-dropdown-item>
|
</gl-disclosure-dropdown-item>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
import currentUserFrecentProjectsQuery from '~/super_sidebar/graphql/queries/current_user_frecent_projects.query.graphql';
|
import currentUserFrecentProjectsQuery from '~/super_sidebar/graphql/queries/current_user_frecent_projects.query.graphql';
|
||||||
|
import { FREQUENTLY_VISITED_PROJECTS_HANDLE } from '~/super_sidebar/components/global_search/command_palette/constants';
|
||||||
import FrequentItems from './frequent_items.vue';
|
import FrequentItems from './frequent_items.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -19,6 +20,7 @@ export default {
|
||||||
viewAllText: s__('Navigation|View all my projects'),
|
viewAllText: s__('Navigation|View all my projects'),
|
||||||
emptyStateText: s__('Navigation|Projects you visit often will appear here.'),
|
emptyStateText: s__('Navigation|Projects you visit often will appear here.'),
|
||||||
},
|
},
|
||||||
|
FREQUENTLY_VISITED_PROJECTS_HANDLE,
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -33,5 +35,6 @@ export default {
|
||||||
:view-all-items-path="projectsPath"
|
:view-all-items-path="projectsPath"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
|
@action="$emit('action', $options.FREQUENTLY_VISITED_PROJECTS_HANDLE)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,26 @@
|
||||||
<script>
|
<script>
|
||||||
|
import {
|
||||||
|
FREQUENTLY_VISITED_PROJECTS_HANDLE,
|
||||||
|
FREQUENTLY_VISITED_GROUPS_HANDLE,
|
||||||
|
} from '~/super_sidebar/components/global_search/command_palette/constants';
|
||||||
|
|
||||||
|
import {
|
||||||
|
EVENT_CLICK_FREQUENT_GROUP_IN_COMMAND_PALETTE,
|
||||||
|
EVENT_CLICK_FREQUENT_PROJECT_IN_COMMAND_PALETTE,
|
||||||
|
} from '~/super_sidebar/components/global_search/tracking_constants';
|
||||||
|
|
||||||
|
import { InternalEvents } from '~/tracking';
|
||||||
import DefaultPlaces from './global_search_default_places.vue';
|
import DefaultPlaces from './global_search_default_places.vue';
|
||||||
import DefaultIssuables from './global_search_default_issuables.vue';
|
import DefaultIssuables from './global_search_default_issuables.vue';
|
||||||
import FrequentGroups from './frequent_groups.vue';
|
import FrequentGroups from './frequent_groups.vue';
|
||||||
import FrequentProjects from './frequent_projects.vue';
|
import FrequentProjects from './frequent_projects.vue';
|
||||||
|
|
||||||
const components = [DefaultPlaces, FrequentProjects, FrequentGroups, DefaultIssuables];
|
const components = [DefaultPlaces, FrequentProjects, FrequentGroups, DefaultIssuables];
|
||||||
|
const trackingMixin = InternalEvents.mixin();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GlobalSearchDefaultItems',
|
name: 'GlobalSearchDefaultItems',
|
||||||
|
mixins: [trackingMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// The components here are expected to:
|
// The components here are expected to:
|
||||||
|
|
@ -36,6 +49,21 @@ export default {
|
||||||
class: 'gl-mt-3',
|
class: 'gl-mt-3',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
trackItems(type) {
|
||||||
|
switch (type) {
|
||||||
|
case FREQUENTLY_VISITED_PROJECTS_HANDLE: {
|
||||||
|
this.trackEvent(EVENT_CLICK_FREQUENT_PROJECT_IN_COMMAND_PALETTE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FREQUENTLY_VISITED_GROUPS_HANDLE: {
|
||||||
|
this.trackEvent(EVENT_CLICK_FREQUENT_GROUP_IN_COMMAND_PALETTE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -48,6 +76,7 @@ export default {
|
||||||
:key="name"
|
:key="name"
|
||||||
v-bind="attrs(index)"
|
v-bind="attrs(index)"
|
||||||
@nothing-to-render="remove(name)"
|
@nothing-to-render="remove(name)"
|
||||||
|
@action="trackItems"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,9 @@ export const EVENT_CLICK_RECENT_EPIC_RESULT_IN_COMMAND_PALETTE =
|
||||||
export const EVENT_CLICK_RECENT_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE =
|
export const EVENT_CLICK_RECENT_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE =
|
||||||
'click_recent_merge_request_result_in_command_palette';
|
'click_recent_merge_request_result_in_command_palette';
|
||||||
export const EVENT_CLICK_USER_RESULT_IN_COMMAND_PALETTE = 'click_user_result_in_command_palette';
|
export const EVENT_CLICK_USER_RESULT_IN_COMMAND_PALETTE = 'click_user_result_in_command_palette';
|
||||||
|
export const EVENT_CLICK_FREQUENT_PROJECT_IN_COMMAND_PALETTE =
|
||||||
|
'click_frequent_project_in_command_palette';
|
||||||
|
export const EVENT_CLICK_FREQUENT_GROUP_IN_COMMAND_PALETTE =
|
||||||
|
'click_frequent_group_in_command_palette';
|
||||||
|
export const EVENT_CLICK_COMMANDS_SUB_MENU_IN_COMMAND_PALETTE =
|
||||||
|
'click_commands_sub_menu_in_command_palette';
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,6 @@ export const TOKEN_TITLE_CLOSED = __('Closed date');
|
||||||
export const TOKEN_TYPE_APPROVED_BY = 'approved-by';
|
export const TOKEN_TYPE_APPROVED_BY = 'approved-by';
|
||||||
export const TOKEN_TYPE_MERGE_USER = 'merge-user';
|
export const TOKEN_TYPE_MERGE_USER = 'merge-user';
|
||||||
export const TOKEN_TYPE_ASSIGNEE = 'assignee';
|
export const TOKEN_TYPE_ASSIGNEE = 'assignee';
|
||||||
export const TOKEN_TYPE_MR_ASSIGNEE = 'mr-assignee';
|
|
||||||
export const TOKEN_TYPE_AUTHOR = 'author';
|
export const TOKEN_TYPE_AUTHOR = 'author';
|
||||||
export const TOKEN_TYPE_CONFIDENTIAL = 'confidential';
|
export const TOKEN_TYPE_CONFIDENTIAL = 'confidential';
|
||||||
export const TOKEN_TYPE_CONTACT = 'contact';
|
export const TOKEN_TYPE_CONTACT = 'contact';
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ module ResolvesMergeRequests
|
||||||
end
|
end
|
||||||
|
|
||||||
rewrite_param_name(args, :reviewer_wildcard_id, :reviewer_id)
|
rewrite_param_name(args, :reviewer_wildcard_id, :reviewer_id)
|
||||||
|
rewrite_param_name(args, :assignee_wildcard_id, :assignee_id)
|
||||||
|
|
||||||
mr_finder = MergeRequestsFinder.new(current_user, args.compact)
|
mr_finder = MergeRequestsFinder.new(current_user, args.compact)
|
||||||
finder = Gitlab::Graphql::Loaders::IssuableLoader.new(mr_parent, mr_finder)
|
finder = Gitlab::Graphql::Loaders::IssuableLoader.new(mr_parent, mr_finder)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ module Resolvers
|
||||||
argument :assignee_username, GraphQL::Types::String,
|
argument :assignee_username, GraphQL::Types::String,
|
||||||
required: false,
|
required: false,
|
||||||
description: 'Username of the assignee.'
|
description: 'Username of the assignee.'
|
||||||
|
argument :assignee_wildcard_id, ::Types::AssigneeWildcardIdEnum,
|
||||||
|
required: false,
|
||||||
|
description: 'Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername.'
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.accept_author
|
def self.accept_author
|
||||||
|
|
@ -126,6 +129,8 @@ module Resolvers
|
||||||
description: 'Title of the milestone.'
|
description: 'Title of the milestone.'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
validates mutually_exclusive: [:assignee_username, :assignee_wildcard_id]
|
||||||
|
validates mutually_exclusive: [:reviewer_username, :reviewer_wildcard_id]
|
||||||
validates mutually_exclusive: [:milestone_title, :milestone_wildcard_id]
|
validates mutually_exclusive: [:milestone_title, :milestone_wildcard_id]
|
||||||
|
|
||||||
def self.single
|
def self.single
|
||||||
|
|
|
||||||
|
|
@ -308,7 +308,7 @@ module MergeRequestsHelper
|
||||||
copy_action_description = _('Copy branch name')
|
copy_action_description = _('Copy branch name')
|
||||||
copy_action_shortcut = 'b'
|
copy_action_shortcut = 'b'
|
||||||
copy_button_title = "#{copy_action_description} <kbd class='flat ml-1' aria-hidden=true>#{copy_action_shortcut}</kbd>"
|
copy_button_title = "#{copy_action_description} <kbd class='flat ml-1' aria-hidden=true>#{copy_action_shortcut}</kbd>"
|
||||||
copy_button = clipboard_button(text: merge_request.source_branch, title: copy_button_title, aria_keyshortcuts: copy_action_shortcut, aria_label: copy_action_description, class: '!gl-hidden md:!gl-inline-block js-source-branch-copy gl-mx-1')
|
copy_button = clipboard_button(text: merge_request.source_branch, title: copy_button_title, aria_keyshortcuts: copy_action_shortcut, aria_label: copy_action_description, class: '!gl-hidden md:!gl-inline-block gl-mx-1')
|
||||||
|
|
||||||
target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), title: merge_request.target_branch, class: 'ref-container gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mx-2'
|
target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), title: merge_request.target_branch, class: 'ref-container gl-display-inline-block gl-text-truncate gl-max-w-26 gl-mx-2'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@
|
||||||
|
|
||||||
.detail-page-header.border-bottom-0.gl-block.gl-pt-5{ class: "sm:!gl-flex #{'is-merge-request' if !fluid_layout}" }
|
.detail-page-header.border-bottom-0.gl-block.gl-pt-5{ class: "sm:!gl-flex #{'is-merge-request' if !fluid_layout}" }
|
||||||
.detail-page-header-body
|
.detail-page-header-body
|
||||||
%h1.title.page-title.gl-font-size-h-display.gl-my-0.gl-display-inline-block.gl-flex-grow-1.gl-break-anywhere{ data: { testid: 'title-content' } }
|
%h1.title.gl-heading-1.gl-my-0.gl-block.gl-grow.gl-break-anywhere{ class: '!gl-m-0', data: { testid: 'title-content' } }
|
||||||
= markdown_field(@merge_request, :title)
|
= markdown_field(@merge_request, :title)
|
||||||
|
|
||||||
- unless hide_gutter_toggle
|
- unless hide_gutter_toggle
|
||||||
%div
|
%div
|
||||||
= render Pajamas::ButtonComponent.new(icon: "chevron-double-lg-left", button_options: { class: "btn-icon gl-float-right gl-block gutter-toggle issuable-gutter-toggle js-sidebar-toggle sm:!gl-hidden" })
|
= render Pajamas::ButtonComponent.new(icon: "chevron-double-lg-left", button_options: { class: "btn-icon gl-float-right gl-block gutter-toggle issuable-gutter-toggle js-sidebar-toggle sm:!gl-hidden" })
|
||||||
|
|
||||||
.detail-page-header-actions.gl-align-self-start.is-merge-request.js-issuable-actions.gl-display-flex
|
.detail-page-header-actions.gl-self-start.is-merge-request.js-issuable-actions.gl-flex.gl-mt-1
|
||||||
- if can_update_merge_request
|
- if can_update_merge_request
|
||||||
- edit_action_description = _('Edit merge request')
|
- edit_action_description = _('Edit merge request')
|
||||||
- edit_action_shortcut = 'e'
|
- edit_action_shortcut = 'e'
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
= render Pajamas::ButtonComponent.new(href: edit_project_merge_request_path(@project, @merge_request), button_options: { aria: {label: edit_action_description, keyshortcuts: edit_action_shortcut}, class: "gl-hidden sm:gl-block gl-align-self-start has-tooltip js-issuable-edit", data: { html: "true", testid: "edit-title-button" }, title: edit_button_title }) do
|
= render Pajamas::ButtonComponent.new(href: edit_project_merge_request_path(@project, @merge_request), button_options: { aria: {label: edit_action_description, keyshortcuts: edit_action_shortcut}, class: "gl-hidden sm:gl-block gl-align-self-start has-tooltip js-issuable-edit", data: { html: "true", testid: "edit-title-button" }, title: edit_button_title }) do
|
||||||
= _('Edit')
|
= _('Edit')
|
||||||
|
|
||||||
.gl-display-flex.gl-flex-direction-column.gl-sm-flex-direction-row.gl-gap-3.gl-w-full.gl-sm-w-auto.gl-mt-2.gl-sm-mt-0
|
.gl-flex.gl-flex-col.sm:gl-flex-row.gl-gap-3.gl-w-full.sm:gl-w-auto.gl-mt-2.sm:gl-mt-0
|
||||||
- if @merge_request.source_project
|
- if @merge_request.source_project
|
||||||
= render 'projects/merge_requests/code_dropdown'
|
= render 'projects/merge_requests/code_dropdown'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,10 @@ module ApplicationWorker
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_ip_address_state
|
||||||
|
set(ip_address_state: ::Gitlab::IpAddressState.current)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def do_push_bulk(args_list)
|
def do_push_bulk(args_list)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
description: User clicks commands sub-menu in footer
|
||||||
|
internal_events: true
|
||||||
|
action: click_commands_sub_menu_in_command_palette
|
||||||
|
identifiers:
|
||||||
|
- namespace
|
||||||
|
- user
|
||||||
|
product_group: global_search
|
||||||
|
milestone: '17.1'
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154150
|
||||||
|
distributions:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tiers:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
description: User clicks a group in the frequent groups section
|
||||||
|
internal_events: true
|
||||||
|
action: click_frequent_group_in_command_palette
|
||||||
|
identifiers:
|
||||||
|
- namespace
|
||||||
|
- user
|
||||||
|
product_group: global_search
|
||||||
|
milestone: '17.1'
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154150
|
||||||
|
distributions:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tiers:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
description: User clicks a project in the frequent projects section
|
||||||
|
internal_events: true
|
||||||
|
action: click_frequent_project_in_command_palette
|
||||||
|
identifiers:
|
||||||
|
- namespace
|
||||||
|
- user
|
||||||
|
product_group: global_search
|
||||||
|
milestone: '17.1'
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154150
|
||||||
|
distributions:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tiers:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
|
@ -784,6 +784,9 @@ Gitlab.ee do
|
||||||
Settings.cron_jobs['elastic_index_bulk_cron_worker'] ||= {}
|
Settings.cron_jobs['elastic_index_bulk_cron_worker'] ||= {}
|
||||||
Settings.cron_jobs['elastic_index_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
|
Settings.cron_jobs['elastic_index_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
|
||||||
Settings.cron_jobs['elastic_index_bulk_cron_worker']['job_class'] ||= 'ElasticIndexBulkCronWorker'
|
Settings.cron_jobs['elastic_index_bulk_cron_worker']['job_class'] ||= 'ElasticIndexBulkCronWorker'
|
||||||
|
Settings.cron_jobs['elastic_index_embedding_bulk_cron_worker'] ||= {}
|
||||||
|
Settings.cron_jobs['elastic_index_embedding_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
|
||||||
|
Settings.cron_jobs['elastic_index_embedding_bulk_cron_worker']['job_class'] ||= 'Search::ElasticIndexEmbeddingBulkCronWorker'
|
||||||
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker'] ||= {}
|
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker'] ||= {}
|
||||||
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
|
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
|
||||||
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker']['job_class'] ||= 'ElasticIndexInitialBulkCronWorker'
|
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker']['job_class'] ||= 'ElasticIndexInitialBulkCronWorker'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
key_path: redis_hll_counters.count_distinct_user_id_from_click_commands_sub_menu_in_command_palette_monthly
|
||||||
|
description: Monthly count of unique users clicking commands sub-menu in footer
|
||||||
|
product_group: global_search
|
||||||
|
performance_indicator_type: []
|
||||||
|
value_type: number
|
||||||
|
status: active
|
||||||
|
milestone: '17.1'
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154150
|
||||||
|
time_frame: 28d
|
||||||
|
data_source: internal_events
|
||||||
|
data_category: optional
|
||||||
|
distribution:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tier:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
events:
|
||||||
|
- name: click_commands_sub_menu_in_command_palette
|
||||||
|
unique: user.id
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
key_path: redis_hll_counters.count_distinct_user_id_from_click_frequent_group_in_command_palette_monthly
|
||||||
|
description: Monthly count of unique users clicking a group in the frequent groups section
|
||||||
|
product_group: global_search
|
||||||
|
performance_indicator_type: []
|
||||||
|
value_type: number
|
||||||
|
status: active
|
||||||
|
milestone: '17.1'
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154150
|
||||||
|
time_frame: 28d
|
||||||
|
data_source: internal_events
|
||||||
|
data_category: optional
|
||||||
|
distribution:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tier:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
events:
|
||||||
|
- name: click_frequent_group_in_command_palette
|
||||||
|
unique: user.id
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
key_path: redis_hll_counters.count_distinct_user_id_from_click_frequent_project_in_command_palette_monthly
|
||||||
|
description: Monthly count of unique users clicking a project in the frequent projects section
|
||||||
|
product_group: global_search
|
||||||
|
performance_indicator_type: []
|
||||||
|
value_type: number
|
||||||
|
status: active
|
||||||
|
milestone: '17.1'
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154150
|
||||||
|
time_frame: 28d
|
||||||
|
data_source: internal_events
|
||||||
|
data_category: optional
|
||||||
|
distribution:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tier:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
events:
|
||||||
|
- name: click_frequent_project_in_command_palette
|
||||||
|
unique: user.id
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
key_path: redis_hll_counters.count_distinct_user_id_from_click_commands_sub_menu_in_command_palette_weekly
|
||||||
|
description: Weekly count of unique users who click commands sub-menu in footer
|
||||||
|
product_group: global_search
|
||||||
|
performance_indicator_type: []
|
||||||
|
value_type: number
|
||||||
|
status: active
|
||||||
|
milestone: '17.1'
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154150
|
||||||
|
time_frame: 7d
|
||||||
|
data_source: internal_events
|
||||||
|
data_category: optional
|
||||||
|
distribution:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tier:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
events:
|
||||||
|
- name: click_commands_sub_menu_in_command_palette
|
||||||
|
unique: user.id
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
key_path: redis_hll_counters.count_distinct_user_id_from_click_frequent_group_in_command_palette_weekly
|
||||||
|
description: Weekly count of unique users who click a group in the frequent groups section
|
||||||
|
product_group: global_search
|
||||||
|
performance_indicator_type: []
|
||||||
|
value_type: number
|
||||||
|
status: active
|
||||||
|
milestone: '17.1'
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154150
|
||||||
|
time_frame: 7d
|
||||||
|
data_source: internal_events
|
||||||
|
data_category: optional
|
||||||
|
distribution:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tier:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
events:
|
||||||
|
- name: click_frequent_group_in_command_palette
|
||||||
|
unique: user.id
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
key_path: redis_hll_counters.count_distinct_user_id_from_click_frequent_project_in_command_palette_weekly
|
||||||
|
description: Weekly count of unique users who click a project in the frequent projects section
|
||||||
|
product_group: global_search
|
||||||
|
performance_indicator_type: []
|
||||||
|
value_type: number
|
||||||
|
status: active
|
||||||
|
milestone: '17.1'
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154150
|
||||||
|
time_frame: 7d
|
||||||
|
data_source: internal_events
|
||||||
|
data_category: optional
|
||||||
|
distribution:
|
||||||
|
- ce
|
||||||
|
- ee
|
||||||
|
tier:
|
||||||
|
- free
|
||||||
|
- premium
|
||||||
|
- ultimate
|
||||||
|
events:
|
||||||
|
- name: click_frequent_project_in_command_palette
|
||||||
|
unique: user.id
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
migration_job_name: BackfillStatusCheckResponsesProjectId
|
||||||
|
description: Backfills sharding key `status_check_responses.project_id` from `merge_requests`.
|
||||||
|
feature_category: compliance_management
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156033
|
||||||
|
milestone: '17.1'
|
||||||
|
queued_migration_version: 20240611142352
|
||||||
|
finalize_after: '2024-07-22'
|
||||||
|
finalized_by: # version of the migration that finalized this BBM
|
||||||
|
|
@ -19,3 +19,4 @@ desired_sharding_key:
|
||||||
table: merge_requests
|
table: merge_requests
|
||||||
sharding_key: target_project_id
|
sharding_key: target_project_id
|
||||||
belongs_to: merge_request
|
belongs_to: merge_request
|
||||||
|
desired_sharding_key_migration_job_name: BackfillStatusCheckResponsesProjectId
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,5 @@ milestone: '13.0'
|
||||||
gitlab_schema: gitlab_main_cell
|
gitlab_schema: gitlab_main_cell
|
||||||
allow_cross_foreign_keys:
|
allow_cross_foreign_keys:
|
||||||
- gitlab_main_clusterwide
|
- gitlab_main_clusterwide
|
||||||
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457095
|
sharding_key:
|
||||||
|
organization_id: organizations
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddOrganizationToVulnerabilityExports < Gitlab::Database::Migration[2.2]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
milestone '17.1'
|
||||||
|
|
||||||
|
INDEX_NAME = 'index_vulnerability_exports_on_organization_id'
|
||||||
|
|
||||||
|
def up
|
||||||
|
with_lock_retries do
|
||||||
|
add_column :vulnerability_exports, :organization_id, :bigint, null: false,
|
||||||
|
default: Organizations::Organization::DEFAULT_ORGANIZATION_ID,
|
||||||
|
if_not_exists: true
|
||||||
|
end
|
||||||
|
|
||||||
|
add_concurrent_foreign_key :vulnerability_exports, :organizations, column: :organization_id, on_delete: :cascade
|
||||||
|
add_concurrent_index :vulnerability_exports, :organization_id, name: INDEX_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
with_lock_retries do
|
||||||
|
remove_foreign_key :vulnerability_exports, column: :organization_id
|
||||||
|
end
|
||||||
|
|
||||||
|
remove_concurrent_index_by_name :vulnerability_exports, INDEX_NAME
|
||||||
|
|
||||||
|
with_lock_retries do
|
||||||
|
remove_column :vulnerability_exports, :organization_id, if_exists: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||||
|
# for more information on how to write migrations for GitLab.
|
||||||
|
|
||||||
|
class UpdateUniqueUserNamespaceIndexOnMemberApprovals < Gitlab::Database::Migration[2.2]
|
||||||
|
milestone '17.1'
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
OLD_INDEX_NAME = 'unique_index_member_approvals_on_pending_status'
|
||||||
|
NEW_INDEX_NAME = 'unique_idx_member_approvals_on_pending_status'
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index :member_approvals, [:user_id, :member_namespace_id],
|
||||||
|
unique: true, where: "status = 0", name: NEW_INDEX_NAME
|
||||||
|
|
||||||
|
remove_concurrent_index_by_name :member_approvals, OLD_INDEX_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_concurrent_index :member_approvals, [:user_id, :member_namespace_id, :new_access_level, :member_role_id],
|
||||||
|
unique: true, where: "status = 0", name: OLD_INDEX_NAME
|
||||||
|
|
||||||
|
remove_concurrent_index_by_name :member_approvals, NEW_INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddProjectIdToStatusCheckResponses < Gitlab::Database::Migration[2.2]
|
||||||
|
milestone '17.1'
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :status_check_responses, :project_id, :bigint
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class IndexStatusCheckResponsesOnProjectId < Gitlab::Database::Migration[2.2]
|
||||||
|
milestone '17.1'
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
INDEX_NAME = 'index_status_check_responses_on_project_id'
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index :status_check_responses, :project_id, name: INDEX_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_concurrent_index_by_name :status_check_responses, INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddStatusCheckResponsesProjectIdFk < Gitlab::Database::Migration[2.2]
|
||||||
|
milestone '17.1'
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_foreign_key :status_check_responses, :projects, column: :project_id, on_delete: :cascade
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
with_lock_retries do
|
||||||
|
remove_foreign_key :status_check_responses, column: :project_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddStatusCheckResponsesProjectIdTrigger < Gitlab::Database::Migration[2.2]
|
||||||
|
milestone '17.1'
|
||||||
|
|
||||||
|
def up
|
||||||
|
install_sharding_key_assignment_trigger(
|
||||||
|
table: :status_check_responses,
|
||||||
|
sharding_key: :project_id,
|
||||||
|
parent_table: :merge_requests,
|
||||||
|
parent_sharding_key: :target_project_id,
|
||||||
|
foreign_key: :merge_request_id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_sharding_key_assignment_trigger(
|
||||||
|
table: :status_check_responses,
|
||||||
|
sharding_key: :project_id,
|
||||||
|
parent_table: :merge_requests,
|
||||||
|
parent_sharding_key: :target_project_id,
|
||||||
|
foreign_key: :merge_request_id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class QueueBackfillStatusCheckResponsesProjectId < Gitlab::Database::Migration[2.2]
|
||||||
|
milestone '17.1'
|
||||||
|
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||||
|
|
||||||
|
MIGRATION = "BackfillStatusCheckResponsesProjectId"
|
||||||
|
DELAY_INTERVAL = 2.minutes
|
||||||
|
BATCH_SIZE = 1000
|
||||||
|
SUB_BATCH_SIZE = 100
|
||||||
|
|
||||||
|
def up
|
||||||
|
queue_batched_background_migration(
|
||||||
|
MIGRATION,
|
||||||
|
:status_check_responses,
|
||||||
|
:id,
|
||||||
|
:project_id,
|
||||||
|
:merge_requests,
|
||||||
|
:target_project_id,
|
||||||
|
:merge_request_id,
|
||||||
|
job_interval: DELAY_INTERVAL,
|
||||||
|
batch_size: BATCH_SIZE,
|
||||||
|
sub_batch_size: SUB_BATCH_SIZE
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
delete_batched_background_migration(
|
||||||
|
MIGRATION,
|
||||||
|
:status_check_responses,
|
||||||
|
:id,
|
||||||
|
[
|
||||||
|
:project_id,
|
||||||
|
:merge_requests,
|
||||||
|
:target_project_id,
|
||||||
|
:merge_request_id
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
eaab626b81ea7df21bdbbcf3f0da019bce66d6bae83a2c18fcec732d22d52c7d
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
f2592bf6d74deffbef5626e33856f33af256a3a5f3074e8cfa31eb6409362f9b
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ec17602042e8e11da5f1faa7b58659fb455dbf1a2dfdd8cfae90fac5f7ee87f6
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
2c2d509c9019c35b59e2231f8c473fd3a0af60bcebf45aa4c0bfa10a590614e5
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
aea0e6c1b36b4ec32f8bbb22c1e2eab6a4f7a060f486382267fc16e355dd72c5
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
4844652782e0966eeb1ce935a21a06291f534623bf8c91f9c2f8c171bd9d1645
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
5f92f0daf25ba601f64916028a9bb980d37a28dc7b0c9c513d1e8bb8aad706b3
|
||||||
|
|
@ -733,6 +733,22 @@ RETURN NEW;
|
||||||
END
|
END
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
CREATE FUNCTION trigger_05ce163deddf() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF NEW."project_id" IS NULL THEN
|
||||||
|
SELECT "target_project_id"
|
||||||
|
INTO NEW."project_id"
|
||||||
|
FROM "merge_requests"
|
||||||
|
WHERE "merge_requests"."id" = NEW."merge_request_id";
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
CREATE FUNCTION trigger_0da002390fdc() RETURNS trigger
|
CREATE FUNCTION trigger_0da002390fdc() RETURNS trigger
|
||||||
LANGUAGE plpgsql
|
LANGUAGE plpgsql
|
||||||
AS $$
|
AS $$
|
||||||
|
|
@ -17221,7 +17237,8 @@ CREATE TABLE status_check_responses (
|
||||||
external_status_check_id bigint NOT NULL,
|
external_status_check_id bigint NOT NULL,
|
||||||
status smallint DEFAULT 0 NOT NULL,
|
status smallint DEFAULT 0 NOT NULL,
|
||||||
retried_at timestamp with time zone,
|
retried_at timestamp with time zone,
|
||||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
project_id bigint
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE SEQUENCE status_check_responses_id_seq
|
CREATE SEQUENCE status_check_responses_id_seq
|
||||||
|
|
@ -18301,7 +18318,8 @@ CREATE TABLE vulnerability_exports (
|
||||||
author_id bigint NOT NULL,
|
author_id bigint NOT NULL,
|
||||||
file_store integer,
|
file_store integer,
|
||||||
format smallint DEFAULT 0 NOT NULL,
|
format smallint DEFAULT 0 NOT NULL,
|
||||||
group_id integer
|
group_id integer,
|
||||||
|
organization_id bigint DEFAULT 1 NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE SEQUENCE vulnerability_exports_id_seq
|
CREATE SEQUENCE vulnerability_exports_id_seq
|
||||||
|
|
@ -28365,6 +28383,8 @@ CREATE INDEX index_status_check_responses_on_external_status_check_id ON status_
|
||||||
|
|
||||||
CREATE INDEX index_status_check_responses_on_merge_request_id ON status_check_responses USING btree (merge_request_id);
|
CREATE INDEX index_status_check_responses_on_merge_request_id ON status_check_responses USING btree (merge_request_id);
|
||||||
|
|
||||||
|
CREATE INDEX index_status_check_responses_on_project_id ON status_check_responses USING btree (project_id);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_status_page_published_incidents_on_issue_id ON status_page_published_incidents USING btree (issue_id);
|
CREATE UNIQUE INDEX index_status_page_published_incidents_on_issue_id ON status_page_published_incidents USING btree (issue_id);
|
||||||
|
|
||||||
CREATE INDEX index_status_page_settings_on_project_id ON status_page_settings USING btree (project_id);
|
CREATE INDEX index_status_page_settings_on_project_id ON status_page_settings USING btree (project_id);
|
||||||
|
|
@ -28713,6 +28733,8 @@ CREATE INDEX index_vulnerability_exports_on_file_store ON vulnerability_exports
|
||||||
|
|
||||||
CREATE INDEX index_vulnerability_exports_on_group_id_not_null ON vulnerability_exports USING btree (group_id) WHERE (group_id IS NOT NULL);
|
CREATE INDEX index_vulnerability_exports_on_group_id_not_null ON vulnerability_exports USING btree (group_id) WHERE (group_id IS NOT NULL);
|
||||||
|
|
||||||
|
CREATE INDEX index_vulnerability_exports_on_organization_id ON vulnerability_exports USING btree (organization_id);
|
||||||
|
|
||||||
CREATE INDEX index_vulnerability_exports_on_project_id_not_null ON vulnerability_exports USING btree (project_id) WHERE (project_id IS NOT NULL);
|
CREATE INDEX index_vulnerability_exports_on_project_id_not_null ON vulnerability_exports USING btree (project_id) WHERE (project_id IS NOT NULL);
|
||||||
|
|
||||||
CREATE INDEX index_vulnerability_external_issue_links_on_author_id ON vulnerability_external_issue_links USING btree (author_id);
|
CREATE INDEX index_vulnerability_external_issue_links_on_author_id ON vulnerability_external_issue_links USING btree (author_id);
|
||||||
|
|
@ -29147,6 +29169,8 @@ CREATE UNIQUE INDEX unique_external_audit_event_destination_namespace_id_and_nam
|
||||||
|
|
||||||
CREATE UNIQUE INDEX unique_google_cloud_logging_configurations_on_namespace_id ON audit_events_google_cloud_logging_configurations USING btree (namespace_id, google_project_id_name, log_id_name);
|
CREATE UNIQUE INDEX unique_google_cloud_logging_configurations_on_namespace_id ON audit_events_google_cloud_logging_configurations USING btree (namespace_id, google_project_id_name, log_id_name);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX unique_idx_member_approvals_on_pending_status ON member_approvals USING btree (user_id, member_namespace_id) WHERE (status = 0);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX unique_idx_namespaces_storage_limit_exclusions_on_namespace_id ON namespaces_storage_limit_exclusions USING btree (namespace_id);
|
CREATE UNIQUE INDEX unique_idx_namespaces_storage_limit_exclusions_on_namespace_id ON namespaces_storage_limit_exclusions USING btree (namespace_id);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX unique_import_source_users_source_identifier_and_import_source ON import_source_users USING btree (source_user_identifier, namespace_id, source_hostname, import_type);
|
CREATE UNIQUE INDEX unique_import_source_users_source_identifier_and_import_source ON import_source_users USING btree (source_user_identifier, namespace_id, source_hostname, import_type);
|
||||||
|
|
@ -29157,8 +29181,6 @@ CREATE UNIQUE INDEX unique_index_for_credit_card_validation_payment_method_xid O
|
||||||
|
|
||||||
CREATE UNIQUE INDEX unique_index_for_project_pages_unique_domain ON project_settings USING btree (pages_unique_domain) WHERE (pages_unique_domain IS NOT NULL);
|
CREATE UNIQUE INDEX unique_index_for_project_pages_unique_domain ON project_settings USING btree (pages_unique_domain) WHERE (pages_unique_domain IS NOT NULL);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX unique_index_member_approvals_on_pending_status ON member_approvals USING btree (user_id, member_namespace_id, new_access_level, member_role_id) WHERE (status = 0);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX unique_index_ml_model_metadata_name ON ml_model_metadata USING btree (model_id, name);
|
CREATE UNIQUE INDEX unique_index_ml_model_metadata_name ON ml_model_metadata USING btree (model_id, name);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX unique_index_ml_model_version_metadata_name ON ml_model_version_metadata USING btree (model_version_id, name);
|
CREATE UNIQUE INDEX unique_index_ml_model_version_metadata_name ON ml_model_version_metadata USING btree (model_version_id, name);
|
||||||
|
|
@ -30787,6 +30809,8 @@ CREATE TRIGGER tags_loose_fk_trigger AFTER DELETE ON tags REFERENCING OLD TABLE
|
||||||
|
|
||||||
CREATE TRIGGER trigger_01b3fc052119 BEFORE INSERT OR UPDATE ON approval_merge_request_rules FOR EACH ROW EXECUTE FUNCTION trigger_01b3fc052119();
|
CREATE TRIGGER trigger_01b3fc052119 BEFORE INSERT OR UPDATE ON approval_merge_request_rules FOR EACH ROW EXECUTE FUNCTION trigger_01b3fc052119();
|
||||||
|
|
||||||
|
CREATE TRIGGER trigger_05ce163deddf BEFORE INSERT OR UPDATE ON status_check_responses FOR EACH ROW EXECUTE FUNCTION trigger_05ce163deddf();
|
||||||
|
|
||||||
CREATE TRIGGER trigger_0da002390fdc BEFORE INSERT OR UPDATE ON operations_feature_flags_issues FOR EACH ROW EXECUTE FUNCTION trigger_0da002390fdc();
|
CREATE TRIGGER trigger_0da002390fdc BEFORE INSERT OR UPDATE ON operations_feature_flags_issues FOR EACH ROW EXECUTE FUNCTION trigger_0da002390fdc();
|
||||||
|
|
||||||
CREATE TRIGGER trigger_0e13f214e504 BEFORE INSERT OR UPDATE ON merge_request_assignment_events FOR EACH ROW EXECUTE FUNCTION trigger_0e13f214e504();
|
CREATE TRIGGER trigger_0e13f214e504 BEFORE INSERT OR UPDATE ON merge_request_assignment_events FOR EACH ROW EXECUTE FUNCTION trigger_0e13f214e504();
|
||||||
|
|
@ -31630,6 +31654,9 @@ ALTER TABLE ONLY merge_request_review_llm_summaries
|
||||||
ALTER TABLE ONLY audit_events_streaming_group_namespace_filters
|
ALTER TABLE ONLY audit_events_streaming_group_namespace_filters
|
||||||
ADD CONSTRAINT fk_8ed182d7da FOREIGN KEY (external_streaming_destination_id) REFERENCES audit_events_group_external_streaming_destinations(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_8ed182d7da FOREIGN KEY (external_streaming_destination_id) REFERENCES audit_events_group_external_streaming_destinations(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY vulnerability_exports
|
||||||
|
ADD CONSTRAINT fk_90e75ccdf8 FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
ALTER TABLE ONLY todos
|
ALTER TABLE ONLY todos
|
||||||
ADD CONSTRAINT fk_91d1f47b13 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_91d1f47b13 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
@ -31813,6 +31840,9 @@ ALTER TABLE ONLY issues
|
||||||
ALTER TABLE ONLY protected_tag_create_access_levels
|
ALTER TABLE ONLY protected_tag_create_access_levels
|
||||||
ADD CONSTRAINT fk_b4eb82fe3c FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_b4eb82fe3c FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY status_check_responses
|
||||||
|
ADD CONSTRAINT fk_b53bf31a72 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
ALTER TABLE ONLY compliance_framework_security_policies
|
ALTER TABLE ONLY compliance_framework_security_policies
|
||||||
ADD CONSTRAINT fk_b5df066d8f FOREIGN KEY (framework_id) REFERENCES compliance_management_frameworks(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_b5df066d8f FOREIGN KEY (framework_id) REFERENCES compliance_management_frameworks(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ choose one or the other; there is no particular benefit in combining them.
|
||||||
|
|
||||||
## Routing rules
|
## Routing rules
|
||||||
|
|
||||||
> - [Default routing rule value](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97908) added in GitLab 15.4.
|
> - [Default routing rule value](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97908) introduced in GitLab 15.4.
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
Mailer jobs cannot be routed by routing rules, and always go to the
|
Mailer jobs cannot be routed by routing rules, and always go to the
|
||||||
|
|
|
||||||
|
|
@ -16069,6 +16069,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="addonuserauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="addonuserauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="addonuserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="addonuserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="addonuserauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="addonuserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="addonuserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="addonuserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="addonuserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
| <a id="addonuserauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
| <a id="addonuserauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
||||||
|
|
@ -16165,6 +16166,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="addonuserreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="addonuserreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="addonuserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="addonuserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="addonuserreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="addonuserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="addonuserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="addonuserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="addonuserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="addonuserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="addonuserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -16877,6 +16879,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="autocompleteduserauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="autocompleteduserauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="autocompleteduserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="autocompleteduserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="autocompleteduserauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="autocompleteduserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="autocompleteduserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="autocompleteduserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="autocompleteduserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
| <a id="autocompleteduserauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
| <a id="autocompleteduserauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
||||||
|
|
@ -16985,6 +16988,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="autocompleteduserreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="autocompleteduserreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="autocompleteduserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="autocompleteduserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="autocompleteduserreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="autocompleteduserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="autocompleteduserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="autocompleteduserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="autocompleteduserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="autocompleteduserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="autocompleteduserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -19073,6 +19077,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="currentuserauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="currentuserauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="currentuserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="currentuserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="currentuserauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="currentuserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="currentuserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="currentuserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="currentuserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
| <a id="currentuserauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
| <a id="currentuserauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
||||||
|
|
@ -19169,6 +19174,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="currentuserreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="currentuserreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="currentuserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="currentuserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="currentuserreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="currentuserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="currentuserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="currentuserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="currentuserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="currentuserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="currentuserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -22374,6 +22380,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="groupmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="groupmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="groupmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="groupmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="groupmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="groupmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="groupmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="groupmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="groupmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="groupmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="groupmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -24387,6 +24394,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="mergerequestassigneeauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="mergerequestassigneeauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="mergerequestassigneeauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="mergerequestassigneeauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="mergerequestassigneeauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="mergerequestassigneeauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="mergerequestassigneeauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="mergerequestassigneeauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="mergerequestassigneeauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
| <a id="mergerequestassigneeauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
| <a id="mergerequestassigneeauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
||||||
|
|
@ -24483,6 +24491,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="mergerequestassigneereviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="mergerequestassigneereviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="mergerequestassigneereviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="mergerequestassigneereviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="mergerequestassigneereviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="mergerequestassigneereviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="mergerequestassigneereviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="mergerequestassigneereviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="mergerequestassigneereviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="mergerequestassigneereviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="mergerequestassigneereviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -24727,6 +24736,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="mergerequestauthorauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="mergerequestauthorauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="mergerequestauthorauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="mergerequestauthorauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="mergerequestauthorauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="mergerequestauthorauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="mergerequestauthorauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="mergerequestauthorauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="mergerequestauthorauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
| <a id="mergerequestauthorauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
| <a id="mergerequestauthorauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
||||||
|
|
@ -24823,6 +24833,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="mergerequestauthorreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="mergerequestauthorreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="mergerequestauthorreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="mergerequestauthorreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="mergerequestauthorreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="mergerequestauthorreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="mergerequestauthorreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="mergerequestauthorreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="mergerequestauthorreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="mergerequestauthorreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="mergerequestauthorreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -25114,6 +25125,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="mergerequestparticipantauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="mergerequestparticipantauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="mergerequestparticipantauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="mergerequestparticipantauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="mergerequestparticipantauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="mergerequestparticipantauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="mergerequestparticipantauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="mergerequestparticipantauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="mergerequestparticipantauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
| <a id="mergerequestparticipantauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
| <a id="mergerequestparticipantauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
||||||
|
|
@ -25210,6 +25222,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="mergerequestparticipantreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="mergerequestparticipantreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="mergerequestparticipantreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="mergerequestparticipantreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="mergerequestparticipantreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="mergerequestparticipantreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="mergerequestparticipantreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="mergerequestparticipantreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="mergerequestparticipantreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="mergerequestparticipantreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="mergerequestparticipantreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -25490,6 +25503,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="mergerequestreviewerauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="mergerequestreviewerauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="mergerequestreviewerauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="mergerequestreviewerauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="mergerequestreviewerauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="mergerequestreviewerauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="mergerequestreviewerauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="mergerequestreviewerauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="mergerequestreviewerauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
| <a id="mergerequestreviewerauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
| <a id="mergerequestreviewerauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
||||||
|
|
@ -25586,6 +25600,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="mergerequestreviewerreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="mergerequestreviewerreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="mergerequestreviewerreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="mergerequestreviewerreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="mergerequestreviewerreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="mergerequestreviewerreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="mergerequestreviewerreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="mergerequestreviewerreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="mergerequestreviewerreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="mergerequestreviewerreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="mergerequestreviewerreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -28514,6 +28529,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="projectmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="projectmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="projectmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="projectmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="projectmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="projectmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="projectmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="projectmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="projectmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="projectmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="projectmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -31342,6 +31358,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="usercoreauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="usercoreauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="usercoreauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="usercoreauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="usercoreauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="usercoreauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="usercoreauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="usercoreauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="usercoreauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
| <a id="usercoreauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
| <a id="usercoreauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
||||||
|
|
@ -31438,6 +31455,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="usercorereviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="usercorereviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="usercorereviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="usercorereviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="usercorereviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="usercorereviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="usercorereviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="usercorereviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="usercorereviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="usercorereviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="usercorereviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
@ -37752,6 +37770,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="userauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="userauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="userauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="userauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="userauthoredmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="userauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="userauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="userauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="userauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
| <a id="userauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
| <a id="userauthoredmergerequestsdeployedafter"></a>`deployedAfter` | [`Time`](#time) | Merge requests deployed after the timestamp. |
|
||||||
|
|
@ -37848,6 +37867,7 @@ four standard [pagination arguments](#pagination-arguments):
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="userreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
| <a id="userreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
|
||||||
| <a id="userreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
| <a id="userreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
|
||||||
|
| <a id="userreviewrequestedmergerequestsassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee presence. Incompatible with assigneeUsernames and assigneeUsername. |
|
||||||
| <a id="userreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
| <a id="userreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
|
||||||
| <a id="userreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
| <a id="userreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after the timestamp. |
|
||||||
| <a id="userreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
| <a id="userreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before the timestamp. |
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,8 @@ For example:
|
||||||
|
|
||||||
1. **GraphQL and other ambiguous endpoints.**
|
1. **GraphQL and other ambiguous endpoints.**
|
||||||
|
|
||||||
Most endpoints have a unique sharding key: the Organization, which directly or indirectly (via a Group or Project) can be used to classify endpoints.
|
Most endpoints have a unique classification key: the Organization, which directly or indirectly (via a Group or Project) can be used to classify endpoints.
|
||||||
Some endpoints are ambiguous in their usage (they don't encode the sharding key), or the sharding key is stored deep in the payload.
|
Some endpoints are ambiguous in their usage (they don't encode the classification key), or the classification key is stored deep in the payload.
|
||||||
In these cases, we need to decide how to handle endpoints like `/api/graphql`.
|
In these cases, we need to decide how to handle endpoints like `/api/graphql`.
|
||||||
|
|
||||||
1. **Small.**
|
1. **Small.**
|
||||||
|
|
@ -207,9 +207,9 @@ The Routing Service implements the following design guidelines:
|
||||||
- rules allows to match by any criteria: header, content of the header, or route path.
|
- rules allows to match by any criteria: header, content of the header, or route path.
|
||||||
1. Agnostic:
|
1. Agnostic:
|
||||||
- Routing service is not aware of high-level concepts like organizations.
|
- Routing service is not aware of high-level concepts like organizations.
|
||||||
- The classification is done per-specification provided in a rules, to find the sharding key.
|
- The classification is done per-specification provided in a rules, to find the classification key.
|
||||||
- The sharding key result is cached.
|
- The classification key result is cached.
|
||||||
- The single sharding key cached is used to handle many similar requests.
|
- The single classification key cached is used to handle many similar requests.
|
||||||
|
|
||||||
The following diagram shows how a user request routes through DNS to the Routing Service deployed
|
The following diagram shows how a user request routes through DNS to the Routing Service deployed
|
||||||
as Cloudflare Worker and the router chooses a cell to send the request to.
|
as Cloudflare Worker and the router chooses a cell to send the request to.
|
||||||
|
|
@ -241,29 +241,10 @@ graph TD;
|
||||||
|
|
||||||
Each Cell will publish a precompiled list of routing rules that will be consumed by the Routing Service:
|
Each Cell will publish a precompiled list of routing rules that will be consumed by the Routing Service:
|
||||||
|
|
||||||
- The routing rules describe how to decode the request, find the sharding key, and make the routing decision.
|
- The routing rules describe how to decode the request, find the classification key, and make the routing decision.
|
||||||
- The routing rules are compiled during the deployment of the Routing Service.
|
- The routing rules are static and defined ahead of time as part of HTTP Router deployment.
|
||||||
- The deployment process fetches latest version of the routing rules from each Cell
|
- The routing rules are defined as a JSON document describing in-order a sequence of operation.
|
||||||
that is part of Routing Service configuration.
|
- The routing rules might be compiled to application code to provide a way faster execution scheme.
|
||||||
- The compilation process merges the routing rules from all Cells.
|
|
||||||
- The conflicting rules prevent routing service from being compiled / started.
|
|
||||||
- Each routing rule entry has a unique identifier to ease the merge.
|
|
||||||
- The Routing Service would be re-deployed only if the list of rules was changed,
|
|
||||||
which shouldn't happen frequently, because we expect the majority of newly added endpoints
|
|
||||||
to already adhere to the prior route rules.
|
|
||||||
- The configuration describes from which Cells the routing rules need to be fetched during deploy.
|
|
||||||
- The published routing rules might make routing decision based on the secret. For example, if the session cookie
|
|
||||||
or authentication token has prefix `c100-` all requests are to be forwarded to the given Cell.
|
|
||||||
- The Cell does publish routing rules at `/api/v4/internal/cells/route_rules.json`.
|
|
||||||
- The rules published by Cell only include endpoints that the particular Cell can process.
|
|
||||||
- The Cell might request to perform dynamic classification based on sharding key, by configuring
|
|
||||||
routing rules to call `/api/v4/internal/cells/classify`.
|
|
||||||
- The routing rules should use `prefix` as a way to speed up classification. During the compilation phase
|
|
||||||
the routing service transforms all found prefixes into a decision tree to speed up any subsequent regex matches.
|
|
||||||
- Some of the prefixes need to be Cell independent, example Personal Access Tokens prefix need to be organization bound and not Cell bound.
|
|
||||||
We want the ability to move an organization from 1 cell to another without changing the Personal Access Token or any other token.
|
|
||||||
- The routing rules is ideally compiled into source code to avoid expensive parsing and evaluation of the rules
|
|
||||||
dynamically as part of deployment.
|
|
||||||
|
|
||||||
The routing rules JSON structure describes all matchers:
|
The routing rules JSON structure describes all matchers:
|
||||||
|
|
||||||
|
|
@ -271,74 +252,65 @@ The routing rules JSON structure describes all matchers:
|
||||||
{
|
{
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"id": "<unique-identifier>",
|
|
||||||
"cookies": {
|
"cookies": {
|
||||||
"<cookie_name>": {
|
"<cookie_name>": {
|
||||||
"prefix": "<match-given-prefix>",
|
|
||||||
"match_regex": "<regex_match>"
|
"match_regex": "<regex_match>"
|
||||||
},
|
},
|
||||||
"<cookie_name2>": {
|
"<cookie_name2>": {
|
||||||
"prefix": "<match-given-prefix>",
|
|
||||||
"match_regex": "<regex_match>"
|
"match_regex": "<regex_match>"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"headers": {
|
"headers": {
|
||||||
"<header_name>": {
|
"<header_name>": {
|
||||||
"prefix": "<match-given-prefix>",
|
|
||||||
"match_regex": "<regex_match>"
|
"match_regex": "<regex_match>"
|
||||||
},
|
},
|
||||||
"<header_name2>": {
|
"<header_name2>": {
|
||||||
"prefix": "<match-given-prefix>",
|
|
||||||
"match_regex": "<regex_match>"
|
"match_regex": "<regex_match>"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"prefix": "<match-given-prefix>",
|
|
||||||
"match_regex": "<regex_match>"
|
"match_regex": "<regex_match>"
|
||||||
},
|
},
|
||||||
"method": ["<list_of_accepted_methods>"],
|
"method": ["<list_of_accepted_methods>"],
|
||||||
|
|
||||||
// If many rules are matched, define which one wins
|
|
||||||
"priority": 1000,
|
|
||||||
|
|
||||||
// Accept request and proxy to the Cell in question
|
|
||||||
"action": "proxy",
|
|
||||||
|
|
||||||
// Classify request based on regex matching groups
|
|
||||||
"action": "classify",
|
"action": "classify",
|
||||||
"classify": {
|
"classify": {
|
||||||
"keys": ["list_of_regex_match_capture_groups"]
|
"type": "session_prefix|project_path|...",
|
||||||
|
"value": "string_build_from_regex_matchers"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Example of the routing rules published by the Cell 100 that makes routing decision based session cookie, and secret.
|
Example of the routing rules that makes routing decision based session cookie, and secret:
|
||||||
The high priority is assigned since the routing rules is secret-based, and should take precedence before all other matchers:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"id": "t4mkd5ndsk58si6uwwz7rdavil9m2hpq",
|
|
||||||
"cookies": {
|
"cookies": {
|
||||||
"_gitlab_session": {
|
"_gitlab_session": {
|
||||||
"prefix": "c100-" // accept `_gitlab_session` that are prefixed with `c100-`
|
"match_regex": "^(?<cell_name>cell.*:)" // accept `_gitlab_session` that are prefixed with `cell1:`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"action": "proxy",
|
"action": "classify",
|
||||||
"priority": 1000
|
"classify": {
|
||||||
|
"type": "session_prefix",
|
||||||
|
"value": "${cell_name}"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "jcshae4d4dtykt8byd6zw1ecccl5dkts",
|
|
||||||
"headers": {
|
"headers": {
|
||||||
"GITLAB_TOKEN": {
|
"GITLAB_TOKEN": {
|
||||||
"prefix": "C100_" // accept `GITLAB_TOKEN` that are prefixed with `C100_`
|
"match_regex": "^(?<cell_name>cell.*:)" // accept `_gitlab_session` that are prefixed with `cell1:`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"action": "proxy",
|
"action": "classify",
|
||||||
"priority": 1000
|
"classify": {
|
||||||
|
"type": "token_prefix",
|
||||||
|
"value": "${cell_name}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -350,14 +322,13 @@ Example of the routing rules published by all Cells that makes routing decision
|
||||||
{
|
{
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"id": "c9scvaiwj51a75kzoh917uwtnw8z4ebl",
|
|
||||||
"path": {
|
"path": {
|
||||||
"prefix": "/api/v4/projects/", // speed-up rule matching
|
|
||||||
"match_regex": "^/api/v4/projects/(?<project_id_or_path_encoded>[^/]+)(/.*)?$"
|
"match_regex": "^/api/v4/projects/(?<project_id_or_path_encoded>[^/]+)(/.*)?$"
|
||||||
},
|
},
|
||||||
"action": "classify",
|
"action": "classify",
|
||||||
"classify": {
|
"classify": {
|
||||||
"keys": ["project_id_or_path_encoded"]
|
"type": "project_id_or_path",
|
||||||
|
"value": "${project_id_or_path_encoded}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -366,21 +337,16 @@ Example of the routing rules published by all Cells that makes routing decision
|
||||||
|
|
||||||
### Classification
|
### Classification
|
||||||
|
|
||||||
Each Cell does implement classification endpoint:
|
The classification is implemented by [the Classify Service of the Topology Service](topology_service.md#classify-service).
|
||||||
|
|
||||||
- The classification endpoint is at `/api/v4/internal/cells/classify` (or gRPC endpoint).
|
- The classification endpoint uses REST (with mTLS) to secure access.
|
||||||
- The classification endpoint accepts a list of the sharding keys. Sharding keys are decoded from request,
|
- The classification endpoint returns only cell name to which information should be routed.
|
||||||
based on the routing rules provided by the Cell.
|
- The classification could return other equivalent classification keys to pollute cache for similar requests.
|
||||||
- The endpoint returns other equivalent sharding keys to pollute cache for similar requests.
|
|
||||||
This is to ensure that all similar requests can be handled quickly without having to classify each time.
|
This is to ensure that all similar requests can be handled quickly without having to classify each time.
|
||||||
- Routing Service tracks the health of Cells, and issues a `classify` request to Cells based on weights,
|
- The HTTP Router retries the `classify` call for a reasonable amount of time.
|
||||||
health of the Cell, or other defined criteria. Weights would indicate which Cell is preferred to perform the
|
- The classification for a given value is cached regardless of returned response (positive or negative).
|
||||||
classification of sharding keys.
|
|
||||||
- Routing Service retries the `classify` call for a reasonable amount of time.
|
|
||||||
The repetitive failure of Cell to `classify` is indicative of Cell being unhealthy.
|
|
||||||
- The `classify` result is cached regardless of returned `action` (proxy or reject).
|
|
||||||
The rejected classification is cached to prevent excessive amount of
|
The rejected classification is cached to prevent excessive amount of
|
||||||
requests for sharding keys that are not found.
|
requests for classification keys that are not found.
|
||||||
- The cached response is for time defined by `expiry` and `refresh`.
|
- The cached response is for time defined by `expiry` and `refresh`.
|
||||||
- The `expiry` defines when the item is removed from cache unless used.
|
- The `expiry` defines when the item is removed from cache unless used.
|
||||||
- The `refresh` defines when the item needs to be reclassified if used.
|
- The `refresh` defines when the item needs to be reclassified if used.
|
||||||
|
|
@ -392,65 +358,46 @@ For the above example:
|
||||||
1. It selects the above `rule` for this request, which requests `classify` for `project_id_or_path_encoded`.
|
1. It selects the above `rule` for this request, which requests `classify` for `project_id_or_path_encoded`.
|
||||||
1. It decodes `project_id_or_path_encoded` to be `1000`.
|
1. It decodes `project_id_or_path_encoded` to be `1000`.
|
||||||
1. Checks the cache if there's `project_id_or_path_encoded=1000` associated to any Cell.
|
1. Checks the cache if there's `project_id_or_path_encoded=1000` associated to any Cell.
|
||||||
1. Sends the request to `/api/v4/internal/cells/classify` if no Cells was found in cache.
|
1. Sends the request to `/api/v1/classify` (`type=project_id_or_path`, `value=1000`) if no Cells was found in cache.
|
||||||
1. Rails responds with the Cell holding the given project, and also all other equivalent sharding keys
|
1. Topology Service responds with the Cell holding the given project, and also all other equivalent classification keys
|
||||||
for the resource that should be put in the cache.
|
for the resource that should be put in the cache.
|
||||||
1. Routing Service caches for the duration specified in configuration, or response.
|
1. Routing Service caches for the duration specified in configuration, or response.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
# POST /api/v4/internal/cells/classify
|
# POST /api/v1/classify
|
||||||
## Request:
|
## Request:
|
||||||
{
|
{
|
||||||
"metadata": {
|
"type": "project_id_or_path",
|
||||||
"rule_id": "c9scvaiwj51a75kzoh917uwtnw8z4ebl",
|
"value": 1000
|
||||||
"headers": {
|
|
||||||
"all_request_headers": "value"
|
|
||||||
},
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/api/v4/projects/100/issues"
|
|
||||||
},
|
|
||||||
"keys": {
|
|
||||||
"project_id_or_path_encoded": 100
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## Response:
|
## Response:
|
||||||
{
|
{
|
||||||
"action": "proxy",
|
"action": "proxy",
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"name": "cell_1",
|
"address": "cell1.gitlab.com"
|
||||||
"url": "https://cell1.gitlab.com"
|
|
||||||
},
|
},
|
||||||
"ttl": "10 minutes",
|
"cache": {
|
||||||
"matched_keys": [ // list of all equivalent keys that should be put in the cache
|
"refresh": "10 minutes",
|
||||||
{ "project_id_or_path_encoded": 100 },
|
"expiry": "10 minutes"
|
||||||
{ "project_id_or_path_encoded": "gitlab-org%2Fgitlab" },
|
},
|
||||||
{ "project_full_path": "gitlab-org/gitlab" },
|
"other_classifications": [ // list of all equivalent keys that should be put in the cache
|
||||||
{ "namespace_full_path": "gitlab-org" },
|
{ "type": "session_prefix", "value": "cell1" },
|
||||||
{ "namespace_id": 10 },
|
{ "type": "project_full_path", "value": "gitlab-org/gitlab" },
|
||||||
{ "organization_full_path": "gitlab-inc" },
|
{ "type": "project_full_path", "value": "gitlab-org/gitlab" },
|
||||||
{ "organization_id": 50 },
|
{ "type": "namespace_full_path", "value": "gitlab-org" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The following code represents a negative response when a sharding key was not found:
|
The following code represents a negative response when a classification key was not found:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
# POST /api/v4/internal/cells/classify
|
# POST /api/v4/internal/cells/classify
|
||||||
## Request:
|
## Request:
|
||||||
{
|
{
|
||||||
"metadata": {
|
"type": "project_id_or_path",
|
||||||
"rule_id": "c9scvaiwj51a75kzoh917uwtnw8z4ebl",
|
"value": 1000
|
||||||
"headers": {
|
|
||||||
"all_request_headers": "value"
|
|
||||||
},
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/api/v4/projects/100/issues"
|
|
||||||
},
|
|
||||||
"keys": {
|
|
||||||
"project_id_or_path_encoded": 100
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## Response:
|
## Response:
|
||||||
|
|
@ -462,49 +409,16 @@ The following code represents a negative response when a sharding key was not fo
|
||||||
"cache": {
|
"cache": {
|
||||||
"refresh": "10 minutes",
|
"refresh": "10 minutes",
|
||||||
"expiry": "10 minutes"
|
"expiry": "10 minutes"
|
||||||
},
|
}
|
||||||
"matched_keys": [ // list of all equivalent keys that should be put in the cache
|
|
||||||
{ "project_id_or_path_encoded": 100 },
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
The Routing Service will use the configuration similar to this:
|
All configuration will be provided via environment variables:
|
||||||
|
|
||||||
```toml
|
- HTTP Router will only configure an address to Topology Service
|
||||||
[[cells]]
|
- The mTLS will be used when connecting to Topology Service to authentication / authorization.
|
||||||
name=cell_1
|
|
||||||
url=https://cell1.gitlab.com
|
|
||||||
key=ABC123
|
|
||||||
classify_weight=100
|
|
||||||
|
|
||||||
[[cells]]
|
|
||||||
name=cell_2
|
|
||||||
url=https://cell2.gitlab.com
|
|
||||||
key=CDE123
|
|
||||||
classify_weight=1
|
|
||||||
|
|
||||||
[cache.memory.classify]
|
|
||||||
refresh_time=10 minutes
|
|
||||||
expiry_time=1 hour
|
|
||||||
|
|
||||||
[cache.external.classify]
|
|
||||||
refresh_time=30 minutes
|
|
||||||
expiry_time=6 hour
|
|
||||||
```
|
|
||||||
|
|
||||||
We assume that this is acceptable to provide a static list of Cells, because:
|
|
||||||
|
|
||||||
1. Static: Cells provisioned are unlikely to be dynamically provisioned and decommissioned.
|
|
||||||
1. Good enough: We can manage such list even up to 100 Cells.
|
|
||||||
1. Simple: We don't have to implement robust service discovery in the service,
|
|
||||||
and we have guarantee that this list is always exhaustive.
|
|
||||||
|
|
||||||
The configuration describes all Cells, URLs, zero-trust keys, and weights,
|
|
||||||
and how long requests should be cached. The `classify_weight` defines how often
|
|
||||||
the Cell should receive classification requests versus other Cells.
|
|
||||||
|
|
||||||
### Deployment
|
### Deployment
|
||||||
|
|
||||||
|
|
@ -544,10 +458,10 @@ There are several phases to fully deploy the HTTP Routing service to GitLab.com.
|
||||||
1. `gitlab-org` is a top-level namespace and lives in `Cell US0` in the `GitLab.com Public` organization.
|
1. `gitlab-org` is a top-level namespace and lives in `Cell US0` in the `GitLab.com Public` organization.
|
||||||
1. `my-company` is a top-level namespace and lives in `Cell EU0` in the `my-organization` organization.
|
1. `my-company` is a top-level namespace and lives in `Cell EU0` in the `my-organization` organization.
|
||||||
|
|
||||||
### Router configured to perform static routing
|
### Router configured to perform the following routing
|
||||||
|
|
||||||
1. The Cell US0 supports all other public-facing projects.
|
1. The Cell US0 supports all other public-facing projects.
|
||||||
1. The Cells is configured to generate all secrets and session cookies with a prefix like `eu0_` for Cell EU0.
|
1. The Cell EU0 configured to generate all secrets and session cookies with a prefix like `cell_eu0_`.
|
||||||
1. The Personal Access Token is scoped to Organization, and because the Organization is part only of a single Cell,
|
1. The Personal Access Token is scoped to Organization, and because the Organization is part only of a single Cell,
|
||||||
the PATs generated are prefixed with Cell identifier.
|
the PATs generated are prefixed with Cell identifier.
|
||||||
1. The Session Cookie encodes Organization in-use, and because the Organization is part only of a single Cell,
|
1. The Session Cookie encodes Organization in-use, and because the Organization is part only of a single Cell,
|
||||||
|
|
@ -555,53 +469,40 @@ There are several phases to fully deploy the HTTP Routing service to GitLab.com.
|
||||||
1. The Cell EU0 allows only private organizations, groups, and projects.
|
1. The Cell EU0 allows only private organizations, groups, and projects.
|
||||||
1. The Cell US0 is a target Cell for all requests unless explicitly prefixed.
|
1. The Cell US0 is a target Cell for all requests unless explicitly prefixed.
|
||||||
|
|
||||||
Cell US0:
|
Router rules:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"id": "tjh147se67wadjzum7onwqiad2b75uft",
|
|
||||||
"path": {
|
|
||||||
"prefix": "/"
|
|
||||||
},
|
|
||||||
"action": "proxy",
|
|
||||||
"priority": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Cell EU0:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"id": "t4mkd5ndsk58si6uwwz7rdavil9m2hpq",
|
|
||||||
"cookies": {
|
"cookies": {
|
||||||
"_gitlab_session": {
|
"_gitlab_session": {
|
||||||
"prefix": "eu0_"
|
"regex_match": "^(?<cell_name>cell.*:)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"path": {
|
"action": "classify",
|
||||||
"prefix": "/"
|
"classify": {
|
||||||
},
|
"type": "session_prefix",
|
||||||
"action": "proxy",
|
"value": "${cell_name}"
|
||||||
"priority": 1000
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "jcshae4d4dtykt8byd6zw1ecccl5dkts",
|
|
||||||
"headers": {
|
"headers": {
|
||||||
"GITLAB_TOKEN": {
|
"GITLAB_TOKEN": {
|
||||||
"prefix": "eu0_"
|
"regex_match": "^(?<cell_name>cell.*-)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"path": {
|
"action": "classify",
|
||||||
"prefix": "/"
|
"classify": {
|
||||||
},
|
"type": "token_prefix",
|
||||||
"action": "proxy",
|
"value": "${cell_name}"
|
||||||
"priority": 1000
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "classify",
|
||||||
|
"classify": {
|
||||||
|
"type": "first_cell",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -609,17 +510,24 @@ Cell EU0:
|
||||||
|
|
||||||
#### Goes to `/my-company/my-project` while logged in into Cell EU0
|
#### Goes to `/my-company/my-project` while logged in into Cell EU0
|
||||||
|
|
||||||
1. Because user switched the Organization to `my-company`, its session cookie is prefixed with `eu0_`.
|
1. Because user switched the Organization to `my-company`, its session cookie is prefixed with `cell_eu0_`.
|
||||||
1. User sends request `/my-company/my-project`, and because the cookie is prefixed with `eu0_` it is directed to Cell EU0.
|
1. User sends request `/my-company/my-project`, and because the cookie is prefixed with `cell_eu0_` it is directed to Cell EU0.
|
||||||
1. `Cell EU0` returns the correct response.
|
1. `Cell EU0` returns the correct response.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant user as User
|
participant user as User
|
||||||
participant router as Router
|
participant router as Router
|
||||||
|
participant cache as Cache
|
||||||
|
participant ts as Topology Service
|
||||||
participant cell_eu0 as Cell EU0
|
participant cell_eu0 as Cell EU0
|
||||||
participant cell_eu1 as Cell EU1
|
participant cell_eu1 as Cell EU1
|
||||||
user->>router: GET /my-company/my-project<br/>_gitlab_session=eu0_uwwz7rdavil9
|
user->>router: GET /my-company/my-project<br/>_gitlab_session=cell_eu0_uwwz7rdavil9
|
||||||
|
router->>+cache: GetClassify(type=session_prefix, value=cell_eu0)
|
||||||
|
cache->>-router: NotFound
|
||||||
|
router->>+ts: Classify(type=session_prefix, value=cell_eu0)
|
||||||
|
ts->>-router: Proxy(address="cell-eu0.gitlab.com")
|
||||||
|
router->>cache: Cache(type=session_prefix, value=cell_eu0) = Proxy(address="cell-eu0.gitlab.com"))
|
||||||
router->>cell_eu0: GET /my-company/my-project
|
router->>cell_eu0: GET /my-company/my-project
|
||||||
cell_eu0->>user: <h1>My Project...
|
cell_eu0->>user: <h1>My Project...
|
||||||
```
|
```
|
||||||
|
|
@ -628,18 +536,24 @@ sequenceDiagram
|
||||||
|
|
||||||
1. User visits `/my-company/my-project`, and because it does not have session cookie, the request is forwarded to `Cell US0`.
|
1. User visits `/my-company/my-project`, and because it does not have session cookie, the request is forwarded to `Cell US0`.
|
||||||
1. User signs in.
|
1. User signs in.
|
||||||
1. GitLab sees that user default organization is `my-company`, so it assigns session cookie with `eu0_` to indicate that
|
1. GitLab sees that user default organization is `my-company`, so it assigns session cookie with `cell_eu0_` to indicate that
|
||||||
user is meant to interact with `my-company`.
|
user is meant to interact with `my-company`.
|
||||||
1. User sends request to `/my-company/my-project` again, now with the session cookie that proxies to `Cell EU0`.
|
1. User sends request to `/my-company/my-project` again, now with the session cookie that proxies to `Cell EU0`.
|
||||||
1. `Cell EU0` returns the correct response.
|
1. `Cell EU0` returns the correct response.
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
The `cache` is intentionally skipped here to reduce diagram complexity.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant user as User
|
participant user as User
|
||||||
participant router as Router
|
participant router as Router
|
||||||
|
participant ts as Topology Service
|
||||||
participant cell_us0 as Cell US0
|
participant cell_us0 as Cell US0
|
||||||
participant cell_eu0 as Cell EU0
|
participant cell_eu0 as Cell EU0
|
||||||
user->>router: GET /my-company/my-project
|
user->>router: GET /my-company/my-project
|
||||||
|
router->>ts: Classify(type=first_cell)
|
||||||
|
ts->>router: Proxy(address="cell-us0.gitlab.com")
|
||||||
router->>cell_us0: GET /my-company/my-project
|
router->>cell_us0: GET /my-company/my-project
|
||||||
cell_us0->>user: HTTP 302 /users/sign_in?redirect=/my-company/my-project
|
cell_us0->>user: HTTP 302 /users/sign_in?redirect=/my-company/my-project
|
||||||
user->>router: GET /users/sign_in?redirect=/my-company/my-project
|
user->>router: GET /users/sign_in?redirect=/my-company/my-project
|
||||||
|
|
@ -647,157 +561,39 @@ sequenceDiagram
|
||||||
cell_us0-->>user: <h1>Sign in...
|
cell_us0-->>user: <h1>Sign in...
|
||||||
user->>router: POST /users/sign_in?redirect=/my-company/my-project
|
user->>router: POST /users/sign_in?redirect=/my-company/my-project
|
||||||
router->>cell_us0: POST /users/sign_in?redirect=/my-company/my-project
|
router->>cell_us0: POST /users/sign_in?redirect=/my-company/my-project
|
||||||
cell_us0->>user: HTTP 302 /my-company/my-project<br/>_gitlab_session=eu0_uwwz7rdavil9
|
cell_us0->>user: HTTP 302 /my-company/my-project<br/>_gitlab_session=cell_eu0_uwwz7rdavil9
|
||||||
user->>router: GET /my-company/my-project<br/>_gitlab_session=eu0_uwwz7rdavil9
|
user->>router: GET /my-company/my-project<br/>_gitlab_session=cell_eu0_uwwz7rdavil9
|
||||||
router->>cell_eu0: GET /my-company/my-project<br/>_gitlab_session=eu0_uwwz7rdavil9
|
router->>ts: Classify(type=session_prefix, value=cell_eu0)
|
||||||
|
ts->>router: Proxy(address="cell-eu0.gitlab.com")
|
||||||
|
router->>cell_eu0: GET /my-company/my-project<br/>_gitlab_session=cell_eu0_uwwz7rdavil9
|
||||||
cell_eu0->>user: <h1>My Project...
|
cell_eu0->>user: <h1>My Project...
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Goes to `/gitlab-org/gitlab` after last step
|
#### Goes to `/gitlab-org/gitlab` after last step
|
||||||
|
|
||||||
User visits `/my-company/my-project`, and because it does not have a session cookie, the request is forwarded to `Cell US0`.
|
User visits `/gitlab-org/gitlab`, and because it does have a session cookie, the request is forwarded to `Cell EU0`.
|
||||||
|
There is no need to ask Topology Service, since the session cookie is cached.
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant user as User
|
|
||||||
participant router as Router
|
|
||||||
participant cell_eu0 as Cell EU0
|
|
||||||
participant cell_us0 as Cell US0
|
|
||||||
user->>router: GET /gitlab-org/gitlab<br/>_gitlab_session=eu0_uwwz7rdavil9
|
|
||||||
router->>cell_eu0: GET /gitlab-org/gitlab
|
|
||||||
cell_eu0->>user: HTTP 404
|
|
||||||
```
|
|
||||||
|
|
||||||
### Router configured to perform dynamic routing based on classification
|
|
||||||
|
|
||||||
The Cells publish route rules that allows to classify the requests.
|
|
||||||
|
|
||||||
Cell US0 and EU0:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"id": "tjh147se67wadjzum7onwqiad2b75uft",
|
|
||||||
"path": {
|
|
||||||
"prefix": "/",
|
|
||||||
"regex": "^/(?top_level_group)[^/]+(/.*)?$",
|
|
||||||
},
|
|
||||||
"action": "classify",
|
|
||||||
"classify": {
|
|
||||||
"keys": ["top_level_group"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "jcshae4d4dtykt8byd6zw1ecccl5dkts",
|
|
||||||
"path": {
|
|
||||||
"prefix": "/"
|
|
||||||
},
|
|
||||||
"action": "proxy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Goes to `/my-company/my-project` while logged in into Cell EU0
|
|
||||||
|
|
||||||
1. The `/my-company/my-project/` is visited.
|
|
||||||
1. Router decodes sharding key `top_level_group=my-company`.
|
|
||||||
1. Router checks if this sharding key is cached.
|
|
||||||
1. Because it is not, the classification request is sent to a random Cell to `/classify`.
|
|
||||||
1. The response of classify is cached.
|
|
||||||
1. The request is then proxied to Cell returned by classification.
|
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant user as User
|
participant user as User
|
||||||
participant router as Router
|
participant router as Router
|
||||||
participant cache as Cache
|
participant cache as Cache
|
||||||
participant cell_us0 as Cell US0
|
participant ts as Topology Service
|
||||||
participant cell_eu0 as Cell EU0
|
participant cell_eu0 as Cell EU0
|
||||||
user->>router: GET /my-company/my-project
|
participant cell_eu1 as Cell EU1
|
||||||
router->>cache: CACHE_GET: top_level_group=my-company
|
user->>router: GET /my-company/my-project<br/>_gitlab_session=cell_eu0_uwwz7rdavil9
|
||||||
cache->>router: CACHE_NOT_FOUND
|
router->>+cache: GetClassify(type=session_prefix, value=cell_eu0)
|
||||||
router->>cell_us0: POST /api/v4/internal/cells/classify<br/>top_level_group=my-company
|
cache->>-router: Proxy(address="cell-eu0.gitlab.com"))
|
||||||
cell_us0->>router: CLASSIFY: top_level_group=my-company, cell=cell_eu0
|
|
||||||
router->>cache: CACHE_SET: top_level_group=my-company, cell=cell_eu0
|
|
||||||
router->>cell_eu0: GET /my-company/my-project
|
router->>cell_eu0: GET /my-company/my-project
|
||||||
cell_eu0->>user: <h1>My Project...
|
cell_eu0->>user: <h1>My Project...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Goes to `/my-company/my-project` while not logged in
|
|
||||||
|
|
||||||
1. The `/my-company/my-project/` is visited.
|
|
||||||
1. Router decodes sharding key `top_level_group=my-company`.
|
|
||||||
1. Router checks if this sharding key is cached.
|
|
||||||
1. Because it is not, the classification request is sent to a random Cell to `/classify`.
|
|
||||||
1. The response of `classify` is cached.
|
|
||||||
1. The request is then proxied to Cell returned by classification.
|
|
||||||
1. Because project is private, user is redirected to sign in.
|
|
||||||
1. The sign-in since is defined to be handled by all Cells, so it is proxied to a random Cell.
|
|
||||||
1. User visits the `/my-company/my-project/` again after logging in.
|
|
||||||
1. The `top_level_group=my-company` is proxied to the correct Cell.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant user as User
|
|
||||||
participant router as Router
|
|
||||||
participant cache as Cache
|
|
||||||
participant cell_us0 as Cell US0
|
|
||||||
participant cell_eu0 as Cell EU0
|
|
||||||
user->>router: GET /my-company/my-project
|
|
||||||
router->>cache: CACHE_GET: top_level_group=my-company
|
|
||||||
cache->>router: CACHE_NOT_FOUND
|
|
||||||
router->>cell_us0: POST /api/v4/internal/cells/classify<br/>top_level_group=my-company
|
|
||||||
cell_us0->>router: CLASSIFY: top_level_group=my-company, cell=cell_eu0
|
|
||||||
router->>cache: CACHE_SET: top_level_group=my-company, cell=cell_eu0
|
|
||||||
router->>cell_eu0: GET /my-company/my-project
|
|
||||||
cell_eu0->>user: HTTP 302 /users/sign_in?redirect=/my-company/my-project
|
|
||||||
user->>router: GET /users/sign_in?redirect=/my-company/my-project
|
|
||||||
router->>cell_us0: GET /users/sign_in?redirect=/my-company/my-project
|
|
||||||
cell_us0-->>user: <h1>Sign in...
|
|
||||||
user->>router: POST /users/sign_in?redirect=/my-company/my-project
|
|
||||||
router->>cell_eu0: POST /users/sign_in?redirect=/my-company/my-project
|
|
||||||
cell_eu0->>user: HTTP 302 /my-company/my-project
|
|
||||||
user->>router: GET /my-company/my-project
|
|
||||||
router->>cache: CACHE_GET: top_level_group=my-company
|
|
||||||
cache->>router: CACHE_FOUND: cell=cell_eu0
|
|
||||||
router->>cell_eu0: GET /my-company/my-project
|
|
||||||
cell_eu0->>user: <h1>My Project...
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Goes to `/gitlab-org/gitlab` after last step
|
|
||||||
|
|
||||||
1. Because the `/gitlab-org` is not found in cache, it will be classified and then directed to correct Cell.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant user as User
|
|
||||||
participant router as Router
|
|
||||||
participant cache as Cache
|
|
||||||
participant cell_us0 as Cell US0
|
|
||||||
participant cell_eu0 as Cell EU0
|
|
||||||
user->>router: GET /gitlab-org/gitlab
|
|
||||||
router->>cache: CACHE_GET: top_level_group=gitlab-org
|
|
||||||
cache->>router: CACHE_NOT_FOUND
|
|
||||||
router->>cell_us0: POST /api/v4/internal/cells/classify<br/>top_level_group=gitlab-org
|
|
||||||
cell_us0->>router: CLASSIFY: top_level_group=gitlab-org, cell=cell_us0
|
|
||||||
router->>cache: CACHE_SET: top_level_group=gitlab-org, cell=cell_us0
|
|
||||||
router->>cell_us0: GET /gitlab-org/gitlab
|
|
||||||
cell_us0->>user: <h1>My Project...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance and reliability considerations
|
### Performance and reliability considerations
|
||||||
|
|
||||||
- It is expected that each Cell can classify all sharding keys.
|
- It is expected that there will be penalty when learning new classification key. However,
|
||||||
- Alternatively the classification could be done by Cluster-wide Data Provider
|
|
||||||
if it would own all data required to classify.
|
|
||||||
- The published routing rules allow to define static criteria, allowing to make routing decision
|
|
||||||
only on a secret. As a result, the Routing Service doesn't add any latency
|
|
||||||
for request processing, and superior resiliency.
|
|
||||||
- It is expected that there will be penalty when learning new sharding key. However,
|
|
||||||
it is expected that multi-layer cache should provide a very high cache-hit-ratio,
|
it is expected that multi-layer cache should provide a very high cache-hit-ratio,
|
||||||
due to low cardinality of sharding key. The sharding key would effectively be mapped
|
due to low cardinality of classification key. The classification key would effectively be mapped
|
||||||
into resource (organization, group, or project), and there's a finite amount of those.
|
into resource (organization, group, or project), and there's a finite amount of those.
|
||||||
|
|
||||||
## Alternatives
|
## Alternatives
|
||||||
|
|
@ -809,7 +605,7 @@ describes an approach where Cell answers with `X-Gitlab-Cell-Redirect` to redire
|
||||||
|
|
||||||
- This is based on a need to buffer the whole request (headers + body) which is very memory intensive.
|
- This is based on a need to buffer the whole request (headers + body) which is very memory intensive.
|
||||||
- This proposal does not provide an easy way to handle mixed deployment of Cells, where Cells might be running different versions.
|
- This proposal does not provide an easy way to handle mixed deployment of Cells, where Cells might be running different versions.
|
||||||
- This proposal likely requires caching significantly more information, since it is based on requests, rather than on decoded sharding keys.
|
- This proposal likely requires caching significantly more information, since it is based on requests, rather than on decoded classification keys.
|
||||||
|
|
||||||
### Learn request
|
### Learn request
|
||||||
|
|
||||||
|
|
@ -819,7 +615,7 @@ is done in a single go in a form of pre-flight check `/api/v4/internal/cells/lea
|
||||||
|
|
||||||
- This makes the whole routes learning dynamic, and dependent on availability of the Cells.
|
- This makes the whole routes learning dynamic, and dependent on availability of the Cells.
|
||||||
- This proposal does not provide an easy way to handle mixed deployment of Cells, where Cells might be running different versions.
|
- This proposal does not provide an easy way to handle mixed deployment of Cells, where Cells might be running different versions.
|
||||||
- This proposal likely requires caching significantly more information, since it is based on requests, rather than on decoded sharding keys.
|
- This proposal likely requires caching significantly more information, since it is based on requests, rather than on decoded classification keys.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ From a development and infrastructure perspective we want to achieve the followi
|
||||||
1. All Cells are accessible under a single domain.
|
1. All Cells are accessible under a single domain.
|
||||||
1. Cells are mostly independent with minimal data sharing. All stateful data is segregated, and minimal data sharing is needed initially. This includes any database and cloud storage buckets.
|
1. Cells are mostly independent with minimal data sharing. All stateful data is segregated, and minimal data sharing is needed initially. This includes any database and cloud storage buckets.
|
||||||
1. Cells need to be able to run independently with different versions.
|
1. Cells need to be able to run independently with different versions.
|
||||||
1. An architecture that allows for eventual cluster-wide data sharing.
|
1. A cluster-wide service is provided to synchronize state between all Cells.
|
||||||
1. A routing solution that is robust, but simple.
|
1. A routing solution that is robust, but simple.
|
||||||
1. All identifiers (primary keys, user, group, and project names) are unique across the cluster, so that we can perform logical re-balancing at a later time. This includes all database tables, except ones using schemas `gitlab_internal`, or `gitlab_shared`.
|
1. All identifiers (primary keys, user, group, and project names) are unique across the cluster, so that we can perform logical re-balancing at a later time. This includes all database tables, except ones using schemas `gitlab_internal`, or `gitlab_shared`.
|
||||||
1. Because all users and groups are unique across the cluster, the same user can access other Organizations and groups at GitLab.com in [Cells 2.0](cells-2.0.md).
|
1. Because all users and groups are unique across the cluster, the same user can access other Organizations and groups at GitLab.com in [Cells 2.0](cells-2.0.md).
|
||||||
|
|
@ -55,9 +55,8 @@ The following statements describe a high-level proposal to achieve a Cells 1.0:
|
||||||
|
|
||||||
1. Terms used:
|
1. Terms used:
|
||||||
|
|
||||||
1. Primary Cell: The current GitLab.com deployment. A special purpose Cell that serves
|
1. Cell: A single isolated deployment of GitLab that connects to the Topology Service.
|
||||||
as a cluster-wide service in this architecture.
|
1. Topology Service: The central service that is the authoritative entity in a cluster. Provides uniqueness and routing information.
|
||||||
1. Secondary Cells: A Cell that connects to the Primary Cell to ensure cluster-wide uniqueness.
|
|
||||||
|
|
||||||
1. Organization properties:
|
1. Organization properties:
|
||||||
|
|
||||||
|
|
@ -80,30 +79,30 @@ The following statements describe a low-level development proposal to achieve th
|
||||||
|
|
||||||
1. Each secret token (personal access token, build token, runner token, etc.) generated by the application includes a unique identifier indicating the Cell, for example `us0`. The identifier should try to obfuscate information about the Cell.
|
1. Each secret token (personal access token, build token, runner token, etc.) generated by the application includes a unique identifier indicating the Cell, for example `us0`. The identifier should try to obfuscate information about the Cell.
|
||||||
1. The session cookie sent to the client is prefixed with a unique identifier indicating the Cell, for example `us0`.
|
1. The session cookie sent to the client is prefixed with a unique identifier indicating the Cell, for example `us0`.
|
||||||
1. The application configuration includes a Cell secret prefix, and information about the Primary Cell.
|
1. The application configuration includes a Cell secret prefix, and the location of the Topology Service.
|
||||||
1. User always logs into the Cell on which the user was created.
|
1. User always logs into the Cell on which the user was created.
|
||||||
|
|
||||||
1. Database properties:
|
1. Database properties:
|
||||||
|
|
||||||
1. Each primary key in the database is unique across the cluster. We use database sequences that are allocated from the Primary Cell.
|
1. Each primary key in the database is unique across the cluster. We use database sequences that are allocated by the Topology Service.
|
||||||
1. We require each table to be classified: to be cluster-wide or Cell-local.
|
1. We require each table to be classified: to be cluster-wide or Cell-local.
|
||||||
1. We follow a model of eventual consistency:
|
1. We follow a model of eventual consistency:
|
||||||
1. All cluster-wide tables are stored in a Cell-local database.
|
1. All cluster-wide tables are stored in a Cell-local database.
|
||||||
1. All cluster-wide tables retain unique constraints across the whole cluster.
|
1. All cluster-wide tables retain unique constraints across the whole cluster.
|
||||||
1. Locally stored cluster-wide tables contain information required by this Cell only, unless it is the Primary Cell.
|
1. Locally stored cluster-wide tables contain information required by this Cell only.
|
||||||
1. The cluster-wide tables are restricted to be modified by the Cell that is authoritative over the particular record:
|
1. The cluster-wide tables are restricted to be modified by the Cell that is authoritative over the particular record:
|
||||||
1. The user record can be modified by the given Cell only if that Cell is the authoritative source of this record.
|
1. The user record can be modified by the given Cell only if that Cell is the authoritative source of this record.
|
||||||
1. In Cells 1.0 we are likely to not be replicating data across cluster,
|
1. In Cells 1.0 we are likely to not be replicating data across cluster,
|
||||||
so the authoritative source is the Cell that contains the record.
|
so the authoritative source is the Cell that contains the record.
|
||||||
1. The Primary Cell serves as a single source of truth for the uniqueness constraint (be it ID or user, group, project uniqueness).
|
1. The Topology Service serves as a single source of truth for the uniqueness constraint (be it ID or user, group, project uniqueness).
|
||||||
1. Secondary Cells use APIs to claim usernames, groups or projects.
|
1. All Cells use gRPC to claim usernames, groups or projects.
|
||||||
1. The Primary Cell holds information about all usernames, groups and projects, and the Cell holding the record.
|
1. The Topology Service holds metadata information that allows to know on which Cell the username, group or project is.
|
||||||
1. The Primary Cell does not hold source information (actual user or project records), only references that are indicative of the Cell where that information is stored.
|
1. The Topology Service does not hold source information (actual user or project records), only references that are indicative of the Cell where that information is stored.
|
||||||
|
|
||||||
1. Routing properties:
|
1. Routing properties:
|
||||||
|
|
||||||
1. We implement a static routing service that performs secret-based routing based on the prefix.
|
1. We implement a routing service that performs secret-based routing based on the prefix.
|
||||||
1. The routing service is implemented as a Cloudflare Worker and is run on edge. The routing service is run with a static list of Cells. Each Cell is described by a proxy URL, and a prefix.
|
1. The routing service is implemented as a Cloudflare Worker and is run on edge. The routing service defines a set of rules, and uses Topology Service to classify how to route data.
|
||||||
1. Cells are exposed over the public internet, but might be guarded with Zero Trust.
|
1. Cells are exposed over the public internet, but might be guarded with Zero Trust.
|
||||||
|
|
||||||
### Architecture overview
|
### Architecture overview
|
||||||
|
|
@ -116,109 +115,56 @@ cloud "Cloudflare" as CF {
|
||||||
}
|
}
|
||||||
|
|
||||||
node "GitLab Inc. Infrastructure" {
|
node "GitLab Inc. Infrastructure" {
|
||||||
node "Primary Cell" as PC {
|
node "Cell Services" as Cell_Services {
|
||||||
frame "GitLab Rails" as PC_Rails {
|
[Topology Service] as TS
|
||||||
[Puma + Workhorse + LB] as PC_Puma
|
[Cloud Spanner] as TS_CS
|
||||||
[Sidekiq] as PC_Sidekiq
|
|
||||||
}
|
|
||||||
|
|
||||||
[Container Registry] as PC_Registry
|
TS --> TS_CS
|
||||||
|
|
||||||
database DB as PC_DB {
|
|
||||||
frame "PostgreSQL Cluster" as PC_PSQL {
|
|
||||||
package "ci" as PC_PSQL_ci {
|
|
||||||
[gitlab_ci] as PC_PSQL_gitlab_ci
|
|
||||||
}
|
|
||||||
|
|
||||||
package "main" as PC_PSQL_main {
|
|
||||||
[gitlab_main_clusterwide] as PC_PSQL_gitlab_main_clusterwide
|
|
||||||
[gitlab_main_cell] as PC_PSQL_gitlab_main_cell
|
|
||||||
}
|
|
||||||
|
|
||||||
PC_PSQL_main -[hidden]-> PC_PSQL_ci
|
|
||||||
}
|
|
||||||
|
|
||||||
frame "Redis Cluster" as PC_Redis {
|
|
||||||
[Redis (many)] as PC_Redis_many
|
|
||||||
}
|
|
||||||
|
|
||||||
frame "Gitaly Cluster" as PC_Gitaly {
|
|
||||||
[Gitaly Nodes (many)] as PC_Gitaly_many
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PC_Rails -[hidden]-> PC_DB
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node "Secondary Cell" as SC {
|
node "A Cell" as Cell {
|
||||||
frame "GitLab Rails" as SC_Rails {
|
frame "GitLab Rails" as Cell_Rails {
|
||||||
[Puma + Workhorse + LB] as SC_Puma
|
[Puma + Workhorse + LB] as Cell_Puma
|
||||||
[Sidekiq] as SC_Sidekiq
|
[Sidekiq] as Cell_Sidekiq
|
||||||
}
|
}
|
||||||
|
|
||||||
[Container Registry] as SC_Registry
|
[Container Registry] as Cell_Registry
|
||||||
|
|
||||||
database DB as SC_DB {
|
database DB as Cell_DB {
|
||||||
frame "PostgreSQL Cluster" as SC_PSQL {
|
frame "PostgreSQL Cluster" as Cell_PSQL {
|
||||||
package "ci" as SC_PSQL_ci {
|
package "ci" as Cell_PSQL_ci {
|
||||||
[gitlab_ci] as SC_PSQL_gitlab_ci
|
[gitlab_ci] as Cell_PSQL_gitlab_ci
|
||||||
}
|
}
|
||||||
|
|
||||||
package "main" as SC_PSQL_main {
|
package "main" as Cell_PSQL_main {
|
||||||
[gitlab_main_clusterwide] as SC_PSQL_gitlab_main_clusterwide
|
[gitlab_main_clusterwide] as Cell_PSQL_gitlab_main_clusterwide
|
||||||
[gitlab_main_cell] as SC_PSQL_gitlab_main_cell
|
[gitlab_main_cell] as Cell_PSQL_gitlab_main_cell
|
||||||
}
|
}
|
||||||
|
|
||||||
SC_PSQL_main -[hidden]-> SC_PSQL_ci
|
PC_PSQL_main -[hidden]-> Cell_PSQL_ci
|
||||||
}
|
}
|
||||||
|
|
||||||
frame "Redis Cluster" as SC_Redis {
|
frame "Redis Cluster" as Cell_Redis {
|
||||||
[Redis (many)] as SC_Redis_many
|
[Redis (many)] as Cell_Redis_many
|
||||||
}
|
}
|
||||||
|
|
||||||
frame "Gitaly Cluster" as SC_Gitaly {
|
frame "Gitaly Cluster" as Cell_Gitaly {
|
||||||
[Gitaly Nodes (many)] as SC_Gitaly_many
|
[Gitaly Nodes (many)] as Cell_Gitaly_many
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SC_Rails -[hidden]-> SC_DB
|
Cell_Rails -[hidden]-> Cell_DB
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CF_RSW --> PC_Puma
|
CF_RSW --> Cell_Puma
|
||||||
CF_RSW --> PC_Registry
|
CF_RSW --> Cell_Registry
|
||||||
CF_RSW --> SC_Puma
|
CF_RSW --> Cell_Puma
|
||||||
CF_RSW --> SC_Registry
|
CF_RSW --> Cell_Registry
|
||||||
|
CF_RSW --> TS
|
||||||
@enduml
|
Cell_Puma --> TS
|
||||||
```
|
Cell_Sidekiq --> TS
|
||||||
|
|
||||||
### API overview
|
|
||||||
|
|
||||||
```plantuml
|
|
||||||
@startuml
|
|
||||||
|
|
||||||
skinparam FrameBackgroundcolor white
|
|
||||||
|
|
||||||
node "GitLab Inc. Infrastructure" {
|
|
||||||
node "Primary Cell" as PC {
|
|
||||||
frame "GitLab Rails" as PC_Rails {
|
|
||||||
[Puma + Workhorse + LB] as PC_Puma
|
|
||||||
[Sidekiq] as PC_Sidekiq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
node "Secondary Cell" as SC {
|
|
||||||
frame SC_Rails [
|
|
||||||
{{
|
|
||||||
[Puma + Workhorse + LB] as SC_Puma
|
|
||||||
[Sidekiq] as SC_Sidekiq
|
|
||||||
}}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SC_Rails -up-> PC_Puma : "Primary Cell API:\n/api/v4/internal/cells/..."
|
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
```
|
```
|
||||||
|
|
@ -238,196 +184,27 @@ The GitLab configuration in `gitlab.yml` is extended with the following paramete
|
||||||
```yaml
|
```yaml
|
||||||
production:
|
production:
|
||||||
gitlab:
|
gitlab:
|
||||||
primary_cell:
|
topology_service:
|
||||||
url: https://cell1.gitlab.com
|
address: https://cell1.gitlab.com
|
||||||
token: abcdef
|
certificate: ...
|
||||||
secrets_prefix: kPptz
|
secrets_prefix: kPptz
|
||||||
```
|
```
|
||||||
|
|
||||||
1. `primary_cell:` configured on Secondary Cells, and indicates the URL endpoint to access the Primary Cell API.
|
### Topology Service
|
||||||
1. `secrets_prefix:` can be used on all Cells, and indicates that each secret and session cookie is prefixed with this identifier.
|
|
||||||
|
|
||||||
### Primary Cell
|
All services supported are described in dedicated documented about [Topology Service](../topology_service.md).
|
||||||
|
|
||||||
The Primary Cell serves a special purpose to ensure cluster uniqueness.
|
|
||||||
The Primary Cell exposes a set of API interfaces to be used by Secondary Cells.
|
|
||||||
The API is considered internal, and is guarded with a secret that is shared with Secondary Cells.
|
|
||||||
|
|
||||||
1. `POST /api/v4/internal/cells/database/claim`
|
|
||||||
|
|
||||||
1. Request:
|
|
||||||
- `table`: table name for the allocated sequence, for example `projects`
|
|
||||||
- `count`: number of IDs to claim that are guaranteed to be unique, for example 100_000
|
|
||||||
1. Response:
|
|
||||||
- `start`: the start sequence value
|
|
||||||
- `limit`: the allowed maximum sequence value
|
|
||||||
|
|
||||||
1. `POST /api/v4/internal/cells/routes`
|
|
||||||
|
|
||||||
1. Request:
|
|
||||||
- `path`: the full path to a resource, for example `my-company/my-project`
|
|
||||||
- `source_type`: the class name for the container holding the resource, for example `project`
|
|
||||||
- `source_id`: the source ID for the container holding the resource, for example `1000`
|
|
||||||
- `namespace_id`: the underlying namespace associated with the resource, for example `32`
|
|
||||||
- `name`: the display name for the resource
|
|
||||||
- `cell_id`: the identifier of the Cell holding the resource
|
|
||||||
1. Response:
|
|
||||||
- 201: Created: The resource was created.
|
|
||||||
- `id:`: The ID of the resource as stored on the Primary Cell.
|
|
||||||
- 409: Conflict: The resource already exists.
|
|
||||||
1. Behavior:
|
|
||||||
1. The endpoint returns an `id`. The same ID has to be used to insert a record in `routes` table for the calling cell.
|
|
||||||
|
|
||||||
1. `GET /api/v4/internal/cells/routes/:id`
|
|
||||||
|
|
||||||
1. Request:
|
|
||||||
- `id`: resource identifier
|
|
||||||
1. Response:
|
|
||||||
- 200: OK: The resource was found. For response parameters look at `POST /api/v4/internal/cells/routes` request parameters.
|
|
||||||
- 404: Not found: The resource does not exit.
|
|
||||||
|
|
||||||
1. `PUT /api/v4/internal/cells/routes/:id`
|
|
||||||
|
|
||||||
1. Request: For parameters look at `POST /api/v4/internal/cells/routes` request parameters.
|
|
||||||
1. Response:
|
|
||||||
- 200: OK: The resource was updated.
|
|
||||||
- 403: Forbidden: The resource cannot be modified, because it is owned by another `cell_id`.
|
|
||||||
- 404: Not found: The resource does not exit.
|
|
||||||
- 409: Conflict: The resource already exists. The uniqueness constraint on `path` failed.
|
|
||||||
1. Behavior:
|
|
||||||
1. The endpoint modifies the given resource as long the `cell_id` matches.
|
|
||||||
|
|
||||||
1. `DELETE /api/v4/internal/cells/routes/:id`
|
|
||||||
|
|
||||||
1. Request: For parameters look at `POST /api/v4/internal/cells/routes` request parameters.
|
|
||||||
1. Response:
|
|
||||||
- 200: OK: The resource with given parameters was successfully deleted.
|
|
||||||
- 404: Not found: The given resource was not found.
|
|
||||||
- 400: Bad request: The resource failed to be deleted.
|
|
||||||
|
|
||||||
1. `POST /api/v4/internal/cells/redirect_routes`
|
|
||||||
1. `GET /api/v4/internal/cells/redirect_routes/:id`
|
|
||||||
1. `PUT /api/v4/internal/cells/redirect_routes/:id`
|
|
||||||
1. `DELETE /api/v4/internal/cells/redirect_routes/:id`
|
|
||||||
|
|
||||||
### Secondary Cell
|
|
||||||
|
|
||||||
The Secondary Cell does not expose any specific API at this point.
|
|
||||||
The Secondary Cell implements a solution to guarantee uniqueness of primary database keys.
|
|
||||||
|
|
||||||
1. `ReplenishDatabaseSequencesWorker`: this worker runs periodically, check all sequences, and replenish them.
|
|
||||||
|
|
||||||
#### Simple uniqueness of Database Sequences
|
|
||||||
|
|
||||||
Simple uniqueness of database sequences refers to
|
|
||||||
the practice of claiming, using and replenishing a cluster-wide unique range of IDs for a table.
|
|
||||||
|
|
||||||
Our DDL schema uses ID generation in the form: `id bigint DEFAULT nextval('product_analytics_events_experimental_id_seq'::regclass) NOT NULL`.
|
|
||||||
|
|
||||||
The `/api/v4/internal/cells/database/claim` would execute the following logic
|
|
||||||
to atomically claim a range of IDs.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
def claim_table_seq(table, count)
|
|
||||||
sql = <<-SQL
|
|
||||||
BEGIN;
|
|
||||||
-- `ALTER SEQUENCE` effectively locks `some_seq` for write and read.
|
|
||||||
ALTER SEQUENCE seq_name INCREMENT BY 1000;
|
|
||||||
SELECT nextval(seq_name); -- Suppose this returned 1001.
|
|
||||||
ALTER SEQUENCE seq_name INCREMENT BY 1; -- UNDO "INCREMENT BY 1000".
|
|
||||||
|
|
||||||
INSERT INTO cells_sequence_claims (cell_id, table_name, start_id, end_id)
|
|
||||||
VALUES (cell_id, table_name, 2, 1001);
|
|
||||||
COMMIT;
|
|
||||||
SQL
|
|
||||||
|
|
||||||
seq_name = "#{table}_id_seq"
|
|
||||||
last_id = select_one(sql, seq_name, limit, seq_name, seq_name).first
|
|
||||||
{ start: last_id - count, limit: count }
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Replenishing available IDs
|
|
||||||
|
|
||||||
The `ReplenishDatabaseSequencesWorker` would check how much space is left in a sequence, and request a new range if the value goes below the threshold.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
def replenish_table_seq(table, lower_limit, count)
|
|
||||||
seq_name = "#{table}_id_seq"
|
|
||||||
# maxval is not existing, so it would have to be implemented different way, but this is to showcase purposes
|
|
||||||
last_id, max_id = select_one("SELECT currval(?), maxval(?)", seq_name, seq_name)
|
|
||||||
return if max_id - last_id > lower_limit
|
|
||||||
|
|
||||||
new_start, new_limit = post("/api/v4/internal/cells/database/claim", { table: table, count: count })
|
|
||||||
execute("ALTER SEQUENCE RESTART ? MAXVALUE ?", new_start, new_limit)
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
This makes us lose the `lower_limit` of IDs of sequence creating gaps. However, in this model we need to replenish the
|
|
||||||
sequence ahead of time, otherwise we will have catastrophic failure on inserting new records.
|
|
||||||
|
|
||||||
The above claiming and replenishing approach can potentially waste too much ID space
|
|
||||||
claimed by each table.
|
|
||||||
|
|
||||||
For example, after having claimed the range from `101` to `200`,
|
|
||||||
a table might choose to replenish after it's used up the first 80 IDs (`101` to `180`)
|
|
||||||
leaving the remaining 20 IDs to be wasted.
|
|
||||||
|
|
||||||
To efficiently utilize all claimed IDs, we introduce the concept of robust uniqueness
|
|
||||||
in which a table maintains two alternating sequences and uses a custom implementation for `nextval` and similar functions.
|
|
||||||
|
|
||||||
#### Robust uniqueness of table sequences
|
|
||||||
|
|
||||||
This is very similar to simple uniqueness, with these additions:
|
|
||||||
|
|
||||||
- We use and alternate between two sequences per-table (A/B), fully utilizing allocated sequences.
|
|
||||||
- We change the `DEFAULT` to accept two arguments to function `nextval2(table_id_seq_a::regclass, table_id_seq_b::regclass)`.
|
|
||||||
- We replenish the sequence as soon as it runs out of free IDs.
|
|
||||||
- We don't create sequence gaps.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE FUNCTION nextval2(seq_a oid, seq_b oid) RETURNS bigint
|
|
||||||
LANGUAGE plpgsql
|
|
||||||
AS $$
|
|
||||||
BEGIN
|
|
||||||
-- pick from sequence that has lower number
|
|
||||||
-- as we want to retain monotonically increasing numbers
|
|
||||||
-- when allocation fails (as the sequence is exhausted) switch to another one
|
|
||||||
SELECT last_value INTO seq_a_last_value FROM seq_a;
|
|
||||||
SELECT last_value INTO seq_b_last_value FROM seq_b;
|
|
||||||
IF seq_a_last_value < seq_b_last_value
|
|
||||||
BEGIN TRY
|
|
||||||
RETURN nextval(seq_a);
|
|
||||||
END TRY
|
|
||||||
BEGIN CATCH
|
|
||||||
RETURN nextval(seq_b);
|
|
||||||
END CATCH;
|
|
||||||
ELSE
|
|
||||||
BEGIN TRY
|
|
||||||
RETURN nextval(seq_b);
|
|
||||||
END TRY
|
|
||||||
BEGIN CATCH
|
|
||||||
RETURN nextval(seq_a);
|
|
||||||
END CATCH;
|
|
||||||
END
|
|
||||||
END
|
|
||||||
$$;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pros
|
## Pros
|
||||||
|
|
||||||
- The proposal is lean:
|
- The proposal is lean:
|
||||||
- Each Cell holds only a fraction of the data that is required for the cluster to operate.
|
- Each Cell holds only a fraction of the data that is required for the cluster to operate.
|
||||||
- The tables marked as `main_clusterwide` that are stored locally can be selectively replicated across Cells following a mesh-architecture.
|
- The tables marked as `main_clusterwide` that are stored locally can be selectively replicated across Cells following a mesh-architecture.
|
||||||
- Based on the assumption that Cells require only a fraction of shared data (like users), it is expected that Secondary Cells might need a small percentage of records across the whole cluster.
|
- Based on the assumption that Cells require only a fraction of shared data (like users), it is expected that Cells might need a small percentage of records across the whole cluster.
|
||||||
- The primary Cell is a single point of failure only for a limited set of features:
|
- The Topology Service is a single point of failure:
|
||||||
- Uniqueness is enforced by the primary Cell.
|
- Reduced set of features allows to make it highly-available service.
|
||||||
- The temporary reliability of the primary Cell has a limited impact on Secondary Cells.
|
- Use highly-available database solution (Cloud Spanner).
|
||||||
- Secondary Cells would not be able to enforce unique constraints: create group, project, or user.
|
|
||||||
- Other functionality of Secondary Cells would continue working as is: push, run CI.
|
|
||||||
- The routing layer makes this service very simple, because it is secret-based and uses prefix.
|
- The routing layer makes this service very simple, because it is secret-based and uses prefix.
|
||||||
- Reliability of the service is not dependent on Cell availability, because at this stage no dynamic classification is required.
|
- Reliability of the service is not dependent on Cell availability. It depends on availability of Topology Service to perform classification.
|
||||||
- We anticipate that the routing layer will evolve to perform regular classification at a later point.
|
|
||||||
- Mixed-deployment compatible by design.
|
- Mixed-deployment compatible by design.
|
||||||
- We do not share database connections. We expose APIs to interact with cluster-wide data.
|
- We do not share database connections. We expose APIs to interact with cluster-wide data.
|
||||||
- The application is responsible to support API compatibility across versions, allowing us to easily support many versions of the application running from day zero.
|
- The application is responsible to support API compatibility across versions, allowing us to easily support many versions of the application running from day zero.
|
||||||
|
|
@ -465,14 +242,14 @@ The table below is a comparison between the existing GitLab.com features, and no
|
||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
1. How do we create new Organizations with the user on Secondary Cell?
|
1. How do we create new Organizations with the user on additional Cells?
|
||||||
|
|
||||||
To be defined.
|
To be defined.
|
||||||
|
|
||||||
1. How do we register new users for the existing Organization on Secondary Cell?
|
1. How do we register new users for the existing Organization on additional Cell?
|
||||||
|
|
||||||
If an Organization is already created, users can be invited.
|
If an Organization is already created, users can be invited.
|
||||||
We can then serve the registration flow from Secondary Cell.
|
We can then serve the registration flow from additional Cell.
|
||||||
|
|
||||||
1. How would users log in?
|
1. How would users log in?
|
||||||
|
|
||||||
|
|
@ -480,9 +257,9 @@ The table below is a comparison between the existing GitLab.com features, and no
|
||||||
- SAML: `https://<GITLAB_DOMAIN>/users/auth/saml/callback` would receive `?organization=gitlab-inc` which would be routed to the correct Cell.
|
- SAML: `https://<GITLAB_DOMAIN>/users/auth/saml/callback` would receive `?organization=gitlab-inc` which would be routed to the correct Cell.
|
||||||
- This would require using the dynamic routing method with a list of Organizations available using a solution with high availability.
|
- This would require using the dynamic routing method with a list of Organizations available using a solution with high availability.
|
||||||
|
|
||||||
1. How do we add a new table if it is initially deployed on Secondary Cell?
|
1. How do we add a new table if it is initially deployed on additional Cell?
|
||||||
|
|
||||||
The Primary Cell is ensuring uniqueness of sequences, so it needs to have `sequence`.
|
The Topology Service is ensuring uniqueness of sequences, so it needs to have `sequence`.
|
||||||
|
|
||||||
1. Is Container Registry cluster-wide or cell-local?
|
1. Is Container Registry cluster-wide or cell-local?
|
||||||
|
|
||||||
|
|
@ -498,8 +275,8 @@ The table below is a comparison between the existing GitLab.com features, and no
|
||||||
|
|
||||||
- GitLab Pages need to be run as a single service that is not run as part of a Cell.
|
- GitLab Pages need to be run as a single service that is not run as part of a Cell.
|
||||||
- Because GitLab Pages use the API we need to make them routable.
|
- Because GitLab Pages use the API we need to make them routable.
|
||||||
- Similar to `routes`, claim `pages_domain` on the Primary Cell
|
- Similar to `routes`, claim `pages_domain` on the Topology Service
|
||||||
- Implement dynamic classification in the routing service, based on a sharding key.
|
- Implement dynamic classification in the routing service, based on a classification key.
|
||||||
- Cons: This adds another table that has to be kept unique cluster-wide.
|
- Cons: This adds another table that has to be kept unique cluster-wide.
|
||||||
|
|
||||||
Alternatively:
|
Alternatively:
|
||||||
|
|
@ -547,22 +324,14 @@ The table below is a comparison between the existing GitLab.com features, and no
|
||||||
Since we don't yet synchronize across the cluster, admin accounts would have to be provided per Cell.
|
Since we don't yet synchronize across the cluster, admin accounts would have to be provided per Cell.
|
||||||
This might be solved by GitLab Dedicated already?
|
This might be solved by GitLab Dedicated already?
|
||||||
|
|
||||||
1. Is it a Primary Cell or cluster-wide service?
|
|
||||||
|
|
||||||
The Primary Cell in fact serves as a cluster-wide service. Depending on our intent it could be named the following:
|
|
||||||
|
|
||||||
- Primary Cell: To clearly state that the Primary Cell has a special purpose today, but we rename it later.
|
|
||||||
- Cluster-wide Data Provider
|
|
||||||
- Topology Service: Alternative name to Cluster-wide Data Provider, indicating that the Primary Cell would implement a Topology Service today.
|
|
||||||
|
|
||||||
1. How are secrets are generated?
|
1. How are secrets are generated?
|
||||||
|
|
||||||
The Cell prefix is used to generate a secret in a way that encodes the prefix. The prefix is added to the generated secret.
|
The Cell prefix is used to generate a secret in a way that encodes the prefix. The prefix is added to the generated secret.
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
- GitLab Runner Tokens are generated in the form: `glrt-2CR8_eVxiioB1QmzPZwa`
|
- GitLab Runner Tokens are generated in the form: `glrt-2CR8_XYZ`
|
||||||
- For Cell prefix: `secrets_prefix: kPptz`
|
- For Cell prefix: `secrets_prefix: kPptz`
|
||||||
- We would generate Runner tokens in the form: `glrt-kPptz_2CR8_eVxiioB1QmzPZwa`
|
- We would generate Runner tokens in the form: `glrt-kPptz_2CR8_XYZ`
|
||||||
|
|
||||||
1. Why secrets-based routing instead of path-based routing?
|
1. Why secrets-based routing instead of path-based routing?
|
||||||
|
|
||||||
|
|
@ -578,7 +347,7 @@ The table below is a comparison between the existing GitLab.com features, and no
|
||||||
how many routes are already classified can be found [in this comment](https://gitlab.com/gitlab-org/gitlab/-/issues/430330#note_1633125914).
|
how many routes are already classified can be found [in this comment](https://gitlab.com/gitlab-org/gitlab/-/issues/430330#note_1633125914).
|
||||||
- In each case the routing service needs to be able to dynamically classify existing routes
|
- In each case the routing service needs to be able to dynamically classify existing routes
|
||||||
based on some defined criteria, requiring significant development effort, and increasing the
|
based on some defined criteria, requiring significant development effort, and increasing the
|
||||||
dependency on the Primary Cell or Cluster-wide service.
|
dependency on the Topology Service.
|
||||||
|
|
||||||
By following secret-based routing we can cut a lot of initial complexity, which allows us to
|
By following secret-based routing we can cut a lot of initial complexity, which allows us to
|
||||||
make the best decision at a later point:
|
make the best decision at a later point:
|
||||||
|
|
@ -619,21 +388,18 @@ The table below is a comparison between the existing GitLab.com features, and no
|
||||||
|
|
||||||
1. How would the Cell find users or projects?
|
1. How would the Cell find users or projects?
|
||||||
|
|
||||||
To be defined. However, we would need `Primary Cell`/`Cluster-wide Data Provider` access to the `routes` table that contains a mapping of all names into objects (group, project, user) and the Cell holding the information.
|
The Cell would use [Classify Service](../topology_service.md#classify-service) of Topology Service.
|
||||||
|
|
||||||
1. Would the User Profile be public if created for enterprise customer?
|
1. Would the User Profile be public if created for enterprise customer?
|
||||||
|
|
||||||
No. Users created on another Cell in a given Organization would be limited to this Organization only.
|
No. Users created on another Cell in a given Organization would be limited to this Organization only.
|
||||||
The User Profile would be available as long as you are logged in.
|
The User Profile would be available as long as you are logged in.
|
||||||
|
|
||||||
1. What is the resiliency of a Primary Cell exposing cluster-wide API?
|
1. What is the resiliency of a Topology Service exposing cluster-wide API?
|
||||||
|
|
||||||
The API would be responsible for ensuring uniqueness of: User, Groups, Projects, Organizations, SSH keys, Pages domains, e-mails.
|
The API would be responsible for ensuring uniqueness of: User, Groups, Projects, Organizations, SSH keys, Pages domains, e-mails.
|
||||||
The API would also be responsible for classifying a sharding key for the routing service.
|
The API would also be responsible for classifying a classification key for the routing service.
|
||||||
We need to ensure that the Primary Cell cluster-wide API is highly available. The solution here could be to:
|
We need to ensure that the Topology Service cluster-wide API is highly available.
|
||||||
|
|
||||||
1. Run the Primary Cell API as an additional node that has dedicated database replica and can work while the main Cell is down.
|
|
||||||
1. Implement a Primary Cell API on top of another highly available database in a different technology than Rails. Forward the API write calls to this storage (claim), but make read API calls (classify) to use this storage.
|
|
||||||
|
|
||||||
1. How can instance-wide CI runners be configured on the new cells?
|
1. How can instance-wide CI runners be configured on the new cells?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,19 @@ graph TD;
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
The Topology Service will use `config.toml` to configure all service parameters.
|
||||||
|
|
||||||
|
#### List of Cells
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[cells]]
|
||||||
|
id = 1
|
||||||
|
address = "cell-us-1.gitlab.com"
|
||||||
|
session_prefix = "cell1:"
|
||||||
|
```
|
||||||
|
|
||||||
### Sequence Service
|
### Sequence Service
|
||||||
|
|
||||||
```proto
|
```proto
|
||||||
|
|
@ -216,6 +229,7 @@ endpoints for Claims within a transaction.
|
||||||
enum ClassifyType {
|
enum ClassifyType {
|
||||||
Route = 1;
|
Route = 1;
|
||||||
Login = 2;
|
Login = 2;
|
||||||
|
SessionPrefix = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ClassifyRequest {
|
message ClassifyRequest {
|
||||||
|
|
@ -280,6 +294,27 @@ sequenceDiagram
|
||||||
The sign-in request going to Cell 1 might at some point later be round-rubin routed to all Cells,
|
The sign-in request going to Cell 1 might at some point later be round-rubin routed to all Cells,
|
||||||
as each Cell should be able to classify user and redirect it to correct Cell.
|
as each Cell should be able to classify user and redirect it to correct Cell.
|
||||||
|
|
||||||
|
#### Session cookie classification workflow with Classify Service
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User1
|
||||||
|
participant HTTP Router
|
||||||
|
participant TS / Classify Service
|
||||||
|
participant Cell 1
|
||||||
|
participant Cell 2
|
||||||
|
|
||||||
|
User1->> HTTP Router :GET "/gitlab-org/gitlab/-/issues"<br>Cookie: _gitlab_session=cell1:df1f861a9e609
|
||||||
|
Note over HTTP Router: Extract "cell1" from `_gitlab_session`
|
||||||
|
HTTP Router->> TS / Classify Service: Classify(SessionPrefix) "cell1"
|
||||||
|
TS / Classify Service->>HTTP Router: gitlab-org/gitlab => Cell 1
|
||||||
|
HTTP Router->> Cell 1: GET "/gitlab-org/gitlab/-/issues"<br>Cookie: _gitlab_session=cell1:df1f861a9e609
|
||||||
|
Cell 2->> HTTP Router: Issues Page Response
|
||||||
|
HTTP Router->>User1: Issues Page Response
|
||||||
|
```
|
||||||
|
|
||||||
|
The session cookie will be validated with `session_prefix` value.
|
||||||
|
|
||||||
### Metadata Service (**future**, implemented for Cells 1.5)
|
### Metadata Service (**future**, implemented for Cells 1.5)
|
||||||
|
|
||||||
The Metadata Service is a way for Cells to distribute information cluster-wide:
|
The Metadata Service is a way for Cells to distribute information cluster-wide:
|
||||||
|
|
@ -383,8 +418,6 @@ sequenceDiagram
|
||||||
|
|
||||||
## Reasons
|
## Reasons
|
||||||
|
|
||||||
The original [Cells 1.0](iterations/cells-1.0.md) described [Primary Cell API](iterations/cells-1.0.md#primary-cell), this changes this decision to implement Topology Service for the following reasons:
|
|
||||||
|
|
||||||
1. Provide stable and well described set of cluster-wide services that can be used
|
1. Provide stable and well described set of cluster-wide services that can be used
|
||||||
by various services (HTTP Routing Service, SSH Routing Service, each Cell).
|
by various services (HTTP Routing Service, SSH Routing Service, each Cell).
|
||||||
1. As part of Cells 1.0 PoC we discovered that we need to provide robust classification API
|
1. As part of Cells 1.0 PoC we discovered that we need to provide robust classification API
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ its framework is removed.
|
||||||
|
|
||||||
- To create, edit, and delete compliance frameworks, users must have either:
|
- To create, edit, and delete compliance frameworks, users must have either:
|
||||||
- The Owner or Maintainer role in the top-level group.
|
- The Owner or Maintainer role in the top-level group.
|
||||||
- Be assigned a [custom role](../../user/custom_roles/abilities.md) with the `admin_compliance_framework`
|
- Be assigned a [custom role](../custom_roles.md) with the `admin_compliance_framework`
|
||||||
[custom permission](../../user/custom_roles/abilities.md#compliance-management).
|
[custom permission](../custom_roles/abilities.md#compliance-management).
|
||||||
- To add or remove a compliance framework to or from a project, the group to which the project belongs must have a
|
- To add or remove a compliance framework to or from a project, the group to which the project belongs must have a
|
||||||
compliance framework.
|
compliance framework.
|
||||||
|
|
||||||
|
|
@ -30,15 +30,15 @@ its framework is removed.
|
||||||
|
|
||||||
You can create, edit, or delete a compliance framework from a compliance framework report. For more information, see:
|
You can create, edit, or delete a compliance framework from a compliance framework report. For more information, see:
|
||||||
|
|
||||||
- [Create a new compliance framework](../../user/compliance/compliance_center/compliance_frameworks_report.md#create-a-new-compliance-framework).
|
- [Create a new compliance framework](../compliance/compliance_center/compliance_frameworks_report.md#create-a-new-compliance-framework).
|
||||||
- [Edit a compliance framework](../../user/compliance/compliance_center/compliance_frameworks_report.md#edit-a-compliance-framework).
|
- [Edit a compliance framework](../compliance/compliance_center/compliance_frameworks_report.md#edit-a-compliance-framework).
|
||||||
- [Delete a compliance framework](../../user/compliance/compliance_center/compliance_frameworks_report.md#delete-a-compliance-framework).
|
- [Delete a compliance framework](../compliance/compliance_center/compliance_frameworks_report.md#delete-a-compliance-framework).
|
||||||
|
|
||||||
You can create, edit, or delete a compliance framework from a compliance projects report. For more information, see:
|
You can create, edit, or delete a compliance framework from a compliance projects report. For more information, see:
|
||||||
|
|
||||||
- [Create a new compliance framework](../../user/compliance/compliance_center/compliance_projects_report.md#create-a-new-compliance-framework).
|
- [Create a new compliance framework](../compliance/compliance_center/compliance_projects_report.md#create-a-new-compliance-framework).
|
||||||
- [Edit a compliance framework](../../user/compliance/compliance_center/compliance_projects_report.md#edit-a-compliance-framework).
|
- [Edit a compliance framework](../compliance/compliance_center/compliance_projects_report.md#edit-a-compliance-framework).
|
||||||
- [Delete a compliance framework](../../user/compliance/compliance_center/compliance_projects_report.md#delete-a-compliance-framework).
|
- [Delete a compliance framework](../compliance/compliance_center/compliance_projects_report.md#delete-a-compliance-framework).
|
||||||
|
|
||||||
Subgroups and projects have access to all compliance frameworks created on their top-level group. However, compliance frameworks cannot be created, edited,
|
Subgroups and projects have access to all compliance frameworks created on their top-level group. However, compliance frameworks cannot be created, edited,
|
||||||
or deleted at the subgroup or project level. Project owners can choose a framework to apply to their projects.
|
or deleted at the subgroup or project level. Project owners can choose a framework to apply to their projects.
|
||||||
|
|
@ -48,7 +48,7 @@ or deleted at the subgroup or project level. Project owners can choose a framewo
|
||||||
Add a compliance framework to a project. Compliance frameworks cannot be added to projects in personal namespaces.
|
Add a compliance framework to a project. Compliance frameworks cannot be added to projects in personal namespaces.
|
||||||
|
|
||||||
To assign a compliance framework to a project, apply the compliance framework through the
|
To assign a compliance framework to a project, apply the compliance framework through the
|
||||||
[Compliance projects report](../../user/compliance/compliance_center/compliance_projects_report.md#apply-a-compliance-framework-to-projects-in-a-group).
|
[Compliance projects report](../compliance/compliance_center/compliance_projects_report.md#apply-a-compliance-framework-to-projects-in-a-group).
|
||||||
|
|
||||||
You can use the [GraphQL API](../../api/graphql/reference/index.md#mutationprojectsetcomplianceframework) to add a
|
You can use the [GraphQL API](../../api/graphql/reference/index.md#mutationprojectsetcomplianceframework) to add a
|
||||||
compliance framework to a project.
|
compliance framework to a project.
|
||||||
|
|
@ -68,7 +68,7 @@ A compliance framework that is set to default has a **default** label.
|
||||||
|
|
||||||
### Set and remove a default by using the compliance center
|
### Set and remove a default by using the compliance center
|
||||||
|
|
||||||
To set as default (or remove the default) from [compliance projects report](../../user/compliance/compliance_center/compliance_projects_report.md#compliance-projects-report):
|
To set as default (or remove the default) from [compliance projects report](../compliance/compliance_center/compliance_projects_report.md#compliance-projects-report):
|
||||||
|
|
||||||
1. On the left sidebar, select **Search or go to** and find your group.
|
1. On the left sidebar, select **Search or go to** and find your group.
|
||||||
1. Select **Secure > Compliance center**.
|
1. Select **Secure > Compliance center**.
|
||||||
|
|
@ -77,7 +77,7 @@ To set as default (or remove the default) from [compliance projects report](../.
|
||||||
1. Select **Set as default**.
|
1. Select **Set as default**.
|
||||||
1. Select **Save changes**.
|
1. Select **Save changes**.
|
||||||
|
|
||||||
To set as default (or remove the default) from [compliance framework report](../../user/compliance/compliance_center/compliance_frameworks_report.md#compliance-frameworks-report):
|
To set as default (or remove the default) from [compliance framework report](../compliance/compliance_center/compliance_frameworks_report.md#compliance-frameworks-report):
|
||||||
|
|
||||||
1. On the left sidebar, select **Search or go to** and find your group.
|
1. On the left sidebar, select **Search or go to** and find your group.
|
||||||
1. Select **Secure > Compliance center**.
|
1. Select **Secure > Compliance center**.
|
||||||
|
|
@ -89,4 +89,4 @@ To set as default (or remove the default) from [compliance framework report](../
|
||||||
## Remove a compliance framework from a project
|
## Remove a compliance framework from a project
|
||||||
|
|
||||||
To remove a compliance framework from one or multiple project in a group, remove the compliance framework through the
|
To remove a compliance framework from one or multiple project in a group, remove the compliance framework through the
|
||||||
[Compliance projects report](../../user/compliance/compliance_center/compliance_projects_report.md#remove-a-compliance-framework-from-projects-in-a-group).
|
[Compliance projects report](../compliance/compliance_center/compliance_projects_report.md#remove-a-compliance-framework-from-projects-in-a-group).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module BackgroundMigration
|
||||||
|
class BackfillStatusCheckResponsesProjectId < BackfillDesiredShardingKeyJob
|
||||||
|
operation_name :backfill_status_check_responses_project_id
|
||||||
|
feature_category :compliance_management
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -6,7 +6,18 @@ module Gitlab
|
||||||
PATH_TRAVERSAL_MESSAGE = 'Potential path traversal attempt detected'
|
PATH_TRAVERSAL_MESSAGE = 'Potential path traversal attempt detected'
|
||||||
# Query param names known to have string parts detected as path traversal even though
|
# Query param names known to have string parts detected as path traversal even though
|
||||||
# they are valid genuine requests
|
# they are valid genuine requests
|
||||||
EXCLUDED_QUERY_PARAM_NAMES = %w[search search_title term name filter filter_projects note body].freeze
|
EXCLUDED_QUERY_PARAM_NAMES = %w[
|
||||||
|
search
|
||||||
|
search_title
|
||||||
|
term
|
||||||
|
name
|
||||||
|
filter
|
||||||
|
filter_projects
|
||||||
|
note
|
||||||
|
body
|
||||||
|
commit_message
|
||||||
|
content
|
||||||
|
].freeze
|
||||||
|
|
||||||
def initialize(app)
|
def initialize(app)
|
||||||
@app = app
|
@app = app
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ module Gitlab
|
||||||
class SetIpAddress
|
class SetIpAddress
|
||||||
def call(_worker_class, job, _queue)
|
def call(_worker_class, job, _queue)
|
||||||
return yield if Feature.disabled?(:sidekiq_ip_address) # rubocop: disable Gitlab/FeatureFlagWithoutActor -- not applicable
|
return yield if Feature.disabled?(:sidekiq_ip_address) # rubocop: disable Gitlab/FeatureFlagWithoutActor -- not applicable
|
||||||
|
return yield unless job.key?('ip_address_state')
|
||||||
|
|
||||||
::Gitlab::IpAddressState.with(job['meta.remote_ip']) do # rubocop: disable CodeReuse/ActiveRecord -- Non-AR
|
::Gitlab::IpAddressState.with(job['ip_address_state']) do # rubocop: disable CodeReuse/ActiveRecord -- Non-AR
|
||||||
yield
|
yield
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -24062,6 +24062,9 @@ msgstr ""
|
||||||
msgid "GlobalSearch|Help"
|
msgid "GlobalSearch|Help"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GlobalSearch|I'm looking for"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "GlobalSearch|In this project"
|
msgid "GlobalSearch|In this project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -24086,9 +24089,6 @@ msgstr ""
|
||||||
msgid "GlobalSearch|Issues assigned to me"
|
msgid "GlobalSearch|Issues assigned to me"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "GlobalSearch|I’m looking for"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "GlobalSearch|Labels"
|
msgid "GlobalSearch|Labels"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,8 @@ module RuboCop
|
||||||
def create_todos_retaining_exclusions(inspected_cop_config)
|
def create_todos_retaining_exclusions(inspected_cop_config)
|
||||||
inspected_cop_config.each do |cop_name, config|
|
inspected_cop_config.each do |cop_name, config|
|
||||||
todo = @todos[cop_name]
|
todo = @todos[cop_name]
|
||||||
todo.add_files(config.fetch('Exclude', []).grep(RETAIN_EXCLUSIONS))
|
excluded_files = config['Exclude'] || []
|
||||||
|
todo.add_files(excluded_files.grep(RETAIN_EXCLUSIONS))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,6 @@ docker buildx build \
|
||||||
--build-arg=CHROME_VERSION="${CHROME_VERSION}" \
|
--build-arg=CHROME_VERSION="${CHROME_VERSION}" \
|
||||||
--build-arg=DOCKER_VERSION="${DOCKER_VERSION}" \
|
--build-arg=DOCKER_VERSION="${DOCKER_VERSION}" \
|
||||||
--build-arg=RUBY_VERSION="${RUBY_VERSION}" \
|
--build-arg=RUBY_VERSION="${RUBY_VERSION}" \
|
||||||
--build-arg=BUNDLER_VERSION="${BUNDLER_VERSION}" \
|
|
||||||
--build-arg=BUILD_OS="${BUILD_OS}" \
|
--build-arg=BUILD_OS="${BUILD_OS}" \
|
||||||
--build-arg=OS_VERSION="${OS_VERSION}" \
|
--build-arg=OS_VERSION="${OS_VERSION}" \
|
||||||
--build-arg=QA_BUILD_TARGET="${QA_BUILD_TARGET}" \
|
--build-arg=QA_BUILD_TARGET="${QA_BUILD_TARGET}" \
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
TOKEN_TYPE_MILESTONE,
|
TOKEN_TYPE_MILESTONE,
|
||||||
TOKEN_TYPE_SOURCE_BRANCH,
|
TOKEN_TYPE_SOURCE_BRANCH,
|
||||||
TOKEN_TYPE_TARGET_BRANCH,
|
TOKEN_TYPE_TARGET_BRANCH,
|
||||||
TOKEN_TYPE_MR_ASSIGNEE,
|
TOKEN_TYPE_ASSIGNEE,
|
||||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||||
import { mergeRequestListTabs } from '~/vue_shared/issuable/list/constants';
|
import { mergeRequestListTabs } from '~/vue_shared/issuable/list/constants';
|
||||||
import { getSortOptions } from '~/issues/list/utils';
|
import { getSortOptions } from '~/issues/list/utils';
|
||||||
|
|
@ -109,7 +109,7 @@ describe('Merge requests list app', () => {
|
||||||
|
|
||||||
it('does not have preloaded users when gon.current_user_id does not exist', () => {
|
it('does not have preloaded users when gon.current_user_id does not exist', () => {
|
||||||
expect(findIssuableList().props('searchTokens')).toMatchObject([
|
expect(findIssuableList().props('searchTokens')).toMatchObject([
|
||||||
{ type: TOKEN_TYPE_MR_ASSIGNEE },
|
{ type: TOKEN_TYPE_ASSIGNEE },
|
||||||
{ type: TOKEN_TYPE_AUTHOR, preloadedUsers: [] },
|
{ type: TOKEN_TYPE_AUTHOR, preloadedUsers: [] },
|
||||||
{ type: TOKEN_TYPE_DRAFT },
|
{ type: TOKEN_TYPE_DRAFT },
|
||||||
{ type: TOKEN_TYPE_MILESTONE },
|
{ type: TOKEN_TYPE_MILESTONE },
|
||||||
|
|
@ -121,7 +121,7 @@ describe('Merge requests list app', () => {
|
||||||
|
|
||||||
describe('when all tokens are available', () => {
|
describe('when all tokens are available', () => {
|
||||||
const urlParams = {
|
const urlParams = {
|
||||||
mr_assignee_username: 'bob',
|
assignee_username: 'bob',
|
||||||
draft: 'yes',
|
draft: 'yes',
|
||||||
milestone_title: 'milestone',
|
milestone_title: 'milestone',
|
||||||
'target_branches[]': 'branch-a',
|
'target_branches[]': 'branch-a',
|
||||||
|
|
@ -153,7 +153,7 @@ describe('Merge requests list app', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(findIssuableList().props('searchTokens')).toMatchObject([
|
expect(findIssuableList().props('searchTokens')).toMatchObject([
|
||||||
{ type: TOKEN_TYPE_MR_ASSIGNEE },
|
{ type: TOKEN_TYPE_ASSIGNEE },
|
||||||
{ type: TOKEN_TYPE_AUTHOR, preloadedUsers },
|
{ type: TOKEN_TYPE_AUTHOR, preloadedUsers },
|
||||||
{ type: TOKEN_TYPE_DRAFT },
|
{ type: TOKEN_TYPE_DRAFT },
|
||||||
{ type: TOKEN_TYPE_MILESTONE },
|
{ type: TOKEN_TYPE_MILESTONE },
|
||||||
|
|
@ -164,7 +164,7 @@ describe('Merge requests list app', () => {
|
||||||
|
|
||||||
it('pre-displays tokens that are in the url search parameters', () => {
|
it('pre-displays tokens that are in the url search parameters', () => {
|
||||||
expect(findIssuableList().props('initialFilterValue')).toMatchObject([
|
expect(findIssuableList().props('initialFilterValue')).toMatchObject([
|
||||||
{ type: TOKEN_TYPE_MR_ASSIGNEE },
|
{ type: TOKEN_TYPE_ASSIGNEE },
|
||||||
{ type: TOKEN_TYPE_DRAFT },
|
{ type: TOKEN_TYPE_DRAFT },
|
||||||
{ type: TOKEN_TYPE_MILESTONE },
|
{ type: TOKEN_TYPE_MILESTONE },
|
||||||
{ type: TOKEN_TYPE_TARGET_BRANCH },
|
{ type: TOKEN_TYPE_TARGET_BRANCH },
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import { GlListboxItem, GlSprintf, GlCollapsibleListbox } from '@gitlab/ui';
|
import { GlListboxItem, GlSprintf, GlCollapsibleListbox } from '@gitlab/ui';
|
||||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import CommandsOverviewDropdown from '~/super_sidebar/components/global_search/command_palette/command_overview_dropdown.vue';
|
import CommandsOverviewDropdown from '~/super_sidebar/components/global_search/command_palette/command_overview_dropdown.vue';
|
||||||
|
import { mockTracking } from 'helpers/tracking_helper';
|
||||||
|
|
||||||
describe('CommandsOverviewDropdown', () => {
|
describe('CommandsOverviewDropdown', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let trackingSpy;
|
||||||
|
|
||||||
const createComponent = () => {
|
const createComponent = () => {
|
||||||
wrapper = shallowMountExtended(CommandsOverviewDropdown, {
|
wrapper = shallowMountExtended(CommandsOverviewDropdown, {
|
||||||
|
|
@ -41,13 +43,14 @@ describe('CommandsOverviewDropdown', () => {
|
||||||
findItems().wrappers.map((w) => w.find('[data-testid="listbox-item-text"]').text());
|
findItems().wrappers.map((w) => w.find('[data-testid="listbox-item-text"]').text());
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||||
createComponent();
|
createComponent();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('template', () => {
|
describe('template', () => {
|
||||||
it('renders header', () => {
|
it('renders header', () => {
|
||||||
expect(findDropdown().find('[data-testid="listbox-header-text"]').text()).toBe(
|
expect(findDropdown().find('[data-testid="listbox-header-text"]').text()).toBe(
|
||||||
'I’m looking for',
|
"I'm looking for",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -65,5 +68,16 @@ describe('CommandsOverviewDropdown', () => {
|
||||||
findDropdown().vm.$emit('select', '@');
|
findDropdown().vm.$emit('select', '@');
|
||||||
expect(wrapper.emitted('selected')).toEqual([['@']]);
|
expect(wrapper.emitted('selected')).toEqual([['@']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('tracks on shown event', () => {
|
||||||
|
findDropdown().vm.$emit('shown');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(
|
||||||
|
undefined,
|
||||||
|
'click_commands_sub_menu_in_command_palette',
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -84,4 +84,15 @@ describe('FrequentlyVisitedGroups', () => {
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledTimes(1);
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('events', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits action on click', () => {
|
||||||
|
findFrequentItems().vm.$emit('action');
|
||||||
|
expect(wrapper.emitted('action')).toStrictEqual([['FREQUENTLY_VISITED_GROUPS_HANDLE']]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import FrequentProjects from '~/super_sidebar/components/global_search/component
|
||||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||||
import currentUserFrecentProjectsQuery from '~/super_sidebar/graphql/queries/current_user_frecent_projects.query.graphql';
|
import currentUserFrecentProjectsQuery from '~/super_sidebar/graphql/queries/current_user_frecent_projects.query.graphql';
|
||||||
import waitForPromises from 'helpers/wait_for_promises';
|
import waitForPromises from 'helpers/wait_for_promises';
|
||||||
|
import { FREQUENTLY_VISITED_PROJECTS_HANDLE } from '~/super_sidebar/components/global_search/command_palette/constants';
|
||||||
import { frecentProjectsMock } from '../../../mock_data';
|
import { frecentProjectsMock } from '../../../mock_data';
|
||||||
|
|
||||||
Vue.use(VueApollo);
|
Vue.use(VueApollo);
|
||||||
|
|
@ -84,4 +85,15 @@ describe('FrequentlyVisitedProjects', () => {
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledTimes(1);
|
expect(spy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('events', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits action on click', () => {
|
||||||
|
findFrequentItems().vm.$emit('action');
|
||||||
|
expect(wrapper.emitted('action')).toStrictEqual([[FREQUENTLY_VISITED_PROJECTS_HANDLE]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,15 @@ import GlobalSearchDefaultPlaces from '~/super_sidebar/components/global_search/
|
||||||
import FrequentProjects from '~/super_sidebar/components/global_search/components/frequent_projects.vue';
|
import FrequentProjects from '~/super_sidebar/components/global_search/components/frequent_projects.vue';
|
||||||
import FrequentGroups from '~/super_sidebar/components/global_search/components/frequent_groups.vue';
|
import FrequentGroups from '~/super_sidebar/components/global_search/components/frequent_groups.vue';
|
||||||
import GlobalSearchDefaultIssuables from '~/super_sidebar/components/global_search/components/global_search_default_issuables.vue';
|
import GlobalSearchDefaultIssuables from '~/super_sidebar/components/global_search/components/global_search_default_issuables.vue';
|
||||||
|
import { mockTracking } from 'helpers/tracking_helper';
|
||||||
|
import {
|
||||||
|
FREQUENTLY_VISITED_PROJECTS_HANDLE,
|
||||||
|
FREQUENTLY_VISITED_GROUPS_HANDLE,
|
||||||
|
} from '~/super_sidebar/components/global_search/command_palette/constants';
|
||||||
|
|
||||||
describe('GlobalSearchDefaultItems', () => {
|
describe('GlobalSearchDefaultItems', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
let trackingSpy;
|
||||||
|
|
||||||
const createComponent = () => {
|
const createComponent = () => {
|
||||||
wrapper = shallowMount(GlobalSearchDefaultItems);
|
wrapper = shallowMount(GlobalSearchDefaultItems);
|
||||||
|
|
@ -23,6 +29,7 @@ describe('GlobalSearchDefaultItems', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
|
||||||
createComponent();
|
createComponent();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -73,4 +80,28 @@ describe('GlobalSearchDefaultItems', () => {
|
||||||
expect(groups.classes()).toEqual(['gl-mt-3']);
|
expect(groups.classes()).toEqual(['gl-mt-3']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('events', () => {
|
||||||
|
it('tracks internal event on default projects component', () => {
|
||||||
|
findProjects().vm.$emit('action', FREQUENTLY_VISITED_PROJECTS_HANDLE);
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(
|
||||||
|
undefined,
|
||||||
|
'click_frequent_project_in_command_palette',
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks internal event on default group component', () => {
|
||||||
|
findProjects().vm.$emit('action', FREQUENTLY_VISITED_GROUPS_HANDLE);
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(
|
||||||
|
undefined,
|
||||||
|
'click_frequent_group_in_command_palette',
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,34 @@ RSpec.describe Resolvers::ProjectMergeRequestsResolver do
|
||||||
|
|
||||||
expect(result).to contain_exactly(merge_request)
|
expect(result).to contain_exactly(merge_request)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'returns error when assignee username and wildcard id are used' do
|
||||||
|
expect_graphql_error_to_be_created(GraphQL::Schema::Validator::ValidationFailedError,
|
||||||
|
'Only one of [reviewerUsername, reviewerWildcardId] arguments is allowed at the same time.') do
|
||||||
|
resolve_mr(project, reviewer_username: current_user.username, reviewer_wildcard_id: 'ANY')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with assignee wildcard param' do
|
||||||
|
it 'filters merge requests by NONE wildcard' do
|
||||||
|
result = resolve_mr(project, assignee_wildcard_id: 'NONE')
|
||||||
|
|
||||||
|
expect(result).to contain_exactly(merge_request2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters merge requests by ANY wildcard' do
|
||||||
|
result = resolve_mr(project, assignee_wildcard_id: 'ANY')
|
||||||
|
|
||||||
|
expect(result).to contain_exactly(merge_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns error when assignee username and wildcard id are used' do
|
||||||
|
expect_graphql_error_to_be_created(GraphQL::Schema::Validator::ValidationFailedError,
|
||||||
|
'Only one of [assigneeUsername, assigneeWildcardId] arguments is allowed at the same time.') do
|
||||||
|
resolve_mr(project, assignee_username: current_user.username, assignee_wildcard_id: 'ANY')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with milestone wildcard param' do
|
context 'with milestone wildcard param' do
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,7 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
|
||||||
:updated_before,
|
:updated_before,
|
||||||
:author_username,
|
:author_username,
|
||||||
:assignee_username,
|
:assignee_username,
|
||||||
|
:assignee_wildcard_id,
|
||||||
:reviewer_username,
|
:reviewer_username,
|
||||||
:reviewer_wildcard_id,
|
:reviewer_wildcard_id,
|
||||||
:review_state,
|
:review_state,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::BackgroundMigration::BackfillStatusCheckResponsesProjectId,
|
||||||
|
feature_category: :compliance_management,
|
||||||
|
schema: 20240611142348 do
|
||||||
|
include_examples 'desired sharding key backfill job' do
|
||||||
|
let(:batch_table) { :status_check_responses }
|
||||||
|
let(:backfill_column) { :project_id }
|
||||||
|
let(:backfill_via_table) { :merge_requests }
|
||||||
|
let(:backfill_via_column) { :target_project_id }
|
||||||
|
let(:backfill_via_foreign_key) { :merge_request_id }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -4,12 +4,14 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::SidekiqMiddleware::SetIpAddress, feature_category: :system_access do
|
RSpec.describe Gitlab::SidekiqMiddleware::SetIpAddress, feature_category: :system_access do
|
||||||
let(:worker) { instance_double(ApplicationWorker) }
|
let(:worker) { instance_double(ApplicationWorker) }
|
||||||
let(:job) { { 'meta.remote_ip' => ip_address } }
|
let(:job) { { 'ip_address_state' => ip_address } }
|
||||||
let(:queue) { 'queue1' }
|
let(:queue) { 'queue1' }
|
||||||
let(:ip_address) { '1.1.1.1' }
|
let(:ip_address) { '1.1.1.1' }
|
||||||
|
|
||||||
describe '#call' do
|
describe '#call' do
|
||||||
it 'sets the IP address in the context' do
|
it 'sets the IP address based on ip_address_state' do
|
||||||
|
expect(::Gitlab::IpAddressState).to receive(:with).once.and_call_original
|
||||||
|
|
||||||
described_class.new.call(worker, job, queue) do
|
described_class.new.call(worker, job, queue) do
|
||||||
expect(::Gitlab::IpAddressState.current).to eq(ip_address)
|
expect(::Gitlab::IpAddressState.current).to eq(ip_address)
|
||||||
end
|
end
|
||||||
|
|
@ -17,10 +19,26 @@ RSpec.describe Gitlab::SidekiqMiddleware::SetIpAddress, feature_category: :syste
|
||||||
expect(::Gitlab::IpAddressState.current).to eq(nil)
|
expect(::Gitlab::IpAddressState.current).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the IP address is absent' do
|
context 'when the ip_address_state key is absent' do
|
||||||
let(:job) { {} }
|
let(:job) { {} }
|
||||||
|
|
||||||
it 'does not set the IP address' do
|
it 'does not set the IP address' do
|
||||||
|
expect(::Gitlab::IpAddressState).not_to receive(:with).with(ip_address)
|
||||||
|
|
||||||
|
described_class.new.call(worker, job, queue) do
|
||||||
|
expect(::Gitlab::IpAddressState.current).to eq(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(::Gitlab::IpAddressState.current).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ip_address_state value is nil' do
|
||||||
|
let(:job) { { 'ip_address_state' => nil } }
|
||||||
|
|
||||||
|
it 'sets IP address to be nil' do
|
||||||
|
expect(::Gitlab::IpAddressState).to receive(:with).once.and_call_original
|
||||||
|
|
||||||
described_class.new.call(worker, job, queue) do
|
described_class.new.call(worker, job, queue) do
|
||||||
expect(::Gitlab::IpAddressState.current).to eq(nil)
|
expect(::Gitlab::IpAddressState.current).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
@ -37,6 +55,8 @@ RSpec.describe Gitlab::SidekiqMiddleware::SetIpAddress, feature_category: :syste
|
||||||
|
|
||||||
context 'when the IP address is present' do
|
context 'when the IP address is present' do
|
||||||
it 'does not set the IP address' do
|
it 'does not set the IP address' do
|
||||||
|
expect(::Gitlab::IpAddressState).not_to receive(:with).with(ip_address)
|
||||||
|
|
||||||
described_class.new.call(worker, job, queue) do
|
described_class.new.call(worker, job, queue) do
|
||||||
expect(::Gitlab::IpAddressState.current).to eq(nil)
|
expect(::Gitlab::IpAddressState.current).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require_migration!
|
||||||
|
|
||||||
|
RSpec.describe QueueBackfillStatusCheckResponsesProjectId, feature_category: :compliance_management do
|
||||||
|
let!(:batched_migration) { described_class::MIGRATION }
|
||||||
|
|
||||||
|
it 'schedules a new batched migration' do
|
||||||
|
reversible_migration do |migration|
|
||||||
|
migration.before -> {
|
||||||
|
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||||
|
}
|
||||||
|
|
||||||
|
migration.after -> {
|
||||||
|
expect(batched_migration).to have_scheduled_batched_migration(
|
||||||
|
table_name: :status_check_responses,
|
||||||
|
column_name: :id,
|
||||||
|
interval: described_class::DELAY_INTERVAL,
|
||||||
|
batch_size: described_class::BATCH_SIZE,
|
||||||
|
sub_batch_size: described_class::SUB_BATCH_SIZE,
|
||||||
|
gitlab_schema: :gitlab_main_cell,
|
||||||
|
job_arguments: [
|
||||||
|
:project_id,
|
||||||
|
:merge_requests,
|
||||||
|
:target_project_id,
|
||||||
|
:merge_request_id
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -98,6 +98,22 @@ RSpec.describe RuboCop::Formatter::TodoFormatter, feature_category: :tooling do
|
||||||
YAML
|
YAML
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with empty exclusions' do
|
||||||
|
before do
|
||||||
|
todo_dir.write('C/EmptyList', <<~YAML)
|
||||||
|
---
|
||||||
|
C/EmptyList:
|
||||||
|
Exclude:
|
||||||
|
YAML
|
||||||
|
|
||||||
|
todo_dir.inspect_all
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not raise an error' do
|
||||||
|
expect { run_formatter }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with existing HAML exclusions' do
|
context 'with existing HAML exclusions' do
|
||||||
before do
|
before do
|
||||||
todo_dir.write('B/TooManyOffenses', <<~YAML)
|
todo_dir.write('B/TooManyOffenses', <<~YAML)
|
||||||
|
|
|
||||||
|
|
@ -584,6 +584,23 @@ RSpec.describe ApplicationWorker, feature_category: :shared do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.with_ip_address_state' do
|
||||||
|
around do |example|
|
||||||
|
Sidekiq::Testing.fake!(&example)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:ip_address) { '1.1.1.1' }
|
||||||
|
|
||||||
|
it 'sets IP state' do
|
||||||
|
allow(::Gitlab::IpAddressState).to receive(:current).and_return(ip_address)
|
||||||
|
|
||||||
|
worker.with_ip_address_state.perform_async
|
||||||
|
|
||||||
|
expect(Sidekiq::Queues[worker.queue].first).to include('ip_address_state' => ip_address)
|
||||||
|
expect(Sidekiq::Queues[worker.queue].length).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when using perform_async/in/at' do
|
context 'when using perform_async/in/at' do
|
||||||
let(:shard_pool) { 'dummy_pool' }
|
let(:shard_pool) { 'dummy_pool' }
|
||||||
let(:shard_name) { 'shard_name' }
|
let(:shard_name) { 'shard_name' }
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2
|
||||||
github.com/BurntSushi/toml v1.4.0
|
github.com/BurntSushi/toml v1.4.0
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0
|
github.com/alecthomas/chroma/v2 v2.14.0
|
||||||
github.com/aws/aws-sdk-go v1.53.7
|
github.com/aws/aws-sdk-go v1.53.16
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/getsentry/raven-go v0.2.0
|
github.com/getsentry/raven-go v0.2.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
|
|
|
||||||
|
|
@ -96,8 +96,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/aws/aws-sdk-go v1.44.256/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
github.com/aws/aws-sdk-go v1.44.256/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||||
github.com/aws/aws-sdk-go v1.53.7 h1:ZSsRYHLRxsbO2rJR2oPMz0SUkJLnBkN+1meT95B6Ixs=
|
github.com/aws/aws-sdk-go v1.53.16 h1:8oZjKQO/ml1WLUZw5hvF7pvYjPf8o9f57Wldoy/q9Qc=
|
||||||
github.com/aws/aws-sdk-go v1.53.7/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
github.com/aws/aws-sdk-go v1.53.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.25.3 h1:xYiLpZTQs1mzvz5PaI6uR0Wh57ippuEthxS4iK5v0n0=
|
github.com/aws/aws-sdk-go-v2 v1.25.3 h1:xYiLpZTQs1mzvz5PaI6uR0Wh57ippuEthxS4iK5v0n0=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.25.3/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I=
|
github.com/aws/aws-sdk-go-v2 v1.25.3/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU=
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue