Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-06-15 15:09:53 +00:00
parent 717436a767
commit 977720d756
79 changed files with 1475 additions and 417 deletions

View File

@ -1,7 +1,15 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { joinPaths, webIDEUrl } from '~/lib/utils/url_utility';
import WebIdeButton from '~/vue_shared/components/web_ide_link.vue';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
export default ({ el, router }) => {
if (!el) return;
@ -15,6 +23,10 @@ export default ({ el, router }) => {
new Vue({
el,
router,
apolloProvider,
provide: {
projectPath,
},
render(h) {
return h(WebIdeButton, {
props: {

View File

@ -14,9 +14,7 @@ export default {
},
computed: {
widgets() {
return [window.gon?.features?.refactorSecurityExtension && 'MrSecurityWidget'].filter(
(w) => w,
);
return ['MrSecurityWidget'];
},
},
};

View File

@ -98,7 +98,6 @@ export default {
MrWidgetRebase: RebaseState,
SourceBranchRemovalStatus,
MrWidgetApprovals,
SecurityReportsApp: () => import('~/vue_shared/security_reports/security_reports_app.vue'),
MergeChecksFailed: () => import('./components/states/merge_checks_failed.vue'),
ReadyToMerge: ReadyToMergeState,
ReportWidgetContainer,
@ -239,9 +238,6 @@ export default {
this.mr.mergePipelinesEnabled && this.mr.sourceProjectId !== this.mr.targetProjectId,
);
},
shouldRenderSecurityReport() {
return Boolean(this.mr?.pipeline?.id);
},
shouldRenderTerraformPlans() {
return Boolean(this.mr?.terraformReportsPath);
},
@ -275,9 +271,6 @@ export default {
hasAlerts() {
return this.hasMergeError || this.showMergePipelineForkWarning;
},
shouldShowSecurityExtension() {
return window.gon?.features?.refactorSecurityExtension;
},
shouldShowMergeDetails() {
if (this.mr.state === 'readyToMerge') return true;
@ -601,15 +594,7 @@ export default {
<mr-widget-approvals v-if="shouldRenderApprovals" :mr="mr" :service="service" />
<report-widget-container>
<extensions-container v-if="hasExtensions" :mr="mr" />
<widget-container v-if="mr && shouldShowSecurityExtension" :mr="mr" />
<security-reports-app
v-if="shouldRenderSecurityReport && !shouldShowSecurityExtension"
:pipeline-id="mr.pipeline.id"
:project-id="mr.sourceProjectId"
:security-reports-docs-path="mr.securityReportsDocsPath"
:target-project-full-path="mr.targetProjectFullPath"
:mr-iid="mr.iid"
/>
<widget-container :mr="mr" />
</report-widget-container>
<div class="mr-section-container mr-widget-workflow">
<div v-if="hasAlerts" class="gl-overflow-hidden mr-widget-alert-container">

View File

@ -1,68 +0,0 @@
<script>
import { GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
export const i18n = {
btnText: __('Fork project'),
title: __('Fork project?'),
message: __(
'You cant edit files directly in this project. Fork this project and submit a merge request with your changes.',
),
};
export default {
name: 'ConfirmForkModal',
components: {
GlModal,
},
model: {
prop: 'visible',
event: 'change',
},
props: {
visible: {
type: Boolean,
required: false,
default: false,
},
modalId: {
type: String,
required: true,
},
forkPath: {
type: String,
required: true,
},
},
computed: {
btnActions() {
return {
cancel: { text: __('Cancel') },
primary: {
text: this.$options.i18n.btnText,
attributes: {
href: this.forkPath,
variant: 'confirm',
'data-qa-selector': 'fork_project_button',
'data-method': 'post',
},
},
};
},
},
i18n,
};
</script>
<template>
<gl-modal
:visible="visible"
data-qa-selector="confirm_fork_modal"
:modal-id="modalId"
:title="$options.i18n.title"
:action-primary="btnActions.primary"
:action-cancel="btnActions.cancel"
@change="$emit('change', $event)"
>
<p>{{ $options.i18n.message }}</p>
</gl-modal>
</template>

View File

@ -0,0 +1,114 @@
<script>
import { GlModal, GlLoadingIcon, GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
import getWritableForksQuery from './get_writable_forks.query.graphql';
export const i18n = {
btnText: __('Create a new fork'),
title: __('Fork project?'),
message: __('You cant edit files directly in this project.'),
existingForksMessage: __(
'To submit your changes in a merge request, switch to one of these forks or create a new fork.',
),
newForkMessage: __('To submit your changes in a merge request, create a new fork.'),
};
export default {
name: 'ConfirmForkModal',
components: {
GlModal,
GlLoadingIcon,
GlLink,
},
inject: {
projectPath: {
default: '',
},
},
model: {
prop: 'visible',
event: 'change',
},
props: {
visible: {
type: Boolean,
required: false,
default: false,
},
modalId: {
type: String,
required: true,
},
forkPath: {
type: String,
required: true,
},
},
data() {
return {
forks: [],
};
},
apollo: {
forks: {
query: getWritableForksQuery,
variables() {
return {
projectPath: this.projectPath,
};
},
update({ project } = {}) {
return project?.visibleForks?.nodes.map((node) => {
return {
text: node.fullPath,
href: node.webUrl,
};
});
},
},
},
computed: {
isLoading() {
return this.$apollo.queries.forks.loading;
},
hasWritableForks() {
return this.forks.length;
},
btnActions() {
return {
cancel: { text: __('Cancel') },
primary: {
text: this.$options.i18n.btnText,
attributes: {
href: this.forkPath,
variant: 'confirm',
'data-qa-selector': 'fork_project_button',
},
},
};
},
},
i18n,
};
</script>
<template>
<gl-modal
:visible="visible"
data-qa-selector="confirm_fork_modal"
:modal-id="modalId"
:title="$options.i18n.title"
:action-primary="btnActions.primary"
:action-cancel="btnActions.cancel"
@change="$emit('change', $event)"
>
<p>{{ $options.i18n.message }}</p>
<gl-loading-icon v-if="isLoading" />
<template v-else-if="hasWritableForks">
<p>{{ $options.i18n.existingForksMessage }}</p>
<div v-for="fork in forks" :key="fork.text">
<gl-link :href="fork.href">{{ fork.text }}</gl-link>
</div>
</template>
<p v-else>{{ $options.i18n.newForkMessage }}</p>
</gl-modal>
</template>

View File

@ -0,0 +1,12 @@
query getWritableForks($projectPath: ID!) {
project(fullPath: $projectPath) {
id
visibleForks(minimumAccessLevel: DEVELOPER) {
nodes {
id
fullPath
webUrl
}
}
}
}

View File

@ -3,7 +3,7 @@ import { GlModal, GlSprintf, GlLink } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import ActionsButton from '~/vue_shared/components/actions_button.vue';
import ConfirmForkModal from '~/vue_shared/components/confirm_fork_modal.vue';
import ConfirmForkModal from '~/vue_shared/components/web_ide/confirm_fork_modal.vue';
import { KEY_EDIT, KEY_WEB_IDE, KEY_GITPOD, KEY_PIPELINE_EDITOR } from './constants';
export const i18n = {
@ -152,9 +152,7 @@ export default {
return this.actions.length > 0;
},
editAction() {
if (!this.showEditButton) {
return null;
}
if (!this.showEditButton) return null;
const handleOptions = this.needsToFork
? {
@ -194,9 +192,7 @@ export default {
return __('Web IDE');
},
webIdeAction() {
if (!this.showWebIdeButton) {
return null;
}
if (!this.showWebIdeButton) return null;
const handleOptions = this.needsToFork
? {
@ -298,6 +294,12 @@ export default {
},
};
},
mountForkModal() {
const { disableForkModal, showWebIdeButton, showEditButton } = this;
if (disableForkModal) return false;
return showWebIdeButton || showEditButton;
},
},
methods: {
showModal(dataKey) {
@ -330,7 +332,7 @@ export default {
</gl-sprintf>
</gl-modal>
<confirm-fork-modal
v-if="showWebIdeButton || showEditButton"
v-if="mountForkModal"
v-model="showForkModal"
:modal-id="forkModalId"
:fork-path="forkPath"

View File

@ -20,9 +20,7 @@ module SearchRateLimitable
def safe_search_scope
# Sometimes search scope can have abusive length or invalid keyword. We don't want
# to send those to redis for rate limit checks, so we guard against that here.
return if Feature.disabled?(:search_rate_limited_scopes) || abuse_detected?
params[:scope]
params[:scope] unless abuse_detected?
end
def abuse_detected?

View File

@ -41,7 +41,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_force_frontend_feature_flag(:content_editor_on_issues, project&.content_editor_on_issues_feature_flag_enabled?)
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
push_frontend_feature_flag(:issue_assignees_widget, @project)
push_frontend_feature_flag(:refactor_security_extension, @project)
push_frontend_feature_flag(:deprecate_vulnerabilities_feedback, @project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
push_frontend_feature_flag(:mr_experience_survey, project)

View File

@ -162,6 +162,41 @@ module Types
extras: [:lookahead],
resolver: ::Resolvers::Achievements::UserAchievementsResolver
field :bio,
type: ::GraphQL::Types::String,
null: true,
description: 'Bio of the user.'
field :linkedin,
type: ::GraphQL::Types::String,
null: true,
description: 'LinkedIn profile name of the user.'
field :twitter,
type: ::GraphQL::Types::String,
null: true,
description: 'Twitter username of the user.'
field :discord,
type: ::GraphQL::Types::String,
null: true,
description: 'Discord ID of the user.'
field :organization,
type: ::GraphQL::Types::String,
null: true,
description: 'Who the user represents or works for.'
field :job_title,
type: ::GraphQL::Types::String,
null: true,
description: 'Job title of the user.'
field :created_at,
type: Types::TimeType,
null: true,
description: 'Timestamp of when the user was created.'
definition_methods do
def resolve_type(object, context)
# in the absence of other information, we cannot tell - just default to

View File

@ -157,17 +157,15 @@ module TreeHelper
}
end
def fork_modal_options(project, ref, path, blob)
def fork_modal_options(project, blob)
if show_edit_button?({ blob: blob })
fork_path = fork_and_edit_path(project, ref, path)
fork_modal_id = "modal-confirm-fork-edit"
elsif show_web_ide_button?
fork_path = ide_fork_and_edit_path(project, ref, path)
fork_modal_id = "modal-confirm-fork-webide"
end
{
fork_path: fork_path,
fork_path: new_namespace_project_fork_path(project_id: project.path, namespace_id: project.namespace.full_path),
fork_modal_id: fork_modal_id
}
end

View File

@ -6,6 +6,9 @@ class PlanLimits < ApplicationRecord
ignore_column :web_hook_calls_high, remove_with: '15.10', remove_after: '2022-02-22'
ignore_column :ci_active_pipelines, remove_with: '16.3', remove_after: '2022-07-22'
attribute :limits_history, :ind_jsonb, default: -> { {} }
validates :limits_history, json_schema: { filename: 'plan_limits_history' }
LimitUndefinedError = Class.new(StandardError)
belongs_to :plan
@ -46,6 +49,34 @@ class PlanLimits < ApplicationRecord
def dashboard_storage_limit_enabled?
false
end
def log_limits_changes(user, new_limits)
new_limits.each do |attribute, value|
limits_history[attribute] ||= []
limits_history[attribute] << {
user_id: user&.id,
username: user&.username,
timestamp: Time.current.utc.to_i,
value: value
}
end
update(limits_history: limits_history)
end
def limit_attribute_changes(attribute)
limit_history = limits_history[attribute]
return [] unless limit_history
limit_history.map do |entry|
{
timestamp: entry[:timestamp],
value: entry[:value],
username: entry[:username],
user_id: entry[:user_id]
}
end
end
end
PlanLimits.prepend_mod_with('PlanLimits')

View File

@ -5,6 +5,7 @@ class UserDetail < ApplicationRecord
extend ::Gitlab::Utils::Override
ignore_column :requires_credit_card_verification, remove_with: '16.1', remove_after: '2023-06-22'
ignore_column :provisioned_by_group_at, remove_with: '16.3', remove_after: '2023-07-22'
REGISTRATION_OBJECTIVE_PAIRS = { basics: 0, move_repository: 1, code_storage: 2, exploring: 3, ci: 4, other: 5, joining_team: 6 }.freeze

View File

@ -9,12 +9,6 @@ class Vulnerability < ApplicationRecord
scope :with_projects, -> { includes(:project) }
# Policy class inferring logic is causing performance
# issues therefore we need to explicitly set it.
def self.declarative_policy_class
:VulnerabilityPolicy
end
def self.link_reference_pattern
nil
end

View File

@ -0,0 +1,115 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"enforcement_limit": {
"type": "array",
"items": {
"type": "object",
"properties": {
"user_id": {
"type": "integer"
},
"username": {
"type": "string"
},
"timestamp": {
"type": "integer"
},
"value": {
"type": "integer"
}
},
"additionalProperties": false,
"required": [
"user_id",
"username",
"timestamp",
"value"
]
}
},
"notification_limit": {
"type": "array",
"items": {
"type": "object",
"properties": {
"user_id": {
"type": "integer"
},
"username": {
"type": "string"
},
"timestamp": {
"type": "integer"
},
"value": {
"type": "integer"
}
},
"additionalProperties": false,
"required": [
"user_id",
"username",
"timestamp",
"value"
]
}
},
"storage_size_limit": {
"type": "array",
"items": {
"type": "object",
"properties": {
"user_id": {
"type": "integer"
},
"username": {
"type": "string"
},
"timestamp": {
"type": "integer"
},
"value": {
"type": "integer"
}
},
"additionalProperties": false,
"required": [
"user_id",
"username",
"timestamp",
"value"
]
}
},
"dashboard_limit_enabled_at": {
"type": "array",
"items": {
"type": "object",
"properties": {
"user_id": {
"type": "integer"
},
"username": {
"type": "string"
},
"timestamp": {
"type": "integer"
},
"value": {
"type": "integer"
}
},
"additionalProperties": false,
"required": [
"user_id",
"username",
"timestamp",
"value"
]
}
}
},
"additionalProperties": false
}

View File

@ -11,16 +11,16 @@
= f.label :raw_blob_request_limit, _('Raw blob request rate limit per minute'), class: 'label-bold'
= f.number_field :raw_blob_request_limit, class: 'form-control gl-form-input'
.form-text.text-muted
= _('Maximum number of requests per minute for each raw path (default is 300). Set to 0 to disable throttling.')
= _('Maximum number of requests per minute for each raw path (default is `300`). Set to `0` to disable throttling.')
.form-group
= f.label :push_event_hooks_limit, class: 'label-bold'
= f.number_field :push_event_hooks_limit, class: 'form-control gl-form-input'
.form-text.text-muted
= _('Maximum number of changes (branches or tags) in a single push for which webhooks and services trigger (default is 3).')
= _('Maximum number of changes (branches or tags) in a single push above which webhooks and integrations are not triggered (default is `3`). Setting to `0` does not disable throttling.')
.form-group
= f.label :push_event_activities_limit, class: 'label-bold'
= f.number_field :push_event_activities_limit, class: 'form-control gl-form-input'
.form-text.text-muted
= _('Threshold number of changes (branches or tags) in a single push above which a bulk push event is created (default is 3).')
= _('Maximum number of changes (branches or tags) in a single push above which a bulk push event is created (default is `3`). Setting to `0` does not disable throttling.')
= f.submit _('Save changes'), pajamas_button: true

View File

@ -1,5 +1,5 @@
- type = blob ? 'blob' : 'tree'
- button_data = web_ide_button_data({ blob: blob })
- fork_options = fork_modal_options(@project, @ref, @path, blob)
- fork_options = fork_modal_options(@project, blob)
.gl-display-inline-block{ data: { options: button_data.merge(fork_options).to_json, web_ide_promo_popover_img: image_path('web-ide-promo-popover.svg') }, id: "js-#{type}-web-ide-link" }

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/413684
milestone: '16.1'
type: development
group: group::tenant scale
default_enabled: false
default_enabled: true

View File

@ -1,8 +1,8 @@
---
name: search_rate_limited_scopes
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118525
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/408521
milestone: '16.0'
name: purchase_code_suggestions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123382
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/415047
milestone: '16.1'
type: development
group: group::global search
group: group::provision
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: refactor_security_extension
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84896
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365320
milestone: '14.10'
type: development
group: group::threat insights
default_enabled: true

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/410121
milestone: '16.0'
type: development
group: group::foundations
default_enabled: false
default_enabled: true

View File

@ -2,6 +2,31 @@
require 'declarative_policy'
# This module speeds up class resolution by caching it.
#
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119924
# See https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/issues/30
module ClassForClassCache
def self.prepended(base)
class << base
attr_accessor :class_for_class_cache
end
base.class_for_class_cache = {}
base.singleton_class.prepend(SingletonClassMethods)
end
module SingletonClassMethods
def class_for_class(subject_class)
class_for_class_cache.fetch(subject_class) do
class_for_class_cache[subject_class] = super
end
end
end
end
DeclarativePolicy.configure do
named_policy :global, ::GlobalPolicy
end
DeclarativePolicy.prepend(ClassForClassCache)

View File

@ -4,6 +4,7 @@ gitlab_schemas:
- gitlab_internal
- gitlab_shared
- gitlab_main
- gitlab_main_cell
- gitlab_pm
# Note that we use ActiveRecord::Base here and not ApplicationRecord.
# This is deliberate, as:

View File

@ -10,4 +10,4 @@ feature_categories:
description: Storing namespaces records for groups, users and projects
introduced_by_url: https://github.com/gitlabhq/gitlabhq/pull/2051
milestone: "<6.0"
gitlab_schema: gitlab_main
gitlab_schema: gitlab_main_cell

View File

@ -9,4 +9,4 @@ feature_categories:
description: TODO
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/9ba1224867665844b117fa037e1465bb706b3685
milestone: "<6.0"
gitlab_schema: gitlab_main
gitlab_schema: gitlab_main_clusterwide

View File

@ -1,2 +1,7 @@
name: gitlab_ci
description: Schema for all Cell-local CI tables, ex. ci_builds, etc.
allow_cross_joins:
- gitlab_shared
allow_cross_transactions:
- gitlab_internal
- gitlab_shared

View File

@ -1,2 +1,7 @@
name: gitlab_main
description: Schema for all Cell-local tables, ex. projects, issues, etc.
description: Legacy schema for all Cell-local tables, ex. projects, issues, etc.
allow_cross_joins:
- gitlab_shared
allow_cross_transactions:
- gitlab_internal
- gitlab_shared

View File

@ -0,0 +1,21 @@
name: gitlab_main_cell
description: Schema for all Cell-local tables, ex. namespaces, projects, etc.
allow_cross_joins:
- gitlab_shared
- gitlab_main
# Temporarily allow cross-joins between clusterwide and cell schemas
# This is to be removed once we annotate all cross-joins between those
- gitlab_main_clusterwide
allow_cross_transactions:
- gitlab_internal
- gitlab_shared
- gitlab_main
# Temporarily allow cross-DB transactions between clusterwide and cell schemas
# This is to be removed once we annotate all cross-DBs between those
- gitlab_main_clusterwide
allow_cross_foreign_keys:
- gitlab_main
# Temporarily allow FKs between clusterwide and cell schemas
# This is to be removed once we remove all FKs between those
- gitlab_main_clusterwide

View File

@ -1,2 +1,17 @@
name: gitlab_main_clusterwide
description: Schema for all Cluster-wide tables, ex. application_settings, etc.
allow_cross_joins:
- gitlab_shared
# temporarily allow cross-joins between clusterwide till all tables
# are moved to either _clusterwide or _cell
- gitlab_main
allow_cross_transactions:
- gitlab_internal
- gitlab_shared
# temporarily allow cross-transaction between clusterwide till all tables
# are moved to either _clusterwide or _cell
- gitlab_main
allow_cross_foreign_keys:
# temporarily allow FKs between clusterwide till all tables
# are moved to either _clusterwide or _cell
- gitlab_main

View File

@ -1,2 +1,7 @@
name: gitlab_pm
description: Schema for all Cell-local package management features.
allow_cross_joins:
- gitlab_shared
allow_cross_transactions:
- gitlab_internal
- gitlab_shared

View File

@ -2,3 +2,7 @@ name: gitlab_shared
description:
Schema for all tables implementing shared features,
ex. loose foreign keys, re-indexing, etc.
allow_cross_joins:
- gitlab_internal
allow_cross_transactions:
- gitlab_internal

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddLimitsHistoryToPlanLimits < Gitlab::Database::Migration[2.1]
def change
add_column :plan_limits, :limits_history, :jsonb, default: {}, null: false
end
end

View File

@ -0,0 +1 @@
056459290c9fdced219b524418237e183540aa1aa97c9e3aeab637a005a04bea

View File

@ -20132,7 +20132,8 @@ CREATE TABLE plan_limits (
web_hook_calls integer DEFAULT 0 NOT NULL,
project_access_token_limit integer DEFAULT 0 NOT NULL,
google_cloud_logging_configurations integer DEFAULT 5 NOT NULL,
ml_model_max_file_size bigint DEFAULT '10737418240'::bigint NOT NULL
ml_model_max_file_size bigint DEFAULT '10737418240'::bigint NOT NULL,
limits_history jsonb DEFAULT '{}'::jsonb NOT NULL
);
CREATE SEQUENCE plan_limits_id_seq

View File

@ -17668,20 +17668,26 @@ A user assigned to a merge request.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestassigneeavatarurl"></a>`avatarUrl` | [`String`](#string) | URL of the user's avatar. |
| <a id="mergerequestassigneebio"></a>`bio` | [`String`](#string) | Bio of the user. |
| <a id="mergerequestassigneebot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
| <a id="mergerequestassigneecallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
| <a id="mergerequestassigneecommitemail"></a>`commitEmail` | [`String`](#string) | User's default commit email. |
| <a id="mergerequestassigneecreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the user was created. |
| <a id="mergerequestassigneediscord"></a>`discord` | [`String`](#string) | Discord ID of the user. |
| <a id="mergerequestassigneeemail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
| <a id="mergerequestassigneeemails"></a>`emails` | [`EmailConnection`](#emailconnection) | User's email addresses. (see [Connections](#connections)) |
| <a id="mergerequestassigneegitpodenabled"></a>`gitpodEnabled` | [`Boolean`](#boolean) | Whether Gitpod is enabled at the user level. |
| <a id="mergerequestassigneegroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="mergerequestassigneegroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="mergerequestassigneeid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="mergerequestassigneejobtitle"></a>`jobTitle` | [`String`](#string) | Job title of the user. |
| <a id="mergerequestassigneelinkedin"></a>`linkedin` | [`String`](#string) | LinkedIn profile name of the user. |
| <a id="mergerequestassigneelocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="mergerequestassigneemergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
| <a id="mergerequestassigneename"></a>`name` | [`String!`](#string) | Human-readable name of the user. Returns `****` if the user is a project bot and the requester does not have permission to view the project. |
| <a id="mergerequestassigneenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestassigneenamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="mergerequestassigneeorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
| <a id="mergerequestassigneepreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="mergerequestassigneeprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="mergerequestassigneeprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@ -17689,6 +17695,7 @@ A user assigned to a merge request.
| <a id="mergerequestassigneesavedreplies"></a>`savedReplies` | [`SavedReplyConnection`](#savedreplyconnection) | Saved replies authored by the user. Will not return saved replies if `saved_replies` feature flag is disabled. (see [Connections](#connections)) |
| <a id="mergerequestassigneestate"></a>`state` | [`UserState!`](#userstate) | State of the user. |
| <a id="mergerequestassigneestatus"></a>`status` | [`UserStatus`](#userstatus) | User status. |
| <a id="mergerequestassigneetwitter"></a>`twitter` | [`String`](#string) | Twitter username of the user. |
| <a id="mergerequestassigneeuserachievements"></a>`userAchievements` **{warning-solid}** | [`UserAchievementConnection`](#userachievementconnection) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Achievements for the user. Only returns for namespaces where the `achievements` feature flag is enabled. |
| <a id="mergerequestassigneeuserpermissions"></a>`userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. |
| <a id="mergerequestassigneeusername"></a>`username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. |
@ -17934,20 +17941,26 @@ The author of the merge request.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestauthoravatarurl"></a>`avatarUrl` | [`String`](#string) | URL of the user's avatar. |
| <a id="mergerequestauthorbio"></a>`bio` | [`String`](#string) | Bio of the user. |
| <a id="mergerequestauthorbot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
| <a id="mergerequestauthorcallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
| <a id="mergerequestauthorcommitemail"></a>`commitEmail` | [`String`](#string) | User's default commit email. |
| <a id="mergerequestauthorcreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the user was created. |
| <a id="mergerequestauthordiscord"></a>`discord` | [`String`](#string) | Discord ID of the user. |
| <a id="mergerequestauthoremail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
| <a id="mergerequestauthoremails"></a>`emails` | [`EmailConnection`](#emailconnection) | User's email addresses. (see [Connections](#connections)) |
| <a id="mergerequestauthorgitpodenabled"></a>`gitpodEnabled` | [`Boolean`](#boolean) | Whether Gitpod is enabled at the user level. |
| <a id="mergerequestauthorgroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="mergerequestauthorgroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="mergerequestauthorid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="mergerequestauthorjobtitle"></a>`jobTitle` | [`String`](#string) | Job title of the user. |
| <a id="mergerequestauthorlinkedin"></a>`linkedin` | [`String`](#string) | LinkedIn profile name of the user. |
| <a id="mergerequestauthorlocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="mergerequestauthormergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
| <a id="mergerequestauthorname"></a>`name` | [`String!`](#string) | Human-readable name of the user. Returns `****` if the user is a project bot and the requester does not have permission to view the project. |
| <a id="mergerequestauthornamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestauthornamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="mergerequestauthororganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
| <a id="mergerequestauthorpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="mergerequestauthorprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="mergerequestauthorprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@ -17955,6 +17968,7 @@ The author of the merge request.
| <a id="mergerequestauthorsavedreplies"></a>`savedReplies` | [`SavedReplyConnection`](#savedreplyconnection) | Saved replies authored by the user. Will not return saved replies if `saved_replies` feature flag is disabled. (see [Connections](#connections)) |
| <a id="mergerequestauthorstate"></a>`state` | [`UserState!`](#userstate) | State of the user. |
| <a id="mergerequestauthorstatus"></a>`status` | [`UserStatus`](#userstatus) | User status. |
| <a id="mergerequestauthortwitter"></a>`twitter` | [`String`](#string) | Twitter username of the user. |
| <a id="mergerequestauthoruserachievements"></a>`userAchievements` **{warning-solid}** | [`UserAchievementConnection`](#userachievementconnection) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Achievements for the user. Only returns for namespaces where the `achievements` feature flag is enabled. |
| <a id="mergerequestauthoruserpermissions"></a>`userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. |
| <a id="mergerequestauthorusername"></a>`username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. |
@ -18234,20 +18248,26 @@ A user participating in a merge request.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestparticipantavatarurl"></a>`avatarUrl` | [`String`](#string) | URL of the user's avatar. |
| <a id="mergerequestparticipantbio"></a>`bio` | [`String`](#string) | Bio of the user. |
| <a id="mergerequestparticipantbot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
| <a id="mergerequestparticipantcallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
| <a id="mergerequestparticipantcommitemail"></a>`commitEmail` | [`String`](#string) | User's default commit email. |
| <a id="mergerequestparticipantcreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the user was created. |
| <a id="mergerequestparticipantdiscord"></a>`discord` | [`String`](#string) | Discord ID of the user. |
| <a id="mergerequestparticipantemail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
| <a id="mergerequestparticipantemails"></a>`emails` | [`EmailConnection`](#emailconnection) | User's email addresses. (see [Connections](#connections)) |
| <a id="mergerequestparticipantgitpodenabled"></a>`gitpodEnabled` | [`Boolean`](#boolean) | Whether Gitpod is enabled at the user level. |
| <a id="mergerequestparticipantgroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="mergerequestparticipantgroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="mergerequestparticipantid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="mergerequestparticipantjobtitle"></a>`jobTitle` | [`String`](#string) | Job title of the user. |
| <a id="mergerequestparticipantlinkedin"></a>`linkedin` | [`String`](#string) | LinkedIn profile name of the user. |
| <a id="mergerequestparticipantlocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="mergerequestparticipantmergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
| <a id="mergerequestparticipantname"></a>`name` | [`String!`](#string) | Human-readable name of the user. Returns `****` if the user is a project bot and the requester does not have permission to view the project. |
| <a id="mergerequestparticipantnamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestparticipantnamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="mergerequestparticipantorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
| <a id="mergerequestparticipantpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="mergerequestparticipantprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="mergerequestparticipantprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@ -18255,6 +18275,7 @@ A user participating in a merge request.
| <a id="mergerequestparticipantsavedreplies"></a>`savedReplies` | [`SavedReplyConnection`](#savedreplyconnection) | Saved replies authored by the user. Will not return saved replies if `saved_replies` feature flag is disabled. (see [Connections](#connections)) |
| <a id="mergerequestparticipantstate"></a>`state` | [`UserState!`](#userstate) | State of the user. |
| <a id="mergerequestparticipantstatus"></a>`status` | [`UserStatus`](#userstatus) | User status. |
| <a id="mergerequestparticipanttwitter"></a>`twitter` | [`String`](#string) | Twitter username of the user. |
| <a id="mergerequestparticipantuserachievements"></a>`userAchievements` **{warning-solid}** | [`UserAchievementConnection`](#userachievementconnection) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Achievements for the user. Only returns for namespaces where the `achievements` feature flag is enabled. |
| <a id="mergerequestparticipantuserpermissions"></a>`userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. |
| <a id="mergerequestparticipantusername"></a>`username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. |
@ -18519,20 +18540,26 @@ A user assigned to a merge request as a reviewer.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestrevieweravatarurl"></a>`avatarUrl` | [`String`](#string) | URL of the user's avatar. |
| <a id="mergerequestreviewerbio"></a>`bio` | [`String`](#string) | Bio of the user. |
| <a id="mergerequestreviewerbot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
| <a id="mergerequestreviewercallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
| <a id="mergerequestreviewercommitemail"></a>`commitEmail` | [`String`](#string) | User's default commit email. |
| <a id="mergerequestreviewercreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the user was created. |
| <a id="mergerequestreviewerdiscord"></a>`discord` | [`String`](#string) | Discord ID of the user. |
| <a id="mergerequestrevieweremail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
| <a id="mergerequestrevieweremails"></a>`emails` | [`EmailConnection`](#emailconnection) | User's email addresses. (see [Connections](#connections)) |
| <a id="mergerequestreviewergitpodenabled"></a>`gitpodEnabled` | [`Boolean`](#boolean) | Whether Gitpod is enabled at the user level. |
| <a id="mergerequestreviewergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="mergerequestreviewergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="mergerequestreviewerid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="mergerequestreviewerjobtitle"></a>`jobTitle` | [`String`](#string) | Job title of the user. |
| <a id="mergerequestreviewerlinkedin"></a>`linkedin` | [`String`](#string) | LinkedIn profile name of the user. |
| <a id="mergerequestreviewerlocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="mergerequestreviewermergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
| <a id="mergerequestreviewername"></a>`name` | [`String!`](#string) | Human-readable name of the user. Returns `****` if the user is a project bot and the requester does not have permission to view the project. |
| <a id="mergerequestreviewernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="mergerequestreviewernamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="mergerequestreviewerorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
| <a id="mergerequestreviewerpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="mergerequestreviewerprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="mergerequestreviewerprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@ -18540,6 +18567,7 @@ A user assigned to a merge request as a reviewer.
| <a id="mergerequestreviewersavedreplies"></a>`savedReplies` | [`SavedReplyConnection`](#savedreplyconnection) | Saved replies authored by the user. Will not return saved replies if `saved_replies` feature flag is disabled. (see [Connections](#connections)) |
| <a id="mergerequestreviewerstate"></a>`state` | [`UserState!`](#userstate) | State of the user. |
| <a id="mergerequestreviewerstatus"></a>`status` | [`UserStatus`](#userstatus) | User status. |
| <a id="mergerequestreviewertwitter"></a>`twitter` | [`String`](#string) | Twitter username of the user. |
| <a id="mergerequestrevieweruserachievements"></a>`userAchievements` **{warning-solid}** | [`UserAchievementConnection`](#userachievementconnection) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Achievements for the user. Only returns for namespaces where the `achievements` feature flag is enabled. |
| <a id="mergerequestrevieweruserpermissions"></a>`userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. |
| <a id="mergerequestreviewerusername"></a>`username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. |
@ -23026,19 +23054,25 @@ Core represention of a GitLab user.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="usercoreavatarurl"></a>`avatarUrl` | [`String`](#string) | URL of the user's avatar. |
| <a id="usercorebio"></a>`bio` | [`String`](#string) | Bio of the user. |
| <a id="usercorebot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
| <a id="usercorecallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
| <a id="usercorecommitemail"></a>`commitEmail` | [`String`](#string) | User's default commit email. |
| <a id="usercorecreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the user was created. |
| <a id="usercorediscord"></a>`discord` | [`String`](#string) | Discord ID of the user. |
| <a id="usercoreemail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
| <a id="usercoreemails"></a>`emails` | [`EmailConnection`](#emailconnection) | User's email addresses. (see [Connections](#connections)) |
| <a id="usercoregitpodenabled"></a>`gitpodEnabled` | [`Boolean`](#boolean) | Whether Gitpod is enabled at the user level. |
| <a id="usercoregroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="usercoregroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="usercoreid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="usercorejobtitle"></a>`jobTitle` | [`String`](#string) | Job title of the user. |
| <a id="usercorelinkedin"></a>`linkedin` | [`String`](#string) | LinkedIn profile name of the user. |
| <a id="usercorelocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="usercorename"></a>`name` | [`String!`](#string) | Human-readable name of the user. Returns `****` if the user is a project bot and the requester does not have permission to view the project. |
| <a id="usercorenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="usercorenamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="usercoreorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
| <a id="usercorepreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="usercoreprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="usercoreprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@ -23046,6 +23080,7 @@ Core represention of a GitLab user.
| <a id="usercoresavedreplies"></a>`savedReplies` | [`SavedReplyConnection`](#savedreplyconnection) | Saved replies authored by the user. Will not return saved replies if `saved_replies` feature flag is disabled. (see [Connections](#connections)) |
| <a id="usercorestate"></a>`state` | [`UserState!`](#userstate) | State of the user. |
| <a id="usercorestatus"></a>`status` | [`UserStatus`](#userstatus) | User status. |
| <a id="usercoretwitter"></a>`twitter` | [`String`](#string) | Twitter username of the user. |
| <a id="usercoreuserachievements"></a>`userAchievements` **{warning-solid}** | [`UserAchievementConnection`](#userachievementconnection) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Achievements for the user. Only returns for namespaces where the `achievements` feature flag is enabled. |
| <a id="usercoreuserpermissions"></a>`userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. |
| <a id="usercoreusername"></a>`username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. |
@ -28038,19 +28073,25 @@ Implementations:
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="useravatarurl"></a>`avatarUrl` | [`String`](#string) | URL of the user's avatar. |
| <a id="userbio"></a>`bio` | [`String`](#string) | Bio of the user. |
| <a id="userbot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
| <a id="usercallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
| <a id="usercommitemail"></a>`commitEmail` | [`String`](#string) | User's default commit email. |
| <a id="usercreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the user was created. |
| <a id="userdiscord"></a>`discord` | [`String`](#string) | Discord ID of the user. |
| <a id="useremail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
| <a id="useremails"></a>`emails` | [`EmailConnection`](#emailconnection) | User's email addresses. (see [Connections](#connections)) |
| <a id="usergitpodenabled"></a>`gitpodEnabled` | [`Boolean`](#boolean) | Whether Gitpod is enabled at the user level. |
| <a id="usergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="usergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="userid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="userjobtitle"></a>`jobTitle` | [`String`](#string) | Job title of the user. |
| <a id="userlinkedin"></a>`linkedin` | [`String`](#string) | LinkedIn profile name of the user. |
| <a id="userlocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="username"></a>`name` | [`String!`](#string) | Human-readable name of the user. Returns `****` if the user is a project bot and the requester does not have permission to view the project. |
| <a id="usernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="usernamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="userorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
| <a id="userpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="userprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="userprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
@ -28058,6 +28099,7 @@ Implementations:
| <a id="usersavedreplies"></a>`savedReplies` | [`SavedReplyConnection`](#savedreplyconnection) | Saved replies authored by the user. Will not return saved replies if `saved_replies` feature flag is disabled. (see [Connections](#connections)) |
| <a id="userstate"></a>`state` | [`UserState!`](#userstate) | State of the user. |
| <a id="userstatus"></a>`status` | [`UserStatus`](#userstatus) | User status. |
| <a id="usertwitter"></a>`twitter` | [`String`](#string) | Twitter username of the user. |
| <a id="useruserachievements"></a>`userAchievements` **{warning-solid}** | [`UserAchievementConnection`](#userachievementconnection) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Achievements for the user. Only returns for namespaces where the `achievements` feature flag is enabled. |
| <a id="useruserpermissions"></a>`userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. |
| <a id="userusername"></a>`username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. |

View File

@ -461,10 +461,10 @@ listed in the descriptions of the relevant settings.
| `projects_api_rate_limit_unauthenticated` | integer | no | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112283) in GitLab 15.10. Max number of requests per 10 minutes per IP address for unauthenticated requests to the [list all projects API](projects.md#list-all-projects). Default: 400. To disable throttling set to 0.|
| `prometheus_metrics_enabled` | boolean | no | Enable Prometheus metrics. |
| `protected_ci_variables` | boolean | no | CI/CD variables are protected by default. |
| `push_event_activities_limit` | integer | no | Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push events are created. [Bulk push events are created](../user/admin_area/settings/push_event_activities_limit.md) if it surpasses that value. |
| `push_event_hooks_limit` | integer | no | Number of changes (branches or tags) in a single push to determine whether webhooks and services fire or not. Webhooks and services aren't submitted if it surpasses that value. |
| `push_event_activities_limit` | integer | no | Maximum number of changes (branches or tags) in a single push above which a [bulk push event is created](../user/admin_area/settings/push_event_activities_limit.md). Setting to `0` does not disable throttling. |
| `push_event_hooks_limit` | integer | no | Maximum number of changes (branches or tags) in a single push above which webhooks and integrations are not triggered. Setting to `0` does not disable throttling. |
| `rate_limiting_response_text` | string | no | When rate limiting is enabled via the `throttle_*` settings, send this plain text response when a rate limit is exceeded. 'Retry later' is sent if this is blank. |
| `raw_blob_request_limit` | integer | no | Max number of requests per minute for each raw path. Default: 300. To disable throttling set to 0.|
| `raw_blob_request_limit` | integer | no | Maximum number of requests per minute for each raw path (default is `300`). Set to `0` to disable throttling.|
| `user_email_lookup_limit` | integer | no | **{warning}** **[Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80631/)** in GitLab 14.9 will be removed in 15.0. Replaced by `search_rate_limit`. Max number of requests per minute for email lookup. Default: 60. To disable throttling set to 0.|
| `search_rate_limit` | integer | no | Max number of requests per minute for performing a search while authenticated. Default: 30. To disable throttling set to 0.|
| `search_rate_limit_unauthenticated` | integer | no | Max number of requests per minute for performing a search while unauthenticated. Default: 10. To disable throttling set to 0.|

View File

@ -414,8 +414,9 @@ scope.
| Component | Milestone | Changes |
|------------------|----------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| GitLab Rails app | `%16.0` | Adapt `register_{group|project}_runner` permissions to take [application setting](https://gitlab.com/gitlab-org/gitlab/-/issues/386712) in consideration. |
| GitLab Rails app | `%16.1` | Make [`POST /api/v4/runners` endpoint](../../../api/runners.md#register-a-new-runner) permanently return `HTTP 410 Gone` if either `allow_runner_registration_token` setting disables registration tokens.<br/>A future v5 version of the API should return `HTTP 404 Not Found`. |
| GitLab Rails app | `%16.1` | Add runner group metadata to the runner list. |
| GitLab Rails app | | Add UI to allow disabling use of registration tokens in top-level group settings. |
| GitLab Rails app | | Make [`POST /api/v4/runners` endpoint](../../../api/runners.md#register-a-new-runner) permanently return `HTTP 410 Gone` if either `allow_runner_registration_token` setting disables registration tokens.<br/>A future v5 version of the API should return `HTTP 404 Not Found`. |
| GitLab Rails app | | Hide legacy UI showing registration with a registration token, if it disabled on in top-level group settings or by admins. |
### Stage 6 - Enforcement
@ -425,18 +426,18 @@ scope.
| GitLab Rails app | `%16.6` | Disable registration tokens for all groups by running database migration (only on GitLab.com) | |
| GitLab Rails app | `%16.6` | Disable registration tokens on the instance level by running database migration (except GitLab.com) | |
| GitLab Rails app | `%16.8` | Disable registration tokens on the instance level for GitLab.com | |
| GitLab Rails app | | Implement new `:create_runner` PPGAT scope so that we don't require a full `api` scope. |
| GitLab Rails app | `%16.3` | Implement new `:create_runner` PPGAT scope so that we don't require a full `api` scope. |
| GitLab Rails app | | Document gotchas when [automatically rotating runner tokens](../../../ci/runners/configure_runners.md#automatically-rotate-authentication-tokens) with multiple machines. |
### Stage 7 - Removals
| Component | Milestone | Changes |
|------------------|----------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| GitLab Rails app | `17.0` | Remove UI enabling registration tokens on the group and instance levels. |
| GitLab Rails app | `17.0` | Remove legacy UI showing registration with a registration token. |
| GitLab Runner | `17.0` | Remove runner model arguments from `register` command (for example `--run-untagged`, `--tag-list`, etc.) |
| GitLab Rails app | `17.0` | Create database migrations to drop `allow_runner_registration_token` setting columns from `application_settings` and `namespace_settings` tables. |
| GitLab Rails app | `17.0` | Create database migrations to drop:<br/>- `runners_registration_token`/`runners_registration_token_encrypted` columns from `application_settings`;<br/>- `runners_token`/`runners_token_encrypted` from `namespaces` table;<br/>- `runners_token`/`runners_token_encrypted` from `projects` table. |
| GitLab Rails app | `17.0` | Remove UI enabling registration tokens on the group and instance levels. |
| GitLab Rails app | `17.0` | Remove legacy UI showing registration with a registration token. |
| GitLab Runner | `17.0` | Remove runner model arguments from `register` command (for example `--run-untagged`, `--tag-list`, etc.) |
| GitLab Rails app | `17.0` | Create database migrations to drop `allow_runner_registration_token` setting columns from `application_settings` and `namespace_settings` tables. |
| GitLab Rails app | `17.0` | Create database migrations to drop:<br/>- `runners_registration_token`/`runners_registration_token_encrypted` columns from `application_settings`;<br/>- `runners_token`/`runners_token_encrypted` from `namespaces` table;<br/>- `runners_token`/`runners_token_encrypted` from `projects` table. |
## FAQ

View File

@ -190,19 +190,19 @@ You can check that you were successful:
### Update the translation files
English UI strings are localized into many languages.
These strings are saved in a `.pot` file, which you must update
These strings are saved in a `.pot` file, which must be regenerated
any time you update UI text.
To generate the localization file:
To automatically regenerate the localization file:
1. Ensure you are in the `gitlab-development-kit/gitlab` directory.
1. Run the following command:
```shell
bin/rake gettext:compile
tooling/bin/gettext_extractor locale/gitlab.pot
```
After several minutes, a `.pot` file is generated in the `/locale` directory.
The `.pot` file will be generated in the `/locale` directory.
Now, in the `gitlab-development-kit/gitlab` directory, if you type `git status`
you should have both files listed:

View File

@ -984,6 +984,13 @@ To open the Admin Area:
1. Select **Admin Area**.
```
To open the **Your work** menu item:
```markdown
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
1. Select **Your work**.
```
To select your avatar:
```markdown

View File

@ -112,71 +112,7 @@ ourselves, or because we think it would benefit the wider community.
Extracting code to a gem also means that we can be sure that the gem
does not contain any hidden dependencies on our application code.
In general, we want to think carefully before doing this as there are
also disadvantages:
### Potential disadvantages
1. Gems - even those maintained by GitLab - do not necessarily go
through the same [code review process](code_review.md) as the main
Rails application.
1. Extracting the code into a separate project means that we need a
minimum of two merge requests to change functionality: one in the gem
to make the functional change, and one in the Rails app to bump the
version.
1. Our needs for our own usage of the gem may not align with the wider
community's needs. In general, if we are not using the latest version
of our own gem, that might be a warning sign.
### Create and publish a Ruby gem
In the case where we do want to extract some library code we've written
to a gem, go through these steps:
1. Determine a suitable name for the gem. If it's a GitLab-owned gem, prefix
the gem name with `gitlab-`. For example, `gitlab-sidekiq-fetcher`.
1. Create the gem or fork as necessary.
1. Ensure the `gitlab_rubygems` group is an owner of the new gem by running:
```shell
gem owner <gem-name> --add gitlab_rubygems
```
1. [Publish the gem to rubygems.org](https://guides.rubygems.org/publishing/#publishing-to-rubygemsorg)
1. Visit `https://rubygems.org/gems/<gem-name>` and verify that the gem published
successfully and `gitlab_rubygems` is also an owner.
1. Start with the code in the Rails application. Here it's fine to have
the code in `lib/` and loaded automatically. We can skip this step if
the step below makes more sense initially.
1. Before extracting to its own project, move the gem to `vendor/gems` and
load it in the `Gemfile` using the `path` option. This gives us a gem
that can be published to RubyGems.org, with its own test suite and
isolated set of dependencies, that is still in our main code tree and
goes through the standard code review process.
- For an example, see the [merge request !57805](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57805).
1. Once the gem is stable - we have been using it in production for a
while with few, if any, changes - extract to its own project under
the [`gitlab-org/ruby/gems` namespace](https://gitlab.com/gitlab-org/ruby/gems/).
- To create this project:
1. Follow the [instructions for new projects](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#creating-a-new-project).
1. Follow the instructions for setting up a [CI/CD configuration](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#cicd-configuration).
1. Follow the instructions for [publishing a project](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#publishing-a-project).
- See [issue #325463](https://gitlab.com/gitlab-org/gitlab/-/issues/325463)
for an example.
- In some cases we may want to move a gem to its own namespace. Some
examples might be that it will naturally have more than one project
(say, something that has plugins as separate libraries), or that we
expect non-GitLab-team-members to be maintainers on this project as
well as GitLab team members.
The latter situation (maintainers from outside GitLab) could also
apply if someone who currently works at GitLab wants to maintain
the gem beyond their time working at GitLab.
When publishing a gem to RubyGems.org, also note the section on
[gem owners](https://about.gitlab.com/handbook/developer-onboarding/#ruby-gems)
in the handbook.
Read more about [Gems development guidelines](gems.md).
## Upgrade Rails

321
doc/development/gems.md Normal file
View File

@ -0,0 +1,321 @@
---
stage: none
group: unassigned
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Gems development guidelines
GitLab uses Gems as a tool to improve code reusability and modularity
in a monolithic codebase.
Sometimes we create libraries within our codebase that we want to
extract, either because their functionality is highly isolated,
we want to use them in other applications
ourselves, or we think it would benefit the wider community.
Extracting code to a gem also means that we can be sure that the gem
does not contain any hidden dependencies on our application code.
## When to use Gems
Gems should be used always when implementing functions that can be considered isolated,
that are decoupled from the business logic of GitLab and can be developed separately. Consider the
following examples where Gem logic could be placed:
The best example where we can look for opportunities to introduce new gems
is the [lib/](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/) folder.
The **lib/** folder is a mix of code that is generic/universal, GitLab-specific, and tightly integrated with the rest of the codebase.
If you cannot find a good place for your code in **lib/** you should strongly
consider creating the new Gem [In the same repo](#in-the-same-repo).
## In the same repo
**Our GitLab Gems should be always put in `gems/` of GitLab monorepo.**
That gives us the advantages of gems (modular code, quicker to run tests in development).
and prevents complexity (coordinating changes across repos, new permissions, multiple projects, etc.).
Gems stored in the same repo should be referenced in `Gemfile` with the `path:` syntax.
They should not be published to RubyGems.
### Advantages
Using Gems can provide several benefits for code maintenance:
- Code Reusability - Gems are isolated libraries that serve single purpose. When using Gems, a common functions
can be isolated in a simple package, that is well documented, tested, and re-used in different applications.
- Modularity - Gems help to create isolation by encapsulating specific functionality within self-contained library.
This helps to better organize code, better define who is owner of a given module, makes it easier to maintain
or update specific gems.
- Small - Gems by design due to implementing isolated set of functions are small. Small projects are much easier
to comprehend, extend and maintain.
- Testing - Using Gems since they are small makes much faster to run all tests, or be very through with testing of the gem.
Since the gem is packaged, not changed too often, it also allows us to run those tests less frequently improving
CI testing time.
### To Do
#### Desired use cases
The `gitlab-utils` is a Gem containing as of set of class that implement common intrisic functions
used by GitLab developers, like `strong_memoize` or `Gitlab::Utils.to_boolean`.
The `gitlab-database-schema-migrations` is a potential Gem containing our extensions to Rails
framework improving how database migrations are stored in repository. This builds on top of Rails
and is not specific to GitLab the application, and could be generally used for other projects
or potentially be upstreamed.
The `gitlab-database-load-balancing` similar to previous is a potential Gem to implement GitLab specific
load balancing to Rails database handling. Since this is rather complex and highly specific code
maintaing it's complexity in a isolated and well tested Gem would help with removing this complexity
from a big monolithic codebase.
The `gitlab-flipper` is another potential Gem implementing all our custom extensions to support feature
flags in a codebase. Over-time the monolithic codebase did grow with the check for feature flags
usage, adding consistency checks and various helpers to track owners of feature flags added. This is
not really part of GitLab business logic and could be used to better track our implementation
of Flipper and possibly much easier change it to dogfood [GitLab Feature Flags](../operations/feature_flags.md).
The `gitlab-ci-reports-parsers` is a potential Gem that could implement all various parsers for various formats.
The parsed output would be transformed into objects that could then be used by GitLab the application
to store it in the database. This functionality could be an additional Gem since it is isolated,
rarely changed, and GitLab Rails only consumes the data.
The same pattern could be applied to all other type of parsers, like security vulnerabilities, or any
other complex structures that need to be transformed into a form that is consumed by GitLab Rails.
The `gitlab-active_record` is a gem adding GitLab specific Active Record patches.
It is very well desired for such to be managed separately to isolate complexity.
#### Other potential use cases
The `gitlab-ci-config` is a potential Gem containing all our CI code used to parse `.gitlab-ci.yml`.
This code is today lightly interlocked with GitLab the application due to lack of proper abstractions.
However, moving this to dedicated Gem could allow us to build various adapters to handle integration
with GitLab the application. The interface would for example define an adapter to resolve `includes:`.
Once we would have a `gitlab-ci-config` Gem it could be used within GitLab and outside of GitLab Rails
and [GitLab CLI](https://gitlab.com/gitlab-org/cli).
### Create and use a new Gem
You can see example adding new Gem: [!121676](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121676).
1. Create a new Ruby Gem in `gems/gitlab-<name-of-gem>` with `bundle gem gems/gitlab-<name-of-gem> --no-exe --no-coc --no-ext --no-mit`.
1. Edit or remove `gitlab-<name-of-gem>/README.md` to provide a simple one paragraph description of the Gem.
1. Edit `gitlab-<name-of-gem>/gitlab-<name-of-gem>.gemspec` and fill the details about the Gem as in the following example:
```ruby
Gem::Specification.new do |spec|
spec.name = "gitlab-<name-of-gem>"
spec.version = Gitlab::NameOfGem::VERSION
spec.authors = ["group::tenant-scale"]
spec.email = ["engineering@gitlab.com"]
spec.summary = "GitLab's RSpec extensions"
spec.description = "A set of useful helpers to configure RSpec with various stubs and CI configs."
spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/gitlab-<name-of-gem>"
spec.required_ruby_version = ">= 2.6.0"
end
```
1. Update `gems/gitlab-<name-of-gem>/.rubocop` with:
```yaml
inherit_from:
- ../../.rubocop.yml
CodeReuse/ActiveRecord:
Enabled: false
AllCops:
TargetRubyVersion: 3.0
Naming/FileName:
Exclude:
- spec/**/*.rb
```
1. Configure CI for a newly added Gem:
- Add `gems/gitlab-<name-of-gem>/.gitlab-ci.yml`:
```yaml
workflow:
rules:
- if: $CI_MERGE_REQUEST_ID
rspec:
image: "ruby:${RUBY_VERSION}"
cache:
key: gitlab-<name-of-gem>
paths:
- gitlab-<name-of-gem>/vendor/ruby
before_script:
- cd vendor/gems/bundler-checksum
- ruby -v # Print out ruby version for debugging
- gem install bundler --no-document # Bundler is not installed with the image
- bundle config set --local path 'vendor' # Install dependencies into ./vendor/ruby
- bundle config set with 'development'
- bundle config set --local frozen 'true' # Disallow Gemfile.lock changes on CI
- bundle config # Show bundler configuration
- bundle install -j $(nproc)
script:
- bundle exec rspec
parallel:
matrix:
- RUBY_VERSION: ["2.7", "3.0", "3.1", "3.2"]
```
- To `.gitlab/ci/rules.gitlab-ci.yml` add:
```yaml
.gems:rules:gitlab-<name-of-gem>:
rules:
- <<: *if-merge-request
changes: ["gems/gitlab-<name-of-gem>/**/*"]
```
- To `.gitlab/ci/gitlab-gems.gitlab-ci.yml` add:
```yaml
gems gitlab-<name-of-gem>:
extends:
- .gems:rules:gitlab-<name-of-gem>
needs: []
trigger:
include: gems/gitlab-<name-of-gem>/.gitlab-ci.yml
strategy: depend
```
1. Reference Gem in `Gemfile` with:
```ruby
gem 'gitlab-<name-of-gem>', path: 'gems/gitlab-<name-of-gem>'
```
## In the external repo
In general, we want to think carefully before doing this as there are
severe disadvantages.
Gems stored in the external repo MUST be referenced in `Gemfile` with `version` syntax.
They MUST be always published to RubyGems.
### Examples
At GitLab we use a number of external gems:
- [LabKit Ruby](https://gitlab.com/gitlab-org/labkit-ruby)
- [GitLab Ruby Gems](https://gitlab.com/gitlab-org/ruby/gems)
### Potential disadvantages
- Gems - even those maintained by GitLab - do not necessarily go
through the same [code review process](code_review.md) as the main
Rails application. This is particularly critical for Application Security.
- Requires setting up CI/CD from scratch, including tools like Danger that
support consistent code review standards.
- Extracting the code into a separate project means that we need a
minimum of two merge requests to change functionality: one in the gem
to make the functional change, and one in the Rails app to bump the
version.
- Integration with `gitlab-rails` requiring a second MR means integration problems
may be discovered late.
- With a smaller pool of reviewers and maintainers compared to `gitlab-rails`,
it may take longer to get code reviewed and the impact of "bus factor" increases.
- Inconsistent workflows for how a new gem version is released. It is currently at
the discretion of library maintainers to decide how it works.
- Promotes knowledge silos because code has less visibility and exposure than `gitlab-rails`.
- We have a well defined process for promoting GitLab reviewers to maintainers.
This is not true for extracted libraries, increasing the risk of lowering the bar for code reviews,
and increasing the risk of shipping a change.
- Our needs for our own usage of the gem may not align with the wider
community's needs. In general, if we are not using the latest version
of our own gem, that might be a warning sign.
### Potential advantages
- Faster feedback loops, since CI/CD runs against smaller repositories.
- Ability to expose the project to the wider community and benefit from external contributions.
- Repository owners are most likely the best audience to review a change, which reduces
the necessity of finding the right reviewers in `gitlab-rails`.
### Create and publish a Ruby gem
The project for a new Gem should always be created in [`gitlab-org/ruby/gems` namespace](https://gitlab.com/gitlab-org/ruby/gems/):
1. Determine a suitable name for the gem. If it's a GitLab-owned gem, prefix
the gem name with `gitlab-`. For example, `gitlab-sidekiq-fetcher`.
1. Create the gem or fork as necessary.
1. Ensure the `gitlab_rubygems` group is an owner of the new gem by running:
```shell
gem owner <gem-name> --add gitlab_rubygems
```
1. [Publish the gem to rubygems.org](https://guides.rubygems.org/publishing/#publishing-to-rubygemsorg)
1. Visit `https://rubygems.org/gems/<gem-name>` and verify that the gem published
successfully and `gitlab_rubygems` is also an owner.
1. Create a project in [`gitlab-org/ruby/gems` namespace](https://gitlab.com/gitlab-org/ruby/gems/).
- To create this project:
1. Follow the [instructions for new projects](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#creating-a-new-project).
1. Follow the instructions for setting up a [CI/CD configuration](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#cicd-configuration).
1. Follow the instructions for [publishing a project](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#publishing-a-project).
- See [issue #325463](https://gitlab.com/gitlab-org/gitlab/-/issues/325463)
for an example.
- In some cases we may want to move a gem to its own namespace. Some
examples might be that it will naturally have more than one project
(say, something that has plugins as separate libraries), or that we
expect users outside GitLab to be maintainers on this project as
well as GitLab team members.
The latter situation (maintainers from outside GitLab) could also
apply if someone who currently works at GitLab wants to maintain
the gem beyond their time working at GitLab.
When publishing a gem to RubyGems.org, also note the section on
[gem owners](https://about.gitlab.com/handbook/developer-onboarding/#ruby-gems)
in the handbook.
## The `vendor/gems/`
The purpose of `vendor/` is to pull into GitLab monorepo external dependencies,
which do have external repositories, but for the sake of simplicity we want
to store them in monorepo:
- The `vendor/gems/` MUST ONLY be used if we are pulling from external repository either via script, or manually.
- The `vendor/gems/` MUST NOT be used for storing in-house gems.
- The `vendor/gems/` MAY accept fixes to make them buildable with GitLab monorepo
- The `gems/` MUST be used for storing all in-house gems that are part of GitLab monorepo.
- The **RubyGems** MUST be used for all externally stored dependencies that are not in `gems/` in GitLab monorepo.
### Handling of an existing gems in `vendor/gems`
- For in-house Gems that do not have external repository and are currently stored in `vendor/gems/`:
- For Gems that are used by other repositories:
- We will migrate it into its own repository.
- We will start or continue publishing them via RubyGems.
- Those Gems will be referenced via version in `Gemfile` and fetched from RubyGems.
- For Gems that are only used by monorepo:
- We will stop publishing new versions to RubyGems.
- We will not pull from RubyGems already published versions since there might
be applications depedent on those.
- We will move those gems to `gems/`.
- Those Gems will be referenced via `path:` in `Gemfile`.
- For `vendor/gems/` that are external and vendored in monorepo:
- We will maintain them in the repository if they require some fixes that cannot be or are not yet upstreamed.
- It is expected that vendored gems might be published by third-party.
- Those Gems will not be published by us to RubyGems.
- Those Gems will be referenced via `path:` in `Gemfile`, since we cannot depend on RubyGems.

View File

@ -6,9 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# GitLab Dedicated
NOTE:
GitLab Dedicated is currently in limited availability. You can learn more and join the waitlist [on our website](https://about.gitlab.com/single-tenant-saas).
GitLab Dedicated is a fully isolated, single-tenant SaaS service that is:
- Hosted and managed by GitLab, Inc.
@ -164,6 +161,6 @@ The following AWS regions are not available:
For more information about the planned improvements to GitLab Dedicated,
see the [category direction page](https://about.gitlab.com/direction/saas-platforms/dedicated/).
## Join the GitLab Dedicated waitlist
## Interested in GitLab Dedicated?
As we scale this new offering, we are making GitLab Dedicated available by inviting customers to [join our waitlist](https://about.gitlab.com/dedicated/).
Learn more about GitLab Dedicated and [talk to an expert](https://about.gitlab.com/dedicated/).

View File

@ -25,11 +25,13 @@ To turn off this sidebar, return to your avatar and turn off the toggle.
## Layout of the left sidebar
At the top of the left sidebar are several shortcuts. Use these shortcuts to
quickly create new items, view your profile, search, and view your list of issues,
show and hide the left sidebar, create new items, search, and view your profile. You can also view your list of issues,
merge requests, and to-do items.
![Top of sidebar](img/sidebar_top_v16_1.png)
If you have hidden the left sidebar, you can display it temporarily by hovering your cursor over the left edge of the GitLab window.
The next area of the left sidebar changes based on the information you're viewing. For example,
you might be viewing a project, exploring projects or groups, or viewing your profile.
Use this area to switch to other areas of the left sidebar.

View File

@ -33,6 +33,6 @@ To modify this setting:
- Through the [Application settings API](../../../api/settings.md#list-of-settings-that-can-be-accessed-via-api-calls)
as `push_event_activities_limit`.
The default value is 3, but it can be greater than or equal 0.
The default value is `3`, but the value can be greater than or equal to `0`. Setting this value to `0` does not disable throttling.
![Push event activities limit](img/push_event_activities_limit_v12_4.png)

View File

@ -21,8 +21,9 @@ projects.
To view the instance level Kubernetes clusters:
1. On the top bar, select **Main menu > Admin**.
1. On the left sidebar, select **Kubernetes**.
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
1. Select **Admin Area**.
1. Select **Kubernetes**.
## Cluster precedence

View File

@ -9,7 +9,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
The Operations Dashboard provides a summary of each project's operational health,
including pipeline and alert status.
To access the dashboard, on the top bar, select **Main menu > Operations**.
To access the dashboard:
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
1. Select **Your work**.
1. Select **Operations**.
## Adding a project to the dashboard

View File

@ -1110,6 +1110,9 @@ and later, the pipeline webhook returns only the latest jobs.
In [GitLab 15.1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89546)
and later, pipeline webhooks triggered by blocked users are not processed.
In [GitLab 16.1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123639)
and later, pipeline webhooks started to expose `object_attributes.name`.
Request header:
```plaintext
@ -1124,6 +1127,7 @@ Payload example:
"object_attributes":{
"id": 31,
"iid": 3,
"name": "Pipeline for branch: master",
"ref": "master",
"tag": false,
"sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2",

View File

@ -14,59 +14,72 @@ These commits are displayed on the merge request's **Commits** tab.
From this tab, you can review commit messages and copy a commit's SHA when you need to
[cherry-pick changes](cherry_pick_changes.md).
## Navigate merge request commits
## View commits in a merge request
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18140) in GitLab 13.0.
To navigate commits in a merge request:
1. Select the **Commits** tab.
1. Select the commit link. The most recent commit is displayed.
1. Navigate through the commits by either:
- Selecting **Prev** and **Next** buttons below the tab buttons.
- Using the <kbd>X</kbd> and <kbd>C</kbd> keyboard shortcuts.
![Merge requests commit navigation](img/commit_nav_v16_0.png)
## Add a comment to a commit
You can add comments and threads to a particular commit.
To see the commits included in a merge request:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Repository > Commits**.
1. Below the commits, in the **Comment** field, enter a comment.
1. Select **Comment** or select the down arrow (**{chevron-down}**) to select **Start thread**.
1. On the left sidebar, select **Merge requests**, then select your merge request.
1. To show a list of the commits in the merge request, newest first, select **Commits** .
To read more about the commit, select **Toggle commit description** (**{ellipsis_h}**)
on any commit.
1. To view the changes in the commit, select the title of the commit link.
1. To view other commits in the merge request, either:
- Select **Prev** or **Next**.
- Use keyboard shortcuts: <kbd>X</kbd> (previous commit) and <kbd>C</kbd> (next commit).
If your merge request builds upon a previous merge request, you might
need to [include more commits for context](#show-commits-from-previous-merge-requests).
### Show commits from previous merge requests
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/320757) in GitLab 14.8.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/320757) in GitLab 14.9. [Feature flag `context_commits`](https://gitlab.com/gitlab-org/gitlab/-/issues/320757) removed.
When you review a merge request, you might need information from previous commits
to help understand the commits you're reviewing. You might need more context
if another merge request:
- Changed files your current merge request doesn't modify, so those files aren't shown
in your current merge request's diff.
- Changed files that you're modifying in your current merge request, and you need
to see the progression of work.
To add previously merged commits to a merge request for more context:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Merge requests**, then select your merge request.
1. Select **Commits**.
1. Scroll to the end of the list of commits, and select **Add previously merged commits**.
1. Select the commits that you want to add.
1. Select **Save changes**.
## Add a comment to a commit
WARNING:
Threads created this way are lost if the commit ID changes after a
force push.
## View merge request commits in context
To add discussion to a specific commit:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29274) in GitLab 13.12 [with a flag](../../../administration/feature_flags.md) named `context_commits`. Enabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/320757) in GitLab 14.8.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/320757) in GitLab 14.9. [Feature flag `context_commits`](https://gitlab.com/gitlab-org/gitlab/-/issues/320757) removed.
When reviewing a merge request, it helps to have more context about the changes
made. That includes unchanged lines in unchanged files, and previous commits
that have already merged that the change is built on.
To add previously merged commits to a merge request for more context:
1. Go to your merge request.
1. Select the **Commits** tab.
1. Scroll to the end of the list of commits, and select **Add previously merged commits**:
1. Select the commits that you want to add.
1. Select **Save changes**.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Repository > Commits**.
1. Below the commits, in the **Comment** field, enter a comment.
1. Save your comment as either a standalone comment, or a thread:
- To add a comment, select **Comment**.
- To start a thread, select the down arrow (**{chevron-down}**), then select **Start thread**.
## View diffs between commits
To view the changes between previously merged commits:
1. On your merge request, select the **Changes** tab.
1. By **Compare**, select the commit you want to view:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Merge requests**, then select your merge request.
1. Select **Changes**.
1. By **Compare** (**{file-tree}**), select the commits to compare:
![Previously merged commits](img/previously_merged_commits_v16_0.png)
If you selected to add previously merged commits, they are displayed in the list.
If you selected to add previously merged commits for context, those commits are
also shown in the list.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,6 +1,6 @@
---
stage: Create
group: Incubation
stage: Plan
group: Knowledge
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---

View File

@ -39,7 +39,7 @@ Each workspace includes its own set of dependencies, libraries, and tools, which
- You must have at least the Developer role in the root group.
- In each public project you want to use this feature for, create a [devfile](#devfile):
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project
1. In the root directory of your project, create a file named `.devfile.yaml`. You can use one of the [example configurations](#example-configurations).
- Ensure the container images used in the devfile support [arbitrary user IDs](#arbitrary-user-ids).

18
gems/README.md Normal file
View File

@ -0,0 +1,18 @@
# GitLab Gems
This directory contains all GitLab built monorepo Gems.
## Development guidelines
The Gems created in this repository should adhere to the following rules:
- MUST: Contain `.gitlab-ci.yml`.
- MUST: Contain `.rubocop.yml` and be based on `gitlab-styles`.
- MUST: Be added to `.gitlab/ci/gitlab-gems.gitlab-ci.yml`.
- MUST NOT: Reference source code outside of `gems/<gem-name>/` with `require_relative "../../lib"`.
- MUST NOT: Require other gems that would result in circular dependencies.
- MAY: Reference other Gems in `gems/` folder or `vendor/gems/` with `gem <name>, path: "../gitlab-rspec"`.
- MAY: Define in `.gemspec` the owning group, like `group::tenant scale`.
- RECOMMENDED: Namespaced with `Gitlab::<GemName>`.
- RECOMMENDED: Be added to `CODEOWNERS`.
- MUST NOT: Have an active associated project created in [gitlab-org/ruby/gems/](https://gitlab.com/gitlab-org/ruby/gems).

View File

@ -132,8 +132,8 @@ module API
optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
optional :project_export_enabled, type: Boolean, desc: 'Enable project export'
optional :prometheus_metrics_enabled, type: Boolean, desc: 'Enable Prometheus metrics'
optional :push_event_hooks_limit, type: Integer, desc: "Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value."
optional :push_event_activities_limit, type: Integer, desc: 'Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value.'
optional :push_event_hooks_limit, type: Integer, desc: "Maximum number of changes (branches or tags) in a single push above which webhooks and integrations are not triggered. Setting to `0` does not disable throttling."
optional :push_event_activities_limit, type: Integer, desc: 'Maximum number of changes (branches or tags) in a single push above which a bulk push event is created. Setting to `0` does not disable throttling.'
optional :recaptcha_enabled, type: Boolean, desc: 'Helps prevent bots from creating accounts'
given recaptcha_enabled: ->(val) { val } do
requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'

View File

@ -65,6 +65,7 @@ module Gitlab
{
id: pipeline.id,
iid: pipeline.iid,
name: pipeline.name,
ref: pipeline.source_ref,
tag: pipeline.tag,
sha: pipeline.sha,

View File

@ -80,6 +80,26 @@ module Gitlab
# rubocop:enable Gitlab/DocUrl
end
private_class_method def self.cross_access_allowed?(type, table_schemas)
table_schemas.any? do |schema|
extra_schemas = table_schemas - [schema]
extra_schemas -= Gitlab::Database.all_gitlab_schemas[schema]&.public_send(type) || [] # rubocop:disable GitlabSecurity/PublicSend
extra_schemas.empty?
end
end
def self.cross_joins_allowed?(table_schemas)
table_schemas.empty? || self.cross_access_allowed?(:allow_cross_joins, table_schemas)
end
def self.cross_transactions_allowed?(table_schemas)
table_schemas.empty? || self.cross_access_allowed?(:allow_cross_transactions, table_schemas)
end
def self.cross_foreign_key_allowed?(table_schemas)
self.cross_access_allowed?(:allow_cross_foreign_keys, table_schemas)
end
def self.dictionary_paths
Gitlab::Database.all_database_connections
.values.map(&:db_docs_dir).uniq

View File

@ -5,12 +5,18 @@ module Gitlab
GitlabSchemaInfo = Struct.new(
:name,
:description,
:allow_cross_joins,
:allow_cross_transactions,
:allow_cross_foreign_keys,
:file_path,
keyword_init: true
) do
def initialize(*)
super
self.name = name.to_sym
self.allow_cross_joins = allow_cross_joins&.map(&:to_sym)&.freeze
self.allow_cross_transactions = allow_cross_transactions&.map(&:to_sym)&.freeze
self.allow_cross_foreign_keys = allow_cross_foreign_keys&.map(&:to_sym)&.freeze
end
def self.load_file(yaml_file)

View File

@ -111,15 +111,15 @@ module Gitlab
context[:modified_tables_by_db][database].merge(tables)
all_tables = context[:modified_tables_by_db].values.flat_map(&:to_a)
schemas = ::Gitlab::Database::GitlabSchema.table_schemas!(all_tables)
schemas += ApplicationRecord.gitlab_transactions_stack
if schemas.many?
messages = ["Cross-database data modification of '#{schemas.to_a.join(", ")}' were detected within " \
"a transaction modifying the '#{all_tables.to_a.join(", ")}' tables. " \
"Please refer to https://docs.gitlab.com/ee/development/database/multiple_databases.html#removing-cross-database-transactions for details on how to resolve this exception."]
messages << cleaned_queries
unless ::Gitlab::Database::GitlabSchema.cross_transactions_allowed?(schemas)
messages = []
messages << "Cross-database data modification of '#{schemas.to_a.join(", ")}' were detected within " \
"a transaction modifying the '#{all_tables.to_a.join(", ")}' tables. "
messages << "Please refer to https://docs.gitlab.com/ee/development/database/multiple_databases.html#removing-cross-database-transactions " \
"for details on how to resolve this exception."
messages += cleaned_queries
raise CrossDatabaseModificationAcrossUnsupportedTablesError, messages.join("\n\n")
end
@ -182,11 +182,11 @@ module Gitlab
end
def self.cleaned_queries
return '' unless dev_or_test_env?
return [] unless dev_or_test_env?
context[:queries].last(QUERY_LIMIT).each_with_index.map do |sql, i|
"#{i}: #{sql}"
end.join("\n")
end
end
def self.in_transaction?

View File

@ -19,7 +19,8 @@ module Gitlab
gitlab_internal: nil,
# Pods specific changes
gitlab_main_clusterwide: :gitlab_main
gitlab_main_clusterwide: :gitlab_main,
gitlab_main_cell: :gitlab_main
}.freeze
class << self
@ -61,6 +62,7 @@ module Gitlab
def restrict_to_ddl_only(parsed)
tables = self.dml_tables(parsed)
schemas = self.dml_schemas(tables)
schemas = self.map_schemas(schemas)
if schemas.any?
self.raise_dml_not_allowed_error("Modifying of '#{tables}' (#{schemas.to_a}) with '#{parsed.sql}'")
@ -78,8 +80,10 @@ module Gitlab
tables = self.dml_tables(parsed)
schemas = self.dml_schemas(tables)
schemas = self.map_schemas(schemas)
allowed_schemas = self.map_schemas(self.allowed_gitlab_schemas)
if (schemas - self.allowed_gitlab_schemas).any?
if (schemas - allowed_schemas).any?
raise DMLAccessDeniedError, \
"Select/DML queries (SELECT/UPDATE/DELETE) do access '#{tables}' (#{schemas.to_a}) " \
"which is outside of list of allowed schemas: '#{self.allowed_gitlab_schemas}'. " \
@ -100,15 +104,19 @@ module Gitlab
end
def dml_schemas(tables)
extra_schemas = ::Gitlab::Database::GitlabSchema.table_schemas!(tables)
::Gitlab::Database::GitlabSchema.table_schemas!(tables)
end
SCHEMA_MAPPING.each do |schema, mapped_schema|
next unless extra_schemas.delete?(schema)
def map_schemas(schemas)
schemas = schemas.to_set
extra_schemas.add(mapped_schema) if mapped_schema
SCHEMA_MAPPING.each do |in_schema, mapped_schema|
next unless schemas.delete?(in_schema)
schemas.add(mapped_schema) if mapped_schema
end
extra_schemas
schemas
end
def raise_dml_not_allowed_error(message)

View File

@ -9,6 +9,8 @@ module Gitlab
@logger ||= Gitlab::AppLogger
end
PATH_TRAVERSAL_REGEX = %r{(\A(\.{1,2})\z|\A\.\.[/\\]|[/\\]\.\.\z|[/\\]\.\.[/\\]|\n)}
# Ensure that the relative path will not traverse outside the base directory
# We url decode the path to avoid passing invalid paths forward in url encoded format.
# Also see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24223#note_284122580
@ -20,9 +22,8 @@ module Gitlab
raise PathTraversalAttackError, 'Invalid path' unless path.is_a?(String)
path = ::Gitlab::Utils.decode_path(path)
path_regex = %r{(\A(\.{1,2})\z|\A\.\.[/\\]|[/\\]\.\.\z|[/\\]\.\.[/\\]|\n)}
if path.match?(path_regex)
if path.match?(PATH_TRAVERSAL_REGEX)
logger.warn(message: "Potential path traversal attempt detected", path: path.to_s)
raise PathTraversalAttackError, 'Invalid path'
end

View File

@ -9608,16 +9608,16 @@ msgstr ""
msgid "CiCatalog|About this project"
msgstr ""
msgid "CiCatalog|CI/CD Catalog resource"
msgid "CiCatalog|CI/CD Catalog"
msgstr ""
msgid "CiCatalog|CI/CD catalog"
msgid "CiCatalog|CI/CD Catalog resource"
msgstr ""
msgid "CiCatalog|Create a pipeline component repository and make reusing pipeline configurations faster and easier."
msgstr ""
msgid "CiCatalog|Get started with the CI/CD catalog"
msgid "CiCatalog|Get started with the CI/CD Catalog"
msgstr ""
msgid "CiCatalog|Go to the project"
@ -11795,9 +11795,6 @@ msgstr ""
msgid "Compute quota:"
msgstr ""
msgid "Confidence"
msgstr ""
msgid "Confidential"
msgstr ""
@ -12974,6 +12971,9 @@ msgstr ""
msgid "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes."
msgstr ""
msgid "Create a new fork"
msgstr ""
msgid "Create a new issue"
msgstr ""
@ -27994,7 +27994,10 @@ msgstr ""
msgid "Maximum number of %{name} (%{count}) exceeded"
msgstr ""
msgid "Maximum number of changes (branches or tags) in a single push for which webhooks and services trigger (default is 3)."
msgid "Maximum number of changes (branches or tags) in a single push above which a bulk push event is created (default is `3`). Setting to `0` does not disable throttling."
msgstr ""
msgid "Maximum number of changes (branches or tags) in a single push above which webhooks and integrations are not triggered (default is `3`). Setting to `0` does not disable throttling."
msgstr ""
msgid "Maximum number of comments exceeded"
@ -28015,7 +28018,7 @@ msgstr ""
msgid "Maximum number of requests per minute for an unauthenticated IP address"
msgstr ""
msgid "Maximum number of requests per minute for each raw path (default is 300). Set to 0 to disable throttling."
msgid "Maximum number of requests per minute for each raw path (default is `300`). Set to `0` to disable throttling."
msgstr ""
msgid "Maximum number of unique IP addresses per user."
@ -47258,9 +47261,6 @@ msgstr ""
msgid "Threshold in bytes at which to reject Sidekiq jobs. Set this to 0 to if you don't want to limit Sidekiq jobs."
msgstr ""
msgid "Threshold number of changes (branches or tags) in a single push above which a bulk push event is created (default is 3)."
msgstr ""
msgid "Throughput"
msgstr ""
@ -47810,6 +47810,12 @@ msgstr ""
msgid "To start using GitLab Enterprise Edition, upload the %{codeOpen}.gitlab-license%{codeClose} file or enter the license key you have received from GitLab Inc."
msgstr ""
msgid "To submit your changes in a merge request, create a new fork."
msgstr ""
msgid "To submit your changes in a merge request, switch to one of these forks or create a new fork."
msgstr ""
msgid "To unsubscribe from this issue, please paste the following link into your browser:"
msgstr ""
@ -52506,6 +52512,9 @@ msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
msgid "You cant edit files directly in this project."
msgstr ""
msgid "You cant edit files directly in this project. Fork this project and submit a merge request with your changes."
msgstr ""

View File

@ -68,7 +68,7 @@ module QA
element :delete_button
end
view 'app/assets/javascripts/vue_shared/components/confirm_fork_modal.vue' do
view 'app/assets/javascripts/vue_shared/components/web_ide/confirm_fork_modal.vue' do
element :fork_project_button
element :confirm_fork_modal
end

View File

@ -58,19 +58,6 @@ RSpec.describe SearchController, feature_category: :global_search do
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:search_rate_limit, scope: [user])
get :show, params: { search: 'hello', scope: 'blobs' * 1000 }
end
context 'when search_rate_limited_scopes feature flag is disabled' do
before do
stub_feature_flags(search_rate_limited_scopes: false)
end
it 'uses just current_user' do
%w[projects blobs users issues merge_requests].each do |scope|
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:search_rate_limit, scope: [user])
get :show, params: { search: 'hello', scope: scope }
end
end
end
end
context 'uses the right partials depending on scope' do
@ -395,19 +382,6 @@ RSpec.describe SearchController, feature_category: :global_search do
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:search_rate_limit, scope: [user])
get :count, params: { search: 'hello', scope: 'blobs' * 1000 }
end
context 'when search_rate_limited_scopes feature flag is disabled' do
before do
stub_feature_flags(search_rate_limited_scopes: false)
end
it 'uses just current_user' do
%w[projects blobs users issues merge_requests].each do |scope|
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:search_rate_limit, scope: [user])
get :count, params: { search: 'hello', scope: scope }
end
end
end
end
it 'raises an error if search term is missing' do
@ -486,19 +460,6 @@ RSpec.describe SearchController, feature_category: :global_search do
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:search_rate_limit, scope: [user])
get :autocomplete, params: { term: 'hello', scope: 'blobs' * 1000 }
end
context 'when search_rate_limited_scopes feature flag is disabled' do
before do
stub_feature_flags(search_rate_limited_scopes: false)
end
it 'uses just current_user' do
%w[projects blobs users issues merge_requests].each do |scope|
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:search_rate_limit, scope: [user])
get :autocomplete, params: { term: 'hello', scope: scope }
end
end
end
end
it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Project (GraphQL fixtures)', feature_category: :groups_and_projects do
describe GraphQL::Query, type: :request do
include ApiHelpers
include GraphqlHelpers
include JavaScriptFixturesHelpers
include ProjectForksHelper
let_it_be(:project) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) }
describe 'writable forks' do
writeable_forks_query_path = 'vue_shared/components/web_ide/get_writable_forks.query.graphql'
let(:query) { get_graphql_query_as_string(writeable_forks_query_path) }
subject { post_graphql(query, current_user: current_user, variables: { projectPath: project.full_path }) }
before do
project.add_developer(current_user)
end
context 'with none' do
it "graphql/#{writeable_forks_query_path}_none.json" do
subject
expect_graphql_errors_to_be_empty
end
end
context 'with some' do
let_it_be(:fork1) { fork_project(project, nil, repository: true) }
let_it_be(:fork2) { fork_project(project, nil, repository: true) }
before_all do
fork1.add_developer(current_user)
fork2.add_developer(current_user)
end
it "graphql/#{writeable_forks_query_path}_some.json" do
subject
expect_graphql_errors_to_be_empty
end
end
end
end
end

View File

@ -12,8 +12,8 @@ describe('MR Widget App', () => {
});
};
it('does not mount if widgets array is empty', () => {
it('renders widget container', () => {
createComponent();
expect(wrapper.findByTestId('mr-widget-app').exists()).toBe(false);
expect(wrapper.findByTestId('mr-widget-app').exists()).toBe(true);
});
});

View File

@ -10,7 +10,6 @@ import getStateQueryResponse from 'test_fixtures/graphql/merge_requests/get_stat
import readyToMergeResponse from 'test_fixtures/graphql/merge_requests/states/ready_to_merge.query.graphql.json';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data';
import api from '~/api';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK, HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status';
@ -30,7 +29,6 @@ import Approvals from '~/vue_merge_request_widget/components/approvals/approvals
import Preparing from '~/vue_merge_request_widget/components/states/mr_widget_preparing.vue';
import WidgetContainer from '~/vue_merge_request_widget/components/widget/app.vue';
import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
import getStateQuery from '~/vue_merge_request_widget/queries/get_state.query.graphql';
import getStateSubscription from '~/vue_merge_request_widget/queries/get_state.subscription.graphql';
import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql';
@ -76,11 +74,6 @@ describe('MrWidgetOptions', () => {
const COLLABORATION_MESSAGE = 'Members who can merge are allowed to add commits';
const findApprovalsWidget = () => wrapper.findComponent(Approvals);
const findPreparingWidget = () => wrapper.findComponent(Preparing);
const findWidgetContainer = () => wrapper.findComponent(WidgetContainer);
const findExtensionToggleButton = () =>
wrapper.find('[data-testid="widget-extension"] [data-testid="toggle-button"]');
const findExtensionLink = (linkHref) =>
wrapper.find(`[data-testid="widget-extension"] [href="${linkHref}"]`);
beforeEach(() => {
gl.mrWidgetData = { ...mockData };
@ -163,9 +156,13 @@ describe('MrWidgetOptions', () => {
return axios.waitForAll();
};
const findExtensionToggleButton = () =>
wrapper.find('[data-testid="widget-extension"] [data-testid="toggle-button"]');
const findExtensionLink = (linkHref) =>
wrapper.find(`[data-testid="widget-extension"] [href="${linkHref}"]`);
const findSuggestPipeline = () => wrapper.find('[data-testid="mr-suggest-pipeline"]');
const findSuggestPipelineButton = () => findSuggestPipeline().find('button');
const findSecurityMrWidget = () => wrapper.find('[data-testid="security-mr-widget"]');
const findWidgetContainer = () => wrapper.findComponent(WidgetContainer);
describe('default', () => {
beforeEach(() => {
@ -870,47 +867,6 @@ describe('MrWidgetOptions', () => {
});
});
describe('security widget', () => {
const setup = (hasPipeline) => {
const mrData = {
...mockData,
...(hasPipeline ? {} : { pipeline: null }),
};
// Override top-level mocked requests, which always use a fresh copy of
// mockData, which always includes the full pipeline object.
mock.onGet(mockData.merge_request_widget_path).reply(() => [HTTP_STATUS_OK, mrData]);
mock.onGet(mockData.merge_request_cached_widget_path).reply(() => [HTTP_STATUS_OK, mrData]);
return createComponent(mrData, {
apolloMock: [
[
securityReportMergeRequestDownloadPathsQuery,
jest
.fn()
.mockResolvedValue({ data: securityReportMergeRequestDownloadPathsQueryResponse }),
],
],
});
};
describe('with a pipeline', () => {
it('renders the security widget', async () => {
await setup(true);
expect(findSecurityMrWidget().exists()).toBe(true);
});
});
describe('with no pipeline', () => {
it('does not render the security widget', async () => {
await setup(false);
expect(findSecurityMrWidget().exists()).toBe(false);
});
});
});
describe('suggestPipeline', () => {
beforeEach(() => {
mock.onAny().reply(HTTP_STATUS_OK);
@ -1179,7 +1135,7 @@ describe('MrWidgetOptions', () => {
await nextTick();
await waitForPromises();
expect(Sentry.captureException).toHaveBeenCalledTimes(2);
expect(Sentry.captureException).toHaveBeenCalledTimes(1);
expect(Sentry.captureException).toHaveBeenCalledWith(new Error('Fetch error'));
expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe('failed');
});
@ -1271,18 +1227,12 @@ describe('MrWidgetOptions', () => {
expect(api.trackRedisCounterEvent).not.toHaveBeenCalled();
});
});
});
describe('widget container', () => {
it('should not be displayed when the refactor_security_extension feature flag is turned off', () => {
createComponent();
expect(findWidgetContainer().exists()).toBe(false);
});
it('should be displayed when the refactor_security_extension feature flag is turned on', () => {
window.gon.features.refactorSecurityExtension = true;
createComponent();
expect(findWidgetContainer().exists()).toBe(true);
});
describe('widget container', () => {
it('renders the widget container when there is MR data', async () => {
await createComponent(mockData);
expect(findWidgetContainer().props('mr')).not.toBeUndefined();
});
});

View File

@ -1,8 +1,17 @@
import { GlModal } from '@gitlab/ui';
import { GlLoadingIcon, GlModal } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import getNoWritableForksResponse from 'test_fixtures/graphql/vue_shared/components/web_ide/get_writable_forks.query.graphql_none.json';
import getSomeWritableForksResponse from 'test_fixtures/graphql/vue_shared/components/web_ide/get_writable_forks.query.graphql_some.json';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ConfirmForkModal, { i18n } from '~/vue_shared/components/confirm_fork_modal.vue';
import ConfirmForkModal, { i18n } from '~/vue_shared/components/web_ide/confirm_fork_modal.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import getWritableForksQuery from '~/vue_shared/components/web_ide/get_writable_forks.query.graphql';
import waitForPromises from 'helpers/wait_for_promises';
describe('vue_shared/components/confirm_fork_modal', () => {
Vue.use(VueApollo);
let wrapper = null;
const forkPath = '/fake/fork/path';
@ -13,13 +22,18 @@ describe('vue_shared/components/confirm_fork_modal', () => {
const findModalProp = (prop) => findModal().props(prop);
const findModalActionProps = () => findModalProp('actionPrimary');
const createComponent = (props = {}) =>
shallowMountExtended(ConfirmForkModal, {
const createComponent = (props = {}, getWritableForksResponse = getNoWritableForksResponse) => {
const fakeApollo = createMockApollo([
[getWritableForksQuery, jest.fn().mockResolvedValue(getWritableForksResponse)],
]);
return shallowMountExtended(ConfirmForkModal, {
propsData: {
...defaultProps,
...props,
},
apolloProvider: fakeApollo,
});
};
describe('visible = false', () => {
beforeEach(() => {
@ -73,4 +87,45 @@ describe('vue_shared/components/confirm_fork_modal', () => {
expect(wrapper.emitted('change')).toEqual([[false]]);
});
});
describe('writable forks', () => {
describe('when loading', () => {
it('shows loading spinner', () => {
wrapper = createComponent();
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
describe('with no writable forks', () => {
it('contains `newForkMessage`', async () => {
wrapper = createComponent();
await waitForPromises();
expect(wrapper.text()).toContain(i18n.newForkMessage);
});
});
describe('with writable forks', () => {
it('contains `existingForksMessage`', async () => {
wrapper = createComponent(null, getSomeWritableForksResponse);
await waitForPromises();
expect(wrapper.text()).toContain(i18n.existingForksMessage);
});
it('renders links to the forks', async () => {
wrapper = createComponent(null, getSomeWritableForksResponse);
await waitForPromises();
const forks = getSomeWritableForksResponse.data.project.visibleForks.nodes;
expect(wrapper.findByText(forks[0].fullPath).attributes('href')).toBe(forks[0].webUrl);
expect(wrapper.findByText(forks[1].fullPath).attributes('href')).toBe(forks[1].webUrl);
});
});
});
});

View File

@ -1,14 +1,18 @@
import { GlModal } from '@gitlab/ui';
import { nextTick } from 'vue';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import getWritableForksResponse from 'test_fixtures/graphql/vue_shared/components/web_ide/get_writable_forks.query.graphql_none.json';
import ActionsButton from '~/vue_shared/components/actions_button.vue';
import WebIdeLink, { i18n } from '~/vue_shared/components/web_ide_link.vue';
import ConfirmForkModal from '~/vue_shared/components/confirm_fork_modal.vue';
import ConfirmForkModal from '~/vue_shared/components/web_ide/confirm_fork_modal.vue';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import { visitUrl } from '~/lib/utils/url_utility';
import getWritableForksQuery from '~/vue_shared/components/web_ide/get_writable_forks.query.graphql';
jest.mock('~/lib/utils/url_utility');
@ -77,9 +81,14 @@ const ACTION_PIPELINE_EDITOR = {
};
describe('vue_shared/components/web_ide_link', () => {
Vue.use(VueApollo);
let wrapper;
function createComponent(props, { mountFn = shallowMountExtended, glFeatures = {} } = {}) {
const fakeApollo = createMockApollo([
[getWritableForksQuery, jest.fn().mockResolvedValue(getWritableForksResponse)],
]);
wrapper = mountFn(WebIdeLink, {
propsData: {
editUrl: TEST_EDIT_URL,
@ -102,6 +111,7 @@ describe('vue_shared/components/web_ide_link', () => {
</div>`,
}),
},
apolloProvider: fakeApollo,
});
}

View File

@ -47,7 +47,14 @@ RSpec.describe GitlabSchema.types['User'], feature_category: :user_profile do
profileEnableGitpodPath
savedReplies
savedReply
user_achievements
userAchievements
bio
linkedin
twitter
discord
organization
jobTitle
createdAt
]
expect(described_class).to include_graphql_fields(*expected_fields)

View File

@ -271,4 +271,42 @@ RSpec.describe TreeHelper do
end
end
end
describe '.fork_modal_options' do
let_it_be(:blob) { project.repository.blob_at('refs/heads/master', @path) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
subject { helper.fork_modal_options(project, blob) }
it 'returns correct fork path' do
expect(subject).to match a_hash_including(fork_path: '/namespace1/project-1/-/forks/new', fork_modal_id: nil)
end
context 'when show_edit_button true' do
before do
allow(helper).to receive(:show_edit_button?).and_return(true)
end
it 'returns correct fork path and modal id' do
expect(subject).to match a_hash_including(
fork_path: '/namespace1/project-1/-/forks/new',
fork_modal_id: 'modal-confirm-fork-edit')
end
end
context 'when show_web_ide_button true' do
before do
allow(helper).to receive(:show_web_ide_button?).and_return(true)
end
it 'returns correct fork path and modal id' do
expect(subject).to match a_hash_including(
fork_path: '/namespace1/project-1/-/forks/new',
fork_modal_id: 'modal-confirm-fork-webide')
end
end
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::DataBuilder::Pipeline do
RSpec.describe Gitlab::DataBuilder::Pipeline, feature_category: :continuous_integration do
let_it_be(:user) { create(:user, :public_email) }
let_it_be(:project) { create(:project, :repository) }
@ -26,6 +26,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
it 'has correct attributes', :aggregate_failures do
expect(attributes).to be_a(Hash)
expect(attributes[:name]).to be_nil
expect(attributes[:ref]).to eq(pipeline.ref)
expect(attributes[:sha]).to eq(pipeline.sha)
expect(attributes[:tag]).to eq(pipeline.tag)
@ -54,6 +55,16 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
expect(data[:source_pipeline]).to be_nil
end
context 'pipeline with metadata' do
let_it_be_with_reload(:pipeline_metadata) do
create(:ci_pipeline_metadata, pipeline: pipeline, name: "My Pipeline")
end
it 'has pipeline name', :aggregate_failures do
expect(attributes[:name]).to eq("My Pipeline")
end
end
context 'build with runner' do
let_it_be(:tag_names) { %w(tag-1 tag-2) }
let_it_be(:ci_runner) { create(:ci_runner, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n) }) }

View File

@ -140,7 +140,7 @@ RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do
end
describe '.table_schemas!' do
let(:tables) { %w[namespaces projects ci_builds] }
let(:tables) { %w[projects issues ci_builds] }
subject { described_class.table_schemas!(tables) }
@ -199,4 +199,82 @@ RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do
end
end
end
context 'when testing cross schema access' do
using RSpec::Parameterized::TableSyntax
before do
allow(Gitlab::Database).to receive(:all_gitlab_schemas).and_return(
[
Gitlab::Database::GitlabSchemaInfo.new(
name: "gitlab_main_clusterwide",
allow_cross_joins: %i[gitlab_shared gitlab_main],
allow_cross_transactions: %i[gitlab_internal gitlab_shared gitlab_main],
allow_cross_foreign_keys: %i[gitlab_main]
),
Gitlab::Database::GitlabSchemaInfo.new(
name: "gitlab_main",
allow_cross_joins: %i[gitlab_shared],
allow_cross_transactions: %i[gitlab_internal gitlab_shared],
allow_cross_foreign_keys: %i[]
),
Gitlab::Database::GitlabSchemaInfo.new(
name: "gitlab_ci",
allow_cross_joins: %i[gitlab_shared],
allow_cross_transactions: %i[gitlab_internal gitlab_shared],
allow_cross_foreign_keys: %i[]
)
].index_by(&:name)
)
end
describe '.cross_joins_allowed?' do
where(:schemas, :result) do
%i[] | true
%i[gitlab_main_clusterwide gitlab_main] | true
%i[gitlab_main_clusterwide gitlab_ci] | false
%i[gitlab_main_clusterwide gitlab_main gitlab_ci] | false
%i[gitlab_main_clusterwide gitlab_internal] | false
%i[gitlab_main gitlab_ci] | false
%i[gitlab_main_clusterwide gitlab_main gitlab_shared] | true
%i[gitlab_main_clusterwide gitlab_shared] | true
end
with_them do
it { expect(described_class.cross_joins_allowed?(schemas)).to eq(result) }
end
end
describe '.cross_transactions_allowed?' do
where(:schemas, :result) do
%i[] | true
%i[gitlab_main_clusterwide gitlab_main] | true
%i[gitlab_main_clusterwide gitlab_ci] | false
%i[gitlab_main_clusterwide gitlab_main gitlab_ci] | false
%i[gitlab_main_clusterwide gitlab_internal] | true
%i[gitlab_main gitlab_ci] | false
%i[gitlab_main_clusterwide gitlab_main gitlab_shared] | true
%i[gitlab_main_clusterwide gitlab_shared] | true
end
with_them do
it { expect(described_class.cross_transactions_allowed?(schemas)).to eq(result) }
end
end
describe '.cross_foreign_key_allowed?' do
where(:schemas, :result) do
%i[] | false
%i[gitlab_main_clusterwide gitlab_main] | true
%i[gitlab_main_clusterwide gitlab_ci] | false
%i[gitlab_main_clusterwide gitlab_internal] | false
%i[gitlab_main gitlab_ci] | false
%i[gitlab_main_clusterwide gitlab_shared] | false
end
with_them do
it { expect(described_class.cross_foreign_key_allowed?(schemas)).to eq(result) }
end
end
end
end

View File

@ -16,7 +16,9 @@ RSpec.describe 'cross-database foreign keys' do
end
def is_cross_db?(fk_record)
Gitlab::Database::GitlabSchema.table_schemas!([fk_record.from_table, fk_record.to_table]).many?
table_schemas = Gitlab::Database::GitlabSchema.table_schemas!([fk_record.from_table, fk_record.to_table])
!Gitlab::Database::GitlabSchema.cross_foreign_key_allowed?(table_schemas)
end
it 'onlies have allowed list of cross-database foreign keys', :aggregate_failures do

View File

@ -2,7 +2,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas, query_analyzers: false do
RSpec.describe Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas,
query_analyzers: false, feature_category: :database do
let(:analyzer) { described_class }
context 'properly analyzes queries' do
@ -15,14 +16,38 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas, query_a
expected_allowed_gitlab_schemas: {
no_schema: :dml_not_allowed,
gitlab_main: :success,
gitlab_main_clusterwide: :success,
gitlab_main_cell: :success,
gitlab_ci: :dml_access_denied # cross-schema access
}
},
"for INSERT" => {
"for SELECT on namespaces" => {
sql: "SELECT 1 FROM namespaces",
expected_allowed_gitlab_schemas: {
no_schema: :dml_not_allowed,
gitlab_main: :success,
gitlab_main_clusterwide: :success,
gitlab_main_cell: :success,
gitlab_ci: :dml_access_denied # cross-schema access
}
},
"for INSERT on projects" => {
sql: "INSERT INTO projects VALUES (1)",
expected_allowed_gitlab_schemas: {
no_schema: :dml_not_allowed,
gitlab_main: :success,
gitlab_main_clusterwide: :success,
gitlab_main_cell: :success,
gitlab_ci: :dml_access_denied # cross-schema access
}
},
"for INSERT on namespaces" => {
sql: "INSERT INTO namespaces VALUES (1)",
expected_allowed_gitlab_schemas: {
no_schema: :dml_not_allowed,
gitlab_main: :success,
gitlab_main_clusterwide: :success,
gitlab_main_cell: :success,
gitlab_ci: :dml_access_denied # cross-schema access
}
},

View File

@ -44,6 +44,18 @@ RSpec.describe Gitlab::PathTraversal, feature_category: :shared do
expect { check_path_traversal!('foo\\..') }.to raise_error(/Invalid path/)
end
it 'detects path traversal in string with encoded chars' do
expect { check_path_traversal!('foo%2F..%2Fbar') }.to raise_error(/Invalid path/)
expect { check_path_traversal!('foo%2F%2E%2E%2Fbar') }.to raise_error(/Invalid path/)
end
it 'detects double encoded chars' do
expect { check_path_traversal!('foo%252F..%2Fbar') }
.to raise_error(Gitlab::Utils::DoubleEncodingError, /is not allowed/)
expect { check_path_traversal!('foo%252F%2E%2E%2Fbar') }
.to raise_error(Gitlab::Utils::DoubleEncodingError, /is not allowed/)
end
it 'does nothing for a safe string' do
expect(check_path_traversal!('./foo')).to eq('./foo')
expect(check_path_traversal!('.test/foo')).to eq('.test/foo')

View File

@ -120,9 +120,9 @@ RSpec.describe Gitlab::Usage::ServicePingReport, :use_clean_rails_memory_store_c
# Because test cases are run inside a transaction, if any query raise and error all queries that follows
# it are automatically canceled by PostgreSQL, to avoid that problem, and to provide exhaustive information
# about every metric, queries are wrapped explicitly in sub transactions.
table = PgQuery.parse(query).tables.first
gitlab_schema = Gitlab::Database::GitlabSchema.tables_to_schema[table]
base_model = gitlab_schema == :gitlab_main ? ApplicationRecord : Ci::ApplicationRecord
table_name = PgQuery.parse(query).tables.first
gitlab_schema = Gitlab::Database::GitlabSchema.table_schema!(table_name)
base_model = Gitlab::Database.schemas_to_base_models.fetch(gitlab_schema).first
base_model.transaction do
base_model.connection.execute(query)&.first&.values&.first

View File

@ -15,6 +15,46 @@ RSpec.describe PlanLimits do
describe 'validations' do
it { is_expected.to validate_numericality_of(:notification_limit).only_integer.is_greater_than_or_equal_to(0) }
it { is_expected.to validate_numericality_of(:enforcement_limit).only_integer.is_greater_than_or_equal_to(0) }
describe 'limits_history' do
context 'when does not match the JSON schema' do
it 'does not allow invalid json' do
expect(subject).not_to allow_value({
invalid_key: {
enforcement_limit: [
{
username: 'mhamda',
timestamp: 1686140606000,
value: 5000
}
],
another_invalid: [
{
username: 'mhamda',
timestamp: 1686140606000,
value: 5000
}
]
}
}).for(:limits_history)
end
end
context 'when matches the JSON schema' do
it 'allows valid json' do
expect(subject).to allow_value({
enforcement_limit: [
{
user_id: 1,
username: 'mhamda',
timestamp: 1686140606000,
value: 5000
}
]
}).for(:limits_history)
end
end
end
end
describe '#exceeded?' do
@ -233,12 +273,17 @@ RSpec.describe PlanLimits do
%w[dashboard_limit_enabled_at]
end
it "has positive values for enabled limits" do
let(:history_columns) do
%w[limits_history]
end
it 'has positive values for enabled limits' do
attributes = plan_limits.attributes
attributes = attributes.except(described_class.primary_key)
attributes = attributes.except(described_class.reflections.values.map(&:foreign_key))
attributes = attributes.except(*columns_with_zero)
attributes = attributes.except(*datetime_columns)
attributes = attributes.except(*history_columns)
expect(attributes).to all(include(be_positive))
end
@ -256,4 +301,95 @@ RSpec.describe PlanLimits do
expect(plan_limits.dashboard_storage_limit_enabled?).to be false
end
end
describe '#log_limits_changes', :freeze_time do
let(:user) { create(:user) }
let(:plan_limits) { create(:plan_limits) }
let(:current_timestamp) { Time.current.utc.to_i }
let(:history) { plan_limits.limits_history }
it 'logs a single attribute change' do
plan_limits.log_limits_changes(user, enforcement_limit: 5_000)
expect(history).to eq(
{ 'enforcement_limit' => [{ 'user_id' => user.id, 'username' => user.username,
'timestamp' => current_timestamp, 'value' => 5_000 }] }
)
end
it 'logs multiple attribute changes' do
plan_limits.log_limits_changes(user, enforcement_limit: 10_000, notification_limit: 20_000)
expect(history).to eq(
{ 'enforcement_limit' => [{ 'user_id' => user.id, 'username' => user.username,
'timestamp' => current_timestamp, 'value' => 10_000 }],
'notification_limit' => [{ 'user_id' => user.id, 'username' => user.username,
'timestamp' => current_timestamp,
'value' => 20_000 }] }
)
end
it 'allows logging dashboard_limit_enabled_at from console (without user)' do
plan_limits.log_limits_changes(nil, dashboard_limit_enabled_at: current_timestamp)
expect(history).to eq(
{ 'dashboard_limit_enabled_at' => [{ 'user_id' => nil, 'username' => nil, 'timestamp' => current_timestamp,
'value' => current_timestamp }] }
)
end
context 'with previous history avilable' do
let(:plan_limits) do
create(:plan_limits,
limits_history: { 'enforcement_limit' => [{ user_id: user.id, username: user.username,
timestamp: current_timestamp,
value: 20_000 },
{ user_id: user.id, username: user.username, timestamp: current_timestamp,
value: 50_000 }] })
end
it 'appends to it' do
plan_limits.log_limits_changes(user, enforcement_limit: 60_000)
expect(history).to eq(
{
'enforcement_limit' => [
{ 'user_id' => user.id, 'username' => user.username, 'timestamp' => current_timestamp,
'value' => 20_000 },
{ 'user_id' => user.id, 'username' => user.username, 'timestamp' => current_timestamp,
'value' => 50_000 },
{ 'user_id' => user.id, 'username' => user.username, 'timestamp' => current_timestamp, 'value' => 60_000 }
]
}
)
end
end
end
describe '#limit_attribute_changes', :freeze_time do
let(:user) { create(:user) }
let(:current_timestamp) { Time.current.utc.to_i }
let(:plan_limits) do
create(:plan_limits,
limits_history: { 'enforcement_limit' => [
{ user_id: user.id, username: user.username, timestamp: current_timestamp,
value: 20_000 }, { user_id: user.id, username: user.username, timestamp: current_timestamp,
value: 50_000 }
] })
end
it 'returns an empty array for attribute with no changes' do
changes = plan_limits.limit_attribute_changes(:notification_limit)
expect(changes).to eq([])
end
it 'returns the changes for a specific attribute' do
changes = plan_limits.limit_attribute_changes(:enforcement_limit)
expect(changes).to eq(
[{ timestamp: current_timestamp, value: 20_000, username: user.username, user_id: user.id },
{ timestamp: current_timestamp, value: 50_000, username: user.username, user_id: user.id }]
)
end
end
end

View File

@ -23,7 +23,6 @@ module Database
ALLOW_THREAD_KEY = :allow_cross_joins_across_databases
ALLOW_ANNOTATE_KEY = ALLOW_THREAD_KEY.to_s.freeze
IGNORED_SCHEMAS = %i[gitlab_shared gitlab_internal].freeze
def self.validate_cross_joins!(sql)
return if Thread.current[ALLOW_THREAD_KEY] || sql.include?(ALLOW_ANNOTATE_KEY)
@ -41,9 +40,8 @@ module Database
end
schemas = ::Gitlab::Database::GitlabSchema.table_schemas!(tables)
schemas.subtract(IGNORED_SCHEMAS)
if schemas.many?
unless ::Gitlab::Database::GitlabSchema.cross_joins_allowed?(schemas)
Thread.current[:has_cross_join_exception] = true
raise CrossJoinAcrossUnsupportedTablesError,
"Unsupported cross-join across '#{tables.join(", ")}' querying '#{schemas.to_a.join(", ")}' discovered " \

View File

@ -42,7 +42,14 @@ RSpec.shared_examples "a user type with merge request interaction type" do
profileEnableGitpodPath
savedReplies
savedReply
user_achievements
userAchievements
bio
linkedin
twitter
discord
organization
jobTitle
createdAt
]
# TODO: 'workspaces' needs to be included, but only when this spec is run in EE context, to account for the

18
vendor/gems/README.md vendored Normal file
View File

@ -0,0 +1,18 @@
# Vendored Gems
This folder is used to store externally pulled dependencies.
## Development guidelines
The data stored in this directory should adhere to the following rules:
- MUST: Contain `GITLAB.md` to indicate where this data was pulled
from with a description of what changes were made.
- MUST: Be added to `.gitlab/ci/vendored-gems.gitlab-ci.yml`.
- MUST NOT: Reference source code from outside of `vendor/gems/` or `require_relative "../../lib"`.
- MUST NOT: Require other gems that would result in circular dependencies.
- SHOULD NOT: Be published to RubyGems under our name.
- SHOULD: Be used with `gem <name>, path: "vendor/mail-smtp_pool"`.
- RECOMMENDED: Be added to `CODEOWNERS`.
- MAY: Reference other Gems in `vendor/gems/` with `gem <name>, path: "../mail-smtp_pool"`.
- MAY: Contain our patches to make them work with the GitLab monorepo, for example to continue to support deprecated or unmaintained dependences.