Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-11-21 18:13:27 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-21 18:13:27 +0300
commitf1c788bb1836083e4f5ed91c1c7494244e6e13ee (patch)
tree2d591f05343513b6b3c9d5ce5839d3652c16f000
parenteec96715e5dcbe934b4caecc6fcf9740a3fc8496 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml37
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml20
-rw-r--r--.rubocop_todo/rspec/verified_doubles.yml1
-rw-r--r--app/assets/javascripts/graphql_shared/issuable_client.js2
-rw-r--r--app/assets/javascripts/graphql_shared/possible_types.json1
-rw-r--r--app/assets/javascripts/lib/utils/datetime/locale_dateformat.js58
-rw-r--r--app/assets/javascripts/lib/utils/datetime/timeago_utility.js3
-rw-r--r--app/assets/javascripts/search/sidebar/components/archived_filter/index.vue4
-rw-r--r--app/assets/javascripts/search/sidebar/components/filters_template.vue6
-rw-r--r--app/assets/javascripts/search/sidebar/components/label_filter/index.vue6
-rw-r--r--app/assets/javascripts/search/sidebar/components/language_filter/index.vue4
-rw-r--r--app/assets/javascripts/search/sidebar/components/radio_filter.vue4
-rw-r--r--app/assets/javascripts/vue_shared/mixins/timeago.js4
-rw-r--r--app/graphql/mutations/projects/star.rb39
-rw-r--r--app/graphql/types/current_user_type.rb12
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/graphql/types/query_type.rb2
-rw-r--r--app/graphql/types/user_interface.rb2
-rw-r--r--app/graphql/types/user_type.rb2
-rw-r--r--app/models/merge_request.rb10
-rw-r--r--app/models/merge_request_diff.rb11
-rw-r--r--config/feature_flags/development/bitbucket_importer_exponential_backoff.yml8
-rw-r--r--doc/administration/auth/oidc.md2
-rw-r--r--doc/api/graphql/reference/index.md383
-rw-r--r--doc/api/groups.md20
-rw-r--r--doc/development/ai_features/duo_chat.md21
-rw-r--r--doc/update/versions/gitlab_16_changes.md1
-rw-r--r--doc/user/packages/dependency_proxy/index.md2
-rw-r--r--lib/bitbucket/connection.rb15
-rw-r--r--lib/bitbucket/exponential_backoff.rb45
-rw-r--r--locale/gitlab.pot3
-rw-r--r--package.json2
-rwxr-xr-xscripts/duo_chat/reporter.rb16
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb2
-rw-r--r--spec/frontend/deploy_keys/components/key_spec.js6
-rw-r--r--spec/frontend/environments/deployment_spec.js10
-rw-r--r--spec/frontend/environments/new_environment_item_spec.js6
-rw-r--r--spec/frontend/feature_flags/mock_data.js2
-rw-r--r--spec/frontend/ide/mock_data.js1
-rw-r--r--spec/frontend/lib/utils/datetime/locale_dateformat_spec.js35
-rw-r--r--spec/frontend/lib/utils/datetime/timeago_utility_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap2
-rw-r--r--spec/frontend/search/sidebar/components/archived_filter_spec.js10
-rw-r--r--spec/frontend/user_lists/components/user_lists_table_spec.js3
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/time_ago_tooltip_spec.js4
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js31
-rw-r--r--spec/graphql/mutations/projects/star_spec.rb73
-rw-r--r--spec/graphql/types/current_user_type_spec.rb11
-rw-r--r--spec/graphql/types/query_type_spec.rb8
-rw-r--r--spec/lib/bitbucket/connection_spec.rb45
-rw-r--r--spec/lib/bitbucket/exponential_backoff_spec.rb62
-rw-r--r--spec/models/merge_request_diff_spec.rb57
-rw-r--r--spec/models/merge_request_spec.rb38
-rw-r--r--yarn.lock8
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"