diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index b1a773af5e3..278880850f8 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -772,17 +772,6 @@ rspec-ee unit gitlab-duo-chat pg14:
- !reference [.base-script, script]
- rspec_paralellized_job "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag real_ai_request"
-rspec-ee unit gitlab-duo-chat-open-ai pg14:
- variables:
- REAL_AI_REQUEST: "true"
- OPENAI_EMBEDDINGS: "true"
- extends:
- - .rspec-ee-base-pg14
- - .rails:rules:ee-gitlab-duo-chat-open-ai
- script:
- - !reference [.base-script, script]
- - rspec_paralellized_job "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag real_ai_request"
-
rspec-ee migration pg14:
extends:
- .rspec-ee-base-pg14
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 1c17668b4a2..22ab62b7ca9 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -2129,14 +2129,6 @@
- if: '$VERTEX_AI_EMBEDDINGS == null'
when: never
-.rails:rules:ee-gitlab-duo-chat-open-ai:
- rules:
- - !reference [".rails:rules:ee-gitlab-duo-chat-base", rules]
- - if: '$OPENAI_API_KEY == null'
- when: never
- - if: '$OPENAI_EMBEDDINGS == null'
- when: never
-
.rails:rules:as-if-foss-migration:
rules:
- !reference [".strict-ee-only-rules", rules]
diff --git a/.rubocop_todo/style/format_string.yml b/.rubocop_todo/style/format_string.yml
index 101b16802f7..73ae3cbf42d 100644
--- a/.rubocop_todo/style/format_string.yml
+++ b/.rubocop_todo/style/format_string.yml
@@ -255,7 +255,6 @@ Style/FormatString:
- 'lib/gitlab/exceptions_app.rb'
- 'lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb'
- 'lib/gitlab/github_import/issuable_finder.rb'
- - 'lib/gitlab/github_import/label_finder.rb'
- 'lib/gitlab/github_import/object_counter.rb'
- 'lib/gitlab/github_import/page_counter.rb'
- 'lib/gitlab/github_import/parallel_scheduling.rb'
diff --git a/Gemfile b/Gemfile
index 8c144a96256..820a336b66c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -363,10 +363,10 @@ gem 'gitlab-labkit', '~> 0.34.0' # rubocop:todo Gemfile/MissingFeatureCategory
gem 'thrift', '>= 0.16.0' # rubocop:todo Gemfile/MissingFeatureCategory
# I18n
-gem 'rails-i18n', '~> 7.0' # rubocop:todo Gemfile/MissingFeatureCategory
-gem 'gettext_i18n_rails', '~> 1.11.0' # rubocop:todo Gemfile/MissingFeatureCategory
-gem 'gettext_i18n_rails_js', '~> 1.3' # rubocop:todo Gemfile/MissingFeatureCategory
-gem 'gettext', '~> 3.3', require: false, group: :development # rubocop:todo Gemfile/MissingFeatureCategory
+gem 'rails-i18n', '~> 7.0', feature_category: :internationalization
+gem 'gettext_i18n_rails', '~> 1.11.0', feature_category: :internationalization
+gem 'gettext_i18n_rails_js', '~> 2.0.0', feature_category: :internationalization
+gem 'gettext', '~> 3.3', require: false, group: :development, feature_category: :internationalization
gem 'batch-loader', '~> 2.0.1' # rubocop:todo Gemfile/MissingFeatureCategory
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 5cc93ef0ee9..1e879605ba8 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -194,15 +194,16 @@
{"name":"fog-local","version":"0.8.0","platform":"ruby","checksum":"263b2d09e54c69d1b87ad7f235a1a1e53c8a674edcedf7512c1715765ad7ef79"},
{"name":"fog-xml","version":"0.1.3","platform":"ruby","checksum":"5604c42649ebb0d8a31bd973aa000c2dd0127f1c1c4c174b69266a2e78e37410"},
{"name":"formatador","version":"0.2.5","platform":"ruby","checksum":"80821869ddacb79e72870ff4bb1531efacd278c04f2df26bc6b4529ee13582bd"},
+{"name":"forwardable","version":"1.3.3","platform":"ruby","checksum":"f17df4bd6afa6f46a003217023fe5716ef88ce261f5c4cf0edbdeed6470cafac"},
{"name":"fugit","version":"1.8.1","platform":"ruby","checksum":"18ffb26813869610f71bb0b7d568c3624d2b3025aeebb6600a18df0c77a6a2b2"},
{"name":"fuubar","version":"2.2.0","platform":"ruby","checksum":"9b0263c4074f39c68b37f1e4e69a7d3cfc7523c41bea43601235daa723179b4a"},
{"name":"fuzzyurl","version":"0.9.0","platform":"ruby","checksum":"542efa80f2bcaadbdc402c2f0b572f2e335a1d53e375aecad68bbb3d86860c0f"},
{"name":"gapic-common","version":"0.18.0","platform":"ruby","checksum":"6fd55a538ce2d63026fa05f379b1aec00788cc060f76903739516ab1ca1496ab"},
{"name":"gemoji","version":"3.0.1","platform":"ruby","checksum":"80553f2f4932a7a95fb1b3c7c63f7dd937e7c8c610164bbdea28fd06eba5f36d"},
{"name":"get_process_mem","version":"0.2.7","platform":"ruby","checksum":"4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba"},
-{"name":"gettext","version":"3.3.6","platform":"ruby","checksum":"ee6bbd1b2f833ee52d7797fa68acbfecc4726aec6b6280fd7eab92aa0190b413"},
+{"name":"gettext","version":"3.4.9","platform":"ruby","checksum":"292864fe6a15c224cee4125a4a72fab426fdbb280e4cff3cfe44935f549b009a"},
{"name":"gettext_i18n_rails","version":"1.11.0","platform":"ruby","checksum":"e19c7e4a256c500f7f38396dca44a282b9838ae278f57c362993a54964b22bbe"},
-{"name":"gettext_i18n_rails_js","version":"1.3.0","platform":"ruby","checksum":"5d10afe4be3639bff78c50a56768c20f39aecdabc580c08aa45573911c2bd687"},
+{"name":"gettext_i18n_rails_js","version":"2.0.0","platform":"ruby","checksum":"7bfb72699e3cdf9a2d892cc816e70442a08d0f4e340b92731249ad38b9205b51"},
{"name":"git","version":"1.18.0","platform":"ruby","checksum":"c9b80462e4565cd3d7a9ba8440c41d2c52244b17b0dad0bfddb46de70630c465"},
{"name":"gitaly","version":"16.5.0.pre.rc1","platform":"ruby","checksum":"ed17515ad04d4663a0efc15c8f2887b705f006133e8b10cc9321460eb0a38353"},
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
@@ -446,9 +447,10 @@
{"name":"pg_query","version":"4.2.3","platform":"ruby","checksum":"1cc9955c7bce8e51e1abc11f1952e3d9d0f1cd4c16c58c56ec75d5aaf1cfd697"},
{"name":"plist","version":"3.6.0","platform":"ruby","checksum":"f468bcf6b72ec6d1585ed6744eb4817c1932a5bf91895ed056e69b7f12ca10f2"},
{"name":"png_quantizator","version":"0.2.1","platform":"ruby","checksum":"6023d4d064125c3a7e02929c95b7320ed6ac0d7341f9e8de0c9ea6576ef3106b"},
-{"name":"po_to_json","version":"1.0.1","platform":"ruby","checksum":"6a7188aa6c42a22c9718f9b39062862ef7f3d8f6a7b4177cae058c3308b56af7"},
+{"name":"po_to_json","version":"2.0.0","platform":"ruby","checksum":"9e59b2904c015d2fcad3ec02022970ad0fb6622f6eb5ba82b47dff99d2fd6b2a"},
{"name":"premailer","version":"1.16.0","platform":"ruby","checksum":"03e4402c448e6bae13fb5f6301a8bde4f3508e1bff90ae7c0972c7be94694786"},
{"name":"premailer-rails","version":"1.10.3","platform":"ruby","checksum":"7cdcb97027866f7a81c490c6d15ada7f39666b5f6375f0821b7e97e0483b112f"},
+{"name":"prime","version":"0.1.2","platform":"ruby","checksum":"d4e956cadfaf04de036dc7dc74f95bf6a285a62cc509b28b7a66b245d19fe3a4"},
{"name":"proc_to_ast","version":"0.1.0","platform":"ruby","checksum":"92a73fa66e2250a83f8589f818b0751bcf227c68f85916202df7af85082f8691"},
{"name":"prometheus-client-mmap","version":"0.28.1","platform":"aarch64-linux","checksum":"b190045625ee8f8b3ef90e583ef7fadeac745810c8a243f1ed5e9b47c18146f0"},
{"name":"prometheus-client-mmap","version":"0.28.1","platform":"arm64-darwin","checksum":"9e7022848493b882d1de9f42d7784f9821e83b2c3b4b2dc9a12c2c8269209a6e"},
@@ -586,6 +588,7 @@
{"name":"simplecov-html","version":"0.12.3","platform":"ruby","checksum":"4b1aad33259ffba8b29c6876c12db70e5750cb9df829486e4c6e5da4fa0aa07b"},
{"name":"simplecov-lcov","version":"0.8.0","platform":"ruby","checksum":"0115f31cb7ef5ec4334f5d9382c67fd43de2e5270e21b65bfc693da82dd713c1"},
{"name":"simplecov_json_formatter","version":"0.1.4","platform":"ruby","checksum":"529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428"},
+{"name":"singleton","version":"0.1.1","platform":"ruby","checksum":"b410b0417fcbb17bdfbc2d478ddba4c91e873d6e51c9d2d16b345c5ee5491c54"},
{"name":"sixarm_ruby_unaccent","version":"1.2.0","platform":"ruby","checksum":"0043a6077bdf2c4b03040152676a07f8bf77144f9b007b1960ee5c94d13a4384"},
{"name":"slack-messenger","version":"2.3.4","platform":"ruby","checksum":"49c611d2be5b0f9c250a3a957b9cc09b9c07b81dacb9843642d87b6fa35609c1"},
{"name":"snaky_hash","version":"2.0.0","platform":"ruby","checksum":"fe8b2e39e8ff69320f7812af73ea06401579e29ff1734a7009567391600687de"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 68590f74505..90e40727be7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -609,6 +609,7 @@ GEM
fog-core
nokogiri (>= 1.5.11, < 2.0.0)
formatador (0.2.5)
+ forwardable (1.3.3)
fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
@@ -627,15 +628,18 @@ GEM
gemoji (3.0.1)
get_process_mem (0.2.7)
ffi (~> 1.0)
- gettext (3.3.6)
+ gettext (3.4.9)
+ erubi
locale (>= 2.0.5)
+ prime
+ racc
text (>= 1.3.0)
gettext_i18n_rails (1.11.0)
fast_gettext (>= 0.9.0)
- gettext_i18n_rails_js (1.3.0)
+ gettext_i18n_rails_js (2.0.0)
gettext (>= 3.0.2)
gettext_i18n_rails (>= 0.7.1)
- po_to_json (>= 1.0.0)
+ po_to_json (>= 2.0.0)
rails (>= 3.2.0)
git (1.18.0)
addressable (~> 2.8)
@@ -1200,7 +1204,7 @@ GEM
google-protobuf (>= 3.22.3)
plist (3.6.0)
png_quantizator (0.2.1)
- po_to_json (1.0.1)
+ po_to_json (2.0.0)
json (>= 1.6.0)
premailer (1.16.0)
addressable
@@ -1209,6 +1213,9 @@ GEM
premailer-rails (1.10.3)
actionmailer (>= 3)
premailer (~> 1.7, >= 1.7.9)
+ prime (0.1.2)
+ forwardable
+ singleton
proc_to_ast (0.1.0)
coderay
parser
@@ -1502,6 +1509,7 @@ GEM
simplecov-html (0.12.3)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
+ singleton (0.1.1)
sixarm_ruby_unaccent (1.2.0)
slack-messenger (2.3.4)
snaky_hash (2.0.0)
@@ -1811,7 +1819,7 @@ DEPENDENCIES
fuubar (~> 2.2.0)
gettext (~> 3.3)
gettext_i18n_rails (~> 1.11.0)
- gettext_i18n_rails_js (~> 1.3)
+ gettext_i18n_rails_js (~> 2.0.0)
gitaly (~> 16.5.0.pre.rc1)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 4.5.1)
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index ac037b69dd7..efc74241941 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -202,7 +202,9 @@ export default {
data-testid="pipeline-info-container"
class="gl-display-flex gl-flex-wrap gl-align-items-center gl-justify-content-space-between"
>
-
+
{{ pipeline.details.event_type_name }}
#{{ pipeline.id }}
-
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
index 7fc4a06cbae..267facb0a50 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
@@ -143,7 +143,9 @@ export default {
:collapsed="mr.mergeDetailsCollapsed"
@toggle="() => mr.toggleMergeDetails()"
>
-
+
diff --git a/app/assets/javascripts/work_items/components/work_item_award_emoji.vue b/app/assets/javascripts/work_items/components/work_item_award_emoji.vue
index 69861892f50..fb710836a88 100644
--- a/app/assets/javascripts/work_items/components/work_item_award_emoji.vue
+++ b/app/assets/javascripts/work_items/components/work_item_award_emoji.vue
@@ -7,7 +7,8 @@ import AwardsList from '~/vue_shared/components/awards_list.vue';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { TYPENAME_USER } from '~/graphql_shared/constants';
-import workItemAwardEmojiQuery from '../graphql/award_emoji.query.graphql';
+import groupWorkItemAwardEmojiQuery from '../graphql/group_award_emoji.query.graphql';
+import projectWorkItemAwardEmojiQuery from '../graphql/award_emoji.query.graphql';
import updateAwardEmojiMutation from '../graphql/update_award_emoji.mutation.graphql';
import {
EMOJI_THUMBSDOWN,
@@ -23,6 +24,7 @@ export default {
components: {
AwardsList,
},
+ inject: ['isGroup'],
props: {
workItemId: {
type: String,
@@ -75,7 +77,9 @@ export default {
},
apollo: {
awardEmoji: {
- query: workItemAwardEmojiQuery,
+ query() {
+ return this.isGroup ? groupWorkItemAwardEmojiQuery : projectWorkItemAwardEmojiQuery;
+ },
variables() {
return {
iid: this.workItemIid,
@@ -116,7 +120,7 @@ export default {
after: this.pageInfo?.endCursor,
},
});
- } catch (error) {
+ } catch {
this.$emit('error', I18N_WORK_ITEM_FETCH_AWARD_EMOJI_ERROR);
}
},
@@ -139,7 +143,7 @@ export default {
return this.awardEmoji.nodes;
}
- // else make a copy of unmutable list and return the list after adding the new emoji
+ // else make a copy of immutable list and return the list after adding the new emoji
const awardEmojiNodes = [...this.awardEmoji.nodes];
awardEmojiNodes.push({
name,
@@ -162,7 +166,7 @@ export default {
},
updateWorkItemAwardEmojiWidgetCache({ cache, name, toggledOn }) {
const query = {
- query: workItemAwardEmojiQuery,
+ query: this.isGroup ? groupWorkItemAwardEmojiQuery : projectWorkItemAwardEmojiQuery,
variables: {
fullPath: this.workItemFullpath,
iid: this.workItemIid,
diff --git a/app/assets/javascripts/work_items/graphql/award_emoji.query.graphql b/app/assets/javascripts/work_items/graphql/award_emoji.query.graphql
index 82a532e1bea..0b9dc546df3 100644
--- a/app/assets/javascripts/work_items/graphql/award_emoji.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/award_emoji.query.graphql
@@ -1,7 +1,7 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "~/work_items/graphql/award_emoji.fragment.graphql"
-query workItemAwardEmojis($fullPath: ID!, $iid: String, $after: String, $pageSize: Int) {
+query projectWorkItemAwardEmojis($fullPath: ID!, $iid: String, $after: String, $pageSize: Int) {
workspace: project(fullPath: $fullPath) {
id
workItems(iid: $iid) {
diff --git a/app/assets/javascripts/work_items/graphql/group_award_emoji.query.graphql b/app/assets/javascripts/work_items/graphql/group_award_emoji.query.graphql
new file mode 100644
index 00000000000..cdf8c7cad04
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/group_award_emoji.query.graphql
@@ -0,0 +1,27 @@
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
+#import "~/work_items/graphql/award_emoji.fragment.graphql"
+
+query groupWorkItemAwardEmojis($fullPath: ID!, $iid: String, $after: String, $pageSize: Int) {
+ workspace: group(fullPath: $fullPath) {
+ id
+ workItems(iid: $iid) {
+ nodes {
+ id
+ iid
+ widgets {
+ ... on WorkItemWidgetAwardEmoji {
+ type
+ awardEmoji(first: $pageSize, after: $after) {
+ pageInfo {
+ ...PageInfo
+ }
+ nodes {
+ ...AwardEmojiFragment
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 440fc7ad586..3b97ab2cc4f 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -379,6 +379,10 @@ $tabs-holder-z-index: 250;
.deployment-info {
margin-bottom: $gl-padding-8;
}
+
+ .gl-button {
+ margin-left: 0;
+ }
}
> *:not(:last-child) {
@@ -645,6 +649,9 @@ $tabs-holder-z-index: 250;
// to the end of the line or to force it to a
// new line if there is not enough space.
flex-grow: 999;
+ // Avoid layout shift of title when Mini Graph
+ // moves below title
+ padding-top: 5px;
}
.label-branch {
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 27f1d1f5528..5009bf7ff0c 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -3,6 +3,7 @@
module CreatesCommit
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
+ include SafeFormatHelper
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil, target_project: nil)
@@ -31,10 +32,10 @@ module CreatesCommit
result = service.new(@project_to_commit_into, current_user, commit_params).execute
if result[:status] == :success
- update_flash_notice(success_notice)
-
success_path = final_success_path(success_path, target_project)
+ update_flash_notice(success_notice, success_path)
+
respond_to do |format|
format.html { redirect_to success_path }
format.json { render json: { message: _("success"), filePath: success_path } }
@@ -65,8 +66,13 @@ module CreatesCommit
private
- def update_flash_notice(success_notice)
- flash[:notice] = success_notice || _("Your changes have been successfully committed.")
+ def update_flash_notice(success_notice, success_path)
+ changes_link = ActionController::Base.helpers.link_to _('changes'), success_path, class: 'gl-link'
+
+ default_message = safe_format(_("Your %{changes_link} have been committed successfully."),
+ changes_link: changes_link)
+
+ flash[:notice] = success_notice || default_message
if create_merge_request?
flash[:notice] =
diff --git a/app/helpers/vite_helper.rb b/app/helpers/vite_helper.rb
index 4d1085a5169..5096d3649b7 100644
--- a/app/helpers/vite_helper.rb
+++ b/app/helpers/vite_helper.rb
@@ -1,22 +1,6 @@
# frozen_string_literal: true
module ViteHelper
- def universal_javascript_include_tag(*args)
- if vite_enabled
- vite_javascript_tag(*args)
- else
- javascript_include_tag(*args)
- end
- end
-
- def universal_asset_path(*args)
- if vite_enabled
- vite_asset_path(*args)
- else
- asset_path(*args)
- end
- end
-
private
def vite_enabled
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index e42f9eeef23..9756e1b7dd3 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -84,7 +84,7 @@ class ActiveSession
)
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- redis.pipelined do |pipeline|
+ Gitlab::Redis::CrossSlot::Pipeline.new(redis).pipelined do |pipeline|
pipeline.setex(
key_name(user.id, session_private_id),
expiry,
@@ -135,9 +135,15 @@ class ActiveSession
redis.srem(lookup_key_name(user.id), session_ids)
+ session_keys = rack_session_keys(session_ids)
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- redis.del(key_names)
- redis.del(rack_session_keys(session_ids))
+ if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ Gitlab::Redis::ClusterUtil.batch_unlink(key_names, redis)
+ Gitlab::Redis::ClusterUtil.batch_unlink(session_keys, redis)
+ else
+ redis.del(key_names)
+ redis.del(session_keys)
+ end
end
end
@@ -206,7 +212,13 @@ class ActiveSession
session_keys.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch|
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- redis.mget(session_keys_batch).compact.map do |raw_session|
+ raw_sessions = if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ Gitlab::Redis::ClusterUtil.batch_get(session_keys_batch, redis)
+ else
+ redis.mget(session_keys_batch)
+ end
+
+ raw_sessions.compact.map do |raw_session|
load_raw_session(raw_session)
end
end
@@ -249,7 +261,13 @@ class ActiveSession
found = Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
entry_keys = session_ids.map { |session_id| key_name(user_id, session_id) }
- session_ids.zip(redis.mget(entry_keys)).to_h
+ entries = if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ Gitlab::Redis::ClusterUtil.batch_get(entry_keys, redis)
+ else
+ redis.mget(entry_keys)
+ end
+
+ session_ids.zip(entries).to_h
end
found.compact!
@@ -258,7 +276,13 @@ class ActiveSession
fallbacks = Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
entry_keys = missing.map { |session_id| key_name_v1(user_id, session_id) }
- missing.zip(redis.mget(entry_keys)).to_h
+ entries = if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ Gitlab::Redis::ClusterUtil.batch_get(entry_keys, redis)
+ else
+ redis.mget(entry_keys)
+ end
+
+ missing.zip(entries).to_h
end
fallbacks.merge(found.compact)
diff --git a/app/models/ci/build_trace_chunks/redis_base.rb b/app/models/ci/build_trace_chunks/redis_base.rb
index 3b7a844d122..5f6b5c30a6a 100644
--- a/app/models/ci/build_trace_chunks/redis_base.rb
+++ b/app/models/ci/build_trace_chunks/redis_base.rb
@@ -71,7 +71,11 @@ module Ci
with_redis do |redis|
# https://gitlab.com/gitlab-org/gitlab/-/issues/224171
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- redis.del(keys)
+ if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ Gitlab::Redis::ClusterUtil.batch_unlink(keys, redis)
+ else
+ redis.del(keys)
+ end
end
end
end
diff --git a/config/feature_flags/development/replicate_object_pool_on_move.yml b/config/feature_flags/development/replicate_object_pool_on_move.yml
index 8f34969a02d..e413c8ee56c 100644
--- a/config/feature_flags/development/replicate_object_pool_on_move.yml
+++ b/config/feature_flags/development/replicate_object_pool_on_move.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/420720
milestone: '16.3'
type: development
group: group::source code
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/use_embeddings_with_vertex.yml b/config/feature_flags/development/use_embeddings_with_vertex.yml
deleted file mode 100644
index 1f37539b4ff..00000000000
--- a/config/feature_flags/development/use_embeddings_with_vertex.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: use_embeddings_with_vertex
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130421
-rollout_issue_url:
-milestone: '16.5'
-type: development
-group: group::duo chat
-default_enabled: false
diff --git a/config/initializers/7_redis.rb b/config/initializers/7_redis.rb
index 060d0a8a67b..25c2c6aa11f 100644
--- a/config/initializers/7_redis.rb
+++ b/config/initializers/7_redis.rb
@@ -27,6 +27,10 @@ Redis::Cluster::SlotLoader.prepend(Gitlab::Patch::SlotLoader)
Redis::Cluster::CommandLoader.prepend(Gitlab::Patch::CommandLoader)
Redis::Cluster.prepend(Gitlab::Patch::RedisCluster)
+if Gitlab::Redis::Workhorse.params[:cluster].present?
+ raise "Do not configure workhorse with a Redis Cluster as pub/sub commands are not cluster-compatible."
+end
+
# Make sure we initialize a Redis connection pool before multi-threaded
# execution starts by
# 1. Sidekiq
diff --git a/config/initializers/action_cable.rb b/config/initializers/action_cable.rb
index 0d2073586be..fb52ac6eb8a 100644
--- a/config/initializers/action_cable.rb
+++ b/config/initializers/action_cable.rb
@@ -11,6 +11,14 @@ end
ActionCable::SubscriptionAdapter::Base.prepend(Gitlab::Patch::ActionCableSubscriptionAdapterIdentifier)
+using_redis_cluster = begin
+ Rails.application.config_for(:cable)[:cluster].present?
+rescue RuntimeError
+ # config/cable.yml does not exist, but that is not the purpose of this check
+end
+
+raise "Do not configure cable.yml with a Redis Cluster as ActionCable only works with Redis." if using_redis_cluster
+
# https://github.com/rails/rails/blob/bb5ac1623e8de08c1b7b62b1368758f0d3bb6379/actioncable/lib/action_cable/subscription_adapter/redis.rb#L18
ActionCable::SubscriptionAdapter::Redis.redis_connector = lambda do |config|
args = config.except(:adapter, :channel_prefix)
diff --git a/config/redis.yml.example b/config/redis.yml.example
index 9d884038af7..a391ae36a65 100644
--- a/config/redis.yml.example
+++ b/config/redis.yml.example
@@ -18,6 +18,11 @@ development:
queues_metadata:
cluster:
- redis://localhost:7001
+ shared_state:
+ cluster:
+ - redis://localhost:7001
+ workhorse:
+ url: redis://localhost:6379
test:
chat:
@@ -38,3 +43,10 @@ test:
queues_metadata:
cluster:
- redis://localhost:7001
+ shared_state:
+ cluster:
+ - redis://localhost:7001
+ # pubsub and workhorse are not redis-cluster compatible
+ # even though they fall-back to shared_state
+ workhorse:
+ url: redis://localhost:6379
diff --git a/db/docs/path_locks.yml b/db/docs/path_locks.yml
index f27856d5dee..ba36f45ce4d 100644
--- a/db/docs/path_locks.yml
+++ b/db/docs/path_locks.yml
@@ -7,4 +7,4 @@ feature_categories:
description: Stores paths to repository blobs locked by users
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/684e9d1b5979e11d2edae11a3028a696bfcdedf8
milestone: '8.9'
-gitlab_schema: gitlab_main
+gitlab_schema: gitlab_main_cell
diff --git a/db/docs/project_repositories.yml b/db/docs/project_repositories.yml
index 2a3e37098c7..fdad3bb3e4f 100644
--- a/db/docs/project_repositories.yml
+++ b/db/docs/project_repositories.yml
@@ -7,4 +7,4 @@ feature_categories:
description: Keeps disk path to repositories and link to the shard
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8614
milestone: '11.6'
-gitlab_schema: gitlab_main
+gitlab_schema: gitlab_main_cell
diff --git a/db/docs/repository_languages.yml b/db/docs/repository_languages.yml
index 506c607cf54..92786d7ec18 100644
--- a/db/docs/repository_languages.yml
+++ b/db/docs/repository_languages.yml
@@ -7,4 +7,4 @@ feature_categories:
description: Keeps relation between projects and repository languages
introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/19480
milestone: '11.2'
-gitlab_schema: gitlab_main
+gitlab_schema: gitlab_main_cell
diff --git a/db/docs/security_orchestration_policy_configurations.yml b/db/docs/security_orchestration_policy_configurations.yml
index c015de47123..388df529835 100644
--- a/db/docs/security_orchestration_policy_configurations.yml
+++ b/db/docs/security_orchestration_policy_configurations.yml
@@ -9,4 +9,4 @@ description: |
Policies are stored in the repository as a YAML file.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53743
milestone: '13.9'
-gitlab_schema: gitlab_main
+gitlab_schema: gitlab_main_cell
diff --git a/db/docs/web_hook_logs.yml b/db/docs/web_hook_logs.yml
index d342c9a9ed0..2635b94f9e6 100644
--- a/db/docs/web_hook_logs.yml
+++ b/db/docs/web_hook_logs.yml
@@ -7,4 +7,4 @@ feature_categories:
description: Webhooks logs data.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/330789c23c777d8ca646eba7c25f39cb7342cdee
milestone: '9.3'
-gitlab_schema: gitlab_main
+gitlab_schema: gitlab_main_cell
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 455731f6c65..dd6cd2099a9 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -471,3 +471,78 @@ REPOSITORY TAG DIGE
gitlab/gitlab-ee latest sha256:723aa6edd8f122d50cae490b1743a616d54d4a910db892314d68470cc39dfb24 (...)
gitlab/gitlab-runner latest sha256:4a18a80f5be5df44cb7575f6b89d1fdda343297c6fd666c015c0e778b276e726 (...)
```
+
+## Creating a Custom GitLab Runner Docker Image
+
+You can create a custom GitLab Runner Docker image to package AWS CLI and Amazon ECR Credential Helper. This setup facilitates
+secure and streamlined interactions with AWS services, especially for containerized applications. For example, to reduce time
+and error-prone manual configurations, teams who deploy microservices on AWS can use this setup to manage, deploy,
+and update Docker images on Amazon ECR, without using manual credential management.
+
+1. [Authenticate GitLab with AWS](../cloud_deployment/index.md#authenticate-gitlab-with-aws).
+1. Create a `Dockerfile` with the following content:
+
+ ```Dockerfile
+ # Control package versions
+ ARG GITLAB_RUNNER_VERSION=v16.4.0
+ ARG AWS_CLI_VERSION=2.2.30
+
+ # AWS CLI and Amazon ECR Credential Helper
+ FROM amazonlinux as aws-tools
+ RUN set -e \
+ && yum update -y \
+ && yum install -y --allowerasing git make gcc curl unzip \
+ && curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" --output "awscliv2.zip" \
+ && unzip awscliv2.zip && ./aws/install -i /usr/local/bin \
+ && yum clean all
+
+ # Download and install ECR Credential Helper
+ RUN curl --location --output /usr/local/bin/docker-credential-ecr-login "https://github.com/awslabs/amazon-ecr-credential-helper/releases/latest/download/docker-credential-ecr-login-linux-amd64"
+ RUN chmod +x /usr/local/bin/docker-credential-ecr-login
+
+ # Configure the ECR Credential Helper
+ RUN mkdir -p /root/.docker
+ RUN echo '{ "credsStore": "ecr-login" }' > /root/.docker/config.json
+
+ # Final image based on GitLab Runner
+ FROM gitlab/gitlab-runner:${GITLAB_RUNNER_VERSION}
+
+ # Install necessary packages
+ RUN apt-get update \
+ && apt-get install -y --no-install-recommends jq procps curl unzip groff libgcrypt20 tar gzip less openssh-client \
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+ # Copy AWS CLI and Amazon ECR Credential Helper binaries
+ COPY --from=aws-tools /usr/local/bin/ /usr/local/bin/
+
+ # Copy ECR Credential Helper Configuration
+ COPY --from=aws-tools /root/.docker/config.json /root/.docker/config.json
+ ```
+
+1. To build the custom GitLab Runner Docker image within a `.gitlab-ci.yml`, include the following example below:
+
+ ```yaml
+ variables:
+ DOCKER_DRIVER: overlay2
+ IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
+ GITLAB_RUNNER_VERSION: v16.4.0
+ AWS_CLI_VERSION: 2.13.21
+
+ stages:
+ - build
+
+ build-image:
+ stage: build
+ script:
+ - echo "Logging into GitLab Container Registry..."
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+ - echo "Building Docker image..."
+ - docker build --build-arg GITLAB_RUNNER_VERSION=${GITLAB_RUNNER_VERSION} --build-arg AWS_CLI_VERSION=${AWS_CLI_VERSION} -t ${IMAGE_NAME} .
+ - echo "Pushing Docker image to GitLab Container Registry..."
+ - docker push ${IMAGE_NAME}
+ rules:
+ - changes:
+ - Dockerfile
+ ```
+
+1. [Register the runner](https://docs.gitlab.com/runner/register/index.html#docker).
diff --git a/doc/ci/variables/index.md b/doc/ci/variables/index.md
index 975157ff917..1fc43e24b98 100644
--- a/doc/ci/variables/index.md
+++ b/doc/ci/variables/index.md
@@ -703,6 +703,68 @@ to enable the `restrict_user_defined_variables` setting. The setting is `disable
If you [store your CI/CD configurations in a different repository](../../ci/pipelines/settings.md#specify-a-custom-cicd-configuration-file),
use this setting for control over the environment the pipeline runs in.
+## Exporting variables
+
+Scripts executed in separate shell contexts do not share exports, aliases,
+local function definitions, or any other local shell updates.
+
+This means that if a job fails, variables created by user-defined scripts are not
+exported.
+
+When runners execute jobs defined in `.gitlab-ci.yml`:
+
+- Scripts specified in `before_script` and the main script are executed together in
+ a single shell context, and are concatenated.
+- Scripts specified in `after_script` run in a shell context completely separate to
+ the `before_script` and the specified scripts.
+
+Regardless of the shell the scripts are executed in, the runner output includes:
+
+- Predefined variables.
+- Variables defined in:
+ - Instance, group, or project CI/CD settings.
+ - The `.gitlab-ci.yml` file in the `variables:` section.
+ - The `.gitlab-ci.yml` file in the `secrets:` section.
+ - The `config.toml`.
+
+The runner cannot handle manual exports, shell aliases, and functions executed in the body of the script, like `export MY_VARIABLE=1`.
+
+For example, in the following `.gitlab-ci.yml` file, the following scripts are defined:
+
+```yaml
+ job:
+ variables:
+ JOB_DEFINED_VARIABLE: "job variable"
+ before_script:
+ - echo "This is the 'before_script' script"
+ - export MY_VARIABLE="variable"
+ script:
+ - echo "This is the 'script' script"
+ - echo "JOB_DEFINED_VARIABLE's value is ${JOB_DEFINED_VARIABLE}"
+ - echo "CI_COMMIT_SHA's value is ${CI_COMMIT_SHA}"
+ - echo "MY_VARIABLE's value is ${MY_VARIABLE}"
+ after_script:
+ - echo "JOB_DEFINED_VARIABLE's value is ${JOB_DEFINED_VARIABLE}"
+ - echo "CI_COMMIT_SHA's value is ${CI_COMMIT_SHA}"
+ - echo "MY_VARIABLE's value is ${MY_VARIABLE}"
+```
+
+When the runner executes the job:
+
+1. `before_script` is executed:
+ 1. Prints to the output.
+ 1. Defines the variable for `MY_VARIABLE`.
+1. `script` is executed:
+ 1. Prints to the output.
+ 1. Prints the value of `JOB_DEFINED_VARIABLE`.
+ 1. Prints the value of `CI_COMMIT_SHA`.
+ 1. Prints the value of `MY_VARIABLE`.
+1. `after_script` is executed in a new, separate shell context:
+ 1. Prints to the output.
+ 1. Prints the value of `JOB_DEFINED_VARIABLE`.
+ 1. Prints the value of `CI_COMMIT_SHA`.
+ 1. Prints an empty value of `MY_VARIABLE`. The variable value cannot be detected because `after_script` is in a separate shell context to `before_script`.
+
## Related topics
- You can configure [Auto DevOps](../../topics/autodevops/index.md) to pass CI/CD variables
diff --git a/doc/development/ai_features/index.md b/doc/development/ai_features/index.md
index 4401a7e3fb1..2c6bec530cd 100644
--- a/doc/development/ai_features/index.md
+++ b/doc/development/ai_features/index.md
@@ -131,36 +131,9 @@ Gitlab::CurrentSettings.update!(anthropic_api_key: )
### Populating embeddings and using embeddings fixture
-Currently we have embeddings generate both with OpenAI and VertexAI. Bellow sections explain how to populate
+Embeddings are generated through VertexAI text embeddings endpoint. The sections below explain how to populate
embeddings in the DB or extract embeddings to be used in specs.
-FLAG:
-We are moving towards having VertexAI embeddings only, so eventually the OpenAI embeddings support will be drop
-as well as the section bellow will be removed.
-
-#### OpenAI embeddings
-
-To seed your development database with the embeddings for GitLab Documentation,
-you may use the pre-generated embeddings and a Rake task.
-
-```shell
-RAILS_ENV=development bundle exec rake gitlab:llm:embeddings:seed_pre_generated
-```
-
-The DBCleaner gem we use clear the database tables before each test runs.
-Instead of fully populating the table `tanuki_bot_mvc` where we store OpenAI embeddings for the documentations,
-we can add a few selected embeddings to the table from a pre-generated fixture.
-
-For instance, to test that the question "How can I reset my password" is correctly
-retrieving the relevant embeddings and answered, we can extract the top N closet embeddings
-to the question into a fixture and only restore a small number of embeddings quickly.
-To facilitate an extraction process, a Rake task been written.
-You can add or remove the questions needed to be tested in the Rake task and run the task to generate a new fixture.
-
-```shell
-RAILS_ENV=development bundle exec rake gitlab:llm:embeddings:extract_embeddings
-```
-
#### VertexAI embeddings
To seed your development database with the embeddings for GitLab Documentation,
diff --git a/doc/development/utilities.md b/doc/development/utilities.md
index 343d03b9d68..83b87d6d289 100644
--- a/doc/development/utilities.md
+++ b/doc/development/utilities.md
@@ -206,7 +206,7 @@ Refer to [`strong_memoize.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/maste
# good
def expensive_method(arg)
- strong_memoize_with(:expensive_method, arg)
+ strong_memoize_with(:expensive_method, arg) do
# ...
end
end
diff --git a/doc/user/group/reporting/git_abuse_rate_limit.md b/doc/user/group/reporting/git_abuse_rate_limit.md
index 1b14edb04d9..d32524b8f5f 100644
--- a/doc/user/group/reporting/git_abuse_rate_limit.md
+++ b/doc/user/group/reporting/git_abuse_rate_limit.md
@@ -13,7 +13,7 @@ On self-managed GitLab, by default this feature is not available. To make it ava
This is the group-level documentation. For self-managed instances, see the [administration documentation](../../admin_area/reporting/git_abuse_rate_limit.md).
-Git abuse rate limiting is a feature to automatically ban users who download, clone, pull, fetch, or fork more than a specified number of repositories of a group in a given time frame. Banned users cannot access the top-level group or any of its non-public subgroups via HTTP or SSH. The rate limit also applies to users who authenticate with a [personal](../../../user/profile/personal_access_tokens.md) or [group access token](../../../user/group/settings/group_access_tokens.md). Access to unrelated groups is unaffected.
+Git abuse rate limiting is a feature to automatically ban users who download, clone, pull, fetch, or fork more than a specified number of repositories of a group in a given time frame. Banned users cannot access the top-level group or any of its non-public subgroups via HTTP or SSH. The rate limit also applies to users who authenticate with [personal](../../../user/profile/personal_access_tokens.md) or [group access tokens](../../../user/group/settings/group_access_tokens.md), as well as [CI/CD job tokens](../../../ci/jobs/ci_job_token.md). Access to unrelated groups is unaffected.
Git abuse rate limiting does not apply to top-level group owners, [deploy tokens](../../../user/project/deploy_tokens/index.md), or [deploy keys](../../../user/project/deploy_keys/index.md).
diff --git a/doc/user/packages/composer_repository/index.md b/doc/user/packages/composer_repository/index.md
index d8662ef6512..6eac299e71f 100644
--- a/doc/user/packages/composer_repository/index.md
+++ b/doc/user/packages/composer_repository/index.md
@@ -225,7 +225,7 @@ To install a package:
Using a CI/CD job token:
```shell
- composer config gitlab-token. gitlab-ci-token ${CI_JOB_TOKEN}
+ composer config -- gitlab-token. gitlab-ci-token "${CI_JOB_TOKEN}"
```
Result in the `auth.json` file:
diff --git a/doc/user/product_analytics/instrumentation/browser_sdk.md b/doc/user/product_analytics/instrumentation/browser_sdk.md
index 1cc74d037b6..912e157f67c 100644
--- a/doc/user/product_analytics/instrumentation/browser_sdk.md
+++ b/doc/user/product_analytics/instrumentation/browser_sdk.md
@@ -8,23 +8,29 @@ info: To determine the technical writer assigned to the Stage/Group associated w
This SDK is for instrumenting web sites and applications to send data for the GitLab [product analytics functionality](../index.md).
-## How to use the Browser-SDK
+## How to use the Browser SDK
### Using the NPM package
Add the NPM package to your package JSON using your preferred package manager:
+::Tabs
+
+:::TabTitle yarn
+
```shell
yarn add @gitlab/application-sdk-browser
```
-OR
+:::TabTitle npm
```shell
npm i @gitlab/application-sdk-browser
```
-Then for browser usage you can import the client SDK:
+::EndTabs
+
+Then, for browser usage import the client SDK:
```javascript
import { glClientSDK } from '@gitlab/application-sdk-browser';
@@ -52,9 +58,9 @@ You can use a specific version of the SDK like this:
```
-## Browser-SDK initialization options
+## Browser SDK initialization options
-Apart from `appId` and `host`, the options below allow you to configure the Browser SDK.
+Apart from `appId` and `host`, you can configure the Browser SDK with the following options:
```typescript
interface GitLabClientSDKOptions {
@@ -73,32 +79,48 @@ interface GitLabClientSDKOptions {
}
```
-| Option | Description |
-| :---------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `appId` | This is the ID given by the GitLab Project Analytics setup guide. This is used to make sure your data is sent to your analytics instance. |
-| `host` | This is the GitLab Project Analytics instance that is given by the setup guide. |
-| `hasCookieConsent` | To use cookies to identify unique users and record their full IP address. This is set to `false` by default. When `false`, users will be considered anonymous users. No cookies or other storage mechanisms will be used to identify users. |
-| `respectGlobalPrivacyControl` | To respect the user's [GPC](https://globalprivacycontrol.org/) configuration to permit or refuse tracking. This is set to `true` by default. When `false`, events will be emitted regardless of user configuration. |
-| `trackerId` | The `trackerId` is used to differentiate between multiple trackers running on the same page or application, as each tracker instance can be configured differently to capture different sets of data. This identifier helps ensure that the data sent to the collector is correctly associated with the correct tracker configuration. `Default trackerId value is set as gitlab`. |
-| `pagePingTracking` | Page ping is a feature that allows you to `track user engagement on your website or application by sending periodic events while a user is actively browsing a page.` Page pings provide valuable insight into how users interact with your content, such as how long they spend on a page, which sections they are viewing, and if they are scrolling or not. `pagePingTracking` can be boolean or an object. If true it enables page ping with default options. if false, it will not enable page ping tracking. it can also be an object containing two options : `minimumVisitLength` - The minimum time that must have elapsed before first heartbeat. `heartbeatDelay` - The interval at which the callback is fired. |
-| `plugins` | Specify which plugins to enable or disable. By default all plugins are enabled. |
+| Option | Description |
+| :---------------------------- | :---------- |
+| `appId` | The ID provided by the GitLab Project Analytics setup guide. This ID ensures your data is sent to your analytics instance. |
+| `host` | The GitLab Project Analytics instance provided by the setup guide. |
+| `hasCookieConsent` | Whether to use cookies to identify unique users and record their full IP address. Set to `false` by default. When `false`, users are considered anonymous users. No cookies or other storage mechanisms are used to identify users. |
+| `respectGlobalPrivacyControl` | Whether to respect the user's [GPC](https://globalprivacycontrol.org/) configuration to permit or refuse tracking. Set to `true` by default. When `false`, events are emitted regardless of user configuration. |
+| `trackerId` | Used to differentiate between multiple trackers running on the same page or application, because each tracker instance can be configured differently to capture different sets of data. This identifier helps ensure that the data sent to the collector is correctly associated with the correct tracker configuration. Default value is `gitlab`. |
+| `pagePingTracking` | Option to track user engagement on your website or application by sending periodic events while a user is actively browsing a page. Page pings provide valuable insight into how users interact with your content, such as how long they spend on a page, which sections they are viewing, and whether they are scrolling. `pagePingTracking` can be boolean or an object. As a boolean, set to `true` it enables page ping with default options, and set to `false` it disables page ping tracking. As an object, it has two options: `minimumVisitLength` (the minimum time that must have elapsed before the first heartbeat) and `heartbeatDelay` (the interval at which the callback is fired). |
+| `plugins` | Specify which plugins to enable or disable. By default all plugins are enabled. |
### Plugins
-- `Client Hints`: It is an alternative the tracking the User Agent, which is particularly useful in those browsers which are freezing the User Agent string.
+- `Client Hints`: An alternative to tracking the User Agent, which is particularly useful in browsers that are freezing the User Agent string.
Enabling this plugin will automatically capture the following context:
-| Context | Example |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
-| [iglu:org.ietf/http_client_hints/jsonschema/1-0-0](https://github.com/snowplow/iglu-central/blob/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0) | `{"isMobile" : false, "brands" : [{"brand" : "Google Chrome", version : "89"}, {"brand" : "Chromium", version : "89"}]}` |
+ For example,
+ [iglu:org.ietf/http_client_hints/jsonschema/1-0-0](https://github.com/snowplow/iglu-central/blob/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0)
+ has the following configuration:
-- `Link Click Tracking`: With this plugin, the tracker will add click event listeners to all link elements. Link clicks are tracked as self-describing events. Each link-click event captures the link’s href attribute. The event also has fields for the link’s ID, classes, and target (where the linked document is opened, such as a new tab or new window).
+ ```json
+ {
+ "isMobile":false,
+ "brands":[
+ {
+ "brand":"Google Chrome",
+ "version":"89"
+ },
+ {
+ "brand":"Chromium",
+ "version":"89"
+ }
+ ]
+ }
+ ```
-- `Performance Timing`: It collects performance-related data from a user's browser using the `Navigation Timing API`. This API provides detailed information about the various stages of loading a web page, such as domain lookup, connection time, content download, and rendering times. This plugin helps to gather insights into how well website performs for users, identify potential performance bottlenecks, and improve the overall user experience.
+- `Link Click Tracking`: With this plugin, the tracker adds click event listeners to all link elements. Link clicks are tracked as self-describing events. Each link-click event captures the link's `href` attribute. The event also has fields for the link's ID, classes, and target (where the linked document is opened, such as a new tab or new window).
-- `Error Tracking`: It helps to capture and track errors that occur on website or application. By monitoring these errors, one can gain insights into potential issues with code or third-party libraries, which can help to improve the overall user experience and maintain the quality of website or application.
+- `Performance Timing`: It collects performance-related data from a user's browser using the `Navigation Timing API`. This API provides detailed information about the various stages of loading a web page, such as domain lookup, connection time, content download, and rendering times. This plugin helps to gather insights into how well a website performs for users, identify potential performance bottlenecks, and improve the overall user experience.
-`By default all the plugins are enabled`. These plugins can be enabled or disabled through the `plugins` object:
+- `Error Tracking`: It helps to capture and track errors that occur on a website or application. By monitoring these errors, you can gain insights into potential issues with code or third-party libraries, which can help to improve the overall user experience, and maintain the quality of the website or application.
+
+By default all plugins are enabled. You can disable or enable these plugins through the `plugins` object:
```typescript
const tracker = glClientSDK({
@@ -124,7 +146,7 @@ glClient.identify(userId, userAttributes);
| Property | Type | Description |
| :--------------- | :-------------------------- | :---------------------------------------------------------------------------- |
-| `userId` | `String` | The user identifier your application users to identify individual users. |
+| `userId` | `String` | The user identifier your application uses to identify individual users. |
| `userAttributes` | `Object`/`Null`/`undefined` | The user attributes that need to be added to the session and tracking events. |
### `page`
@@ -152,9 +174,9 @@ glClient.track(eventName, eventAttributes);
| `eventName` | `String` | The name of the custom event. |
| `eventAttributes` | `Object`/`Null`/`undefined` | The event attributes that need to be added to the tracked event. |
-### refreshLinkClickTracking
+### `refreshLinkClickTracking`
-enableLinkClickTracking only tracks clicks on links which exist when the page has loaded. If new links can be added to the page after then which you wish to track, just use refreshLinkClickTracking.
+`enableLinkClickTracking` tracks only clicks on links that exist when the page has loaded. To track new links added to the page after it has been loaded, use `refreshLinkClickTracking`.
```javascript
glClient.refreshLinkClickTracking();
@@ -163,9 +185,9 @@ glClient.refreshLinkClickTracking();
### `trackError`
NOTE:
-While `trackError` is supported on the Browser SDK the resulting events are currently not yet used or available anywhere.
+`trackError` is supported on the Browser SDK, but the resulting events are not used or available.
-Used to capture errors. This works only when the `errorTracking` plugin is enabled. As mentioned in [Plugins](#plugins) section, By default it is enabled.
+Used to capture errors. This works only when the `errorTracking` plugin is enabled. The [plugin](#plugins) is enabled by default.
```javascript
glClient.trackError(eventAttributes);
@@ -190,17 +212,17 @@ try {
| Property | Type | Description |
| :---------------- | :------- | :------------------------------------------------------------------------------------------------------------------- |
-| `eventAttributes` | `Object` | The event attributes that need to be added to the tracked event. `messeage` is a mandatory key in `eventAttributes`. |
+| `eventAttributes` | `Object` | The event attributes that need to be added to the tracked event. `message` is a mandatory key in `eventAttributes`. |
### `addCookieConsent`
-`addCookieConsent` is used to allow tracking of user identifiers via cookies. By default `hasCookieConsent` is false and no user identifiers are passed. To enable tracking of user identifiers call the `addCookieConsent` method. This is not needed if you intialised the Browser SDK with `hasCookieConsent` set to true.
+`addCookieConsent` is used to allow tracking of user identifiers via cookies. By default `hasCookieConsent` is false, and no user identifiers are passed. To enable tracking of user identifiers, call the `addCookieConsent` method. This step is not needed if you intialized the Browser SDK with `hasCookieConsent` set to true.
```javascript
glClient.addCookieConsent();
```
-### setCustomUrl
+### `setCustomUrl`
Used to set a custom URL for tracking.
@@ -212,7 +234,7 @@ glClient.setCustomUrl(url);
| :------- | :------- | :------------------------------------------------ |
| `url` | `String` | The custom URL that you want to set for tracking. |
-### setReferrerUrl
+### `setReferrerUrl`
Used to set a referrer URL for tracking.
@@ -224,9 +246,9 @@ glClient.setReferrerUrl(url);
| :------- | :------- | :-------------------------------------------------- |
| `url` | `String` | The referrer URL that you want to set for tracking. |
-### setDocumentTitle
+### `setDocumentTitle`
-Used to override document title.
+Used to override the document title.
```javascript
glClient.setDocumentTitle(title);
@@ -234,18 +256,18 @@ glClient.setDocumentTitle(title);
| Property | Type | Description |
| :------- | :------- | :--------------------------------- |
-| `title` | `String` | The document title you want to set |
+| `title` | `String` | The document title you want to set. |
## Contribute
-Want to contribute to Browser-SDK? follow [contributing guide](https://gitlab.com/gitlab-org/analytics-section/product-analytics/gl-application-sdk-js/-/blob/main/docs/Contributing.md).
+If you would like to contribute to Browser SDK, follow the [contributing guide](https://gitlab.com/gitlab-org/analytics-section/product-analytics/gl-application-sdk-js/-/blob/main/docs/Contributing.md).
## Troubleshooting
-If the Browser SDK is not sending events or is behaving in an unexpected way, take the following actions:
+If the Browser SDK is not sending events, or behaving in an unexpected way, take the following actions:
-- Verify that the appId and host values in the options object are correct.
-- Check if any browser privacy settings, extensions, or ad blockers are interfering with the Browser SDK.
+1. Verify that the `appId` and host values in the options object are correct.
+1. Check if any browser privacy settings, extensions, or ad blockers are interfering with the Browser SDK.
-For more information and assistance, consult the [Snowplow documentation](https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/browser-tracker/browser-tracker-v3-reference/)
-or contact the [Analytics Instrumentation](https://about.gitlab.com/handbook/engineering/development/analytics/analytics-instrumentation/#team-members) team.
+For more information and assistance, see the [Snowplow documentation](https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/browser-tracker/browser-tracker-v3-reference/)
+or contact the [Analytics Instrumentation team](https://about.gitlab.com/handbook/engineering/development/analytics/analytics-instrumentation/#team-members).
diff --git a/doc/user/project/issues/issue_weight.md b/doc/user/project/issues/issue_weight.md
index b1a1390d3d2..ddd08ee1de0 100644
--- a/doc/user/project/issues/issue_weight.md
+++ b/doc/user/project/issues/issue_weight.md
@@ -10,7 +10,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
When you have a lot of issues, it can be hard to get an overview.
With weighted issues, you can get a better idea of how much time,
-value, or complexity a given issue has or costs.
+value, or complexity a given issue has or costs. You can also [sort by weight](sorting_issue_lists.md#sorting-by-weight)
+to see which issues need to be prioritized.
## View the issue weight
diff --git a/lib/gitlab/github_import/label_finder.rb b/lib/gitlab/github_import/label_finder.rb
index 39e669dbba4..d0bbd2bc7cf 100644
--- a/lib/gitlab/github_import/label_finder.rb
+++ b/lib/gitlab/github_import/label_finder.rb
@@ -7,6 +7,7 @@ module Gitlab
# The base cache key to use for storing/retrieving label IDs.
CACHE_KEY = 'github-import/label-finder/%{project}/%{name}'
+ CACHE_OBJECT_NOT_FOUND = -1
# project - An instance of `Project`.
def initialize(project)
@@ -15,7 +16,18 @@ module Gitlab
# Returns the label ID for the given name.
def id_for(name)
- Gitlab::Cache::Import::Caching.read_integer(cache_key_for(name))
+ cache_key = cache_key_for(name)
+ val = Gitlab::Cache::Import::Caching.read_integer(cache_key)
+
+ return val if Feature.disabled?(:import_fallback_to_db_empty_cache, project)
+
+ return if val == CACHE_OBJECT_NOT_FOUND
+ return val if val.present?
+
+ object_id = project.labels.with_title(name).pick(:id) || CACHE_OBJECT_NOT_FOUND
+
+ Gitlab::Cache::Import::Caching.write(cache_key, object_id)
+ object_id == CACHE_OBJECT_NOT_FOUND ? nil : object_id
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -32,7 +44,7 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
def cache_key_for(name)
- CACHE_KEY % { project: project.id, name: name }
+ format(CACHE_KEY, project: project.id, name: name)
end
end
end
diff --git a/lib/gitlab/issues/rebalancing/state.rb b/lib/gitlab/issues/rebalancing/state.rb
index 12cc5f6e5dd..c60dac6f571 100644
--- a/lib/gitlab/issues/rebalancing/state.rb
+++ b/lib/gitlab/issues/rebalancing/state.rb
@@ -100,7 +100,7 @@ module Gitlab
def refresh_keys_expiration
with_redis do |redis|
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- redis.pipelined do |pipeline|
+ Gitlab::Redis::CrossSlot::Pipeline.new(redis).pipelined do |pipeline|
pipeline.expire(issue_ids_key, REDIS_EXPIRY_TIME)
pipeline.expire(current_index_key, REDIS_EXPIRY_TIME)
pipeline.expire(current_project_key, REDIS_EXPIRY_TIME)
diff --git a/lib/gitlab/redis/cluster_util.rb b/lib/gitlab/redis/cluster_util.rb
index 5f1f39b5237..9e307940de3 100644
--- a/lib/gitlab/redis/cluster_util.rb
+++ b/lib/gitlab/redis/cluster_util.rb
@@ -26,6 +26,15 @@ module Gitlab
end
expired_count
end
+
+ # Redis cluster alternative to mget
+ def batch_get(keys, redis)
+ keys.each_slice(1000).flat_map do |subset|
+ Gitlab::Redis::CrossSlot::Pipeline.new(redis).pipelined do |pipeline|
+ subset.map { |key| pipeline.get(key) }
+ end
+ end
+ end
end
end
end
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index 778d278146d..ae4aca7ff92 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -94,8 +94,17 @@ module Gitlab
keys = job_ids.map { |jid| key_for(jid) }
- with_redis { |redis| redis.mget(*keys) }
- .map { |result| !result.nil? }
+ status = with_redis do |redis|
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ Gitlab::Redis::ClusterUtil.batch_get(keys, redis)
+ else
+ redis.mget(*keys)
+ end
+ end
+ end
+
+ status.map { |result| !result.nil? }
end
# Returns the JIDs that are completed
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 54b4ae5a51a..f4970adf21f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5491,6 +5491,9 @@ msgstr ""
msgid "Analytics|Create your visualization"
msgstr ""
+msgid "Analytics|Current month to date"
+msgstr ""
+
msgid "Analytics|Custom dashboards"
msgstr ""
@@ -5557,6 +5560,9 @@ msgstr ""
msgid "Analytics|Event Props"
msgstr ""
+msgid "Analytics|Event counts update hourly"
+msgstr ""
+
msgid "Analytics|Failed to fetch data"
msgstr ""
@@ -5602,6 +5608,9 @@ msgstr ""
msgid "Analytics|Pages"
msgstr ""
+msgid "Analytics|Previous month"
+msgstr ""
+
msgid "Analytics|Referer"
msgstr ""
@@ -5638,6 +5647,9 @@ msgstr ""
msgid "Analytics|Something went wrong while loading available visualizations. Refresh the page to try again."
msgstr ""
+msgid "Analytics|Something went wrong while loading product analytics usage data. Refresh the page to try again."
+msgstr ""
+
msgid "Analytics|Something went wrong while loading the dashboard. Refresh the page to try again or see %{linkStart}troubleshooting documentation%{linkEnd}."
msgstr ""
@@ -5653,6 +5665,12 @@ msgstr ""
msgid "Analytics|Target URL"
msgstr ""
+msgid "Analytics|This group has no projects with product analytics onboarded in the current or previous month."
+msgstr ""
+
+msgid "Analytics|This table excludes projects that do not have product analytics onboarded."
+msgstr ""
+
msgid "Analytics|To create your own dashboards, first configure a project to store your dashboards."
msgstr ""
@@ -5665,6 +5683,9 @@ msgstr ""
msgid "Analytics|Updating visualization %{visualizationName}"
msgstr ""
+msgid "Analytics|Usage by project"
+msgstr ""
+
msgid "Analytics|Use the visualization designer to create custom visualizations. After you save a visualization, you can add it to a dashboard."
msgstr ""
@@ -55202,6 +55223,9 @@ msgstr[1] ""
msgid "YouTube"
msgstr ""
+msgid "Your %{changes_link} have been committed successfully."
+msgstr ""
+
msgid "Your %{group} membership will now expire in %{days}."
msgstr ""
@@ -55901,6 +55925,9 @@ msgid_plural "changes"
msgstr[0] ""
msgstr[1] ""
+msgid "changes"
+msgstr ""
+
msgid "check"
msgid_plural "checks"
msgstr[0] ""
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb
index ac70165f107..155ebeef840 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb
@@ -25,7 +25,7 @@ module QA
Page::File::Show.perform do |file|
aggregate_failures 'file details' do
- expect(file).to have_notice('Your changes have been successfully committed.')
+ expect(file).to have_notice('Your changes have been committed successfully.')
expect(file).to have_file_content(updated_file_content)
expect(file).to have_commit_message(commit_message_for_update)
end
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 36fe4a010a0..4f644812aa7 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -21,6 +21,7 @@ sed -i 's|url:.*$|url: redis://redis:6379|g' config/resque.yml
if [[ "$USE_REDIS_CLUSTER" != "false" ]] && [[ "$SETUP_DB" != "false" ]]; then
cp config/redis.yml.example config/redis.yml
sed -i 's|- .*$|- redis://rediscluster:7001|g' config/redis.yml
+ sed -i 's|url:.*$|url: redis://redis:6379|g' config/redis.yml
fi
setup_database_yml
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index 7603696c60c..8618dca5873 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -50,7 +50,10 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork
click_button 'Commit changes'
wait_for_requests
- expect(page).to have_content('Your changes have been successfully committed')
+ expect(page).to have_content('Your changes have been committed successfully')
+ page.within '.flash-container' do
+ expect(page).to have_link 'changes'
+ end
expect(page).to have_content(content)
end
end
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index e8a9edcc0cc..9c4f70a68b8 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -120,7 +120,7 @@ RSpec.describe 'Editing file blob', :js, feature_category: :groups_and_projects
it 'updates content' do
edit_and_commit
- expect(page).to have_content 'successfully committed'
+ expect(page).to have_content 'committed successfully.'
expect(page).to have_content 'NextFeature'
end
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index 10fa4a21359..5612f6a53b2 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -79,6 +79,25 @@ RSpec.describe 'Projects > Files > User edits files', :js, feature_category: :gr
expect(page).to have_content('*.rbca')
end
+ it 'displays a flash message with a link when an edited file was committed' do
+ click_link('.gitignore')
+ edit_in_single_file_editor
+ find('.file-editor', match: :first)
+
+ editor_set_value('*.rbca')
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Commit changes')
+
+ expect(page).to have_current_path(project_blob_path(project, 'master/.gitignore'), ignore_query: true)
+
+ wait_for_requests
+
+ expect(page).to have_content('Your changes have been committed successfully')
+ page.within '.flash-container' do
+ expect(page).to have_link 'changes'
+ end
+ end
+
it 'commits an edited file to a new branch' do
click_link('.gitignore')
edit_in_single_file_editor
diff --git a/spec/frontend/work_items/components/work_item_award_emoji_spec.js b/spec/frontend/work_items/components/work_item_award_emoji_spec.js
index f8c5f8edc4c..a756bfa6889 100644
--- a/spec/frontend/work_items/components/work_item_award_emoji_spec.js
+++ b/spec/frontend/work_items/components/work_item_award_emoji_spec.js
@@ -9,7 +9,8 @@ import { isLoggedIn } from '~/lib/utils/common_utils';
import AwardList from '~/vue_shared/components/awards_list.vue';
import WorkItemAwardEmoji from '~/work_items/components/work_item_award_emoji.vue';
import updateAwardEmojiMutation from '~/work_items/graphql/update_award_emoji.mutation.graphql';
-import workItemAwardEmojiQuery from '~/work_items/graphql/award_emoji.query.graphql';
+import groupWorkItemAwardEmojiQuery from '~/work_items/graphql/group_award_emoji.query.graphql';
+import projectWorkItemAwardEmojiQuery from '~/work_items/graphql/award_emoji.query.graphql';
import {
EMOJI_THUMBSUP,
EMOJI_THUMBSDOWN,
@@ -42,6 +43,7 @@ describe('WorkItemAwardEmoji component', () => {
const workItemQueryResponse = workItemByIidResponseFactory();
const mockWorkItem = workItemQueryResponse.data.workspace.workItems.nodes[0];
+ const groupAwardEmojiQuerySuccessHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
const awardEmojiQuerySuccessHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
const awardEmojiQueryEmptyHandler = jest.fn().mockResolvedValue(
workItemByIidResponseFactory({
@@ -83,10 +85,12 @@ describe('WorkItemAwardEmoji component', () => {
awardEmojiQueryHandler = awardEmojiQuerySuccessHandler,
awardEmojiMutationHandler = awardEmojiAddSuccessHandler,
workItemIid = '1',
+ isGroup = false,
} = {}) => {
mockApolloProvider = createMockApollo(
[
- [workItemAwardEmojiQuery, awardEmojiQueryHandler],
+ [projectWorkItemAwardEmojiQuery, awardEmojiQueryHandler],
+ [groupWorkItemAwardEmojiQuery, groupAwardEmojiQuerySuccessHandler],
[updateAwardEmojiMutation, awardEmojiMutationHandler],
],
{},
@@ -108,6 +112,9 @@ describe('WorkItemAwardEmoji component', () => {
wrapper = shallowMount(WorkItemAwardEmoji, {
isLoggedIn: isLoggedIn(),
apolloProvider: mockApolloProvider,
+ provide: {
+ isGroup,
+ },
propsData: {
workItemId: 'gid://gitlab/WorkItem/1',
workItemFullpath: 'test-project-path',
@@ -270,7 +277,7 @@ describe('WorkItemAwardEmoji component', () => {
};
});
- it('calls mutation succesfully and adds the award emoji with proper user details', async () => {
+ it('calls mutation successfully and adds the award emoji with proper user details', async () => {
createComponent({
awardEmojiMutationHandler: awardEmojiAddSuccessHandler,
});
@@ -345,4 +352,18 @@ describe('WorkItemAwardEmoji component', () => {
});
});
});
+
+ describe('group award emoji query', () => {
+ it('is not called in a project context', () => {
+ createComponent();
+
+ expect(groupAwardEmojiQuerySuccessHandler).not.toHaveBeenCalled();
+ });
+
+ it('is called in a group context', () => {
+ createComponent({ isGroup: true });
+
+ expect(groupAwardEmojiQuerySuccessHandler).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/helpers/vite_helper_spec.rb b/spec/helpers/vite_helper_spec.rb
deleted file mode 100644
index edb5650ab1a..00000000000
--- a/spec/helpers/vite_helper_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe ViteHelper, feature_category: :tooling do
- let(:source) { 'foo.js' }
- let(:vite_source) { 'vite/foo.js' }
- let(:vite_tag) { '' }
- let(:webpack_source) { 'webpack/foo.js' }
- let(:webpack_tag) { '' }
-
- context 'when vite enabled' do
- before do
- stub_rails_env('development')
- stub_feature_flags(vite: true)
-
- allow(helper).to receive(:vite_javascript_tag).and_return(vite_tag)
- allow(helper).to receive(:vite_asset_path).and_return(vite_source)
- allow(helper).to receive(:vite_stylesheet_tag).and_return(vite_tag)
- allow(helper).to receive(:vite_asset_url).and_return(vite_source)
- allow(helper).to receive(:vite_running).and_return(true)
- end
-
- describe '#universal_javascript_include_tag' do
- it 'returns vite javascript tag' do
- expect(helper.universal_javascript_include_tag(source)).to eq(vite_tag)
- end
- end
-
- describe '#universal_asset_path' do
- it 'returns vite asset path' do
- expect(helper.universal_asset_path(source)).to eq(vite_source)
- end
- end
- end
-
- context 'when vite disabled' do
- before do
- stub_feature_flags(vite: false)
-
- allow(helper).to receive(:javascript_include_tag).and_return(webpack_tag)
- allow(helper).to receive(:asset_path).and_return(webpack_source)
- allow(helper).to receive(:stylesheet_link_tag).and_return(webpack_tag)
- allow(helper).to receive(:path_to_stylesheet).and_return(webpack_source)
- end
-
- describe '#universal_javascript_include_tag' do
- it 'returns webpack javascript tag' do
- expect(helper.universal_javascript_include_tag(source)).to eq(webpack_tag)
- end
- end
-
- describe '#universal_asset_path' do
- it 'returns ActionView asset path' do
- expect(helper.universal_asset_path(source)).to eq(webpack_source)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index c6cd5e55754..a6f21fe7438 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -22,7 +22,9 @@ RSpec.describe 'cross-database foreign keys' do
'merge_requests.updated_by_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
'merge_requests.merge_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
'merge_requests.author_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
+ 'path_locks.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429380
'project_authorizations.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422044
+ 'security_orchestration_policy_configurations.bot_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/429438
'user_group_callouts.user_id' # https://gitlab.com/gitlab-org/gitlab/-/issues/421287
]
end
diff --git a/spec/lib/gitlab/github_import/label_finder_spec.rb b/spec/lib/gitlab/github_import/label_finder_spec.rb
index 9905fce2a20..e46595974d1 100644
--- a/spec/lib/gitlab/github_import/label_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/label_finder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache, feature_category: :importers do
let_it_be(:project) { create(:project) }
let_it_be(:finder) { described_class.new(project) }
let_it_be(:bug) { create(:label, project: project, name: 'Bug') }
@@ -18,23 +18,64 @@ RSpec.describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
expect(finder.id_for(feature.name)).to eq(feature.id)
end
- it 'returns nil for an empty cache key' do
+ it 'fetches object id from database if not in cache' do
key = finder.cache_key_for(bug.name)
Gitlab::Cache::Import::Caching.write(key, '')
- expect(finder.id_for(bug.name)).to be_nil
+ expect(finder.id_for(bug.name)).to eq(bug.id)
end
it 'returns nil for a non existing label name' do
expect(finder.id_for('kittens')).to be_nil
end
+
+ it 'returns nil and skips database read if cache has no record' do
+ key = finder.cache_key_for(bug.name)
+
+ Gitlab::Cache::Import::Caching.write(key, -1)
+
+ expect(finder.id_for(bug.name)).to be_nil
+ end
end
context 'without a cache in place' do
- it 'returns nil for a label' do
+ it 'caches the ID of a database row and returns the ID' do
+ expect(Gitlab::Cache::Import::Caching)
+ .to receive(:write)
+ .with("github-import/label-finder/#{project.id}/#{feature.name}", feature.id)
+ .and_call_original
+
+ expect(finder.id_for(feature.name)).to eq(feature.id)
+ end
+ end
+
+ context 'with FF import_fallback_to_db_empty_cache disabled' do
+ before do
+ stub_feature_flags(import_fallback_to_db_empty_cache: false)
+ end
+
+ it 'returns nil for a non existing label name' do
+ expect(finder.id_for('kittens')).to be_nil
+ end
+
+ it 'does not fetch object id from database if not in cache' do
expect(finder.id_for(feature.name)).to be_nil
end
+
+ it 'fetches object id from cache if present' do
+ finder.build_cache
+
+ expect(finder.id_for(feature.name)).to eq(feature.id)
+ end
+
+ it 'returns -1 if cache is -1' do
+ key = finder.cache_key_for(bug.name)
+
+ Gitlab::Cache::Import::Caching.write(key, -1)
+
+ expect(finder.id_for(bug.name)).to eq(-1)
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index cd899a79451..f1e1ea3d936 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -1061,6 +1061,7 @@ approval_rules:
- users
- groups
- group_users
+ - group_members
- security_orchestration_policy_configuration
- protected_branches
- approval_merge_request_rule_sources
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index 698c8a37d48..072a9c5f008 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -7,6 +7,7 @@ require 'support/helpers/rails_helpers'
RSpec.describe Gitlab::InstrumentationHelper, :clean_gitlab_redis_repository_cache, :clean_gitlab_redis_cache,
:use_null_store_as_repository_cache, feature_category: :scalability do
using RSpec::Parameterized::TableSyntax
+ include RedisHelpers
describe '.add_instrumentation_data', :request_store do
let(:payload) { {} }
@@ -39,11 +40,18 @@ RSpec.describe Gitlab::InstrumentationHelper, :clean_gitlab_redis_repository_cac
end
context 'when Redis calls are made' do
+ let_it_be(:redis_store_class) { define_helper_redis_store_class }
+
+ before do # init redis connection with `test` env details
+ redis_store_class.with(&:ping)
+ RequestStore.clear!
+ end
+
it 'adds Redis data and omits Gitaly data' do
stub_rails_env('staging') # to avoid raising CrossSlotError
- Gitlab::Redis::Sessions.with { |redis| redis.mset('test-cache', 123, 'test-cache2', 123) }
+ redis_store_class.with { |redis| redis.mset('test-cache', 123, 'test-cache2', 123) }
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- Gitlab::Redis::Sessions.with { |redis| redis.mget('cache-test', 'cache-test-2') }
+ redis_store_class.with { |redis| redis.mget('cache-test', 'cache-test-2') }
end
Gitlab::Redis::Queues.with { |redis| redis.set('test-queues', 321) }
diff --git a/spec/lib/gitlab/issues/rebalancing/state_spec.rb b/spec/lib/gitlab/issues/rebalancing/state_spec.rb
index b457e0bf1dc..a0ea5fec8ec 100644
--- a/spec/lib/gitlab/issues/rebalancing/state_spec.rb
+++ b/spec/lib/gitlab/issues/rebalancing/state_spec.rb
@@ -231,8 +231,16 @@ RSpec.describe Gitlab::Issues::Rebalancing::State, :clean_gitlab_redis_shared_st
def check_existing_keys
index = 0
- # spec only, we do not actually scan keys in the code
- recently_finished_keys_count = Gitlab::Redis::SharedState.with { |redis| redis.scan(0, match: "#{described_class::RECENTLY_FINISHED_REBALANCE_PREFIX}:*") }.last.count
+ cursor = '0'
+ recently_finished_keys_count = 0
+
+ # loop to scan since it may run against a Redis Cluster
+ loop do
+ # spec only, we do not actually scan keys in the code
+ cursor, items = Gitlab::Redis::SharedState.with { |redis| redis.scan(cursor, match: "#{described_class::RECENTLY_FINISHED_REBALANCE_PREFIX}:*") }
+ recently_finished_keys_count += items.count
+ break if cursor == '0'
+ end
index += 1 if rebalance_caching.get_current_index > 0
index += 1 if rebalance_caching.get_current_project_id.present?
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 7a88b133a98..538eb51387e 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -1059,7 +1059,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
end
def value_in_queues
- Gitlab::Redis::SharedState.with do |redis|
+ Gitlab::Redis::Workhorse.with do |redis|
runner_queue_key = runner.send(:runner_queue_key)
redis.get(runner_queue_key)
end