Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-11-19 21:32:51 +00:00
parent fdc5ce9759
commit 6200f12079
60 changed files with 587 additions and 621 deletions

View File

@ -1,7 +0,0 @@
---
# Cop supports --autocorrect.
Lint/RedundantSafeNavigation:
Exclude:
- 'app/presenters/packages/nuget/version_helpers.rb'
- 'lib/gitlab/ci/jwt_v2/claim_mapper.rb'
- 'lib/security/ci_configuration/sast_build_action.rb'

View File

@ -480,7 +480,7 @@ group :development do
gem 'listen', '~> 3.7' # rubocop:todo Gemfile/MissingFeatureCategory
gem 'ruby-lsp', "~> 0.19.0", require: false, feature_category: :tooling
gem 'ruby-lsp', "~> 0.21.0", require: false, feature_category: :tooling
gem 'ruby-lsp-rails', "~> 0.3.6", feature_category: :tooling

View File

@ -510,7 +510,7 @@
{"name":"premailer","version":"1.23.0","platform":"ruby","checksum":"f0d7f6ba299559c96ddf982aa5263f85e5617c86437c8d8ffff120813b2d7efb"},
{"name":"premailer-rails","version":"1.12.0","platform":"ruby","checksum":"c13815d161b9bc7f7d3d81396b0bb0a61a90fa9bd89931548bf4e537c7710400"},
{"name":"prime","version":"0.1.2","platform":"ruby","checksum":"d4e956cadfaf04de036dc7dc74f95bf6a285a62cc509b28b7a66b245d19fe3a4"},
{"name":"prism","version":"1.1.0","platform":"ruby","checksum":"45f0d269eb09d0e2d9380fe41a96955386f7990c8e8d65de64a0b6a5a6be9b7b"},
{"name":"prism","version":"1.2.0","platform":"ruby","checksum":"24ff9cd3232346e68052659f14c9a618022ea98935f774df465206aba5c06d2f"},
{"name":"proc_to_ast","version":"0.1.0","platform":"ruby","checksum":"92a73fa66e2250a83f8589f818b0751bcf227c68f85916202df7af85082f8691"},
{"name":"prometheus-client-mmap","version":"1.1.1","platform":"aarch64-linux","checksum":"35fd23296854a1888c58198cc5776a99e5f5a729bc4262327cd1c44219e7dda2"},
{"name":"prometheus-client-mmap","version":"1.1.1","platform":"arm64-darwin","checksum":"1fc09a3f76cb3c69fde085dc63a986524e2606b2e36cc64122b49836ee6b1779"},
@ -550,7 +550,7 @@
{"name":"rb-fsevent","version":"0.11.2","platform":"ruby","checksum":"43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe"},
{"name":"rb-inotify","version":"0.10.1","platform":"ruby","checksum":"050062d4f31d307cca52c3f6a7f4b946df8de25fc4bd373e1a5142e41034a7ca"},
{"name":"rb_sys","version":"0.9.94","platform":"ruby","checksum":"8a35bed8e7906867b958be58772c779fc4afacacc86ceab921149cccb9eb4cca"},
{"name":"rbs","version":"3.5.1","platform":"ruby","checksum":"8ed89f6b088796e67ebc88141eb5b0f1a61a6a76cb63a499fdf119c58219cbae"},
{"name":"rbs","version":"3.6.1","platform":"ruby","checksum":"ed7273d018556844583d1785ac54194e67eec594d68e317d57fa90ad035532c0"},
{"name":"rbtrace","version":"0.5.1","platform":"ruby","checksum":"e8cba64d462bfb8ba102d7be2ecaacc789247d52ac587d8003549d909cb9c5dc"},
{"name":"rchardet","version":"1.8.0","platform":"ruby","checksum":"693acd5253d5ade81a51940697955f6dd4bb2f0d245bda76a8e23deec70a52c7"},
{"name":"re2","version":"2.7.0","platform":"aarch64-linux","checksum":"778921298b6e8aba26a6230dd298c9b361b92e45024f81fa6aee788060fa307c"},
@ -615,9 +615,9 @@
{"name":"rubocop-rspec","version":"3.0.5","platform":"ruby","checksum":"c6a8e29fb1b00d227c32df159e92f5ebb9e0ff734e52955fb13aff5c74977e0f"},
{"name":"rubocop-rspec_rails","version":"2.30.0","platform":"ruby","checksum":"888112e83f9d7ef7ad2397e9d69a0b9614a4bae24f072c399804a180f80c4c46"},
{"name":"ruby-fogbugz","version":"0.3.0","platform":"ruby","checksum":"5e04cde474648f498a71cf1e1a7ab42c66b953862fbe224f793ec0a7a1d5f657"},
{"name":"ruby-lsp","version":"0.19.1","platform":"ruby","checksum":"d013f937a6a3e03f19026c5234d7f22f219e45f3fdb39a80a3e816b0a2148ced"},
{"name":"ruby-lsp-rails","version":"0.3.17","platform":"ruby","checksum":"24e80e313b2a8990e1fe37a4165b3877b3b587ef1de931ae89338512502d1fd1"},
{"name":"ruby-lsp-rspec","version":"0.1.15","platform":"ruby","checksum":"d1eed4aa9d16f41ab04943ca881f7ce5389958fb8c88da2d3a1df9cefdffbd9d"},
{"name":"ruby-lsp","version":"0.21.3","platform":"ruby","checksum":"51c4e327740ce2f09e59e241fe6e67242ba8fc6fb200dddfedd4cb57d9dd5ec1"},
{"name":"ruby-lsp-rails","version":"0.3.26","platform":"ruby","checksum":"f58e92c17a78a7df27bd563b32cc1557400fcd5e7f1d0c782ca272b9b34b6351"},
{"name":"ruby-lsp-rspec","version":"0.1.18","platform":"ruby","checksum":"4e6e892f52eb4f548cb43e61c59de43bcc9d785f588f137e61601bb7271dc461"},
{"name":"ruby-magic","version":"0.6.0","platform":"ruby","checksum":"7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101"},
{"name":"ruby-openai","version":"3.7.0","platform":"ruby","checksum":"fb735d4c055e282ade264cab9864944c05a8a10e0cddd45a0551e8a9851b1850"},
{"name":"ruby-progressbar","version":"1.11.0","platform":"ruby","checksum":"cc127db3866dc414ffccbf92928a241e585b3aa2b758a5563e74a6ee0f57d50a"},
@ -684,7 +684,7 @@
{"name":"snaky_hash","version":"2.0.0","platform":"ruby","checksum":"fe8b2e39e8ff69320f7812af73ea06401579e29ff1734a7009567391600687de"},
{"name":"snowplow-tracker","version":"0.8.0","platform":"ruby","checksum":"7ba6f4f1443a829845fd28e63eda72d9d3d247f485310ddcccaebbc52b734a38"},
{"name":"solargraph","version":"0.47.2","platform":"ruby","checksum":"87ca4b799b9155c2c31c15954c483e952fdacd800f52d6709b901dd447bcac6a"},
{"name":"sorbet-runtime","version":"0.5.11266","platform":"ruby","checksum":"deb2c3054811fbcce0a888682d820f691895b84e3d8ac7bc7959e988ca0c58bb"},
{"name":"sorbet-runtime","version":"0.5.11647","platform":"ruby","checksum":"64b65112f2e6a5323310ca9ac0d7d9a6be63aade5a62a6225fe066042ff4fdb6"},
{"name":"spamcheck","version":"1.3.0","platform":"ruby","checksum":"a46082752257838d8484c844736e309ec499f85dcc51283a5f973b33f1c994f5"},
{"name":"spring","version":"4.1.0","platform":"ruby","checksum":"f17f080fb0df558d663c897a6229ed3d5cc54819ab51876ea6eef49a67f0a3cb"},
{"name":"spring-commands-rspec","version":"1.0.4","platform":"ruby","checksum":"6202e54fa4767452e3641461a83347645af478bf45dddcca9737b43af0dd1a2c"},

View File

@ -1431,7 +1431,7 @@ GEM
prime (0.1.2)
forwardable
singleton
prism (1.1.0)
prism (1.2.0)
proc_to_ast (0.1.0)
coderay
parser
@ -1519,7 +1519,7 @@ GEM
rb-inotify (0.10.1)
ffi (~> 1.0)
rb_sys (0.9.94)
rbs (3.5.1)
rbs (3.6.1)
logger
rbtrace (0.5.1)
ffi (>= 1.0.6)
@ -1658,15 +1658,15 @@ GEM
ruby-fogbugz (0.3.0)
crack (~> 0.4)
multipart-post (~> 2.0)
ruby-lsp (0.19.1)
ruby-lsp (0.21.3)
language_server-protocol (~> 3.17.0)
prism (>= 1.1, < 2.0)
prism (>= 1.2, < 2.0)
rbs (>= 3, < 4)
sorbet-runtime (>= 0.5.10782)
ruby-lsp-rails (0.3.17)
ruby-lsp (>= 0.19.0, < 0.20.0)
ruby-lsp-rspec (0.1.15)
ruby-lsp (~> 0.19.0)
ruby-lsp-rails (0.3.26)
ruby-lsp (>= 0.21.2, < 0.22.0)
ruby-lsp-rspec (0.1.18)
ruby-lsp (~> 0.21.0)
ruby-magic (0.6.0)
mini_portile2 (~> 2.8)
ruby-openai (3.7.0)
@ -1765,7 +1765,7 @@ GEM
thor (~> 1.0)
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
sorbet-runtime (0.5.11266)
sorbet-runtime (0.5.11647)
spamcheck (1.3.0)
grpc (~> 1.0)
spring (4.1.0)
@ -2262,7 +2262,7 @@ DEPENDENCIES
rspec_profiling (~> 0.0.9)
rubocop
ruby-fogbugz (~> 0.3.0)
ruby-lsp (~> 0.19.0)
ruby-lsp (~> 0.21.0)
ruby-lsp-rails (~> 0.3.6)
ruby-lsp-rspec (~> 0.1.10)
ruby-magic (~> 0.6)

View File

@ -518,7 +518,7 @@
{"name":"premailer","version":"1.23.0","platform":"ruby","checksum":"f0d7f6ba299559c96ddf982aa5263f85e5617c86437c8d8ffff120813b2d7efb"},
{"name":"premailer-rails","version":"1.12.0","platform":"ruby","checksum":"c13815d161b9bc7f7d3d81396b0bb0a61a90fa9bd89931548bf4e537c7710400"},
{"name":"prime","version":"0.1.2","platform":"ruby","checksum":"d4e956cadfaf04de036dc7dc74f95bf6a285a62cc509b28b7a66b245d19fe3a4"},
{"name":"prism","version":"1.1.0","platform":"ruby","checksum":"45f0d269eb09d0e2d9380fe41a96955386f7990c8e8d65de64a0b6a5a6be9b7b"},
{"name":"prism","version":"1.2.0","platform":"ruby","checksum":"24ff9cd3232346e68052659f14c9a618022ea98935f774df465206aba5c06d2f"},
{"name":"proc_to_ast","version":"0.1.0","platform":"ruby","checksum":"92a73fa66e2250a83f8589f818b0751bcf227c68f85916202df7af85082f8691"},
{"name":"prometheus-client-mmap","version":"1.1.1","platform":"aarch64-linux","checksum":"35fd23296854a1888c58198cc5776a99e5f5a729bc4262327cd1c44219e7dda2"},
{"name":"prometheus-client-mmap","version":"1.1.1","platform":"arm64-darwin","checksum":"1fc09a3f76cb3c69fde085dc63a986524e2606b2e36cc64122b49836ee6b1779"},
@ -561,10 +561,10 @@
{"name":"rb-fsevent","version":"0.11.2","platform":"ruby","checksum":"43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe"},
{"name":"rb-inotify","version":"0.10.1","platform":"ruby","checksum":"050062d4f31d307cca52c3f6a7f4b946df8de25fc4bd373e1a5142e41034a7ca"},
{"name":"rb_sys","version":"0.9.94","platform":"ruby","checksum":"8a35bed8e7906867b958be58772c779fc4afacacc86ceab921149cccb9eb4cca"},
{"name":"rbs","version":"3.5.1","platform":"ruby","checksum":"8ed89f6b088796e67ebc88141eb5b0f1a61a6a76cb63a499fdf119c58219cbae"},
{"name":"rbs","version":"3.6.1","platform":"ruby","checksum":"ed7273d018556844583d1785ac54194e67eec594d68e317d57fa90ad035532c0"},
{"name":"rbtrace","version":"0.5.1","platform":"ruby","checksum":"e8cba64d462bfb8ba102d7be2ecaacc789247d52ac587d8003549d909cb9c5dc"},
{"name":"rchardet","version":"1.8.0","platform":"ruby","checksum":"693acd5253d5ade81a51940697955f6dd4bb2f0d245bda76a8e23deec70a52c7"},
{"name":"rdoc","version":"6.7.0","platform":"ruby","checksum":"b17d5f0f57b0853d7b880d4360a32c7caf8dbb81f8503a36426df809e617f379"},
{"name":"rdoc","version":"6.8.1","platform":"ruby","checksum":"0128002d1bfc4892bdd780940841e4ca41275f63781fd832d11bc8ba4461462c"},
{"name":"re2","version":"2.7.0","platform":"aarch64-linux","checksum":"778921298b6e8aba26a6230dd298c9b361b92e45024f81fa6aee788060fa307c"},
{"name":"re2","version":"2.7.0","platform":"arm-linux","checksum":"d328b5286d83ae265e13b855da8e348a976f80f91b748045b52073a570577954"},
{"name":"re2","version":"2.7.0","platform":"arm64-darwin","checksum":"7d993f27a1afac4001c539a829e2af211ced62604930c90df32a307cf74cb4a4"},
@ -628,9 +628,9 @@
{"name":"rubocop-rspec","version":"3.0.5","platform":"ruby","checksum":"c6a8e29fb1b00d227c32df159e92f5ebb9e0ff734e52955fb13aff5c74977e0f"},
{"name":"rubocop-rspec_rails","version":"2.30.0","platform":"ruby","checksum":"888112e83f9d7ef7ad2397e9d69a0b9614a4bae24f072c399804a180f80c4c46"},
{"name":"ruby-fogbugz","version":"0.3.0","platform":"ruby","checksum":"5e04cde474648f498a71cf1e1a7ab42c66b953862fbe224f793ec0a7a1d5f657"},
{"name":"ruby-lsp","version":"0.19.1","platform":"ruby","checksum":"d013f937a6a3e03f19026c5234d7f22f219e45f3fdb39a80a3e816b0a2148ced"},
{"name":"ruby-lsp-rails","version":"0.3.17","platform":"ruby","checksum":"24e80e313b2a8990e1fe37a4165b3877b3b587ef1de931ae89338512502d1fd1"},
{"name":"ruby-lsp-rspec","version":"0.1.15","platform":"ruby","checksum":"d1eed4aa9d16f41ab04943ca881f7ce5389958fb8c88da2d3a1df9cefdffbd9d"},
{"name":"ruby-lsp","version":"0.21.3","platform":"ruby","checksum":"51c4e327740ce2f09e59e241fe6e67242ba8fc6fb200dddfedd4cb57d9dd5ec1"},
{"name":"ruby-lsp-rails","version":"0.3.26","platform":"ruby","checksum":"f58e92c17a78a7df27bd563b32cc1557400fcd5e7f1d0c782ca272b9b34b6351"},
{"name":"ruby-lsp-rspec","version":"0.1.18","platform":"ruby","checksum":"4e6e892f52eb4f548cb43e61c59de43bcc9d785f588f137e61601bb7271dc461"},
{"name":"ruby-magic","version":"0.6.0","platform":"ruby","checksum":"7b2138877b7d23aff812c95564eba6473b74b815ef85beb0eb792e729a2b6101"},
{"name":"ruby-openai","version":"3.7.0","platform":"ruby","checksum":"fb735d4c055e282ade264cab9864944c05a8a10e0cddd45a0551e8a9851b1850"},
{"name":"ruby-progressbar","version":"1.11.0","platform":"ruby","checksum":"cc127db3866dc414ffccbf92928a241e585b3aa2b758a5563e74a6ee0f57d50a"},
@ -697,7 +697,7 @@
{"name":"snaky_hash","version":"2.0.0","platform":"ruby","checksum":"fe8b2e39e8ff69320f7812af73ea06401579e29ff1734a7009567391600687de"},
{"name":"snowplow-tracker","version":"0.8.0","platform":"ruby","checksum":"7ba6f4f1443a829845fd28e63eda72d9d3d247f485310ddcccaebbc52b734a38"},
{"name":"solargraph","version":"0.47.2","platform":"ruby","checksum":"87ca4b799b9155c2c31c15954c483e952fdacd800f52d6709b901dd447bcac6a"},
{"name":"sorbet-runtime","version":"0.5.11266","platform":"ruby","checksum":"deb2c3054811fbcce0a888682d820f691895b84e3d8ac7bc7959e988ca0c58bb"},
{"name":"sorbet-runtime","version":"0.5.11647","platform":"ruby","checksum":"64b65112f2e6a5323310ca9ac0d7d9a6be63aade5a62a6225fe066042ff4fdb6"},
{"name":"spamcheck","version":"1.3.0","platform":"ruby","checksum":"a46082752257838d8484c844736e309ec499f85dcc51283a5f973b33f1c994f5"},
{"name":"spring","version":"4.1.0","platform":"ruby","checksum":"f17f080fb0df558d663c897a6229ed3d5cc54819ab51876ea6eef49a67f0a3cb"},
{"name":"spring-commands-rspec","version":"1.0.4","platform":"ruby","checksum":"6202e54fa4767452e3641461a83347645af478bf45dddcca9737b43af0dd1a2c"},

View File

@ -1446,7 +1446,7 @@ GEM
prime (0.1.2)
forwardable
singleton
prism (1.1.0)
prism (1.2.0)
proc_to_ast (0.1.0)
coderay
parser
@ -1541,14 +1541,14 @@ GEM
rb-inotify (0.10.1)
ffi (~> 1.0)
rb_sys (0.9.94)
rbs (3.5.1)
rbs (3.6.1)
logger
rbtrace (0.5.1)
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
optimist (>= 3.0.0)
rchardet (1.8.0)
rdoc (6.7.0)
rdoc (6.8.1)
psych (>= 4.0.0)
re2 (2.7.0)
mini_portile2 (~> 2.8.5)
@ -1684,15 +1684,15 @@ GEM
ruby-fogbugz (0.3.0)
crack (~> 0.4)
multipart-post (~> 2.0)
ruby-lsp (0.19.1)
ruby-lsp (0.21.3)
language_server-protocol (~> 3.17.0)
prism (>= 1.1, < 2.0)
prism (>= 1.2, < 2.0)
rbs (>= 3, < 4)
sorbet-runtime (>= 0.5.10782)
ruby-lsp-rails (0.3.17)
ruby-lsp (>= 0.19.0, < 0.20.0)
ruby-lsp-rspec (0.1.15)
ruby-lsp (~> 0.19.0)
ruby-lsp-rails (0.3.26)
ruby-lsp (>= 0.21.2, < 0.22.0)
ruby-lsp-rspec (0.1.18)
ruby-lsp (~> 0.21.0)
ruby-magic (0.6.0)
mini_portile2 (~> 2.8)
ruby-openai (3.7.0)
@ -1791,7 +1791,7 @@ GEM
thor (~> 1.0)
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
sorbet-runtime (0.5.11266)
sorbet-runtime (0.5.11647)
spamcheck (1.3.0)
grpc (~> 1.0)
spring (4.1.0)
@ -2289,7 +2289,7 @@ DEPENDENCIES
rspec_profiling (~> 0.0.9)
rubocop
ruby-fogbugz (~> 0.3.0)
ruby-lsp (~> 0.19.0)
ruby-lsp (~> 0.21.0)
ruby-lsp-rails (~> 0.3.6)
ruby-lsp-rspec (~> 0.1.10)
ruby-magic (~> 0.6)

View File

@ -68,7 +68,6 @@ export const DEFAULT_FIELDS = [
export const JOBS_DEFAULT_FIELDS = DEFAULT_FIELDS.filter((field) => field.key !== 'stage');
export const JOBS_TAB_FIELDS = DEFAULT_FIELDS.filter((field) => field.key !== 'pipeline');
export const POLL_INTERVAL = 10000;
export const JOBS_PER_PAGE = 30;
export const DEFAULT_PAGINATION = {
first: JOBS_PER_PAGE,

View File

@ -19,14 +19,8 @@ export default (containerId = 'js-jobs-table') => {
return false;
}
const {
fullPath,
jobStatuses,
pipelineEditorPath,
emptyStateSvgPath,
admin,
graphqlResourceEtag,
} = containerEl.dataset;
const { fullPath, jobStatuses, pipelineEditorPath, emptyStateSvgPath, admin } =
containerEl.dataset;
return new Vue({
el: containerEl,
@ -37,7 +31,6 @@ export default (containerId = 'js-jobs-table') => {
pipelineEditorPath,
jobStatuses: JSON.parse(jobStatuses),
admin: parseBoolean(admin),
graphqlResourceEtag,
},
render(createElement) {
return createElement(JobsTableApp);

View File

@ -1,6 +1,5 @@
<script>
import { GlAlert, GlKeysetPagination } from '@gitlab/ui';
import { toggleQueryPollingByVisibility, etagQueryHeaders } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import { createAlert } from '~/alert';
import { setUrlParams, updateHistory, queryToObject } from '~/lib/utils/url_utility';
@ -14,7 +13,7 @@ import GetJobsCount from './graphql/queries/get_jobs_count.query.graphql';
import JobsTable from './components/jobs_table.vue';
import JobsTableEmptyState from './components/jobs_table_empty_state.vue';
import JobsTableTabs from './components/jobs_table_tabs.vue';
import { RAW_TEXT_WARNING, DEFAULT_PAGINATION, JOBS_PER_PAGE, POLL_INTERVAL } from './constants';
import { RAW_TEXT_WARNING, DEFAULT_PAGINATION, JOBS_PER_PAGE } from './constants';
export default {
name: 'JobsPageApp',
@ -35,13 +34,10 @@ export default {
JobsSkeletonLoader,
},
mixins: [glFeatureFlagsMixin()],
inject: ['fullPath', 'graphqlResourceEtag'],
inject: ['fullPath'],
apollo: {
jobs: {
query: GetJobs,
context() {
return etagQueryHeaders('jobs_table', this.graphqlResourceEtag);
},
variables() {
return {
fullPath: this.fullPath,
@ -60,13 +56,9 @@ export default {
this.error = this.$options.i18n.jobsFetchErrorMsg;
reportToSentry(this.$options.name, error);
},
pollInterval: POLL_INTERVAL,
},
jobsCount: {
query: GetJobsCount,
context() {
return etagQueryHeaders('jobs_table', this.graphqlResourceEtag);
},
variables() {
return {
fullPath: this.fullPath,
@ -80,7 +72,6 @@ export default {
this.error = this.$options.i18n.jobsCountErrorMsg;
reportToSentry(this.$options.name, error);
},
pollInterval: POLL_INTERVAL,
},
},
data() {
@ -138,10 +129,6 @@ export default {
}
},
},
mounted() {
toggleQueryPollingByVisibility(this.$apollo.queries.jobs);
toggleQueryPollingByVisibility(this.$apollo.queries.jobsCount);
},
methods: {
resetRequestData() {
if (this.glFeatures.populateAndUseBuildNamesTable) {

View File

@ -13,9 +13,5 @@ module Routing
def graphql_etag_project_on_demand_scan_counts_path(project)
[api_graphql_path, "on_demand_scan/counts/#{project.full_path}"].join(':')
end
def graphql_etag_jobs_path(project)
[api_graphql_path, "projects/#{project.full_path}/jobs"].join(':')
end
end
end

View File

@ -1702,17 +1702,6 @@ class MergeRequest < ApplicationRecord
has_ci? || project.has_ci?
end
def mergeable_ci_state?
# When using MWCP auto merge strategy, the ci must be mergeable, regardless of the project setting
return true unless only_allow_merge_if_pipeline_succeeds? || (auto_merge_strategy == ::AutoMergeService::STRATEGY_MERGE_WHEN_CHECKS_PASS && has_ci_enabled?)
return false unless diff_head_pipeline
return false if pipeline_creating?
return true if project.allow_merge_on_skipped_pipeline?(inherit_group_setting: true) && diff_head_pipeline.skipped?
diff_head_pipeline.success?
end
def environments_in_head_pipeline(deployment_status: nil)
diff_head_pipeline&.environments_in_self_and_project_descendants(deployment_status: deployment_status) || Environment.none
end
@ -2437,10 +2426,6 @@ class MergeRequest < ApplicationRecord
)
end
def pipeline_creating?
Ci::PipelineCreation::Requests.pipeline_creating_for_merge_request?(self)
end
def merge_base_pipelines
return ::Ci::Pipeline.none unless diff_head_pipeline&.target_sha

View File

@ -32,8 +32,8 @@ module Packages
def compare_core_parts(a_core_parts, b_core_parts)
while a_core_parts.any? || b_core_parts.any?
a_part = a_core_parts.shift&.to_i || 0
b_part = b_core_parts.shift&.to_i || 0
a_part = a_core_parts.shift.to_i
b_part = b_core_parts.shift.to_i
return a_part <=> b_part if a_part != b_part
end
end

View File

@ -7,9 +7,10 @@ module MergeRequests
description 'Checks whether CI has passed'
def execute
return inactive unless merge_request.auto_merge_enabled? || merge_request.only_allow_merge_if_pipeline_succeeds?
return inactive unless merge_request.auto_merge_enabled? ||
merge_request.only_allow_merge_if_pipeline_succeeds?
if merge_request.mergeable_ci_state?
if mergeable_ci_state?
success
else
failure
@ -23,6 +24,32 @@ module MergeRequests
def cacheable?
false
end
private
def mergeable_ci_state?
return true unless pipeline_must_succeed?
return false unless merge_request.diff_head_pipeline
return false if pipeline_creating?
return true if can_skip_diff_head_pipeline?
merge_request.diff_head_pipeline.success?
end
def pipeline_creating?
Ci::PipelineCreation::Requests.pipeline_creating_for_merge_request?(merge_request)
end
def pipeline_must_succeed?
merge_request.only_allow_merge_if_pipeline_succeeds? ||
(merge_request.auto_merge_strategy == ::AutoMergeService::STRATEGY_MERGE_WHEN_CHECKS_PASS &&
merge_request.has_ci_enabled?)
end
def can_skip_diff_head_pipeline?
merge_request.project.allow_merge_on_skipped_pipeline?(inherit_group_setting: true) &&
merge_request.diff_head_pipeline.skipped?
end
end
end
end

View File

@ -3,4 +3,4 @@
- add_page_specific_style 'page_bundles/merge_request'
- admin = local_assigns.fetch(:admin, false)
#js-jobs-table{ data: { admin: admin, full_path: @project.full_path, job_statuses: job_statuses.to_json, pipeline_editor_path: project_ci_pipeline_editor_path(@project), empty_state_svg_path: image_path('illustrations/empty-state/empty-pipeline-md.svg'), graphql_resource_etag: graphql_etag_jobs_path(@project) } }
#js-jobs-table{ data: { admin: admin, full_path: @project.full_path, job_statuses: job_statuses.to_json, pipeline_editor_path: project_ci_pipeline_editor_path(@project), empty_state_svg_path: image_path('illustrations/empty-state/empty-pipeline-md.svg') } }

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142339
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/440185
milestone: '17.0'
type: development
group: group::pipeline security
group: group::pipeline execution
default_enabled: false

View File

@ -1,9 +0,0 @@
---
name: prompt_migration_ci_editor_assistant
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/475051
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167401
rollout_issue_url: https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/issues/595
milestone: '17.6'
group: group::custom models
type: experiment
default_enabled: false

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93179
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369441
milestone: '15.3'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: false

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76509
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347151
milestone: '14.10'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: false

View File

@ -4,6 +4,6 @@ feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/435848
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161075
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/474908
milestone: '17.3'
group: group::pipeline execution
group: group::pipeline security
type: ops
default_enabled: false

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124112
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/415503
milestone: '16.2'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: true

View File

@ -4,6 +4,6 @@ feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/435051
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140318
rollout_issue_url:
milestone: '16.7'
group: group::pipeline security
group: group::pipeline execution
type: ops
default_enabled: false

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124112
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/415503
milestone: '16.2'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: false

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124112
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/415503
milestone: '16.2'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: false

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84701
rollout_issue_url:
milestone: '15.1'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: true

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84701
rollout_issue_url:
milestone: '15.8'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: false

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84701
rollout_issue_url:
milestone: '15.8'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: false

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50922
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/296772
milestone: '13.8'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: true

View File

@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111147
rollout_issue_url:
milestone: '15.9'
type: ops
group: group::pipeline security
group: group::pipeline execution
default_enabled: false

View File

@ -11,9 +11,15 @@
- major: 9
minor: 5
- major: 10
minor: 0
- major: 10
minor: 8
- major: 11
minor: 0
- major: 11
minor: 11

View File

@ -5,4 +5,4 @@ feature_category: infrastructure_as_code
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155438
milestone: '17.1'
queued_migration_version: 20240605132810
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20241118232838'

View File

@ -0,0 +1,7 @@
---
migration_job_name: BackfillVulnerabilityNamespaceHistoricalStatistics
description: Fills historical vulnerability statistics data aggregated by namespace and traversal ids
feature_category: vulnerability_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165928
milestone: '17.7'
queued_migration_version: 20241113144244

View File

@ -12,6 +12,9 @@ class AddPersonalNamespaceIdToEvents2 < Gitlab::Database::Migration[2.2]
return if column_exists?(:events, :personal_namespace_id)
with_lock_retries(raise_on_exhaustion: true) do
# Doing DDL in post-deployment migration is discouraged in general,
# this is done as a workaround to prevent production incidents when
# changing the schema for very high-traffic table
add_column :events, :personal_namespace_id, :bigint
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class QueueBackfillVulnerabilityNamespaceHistoricalStatistics < Gitlab::Database::Migration[2.2]
milestone '17.7'
restrict_gitlab_migration gitlab_schema: :gitlab_sec
MIGRATION = "BackfillVulnerabilityNamespaceHistoricalStatistics"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 5000
SUB_BATCH_SIZE = 500
def up
queue_batched_background_migration(
MIGRATION,
:vulnerability_historical_statistics,
:id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :vulnerability_historical_statistics, :id, [])
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeBackfillTerraformStateVersionsProjectId < Gitlab::Database::Migration[2.2]
milestone '17.6'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillTerraformStateVersionsProjectId',
table_name: :terraform_state_versions,
column_name: :id,
job_arguments: [:project_id, :terraform_states, :project_id, :terraform_state_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
6ba2138029af433b964e5c729c3f2a8a606ae6183402f5c9f2ba9779e2fb84f2

View File

@ -0,0 +1 @@
f5bb4c59fcfd891e4a9793355ed9499b79b2ee9ef7ca4d74399b602db38f1705

View File

@ -571,6 +571,21 @@ If you are using the Linux package installation, something might have failed dur
This can be caused by orphaned records in the project registry. They are being cleaned
periodically using a registry worker, so give it some time to fix it itself.
### Failed checksums on primary site
Failed checksums identified by the Geo Primary Verification information screen can be caused by missing files or mismatched checksums. You can find error messages like `"Repository cannot be checksummed because it does not exist"` or `"File is not checksummable"` in the `gitlab-rails/geo.log` file.
For additional information about failed items, run the [integrity check Rake tasks](../../../raketasks/check.md#uploaded-files-integrity):
```ruby
sudo gitlab-rake gitlab:artifacts:check
sudo gitlab-rake gitlab:ci_secure_files:check
sudo gitlab-rake gitlab:lfs:check
sudo gitlab-rake gitlab:uploads:check
```
For detailed information about individual errors, use the `VERBOSE=1` variable.
### Secondary site shows "Unhealthy" in UI
If you have updated the value of `external_url` in `/etc/gitlab/gitlab.rb` for the primary site or changed the protocol from `http` to `https`, you may see that secondary sites are shown as `Unhealthy`. You may also find the following error in `geo.log`:

View File

@ -28,7 +28,8 @@ in the `Gitlab::Access` module as `access_level`.
The `group_saml_identity` attribute is only visible to group owners for [SSO-enabled groups](../user/group/saml_sso/index.md).
The `email` attribute is only visible to group owners for [enterprise users](../user/enterprise_user/index.md) of the group when an API request is sent to the group itself, or that group's subgroups or projects.
The `email` attribute is only visible to group owners for [enterprise users](../user/enterprise_user/index.md)
of the group when an API request is sent to the group itself, or that group's subgroups or projects.
## List all members of a group or project
@ -42,17 +43,19 @@ GET /groups/:id/members
GET /projects/:id/members
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths) |
| `query` | string | no | A query string to search for members |
| `user_ids` | array of integers | no | Filter the results on the given user IDs |
| `skip_users` | array of integers | no | Filter skipped users out of the results |
| `show_seat_info` | boolean | no | Show seat information for users |
| Attribute | Type | Required | Description |
|------------------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths). |
| `query` | string | no | Filters results based on a given name, email, or username. Use partial values to widen the scope of the query. |
| `user_ids` | array of integers | no | Filter the results on the given user IDs. |
| `skip_users` | array of integers | no | Filter skipped users out of the results. |
| `show_seat_info` | boolean | no | Show seat information for users. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members"
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/members"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/projects/:id/members"
```
Example response:
@ -137,17 +140,19 @@ GET /groups/:id/members/all
GET /projects/:id/members/all
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths). |
| `query` | string | no | A query string to search for members. |
| `user_ids` | array of integers | no | Filter the results on the given user IDs. |
| `show_seat_info` | boolean | no | Show seat information for users. |
| `state` | string | no | Filter results by member state, one of `awaiting` or `active`. Premium and Ultimate only. |
| Attribute | Type | Required | Description |
|------------------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths). |
| `query` | string | no | Filters results based on a given name, email, or username. Use partial values to widen the scope of the query. |
| `user_ids` | array of integers | no | Filter the results on the given user IDs. |
| `show_seat_info` | boolean | no | Show seat information for users. |
| `state` | string | no | Filter results by member state, one of `awaiting` or `active`. Premium and Ultimate only. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/all"
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/members/all"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/all"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/projects/:id/members/all"
```
Example response:
@ -231,14 +236,16 @@ GET /groups/:id/members/:user_id
GET /projects/:id/members/:user_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths) |
| `user_id` | integer | yes | The user ID of the member |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the member. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id"
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/members/:user_id"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/:user_id"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/projects/:id/members/:user_id"
```
Example response:
@ -288,12 +295,14 @@ GET /projects/:id/members/all/:user_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths) |
| `user_id` | integer | yes | The user ID of the member |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the member. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/all/:user_id"
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/members/all/:user_id"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/all/:user_id"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/projects/:id/members/all/:user_id"
```
Example response:
@ -324,27 +333,26 @@ Example response:
## List all billable members of a group
Gets a list of group members that count as billable. The list includes members in subgroups and projects.
Prerequisites:
- You must have the Owner role to access the API endpoint for billing permissions, as shown in [billing permissions](../user/free_user_limit.md).
Gets a list of group members that count as billable. The list includes members in subgroups and projects.
This API endpoint works on top-level groups only. It does not work on subgroups.
- This API endpoint works on top-level groups only. It does not work on subgroups.
This function takes [pagination](rest/index.md#pagination) parameters `page` and `per_page` to restrict the list of users.
Use the `search` parameter to search for billable group members by name and `sort` to sort the results.
Use the `search` parameter to search for billable group members by name, and `sort` to sort the results.
```plaintext
GET /groups/:id/billable_members
```
| Attribute | Type | Required | Description |
| ----------------------------- | --------------- | --------- |-------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `search` | string | no | A query string to search for group members by name, username, or public email. |
| `sort` | string | no | A query string containing parameters that specify the sort attribute and order. See supported values below. |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths). |
| `search` | string | no | A query string to search for group members by name, username, or public email. |
| `sort` | string | no | A query string containing parameters that specify the sort attribute and order. See supported values below. |
The supported values for the `sort` attribute are:
@ -362,7 +370,8 @@ The supported values for the `sort` attribute are:
| `last_activity_on_desc` | Last active date, descending |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/billable_members"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/billable_members"
```
Example response:
@ -416,28 +425,32 @@ Example response:
Gets a list of memberships for a billable member of a group.
Lists all projects and groups a user is a member of. Only projects and groups within the group hierarchy are included.
For instance, if the requested group is `Root Group`, and the requested user is a direct member of both `Root Group / Sub Group One` and `Other Group / Sub Group Two`, then only `Root Group / Sub Group One` will be returned, because `Other Group / Sub Group Two` is not within the `Root Group` hierarchy.
Prerequisites:
The response represents only direct memberships. Inherited memberships are not included.
- The response represents only direct memberships. Inherited memberships are not included.
- This API endpoint works on top-level groups only. It does not work on subgroups.
- This API endpoint requires permission to administer memberships for the group.
This API endpoint works on top-level groups only. It does not work on subgroups.
Lists all projects and groups a user is a member of. Only projects and groups in the group hierarchy
are included. For instance, if the requested group is `Root Group`, and the requested user is a direct member
of both `Root Group / Sub Group One` and `Other Group / Sub Group Two`, then only `Root Group / Sub Group One`
is returned, because `Other Group / Sub Group Two` is not in the `Root Group` hierarchy.
This API endpoint requires permission to administer memberships for the group.
This API endpoint takes [pagination](rest/index.md#pagination) parameters `page` and `per_page` to restrict the list of memberships.
This API endpoint takes [pagination](rest/index.md#pagination) parameters `page` and `per_page` to restrict
the list of memberships.
```plaintext
GET /groups/:id/billable_members/:user_id/memberships
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `user_id` | integer | yes | The user ID of the billable member |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the billable member. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/billable_members/:user_id/memberships"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/billable_members/:user_id/memberships"
```
Example response:
@ -480,28 +493,30 @@ DETAILS:
Gets a list of indirect memberships for a billable member of a group.
Prerequisites:
- This API endpoint works on top-level groups only. It does not work on subgroups.
- This API endpoint requires permission to administer memberships for the group.
Lists all projects and groups that a user is a member of, that have been invited to the requested root group.
For instance, if the requested group is `Root Group`, and the requested user is a direct member of `Other Group / Sub Group Two`, which was invited to `Root Group`, then only `Other Group / Sub Group Two` is returned.
The response lists only indirect memberships. Direct memberships are not included.
This API endpoint works on top-level groups only. It does not work on subgroups.
This API endpoint requires permission to administer memberships for the group.
This API endpoint takes [pagination](rest/index.md#pagination) parameters `page` and `per_page` to restrict the list of memberships.
```plaintext
GET /groups/:id/billable_members/:user_id/indirect
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `user_id` | integer | yes | The user ID of the billable member |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the billable member. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/billable_members/:user_id/indirect"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/billable_members/:user_id/indirect"
```
Example response:
@ -528,20 +543,21 @@ Example response:
Removes a billable member from a group and its subgroups and projects.
The user does not need to be a group member to qualify for removal.
For example, if the user was added directly to a project within the group, you can
For example, if the user was added directly to a project in the group, you can
still use this API to remove them.
```plaintext
DELETE /groups/:id/billable_members/:user_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `user_id` | integer | yes | The user ID of the member |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the member. |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/billable_members/:user_id"
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/billable_members/:user_id"
```
## Change membership state of a user in a group
@ -549,21 +565,24 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86705) in GitLab 15.0.
Changes the membership state of a user in a group.
When a user is over [the free user limit](../user/free_user_limit.md), changing their membership state for a group or project to `awaiting` or `active` can allow them to
access that group or project. The change is applied to applied to all subgroups and projects.
When a user is over [the free user limit](../user/free_user_limit.md), changing their membership state
for a group or project to `awaiting` or `active` can allow them to access that group or project. The change
is applied to applied to all subgroups and projects.
```plaintext
PUT /groups/:id/members/:user_id/state
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the member. |
| `state` | string | yes | The new state for the user. State is either `awaiting` or `active`. |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the member. |
| `state` | string | yes | The new state for the user. State is either `awaiting` or `active`. |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id/state?state=active"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/:user_id/state?state=active"
```
Example response:
@ -583,15 +602,15 @@ POST /groups/:id/members
POST /projects/:id/members
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths). |
| `user_id` | integer/string | yes, if `username` is not provided | The user ID of the new member or multiple IDs separated by commas. |
| `username` | string | yes, if `user_id` is not provided | The username of the new member or multiple usernames separated by commas. |
| `access_level` | integer | yes | [A valid access level](access_requests.md#valid-access-levels). |
| `expires_at` | string | no | A date string in the format `YEAR-MONTH-DAY`. |
| `invite_source` | string | no | The source of the invitation that starts the member creation process. GitLab team members can view more information in this confidential issue: `https://gitlab.com/gitlab-org/gitlab/-/issues/327120>`. |
| `member_role_id` | integer | no | The ID of a member role. Ultimate only. |
| Attribute | Type | Required | Description |
|------------------|-------------------|------------------------------------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths). |
| `user_id` | integer or string | yes, if `username` is not provided | The user ID of the new member or multiple IDs separated by commas. |
| `username` | string | yes, if `user_id` is not provided | The username of the new member or multiple usernames separated by commas. |
| `access_level` | integer | yes | [A valid access level](access_requests.md#valid-access-levels). |
| `expires_at` | string | no | A date string in the format `YEAR-MONTH-DAY`. |
| `invite_source` | string | no | The source of the invitation that starts the member creation process. GitLab team members can view more information in this confidential issue: `https://gitlab.com/gitlab-org/gitlab/-/issues/327120>`. |
| `member_role_id` | integer | no | The ID of a member role. Ultimate only. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
@ -677,17 +696,19 @@ PUT /groups/:id/members/:user_id
PUT /projects/:id/members/:user_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths) |
| `user_id` | integer | yes | The user ID of the member |
| `access_level` | integer | yes | A [valid access level](access_requests.md#valid-access-levels) |
| `expires_at` | string | no | A date string in the format `YEAR-MONTH-DAY` |
| `member_role_id` | integer | no | The ID of a member role. Ultimate only. |
| Attribute | Type | Required | Description |
|------------------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the member. |
| `access_level` | integer | yes | A [valid access level](access_requests.md#valid-access-levels). |
| `expires_at` | string | no | A date string in the format `YEAR-MONTH-DAY`. |
| `member_role_id` | integer | no | The ID of a member role. Ultimate only. |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id?access_level=40"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/members/:user_id?access_level=40"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/:user_id?access_level=40"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/projects/:id/members/:user_id?access_level=40"
```
Example response:
@ -741,13 +762,14 @@ by LDAP through Group Sync. You can allow access level overrides by calling this
POST /groups/:id/members/:user_id/override
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `user_id` | integer | yes | The user ID of the member |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the member. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id/override"
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/:user_id/override"
```
Example response:
@ -785,13 +807,14 @@ level to the LDAP-prescribed value.
DELETE /groups/:id/members/:user_id/override
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| `user_id` | integer | yes | The user ID of the member |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the member. |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id/override"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/:user_id/override"
```
Example response:
@ -825,7 +848,7 @@ Example response:
Removes a user from a group or project where the user has been explicitly assigned a role.
The user needs to be a group member to qualify for removal.
For example, if the user was added directly to a project within the group but not this
For example, if the user was added directly to a project in the group but not this
group explicitly, you cannot use this API to remove them. See
[Remove a billable member from a group](#remove-a-billable-member-from-a-group) for an alternative approach.
@ -834,18 +857,20 @@ DELETE /groups/:id/members/:user_id
DELETE /projects/:id/members/:user_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths) |
| `user_id` | integer | yes | The user ID of the member |
| `skip_subresources` | boolean | false | Whether the deletion of direct memberships of the removed member in subgroups and projects should be skipped. Default is `false`. |
| `unassign_issuables` | boolean | false | Whether the removed member should be unassigned from any issues or merge requests inside a given group or project. Default is `false`. |
| Attribute | Type | Required | Description |
|----------------------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the project or group](rest/index.md#namespaced-paths). |
| `user_id` | integer | yes | The user ID of the member. |
| `skip_subresources` | boolean | false | Whether the deletion of direct memberships of the removed member in subgroups and projects should be skipped. Default is `false`. |
| `unassign_issuables` | boolean | false | Whether the removed member should be unassigned from any issues or merge requests inside a given group or project. Default is `false`. |
Example request:
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id"
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/members/:user_id"
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/:user_id"
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/projects/:id/members/:user_id"
```
## Approve a member for a group
@ -856,15 +881,16 @@ Approves a pending user for a group and its subgroups and projects.
PUT /groups/:id/members/:member_id/approve
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the root group](rest/index.md#namespaced-paths) |
| `member_id` | integer | yes | The ID of the member |
| Attribute | Type | Required | Description |
|-------------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the root group](rest/index.md#namespaced-paths). |
| `member_id` | integer | yes | The ID of the member. |
Example request:
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:member_id/approve"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/:member_id/approve"
```
## Approve all pending members for a group
@ -875,40 +901,44 @@ Approves all pending users for a group and its subgroups and projects.
POST /groups/:id/members/approve_all
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the root group](rest/index.md#namespaced-paths) |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the root group](rest/index.md#namespaced-paths). |
Example request:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/approve_all"
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/members/approve_all"
```
## List pending members of a group and its subgroups and projects
For a group and its subgroups and projects, get a list of all members in an `awaiting` state and those who are invited but do not have a GitLab account.
For a group and its subgroups and projects, get a list of all members in an `awaiting` state and those
who are invited but do not have a GitLab account.
Prerequisites:
- This API endpoint works on top-level groups only. It does not work on subgroups.
- This API endpoint requires permission to administer members for the group.
This request returns all matching group and project members from all groups and projects in the root group's hierarchy.
When the member is an invited user that has not signed up for a GitLab account yet, the invited email address is returned.
This API endpoint works on top-level groups only. It does not work on subgroups.
This API endpoint requires permission to administer members for the group.
This API endpoint takes [pagination](rest/index.md#pagination) parameters `page` and `per_page` to restrict the list of members.
```plaintext
GET /groups/:id/pending_members
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths) |
| Attribute | Type | Required | Description |
|-----------|-------------------|----------|-------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-paths). |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/pending_members"
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/groups/:id/pending_members"
```
Example response:

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillVulnerabilityNamespaceHistoricalStatistics < BatchedMigrationJob
feature_category :vulnerability_management
def perform
# no-op. The logic is defined in EE module.
end
end
end
end
Gitlab::BackgroundMigration::BackfillVulnerabilityNamespaceHistoricalStatistics.prepend_mod

View File

@ -16,7 +16,7 @@ module Gitlab
end
def to_h
mapper&.to_h || {}
mapper.to_h
end
private

View File

@ -13,11 +13,6 @@ module Gitlab
'pipelines_graph',
'continuous_integration'
],
[
%r{\Aprojects/.+/.+/jobs\z},
'jobs_table',
'continuous_integration'
],
[
%r(\Apipelines/sha/\w{#{Gitlab::Git::Commit::MIN_SHA_LENGTH},#{Gitlab::Git::Commit::MAX_SHA_LENGTH}}\z)o,
'ci_editor',

View File

@ -21,8 +21,8 @@ module Security
end
def collect_values(config, key)
global_variables = config[:global]&.to_h { |k| [k[:field], k[key]] } || {}
pipeline_variables = config[:pipeline]&.to_h { |k| [k[:field], k[key]] } || {}
global_variables = config[:global].to_h { |k| [k[:field], k[key]] }
pipeline_variables = config[:pipeline].to_h { |k| [k[:field], k[key]] }
analyzer_variables = collect_analyzer_values(config, key)

View File

@ -14082,6 +14082,9 @@ msgstr ""
msgid "CompareRevisions|View open merge request"
msgstr ""
msgid "Comparison pipelines"
msgstr ""
msgid "Complete"
msgstr ""
@ -49386,6 +49389,9 @@ msgstr ""
msgid "Security dashboard"
msgstr ""
msgid "Security policy"
msgstr ""
msgid "Security policy bot cannot be added as a group member"
msgstr ""
@ -50540,6 +50546,9 @@ msgstr ""
msgid "SecurityOrchestration|You already have the maximum %{maximumAllowed} %{policyType} %{instance}."
msgstr ""
msgid "SecurityOrchestration|You can add a maximum of %{rulesCount} %{rules}."
msgstr ""
msgid "SecurityOrchestration|You can select this option only once."
msgstr ""
@ -53201,6 +53210,9 @@ msgstr ""
msgid "Source branch"
msgstr ""
msgid "Source branch (%{branch})"
msgstr ""
msgid "Source branch does not exist"
msgstr ""
@ -54724,6 +54736,9 @@ msgstr ""
msgid "Target branch"
msgstr ""
msgid "Target branch (%{branch})"
msgstr ""
msgid "Target branch: %{target_branch}"
msgstr ""

View File

@ -75,7 +75,7 @@
"@gitlab/fonts": "^1.3.0",
"@gitlab/query-language": "^0.0.5-a-20241112",
"@gitlab/svgs": "3.121.0",
"@gitlab/ui": "102.1.0",
"@gitlab/ui": "103.1.0",
"@gitlab/vue-router-vue3": "npm:vue-router@4.1.6",
"@gitlab/vuex-vue3": "npm:vuex@4.0.0",
"@gitlab/web-ide": "^0.0.1-dev-20240909013227",

View File

@ -250,7 +250,6 @@ spec/frontend/ide/ide_router_spec.js
spec/frontend/ide/sync_router_and_store_spec.js
spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
spec/frontend/integrations/beyond_identity/components/exclusions_list_spec.js
spec/frontend/integrations/edit/components/integration_form_spec.js
spec/frontend/integrations/edit/components/sections/connection_spec.js
spec/frontend/invite_members/components/members_token_select_spec.js
spec/frontend/issuable/components/issuable_by_email_spec.js

View File

@ -7,7 +7,7 @@ RSpec.describe 'UpgradePath', feature_category: :shared do
upgrade_path = YAML.safe_load_file(Rails.root.join('config/upgrade_path.yml'))
expect(upgrade_path.first).to eq({ "major" => 8, "minor" => 11 })
expect(upgrade_path[13]).to eq({ "major" => 14, "minor" => 0,
expect(upgrade_path[15]).to eq({ "major" => 14, "minor" => 0,
"comments" => "**Migrations can take a long time!**" })
end
end

View File

@ -34,9 +34,7 @@ RSpec.describe 'Editing file blob', :js, feature_category: :source_code_manageme
# there may be no diff and nothing to render.
fill_editor(content: "class NextFeature#{object_id}\\nend\\n")
if commit_changes
click_button 'Commit changes'
end
click_button 'Commit changes' if commit_changes
end
def fill_editor(content: 'class NextFeature\\nend\\n')

View File

@ -4,7 +4,6 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { toggleQueryPollingByVisibility } from '~/graphql_shared/utils';
import { TEST_HOST } from 'spec/test_constants';
import { createAlert } from '~/alert';
import getJobsQuery from '~/ci/jobs_page/graphql/queries/get_jobs.query.graphql';
@ -21,12 +20,7 @@ import {
mockFailedSearchToken,
mockJobsCountResponse,
} from 'jest/ci/jobs_mock_data';
import {
RAW_TEXT_WARNING,
DEFAULT_PAGINATION,
JOBS_PER_PAGE,
POLL_INTERVAL,
} from '~/ci/jobs_page/constants';
import { RAW_TEXT_WARNING, DEFAULT_PAGINATION, JOBS_PER_PAGE } from '~/ci/jobs_page/constants';
const projectPath = 'gitlab-org/gitlab';
Vue.use(VueApollo);
@ -71,7 +65,6 @@ describe('Job table app', () => {
wrapper = mountFn(JobsTableApp, {
provide: {
fullPath: projectPath,
graphqlResourceEtag: '/etag',
glFeatures: {
populateAndUseBuildNamesTable: flagState,
},
@ -442,37 +435,6 @@ describe('Job table app', () => {
});
});
describe('polling', () => {
beforeEach(async () => {
createComponent();
await waitForPromises();
});
it('polls for project jobs and job count', async () => {
expect(successHandler).toHaveBeenCalledTimes(1);
expect(countSuccessHandler).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(POLL_INTERVAL);
await waitForPromises();
expect(successHandler).toHaveBeenCalledTimes(2);
expect(countSuccessHandler).toHaveBeenCalledTimes(2);
jest.advanceTimersByTime(POLL_INTERVAL);
await waitForPromises();
expect(successHandler).toHaveBeenCalledTimes(3);
expect(countSuccessHandler).toHaveBeenCalledTimes(3);
});
it('should set up a toggle visibility', () => {
expect(toggleQueryPollingByVisibility).toHaveBeenCalledTimes(2);
});
});
describe('pagination', () => {
it('displays keyset pagination', async () => {
createComponent();

View File

@ -220,30 +220,27 @@ describe('IntegrationForm', () => {
});
describe.each`
formActive | novalidate
${true} | ${undefined}
${false} | ${'true'}
`(
'when `toggle-integration-active` is emitted with $formActive',
({ formActive, novalidate }) => {
beforeEach(() => {
createComponent({
customStateProps: {
sections: [mockSectionConnection],
manualActivation: true,
initialActivated: false,
},
});
const section = findAllSections().at(0);
section.vm.$emit('toggle-integration-active', formActive);
formActive | method
${true} | ${'toBeUndefined'}
${false} | ${'toBeDefined'}
`('when `toggle-integration-active` is emitted with $formActive', ({ formActive, method }) => {
beforeEach(() => {
createComponent({
customStateProps: {
sections: [mockSectionConnection],
manualActivation: true,
initialActivated: false,
},
});
it(`sets noValidate to ${novalidate}`, () => {
expect(findGlForm().attributes('novalidate')).toBe(novalidate);
});
},
);
const section = findAllSections().at(0);
section.vm.$emit('toggle-integration-active', formActive);
});
it(`checks noValidate ${method}`, () => {
expect(findGlForm().attributes('novalidate'))[method]();
});
});
describe('when section emits `request-jira-issue-types` event', () => {
beforeEach(() => {
@ -285,28 +282,25 @@ describe('IntegrationForm', () => {
});
describe.each`
formActive | novalidate
${true} | ${undefined}
${false} | ${'true'}
`(
'when `toggle-integration-active` is emitted with $formActive',
({ formActive, novalidate }) => {
beforeEach(() => {
createComponent({
customStateProps: {
manualActivation: true,
initialActivated: false,
},
});
findActiveCheckbox().vm.$emit('toggle-integration-active', formActive);
formActive | method
${true} | ${'toBeUndefined'}
${false} | ${'toBeDefined'}
`('when `toggle-integration-active` is emitted with $formActive', ({ formActive, method }) => {
beforeEach(() => {
createComponent({
customStateProps: {
manualActivation: true,
initialActivated: false,
},
});
it(`sets noValidate to ${novalidate}`, () => {
expect(findGlForm().attributes('novalidate')).toBe(novalidate);
});
},
);
findActiveCheckbox().vm.$emit('toggle-integration-active', formActive);
});
it(`checks noValidate ${method}`, () => {
expect(findGlForm().attributes('novalidate'))[method]();
});
});
});
describe('Response to the "save" event (form submission)', () => {

View File

@ -31,7 +31,7 @@ exports[`packages_list_app renders 1`] = `
You have no Terraform modules in your project
</h1>
<p
class="gl-mb-0 gl-mt-4"
class="gl-mb-0 gl-mt-4 gl-text-subtle"
>
Learn how to
<b-link-stub

View File

@ -375,9 +375,5 @@ RSpec.describe GitlabRoutingHelper do
expect(graphql_etag_pipeline_path(pipeline)).to eq('/api/graphql:pipelines/id/5')
end
end
it 'returns an ETag path for jobs' do
expect(graphql_etag_jobs_path(project)).to eq("/api/graphql:projects/#{project.full_path}/jobs")
end
end
end

View File

@ -25,15 +25,6 @@ RSpec.describe Gitlab::EtagCaching::Router do
expect(result.urgency).to eq ::Gitlab::EndpointAttributes::DEFAULT_URGENCY
end
it 'matches jobs endpoint' do
result = match_route('/api/graphql', 'projects/a/b/jobs')
expect(result).to be_present
expect(result.name).to eq 'jobs_table'
expect(result.router).to eq Gitlab::EtagCaching::Router::Graphql
expect(result.urgency).to eq ::Gitlab::EndpointAttributes::DEFAULT_URGENCY
end
it 'matches pipeline sha endpoint' do
result = match_route('/api/graphql', 'pipelines/sha/4asd12lla2jiwjdqw9as32glm8is8hiu8s2c5jsw')

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillVulnerabilityNamespaceHistoricalStatistics, feature_category: :vulnerability_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
gitlab_schema: :gitlab_sec,
table_name: :vulnerability_historical_statistics,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -3845,7 +3845,9 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
with_them do
it 'overrides mergeable_ci_state?' do
allow(subject).to receive(:mergeable_ci_state?) { mergeable_ci_state }
allow_next_instance_of(MergeRequests::Mergeability::CheckCiStatusService) do |check|
allow(check).to receive(:mergeable_ci_state?).and_return(mergeable_ci_state)
end
expect(subject.mergeable?(skip_ci_check: skip_ci_check)).to eq(expected_mergeable)
end
@ -4016,7 +4018,9 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
subject { create(:merge_request) }
it 'checks if merge request can be merged' do
allow(subject).to receive(:mergeable_ci_state?) { true }
allow_next_instance_of(MergeRequests::Mergeability::CheckCiStatusService) do |check|
allow(check).to receive(:mergeable_ci_state?).and_return(true)
end
expect(subject).to receive(:check_mergeability)
subject.mergeable?
@ -4060,7 +4064,9 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
context 'when #mergeable_ci_state? is false' do
before do
allow(subject.project).to receive(:only_allow_merge_if_pipeline_succeeds?) { true }
allow(subject).to receive(:mergeable_ci_state?) { false }
allow_next_instance_of(MergeRequests::Mergeability::CheckCiStatusService) do |check|
allow(check).to receive(:mergeable_ci_state?).and_return(false)
end
end
it 'returns false' do
@ -4237,193 +4243,6 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
end
end
describe '#mergeable_ci_state?' do
let(:pipeline) { build(:ci_empty_pipeline) }
before do
allow(subject).to receive(:head_pipeline) { pipeline }
end
context 'when the auto merge strategy is merge when checks pass and project has ci' do
subject { build(:merge_request, source_project: project, auto_merge_strategy: ::AutoMergeService::STRATEGY_MERGE_WHEN_CHECKS_PASS, auto_merge_enabled: true) }
let(:project) { build(:project, :auto_devops, only_allow_merge_if_pipeline_succeeds: false) }
before do
allow(subject).to receive(:has_ci_enabled?).and_return(true)
end
context 'and a failed pipeline is associated' do
before do
pipeline.status = 'failed'
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
context 'and a successful pipeline is associated' do
before do
pipeline.status = 'success'
allow(subject).to receive(:head_pipeline) { pipeline }
end
context 'and no pipelines are being created' do
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'and pipelines are being created' do
let!(:creation) { Ci::PipelineCreation::Requests.start_for_merge_request(subject) }
after do
Ci::PipelineCreation::Requests.succeeded(creation, pipeline.id)
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
end
context 'and a skipped pipeline is associated' do
before do
pipeline.status = 'skipped'
allow(subject).to receive(:head_pipeline).and_return(pipeline)
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
context 'when project allows skipped pipelines' do
before do
project.allow_merge_on_skipped_pipeline = true
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
end
context 'when no pipeline is associated' do
before do
allow(subject).to receive(:head_pipeline).and_return(nil)
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
end
context 'when it is only allowed to merge when build is green' do
subject { build(:merge_request, source_project: project) }
let(:project) { build(:project, :repository, only_allow_merge_if_pipeline_succeeds: true) }
context 'and a failed pipeline is associated' do
before do
pipeline.status = 'failed'
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
context 'and a successful pipeline is associated' do
before do
pipeline.status = 'success'
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'and a skipped pipeline is associated' do
before do
pipeline.status = 'skipped'
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
context 'when no pipeline is associated' do
before do
allow(subject).to receive(:head_pipeline).and_return(nil)
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
end
context 'when it is only allowed to merge when build is green or skipped' do
let(:project) { build(:project, :repository, only_allow_merge_if_pipeline_succeeds: true, allow_merge_on_skipped_pipeline: true) }
subject { build(:merge_request, source_project: project) }
context 'and a failed pipeline is associated' do
before do
pipeline.status = 'failed'
allow(subject).to receive(:head_pipeline).and_return(pipeline)
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
context 'and a successful pipeline is associated' do
before do
pipeline.status = 'success'
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'and a skipped pipeline is associated' do
before do
pipeline.status = 'skipped'
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'when no pipeline is associated' do
before do
allow(subject).to receive(:head_pipeline).and_return(nil)
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
end
context 'when merges are not restricted to green builds' do
let(:project) { build(:project, :repository, only_allow_merge_if_pipeline_succeeds: false) }
subject { build(:merge_request, source_project: project) }
context 'and a failed pipeline is associated' do
before do
pipeline.statuses << build(:commit_status, status: 'failed', project: project)
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'when no pipeline is associated' do
before do
allow(subject).to receive(:head_pipeline) { nil }
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'and a skipped pipeline is associated' do
before do
pipeline.status = 'skipped'
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'when no pipeline is associated' do
before do
allow(subject).to receive(:head_pipeline).and_return(nil)
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
end
end
describe '#mergeable_discussions_state?' do
let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) }

View File

@ -213,7 +213,9 @@ RSpec.describe AutoMerge::MergeWhenChecksPassService, feature_category: :code_re
context 'when the pipeline has succeeded' do
before do
allow(mr_merge_if_green_enabled).to receive(:diff_head_pipeline_success?).and_return(true)
allow(mr_merge_if_green_enabled).to receive(:mergeable_ci_state?).and_return(true)
allow_next_instance_of(MergeRequests::Mergeability::CheckCiStatusService) do |check|
allow(check).to receive(:mergeable_ci_state?).and_return(true)
end
end
context 'when the merge request is mergable' do

View File

@ -743,7 +743,9 @@ RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workf
context 'with failing CI' do
before do
allow(merge_request.project).to receive(:only_allow_merge_if_pipeline_succeeds) { true }
allow(merge_request).to receive(:mergeable_ci_state?) { false }
allow_next_instance_of(MergeRequests::Mergeability::CheckCiStatusService) do |check|
allow(check).to receive(:mergeable_ci_state?).and_return(false)
end
end
it 'logs and saves error' do

View File

@ -5,94 +5,139 @@ require 'spec_helper'
RSpec.describe MergeRequests::Mergeability::CheckCiStatusService, feature_category: :code_review_workflow do
subject(:check_ci_status) { described_class.new(merge_request: merge_request, params: params) }
let_it_be(:project) { build(:project) }
let_it_be(:merge_request) { build(:merge_request, source_project: project) }
let(:project) do
build(:project,
auto_devops_status,
only_allow_merge_if_pipeline_succeeds: only_allow_merge_if_pipeline_succeeds,
allow_merge_on_skipped_pipeline: allow_merge_on_skipped_pipeline)
end
let(:merge_request) do
build(:merge_request,
source_project: project,
auto_merge_strategy: auto_merge_strategy,
auto_merge_enabled: auto_merge_enabled)
end
let(:allow_merge_on_skipped_pipeline) { false }
let(:only_allow_merge_if_pipeline_succeeds) { false }
let(:auto_merge_strategy) { nil }
let(:auto_merge_enabled) { false }
let(:auto_devops_status) { :auto_devops_disabled }
let(:params) { { skip_ci_check: skip_check } }
let(:skip_check) { false }
let(:result) { check_ci_status.execute }
it_behaves_like 'mergeability check service', :ci_must_pass, 'Checks whether CI has passed'
describe '#execute' do
let(:result) { check_ci_status.execute }
before do
allow(merge_request)
.to receive(:only_allow_merge_if_pipeline_succeeds?)
.and_return(only_allow_merge_if_pipeline_succeeds)
allow(merge_request)
.to receive(:auto_merge_enabled?)
.and_return(auto_merge_enabled?)
shared_examples 'a valid diff head pipeline is required' do
context 'when there is no diff head pipeline' do
it 'is failure' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
end
end
context 'when only_allow_merge_if_pipeline_succeeds is true' do
context 'when there is a diff head pipeline' do
let(:pipeline) { create(:ci_empty_pipeline, sha: '1982309812309812') }
before do
merge_request.update_attribute(:head_pipeline_id, pipeline.id)
end
context 'when there is a pipeline being created' do
before do
allow(Ci::PipelineCreation::Requests).to receive(:pipeline_creating_for_merge_request?).and_return(true)
end
it 'is failure' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
end
end
context 'when there is no pipeline being created' do
before do
allow(Ci::PipelineCreation::Requests).to receive(:pipeline_creating_for_merge_request?).and_return(false)
end
context 'when the diff head pipeline is skipped' do
before do
pipeline.update_attribute(:status, :skipped)
end
context 'when it is allowed to be skipped' do
let(:allow_merge_on_skipped_pipeline) { true }
it 'is success' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
end
end
context 'when it is not allowed to be skipped' do
let(:allow_merge_on_skipped_pipeline) { false }
it 'is failed' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
end
end
end
context 'when the diff head pipeline is successful' do
before do
pipeline.update_attribute(:status, :success)
end
it 'is success' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
end
end
context 'when the diff head pipeline is not skipped or successful' do
it 'is failed' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
end
end
end
end
end
describe '#execute' do
context 'when a successful pipeline is required for merge' do
let(:only_allow_merge_if_pipeline_succeeds) { true }
context 'when merge_request.auto_merge_enabled? is false' do
let(:auto_merge_enabled?) { false }
before do
expect(merge_request).to receive(:mergeable_ci_state?).and_return(mergeable)
end
context 'when the merge request is in a mergeable state' do
let(:mergeable) { true }
it 'returns a check result with status success' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
end
end
context 'when the merge request is not in a mergeable state' do
let(:mergeable) { false }
it 'returns a check result with status failed' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
expect(result.payload[:identifier]).to eq :ci_must_pass
end
end
end
context 'when merge_request.auto_merge_enabled? is true ' do
let(:auto_merge_enabled?) { true }
before do
expect(merge_request).to receive(:mergeable_ci_state?).and_return(mergeable)
end
context 'when the merge request is in a mergeable state' do
let(:mergeable) { true }
it 'returns a check result with status success' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
end
end
context 'when the merge request is not in a mergeable state' do
let(:mergeable) { false }
it 'returns a check result with status failed' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS
expect(result.payload[:identifier]).to eq :ci_must_pass
end
end
end
it_behaves_like 'a valid diff head pipeline is required'
end
context 'when only_allow_merge_if_pipeline_succeeds is false' do
let(:only_allow_merge_if_pipeline_succeeds) { false }
let(:auto_merge_enabled?) { false }
context 'when merge_request.auto_merge_enabled? is false' do
it 'returns a check result with inactive status' do
context 'when a successful pipeline is not required for merge' do
context 'when auto merge is not enabled' do
it 'is inactive' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::INACTIVE_STATUS
end
end
context 'when auto merge is enabled' do
let(:auto_merge_enabled) { true }
let(:auto_merge_strategy) { ::AutoMergeService::STRATEGY_MERGE_WHEN_CHECKS_PASS }
context 'when the auto merge strategy is STATEGY_MERGE_WHEN_CHECKS_PASS and ci is disabled' do
it 'is success' do
expect(result.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS
end
end
context 'when the auto merge strategy is STATEGY_MERGE_WHEN_CHECKS_PASS and ci is enabled' do
let(:auto_merge_strategy) { ::AutoMergeService::STRATEGY_MERGE_WHEN_CHECKS_PASS }
let(:auto_devops_status) { :auto_devops }
it_behaves_like 'a valid diff head pipeline is required'
end
end
end
end
describe '#skip?' do
context 'when skip check is true' do
context 'when skip check is present in the params' do
let(:skip_check) { true }
it 'returns true' do
@ -100,7 +145,7 @@ RSpec.describe MergeRequests::Mergeability::CheckCiStatusService, feature_catego
end
end
context 'when skip check is false' do
context 'when skip check is not present in the params' do
let(:skip_check) { false }
it 'returns false' do

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
RSpec.shared_examples "set_current_context" do
RSpec.shared_examples 'set_current_context' do
it 'sets the metadata of the request in the context' do |example|
raise('this shared example should be used in a request spec only') unless example.metadata[:type] == :request

View File

@ -6,6 +6,7 @@ const glob = require('glob');
const sass = require('sass');
const webpack = require('webpack');
const { red } = require('chalk');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const IS_EE = require('../../config/helpers/is_ee_env');
const IS_JH = require('../../config/helpers/is_jh_env');
const gitlabWebpackConfig = require('../../config/webpack.config');
@ -178,11 +179,35 @@ module.exports = function storybookWebpackConfig({ config }) {
include: /node_modules/,
type: 'javascript/auto',
},
{
test: /\.(js|cjs)$/,
include: (modulePath) =>
/node_modules\/(jsonc-parser|monaco-editor|monaco-worker-manager|monaco-marker-data-provider)/.test(
modulePath,
) || /node_modules\/yaml/.test(modulePath),
use: transpileDependencyConfig,
},
];
// Silence webpack warnings about moment/pikaday not being able to resolve.
config.plugins.push(new webpack.IgnorePlugin(/moment/, /pikaday/));
config.plugins.push(
new MonacoWebpackPlugin({
filename: '[name].[contenthash:8].worker.js',
customLanguages: [
{
label: 'yaml',
entry: 'monaco-yaml',
worker: {
id: 'monaco-yaml/yamlWorker',
entry: 'monaco-yaml/yaml.worker',
},
},
],
}),
);
if (!IS_EE) {
config.plugins.push(
new webpack.NormalModuleReplacementPlugin(/^ee_component\/(.*)\.vue/, (resource) => {

View File

@ -1429,10 +1429,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.121.0.tgz#57cacc895929aef4320632396373797a64b230ff"
integrity sha512-ZekVjdMZrjrNEjdrOHsJYCu7A+ea3AkuNUxWIZ3FaNgJj4Oh21RlTP7bQKnRSXVhBbV1jg1PgzQ1ANEoCW8t4g==
"@gitlab/ui@102.1.0":
version "102.1.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-102.1.0.tgz#ea52d414b56e28cdd7e1bd572d7e5c057238384a"
integrity sha512-8A2qNm2JYf9/xEF0Sh0XVVrZlp7YzFws+sPOQ4CVdwyoyBtXU4cACxBfb+/t1OvTrzX42gKnHjIbolN80E51NA==
"@gitlab/ui@103.1.0":
version "103.1.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-103.1.0.tgz#7b446e72873ed9608211482b1f80419a0def3d4c"
integrity sha512-KpmJHY1+armClHecG1FoLrERpYgDK4GesbB82HsYEF1EydSZeyQPrrQEYdY83rqF1POYXBHOEzugmteOA5nUvg==
dependencies:
"@floating-ui/dom" "1.4.3"
echarts "^5.3.2"