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:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/analytics/shared/utils.js2
-rw-r--r--app/assets/javascripts/super_sidebar/components/context_switcher.vue11
-rw-r--r--app/assets/javascripts/super_sidebar/components/super_sidebar.vue1
-rw-r--r--app/assets/javascripts/super_sidebar/utils.js66
-rw-r--r--app/controllers/groups/group_members_controller.rb2
-rw-r--r--app/controllers/repositories/git_http_controller.rb5
-rw-r--r--app/finders/group_members_finder.rb7
-rw-r--r--app/helpers/sidebars_helper.rb33
-rw-r--r--app/models/ci/build.rb4
-rw-r--r--app/models/ci/pipeline.rb16
-rw-r--r--app/models/ci/pipeline_schedule.rb8
-rw-r--r--app/models/member.rb6
-rw-r--r--app/services/ci/queue/pending_builds_strategy.rb7
-rw-r--r--app/services/ci/update_build_queue_service.rb2
-rw-r--r--config/feature_flags/development/create_runner_machine.yml8
-rw-r--r--config/feature_flags/development/log_user_git_push_activity.yml8
-rw-r--r--config/feature_flags/development/pipeline_trigger_merge_status.yml8
-rw-r--r--config/feature_flags/development/use_traversal_ids_for_self_and_hierarchy.yml2
-rw-r--r--db/post_migrate/20230228021910_ensure_timelogs_note_id_bigint_backfill_is_finished_for_gitlab_dot_com.rb1
-rw-r--r--doc/api/merge_trains.md71
-rw-r--r--doc/development/advanced_search.md2
-rw-r--r--doc/development/cicd/index.md1
-rw-r--r--doc/development/gitaly.md2
-rw-r--r--doc/development/lfs.md2
-rw-r--r--doc/development/merge_request_concepts/diffs/index.md2
-rw-r--r--doc/development/project_templates.md2
-rw-r--r--doc/development/repository_mirroring.md2
-rw-r--r--doc/development/secure_coding_guidelines.md2
-rw-r--r--doc/integration/glab/index.md2
-rw-r--r--doc/policy/alpha-beta-support.md2
-rw-r--r--doc/raketasks/backup_restore.md2
-rw-r--r--doc/raketasks/cleanup.md2
-rw-r--r--doc/user/analytics/dora_metrics.md2
-rw-r--r--doc/user/analytics/value_streams_dashboard.md4
-rw-r--r--doc/user/application_security/dast/authentication.md4
-rw-r--r--doc/user/compliance/license_scanning_of_cyclonedx_files/index.md7
-rw-r--r--doc/user/group/value_stream_analytics/img/object_hierarchy_example_V14_10.pngbin0 -> 20826 bytes
-rw-r--r--doc/user/group/value_stream_analytics/index.md237
-rw-r--r--doc/user/project/clusters/runbooks/index.md2
-rw-r--r--doc/user/project/issues/crosslinking_issues.md4
-rw-r--r--doc/user/project/milestones/index.md5
-rw-r--r--doc/user/project/working_with_projects.md7
-rw-r--r--doc/user/snippets.md4
-rw-r--r--lib/api/ci/helpers/runner.rb2
-rw-r--r--lib/api/helpers/internal_helpers.rb1
-rw-r--r--lib/gitlab/database/async_constraints/postgres_async_constraint_validation.rb12
-rw-r--r--lib/gitlab/database/migrations/batched_background_migration_helpers.rb6
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb29
-rw-r--r--spec/controllers/repositories/git_http_controller_spec.rb63
-rw-r--r--spec/factories/ci/builds.rb2
-rw-r--r--spec/finders/group_members_finder_spec.rb52
-rw-r--r--spec/frontend/analytics/cycle_analytics/base_spec.js2
-rw-r--r--spec/frontend/analytics/shared/utils_spec.js8
-rw-r--r--spec/frontend/super_sidebar/components/context_switcher_spec.js29
-rw-r--r--spec/frontend/super_sidebar/utils_spec.js115
-rw-r--r--spec/helpers/sidebars_helper_spec.rb60
-rw-r--r--spec/lib/api/ci/helpers/runner_spec.rb68
-rw-r--r--spec/lib/api/helpers/internal_helpers_spec.rb60
-rw-r--r--spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb19
-rw-r--r--spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb11
-rw-r--r--spec/models/ci/pipeline_spec.rb148
-rw-r--r--spec/models/member_spec.rb18
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb59
-rw-r--r--spec/requests/api/ci/runner/runners_verify_post_spec.rb95
-rw-r--r--spec/requests/api/internal/base_spec.rb10
-rw-r--r--spec/requests/git_http_spec.rb6
-rw-r--r--spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb13
70 files changed, 1190 insertions, 273 deletions
diff --git a/Gemfile b/Gemfile
index 172e7a7fb49..719e0393b00 100644
--- a/Gemfile
+++ b/Gemfile
@@ -305,7 +305,7 @@ gem 'licensee', '~> 9.15'
gem 'charlock_holmes', '~> 0.7.7'
# Detect mime content type from content
-gem 'ruby-magic', '~> 0.5'
+gem 'ruby-magic', '~> 0.6'
# Faster blank
gem 'fast_blank'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 9bbc459802f..cc158c73eb8 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -524,7 +524,7 @@
{"name":"rubocop-rails","version":"2.17.4","platform":"ruby","checksum":"8004149a14372d3d6cededd000357879fa7eb0421403a7a26bc717e2a98bbedb"},
{"name":"rubocop-rspec","version":"2.18.1","platform":"ruby","checksum":"41c6455630fc98b809ebca047413389e2b7e3f68975028365c07bfea878db5ee"},
{"name":"ruby-fogbugz","version":"0.3.0","platform":"ruby","checksum":"5e04cde474648f498a71cf1e1a7ab42c66b953862fbe224f793ec0a7a1d5f657"},
-{"name":"ruby-magic","version":"0.5.5","platform":"ruby","checksum":"d2cc5b6b719831c3108a4f8a62bf3314c1af6cb09c98e2b5a3f9509bf8814e6c"},
+{"name":"ruby-magic","version":"0.6.0","platform":"ruby","checksum":"7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101"},
{"name":"ruby-progressbar","version":"1.11.0","platform":"ruby","checksum":"cc127db3866dc414ffccbf92928a241e585b3aa2b758a5563e74a6ee0f57d50a"},
{"name":"ruby-saml","version":"1.13.0","platform":"ruby","checksum":"d31cbdf5fb8fdd6aa3187e48dba3085cfeb751af30276a5739aa3659a66f069c"},
{"name":"ruby-statistics","version":"3.0.0","platform":"ruby","checksum":"610301370346931cb701e3a8d3d3e28eb65681162cae6066c0c11abf20efdc81"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 004e463d3bc..e3bf7f6cb1b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1327,7 +1327,7 @@ GEM
ruby-fogbugz (0.3.0)
crack (~> 0.4)
multipart-post (~> 2.0)
- ruby-magic (0.5.5)
+ ruby-magic (0.6.0)
mini_portile2 (~> 2.8)
ruby-progressbar (1.11.0)
ruby-saml (1.13.0)
@@ -1864,7 +1864,7 @@ DEPENDENCIES
rspec_profiling (~> 0.0.6)
rubocop
ruby-fogbugz (~> 0.3.0)
- ruby-magic (~> 0.5)
+ ruby-magic (~> 0.6)
ruby-progressbar (~> 1.10)
ruby-saml (~> 1.13.0)
ruby_parser (~> 3.19)
diff --git a/app/assets/javascripts/analytics/shared/utils.js b/app/assets/javascripts/analytics/shared/utils.js
index a7533f1f21d..a85f3fb3730 100644
--- a/app/assets/javascripts/analytics/shared/utils.js
+++ b/app/assets/javascripts/analytics/shared/utils.js
@@ -132,7 +132,7 @@ export const fetchMetricsData = (requests = [], requestPath, params) => {
export const generateValueStreamsDashboardLink = (groupPath, projectPaths = []) => {
if (groupPath.length) {
const query = projectPaths.length ? `?query=${projectPaths.join(',')}` : '';
- const dashboardsSlug = '/-/analytics/dashboards';
+ const dashboardsSlug = '/-/analytics/dashboards/value_streams_dashboard';
const segments = [gon.relative_url_root || '', '/', groupPath, dashboardsSlug];
return joinPaths(...segments).concat(query);
}
diff --git a/app/assets/javascripts/super_sidebar/components/context_switcher.vue b/app/assets/javascripts/super_sidebar/components/context_switcher.vue
index c8ca30cb545..a25ab929ba6 100644
--- a/app/assets/javascripts/super_sidebar/components/context_switcher.vue
+++ b/app/assets/javascripts/super_sidebar/components/context_switcher.vue
@@ -2,6 +2,7 @@
import { GlSearchBoxByType } from '@gitlab/ui';
import { s__ } from '~/locale';
import { contextSwitcherItems } from '../mock_data';
+import { trackContextAccess } from '../utils';
import NavItem from './nav_item.vue';
import FrequentProjectsList from './frequent_projects_list.vue';
import FrequentGroupsList from './frequent_groups_list.vue';
@@ -30,8 +31,18 @@ export default {
type: String,
required: true,
},
+ currentContext: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
contextSwitcherItems,
+ created() {
+ if (this.currentContext.namespace) {
+ trackContextAccess(this.username, this.currentContext);
+ }
+ },
};
</script>
diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
index 9a182d60c94..39ae4df08a7 100644
--- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
+++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
@@ -67,6 +67,7 @@ export default {
:username="sidebarData.username"
:projects-path="sidebarData.projects_path"
:groups-path="sidebarData.groups_path"
+ :current-context="sidebarData.current_context"
/>
</gl-collapse>
<gl-collapse :visible="!contextSwitcherOpened">
diff --git a/app/assets/javascripts/super_sidebar/utils.js b/app/assets/javascripts/super_sidebar/utils.js
index caf18adc5c6..a1ffb13f1db 100644
--- a/app/assets/javascripts/super_sidebar/utils.js
+++ b/app/assets/javascripts/super_sidebar/utils.js
@@ -1,11 +1,17 @@
-import { FREQUENT_ITEMS } from '~/frequent_items/constants';
+import AccessorUtilities from '~/lib/utils/accessor';
+import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/frequent_items/constants';
-// This imitates getTopFrequentItems from app/assets/javascripts/frequent_items/utils.js, but
-// adjusts the rules to accommodate for the context switcher's designs.
-export const getTopFrequentItems = (items = [], maxCount) => {
- const frequentItems = items.filter((item) => item.frequency >= FREQUENT_ITEMS.ELIGIBLE_FREQUENCY);
-
- frequentItems.sort((itemA, itemB) => {
+/**
+ * This takes an array of project or groups that were stored in the local storage, to be shown in
+ * the context switcher, and sorts them by frequency and last access date.
+ * In the resulting array, the most popular item (highest frequency and most recent access date) is
+ * placed at the first index, while the least popular is at the last index.
+ *
+ * @param {Array} items The projects or groups stored in the local storage
+ * @returns The items, sorted by frequency and last access date
+ */
+const sortItemsByFrequencyAndLastAccess = (items) =>
+ items.sort((itemA, itemB) => {
// Sort all frequent items in decending order of frequency
// and then by lastAccessedOn with recent most first
if (itemA.frequency !== itemB.frequency) {
@@ -17,5 +23,51 @@ export const getTopFrequentItems = (items = [], maxCount) => {
return 0;
});
+// This imitates getTopFrequentItems from app/assets/javascripts/frequent_items/utils.js, but
+// adjusts the rules to accommodate for the context switcher's designs.
+export const getTopFrequentItems = (items = [], maxCount) => {
+ const frequentItems = items.filter((item) => item.frequency >= FREQUENT_ITEMS.ELIGIBLE_FREQUENCY);
+ sortItemsByFrequencyAndLastAccess(frequentItems);
+
return frequentItems.slice(0, maxCount);
};
+
+const updateItemAccess = (item) => {
+ const now = Date.now();
+ const neverAccessed = !item.lastAccessedOn;
+ const shouldUpdate =
+ neverAccessed || Math.abs(now - item.lastAccessedOn) / FIFTEEN_MINUTES_IN_MS > 1;
+ const currentFrequency = item.frequency ?? 0;
+
+ return {
+ ...item,
+ frequency: shouldUpdate ? currentFrequency + 1 : currentFrequency,
+ lastAccessedOn: shouldUpdate ? now : item.lastAccessedOn,
+ };
+};
+
+export const trackContextAccess = (username, context) => {
+ if (!AccessorUtilities.canUseLocalStorage()) {
+ return false;
+ }
+
+ const storageKey = `${username}/frequent-${context.namespace}`;
+ const storedRawItems = localStorage.getItem(storageKey);
+ const storedItems = storedRawItems ? JSON.parse(storedRawItems) : [];
+ const existingItemIndex = storedItems.findIndex(
+ (cachedItem) => cachedItem.id === context.item.id,
+ );
+
+ if (existingItemIndex > -1) {
+ storedItems[existingItemIndex] = updateItemAccess(storedItems[existingItemIndex]);
+ } else {
+ const newItem = updateItemAccess(context.item);
+ if (storedItems.length === FREQUENT_ITEMS.MAX_COUNT) {
+ sortItemsByFrequencyAndLastAccess(storedItems);
+ storedItems.pop();
+ }
+ storedItems.push(newItem);
+ }
+
+ return localStorage.setItem(storageKey, JSON.stringify(storedItems));
+};
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index a1ccf6d51f4..685c8292787 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -72,7 +72,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
def filter_params
- params.permit(:two_factor, :search).merge(sort: @sort)
+ params.permit(:two_factor, :search, :user_type).merge(sort: @sort)
end
def membershipable_members
diff --git a/app/controllers/repositories/git_http_controller.rb b/app/controllers/repositories/git_http_controller.rb
index 018d98f2786..4f228ced542 100644
--- a/app/controllers/repositories/git_http_controller.rb
+++ b/app/controllers/repositories/git_http_controller.rb
@@ -20,6 +20,7 @@ module Repositories
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
def info_refs
log_user_activity if upload_pack?
+ log_user_activity if receive_pack? && Feature.enabled?(:log_user_git_push_activity)
render_ok
end
@@ -50,6 +51,10 @@ module Repositories
git_command == 'git-upload-pack'
end
+ def receive_pack?
+ git_command == 'git-receive-pack'
+ end
+
def git_command
if action_name == 'info_refs'
params[:service]
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
index 47ed623b252..05645dacab9 100644
--- a/app/finders/group_members_finder.rb
+++ b/app/finders/group_members_finder.rb
@@ -64,6 +64,7 @@ class GroupMembersFinder < UnionFinder
members = members.by_access_level(params[:access_levels])
end
+ members = filter_by_user_type(members)
members = apply_additional_filters(members)
by_created_at(members)
@@ -91,6 +92,12 @@ class GroupMembersFinder < UnionFinder
end
end
+ def filter_by_user_type(members)
+ return members unless params[:user_type] && can_manage_members
+
+ members.filter_by_user_type(params[:user_type])
+ end
+
def apply_additional_filters(members)
# overridden in EE to include additional filtering conditions.
members
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index 01dbddc5d5b..6c9688b0f9d 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -87,7 +87,8 @@ module SidebarsHelper
gitlab_version_check: gitlab_version_check,
gitlab_com_but_not_canary: Gitlab.com_but_not_canary?,
gitlab_com_and_canary: Gitlab.com_and_canary?,
- canary_toggle_com_url: Gitlab::Saas.canary_toggle_com_url
+ canary_toggle_com_url: Gitlab::Saas.canary_toggle_com_url,
+ current_context: super_sidebar_current_context(project: project, group: group)
}
end
@@ -229,6 +230,36 @@ module SidebarsHelper
show_security_dashboard: false
}
end
+
+ def super_sidebar_current_context(project: nil, group: nil)
+ if project&.persisted?
+ return {
+ namespace: 'projects',
+ item: {
+ id: project.id,
+ name: project.name,
+ namespace: project.full_name,
+ webUrl: project_path(project),
+ avatarUrl: project.avatar_url
+ }
+ }
+ end
+
+ if group&.persisted?
+ return {
+ namespace: 'groups',
+ item: {
+ id: group.id,
+ name: group.name,
+ namespace: group.full_name,
+ webUrl: group_path(group),
+ avatarUrl: group.avatar_url
+ }
+ }
+ end
+
+ {}
+ end
end
SidebarsHelper.prepend_mod_with('SidebarsHelper')
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 8211e7e196e..9b3683d1625 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -1102,10 +1102,6 @@ module Ci
::Ci::PendingBuild.upsert_from_build!(self)
end
- def create_runtime_metadata!
- ::Ci::RunningBuild.upsert_shared_runner_build!(self)
- end
-
##
# We can have only one queuing entry or running build tracking entry,
# because there is a unique index on `build_id` in each table, but we need
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 14fb14fb6ff..1c0b162e3df 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -335,6 +335,22 @@ module Ci
AutoDevops::DisableWorker.perform_async(pipeline.id) if pipeline.auto_devops_source?
end
end
+
+ after_transition any => [:running, *::Ci::Pipeline.completed_statuses] do |pipeline|
+ project = pipeline&.project
+
+ next unless project
+ next unless Feature.enabled?(:pipeline_trigger_merge_status, project)
+
+ pipeline.run_after_commit do
+ next if pipeline.child?
+ next unless project.only_allow_merge_if_pipeline_succeeds?(inherit_group_setting: true)
+
+ pipeline.all_merge_requests.opened.each do |merge_request|
+ GraphqlTriggers.merge_request_merge_status_updated(merge_request)
+ end
+ end
+ end
end
scope :internal, -> { where(source: internal_sources) }
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index a2c1a13824a..d15d1aa81c8 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -32,8 +32,6 @@ module Ci
scope :preloaded, -> { preload(:owner, project: [:route]) }
scope :owned_by, ->(user) { where(owner: user) }
- before_destroy :nullify_dependent_associations_in_batches
-
accepts_nested_attributes_for :variables, allow_destroy: true
alias_attribute :real_next_run, :next_run_at
@@ -84,6 +82,12 @@ module Ci
def worker_cron_expression
Settings.cron_jobs['pipeline_schedule_worker']['cron']
end
+
+ def destroy
+ nullify_dependent_associations_in_batches
+
+ super
+ end
end
end
diff --git a/app/models/member.rb b/app/models/member.rb
index e97c9e929ac..4329b61fc3d 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -320,6 +320,12 @@ class Member < ApplicationRecord
end
end
+ def filter_by_user_type(value)
+ return unless ::User.user_types.key?(value)
+
+ left_join_users.merge(::User.where(user_type: value))
+ end
+
def sort_by_attribute(method)
case method.to_s
when 'access_level_asc' then reorder(access_level: :asc)
diff --git a/app/services/ci/queue/pending_builds_strategy.rb b/app/services/ci/queue/pending_builds_strategy.rb
index cfafe66d10b..b2929390e58 100644
--- a/app/services/ci/queue/pending_builds_strategy.rb
+++ b/app/services/ci/queue/pending_builds_strategy.rb
@@ -57,9 +57,10 @@ module Ci
# if disaster recovery is enabled, we fallback to FIFO scheduling
relation.order('ci_pending_builds.build_id ASC')
else
- # Implement fair scheduling
- # this returns builds that are ordered by number of running builds
- # we prefer projects that don't use shared runners at all
+ # Implements Fair Scheduling
+ # Builds are ordered by projects that have the fewest running builds.
+ # This keeps projects that create many builds at once from hogging capacity but
+ # has the downside of penalizing projects with lots of builds created in a short period of time
relation
.with(running_builds_for_shared_runners_cte.to_arel)
.joins("LEFT JOIN project_builds ON ci_pending_builds.project_id = project_builds.project_id")
diff --git a/app/services/ci/update_build_queue_service.rb b/app/services/ci/update_build_queue_service.rb
index 58927a90b6e..40941dd4cd0 100644
--- a/app/services/ci/update_build_queue_service.rb
+++ b/app/services/ci/update_build_queue_service.rb
@@ -37,7 +37,7 @@ module Ci
end
##
- # Force recemove build from the queue, without checking a transition state
+ # Force remove build from the queue, without checking a transition state
#
def remove!(build)
removed = build.all_queuing_entries.delete_all
diff --git a/config/feature_flags/development/create_runner_machine.yml b/config/feature_flags/development/create_runner_machine.yml
new file mode 100644
index 00000000000..5ad3766ff6e
--- /dev/null
+++ b/config/feature_flags/development/create_runner_machine.yml
@@ -0,0 +1,8 @@
+---
+name: create_runner_machine
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109983
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/390261
+milestone: '15.9'
+type: development
+group: group::runner
+default_enabled: false
diff --git a/config/feature_flags/development/log_user_git_push_activity.yml b/config/feature_flags/development/log_user_git_push_activity.yml
new file mode 100644
index 00000000000..5e45670a8cd
--- /dev/null
+++ b/config/feature_flags/development/log_user_git_push_activity.yml
@@ -0,0 +1,8 @@
+---
+name: log_user_git_push_activity
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112527
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/393283
+milestone: "15.10"
+type: development
+group: group::utilization
+default_enabled: false
diff --git a/config/feature_flags/development/pipeline_trigger_merge_status.yml b/config/feature_flags/development/pipeline_trigger_merge_status.yml
new file mode 100644
index 00000000000..13c3996cbc0
--- /dev/null
+++ b/config/feature_flags/development/pipeline_trigger_merge_status.yml
@@ -0,0 +1,8 @@
+---
+name: pipeline_trigger_merge_status
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112525
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/392989
+milestone: '15.10'
+type: development
+group: group::code review
+default_enabled: false
diff --git a/config/feature_flags/development/use_traversal_ids_for_self_and_hierarchy.yml b/config/feature_flags/development/use_traversal_ids_for_self_and_hierarchy.yml
index 06bddc2aa1c..2455cdca593 100644
--- a/config/feature_flags/development/use_traversal_ids_for_self_and_hierarchy.yml
+++ b/config/feature_flags/development/use_traversal_ids_for_self_and_hierarchy.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348527
milestone: '14.7'
type: development
group: group::workspace
-default_enabled: false
+default_enabled: true
diff --git a/db/post_migrate/20230228021910_ensure_timelogs_note_id_bigint_backfill_is_finished_for_gitlab_dot_com.rb b/db/post_migrate/20230228021910_ensure_timelogs_note_id_bigint_backfill_is_finished_for_gitlab_dot_com.rb
index 7a9bf2436b9..0596427216d 100644
--- a/db/post_migrate/20230228021910_ensure_timelogs_note_id_bigint_backfill_is_finished_for_gitlab_dot_com.rb
+++ b/db/post_migrate/20230228021910_ensure_timelogs_note_id_bigint_backfill_is_finished_for_gitlab_dot_com.rb
@@ -2,6 +2,7 @@
class EnsureTimelogsNoteIdBigintBackfillIsFinishedForGitlabDotCom < Gitlab::Database::Migration[2.1]
restrict_gitlab_migration gitlab_schema: :gitlab_main
+ disable_ddl_transaction!
def up
return unless should_run?
diff --git a/doc/api/merge_trains.md b/doc/api/merge_trains.md
index 6d5d12a618c..afa5b38b9c3 100644
--- a/doc/api/merge_trains.md
+++ b/doc/api/merge_trains.md
@@ -220,3 +220,74 @@ Example response:
"duration":null
}
```
+
+## Add a merge request to a merge train
+
+Add a merge request to the merge train targeting the merge request's target branch.
+
+```plaintext
+POST /projects/:id/merge_trains/merge_requests/:merge_request_iid
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+| ------------------------------ | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
+| `merge_request_iid` | integer | yes | The internal ID of the merge request. |
+| `when_pipeline_succeeds` | boolean | no | If true, the merge request is added to the merge train when the pipeline succeeds. When false or unspecified, the merge request is added directly to the merge train. |
+| `sha` | string | no | If present, the SHA must match the `HEAD` of the source branch, otherwise the merge fails. |
+| `squash` | boolean | no | If true, the commits are squashed into a single commit on merge. |
+
+Example request:
+
+```shell
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/597/merge_trains/merge_requests/1"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 267,
+ "merge_request": {
+ "id": 273,
+ "iid": 1,
+ "project_id": 597,
+ "title": "My title 9",
+ "description": null,
+ "state": "opened",
+ "created_at": "2022-10-31T19:06:05.725Z",
+ "updated_at": "2022-10-31T19:06:05.725Z",
+ "web_url": "http://localhost/namespace18/project21/-/merge_requests/1"
+ },
+ "user": {
+ "id": 933,
+ "username": "user12",
+ "name": "Sidney Jones31",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/6c8365de387cb3db10ecc7b1880203c4?s=80\u0026d=identicon",
+ "web_url": "http://localhost/user12"
+ },
+ "pipeline": {
+ "id": 273,
+ "iid": 1,
+ "project_id": 598,
+ "sha": "b83d6e391c22777fca1ed3012fce84f633d7fed0",
+ "ref": "main",
+ "status": "pending",
+ "source": "push",
+ "created_at": "2022-10-31T19:06:06.231Z",
+ "updated_at": "2022-10-31T19:06:06.231Z",
+ "web_url": "http://localhost/namespace19/project22/-/pipelines/273"
+ },
+ "created_at": "2022-10-31T19:06:06.237Z",
+ "updated_at":"2022-10-31T19:06:06.237Z",
+ "target_branch":"main",
+ "status":"idle",
+ "merged_at":null,
+ "duration":null
+ }
+]
+```
diff --git a/doc/development/advanced_search.md b/doc/development/advanced_search.md
index 4c779088d01..73a8191b789 100644
--- a/doc/development/advanced_search.md
+++ b/doc/development/advanced_search.md
@@ -13,7 +13,7 @@ the [Elasticsearch integration documentation](../integration/advanced_search/ela
## Deep Dive
-In June 2019, Mario de la Ossa hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on the GitLab [Elasticsearch integration](../integration/advanced_search/elasticsearch.md) to share his domain specific knowledge with anyone who may work in this part of the codebase in the future. You can find the <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [recording on YouTube](https://www.youtube.com/watch?v=vrvl-tN2EaA), and the slides on [Google Slides](https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf). Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details might have changed, it should still serve as a good introduction.
+In June 2019, Mario de la Ossa hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/-/issues/1`) on the GitLab [Elasticsearch integration](../integration/advanced_search/elasticsearch.md) to share his domain specific knowledge with anyone who may work in this part of the codebase in the future. You can find the <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [recording on YouTube](https://www.youtube.com/watch?v=vrvl-tN2EaA), and the slides on [Google Slides](https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf). Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details might have changed, it should still serve as a good introduction.
In August 2020, a second Deep Dive was hosted, focusing on [GitLab-specific architecture for multi-indices support](#zero-downtime-reindexing-with-multiple-indices). The <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [recording on YouTube](https://www.youtube.com/watch?v=0WdPR9oB2fg) and the [slides](https://lulalala.gitlab.io/gitlab-elasticsearch-deepdive/) are available. Everything covered in this deep dive was accurate as of GitLab 13.3.
diff --git a/doc/development/cicd/index.md b/doc/development/cicd/index.md
index 2e2907feae1..2cc8fca02dd 100644
--- a/doc/development/cicd/index.md
+++ b/doc/development/cicd/index.md
@@ -147,6 +147,7 @@ This API endpoint runs [`Ci::RegisterJobService`](https://gitlab.com/gitlab-org/
There are 3 top level queries that this service uses to gather the majority of the jobs and they are selected based on the level where the runner is registered to:
- Select jobs for shared runner (instance level)
+ - Utilizes a fair scheduling algorithm which prioritizes projects with fewer running builds
- Select jobs for group runner
- Select jobs for project runner
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index 5b481661e7d..e7d04a6677e 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -14,7 +14,7 @@ Workhorse and GitLab Shell.
<!-- vale gitlab.Spelling = NO -->
In May 2019, Bob Van Landuyt
-hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
+hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/-/issues/1`)
on the [Gitaly project](https://gitlab.com/gitlab-org/gitaly). It included how to contribute to it as a
Ruby developer, and shared domain-specific knowledge with anyone who may work in this part of the
codebase in the future.
diff --git a/doc/development/lfs.md b/doc/development/lfs.md
index 7f44d12ed95..8f6d54e08e3 100644
--- a/doc/development/lfs.md
+++ b/doc/development/lfs.md
@@ -11,7 +11,7 @@ user documentation, see [Git Large File Storage](../topics/git/lfs/index.md).
## Deep Dive
-In April 2019, Francisco Javier López hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
+In April 2019, Francisco Javier López hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/-/issues/1`)
on the GitLab [Git LFS](../topics/git/lfs/index.md) implementation to share domain-specific
knowledge with anyone who may work in this part of the codebase in the future.
You can find the <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [recording on YouTube](https://www.youtube.com/watch?v=Yyxwcksr0Qc),
diff --git a/doc/development/merge_request_concepts/diffs/index.md b/doc/development/merge_request_concepts/diffs/index.md
index 8ef3f01aba9..c2dec5c4753 100644
--- a/doc/development/merge_request_concepts/diffs/index.md
+++ b/doc/development/merge_request_concepts/diffs/index.md
@@ -17,7 +17,7 @@ We rely on different sources to present diffs. These include:
<!-- vale gitlab.Spelling = NO -->
In January 2019, Oswaldo Ferreira hosted a Deep Dive (GitLab team members only:
-`https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab Diffs and Commenting on Diffs
+`https://gitlab.com/gitlab-org/create-stage/-/issues/1`) on GitLab Diffs and Commenting on Diffs
functionality to share domain-specific knowledge with anyone who may work in this part of the
codebase in the future:
diff --git a/doc/development/project_templates.md b/doc/development/project_templates.md
index 3320f3134fb..31537b21527 100644
--- a/doc/development/project_templates.md
+++ b/doc/development/project_templates.md
@@ -35,7 +35,7 @@ installed:
1. In your selected namespace, create a public project.
1. Add the project content you want to use in the template. Do not include unnecessary assets or dependencies. For an example,
[see this project](https://gitlab.com/gitlab-org/project-templates/dotnetcore).
-1. When the project is ready for review, [create an issue](https://gitlab.com/gitlab-org/gitlab/issues) with a link to your project.
+1. When the project is ready for review, [create an issue](https://gitlab.com/gitlab-org/gitlab/-/issues) with a link to your project.
In your issue, mention the Create:Source Code [Backend Engineering Manager and Product Manager](https://about.gitlab.com/handbook/product/categories/#source-code-group)
for the Templates feature.
diff --git a/doc/development/repository_mirroring.md b/doc/development/repository_mirroring.md
index a9164398afb..6d95dec823b 100644
--- a/doc/development/repository_mirroring.md
+++ b/doc/development/repository_mirroring.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
<!-- vale gitlab.Spelling = NO -->
-In December 2018, Tiago Botelho hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
+In December 2018, Tiago Botelho hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/-/issues/1`)
on the GitLab [Pull Repository Mirroring functionality](../user/project/repository/mirror/pull.md)
to share his domain specific knowledge with anyone who may work in this part of the
codebase in the future. You can find the <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [recording on YouTube](https://www.youtube.com/watch?v=sSZq0fpdY-Y),
diff --git a/doc/development/secure_coding_guidelines.md b/doc/development/secure_coding_guidelines.md
index 259d4bf969d..d41f1d76994 100644
--- a/doc/development/secure_coding_guidelines.md
+++ b/doc/development/secure_coding_guidelines.md
@@ -432,7 +432,7 @@ References:
### Select examples of past XSS issues affecting GitLab
-- [Stored XSS in user status](https://gitlab.com/gitlab-org/gitlab-foss/issues/55320)
+- [Stored XSS in user status](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55320)
- [XSS vulnerability on custom project templates form](https://gitlab.com/gitlab-org/gitlab/-/issues/197302)
- [Stored XSS in branch names](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55320)
- [Stored XSS in merge request pages](https://gitlab.com/gitlab-org/gitlab/-/issues/35096)
diff --git a/doc/integration/glab/index.md b/doc/integration/glab/index.md
index 3e143b9f8c2..427751a0297 100644
--- a/doc/integration/glab/index.md
+++ b/doc/integration/glab/index.md
@@ -74,7 +74,7 @@ To authenticate with your GitLab account, run `glab auth login`.
## Report issues
-Open an issue in the [`gitlab-org/cli` repository](https://gitlab.com/gitlab-org/cli/issues/new)
+Open an issue in the [`gitlab-org/cli` repository](https://gitlab.com/gitlab-org/cli/-/issues/new)
to send us feedback.
## Related topics
diff --git a/doc/policy/alpha-beta-support.md b/doc/policy/alpha-beta-support.md
index 1c7e9e77751..98910f9a3bb 100644
--- a/doc/policy/alpha-beta-support.md
+++ b/doc/policy/alpha-beta-support.md
@@ -12,7 +12,7 @@ All other features are considered to be Generally Available (GA).
## Alpha features
-Support is **not** provided for Alpha features and issues with them should be opened in the [GitLab issue tracker](https://gitlab.com/gitlab-org/gitlab/issues).
+Support is **not** provided for Alpha features and issues with them should be opened in the [GitLab issue tracker](https://gitlab.com/gitlab-org/gitlab/-/issues).
Characteristics of Alpha features:
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index a13d38a199d..9d82bbafe88 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -788,7 +788,7 @@ Truncating filenames to resolve the error involves:
#### Clean up remote uploaded files
-A [known issue](https://gitlab.com/gitlab-org/gitlab-foss/issues/45425) caused object store uploads to remain after a parent resource was deleted. This issue was [resolved](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18698).
+A [known issue](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/45425) caused object store uploads to remain after a parent resource was deleted. This issue was [resolved](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18698).
To fix these files, you must clean up all remote uploaded files that are in the storage but not tracked in the `uploads` database table.
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index 073a0351ec5..3f716900d17 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -154,7 +154,7 @@ These commands don't work for artifacts stored on
[object storage](../administration/object_storage.md).
WARNING:
-Prior to GitLab 14.9, this task incorrectly deletes [pipeline artifacts](../ci/pipelines/pipeline_artifacts.md)
+Prior to GitLab 14.9, this task incorrectly deletes [pipeline artifacts](../ci/pipelines/pipeline_artifacts.md).
[The bug fix](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81022) was
also back-ported to 14.6.6, 14.7.5, and 14.8.3. Upgrade to a release with the bug
fix to avoid data loss.
diff --git a/doc/user/analytics/dora_metrics.md b/doc/user/analytics/dora_metrics.md
index 9ac949f05b4..04742ecc6d5 100644
--- a/doc/user/analytics/dora_metrics.md
+++ b/doc/user/analytics/dora_metrics.md
@@ -29,7 +29,7 @@ For software leaders, tracking velocity alongside quality metrics ensures they'r
## DORA Metrics dashboard in Value Stream Analytics
-The four DORA metrics are available out-of-the-box in the [Value Stream Analytics (VSA) overview dashboard](../group/value_stream_analytics/index.md#view-dora-metrics-and-key-metrics-for-a-group).
+The four DORA metrics are available out-of-the-box in the [Value Stream Analytics (VSA) overview dashboard](../group/value_stream_analytics/index.md#view-value-stream-analytics).
This helps you visualize the engineering work in the context of end-to-end value delivery.
The One DevOps Platform [Value Stream Management](https://gitlab.com/gitlab-org/gitlab/-/value_stream_analytics) provides end-to-end visibility to the entire software delivery lifecycle.
diff --git a/doc/user/analytics/value_streams_dashboard.md b/doc/user/analytics/value_streams_dashboard.md
index 71931639b0b..fd4cdd0d65f 100644
--- a/doc/user/analytics/value_streams_dashboard.md
+++ b/doc/user/analytics/value_streams_dashboard.md
@@ -18,8 +18,8 @@ The Value Streams Dashboard is a customizable dashboard that enables decision-ma
This page is a work in progress, and we're updating the information as we add more features.
For more information, see the [Value Stream Management category direction page](https://about.gitlab.com/direction/plan/value_stream_management/).
-After the feature flag is enabled, to open the new page, append this path `/analytics/dashboards` to the group URL
-(for example, `https://gitlab.com/groups/gitlab-org/-/analytics/dashboards`).
+After the feature flag is enabled, to open the new page, append this path `/analytics/dashboards/value_streams_dashboard` to the group URL
+(for example, `https://gitlab.com/groups/gitlab-org/-/analytics/dashboards/value_streams_dashboard`).
## Initial use case
diff --git a/doc/user/application_security/dast/authentication.md b/doc/user/application_security/dast/authentication.md
index 440f08fcfcd..03a273ffff9 100644
--- a/doc/user/application_security/dast/authentication.md
+++ b/doc/user/application_security/dast/authentication.md
@@ -74,10 +74,10 @@ To run a DAST authenticated scan:
| `DAST_AUTH_VERIFICATION_URL` <sup>1</sup> | URL | Verifies successful authentication by checking the URL in the browser once the login form has been submitted. Example: `"https://example.com/loggedin_page"`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8. |
| `DAST_BROWSER_PATH_TO_LOGIN_FORM` <sup>1</sup> | [selector](#finding-an-elements-selector) | Comma-separated list of selectors that are selected prior to attempting to enter `DAST_USERNAME` and `DAST_PASSWORD` into the login form. Example: `"css:.navigation-menu,css:.login-menu-item"`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326633) in GitLab 14.1. |
| `DAST_EXCLUDE_URLS` <sup>1</sup> | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. |
-| `DAST_FIRST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when selected submits the username form of a multi-page login process. For example, `css:button[type='user-submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
+| `DAST_FIRST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when selected submits the username form of a multi-page login process. For example, `css:button[type='user-submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9894) in GitLab 12.4. |
| `DAST_PASSWORD` <sup>1</sup> | string | The password to authenticate to in the website. Example: `P@55w0rd!` |
| `DAST_PASSWORD_FIELD` | string | The selector of password field at the sign-in HTML form. Example: `id:password` |
-| `DAST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when selected submits the login form or the password form of a multi-page login process. For example, `css:button[type='submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
+| `DAST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when selected submits the login form or the password form of a multi-page login process. For example, `css:button[type='submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9894) in GitLab 12.4. |
| `DAST_USERNAME` <sup>1</sup> | string | The username to authenticate to in the website. Example: `admin` |
| `DAST_USERNAME_FIELD` <sup>1</sup> | string | The selector of username field at the sign-in HTML form. Example: `name:username` |
diff --git a/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md b/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md
index 97d6c97f286..06e2c892dba 100644
--- a/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md
+++ b/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md
@@ -7,10 +7,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# License scanning of CycloneDX files **(ULTIMATE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/384932) in GitLab 15.9 [with two flags](../../../administration/feature_flags.md) named `license_scanning_sbom_scanner` and `package_metadata_synchronization`. Both flags are disabled by default and both flags must be enabled for this feature to work.
-
-FLAG:
-On self-managed GitLab, this feature is not available.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/384932) in GitLab 15.9 [with two flags](../../../administration/feature_flags.md) named `license_scanning_sbom_scanner` and `package_metadata_synchronization`. Both flags are disabled by default and both flags must be enabled for this feature to work.
+> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/385173) in GitLab 15.9.
+> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/385173) in GitLab 15.10.
To detect the licenses in use, License Compliance relies on running the
[Dependency Scanning CI Jobs](../../application_security/dependency_scanning/index.md),
diff --git a/doc/user/group/value_stream_analytics/img/object_hierarchy_example_V14_10.png b/doc/user/group/value_stream_analytics/img/object_hierarchy_example_V14_10.png
new file mode 100644
index 00000000000..9c4d4ae7718
--- /dev/null
+++ b/doc/user/group/value_stream_analytics/img/object_hierarchy_example_V14_10.png
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/index.md b/doc/user/group/value_stream_analytics/index.md
index 3546922b00c..8f5f56e2f9f 100644
--- a/doc/user/group/value_stream_analytics/index.md
+++ b/doc/user/group/value_stream_analytics/index.md
@@ -23,6 +23,105 @@ Use value stream analytics to identify:
Value stream analytics is also available for [projects](../../analytics/value_stream_analytics.md).
+## How value stream analytics works
+
+### How value stream analytics aggregates data
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335391) in GitLab 14.5.
+> - Filter by stop date toggle [added](https://gitlab.com/gitlab-org/gitlab/-/issues/352428) in GitLab 14.9
+> - Data refresh badge [added](https://gitlab.com/gitlab-org/gitlab/-/issues/341739) in GitLab 14.9
+> - Filter by stop date toggle [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84356) in GitLab 14.9
+> - Enable filtering by stop date [added](https://gitlab.com/gitlab-org/gitlab/-/issues/355000) in GitLab 15.0
+
+Value stream analytics uses a backend process to collect and aggregate stage-level data, which
+ensures it can scale for large groups with a high number of issues and merge requests. Due to this process,
+there may be a slight delay between when an action is taken (for example, closing an issue) and when the data
+displays on the value stream analytics page.
+
+It may take up to 10 minutes to process the data and display results. Data collection may take
+longer than 10 minutes in the following cases:
+
+- If this is the first time you are viewing value stream analytics and have not yet [created a value stream](#create-a-value-stream-with-gitlab-default-stages).
+- If the group hierarchy has been re-arranged.
+- If there have been bulk updates on issues and merge requests.
+
+To view when the data was most recently updated, in the right corner next to **Edit**, hover over the **Last updated** badge.
+
+### How value stream analytics measures stages
+
+Value stream analytics measures each stage from its start event to its end event.
+
+For example, a stage might start when a user adds a label to an issue, and ends when they add another label.
+Items aren't included in the stage time calculation if they have not reached the end event.
+
+Value stream analytics allows you to customize your stages based on pre-defined events. To make the
+configuration easier, GitLab provides a pre-defined list of stages that can be used as a template
+
+Each pre-defined stages of value stream analytics is further described in the table below.
+
+| Stage | Measurement method |
+| ------- | -------------------- |
+| Issue | The median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whichever comes first. The label is tracked only if it already has an [issue board list](../../project/issue_board.md) created for it. |
+| Plan | The median time between the action you took for the previous stage, and pushing the first commit to the branch. The first commit on the branch triggers the separation between **Plan** and **Code**. At least one of the commits in the branch must contain the related issue number (for example, `#42`). If none of the commits in the branch mention the related issue number, it is not considered in the measurement time of the stage. |
+| Code | The median time between pushing a first commit (previous stage) and creating a merge request (MR) related to that commit. The key to keep the process tracked is to include the [issue closing pattern](../../project/issues/managing_issues.md#default-closing-pattern) in the description of the merge request. For example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request. If the closing pattern is not present, then the calculation uses the creation time of the first commit in the merge request as the start time. |
+| Test | The median time to run the entire pipeline for that project. It's related to the time GitLab CI/CD takes to run every job for the commits pushed to that merge request. It is basically the start->finish time for all pipelines. |
+| Review | The median time taken to review a merge request that has a closing issue pattern, between its creation and until it's merged. |
+| Staging | The median time between merging a merge request that has a closing issue pattern until the very first deployment to a [production environment](#how-value-stream-analytics-identifies-the-production-environment). If there isn't a production environment, this is not tracked. |
+
+For information about how value stream analytics calculates each stage, see the [Value stream analytics development guide](../../../development/value_stream_analytics.md).
+
+#### Example workflow
+
+This example shows a workflow through all seven stages in one day.
+
+If a stage does not include a start and a stop time, its data is not included in the median time.
+In this example, milestones have been created and CI/CD for testing and setting environments is configured.
+
+- 09:00: Create issue. **Issue** stage starts.
+- 11:00: Add issue to a milestone, start work on the issue, and create a branch locally.
+ **Issue** stage stops and **Plan** stage starts.
+- 12:00: Make the first commit.
+- 12:30: Make the second commit to the branch that mentions the issue number.
+ **Plan** stage stops and **Code** stage starts.
+- 14:00: Push branch and create a merge request that contains the
+ [issue closing pattern](../../project/issues/managing_issues.md#closing-issues-automatically).
+ **Code** stage stops and **Test** and **Review** stages start.
+- GitLab CI/CD takes 5 minutes to run scripts defined in [`.gitlab-ci.yml`](../../../ci/yaml/index.md).
+- 19:00: Merge the merge request. **Review** stage stops and **Staging** stage starts.
+- 19:30: Deployment to the `production` environment finishes. **Staging** stops.
+
+Value stream analytics records the following times for each stage:
+
+- **Issue**: 09:00 to 11:00: 2 hrs
+- **Plan**: 11:00 to 12:00: 1 hr
+- **Code**: 12:00 to 14:00: 2 hrs
+- **Test**: 5 minutes
+- **Review**: 14:00 to 19:00: 5 hrs
+- **Staging**: 19:00 to 19:30: 30 minutes
+
+Keep in mind the following observations related to this example:
+
+- This example demonstrates that it doesn't matter if your first
+ commit doesn't mention the issue number, you can do this later in any commit
+ on the branch you are working on.
+- The **Test** stage is used in the calculation for the overall time of
+ the cycle. It is included in the **Review** process, as every MR should be
+ tested.
+- This example illustrates only **one cycle** of the seven stages. The value stream analytics dashboard
+ shows the median time for multiple cycles.
+
+### How value stream analytics identifies the production environment
+
+Value stream analytics identifies [production environments](../../../ci/environments/index.md#deployment-tier-of-environments) by looking for project
+[environments](../../../ci/yaml/index.md#environment) with a name matching any of these patterns:
+
+- `prod` or `prod/*`
+- `production` or `production/*`
+
+These patterns are not case-sensitive.
+
+You can change the name of a project environment in your GitLab CI/CD configuration.
+
## View value stream analytics
> - Filtering [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13216) in GitLab 13.3
@@ -55,23 +154,21 @@ completed during the selected stage.
The table shows a list of related workflow items for the selected stage. Based on the stage you select, this can be:
-- CI/CD jobs
- Issues
- Merge requests
-- Pipelines
-## View DORA metrics and key metrics for a group
+## Value stream analytics metrics
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210315) in GitLab 13.0.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/323982) in GitLab 13.12.
-The **Overview** dashboard in value stream analytics shows key metrics and DORA metrics of group performance. Based on the filter you select,
+The **Overview** page in value stream analytics shows key metrics and DORA metrics of group performance. Based on the filter you select,
the dashboard automatically aggregates DORA metrics and displays the current status of the value stream. Select a DORA metric to view its chart.
Prerequisite:
- To view deployment metrics, you must have a
-[production environment configured](../../../ci/environments/index.md#deployment-tier-of-environments).
+[production environment configured](#how-value-stream-analytics-identifies-the-production-environment).
To view the DORA metrics and key metrics:
@@ -86,16 +183,16 @@ To view the DORA metrics and key metrics:
- In the **To** field, select an end date.
Key metrics and DORA metrics display below the **Filter results** text box.
-### Key metrics in the value stream
+### Key metrics
-The **Overview** dashboard shows the following key metrics that measure team performance:
+The **Overview** page shows the following key metrics that measure team performance:
-- Lead time: Median time from when the issue was created to when it was closed.
-- Cycle time: Median time from first commit to issue closed. GitLab measures cycle time from the earliest commit of a
+- **Lead time**: Median time from when the issue was created to when it was closed.
+- **Cycle time**: Median time from first commit to issue closed. GitLab measures cycle time from the earliest commit of a
[linked issue's merge request](../../project/issues/crosslinking_issues.md#from-commit-messages) to when that issue is closed.
The cycle time approach underestimates the lead time because merge request creation is always later than commit time.
-- New issues: Number of new issues created.
-- Deploys: Total number of deployments to production.
+- **New issues**: Number of new issues created.
+- **Deploys**: Total number of deployments to production.
### DORA metrics **(ULTIMATE)**
@@ -118,35 +215,6 @@ NOTE:
In GitLab 13.9 and later, deployment frequency metrics are calculated based on when the deployment was finished.
In GitLab 13.8 and earlier, deployment frequency metrics are calculated based on when the deployment was created.
-<div class="video-fallback">
- See the video: <a href="https://www.youtube.com/watch?v=wQU-mWvNSiI">DORA metrics and value stream analytics</a>.
-</div>
-<figure class="video-container">
- <iframe src="https://www.youtube-nocookie.com/embed/wQU-mWvNSiI" frameborder="0" allowfullscreen> </iframe>
-</figure>
-
-### How value stream analytics aggregates data
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335391) in GitLab 14.5.
-> - Filter by stop date toggle [added](https://gitlab.com/gitlab-org/gitlab/-/issues/352428) in GitLab 14.9
-> - Data refresh badge [added](https://gitlab.com/gitlab-org/gitlab/-/issues/341739) in GitLab 14.9
-> - Filter by stop date toggle [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84356) in GitLab 14.9
-> - Enable filtering by stop date [added](https://gitlab.com/gitlab-org/gitlab/-/issues/355000) in GitLab 15.0
-
-Value stream analytics uses a backend process to collect and aggregate stage-level data, which
-ensures it can scale for large groups with a high number of issues and merge requests. Due to this process,
-there may be a slight delay between when an action is taken (for example, closing an issue) and when the data
-displays on the value stream analytics page.
-
-It may take up to 10 minutes to process the data and display results. Data collection may take
-longer than 10 minutes in the following cases:
-
-- If this is the first time you are viewing value stream analytics and have not yet [created a value stream](#create-a-value-stream-with-gitlab-default-stages).
-- If the group hierarchy has been re-arranged.
-- If there have been bulk updates on issues and merge requests.
-
-To view when the data was most recently updated, in the right corner next to **Edit**, hover over the **Last updated** badge.
-
## View metrics for each development stage
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210315) in GitLab 13.0.
@@ -171,82 +239,9 @@ NOTE:
The date range selector filters items by the event time. The event time is when the
selected stage finished for the given item.
-## How value stream analytics measures stages
-
-Value stream analytics measures each stage from its start event to its end event.
-
-For example, a stage might start when a user adds a label to an issue, and ends when they add another label.
-Items aren't included in the stage time calculation if they have not reached the end event.
-
-Value stream analytics allows you to customize your stages based on pre-defined events. To make the
-configuration easier, GitLab provides a pre-defined list of stages that can be used as a template
-
-Each pre-defined stages of value stream analytics is further described in the table below.
-
-| Stage | Measurement method |
-| ------- | -------------------- |
-| Issue | The median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whichever comes first. The label is tracked only if it already has an [issue board list](../../project/issue_board.md) created for it. |
-| Plan | The median time between the action you took for the previous stage, and pushing the first commit to the branch. The first commit on the branch triggers the separation between **Plan** and **Code**. At least one of the commits in the branch must contain the related issue number (for example, `#42`). If none of the commits in the branch mention the related issue number, it is not considered in the measurement time of the stage. |
-| Code | The median time between pushing a first commit (previous stage) and creating a merge request (MR) related to that commit. The key to keep the process tracked is to include the [issue closing pattern](../../project/issues/managing_issues.md#default-closing-pattern) in the description of the merge request. For example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request. If the closing pattern is not present, then the calculation uses the creation time of the first commit in the merge request as the start time. |
-| Test | The median time to run the entire pipeline for that project. It's related to the time GitLab CI/CD takes to run every job for the commits pushed to that merge request. It is basically the start->finish time for all pipelines. |
-| Review | The median time taken to review a merge request that has a closing issue pattern, between its creation and until it's merged. |
-| Staging | The median time between merging a merge request that has a closing issue pattern until the very first deployment to a [production environment](#how-value-stream-analytics-identifies-the-production-environment). If there isn't a production environment, this is not tracked. |
-
-For information about how value stream analytics calculates each stage, see the [Value stream analytics development guide](../../../development/value_stream_analytics.md).
+## Create a value stream
-### Example workflow
-
-This example shows a workflow through all seven stages in one day.
-
-If a stage does not include a start and a stop time, its data is not included in the median time.
-In this example, milestones have been created and CI/CD for testing and setting environments is configured.
-
-- 09:00: Create issue. **Issue** stage starts.
-- 11:00: Add issue to a milestone, start work on the issue, and create a branch locally.
- **Issue** stage stops and **Plan** stage starts.
-- 12:00: Make the first commit.
-- 12:30: Make the second commit to the branch that mentions the issue number.
- **Plan** stage stops and **Code** stage starts.
-- 14:00: Push branch and create a merge request that contains the
- [issue closing pattern](../../project/issues/managing_issues.md#closing-issues-automatically).
- **Code** stage stops and **Test** and **Review** stages start.
-- GitLab CI/CD takes 5 minutes to run scripts defined in [`.gitlab-ci.yml`](../../../ci/yaml/index.md).
-- 19:00: Merge the merge request. **Review** stage stops and **Staging** stage starts.
-- 19:30: Deployment to the `production` environment finishes. **Staging** stops.
-
-Value stream analytics records the following times for each stage:
-
-- **Issue**: 09:00 to 11:00: 2 hrs
-- **Plan**: 11:00 to 12:00: 1 hr
-- **Code**: 12:00 to 14:00: 2 hrs
-- **Test**: 5 minutes
-- **Review**: 14:00 to 19:00: 5 hrs
-- **Staging**: 19:00 to 19:30: 30 minutes
-
-Keep in mind the following observations related to this example:
-
-- This example demonstrates that it doesn't matter if your first
- commit doesn't mention the issue number, you can do this later in any commit
- on the branch you are working on.
-- The **Test** stage is used in the calculation for the overall time of
- the cycle. It is included in the **Review** process, as every MR should be
- tested.
-- This example illustrates only **one cycle** of the seven stages. The value stream analytics dashboard
- shows the median time for multiple cycles.
-
-## How value stream analytics identifies the production environment
-
-Value stream analytics identifies production environments by looking for project
-[environments](../../../ci/yaml/index.md#environment) with a name matching any of these patterns:
-
-- `prod` or `prod/*`
-- `production` or `production/*`
-
-These patterns are not case-sensitive.
-
-You can change the name of a project environment in your GitLab CI/CD configuration.
-
-## Create a value stream with GitLab default stages
+### Create a value stream with GitLab default stages
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221202) in GitLab 13.3
@@ -269,7 +264,7 @@ create custom stages in addition to those provided in the default template.
NOTE:
If you have recently upgraded to GitLab Premium, it can take up to 30 minutes for data to collect and display.
-## Create a value stream with custom stages
+### Create a value stream with custom stages
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50229) in GitLab 13.7.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55572) in GitLab 13.10.
@@ -287,7 +282,7 @@ When you create a value stream, you can create and add custom stages that align
1. To re-order the stages, select the up or down arrows.
1. Select **Create value stream**.
-### Label-based stages for custom value streams
+#### Label-based stages for custom value streams
To measure complex workflows, you can use [scoped labels](../../project/labels.md#scoped-labels). For example, to measure deployment
time from a staging environment to production, you could use the following labels:
@@ -297,6 +292,14 @@ time from a staging environment to production, you could use the following label
![Label-based value stream analytics stage](img/vsa_label_based_stage_v14_0.png "Creating a label-based value stream analytics stage")
+#### Example for custom value stream configuration
+
+![Example configuration](img/object_hierarchy_example_V14_10.png "Example custom value stream configuration")
+
+In the example above, two independent value streams are set up for two teams that are using different development workflows in the **Test Group** (top-level namespace).
+
+The first value stream uses standard timestamp-based events for defining the stages. The second value stream uses label events.
+
## Edit a value stream
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267537) in GitLab 13.10.
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index df0ffff8561..b1f2a9dbb79 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -107,7 +107,7 @@ the components outlined above and the pre-loaded demo runbook.
'''
We set user's id, login and access token on single user image to
enable repository integration for JupyterHub.
- See: https://gitlab.com/gitlab-org/gitlab-foss/issues/47138#note_154294790
+ See: https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47138#note_154294790
'''
auth_state = await spawner.user.get_auth_state()
diff --git a/doc/user/project/issues/crosslinking_issues.md b/doc/user/project/issues/crosslinking_issues.md
index 52da1acd32a..f1f41de7cb4 100644
--- a/doc/user/project/issues/crosslinking_issues.md
+++ b/doc/user/project/issues/crosslinking_issues.md
@@ -39,10 +39,10 @@ git commit -m "this is my commit message. Ref projectname#xxx"
```
If they are not in the same group, you can add the full URL to the issue
-(`https://gitlab.com/<username>/<projectname>/issues/<xxx>`).
+(`https://gitlab.com/<username>/<projectname>/-/issues/<xxx>`).
```shell
-git commit -m "this is my commit message. Related to https://gitlab.com/<username>/<projectname>/issues/<xxx>"
+git commit -m "this is my commit message. Related to https://gitlab.com/<username>/<projectname>/-/issues/<xxx>"
```
Of course, you can replace `gitlab.com` with the URL of your own GitLab instance.
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 5f9a2961df5..e9de780655a 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -65,7 +65,10 @@ Improving this experience is tracked in issue [339009](https://gitlab.com/gitlab
You can view all the milestones you have access to in the entire GitLab namespace.
You might not see some milestones because they're in projects or groups you're not a member of.
-To do so, on the top bar select **Main menu > Milestones**.
+To do so:
+
+1. On the top bar select **Main menu > Your work**.
+1. On the left sidebar, select **Milestones**.
### View milestone details
diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md
index 81494ab7a5a..5910cd5a11c 100644
--- a/doc/user/project/working_with_projects.md
+++ b/doc/user/project/working_with_projects.md
@@ -11,12 +11,9 @@ code are saved in projects, and most features are in the scope of projects.
## View projects
-To view projects, on the top bar, select **Main menu > Projects > View all projects**.
+To view all your projects, on the top bar, select **Main menu > Projects > View all projects**.
-NOTE:
-The **Explore projects** tab is visible to unauthenticated users unless the
-[**Public** visibility level](../admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels)
-is restricted. Then the tab is visible only to authenticated users.
+To browse all public projects, select **Main menu > Explore > Projects**.
### Who can view the Projects page
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
index 0532ed27010..7ca897288e1 100644
--- a/doc/user/snippets.md
+++ b/doc/user/snippets.md
@@ -67,10 +67,10 @@ In GitLab versions 13.0 and later, snippets are [versioned by default](#versione
To discover all snippets visible to you in GitLab, you can:
- **View all snippets visible to you**: On the top bar of your GitLab
- instance, select **Main menu > Snippets** to view your snippets dashboard.
+ instance, select **Main menu > Your work** and then **Snippets** to view your snippets dashboard.
- **Visit [GitLab snippets](https://gitlab.com/dashboard/snippets)** for your snippets on GitLab.com.
- **Explore all public snippets**: On the top bar of your GitLab
- instance, select **Main menu > Snippets** and select **Explore snippets** to view
+ instance, select **Main menu > Explore** and select **Snippets** to view
[all public snippets](https://gitlab.com/explore/snippets).
- **View a project's snippets**: In your project,
go to **Snippets**.
diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb
index 77f5cf372aa..833ce5e32fa 100644
--- a/lib/api/ci/helpers/runner.rb
+++ b/lib/api/ci/helpers/runner.rb
@@ -53,6 +53,8 @@ module API
end
def current_runner_machine
+ return if Feature.disabled?(:create_runner_machine)
+
strong_memoize(:current_runner_machine) do
system_xid = params.fetch(:system_id, LEGACY_SYSTEM_XID)
current_runner&.ensure_machine(system_xid) { |m| m.contacted_at = Time.current }
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 816b8deb461..0a6b288e3f8 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -57,6 +57,7 @@ module API
def log_user_activity(actor)
commands = Gitlab::GitAccess::DOWNLOAD_COMMANDS
+ commands += Gitlab::GitAccess::PUSH_COMMANDS if Feature.enabled?(:log_user_git_push_activity)
return unless commands.include?(params[:action])
diff --git a/lib/gitlab/database/async_constraints/postgres_async_constraint_validation.rb b/lib/gitlab/database/async_constraints/postgres_async_constraint_validation.rb
index acc5dff9995..7fb62948119 100644
--- a/lib/gitlab/database/async_constraints/postgres_async_constraint_validation.rb
+++ b/lib/gitlab/database/async_constraints/postgres_async_constraint_validation.rb
@@ -17,11 +17,17 @@ module Gitlab
enum constraint_type: { foreign_key: 0, check_constraint: 1 }
scope :ordered, -> { order(attempts: :asc, id: :asc) }
- scope :foreign_key_type, -> { columns_hash.key?('constraint_type') ? foreign_key : all }
+ scope :foreign_key_type, -> { constraint_type_exists? ? foreign_key : all }
scope :check_constraint_type, -> { check_constraint }
- def self.table_available?
- connection.table_exists?(table_name)
+ class << self
+ def table_available?
+ connection.table_exists?(table_name)
+ end
+
+ def constraint_type_exists?
+ connection.column_exists?(table_name, :constraint_type)
+ end
end
end
end
diff --git a/lib/gitlab/database/migrations/batched_background_migration_helpers.rb b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
index ded7ce251be..cb2a98b553f 100644
--- a/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
+++ b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
@@ -201,6 +201,12 @@ module Gitlab
def ensure_batched_background_migration_is_finished(job_class_name:, table_name:, column_name:, job_arguments:, finalize: true)
Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_dml_mode!
+ if transaction_open?
+ raise 'The `ensure_batched_background_migration_is_finished` cannot be run inside a transaction. ' \
+ 'You can disable transactions by calling `disable_ddl_transaction!` in the body of ' \
+ 'your migration class.'
+ end
+
Gitlab::Database::BackgroundMigration::BatchedMigration.reset_column_information
migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration(
Gitlab::Database.gitlab_schemas_for_connection(connection),
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index be3a0ff0ba5..35efcb664c0 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -55,6 +55,20 @@ RSpec.describe Groups::GroupMembersController do
expect(assigns(:invited_members).count).to eq(1)
end
+
+ context 'when filtering by user type' do
+ let_it_be(:service_account) { create(:user, :service_account) }
+
+ before do
+ group.add_developer(service_account)
+ end
+
+ it 'returns only service accounts' do
+ get :index, params: { group_id: group, user_type: 'service_account' }
+
+ expect(assigns(:members).map(&:user_id)).to match_array([service_account.id])
+ end
+ end
end
context 'when user cannot manage members' do
@@ -67,6 +81,21 @@ RSpec.describe Groups::GroupMembersController do
expect(assigns(:invited_members)).to be_nil
end
+
+ context 'when filtering by user type' do
+ let_it_be(:service_account) { create(:user, :service_account) }
+
+ before do
+ group.add_developer(user)
+ group.add_developer(service_account)
+ end
+
+ it 'returns only service accounts' do
+ get :index, params: { group_id: group, user_type: 'service_account' }
+
+ expect(assigns(:members).map(&:user_id)).to match_array([user.id, service_account.id])
+ end
+ end
end
context 'when user has owner access to subgroup' do
diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb
index 93857435c9d..276bd9b65b9 100644
--- a/spec/controllers/repositories/git_http_controller_spec.rb
+++ b/spec/controllers/repositories/git_http_controller_spec.rb
@@ -26,6 +26,58 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
end
end
+ shared_examples 'handles user activity' do
+ it 'updates the user activity' do
+ activity_project = container.is_a?(PersonalSnippet) ? nil : project
+
+ activity_service = instance_double(Users::ActivityService)
+
+ args = { author: user, project: activity_project, namespace: activity_project&.namespace }
+ expect(Users::ActivityService).to receive(:new).with(args).and_return(activity_service)
+
+ expect(activity_service).to receive(:execute)
+
+ get :info_refs, params: params
+ end
+ end
+
+ shared_examples 'handles logging git upload pack operation' do
+ before do
+ password = user.try(:password) || user.try(:token)
+ request.headers.merge! auth_env(user.username, password, nil)
+ end
+
+ context 'with git pull/fetch/clone action' do
+ let(:params) { super().merge(service: 'git-upload-pack') }
+
+ it_behaves_like 'handles user activity'
+ end
+ end
+
+ shared_examples 'handles logging git receive pack operation' do
+ let(:params) { super().merge(service: 'git-receive-pack') }
+
+ before do
+ request.headers.merge! auth_env(user.username, user.password, nil)
+ end
+
+ context 'with git push action when log_user_git_push_activity is enabled' do
+ it_behaves_like 'handles user activity'
+ end
+
+ context 'when log_user_git_push_activity is disabled' do
+ before do
+ stub_feature_flags(log_user_git_push_activity: false)
+ end
+
+ it 'does not log user activity' do
+ expect(controller).not_to receive(:log_user_activity)
+
+ get :info_refs, params: params
+ end
+ end
+ end
+
context 'when repository container is a project' do
it_behaves_like Repositories::GitHttpController do
let(:container) { project }
@@ -33,6 +85,8 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
let(:access_checker_class) { Gitlab::GitAccess }
it_behaves_like 'handles unavailable Gitaly'
+ it_behaves_like 'handles logging git upload pack operation'
+ it_behaves_like 'handles logging git receive pack operation'
describe 'POST #git_upload_pack' do
before do
@@ -83,6 +137,8 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
let(:container) { project }
let(:user) { create(:deploy_token, :project, projects: [project]) }
let(:access_checker_class) { Gitlab::GitAccess }
+
+ it_behaves_like 'handles logging git upload pack operation'
end
end
end
@@ -92,6 +148,9 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
let(:container) { create(:project_wiki, :empty_repo, project: project) }
let(:user) { project.first_owner }
let(:access_checker_class) { Gitlab::GitAccessWiki }
+
+ it_behaves_like 'handles logging git upload pack operation'
+ it_behaves_like 'handles logging git receive pack operation'
end
end
@@ -102,6 +161,8 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
let(:access_checker_class) { Gitlab::GitAccessSnippet }
it_behaves_like 'handles unavailable Gitaly'
+ it_behaves_like 'handles logging git upload pack operation'
+ it_behaves_like 'handles logging git receive pack operation'
end
end
@@ -112,6 +173,8 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
let(:access_checker_class) { Gitlab::GitAccessSnippet }
it_behaves_like 'handles unavailable Gitaly'
+ it_behaves_like 'handles logging git upload pack operation'
+ it_behaves_like 'handles logging git receive pack operation'
end
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index c44eef0ab37..dc75e17499c 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -415,7 +415,7 @@ FactoryBot.define do
runner factory: :ci_runner
after(:create) do |build|
- build.create_runtime_metadata!
+ ::Ci::RunningBuild.upsert_shared_runner_build!(build)
end
end
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index 4a5eb389906..5d748f71816 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -225,4 +225,56 @@ RSpec.describe GroupMembersFinder, '#execute', feature_category: :subgroups do
end
end
end
+
+ context 'filter by user type' do
+ subject(:by_user_type) { described_class.new(group, user1, params: { user_type: user_type }).execute }
+
+ let_it_be(:service_account) { create(:user, :service_account) }
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+
+ let_it_be(:service_account_member) { group.add_developer(service_account) }
+ let_it_be(:project_bot_member) { group.add_developer(project_bot) }
+
+ context 'when the user is an owner' do
+ before do
+ group.add_owner(user1)
+ end
+
+ context 'when filtering by project bots' do
+ let(:user_type) { 'project_bot' }
+
+ it 'returns filtered members' do
+ expect(by_user_type).to match_array([project_bot_member])
+ end
+ end
+
+ context 'when filtering by service accounts' do
+ let(:user_type) { 'service_account' }
+
+ it 'returns filtered members' do
+ expect(by_user_type).to match_array([service_account_member])
+ end
+ end
+ end
+
+ context 'when the user is a maintainer' do
+ let(:user_type) { 'service_account' }
+
+ let_it_be(:user1_member) { group.add_maintainer(user1) }
+
+ it 'returns unfiltered members' do
+ expect(by_user_type).to match_array([user1_member, service_account_member, project_bot_member])
+ end
+ end
+
+ context 'when the user is a developer' do
+ let(:user_type) { 'service_account' }
+
+ let_it_be(:user1_member) { group.add_developer(user1) }
+
+ it 'returns unfiltered members' do
+ expect(by_user_type).to match_array([user1_member, service_account_member, project_bot_member])
+ end
+ end
+ end
end
diff --git a/spec/frontend/analytics/cycle_analytics/base_spec.js b/spec/frontend/analytics/cycle_analytics/base_spec.js
index 6a2655957b7..033916eabcd 100644
--- a/spec/frontend/analytics/cycle_analytics/base_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/base_spec.js
@@ -179,7 +179,7 @@ describe('Value stream analytics component', () => {
it('renders a link to the value streams dashboard', () => {
expect(findOverviewMetrics().props('dashboardsPath')).toBeDefined();
expect(findOverviewMetrics().props('dashboardsPath')).toBe(
- '/groups/foo/-/analytics/dashboards?query=full/path/to/foo',
+ '/groups/foo/-/analytics/dashboards/value_streams_dashboard?query=full/path/to/foo',
);
});
});
diff --git a/spec/frontend/analytics/shared/utils_spec.js b/spec/frontend/analytics/shared/utils_spec.js
index e020ff999fc..24af7b836d5 100644
--- a/spec/frontend/analytics/shared/utils_spec.js
+++ b/spec/frontend/analytics/shared/utils_spec.js
@@ -218,9 +218,9 @@ describe('generateValueStreamsDashboardLink', () => {
it.each`
groupPath | projectPaths | result
${''} | ${[]} | ${''}
- ${'groups/fake-group'} | ${[]} | ${'/groups/fake-group/-/analytics/dashboards'}
- ${'groups/fake-group'} | ${['fake-path/project_1']} | ${'/groups/fake-group/-/analytics/dashboards?query=fake-path/project_1'}
- ${'groups/fake-group'} | ${['fake-path/project_1', 'fake-path/project_2']} | ${'/groups/fake-group/-/analytics/dashboards?query=fake-path/project_1,fake-path/project_2'}
+ ${'groups/fake-group'} | ${[]} | ${'/groups/fake-group/-/analytics/dashboards/value_streams_dashboard'}
+ ${'groups/fake-group'} | ${['fake-path/project_1']} | ${'/groups/fake-group/-/analytics/dashboards/value_streams_dashboard?query=fake-path/project_1'}
+ ${'groups/fake-group'} | ${['fake-path/project_1', 'fake-path/project_2']} | ${'/groups/fake-group/-/analytics/dashboards/value_streams_dashboard?query=fake-path/project_1,fake-path/project_2'}
`(
'generates the dashboard link when groupPath=$groupPath and projectPaths=$projectPaths',
({ groupPath, projectPaths, result }) => {
@@ -235,7 +235,7 @@ describe('generateValueStreamsDashboardLink', () => {
it('with includes a relative path if one is set', () => {
expect(generateValueStreamsDashboardLink('groups/fake-path', ['project_1'])).toBe(
- '/foobar/groups/fake-path/-/analytics/dashboards?query=project_1',
+ '/foobar/groups/fake-path/-/analytics/dashboards/value_streams_dashboard?query=project_1',
);
});
});
diff --git a/spec/frontend/super_sidebar/components/context_switcher_spec.js b/spec/frontend/super_sidebar/components/context_switcher_spec.js
index dc240b85e50..054076e0d21 100644
--- a/spec/frontend/super_sidebar/components/context_switcher_spec.js
+++ b/spec/frontend/super_sidebar/components/context_switcher_spec.js
@@ -2,6 +2,13 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ContextSwitcher from '~/super_sidebar/components/context_switcher.vue';
import FrequentProjectsList from '~/super_sidebar/components/frequent_projects_list.vue';
import FrequentGroupsList from '~/super_sidebar/components/frequent_groups_list.vue';
+import { trackContextAccess } from '~/super_sidebar/utils';
+
+jest.mock('~/super_sidebar/utils', () => ({
+ getStorageKeyFor: jest.requireActual('~/super_sidebar/utils').getStorageKeyFor,
+ getTopFrequentItems: jest.requireActual('~/super_sidebar/utils').getTopFrequentItems,
+ trackContextAccess: jest.fn(),
+}));
const username = 'root';
const projectsPath = 'projectsPath';
@@ -13,12 +20,13 @@ describe('ContextSwitcher component', () => {
const findFrequentProjectsList = () => wrapper.findComponent(FrequentProjectsList);
const findFrequentGroupsList = () => wrapper.findComponent(FrequentGroupsList);
- const createWrapper = () => {
+ const createWrapper = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(ContextSwitcher, {
propsData: {
username,
projectsPath,
groupsPath,
+ ...props,
},
});
};
@@ -40,4 +48,23 @@ describe('ContextSwitcher component', () => {
viewAllLink: groupsPath,
});
});
+
+ describe('item access tracking', () => {
+ it('does not track anything if not within a trackable context', () => {
+ createWrapper();
+
+ expect(trackContextAccess).not.toHaveBeenCalled();
+ });
+
+ it('tracks item access if within a trackable context', () => {
+ const currentContext = { namespace: 'groups' };
+ createWrapper({
+ props: {
+ currentContext,
+ },
+ });
+
+ expect(trackContextAccess).toHaveBeenCalledWith(username, currentContext);
+ });
+ });
});
diff --git a/spec/frontend/super_sidebar/utils_spec.js b/spec/frontend/super_sidebar/utils_spec.js
index b0165e9718d..d1c5bf8f731 100644
--- a/spec/frontend/super_sidebar/utils_spec.js
+++ b/spec/frontend/super_sidebar/utils_spec.js
@@ -1,4 +1,7 @@
-import { getTopFrequentItems } from '~/super_sidebar/utils';
+import { getTopFrequentItems, trackContextAccess } from '~/super_sidebar/utils';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import AccessorUtilities from '~/lib/utils/accessor';
+import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/frequent_items/constants';
import { unsortedFrequentItems, sortedFrequentItems } from '../frequent_items/mock_data';
describe('Super sidebar utils spec', () => {
@@ -24,4 +27,114 @@ describe('Super sidebar utils spec', () => {
expect(result).toEqual(expectedResult);
});
});
+
+ describe('trackContextAccess', () => {
+ useLocalStorageSpy();
+
+ const username = 'root';
+ const context = {
+ namespace: 'groups',
+ item: { id: 1 },
+ };
+ const storageKey = `${username}/frequent-${context.namespace}`;
+
+ it('returns `false` if local storage is not available', () => {
+ jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(false);
+
+ expect(trackContextAccess()).toBe(false);
+ });
+
+ it('creates a new item if it does not exist in the local storage', () => {
+ trackContextAccess(username, context);
+
+ expect(window.localStorage.setItem).toHaveBeenCalledWith(
+ storageKey,
+ JSON.stringify([
+ {
+ id: 1,
+ frequency: 1,
+ lastAccessedOn: Date.now(),
+ },
+ ]),
+ );
+ });
+
+ it('updates existing item if it was persisted to the local storage over 15 minutes ago', () => {
+ window.localStorage.setItem(
+ storageKey,
+ JSON.stringify([
+ {
+ id: 1,
+ frequency: 2,
+ lastAccessedOn: Date.now() - FIFTEEN_MINUTES_IN_MS - 1,
+ },
+ ]),
+ );
+ trackContextAccess(username, context);
+
+ expect(window.localStorage.setItem).toHaveBeenCalledWith(
+ storageKey,
+ JSON.stringify([
+ {
+ id: 1,
+ frequency: 3,
+ lastAccessedOn: Date.now(),
+ },
+ ]),
+ );
+ });
+
+ it('leaves item as is if it was persisted to the local storage under 15 minutes ago', () => {
+ const jsonString = JSON.stringify([
+ {
+ id: 1,
+ frequency: 2,
+ lastAccessedOn: Date.now() - FIFTEEN_MINUTES_IN_MS,
+ },
+ ]);
+ window.localStorage.setItem(storageKey, jsonString);
+
+ expect(window.localStorage.setItem).toHaveBeenCalledTimes(1);
+ expect(window.localStorage.setItem).toHaveBeenCalledWith(storageKey, jsonString);
+
+ trackContextAccess(username, context);
+
+ expect(window.localStorage.setItem).toHaveBeenCalledTimes(3);
+ expect(window.localStorage.setItem).toHaveBeenLastCalledWith(storageKey, jsonString);
+ });
+
+ it('replaces the least popular item in the local storage once the persisted items limit has been hit', () => {
+ // Add the maximum amount of items to the local storage, in increasing popularity
+ const storedItems = Array.from({ length: FREQUENT_ITEMS.MAX_COUNT }).map((_, i) => ({
+ id: i + 1,
+ frequency: i + 1,
+ lastAccessedOn: Date.now(),
+ }));
+ // The first item is considered the least popular one as it has the lowest frequency (1)
+ const [leastPopularItem] = storedItems;
+ // Persist the list to the local storage
+ const jsonString = JSON.stringify(storedItems);
+ window.localStorage.setItem(storageKey, jsonString);
+ // Track some new item that hasn't been visited yet
+ const newItem = {
+ id: FREQUENT_ITEMS.MAX_COUNT + 1,
+ };
+ trackContextAccess(username, {
+ namespace: 'groups',
+ item: newItem,
+ });
+ // Finally, retrieve the final data from the local storage
+ const finallyStoredItems = JSON.parse(window.localStorage.getItem(storageKey));
+
+ expect(finallyStoredItems).not.toEqual(expect.arrayContaining([leastPopularItem]));
+ expect(finallyStoredItems).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ id: newItem.id,
+ frequency: 1,
+ }),
+ ]),
+ );
+ });
+ });
});
diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb
index b602fe3548e..dbb6f9bd9f3 100644
--- a/spec/helpers/sidebars_helper_spec.rb
+++ b/spec/helpers/sidebars_helper_spec.rb
@@ -186,6 +186,66 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
)
)
end
+
+ describe 'current context' do
+ context 'when current context is a project' do
+ let_it_be(:project) { build(:project) }
+
+ subject do
+ helper.super_sidebar_context(user, group: nil, project: project, panel: panel)
+ end
+
+ before do
+ allow(project).to receive(:persisted?).and_return(true)
+ end
+
+ it 'returns project context' do
+ expect(subject[:current_context]).to eq({
+ namespace: 'projects',
+ item: {
+ id: project.id,
+ avatarUrl: project.avatar_url,
+ name: project.name,
+ namespace: project.full_name,
+ webUrl: project_path(project)
+ }
+ })
+ end
+ end
+
+ context 'when current context is a group' do
+ subject do
+ helper.super_sidebar_context(user, group: group, project: nil, panel: panel)
+ end
+
+ before do
+ allow(group).to receive(:persisted?).and_return(true)
+ end
+
+ it 'returns group context' do
+ expect(subject[:current_context]).to eq({
+ namespace: 'groups',
+ item: {
+ id: group.id,
+ avatarUrl: group.avatar_url,
+ name: group.name,
+ namespace: group.full_name,
+ webUrl: group_path(group)
+ }
+ })
+ end
+ end
+
+ context 'when current context is not tracked' do
+ subject do
+ helper.super_sidebar_context(user, group: nil, project: nil, panel: panel)
+ end
+
+ it 'returns no context' do
+ expect(subject[:current_context]).to eq({})
+ end
+ end
+ end
end
describe '#super_sidebar_nav_panel' do
diff --git a/spec/lib/api/ci/helpers/runner_spec.rb b/spec/lib/api/ci/helpers/runner_spec.rb
index 1f9433c27fc..8264db8344d 100644
--- a/spec/lib/api/ci/helpers/runner_spec.rb
+++ b/spec/lib/api/ci/helpers/runner_spec.rb
@@ -73,38 +73,68 @@ RSpec.describe API::Ci::Helpers::Runner do
subject(:current_runner_machine) { helper.current_runner_machine }
- context 'when runner machine already exists' do
+ context 'with create_runner_machine FF enabled' do
before do
- allow(helper).to receive(:params).and_return(token: runner.token, system_id: runner_machine.system_xid)
+ stub_feature_flags(create_runner_machine: true)
end
- it { is_expected.to eq(runner_machine) }
+ context 'when runner machine already exists' do
+ before do
+ allow(helper).to receive(:params).and_return(token: runner.token, system_id: runner_machine.system_xid)
+ end
- it 'does not update the contacted_at field' do
- expect(current_runner_machine.contacted_at).to eq 1.hour.ago
+ it { is_expected.to eq(runner_machine) }
+
+ it 'does not update the contacted_at field' do
+ expect(current_runner_machine.contacted_at).to eq 1.hour.ago
+ end
end
- end
- context 'when runner machine cannot be found' do
- it 'creates a new runner machine', :aggregate_failures do
- allow(helper).to receive(:params).and_return(token: runner.token, system_id: 'new_system_id')
+ context 'when runner machine cannot be found' do
+ it 'creates a new runner machine', :aggregate_failures do
+ allow(helper).to receive(:params).and_return(token: runner.token, system_id: 'new_system_id')
+
+ expect { current_runner_machine }.to change { Ci::RunnerMachine.count }.by(1)
+
+ expect(current_runner_machine).not_to be_nil
+ expect(current_runner_machine.system_xid).to eq('new_system_id')
+ expect(current_runner_machine.contacted_at).to eq(Time.current)
+ expect(current_runner_machine.runner).to eq(runner)
+ end
+
+ it 'creates a new <legacy> runner machine if system_id is not specified', :aggregate_failures do
+ allow(helper).to receive(:params).and_return(token: runner.token)
+
+ expect { current_runner_machine }.to change { Ci::RunnerMachine.count }.by(1)
- expect { current_runner_machine }.to change { Ci::RunnerMachine.count }.by(1)
+ expect(current_runner_machine).not_to be_nil
+ expect(current_runner_machine.system_xid).to eq(::API::Ci::Helpers::Runner::LEGACY_SYSTEM_XID)
+ expect(current_runner_machine.runner).to eq(runner)
+ end
+ end
+ end
- expect(current_runner_machine).not_to be_nil
- expect(current_runner_machine.system_xid).to eq('new_system_id')
- expect(current_runner_machine.contacted_at).to eq(Time.current)
- expect(current_runner_machine.runner).to eq(runner)
+ context 'with create_runner_machine FF disabled' do
+ before do
+ stub_feature_flags(create_runner_machine: false)
end
- it 'creates a new <legacy> runner machine if system_id is not specified', :aggregate_failures do
+ it 'does not return runner machine if no system_id specified' do
allow(helper).to receive(:params).and_return(token: runner.token)
- expect { current_runner_machine }.to change { Ci::RunnerMachine.count }.by(1)
+ is_expected.to be_nil
+ end
+
+ context 'when runner machine can not be found' do
+ before do
+ allow(helper).to receive(:params).and_return(token: runner.token, system_id: 'new_system_id')
+ end
+
+ it 'does not create a new runner machine', :aggregate_failures do
+ expect { current_runner_machine }.not_to change { Ci::RunnerMachine.count }
- expect(current_runner_machine).not_to be_nil
- expect(current_runner_machine.system_xid).to eq(::API::Ci::Helpers::Runner::LEGACY_SYSTEM_XID)
- expect(current_runner_machine.runner).to eq(runner)
+ expect(current_runner_machine).to be_nil
+ end
end
end
end
diff --git a/spec/lib/api/helpers/internal_helpers_spec.rb b/spec/lib/api/helpers/internal_helpers_spec.rb
new file mode 100644
index 00000000000..847b711f829
--- /dev/null
+++ b/spec/lib/api/helpers/internal_helpers_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe API::Helpers::InternalHelpers, feature_category: :api do
+ describe "log user git operation activity" do
+ let_it_be(:project) { create(:project) }
+ let(:user) { project.first_owner }
+ let(:internal_helper) do
+ Class.new { include API::Helpers::InternalHelpers }.new
+ end
+
+ before do
+ allow(internal_helper).to receive(:project).and_return(project)
+ end
+
+ shared_examples "handles log git operation activity" do
+ it "log the user activity" do
+ activity_service = instance_double(::Users::ActivityService)
+
+ args = { author: user, project: project, namespace: project&.namespace }
+
+ expect(Users::ActivityService).to receive(:new).with(args).and_return(activity_service)
+ expect(activity_service).to receive(:execute)
+
+ internal_helper.log_user_activity(user)
+ end
+ end
+
+ context "when git pull/fetch/clone action" do
+ before do
+ allow(internal_helper).to receive(:params).and_return(action: "git-upload-pack")
+ end
+
+ context "with log the user activity" do
+ it_behaves_like "handles log git operation activity"
+ end
+ end
+
+ context "when git push action" do
+ before do
+ allow(internal_helper).to receive(:params).and_return(action: "git-receive-pack")
+ end
+
+ it "does not log the user activity when log_user_git_push_activity is disabled" do
+ stub_feature_flags(log_user_git_push_activity: false)
+
+ expect(::Users::ActivityService).not_to receive(:new)
+
+ internal_helper.log_user_activity(user)
+ end
+
+ context "with log the user activity when log_user_git_push_activity is enabled" do
+ stub_feature_flags(log_user_git_push_activity: true)
+
+ it_behaves_like "handles log git operation activity"
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb b/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb
index da9cc18f7d7..52fbf6d2f9b 100644
--- a/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb
+++ b/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb
@@ -41,9 +41,9 @@ RSpec.describe Gitlab::Database::AsyncConstraints::PostgresAsyncConstraintValida
it { is_expected.to eq([failed_validation]) }
it 'does not apply the filter if the column is not present' do
- expect(described_class).to receive(:columns_hash).and_wrap_original do |method|
- method.call.except('constraint_type')
- end
+ expect(described_class)
+ .to receive(:constraint_type_exists?)
+ .and_return(false)
is_expected.to match_array([failed_validation, new_validation])
end
@@ -76,6 +76,19 @@ RSpec.describe Gitlab::Database::AsyncConstraints::PostgresAsyncConstraintValida
end
end
+ describe '.constraint_type_exists?' do
+ it { expect(described_class.constraint_type_exists?).to be_truthy }
+
+ it 'always asks the database' do
+ control = ActiveRecord::QueryRecorder.new(skip_schema_queries: false) do
+ described_class.constraint_type_exists?
+ end
+
+ expect(control.count).to be >= 1
+ expect { described_class.constraint_type_exists? }.to issue_same_number_of_queries_as(control)
+ end
+ end
+
describe '#handle_exception!' do
let_it_be_with_reload(:constraint_validation) { create(:postgres_async_constraint_validation) }
diff --git a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
index 5003efb9308..f5ce207773f 100644
--- a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
@@ -513,6 +513,17 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
end
end
+ context 'when within transaction' do
+ before do
+ allow(migration).to receive(:transaction_open?).and_return(true)
+ end
+
+ it 'does raise an exception' do
+ expect { ensure_batched_background_migration_is_finished }
+ .to raise_error /`ensure_batched_background_migration_is_finished` cannot be run inside a transaction./
+ end
+ end
+
it 'finalizes the migration' do
expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!).twice
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 150c634d01c..d23539a8a6c 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1681,6 +1681,154 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
end
end
+ describe 'merge status subscription trigger' do
+ shared_examples 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated' do
+ context 'when state transitions to running' do
+ it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.run }
+ end
+ end
+
+ context 'when state transitions to success' do
+ it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.succeed }
+ end
+ end
+
+ context 'when state transitions to failed' do
+ it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.drop }
+ end
+ end
+
+ context 'when state transitions to canceled' do
+ it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.cancel }
+ end
+ end
+
+ context 'when state transitions to skipped' do
+ it_behaves_like 'does not trigger GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.skip }
+ end
+ end
+ end
+
+ shared_examples 'state transition triggering GraphQL subscription mergeRequestMergeStatusUpdated' do
+ context 'when state transitions to running' do
+ it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.run }
+ end
+ end
+
+ context 'when state transitions to success' do
+ it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.succeed }
+ end
+ end
+
+ context 'when state transitions to failed' do
+ it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.drop }
+ end
+ end
+
+ context 'when state transitions to canceled' do
+ it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.cancel }
+ end
+ end
+
+ context 'when state transitions to skipped' do
+ it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do
+ let(:action) { pipeline.skip }
+ end
+ end
+
+ context 'when only_allow_merge_if_pipeline_succeeds? returns false' do
+ let(:only_allow_merge_if_pipeline_succeeds?) { false }
+
+ it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated'
+ end
+
+ context 'when pipeline_trigger_merge_status feature flag is disabled' do
+ before do
+ stub_feature_flags(pipeline_trigger_merge_status: false)
+ end
+
+ it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated'
+ end
+ end
+
+ context 'when pipeline has merge requests' do
+ let(:merge_request) do
+ create(
+ :merge_request,
+ :simple,
+ source_project: project,
+ target_project: project
+ )
+ end
+
+ let(:only_allow_merge_if_pipeline_succeeds?) { true }
+
+ before do
+ allow(project)
+ .to receive(:only_allow_merge_if_pipeline_succeeds?)
+ .and_return(only_allow_merge_if_pipeline_succeeds?)
+ end
+
+ context 'when for a specific merge request' do
+ let(:pipeline) do
+ create(
+ :ci_pipeline,
+ project: project,
+ merge_request: merge_request
+ )
+ end
+
+ it_behaves_like 'state transition triggering GraphQL subscription mergeRequestMergeStatusUpdated'
+
+ context 'when pipeline is a child' do
+ let(:parent_pipeline) do
+ create(
+ :ci_pipeline,
+ project: project,
+ merge_request: merge_request
+ )
+ end
+
+ let(:pipeline) do
+ create(
+ :ci_pipeline,
+ child_of: parent_pipeline,
+ merge_request: merge_request
+ )
+ end
+
+ it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated'
+ end
+ end
+
+ context 'when for merge requests matching the source branch and SHA' do
+ let(:pipeline) do
+ create(
+ :ci_pipeline,
+ project: project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha
+ )
+ end
+
+ it_behaves_like 'state transition triggering GraphQL subscription mergeRequestMergeStatusUpdated'
+ end
+ end
+
+ context 'when pipeline has no merge requests' do
+ it_behaves_like 'state transition not triggering GraphQL subscription mergeRequestMergeStatusUpdated'
+ end
+ end
+
def create_build(name, *traits, queued_at: current, started_from: 0, **opts)
create(:ci_build, *traits,
name: name,
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 781da6d07df..30607116c61 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -683,6 +683,24 @@ RSpec.describe Member, feature_category: :subgroups do
end
end
+ describe '.filter_by_user_type' do
+ let_it_be(:service_account) { create(:user, :service_account) }
+ let_it_be(:service_account_member) { create(:group_member, user: service_account) }
+ let_it_be(:other_member) { create(:group_member) }
+
+ context 'when the user type is valid' do
+ it 'returns service accounts' do
+ expect(described_class.filter_by_user_type('service_account')).to match_array([service_account_member])
+ end
+ end
+
+ context 'when the user type is invalid' do
+ it 'returns nil' do
+ expect(described_class.filter_by_user_type('invalid_type')).to eq(nil)
+ end
+ end
+ end
+
describe '#accept_request' do
let(:member) { create(:project_member, requested_at: Time.current.utc) }
diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
index ba30b2f2690..28dbc4fd168 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -122,33 +122,56 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
context 'when system_id parameter is specified' do
subject(:request) { request_job(**args) }
- context 'when ci_runner_machines with same system_xid does not exist' do
- let(:args) { { system_id: 's_some_system_id' } }
+ context 'with create_runner_machine FF enabled' do
+ before do
+ stub_feature_flags(create_runner_machine: true)
+ end
- it 'creates respective ci_runner_machines record', :freeze_time do
- expect { request }.to change { runner.runner_machines.reload.count }.from(0).to(1)
+ context 'when ci_runner_machines with same system_xid does not exist' do
+ let(:args) { { system_id: 's_some_system_id' } }
- machine = runner.runner_machines.last
- expect(machine.system_xid).to eq args[:system_id]
- expect(machine.runner).to eq runner
- expect(machine.contacted_at).to eq Time.current
+ it 'creates respective ci_runner_machines record', :freeze_time do
+ expect { request }.to change { runner.runner_machines.reload.count }.from(0).to(1)
+
+ machine = runner.runner_machines.last
+ expect(machine.system_xid).to eq args[:system_id]
+ expect(machine.runner).to eq runner
+ expect(machine.contacted_at).to eq Time.current
+ end
end
- end
- context 'when ci_runner_machines with same system_xid already exists', :freeze_time do
- let(:args) { { system_id: 's_existing_system_id' } }
- let!(:runner_machine) do
- create(:ci_runner_machine, runner: runner, system_xid: args[:system_id], contacted_at: 1.hour.ago)
+ context 'when ci_runner_machines with same system_xid already exists', :freeze_time do
+ let(:args) { { system_id: 's_existing_system_id' } }
+ let!(:runner_machine) do
+ create(:ci_runner_machine, runner: runner, system_xid: args[:system_id], contacted_at: 1.hour.ago)
+ end
+
+ it 'does not create new ci_runner_machines record' do
+ expect { request }.not_to change { Ci::RunnerMachine.count }
+ end
+
+ it 'updates the contacted_at field' do
+ request
+
+ expect(runner_machine.reload.contacted_at).to eq Time.current
+ end
end
+ end
- it 'does not create new ci_runner_machines record' do
- expect { request }.not_to change { Ci::RunnerMachine.count }
+ context 'with create_runner_machine FF disabled' do
+ before do
+ stub_feature_flags(create_runner_machine: false)
end
- it 'updates the contacted_at field' do
- request
+ context 'when ci_runner_machines with same system_xid does not exist' do
+ let(:args) { { system_id: 's_some_system_id' } }
+
+ it 'does not create respective ci_runner_machines record', :freeze_time, :aggregate_failures do
+ expect { request }.not_to change { runner.runner_machines.reload.count }
- expect(runner_machine.reload.contacted_at).to eq Time.current
+ expect(response).to have_gitlab_http_status(:created)
+ expect(runner.runner_machines).to be_empty
+ end
end
end
end
diff --git a/spec/requests/api/ci/runner/runners_verify_post_spec.rb b/spec/requests/api/ci/runner/runners_verify_post_spec.rb
index f188b095ace..1b7dfe7706c 100644
--- a/spec/requests/api/ci/runner/runners_verify_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_verify_post_spec.rb
@@ -45,12 +45,33 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
context 'when valid token is provided' do
let(:params) { { token: runner.token } }
- context 'with glrt-prefixed token' do
- let_it_be(:registration_token) { 'glrt-abcdefg123456' }
- let_it_be(:registration_type) { :authenticated_user }
- let_it_be(:runner) do
- create(:ci_runner, registration_type: registration_type,
- token: registration_token, token_expires_at: 3.days.from_now)
+ context 'with create_runner_machine FF enabled' do
+ before do
+ stub_feature_flags(create_runner_machine: true)
+ end
+
+ context 'with glrt-prefixed token' do
+ let_it_be(:registration_token) { 'glrt-abcdefg123456' }
+ let_it_be(:registration_type) { :authenticated_user }
+ let_it_be(:runner) do
+ create(:ci_runner, registration_type: registration_type,
+ token: registration_token, token_expires_at: 3.days.from_now)
+ end
+
+ it 'verifies Runner credentials' do
+ verify
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq({
+ 'id' => runner.id,
+ 'token' => runner.token,
+ 'token_expires_at' => runner.token_expires_at.iso8601(3)
+ })
+ end
+
+ it 'does not update contacted_at' do
+ expect { verify }.not_to change { runner.reload.contacted_at }.from(nil)
+ end
end
it 'verifies Runner credentials' do
@@ -64,29 +85,43 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
})
end
- it 'does not update contacted_at' do
- expect { verify }.not_to change { runner.reload.contacted_at }.from(nil)
+ it 'updates contacted_at' do
+ expect { verify }.to change { runner.reload.contacted_at }.from(nil).to(Time.current)
end
- end
- it 'verifies Runner credentials' do
- verify
+ context 'with non-expiring runner token' do
+ before do
+ runner.update!(token_expires_at: nil)
+ end
+
+ it 'verifies Runner credentials' do
+ verify
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq({
+ 'id' => runner.id,
+ 'token' => runner.token,
+ 'token_expires_at' => nil
+ })
+ end
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq({
- 'id' => runner.id,
- 'token' => runner.token,
- 'token_expires_at' => runner.token_expires_at.iso8601(3)
- })
- end
+ it_behaves_like 'storing arguments in the application context for the API' do
+ let(:expected_params) { { client_id: "runner/#{runner.id}" } }
+ end
- it 'updates contacted_at' do
- expect { verify }.to change { runner.reload.contacted_at }.from(nil).to(Time.current)
+ context 'when system_id is provided' do
+ let(:params) { { token: runner.token, system_id: 's_some_system_id' } }
+
+ it 'creates a runner_machine' do
+ expect { verify }.to change { Ci::RunnerMachine.count }.by(1)
+ end
+ end
end
- context 'with non-expiring runner token' do
+ context 'with create_runner_machine FF disabled' do
before do
- runner.update!(token_expires_at: nil)
+ stub_feature_flags(create_runner_machine: false)
end
it 'verifies Runner credentials' do
@@ -96,20 +131,18 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
expect(json_response).to eq({
'id' => runner.id,
'token' => runner.token,
- 'token_expires_at' => nil
+ 'token_expires_at' => runner.token_expires_at.iso8601(3)
})
end
- end
- it_behaves_like 'storing arguments in the application context for the API' do
- let(:expected_params) { { client_id: "runner/#{runner.id}" } }
- end
+ context 'when system_id is provided' do
+ let(:params) { { token: runner.token, system_id: 's_some_system_id' } }
- context 'when system_id is provided' do
- let(:params) { { token: runner.token, system_id: 's_some_system_id' } }
+ it 'does not create a runner_machine', :aggregate_failures do
+ expect { verify }.not_to change { Ci::RunnerMachine.count }
- it 'creates a runner_machine' do
- expect { verify }.to change { Ci::RunnerMachine.count }.by(1)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
end
end
end
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index c1f256a5d28..dff41c4c477 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -514,7 +514,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(json_response["gl_repository"]).to eq("wiki-#{project.id}")
expect(json_response["gl_key_type"]).to eq("key")
expect(json_response["gl_key_id"]).to eq(key.id)
- expect(user.reload.last_activity_on).to be_nil
+ expect(user.reload.last_activity_on).to eql(Date.today)
end
it_behaves_like 'sets hook env' do
@@ -553,7 +553,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(json_response["status"]).to be_truthy
expect(json_response["gl_project_path"]).to eq(personal_snippet.repository.full_path)
expect(json_response["gl_repository"]).to eq("snippet-#{personal_snippet.id}")
- expect(user.reload.last_activity_on).to be_nil
+ expect(user.reload.last_activity_on).to eql(Date.today)
end
it_behaves_like 'sets hook env' do
@@ -585,7 +585,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(json_response["status"]).to be_truthy
expect(json_response["gl_project_path"]).to eq(project_snippet.repository.full_path)
expect(json_response["gl_repository"]).to eq("snippet-#{project_snippet.id}")
- expect(user.reload.last_activity_on).to be_nil
+ expect(user.reload.last_activity_on).to eql(Date.today)
end
it_behaves_like 'sets hook env' do
@@ -703,7 +703,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
- expect(user.reload.last_activity_on).to be_nil
+ expect(user.reload.last_activity_on).to eql(Date.today)
end
it_behaves_like 'rate limited request' do
@@ -862,7 +862,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(json_response['status']).to be_truthy
expect(json_response['payload']).to eql(payload)
expect(json_response['gl_console_messages']).to eql(console_messages)
- expect(user.reload.last_activity_on).to be_nil
+ expect(user.reload.last_activity_on).to eql(Date.today)
end
end
end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index eaac2a77353..d3d1a2a6cd0 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -230,6 +230,12 @@ RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do
context 'when authenticated' do
it 'creates a new project under the existing namespace' do
+ # current scenario does not matter with the user activity case,
+ # so stub/double it to escape more sql running times limit
+ activity_service = instance_double(::Users::ActivityService)
+ allow(::Users::ActivityService).to receive(:new).and_return(activity_service)
+ allow(activity_service).to receive(:execute)
+
expect do
upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
index 39c300d547d..e75188f8249 100644
--- a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
@@ -60,19 +60,6 @@ RSpec.shared_examples Repositories::GitHttpController do
expect(response).to have_gitlab_http_status(:ok)
end
- it 'updates the user activity' do
- activity_project = container.is_a?(PersonalSnippet) ? nil : project
-
- activity_service = instance_double(Users::ActivityService)
-
- args = { author: user, project: activity_project, namespace: activity_project&.namespace }
- expect(Users::ActivityService).to receive(:new).with(args).and_return(activity_service)
-
- expect(activity_service).to receive(:execute)
-
- get :info_refs, params: params
- end
-
include_context 'parsed logs' do
it 'adds user info to the logs' do
get :info_refs, params: params