diff options
55 files changed, 992 insertions, 174 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 96793b1f5d4..098c5a8a5e1 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -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: +.rspec-ee-base-gitlab-duo: + extends: + - .rspec-ee-base-pg14 variables: REAL_AI_REQUEST: "true" - RSPEC_RETRY_RETRY_COUNT: 0 + +rspec-ee unit gitlab-duo-chat-zeroshot pg14: 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"] + - .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: diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index f9690b0247c..6a5bade71f4 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -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 diff --git a/.rubocop_todo/rspec/verified_doubles.yml b/.rubocop_todo/rspec/verified_doubles.yml index d26067d2783..d907b372477 100644 --- a/.rubocop_todo/rspec/verified_doubles.yml +++ b/.rubocop_todo/rspec/verified_doubles.yml @@ -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' diff --git a/app/assets/javascripts/graphql_shared/issuable_client.js b/app/assets/javascripts/graphql_shared/issuable_client.js index 9537c9ef8a6..d0ba34b6127 100644 --- a/app/assets/javascripts/graphql_shared/issuable_client.js +++ b/app/assets/javascripts/graphql_shared/issuable_client.js @@ -146,7 +146,7 @@ export const config = { }, IssueConnection: { merge(existing = { nodes: [] }, incoming, { args }) { - if (!args.after) { + if (!args?.after) { return incoming; } return { diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index ee5f8c2ac2f..4ef0d067030 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -150,6 +150,7 @@ "User": [ "AddOnUser", "AutocompletedUser", + "CurrentUser", "MergeRequestAssignee", "MergeRequestAuthor", "MergeRequestParticipant", diff --git a/app/assets/javascripts/lib/utils/datetime/locale_dateformat.js b/app/assets/javascripts/lib/utils/datetime/locale_dateformat.js index 81922c4da1d..f1446fa5ac4 100644 --- a/app/assets/javascripts/lib/utils/datetime/locale_dateformat.js +++ b/app/assets/javascripts/lib/utils/datetime/locale_dateformat.js @@ -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 diff --git a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js index a25acd5c711..3a94b26ee35 100644 --- a/app/assets/javascripts/lib/utils/datetime/timeago_utility.js +++ b/app/assets/javascripts/lib/utils/datetime/timeago_utility.js @@ -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)); }); } diff --git a/app/assets/javascripts/search/sidebar/components/archived_filter/index.vue b/app/assets/javascripts/search/sidebar/components/archived_filter/index.vue index 914ff99075b..0308db17dc4 100644 --- a/app/assets/javascripts/search/sidebar/components/archived_filter/index.vue +++ b/app/assets/javascripts/search/sidebar/components/archived_filter/index.vue @@ -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" diff --git a/app/assets/javascripts/search/sidebar/components/filters_template.vue b/app/assets/javascripts/search/sidebar/components/filters_template.vue index a3aa392d7fc..2f40a430bfa 100644 --- a/app/assets/javascripts/search/sidebar/components/filters_template.vue +++ b/app/assets/javascripts/search/sidebar/components/filters_template.vue @@ -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"> diff --git a/app/assets/javascripts/search/sidebar/components/label_filter/index.vue b/app/assets/javascripts/search/sidebar/components/label_filter/index.vue index a53f519161b..106093b5ad1 100644 --- a/app/assets/javascripts/search/sidebar/components/label_filter/index.vue +++ b/app/assets/javascripts/search/sidebar/components/label_filter/index.vue @@ -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" diff --git a/app/assets/javascripts/search/sidebar/components/language_filter/index.vue b/app/assets/javascripts/search/sidebar/components/language_filter/index.vue index 4a9975641c6..d0c895530cd 100644 --- a/app/assets/javascripts/search/sidebar/components/language_filter/index.vue +++ b/app/assets/javascripts/search/sidebar/components/language_filter/index.vue @@ -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" diff --git a/app/assets/javascripts/search/sidebar/components/radio_filter.vue b/app/assets/javascripts/search/sidebar/components/radio_filter.vue index d67844b93a7..f961bfea608 100644 --- a/app/assets/javascripts/search/sidebar/components/radio_filter.vue +++ b/app/assets/javascripts/search/sidebar/components/radio_filter.vue @@ -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) }} diff --git a/app/assets/javascripts/vue_shared/mixins/timeago.js b/app/assets/javascripts/vue_shared/mixins/timeago.js index 61e45fa5195..438da925937 100644 --- a/app/assets/javascripts/vue_shared/mixins/timeago.js +++ b/app/assets/javascripts/vue_shared/mixins/timeago.js @@ -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); }, }, }; diff --git a/app/graphql/mutations/projects/star.rb b/app/graphql/mutations/projects/star.rb new file mode 100644 index 00000000000..e4b64235c9a --- /dev/null +++ b/app/graphql/mutations/projects/star.rb @@ -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 diff --git a/app/graphql/types/current_user_type.rb b/app/graphql/types/current_user_type.rb new file mode 100644 index 00000000000..d5ecdeba9e2 --- /dev/null +++ b/app/graphql/types/current_user_type.rb @@ -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 diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index d0a9ea11a27..3a2dea92cdc 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -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 diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 6b7814754a4..9acff9eb2b1 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -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, diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb index 040711b5f58..2a628bb3ceb 100644 --- a/app/graphql/types/user_interface.rb +++ b/app/graphql/types/user_interface.rb @@ -229,5 +229,3 @@ module Types end end end - -Types::UserInterface.prepend_mod diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb index 87ca5fddf14..c5910236d51 100644 --- a/app/graphql/types/user_type.rb +++ b/app/graphql/types/user_type.rb @@ -14,3 +14,5 @@ module Types present_using UserPresenter end end + +Types::UserType.prepend_mod diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 051568a5674..c8fc10e6c94 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -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 diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 4c87b78d9f2..000fb9117af 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -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. diff --git a/config/feature_flags/development/bitbucket_importer_exponential_backoff.yml b/config/feature_flags/development/bitbucket_importer_exponential_backoff.yml new file mode 100644 index 00000000000..310abda55e7 --- /dev/null +++ b/config/feature_flags/development/bitbucket_importer_exponential_backoff.yml @@ -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 diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md index d3ecd2771e6..40fcd897fbc 100644 --- a/doc/administration/auth/oidc.md +++ b/doc/administration/auth/oidc.md @@ -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. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 2d3c24c0e91..7c378b2cefb 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -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: diff --git a/doc/api/groups.md b/doc/api/groups.md index f5c91e20b1a..1643b388d29 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -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, diff --git a/doc/development/ai_features/duo_chat.md b/doc/development/ai_features/duo_chat.md index dfaad73220f..2fde672aa7e 100644 --- a/doc/development/ai_features/duo_chat.md +++ b/doc/development/ai_features/duo_chat.md @@ -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. diff --git a/doc/update/versions/gitlab_16_changes.md b/doc/update/versions/gitlab_16_changes.md index 5f9e9943925..ed070c57d0a 100644 --- a/doc/update/versions/gitlab_16_changes.md +++ b/doc/update/versions/gitlab_16_changes.md @@ -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 diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md index df348b012fb..1b91b63c0d4 100644 --- a/doc/user/packages/dependency_proxy/index.md +++ b/doc/user/packages/dependency_proxy/index.md @@ -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. diff --git a/lib/bitbucket/connection.rb b/lib/bitbucket/connection.rb index 64550a0525c..f28b2a0a899 100644 --- a/lib/bitbucket/connection.rb +++ b/lib/bitbucket/connection.rb @@ -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 diff --git a/lib/bitbucket/exponential_backoff.rb b/lib/bitbucket/exponential_backoff.rb new file mode 100644 index 00000000000..702010409de --- /dev/null +++ b/lib/bitbucket/exponential_backoff.rb @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a9b5bc8a432..5b25b19283c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -42593,6 +42593,9 @@ msgstr "" msgid "Search files" msgstr "" +msgid "Search filters" +msgstr "" + msgid "Search for Namespace" msgstr "" diff --git a/package.json b/package.json index b97ba203978..5fd2092af1b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/duo_chat/reporter.rb b/scripts/duo_chat/reporter.rb index 686a49164a7..e72a393694f 100755 --- a/scripts/duo_chat/reporter.rb +++ b/scripts/duo_chat/reporter.rb @@ -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 diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 5bcd0d28fd9..d481d90792d 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -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 diff --git a/spec/frontend/deploy_keys/components/key_spec.js b/spec/frontend/deploy_keys/components/key_spec.js index 3c4fa2a6de6..e57da4df150 100644 --- a/spec/frontend/deploy_keys/components/key_spec.js +++ b/spec/frontend/deploy_keys/components/key_spec.js @@ -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({ diff --git a/spec/frontend/environments/deployment_spec.js b/spec/frontend/environments/deployment_spec.js index 4cbbb60b74c..bc0f1c58e7d 100644 --- a/spec/frontend/environments/deployment_spec.js +++ b/spec/frontend/environments/deployment_spec.js @@ -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); }); diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js index 7ee31bf2c62..e586bbfc59d 100644 --- a/spec/frontend/environments/new_environment_item_spec.js +++ b/spec/frontend/environments/new_environment_item_spec.js @@ -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'); }); diff --git a/spec/frontend/feature_flags/mock_data.js b/spec/frontend/feature_flags/mock_data.js index 4c40c2acf01..61e96057017 100644 --- a/spec/frontend/feature_flags/mock_data.js +++ b/spec/frontend/feature_flags/mock_data.js @@ -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', }; diff --git a/spec/frontend/ide/mock_data.js b/spec/frontend/ide/mock_data.js index b1f192e1d98..722f15db87d 100644 --- a/spec/frontend/ide/mock_data.js +++ b/spec/frontend/ide/mock_data.js @@ -14,6 +14,7 @@ export const projectData = { commit: { id: '123', short_id: 'abc123de', + committed_date: '2019-09-13T15:37:30+0300', }, }, }, diff --git a/spec/frontend/lib/utils/datetime/locale_dateformat_spec.js b/spec/frontend/lib/utils/datetime/locale_dateformat_spec.js index 0fe2e049174..3b132a89068 100644 --- a/spec/frontend/lib/utils/datetime/locale_dateformat_spec.js +++ b/spec/frontend/lib/utils/datetime/locale_dateformat_spec.js @@ -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'); diff --git a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js index 9462d006068..53ed524116e 100644 --- a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js +++ b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js @@ -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 }) => { diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap index 4c003a7b9bc..cbf2184d879 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap @@ -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> diff --git a/spec/frontend/search/sidebar/components/archived_filter_spec.js b/spec/frontend/search/sidebar/components/archived_filter_spec.js index 9ed677ca297..9e8ababa5da 100644 --- a/spec/frontend/search/sidebar/components/archived_filter_spec.js +++ b/spec/frontend/search/sidebar/components/archived_filter_spec.js @@ -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', () => { diff --git a/spec/frontend/user_lists/components/user_lists_table_spec.js b/spec/frontend/user_lists/components/user_lists_table_spec.js index 96e9705f02b..26b33bcd46d 100644 --- a/spec/frontend/user_lists/components/user_lists_table_spec.js +++ b/spec/frontend/user_lists/components/user_lists_table_spec.js @@ -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), ); }); diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js index 35b4e222e01..3f0eb946194 100644 --- a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js @@ -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, }); }); diff --git a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js index 26d4cb17258..21c58d662e3 100644 --- a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js +++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js @@ -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); }); diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js index 47da111b604..98a87ddbcce 100644 --- a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js +++ b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js @@ -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); }); }); diff --git a/spec/graphql/mutations/projects/star_spec.rb b/spec/graphql/mutations/projects/star_spec.rb new file mode 100644 index 00000000000..6b1811dcd39 --- /dev/null +++ b/spec/graphql/mutations/projects/star_spec.rb @@ -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 diff --git a/spec/graphql/types/current_user_type_spec.rb b/spec/graphql/types/current_user_type_spec.rb new file mode 100644 index 00000000000..ff7a529a057 --- /dev/null +++ b/spec/graphql/types/current_user_type_spec.rb @@ -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 diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index f2a63fbeb57..0b5739be9a1 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -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'] } diff --git a/spec/lib/bitbucket/connection_spec.rb b/spec/lib/bitbucket/connection_spec.rb index 2b35a37558c..6cf010f2eed 100644 --- a/spec/lib/bitbucket/connection_spec.rb +++ b/spec/lib/bitbucket/connection_spec.rb @@ -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 diff --git a/spec/lib/bitbucket/exponential_backoff_spec.rb b/spec/lib/bitbucket/exponential_backoff_spec.rb new file mode 100644 index 00000000000..b52a83731f4 --- /dev/null +++ b/spec/lib/bitbucket/exponential_backoff_spec.rb @@ -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 diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 0771956b25a..89e9ea0003a 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -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 diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index faea166c77d..1b4e74b4f01 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -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 diff --git a/yarn.lock b/yarn.lock index 680b511c0c9..8ddb1ee76e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" |