Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
eec96715e5
commit
f1c788bb18
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ export const config = {
|
|||
},
|
||||
IssueConnection: {
|
||||
merge(existing = { nodes: [] }, incoming, { args }) {
|
||||
if (!args.after) {
|
||||
if (!args?.after) {
|
||||
return incoming;
|
||||
}
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@
|
|||
"User": [
|
||||
"AddOnUser",
|
||||
"AutocompletedUser",
|
||||
"CurrentUser",
|
||||
"MergeRequestAssignee",
|
||||
"MergeRequestAuthor",
|
||||
"MergeRequestParticipant",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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) }}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -229,5 +229,3 @@ module Types
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Types::UserInterface.prepend_mod
|
||||
|
|
|
|||
|
|
@ -14,3 +14,5 @@ module Types
|
|||
present_using UserPresenter
|
||||
end
|
||||
end
|
||||
|
||||
Types::UserType.prepend_mod
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -42593,6 +42593,9 @@ msgstr ""
|
|||
msgid "Search files"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search filters"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search for Namespace"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const projectData = {
|
|||
commit: {
|
||||
id: '123',
|
||||
short_id: 'abc123de',
|
||||
committed_date: '2019-09-13T15:37:30+0300',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'] }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue