Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-11-21 15:13:27 +00:00
parent eec96715e5
commit f1c788bb18
55 changed files with 993 additions and 175 deletions

View File

@ -765,41 +765,46 @@ rspec system pg14-as-if-foss clusterwide-db:
- .clusterwide-db
- .rails:rules:clusterwide-db
rspec-ee unit gitlab-duo-chat pg14:
variables:
REAL_AI_REQUEST: "true"
RSPEC_RETRY_RETRY_COUNT: 0
.rspec-ee-base-gitlab-duo:
extends:
- .rspec-ee-base-pg14
- .rails:rules:ee-gitlab-duo-chat-base
parallel:
matrix:
- DUO_RSPEC: ["lib/gitlab/llm/chain/agents/zero_shot/executor_real_requests_spec.rb", "support_specs/helpers/chat_qa_evaluation_helpers_spec.rb"]
variables:
REAL_AI_REQUEST: "true"
rspec-ee unit gitlab-duo-chat-zeroshot pg14:
extends:
- .rspec-ee-base-gitlab-duo
- .rails:rules:ee-gitlab-duo-chat-optional
script:
- !reference [.base-script, script]
- bundle exec rspec -Ispec -rspec_helper --failure-exit-code 0 --tag real_ai_request --color -- ee/spec/${DUO_RSPEC}
- rspec_paralellized_job "--tag zeroshot_executor"
rspec-ee unit gitlab-duo-chat-qa-fast pg14:
extends:
- .rspec-ee-base-gitlab-duo
- .rails:rules:ee-gitlab-duo-chat-qa-fast
script:
- !reference [.base-script, script]
- rspec_paralellized_job "--tag fast_chat_qa_evaluation"
rspec-ee unit gitlab-duo-chat-qa pg14:
variables:
REAL_AI_REQUEST: "true"
QA_EVAL_REPORT_FILENAME: "qa_evaluation_report.md"
RSPEC_RETRY_RETRY_COUNT: 0
extends:
- .rspec-ee-base-pg14
- .rails:rules:ee-gitlab-duo-chat-base
parallel:
matrix:
- DUO_RSPEC: ["qa_epic_spec.rb", "qa_issue_spec.rb"]
- .rspec-ee-base-gitlab-duo
- .rails:rules:ee-gitlab-duo-chat-qa-full
script:
- !reference [.base-script, script]
- source ./scripts/utils.sh
- install_gitlab_gem
- bundle exec rspec -Ispec -rspec_helper --failure-exit-code 0 --tag real_ai_request --color -- ee/spec/lib/gitlab/llm/chain/agents/zero_shot/${DUO_RSPEC}
- bundle exec rspec -Ispec -rspec_helper --failure-exit-code 0 --color --tag chat_qa_evaluation -- ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb
- ./scripts/duo_chat/reporter.rb
artifacts:
expire_in: 5d
paths:
- tmp/duo_chat/qa*.json
- "${DUO_RSPEC}.md"
- "${QA_EVAL_REPORT_FILENAME}"
rspec-ee migration pg14:
extends:

View File

@ -2125,8 +2125,28 @@
when: never
- if: '$VERTEX_AI_CREDENTIALS == null'
when: never
- <<: *if-fork-merge-request
when: never
.rails:rules:ee-gitlab-duo-chat-optional:
rules:
- !reference [".rails:rules:ee-gitlab-duo-chat-base", rules]
- <<: *if-merge-request
changes: *backend-patterns
when: manual
allow_failure: true
.rails:rules:ee-gitlab-duo-chat-qa-fast:
rules:
- !reference [".rails:rules:ee-gitlab-duo-chat-base", rules]
- <<: *if-merge-request
changes: *ai-patterns
.rails:rules:ee-gitlab-duo-chat-qa-full:
rules:
- !reference [".rails:rules:ee-gitlab-duo-chat-optional", rules]
- <<: *if-default-branch-refs
changes: *setup-test-env-patterns
when: manual
allow_failure: true

View File

@ -325,6 +325,7 @@ RSpec/VerifiedDoubles:
- 'spec/lib/banzai/render_context_spec.rb'
- 'spec/lib/banzai/renderer_spec.rb'
- 'spec/lib/bitbucket/connection_spec.rb'
- 'spec/lib/bitbucket/exponential_backoff_spec.rb'
- 'spec/lib/bitbucket/paginator_spec.rb'
- 'spec/lib/bitbucket_server/paginator_spec.rb'
- 'spec/lib/bulk_imports/clients/http_spec.rb'

View File

@ -146,7 +146,7 @@ export const config = {
},
IssueConnection: {
merge(existing = { nodes: [] }, incoming, { args }) {
if (!args.after) {
if (!args?.after) {
return incoming;
}
return {

View File

@ -150,6 +150,7 @@
"User": [
"AddOnUser",
"AutocompletedUser",
"CurrentUser",
"MergeRequestAssignee",
"MergeRequestAuthor",
"MergeRequestParticipant",

View File

@ -3,25 +3,36 @@ import { createDateTimeFormat } from '~/locale';
/**
* Format a Date with the help of {@link DateTimeFormat.asDateTime}
*
* Note: In case you can use localDateFormat.asDateTime directly, please do that.
* Note: In case you can use localeDateFormat.asDateTime directly, please do that.
*
* @example
* localDateFormat[DATE_WITH_TIME_FORMAT].format(date) // returns 'Jul 6, 2020, 2:43 PM'
* localDateFormat[DATE_WITH_TIME_FORMAT].formatRange(date, date) // returns 'Jul 6, 2020, 2:45PM 8:43 PM'
* localeDateFormat[DATE_WITH_TIME_FORMAT].format(date) // returns 'Jul 6, 2020, 2:43 PM'
* localeDateFormat[DATE_WITH_TIME_FORMAT].formatRange(date, date) // returns 'Jul 6, 2020, 2:45PM 8:43 PM'
*/
export const DATE_WITH_TIME_FORMAT = 'asDateTime';
/**
* Format a Date with the help of {@link DateTimeFormat.asDateTimeFull}
*
* Note: In case you can use localeDateFormat.asDateTimeFull directly, please do that.
*
* @example
* localeDateFormat[DATE_TIME_FULL_FORMAT].format(date) // returns 'July 6, 2020 at 2:43:12 PM GMT'
*/
export const DATE_TIME_FULL_FORMAT = 'asDateTimeFull';
/**
* Format a Date with the help of {@link DateTimeFormat.asDate}
*
* Note: In case you can use localDateFormat.asDate directly, please do that.
* Note: In case you can use localeDateFormat.asDate directly, please do that.
*
* @example
* localDateFormat[DATE_ONLY_FORMAT].format(date) // returns 'Jul 05, 2023'
* localDateFormat[DATE_ONLY_FORMAT].formatRange(date, date) // returns 'Jul 05 - Jul 07, 2023'
* localeDateFormat[DATE_ONLY_FORMAT].format(date) // returns 'Jul 05, 2023'
* localeDateFormat[DATE_ONLY_FORMAT].formatRange(date, date) // returns 'Jul 05 - Jul 07, 2023'
*/
export const DATE_ONLY_FORMAT = 'asDate';
export const DEFAULT_DATE_TIME_FORMAT = DATE_WITH_TIME_FORMAT;
export const DATE_TIME_FORMATS = [DATE_WITH_TIME_FORMAT, DATE_ONLY_FORMAT];
export const DATE_TIME_FORMATS = [DATE_WITH_TIME_FORMAT, DATE_TIME_FULL_FORMAT, DATE_ONLY_FORMAT];
/**
* The DateTimeFormat utilities support formatting a number of types,
@ -54,7 +65,7 @@ class DateTimeFormat {
* @example
* // en-US: returns something like Jul 6, 2020, 2:43 PM
* // en-GB: returns something like 6 Jul 2020, 14:43
* localDateFormat.asDateTime.format(date)
* localeDateFormat.asDateTime.format(date)
*
* @returns {DateTimeFormatter}
*/
@ -68,6 +79,32 @@ class DateTimeFormat {
})
);
}
/**
* Locale aware formatter to a complete date time.
*
* This is needed if you need to convey a full timestamp including timezone and seconds.
*
* This is mainly used in tooltips. Use {@link DateTimeFormat.asDateTime}
* if you don't need to show all the information.
*
*
* @example
* // en-US: returns something like July 6, 2020 at 2:43:12 PM GMT
* // en-GB: returns something like 6 July 2020 at 14:43:12 GMT
* localeDateFormat.asDateTimeFull.format(date)
*
* @returns {DateTimeFormatter}
*/
get asDateTimeFull() {
return (
this.#formatters[DATE_TIME_FULL_FORMAT] ||
this.#createFormatter(DATE_TIME_FULL_FORMAT, {
dateStyle: 'long',
timeStyle: 'long',
hourCycle: DateTimeFormat.#hourCycle,
})
);
}
/**
* Locale aware formatter to display a only the date.
@ -77,12 +114,12 @@ class DateTimeFormat {
* @example
* // en-US: returns something like Jul 6, 2020
* // en-GB: returns something like 6 Jul 2020
* localDateFormat.asDate.format(date)
* localeDateFormat.asDate.format(date)
*
* @example
* // en-US: returns something like Jul 6 7, 2020
* // en-GB: returns something like 6-7 Jul 2020
* localDateFormat.asDate.formatRange(date, date2)
* localeDateFormat.asDate.formatRange(date, date2)
*
* @returns {DateTimeFormatter}
*/
@ -177,6 +214,7 @@ class DateTimeFormat {
*
* DateTime (showing both date and times):
* - {@link DateTimeFormat.asDateTime localeDateFormat.asDateTime} - the default format for date times
* - {@link DateTimeFormat.asDateTimeFull localeDateFormat.asDateTimeFull} - full format, including timezone and seconds
*
* Date (showing date only):
* - {@link DateTimeFormat.asDate localeDateFormat.asDate} - the default format for a date

View File

@ -1,7 +1,6 @@
import * as timeago from 'timeago.js';
import { languageCode, s__ } from '~/locale';
import { DEFAULT_DATE_TIME_FORMAT, localeDateFormat } from '~/lib/utils/datetime/locale_dateformat';
import { formatDate } from './date_format_utility';
/**
* Timeago uses underscores instead of dashes to separate language from country code.
@ -130,7 +129,7 @@ export const localTimeAgo = (elements, updateTooltip = true) => {
function addTimeAgoTooltip() {
elements.forEach((el) => {
// Recreate with custom template
el.setAttribute('title', formatDate(el.dateTime));
el.setAttribute('title', localeDateFormat.asDateTimeFull.format(el.dateTime));
});
}

View File

@ -48,9 +48,9 @@ export default {
<template>
<gl-form-checkbox-group v-model="selectedFilter">
<h5 class="gl-mt-0 gl-mb-5 gl-font-sm">
<div class="gl-mb-2 gl-font-weight-bold gl-font-sm" data-testid="archived-filter-title">
{{ $options.archivedFilterData.headerLabel }}
</h5>
</div>
<gl-form-checkbox
class="gl-flex-grow-1 gl-display-inline-flex gl-justify-content-space-between gl-w-full"
:class="$options.LABEL_DEFAULT_CLASSES"

View File

@ -40,7 +40,11 @@ export default {
</script>
<template>
<gl-form class="issue-filters gl-px-5 gl-pt-0" @submit.prevent="applyQueryWithTracking">
<gl-form
class="issue-filters gl-px-5 gl-pt-0"
:aria-label="__('Search filters')"
@submit.prevent="applyQueryWithTracking"
>
<slot></slot>
<div class="gl-display-flex gl-align-items-center gl-mt-4">
<gl-button category="primary" variant="confirm" type="submit" :disabled="!sidebarDirty">

View File

@ -179,10 +179,10 @@ export default {
<template>
<div class="gl-pb-0 gl-md-pt-0 label-filter gl-relative">
<h5 class="gl-my-0 gl-font-sm" data-testid="label-filter-title">
<div class="gl-mb-2 gl-font-weight-bold gl-font-sm" data-testid="label-filter-title">
{{ $options.labelFilterData.header }}
</h5>
<div class="gl-my-5">
</div>
<div>
<gl-label
v-for="label in unappliedNewLabels"
:key="label.key"

View File

@ -75,9 +75,9 @@ export default {
<template>
<div v-if="hasBuckets" class="language-filter-checkbox">
<h5 class="gl-mt-0 gl-mb-5 gl-font-sm">
<div class="gl-mb-2 gl-font-weight-bold gl-font-sm">
{{ $options.languageFilterData.header }}
</h5>
</div>
<div
v-if="!aggregations.error"
class="gl-overflow-x-hidden gl-overflow-y-auto"

View File

@ -57,9 +57,9 @@ export default {
<template>
<div>
<h5 class="gl-mt-0 gl-mb-5 gl-font-sm">
<div class="gl-mb-2 gl-font-weight-bold gl-font-sm">
{{ filterData.header }}
</h5>
</div>
<gl-form-radio-group v-model="selectedFilter">
<gl-form-radio v-for="f in filtersArray" :key="f.value" :value="f.value">
{{ radioLabel(f) }}

View File

@ -1,4 +1,4 @@
import { formatDate, getTimeago, timeagoLanguageCode } from '~/lib/utils/datetime_utility';
import { getTimeago, localeDateFormat, timeagoLanguageCode } from '~/lib/utils/datetime_utility';
/**
* Mixin with time ago methods used in some vue components
@ -12,7 +12,7 @@ export default {
},
tooltipTitle(time) {
return formatDate(time);
return localeDateFormat.asDateTimeFull.format(time);
},
},
};

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
module Mutations
module Projects
class Star < BaseMutation
graphql_name 'StarProject'
authorize :read_project
argument :project_id,
::Types::GlobalIDType[::Project],
required: true,
description: 'Full path of the project to star or unstar.'
argument :starred,
GraphQL::Types::Boolean,
required: true,
description: 'Indicates whether to star or unstar the project.'
field :count,
GraphQL::Types::String,
null: false,
description: 'Number of stars for the project.'
def resolve(project_id:, starred:)
project = authorized_find!(id: project_id)
if current_user.starred?(project) != starred
current_user.toggle_star(project)
project.reset
end
{
count: project.star_count
}
end
end
end
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Types
# rubocop:disable Graphql/AuthorizeTypes -- This is not necessary because the superclass declares the authorization
class CurrentUserType < ::Types::UserType
graphql_name 'CurrentUser'
description 'The currently authenticated GitLab user.'
end
# rubocop:enable Graphql/AuthorizeTypes
end
::Types::CurrentUserType.prepend_mod

View File

@ -108,6 +108,7 @@ module Types
mount_mutation Mutations::Notes::Destroy
mount_mutation Mutations::Organizations::Create, alpha: { milestone: '16.6' }
mount_mutation Mutations::Projects::SyncFork, calls_gitaly: true, alpha: { milestone: '15.9' }
mount_mutation Mutations::Projects::Star, alpha: { milestone: '16.7' }
mount_mutation Mutations::Releases::Create
mount_mutation Mutations::Releases::Update
mount_mutation Mutations::Releases::Delete

View File

@ -48,7 +48,7 @@ module Types
required: true,
description: 'Global ID of the container repository.'
end
field :current_user, Types::UserType,
field :current_user, Types::CurrentUserType,
null: true,
description: "Get information about current user."
field :design_management, Types::DesignManagementType,

View File

@ -229,5 +229,3 @@ module Types
end
end
end
Types::UserInterface.prepend_mod

View File

@ -14,3 +14,5 @@ module Types
present_using UserPresenter
end
end
Types::UserType.prepend_mod

View File

@ -2172,15 +2172,7 @@ class MergeRequest < ApplicationRecord
end
def current_patch_id_sha
return merge_request_diff.patch_id_sha if merge_request_diff.patch_id_sha.present?
base_sha = diff_refs&.base_sha
head_sha = diff_refs&.head_sha
return unless base_sha && head_sha
return if base_sha == head_sha
project.repository.get_patch_id(base_sha, head_sha)
merge_request_diff.get_patch_id_sha
end
private

View File

@ -237,6 +237,17 @@ class MergeRequestDiff < ApplicationRecord
)
end
def get_patch_id_sha
return patch_id_sha if patch_id_sha.present?
set_patch_id_sha
return unless patch_id_sha.present?
save
patch_id_sha
end
def set_as_latest_diff
# Don't set merge_head diff as latest so it won't get considered as the
# MergeRequest#merge_request_diff.

View File

@ -0,0 +1,8 @@
---
name: bitbucket_importer_exponential_backoff
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136842
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432379
milestone: '16.7'
type: development
group: group::import and integrate
default_enabled: false

View File

@ -12,7 +12,7 @@ as an OmniAuth provider.
To enable the OpenID Connect OmniAuth provider, you must register your application
with an OpenID Connect provider.
The OpenID Connect provides you with a client's details and secret for you to use.
The OpenID Connect provider provides you with a client's details and secret for you to use.
1. On your GitLab server, open the configuration file.

View File

@ -264,7 +264,7 @@ Returns [`CurrentLicense`](#currentlicense).
Get information about current user.
Returns [`UserCore`](#usercore).
Returns [`CurrentUser`](#currentuser).
### `Query.designManagement`
@ -1011,7 +1011,7 @@ Returns [`Workspace`](#workspace).
### `Query.workspaces`
Find workspaces owned by the current user by their IDs.
Find workspaces owned by the current user.
WARNING:
**Introduced** in 16.0.
@ -5209,6 +5209,29 @@ Input type: `MemberRoleCreateInput`
| <a id="mutationmemberrolecreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationmemberrolecreatememberrole"></a>`memberRole` | [`MemberRole`](#memberrole) | Updated member role. |
### `Mutation.memberRoleDelete`
WARNING:
**Introduced** in 16.7.
This feature is an Experiment. It can be changed or removed at any time.
Input type: `MemberRoleDeleteInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationmemberroledeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationmemberroledeleteid"></a>`id` | [`MemberRoleID!`](#memberroleid) | ID of the member role to delete. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationmemberroledeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationmemberroledeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationmemberroledeletememberrole"></a>`memberRole` | [`MemberRole`](#memberrole) | Deleted member role. |
### `Mutation.memberRoleUpdate`
Input type: `MemberRoleUpdateInput`
@ -6792,6 +6815,30 @@ Input type: `SecurityTrainingUpdateInput`
| <a id="mutationsecuritytrainingupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationsecuritytrainingupdatetraining"></a>`training` | [`ProjectSecurityTraining`](#projectsecuritytraining) | Represents the training entity subject to mutation. |
### `Mutation.starProject`
WARNING:
**Introduced** in 16.7.
This feature is an Experiment. It can be changed or removed at any time.
Input type: `StarProjectInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationstarprojectclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationstarprojectprojectid"></a>`projectId` | [`ProjectID!`](#projectid) | Full path of the project to star or unstar. |
| <a id="mutationstarprojectstarred"></a>`starred` | [`Boolean!`](#boolean) | Indicates whether to star or unstar the project. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationstarprojectclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationstarprojectcount"></a>`count` | [`String!`](#string) | Number of stars for the project. |
| <a id="mutationstarprojecterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.terraformStateDelete`
Input type: `TerraformStateDeleteInput`
@ -13887,6 +13934,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
Workspaces owned by the current user.
WARNING:
**Introduced** in 16.6.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkspaceConnection`](#workspaceconnection).
This field returns a [connection](#connections). It accepts the
@ -14561,6 +14612,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
Workspaces owned by the current user.
WARNING:
**Introduced** in 16.6.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkspaceConnection`](#workspaceconnection).
This field returns a [connection](#connections). It accepts the
@ -16280,6 +16335,291 @@ Represents the current license.
| <a id="currentlicenseusersinlicensecount"></a>`usersInLicenseCount` | [`Int`](#int) | Number of paid users in the license. |
| <a id="currentlicenseusersoverlicensecount"></a>`usersOverLicenseCount` | [`Int`](#int) | Number of users over the paid users in the license. |
### `CurrentUser`
The currently authenticated GitLab user.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentuseravatarurl"></a>`avatarUrl` | [`String`](#string) | URL of the user's avatar. |
| <a id="currentuserbio"></a>`bio` | [`String`](#string) | Bio of the user. |
| <a id="currentuserbot"></a>`bot` | [`Boolean!`](#boolean) | Indicates if the user is a bot. |
| <a id="currentusercallouts"></a>`callouts` | [`UserCalloutConnection`](#usercalloutconnection) | User callouts that belong to the user. (see [Connections](#connections)) |
| <a id="currentusercommitemail"></a>`commitEmail` | [`String`](#string) | User's default commit email. |
| <a id="currentusercreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the user was created. |
| <a id="currentuserdiscord"></a>`discord` | [`String`](#string) | Discord ID of the user. |
| <a id="currentuseremail"></a>`email` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.7. This was renamed. Use: [`User.publicEmail`](#userpublicemail). |
| <a id="currentuseremails"></a>`emails` | [`EmailConnection`](#emailconnection) | User's email addresses. (see [Connections](#connections)) |
| <a id="currentusergitpodenabled"></a>`gitpodEnabled` | [`Boolean`](#boolean) | Whether Gitpod is enabled at the user level. |
| <a id="currentusergroupcount"></a>`groupCount` | [`Int`](#int) | Group count for the user. |
| <a id="currentusergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
| <a id="currentuserid"></a>`id` | [`ID!`](#id) | ID of the user. |
| <a id="currentuseride"></a>`ide` | [`Ide`](#ide) | IDE settings. |
| <a id="currentuserjobtitle"></a>`jobTitle` | [`String`](#string) | Job title of the user. |
| <a id="currentuserlastactivityon"></a>`lastActivityOn` | [`Date`](#date) | Date the user last performed any actions. |
| <a id="currentuserlinkedin"></a>`linkedin` | [`String`](#string) | LinkedIn profile name of the user. |
| <a id="currentuserlocation"></a>`location` | [`String`](#string) | Location of the user. |
| <a id="currentusername"></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="currentusernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
| <a id="currentusernamespacecommitemails"></a>`namespaceCommitEmails` | [`NamespaceCommitEmailConnection`](#namespacecommitemailconnection) | User's custom namespace commit emails. (see [Connections](#connections)) |
| <a id="currentuserorganization"></a>`organization` | [`String`](#string) | Who the user represents or works for. |
| <a id="currentuserorganizations"></a>`organizations` **{warning-solid}** | [`OrganizationConnection`](#organizationconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Organizations where the user has access. |
| <a id="currentuserpreferencesgitpodpath"></a>`preferencesGitpodPath` | [`String`](#string) | Web path to the Gitpod section within user preferences. |
| <a id="currentuserprofileenablegitpodpath"></a>`profileEnableGitpodPath` | [`String`](#string) | Web path to enable Gitpod for the user. |
| <a id="currentuserprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
| <a id="currentuserpronouns"></a>`pronouns` | [`String`](#string) | Pronouns of the user. |
| <a id="currentuserpublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
| <a id="currentusersavedreplies"></a>`savedReplies` | [`SavedReplyConnection`](#savedreplyconnection) | Saved replies authored by the user. (see [Connections](#connections)) |
| <a id="currentuserstate"></a>`state` | [`UserState!`](#userstate) | State of the user. |
| <a id="currentuserstatus"></a>`status` | [`UserStatus`](#userstatus) | User status. |
| <a id="currentusertwitter"></a>`twitter` | [`String`](#string) | Twitter username of the user. |
| <a id="currentuseruserachievements"></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="currentuseruserpermissions"></a>`userPermissions` | [`UserPermissions!`](#userpermissions) | Permissions for the current user on the resource. |
| <a id="currentuserusername"></a>`username` | [`String!`](#string) | Username of the user. Unique within this instance of GitLab. |
| <a id="currentuserwebpath"></a>`webPath` | [`String!`](#string) | Web path of the user. |
| <a id="currentuserweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the user. |
#### Fields with arguments
##### `CurrentUser.assignedMergeRequests`
Merge requests assigned to the user.
Returns [`MergeRequestConnection`](#mergerequestconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentuserassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="currentuserassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="currentuserassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="currentuserassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
| <a id="currentuserassignedmergerequestsdraft"></a>`draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. |
| <a id="currentuserassignedmergerequestsgroupid"></a>`groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. |
| <a id="currentuserassignedmergerequestsiids"></a>`iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. |
| <a id="currentuserassignedmergerequestslabels"></a>`labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. |
| <a id="currentuserassignedmergerequestsmergedafter"></a>`mergedAfter` | [`Time`](#time) | Merge requests merged after this date. |
| <a id="currentuserassignedmergerequestsmergedbefore"></a>`mergedBefore` | [`Time`](#time) | Merge requests merged before this date. |
| <a id="currentuserassignedmergerequestsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Title of the milestone. |
| <a id="currentuserassignedmergerequestsnot"></a>`not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. |
| <a id="currentuserassignedmergerequestsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. |
| <a id="currentuserassignedmergerequestsprojectpath"></a>`projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. |
| <a id="currentuserassignedmergerequestsreviewerusername"></a>`reviewerUsername` | [`String`](#string) | Username of the reviewer. |
| <a id="currentuserassignedmergerequestssort"></a>`sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. |
| <a id="currentuserassignedmergerequestssourcebranches"></a>`sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. |
| <a id="currentuserassignedmergerequestsstate"></a>`state` | [`MergeRequestState`](#mergerequeststate) | Merge request state. If provided, all resolved merge requests will have this state. |
| <a id="currentuserassignedmergerequeststargetbranches"></a>`targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. |
| <a id="currentuserassignedmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| <a id="currentuserassignedmergerequestsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
##### `CurrentUser.authoredMergeRequests`
Merge requests authored by the user.
Returns [`MergeRequestConnection`](#mergerequestconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentuserauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="currentuserauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="currentuserauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="currentuserauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
| <a id="currentuserauthoredmergerequestsdraft"></a>`draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. |
| <a id="currentuserauthoredmergerequestsgroupid"></a>`groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. |
| <a id="currentuserauthoredmergerequestsiids"></a>`iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. |
| <a id="currentuserauthoredmergerequestslabels"></a>`labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. |
| <a id="currentuserauthoredmergerequestsmergedafter"></a>`mergedAfter` | [`Time`](#time) | Merge requests merged after this date. |
| <a id="currentuserauthoredmergerequestsmergedbefore"></a>`mergedBefore` | [`Time`](#time) | Merge requests merged before this date. |
| <a id="currentuserauthoredmergerequestsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Title of the milestone. |
| <a id="currentuserauthoredmergerequestsnot"></a>`not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. |
| <a id="currentuserauthoredmergerequestsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. |
| <a id="currentuserauthoredmergerequestsprojectpath"></a>`projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. |
| <a id="currentuserauthoredmergerequestsreviewerusername"></a>`reviewerUsername` | [`String`](#string) | Username of the reviewer. |
| <a id="currentuserauthoredmergerequestssort"></a>`sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. |
| <a id="currentuserauthoredmergerequestssourcebranches"></a>`sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. |
| <a id="currentuserauthoredmergerequestsstate"></a>`state` | [`MergeRequestState`](#mergerequeststate) | Merge request state. If provided, all resolved merge requests will have this state. |
| <a id="currentuserauthoredmergerequeststargetbranches"></a>`targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. |
| <a id="currentuserauthoredmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| <a id="currentuserauthoredmergerequestsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
##### `CurrentUser.groups`
Groups where the user has access.
Returns [`GroupConnection`](#groupconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentusergroupspermissionscope"></a>`permissionScope` | [`GroupPermission`](#grouppermission) | Filter by permissions the user has on groups. |
| <a id="currentusergroupssearch"></a>`search` | [`String`](#string) | Search by group name or path. |
##### `CurrentUser.reviewRequestedMergeRequests`
Merge requests assigned to the user for review.
Returns [`MergeRequestConnection`](#mergerequestconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentuserreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="currentuserreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="currentuserreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="currentuserreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="currentuserreviewrequestedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
| <a id="currentuserreviewrequestedmergerequestsdraft"></a>`draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. |
| <a id="currentuserreviewrequestedmergerequestsgroupid"></a>`groupId` | [`GroupID`](#groupid) | The global ID of the group the authored merge requests should be in. Merge requests in subgroups are included. |
| <a id="currentuserreviewrequestedmergerequestsiids"></a>`iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. |
| <a id="currentuserreviewrequestedmergerequestslabels"></a>`labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. |
| <a id="currentuserreviewrequestedmergerequestsmergedafter"></a>`mergedAfter` | [`Time`](#time) | Merge requests merged after this date. |
| <a id="currentuserreviewrequestedmergerequestsmergedbefore"></a>`mergedBefore` | [`Time`](#time) | Merge requests merged before this date. |
| <a id="currentuserreviewrequestedmergerequestsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Title of the milestone. |
| <a id="currentuserreviewrequestedmergerequestsnot"></a>`not` | [`MergeRequestsResolverNegatedParams`](#mergerequestsresolvernegatedparams) | List of negated arguments. Warning: this argument is experimental and a subject to change in future. |
| <a id="currentuserreviewrequestedmergerequestsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | The global ID of the project the authored merge requests should be in. Incompatible with projectPath. |
| <a id="currentuserreviewrequestedmergerequestsprojectpath"></a>`projectPath` | [`String`](#string) | The full-path of the project the authored merge requests should be in. Incompatible with projectId. |
| <a id="currentuserreviewrequestedmergerequestssort"></a>`sort` | [`MergeRequestSort`](#mergerequestsort) | Sort merge requests by this criteria. |
| <a id="currentuserreviewrequestedmergerequestssourcebranches"></a>`sourceBranches` | [`[String!]`](#string) | Array of source branch names. All resolved merge requests will have one of these branches as their source. |
| <a id="currentuserreviewrequestedmergerequestsstate"></a>`state` | [`MergeRequestState`](#mergerequeststate) | Merge request state. If provided, all resolved merge requests will have this state. |
| <a id="currentuserreviewrequestedmergerequeststargetbranches"></a>`targetBranches` | [`[String!]`](#string) | Array of target branch names. All resolved merge requests will have one of these branches as their target. |
| <a id="currentuserreviewrequestedmergerequestsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Merge requests updated after this timestamp. |
| <a id="currentuserreviewrequestedmergerequestsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Merge requests updated before this timestamp. |
##### `CurrentUser.savedReply`
Saved reply authored by the user.
Returns [`SavedReply`](#savedreply).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentusersavedreplyid"></a>`id` | [`UsersSavedReplyID!`](#userssavedreplyid) | ID of a saved reply. |
##### `CurrentUser.snippets`
Snippets authored by the user.
Returns [`SnippetConnection`](#snippetconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentusersnippetsids"></a>`ids` | [`[SnippetID!]`](#snippetid) | Array of global snippet IDs. For example, `gid://gitlab/ProjectSnippet/1`. |
| <a id="currentusersnippetstype"></a>`type` | [`TypeEnum`](#typeenum) | Type of snippet. |
| <a id="currentusersnippetsvisibility"></a>`visibility` | [`VisibilityScopesEnum`](#visibilityscopesenum) | Visibility of the snippet. |
##### `CurrentUser.starredProjects`
Projects starred by the user.
Returns [`ProjectConnection`](#projectconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentuserstarredprojectssearch"></a>`search` | [`String`](#string) | Search query. |
##### `CurrentUser.timelogs`
Time logged by the user.
Returns [`TimelogConnection`](#timelogconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentusertimelogsenddate"></a>`endDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or before endDate. |
| <a id="currentusertimelogsendtime"></a>`endTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or before endTime. |
| <a id="currentusertimelogsgroupid"></a>`groupId` | [`GroupID`](#groupid) | List timelogs for a group. |
| <a id="currentusertimelogsprojectid"></a>`projectId` | [`ProjectID`](#projectid) | List timelogs for a project. |
| <a id="currentusertimelogssort"></a>`sort` | [`TimelogSort`](#timelogsort) | List timelogs in a particular order. |
| <a id="currentusertimelogsstartdate"></a>`startDate` | [`Time`](#time) | List timelogs within a date range where the logged date is equal to or after startDate. |
| <a id="currentusertimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
| <a id="currentusertimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
##### `CurrentUser.todos`
To-do items of the user.
Returns [`TodoConnection`](#todoconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentusertodosaction"></a>`action` | [`[TodoActionEnum!]`](#todoactionenum) | Action to be filtered. |
| <a id="currentusertodosauthorid"></a>`authorId` | [`[ID!]`](#id) | ID of an author. |
| <a id="currentusertodosgroupid"></a>`groupId` | [`[ID!]`](#id) | ID of a group. |
| <a id="currentusertodosprojectid"></a>`projectId` | [`[ID!]`](#id) | ID of a project. |
| <a id="currentusertodosstate"></a>`state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. |
| <a id="currentusertodostype"></a>`type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. |
##### `CurrentUser.workspaces`
Workspaces owned by the current user.
WARNING:
**Introduced** in 16.6.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkspaceConnection`](#workspaceconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="currentuserworkspacesids"></a>`ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| <a id="currentuserworkspacesincludeactualstates"></a>`includeActualStates` | [`[String!]`](#string) | Includes all workspaces that match any of the actual states. |
| <a id="currentuserworkspacesprojectids"></a>`projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project id. |
### `CustomEmoji`
A custom emoji uploaded by user.
@ -21046,6 +21386,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
Workspaces owned by the current user.
WARNING:
**Introduced** in 16.6.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkspaceConnection`](#workspaceconnection).
This field returns a [connection](#connections). It accepts the
@ -21328,6 +21672,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
Workspaces owned by the current user.
WARNING:
**Introduced** in 16.6.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkspaceConnection`](#workspaceconnection).
This field returns a [connection](#connections). It accepts the
@ -21673,6 +22021,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
Workspaces owned by the current user.
WARNING:
**Introduced** in 16.6.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkspaceConnection`](#workspaceconnection).
This field returns a [connection](#connections). It accepts the
@ -21991,6 +22343,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
Workspaces owned by the current user.
WARNING:
**Introduced** in 16.6.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkspaceConnection`](#workspaceconnection).
This field returns a [connection](#connections). It accepts the
@ -26965,6 +27321,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
Workspaces owned by the current user.
WARNING:
**Introduced** in 16.6.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkspaceConnection`](#workspaceconnection).
This field returns a [connection](#connections). It accepts the
@ -32309,6 +32669,7 @@ Implementations:
- [`AddOnUser`](#addonuser)
- [`AutocompletedUser`](#autocompleteduser)
- [`CurrentUser`](#currentuser)
- [`MergeRequestAssignee`](#mergerequestassignee)
- [`MergeRequestAuthor`](#mergerequestauthor)
- [`MergeRequestParticipant`](#mergerequestparticipant)
@ -32574,24 +32935,6 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="usertodosstate"></a>`state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. |
| <a id="usertodostype"></a>`type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. |
###### `User.workspaces`
Workspaces owned by the current user.
Returns [`WorkspaceConnection`](#workspaceconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
####### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="userworkspacesids"></a>`ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
| <a id="userworkspacesincludeactualstates"></a>`includeActualStates` | [`[String!]`](#string) | Includes all workspaces that match any of the actual states. |
| <a id="userworkspacesprojectids"></a>`projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project id. |
#### `WorkItemWidget`
Implementations:

View File

@ -1911,6 +1911,9 @@ GET /groups/:id/push_rule
{
"id": 2,
"created_at": "2020-08-17T19:09:19.580Z",
"commit_committer_check": true,
"commit_committer_name_check": true,
"reject_unsigned_commits": false,
"commit_message_regex": "[a-zA-Z]",
"commit_message_negative_regex": "[x+]",
"branch_name_regex": "[a-z]",
@ -1923,19 +1926,6 @@ GET /groups/:id/push_rule
}
```
Users on [GitLab Premium or Ultimate](https://about.gitlab.com/pricing/) also see
the `commit_committer_check` and `reject_unsigned_commits` parameters:
```json
{
"id": 2,
"created_at": "2020-08-17T19:09:19.580Z",
"commit_committer_check": true,
"reject_unsigned_commits": false,
...
}
```
### Add group push rule
Adds [push rules](../user/group/access_and_permissions.md#group-push-rules) to the specified group.
@ -1952,6 +1942,7 @@ POST /groups/:id/push_rule
| `deny_delete_tag` | boolean | no | Deny deleting a tag |
| `member_check` | boolean | no | Allows only GitLab users to author commits |
| `prevent_secrets` | boolean | no | [Files that are likely to contain secrets](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/checks/files_denylist.yml) are rejected |
| `commit_committer_name_check` | boolean | no | Users can only push commits to this repository if the commit author name is consistent with their GitLab account name |
| `commit_message_regex` | string | no | All commit messages must match the regular expression provided in this attribute, for example, `Fixed \d+\..*` |
| `commit_message_negative_regex` | string | no | Commit messages matching the regular expression provided in this attribute aren't allowed, for example, `ssh\:\/\/` |
| `branch_name_regex` | string | no | All branch names must match the regular expression provided in this attribute, for example, `(feature|hotfix)\/*` |
@ -1971,6 +1962,7 @@ Response:
{
"id": 19,
"created_at": "2020-08-31T15:53:00.073Z",
"commit_committer_name_check": false,
"commit_message_regex": "[a-zA-Z]",
"commit_message_negative_regex": "[x+]",
"branch_name_regex": null,
@ -1999,6 +1991,7 @@ PUT /groups/:id/push_rule
| `deny_delete_tag` | boolean | no | Deny deleting a tag |
| `member_check` | boolean | no | Restricts commits to be authored by existing GitLab users only |
| `prevent_secrets` | boolean | no | [Files that are likely to contain secrets](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/checks/files_denylist.yml) are rejected |
| `commit_committer_name_check` | boolean | no | Users can only push commits to this repository if the commit author name is consistent with their GitLab account name |
| `commit_message_regex` | string | no | All commit messages must match the regular expression provided in this attribute, for example, `Fixed \d+\..*` |
| `commit_message_negative_regex` | string | no | Commit messages matching the regular expression provided in this attribute aren't allowed, for example, `ssh\:\/\/` |
| `branch_name_regex` | string | no | All branch names must match the regular expression provided in this attribute, for example, `(feature|hotfix)\/*` |
@ -2018,6 +2011,7 @@ Response:
{
"id": 19,
"created_at": "2020-08-31T15:53:00.073Z",
"commit_committer_name_check": false,
"commit_message_regex": "[a-zA-Z]",
"commit_message_negative_regex": "[x+]",
"branch_name_regex": null,

View File

@ -109,17 +109,28 @@ make sure a new fixture is generated and committed together with the change.
## Running the rspecs tagged with `real_ai_request`
The rspecs tagged with the metadata `real_ai_request` can be run in GitLab project's CI by triggering
`rspec-ee unit gitlab-duo-chat`.
The former runs with Vertex APIs enabled. The CI jobs are optional and allowed to fail to account for
the non-deterministic nature of LLM responses.
The following CI jobs for GitLab project run the rspecs tagged with `real_ai_request`:
- `rspec-ee unit gitlab-duo-chat-zeroshot`:
the job runs `ee/spec/lib/gitlab/llm/chain/agents/zero_shot/executor_real_requests_spec.rb`.
The job is optionally triggered and allowed to fail.
- `rspec-ee unit gitlab-duo-chat-qa`:
The job runs the QA evaluation tests in
`ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb`.
The job is optionally triggered and allowed to fail.
- `rspec-ee unit gitlab-duo-chat-qa-fast`:
The job runs a single QA evaluation test from `ee/spec/lib/gitlab/llm/chain/agents/zero_shot/qa_evaluation_spec.rb`.
The job is always run and not allowed to fail. Although there's a chance that the QA test still might fail,
it is cheap and fast to run and intended to prevent a regression in the QA test helpers.
### Management of credentials and API keys for CI jobs
All API keys required to run the rspecs should be [masked](../../ci/variables/index.md#mask-a-cicd-variable)
The exception is GCP credentials as they contain characters that prevent them from being masked.
Because `rspec-ee unit gitlab-duo-chat` needs to run on MR branches, GCP credentials cannot be added as a protected variable
Because the CI jobs need to run on MR branches, GCP credentials cannot be added as a protected variable
and must be added as a regular CI variable.
For security, the GCP credentials and the associated project added to
GitLab project's CI must not be able to access any production infrastructure and sandboxed.

View File

@ -34,6 +34,7 @@ For more information about upgrading GitLab Helm Chart, see [the release notes f
- Git 2.42.0 and later is required by Gitaly. For self-compiled installations, you should use the [Git version provided by Gitaly](../../install/installation.md#git).
- A regression may sometimes cause an [HTTP 500 error when navigating a group](https://gitlab.com/gitlab-org/gitlab/-/issues/431659). Upgrading to GitLab 16.6 or later resolves the issue.
- A regression may cause [Unselected Advanced Search facets to not load](https://gitlab.com/gitlab-org/gitlab/-/issues/428246). Upgrading to 16.6 or later resolves the issue.
### Linux package installations

View File

@ -170,7 +170,7 @@ build:
before_script:
- docker login -u $CI_DEPENDENCY_PROXY_USER -p $CI_DEPENDENCY_PROXY_PASSWORD $CI_DEPENDENCY_PROXY_SERVER
script:
- docker build -t test .
- docker build -t test .
```
You can also use [custom CI/CD variables](../../../ci/variables/index.md#for-a-project) to store and access your personal access token or deploy token.

View File

@ -2,6 +2,8 @@
module Bitbucket
class Connection
include Bitbucket::ExponentialBackoff
DEFAULT_API_VERSION = '2.0'
DEFAULT_BASE_URI = 'https://api.bitbucket.org/'
DEFAULT_QUERY = {}.freeze
@ -22,7 +24,14 @@ module Bitbucket
def get(path, extra_query = {})
refresh! if expired?
response = connection.get(build_url(path), params: @default_query.merge(extra_query))
response = if Feature.enabled?(:bitbucket_importer_exponential_backoff)
retry_with_exponential_backoff do
connection.get(build_url(path), params: @default_query.merge(extra_query))
end
else
connection.get(build_url(path), params: @default_query.merge(extra_query))
end
response.parsed
end
@ -44,6 +53,10 @@ module Bitbucket
@client ||= OAuth2::Client.new(provider.app_id, provider.app_secret, options)
end
def logger
Gitlab::BitbucketImport::Logger
end
def connection
@connection ||= OAuth2::AccessToken.new(client, @token, refresh_token: @refresh_token, expires_at: @expires_at, expires_in: @expires_in)
end

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
module Bitbucket
module ExponentialBackoff
extend ActiveSupport::Concern
INITIAL_DELAY = 1.second
EXPONENTIAL_BASE = 2
MAX_RETRIES = 3
RateLimitError = Class.new(StandardError)
def retry_with_exponential_backoff(&block)
run_retry_with_exponential_backoff(&block)
end
private
def run_retry_with_exponential_backoff
retries = 0
delay = INITIAL_DELAY
loop do
return yield
rescue OAuth2::Error => e
retries, delay = handle_error(retries, delay, e.message)
next
end
end
def handle_error(retries, delay, error)
retries += 1
raise RateLimitError, "Maximum number of retries (#{MAX_RETRIES}) exceeded. #{error}" if retries >= MAX_RETRIES
delay *= EXPONENTIAL_BASE * (1 + Random.rand)
logger.info(message: "Retrying in #{delay} seconds due to #{error}")
sleep delay
[retries, delay]
end
end
end

View File

@ -42593,6 +42593,9 @@ msgstr ""
msgid "Search files"
msgstr ""
msgid "Search filters"
msgstr ""
msgid "Search for Namespace"
msgstr ""

View File

@ -59,7 +59,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/svgs": "3.71.0",
"@gitlab/ui": "^69.0.0",
"@gitlab/ui": "^69.1.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "^0.0.1-dev-20231116214726",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",

View File

@ -5,7 +5,7 @@ require 'gitlab'
require 'json'
class Reporter
IDENTIFIABLE_NOTE_TAG = 'gitlab-org/ai-powered/ai-framework:duo-chat-qa-evaluation-'
IDENTIFIABLE_NOTE_TAG = 'gitlab-org/ai-powered/ai-framework:duo-chat-qa-evaluation'
GRADE_TO_EMOJI_MAPPING = {
correct: ":white_check_mark:",
@ -25,7 +25,7 @@ class Reporter
.merge_request_notes(ci_project_id, merge_request_iid)
.auto_paginate
.select do |note|
note.body.include? note_identifier_tag
note.body.include? IDENTIFIABLE_NOTE_TAG
end
note = report_notes.max_by { |note| Time.parse(note.created_at) }
@ -47,17 +47,13 @@ class Reporter
private
def report_filename
"#{ENV['DUO_RSPEC']}.md"
ENV['QA_EVAL_REPORT_FILENAME']
end
def artifact_path
File.join(ENV['CI_PROJECT_DIR'], report_filename)
end
def note_identifier_tag
"#{IDENTIFIABLE_NOTE_TAG}#{ENV['DUO_RSPEC']}"
end
def com_gitlab_client
@com_gitlab_client ||= Gitlab.client(
endpoint: "https://gitlab.com/api/v4",
@ -67,7 +63,7 @@ class Reporter
def report_note
report = <<~MARKDOWN
<!-- #{note_identifier_tag} -->
<!-- #{IDENTIFIABLE_NOTE_TAG} -->
## GitLab Duo Chat QA evaluation
@ -105,7 +101,7 @@ class Reporter
if report.length > 1000000
return <<~MARKDOWN
<!-- #{note_identifier_tag} -->
<!-- #{IDENTIFIABLE_NOTE_TAG} -->
## GitLab Duo Chat QA evaluation
@ -125,7 +121,7 @@ class Reporter
def report_data
@report_data ||= Dir[File.join(ENV['CI_PROJECT_DIR'], "tmp/duo_chat/qa*.json")]
.map { |file| JSON.parse(File.read(file)) }
.flat_map { |file| JSON.parse(File.read(file)) }
end
def eval_content

View File

@ -111,7 +111,7 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :continuous_integrat
page.within('[data-testid="pipeline-schedule-table-row"]') do
expect(page).to have_content('pipeline schedule')
expect(find('[data-testid="next-run-cell"] time')['title'])
.to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y'))
.to include(pipeline_schedule.real_next_run.strftime('%B %-d, %Y'))
expect(page).to have_link('master')
expect(find("[data-testid='last-pipeline-status'] a")['href']).to include(pipeline.id.to_s)
end

View File

@ -4,7 +4,7 @@ import data from 'test_fixtures/deploy_keys/keys.json';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import key from '~/deploy_keys/components/key.vue';
import DeployKeysStore from '~/deploy_keys/store';
import { getTimeago, formatDate } from '~/lib/utils/datetime_utility';
import { getTimeago, localeDateFormat } from '~/lib/utils/datetime_utility';
describe('Deploy keys key', () => {
let wrapper;
@ -64,7 +64,9 @@ describe('Deploy keys key', () => {
const expiryComponent = wrapper.find('[data-testid="expires-at-tooltip"]');
const tooltip = getBinding(expiryComponent.element, 'gl-tooltip');
expect(tooltip).toBeDefined();
expect(expiryComponent.attributes('title')).toBe(`${formatDate(expiresAt)}`);
expect(expiryComponent.attributes('title')).toBe(
`${localeDateFormat.asDateTimeFull.format(expiresAt)}`,
);
});
it('renders never when no expiration date', () => {
createComponent({

View File

@ -4,7 +4,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { useFakeDate } from 'helpers/fake_date';
import { stubTransition } from 'helpers/stub_transition';
import { formatDate } from '~/lib/utils/datetime_utility';
import { localeDateFormat } from '~/lib/utils/datetime_utility';
import { __, s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Deployment from '~/environments/components/deployment.vue';
@ -158,7 +158,9 @@ describe('~/environments/components/deployment.vue', () => {
describe('is present', () => {
it('shows the timestamp the deployment was deployed at', () => {
wrapper = createWrapper();
const date = wrapper.findByTitle(formatDate(deployment.createdAt));
const date = wrapper.findByTitle(
localeDateFormat.asDateTimeFull.format(deployment.createdAt),
);
expect(date.text()).toBe('1 day ago');
});
@ -166,7 +168,9 @@ describe('~/environments/components/deployment.vue', () => {
describe('is not present', () => {
it('does not show the timestamp', () => {
wrapper = createWrapper({ propsData: { deployment: { ...deployment, createdAt: null } } });
const date = wrapper.findByTitle(formatDate(deployment.createdAt));
const date = wrapper.findByTitle(
localeDateFormat.asDateTimeFull.format(deployment.createdAt),
);
expect(date.exists()).toBe(false);
});

View File

@ -5,7 +5,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { stubTransition } from 'helpers/stub_transition';
import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
import { getTimeago, localeDateFormat } from '~/lib/utils/datetime_utility';
import { __, s__, sprintf } from '~/locale';
import EnvironmentItem from '~/environments/components/new_environment_item.vue';
import EnvironmentActions from '~/environments/components/environment_actions.vue';
@ -253,7 +253,9 @@ describe('~/environments/components/new_environment_item.vue', () => {
});
it('shows when the environment auto stops', () => {
const autoStop = wrapper.findByTitle(formatDate(environment.autoStopAt));
const autoStop = wrapper.findByTitle(
localeDateFormat.asDateTimeFull.format(environment.autoStopAt),
);
expect(autoStop.text()).toBe('in 1 minute');
});

View File

@ -56,7 +56,7 @@ export const userList = {
iid: 2,
project_id: 1,
created_at: '2020-02-04T08:13:10.507Z',
updated_at: '2020-02-04T08:13:10.507Z',
updated_at: '2020-02-05T08:14:10.507Z',
path: '/path/to/user/list',
edit_path: '/path/to/user/list/edit',
};

View File

@ -14,6 +14,7 @@ export const projectData = {
commit: {
id: '123',
short_id: 'abc123de',
committed_date: '2019-09-13T15:37:30+0300',
},
},
},

View File

@ -55,6 +55,41 @@ describe('localeDateFormat (en-US)', () => {
});
});
describe('#asDateTimeFull', () => {
it('exposes a working date formatter', () => {
expectDateString(localeDateFormat.asDateTimeFull.format(date)).toBe(
'July 9, 1983 at 2:15:23 PM GMT',
);
expectDateString(localeDateFormat.asDateTimeFull.format(nextYear)).toBe(
'January 10, 1984 at 7:47:54 AM GMT',
);
});
it('exposes a working date range formatter', () => {
expectDateString(localeDateFormat.asDateTimeFull.formatRange(date, nextYear)).toBe(
'July 9, 1983 at 2:15:23 PM GMT January 10, 1984 at 7:47:54 AM GMT',
);
expectDateString(localeDateFormat.asDateTimeFull.formatRange(date, sameMonth)).toBe(
'July 9, 1983 at 2:15:23 PM GMT July 12, 1983 at 12:36:02 PM GMT',
);
expectDateString(localeDateFormat.asDateTimeFull.formatRange(date, sameDay)).toBe(
'July 9, 1983, 2:15:23 PM GMT 6:27:09 PM GMT',
);
});
it.each([
['automatic', 0, '2:15:23 PM'],
['h12 preference', 1, '2:15:23 PM'],
['h24 preference', 2, '14:15:23'],
])("respects user's hourCycle preference: %s", (_, timeDisplayFormat, result) => {
window.gon.time_display_format = timeDisplayFormat;
expectDateString(localeDateFormat.asDateTimeFull.format(date)).toContain(result);
expectDateString(localeDateFormat.asDateTimeFull.formatRange(date, nextYear)).toContain(
result,
);
});
});
describe('#asDate', () => {
it('exposes a working date formatter', () => {
expectDateString(localeDateFormat.asDate.format(date)).toBe('Jul 9, 1983');

View File

@ -144,7 +144,7 @@ describe('TimeAgo utils', () => {
it.each`
updateTooltip | title
${false} | ${'some time'}
${true} | ${'Feb 18, 2020 10:22pm UTC'}
${true} | ${'February 18, 2020 at 10:22:32 PM GMT'}
`(
`has content: '${text}' and tooltip: '$title' with updateTooltip = $updateTooltip`,
({ updateTooltip, title }) => {

View File

@ -82,7 +82,7 @@ exports[`packages_list_row renders 1`] = `
Published
<time
datetime="2020-05-17T14:23:32Z"
title="May 17, 2020 2:23pm UTC"
title="May 17, 2020 at 2:23:32 PM GMT"
>
1 month ago
</time>

View File

@ -33,7 +33,7 @@ describe('ArchivedFilter', () => {
const findCheckboxFilter = () => wrapper.findComponent(GlFormCheckboxGroup);
const findCheckboxFilterLabel = () => wrapper.findByTestId('label');
const findH5 = () => wrapper.findComponent('h5');
const findTitle = () => wrapper.findByTestId('archived-filter-title');
describe('old sidebar', () => {
beforeEach(() => {
@ -45,8 +45,8 @@ describe('ArchivedFilter', () => {
});
it('renders the divider', () => {
expect(findH5().exists()).toBe(true);
expect(findH5().text()).toBe(archivedFilterData.headerLabel);
expect(findTitle().exists()).toBe(true);
expect(findTitle().text()).toBe(archivedFilterData.headerLabel);
});
it('wraps the label element with a tooltip', () => {
@ -66,8 +66,8 @@ describe('ArchivedFilter', () => {
});
it("doesn't render the divider", () => {
expect(findH5().exists()).toBe(true);
expect(findH5().text()).toBe(archivedFilterData.headerLabel);
expect(findTitle().exists()).toBe(true);
expect(findTitle().text()).toBe(archivedFilterData.headerLabel);
});
it('wraps the label element with a tooltip', () => {

View File

@ -5,6 +5,7 @@ import { nextTick } from 'vue';
import { timeagoLanguageCode } from '~/lib/utils/datetime/timeago_utility';
import UserListsTable from '~/user_lists/components/user_lists_table.vue';
import { userList } from 'jest/feature_flags/mock_data';
import { localeDateFormat } from '~/lib/utils/datetime/locale_dateformat';
jest.mock('timeago.js', () => ({
format: jest.fn().mockReturnValue('2 weeks ago'),
@ -33,7 +34,7 @@ describe('User Lists Table', () => {
it('should set the title for a tooltip on the created stamp', () => {
expect(wrapper.find('[data-testid="ffUserListTimestamp"]').attributes('title')).toBe(
'Feb 4, 2020 8:13am UTC',
localeDateFormat.asDateTimeFull.format(userList.created_at),
);
});

View File

@ -8,6 +8,7 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import MRWidgetPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import LegacyPipelineMiniGraph from '~/ci/pipeline_mini_graph/legacy_pipeline_mini_graph.vue';
import { SUCCESS } from '~/vue_merge_request_widget/constants';
import { localeDateFormat } from '~/lib/utils/datetime/locale_dateformat';
import mockData from '../mock_data';
describe('MRWidgetPipeline', () => {
@ -93,7 +94,7 @@ describe('MRWidgetPipeline', () => {
it('should render pipeline finished timestamp', () => {
expect(findPipelineFinishedAt().attributes()).toMatchObject({
title: 'Apr 7, 2017 2:00pm UTC',
title: localeDateFormat.asDateTimeFull.format(mockData.pipeline.details.finished_at),
datetime: mockData.pipeline.details.finished_at,
});
});

View File

@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlTruncate } from '@gitlab/ui';
import timezoneMock from 'timezone-mock';
import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
import { getTimeago } from '~/lib/utils/datetime_utility';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { DATE_ONLY_FORMAT } from '~/lib/utils/datetime/locale_dateformat';
@ -33,7 +33,7 @@ describe('Time ago with tooltip component', () => {
it('should render timeago with a bootstrap tooltip', () => {
buildVm();
expect(vm.attributes('title')).toEqual(formatDate(timestamp));
expect(vm.attributes('title')).toEqual('May 8, 2017 at 2:57:39 PM GMT');
expect(vm.text()).toEqual(timeAgoTimestamp);
});

View File

@ -6,6 +6,7 @@ import IssuableItem from '~/vue_shared/issuable/list/components/issuable_item.vu
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import IssuableAssignees from '~/issuable/components/issue_assignees.vue';
import { localeDateFormat } from '~/lib/utils/datetime/locale_dateformat';
import { mockIssuable, mockRegularLabel } from '../mock_data';
const createComponent = ({
@ -168,15 +169,20 @@ describe('IssuableItem', () => {
it('returns timestamp based on `issuable.updatedAt` when the issue is open', () => {
wrapper = createComponent();
expect(findTimestampWrapper().attributes('title')).toBe('Sep 10, 2020 11:41am UTC');
expect(findTimestampWrapper().attributes('title')).toBe(
localeDateFormat.asDateTimeFull.format(mockIssuable.updatedAt),
);
});
it('returns timestamp based on `issuable.closedAt` when the issue is closed', () => {
const closedAt = '2020-06-18T11:30:00Z';
wrapper = createComponent({
issuable: { ...mockIssuable, closedAt: '2020-06-18T11:30:00Z', state: 'closed' },
issuable: { ...mockIssuable, closedAt, state: 'closed' },
});
expect(findTimestampWrapper().attributes('title')).toBe('Jun 18, 2020 11:30am UTC');
expect(findTimestampWrapper().attributes('title')).toBe(
localeDateFormat.asDateTimeFull.format(closedAt),
);
});
it('returns timestamp based on `issuable.updatedAt` when the issue is closed but `issuable.closedAt` is undefined', () => {
@ -184,7 +190,9 @@ describe('IssuableItem', () => {
issuable: { ...mockIssuable, closedAt: undefined, state: 'closed' },
});
expect(findTimestampWrapper().attributes('title')).toBe('Sep 10, 2020 11:41am UTC');
expect(findTimestampWrapper().attributes('title')).toBe(
localeDateFormat.asDateTimeFull.format(mockIssuable.updatedAt),
);
});
});
@ -409,7 +417,9 @@ describe('IssuableItem', () => {
const createdAtEl = wrapper.find('[data-testid="issuable-created-at"]');
expect(createdAtEl.exists()).toBe(true);
expect(createdAtEl.attributes('title')).toBe('Jun 29, 2020 1:52pm UTC');
expect(createdAtEl.attributes('title')).toBe(
localeDateFormat.asDateTimeFull.format(mockIssuable.createdAt),
);
expect(createdAtEl.text()).toBe(wrapper.vm.createdAt);
});
@ -535,7 +545,9 @@ describe('IssuableItem', () => {
const timestampEl = wrapper.find('[data-testid="issuable-timestamp"]');
expect(timestampEl.attributes('title')).toBe('Sep 10, 2020 11:41am UTC');
expect(timestampEl.attributes('title')).toBe(
localeDateFormat.asDateTimeFull.format(mockIssuable.updatedAt),
);
expect(timestampEl.text()).toBe(wrapper.vm.formattedTimestamp);
});
@ -549,13 +561,16 @@ describe('IssuableItem', () => {
});
it('renders issuable closedAt info and does not render updatedAt info', () => {
const closedAt = '2022-06-18T11:30:00Z';
wrapper = createComponent({
issuable: { ...mockIssuable, closedAt: '2022-06-18T11:30:00Z', state: 'closed' },
issuable: { ...mockIssuable, closedAt, state: 'closed' },
});
const timestampEl = wrapper.find('[data-testid="issuable-timestamp"]');
expect(timestampEl.attributes('title')).toBe('Jun 18, 2022 11:30am UTC');
expect(timestampEl.attributes('title')).toBe(
localeDateFormat.asDateTimeFull.format(closedAt),
);
expect(timestampEl.text()).toBe(wrapper.vm.formattedTimestamp);
});
});

View File

@ -0,0 +1,73 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Projects::Star, feature_category: :groups_and_projects do
describe '#resolve' do
let_it_be(:user, freeze: true) { create(:user) }
subject(:mutation) do
described_class
.new(object: nil, context: { current_user: user }, field: nil)
.resolve(project_id: project.to_global_id, starred: starred)
end
context 'when the user has read access to the project' do
let_it_be_with_reload(:project) { create(:project, :public) }
context 'and the project is not starred' do
context 'and the user stars the project' do
let(:starred) { true }
it 'stars the project for the current user' do
expect(mutation).to include(count: 1)
expect(project.reset.starrers).to include(user)
end
end
context 'and the user unstars the project' do
let(:starred) { false }
it 'does not raise an error or change the number of stars' do
expect(mutation).to include(count: 0)
expect(project.reset.starrers).not_to include(user)
end
end
end
context 'and the project is starred' do
before do
user.toggle_star(project)
end
context 'and the user stars the project' do
let(:starred) { true }
it 'does not raise an error or change the number of stars' do
expect(mutation).to include(count: 1)
expect(project.reset.starrers).to include(user)
end
end
context 'and the user unstars the project' do
let(:starred) { false }
it 'unstars the project for the current user' do
expect(mutation).to include(count: 0)
expect(project.reset.starrers).not_to include(user)
end
end
end
end
context 'when the user does not have read access to the project' do
let_it_be(:project, freeze: true) { create(:project, :private) }
let(:starred) { true }
it 'raises an error' do
expect { mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect(project.starrers).not_to include(user)
end
end
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['CurrentUser'], feature_category: :user_profile do
specify { expect(described_class.graphql_name).to eq('CurrentUser') }
it "inherits authorization policies from the UserType superclass" do
expect(described_class).to require_graphql_authorizations(:read_user)
end
end

View File

@ -13,6 +13,14 @@ RSpec.describe GitlabSchema.types['Query'], feature_category: :shared do
expect(described_class).to have_graphql_fields(*expected_foss_fields).at_least
end
describe 'current_user field' do
subject { described_class.fields['currentUser'] }
it 'returns current user' do
is_expected.to have_graphql_type(Types::CurrentUserType)
end
end
describe 'namespace field' do
subject { described_class.fields['namespace'] }

View File

@ -19,6 +19,10 @@ RSpec.describe Bitbucket::Connection, feature_category: :integrations do
token_url: OmniAuth::Strategies::Bitbucket.default_options[:client_options]['token_url']
}
expect_next_instance_of(described_class) do |instance|
expect(instance).to receive(:retry_with_exponential_backoff).and_call_original
end
expect(OAuth2::Client)
.to receive(:new)
.with(anything, anything, expected_client_options)
@ -31,6 +35,47 @@ RSpec.describe Bitbucket::Connection, feature_category: :integrations do
connection.get('/users')
end
context 'when the API returns an error' do
before do
allow_next_instance_of(OAuth2::AccessToken) do |instance|
allow(instance).to receive(:get).and_raise(OAuth2::Error, 'some error')
end
stub_const('Bitbucket::ExponentialBackoff::INITIAL_DELAY', 0.0)
allow(Random).to receive(:rand).and_return(0.001)
end
it 'logs the retries and raises an error if it does not succeed on retry' do
expect(Gitlab::BitbucketImport::Logger).to receive(:info)
.with(message: 'Retrying in 0.0 seconds due to some error')
.twice
connection = described_class.new({ token: token })
expect { connection.get('/users') }.to raise_error(Bitbucket::ExponentialBackoff::RateLimitError)
end
end
context 'when the bitbucket_importer_exponential_backoff feature flag is disabled' do
before do
stub_feature_flags(bitbucket_importer_exponential_backoff: false)
end
it 'does not run with exponential backoff' do
expect_next_instance_of(described_class) do |instance|
expect(instance).not_to receive(:retry_with_exponential_backoff).and_call_original
end
expect_next_instance_of(OAuth2::AccessToken) do |instance|
expect(instance).to receive(:get).and_return(double(parsed: true))
end
connection = described_class.new({ token: token })
connection.get('/users')
end
end
end
describe '#expired?' do

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Bitbucket::ExponentialBackoff, feature_category: :importers do
let(:service) { dummy_class.new }
let(:body) { 'test' }
let(:parsed_response) { instance_double(Net::HTTPResponse, body: body.to_json) }
let(:response) { double(Faraday::Response, body: body, parsed: parsed_response) }
let(:response_caller) { -> { response } }
let(:dummy_class) do
Class.new do
def logger
@logger ||= Logger.new(File::NULL)
end
def dummy_method(response_caller)
retry_with_exponential_backoff do
response_caller.call
end
end
include Bitbucket::ExponentialBackoff
end
end
subject(:execute) { service.dummy_method(response_caller) }
describe '.retry_with_exponential_backoff' do
let(:max_retries) { described_class::MAX_RETRIES }
context 'when the function succeeds on the first try' do
it 'calls the function once and returns its result' do
expect(response_caller).to receive(:call).once.and_call_original
expect(Gitlab::Json.parse(execute.parsed.body)).to eq(body)
end
end
context 'when the function response is an error' do
let(:error) { 'Rate limit for this resource has been exceeded' }
before do
stub_const("#{described_class.name}::INITIAL_DELAY", 0.0)
allow(Random).to receive(:rand).and_return(0.001)
end
it 'raises a RateLimitError if the maximum number of retries is exceeded' do
allow(response_caller).to receive(:call).and_raise(OAuth2::Error, error)
message = "Maximum number of retries (#{max_retries}) exceeded. #{error}"
expect do
execute
end.to raise_error(described_class::RateLimitError, message)
expect(response_caller).to have_received(:call).exactly(max_retries).times
end
end
end
end

View File

@ -761,6 +761,63 @@ RSpec.describe MergeRequestDiff, feature_category: :code_review_workflow do
end
end
describe '#get_patch_id_sha' do
let(:mr_diff) { create(:merge_request).merge_request_diff }
context 'when the patch_id exists on the model' do
it 'returns the patch_id' do
expect(mr_diff.patch_id_sha).not_to be_nil
expect(mr_diff.get_patch_id_sha).to eq(mr_diff.patch_id_sha)
end
end
context 'when the patch_id does not exist on the model' do
it 'retrieves the patch id, saves the model, and returns it' do
expect(mr_diff.patch_id_sha).not_to be_nil
patch_id = mr_diff.patch_id_sha
mr_diff.update!(patch_id_sha: nil)
expect(mr_diff.get_patch_id_sha).to eq(patch_id)
expect(mr_diff.reload.patch_id_sha).to eq(patch_id)
end
context 'when base_sha is nil' do
before do
mr_diff.update!(patch_id_sha: nil)
allow(mr_diff).to receive(:base_commit_sha).and_return(nil)
end
it 'returns nil' do
expect(mr_diff.reload.get_patch_id_sha).to be_nil
end
end
context 'when head_sha is nil' do
before do
mr_diff.update!(patch_id_sha: nil)
allow(mr_diff).to receive(:head_commit_sha).and_return(nil)
end
it 'returns nil' do
expect(mr_diff.reload.get_patch_id_sha).to be_nil
end
end
context 'when base_sha and head_sha dont match' do
before do
mr_diff.update!(patch_id_sha: nil)
allow(mr_diff).to receive(:head_commit_sha).and_return('123123')
allow(mr_diff).to receive(:base_commit_sha).and_return('43121')
end
it 'returns nil' do
expect(mr_diff.reload.get_patch_id_sha).to be_nil
end
end
end
end
describe '#save_diffs' do
it 'saves collected state' do
mr_diff = create(:merge_request).merge_request_diff

View File

@ -6021,47 +6021,11 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
subject(:current_patch_id_sha) { merge_request.current_patch_id_sha }
before do
allow(merge_request).to receive(:merge_request_diff).and_return(merge_request_diff)
allow(merge_request).to receive(:latest_merge_request_diff).and_return(merge_request_diff)
allow(merge_request_diff).to receive(:patch_id_sha).and_return(patch_id)
end
it { is_expected.to eq(patch_id) }
context 'when related merge_request_diff does not have a patch_id_sha' do
let(:diff_refs) { instance_double(Gitlab::Diff::DiffRefs, base_sha: base_sha, head_sha: head_sha) }
let(:base_sha) { 'abc123' }
let(:head_sha) { 'def456' }
before do
allow(merge_request_diff).to receive(:patch_id_sha).and_return(nil)
allow(merge_request).to receive(:diff_refs).and_return(diff_refs)
allow(merge_request.project.repository)
.to receive(:get_patch_id)
.with(diff_refs.base_sha, diff_refs.head_sha)
.and_return(patch_id)
end
it { is_expected.to eq(patch_id) }
context 'when base_sha is nil' do
let(:base_sha) { nil }
it { is_expected.to be_nil }
end
context 'when head_sha is nil' do
let(:head_sha) { nil }
it { is_expected.to be_nil }
end
context 'when base_sha and head_sha match' do
let(:head_sha) { base_sha }
it { is_expected.to be_nil }
end
end
end
describe '#all_mergeability_checks_results' do

View File

@ -1274,10 +1274,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.71.0.tgz#ff4a3cf22cd12b3c861ef2065583cc49923cf5f8"
integrity sha512-aYjC9uef5Q3CDg4Zu9fh0mce4jO2LANaEgRLutoAYRXG4ymWwRmgP8SZmZyQY0B4hcZjBfUsyVykIhVnlNcRLw==
"@gitlab/ui@^69.0.0":
version "69.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-69.0.0.tgz#981979e1f4a639ef21f266c9e96b8aa6a625db87"
integrity sha512-HCWLX0/1NwQILeRQ1c0yVCcjGftX7L8Put4mmEaoCPGcX1r8aE/mdkqTzOp9vTzi3OxzaJ8FDA/GV7RaxdGP0A==
"@gitlab/ui@^69.1.0":
version "69.1.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-69.1.0.tgz#9e81244b7328b7e739b43b4a3413cd02c4b87d60"
integrity sha512-e5XNJ4M0mhD82RhMs4U1fqAPSxqS8yEcsTYukiPfTAMDdCF1Q0ejYNmkBYUjrY0wktUrOhzbwhlaVJB0YQ15Zw==
dependencies:
"@floating-ui/dom" "1.2.9"
bootstrap-vue "2.23.1"