diff --git a/.eslintrc.yml b/.eslintrc.yml
index 461c87b5dce..51272c5cf08 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -51,6 +51,9 @@ rules:
- '^$'
- '^variables$'
- 'attrs?$'
+ '@gitlab/vue-no-undef-apollo-properties': error
+ '@gitlab/tailwind': error
+ '@gitlab/vue-tailwind': error
no-param-reassign:
- error
- props: true
@@ -184,6 +187,8 @@ overrides:
rules:
'@gitlab/require-i18n-strings': off
'@gitlab/no-runtime-template-compiler': off
+ '@gitlab/tailwind': off
+ '@gitlab/vue-tailwind': off
'require-await': error
'import/no-dynamic-require': off
'no-import-assign': off
diff --git a/.gitlab/ci/release-environments/security.gitlab-ci.yml b/.gitlab/ci/release-environments/security.gitlab-ci.yml
index 615fd684aaf..100ecffaf44 100644
--- a/.gitlab/ci/release-environments/security.gitlab-ci.yml
+++ b/.gitlab/ci/release-environments/security.gitlab-ci.yml
@@ -6,7 +6,7 @@ include:
inputs:
cng_path: 'charts/components/images'
- project: 'gitlab-org/quality/pipeline-common'
- ref: '8.21.0'
+ ref: '8.22.0'
file: ci/base.gitlab-ci.yml
stages:
diff --git a/.gitlab/ci/vendored-gems.gitlab-ci.yml b/.gitlab/ci/vendored-gems.gitlab-ci.yml
index 0f0604ad194..c0e0f91f0f5 100644
--- a/.gitlab/ci/vendored-gems.gitlab-ci.yml
+++ b/.gitlab/ci/vendored-gems.gitlab-ci.yml
@@ -53,5 +53,5 @@ include:
gem_path_prefix: "vendor/gems/"
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
inputs:
- gem_name: "sidekiq"
+ gem_name: "sidekiq-7.2.4"
gem_path_prefix: "vendor/gems/"
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index 594883604b1..d3ff190c714 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -3916,8 +3916,6 @@ Layout/LineLength:
- 'spec/requests/api/commit_statuses_spec.rb'
- 'spec/requests/api/commits_spec.rb'
- 'spec/requests/api/composer_packages_spec.rb'
- - 'spec/requests/api/conan_instance_packages_spec.rb'
- - 'spec/requests/api/conan_project_packages_spec.rb'
- 'spec/requests/api/debian_group_packages_spec.rb'
- 'spec/requests/api/debian_project_packages_spec.rb'
- 'spec/requests/api/deploy_keys_spec.rb'
diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index a4938dde185..0093ca1cdb7 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -2168,8 +2168,6 @@ RSpec/ContextWording:
- 'spec/requests/api/commit_statuses_spec.rb'
- 'spec/requests/api/commits_spec.rb'
- 'spec/requests/api/composer_packages_spec.rb'
- - 'spec/requests/api/conan_instance_packages_spec.rb'
- - 'spec/requests/api/conan_project_packages_spec.rb'
- 'spec/requests/api/dependency_proxy_spec.rb'
- 'spec/requests/api/deploy_keys_spec.rb'
- 'spec/requests/api/deploy_tokens_spec.rb'
diff --git a/Gemfile b/Gemfile
index 22e4d5900d7..550562a78bf 100644
--- a/Gemfile
+++ b/Gemfile
@@ -279,7 +279,7 @@ end
gem 'state_machines-activerecord', '~> 0.8.0' # rubocop:todo Gemfile/MissingFeatureCategory
# Background jobs
-gem 'sidekiq', path: 'vendor/gems/sidekiq', require: 'sidekiq', feature_category: :scalability
+gem 'sidekiq', path: 'vendor/gems/sidekiq-7.2.4', require: 'sidekiq', feature_category: :scalability
gem 'sidekiq-cron', '~> 1.12.0', feature_category: :scalability
gem 'gitlab-sidekiq-fetcher',
path: 'vendor/gems/sidekiq-reliable-fetch',
@@ -463,7 +463,7 @@ end
gem 'warning', '~> 1.3.0' # rubocop:todo Gemfile/MissingFeatureCategory
group :development do
- gem 'lefthook', '~> 1.6.8', require: false, feature_category: :tooling
+ gem 'lefthook', '~> 1.7.0', require: false, feature_category: :tooling
gem 'rubocop', feature_category: :tooling
gem 'solargraph', '~> 0.47.2', require: false # rubocop:todo Gemfile/MissingFeatureCategory
diff --git a/Gemfile.checksum b/Gemfile.checksum
index ff82d5d804c..278f7d913c7 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -319,7 +319,7 @@
{"name":"httpclient","version":"2.8.3","platform":"ruby","checksum":"2951e4991214464c3e92107e46438527d23048e634f3aee91c719e0bdfaebda6"},
{"name":"i18n","version":"1.14.4","platform":"ruby","checksum":"c7deedead0866ea9102975a4eab7968f53de50793a0c211a37808f75dd187551"},
{"name":"i18n_data","version":"0.13.1","platform":"ruby","checksum":"e5aa99b09a69b463bb0443fc1f9540351a49f3d1541c5e91316bafa035c63f66"},
-{"name":"icalendar","version":"2.10.1","platform":"ruby","checksum":"1f3108bb95c89e03d418ac95b2fd6182c0b5d112bbe757cf6e23e3282a3f710e"},
+{"name":"icalendar","version":"2.10.2","platform":"ruby","checksum":"d70ecdca4219ad6af220d8cb6aa78170f1931c6e56be45fdf0e077060a555608"},
{"name":"ice_cube","version":"0.16.4","platform":"ruby","checksum":"da117e5de24bdc33931be629f9b55048641924442c7e9b72fedc05e5592531b7"},
{"name":"ice_nine","version":"0.11.2","platform":"ruby","checksum":"5d506a7d2723d5592dc121b9928e4931742730131f22a1a37649df1c1e2e63db"},
{"name":"imagen","version":"0.1.8","platform":"ruby","checksum":"fde7b727d4fe79c6bb5ac46c1f7184bf87a6d54df54d712ad2be039d2f93a162"},
@@ -352,7 +352,7 @@
{"name":"kubeclient","version":"4.11.0","platform":"ruby","checksum":"4985fcd749fb8c364a668a8350a49821647f03aa52d9ee6cbc582beb8e883fcc"},
{"name":"language_server-protocol","version":"3.17.0.3","platform":"ruby","checksum":"3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f"},
{"name":"launchy","version":"2.5.2","platform":"ruby","checksum":"8aa0441655aec5514008e1d04892c2de3ba57bd337afb984568da091121a241b"},
-{"name":"lefthook","version":"1.6.18","platform":"ruby","checksum":"b66c42a4de398c9bbe9bdb0ee3fc669f124244aced56d21e2e074f2980343173"},
+{"name":"lefthook","version":"1.7.14","platform":"ruby","checksum":"955c18c4dabaf92f82f085bcbf5955015853fe306f20c73343509ecd3d1bc236"},
{"name":"letter_opener","version":"1.10.0","platform":"ruby","checksum":"2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2"},
{"name":"letter_opener_web","version":"3.0.0","platform":"ruby","checksum":"3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860"},
{"name":"libyajl2","version":"2.1.0","platform":"ruby","checksum":"aa5df6c725776fc050c8418450de0f7c129cb7200b811907c4c0b3b5c0aea0ef"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 493d3edee4c..7b90af74045 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -193,6 +193,15 @@ PATH
nokogiri (>= 1.4.4)
omniauth (~> 2.0)
+PATH
+ remote: vendor/gems/sidekiq-7.2.4
+ specs:
+ sidekiq (7.2.4)
+ concurrent-ruby (< 2)
+ connection_pool (>= 2.3.0)
+ rack (>= 2.2.4)
+ redis-client (>= 0.19.0)
+
PATH
remote: vendor/gems/sidekiq-reliable-fetch
specs:
@@ -200,16 +209,6 @@ PATH
json (>= 2.5)
sidekiq (~> 7.0)
-PATH
- remote: vendor/gems/sidekiq
- specs:
- sidekiq (7.3.1)
- concurrent-ruby (< 2)
- connection_pool (>= 2.3.0)
- logger
- rack (>= 2.2.4)
- redis-client (>= 0.22.2)
-
GEM
remote: https://rubygems.org/
specs:
@@ -986,7 +985,7 @@ GEM
i18n (1.14.4)
concurrent-ruby (~> 1.0)
i18n_data (0.13.1)
- icalendar (2.10.1)
+ icalendar (2.10.2)
ice_cube (~> 0.16)
ice_cube (0.16.4)
ice_nine (0.11.2)
@@ -1058,7 +1057,7 @@ GEM
language_server-protocol (3.17.0.3)
launchy (2.5.2)
addressable (~> 2.8)
- lefthook (1.6.18)
+ lefthook (1.7.14)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (3.0.0)
@@ -2132,7 +2131,7 @@ DEPENDENCIES
knapsack (~> 4.0.0)
kramdown (~> 2.3.1)
kubeclient (~> 4.11.0)
- lefthook (~> 1.6.8)
+ lefthook (~> 1.7.0)
letter_opener_web (~> 3.0.0)
license_finder (~> 7.0)
licensee (~> 9.16)
diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum
index 56a046203b3..4daae94a223 100644
--- a/Gemfile.next.checksum
+++ b/Gemfile.next.checksum
@@ -356,7 +356,7 @@
{"name":"kubeclient","version":"4.11.0","platform":"ruby","checksum":"4985fcd749fb8c364a668a8350a49821647f03aa52d9ee6cbc582beb8e883fcc"},
{"name":"language_server-protocol","version":"3.17.0.3","platform":"ruby","checksum":"3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f"},
{"name":"launchy","version":"2.5.0","platform":"ruby","checksum":"954243c4255920982ce682f89a42e76372dba94770bf09c23a523e204bdebef5"},
-{"name":"lefthook","version":"1.6.18","platform":"ruby","checksum":"b66c42a4de398c9bbe9bdb0ee3fc669f124244aced56d21e2e074f2980343173"},
+{"name":"lefthook","version":"1.7.14","platform":"ruby","checksum":"955c18c4dabaf92f82f085bcbf5955015853fe306f20c73343509ecd3d1bc236"},
{"name":"letter_opener","version":"1.10.0","platform":"ruby","checksum":"2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2"},
{"name":"letter_opener_web","version":"3.0.0","platform":"ruby","checksum":"3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860"},
{"name":"libyajl2","version":"2.1.0","platform":"ruby","checksum":"aa5df6c725776fc050c8418450de0f7c129cb7200b811907c4c0b3b5c0aea0ef"},
diff --git a/Gemfile.next.lock b/Gemfile.next.lock
index 83771a629f7..8718df9a3ca 100644
--- a/Gemfile.next.lock
+++ b/Gemfile.next.lock
@@ -193,6 +193,15 @@ PATH
nokogiri (>= 1.4.4)
omniauth (~> 2.0)
+PATH
+ remote: vendor/gems/sidekiq-7.2.4
+ specs:
+ sidekiq (7.2.4)
+ concurrent-ruby (< 2)
+ connection_pool (>= 2.3.0)
+ rack (>= 2.2.4)
+ redis-client (>= 0.19.0)
+
PATH
remote: vendor/gems/sidekiq-reliable-fetch
specs:
@@ -200,16 +209,6 @@ PATH
json (>= 2.5)
sidekiq (~> 7.0)
-PATH
- remote: vendor/gems/sidekiq
- specs:
- sidekiq (7.3.1)
- concurrent-ruby (< 2)
- connection_pool (>= 2.3.0)
- logger
- rack (>= 2.2.4)
- redis-client (>= 0.22.2)
-
GEM
remote: https://rubygems.org/
specs:
@@ -1071,7 +1070,7 @@ GEM
language_server-protocol (3.17.0.3)
launchy (2.5.0)
addressable (~> 2.7)
- lefthook (1.6.18)
+ lefthook (1.7.14)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (3.0.0)
@@ -2159,7 +2158,7 @@ DEPENDENCIES
knapsack (~> 4.0.0)
kramdown (~> 2.3.1)
kubeclient (~> 4.11.0)
- lefthook (~> 1.6.8)
+ lefthook (~> 1.7.0)
letter_opener_web (~> 3.0.0)
license_finder (~> 7.0)
licensee (~> 9.16)
diff --git a/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue b/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue
index 53a2a9677a7..2d8e1dd076f 100644
--- a/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue
@@ -31,6 +31,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
abuseReportNotes: {
query: abuseReportNotesQuery,
variables() {
diff --git a/app/assets/javascripts/behaviors/copy_code.js b/app/assets/javascripts/behaviors/copy_code.js
index 218a402772f..59f0c169ffd 100644
--- a/app/assets/javascripts/behaviors/copy_code.js
+++ b/app/assets/javascripts/behaviors/copy_code.js
@@ -24,6 +24,7 @@ class CopyCodeButton extends HTMLElement {
type: 'button',
class: 'btn btn-default btn-md gl-button btn-icon has-tooltip',
'data-title': __('Copy to clipboard'),
+ 'aria-label': __('Copy to clipboard'),
'data-clipboard-target': `pre#${this.for}`,
});
diff --git a/app/assets/javascripts/blob/components/blob_header.vue b/app/assets/javascripts/blob/components/blob_header.vue
index d259743fd24..9ff9fa75dbe 100644
--- a/app/assets/javascripts/blob/components/blob_header.vue
+++ b/app/assets/javascripts/blob/components/blob_header.vue
@@ -17,6 +17,7 @@ export default {
WebIdeLink: () => import('ee_else_ce/vue_shared/components/web_ide_link.vue'),
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
currentUser: {
query: userInfoQuery,
error() {
diff --git a/app/assets/javascripts/boards/components/board_app.vue b/app/assets/javascripts/boards/components/board_app.vue
index 40819fa55fc..f242011e072 100644
--- a/app/assets/javascripts/boards/components/board_app.vue
+++ b/app/assets/javascripts/boards/components/board_app.vue
@@ -50,6 +50,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
activeBoardItem: {
query: activeBoardItemQuery,
variables() {
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 10f88ec7081..cdf9ee9f165 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -42,6 +42,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
activeBoardItem: {
query: activeBoardItemQuery,
variables() {
@@ -50,6 +51,7 @@ export default {
};
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
selectedBoardItems: {
query: selectedBoardItemsQuery,
},
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index 6221a4181de..ff6a849a05f 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -79,6 +79,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
isShowingLabels: {
query: isShowingLabelsQuery,
update: (data) => data.isShowingLabels,
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index dd592b58ce8..8998cb74a7d 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -96,6 +96,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
activeBoardCard: {
query: activeBoardItemQuery,
variables: {
diff --git a/app/assets/javascripts/boards/components/board_drawer_wrapper.vue b/app/assets/javascripts/boards/components/board_drawer_wrapper.vue
index d2c4e626efe..0ac407a53da 100644
--- a/app/assets/javascripts/boards/components/board_drawer_wrapper.vue
+++ b/app/assets/javascripts/boards/components/board_drawer_wrapper.vue
@@ -30,6 +30,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
activeBoardItem: {
query: activeBoardItemQuery,
variables() {
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 56778ab789b..38e1cb1dac2 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -97,6 +97,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
error: {
query: errorQuery,
update: (data) => data.boardsAppError,
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 0b3ac01b80f..95d93ede04f 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -89,6 +89,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
boardList: {
query: listQuery,
variables() {
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index 90a3253ccb4..20457f60adf 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -206,6 +206,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
boardList: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query() {
diff --git a/app/assets/javascripts/ci/admin/jobs_table/admin_jobs_table_app.vue b/app/assets/javascripts/ci/admin/jobs_table/admin_jobs_table_app.vue
index 61a619580c1..165e8457a88 100644
--- a/app/assets/javascripts/ci/admin/jobs_table/admin_jobs_table_app.vue
+++ b/app/assets/javascripts/ci/admin/jobs_table/admin_jobs_table_app.vue
@@ -90,6 +90,7 @@ export default {
this.error = this.$options.i18n.jobsCountErrorMsg;
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
cancelable: {
query: getCancelableJobs,
update(data) {
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue
index 8d4f4391f66..3363088aed2 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_shared.vue
@@ -158,6 +158,7 @@ export default {
}
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
environments: {
query() {
return this.queryData?.environments?.query || {};
diff --git a/app/assets/javascripts/ci/pipeline_details/graph/graph_component_wrapper.vue b/app/assets/javascripts/ci/pipeline_details/graph/graph_component_wrapper.vue
index e09c3da9c2c..4401851a871 100644
--- a/app/assets/javascripts/ci/pipeline_details/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/ci/pipeline_details/graph/graph_component_wrapper.vue
@@ -105,6 +105,7 @@ export default {
);
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
headerPipeline: {
query: getPipelineQuery,
// this query is already being called in pipeline_header.vue, which shares the same cache as this component
diff --git a/app/assets/javascripts/ci/pipeline_details/manual_variables/manual_variables.vue b/app/assets/javascripts/ci/pipeline_details/manual_variables/manual_variables.vue
index c2288b62eb9..4502703a963 100644
--- a/app/assets/javascripts/ci/pipeline_details/manual_variables/manual_variables.vue
+++ b/app/assets/javascripts/ci/pipeline_details/manual_variables/manual_variables.vue
@@ -13,6 +13,7 @@ export default {
},
inject: ['manualVariablesCount', 'projectPath', 'pipelineIid'],
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
variables: {
query: getManualVariablesQuery,
skip() {
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/commit/commit_section.vue b/app/assets/javascripts/ci/pipeline_editor/components/commit/commit_section.vue
index b7616c02601..7ee5ad85209 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/commit/commit_section.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/commit/commit_section.vue
@@ -62,6 +62,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
currentBranch: {
query: getCurrentBranch,
update(data) {
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue b/app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue
index 0064dc51d97..c0ebf2c3bb6 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue
@@ -75,6 +75,7 @@ export default {
this.showFetchError();
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
currentBranch: {
query: getCurrentBranch,
update(data) {
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue b/app/assets/javascripts/ci/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
index b5299c0f7d5..23d87648365 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue
@@ -29,6 +29,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
appStatus: {
query: getAppStatus,
update(data) {
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue
index 68a99a81b09..e94428cd332 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue
@@ -47,12 +47,14 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
pipelineEtag: {
query: getPipelineEtag,
update(data) {
return data.etags?.pipeline;
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
pipeline: {
context() {
return getQueryHeaders(this.pipelineEtag);
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/header/validation_segment.vue b/app/assets/javascripts/ci/pipeline_editor/components/header/validation_segment.vue
index d54ad78b3d3..8564d2555c2 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/header/validation_segment.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/header/validation_segment.vue
@@ -50,6 +50,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
appStatus: {
query: getAppStatus,
update(data) {
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue
index 8bc9cf7316a..c8e816bcbb2 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue
@@ -56,6 +56,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
runners: {
query: getRunnerTags,
update(data) {
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue
index cd6150031d4..2714316b978 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue
@@ -101,6 +101,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
appStatus: {
query: getAppStatus,
update(data) {
diff --git a/app/assets/javascripts/ci/pipeline_editor/components/validate/ci_validate.vue b/app/assets/javascripts/ci/pipeline_editor/components/validate/ci_validate.vue
index 5296dbb0c59..b5007113ba6 100644
--- a/app/assets/javascripts/ci/pipeline_editor/components/validate/ci_validate.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/components/validate/ci_validate.vue
@@ -80,6 +80,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
initialBlobContent: {
query: getBlobContent,
variables() {
@@ -93,6 +94,7 @@ export default {
return data?.project?.repository?.blobs?.nodes[0]?.rawBlob;
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
currentBranch: {
query: getCurrentBranch,
update(data) {
diff --git a/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_app.vue
index 579d850fda5..eb3e7959df7 100644
--- a/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/ci/pipeline_editor/pipeline_editor_app.vue
@@ -169,12 +169,14 @@ export default {
}
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
appStatus: {
query: getAppStatus,
update(data) {
return data.app.status;
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
commitSha: {
query: getLatestCommitShaQuery,
skip({ currentBranch }) {
@@ -202,6 +204,7 @@ export default {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
currentBranch: {
query: getCurrentBranch,
update(data) {
diff --git a/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue
index db75c05bbe6..0b3413b13f3 100644
--- a/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue
+++ b/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue
@@ -146,6 +146,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
ciConfigVariables: {
fetchPolicy: fetchPolicies.NO_CACHE,
query: ciConfigVariablesQuery,
diff --git a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
index 43d910ea326..dab115376fc 100644
--- a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
+++ b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue
@@ -42,6 +42,7 @@ export default {
}),
borderClasses: 'gl-border-b-1 gl-border-b-solid gl-border-b-gray-100',
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
agentEvents: {
query: getAgentActivityEventsQuery,
variables() {
diff --git a/app/assets/javascripts/clusters/agents/components/show.vue b/app/assets/javascripts/clusters/agents/components/show.vue
index 370bee7807d..7efa6e1120b 100644
--- a/app/assets/javascripts/clusters/agents/components/show.vue
+++ b/app/assets/javascripts/clusters/agents/components/show.vue
@@ -32,6 +32,7 @@ export default {
},
connectModalId: CONNECT_MODAL_ID,
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
clusterAgent: {
query: getClusterAgentQuery,
variables() {
diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue
index 10ba736dd92..5307d20cb1e 100644
--- a/app/assets/javascripts/clusters_list/components/agents.vue
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -24,6 +24,7 @@ export default {
AGENT_FEEDBACK_ISSUE,
AGENT_FEEDBACK_KEY,
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
agents: {
query: getAgentsQuery,
variables() {
@@ -41,6 +42,7 @@ export default {
this.queryErrored = true;
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
treeList: {
query: getTreeList,
variables() {
diff --git a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
index 87c31698d30..28c2efc4afb 100644
--- a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
+++ b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
@@ -60,6 +60,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
agents: {
query: agentConfigurations,
variables() {
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index 93d7aed9289..ddd327cf789 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -48,6 +48,9 @@ export default {
update(data) {
return data?.project?.deployKeys || [];
},
+ skip() {
+ return !this.currentPage || !this.currentScope;
+ },
error(error) {
createAlert({
message: s__('DeployKeys|Error getting deploy keys'),
@@ -65,9 +68,11 @@ export default {
return pageInfo || {};
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
currentPage: {
query: currentPageQuery,
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
currentScope: {
query: currentScopeQuery,
},
@@ -79,6 +84,8 @@ export default {
return {
deployKeys: [],
pageInfo: {},
+ currentPage: null,
+ currentScope: null,
deployKeyToRemove: null,
};
},
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index a408a1a04e1..3fd8cdf00af 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -34,6 +34,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
currentScope: {
query: currentScopeQuery,
},
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index e56f9703f49..81fa13235d1 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -165,6 +165,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
getMRCodequalityAndSecurityReports: {
query: getMRCodequalityAndSecurityReports,
pollInterval: FINDINGS_POLL_INTERVAL,
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json
index 23fc89a4d6c..1c2613ba724 100644
--- a/app/assets/javascripts/editor/schema/ci.json
+++ b/app/assets/javascripts/editor/schema/ci.json
@@ -2389,6 +2389,10 @@
"path_prefix": {
"type": "string",
"markdownDescription": "The GitLab Pages URL path prefix used in this version of pages. The given value is converted to lowercase, shortened to 63 bytes, and everything except alphanumeric characters is replaced with a hyphen. Leading and trailing hyphens are not permitted."
+ },
+ "expire_in": {
+ "type": "string",
+ "markdownDescription": "How long the deployment should be active. Deployments that have expired are no longer available on the web. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'. Set to 'never' to prevent extra deployments from expiring. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#pagesexpire_in)."
}
}
}
diff --git a/app/assets/javascripts/environments/components/deployment.vue b/app/assets/javascripts/environments/components/deployment.vue
index ebf4c4ba5d2..a0efa55a7f8 100644
--- a/app/assets/javascripts/environments/components/deployment.vue
+++ b/app/assets/javascripts/environments/components/deployment.vue
@@ -116,6 +116,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
tags: {
query: deploymentDetails,
variables() {
diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue
index 0eebd81b671..44d681b71bd 100644
--- a/app/assets/javascripts/environments/components/edit_environment.vue
+++ b/app/assets/javascripts/environments/components/edit_environment.vue
@@ -15,6 +15,7 @@ export default {
mixins: [glFeatureFlagsMixin()],
inject: ['projectEnvironmentsPath', 'projectPath', 'environmentName'],
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
environment: {
query: getEnvironment,
variables() {
diff --git a/app/assets/javascripts/environments/components/environment_flux_resource_selector.vue b/app/assets/javascripts/environments/components/environment_flux_resource_selector.vue
index 6d78dfc3195..0405acffa4d 100644
--- a/app/assets/javascripts/environments/components/environment_flux_resource_selector.vue
+++ b/app/assets/javascripts/environments/components/environment_flux_resource_selector.vue
@@ -52,6 +52,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
fluxKustomizations: {
query: fluxKustomizationsQuery,
variables() {
@@ -75,6 +76,7 @@ export default {
}
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
fluxHelmReleases: {
query: fluxHelmReleasesQuery,
variables() {
diff --git a/app/assets/javascripts/environments/components/environment_folder.vue b/app/assets/javascripts/environments/components/environment_folder.vue
index ccde9d0fcae..ab5739a5096 100644
--- a/app/assets/javascripts/environments/components/environment_folder.vue
+++ b/app/assets/javascripts/environments/components/environment_folder.vue
@@ -34,6 +34,7 @@ export default {
return { visible: false, interval: undefined };
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
folder: {
query: folderQuery,
variables() {
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index 729309b206e..17bd5f2d071 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -42,6 +42,7 @@ export default {
GlTabs,
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
environmentApp: {
query: environmentAppQuery,
variables() {
diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue
index 0ae05061704..a4d86e6e860 100644
--- a/app/assets/javascripts/environments/components/new_environment_item.vue
+++ b/app/assets/javascripts/environments/components/new_environment_item.vue
@@ -62,6 +62,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
isLastDeployment: {
query: isLastDeployment,
variables() {
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_logs.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_logs.vue
index 51bfa7cef07..884e212b008 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_logs.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_logs.vue
@@ -48,6 +48,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
k8sLogs: {
query: k8sLogsQuery,
variables() {
@@ -62,6 +63,7 @@ export default {
return Boolean(!this.gitlabAgentId);
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
environment: {
query: environmentClusterAgentQuery,
variables() {
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue
index 10c91d68534..946a601bf43 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue
@@ -91,6 +91,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
fluxKustomization: {
query: fluxKustomizationQuery,
variables() {
@@ -108,6 +109,7 @@ export default {
this.fluxApiError = err.message;
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
fluxHelmRelease: {
query: fluxHelmReleaseQueryStatus,
variables() {
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_pods.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_pods.vue
index 9f484365a79..98e18489ba0 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_pods.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_pods.vue
@@ -23,6 +23,7 @@ export default {
WorkloadTable,
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
k8sPods: {
query: k8sPodsQuery,
variables() {
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_services.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_services.vue
index 9a9d3c0f6e3..67b745bcbe0 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_services.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_services.vue
@@ -18,6 +18,7 @@ export default {
GlLoadingIcon,
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
k8sServices: {
query: k8sServicesQuery,
variables() {
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue
index 1781836a205..2c74b5fe8b2 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_summary.vue
@@ -33,6 +33,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
k8sDeployments: {
query: k8sDeploymentsQuery,
variables() {
diff --git a/app/assets/javascripts/environments/environment_details/index.vue b/app/assets/javascripts/environments/environment_details/index.vue
index affbea29933..cea9be0f6ee 100644
--- a/app/assets/javascripts/environments/environment_details/index.vue
+++ b/app/assets/javascripts/environments/environment_details/index.vue
@@ -38,6 +38,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
environment: {
query: environmentClusterAgentQuery,
variables() {
diff --git a/app/assets/javascripts/environments/folder/environments_folder_app.vue b/app/assets/javascripts/environments/folder/environments_folder_app.vue
index 0b0fa801d5c..5353b42f169 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_app.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_app.vue
@@ -56,6 +56,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
folder: {
query: folderQuery,
variables() {
diff --git a/app/assets/javascripts/import_entities/components/import_target_dropdown.vue b/app/assets/javascripts/import_entities/components/import_target_dropdown.vue
index 22d73e1d627..afe4c57e0ce 100644
--- a/app/assets/javascripts/import_entities/components/import_target_dropdown.vue
+++ b/app/assets/javascripts/import_entities/components/import_target_dropdown.vue
@@ -57,6 +57,7 @@ export default {
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
namespaces: {
query: searchNamespacesWhereUserCanImportProjectsQuery,
variables() {
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
index edc80201787..8ccc5c1edc9 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
@@ -121,12 +121,14 @@ export default {
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
bulkImportSourceGroups: {
query: bulkImportSourceGroupsQuery,
variables() {
return { page: this.page, filter: this.filter, perPage: this.perPage };
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
availableNamespaces: {
query: searchNamespacesWhereUserCanImportProjectsQuery,
update(data) {
diff --git a/app/assets/javascripts/issuable/components/related_issuable_item.vue b/app/assets/javascripts/issuable/components/related_issuable_item.vue
index b6bde4bba8c..d026921cc50 100644
--- a/app/assets/javascripts/issuable/components/related_issuable_item.vue
+++ b/app/assets/javascripts/issuable/components/related_issuable_item.vue
@@ -183,7 +183,7 @@ export default {
-
+
diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue
index 427aad8d96b..07c837a1fd2 100644
--- a/app/assets/javascripts/issues/show/components/header_actions.vue
+++ b/app/assets/javascripts/issues/show/components/header_actions.vue
@@ -103,6 +103,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
issuableReference: {
query: issueReferenceQuery,
variables() {
diff --git a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue
index e3fa0ce8073..616a9932d49 100644
--- a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue
+++ b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue
@@ -33,6 +33,7 @@ export default {
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
projects: {
query: getProjectsQuery,
variables() {
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_action.js b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_action.js
index a6081303bf8..b13b20e1116 100644
--- a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_action.js
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_action.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { InternalEvents } from '~/tracking';
export function confirmAction(
message,
@@ -13,6 +14,7 @@ export function confirmAction(
title,
hideCancel,
size,
+ trackingEvent,
} = {},
) {
return new Promise((resolve) => {
@@ -42,6 +44,13 @@ export function confirmAction(
on: {
confirmed() {
confirmed = true;
+ if (trackingEvent) {
+ InternalEvents.trackEvent(trackingEvent.name, {
+ label: trackingEvent.label,
+ property: trackingEvent.property,
+ value: trackingEvent.value,
+ });
+ }
},
closed() {
component.$destroy();
diff --git a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
index 0e959e899e9..36c0d735fce 100644
--- a/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
+++ b/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js
@@ -1,13 +1,34 @@
import { confirmAction } from './confirm_action';
function confirmViaGlModal(message, element) {
- const { confirmBtnVariant, title, isHtmlMessage } = element.dataset;
+ const {
+ confirmBtnVariant,
+ title,
+ isHtmlMessage,
+ trackingEventName,
+ trackingEventLabel,
+ trackingEventProperty,
+ trackingEventValue,
+ } = element.dataset;
const screenReaderText =
element.querySelector('.gl-sr-only')?.textContent ||
element.querySelector('.sr-only')?.textContent ||
element.getAttribute('aria-label');
+ const getTrackingEventConfig = (trackingEventNameFromDataset) => {
+ if (!trackingEventNameFromDataset) return null;
+
+ return {
+ name: trackingEventNameFromDataset,
+ label: trackingEventLabel,
+ property: trackingEventProperty,
+ value: trackingEventValue,
+ };
+ };
+
+ const trackingEventConfig = getTrackingEventConfig(trackingEventName);
+
const config = {
...(screenReaderText && { primaryBtnText: screenReaderText }),
...(confirmBtnVariant && { primaryBtnVariant: confirmBtnVariant }),
@@ -15,6 +36,10 @@ function confirmViaGlModal(message, element) {
...(isHtmlMessage && { modalHtmlMessage: message }),
};
+ if (trackingEventConfig) {
+ config.trackingEvent = trackingEventConfig;
+ }
+
return confirmAction(message, config);
}
diff --git a/app/assets/javascripts/members/placeholders/components/placeholder_actions.vue b/app/assets/javascripts/members/placeholders/components/placeholder_actions.vue
index 0c0d8f56197..69174eec730 100644
--- a/app/assets/javascripts/members/placeholders/components/placeholder_actions.vue
+++ b/app/assets/javascripts/members/placeholders/components/placeholder_actions.vue
@@ -55,6 +55,7 @@ export default {
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
users: {
query: searchUsersQuery,
variables() {
diff --git a/app/assets/javascripts/members/placeholders/components/placeholders_table.vue b/app/assets/javascripts/members/placeholders/components/placeholders_table.vue
index f8736822bc7..ec2369a9ca4 100644
--- a/app/assets/javascripts/members/placeholders/components/placeholders_table.vue
+++ b/app/assets/javascripts/members/placeholders/components/placeholders_table.vue
@@ -63,6 +63,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
sourceUsers: {
query: importSourceUsersQuery,
variables() {
diff --git a/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue b/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue
index ea9bb7ee887..8afb11d40dd 100644
--- a/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue
+++ b/app/assets/javascripts/merge_requests/components/reviewers/reviewer_dropdown.vue
@@ -12,6 +12,7 @@ import userPermissionsQuery from './queries/user_permissions.query.graphql';
export default {
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
userPermissions: {
query: userPermissionsQuery,
variables() {
diff --git a/app/assets/javascripts/merge_requests/components/sticky_header.vue b/app/assets/javascripts/merge_requests/components/sticky_header.vue
index c7cf2197aa9..2ceb37a76cb 100644
--- a/app/assets/javascripts/merge_requests/components/sticky_header.vue
+++ b/app/assets/javascripts/merge_requests/components/sticky_header.vue
@@ -31,6 +31,7 @@ export default {
TYPE_MERGE_REQUEST,
apollo: {
$subscribe: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
title: {
query() {
return titleSubscription;
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index a5827cc815c..c2ccdaeadd0 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -174,6 +174,7 @@ export default {
},
canResolve() {
if (!this.discussionRoot) return false;
+ if (!this.note.resolvable) return false;
return this.note.current_user.can_resolve_discussion;
},
diff --git a/app/assets/javascripts/pages/import/fogbugz/new_user_map/components/user_select.vue b/app/assets/javascripts/pages/import/fogbugz/new_user_map/components/user_select.vue
index 912b84dbae6..33767956c60 100644
--- a/app/assets/javascripts/pages/import/fogbugz/new_user_map/components/user_select.vue
+++ b/app/assets/javascripts/pages/import/fogbugz/new_user_map/components/user_select.vue
@@ -20,6 +20,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
usersQuery: {
query: searchUsersQuery,
variables() {
diff --git a/app/assets/javascripts/pages/projects/show/empty_project.js b/app/assets/javascripts/pages/projects/show/empty_project.js
new file mode 100644
index 00000000000..89bfaa43f59
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/show/empty_project.js
@@ -0,0 +1,14 @@
+import { GlTabsBehavior, HISTORY_TYPE_HASH } from '~/tabs';
+
+export default class EmptyProject {
+ constructor() {
+ this.configureGitTabsEl = document.querySelector('.js-configure-git-tabs');
+
+ this.emptyProjectTabsEl = document.querySelector('.js-empty-project-tabs');
+
+ // eslint-disable-next-line no-new
+ new GlTabsBehavior(this.configureGitTabsEl, { history: HISTORY_TYPE_HASH });
+ // eslint-disable-next-line no-new
+ new GlTabsBehavior(this.emptyProjectTabsEl, { history: HISTORY_TYPE_HASH });
+ }
+}
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index a123a134ef3..11d3c4ad2cc 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -10,6 +10,7 @@ import initReadMore from '~/read_more';
import initAmbiguousRefModal from '~/ref/init_ambiguous_ref_modal';
import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
import initSourceCodeDropdowns from '~/vue_shared/components/download_dropdown/init_download_dropdowns';
+import EmptyProject from '~/pages/projects/show/empty_project';
import { initHomePanel } from '../home_panel';
// Project show page loads different overview content based on user preferences
@@ -81,6 +82,15 @@ const initCodeDropdown = () => {
});
};
+const initEmptyProjectTabs = () => {
+ const emptyProjectEl = document.querySelector('#js-project-show-empty-page');
+
+ if (!emptyProjectEl) return;
+
+ new EmptyProject(); // eslint-disable-line no-new
+};
+
initCodeDropdown();
initSourceCodeDropdowns();
initFindFileShortcut();
+initEmptyProjectTabs();
diff --git a/app/assets/javascripts/pipeline_wizard/components/commit.vue b/app/assets/javascripts/pipeline_wizard/components/commit.vue
index 29481bf4444..3117d0afdd3 100644
--- a/app/assets/javascripts/pipeline_wizard/components/commit.vue
+++ b/app/assets/javascripts/pipeline_wizard/components/commit.vue
@@ -116,6 +116,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
project: {
query: getFileMetaDataQuery,
variables() {
diff --git a/app/assets/javascripts/profile/components/snippets/snippets_tab.vue b/app/assets/javascripts/profile/components/snippets/snippets_tab.vue
index abc66e16b97..e830fa1aad6 100644
--- a/app/assets/javascripts/profile/components/snippets/snippets_tab.vue
+++ b/app/assets/javascripts/profile/components/snippets/snippets_tab.vue
@@ -37,6 +37,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
userSnippets: {
query: getUserSnippets,
variables() {
diff --git a/app/assets/javascripts/profile/components/user_achievements.vue b/app/assets/javascripts/profile/components/user_achievements.vue
index 17314da26c6..12b14816dcf 100644
--- a/app/assets/javascripts/profile/components/user_achievements.vue
+++ b/app/assets/javascripts/profile/components/user_achievements.vue
@@ -14,6 +14,7 @@ export default {
mixins: [timeagoMixin],
inject: ['rootUrl', 'userId'],
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
userAchievements: {
query: getUserAchievements,
variables() {
diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_refs.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_refs.vue
index 52cf9b05c1b..1be46b15102 100644
--- a/app/assets/javascripts/projects/commit_box/info/components/commit_refs.vue
+++ b/app/assets/javascripts/projects/commit_box/info/components/commit_refs.vue
@@ -21,6 +21,7 @@ export default {
},
inject: ['fullPath', 'commitSha'],
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
project: {
query: commitReferencesQuery,
variables() {
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
index 0e0fc52ef6f..80ff0ccf813 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
@@ -10,6 +10,7 @@ import {
visitUrl,
setUrlParams,
} from '~/lib/utils/url_utility';
+import { InternalEvents } from '~/tracking';
import { helpPagePath } from '~/helpers/help_page_helper';
import branchRulesQuery from 'ee_else_ce/projects/settings/branch_rules/queries/branch_rules_details.query.graphql';
import { createAlert } from '~/alert';
@@ -19,6 +20,15 @@ import CrudComponent from '~/vue_shared/components/crud_component.vue';
import SettingsSection from '~/vue_shared/components/settings/settings_section.vue';
import editBranchRuleMutation from 'ee_else_ce/projects/settings/branch_rules/mutations/edit_branch_rule.mutation.graphql';
import { getAccessLevels, getAccessLevelInputFromEdges } from 'ee_else_ce/projects/settings/utils';
+import {
+ BRANCH_RULE_DETAILS_LABEL,
+ CHANGED_BRANCH_RULE_TARGET,
+ CHANGED_ALLOWED_TO_MERGE,
+ CHANGED_ALLOWED_TO_PUSH_AND_MERGE,
+ CHANGED_ALLOW_FORCE_PUSH,
+ UNPROTECTED_BRANCH,
+ CHANGED_REQUIRE_CODEOWNER_APPROVAL,
+} from 'ee_else_ce/projects/settings/branch_rules/tracking/constants';
import deleteBranchRuleMutation from '../../mutations/branch_rule_delete.mutation.graphql';
import BranchRuleModal from '../../../components/branch_rule_modal.vue';
import Protection from './protection.vue';
@@ -83,6 +93,7 @@ export default {
showCodeOwners: { default: false },
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
project: {
query: branchRulesQuery,
variables() {
@@ -225,6 +236,9 @@ export default {
.then(
// eslint-disable-next-line consistent-return
({ data: { branchRuleDelete } = {} } = {}) => {
+ InternalEvents.trackEvent(UNPROTECTED_BRANCH, {
+ label: BRANCH_RULE_DETAILS_LABEL,
+ });
const [error] = branchRuleDelete.errors;
if (error) {
return createAlert({
@@ -252,6 +266,12 @@ export default {
openAllowedToPushAndMergeDrawer() {
this.isAllowedToPushAndMergeDrawerOpen = true;
},
+ onEditRuleTarget(ruleTarget) {
+ this.editBranchRule({
+ name: ruleTarget,
+ trackEvent: CHANGED_BRANCH_RULE_TARGET,
+ });
+ },
onEnableForcePushToggle(isChecked) {
this.isAllowForcePushLoading = true;
const toastMessage = isChecked
@@ -261,6 +281,7 @@ export default {
this.editBranchRule({
branchProtection: { allowForcePush: isChecked },
toastMessage,
+ trackEvent: CHANGED_ALLOW_FORCE_PUSH,
});
},
onEnableCodeOwnersToggle(isChecked) {
@@ -272,6 +293,7 @@ export default {
this.editBranchRule({
branchProtection: { codeOwnerApprovalRequired: isChecked },
toastMessage,
+ trackEvent: CHANGED_REQUIRE_CODEOWNER_APPROVAL,
});
},
onEditAccessLevels(accessLevels) {
@@ -281,15 +303,22 @@ export default {
this.editBranchRule({
branchProtection: { mergeAccessLevels: accessLevels },
toastMessage: s__('BranchRules|Allowed to merge updated'),
+ trackEvent: CHANGED_ALLOWED_TO_MERGE,
});
} else if (this.isAllowedToPushAndMergeDrawerOpen) {
this.editBranchRule({
branchProtection: { pushAccessLevels: accessLevels },
toastMessage: s__('BranchRules|Allowed to push and merge updated'),
+ trackEvent: CHANGED_ALLOWED_TO_PUSH_AND_MERGE,
});
}
},
- editBranchRule({ name = this.branchRule.name, branchProtection = null, toastMessage = '' }) {
+ editBranchRule({
+ name = this.branchRule.name,
+ branchProtection = null,
+ toastMessage = '',
+ trackEvent = '',
+ }) {
this.$apollo
.mutate({
mutation: editBranchRuleMutation,
@@ -317,6 +346,12 @@ export default {
return;
}
+ if (trackEvent.length) {
+ InternalEvents.trackEvent(trackEvent, {
+ label: BRANCH_RULE_DETAILS_LABEL,
+ });
+ }
+
const isRedirectNeeded = !branchProtection;
if (isRedirectNeeded) {
visitUrl(setUrlParams({ branch: name }));
@@ -559,7 +594,7 @@ export default {
:ref="$options.editModalId"
:title="$options.i18n.updateTargetRule"
:action-primary-text="$options.i18n.update"
- @primary="editBranchRule({ name: $event })"
+ @primary="onEditRuleTarget"
/>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/tracking/constants.js b/app/assets/javascripts/projects/settings/branch_rules/tracking/constants.js
new file mode 100644
index 00000000000..0315973d552
--- /dev/null
+++ b/app/assets/javascripts/projects/settings/branch_rules/tracking/constants.js
@@ -0,0 +1,10 @@
+export const BRANCH_RULE_DETAILS_LABEL = 'branch_rule_details';
+export const REPOSITORY_SETTINGS_LABEL = 'repository_settings';
+
+export const PROTECTED_BRANCH = 'protect_branch';
+export const CHANGED_BRANCH_RULE_TARGET = 'change_branch_rule_target';
+export const CHANGED_ALLOWED_TO_MERGE = 'change_allowed_to_merge';
+export const CHANGED_ALLOWED_TO_PUSH_AND_MERGE = 'change_allowed_to_push_and_merge';
+export const CHANGED_ALLOW_FORCE_PUSH = 'change_allow_force_push';
+export const UNPROTECTED_BRANCH = 'unprotect_branch';
+export const CHANGED_REQUIRE_CODEOWNER_APPROVAL = 'change_require_codeowner_approval';
diff --git a/app/assets/javascripts/projects/settings/components/branch_rule_modal.vue b/app/assets/javascripts/projects/settings/components/branch_rule_modal.vue
index f0107e07847..dfadc4efbda 100644
--- a/app/assets/javascripts/projects/settings/components/branch_rule_modal.vue
+++ b/app/assets/javascripts/projects/settings/components/branch_rule_modal.vue
@@ -37,6 +37,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
project: {
query: getProtectableBranches,
variables() {
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
index 02f1d293f65..581ffaa7cba 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
@@ -2,11 +2,16 @@
import { GlButton, GlModal, GlModalDirective, GlDisclosureDropdown } from '@gitlab/ui';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import { createAlert } from '~/alert';
+import { InternalEvents } from '~/tracking';
import branchRulesQuery from 'ee_else_ce/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { expandSection } from '~/settings_panels';
import { scrollToElement } from '~/lib/utils/common_utils';
import { visitUrl } from '~/lib/utils/url_utility';
+import {
+ BRANCH_RULE_DETAILS_LABEL,
+ PROTECTED_BRANCH,
+} from '~/projects/settings/branch_rules/tracking/constants';
import BranchRuleModal from '../../components/branch_rule_modal.vue';
import createBranchRuleMutation from './graphql/mutations/create_branch_rule.mutation.graphql';
import BranchRule from './components/branch_rule.vue';
@@ -124,6 +129,9 @@ export default {
this.$refs[this.$options.modalId].show();
},
addBranchRule({ name }) {
+ InternalEvents.trackEvent(PROTECTED_BRANCH, {
+ label: BRANCH_RULE_DETAILS_LABEL,
+ });
this.$apollo
.mutate({
mutation: createBranchRuleMutation,
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js b/app/assets/javascripts/protected_branches/protected_branch_edit.js
index 67636c36155..c2ed3735504 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js
@@ -3,6 +3,14 @@ import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import { initToggle } from '~/toggles';
+import {
+ REPOSITORY_SETTINGS_LABEL,
+ CHANGED_ALLOWED_TO_MERGE,
+ CHANGED_ALLOWED_TO_PUSH_AND_MERGE,
+ CHANGED_ALLOW_FORCE_PUSH,
+ CHANGED_REQUIRE_CODEOWNER_APPROVAL,
+} from 'ee_else_ce/projects/settings/branch_rules/tracking/constants';
+import { InternalEvents } from '~/tracking';
import { initAccessDropdown } from '~/projects/settings/init_access_dropdown';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
@@ -39,6 +47,9 @@ export default class ProtectedBranchEdit {
allow_force_push: value,
},
() => {
+ InternalEvents.trackEvent(CHANGED_ALLOW_FORCE_PUSH, {
+ label: REPOSITORY_SETTINGS_LABEL,
+ });
forcePushToggle.isLoading = false;
forcePushToggle.disabled = false;
},
@@ -57,6 +68,9 @@ export default class ProtectedBranchEdit {
code_owner_approval_required: value,
},
() => {
+ InternalEvents.trackEvent(CHANGED_REQUIRE_CODEOWNER_APPROVAL, {
+ label: REPOSITORY_SETTINGS_LABEL,
+ });
codeOwnerToggle.isLoading = false;
codeOwnerToggle.disabled = false;
},
@@ -73,6 +87,7 @@ export default class ProtectedBranchEdit {
ACCESS_LEVELS.MERGE,
gon.merge_access_levels,
'protected-branch-allowed-to-merge',
+ CHANGED_ALLOWED_TO_MERGE,
);
// Allowed to Push dropdown
@@ -81,11 +96,12 @@ export default class ProtectedBranchEdit {
ACCESS_LEVELS.PUSH,
gon.push_access_levels,
'protected-branch-allowed-to-push',
+ CHANGED_ALLOWED_TO_PUSH_AND_MERGE,
);
}
// eslint-disable-next-line max-params
- buildDropdown(selector, accessLevel, accessLevelsData, testId) {
+ buildDropdown(selector, accessLevel, accessLevelsData, testId, trackingEventName) {
const [el] = this.$wrap.find(`.${selector}`);
if (!el) return undefined;
@@ -103,7 +119,7 @@ export default class ProtectedBranchEdit {
});
dropdown.$on('select', (selected) => this.onSelectItems(accessLevel, selected));
- dropdown.$on('hidden', () => this.onDropdownHide());
+ dropdown.$on('hidden', () => this.onDropdownHide(trackingEventName));
this.initSelectedItems(dropdown, accessLevel);
return dropdown;
@@ -126,9 +142,9 @@ export default class ProtectedBranchEdit {
this.hasChanges = true;
}
- onDropdownHide() {
+ onDropdownHide(trackingEventName) {
if (!this.hasChanges) return;
- this.updatePermissions();
+ this.updatePermissions(trackingEventName);
}
updateProtectedBranch(formData, callback) {
@@ -142,13 +158,16 @@ export default class ProtectedBranchEdit {
});
}
- updatePermissions() {
+ updatePermissions(trackingEventName) {
const formData = Object.values(ACCESS_LEVELS).reduce((acc, level) => {
acc[`${level}_attributes`] = this.selectedItems[level];
return acc;
}, {});
this.updateProtectedBranch(formData, ({ data }) => {
this.hasChanges = false;
+ InternalEvents.trackEvent(trackingEventName, {
+ label: REPOSITORY_SETTINGS_LABEL,
+ });
Object.values(ACCESS_LEVELS).forEach((level) => {
this.setSelectedItemsToDropdown(data[level], level);
});
diff --git a/app/assets/javascripts/releases/components/app_index.vue b/app/assets/javascripts/releases/components/app_index.vue
index 835ab5afe56..74a8955d5f0 100644
--- a/app/assets/javascripts/releases/components/app_index.vue
+++ b/app/assets/javascripts/releases/components/app_index.vue
@@ -55,6 +55,7 @@ export default {
* quickly than `fullGraphqlResponse`, which allows the page to show
* meaningful content to the user much earlier.
*/
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
singleGraphqlResponse: {
query: allReleasesQuery,
// This trick only works when paginating _forward_.
@@ -76,6 +77,7 @@ export default {
this.singleRequestError = true;
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
fullGraphqlResponse: {
query: allReleasesQuery,
variables() {
@@ -94,6 +96,7 @@ export default {
});
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
isCatalogResource: {
query: getCiCatalogSettingsQuery,
variables() {
diff --git a/app/assets/javascripts/releases/components/app_show.vue b/app/assets/javascripts/releases/components/app_show.vue
index 111d9e232c5..4f5ce64cfa7 100644
--- a/app/assets/javascripts/releases/components/app_show.vue
+++ b/app/assets/javascripts/releases/components/app_show.vue
@@ -22,6 +22,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
release: {
query: oneReleaseQuery,
variables() {
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index 50fb1387ebc..dfddab00e65 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -39,6 +39,7 @@ export default {
explainCodeAvailable: { default: false },
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
projectInfo: {
query: projectInfoQuery,
variables() {
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue b/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
index cf77a5ca82c..f7b7f404a0f 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
@@ -26,6 +26,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
issuable: {
query() {
return assigneesQueries[this.issuableType].query;
diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
index ae93f43d9c6..1d3a5c71256 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue
@@ -50,6 +50,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
issuable: {
query: getMergeRequestReviewersQuery,
variables() {
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index 5ac28df9bfe..e406a9d50c7 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -110,6 +110,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
issuableTimeTracking: {
query() {
return timeTrackingQueries[this.issuableType].query;
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracking_report.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracking_report.vue
index dc6617ec92f..d66bc140f70 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracking_report.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracking_report.vue
@@ -89,6 +89,7 @@ export default {
return Boolean(this.timelogs);
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItem: {
query: workItemByIidQuery,
variables() {
diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue b/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue
index 5c4c79821f9..ade5ad74e69 100644
--- a/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue
+++ b/app/assets/javascripts/sidebar/components/todo_toggle/sidebar_todo_widget.vue
@@ -52,6 +52,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
todoId: {
query() {
return todoQueries[this.issuableType].query;
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_groups.vue b/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_groups.vue
index e7e3cbf4a47..70e61601fd4 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_groups.vue
+++ b/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_groups.vue
@@ -11,6 +11,7 @@ export default {
},
inject: ['groupsPath'],
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
frecentGroups: {
query: currentUserFrecentGroupsQuery,
},
diff --git a/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_projects.vue b/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_projects.vue
index 12a6776cc49..db8c5174f48 100644
--- a/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_projects.vue
+++ b/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_projects.vue
@@ -11,6 +11,7 @@ export default {
},
inject: ['projectsPath'],
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
frecentProjects: {
query: currentUserFrecentProjectsQuery,
},
diff --git a/app/assets/javascripts/terraform/components/terraform_list.vue b/app/assets/javascripts/terraform/components/terraform_list.vue
index 3629ecc2d87..e420d86d96c 100644
--- a/app/assets/javascripts/terraform/components/terraform_list.vue
+++ b/app/assets/javascripts/terraform/components/terraform_list.vue
@@ -8,6 +8,7 @@ import StatesTable from './states_table.vue';
export default {
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
states: {
query: getStatesQuery,
variables() {
diff --git a/app/assets/javascripts/token_access/components/token_permissions.vue b/app/assets/javascripts/token_access/components/token_permissions.vue
index 69e45843ccf..99248f35c7e 100644
--- a/app/assets/javascripts/token_access/components/token_permissions.vue
+++ b/app/assets/javascripts/token_access/components/token_permissions.vue
@@ -19,6 +19,7 @@ export default {
},
inject: ['fullPath'],
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
ciCdSettings: {
query: getCiJobTokenPermissionsQuery,
variables() {
diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/merge_checks.subscription.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/merge_checks.subscription.graphql
index 9cf2b9be405..54f4473c07c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/queries/merge_checks.subscription.graphql
+++ b/app/assets/javascripts/vue_merge_request_widget/queries/merge_checks.subscription.graphql
@@ -1,4 +1,4 @@
-subscription mergeChecksSubscrption($issuableId: IssuableID!) {
+subscription mergeChecksSubscription($issuableId: IssuableID!) {
mergeRequestMergeStatusUpdated(issuableId: $issuableId) {
... on MergeRequest {
id
diff --git a/app/assets/javascripts/vue_merge_request_widget/widgets/security_reports/mr_widget_security_reports.vue b/app/assets/javascripts/vue_merge_request_widget/widgets/security_reports/mr_widget_security_reports.vue
index 5ad44a4c9d9..c5f5d90ae3d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/widgets/security_reports/mr_widget_security_reports.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/widgets/security_reports/mr_widget_security_reports.vue
@@ -31,6 +31,7 @@ export default {
},
reportTypes: ['sast', 'secret_detection'],
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
reportArtifacts: {
query: securityReportMergeRequestDownloadPathsQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/create_work_item.vue b/app/assets/javascripts/work_items/components/create_work_item.vue
index 2bfa0541b94..22513185b3d 100644
--- a/app/assets/javascripts/work_items/components/create_work_item.vue
+++ b/app/assets/javascripts/work_items/components/create_work_item.vue
@@ -116,6 +116,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItem: {
query: workItemByIidQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_actions.vue b/app/assets/javascripts/work_items/components/work_item_actions.vue
index 6e9e53ce033..3d21f5757b1 100644
--- a/app/assets/javascripts/work_items/components/work_item_actions.vue
+++ b/app/assets/javascripts/work_items/components/work_item_actions.vue
@@ -180,6 +180,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItemTypes: {
query: namespaceWorkItemTypesQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_award_emoji.vue b/app/assets/javascripts/work_items/components/work_item_award_emoji.vue
index d72573c2048..dda119f2785 100644
--- a/app/assets/javascripts/work_items/components/work_item_award_emoji.vue
+++ b/app/assets/javascripts/work_items/components/work_item_award_emoji.vue
@@ -74,6 +74,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
awardEmoji: {
query: projectWorkItemAwardEmojiQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_created_updated.vue b/app/assets/javascripts/work_items/components/work_item_created_updated.vue
index e94428e4e58..f8c0b419350 100644
--- a/app/assets/javascripts/work_items/components/work_item_created_updated.vue
+++ b/app/assets/javascripts/work_items/components/work_item_created_updated.vue
@@ -64,6 +64,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItem: {
query: workItemByIidQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_crm_contacts.vue b/app/assets/javascripts/work_items/components/work_item_crm_contacts.vue
index ac7e08b5a7d..bd6c71ee886 100644
--- a/app/assets/javascripts/work_items/components/work_item_crm_contacts.vue
+++ b/app/assets/javascripts/work_items/components/work_item_crm_contacts.vue
@@ -105,6 +105,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
searchItems: {
query: getGroupContactsQuery,
variables() {
@@ -123,6 +124,7 @@ export default {
this.$emit('error', I18N_WORK_ITEM_ERROR_FETCHING_CRM_CONTACTS);
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItem: {
query: workItemByIidQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 5fb327dbe74..f8d37cf33bd 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -202,6 +202,7 @@ export default {
},
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
allowedChildTypes: {
query: getAllowedWorkItemChildTypes,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_development/work_item_development.vue b/app/assets/javascripts/work_items/components/work_item_development/work_item_development.vue
index 9356112ae69..906e3edb1e2 100644
--- a/app/assets/javascripts/work_items/components/work_item_development/work_item_development.vue
+++ b/app/assets/javascripts/work_items/components/work_item_development/work_item_development.vue
@@ -37,6 +37,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItem: {
query: workItemByIidQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_labels.vue b/app/assets/javascripts/work_items/components/work_item_labels.vue
index 12d6f48bca6..a3c8d9e28e1 100644
--- a/app/assets/javascripts/work_items/components/work_item_labels.vue
+++ b/app/assets/javascripts/work_items/components/work_item_labels.vue
@@ -170,6 +170,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItem: {
query: workItemByIidQuery,
variables() {
@@ -193,6 +194,7 @@ export default {
this.$emit('error', i18n.fetchError);
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
searchLabels: {
query() {
return this.isGroup ? groupLabelsQuery : projectLabelsQuery;
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue
index 6400f0aa1f8..07f391d7abb 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue
@@ -5,8 +5,10 @@ import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import { ESC_KEY } from '~/lib/utils/keys';
import { s__ } from '~/locale';
import { defaultSortableOptions, DRAG_DELAY } from '~/sortable/constants';
+import { sortableStart, sortableEnd } from '~/sortable/utils';
import { WORK_ITEM_TYPE_VALUE_OBJECTIVE, WORK_ITEM_TYPE_VALUE_EPIC } from '../../constants';
import { findHierarchyWidgets, findHierarchyWidgetChildren } from '../../utils';
@@ -87,6 +89,7 @@ export default {
currentClientY: 0,
childrenWorkItems: [],
toParent: {},
+ dragCancelled: false,
};
},
computed: {
@@ -123,6 +126,9 @@ export default {
return this.$apollo.provider.clients.defaultClient;
},
},
+ mounted() {
+ this.handleDocumentKeyup = this.handleKeyUp.bind(this);
+ },
methods: {
async removeChild(child) {
try {
@@ -279,8 +285,19 @@ export default {
parentId: toParentId,
};
},
+ handleDragOnStart() {
+ sortableStart();
+ this.dragCancelled = false;
+ // Attach listener to detect `ESC` key press to cancel drag.
+ document.addEventListener('keyup', this.handleDocumentKeyup);
+ },
async handleDragOnEnd(params) {
clearTimeout(this.toggleTimer);
+ sortableEnd();
+ document.removeEventListener('keyup', this.handleDocumentKeyup);
+ // Drag was cancelled, prevent reordering.
+ if (this.dragCancelled) return;
+
const { oldIndex, newIndex, from, to } = params;
const fromParentId = from.dataset.parentId;
const toParentId = to.dataset.parentId;
@@ -458,6 +475,16 @@ export default {
},
});
},
+ handleKeyUp(e) {
+ if (e.code === ESC_KEY) {
+ this.dragCancelled = true;
+ // Sortable.js internally listens for `mouseup` event on document
+ // to register drop event, see https://github.com/SortableJS/Sortable/blob/master/src/Sortable.js#L625
+ // We need to manually trigger it to simulate cancel behaviour as VueDraggable doesn't
+ // natively support it, see https://github.com/SortableJS/Vue.Draggable/issues/968.
+ document.dispatchEvent(new Event('mouseup'));
+ }
+ },
},
};
@@ -470,6 +497,7 @@ export default {
data-testid="child-items-container"
:class="{ 'sortable-container gl-cursor-grab': canReorder, 'disabled-content': disableList }"
:move="onMove"
+ @start="handleDragOnStart"
@end="handleDragOnEnd"
>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue
index ba2bdb73899..b7045b11d33 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue
@@ -35,6 +35,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItem: {
query: workItemByIidQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
index 72bc91de479..8e6e494c448 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
@@ -12,8 +12,14 @@ import {
WORK_ITEM_TYPE_ENUM_KEY_RESULT,
WORK_ITEM_TYPE_ENUM_EPIC,
CHILD_ITEMS_ANCHOR,
+ WORKITEM_TREE_SHOWLABELS_LOCALSTORAGEKEY,
} from '../../constants';
-import { findHierarchyWidgets, getDefaultHierarchyChildrenCount } from '../../utils';
+import {
+ findHierarchyWidgets,
+ getDefaultHierarchyChildrenCount,
+ saveShowLabelsToLocalStorage,
+ getShowLabelsFromLocalStorage,
+} from '../../utils';
import getWorkItemTreeQuery from '../../graphql/work_item_tree.query.graphql';
import WorkItemChildrenLoadMore from '../shared/work_item_children_load_more.vue';
import WorkItemMoreActions from '../shared/work_item_more_actions.vue';
@@ -93,13 +99,16 @@ export default {
formType: null,
childType: null,
widgetName: CHILD_ITEMS_ANCHOR,
+ defaultShowLabels: true,
showLabels: true,
fetchNextPageInProgress: false,
workItem: {},
disableContent: false,
+ showLabelsLocalStorageKey: WORKITEM_TREE_SHOWLABELS_LOCALSTORAGEKEY,
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
hierarchyWidget: {
query: getWorkItemTreeQuery,
variables() {
@@ -181,6 +190,12 @@ export default {
return this.showRolledUpWeight && this.rolledUpWeight !== null;
},
},
+ mounted() {
+ this.showLabels = getShowLabelsFromLocalStorage(
+ this.showLabelsLocalStorageKey,
+ this.defaultShowLabels,
+ );
+ },
methods: {
genericActionItems(workItem) {
const enumType = WORK_ITEM_TYPE_VALUE_MAP[workItem];
@@ -210,6 +225,10 @@ export default {
showModal({ event, child }) {
this.$emit('show-modal', { event, modalWorkItem: child });
},
+ toggleShowLabels() {
+ this.showLabels = !this.showLabels;
+ saveShowLabelsToLocalStorage(this.showLabelsLocalStorageKey, this.showLabels);
+ },
async fetchNextPage() {
if (this.hasNextPage && !this.fetchNextPageInProgress) {
this.fetchNextPageInProgress = true;
@@ -266,7 +285,7 @@ export default {
:work-item-type="workItemType"
:show-labels="showLabels"
show-view-roadmap-action
- @toggle-show-labels="showLabels = !showLabels"
+ @toggle-show-labels="toggleShowLabels"
/>
diff --git a/app/assets/javascripts/work_items/components/work_item_notes.vue b/app/assets/javascripts/work_items/components/work_item_notes.vue
index 92a933fcc63..5bc8a710fae 100644
--- a/app/assets/javascripts/work_items/components/work_item_notes.vue
+++ b/app/assets/javascripts/work_items/components/work_item_notes.vue
@@ -189,6 +189,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItemNotes: {
query: workItemNotesByIidQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_parent.vue b/app/assets/javascripts/work_items/components/work_item_parent.vue
index 307f13687fd..17b60a28a87 100644
--- a/app/assets/javascripts/work_items/components/work_item_parent.vue
+++ b/app/assets/javascripts/work_items/components/work_item_parent.vue
@@ -128,6 +128,7 @@ export default {
},
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workspaceWorkItems: {
query() {
// TODO: Remove the this.isIssue check once issues are migrated to work items
@@ -155,6 +156,7 @@ export default {
this.$emit('error', this.$options.i18n.workItemsFetchError);
},
},
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItemsByReference: {
query: workItemsByReferencesQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/components/work_item_prefetch.vue b/app/assets/javascripts/work_items/components/work_item_prefetch.vue
index bb01d9285ff..82bad5dfd7c 100644
--- a/app/assets/javascripts/work_items/components/work_item_prefetch.vue
+++ b/app/assets/javascripts/work_items/components/work_item_prefetch.vue
@@ -26,6 +26,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItem: {
query() {
return workItemByIidQuery;
diff --git a/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue b/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue
index c8d149d65e3..312c5327ab9 100644
--- a/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue
+++ b/app/assets/javascripts/work_items/components/work_item_relationships/work_item_relationships.vue
@@ -8,8 +8,16 @@ import CrudComponent from '~/vue_shared/components/crud_component.vue';
import workItemLinkedItemsQuery from '../../graphql/work_item_linked_items.query.graphql';
import removeLinkedItemsMutation from '../../graphql/remove_linked_items.mutation.graphql';
-import { findLinkedItemsWidget } from '../../utils';
-import { LINKED_CATEGORIES_MAP, LINKED_ITEMS_ANCHOR } from '../../constants';
+import {
+ findLinkedItemsWidget,
+ saveShowLabelsToLocalStorage,
+ getShowLabelsFromLocalStorage,
+} from '../../utils';
+import {
+ LINKED_CATEGORIES_MAP,
+ LINKED_ITEMS_ANCHOR,
+ WORKITEM_RELATIONSHIPS_SHOWLABELS_LOCALSTORAGEKEY,
+} from '../../constants';
import WorkItemMoreActions from '../shared/work_item_more_actions.vue';
import WorkItemRelationshipList from './work_item_relationship_list.vue';
@@ -102,8 +110,10 @@ export default {
linksIsBlockedBy: [],
linksBlocks: [],
widgetName: LINKED_ITEMS_ANCHOR,
+ defaultShowLabels: true,
showLabels: true,
linkedWorkItems: [],
+ showLabelsLocalStorageKey: WORKITEM_RELATIONSHIPS_SHOWLABELS_LOCALSTORAGEKEY,
};
},
computed: {
@@ -120,6 +130,12 @@ export default {
return !this.error && this.linkedWorkItems.length === 0;
},
},
+ mounted() {
+ this.showLabels = getShowLabelsFromLocalStorage(
+ this.showLabelsLocalStorageKey,
+ this.defaultShowLabels,
+ );
+ },
methods: {
showLinkItemForm() {
this.$refs.widget.showForm();
@@ -127,6 +143,10 @@ export default {
hideLinkItemForm() {
this.$refs.widget.hideForm();
},
+ toggleShowLabels() {
+ this.showLabels = !this.showLabels;
+ saveShowLabelsToLocalStorage(this.showLabelsLocalStorageKey, this.showLabels);
+ },
async removeLinkedItem(linkedItem) {
try {
const {
@@ -226,7 +246,7 @@ export default {
:work-item-type="workItemType"
:show-labels="showLabels"
:show-view-roadmap-action="false"
- @toggle-show-labels="showLabels = !showLabels"
+ @toggle-show-labels="toggleShowLabels"
/>
diff --git a/app/assets/javascripts/work_items/components/work_item_state_toggle.vue b/app/assets/javascripts/work_items/components/work_item_state_toggle.vue
index 201f1e4701d..4a5afb230da 100644
--- a/app/assets/javascripts/work_items/components/work_item_state_toggle.vue
+++ b/app/assets/javascripts/work_items/components/work_item_state_toggle.vue
@@ -66,6 +66,7 @@ export default {
};
},
apollo: {
+ // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
workItem: {
query: workItemByIidQuery,
variables() {
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index c70e25a4ee7..593b44187b4 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -362,3 +362,7 @@ export const ROUTES = {
new: 'new',
design: 'design',
};
+
+export const WORKITEM_LINKS_SHOWLABELS_LOCALSTORAGEKEY = 'workItemLinks.showLabels';
+export const WORKITEM_TREE_SHOWLABELS_LOCALSTORAGEKEY = 'workItemTree.showLabels';
+export const WORKITEM_RELATIONSHIPS_SHOWLABELS_LOCALSTORAGEKEY = 'workItemRelationships.showLabels';
diff --git a/app/assets/javascripts/work_items/graphql/add_linked_items.mutation.graphql b/app/assets/javascripts/work_items/graphql/add_linked_items.mutation.graphql
index 7824289b427..2d1e7d16179 100644
--- a/app/assets/javascripts/work_items/graphql/add_linked_items.mutation.graphql
+++ b/app/assets/javascripts/work_items/graphql/add_linked_items.mutation.graphql
@@ -1,6 +1,4 @@
#import "./work_item.fragment.graphql"
-#import "./work_item_metadata_widgets.fragment.graphql"
-#import "./work_item_metadata_widgets_extras.fragment.graphql"
#import "./work_item_linked_items.fragment.graphql"
mutation addLinkedItems($input: WorkItemAddLinkedItemsInput!) {
diff --git a/app/assets/javascripts/work_items/graphql/work_item_linked_items.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item_linked_items.fragment.graphql
index 0bb0aab452a..ded0145f224 100644
--- a/app/assets/javascripts/work_items/graphql/work_item_linked_items.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_item_linked_items.fragment.graphql
@@ -1,5 +1,5 @@
-#import "./work_item_metadata_widgets.fragment.graphql"
-#import "./work_item_metadata_widgets_extras.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/work_item_metadata_widgets.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/work_item_metadata_widgets_extras.fragment.graphql"
fragment WorkItemLinkedItemsFragment on WorkItem {
widgets {
diff --git a/app/assets/javascripts/work_items/utils.js b/app/assets/javascripts/work_items/utils.js
index a258540efcc..eeec71e4656 100644
--- a/app/assets/javascripts/work_items/utils.js
+++ b/app/assets/javascripts/work_items/utils.js
@@ -1,5 +1,8 @@
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { queryToObject } from '~/lib/utils/url_utility';
+import AccessorUtilities from '~/lib/utils/accessor';
+import { parseBoolean } from '~/lib/utils/common_utils';
+
import {
NEW_WORK_ITEM_IID,
WIDGET_TYPE_ASSIGNEES,
@@ -220,3 +223,16 @@ export const newWorkItemId = (workItemType) => {
const workItemTypeLowercase = workItemType.split(' ').join('-').toLowerCase();
return `${NEW_WORK_ITEM_GID}-${workItemTypeLowercase}`;
};
+
+export const saveShowLabelsToLocalStorage = (showLabelsLocalStorageKey, value) => {
+ if (AccessorUtilities.canUseLocalStorage()) {
+ localStorage.setItem(showLabelsLocalStorageKey, value);
+ }
+};
+
+export const getShowLabelsFromLocalStorage = (showLabelsLocalStorageKey, defaultValue = true) => {
+ if (AccessorUtilities.canUseLocalStorage()) {
+ return parseBoolean(localStorage.getItem(showLabelsLocalStorageKey) ?? defaultValue);
+ }
+ return null;
+};
diff --git a/app/controllers/import/source_users_controller.rb b/app/controllers/import/source_users_controller.rb
index 63fef11a685..ded2e9f3bfc 100644
--- a/app/controllers/import/source_users_controller.rb
+++ b/app/controllers/import/source_users_controller.rb
@@ -5,8 +5,7 @@ module Import
prepend_before_action :check_feature_flag!
before_action :source_user
- before_action :check_current_user_matches_invite!
- before_action :check_source_user_status!
+ before_action :check_source_user_valid!
respond_to :html
feature_category :importers
@@ -37,16 +36,10 @@ module Import
private
- def check_source_user_status!
- return if source_user.awaiting_approval?
+ def check_source_user_valid!
+ return if source_user.awaiting_approval? && current_user_matches_invite?
- redirect_to(dashboard_groups_path, alert: s_('UserMapping|The invitation is no longer valid.'))
- end
-
- def check_current_user_matches_invite!
- return if current_user_matches_invite?
-
- flash[:raw] = banner('cancel_invite')
+ flash[:raw] = banner('invalid_invite')
redirect_to(dashboard_groups_path)
end
diff --git a/app/graphql/types/work_items/widgets/hierarchy_type.rb b/app/graphql/types/work_items/widgets/hierarchy_type.rb
index d31debc1a90..398d103b227 100644
--- a/app/graphql/types/work_items/widgets/hierarchy_type.rb
+++ b/app/graphql/types/work_items/widgets/hierarchy_type.rb
@@ -36,6 +36,10 @@ module Types
null: false, description: 'Counts of descendant work items by work item type and state.',
alpha: { milestone: '17.3' }
+ field :depth_limit_reached_by_type, [Types::WorkItems::WorkItemTypeDepthLimitReachedByType],
+ null: false, description: 'Depth limit reached by allowed work item type.',
+ alpha: { milestone: '17.4' }
+
# rubocop: disable CodeReuse/ActiveRecord
def has_children?
BatchLoader::GraphQL.for(object.work_item.id).batch(default_value: false) do |ids, loader|
diff --git a/app/graphql/types/work_items/work_item_type_depth_limit_reached_by_type.rb b/app/graphql/types/work_items/work_item_type_depth_limit_reached_by_type.rb
new file mode 100644
index 00000000000..9838ddf45e1
--- /dev/null
+++ b/app/graphql/types/work_items/work_item_type_depth_limit_reached_by_type.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ # rubocop: disable Graphql/AuthorizeTypes -- Parent node applies authorization
+ class WorkItemTypeDepthLimitReachedByType < BaseObject
+ graphql_name 'WorkItemTypeDepthLimitReachedByType'
+ description 'Represents Depth limit reached for the allowed work item type.'
+
+ field :work_item_type, Types::WorkItems::TypeType, null: false,
+ description: 'Work item type.'
+
+ field :depth_limit_reached, GraphQL::Types::Boolean,
+ null: false,
+ description: 'Indicates if maximum allowed depth has been reached for the descendant type.'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 4fbe3f597b0..e05f4e84d0f 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -227,6 +227,10 @@ module Ci
.or(with_job_artifacts.where(project_id: project_id, job_artifacts: { file_type: 'dotenv' })).distinct
end
+ scope :with_pipeline_source_type, ->(pipeline_source_type) { joins(:pipeline).where(pipeline: { source: pipeline_source_type }) }
+ scope :created_after, ->(time) { where(arel_table[:created_at].gt(time)) }
+ scope :updated_after, ->(time) { where(arel_table[:updated_at].gt(time)) }
+
add_authentication_token_field :token,
encrypted: :required,
format_with_prefix: :prefix_and_partition_for_token
diff --git a/app/models/concerns/resolvable_note.rb b/app/models/concerns/resolvable_note.rb
index 23abc5d5c22..d19e8f9a660 100644
--- a/app/models/concerns/resolvable_note.rb
+++ b/app/models/concerns/resolvable_note.rb
@@ -12,7 +12,7 @@ module ResolvableNote
validates :resolved_by, presence: true, if: :resolved?
# Keep this scope in sync with `#potentially_resolvable?`
- scope :potentially_resolvable, -> { where(type: RESOLVABLE_TYPES).where(noteable_type: Noteable.resolvable_types) }
+ scope :potentially_resolvable, -> { where(type: resolvable_types).where(noteable_type: Noteable.resolvable_types) }
# Keep this scope in sync with `#resolvable?`
scope :resolvable, -> { potentially_resolvable.user }
@@ -31,11 +31,16 @@ module ResolvableNote
def unresolve!
resolved.update_all(updated_at: Time.current, resolved_at: nil, resolved_by_id: nil)
end
+
+ # overridden on EE
+ def resolvable_types
+ RESOLVABLE_TYPES
+ end
end
# Keep this method in sync with the `potentially_resolvable` scope
def potentially_resolvable?
- RESOLVABLE_TYPES.include?(self.class.name) && noteable&.supports_resolvable_notes?
+ self.class.resolvable_types.include?(self.class.name) && noteable&.supports_resolvable_notes?
end
# Keep this method in sync with the `resolvable` scope
@@ -88,3 +93,5 @@ module ResolvableNote
unresolve_without_save && save!
end
end
+
+ResolvableNote::ClassMethods.prepend_mod_with('ResolvableNote::ClassMethods')
diff --git a/app/models/import/source_user.rb b/app/models/import/source_user.rb
index 15595685e9e..57d6e6ad09b 100644
--- a/app/models/import/source_user.rb
+++ b/app/models/import/source_user.rb
@@ -21,7 +21,9 @@ module Import
validates :namespace_id, :import_type, :source_hostname, :source_user_identifier, :status, presence: true
validates :placeholder_user_id, presence: true, unless: :completed?
- validates :reassign_to_user_id, presence: true, if: -> { reassignment_in_progress? || completed? }
+ validates :reassign_to_user_id, presence: true, if: -> {
+ awaiting_approval? || reassignment_in_progress? || completed?
+ }
validates :reassign_to_user_id, absence: true, if: -> { pending_reassignment? || keep_as_placeholder? }
validates :reassign_to_user_id, uniqueness: {
scope: [:namespace_id, :source_hostname, :import_type],
diff --git a/app/models/work_item.rb b/app/models/work_item.rb
index cde805f97fa..6fe67ec752c 100644
--- a/app/models/work_item.rb
+++ b/app/models/work_item.rb
@@ -215,6 +215,20 @@ class WorkItem < Issue
dates_source&.start_date || read_attribute(:start_date)
end
+ def max_depth_reached?(child_type)
+ restriction = ::WorkItems::HierarchyRestriction.find_by_parent_type_id_and_child_type_id(
+ work_item_type_id,
+ child_type.id
+ )
+ return false unless restriction&.maximum_depth
+
+ if work_item_type_id == child_type.id
+ same_type_base_and_ancestors.count >= restriction.maximum_depth
+ else
+ hierarchy(different_type_id: child_type.id).base_and_ancestors.count >= restriction.maximum_depth
+ end
+ end
+
private
override :parent_link_confidentiality
@@ -237,6 +251,7 @@ class WorkItem < Issue
def hierarchy(options = {})
base = self.class.where(id: id)
base = base.where(work_item_type_id: work_item_type_id) if options[:same_type]
+ base = base.where(work_item_type_id: options[:different_type_id]) if options[:different_type_id]
::Gitlab::WorkItems::WorkItemHierarchy.new(base, options: options)
end
diff --git a/app/models/work_items/widgets/hierarchy.rb b/app/models/work_items/widgets/hierarchy.rb
index d16a02bb127..ff5de8f0bb0 100644
--- a/app/models/work_items/widgets/hierarchy.rb
+++ b/app/models/work_items/widgets/hierarchy.rb
@@ -27,6 +27,12 @@ module WorkItems
end
end
+ def depth_limit_reached_by_type
+ work_item.work_item_type.descendant_types.map do |child_type|
+ { work_item_type: child_type, depth_limit_reached: work_item.max_depth_reached?(child_type) }
+ end
+ end
+
def self.quick_action_commands
[:set_parent, :add_child, :remove_parent, :remove_child]
end
diff --git a/app/views/import/source_users/_cancel_invite.html.haml b/app/views/import/source_users/_invalid_invite.html.haml
similarity index 64%
rename from app/views/import/source_users/_cancel_invite.html.haml
rename to app/views/import/source_users/_invalid_invite.html.haml
index cd67b2f559d..1e6760fa0bd 100644
--- a/app/views/import/source_users/_cancel_invite.html.haml
+++ b/app/views/import/source_users/_invalid_invite.html.haml
@@ -1,6 +1,6 @@
-= render Pajamas::AlertComponent.new(variant: :danger, title: s_('UserMapping|Reassignment cancelled'), alert_options: { class: 'gl-mt-4' }) do |c|
+= render Pajamas::AlertComponent.new(variant: :danger, title: s_('UserMapping|Reassignment not available'), alert_options: { class: 'gl-mt-4' }) do |c|
- c.with_body do
- = s_('UserMapping|The reassignment has been cancelled by the group owner.')
+ = s_('UserMapping|You might have already accepted or rejected the reassignment, or it might have been canceled by the group owner.')
- c.with_actions do
= render Pajamas::ButtonComponent.new(variant: :default,
href: help_page_path('user/project/import/index', anchor: 'accept-contribution-reassignment'),
diff --git a/app/views/projects/_empty_git_config.html.haml b/app/views/projects/_empty_git_config.html.haml
new file mode 100644
index 00000000000..0d73a0533b6
--- /dev/null
+++ b/app/views/projects/_empty_git_config.html.haml
@@ -0,0 +1,11 @@
+- if scope == 'local'
+ %h4.gl-text-base= _('Git local setup')
+ %p= _('Configure your Git identity locally to use it only for this project:')
+- if scope == 'global'
+ %h4.gl-text-base= _('Git global setup')
+ %p= _('Configure your Git identity globally to use it for all current and future projects on your machine:')
+
+%pre.code.js-syntax-highlight
+ :preserve
+ git config --#{scope} user.name "#{h git_user_name}"
+ git config --#{scope} user.email "#{h git_user_email}"
diff --git a/app/views/projects/_empty_git_instructions.html.haml b/app/views/projects/_empty_git_instructions.html.haml
new file mode 100644
index 00000000000..33d2d9cb866
--- /dev/null
+++ b/app/views/projects/_empty_git_instructions.html.haml
@@ -0,0 +1,46 @@
+- default_branch_name = @project.default_branch_or_main
+- escaped_default_branch_name = default_branch_name.shellescape
+
+- git_push_target = ''
+- if protocol == 'ssh'
+ - ssh_doc = link_to('', help_page_path('user/ssh'), target: '_blank', rel: 'noopener noreferrer')
+ %p.gl-mt-2= safe_format(_('%{link}How to use SSH keys%{link_end}?'), tag_pair(ssh_doc, :link, :link_end))
+ - git_push_target = content_tag(:span, ssh_clone_url_to_repo(@project), class: 'js-clone')
+- if protocol == 'https'
+ - git_push_target = content_tag(:span, http_clone_url_to_repo(@project), class: 'js-clone')
+- if protocol == 'kerberos'
+ - git_push_target = content_tag(:span, @project.kerberos_url_to_repo, class: 'js-clone')
+
+%h4.gl-text-base= _('Create a new repository')
+%pre.code.js-syntax-highlight
+ :preserve
+ git clone #{git_push_target}
+ cd #{h @project.path}
+ git switch --create #{h escaped_default_branch_name}
+ touch README.md
+ git add README.md
+ git commit -m "add README"
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push --set-upstream origin #{h escaped_default_branch_name}
+%h4.gl-text-base= _('Push an existing folder')
+%pre.code.js-syntax-highlight
+ :preserve
+ cd existing_folder
+ git init --initial-branch=#{h escaped_default_branch_name}
+ git remote add origin #{git_push_target}
+ git add .
+ git commit -m "Initial commit"
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push --set-upstream origin #{h escaped_default_branch_name}
+%h4.gl-text-base= _('Push an existing Git repository')
+%pre.code.js-syntax-highlight
+ :preserve
+ cd existing_repo
+ git remote rename origin old-origin
+ git remote add origin #{git_push_target}
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push --set-upstream origin --all
+ git push --set-upstream origin --tags
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index ddebefa361d..f706a532477 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,8 +1,6 @@
- add_page_specific_style 'page_bundles/projects'
- add_page_specific_style 'page_bundles/project'
-- default_branch_name = @project.default_branch_or_main
-- escaped_default_branch_name = default_branch_name.shellescape
- @skip_current_level_breadcrumb = true
= render partial: 'flash_messages', locals: { project: @project }
@@ -23,58 +21,50 @@
= render Pajamas::CardComponent.new(card_options: { class: 'gl-mb-5' }, body_options: { class: 'gl-bg-gray-10 gl-p-5 gl-rounded-base' }) do |c|
- c.with_body do
- %h4.gl-text-lg.gl-my-0= _('The repository for this project is empty')
+ %h2.gl-text-lg.gl-my-0= _('The repository for this project is empty')
- if @project.can_current_user_push_code?
- %p.gl-m-0.gl-mt-2.gl-text-secondary= _('You can get started by cloning the repository or start adding files to it with one of the following options.')
+ %p.gl-m-0.gl-mt-2.gl-text-secondary= _('To get started, clone the repository or upload some files.')
- if can?(current_user, :push_code, @project)
= render Pajamas::CardComponent.new(header_options: { class: 'gl-py-4' }) do |c|
- c.with_header do
- %h5.gl-text-lg.gl-m-0= _('Command line instructions')
+ %h2.gl-text-lg.gl-m-0= _('Command line instructions')
- c.with_body do
%p
= _('You can also upload existing files from your computer using the instructions below.')
.git-empty.js-git-empty
- %h5= _('Git global setup')
- %pre.js-syntax-highlight
- :preserve
- git config --global user.name "#{h git_user_name}"
- git config --global user.email "#{h git_user_email}"
-
- %h5= _('Create a new repository')
- %pre.js-syntax-highlight
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- cd #{h @project.path}
- git switch --create #{h escaped_default_branch_name}
- touch README.md
- git add README.md
- git commit -m "add README"
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push --set-upstream origin #{h escaped_default_branch_name }
-
- %h5= _('Push an existing folder')
- %pre.js-syntax-highlight
- :preserve
- cd existing_folder
- git init --initial-branch=#{h escaped_default_branch_name}
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- git add .
- git commit -m "Initial commit"
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push --set-upstream origin #{h escaped_default_branch_name }
-
- %h5= _('Push an existing Git repository')
- %pre.js-syntax-highlight
- :preserve
- cd existing_repo
- git remote rename origin old-origin
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push --set-upstream origin --all
- git push --set-upstream origin --tags
+ %h3.gl-text-lg= _('Configure your Git identity')
+ - git_get_started_doc = link_to('', help_page_path('topics/git/get_started'), target: '_blank', rel: 'noopener noreferrer')
+ - git_config_doc = link_to('', help_page_path('topics/git/how_to_install_git/index', anchor: 'configure-git'), target: '_blank', rel: 'noopener noreferrer')
+ %p= safe_format(_("%{get_started}Get started with Git%{get_started_end} and learn %{git_config}how to configure it%{git_config_end}."), tag_pair(git_get_started_doc, :get_started, :get_started_end), tag_pair(git_config_doc, :git_config, :git_config_end))
+ .scrolling-tabs-container.inner-page-scroll-tabs
+ = gl_tabs_nav({ class: 'js-configure-git-tabs' }) do
+ = gl_tab_link_to '#', item_active: true, 'aria-controls': 'local' do
+ = _('Local')
+ = gl_tab_link_to '#', 'aria-controls': 'global' do
+ = _('Global')
+ .tab-content
+ .tab-pane.active#local
+ = render partial: 'empty_git_config', locals: { scope: 'local' }
+ .tab-pane#global
+ = render partial: 'empty_git_config', locals: { scope: 'global' }
+ %h3.gl-text-lg= _('Add files')
+ - if ssh_enabled?
+ %p= _("Push files to this repository using SSH or HTTPS. If you're unsure, we recommend SSH.")
+ .scrolling-tabs-container.inner-page-scroll-tabs
+ = gl_tabs_nav({ class: 'js-empty-project-tabs' }) do
+ = gl_tab_link_to '#', item_active: true, 'aria-controls': 'ssh' do
+ = _('SSH')
+ = gl_tab_link_to '#', 'aria-controls': 'https' do
+ = _('HTTPS')
+ = render_if_exists 'projects/empty_kerberos_tab_link'
+ .tab-content
+ .tab-pane.active#ssh
+ = render partial: 'empty_git_instructions', locals: { protocol: 'ssh' }
+ .tab-pane#https
+ = render partial: 'empty_git_instructions', locals: { protocol: 'https' }
+ = render_if_exists 'projects/empty_kerberos_pane'
+ - else
+ = render partial: 'empty_git_instructions', locals: { protocol: 'https' }
.project-page-layout-sidebar.js-show-on-project-root.gl-mt-5
= render "sidebar"
diff --git a/app/views/protected_branches/shared/_create_protected_branch.html.haml b/app/views/protected_branches/shared/_create_protected_branch.html.haml
index 474484abf65..4efb4196375 100644
--- a/app/views/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/protected_branches/shared/_create_protected_branch.html.haml
@@ -43,6 +43,6 @@
- force_push_link_start = ''.html_safe % { url: force_push_docs_url }
= (s_("ProtectedBranch|Allow all users with push access to %{tag_start}force push%{tag_end}.") % { tag_start: force_push_link_start, tag_end: '' }).html_safe
= render_if_exists 'protected_branches/ee/code_owner_approval_form', f: f, protected_branch_entity: protected_branch_entity
- = f.submit s_('ProtectedBranch|Protect'), disabled: true, data: { testid: 'protect-button' }, pajamas_button: true
+ = f.submit s_('ProtectedBranch|Protect'), disabled: true, data: { testid: 'protect-button', event_tracking: 'protect_branch', event_label: 'repository_settings' }, pajamas_button: true
= render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do
= _('Cancel')
diff --git a/app/views/protected_branches/shared/_protected_branch.html.haml b/app/views/protected_branches/shared/_protected_branch.html.haml
index bfd11457419..a655800b684 100644
--- a/app/views/protected_branches/shared/_protected_branch.html.haml
+++ b/app/views/protected_branches/shared/_protected_branch.html.haml
@@ -35,6 +35,6 @@
href: [protected_branch_entity, protected_branch, { update_section: 'js-protected-branches-settings' }],
method: :delete,
disabled: local_assigns[:protected_from_deletion],
- button_options: { update_section: 'js-protected-branches-settings', aria: { label: s_('ProtectedBranch|Unprotect branch') }, data: { confirm: s_('ProtectedBranch|Branch will be writable for developers. Are you sure?'), confirm_btn_variant: 'danger' } },
+ button_options: { update_section: 'js-protected-branches-settings', aria: { label: s_('ProtectedBranch|Unprotect branch') }, data: { confirm: s_('ProtectedBranch|Branch will be writable for developers. Are you sure?'), confirm_btn_variant: 'danger', tracking_event_name: 'unprotect_branch', tracking_event_label: 'repository_settings' } },
category: :secondary) do
= s_('ProtectedBranch|Unprotect')
diff --git a/app/views/protected_branches/shared/_update_protected_branch.html.haml b/app/views/protected_branches/shared/_update_protected_branch.html.haml
index e9484d86bb3..4b2400a558b 100644
--- a/app/views/protected_branches/shared/_update_protected_branch.html.haml
+++ b/app/views/protected_branches/shared/_update_protected_branch.html.haml
@@ -15,6 +15,7 @@
%td
= render Pajamas::ToggleComponent.new(classes: 'js-force-push-toggle',
+ data: { event_tracking: 'change_allow_force_push', event_label: 'repository_settings' },
label: s_("ProtectedBranch|Toggle allowed to force push"),
is_checked: protected_branch.allow_force_push,
label_position: :hidden)
diff --git a/config/bounded_contexts.yml b/config/bounded_contexts.yml
index 2c17b8b5780..40372cebdd0 100644
--- a/config/bounded_contexts.yml
+++ b/config/bounded_contexts.yml
@@ -277,6 +277,7 @@ domains:
- security_policy_management
- software_composition_analysis
- static_application_security_testing
+ - secret_detection
ServiceDesk:
description:
diff --git a/config/feature_flags/gitlab_com_derisk/omniauth_validate_email_length.yml b/config/feature_flags/gitlab_com_derisk/omniauth_validate_email_length.yml
new file mode 100644
index 00000000000..c800200979d
--- /dev/null
+++ b/config/feature_flags/gitlab_com_derisk/omniauth_validate_email_length.yml
@@ -0,0 +1,9 @@
+---
+name: omniauth_validate_email_length
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/460714
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161205
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/475077
+milestone: '17.4'
+group: group::authentication
+type: gitlab_com_derisk
+default_enabled: false
diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml
index 89d68140876..5c0d04d2c50 100644
--- a/config/gitlab_loose_foreign_keys.yml
+++ b/config/gitlab_loose_foreign_keys.yml
@@ -391,6 +391,10 @@ project_authorizations:
- table: users
column: user_id
on_delete: async_delete
+project_security_exclusions:
+ - table: projects
+ column: project_id
+ on_delete: async_delete
projects:
- table: organizations
column: organization_id
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index aa96951cdaa..d074b1ae36e 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -933,6 +933,9 @@ Gitlab.ee do
Settings.cron_jobs['observability_alert_query_worker'] ||= {}
Settings.cron_jobs['observability_alert_query_worker']['cron'] ||= '* * * * *'
Settings.cron_jobs['observability_alert_query_worker']['job_class'] = 'Observability::AlertQueryWorker'
+ Settings.cron_jobs['report_security_policies_metrics_worker.rb'] ||= {}
+ Settings.cron_jobs['report_security_policies_metrics_worker.rb']['cron'] ||= '*/1 * * * *'
+ Settings.cron_jobs['report_security_policies_metrics_worker.rb']['job_class'] = 'Security::Policies::ReportSecurityPoliciesMetricsWorker'
Gitlab.com do
Settings.cron_jobs['disable_legacy_open_source_license_for_inactive_projects'] ||= {}
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 86a298fe3f8..c156d54e41f 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -38,7 +38,7 @@ strict_args_mode = Gitlab.dev_or_test_env? ? :warn : false
Sidekiq.strict_args!(strict_args_mode)
# Perform version check before configuring server with the custome scheduled job enqueue class
-unless Gem::Version.new(Sidekiq::VERSION) == Gem::Version.new('7.3.1')
+unless Gem::Version.new(Sidekiq::VERSION) == Gem::Version.new('7.2.4')
raise 'New version of Sidekiq detected, please either update the version for this check ' \
'and update Gitlab::SidekiqSharding::ScheduledEnq is compatible.'
end
diff --git a/db/docs/group_wiki_repository_states.yml b/db/docs/group_wiki_repository_states.yml
index cf7a8d20260..75e16d7e5cb 100644
--- a/db/docs/group_wiki_repository_states.yml
+++ b/db/docs/group_wiki_repository_states.yml
@@ -8,14 +8,13 @@ feature_categories:
classes:
- Geo::GroupWikiRepositoryState
gitlab_schema: gitlab_main_cell
-sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/465224
-# desired_sharding_key_spec.rb assumes the parent table's primary key is `id`
-# desired_sharding_key:
-# group_id:
-# references: namespaces
-# backfill_via:
-# parent:
-# foreign_key: group_wiki_repository_id
-# table: group_wiki_repositories
-# sharding_key: group_id
-# belongs_to: group_wiki_repository
+desired_sharding_key:
+ group_id:
+ references: namespaces
+ backfill_via:
+ parent:
+ foreign_key: group_wiki_repository_id
+ table: group_wiki_repositories
+ table_primary_key: group_id
+ sharding_key: group_id
+ belongs_to: group_wiki_repository
diff --git a/db/docs/p_ci_pipeline_variables.yml b/db/docs/p_ci_pipeline_variables.yml
index 97063637cb6..7f801355b5c 100644
--- a/db/docs/p_ci_pipeline_variables.yml
+++ b/db/docs/p_ci_pipeline_variables.yml
@@ -14,7 +14,7 @@ desired_sharding_key:
backfill_via:
parent:
foreign_key: pipeline_id
- table: ci_pipelines
+ table: p_ci_pipelines
sharding_key: project_id
belongs_to: pipeline
foreign_key_name: fk_f29c5f4380_p
diff --git a/db/docs/project_security_exclusions.yml b/db/docs/project_security_exclusions.yml
new file mode 100644
index 00000000000..3329a197c2b
--- /dev/null
+++ b/db/docs/project_security_exclusions.yml
@@ -0,0 +1,12 @@
+---
+table_name: project_security_exclusions
+classes:
+- Security::ProjectSecurityExclusion
+feature_categories:
+- secret_detection
+description: Store project-level exclusions to exclude from security analyzers scanning
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/163732
+milestone: '17.4'
+gitlab_schema: gitlab_sec
+sharding_key:
+ project_id: projects
diff --git a/db/migrate/20240828081719_create_project_security_exclusions.rb b/db/migrate/20240828081719_create_project_security_exclusions.rb
new file mode 100644
index 00000000000..e8806bbe7bd
--- /dev/null
+++ b/db/migrate/20240828081719_create_project_security_exclusions.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CreateProjectSecurityExclusions < Gitlab::Database::Migration[2.2]
+ milestone '17.4'
+
+ def change
+ create_table :project_security_exclusions do |t|
+ t.bigint :project_id, index: true, null: false
+ t.timestamps_with_timezone null: false
+ t.integer :scanner, limit: 2, null: false
+ t.integer :type, limit: 2, null: false
+ t.boolean :active, null: false, default: true
+ t.text :description, limit: 255
+ t.text :value, limit: 255, null: false
+ end
+ end
+end
diff --git a/db/migrate/20240830105808_add_index_to_subscription_add_on_purchases_started_at_and_expires_on.rb b/db/migrate/20240830105808_add_index_to_subscription_add_on_purchases_started_at_and_expires_on.rb
new file mode 100644
index 00000000000..e9c58423a6d
--- /dev/null
+++ b/db/migrate/20240830105808_add_index_to_subscription_add_on_purchases_started_at_and_expires_on.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToSubscriptionAddOnPurchasesStartedAtAndExpiresOn < Gitlab::Database::Migration[2.2]
+ disable_ddl_transaction!
+
+ milestone '17.4'
+
+ INDEX_NAME = 'idx_subscription_add_on_purchases_on_started_on_and_expires_on'
+
+ def up
+ add_concurrent_index :subscription_add_on_purchases, [:started_at, :expires_on], name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index :subscription_add_on_purchases, [:started_at, :expires_on], name: INDEX_NAME
+ end
+end
diff --git a/db/migrate/20240902032325_add_personal_namespace_id_to_events.rb b/db/migrate/20240902032325_add_personal_namespace_id_to_events.rb
new file mode 100644
index 00000000000..990c68e9d7f
--- /dev/null
+++ b/db/migrate/20240902032325_add_personal_namespace_id_to_events.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddPersonalNamespaceIdToEvents < Gitlab::Database::Migration[2.2]
+ milestone '17.4'
+
+ def up
+ add_column :events, :personal_namespace_id, :bigint
+ end
+
+ def down
+ remove_column :events, :personal_namespace_id
+ end
+end
diff --git a/db/post_migrate/20240902014331_sync_fk_referencing_p_ci_pipelines.rb b/db/post_migrate/20240902014331_sync_fk_referencing_p_ci_pipelines.rb
new file mode 100644
index 00000000000..75b339e8727
--- /dev/null
+++ b/db/post_migrate/20240902014331_sync_fk_referencing_p_ci_pipelines.rb
@@ -0,0 +1,186 @@
+# frozen_string_literal: true
+
+class SyncFkReferencingPCiPipelines < Gitlab::Database::Migration[2.2]
+ include Gitlab::Database::PartitioningMigrationHelpers
+
+ milestone '17.4'
+ disable_ddl_transaction!
+
+ FOREIGN_KEYS = [
+ {
+ source_table: :ci_pipeline_chat_data,
+ name: :fk_64ebfab6b3_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ },
+ {
+ source_table: :ci_sources_pipelines,
+ name: :fk_d4e29af7d7_p_tmp,
+ column: [:source_partition_id, :source_pipeline_id]
+ },
+ {
+ source_table: :ci_sources_pipelines,
+ name: :fk_e1bad85861_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ },
+ {
+ source_table: :ci_sources_projects,
+ name: :fk_rails_10a1eb379a_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ },
+ {
+ source_table: :ci_pipeline_metadata,
+ name: :fk_rails_50c1e9ea10_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ },
+ {
+ source_table: :ci_pipeline_messages,
+ name: :fk_rails_8d3b04e3e1_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ },
+ {
+ source_table: :ci_pipelines_config,
+ name: :fk_rails_906c9a2533_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ },
+ {
+ source_table: :ci_pipeline_artifacts,
+ name: :fk_rails_a9e811a466_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ },
+ {
+ source_table: :ci_daily_build_group_report_results,
+ name: :fk_rails_ee072d13b3_p_tmp,
+ column: [:partition_id, :last_pipeline_id]
+ }
+ ]
+
+ P_FOREIGN_KEYS = [
+ {
+ source_table: :p_ci_pipelines,
+ name: :fk_262d4c2d19_p_tmp,
+ column: [:auto_canceled_by_partition_id, :auto_canceled_by_id],
+ on_delete: :nullify
+ },
+ {
+ source_table: :p_ci_builds,
+ name: :fk_87f4cefcda_p_tmp,
+ column: [:upstream_pipeline_partition_id, :upstream_pipeline_id]
+ },
+ {
+ source_table: :p_ci_builds,
+ name: :fk_a2141b1522_p_tmp,
+ column: [:auto_canceled_by_partition_id, :auto_canceled_by_id],
+ on_delete: :nullify
+ },
+ {
+ source_table: :p_ci_builds,
+ name: :fk_d3130c9a7f_p_tmp,
+ column: [:partition_id, :commit_id]
+ },
+ {
+ source_table: :p_ci_pipeline_variables,
+ name: :fk_f29c5f4380_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ },
+ {
+ source_table: :p_ci_stages,
+ name: :fk_fb57e6cc56_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ },
+ {
+ source_table: :p_ci_builds_execution_configs,
+ name: :fk_rails_c26408d02c_p_tmp,
+ column: [:partition_id, :pipeline_id]
+ }
+ ]
+
+ OLD_REFERENCING_TABLE = :ci_pipelines
+ NEW_REFERENCING_TABLE = :p_ci_pipelines
+
+ def up
+ FOREIGN_KEYS.each do |options|
+ with_lock_retries do
+ validate_foreign_key(options[:source_table], options[:column], name: options[:name]) # rubocop:disable Migration/WithLockRetriesDisallowedMethod -- this method should be added to the allowlist
+ end
+ replace_foreign_key_for_new_referencing_table(options)
+ end
+
+ P_FOREIGN_KEYS.each do |options|
+ add_concurrent_partitioned_foreign_key(
+ options[:source_table], NEW_REFERENCING_TABLE,
+ **with_defaults(options, validate: true)
+ )
+ replace_foreign_key_for_new_referencing_table(options, partitioned: true)
+ end
+ end
+
+ def down
+ FOREIGN_KEYS.each do |options|
+ restore_foreign_key_for_old_source_table(options)
+
+ add_concurrent_foreign_key(
+ options[:source_table], NEW_REFERENCING_TABLE,
+ **with_defaults(options, validate: false)
+ )
+ end
+
+ P_FOREIGN_KEYS.each do |options|
+ restore_foreign_key_for_old_source_table(options, partitioned: true)
+
+ add_concurrent_partitioned_foreign_key(
+ options[:source_table], NEW_REFERENCING_TABLE,
+ **with_defaults(options, validate: false)
+ )
+ end
+ end
+
+ private
+
+ def with_defaults(options, validate:, name: nil)
+ options.except(:source_table).with_defaults(
+ target_column: [:partition_id, :id],
+ on_update: :cascade,
+ on_delete: :cascade,
+ reverse_lock_order: true,
+ validate: validate
+ ).tap { |opts| opts[:name] = name if name.present? }
+ end
+
+ def replace_foreign_key_for_new_referencing_table(options, partitioned: false)
+ target_fk_name = options[:name].to_s.gsub('_tmp', '')
+ with_lock_retries do
+ remove_foreign_key_if_exists(old_source_table(options), name: target_fk_name, reverse_lock_order: true)
+
+ if partitioned
+ rename_partitioned_foreign_key(options[:source_table], options[:name], target_fk_name)
+ else
+ rename_constraint(options[:source_table], options[:name], target_fk_name)
+ end
+ end
+ end
+
+ def restore_foreign_key_for_old_source_table(options, partitioned: false)
+ target_fk_name = options[:name].to_s.gsub('_tmp', '')
+ with_lock_retries do
+ remove_foreign_key_if_exists(options[:source_table], name: target_fk_name, reverse_lock_order: true)
+ end
+
+ if partitioned && old_source_table(options) == options[:source_table]
+ add_concurrent_partitioned_foreign_key(
+ old_source_table(options), OLD_REFERENCING_TABLE,
+ **with_defaults(options, name: target_fk_name, validate: true)
+ )
+ else
+ add_concurrent_foreign_key(
+ old_source_table(options), OLD_REFERENCING_TABLE,
+ **with_defaults(options, name: target_fk_name, validate: true)
+ )
+ end
+ end
+
+ def old_source_table(options)
+ return OLD_REFERENCING_TABLE if options[:source_table] == NEW_REFERENCING_TABLE
+
+ options[:source_table]
+ end
+end
diff --git a/db/post_migrate/20240902033136_async_add_index_on_events_personal_namespace_id.rb b/db/post_migrate/20240902033136_async_add_index_on_events_personal_namespace_id.rb
new file mode 100644
index 00000000000..cf0d711cc76
--- /dev/null
+++ b/db/post_migrate/20240902033136_async_add_index_on_events_personal_namespace_id.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AsyncAddIndexOnEventsPersonalNamespaceId < Gitlab::Database::Migration[2.2]
+ milestone '17.4'
+
+ INDEX_NAME = 'index_events_on_personal_namespace_id'
+
+ # rubocop:disable Migration/PreventIndexCreation -- https://gitlab.com/gitlab-org/gitlab/-/issues/462801#note_2081632603
+ def up
+ prepare_async_index :events, :personal_namespace_id, name: INDEX_NAME,
+ where: 'personal_namespace_id IS NOT NULL'
+ end
+ # rubocop:enable Migration/PreventIndexCreation
+
+ def down
+ unprepare_async_index :events, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20240828081719 b/db/schema_migrations/20240828081719
new file mode 100644
index 00000000000..a77dc8e740d
--- /dev/null
+++ b/db/schema_migrations/20240828081719
@@ -0,0 +1 @@
+caaf0e4b4b333dfa5550750a7d1c5e3258d91e88a54cb3a2387629b295d738d7
\ No newline at end of file
diff --git a/db/schema_migrations/20240830105808 b/db/schema_migrations/20240830105808
new file mode 100644
index 00000000000..520ef10d1a7
--- /dev/null
+++ b/db/schema_migrations/20240830105808
@@ -0,0 +1 @@
+bf59d7765d303d266623a1a15c5cf17a3eacbf7cd6433dc44530fb509c634158
\ No newline at end of file
diff --git a/db/schema_migrations/20240902014331 b/db/schema_migrations/20240902014331
new file mode 100644
index 00000000000..2e4efafe63c
--- /dev/null
+++ b/db/schema_migrations/20240902014331
@@ -0,0 +1 @@
+5a44ddd188d397cf19c9db734e97cdbe902b72125674d93604f713efa7fa3177
\ No newline at end of file
diff --git a/db/schema_migrations/20240902032325 b/db/schema_migrations/20240902032325
new file mode 100644
index 00000000000..9b96326f812
--- /dev/null
+++ b/db/schema_migrations/20240902032325
@@ -0,0 +1 @@
+e29d0f63cf834c93eea17f518d4ba7f0ef1d3d8f4fe44f77e78f7baa161a074b
\ No newline at end of file
diff --git a/db/schema_migrations/20240902033136 b/db/schema_migrations/20240902033136
new file mode 100644
index 00000000000..137d9e127eb
--- /dev/null
+++ b/db/schema_migrations/20240902033136
@@ -0,0 +1 @@
+3d0701c873c2510793d0248ffe691ddbbb5e31418f881d8ad4443e94065896ba
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 7a535a00a03..8f20234efb3 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -10844,6 +10844,7 @@ CREATE TABLE events (
id bigint NOT NULL,
target_id bigint,
imported_from smallint DEFAULT 0 NOT NULL,
+ personal_namespace_id bigint,
CONSTRAINT check_97e06e05ad CHECK ((octet_length(fingerprint) <= 128))
);
@@ -16596,6 +16597,29 @@ CREATE SEQUENCE project_secrets_managers_id_seq
ALTER SEQUENCE project_secrets_managers_id_seq OWNED BY project_secrets_managers.id;
+CREATE TABLE project_security_exclusions (
+ id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ scanner smallint NOT NULL,
+ type smallint NOT NULL,
+ active boolean DEFAULT true NOT NULL,
+ description text,
+ value text NOT NULL,
+ CONSTRAINT check_3c70ee8804 CHECK ((char_length(description) <= 255)),
+ CONSTRAINT check_3e918b71ed CHECK ((char_length(value) <= 255))
+);
+
+CREATE SEQUENCE project_security_exclusions_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE project_security_exclusions_id_seq OWNED BY project_security_exclusions.id;
+
CREATE TABLE project_security_settings (
project_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -22076,6 +22100,8 @@ ALTER TABLE ONLY project_saved_replies ALTER COLUMN id SET DEFAULT nextval('proj
ALTER TABLE ONLY project_secrets_managers ALTER COLUMN id SET DEFAULT nextval('project_secrets_managers_id_seq'::regclass);
+ALTER TABLE ONLY project_security_exclusions ALTER COLUMN id SET DEFAULT nextval('project_security_exclusions_id_seq'::regclass);
+
ALTER TABLE ONLY project_security_settings ALTER COLUMN project_id SET DEFAULT nextval('project_security_settings_project_id_seq'::regclass);
ALTER TABLE ONLY project_states ALTER COLUMN id SET DEFAULT nextval('project_states_id_seq'::regclass);
@@ -24644,6 +24670,9 @@ ALTER TABLE ONLY project_saved_replies
ALTER TABLE ONLY project_secrets_managers
ADD CONSTRAINT project_secrets_managers_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY project_security_exclusions
+ ADD CONSTRAINT project_security_exclusions_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY project_security_settings
ADD CONSTRAINT project_security_settings_pkey PRIMARY KEY (project_id);
@@ -26761,6 +26790,8 @@ CREATE INDEX idx_streaming_headers_on_external_audit_event_destination_id ON aud
CREATE INDEX idx_streaming_instance_namespace_filters_on_namespace_id ON audit_events_streaming_instance_namespace_filters USING btree (namespace_id);
+CREATE INDEX idx_subscription_add_on_purchases_on_started_on_and_expires_on ON subscription_add_on_purchases USING btree (started_at, expires_on);
+
CREATE INDEX idx_test_reports_on_issue_id_created_at_and_id ON requirements_management_test_reports USING btree (issue_id, created_at, id);
CREATE UNIQUE INDEX idx_uniq_analytics_dashboards_pointers_on_project_id ON analytics_dashboards_pointers USING btree (project_id);
@@ -29625,6 +29656,8 @@ CREATE INDEX index_project_saved_replies_on_project_id ON project_saved_replies
CREATE UNIQUE INDEX index_project_secrets_managers_on_project_id ON project_secrets_managers USING btree (project_id);
+CREATE INDEX index_project_security_exclusions_on_project_id ON project_security_exclusions USING btree (project_id);
+
CREATE INDEX index_project_settings_on_legacy_os_license_project_id ON project_settings USING btree (project_id) WHERE (legacy_open_source_license_available = true);
CREATE INDEX index_project_settings_on_project_id_partially ON project_settings USING btree (project_id) WHERE (has_vulnerabilities IS TRUE);
@@ -33221,11 +33254,8 @@ ALTER TABLE ONLY agent_activity_events
ALTER TABLE ONLY zoekt_repositories
ADD CONSTRAINT fk_25a92aeccd FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL;
-ALTER TABLE ONLY ci_pipelines
- ADD CONSTRAINT fk_262d4c2d19_p FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL;
-
-ALTER TABLE ONLY ci_pipelines
- ADD CONSTRAINT fk_262d4c2d19_p_tmp FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID;
+ALTER TABLE p_ci_pipelines
+ ADD CONSTRAINT fk_262d4c2d19_p FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL;
ALTER TABLE ONLY user_namespace_callouts
ADD CONSTRAINT fk_27a69fd1bd FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -33576,10 +33606,7 @@ ALTER TABLE ONLY approval_group_rules
ADD CONSTRAINT fk_64450bea52 FOREIGN KEY (security_orchestration_policy_configuration_id) REFERENCES security_orchestration_policy_configurations(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_pipeline_chat_data
- ADD CONSTRAINT fk_64ebfab6b3_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_pipeline_chat_data
- ADD CONSTRAINT fk_64ebfab6b3_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_64ebfab6b3_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY cluster_agent_tokens
ADD CONSTRAINT fk_64f741f626 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -33762,10 +33789,7 @@ ALTER TABLE ONLY packages_package_files
ADD CONSTRAINT fk_86f0f182f8 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
ALTER TABLE p_ci_builds
- ADD CONSTRAINT fk_87f4cefcda_p FOREIGN KEY (upstream_pipeline_partition_id, upstream_pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_builds
- ADD CONSTRAINT fk_87f4cefcda_p_tmp FOREIGN KEY (upstream_pipeline_partition_id, upstream_pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_87f4cefcda_p FOREIGN KEY (upstream_pipeline_partition_id, upstream_pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY approval_group_rules_users
ADD CONSTRAINT fk_888a0df3b7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
@@ -33912,10 +33936,7 @@ ALTER TABLE ONLY subscription_add_on_purchases
ADD CONSTRAINT fk_a1db288990 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE p_ci_builds
- ADD CONSTRAINT fk_a2141b1522_p FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL;
-
-ALTER TABLE ONLY ci_builds
- ADD CONSTRAINT fk_a2141b1522_p_tmp FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID;
+ ADD CONSTRAINT fk_a2141b1522_p FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL;
ALTER TABLE ONLY protected_environment_approval_rules
ADD CONSTRAINT fk_a3cc825836 FOREIGN KEY (protected_environment_project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -34239,10 +34260,7 @@ ALTER TABLE ONLY dast_pre_scan_verifications
ADD CONSTRAINT fk_d23ad33d6e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE p_ci_builds
- ADD CONSTRAINT fk_d3130c9a7f_p FOREIGN KEY (partition_id, commit_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_builds
- ADD CONSTRAINT fk_d3130c9a7f_p_tmp FOREIGN KEY (partition_id, commit_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_d3130c9a7f_p FOREIGN KEY (partition_id, commit_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY boards_epic_user_preferences
ADD CONSTRAINT fk_d32c3d693c FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -34251,10 +34269,7 @@ ALTER TABLE ONLY vulnerability_state_transitions
ADD CONSTRAINT fk_d3ede71c58 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_sources_pipelines
- ADD CONSTRAINT fk_d4e29af7d7_p FOREIGN KEY (source_partition_id, source_pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_sources_pipelines
- ADD CONSTRAINT fk_d4e29af7d7_p_tmp FOREIGN KEY (source_partition_id, source_pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_d4e29af7d7_p FOREIGN KEY (source_partition_id, source_pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY operations_strategies_user_lists
ADD CONSTRAINT fk_d4f7076369 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -34338,10 +34353,7 @@ ALTER TABLE ONLY ci_resources
ADD CONSTRAINT fk_e169a8e3d5_p FOREIGN KEY (partition_id, build_id) REFERENCES p_ci_builds(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL;
ALTER TABLE ONLY ci_sources_pipelines
- ADD CONSTRAINT fk_e1bad85861_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_sources_pipelines
- ADD CONSTRAINT fk_e1bad85861_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_e1bad85861_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE p_ci_builds_metadata
ADD CONSTRAINT fk_e20479742e_p FOREIGN KEY (partition_id, build_id) REFERENCES p_ci_builds(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
@@ -34461,10 +34473,7 @@ ALTER TABLE ONLY workspaces_agent_configs
ADD CONSTRAINT fk_f25d0fbfae FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE p_ci_pipeline_variables
- ADD CONSTRAINT fk_f29c5f4380_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_pipeline_variables
- ADD CONSTRAINT fk_f29c5f4380_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_f29c5f4380_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY zoekt_indices
ADD CONSTRAINT fk_f34800a202 FOREIGN KEY (zoekt_node_id) REFERENCES zoekt_nodes(id) ON DELETE CASCADE;
@@ -34506,10 +34515,7 @@ ALTER TABLE ONLY application_settings
ADD CONSTRAINT fk_f9867b3540 FOREIGN KEY (web_ide_oauth_application_id) REFERENCES oauth_applications(id) ON DELETE SET NULL;
ALTER TABLE p_ci_stages
- ADD CONSTRAINT fk_fb57e6cc56_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_stages
- ADD CONSTRAINT fk_fb57e6cc56_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_fb57e6cc56_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY agent_group_authorizations
ADD CONSTRAINT fk_fb70782616 FOREIGN KEY (agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
@@ -34686,10 +34692,7 @@ ALTER TABLE ONLY audit_events_streaming_headers
ADD CONSTRAINT fk_rails_109fcf96e2 FOREIGN KEY (external_audit_event_destination_id) REFERENCES audit_events_external_audit_event_destinations(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_sources_projects
- ADD CONSTRAINT fk_rails_10a1eb379a_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_sources_projects
- ADD CONSTRAINT fk_rails_10a1eb379a_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_rails_10a1eb379a_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY zoom_meetings
ADD CONSTRAINT fk_rails_1190f0e0fa FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -35151,10 +35154,7 @@ ALTER TABLE ONLY status_page_settings
ADD CONSTRAINT fk_rails_506e5ba391 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_pipeline_metadata
- ADD CONSTRAINT fk_rails_50c1e9ea10_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_pipeline_metadata
- ADD CONSTRAINT fk_rails_50c1e9ea10_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_rails_50c1e9ea10_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY project_repository_storage_moves
ADD CONSTRAINT fk_rails_5106dbd44a FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -35607,10 +35607,7 @@ ALTER TABLE ONLY import_placeholder_memberships
ADD CONSTRAINT fk_rails_8cdeffd260 FOREIGN KEY (source_user_id) REFERENCES import_source_users(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_pipeline_messages
- ADD CONSTRAINT fk_rails_8d3b04e3e1_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_pipeline_messages
- ADD CONSTRAINT fk_rails_8d3b04e3e1_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_rails_8d3b04e3e1_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE incident_management_pending_alert_escalations
ADD CONSTRAINT fk_rails_8d8de95da9 FOREIGN KEY (alert_id) REFERENCES alert_management_alerts(id) ON DELETE CASCADE;
@@ -35640,10 +35637,7 @@ ALTER TABLE ONLY organization_details
ADD CONSTRAINT fk_rails_8facb04bef FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_pipelines_config
- ADD CONSTRAINT fk_rails_906c9a2533_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_pipelines_config
- ADD CONSTRAINT fk_rails_906c9a2533_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_rails_906c9a2533_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY approval_project_rules_groups
ADD CONSTRAINT fk_rails_9071e863d1 FOREIGN KEY (approval_project_rule_id) REFERENCES approval_project_rules(id) ON DELETE CASCADE;
@@ -35826,10 +35820,7 @@ ALTER TABLE ONLY saved_replies
ADD CONSTRAINT fk_rails_a8bf5bf111 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_pipeline_artifacts
- ADD CONSTRAINT fk_rails_a9e811a466_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_pipeline_artifacts
- ADD CONSTRAINT fk_rails_a9e811a466_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_rails_a9e811a466_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY merge_request_user_mentions
ADD CONSTRAINT fk_rails_aa1b2961b1 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
@@ -36009,7 +36000,7 @@ ALTER TABLE ONLY project_feature_usages
ADD CONSTRAINT fk_rails_c22a50024b FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE p_ci_builds_execution_configs
- ADD CONSTRAINT fk_rails_c26408d02c_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
+ ADD CONSTRAINT fk_rails_c26408d02c_p FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY user_canonical_emails
ADD CONSTRAINT fk_rails_c2bd828b51 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
@@ -36345,10 +36336,7 @@ ALTER TABLE ONLY packages_debian_group_distributions
ADD CONSTRAINT fk_rails_ede0bb937f FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY ci_daily_build_group_report_results
- ADD CONSTRAINT fk_rails_ee072d13b3_p FOREIGN KEY (partition_id, last_pipeline_id) REFERENCES ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-ALTER TABLE ONLY ci_daily_build_group_report_results
- ADD CONSTRAINT fk_rails_ee072d13b3_p_tmp FOREIGN KEY (partition_id, last_pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_rails_ee072d13b3_p FOREIGN KEY (partition_id, last_pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY import_source_users
ADD CONSTRAINT fk_rails_ee30e569be FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
diff --git a/doc/administration/self_hosted_models/install_infrastructure.md b/doc/administration/self_hosted_models/install_infrastructure.md
index 377d1495ca9..e262f1ec2ba 100644
--- a/doc/administration/self_hosted_models/install_infrastructure.md
+++ b/doc/administration/self_hosted_models/install_infrastructure.md
@@ -202,6 +202,24 @@ should open the AI Gateway API documentation.
CLOUD_CONNECTOR_SELF_SIGN_TOKENS=1
```
+1. Where your GitLab instance is installed, [run the following Rake task](../../raketasks/index.md) to activate GitLab Duo features:
+
+ ```shell
+ sudo gitlab-rake gitlab:duo:enable_feature_flags
+ ```
+
+1. [Start a GitLab Rails console](../feature_flags.md#start-the-gitlab-rails-console):
+
+ ```shell
+ sudo gitlab-rails console
+ ```
+
+ In the console, enable the `ai_custom_model` feature flag:
+
+ ```shell
+ feature.enable(:ai_custom_model)
+ ```
+
1. After you've set up the environment variables, run the image. For example:
```shell
@@ -251,6 +269,115 @@ To upgrade the AI Gateway, download the newest Docker image tag.
1. Ensure that the environment variables are all set correctly
+### Install by using the AI Gateway Helm chart
+
+#### Prerequisites
+
+To complete this guide, you must have the following:
+
+- A domain you own, that you can add a DNS record to.
+- A Kubernetes cluster.
+- A working installation of `kubectl`.
+- A working installation of Helm, version v3.11.0 or later.
+
+For more information, see [Test the GitLab chart on GKE or EKS](https://docs.gitlab.com/charts/quickstart/index.html).
+
+#### Add the AI Gateway Helm repository
+
+Add the AI Gateway Helm repository to Helm’s configuration:
+
+```shell
+helm repo add ai-gateway \
+https://gitlab.com/api/v4/projects/gitlab-org%2fcharts%2fai-gateway-helm-chart/packages/helm/devel
+```
+
+#### Install the AI Gateway
+
+1. Create the `ai-gateway` namespace:
+
+ ```shell
+ kubectl create namespace ai-gateway
+ ```
+
+1. Generate the certificate for the domain where you plan to expose the AI Gateway.
+1. Create the TLS secret in the previously created namespace:
+
+ ```shell
+ kubectl -n ai-gateway create secret tls ai-gateway-tls --cert="" --key=""
+ ```
+
+1. For the AI Gateway to access the API, it must know where the GitLab instance
+is located. To do this, set the `gitlab.url` and
+`gitlab.apiUrl` together with the `ingress.hosts` and `ingress.tls` values as follows:
+
+ ```shell
+ helm repo add ai-gateway \
+ https://gitlab.com/api/v4/projects/gitlab-org%2fcharts%2fai-gateway-helm-chart/packages/helm/devel
+ helm repo update
+
+ helm upgrade --install ai-gateway \
+ ai-gateway/ai-gateway \
+ --version 0.1.1 \
+ --namespace=ai-gateway \
+ --set="gitlab.url=https://" \
+ --set="gitlab.apiUrl=https:///api/v4/" \
+ --set "ingress.enabled=true" \
+ --set "ingress.hosts[0].host=" \
+ --set "ingress.hosts[0].paths[0].path=/" \
+ --set "ingress.hosts[0].paths[0].pathType=ImplementationSpecific" \
+ --set "ingress.tls[0].secretName=ai-gateway-tls" \
+ --set "ingress.tls[0].hosts[0]=" \
+ --set="ingress.className=nginx" \
+ --timeout=300s --wait --wait-for-jobs
+ ```
+
+This step can take will take a few seconds in order for all resources to be allocated and the AI Gateway to start.
+
+Wait for your pods to get up and running:
+
+```shell
+kubectl wait pod \
+ --all \
+ --for=condition=Ready \
+ --namespace=ai-gateway \
+ --timeout=300s
+```
+
+When it's done, you can proceed with setting up your IP ingresses and DNS records.
+
+#### Installation steps in the GitLab instance
+
+1. For the GitLab instance to know where AI Gateway is located so it can access
+ the gateway, set the environment variable `AI_GATEWAY_URL` inside your GitLab
+ instance environment variables:
+
+ ```shell
+ AI_GATEWAY_URL=https://
+ CLOUD_CONNECTOR_SELF_SIGN_TOKENS=1
+ ```
+
+1. Where your GitLab instance is installed, [run the following Rake task](../../raketasks/index.md) to activate GitLab Duo features:
+
+ ```shell
+ sudo gitlab-rake gitlab:duo:enable_feature_flags
+ ```
+
+1. [Start a GitLab Rails console](../feature_flags.md#start-the-gitlab-rails-console):
+
+ ```shell
+ sudo gitlab-rails console
+ ```
+
+ In the console, enable the `ai_custom_model` feature flag:
+
+ ```shell
+ Feature.enable(:ai_custom_model)
+ ```
+
+ Exit your console.
+
+With those steps completed, your Helm chart installation is complete.
+
## Alternative installation methods
For information on alternative ways to install the AI Gateway, see [issue 463773](https://gitlab.com/gitlab-org/gitlab/-/issues/463773).
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index c0dd9bef439..0c4caea27f9 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -29163,7 +29163,8 @@ Project-level settings for product analytics provider.
| `savedReplies` **{warning-solid}** | [`ProjectSavedReplyConnection`](#projectsavedreplyconnection) | **Introduced** in GitLab 16.11. **Status**: Experiment. Saved replies available to the project. Available only when feature flag `project_saved_replies_flag` is enabled. |
| `securityDashboardPath` | [`String`](#string) | Path to project's security dashboard. |
| `securityPolicyProject` | [`Project`](#project) | Security policy project assigned to the project, absent if assigned to a parent group. |
-| `securityPolicyProjectLinkedNamespaces` | [`NamespaceConnection`](#namespaceconnection) | Namespaces linked to the project, when used as Security Policy Project. (see [Connections](#connections)) |
+| `securityPolicyProjectLinkedGroups` | [`GroupConnection`](#groupconnection) | Groups linked to the project, when used as Security Policy Project. (see [Connections](#connections)) |
+| `securityPolicyProjectLinkedNamespaces` **{warning-solid}** | [`NamespaceConnection`](#namespaceconnection) | **Deprecated** in GitLab 17.4. This was renamed. Use: `security_policy_project_linked_groups`. |
| `securityPolicyProjectLinkedProjects` | [`ProjectConnection`](#projectconnection) | Projects linked to the project, when used as Security Policy Project. (see [Connections](#connections)) |
| `securityScanners` | [`SecurityScanners`](#securityscanners) | Information about security analyzers used in the project. |
| `sentryErrors` | [`SentryErrorCollection`](#sentryerrorcollection) | Paginated collection of Sentry errors on the project. |
@@ -34654,6 +34655,17 @@ Represents work item counts for the work item type.
| `countsByState` | [`WorkItemStateCountsType!`](#workitemstatecountstype) | Total number of work items for the represented states. |
| `workItemType` | [`WorkItemType!`](#workitemtype) | Work item type. |
+### `WorkItemTypeDepthLimitReachedByType`
+
+Represents Depth limit reached for the allowed work item type.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `depthLimitReached` | [`Boolean!`](#boolean) | Indicates if maximum allowed depth has been reached for the descendant type. |
+| `workItemType` | [`WorkItemType!`](#workitemtype) | Work item type. |
+
### `WorkItemWidgetAssignees`
Represents an assignees widget.
@@ -34874,6 +34886,7 @@ Represents a hierarchy widget.
| ---- | ---- | ----------- |
| `ancestors` | [`WorkItemConnection`](#workitemconnection) | Ancestors (parents) of the work item. (see [Connections](#connections)) |
| `children` | [`WorkItemConnection`](#workitemconnection) | Child work items. (see [Connections](#connections)) |
+| `depthLimitReachedByType` **{warning-solid}** | [`[WorkItemTypeDepthLimitReachedByType!]!`](#workitemtypedepthlimitreachedbytype) | **Introduced** in GitLab 17.4. **Status**: Experiment. Depth limit reached by allowed work item type. |
| `hasChildren` | [`Boolean!`](#boolean) | Indicates if the work item has children. |
| `hasParent` | [`Boolean!`](#boolean) | Indicates if the work item has a parent. |
| `parent` | [`WorkItem`](#workitem) | Parent work item. |
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index 7e54b94d1e2..49633c4b625 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -224,6 +224,9 @@ Example of response
### Get variables of a pipeline
+Get the variables of a pipeline. Does not include variables that come from a pipeline schedule.
+For more information, see [issue 250850](https://gitlab.com/gitlab-org/gitlab/-/issues/250850).
+
```plaintext
GET /projects/:id/pipelines/:pipeline_id/variables
```
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index f65cdcacdd4..bcf7a4ecbed 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -3461,7 +3461,7 @@ as an artifact and published with GitLab Pages.
DETAILS:
**Tier:** Premium, Ultimate
-**Offering:** Self-managed
+**Offering:** GitLab.com, Self-managed, GitLab Dedicated
**Status:** Experiment
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129534) in GitLab 16.7 as an [experiment](../../policy/experiment-beta-support.md) [with a flag](../../user/feature_flags.md) named `pages_multiple_versions_setting`, disabled by default.
@@ -3493,6 +3493,50 @@ pages:
In this example, a different pages deployment is created for each branch.
+### `pages:pages.expire_in`
+
+DETAILS:
+**Tier:** Premium, Ultimate
+**Offering:** GitLab.com, Self-managed, GitLab Dedicated
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/456478) in GitLab 17.4
+
+Use `expire_in` to specify how long a deployment should be available before
+it expires. After the deployment is expired, it's deactivated by a cron
+job running every 10 minutes.
+
+Extra deployments expire by default. To prevent them from expiring, set the
+value to `never`.
+
+**Keyword type**: Job keyword. You can use it only as part of a `pages` job.
+
+**Possible inputs**: The expiry time. If no unit is provided, the time is in seconds.
+Valid values include:
+
+- `'42'`
+- `42 seconds`
+- `3 mins 4 sec`
+- `2 hrs 20 min`
+- `2h20min`
+- `6 mos 1 day`
+- `47 yrs 6 mos and 4d`
+- `3 weeks and 2 days`
+- `never`
+
+**Example of `pages:pages.expire_in`**:
+
+```yaml
+pages:
+ stage: deploy
+ script:
+ - echo "Pages accessible through ${CI_PAGES_URL}"
+ pages:
+ expire_in: 1 week
+ artifacts:
+ paths:
+ - public
+```
+
### `parallel`
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336576) in GitLab 15.9, the maximum value for `parallel` is increased from 50 to 200.
diff --git a/doc/ci/yaml/inputs.md b/doc/ci/yaml/inputs.md
index 380a60558b8..844e82a97b0 100644
--- a/doc/ci/yaml/inputs.md
+++ b/doc/ci/yaml/inputs.md
@@ -106,7 +106,7 @@ spec:
stage: $[[ inputs.job-stage ]]
script:
- echo "scanning website -e $[[ inputs.environment ]] -c $[[ inputs.concurrency ]] -v $[[ inputs.version ]]"
- - if [ $[[ inputs.export_results ]] ]; then echo "export results"; fi
+ - if $[[ inputs.export_results ]]; then echo "export results"; fi
```
In this example:
diff --git a/doc/development/cells/index.md b/doc/development/cells/index.md
index fb7beb25536..c5fc80879eb 100644
--- a/doc/development/cells/index.md
+++ b/doc/development/cells/index.md
@@ -164,6 +164,7 @@ desired_sharding_key:
parent:
foreign_key: scanner_id
table: vulnerability_scanners
+ table_primary_key: id # Optional. Defaults to 'id'
sharding_key: project_id
belongs_to: scanner
```
@@ -192,6 +193,7 @@ desired_sharding_key:
parent:
foreign_key: package_file_id
table: packages_package_files
+ table_primary_key: id # Optional. Defaults to 'id'
sharding_key: project_id
belongs_to: package_file
awaiting_backfill_on_parent: true
diff --git a/doc/development/observability/index.md b/doc/development/observability/index.md
index 5b78b980c1c..272b3139d7c 100644
--- a/doc/development/observability/index.md
+++ b/doc/development/observability/index.md
@@ -89,19 +89,19 @@ You can reference the instructions for running the demo app [here](https://opent
```yaml
exporters:
- otlphttp/gitlab:
- endpoint: http://gdk.test:3443/api/v4/$PROJECT_ID/observability/
- headers:
- "private-token": "$TOKEN"
+ otlphttp/gitlab:
+ endpoint: http://gdk.test:3443/api/v4/$PROJECT_ID/observability/
+ headers:
+ "private-token": "$TOKEN"
service:
- pipelines:
- traces:
- exporters: [spanmetrics, otlphttp/gitlab]
- metrics:
- exporters: [otlphttp/gitlab]
- logs:
- exporters: [otlphttp/gitlab]
+ pipelines:
+ traces:
+ exporters: [spanmetrics, otlphttp/gitlab]
+ metrics:
+ exporters: [otlphttp/gitlab]
+ logs:
+ exporters: [otlphttp/gitlab]
```
1. Save the config and start the demo app:
diff --git a/doc/subscriptions/customers_portal.md b/doc/subscriptions/customers_portal.md
index 49a32529b04..63e1432f6a4 100644
--- a/doc/subscriptions/customers_portal.md
+++ b/doc/subscriptions/customers_portal.md
@@ -178,7 +178,13 @@ To link a GitLab.com account to your Customers Portal profile:
## Change the linked account
-Customers are required to use their GitLab.com account to register for a new Customers Portal profile.
+If you want to link your Customers Portal account to a different GitLab.com account,
+you must use your GitLab.com account to register for a new Customers Portal profile.
+
+If you want to change subscription contacts, you can instead do either of the following:
+
+- [Change the billing contact](#change-your-billing-contact).
+- [Change the subscription contact](#change-your-subscription-contact).
If you have a legacy Customers Portal profile that is not linked to a GitLab.com account, you may still [sign in](https://customers.gitlab.com/customers/sign_in?legacy=true) using a one-time sign-in link sent to your email. However, you should [create](https://gitlab.com/users/sign_up) and [link a GitLab.com account](#change-the-linked-account) to ensure continued access to the Customers Portal.
diff --git a/doc/topics/build_your_application.md b/doc/topics/build_your_application.md
index 9f0a1b2310c..ee743c055d9 100644
--- a/doc/topics/build_your_application.md
+++ b/doc/topics/build_your_application.md
@@ -9,11 +9,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Use CI/CD to generate your application.
-| | | |
+| | | |
|--|--|--|
-| [**Getting started**](../ci/index.md)
Overview of how features fit together. | [**CI/CD YAML syntax reference**](../ci/yaml/index.md)
Pipeline definition, artifacts, debugging, examples, steps. | [**Runners**](https://docs.gitlab.com/runner/)
Execution, agents, job processing, installation, configuration. |
-| [**Pipelines**](../ci/pipelines/index.md)
Configuration, automation, stages, jobs, schedules, efficiency. | [**Jobs**](../ci/jobs/index.md)
Configuration, logs, artifacts. | [**CI/CD components**](../ci/components/index.md)
Reusable, versioned CI/CD components for pipelines. |
-| [**Variables**](../ci/variables/index.md)
Configuration, usage, security, troubleshooting. | [**Pipeline security**](../ci/pipelines/pipeline_security.md)
Secrets management, job tokens, secure files, cloud security. | [**Services**](../ci/services/index.md)
Reusable database or caching images. |
-| [**Auto DevOps**](autodevops/index.md)
Automated DevOps, CI/CD, language detection, deployment, customization. | [**Testing**](../ci/testing/index.md)
Unit tests, integration tests, test reports, coverage, quality assurance. | [**SSH keys**](../ci/ssh_keys/index.md)
Authentication, secure access, deployment, remote execution, key management. |
-| [**ChatOps**](../ci/chatops/index.md)
Collaboration, chat integration, commands, automation, communication. | [**Mobile DevOps**](../ci/mobile_devops.md)
Mobile apps, Android, build automation, app distribution. | [**Google cloud integration**](../ci/gitlab_google_cloud_integration/index.md)
Cloud services, Kubernetes deployments. |
-| [**External repository integrations**](../ci/ci_cd_for_external_repos/index.md)
GitHub, Bitbucket, external sources, mirroring, cross-platform. | | |
+| [**Getting started**](../ci/index.md)
Overview of how CI/CD features fit together. | [**CI/CD YAML syntax reference**](../ci/yaml/index.md)
Pipeline configuration keywords, syntax, examples, inputs. | [**Runners**](https://docs.gitlab.com/runner/)
Installation, configuration, job execution. |
+| [**Pipelines**](../ci/pipelines/index.md)
Configuration, automation, stages, schedules, efficiency. | [**Jobs**](../ci/jobs/index.md)
Configuration, rules, caching, artifacts, logs. | [**CI/CD components**](../ci/components/index.md)
Reusable, versioned CI/CD components for pipelines. |
+| [**CI/CD variables**](../ci/variables/index.md)
Configuration, usage, security. | [**Pipeline security**](../ci/pipelines/pipeline_security.md)
Secrets management, job tokens, secure files, cloud security. | [**Debugging**](../ci/debugging.md)
Configuration validation, warnings, errors, troubleshooting. |
+| [**Auto DevOps**](autodevops/index.md)
Automated DevOps, language detection, deployment, customization. | [**Testing**](../ci/testing/index.md)
Unit tests, integration tests, test reports, coverage, quality assurance. | [**SSH keys**](../ci/ssh_keys/index.md)
Authentication, secure access, deployment, remote execution, key management. |
+| [**Mobile DevOps**](../ci/mobile_devops.md)
Mobile apps, Android, build automation, app distribution. | [**Google cloud integration**](../ci/gitlab_google_cloud_integration/index.md)
Cloud services, Kubernetes deployments. | [**External repository integrations**](../ci/ci_cd_for_external_repos/index.md)
GitHub, Bitbucket, external sources, mirroring, cross-platform. |
+| [**Migrate to GitLab CI/CD**](../ci/migration/plan_a_migration.md)
Migrate from Jenkins, GitHub Actions, others. | | |
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 0e9a4596078..84d546ca920 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -175,6 +175,56 @@ The project maintainer can disable this feature on:
1. Deselect the **Use unique domain** checkbox.
1. Select **Save changes**.
+## Expiring deployments
+
+DETAILS:
+**Tier:** Premium, Ultimate
+**Offering:** GitLab.com, Self-managed, GitLab Dedicated
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162826) in GitLab 17.4.
+
+You can configure your Pages deployments to be automatically deleted after
+a period of time has passed by specifying a duration at `pages.expire_in`:
+
+```yaml
+pages:
+ stage: deploy
+ script:
+ - ...
+ pages:
+ expire_in: 1 week
+ artifacts:
+ paths:
+ - public
+```
+
+By default, [extra deployments](#create-multiple-deployments) expire automatically after 24 hours.
+To disable this behavior, set `pages.expire_in` to `never`.
+
+Expired deployments are stopped by a cron job that runs every 10 minutes.
+Stopped deployments are subsequently deleted by another cron job that also
+runs every 10 minutes. To recover it, follow the steps described in
+[Recover a stopped deployment](#recover-a-stopped-deployment).
+
+A stopped or deleted deployment is no longer available on the web. Users will
+see a 404 Not found error page at its URL, until another deployment is created
+with the same URL configuration.
+
+### Recover a stopped deployment
+
+Prerequisites:
+
+- You must have at least the Maintainer role for the project.
+
+To recover a stopped deployment that has not yet been deleted:
+
+1. On the left sidebar, select **Search or go to** and find your project.
+1. Select **Deploy > Pages**.
+1. Near **Deployments** turn on the **Include stopped deployments** toggle.
+ If your deployment has not been deleted yet, it should be included in the
+ list.
+1. Expand the deployment you want to recover and select **Restore**.
+
## Create multiple deployments
DETAILS:
@@ -236,7 +286,12 @@ The number of extra deployments is limited by the root-level namespace. For spec
By default, extra deployments expire after 24 hours, after which they are deleted.
If you're using a self-hosted instance, your instance admin can
-[configure a different duration](../../../administration/pages/index.md#configure-the-default-expiry-for-extra-deployments).
+[configure a different default duration](../../../administration/pages/index.md#configure-the-default-expiry-for-extra-deployments).
+
+To customize the expiry time, [configure `pages.expire_in`](#expiring-deployments).
+
+To prevent deployments from automatically expiring, set `pages.expire_in` to
+`never`.
### Path clash
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e84664c086b..7bfbf9a985d 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -246,8 +246,8 @@ module API
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::ComposerPackages
- mount ::API::ConanInstancePackages
- mount ::API::ConanProjectPackages
+ mount ::API::Conan::V1::InstancePackages
+ mount ::API::Conan::V1::ProjectPackages
mount ::API::ContainerRegistryEvent
mount ::API::ContainerRepositories
mount ::API::DebianGroupPackages
diff --git a/lib/api/conan/v1/instance_packages.rb b/lib/api/conan/v1/instance_packages.rb
new file mode 100644
index 00000000000..5697edab95f
--- /dev/null
+++ b/lib/api/conan/v1/instance_packages.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# Conan Instance-Level Package Manager Client API
+module API
+ module Conan
+ module V1
+ class InstancePackages < ::API::Base
+ helpers do
+ def search_project
+ nil
+ end
+ end
+
+ namespace 'packages/conan/v1' do
+ include ::API::Concerns::Packages::ConanEndpoints
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/conan/v1/project_packages.rb b/lib/api/conan/v1/project_packages.rb
new file mode 100644
index 00000000000..aafda7647be
--- /dev/null
+++ b/lib/api/conan/v1/project_packages.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+# Conan Project-Level Package Manager Client API
+module API
+ module Conan
+ module V1
+ class ProjectPackages < ::API::Base
+ helpers do
+ def search_project
+ project
+ end
+ end
+
+ params do
+ requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
+ end
+
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ namespace ':id/packages/conan/v1' do
+ include ::API::Concerns::Packages::ConanEndpoints
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/conan_instance_packages.rb b/lib/api/conan_instance_packages.rb
deleted file mode 100644
index 5f302dba488..00000000000
--- a/lib/api/conan_instance_packages.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-# Conan Instance-Level Package Manager Client API
-module API
- class ConanInstancePackages < ::API::Base
- helpers do
- def search_project
- nil
- end
- end
-
- namespace 'packages/conan/v1' do
- include ::API::Concerns::Packages::ConanEndpoints
- end
- end
-end
diff --git a/lib/api/conan_project_packages.rb b/lib/api/conan_project_packages.rb
deleted file mode 100644
index a63b3cfd619..00000000000
--- a/lib/api/conan_project_packages.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-# Conan Project-Level Package Manager Client API
-module API
- class ConanProjectPackages < ::API::Base
- helpers do
- def search_project
- project
- end
- end
-
- params do
- requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
- end
-
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- namespace ':id/packages/conan/v1' do
- include ::API::Concerns::Packages::ConanEndpoints
- end
- end
- end
-end
diff --git a/lib/gitaly/server.rb b/lib/gitaly/server.rb
index 73d01c00cb1..4b5a0b207ea 100644
--- a/lib/gitaly/server.rb
+++ b/lib/gitaly/server.rb
@@ -110,9 +110,11 @@ module Gitaly
end
def server_signature
- @server_signature ||= Gitlab::GitalyClient::ServerService.new(@storage).server_signature
- rescue GRPC::Unavailable, GRPC::DeadlineExceeded
- ServerSignature.new(public_key: nil, error: true)
+ @server_signature ||= begin
+ Gitlab::GitalyClient::ServerService.new(@storage).server_signature
+ rescue GRPC::Unavailable, GRPC::DeadlineExceeded
+ ServerSignature.new(public_key: nil, error: true)
+ end
end
def info
diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb
index c2b49c1c068..ce7c29f2d12 100644
--- a/lib/gitlab/auth/o_auth/auth_hash.rb
+++ b/lib/gitlab/auth/o_auth/auth_hash.rb
@@ -7,9 +7,11 @@ module Gitlab
module OAuth
class AuthHash
attr_reader :auth_hash
+ attr_accessor :errors
def initialize(auth_hash)
@auth_hash = auth_hash
+ @errors = {}
end
def uid
@@ -119,13 +121,34 @@ module Gitlab
# Get the first part of the email address (before @)
# In addition in removes illegal characters
+ # Perform length validation twice:
+ # - Before normalization to prevent normalizing excessively long strings
+ # - After normalization to ensure certain normalized multibyte characters don't exceed length.
def generate_username(email)
- email.match(/^[^@]*/)[0].mb_chars.unicode_normalize(:nfkd).gsub(/[^\x00-\x7F]/, '').to_s
+ return unless valid_email_username_length?(email)
+
+ username = mb_chars_unicode_normalize(email.match(/^[^@]*/)[0])
+ username if valid_email_username_length?(username)
end
def generate_temporarily_email(username)
"temp-email-for-oauth-#{username}@gitlab.localhost"
end
+
+ # RFC 3606 and RFC 2821 restrict total email length to
+ # 254 characters. Do not allow longer emails to be passed in
+ # because unicode normalization can be intensive.
+ def valid_email_username_length?(email_or_username)
+ return true unless Feature.enabled?(:omniauth_validate_email_length, :instance)
+ return true if email_or_username.length <= 254
+
+ errors[:identity_provider_email] = _("must be 254 characters or less.")
+ false
+ end
+
+ def mb_chars_unicode_normalize(string)
+ string.mb_chars.unicode_normalize(:nfkd).gsub(/[^\x00-\x7F]/, '').to_s
+ end
end
end
end
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index 3a85ffe22c0..68da13765fa 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -45,7 +45,7 @@ module Gitlab
end
def valid?
- gl_user.try(:valid?)
+ !any_auth_hash_errors? && gl_user.try(:valid?)
end
def valid_sign_in?
@@ -53,6 +53,8 @@ module Gitlab
end
def save(provider = protocol_name)
+ return false if any_auth_hash_errors?
+
raise SigninDisabledForProviderError if oauth_provider_disabled?
raise SignupDisabledError unless gl_user
@@ -308,6 +310,19 @@ module Gitlab
auto_link = Array(auto_link)
auto_link.include?(auth_hash.provider)
end
+
+ def any_auth_hash_errors?
+ return false if auth_hash.errors.empty?
+
+ assign_errors_from_auth_hash
+ true
+ end
+
+ def assign_errors_from_auth_hash
+ auth_hash.errors.each do |attr, error|
+ gl_user.errors.add(attr, error)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/pages.rb b/lib/gitlab/ci/config/entry/pages.rb
index 57d9e944f51..e61bdb9d693 100644
--- a/lib/gitlab/ci/config/entry/pages.rb
+++ b/lib/gitlab/ci/config/entry/pages.rb
@@ -9,7 +9,7 @@ module Gitlab
# Entry that represents the pages attributes
#
class Pages < ::Gitlab::Config::Entry::Node
- ALLOWED_KEYS = %i[path_prefix].freeze
+ ALLOWED_KEYS = %i[path_prefix expire_in].freeze
include ::Gitlab::Config::Entry::Attributable
include ::Gitlab::Config::Entry::Validatable
@@ -22,6 +22,7 @@ module Gitlab
with_options allow_nil: true do
validates :path_prefix, type: String
+ validates :expire_in, duration: { parser: ::Gitlab::Ci::Build::DurationParser }
end
end
end
diff --git a/lib/gitlab/patch/sidekiq_cron_poller.rb b/lib/gitlab/patch/sidekiq_cron_poller.rb
index 27361fa27af..a40ebae4789 100644
--- a/lib/gitlab/patch/sidekiq_cron_poller.rb
+++ b/lib/gitlab/patch/sidekiq_cron_poller.rb
@@ -7,7 +7,7 @@
require 'sidekiq/version'
require 'sidekiq/cron/version'
-if Gem::Version.new(Sidekiq::VERSION) != Gem::Version.new('7.3.1')
+if Gem::Version.new(Sidekiq::VERSION) != Gem::Version.new('7.2.4')
raise 'New version of sidekiq detected, please remove or update this patch'
end
diff --git a/lib/gitlab/patch/sidekiq_job_setter.rb b/lib/gitlab/patch/sidekiq_job_setter.rb
index fd7e2c29b09..c16218c7cde 100644
--- a/lib/gitlab/patch/sidekiq_job_setter.rb
+++ b/lib/gitlab/patch/sidekiq_job_setter.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-if Gem::Version.new(Sidekiq::VERSION) != Gem::Version.new('7.3.1')
+if Gem::Version.new(Sidekiq::VERSION) != Gem::Version.new('7.2.4')
raise 'New version of sidekiq detected, please remove or update this patch'
end
diff --git a/lib/omni_auth/strategies/jwt.rb b/lib/omni_auth/strategies/jwt.rb
index 44caa2095f9..1caf48b9679 100644
--- a/lib/omni_auth/strategies/jwt.rb
+++ b/lib/omni_auth/strategies/jwt.rb
@@ -7,7 +7,12 @@ require 'jwt'
module OmniAuth
module Strategies
class Jwt
+ # Many web servers limit max header size to 8KB. It's also possible to POST a JWT using GET method
+ # to avoid header limit. Allow up to 10KB for flexibility while still balancing performance.
+ MAX_JWT_BYTESIZE = 10_000
+
ClaimInvalid = Class.new(StandardError)
+ JwtTooLarge = Class.new(StandardError)
include OmniAuth::Strategy
@@ -38,19 +43,13 @@ module OmniAuth
end
def decoded
- secret =
- case options.algorithm
- when *%w[RS256 RS384 RS512]
- OpenSSL::PKey::RSA.new(options.secret).public_key
- when *%w[ES256 ES384 ES512]
- OpenSSL::PKey::EC.new(options.secret)
- when *%w[HS256 HS384 HS512]
- options.secret
- else
- raise NotImplementedError, "Unsupported algorithm: #{options.algorithm}"
- end
+ jwt = request.params['jwt']
- @decoded ||= ::JWT.decode(request.params['jwt'], secret, true, { algorithm: options.algorithm }).first
+ if Feature.enabled?(:omniauth_validate_email_length, :instance) && jwt.bytesize >= MAX_JWT_BYTESIZE
+ raise JwtTooLarge, _('JWT must be less than 10KB')
+ end
+
+ @decoded ||= ::JWT.decode(jwt, secret, true, { algorithm: options.algorithm }).first
(options.required_claims || []).each do |field|
raise ClaimInvalid, "Missing required '#{field}' claim" unless @decoded.key?(field.to_s)
@@ -69,6 +68,21 @@ module OmniAuth
super
rescue ClaimInvalid => e
fail! :claim_invalid, e
+ rescue JwtTooLarge => e
+ fail! :jwt_too_large, e
+ end
+
+ def secret
+ case options.algorithm
+ when *%w[RS256 RS384 RS512]
+ OpenSSL::PKey::RSA.new(options.secret).public_key
+ when *%w[ES256 ES384 ES512]
+ OpenSSL::PKey::EC.new(options.secret)
+ when *%w[HS256 HS384 HS512]
+ options.secret
+ else
+ raise NotImplementedError, "Unsupported algorithm: #{options.algorithm}"
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index abbf27ea67d..54b6dec1ff1 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -865,6 +865,9 @@ msgstr ""
msgid "%{fork_icon} %{source_project_path}:%{source_branch}"
msgstr ""
+msgid "%{get_started}Get started with Git%{get_started_end} and learn %{git_config}how to configure it%{git_config_end}."
+msgstr ""
+
msgid "%{global_id} is not a valid ID for %{expected_types}."
msgstr ""
@@ -1046,6 +1049,9 @@ msgstr ""
msgid "%{linkStart}Advanced search%{linkEnd} is enabled."
msgstr ""
+msgid "%{link}How to use SSH keys%{link_end}?"
+msgstr ""
+
msgid "%{listToShow}, and %{awardsListLength} more"
msgstr ""
@@ -3299,6 +3305,9 @@ msgstr ""
msgid "Add existing issue"
msgstr ""
+msgid "Add files"
+msgstr ""
+
msgid "Add group or project"
msgstr ""
@@ -14524,6 +14533,15 @@ msgstr ""
msgid "Configure with a merge request"
msgstr ""
+msgid "Configure your Git identity"
+msgstr ""
+
+msgid "Configure your Git identity globally to use it for all current and future projects on your machine:"
+msgstr ""
+
+msgid "Configure your Git identity locally to use it only for this project:"
+msgstr ""
+
msgid "Configure your environments to be deployed to specific geographical regions"
msgstr ""
@@ -24252,6 +24270,9 @@ msgstr ""
msgid "Git global setup"
msgstr ""
+msgid "Git local setup"
+msgstr ""
+
msgid "Git repository URL"
msgstr ""
@@ -24804,6 +24825,9 @@ msgstr ""
msgid "Given inputs not defined in the `spec` section of the included configuration file"
msgstr ""
+msgid "Global"
+msgstr ""
+
msgid "Global SAML group membership lock"
msgstr ""
@@ -26714,6 +26738,9 @@ msgstr ""
msgid "HTTP Basic: Access denied. The provided password or token is incorrect or your account has 2FA enabled and you must use a personal access token instead of a password. See %{help_page_url}"
msgstr ""
+msgid "HTTPS"
+msgstr ""
+
msgid "Harbor Registry"
msgstr ""
@@ -30045,6 +30072,9 @@ msgstr ""
msgid "Iteration|cannot be more than 500 years in the future"
msgstr ""
+msgid "JWT must be less than 10KB"
+msgstr ""
+
msgid "Jan"
msgstr ""
@@ -30975,6 +31005,9 @@ msgstr ""
msgid "Keep sidebar visible"
msgstr ""
+msgid "Kerberos"
+msgstr ""
+
msgid "Kerberos access denied"
msgstr ""
@@ -44164,6 +44197,9 @@ msgstr ""
msgid "Push events"
msgstr ""
+msgid "Push files to this repository using SSH or HTTPS. If you're unsure, we recommend SSH."
+msgstr ""
+
msgid "Push mirrors will %{strong_open}not%{strong_close} sync LFS objects over SSH."
msgstr ""
@@ -47238,6 +47274,9 @@ msgstr ""
msgid "SOC2"
msgstr ""
+msgid "SSH"
+msgstr ""
+
msgid "SSH Key"
msgstr ""
@@ -56230,6 +56269,9 @@ msgstr ""
msgid "To get started, click the link below to confirm your account."
msgstr ""
+msgid "To get started, clone the repository or upload some files."
+msgstr ""
+
msgid "To get started, please enter your Gitea host URL and a %{link_to_personal_token}."
msgstr ""
@@ -58748,9 +58790,6 @@ msgstr ""
msgid "UserMapping|Reassignment approved"
msgstr ""
-msgid "UserMapping|Reassignment cancelled"
-msgstr ""
-
msgid "UserMapping|Reassignment details:"
msgstr ""
@@ -58763,6 +58802,9 @@ msgstr ""
msgid "UserMapping|Reassignment in progress."
msgstr ""
+msgid "UserMapping|Reassignment not available"
+msgstr ""
+
msgid "UserMapping|Reassignment rejected"
msgstr ""
@@ -58808,12 +58850,6 @@ msgstr ""
msgid "UserMapping|The invitation could not be declined."
msgstr ""
-msgid "UserMapping|The invitation is no longer valid."
-msgstr ""
-
-msgid "UserMapping|The reassignment has been cancelled by the group owner."
-msgstr ""
-
msgid "UserMapping|There was a problem cancelling placeholder user reassignment."
msgstr ""
@@ -58847,6 +58883,9 @@ msgstr ""
msgid "UserMapping|You have rejected the reassignment of contributions from %{strong_open}%{source_user_name} (@%{source_username})%{strong_close} on %{strong_open}%{source_hostname}%{strong_close} to yourself on the group %{strong_open}%{destination_group}%{strong_close}."
msgstr ""
+msgid "UserMapping|You might have already accepted or rejected the reassignment, or it might have been canceled by the group owner."
+msgstr ""
+
msgid "UserProfile|%{count} %{file}"
msgstr ""
@@ -62226,9 +62265,6 @@ msgstr ""
msgid "You can find more information about GitLab subscriptions in %{subscriptions_doc_link}."
msgstr ""
-msgid "You can get started by cloning the repository or start adding files to it with one of the following options."
-msgstr ""
-
msgid "You can invite a new member to %{project_name} or invite another group."
msgstr ""
@@ -64765,6 +64801,9 @@ msgstr ""
msgid "mrWidget|Your password"
msgstr ""
+msgid "must be 254 characters or less."
+msgstr ""
+
msgid "must be a boolean value"
msgstr ""
diff --git a/package.json b/package.json
index 8cf385ab01a..4da046e2055 100644
--- a/package.json
+++ b/package.json
@@ -251,7 +251,7 @@
"yaml": "^2.0.0-10"
},
"devDependencies": {
- "@gitlab/eslint-plugin": "20.0.0",
+ "@gitlab/eslint-plugin": "20.2.0",
"@gitlab/stylelint-config": "6.2.1",
"@graphql-eslint/eslint-plugin": "3.20.1",
"@originjs/vite-plugin-commonjs": "^1.0.3",
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 4b9b56dfaf3..e489784c67d 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -28,6 +28,7 @@ RSpec.describe 'Database schema', feature_category: :database do
p_ci_build_trace_metadata: [%w[partition_id build_id], %w[partition_id trace_artifact_id]], # the index on build_id is enough
p_ci_builds: [%w[partition_id stage_id], %w[partition_id execution_config_id], %w[auto_canceled_by_partition_id auto_canceled_by_id], %w[upstream_pipeline_partition_id upstream_pipeline_id], %w[partition_id commit_id]], # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142804#note_1745483081
p_ci_builds_execution_configs: [%w[partition_id pipeline_id]], # the index on pipeline_id is enough
+ p_ci_pipelines: [%w[auto_canceled_by_partition_id auto_canceled_by_id]], # index on auto_canceled_by_id is sufficient
p_ci_pipeline_variables: [%w[partition_id pipeline_id]], # index on pipeline_id is sufficient
p_ci_stages: [%w[partition_id pipeline_id]], # the index on pipeline_id is sufficient
# `search_index_id index_type` is the composite foreign key configured for `search_namespace_index_assignments`,
@@ -100,7 +101,7 @@ RSpec.describe 'Database schema', feature_category: :database do
deployments: %w[deployable_id user_id],
draft_notes: %w[discussion_id commit_id],
epics: %w[updated_by_id last_edited_by_id state_id],
- events: %w[target_id],
+ events: %w[target_id personal_namespace_id],
forked_project_links: %w[forked_from_project_id],
geo_event_log: %w[hashed_storage_attachments_event_id repositories_changed_event_id],
geo_node_statuses: %w[last_event_id cursor_last_event_id],
diff --git a/spec/factories/import_source_users.rb b/spec/factories/import_source_users.rb
index 2943d1a8355..2f089a83293 100644
--- a/spec/factories/import_source_users.rb
+++ b/spec/factories/import_source_users.rb
@@ -23,6 +23,7 @@ FactoryBot.define do
end
trait :awaiting_approval do
+ with_reassign_to_user
status { 1 }
end
diff --git a/spec/finders/import/source_users_finder_spec.rb b/spec/finders/import/source_users_finder_spec.rb
index 941b23fdfbd..58be0741d83 100644
--- a/spec/finders/import/source_users_finder_spec.rb
+++ b/spec/finders/import/source_users_finder_spec.rb
@@ -5,10 +5,10 @@ require 'spec_helper'
RSpec.describe Import::SourceUsersFinder, feature_category: :importers do
let_it_be(:user) { build_stubbed(:user) }
let_it_be(:group) { create(:group) }
- let_it_be(:source_user_1) { create(:import_source_user, namespace: group, status: 0, source_name: 'b') }
- let_it_be(:source_user_2) { create(:import_source_user, namespace: group, status: 1, source_name: 'c') }
+ let_it_be(:source_user_1) { create(:import_source_user, :pending_reassignment, namespace: group, source_name: 'b') }
+ let_it_be(:source_user_2) { create(:import_source_user, :awaiting_approval, namespace: group, source_name: 'c') }
let_it_be(:source_user_3) do
- create(:import_source_user, :with_reassign_to_user, namespace: group, status: 2, source_name: 'a')
+ create(:import_source_user, :reassignment_in_progress, namespace: group, source_name: 'a')
end
let_it_be(:import_source_users) { [source_user_1, source_user_2, source_user_3] }
diff --git a/spec/frontend/deploy_keys/components/app_spec.js b/spec/frontend/deploy_keys/components/app_spec.js
index 5e012bc1c51..2c130ad34cf 100644
--- a/spec/frontend/deploy_keys/components/app_spec.js
+++ b/spec/frontend/deploy_keys/components/app_spec.js
@@ -81,9 +81,12 @@ describe('Deploy keys app component', () => {
const findNavigationTabs = () => wrapper.findComponent(NavigationTabs);
it('renders loading icon while waiting for request', async () => {
+ currentScopeMock.mockResolvedValue('enabledKeys');
+ currentPageMock.mockResolvedValue(1);
deployKeyMock.mockReturnValue(new Promise(() => {}));
mountComponent();
+ await waitForPromises();
await nextTick();
expect(findLoadingIcon().exists()).toBe(true);
});
@@ -180,6 +183,8 @@ describe('Deploy keys app component', () => {
});
it('re-fetches deploy keys when disabling a key', async () => {
+ currentScopeMock.mockResolvedValue('enabledKeys');
+ currentPageMock.mockResolvedValue(1);
confirmRemoveKeyMock.mockReturnValue(key);
await mountComponent();
expect(deployKeyMock).toHaveBeenCalledTimes(1);
diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
index fab5a7b8844..88423c8a30a 100644
--- a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
+++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
@@ -1,10 +1,12 @@
import Vue, { nextTick } from 'vue';
import { createWrapper } from '@vue/test-utils';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_action';
import ConfirmModal from '~/lib/utils/confirm_via_gl_modal/confirm_modal.vue';
const originalMount = Vue.prototype.$mount;
+const { bindInternalEventDocument } = useMockInternalEventsTracking();
describe('confirmAction', () => {
let modalWrapper;
@@ -101,4 +103,19 @@ describe('confirmAction', () => {
await expect(confirActionPromise).resolves.toBe(true);
});
+
+ it('emits a tracking event when modal emit `confirmed` and event name is provided', async () => {
+ const trackingEvent = {
+ name: 'test_event',
+ label: 'test_label',
+ };
+ await renderRootComponent('', { trackingEvent });
+ const { trackEventSpy } = bindInternalEventDocument(modalWrapper.element);
+
+ modal.vm.$emit('confirmed');
+
+ expect(trackEventSpy).toHaveBeenCalledWith('test_event', {
+ label: 'test_label',
+ });
+ });
});
diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal_spec.js
index 6966c79b232..c7a43f58e6e 100644
--- a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal_spec.js
+++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal_spec.js
@@ -77,4 +77,22 @@ describe('confirmViaGlModal', () => {
expect(confirmAction).toHaveBeenCalledWith(message, { modalHtmlMessage: message });
});
+
+ it('uses data-tracking-event attributes to prepare trackEventConfig', () => {
+ el = createElement(
+ ``,
+ );
+ const message = 'Test message';
+
+ confirmViaGlModal(message, el);
+
+ expect(confirmAction).toHaveBeenCalledWith(message, {
+ trackingEvent: {
+ name: 'test_event',
+ label: 'test_label',
+ property: 'test_property',
+ value: 'test_value',
+ },
+ });
+ });
});
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
index d58c76988fe..bdf6a1cfed6 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
@@ -3,6 +3,7 @@ import VueApollo from 'vue-apollo';
import { GlModal, GlCollapsibleListbox, GlToast } from '@gitlab/ui';
import { sprintf } from '~/locale';
import * as util from '~/lib/utils/url_utility';
+import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -75,6 +76,7 @@ describe('View branch rules', () => {
.mockResolvedValue(protectableBranchesMockResponse);
const errorHandler = jest.fn().mockRejectedValue('error');
const showToast = jest.fn();
+ const { bindInternalEventDocument } = useMockInternalEventsTracking();
const createComponent = async ({
glFeatures = { editBranchRules: true },
@@ -289,6 +291,16 @@ describe('View branch rules', () => {
);
});
+ it('emits a tracking event when edit button in modal is clicked', async () => {
+ const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
+ findBranchRuleModal().vm.$emit('primary', '*-test');
+ await waitForPromises();
+
+ expect(trackEventSpy).toHaveBeenCalledWith('change_branch_rule_target', {
+ label: 'branch_rule_details',
+ });
+ });
+
it('shows an alert if response contains an error', async () => {
const mockResponse = { branchRuleUpdate: { errors: ['some error'], branchRule: null } };
const editMutationHandler = jest
@@ -345,6 +357,17 @@ describe('View branch rules', () => {
expect(util.visitUrl).toHaveBeenCalledWith('/-/settings/repository#branch_rules');
});
+ it('emits tracking event when branch rule is deleted', async () => {
+ const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
+ findDeleteRuleModal().vm.$emit('ok');
+ await nextTick();
+ await waitForPromises();
+
+ expect(trackEventSpy).toHaveBeenCalledWith('unprotect_branch', {
+ label: 'branch_rule_details',
+ });
+ });
+
it('if error happens it shows an alert', async () => {
await createComponent({
glFeatures: { editBranchRules: true },
@@ -391,7 +414,7 @@ describe('View branch rules', () => {
drawerType | title | findProtection | accessLevels
${'merge'} | ${'Edit allowed to merge'} | ${findAllowedToMerge} | ${{ mergeAccessLevels: [{ accessLevel: 30 }] }}
${'push and merge'} | ${'Edit allowed to push and merge'} | ${findAllowedToPush} | ${{ pushAccessLevels: [{ accessLevel: 30 }] }}
- `('allowed to $drawerType drawer', ({ title, findProtection, accessLevels }) => {
+ `('allowed to $drawerType drawer', ({ drawerType, title, findProtection, accessLevels }) => {
const openEditRuleDrawer = () => {
findProtection().vm.$emit('edit');
return nextTick();
@@ -435,6 +458,19 @@ describe('View branch rules', () => {
expect(findRuleDrawer().props('isLoading')).toEqual(false);
});
+
+ it('emits a tracking event when save button is clicked', async () => {
+ const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
+ await openEditRuleDrawer();
+ findRuleDrawer().vm.$emit('editRule', [{ accessLevel: 30 }]);
+ await waitForPromises();
+
+ const eventName =
+ drawerType === 'merge' ? 'change_allowed_to_merge' : 'change_allowed_to_push_and_merge';
+ expect(trackEventSpy).toHaveBeenCalledWith(eventName, {
+ label: 'branch_rule_details',
+ });
+ });
});
describe('Allow force push editing', () => {
@@ -468,6 +504,16 @@ describe('View branch rules', () => {
}),
);
});
+
+ it('emits a tracking event when a toggle is triggered', async () => {
+ const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
+ findAllowForcePushToggle().vm.$emit('toggle', false);
+ await nextTick();
+ await waitForPromises();
+ expect(trackEventSpy).toHaveBeenCalledWith('change_allow_force_push', {
+ label: 'branch_rule_details',
+ });
+ });
});
describe('When rendered for a non-existing rule', () => {
diff --git a/spec/frontend/projects/settings/repository/branch_rules/app_spec.js b/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
index fd06a5ced81..c1206556a5b 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
@@ -6,6 +6,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import BranchRules from '~/projects/settings/repository/branch_rules/app.vue';
import BranchRule from '~/projects/settings/repository/branch_rules/components/branch_rule.vue';
import branchRulesQuery from 'ee_else_ce/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql';
@@ -46,6 +47,7 @@ describe('Branch rules app', () => {
.fn()
.mockResolvedValue(protectableBranchesMockResponse);
const addBranchRulesItems = [I18N.branchName, I18N.allBranches, I18N.allProtectedBranches];
+ const { bindInternalEventDocument } = useMockInternalEventsTracking();
const createComponent = async ({
glFeatures = { editBranchRules: true },
@@ -179,6 +181,18 @@ describe('Branch rules app', () => {
message: 'Something went wrong while creating branch rule.',
});
});
+
+ it('emits a tracking event when a rule is added', async () => {
+ const { trackEventSpy } = bindInternalEventDocument(wrapper.element);
+ findCreateBranchRuleListbox().vm.$emit('select', 'main');
+ await nextTick();
+ findModal().vm.$emit('primary');
+ await nextTick();
+
+ expect(trackEventSpy).toHaveBeenCalledWith('protect_branch', {
+ label: 'branch_rule_details',
+ });
+ });
});
describe('Add branch rule when editBranchRules FF disabled', () => {
diff --git a/spec/frontend/protected_branches/protected_branch_edit_spec.js b/spec/frontend/protected_branches/protected_branch_edit_spec.js
index edcc91e07f2..ac559611f69 100644
--- a/spec/frontend/protected_branches/protected_branch_edit_spec.js
+++ b/spec/frontend/protected_branches/protected_branch_edit_spec.js
@@ -7,6 +7,7 @@ import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import ProtectedBranchEdit from '~/protected_branches/protected_branch_edit';
import waitForPromises from 'helpers/wait_for_promises';
+import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
jest.mock('~/alert');
@@ -64,6 +65,8 @@ const PUSH_DROPDOWN_TESTID = 'protected-branch-allowed-to-push';
const INIT_MERGE_DATA_TESTID = 'js-allowed-to-merge';
const INIT_PUSH_DATA_TESTID = 'js-allowed-to-push';
+const { bindInternalEventDocument } = useMockInternalEventsTracking();
+
describe('ProtectedBranchEdit', () => {
let mock;
@@ -280,6 +283,32 @@ describe('ProtectedBranchEdit', () => {
}),
);
});
+
+ it('emits a tracking event for Allowed to merge dropdown', async () => {
+ const selectedValue = [{ access_level: 40 }];
+ const ProtectedBranchEditInstance = create();
+ const dropdown = ProtectedBranchEditInstance.merge_access_levels_dropdown;
+ const { trackEventSpy } = bindInternalEventDocument(dropdown.element);
+ dropdown.$emit('select', selectedValue);
+ dropdown.$emit('hidden');
+ await waitForPromises();
+ expect(trackEventSpy).toHaveBeenCalledWith('change_allowed_to_merge', {
+ label: 'repository_settings',
+ });
+ });
+
+ it('emits a tracking event for Allowed to push and merge dropdown', async () => {
+ const selectedValue = [{ access_level: 40 }];
+ const ProtectedBranchEditInstance = create();
+ const dropdown = ProtectedBranchEditInstance.push_access_levels_dropdown;
+ const { trackEventSpy } = bindInternalEventDocument(dropdown.element);
+ dropdown.$emit('select', selectedValue);
+ dropdown.$emit('hidden');
+ await waitForPromises();
+ expect(trackEventSpy).toHaveBeenCalledWith('change_allowed_to_push_and_merge', {
+ label: 'repository_settings',
+ });
+ });
});
});
@@ -328,65 +357,79 @@ describe('ProtectedBranchEdit', () => {
});
describe.each`
- description | checkedOption | patchParam | finder
- ${'force push'} | ${'forcePushToggleChecked'} | ${'allow_force_push'} | ${findForcePushToggle}
- ${'code owner'} | ${'codeOwnerToggleChecked'} | ${'code_owner_approval_required'} | ${findCodeOwnerToggle}
- `('when unchecked $description toggle button', ({ checkedOption, patchParam, finder }) => {
- let toggle;
+ description | checkedOption | patchParam | finder | trackingEvent
+ ${'force push'} | ${'forcePushToggleChecked'} | ${'allow_force_push'} | ${findForcePushToggle} | ${'change_allow_force_push'}
+ ${'code owner'} | ${'codeOwnerToggleChecked'} | ${'code_owner_approval_required'} | ${findCodeOwnerToggle} | ${'change_require_codeowner_approval'}
+ `(
+ 'when unchecked $description toggle button',
+ ({ checkedOption, patchParam, finder, trackingEvent }) => {
+ let toggle;
- beforeEach(() => {
- create({ [checkedOption]: false });
-
- toggle = finder();
- });
-
- it('is not changed', () => {
- expect(toggle).not.toHaveClass(IS_CHECKED_CLASS);
- expect(toggle.querySelector(IS_LOADING_SELECTOR)).toBe(null);
- expect(toggle).not.toHaveClass(IS_DISABLED_CLASS);
- });
-
- describe('when clicked', () => {
beforeEach(() => {
- mock
- .onPatch(TEST_URL, { protected_branch: { [patchParam]: true } })
- .replyOnce(HTTP_STATUS_OK, {});
+ create({ [checkedOption]: false });
+
+ toggle = finder();
});
- it('checks and disables button', async () => {
- await toggle.click();
-
- expect(toggle).toHaveClass(IS_CHECKED_CLASS);
- expect(toggle.querySelector(IS_LOADING_SELECTOR)).not.toBe(null);
- expect(toggle).toHaveClass(IS_DISABLED_CLASS);
- });
-
- it('sends update to BE', async () => {
- await toggle.click();
-
- await axios.waitForAll();
-
- // Args are asserted in the `.onPatch` call
- expect(mock.history.patch).toHaveLength(1);
-
- expect(toggle).not.toHaveClass(IS_DISABLED_CLASS);
+ it('is not changed', () => {
+ expect(toggle).not.toHaveClass(IS_CHECKED_CLASS);
expect(toggle.querySelector(IS_LOADING_SELECTOR)).toBe(null);
- expect(createAlert).not.toHaveBeenCalled();
- });
- });
-
- describe('when clicked and BE error', () => {
- beforeEach(() => {
- mock.onPatch(TEST_URL).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
- toggle.click();
+ expect(toggle).not.toHaveClass(IS_DISABLED_CLASS);
});
- it('alerts error', async () => {
- await axios.waitForAll();
+ describe('when clicked', () => {
+ beforeEach(() => {
+ mock
+ .onPatch(TEST_URL, { protected_branch: { [patchParam]: true } })
+ .replyOnce(HTTP_STATUS_OK, {});
+ });
- expect(createAlert).toHaveBeenCalled();
+ it('checks and disables button', async () => {
+ await toggle.click();
+
+ expect(toggle).toHaveClass(IS_CHECKED_CLASS);
+ expect(toggle.querySelector(IS_LOADING_SELECTOR)).not.toBe(null);
+ expect(toggle).toHaveClass(IS_DISABLED_CLASS);
+ });
+
+ it('sends update to BE', async () => {
+ await toggle.click();
+
+ await axios.waitForAll();
+
+ // Args are asserted in the `.onPatch` call
+ expect(mock.history.patch).toHaveLength(1);
+
+ expect(toggle).not.toHaveClass(IS_DISABLED_CLASS);
+ expect(toggle.querySelector(IS_LOADING_SELECTOR)).toBe(null);
+ expect(createAlert).not.toHaveBeenCalled();
+ });
+
+ it('emits a tracking event when clicked', async () => {
+ const { trackEventSpy } = bindInternalEventDocument(toggle.element);
+
+ await toggle.click();
+ await axios.waitForAll();
+
+ expect(trackEventSpy).toHaveBeenCalledWith(trackingEvent, {
+ label: 'repository_settings',
+ });
+ });
});
- });
- });
+
+ describe('when clicked and BE error', () => {
+ beforeEach(() => {
+ mock.onPatch(TEST_URL).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ toggle.click();
+ });
+
+ it('alerts error', async () => {
+ await axios.waitForAll();
+
+ expect(createAlert).toHaveBeenCalled();
+ });
+ });
+ },
+ );
});
});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js
index 95251099b7b..e4c0b2e9477 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js
@@ -6,6 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { isLoggedIn } from '~/lib/utils/common_utils';
+import { ESC_KEY } from '~/lib/utils/keys';
import WorkItemChildrenWrapper from '~/work_items/components/work_item_links/work_item_children_wrapper.vue';
import WorkItemLinkChild from '~/work_items/components/work_item_links/work_item_link_child.vue';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
@@ -172,20 +173,52 @@ describe('WorkItemChildrenWrapper', () => {
});
describe('drag & drop', () => {
+ let dragParams;
+
beforeEach(() => {
isLoggedIn.mockReturnValue(true);
createComponent({ canUpdate: true, children: childrenWorkItemsObjectives });
+
+ dragParams = {
+ oldIndex: 1,
+ newIndex: 0,
+ from: wrapper.element,
+ to: wrapper.element,
+ };
+ });
+
+ it('adds a class `is-dragging` to document body when dragging', async () => {
+ expect(document.body.classList.contains('is-dragging')).toBe(false);
+
+ wrapper.findComponent(Draggable).vm.$emit('start');
+
+ expect(document.body.classList.contains('is-dragging')).toBe(true);
+
+ wrapper.findComponent(Draggable).vm.$emit('end', dragParams);
+ await nextTick();
+
+ expect(document.body.classList.contains('is-dragging')).toBe(false);
+ });
+
+ it('dispatches `mouseup` event and cancels drag when Escape key is pressed', async () => {
+ jest.spyOn(document, 'dispatchEvent');
+ wrapper.findComponent(Draggable).vm.$emit('start');
+
+ const event = new Event('keyup');
+ event.code = ESC_KEY;
+ document.dispatchEvent(event);
+
+ wrapper.findComponent(Draggable).vm.$emit('end', dragParams);
+ await nextTick();
+
+ expect(document.dispatchEvent).toHaveBeenCalledWith(new Event('mouseup'));
+ expect(moveWorkItemMutationSuccessHandler).not.toHaveBeenCalled();
});
it('does not fetch nested children when reordering within the same work item', async () => {
expect(wrapper.findComponent(Draggable).exists()).toBe(true);
- wrapper.findComponent(Draggable).vm.$emit('end', {
- oldIndex: 1,
- newIndex: 0,
- from: wrapper.element,
- to: wrapper.element,
- });
+ wrapper.findComponent(Draggable).vm.$emit('end', dragParams);
await nextTick();
expect(getWorkItemTreeQueryHandler).not.toHaveBeenCalled();
@@ -195,9 +228,7 @@ describe('WorkItemChildrenWrapper', () => {
expect(wrapper.findComponent(Draggable).exists()).toBe(true);
wrapper.findComponent(Draggable).vm.$emit('end', {
- oldIndex: 1,
- newIndex: 0,
- from: wrapper.element,
+ ...dragParams,
to: { dataset: { parentId: 'gid://gitlab/WorkItem/5', parentTitle: 'Objective 19' } },
});
await nextTick();
@@ -208,12 +239,7 @@ describe('WorkItemChildrenWrapper', () => {
it('calls move mutation with reorder params when reordering within the same work item', async () => {
expect(wrapper.findComponent(Draggable).exists()).toBe(true);
- wrapper.findComponent(Draggable).vm.$emit('end', {
- oldIndex: 1,
- newIndex: 0,
- from: wrapper.element,
- to: wrapper.element,
- });
+ wrapper.findComponent(Draggable).vm.$emit('end', dragParams);
await waitForPromises();
expect(moveWorkItemMutationSuccessHandler).toHaveBeenCalledWith({
@@ -231,9 +257,7 @@ describe('WorkItemChildrenWrapper', () => {
expect(wrapper.findComponent(Draggable).exists()).toBe(true);
wrapper.findComponent(Draggable).vm.$emit('end', {
- oldIndex: 1,
- newIndex: 0,
- from: wrapper.element,
+ ...dragParams,
to: { dataset: { parentId: 'gid://gitlab/WorkItem/5', parentTitle: 'Objective 19' } },
});
await waitForPromises();
@@ -257,9 +281,7 @@ describe('WorkItemChildrenWrapper', () => {
});
wrapper.findComponent(Draggable).vm.$emit('end', {
- oldIndex: 1,
- newIndex: 0,
- from: wrapper.element,
+ ...dragParams,
to: { dataset: { parentId: 'gid://gitlab/WorkItem/5', parentTitle: 'Objective 19' } },
});
await waitForPromises();
@@ -277,12 +299,7 @@ describe('WorkItemChildrenWrapper', () => {
moveWorkItemMutationHandler: moveWorkItemMutationFailureHandler,
});
- wrapper.findComponent(Draggable).vm.$emit('end', {
- oldIndex: 1,
- newIndex: 0,
- from: wrapper.element,
- to: wrapper.element,
- });
+ wrapper.findComponent(Draggable).vm.$emit('end', dragParams);
await waitForPromises();
expect(moveWorkItemMutationFailureHandler).toHaveBeenCalled();
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
index 9ffd699c680..f646f5395a4 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
@@ -21,6 +21,8 @@ import WorkItemMoreActions from '~/work_items/components/shared/work_item_more_a
import { FORM_TYPES } from '~/work_items/constants';
import getWorkItemTreeQuery from '~/work_items/graphql/work_item_tree.query.graphql';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import * as utils from '~/work_items/utils';
import {
getIssueDetailsResponse,
workItemHierarchyTreeResponse,
@@ -308,6 +310,18 @@ describe('WorkItemLinks', () => {
});
describe('more actions', () => {
+ useLocalStorageSpy();
+
+ beforeEach(async () => {
+ jest.spyOn(utils, 'getShowLabelsFromLocalStorage');
+ jest.spyOn(utils, 'saveShowLabelsToLocalStorage');
+ await createComponent();
+ });
+
+ afterEach(() => {
+ localStorage.clear();
+ });
+
it('renders the `WorkItemMoreActions` component', async () => {
await createComponent();
@@ -337,5 +351,14 @@ describe('WorkItemLinks', () => {
expect(findWorkItemLinkChildrenWrapper().props('showLabels')).toBe(true);
});
+
+ it('calls saveShowLabelsToLocalStorage on toggle', () => {
+ findMoreActions().vm.$emit('toggle-show-labels');
+ expect(utils.saveShowLabelsToLocalStorage).toHaveBeenCalled();
+ });
+
+ it('calls getShowLabelsFromLocalStorage on mount', () => {
+ expect(utils.getShowLabelsFromLocalStorage).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
index e560812855d..ce338e5a54b 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
@@ -21,6 +21,8 @@ import {
WORK_ITEM_TYPE_VALUE_EPIC,
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
} from '~/work_items/constants';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import * as utils from '~/work_items/utils';
import {
workItemHierarchyTreeResponse,
workItemHierarchyPaginatedTreeResponse,
@@ -261,6 +263,18 @@ describe('WorkItemTree', () => {
});
describe('more actions', () => {
+ useLocalStorageSpy();
+
+ beforeEach(async () => {
+ jest.spyOn(utils, 'getShowLabelsFromLocalStorage');
+ jest.spyOn(utils, 'saveShowLabelsToLocalStorage');
+ await createComponent();
+ });
+
+ afterEach(() => {
+ localStorage.clear();
+ });
+
it.each`
visible | workItemType
${true} | ${WORK_ITEM_TYPE_VALUE_EPIC}
@@ -294,5 +308,14 @@ describe('WorkItemTree', () => {
expect(findWorkItemLinkChildrenWrapper().props('showLabels')).toBe(true);
});
+
+ it('calls saveShowLabelsToLocalStorage on toggle', () => {
+ findMoreActions().vm.$emit('toggle-show-labels');
+ expect(utils.saveShowLabelsToLocalStorage).toHaveBeenCalled();
+ });
+
+ it('calls getShowLabelsFromLocalStorage on mount', () => {
+ expect(utils.getShowLabelsFromLocalStorage).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js
index 85060a76187..caa01670a7a 100644
--- a/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js
+++ b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js
@@ -14,6 +14,8 @@ import workItemLinkedItemsQuery from '~/work_items/graphql/work_item_linked_item
import WorkItemMoreActions from '~/work_items/components/shared/work_item_more_actions.vue';
import removeLinkedItemsMutation from '~/work_items/graphql/remove_linked_items.mutation.graphql';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import * as utils from '~/work_items/utils';
import {
removeLinkedWorkItemResponse,
workItemLinkedItemsResponse,
@@ -186,6 +188,18 @@ describe('WorkItemRelationships', () => {
);
describe('more actions', () => {
+ useLocalStorageSpy();
+
+ beforeEach(async () => {
+ jest.spyOn(utils, 'getShowLabelsFromLocalStorage');
+ jest.spyOn(utils, 'saveShowLabelsToLocalStorage');
+ await createComponent();
+ });
+
+ afterEach(() => {
+ localStorage.clear();
+ });
+
it('renders the `WorkItemMoreActions` component', async () => {
await createComponent();
@@ -215,5 +229,14 @@ describe('WorkItemRelationships', () => {
expect(findAllWorkItemRelationshipListComponents().at(0).props('showLabels')).toBe(true);
});
+
+ it('calls saveShowLabelsToLocalStorage on toggle', () => {
+ findMoreActions().vm.$emit('toggle-show-labels');
+ expect(utils.saveShowLabelsToLocalStorage).toHaveBeenCalled();
+ });
+
+ it('calls getShowLabelsFromLocalStorage on mount', () => {
+ expect(utils.getShowLabelsFromLocalStorage).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/frontend/work_items/utils_spec.js b/spec/frontend/work_items/utils_spec.js
index 5c294502f44..d5950726321 100644
--- a/spec/frontend/work_items/utils_spec.js
+++ b/spec/frontend/work_items/utils_spec.js
@@ -5,7 +5,10 @@ import {
isReference,
getWorkItemIcon,
workItemRoadmapPath,
+ saveShowLabelsToLocalStorage,
+ getShowLabelsFromLocalStorage,
} from '~/work_items/utils';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
describe('autocompleteDataSources', () => {
beforeEach(() => {
@@ -142,3 +145,57 @@ describe('workItemRoadmapPath', () => {
);
});
});
+
+describe('utils for remembering user showLabel preferences', () => {
+ useLocalStorageSpy();
+
+ afterEach(() => {
+ localStorage.clear();
+ });
+
+ describe('saveShowLabelsToLocalStorage', () => {
+ it('saves the value to localStorage', () => {
+ const TEST_KEY = `test-key-${new Date().getTime}`;
+
+ expect(localStorage.getItem(TEST_KEY)).toBe(null);
+
+ saveShowLabelsToLocalStorage(TEST_KEY, true);
+ expect(localStorage.setItem).toHaveBeenCalled();
+ expect(localStorage.getItem(TEST_KEY)).toBe(true);
+ });
+ });
+
+ describe('getShowLabelsFromLocalStorage', () => {
+ it('defaults to true when there is no value from localStorage and no default value is passed', () => {
+ const TEST_KEY = `test-key-${new Date().getTime}`;
+
+ expect(localStorage.getItem(TEST_KEY)).toBe(null);
+
+ const result = getShowLabelsFromLocalStorage(TEST_KEY);
+ expect(localStorage.getItem).toHaveBeenCalled();
+ expect(result).toBe(true);
+ });
+
+ it('returns the default boolean value passed when there is no value from localStorage', () => {
+ const TEST_KEY = `test-key-${new Date().getTime}`;
+ const DEFAULT_VALUE = false;
+
+ expect(localStorage.getItem(TEST_KEY)).toBe(null);
+
+ const result = getShowLabelsFromLocalStorage(TEST_KEY, DEFAULT_VALUE);
+ expect(localStorage.getItem).toHaveBeenCalled();
+ expect(result).toBe(false);
+ });
+
+ it('returns the boolean value from localStorage if it exists', () => {
+ const TEST_KEY = `test-key-${new Date().getTime}`;
+ const DEFAULT_VALUE = true;
+
+ localStorage.setItem(TEST_KEY, 'false');
+
+ const newResult = getShowLabelsFromLocalStorage(TEST_KEY, DEFAULT_VALUE);
+ expect(localStorage.getItem).toHaveBeenCalled();
+ expect(newResult).toBe(false);
+ });
+ });
+});
diff --git a/spec/graphql/types/work_items/widgets/hierarchy_type_spec.rb b/spec/graphql/types/work_items/widgets/hierarchy_type_spec.rb
index 7cc56c8d97f..cada866bde1 100644
--- a/spec/graphql/types/work_items/widgets/hierarchy_type_spec.rb
+++ b/spec/graphql/types/work_items/widgets/hierarchy_type_spec.rb
@@ -4,7 +4,8 @@ require 'spec_helper'
RSpec.describe Types::WorkItems::Widgets::HierarchyType, feature_category: :team_planning do
it 'exposes the expected fields' do
- expected_fields = %i[parent children has_children has_parent ancestors type rolled_up_counts_by_type]
+ expected_fields = %i[parent children has_children has_parent ancestors type rolled_up_counts_by_type
+ depthLimitReachedByType]
expect(described_class).to have_graphql_fields(*expected_fields)
end
diff --git a/spec/graphql/types/work_items/work_item_type_depth_limit_reached_by_type_spec.rb b/spec/graphql/types/work_items/work_item_type_depth_limit_reached_by_type_spec.rb
new file mode 100644
index 00000000000..7b2bbefeba1
--- /dev/null
+++ b/spec/graphql/types/work_items/work_item_type_depth_limit_reached_by_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::WorkItems::WorkItemTypeDepthLimitReachedByType, feature_category: :team_planning do
+ it 'exposes the expected fields' do
+ expected_fields = %i[work_item_type depth_limit_reached]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb
index d9bb0fed09a..20029012e99 100644
--- a/spec/lib/gitaly/server_spec.rb
+++ b/spec/lib/gitaly/server_spec.rb
@@ -158,7 +158,7 @@ RSpec.describe Gitaly::Server do
before do
allow_next_instance_of(::Gitlab::GitalyClient::ServerService) do |instance|
allow(instance).to receive(:server_signature).and_raise(GRPC::Unavailable)
- end
+ end.once
end
it 'returns an error and no public_key' do
diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
index 5d01f09df41..97a16c443f4 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Auth::OAuth::AuthHash, feature_category: :user_management do
+RSpec.describe Gitlab::Auth::OAuth::AuthHash, aggregate_failures: true, feature_category: :user_management do
let(:provider) { 'openid_connect' }
let(:auth_hash) do
described_class.new(
@@ -67,6 +67,7 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, feature_category: :user_management
it { expect(auth_hash.name).to eql name_utf8 }
it { expect(auth_hash.password).not_to be_empty }
it { expect(auth_hash.location).to eq 'some locality, some country' }
+ it { expect(auth_hash.errors).to be_empty }
end
context 'email not provided' do
@@ -184,6 +185,86 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash, feature_category: :user_management
end
end
+ context 'for email address length validation prior to generating a username' do
+ let(:info_hash) do
+ {
+ email: email,
+ name: 'GitLab test',
+ uid: uid_ascii
+ }
+ end
+
+ context 'when the email address is not too long' do
+ let(:local_part) { generate(:username) }
+ let(:email) { "#{local_part}@example.com" }
+
+ it 'normalizes the string' do
+ expect(auth_hash).to receive(:mb_chars_unicode_normalize).and_call_original
+
+ expect(auth_hash.username).to eq(local_part)
+ expect(auth_hash.errors).to eq({})
+ end
+ end
+
+ context 'when the whole email address is longer than 254 characters' do
+ # Email with unicode characters
+ def long_email_local_part
+ "longemail𒐫" * 300
+ end
+
+ let(:email) { "#{long_email_local_part}@example.com" }
+
+ it 'produces an error and does not normalize the string' do
+ expect(auth_hash).not_to receive(:mb_chars_unicode_normalize).and_call_original
+
+ expect(auth_hash.username).to be_empty
+ expect(auth_hash.errors).to eq({ identity_provider_email: _("must be 254 characters or less.") })
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(omniauth_validate_email_length: false)
+ end
+
+ it 'normalizes the string' do
+ expect(auth_hash).to receive(:mb_chars_unicode_normalize).and_call_original
+
+ expect(auth_hash.username).to eq('longemail' * 300)
+ expect(auth_hash.errors).to be_empty
+ end
+ end
+ end
+
+ context 'when the local part of the email address is longer than 254 characters after normalization' do
+ # Email with unicode characters that normalize to multiple characters
+ def long_email_local_part
+ "email℀℀℀℀℀" * 24
+ end
+
+ let(:email) { "#{long_email_local_part}@example.com" }
+
+ it 'normalizes the string and produces an error' do
+ expect(auth_hash).to receive(:mb_chars_unicode_normalize).and_call_original
+
+ expect(auth_hash.username).to be_empty
+ expect(auth_hash.errors).to eq({ identity_provider_email: _("must be 254 characters or less.") })
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(omniauth_validate_email_length: false)
+ end
+
+ it 'normalizes the string' do
+ expect(auth_hash).to receive(:mb_chars_unicode_normalize).and_call_original
+
+ expect(auth_hash.username).not_to be_empty
+ expect(auth_hash.errors).to be_empty
+ end
+ end
+ end
+ end
+
describe '#get_from_auth_hash_or_info' do
context 'for a key not within auth_hash' do
let(:auth_hash) do
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 580a11fe1d3..4c72a91b121 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do
+RSpec.describe Gitlab::Auth::OAuth::User, aggregate_failures: true, feature_category: :system_access do
include LdapHelpers
let_it_be(:organization) { create(:organization) }
@@ -180,6 +180,28 @@ RSpec.describe Gitlab::Auth::OAuth::User, feature_category: :system_access do
end
end
+ context 'when email address is too long' do
+ def long_email_local_part
+ "reallylongemail" * 300
+ end
+
+ let(:info_hash) do
+ {
+ email: "#{long_email_local_part}@example.com"
+ }
+ end
+
+ it 'generates an empty username and produces an error' do
+ oauth_user.save # rubocop:disable Rails/SaveBang -- Not an ActiveRecord object
+
+ expect(gl_user.username).to eq("blank")
+ expect(gl_user.errors.full_messages.to_sentence)
+ .to eq("Identity provider email " + _("must be 254 characters or less."))
+ expect(oauth_user).not_to be_valid
+ expect(oauth_user).not_to be_valid_sign_in
+ end
+ end
+
it 'marks user as having password_automatically_set' do
stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
diff --git a/spec/lib/gitlab/ci/config/entry/pages_spec.rb b/spec/lib/gitlab/ci/config/entry/pages_spec.rb
index 0ee692a443e..be2cbc23b3b 100644
--- a/spec/lib/gitlab/ci/config/entry/pages_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/pages_spec.rb
@@ -17,25 +17,17 @@ RSpec.describe Gitlab::Ci::Config::Entry::Pages, feature_category: :pages do
context 'when value is a hash' do
context 'when the hash is valid' do
- let(:config) { { path_prefix: 'prefix' } }
+ let(:config) { { path_prefix: 'prefix', expire_in: '1 day' } }
it 'is valid' do
expect(entry).to be_valid
expect(entry.value).to eq({
- path_prefix: 'prefix'
+ path_prefix: 'prefix',
+ expire_in: '1 day'
})
end
end
- context 'when path_prefix key is not a string' do
- let(:config) { { path_prefix: 1 } }
-
- it 'is invalid' do
- expect(entry).not_to be_valid
- expect(entry.errors).to include('pages path prefix should be a string')
- end
- end
-
context 'when hash contains not allowed keys' do
let(:config) { { unknown: 'echo' } }
@@ -44,6 +36,61 @@ RSpec.describe Gitlab::Ci::Config::Entry::Pages, feature_category: :pages do
expect(entry.errors).to include('pages config contains unknown keys: unknown')
end
end
+
+ context 'when it specifies path_prefix' do
+ context 'and it is not a string' do
+ let(:config) { { path_prefix: 1 } }
+
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include('pages path prefix should be a string')
+ end
+ end
+ end
+
+ context 'when it specifies expire_in' do
+ context 'and it is a duration string' do
+ let(:config) { { expire_in: '1 day' } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq({
+ expire_in: '1 day'
+ })
+ end
+ end
+
+ context 'and it is never' do
+ let(:config) { { expire_in: 'never' } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq({
+ expire_in: 'never'
+ })
+ end
+ end
+
+ context 'and it is nil' do
+ let(:config) { { expire_in: nil } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq({
+ expire_in: nil
+ })
+ end
+ end
+
+ context 'and it is an invalid duration' do
+ let(:config) { { expire_in: 'some string that cant be parsed' } }
+
+ it 'is valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include('pages expire in should be a duration')
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
index ca5a8041cdf..7eb303b63bb 100644
--- a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
@@ -233,7 +233,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware, :clean_
it 'raises an error and retries', :aggregate_failures do
expect do
Gitlab::SidekiqSharding::Validator.allow_unrouted_sidekiq_calls { process_job(job) }
- end.to raise_error(Sidekiq::JobRetry::Handled)
+ end.to raise_error(Sidekiq::JobRetry::Skip)
job_for_retry = Gitlab::SidekiqSharding::Validator.allow_unrouted_sidekiq_calls { Sidekiq::RetrySet.new.first }
expect(job_for_retry['error_class']).to eq('Gitlab::Database::LoadBalancing::SidekiqServerMiddleware::JobReplicaNotUpToDate')
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index e13a07c820a..baa29e4e3ce 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -882,6 +882,7 @@ project:
- security_policy_management_project_linked_configurations
- security_policy_project_linked_projects
- security_policy_project_linked_namespaces
+- security_policy_project_linked_groups
- relation_import_trackers
- saved_replies
- security_policy_project_links
@@ -892,6 +893,7 @@ project:
- observability_metrics
- observability_traces
- observability_logs
+- security_exclusions
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index cdac395b74a..b1a8a9f4da3 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::SidekiqLogging::StructuredLogger, feature_category: :shared do
+RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
before do
# We disable a memory instrumentation feature
# as this requires a special patched Ruby
@@ -492,13 +492,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger, feature_category: :shar
'completed_at' => current_utc_time.to_i }
end
- let(:config) do
- config = Sidekiq::Config.new
- config.logger = Sidekiq.logger
- config
- end
-
- subject { described_class.new(config) }
+ subject { described_class.new(Sidekiq.logger) }
it 'update payload correctly' do
travel_to(current_utc_time) do
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index 58a18cbb8e8..ce7905fef0c 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe OmniAuth::Strategies::Jwt do
include DeviseHelpers
describe '#decoded' do
- subject { described_class.new({}) }
+ subject(:jwt_strategy) { described_class.new({}) }
let(:timestamp) { Time.now.to_i }
let(:jwt_config) { Devise.omniauth_configs[:jwt] }
@@ -127,5 +127,34 @@ RSpec.describe OmniAuth::Strategies::Jwt do
expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
end
end
+
+ context 'when the JWT is larger than 10KB' do
+ def email_local_part
+ 'really_long_email' * 500
+ end
+
+ let(:claims) do
+ {
+ id: 123,
+ name: "user_example",
+ email: "#{email_local_part}@example.com",
+ iat: timestamp
+ }
+ end
+
+ it 'raises error' do
+ expect { jwt_strategy.decoded }.to raise_error(OmniAuth::Strategies::Jwt::JwtTooLarge)
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(omniauth_validate_email_length: false)
+ end
+
+ it 'does not raise an error' do
+ expect { jwt_strategy.decoded }.not_to raise_error
+ end
+ end
+ end
end
end
diff --git a/spec/migrations/20240902054331_sync_fk_referencing_p_ci_pipelines_spec.rb b/spec/migrations/20240902054331_sync_fk_referencing_p_ci_pipelines_spec.rb
new file mode 100644
index 00000000000..fe75134c4cf
--- /dev/null
+++ b/spec/migrations/20240902054331_sync_fk_referencing_p_ci_pipelines_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe SyncFkReferencingPCiPipelines, migration: :gitlab_ci, feature_category: :continuous_integration do
+ let(:all_tmp_fk_names) do
+ (described_class::FOREIGN_KEYS + described_class::P_FOREIGN_KEYS).pluck(:name)
+ end
+
+ let(:all_fk_names) do
+ all_tmp_fk_names.map { |name| name.to_s.gsub('_tmp', '') }
+ end
+
+ it 'validates and renames the fks' do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(fk_count_for(:p_ci_pipelines, all_tmp_fk_names, is_valid: false)).to eq(16)
+ expect(fk_count_for(:p_ci_pipelines, all_fk_names)).to eq(0)
+ expect(fk_count_for(:ci_pipelines, all_fk_names)).to eq(16)
+ }
+ migration.after -> {
+ expect(fk_count_for(:p_ci_pipelines, all_tmp_fk_names, is_valid: false)).to eq(0)
+ expect(fk_count_for(:p_ci_pipelines, all_fk_names)).to eq(16)
+ expect(fk_count_for(:ci_pipelines, all_fk_names)).to eq(0)
+ }
+ end
+ end
+
+ private
+
+ def fk_count_for(referenced_table, names, is_valid: true)
+ Gitlab::Database::PostgresForeignKey
+ .by_referenced_table_name(referenced_table)
+ .where(name: names)
+ .where(is_valid: is_valid)
+ .count('DISTINCT name')
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index ea035b99b04..18b8ef2999d 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -103,6 +103,42 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
end
+ describe 'scopes' do
+ let_it_be(:old_project) { create(:project) }
+ let_it_be(:new_project) { create(:project) }
+ let_it_be(:old_build) { create(:ci_build, created_at: 1.week.ago, updated_at: 1.week.ago, project: old_project) }
+ let_it_be(:new_build) { create(:ci_build, created_at: 1.minute.ago, updated_at: 1.minute.ago, project: new_project) }
+
+ describe 'created_after' do
+ subject { described_class.created_after(1.day.ago) }
+
+ it 'returns the builds created after the given time' do
+ is_expected.to contain_exactly(new_build, build)
+ end
+ end
+
+ describe 'updated_after' do
+ subject { described_class.updated_after(1.day.ago) }
+
+ it 'returns the builds updated after the given time' do
+ is_expected.to contain_exactly(new_build, build)
+ end
+ end
+
+ describe 'with_pipeline_source_type' do
+ let_it_be(:pipeline) { create(:ci_pipeline, source: :security_orchestration_policy) }
+ let_it_be(:build) { create(:ci_build, pipeline: pipeline) }
+ let_it_be(:push_pipeline) { create(:ci_pipeline, source: :push) }
+ let_it_be(:push_build) { create(:ci_build, pipeline: push_pipeline) }
+
+ subject { described_class.with_pipeline_source_type('security_orchestration_policy') }
+
+ it 'returns the builds updated after the given time' do
+ is_expected.to contain_exactly(build)
+ end
+ end
+ end
+
describe 'callbacks' do
context 'when running after_create callback' do
it 'executes hooks' do
diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb
index c57f3ba6d84..0b80d9f3e4b 100644
--- a/spec/models/concerns/resolvable_note_spec.rb
+++ b/spec/models/concerns/resolvable_note_spec.rb
@@ -42,6 +42,15 @@ RSpec.describe Note, ResolvableNote, feature_category: :code_review_workflow do
end
end
+ describe '.resolvable_types' do
+ specify do
+ types = described_class::RESOLVABLE_TYPES
+ types += EE::ResolvableNote::EE_RESOLVABLE_TYPES if Gitlab.ee?
+
+ expect(described_class.resolvable_types).to eq(types)
+ end
+ end
+
describe ".resolve!" do
let(:current_user) { create(:user) }
let!(:commit_note) { create(:diff_note_on_commit, project: project) }
diff --git a/spec/models/import/source_user_spec.rb b/spec/models/import/source_user_spec.rb
index 5e9dac7b104..026cedc0038 100644
--- a/spec/models/import/source_user_spec.rb
+++ b/spec/models/import/source_user_spec.rb
@@ -30,6 +30,12 @@ RSpec.describe Import::SourceUser, type: :model, feature_category: :importers do
it { is_expected.to validate_presence_of(:reassign_to_user_id) }
end
+ context 'when awaiting_approval' do
+ subject { build(:import_source_user, :awaiting_approval) }
+
+ it { is_expected.to validate_presence_of(:reassign_to_user_id) }
+ end
+
context 'when reassignment_in_progress' do
subject { build(:import_source_user, :reassignment_in_progress) }
@@ -96,12 +102,6 @@ RSpec.describe Import::SourceUser, type: :model, feature_category: :importers do
it 'begins in pending state' do
expect(described_class.new.pending_reassignment?).to eq(true)
end
-
- it 'does not transition to reassignment_in_progress without a reassign_to_user' do
- import_source_user = create(:import_source_user, :awaiting_approval, reassign_to_user: nil)
-
- expect { import_source_user.accept! }.to raise_error(StateMachines::InvalidTransition)
- end
end
describe '.find_source_user' do
@@ -175,7 +175,10 @@ RSpec.describe Import::SourceUser, type: :model, feature_category: :importers do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:source_user_1) { create(:import_source_user, namespace: namespace, status: 4, source_name: 'd') }
let_it_be(:source_user_2) { create(:import_source_user, namespace: namespace, status: 3, source_name: 'c') }
- let_it_be(:source_user_3) { create(:import_source_user, namespace: namespace, status: 1, source_name: 'a') }
+ let_it_be(:source_user_3) do
+ create(:import_source_user, :with_reassign_to_user, namespace: namespace, status: 1, source_name: 'a')
+ end
+
let_it_be(:source_user_4) do
create(:import_source_user, :with_reassign_to_user, namespace: namespace, status: 2, source_name: 'b')
end
diff --git a/spec/models/work_item_spec.rb b/spec/models/work_item_spec.rb
index 767937ecdc9..213ba7a1917 100644
--- a/spec/models/work_item_spec.rb
+++ b/spec/models/work_item_spec.rb
@@ -830,4 +830,65 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
specify { expect(work_item.start_date).to eq(work_item.dates_source.start_date) }
end
end
+
+ describe '#max_depth_reached?' do
+ let_it_be(:work_item) { create(:work_item) }
+ let_it_be(:child_type) { create(:work_item_type) }
+
+ context 'when there is no hierarchy restriction' do
+ it 'returns false' do
+ expect(work_item.max_depth_reached?(child_type)).to be false
+ end
+ end
+
+ context 'when there is a hierarchy restriction without maximum depth' do
+ before do
+ create(:hierarchy_restriction,
+ parent_type_id: work_item.work_item_type_id,
+ child_type_id: child_type.id,
+ maximum_depth: nil)
+ end
+
+ it 'returns false' do
+ expect(work_item.max_depth_reached?(child_type)).to be false
+ end
+ end
+
+ context 'when there is a hierarchy restriction with maximum depth' do
+ let(:max_depth) { 3 }
+
+ before do
+ create(:hierarchy_restriction,
+ parent_type_id: work_item.work_item_type_id,
+ child_type_id: child_type.id,
+ maximum_depth: max_depth)
+ end
+
+ context 'when work item type is the same as child type' do
+ let(:child_type) { work_item.work_item_type }
+
+ it 'returns true when depth is reached' do
+ allow(work_item).to receive_message_chain(:same_type_base_and_ancestors, :count).and_return(max_depth)
+ expect(work_item.max_depth_reached?(child_type)).to be true
+ end
+
+ it 'returns false when depth is not reached' do
+ allow(work_item).to receive_message_chain(:same_type_base_and_ancestors, :count).and_return(max_depth - 1)
+ expect(work_item.max_depth_reached?(child_type)).to be false
+ end
+ end
+
+ context 'when work item type is different from child type' do
+ it 'returns true when depth is reached' do
+ allow(work_item).to receive_message_chain(:hierarchy, :base_and_ancestors, :count).and_return(max_depth)
+ expect(work_item.max_depth_reached?(child_type)).to be true
+ end
+
+ it 'returns false when depth is not reached' do
+ allow(work_item).to receive_message_chain(:hierarchy, :base_and_ancestors, :count).and_return(max_depth - 1)
+ expect(work_item.max_depth_reached?(child_type)).to be false
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/work_items/widgets/hierarchy_spec.rb b/spec/models/work_items/widgets/hierarchy_spec.rb
index 9ff9068d3b5..7e5ad4116df 100644
--- a/spec/models/work_items/widgets/hierarchy_spec.rb
+++ b/spec/models/work_items/widgets/hierarchy_spec.rb
@@ -109,7 +109,7 @@ RSpec.describe WorkItems::Widgets::Hierarchy, feature_category: :team_planning d
create(:parent_link, work_item_parent: sub_epic_2, work_item: sub_issue_2)
end
- it 'returns placeholder data' do
+ it 'returns rolled up dates by work item type and state' do
is_expected.to contain_exactly(
{
work_item_type: WorkItems::Type.default_by_type(:epic),
@@ -126,4 +126,46 @@ RSpec.describe WorkItems::Widgets::Hierarchy, feature_category: :team_planning d
)
end
end
+
+ describe '#depth_limit_reached_by_type' do
+ let_it_be(:work_item) { create(:work_item, :epic) }
+ let_it_be(:hierarchy) { described_class.new(work_item) }
+ let_it_be(:descendant_type1) { create(:work_item_type, :epic) }
+ let_it_be(:descendant_type2) { create(:work_item_type, :issue) }
+
+ before do
+ allow(work_item.work_item_type).to receive(:descendant_types).and_return([descendant_type1, descendant_type2])
+ end
+
+ it 'returns an array of hashes with work_item_type and depth_limit_reached' do
+ allow(work_item).to receive(:max_depth_reached?).with(descendant_type1).and_return(true)
+ allow(work_item).to receive(:max_depth_reached?).with(descendant_type2).and_return(false)
+
+ result = hierarchy.depth_limit_reached_by_type
+
+ expect(result).to contain_exactly(
+ { work_item_type: descendant_type1, depth_limit_reached: true },
+ { work_item_type: descendant_type2, depth_limit_reached: false }
+ )
+ end
+
+ it 'calls max_depth_reached? for each descendant type' do
+ expect(work_item).to receive(:max_depth_reached?).with(descendant_type1).once
+ expect(work_item).to receive(:max_depth_reached?).with(descendant_type2).once
+
+ hierarchy.depth_limit_reached_by_type
+ end
+
+ context 'when there are no descendant types' do
+ before do
+ allow(work_item.work_item_type).to receive(:descendant_types).and_return([])
+ end
+
+ it 'returns an empty array' do
+ result = hierarchy.depth_limit_reached_by_type
+
+ expect(result).to eq([])
+ end
+ end
+ end
end
diff --git a/spec/requests/api/conan/v1/instance_packages_spec.rb b/spec/requests/api/conan/v1/instance_packages_spec.rb
new file mode 100644
index 00000000000..764bebc16b7
--- /dev/null
+++ b/spec/requests/api/conan/v1/instance_packages_spec.rb
@@ -0,0 +1,190 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Conan::V1::InstancePackages, feature_category: :package_registry do
+ let(:snowplow_gitlab_standard_context) do
+ { user: user, project: project, namespace: project.namespace, property: 'i_package_conan_user' }
+ end
+
+ include_context 'conan api setup'
+
+ describe 'GET /api/v4/packages/conan/v1/ping' do
+ let_it_be(:url) { '/packages/conan/v1/ping' }
+
+ it_behaves_like 'conan ping endpoint'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/search' do
+ let_it_be(:url) { '/packages/conan/v1/conans/search' }
+
+ it_behaves_like 'conan search endpoint'
+
+ it_behaves_like 'conan FIPS mode' do
+ let(:params) { { q: package.conan_recipe } }
+
+ subject { get api(url), params: params }
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/users/authenticate' do
+ let_it_be(:url) { '/packages/conan/v1/users/authenticate' }
+
+ it_behaves_like 'conan authenticate endpoint'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/users/check_credentials' do
+ let_it_be(:url) { "/packages/conan/v1/users/check_credentials" }
+
+ it_behaves_like 'conan check_credentials endpoint'
+ end
+
+ context 'with recipe endpoints' do
+ include_context 'conan recipe endpoints'
+
+ let(:project_id) { 9999 }
+ let(:url_prefix) { "#{Settings.gitlab.base_url}/api/v4" }
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
+ let(:recipe_path) { package.conan_recipe_path }
+ let(:url) { "/packages/conan/v1/conans/#{recipe_path}" }
+
+ it_behaves_like 'recipe snapshot endpoint'
+ end
+
+ describe "GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel" \
+ "/packages/:conan_package_reference" do
+ let(:recipe_path) { package.conan_recipe_path }
+ let(:url) { "/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}" }
+
+ it_behaves_like 'package snapshot endpoint'
+ end
+
+ describe "GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel" \
+ "/digest" do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/digest"), headers: headers }
+
+ it_behaves_like 'recipe download_urls endpoint'
+ end
+
+ describe "GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel" \
+ "/packages/:conan_package_reference/download_urls" do
+ subject do
+ get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls"),
+ headers: headers
+ end
+
+ it_behaves_like 'package download_urls endpoint'
+ end
+
+ describe "GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel" \
+ "/download_urls" do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/download_urls"), headers: headers }
+
+ it_behaves_like 'recipe download_urls endpoint'
+ end
+
+ describe "GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel" \
+ "/packages/:conan_package_reference/digest" do
+ subject do
+ get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest"), headers: headers
+ end
+
+ it_behaves_like 'package download_urls endpoint'
+ end
+
+ describe "POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel" \
+ "/upload_urls" do
+ subject do
+ post api("/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params.to_json, headers: headers
+ end
+
+ it_behaves_like 'recipe upload_urls endpoint'
+ end
+
+ describe "POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel" \
+ "/packages/:conan_package_reference/upload_urls" do
+ subject do
+ post api("/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params.to_json,
+ headers: headers
+ end
+
+ it_behaves_like 'package upload_urls endpoint'
+ end
+
+ describe "DELETE /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel" do
+ subject { delete api("/packages/conan/v1/conans/#{recipe_path}"), headers: headers }
+
+ it_behaves_like 'delete package endpoint'
+ end
+ end
+
+ context 'with file download endpoints' do
+ include_context 'conan file download endpoints'
+
+ describe "GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel" \
+ "/:recipe_revision/export/:file_name" do
+ subject do
+ get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}"),
+ headers: headers
+ end
+
+ it_behaves_like 'recipe file download endpoint'
+ it_behaves_like 'project not found by recipe'
+ end
+
+ describe "GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel" \
+ "/:recipe_revision/package/:conan_package_reference/:package_revision/:file_name" do
+ subject do
+ get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package" \
+ "/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}"),
+ headers: headers
+ end
+
+ it_behaves_like 'package file download endpoint'
+ it_behaves_like 'project not found by recipe'
+ end
+ end
+
+ context 'with file upload endpoints' do
+ include_context 'conan file upload endpoints'
+
+ describe "PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel" \
+ "/:recipe_revision/export/:file_name/authorize" do
+ let(:file_name) { 'conanfile.py' }
+
+ subject do
+ put api("/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}/authorize"), headers: headers_with_token
+ end
+
+ it_behaves_like 'workhorse authorize endpoint'
+ end
+
+ describe "PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel" \
+ "/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize" do
+ let(:file_name) { 'conaninfo.txt' }
+
+ subject do
+ put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}/authorize"),
+ headers: headers_with_token
+ end
+
+ it_behaves_like 'workhorse authorize endpoint'
+ end
+
+ describe "PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel" \
+ "/:recipe_revision/export/:file_name" do
+ let(:url) { "/api/v4/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}" }
+
+ it_behaves_like 'workhorse recipe file upload endpoint'
+ end
+
+ describe "PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel" \
+ "/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name" do
+ let(:url) { "/api/v4/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}" }
+
+ it_behaves_like 'workhorse package file upload endpoint'
+ end
+ end
+end
diff --git a/spec/requests/api/conan_project_packages_spec.rb b/spec/requests/api/conan/v1/project_packages_spec.rb
similarity index 52%
rename from spec/requests/api/conan_project_packages_spec.rb
rename to spec/requests/api/conan/v1/project_packages_spec.rb
index 6463648d004..57af161dde0 100644
--- a/spec/requests/api/conan_project_packages_spec.rb
+++ b/spec/requests/api/conan/v1/project_packages_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
+
require 'spec_helper'
-RSpec.describe API::ConanProjectPackages, feature_category: :package_registry do
+RSpec.describe API::Conan::V1::ProjectPackages, feature_category: :package_registry do
include_context 'conan api setup'
let(:project_id) { project.id }
@@ -70,7 +71,7 @@ RSpec.describe API::ConanProjectPackages, feature_category: :package_registry do
it_behaves_like 'conan check_credentials endpoint'
end
- context 'recipe endpoints' do
+ context 'with recipe endpoints' do
include_context 'conan recipe endpoints'
let(:url_prefix) { "#{Settings.gitlab.base_url}/api/v4/projects/#{project_id}" }
@@ -78,82 +79,112 @@ RSpec.describe API::ConanProjectPackages, feature_category: :package_registry do
subject { get api(url), headers: headers }
- describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
+ describe "GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel" do
let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}" }
it_behaves_like 'recipe snapshot endpoint'
it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
- describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference' do
- let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}" }
+ describe "GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel/packages/:conan_package_reference" do
+ let(:url) do
+ "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}"
+ end
it_behaves_like 'package snapshot endpoint'
it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
- describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/digest' do
+ describe "GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel/digest" do
let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/digest" }
it_behaves_like 'recipe download_urls endpoint'
it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
- describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/download_urls' do
- let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls" }
+ describe "GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel/packages/:conan_package_reference/download_urls" do
+ let(:url) do
+ "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}" \
+ "/download_urls"
+ end
it_behaves_like 'package download_urls endpoint'
it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
- describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/download_urls' do
+ describe "GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel/download_urls" do
let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/download_urls" }
it_behaves_like 'recipe download_urls endpoint'
it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
- describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/digest' do
- let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest" }
+ describe "GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel/packages/:conan_package_reference/digest" do
+ let(:url) do
+ "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest"
+ end
it_behaves_like 'package download_urls endpoint'
it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
- describe 'POST /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/upload_urls' do
- subject { post api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params.to_json, headers: headers }
+ describe "POST /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel/upload_urls" do
+ subject do
+ post api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params.to_json,
+ headers: headers
+ end
it_behaves_like 'recipe upload_urls endpoint'
end
- describe 'POST /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/upload_urls' do
- subject { post api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params.to_json, headers: headers }
+ describe "POST /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel/packages/:conan_package_reference/upload_urls" do
+ subject do
+ post api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"),
+ params: params.to_json, headers: headers
+ end
it_behaves_like 'package upload_urls endpoint'
end
- describe 'DELETE /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
+ describe "DELETE /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username" \
+ "/:package_channel" do
subject { delete api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}"), headers: headers }
it_behaves_like 'delete package endpoint'
end
end
- context 'file download endpoints' do
+ context 'with file download endpoints' do
include_context 'conan file download endpoints'
subject { get api(url), headers: headers }
- describe 'GET /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
- let(:url) { "/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}" }
+ describe "GET /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username" \
+ "/:package_channel/:recipe_revision/export/:file_name" do
+ let(:url) do
+ "/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}" \
+ "/export/#{recipe_file.file_name}"
+ end
it_behaves_like 'recipe file download endpoint'
it_behaves_like 'project not found by project id'
it_behaves_like 'accept get request on private project with access to package registry for everyone'
end
- describe 'GET /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/package/:conan_package_reference/:package_revision/:file_name' do
- let(:url) { "/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}" }
+ describe "GET /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username" \
+ "/:package_channel/:recipe_revision/package/:conan_package_reference/:package_revision/:file_name" do
+ let(:url) do
+ "/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package" \
+ "/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}"
+ end
it_behaves_like 'package file download endpoint'
it_behaves_like 'project not found by project id'
@@ -161,33 +192,46 @@ RSpec.describe API::ConanProjectPackages, feature_category: :package_registry do
end
end
- context 'file upload endpoints' do
+ context 'with file upload endpoints' do
include_context 'conan file upload endpoints'
- describe 'PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
+ describe "PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username" \
+ "/:package_channel/:recipe_revision/export/:file_name/authorize" do
let(:file_name) { 'conanfile.py' }
- subject { put api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}/authorize"), headers: headers_with_token }
+ subject do
+ put api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}/authorize"),
+ headers: headers_with_token
+ end
it_behaves_like 'workhorse authorize endpoint'
end
- describe 'PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do
+ describe "PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username" \
+ "/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize" do
let(:file_name) { 'conaninfo.txt' }
- subject { put api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}/authorize"), headers: headers_with_token }
+ subject do
+ put api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}" \
+ "/authorize"),
+ headers: headers_with_token
+ end
it_behaves_like 'workhorse authorize endpoint'
end
- describe 'PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
+ describe "PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username" \
+ "/:package_channel/:recipe_revision/export/:file_name" do
let(:url) { "/api/v4/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}" }
it_behaves_like 'workhorse recipe file upload endpoint'
end
- describe 'PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name' do
- let(:url) { "/api/v4/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}" }
+ describe "PUT /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username" \
+ "/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name" do
+ let(:url) do
+ "/api/v4/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}"
+ end
it_behaves_like 'workhorse package file upload endpoint'
end
diff --git a/spec/requests/api/conan_instance_packages_spec.rb b/spec/requests/api/conan_instance_packages_spec.rb
deleted file mode 100644
index f8147bfac1a..00000000000
--- a/spec/requests/api/conan_instance_packages_spec.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::ConanInstancePackages, feature_category: :package_registry do
- let(:snowplow_gitlab_standard_context) { { user: user, project: project, namespace: project.namespace, property: 'i_package_conan_user' } }
-
- include_context 'conan api setup'
-
- describe 'GET /api/v4/packages/conan/v1/ping' do
- let_it_be(:url) { '/packages/conan/v1/ping' }
-
- it_behaves_like 'conan ping endpoint'
- end
-
- describe 'GET /api/v4/packages/conan/v1/conans/search' do
- let_it_be(:url) { '/packages/conan/v1/conans/search' }
-
- it_behaves_like 'conan search endpoint'
-
- it_behaves_like 'conan FIPS mode' do
- let(:params) { { q: package.conan_recipe } }
-
- subject { get api(url), params: params }
- end
- end
-
- describe 'GET /api/v4/packages/conan/v1/users/authenticate' do
- let_it_be(:url) { '/packages/conan/v1/users/authenticate' }
-
- it_behaves_like 'conan authenticate endpoint'
- end
-
- describe 'GET /api/v4/packages/conan/v1/users/check_credentials' do
- let_it_be(:url) { "/packages/conan/v1/users/check_credentials" }
-
- it_behaves_like 'conan check_credentials endpoint'
- end
-
- context 'recipe endpoints' do
- include_context 'conan recipe endpoints'
-
- let(:project_id) { 9999 }
- let(:url_prefix) { "#{Settings.gitlab.base_url}/api/v4" }
-
- describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
- let(:recipe_path) { package.conan_recipe_path }
- let(:url) { "/packages/conan/v1/conans/#{recipe_path}" }
-
- it_behaves_like 'recipe snapshot endpoint'
- end
-
- describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference' do
- let(:recipe_path) { package.conan_recipe_path }
- let(:url) { "/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}" }
-
- it_behaves_like 'package snapshot endpoint'
- end
-
- describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/digest' do
- subject { get api("/packages/conan/v1/conans/#{recipe_path}/digest"), headers: headers }
-
- it_behaves_like 'recipe download_urls endpoint'
- end
-
- describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/download_urls' do
- subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls"), headers: headers }
-
- it_behaves_like 'package download_urls endpoint'
- end
-
- describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/download_urls' do
- subject { get api("/packages/conan/v1/conans/#{recipe_path}/download_urls"), headers: headers }
-
- it_behaves_like 'recipe download_urls endpoint'
- end
-
- describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/digest' do
- subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest"), headers: headers }
-
- it_behaves_like 'package download_urls endpoint'
- end
-
- describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/upload_urls' do
- subject { post api("/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params.to_json, headers: headers }
-
- it_behaves_like 'recipe upload_urls endpoint'
- end
-
- describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/upload_urls' do
- subject { post api("/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params.to_json, headers: headers }
-
- it_behaves_like 'package upload_urls endpoint'
- end
-
- describe 'DELETE /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
- subject { delete api("/packages/conan/v1/conans/#{recipe_path}"), headers: headers }
-
- it_behaves_like 'delete package endpoint'
- end
- end
-
- context 'file download endpoints' do
- include_context 'conan file download endpoints'
-
- describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
- subject do
- get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}"),
- headers: headers
- end
-
- it_behaves_like 'recipe file download endpoint'
- it_behaves_like 'project not found by recipe'
- end
-
- describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/package/:conan_package_reference/:package_revision/:file_name' do
- subject do
- get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}"),
- headers: headers
- end
-
- it_behaves_like 'package file download endpoint'
- it_behaves_like 'project not found by recipe'
- end
- end
-
- context 'file upload endpoints' do
- include_context 'conan file upload endpoints'
-
- describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
- let(:file_name) { 'conanfile.py' }
-
- subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}/authorize"), headers: headers_with_token }
-
- it_behaves_like 'workhorse authorize endpoint'
- end
-
- describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do
- let(:file_name) { 'conaninfo.txt' }
-
- subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}/authorize"), headers: headers_with_token }
-
- it_behaves_like 'workhorse authorize endpoint'
- end
-
- describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
- let(:url) { "/api/v4/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}" }
-
- it_behaves_like 'workhorse recipe file upload endpoint'
- end
-
- describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name' do
- let(:url) { "/api/v4/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}" }
-
- it_behaves_like 'workhorse package file upload endpoint'
- end
- end
-end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index d95d6b972fd..1db3c87c2c4 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -192,6 +192,12 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
closed
}
}
+ depthLimitReachedByType {
+ workItemType {
+ name
+ }
+ depthLimitReached
+ }
}
}
GRAPHQL
@@ -220,6 +226,12 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
'closed' => 0
}
)
+ ]),
+ 'depthLimitReachedByType' => match_array([
+ hash_including(
+ 'workItemType' => hash_including('name' => 'Task'),
+ 'depthLimitReached' => false
+ )
])
)
)
diff --git a/spec/requests/api/web_commits_spec.rb b/spec/requests/api/web_commits_spec.rb
index 0391abbf998..c4f8e6674f0 100644
--- a/spec/requests/api/web_commits_spec.rb
+++ b/spec/requests/api/web_commits_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe API::WebCommits, feature_category: :source_code_management do
+RSpec.describe API::WebCommits, :clean_gitlab_redis_cache, feature_category: :source_code_management do
describe 'GET /web_commits/public_key' do
context 'when Gitaly is available' do
let(:public_key) { 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFcykDaUT7x4oXyUCfgqJhfAXRbhtsLl4fi4142zrPCI' }
diff --git a/spec/requests/import/source_users_controller_spec.rb b/spec/requests/import/source_users_controller_spec.rb
index cd576658381..804c8a1b61d 100644
--- a/spec/requests/import/source_users_controller_spec.rb
+++ b/spec/requests/import/source_users_controller_spec.rb
@@ -26,18 +26,29 @@ RSpec.describe Import::SourceUsersController, feature_category: :importers do
end
shared_examples 'it requires awaiting approval status' do
- it 'show error message' do
+ it 'shows error message' do
source_user.accept!
- subject
+ expect { subject }.not_to change { source_user.reload.status }
expect(response).to redirect_to(dashboard_groups_path)
- expect(flash[:alert]).to match(/The invitation is no longer valid./)
+ expect(flash[:raw]).to match(/Reassignment not available/)
+ end
+ end
+
+ shared_examples 'it requires the user is the reassign to user' do
+ it 'shows error message' do
+ source_user.update!(reassign_to_user: create(:user))
+
+ expect { subject }.not_to change { source_user.reload.status }
+
+ expect(response).to redirect_to(dashboard_groups_path)
+ expect(flash[:raw]).to match(/Reassignment not available/)
end
end
let_it_be_with_reload(:source_user) do
- create(:import_source_user, :with_reassign_to_user, :with_reassigned_by_user, :awaiting_approval)
+ create(:import_source_user, :with_reassigned_by_user, :awaiting_approval)
end
describe 'POST /accept' do
@@ -65,15 +76,6 @@ RSpec.describe Import::SourceUsersController, feature_category: :importers do
expect(flash[:raw]).to match(/Reassignment approved/)
end
- it 'can only be accepted by the reassign_to_user' do
- source_user.update!(reassign_to_user: create(:user))
-
- expect { accept_invite }.not_to change { source_user.reload.status }
-
- expect(response).to redirect_to(dashboard_groups_path)
- expect(flash[:raw]).to match(/Reassignment cancelled/)
- end
-
it 'cannot be accepted twice' do
allow(Import::SourceUser).to receive(:find).and_return(source_user)
allow(source_user).to receive(:accept).and_return(false)
@@ -85,6 +87,7 @@ RSpec.describe Import::SourceUsersController, feature_category: :importers do
end
it_behaves_like 'it requires awaiting approval status'
+ it_behaves_like 'it requires the user is the reassign to user'
end
it_behaves_like 'it requires feature flag'
@@ -124,6 +127,7 @@ RSpec.describe Import::SourceUsersController, feature_category: :importers do
end
it_behaves_like 'it requires awaiting approval status'
+ it_behaves_like 'it requires the user is the reassign to user'
end
it_behaves_like 'it requires feature flag'
@@ -146,19 +150,8 @@ RSpec.describe Import::SourceUsersController, feature_category: :importers do
expect(response).to have_gitlab_http_status(:success)
end
- context 'when the user is not the reassign_to_user' do
- it 'does not show invite and shows the invalid invite error message' do
- source_user.update!(reassign_to_user: create(:user))
- source_user.accept!
-
- show_invite
-
- expect(response).to redirect_to(dashboard_groups_path)
- expect(flash[:raw]).to match(/Reassignment cancelled/)
- end
- end
-
it_behaves_like 'it requires awaiting approval status'
+ it_behaves_like 'it requires the user is the reassign to user'
end
it_behaves_like 'it requires feature flag'
diff --git a/spec/services/import/source_users/accept_reassignment_service_spec.rb b/spec/services/import/source_users/accept_reassignment_service_spec.rb
index a125680ffd6..74ee78c0570 100644
--- a/spec/services/import/source_users/accept_reassignment_service_spec.rb
+++ b/spec/services/import/source_users/accept_reassignment_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Import::SourceUsers::AcceptReassignmentService, feature_category: :importers do
- let(:import_source_user) { create(:import_source_user, :awaiting_approval, :with_reassign_to_user) }
+ let(:import_source_user) { create(:import_source_user, :awaiting_approval) }
let(:current_user) { import_source_user.reassign_to_user }
let(:service) { described_class.new(import_source_user, current_user: current_user) }
@@ -45,17 +45,22 @@ RSpec.describe Import::SourceUsers::AcceptReassignmentService, feature_category:
it_behaves_like 'current user does not have permission to accept reassignment'
end
- context 'when there is no user to reassign to' do
- before do
- import_source_user.update!(reassign_to_user: nil)
- end
+ context 'when no current user is provided' do
+ let(:current_user) { nil }
it_behaves_like 'current user does not have permission to accept reassignment'
+ end
- context 'and no current user is provided' do
- let(:current_user) { nil }
+ context 'when the source user is not awaiting approval' do
+ let(:import_source_user) { create(:import_source_user, :reassignment_in_progress) }
- it_behaves_like 'current user does not have permission to accept reassignment'
+ it 'returns transition error' do
+ expect(Import::ReassignPlaceholderUserRecordsWorker).not_to receive(:perform_async)
+
+ result = service.execute
+
+ expect(result).to be_error
+ expect(result.message).to include('Status cannot transition via "accept"')
end
end
end
diff --git a/spec/services/import/source_users/reject_reassignment_service_spec.rb b/spec/services/import/source_users/reject_reassignment_service_spec.rb
index 77506b4213e..04d21f2359d 100644
--- a/spec/services/import/source_users/reject_reassignment_service_spec.rb
+++ b/spec/services/import/source_users/reject_reassignment_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Import::SourceUsers::RejectReassignmentService, feature_category: :importers do
- let(:import_source_user) { create(:import_source_user, :awaiting_approval, :with_reassign_to_user) }
+ let(:import_source_user) { create(:import_source_user, :awaiting_approval) }
let(:current_user) { import_source_user.reassign_to_user }
let(:service) { described_class.new(import_source_user, current_user: current_user) }
diff --git a/spec/services/import/source_users/resend_notification_service_spec.rb b/spec/services/import/source_users/resend_notification_service_spec.rb
index 6a95e6a1774..0144a1100e0 100644
--- a/spec/services/import/source_users/resend_notification_service_spec.rb
+++ b/spec/services/import/source_users/resend_notification_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Import::SourceUsers::ResendNotificationService, feature_category: :importers do
- let_it_be_with_reload(:import_source_user) { create(:import_source_user, :with_reassign_to_user, :awaiting_approval) }
+ let_it_be_with_reload(:import_source_user) { create(:import_source_user, :awaiting_approval) }
let_it_be(:current_user) { import_source_user.namespace.owner }
let(:service) { described_class.new(import_source_user, current_user: current_user) }
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 03fbc6daa3c..2d333068891 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -6842,8 +6842,6 @@
- './spec/requests/api/commits_spec.rb'
- './spec/requests/api/commit_statuses_spec.rb'
- './spec/requests/api/composer_packages_spec.rb'
-- './spec/requests/api/conan_instance_packages_spec.rb'
-- './spec/requests/api/conan_project_packages_spec.rb'
- './spec/requests/api/container_registry_event_spec.rb'
- './spec/requests/api/container_repositories_spec.rb'
- './spec/requests/api/debian_group_packages_spec.rb'
diff --git a/spec/support/shared_contexts/jobs/handling_retried_jobs_shared_context.rb b/spec/support/shared_contexts/jobs/handling_retried_jobs_shared_context.rb
index a4b7a463ec1..428eff77373 100644
--- a/spec/support/shared_contexts/jobs/handling_retried_jobs_shared_context.rb
+++ b/spec/support/shared_contexts/jobs/handling_retried_jobs_shared_context.rb
@@ -19,7 +19,7 @@ RSpec.shared_context 'when handling retried jobs' do |url|
begin
Sidekiq::JobRetry.new(Sidekiq).local(klass, message, klass.queue) { raise 'boom' }
- rescue Sidekiq::JobRetry::Handled
+ rescue Sidekiq::JobRetry::Skip
# Sidekiq scheduled the retry
end
end
diff --git a/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
index 0481ab8d691..5aae837e1b1 100644
--- a/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
+++ b/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
@@ -92,12 +92,6 @@ RSpec.shared_context 'structured_logger' do
)
end
- let(:config) do
- config = Sidekiq::Config.new
- config.logger = logger
- config
- end
-
before do
allow(subject).to receive(:current_time).and_return(timestamp.to_f)
@@ -108,7 +102,7 @@ RSpec.shared_context 'structured_logger' do
allow(Process).to receive(:clock_gettime).with(anything, :float_millisecond).and_call_original
end
- subject { described_class.new(config) }
+ subject { described_class.new(logger) }
def call_subject(job, queue)
# This structured logger strongly depends on execution of `InstrumentationLogger`
diff --git a/vendor/gems/sidekiq/.gitlab-ci.yml b/vendor/gems/sidekiq-7.2.4/.gitlab-ci.yml
similarity index 92%
rename from vendor/gems/sidekiq/.gitlab-ci.yml
rename to vendor/gems/sidekiq-7.2.4/.gitlab-ci.yml
index 396922d009f..ff9efb306ca 100644
--- a/vendor/gems/sidekiq/.gitlab-ci.yml
+++ b/vendor/gems/sidekiq-7.2.4/.gitlab-ci.yml
@@ -1,7 +1,7 @@
include:
- local: gems/gem.gitlab-ci.yml
inputs:
- gem_name: "sidekiq"
+ gem_name: "sidekiq-7.2.4"
gem_path_prefix: "vendor/gems/"
rspec:
diff --git a/vendor/gems/sidekiq/Changes.md b/vendor/gems/sidekiq-7.2.4/Changes.md
similarity index 97%
rename from vendor/gems/sidekiq/Changes.md
rename to vendor/gems/sidekiq-7.2.4/Changes.md
index 19561399fc5..34fdca72989 100644
--- a/vendor/gems/sidekiq/Changes.md
+++ b/vendor/gems/sidekiq-7.2.4/Changes.md
@@ -2,49 +2,6 @@
[Sidekiq Changes](https://github.com/sidekiq/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/sidekiq/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/sidekiq/sidekiq/blob/main/Ent-Changes.md)
-7.3.1
-----------
-
-- Don't count job interruptions as failures in metrics [#6386]
-- Add frozen string literal to a number of .rb files.
-- Fix frozen string error with style_tag and script_tag [#6371]
-- Fix an error on Ruby 2.7 because of usage of `Hash#except` [#6376]
-
-7.3.0
-----------
-
-- **NEW FEATURE** Add `Sidekiq::IterableJob`, iteration support for long-running jobs. [#6286, fatkodima]
- Iterable jobs are interruptible and can restart quickly if
- running during a deploy. You must ensure that `each_iteration`
- doesn't take more than Sidekiq's `-t` timeout (default: 25 seconds). Iterable jobs must not implement `perform`.
-```ruby
-class ProcessArrayJob
- include Sidekiq::IterableJob
- def build_enumerator(*args, **kwargs)
- array_enumerator(args, **kwargs)
- end
- def each_iteration(arg)
- puts arg
- end
-end
-ProcessArrayJob.perform_async(1, 2, 3)
-```
-See the [Iteration](//github.com/sidekiq/sidekiq/wiki/Iteration) wiki page and the RDoc in `Sidekiq::IterableJob`.
-This feature should be considered BETA until the next minor release.
-- **SECURITY** The Web UI no longer allows extensions to use `', obj.script_tag("sidekiq.js")
- end
-
- it "tests style_tag" do
- obj = Helpers.new
- assert_equal '', obj.style_tag("sidekiq.css")
- end
-
it "tests locale determination" do
obj = Helpers.new
assert_equal "en", obj.locale
@@ -110,23 +96,15 @@ describe "Web helpers" do
assert_equal "en", obj.locale
end
- it "handles invalid locales" do
+ it "tests user selected locale" do
obj = Helpers.new("HTTP_ACCEPT_LANGUAGE" => "*")
- obj.instance_eval do
- def session
- {locale: "xx"}
- end
- end
- assert_equal "en", obj.locale
- end
- it "uses the user selected locale" do
- obj = Helpers.new("HTTP_ACCEPT_LANGUAGE" => "*")
obj.instance_eval do
def session
{locale: "es"}
end
end
+
assert_equal "es", obj.locale
end
@@ -134,7 +112,7 @@ describe "Web helpers" do
obj = Helpers.new
expected = %w[
ar cs da de el en es fa fr gd he hi it ja
- ko lt nb nl pl pt pt-br ru sv ta tr uk ur
+ ko lt nb nl pl pt pt-br ru sv ta uk ur
vi zh-cn zh-tw
]
assert_equal expected, obj.available_locales.sort
diff --git a/vendor/gems/sidekiq/test/web_test.rb b/vendor/gems/sidekiq-7.2.4/test/web_test.rb
similarity index 95%
rename from vendor/gems/sidekiq/test/web_test.rb
rename to vendor/gems/sidekiq-7.2.4/test/web_test.rb
index 17d0c7fc19c..dc3ad82a185 100644
--- a/vendor/gems/sidekiq/test/web_test.rb
+++ b/vendor/gems/sidekiq-7.2.4/test/web_test.rb
@@ -35,7 +35,6 @@ describe Sidekiq::Web do
before do
@config = reset!
app.middlewares.clear
- app.use Rack::Session::Cookie, secrets: "35c5108120cb479eecb4e947e423cad6da6f38327cf0ebb323e30816d74fa01f"
end
it "passes on unexpected methods" do
@@ -70,9 +69,8 @@ describe Sidekiq::Web do
policies = last_response.headers["Content-Security-Policy"].split("; ")
assert_includes(policies, "connect-src 'self' https: http: wss: ws:")
assert_includes(policies, "style-src 'self' https: http: 'unsafe-inline'")
- assert_includes(policies, "script-src 'self' 'nonce-#{last_request.env[:csp_nonce]}'")
+ assert_includes(policies, "script-src 'self' https: http:")
assert_includes(policies, "object-src 'none'")
- assert_operator(24, :>=, last_request.env[:csp_nonce].length)
end
it "provides a cheap HEAD response" do
@@ -150,12 +148,9 @@ describe Sidekiq::Web do
end
it "can display queues" do
- Time.stub(:now, Time.now) do
- assert Sidekiq::Client.push("queue" => :foo, "class" => WebJob, "args" => [1, 3])
-
- get "/queues"
- end
+ assert Sidekiq::Client.push("queue" => :foo, "class" => WebJob, "args" => [1, 3])
+ get "/queues"
assert_equal 200, last_response.status
assert_match(/foo/, last_response.body)
refute_match(/HardJob/, last_response.body)
@@ -216,7 +211,7 @@ describe Sidekiq::Web do
assert_equal 302, last_response.status
@config.redis do |conn|
- refute_includes conn.smembers("queues"), "foo"
+ refute conn.smembers("queues").include?("foo")
refute(conn.exists("queue:foo") > 0)
end
end
@@ -307,7 +302,7 @@ describe Sidekiq::Web do
assert_equal 302, last_response.status
@config.redis do |conn|
- refute_includes conn.lrange("queue:foo", 0, -1), "{\"foo\":\"bar\"}"
+ refute conn.lrange("queue:foo", 0, -1).include?("{\"foo\":\"bar\"}")
end
end
@@ -559,11 +554,11 @@ describe Sidekiq::Web do
assert_equal 200, last_response.status
assert_match(/FailJob/, last_response.body)
- assert_includes last_response.body, "fail message: <a>hello</a>"
- refute_includes last_response.body, "fail message: hello"
+ assert last_response.body.include?("fail message: <a>hello</a>")
+ assert !last_response.body.include?("fail message: hello")
- assert_includes last_response.body, "args\">"<a>hello</a>"<"
- refute_includes last_response.body, "args\">hello<"
+ assert last_response.body.include?("args\">"<a>hello</a>"<")
+ assert !last_response.body.include?("args\">hello<")
# on /workers page
@config.redis do |conn|
@@ -580,8 +575,8 @@ describe Sidekiq::Web do
assert_equal 200, last_response.status
assert_match(/FailJob/, last_response.body)
assert_match(/frumduz/, last_response.body)
- assert_includes last_response.body, "<a>hello</a>"
- refute_includes last_response.body, "hello"
+ assert last_response.body.include?("<a>hello</a>")
+ assert !last_response.body.include?("hello")
# on /queues page
params = add_xss_retry # sorry, don't know how to easily make this show up on queues page otherwise.
@@ -590,8 +585,8 @@ describe Sidekiq::Web do
get "/queues/foo"
assert_equal 200, last_response.status
- assert_includes last_response.body, "<a>hello</a>"
- refute_includes last_response.body, "hello"
+ assert last_response.body.include?("<a>hello</a>")
+ assert !last_response.body.include?("hello")
end
it "can show user defined tab" do
@@ -612,13 +607,10 @@ describe Sidekiq::Web do
session_data = {"rack.session" => {}}
headers = {"HTTP_REFERER" => "http://example.org/path", "HTTP_BASE_URL" => "http://example.org/"}
- post "/change_locale", {"locale" => "none"}, session_data.merge(headers)
- assert_nil last_request.env["rack.session"][:locale]
- assert_equal 302, last_response.status
- assert_equal "http://example.org/path", last_response.location
-
post "/change_locale", {"locale" => "es"}, session_data.merge(headers)
+
assert_equal "es", last_request.env["rack.session"][:locale]
+
assert_equal 302, last_response.status
assert_equal "http://example.org/path", last_response.location
end
diff --git a/vendor/gems/sidekiq/tmp/app/sidekiq/foo_job.rb b/vendor/gems/sidekiq-7.2.4/tmp/app/sidekiq/foo_job.rb
similarity index 100%
rename from vendor/gems/sidekiq/tmp/app/sidekiq/foo_job.rb
rename to vendor/gems/sidekiq-7.2.4/tmp/app/sidekiq/foo_job.rb
diff --git a/vendor/gems/sidekiq/web/assets/images/apple-touch-icon.png b/vendor/gems/sidekiq-7.2.4/web/assets/images/apple-touch-icon.png
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/images/apple-touch-icon.png
rename to vendor/gems/sidekiq-7.2.4/web/assets/images/apple-touch-icon.png
diff --git a/vendor/gems/sidekiq/web/assets/images/favicon.ico b/vendor/gems/sidekiq-7.2.4/web/assets/images/favicon.ico
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/images/favicon.ico
rename to vendor/gems/sidekiq-7.2.4/web/assets/images/favicon.ico
diff --git a/vendor/gems/sidekiq/web/assets/images/logo.png b/vendor/gems/sidekiq-7.2.4/web/assets/images/logo.png
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/images/logo.png
rename to vendor/gems/sidekiq-7.2.4/web/assets/images/logo.png
diff --git a/vendor/gems/sidekiq/web/assets/images/status.png b/vendor/gems/sidekiq-7.2.4/web/assets/images/status.png
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/images/status.png
rename to vendor/gems/sidekiq-7.2.4/web/assets/images/status.png
diff --git a/vendor/gems/sidekiq/web/assets/javascripts/application.js b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/application.js
similarity index 99%
rename from vendor/gems/sidekiq/web/assets/javascripts/application.js
rename to vendor/gems/sidekiq-7.2.4/web/assets/javascripts/application.js
index cd1744bb62f..01b175c1758 100644
--- a/vendor/gems/sidekiq/web/assets/javascripts/application.js
+++ b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/application.js
@@ -34,7 +34,6 @@ function addListeners() {
addShiftClickListeners()
updateFuzzyTimes();
updateNumbers();
- updateProgressBars();
setLivePollFromUrl();
var buttons = document.querySelectorAll(".live-poll");
@@ -181,8 +180,4 @@ function showError(error) {
function updateLocale(event) {
event.target.form.submit();
-}
-
-function updateProgressBars() {
- document.querySelectorAll('.progress-bar').forEach(bar => { bar.style.width = bar.dataset.width + "%"})
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/vendor/gems/sidekiq/web/assets/javascripts/base-charts.js b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/base-charts.js
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/javascripts/base-charts.js
rename to vendor/gems/sidekiq-7.2.4/web/assets/javascripts/base-charts.js
diff --git a/vendor/gems/sidekiq/web/assets/javascripts/chart.min.js b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/chart.min.js
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/javascripts/chart.min.js
rename to vendor/gems/sidekiq-7.2.4/web/assets/javascripts/chart.min.js
diff --git a/vendor/gems/sidekiq/web/assets/javascripts/chartjs-plugin-annotation.min.js b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/chartjs-plugin-annotation.min.js
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/javascripts/chartjs-plugin-annotation.min.js
rename to vendor/gems/sidekiq-7.2.4/web/assets/javascripts/chartjs-plugin-annotation.min.js
diff --git a/vendor/gems/sidekiq/web/assets/javascripts/dashboard-charts.js b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/dashboard-charts.js
similarity index 86%
rename from vendor/gems/sidekiq/web/assets/javascripts/dashboard-charts.js
rename to vendor/gems/sidekiq-7.2.4/web/assets/javascripts/dashboard-charts.js
index b144dc8187a..224f95af50f 100644
--- a/vendor/gems/sidekiq/web/assets/javascripts/dashboard-charts.js
+++ b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/dashboard-charts.js
@@ -108,27 +108,17 @@ class RealtimeChart extends DashboardChart {
}
renderLegend(dp) {
- const entry1 = this.legendEntry(dp[0]);
- const entry2 = this.legendEntry(dp[1]);
- const time = document.createElement("span");
- time.classList.add("time");
- time.innerText = dp[0].label;
-
- this.legend.replaceChildren(entry1, entry2, time)
- }
-
- legendEntry(dp) {
- const wrapper = document.createElement("span");
-
- const swatch = document.createElement("span");
- swatch.classList.add("swatch");
- swatch.style.backgroundColor = dp.dataset.borderColor;
- wrapper.appendChild(swatch)
-
- const label = document.createElement("span");
- label.innerText = `${dp.dataset.label}: ${dp.formattedValue}`;
- wrapper.appendChild(label)
- return wrapper;
+ this.legend.innerHTML = `
+
+
+ ${dp[0].dataset.label}: ${dp[0].formattedValue}
+
+
+
+ ${dp[1].dataset.label}: ${dp[1].formattedValue}
+
+ ${dp[0].label}
+ `;
}
renderCursor(dp) {
@@ -189,4 +179,4 @@ class RealtimeChart extends DashboardChart {
if (hc != null) {
var htc = new DashboardChart(hc, JSON.parse(hc.textContent))
window.historyChart = htc
- }
+ }
\ No newline at end of file
diff --git a/vendor/gems/sidekiq/web/assets/javascripts/dashboard.js b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/dashboard.js
similarity index 98%
rename from vendor/gems/sidekiq/web/assets/javascripts/dashboard.js
rename to vendor/gems/sidekiq-7.2.4/web/assets/javascripts/dashboard.js
index 2d05bd19dea..c6e582fcf9a 100644
--- a/vendor/gems/sidekiq/web/assets/javascripts/dashboard.js
+++ b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/dashboard.js
@@ -28,7 +28,7 @@ var pulseBeacon = function() {
}
var setSliderLabel = function(val) {
- document.getElementById('sldr-text').innerText = Math.round(parseFloat(val) / 1000) + ' s';
+ document.getElementById('sldr-text').innerText = Math.round(parseFloat(val) / 1000) + ' sec';
}
var ready = (callback) => {
diff --git a/vendor/gems/sidekiq/web/assets/javascripts/metrics.js b/vendor/gems/sidekiq-7.2.4/web/assets/javascripts/metrics.js
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/javascripts/metrics.js
rename to vendor/gems/sidekiq-7.2.4/web/assets/javascripts/metrics.js
diff --git a/vendor/gems/sidekiq/web/assets/stylesheets/application-dark.css b/vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/application-dark.css
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/stylesheets/application-dark.css
rename to vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/application-dark.css
diff --git a/vendor/gems/sidekiq/web/assets/stylesheets/application-rtl.css b/vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/application-rtl.css
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/stylesheets/application-rtl.css
rename to vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/application-rtl.css
diff --git a/vendor/gems/sidekiq/web/assets/stylesheets/application.css b/vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/application.css
similarity index 99%
rename from vendor/gems/sidekiq/web/assets/stylesheets/application.css
rename to vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/application.css
index f347cedad2f..cb610479361 100644
--- a/vendor/gems/sidekiq/web/assets/stylesheets/application.css
+++ b/vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/application.css
@@ -72,14 +72,6 @@ h1, h2, h3 {
line-height: 45px;
}
-.progress {
- margin-bottom: 0;
-}
-
-.w-50 {
- width: 50%;
-}
-
.header-container, .header-container .page-title-container {
display: flex;
justify-content: space-between;
@@ -634,12 +626,8 @@ div.interval-slider input {
.container {
padding: 0;
}
-.navbar-fixed-bottom {
- position: relative;
- top: auto;
-}
@media (max-width: 767px) {
- .navbar-fixed-top {
+ .navbar-fixed-top, .navbar-fixed-bottom {
position: relative;
top: auto;
}
diff --git a/vendor/gems/sidekiq/web/assets/stylesheets/bootstrap-rtl.min.css b/vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/bootstrap-rtl.min.css
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/stylesheets/bootstrap-rtl.min.css
rename to vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/bootstrap-rtl.min.css
diff --git a/vendor/gems/sidekiq/web/assets/stylesheets/bootstrap.css b/vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/bootstrap.css
similarity index 100%
rename from vendor/gems/sidekiq/web/assets/stylesheets/bootstrap.css
rename to vendor/gems/sidekiq-7.2.4/web/assets/stylesheets/bootstrap.css
diff --git a/vendor/gems/sidekiq/web/locales/ar.yml b/vendor/gems/sidekiq-7.2.4/web/locales/ar.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/ar.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/ar.yml
diff --git a/vendor/gems/sidekiq/web/locales/cs.yml b/vendor/gems/sidekiq-7.2.4/web/locales/cs.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/cs.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/cs.yml
diff --git a/vendor/gems/sidekiq/web/locales/da.yml b/vendor/gems/sidekiq-7.2.4/web/locales/da.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/da.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/da.yml
diff --git a/vendor/gems/sidekiq/web/locales/de.yml b/vendor/gems/sidekiq-7.2.4/web/locales/de.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/de.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/de.yml
diff --git a/vendor/gems/sidekiq/web/locales/el.yml b/vendor/gems/sidekiq-7.2.4/web/locales/el.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/el.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/el.yml
diff --git a/vendor/gems/sidekiq/web/locales/en.yml b/vendor/gems/sidekiq-7.2.4/web/locales/en.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/en.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/en.yml
diff --git a/vendor/gems/sidekiq/web/locales/es.yml b/vendor/gems/sidekiq-7.2.4/web/locales/es.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/es.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/es.yml
diff --git a/vendor/gems/sidekiq/web/locales/fa.yml b/vendor/gems/sidekiq-7.2.4/web/locales/fa.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/fa.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/fa.yml
diff --git a/vendor/gems/sidekiq/web/locales/fr.yml b/vendor/gems/sidekiq-7.2.4/web/locales/fr.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/fr.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/fr.yml
diff --git a/vendor/gems/sidekiq/web/locales/gd.yml b/vendor/gems/sidekiq-7.2.4/web/locales/gd.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/gd.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/gd.yml
diff --git a/vendor/gems/sidekiq/web/locales/he.yml b/vendor/gems/sidekiq-7.2.4/web/locales/he.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/he.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/he.yml
diff --git a/vendor/gems/sidekiq/web/locales/hi.yml b/vendor/gems/sidekiq-7.2.4/web/locales/hi.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/hi.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/hi.yml
diff --git a/vendor/gems/sidekiq/web/locales/it.yml b/vendor/gems/sidekiq-7.2.4/web/locales/it.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/it.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/it.yml
diff --git a/vendor/gems/sidekiq/web/locales/ja.yml b/vendor/gems/sidekiq-7.2.4/web/locales/ja.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/ja.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/ja.yml
diff --git a/vendor/gems/sidekiq/web/locales/ko.yml b/vendor/gems/sidekiq-7.2.4/web/locales/ko.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/ko.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/ko.yml
diff --git a/vendor/gems/sidekiq/web/locales/lt.yml b/vendor/gems/sidekiq-7.2.4/web/locales/lt.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/lt.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/lt.yml
diff --git a/vendor/gems/sidekiq/web/locales/nb.yml b/vendor/gems/sidekiq-7.2.4/web/locales/nb.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/nb.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/nb.yml
diff --git a/vendor/gems/sidekiq/web/locales/nl.yml b/vendor/gems/sidekiq-7.2.4/web/locales/nl.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/nl.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/nl.yml
diff --git a/vendor/gems/sidekiq/web/locales/pl.yml b/vendor/gems/sidekiq-7.2.4/web/locales/pl.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/pl.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/pl.yml
diff --git a/vendor/gems/sidekiq/web/locales/pt-br.yml b/vendor/gems/sidekiq-7.2.4/web/locales/pt-br.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/pt-br.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/pt-br.yml
diff --git a/vendor/gems/sidekiq/web/locales/pt.yml b/vendor/gems/sidekiq-7.2.4/web/locales/pt.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/pt.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/pt.yml
diff --git a/vendor/gems/sidekiq/web/locales/ru.yml b/vendor/gems/sidekiq-7.2.4/web/locales/ru.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/ru.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/ru.yml
diff --git a/vendor/gems/sidekiq/web/locales/sv.yml b/vendor/gems/sidekiq-7.2.4/web/locales/sv.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/sv.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/sv.yml
diff --git a/vendor/gems/sidekiq/web/locales/ta.yml b/vendor/gems/sidekiq-7.2.4/web/locales/ta.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/ta.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/ta.yml
diff --git a/vendor/gems/sidekiq/web/locales/uk.yml b/vendor/gems/sidekiq-7.2.4/web/locales/uk.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/uk.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/uk.yml
diff --git a/vendor/gems/sidekiq/web/locales/ur.yml b/vendor/gems/sidekiq-7.2.4/web/locales/ur.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/ur.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/ur.yml
diff --git a/vendor/gems/sidekiq/web/locales/vi.yml b/vendor/gems/sidekiq-7.2.4/web/locales/vi.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/vi.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/vi.yml
diff --git a/vendor/gems/sidekiq/web/locales/zh-cn.yml b/vendor/gems/sidekiq-7.2.4/web/locales/zh-cn.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/zh-cn.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/zh-cn.yml
diff --git a/vendor/gems/sidekiq/web/locales/zh-tw.yml b/vendor/gems/sidekiq-7.2.4/web/locales/zh-tw.yml
similarity index 100%
rename from vendor/gems/sidekiq/web/locales/zh-tw.yml
rename to vendor/gems/sidekiq-7.2.4/web/locales/zh-tw.yml
diff --git a/vendor/gems/sidekiq/web/views/_footer.erb b/vendor/gems/sidekiq-7.2.4/web/views/_footer.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/_footer.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/_footer.erb
diff --git a/vendor/gems/sidekiq/web/views/_job_info.erb b/vendor/gems/sidekiq-7.2.4/web/views/_job_info.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/_job_info.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/_job_info.erb
diff --git a/vendor/gems/sidekiq/web/views/_metrics_period_select.erb b/vendor/gems/sidekiq-7.2.4/web/views/_metrics_period_select.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/_metrics_period_select.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/_metrics_period_select.erb
diff --git a/vendor/gems/sidekiq/web/views/_nav.erb b/vendor/gems/sidekiq-7.2.4/web/views/_nav.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/_nav.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/_nav.erb
diff --git a/vendor/gems/sidekiq/web/views/_paging.erb b/vendor/gems/sidekiq-7.2.4/web/views/_paging.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/_paging.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/_paging.erb
diff --git a/vendor/gems/sidekiq/web/views/_poll_link.erb b/vendor/gems/sidekiq-7.2.4/web/views/_poll_link.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/_poll_link.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/_poll_link.erb
diff --git a/vendor/gems/sidekiq/web/views/_status.erb b/vendor/gems/sidekiq-7.2.4/web/views/_status.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/_status.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/_status.erb
diff --git a/vendor/gems/sidekiq/web/views/_summary.erb b/vendor/gems/sidekiq-7.2.4/web/views/_summary.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/_summary.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/_summary.erb
diff --git a/vendor/gems/sidekiq/web/views/busy.erb b/vendor/gems/sidekiq-7.2.4/web/views/busy.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/busy.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/busy.erb
diff --git a/vendor/gems/sidekiq/web/views/dashboard.erb b/vendor/gems/sidekiq-7.2.4/web/views/dashboard.erb
similarity index 91%
rename from vendor/gems/sidekiq/web/views/dashboard.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/dashboard.erb
index 7ec0594af8b..1f440060592 100644
--- a/vendor/gems/sidekiq/web/views/dashboard.erb
+++ b/vendor/gems/sidekiq-7.2.4/web/views/dashboard.erb
@@ -1,4 +1,4 @@
-
+
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/vendor/gems/sidekiq/web/views/dead.erb b/vendor/gems/sidekiq-7.2.4/web/views/dead.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/dead.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/dead.erb
diff --git a/vendor/gems/sidekiq/web/views/filtering.erb b/vendor/gems/sidekiq-7.2.4/web/views/filtering.erb
similarity index 100%
rename from vendor/gems/sidekiq/web/views/filtering.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/filtering.erb
diff --git a/vendor/gems/sidekiq/web/views/layout.erb b/vendor/gems/sidekiq-7.2.4/web/views/layout.erb
similarity index 77%
rename from vendor/gems/sidekiq/web/views/layout.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/layout.erb
index 7342720eebd..3cbe9c2b529 100644
--- a/vendor/gems/sidekiq/web/views/layout.erb
+++ b/vendor/gems/sidekiq-7.2.4/web/views/layout.erb
@@ -5,20 +5,20 @@
-
+
<% if rtl? %>
-
+
<% end %>
-
-
+
+
<% if rtl? %>
-
+
<% end %>
-
+
<%= display_custom_head %>
diff --git a/vendor/gems/sidekiq/web/views/metrics.erb b/vendor/gems/sidekiq-7.2.4/web/views/metrics.erb
similarity index 94%
rename from vendor/gems/sidekiq/web/views/metrics.erb
rename to vendor/gems/sidekiq-7.2.4/web/views/metrics.erb
index 86726ff4b19..5eefa33ff2f 100644
--- a/vendor/gems/sidekiq/web/views/metrics.erb
+++ b/vendor/gems/sidekiq-7.2.4/web/views/metrics.erb
@@ -1,6 +1,6 @@
-
-
-
+
+
+