Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									e6fed37d94
								
							
						
					
					
						commit
						f8a5275c45
					
				|  | @ -1463,14 +1463,15 @@ | ||||||
| 
 | 
 | ||||||
| .qa:rules:package-and-test-sidebar: | .qa:rules:package-and-test-sidebar: | ||||||
|   rules: |   rules: | ||||||
|     - !reference [".qa:rules:package-and-test-common", rules] |     - <<: *if-merge-request | ||||||
|     - <<: *if-dot-com-gitlab-org-schedule |       changes: *code-patterns | ||||||
|  |       when: manual | ||||||
|  |       allow_failure: true | ||||||
|  |     - <<: *if-default-branch-schedule-nightly | ||||||
|       allow_failure: true |       allow_failure: true | ||||||
|       variables: |       variables: | ||||||
|         SKIP_REPORT_IN_ISSUES: "true" |         SKIP_REPORT_IN_ISSUES: "false" | ||||||
|         PROCESS_TEST_RESULTS: "false" |         PROCESS_TEST_RESULTS: "true" | ||||||
|         KNAPSACK_GENERATE_REPORT: "false" |  | ||||||
|         UPDATE_QA_CACHE: "false" |  | ||||||
|         QA_SAVE_TEST_METRICS: "true" |         QA_SAVE_TEST_METRICS: "true" | ||||||
|         QA_EXPORT_TEST_METRICS: "false" |         QA_EXPORT_TEST_METRICS: "false" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										2
									
								
								Gemfile
								
								
								
								
							|  | @ -46,7 +46,7 @@ gem 'devise', '~> 4.8.1' | ||||||
| gem 'devise-pbkdf2-encryptable', '~> 0.0.0', path: 'vendor/gems/devise-pbkdf2-encryptable' | gem 'devise-pbkdf2-encryptable', '~> 0.0.0', path: 'vendor/gems/devise-pbkdf2-encryptable' | ||||||
| gem 'bcrypt', '~> 3.1', '>= 3.1.14' | gem 'bcrypt', '~> 3.1', '>= 3.1.14' | ||||||
| gem 'doorkeeper', '~> 5.6', '>= 5.6.6' | gem 'doorkeeper', '~> 5.6', '>= 5.6.6' | ||||||
| gem 'doorkeeper-openid_connect', '~> 1.8', '>= 1.8.5' | gem 'doorkeeper-openid_connect', '~> 1.8', '>= 1.8.6' | ||||||
| gem 'rexml', '~> 3.2.5' | gem 'rexml', '~> 3.2.5' | ||||||
| gem 'ruby-saml', '~> 1.13.0' | gem 'ruby-saml', '~> 1.13.0' | ||||||
| gem 'omniauth', '~> 2.1.0' | gem 'omniauth', '~> 2.1.0' | ||||||
|  |  | ||||||
|  | @ -123,7 +123,7 @@ | ||||||
| {"name":"docile","version":"1.4.0","platform":"ruby","checksum":"5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3"}, | {"name":"docile","version":"1.4.0","platform":"ruby","checksum":"5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3"}, | ||||||
| {"name":"domain_name","version":"0.5.20190701","platform":"ruby","checksum":"000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851"}, | {"name":"domain_name","version":"0.5.20190701","platform":"ruby","checksum":"000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851"}, | ||||||
| {"name":"doorkeeper","version":"5.6.6","platform":"ruby","checksum":"2344e86c77770526efcda893b5217aa13d1c7eb1b40de840b58b19eb1ff757e0"}, | {"name":"doorkeeper","version":"5.6.6","platform":"ruby","checksum":"2344e86c77770526efcda893b5217aa13d1c7eb1b40de840b58b19eb1ff757e0"}, | ||||||
| {"name":"doorkeeper-openid_connect","version":"1.8.5","platform":"ruby","checksum":"d4ee57687945402843c948cee399c758cdddf04468c42b1fb02a8800dd0627f6"}, | {"name":"doorkeeper-openid_connect","version":"1.8.6","platform":"ruby","checksum":"8dc46543e697476f441496a5d465bbc68c10d052e54348cec4db06d123b1e003"}, | ||||||
| {"name":"dotenv","version":"2.7.6","platform":"ruby","checksum":"2451ed5e8e43776d7a787e51d6f8903b98e446146c7ad143d5678cc2c409d547"}, | {"name":"dotenv","version":"2.7.6","platform":"ruby","checksum":"2451ed5e8e43776d7a787e51d6f8903b98e446146c7ad143d5678cc2c409d547"}, | ||||||
| {"name":"dry-configurable","version":"0.12.0","platform":"ruby","checksum":"87a9579a04dfbae73e401d694282800d64bbdb8631cb3e987bfb79b673df7c67"}, | {"name":"dry-configurable","version":"0.12.0","platform":"ruby","checksum":"87a9579a04dfbae73e401d694282800d64bbdb8631cb3e987bfb79b673df7c67"}, | ||||||
| {"name":"dry-container","version":"0.7.2","platform":"ruby","checksum":"a071824ba3451048b23500210f96a2b9facd6e46ac687f65e49c75d18786f6da"}, | {"name":"dry-container","version":"0.7.2","platform":"ruby","checksum":"a071824ba3451048b23500210f96a2b9facd6e46ac687f65e49c75d18786f6da"}, | ||||||
|  |  | ||||||
|  | @ -393,7 +393,7 @@ GEM | ||||||
|       unf (>= 0.0.5, < 1.0.0) |       unf (>= 0.0.5, < 1.0.0) | ||||||
|     doorkeeper (5.6.6) |     doorkeeper (5.6.6) | ||||||
|       railties (>= 5) |       railties (>= 5) | ||||||
|     doorkeeper-openid_connect (1.8.5) |     doorkeeper-openid_connect (1.8.6) | ||||||
|       doorkeeper (>= 5.5, < 5.7) |       doorkeeper (>= 5.5, < 5.7) | ||||||
|       jwt (>= 2.5) |       jwt (>= 2.5) | ||||||
|     dotenv (2.7.6) |     dotenv (2.7.6) | ||||||
|  | @ -1714,7 +1714,7 @@ DEPENDENCIES | ||||||
|   diffy (~> 3.4) |   diffy (~> 3.4) | ||||||
|   discordrb-webhooks (~> 3.4) |   discordrb-webhooks (~> 3.4) | ||||||
|   doorkeeper (~> 5.6, >= 5.6.6) |   doorkeeper (~> 5.6, >= 5.6.6) | ||||||
|   doorkeeper-openid_connect (~> 1.8, >= 1.8.5) |   doorkeeper-openid_connect (~> 1.8, >= 1.8.6) | ||||||
|   duo_api (~> 1.3) |   duo_api (~> 1.3) | ||||||
|   ed25519 (~> 1.3.0) |   ed25519 (~> 1.3.0) | ||||||
|   elasticsearch-api (= 7.13.3) |   elasticsearch-api (= 7.13.3) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,31 @@ | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   inject: { | ||||||
|  |     canAdminAchievement: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |     canAwardAchievement: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |     groupFullPath: { | ||||||
|  |       type: String, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |     groupId: { | ||||||
|  |       type: Number, | ||||||
|  |       required: true, | ||||||
|  |     }, | ||||||
|  |     textQuery: { | ||||||
|  |       type: String, | ||||||
|  |       required: false, | ||||||
|  |       default: null, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <div></div> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | export const INDEX_ROUTE_NAME = 'index'; | ||||||
|  | export const NEW_ROUTE_NAME = 'new'; | ||||||
|  | export const EDIT_ROUTE_NAME = 'edit'; | ||||||
|  | export const trackViewsOptions = { | ||||||
|  |   category: 'Achievements' /* eslint-disable-line @gitlab/require-i18n-strings */, | ||||||
|  |   action: 'view_achievements_list', | ||||||
|  | }; | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from './constants'; | ||||||
|  | 
 | ||||||
|  | export default [ | ||||||
|  |   { | ||||||
|  |     name: INDEX_ROUTE_NAME, | ||||||
|  |     path: '/', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: NEW_ROUTE_NAME, | ||||||
|  |     path: '/new', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: EDIT_ROUTE_NAME, | ||||||
|  |     path: '/:id/edit', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | @ -0,0 +1,43 @@ | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import VueApollo from 'vue-apollo'; | ||||||
|  | import VueRouter from 'vue-router'; | ||||||
|  | import createDefaultClient from '~/lib/graphql'; | ||||||
|  | import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; | ||||||
|  | import AchievementsApp from '~/achievements/components/achievements_app.vue'; | ||||||
|  | import routes from '~/achievements/routes'; | ||||||
|  | 
 | ||||||
|  | Vue.use(VueApollo); | ||||||
|  | Vue.use(VueRouter); | ||||||
|  | 
 | ||||||
|  | const init = () => { | ||||||
|  |   const el = document.getElementById('js-achievements-app'); | ||||||
|  | 
 | ||||||
|  |   if (!el) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const apolloProvider = new VueApollo({ | ||||||
|  |     defaultClient: createDefaultClient(), | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const { basePath, viewModel } = el.dataset; | ||||||
|  |   const provide = JSON.parse(viewModel); | ||||||
|  | 
 | ||||||
|  |   const router = new VueRouter({ | ||||||
|  |     base: basePath, | ||||||
|  |     mode: 'history', | ||||||
|  |     routes, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return new Vue({ | ||||||
|  |     el, | ||||||
|  |     router, | ||||||
|  |     apolloProvider, | ||||||
|  |     provide: convertObjectPropsToCamelCase(provide), | ||||||
|  |     render(createElement) { | ||||||
|  |       return createElement(AchievementsApp); | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | init(); | ||||||
|  | @ -30,11 +30,6 @@ export default { | ||||||
|       required: false, |       required: false, | ||||||
|       default: 'div', |       default: 'div', | ||||||
|     }, |     }, | ||||||
|     collectionStyle: { |  | ||||||
|       type: Boolean, |  | ||||||
|       required: false, |  | ||||||
|       default: false, |  | ||||||
|     }, |  | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|  | @ -94,13 +89,7 @@ export default { | ||||||
|         </slot> |         </slot> | ||||||
|       </span> |       </span> | ||||||
| 
 | 
 | ||||||
|       <span |       <span class="gl-pr-3 gl-text-gray-900 gl-truncate-end"> | ||||||
|         class="gl-pr-3 gl-truncate-end gl-text-gray-900" |  | ||||||
|         :class="{ |  | ||||||
|           'gl-font-sm gl-font-weight-semibold': collectionStyle, |  | ||||||
|         }" |  | ||||||
|         data-testid="section-title" |  | ||||||
|       > |  | ||||||
|         {{ item.title }} |         {{ item.title }} | ||||||
|       </span> |       </span> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -70,7 +70,6 @@ export default { | ||||||
|     :item="sectionItem" |     :item="sectionItem" | ||||||
|     :expanded="expanded" |     :expanded="expanded" | ||||||
|     :separated="true" |     :separated="true" | ||||||
|     collection-style |  | ||||||
|     @collapse-toggle="expanded = !expanded" |     @collapse-toggle="expanded = !expanded" | ||||||
|   > |   > | ||||||
|     <draggable |     <draggable | ||||||
|  |  | ||||||
|  | @ -150,7 +150,6 @@ export default { | ||||||
|         v-for="item in nonStaticItems" |         v-for="item in nonStaticItems" | ||||||
|         :key="item.id" |         :key="item.id" | ||||||
|         :item="item" |         :item="item" | ||||||
|         :collection-style="supportsPins" |  | ||||||
|         tag="li" |         tag="li" | ||||||
|         @pin-add="createPin" |         @pin-add="createPin" | ||||||
|         @pin-remove="destroyPin" |         @pin-remove="destroyPin" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Groups | ||||||
|  |   class AchievementsController < Groups::ApplicationController | ||||||
|  |     feature_category :user_profile | ||||||
|  |     urgency :low | ||||||
|  | 
 | ||||||
|  |     before_action :authorize_read_achievement! | ||||||
|  | 
 | ||||||
|  |     private | ||||||
|  | 
 | ||||||
|  |     def authorize_read_achievement! | ||||||
|  |       render_404 unless can?(current_user, :read_achievement, group) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -613,8 +613,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo | ||||||
|     Feature.enabled?(:summarize_my_code_review, current_user) && |     Feature.enabled?(:summarize_my_code_review, current_user) && | ||||||
|       namespace.group_namespace? && |       namespace.group_namespace? && | ||||||
|       namespace.licensed_feature_available?(:summarize_my_mr_code_review) && |       namespace.licensed_feature_available?(:summarize_my_mr_code_review) && | ||||||
|       namespace.experiment_features_enabled && |       Gitlab::Llm::StageCheck.available?(namespace, :summarize_my_mr_code_review) && | ||||||
|       namespace.third_party_ai_features_enabled && |  | ||||||
|       merge_request.send_to_ai? |       merge_request.send_to_ai? | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | - breadcrumb_title _('Achievements') | ||||||
|  | - page_title _('Achievements') | ||||||
|  | - @content_wrapper_class = "gl-relative" | ||||||
|  | 
 | ||||||
|  | = content_for :after_content do | ||||||
|  |   #js-achievements-form-portal | ||||||
|  | 
 | ||||||
|  | #js-achievements-app{ data: { base_path: group_achievements_path(@group), view_model: Gitlab::Json.generate({ | ||||||
|  |   can_admin_achievement: can?(current_user, :admin_achievement, @group), | ||||||
|  |   can_award_achievement: can?(current_user, :award_achievement, @group), | ||||||
|  |   group_full_path: @group.full_path, | ||||||
|  |   group_id: @group.id, | ||||||
|  |   text_query: params[:search] | ||||||
|  |   }) } } | ||||||
|  | @ -1272,6 +1272,15 @@ | ||||||
|   :weight: 1 |   :weight: 1 | ||||||
|   :idempotent: false |   :idempotent: false | ||||||
|   :tags: [] |   :tags: [] | ||||||
|  | - :name: github_importer:github_import_pull_requests_import_merged_by | ||||||
|  |   :worker_name: Gitlab::GithubImport::PullRequests::ImportMergedByWorker | ||||||
|  |   :feature_category: :importers | ||||||
|  |   :has_external_dependencies: true | ||||||
|  |   :urgency: :low | ||||||
|  |   :resource_boundary: :cpu | ||||||
|  |   :weight: 1 | ||||||
|  |   :idempotent: false | ||||||
|  |   :tags: [] | ||||||
| - :name: github_importer:github_import_pull_requests_import_review | - :name: github_importer:github_import_pull_requests_import_review | ||||||
|   :worker_name: Gitlab::GithubImport::PullRequests::ImportReviewWorker |   :worker_name: Gitlab::GithubImport::PullRequests::ImportReviewWorker | ||||||
|   :feature_category: :importers |   :feature_category: :importers | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
|  | # TODO: remove in 16.1 milestone | ||||||
|  | # https://gitlab.com/gitlab-org/gitlab/-/issues/409706 | ||||||
| module Gitlab | module Gitlab | ||||||
|   module GithubImport |   module GithubImport | ||||||
|     class ImportPullRequestMergedByWorker # rubocop:disable Scalability/IdempotentWorker |     class ImportPullRequestMergedByWorker # rubocop:disable Scalability/IdempotentWorker | ||||||
|  | @ -12,7 +14,7 @@ module Gitlab | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def importer_class |       def importer_class | ||||||
|         Importer::PullRequestMergedByImporter |         Importer::PullRequests::MergedByImporter | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def object_type |       def object_type | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| # TODO: remove in 16.X milestone | # TODO: remove in 16.1 milestone | ||||||
| # https://gitlab.com/gitlab-org/gitlab/-/issues/377059 | # https://gitlab.com/gitlab-org/gitlab/-/issues/409706 | ||||||
| module Gitlab | module Gitlab | ||||||
|   module GithubImport |   module GithubImport | ||||||
|     class ImportReleaseAttachmentsWorker # rubocop:disable Scalability/IdempotentWorker |     class ImportReleaseAttachmentsWorker # rubocop:disable Scalability/IdempotentWorker | ||||||
|  |  | ||||||
|  | @ -0,0 +1,25 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Gitlab | ||||||
|  |   module GithubImport | ||||||
|  |     module PullRequests | ||||||
|  |       class ImportMergedByWorker # rubocop:disable Scalability/IdempotentWorker | ||||||
|  |         include ObjectImporter | ||||||
|  | 
 | ||||||
|  |         worker_resource_boundary :cpu | ||||||
|  | 
 | ||||||
|  |         def representation_class | ||||||
|  |           Gitlab::GithubImport::Representation::PullRequest | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def importer_class | ||||||
|  |           Importer::PullRequests::MergedByImporter | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def object_type | ||||||
|  |           :pull_request_merged_by | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -15,7 +15,7 @@ module Gitlab | ||||||
|         # client - An instance of Gitlab::GithubImport::Client. |         # client - An instance of Gitlab::GithubImport::Client. | ||||||
|         # project - An instance of Project. |         # project - An instance of Project. | ||||||
|         def import(client, project) |         def import(client, project) | ||||||
|           waiter = Importer::PullRequestsMergedByImporter |           waiter = Importer::PullRequests::AllMergedByImporter | ||||||
|             .new(project, client) |             .new(project, client) | ||||||
|             .execute |             .execute | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| --- |  | ||||||
| name: namespace_storage_limit_bypass_date_check |  | ||||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86794 |  | ||||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/361785 |  | ||||||
| milestone: '15.0' |  | ||||||
| type: development |  | ||||||
| group: group::utilization |  | ||||||
| default_enabled: false |  | ||||||
|  | @ -1,23 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| # This pulls in |  | ||||||
| # https://github.com/doorkeeper-gem/doorkeeper-openid_connect/pull/194 |  | ||||||
| # to ensure generated `kid` values are RFC 7638-compliant. |  | ||||||
| require 'doorkeeper/openid_connect' |  | ||||||
| 
 |  | ||||||
| raise 'This patch is only needed for doorkeeper_openid_connect v1.8.5' if Doorkeeper::OpenidConnect::VERSION != '1.8.5' |  | ||||||
| 
 |  | ||||||
| module Doorkeeper |  | ||||||
|   module OpenidConnect |  | ||||||
|     def self.signing_key |  | ||||||
|       key = |  | ||||||
|         if %i[HS256 HS384 HS512].include?(signing_algorithm) |  | ||||||
|           configuration.signing_key |  | ||||||
|         else |  | ||||||
|           OpenSSL::PKey.read(configuration.signing_key) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|       ::JWT::JWK.new(key, { kid_generator: JWT::JWK::Thumbprint }) |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -158,6 +158,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do | ||||||
|       resources :contacts, only: [:index, :new, :edit] |       resources :contacts, only: [:index, :new, :edit] | ||||||
|       resources :organizations, only: [:index, :new, :edit] |       resources :organizations, only: [:index, :new, :edit] | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|  |     resources :achievements, only: [:index, :new, :edit] | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   scope( |   scope( | ||||||
|  |  | ||||||
|  | @ -2,16 +2,20 @@ const { parse, compile: compilerDomCompile } = require('@vue/compiler-dom'); | ||||||
| 
 | 
 | ||||||
| const COMMENT_NODE_TYPE = 3; | const COMMENT_NODE_TYPE = 3; | ||||||
| 
 | 
 | ||||||
| const getPropIndex = (node, prop) => node.props?.findIndex((p) => p.name === prop) ?? -1; | const hasProp = (node, prop) => node.props?.some((p) => p.name === prop); | ||||||
| 
 | 
 | ||||||
| function modifyKeysInsideTemplateTag(templateNode) { | function modifyKeysInsideTemplateTag(templateNode) { | ||||||
|  |   if (!templateNode.tag === 'template' || !hasProp(templateNode, 'for')) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   let keyCandidate = null; |   let keyCandidate = null; | ||||||
|   for (const node of templateNode.children) { |   for (const node of templateNode.children) { | ||||||
|     const keyBindingIndex = node.props |     const keyBindingIndex = node.props | ||||||
|       ? node.props.findIndex((prop) => prop.arg && prop.arg.content === 'key') |       ? node.props.findIndex((prop) => prop.arg && prop.arg.content === 'key') | ||||||
|       : -1; |       : -1; | ||||||
| 
 | 
 | ||||||
|     if (keyBindingIndex !== -1 && getPropIndex(node, 'for') === -1) { |     if (keyBindingIndex !== -1 && !hasProp(node, 'for')) { | ||||||
|       if (!keyCandidate) { |       if (!keyCandidate) { | ||||||
|         keyCandidate = node.props[keyBindingIndex]; |         keyCandidate = node.props[keyBindingIndex]; | ||||||
|       } |       } | ||||||
|  | @ -24,40 +28,97 @@ function modifyKeysInsideTemplateTag(templateNode) { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function getSlotName(node) { | ||||||
|  |   return node?.props?.find((prop) => prop.name === 'slot')?.arg?.content; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function filterCommentNodeAndTrailingSpace(node, idx, list) { | ||||||
|  |   if (node.type === COMMENT_NODE_TYPE) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (node.content !== ' ') { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (list[idx - 1]?.type === COMMENT_NODE_TYPE) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function filterCommentNodes(node) { | ||||||
|  |   const { length: originalLength } = node.children; | ||||||
|  |   // eslint-disable-next-line no-param-reassign
 | ||||||
|  |   node.children = node.children.filter(filterCommentNodeAndTrailingSpace); | ||||||
|  |   if (node.children.length !== originalLength) { | ||||||
|  |     // trim remaining spaces
 | ||||||
|  |     while (node.children.at(-1)?.content === ' ') { | ||||||
|  |       node.children.pop(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function dropVOnceForChildrenInsideVIfBecauseOfIssue7725(node) { | ||||||
|  |   // See https://github.com/vuejs/core/issues/7725 for details
 | ||||||
|  |   if (!hasProp(node, 'if')) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   node.children?.forEach((child) => { | ||||||
|  |     if (Array.isArray(child.props)) { | ||||||
|  |       // eslint-disable-next-line no-param-reassign
 | ||||||
|  |       child.props = child.props.filter((prop) => prop.name !== 'once'); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function fixSameSlotsInsideTemplateFailingWhenUsingWhitespacePreserveDueToIssue6063(node) { | ||||||
|  |   // See https://github.com/vuejs/core/issues/6063 for details
 | ||||||
|  |   // eslint-disable-next-line no-param-reassign
 | ||||||
|  |   node.children = node.children.filter((child, idx) => { | ||||||
|  |     if (child.content !== ' ') { | ||||||
|  |       // We need to drop only comment nodes
 | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const previousNodeSlotName = getSlotName(node.children[idx - 1]); | ||||||
|  |     const nextNodeSlotName = getSlotName(node.children[idx + 1]); | ||||||
|  | 
 | ||||||
|  |     if (previousNodeSlotName && previousNodeSlotName === nextNodeSlotName) { | ||||||
|  |       // We have a space beween two slot entries with same slot name, we need to drop it
 | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|   parse, |   parse, | ||||||
|   compile(template, options) { |   compile(template, options) { | ||||||
|     const rootNode = parse(template, options); |     const rootNode = parse(template, options); | ||||||
| 
 | 
 | ||||||
|     // We do not want to switch to whitespace: collapse mode which is Vue.js 3 default
 |  | ||||||
|     // It will be too devastating to codebase
 |  | ||||||
| 
 |  | ||||||
|     // However, without `whitespace: condense` Vue will treat spaces between comments
 |  | ||||||
|     // and nodes itself as text nodes, resulting in multi-root component
 |  | ||||||
|     // For multi-root component passing classes / attributes fallthrough will not work
 |  | ||||||
| 
 |  | ||||||
|     // See https://github.com/vuejs/core/issues/7909 for details
 |  | ||||||
| 
 |  | ||||||
|     // To fix that we simply drop all component comments only on top-level
 |  | ||||||
|     rootNode.children = rootNode.children.filter((n) => n.type !== COMMENT_NODE_TYPE); |  | ||||||
| 
 |  | ||||||
|     const pendingNodes = [rootNode]; |     const pendingNodes = [rootNode]; | ||||||
|     while (pendingNodes.length) { |     while (pendingNodes.length) { | ||||||
|       const currentNode = pendingNodes.pop(); |       const currentNode = pendingNodes.pop(); | ||||||
|       if (getPropIndex(currentNode, 'for') !== -1) { |       if (Array.isArray(currentNode.children)) { | ||||||
|         if (currentNode.tag === 'template') { |         // This one will be dropped all together with compiler when we drop Vue.js 2 support
 | ||||||
|           // This one will be dropped all together with compiler when we drop Vue.js 2 support
 |         modifyKeysInsideTemplateTag(currentNode); | ||||||
|           modifyKeysInsideTemplateTag(currentNode); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         // This one will be dropped when https://github.com/vuejs/core/issues/7725 will be fixed
 |         dropVOnceForChildrenInsideVIfBecauseOfIssue7725(currentNode); | ||||||
|         const vOncePropIndex = getPropIndex(currentNode, 'once'); | 
 | ||||||
|         if (vOncePropIndex !== -1) { |         // See https://github.com/vuejs/core/issues/7909 for details
 | ||||||
|           currentNode.props.splice(vOncePropIndex, 1); |         // However, this issue applies not only to root-level nodes
 | ||||||
|         } |         // But on any level comments could change slot emptiness detection
 | ||||||
|  |         // so we simply drop them
 | ||||||
|  |         filterCommentNodes(currentNode); | ||||||
|  | 
 | ||||||
|  |         fixSameSlotsInsideTemplateFailingWhenUsingWhitespacePreserveDueToIssue6063(currentNode); | ||||||
|  | 
 | ||||||
|  |         currentNode.children.forEach((child) => pendingNodes.push(child)); | ||||||
|       } |       } | ||||||
| 
 |  | ||||||
|       currentNode.children?.forEach((child) => pendingNodes.push(child)); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return compilerDomCompile(rootNode, options); |     return compilerDomCompile(rootNode, options); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,41 @@ | ||||||
|  | # This is a template for a feature deprecation. | ||||||
|  | # | ||||||
|  | # Please refer to the deprecation guidelines to confirm your understanding of the | ||||||
|  | # definitions for "Deprecation", "End of Support", and "Removal": | ||||||
|  | # https://docs.gitlab.com/ee/development/deprecation_guidelines/#terminology | ||||||
|  | # | ||||||
|  | # Deprecations must be announced at least three releases prior to removal. | ||||||
|  | # See the OPTIONAL END OF SUPPORT FIELDS section below if an End of Support period also applies. | ||||||
|  | # | ||||||
|  | # Breaking changes must happen in a major release. | ||||||
|  | # | ||||||
|  | # For more information please refer to the handbook documentation here: | ||||||
|  | # https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations | ||||||
|  | # | ||||||
|  | # Please delete this line and above before submitting your merge request. | ||||||
|  | # | ||||||
|  | # REQUIRED FIELDS | ||||||
|  | # | ||||||
|  | - title: "`POST ci/lint` API endpoint removed"  # (required) The name of the feature to be deprecated | ||||||
|  |   announcement_milestone: "15.7"  # (required) The milestone when this feature was first announced as deprecated. | ||||||
|  |   removal_milestone: "16.0"  # (required) The milestone when this feature is planned to be removed | ||||||
|  |   breaking_change: true  # (required) If this deprecation is a breaking change, set this value to true | ||||||
|  |   reporter: dhershkovitch  # (required) GitLab username of the person reporting the deprecation | ||||||
|  |   stage: verify  # (required) String value of the stage that the feature was created in. e.g., Growth | ||||||
|  |   issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381669  # (required) Link to the deprecation issue in GitLab | ||||||
|  |   body: |  # (required) Do not modify this line, instead modify the lines below. | ||||||
|  |     The `POST ci/lint` API endpoint was deprecated in 15.7, and removed in 16.0. This endpoint did not validate the full range of CI/CD configuration options. Instead, use [`POST /projects/:id/ci/lint`](https://docs.gitlab.com/ee/api/lint.html#validate-a-ci-yaml-configuration-with-a-namespace), which properly validates CI/CD configuration. | ||||||
|  | # | ||||||
|  | # OPTIONAL END OF SUPPORT FIELDS | ||||||
|  | # | ||||||
|  | # If an End of Support period applies, the announcement should be shared with GitLab Support | ||||||
|  | # in the `#spt_managers` channel in Slack, and mention `@gitlab-com/support` in this MR. | ||||||
|  | # | ||||||
|  |   end_of_support_milestone:  # (optional) Use "XX.YY" format. The milestone when support for this feature will end. | ||||||
|  |   # | ||||||
|  |   # OTHER OPTIONAL FIELDS | ||||||
|  |   # | ||||||
|  |   tiers:  # (optional - may be required in the future) An array of tiers that the feature is available in currently.  e.g., [Free, Silver, Gold, Core, Premium, Ultimate] | ||||||
|  |   documentation_url:  # (optional) This is a link to the current documentation page | ||||||
|  |   image_url:  # (optional) This is a link to a thumbnail image depicting the feature | ||||||
|  |   video_url:  # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg | ||||||
|  | @ -761,7 +761,7 @@ users, [see what to do when no users are found](#no-users-are-found). | ||||||
| ### GitLab logs | ### GitLab logs | ||||||
| 
 | 
 | ||||||
| If a user account is blocked or unblocked due to the LDAP configuration, a | If a user account is blocked or unblocked due to the LDAP configuration, a | ||||||
| message is [logged to `application.log`](../../logs/index.md#applicationlog). | message is [logged to `application_json.log`](../../logs/index.md#application_jsonlog). | ||||||
| 
 | 
 | ||||||
| If there is an unexpected error during an LDAP lookup (configuration error, | If there is an unexpected error during an LDAP lookup (configuration error, | ||||||
| timeout), the sign-in is rejected and a message is [logged to `production.log`](../../logs/index.md#productionlog). | timeout), the sign-in is rejected and a message is [logged to `production.log`](../../logs/index.md#productionlog). | ||||||
|  |  | ||||||
|  | @ -350,7 +350,9 @@ the `view_duration_s` is calculated by [`duration_s - db_duration_s`](https://gi | ||||||
| Therefore, `view_duration_s` can be affected by multiple different factors, like read-write | Therefore, `view_duration_s` can be affected by multiple different factors, like read-write | ||||||
| process on Redis or external HTTP, not only the serialization process. | process on Redis or external HTTP, not only the serialization process. | ||||||
| 
 | 
 | ||||||
| ## `application.log` | ## `application.log` (deprecated) | ||||||
|  | 
 | ||||||
|  | > [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111046) in GitLab 15.10. | ||||||
| 
 | 
 | ||||||
| Depending on your installation method, this file is located at: | Depending on your installation method, this file is located at: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ You must replace the `vault.example.com` URL below with the URL of your Vault se | ||||||
| 
 | 
 | ||||||
| ## How it works | ## How it works | ||||||
| 
 | 
 | ||||||
| Each job has JSON Web Token (JWT) provided as CI/CD variable named `CI_JOB_JWT`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication) method. | ID tokens are JSON Web Tokens (JWTs) used for OIDC authentication with third-party services. If a job has at least one ID token defined, the `secrets` keyword automatically uses that token to authenticate with Vault. | ||||||
| 
 | 
 | ||||||
| The following fields are included in the JWT: | The following fields are included in the JWT: | ||||||
| 
 | 
 | ||||||
|  | @ -256,61 +256,36 @@ $ vault write auth/jwt/config \ | ||||||
| 
 | 
 | ||||||
| For the full list of available configuration options, see Vault's [API documentation](https://developer.hashicorp.com/vault/api-docs/auth/jwt#configure). | For the full list of available configuration options, see Vault's [API documentation](https://developer.hashicorp.com/vault/api-docs/auth/jwt#configure). | ||||||
| 
 | 
 | ||||||
| The following job, when run for the default branch, is able to read secrets under `secret/myproject/staging/`, but not the secrets under `secret/myproject/production/`: | In GitLab, create the following [CI/CD variables](../../variables/index.md#for-a-project) to provide details about your Vault server: | ||||||
|  | 
 | ||||||
|  | - `VAULT_SERVER_URL` - The URL of your Vault server, for example `https://vault.example.com:8200`. | ||||||
|  | - `VAULT_AUTH_ROLE` - Optional. The role to use when attempting to authenticate. If no role is specified, Vault uses the [default role](https://developer.hashicorp.com/vault/api-docs/auth/jwt#default_role) specified when the authentication method was configured. | ||||||
|  | - `VAULT_AUTH_PATH` - Optional. The path where the authentication method is mounted. Default is `jwt`. | ||||||
|  | - `VAULT_NAMESPACE` - Optional. The [Vault Enterprise namespace](https://developer.hashicorp.com/vault/docs/enterprise/namespaces) to use for reading secrets and authentication. If no namespace is specified, Vault uses the root (`/`) namespace. The setting is ignored by Vault Open Source. | ||||||
|  | 
 | ||||||
|  | The following job, when run for the default branch, can read secrets under `secret/myproject/staging/`, but not the secrets under `secret/myproject/production/`: | ||||||
| 
 | 
 | ||||||
| ```yaml | ```yaml | ||||||
| read_secrets: | job_with_secrets: | ||||||
|   image: vault:latest |   id_tokens: | ||||||
|  |     VAULT_ID_TOKEN: | ||||||
|  |       aud: https://example.vault.com | ||||||
|  |   secrets: | ||||||
|  |     STAGING_DB_PASSWORD: | ||||||
|  |       vault: secret/myproject/staging/db/password@secrets # authenticates using $VAULT_ID_TOKEN | ||||||
|   script: |   script: | ||||||
|     # Check job's ref name |     - access-staging-db.sh --token $STAGING_DB_PASSWORD | ||||||
|     - echo $CI_COMMIT_REF_NAME |  | ||||||
|     # and is this ref protected |  | ||||||
|     - echo $CI_COMMIT_REF_PROTECTED |  | ||||||
|     # Vault's address can be provided here or as CI/CD variable |  | ||||||
|     - export VAULT_ADDR=http://vault.example.com:8200 |  | ||||||
|     # Authenticate and get token. Token expiry time and other properties can be configured |  | ||||||
|     # when configuring JWT Auth - https://developer.hashicorp.com/vault/api-docs/auth/jwt#parameters-1 |  | ||||||
|     - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-staging jwt=$CI_JOB_JWT)" |  | ||||||
|     # Now use the VAULT_TOKEN to read the secret and store it in an environment variable |  | ||||||
|     - export PASSWORD="$(vault kv get -field=password secret/myproject/staging/db)" |  | ||||||
|     # Use the secret |  | ||||||
|     - echo $PASSWORD |  | ||||||
|     # This will fail because the role myproject-staging can not read secrets from secret/myproject/production/* |  | ||||||
|     - export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)" |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| NOTE: | In this example: | ||||||
| If you're using a Vault instance provided by HashiCorp Cloud Platform, |  | ||||||
| you need to export the `VAULT_NAMESPACE` variable. Its default value is `admin`. |  | ||||||
| 
 | 
 | ||||||
|  | - `@secrets` - The vault name, where your Secrets Engines are enabled. | ||||||
| 
 | - `secret/myproject/staging/db` - The path location of the secret in Vault. | ||||||
| The following job is able to authenticate using the `myproject-production` role and read secrets under `/secret/myproject/production/`: | - `password` The field to be fetched within the referenced secret. | ||||||
| 
 |  | ||||||
| ```yaml |  | ||||||
| read_secrets: |  | ||||||
|   image: vault:latest |  | ||||||
|   script: |  | ||||||
|     # Check job's ref name |  | ||||||
|     - echo $CI_COMMIT_REF_NAME |  | ||||||
|     # and is this ref protected |  | ||||||
|     - echo $CI_COMMIT_REF_PROTECTED |  | ||||||
|     # Vault's address can be provided here or as CI/CD variable |  | ||||||
|     - export VAULT_ADDR=http://vault.example.com:8200 |  | ||||||
|     # Authenticate and get token. Token expiry time and other properties can be configured |  | ||||||
|     # when configuring JWT Auth - https://developer.hashicorp.com/vault/api-docs/auth/jwt#parameters-1 |  | ||||||
|     - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-production jwt=$CI_JOB_JWT)" |  | ||||||
|     # Now use the VAULT_TOKEN to read the secret and store it in environment variable |  | ||||||
|     - export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)" |  | ||||||
|     # Use the secret |  | ||||||
|     - echo $PASSWORD |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| ### Limit token access to Vault secrets | ### Limit token access to Vault secrets | ||||||
| 
 | 
 | ||||||
| You can control `CI_JOB_JWT` access to Vault secrets by using Vault protections | You can control ID token access to Vault secrets by using Vault protections | ||||||
| and GitLab features. For example, restrict the token by: | and GitLab features. For example, restrict the token by: | ||||||
| 
 | 
 | ||||||
| - Using Vault [bound claims](https://developer.hashicorp.com/vault/docs/auth/jwt#bound-claims) | - Using Vault [bound claims](https://developer.hashicorp.com/vault/docs/auth/jwt#bound-claims) | ||||||
|  |  | ||||||
|  | @ -65,7 +65,7 @@ test2: | ||||||
| 
 | 
 | ||||||
| `&` sets up the name of the anchor (`job_configuration`), `<<` means "merge the | `&` sets up the name of the anchor (`job_configuration`), `<<` means "merge the | ||||||
| given hash into the current one," and `*` includes the named anchor | given hash into the current one," and `*` includes the named anchor | ||||||
| (`job_configuration` again). The expanded version of this example is: | (`job_configuration` again). The [expanded](../pipeline_editor/index.md#view-full-configuration) version of this example is: | ||||||
| 
 | 
 | ||||||
| ```yaml | ```yaml | ||||||
| .job_template: | .job_template: | ||||||
|  | @ -123,7 +123,7 @@ test:mysql: | ||||||
|   services: *mysql_configuration |   services: *mysql_configuration | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The expanded version is: | The [expanded](../pipeline_editor/index.md#view-full-configuration) version is: | ||||||
| 
 | 
 | ||||||
| ```yaml | ```yaml | ||||||
| .job_template: | .job_template: | ||||||
|  |  | ||||||
|  | @ -83,3 +83,65 @@ Quoting the [documentation](https://clickhouse.com/docs/en/sql-reference/stateme | ||||||
| > If there's some aggregation in the view query, it's applied only to the batch | > If there's some aggregation in the view query, it's applied only to the batch | ||||||
| > of freshly inserted data. Any changes to existing data of the source table | > of freshly inserted data. Any changes to existing data of the source table | ||||||
| > (like update, delete, drop a partition, etc.) do not change the materialized view. | > (like update, delete, drop a partition, etc.) do not change the materialized view. | ||||||
|  | 
 | ||||||
|  | ## Secure and sensible defaults | ||||||
|  | 
 | ||||||
|  | ClickHouse instances should follow these security recommendations: | ||||||
|  | 
 | ||||||
|  | ### Users | ||||||
|  | 
 | ||||||
|  | Files: `users.xml` and `config.xml`. | ||||||
|  | 
 | ||||||
|  | | Topic | Security Requirement | Reason | | ||||||
|  | | ----- | -------------------- | ------ | | ||||||
|  | | [`user_name/password`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-namepassword) | Usernames **must not** be blank. Passwords **must** use `password_sha256_hex` and **must not** be blank. | `plaintext` and `password_double_sha1_hex` are insecure. If username isn't specified, [`default` is used with no password](https://clickhouse.com/docs/en/operations/settings/settings-users/). | | ||||||
|  | | [`access_management`](https://clickhouse.com/docs/en/operations/settings/settings-users/#access_management-user-setting) | Use Server [configuration files](https://clickhouse.com/docs/en/operations/configuration-files) `users.xml` and `config.xml`. Avoid SQL-driven workflow. | SQL-driven workflow implies that at least one user has `access_management` which can be avoided via configuration files. These files are easier to audit and monitor too, considering that ["You can't manage the same access entity by both configuration methods simultaneously."](https://clickhouse.com/docs/en/operations/access-rights/#access-control). | | ||||||
|  | | [`user_name/networks`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-namenetworks) | At least one of `<ip>`, `<host>`, `<host_regexp>` **must** be set. Do not use `<ip>::/0</ip>` to open access for any network. | Network controls. ([Trust cautiously](https://about.gitlab.com/handbook/security/architecture/#trust-cautiously) principle) | | ||||||
|  | | [`user_name/profile`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-nameprofile) | Use profiles to set similar properties across multiple users and set limits (from the user interface). | [Least privilege](https://about.gitlab.com/handbook/security/architecture/#assign-the-least-privilege-possible) principle and limits. | | ||||||
|  | | [`user_name/quota`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-namequota) | Set quotas for users whenever possible. | Limit resource usage over a period of time or track the use of resources. | | ||||||
|  | | [`user_name/databases`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-namedatabases) | Restrict access to data, and avoid users with full access. | [Least privilege](https://about.gitlab.com/handbook/security/architecture/#assign-the-least-privilege-possible) principle. | | ||||||
|  | 
 | ||||||
|  | ### Network | ||||||
|  | 
 | ||||||
|  | Files: `config.xml` | ||||||
|  | 
 | ||||||
|  | | Topic | Security Requirement | Reason | | ||||||
|  | | ----- | -------------------- | ------ | | ||||||
|  | | [`mysql_port`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#server_configuration_parameters-mysql_port) | Disable MySQL access unless strictly necessary:<br/> `<!-- <mysql_port>9004</mysql_port> -->`. | Close unnecessary ports and features exposure. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth) principle) | | ||||||
|  | | [`postgresql_port`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#server_configuration_parameters-postgresql_port) | Disable PostgreSQL access unless strictly necessary:<br/> `<!-- <mysql_port>9005</mysql_port> -->` | Close unnecessary ports and features exposure. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth) principle) | | ||||||
|  | | [`http_port/https_port`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#http-porthttps-port) & [`tcp_port/tcp_port_secure`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#http-porthttps-port) | Configure [SSL-TLS](https://clickhouse.com/docs/en/guides/sre/configuring-ssl), and disable non SSL ports:<br/>`<!-- <http_port>8123</http_port> -->`<br/>`<!-- <tcp_port>9000</tcp_port> -->`<br/>and enable secure ports:<br/>`<https_port>8443</https_port>`<br/>`<tcp_port_secure>9440</tcp_port_secure>` | Encrypt data in transit. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth) principle) | | ||||||
|  | | [`interserver_http_host`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#interserver-http-host) | Disable `interserver_http_host` in favor of `interserver_https_host` (`<interserver_https_port>9010</interserver_https_port>`) if ClickHouse is configured as a cluster. | Encrypt data in transit. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth) principle) | | ||||||
|  | 
 | ||||||
|  | ### Storage | ||||||
|  | 
 | ||||||
|  | | Topic | Security Requirement | Reason | | ||||||
|  | | ----- | -------------------- | ------ | | ||||||
|  | | Permissions | ClickHouse runs by default with the `clickhouse` user. Running as `root` is never needed. Use the principle of least privileges for the folders: `/etc/clickhouse-server`, `/var/lib/clickhouse`, `/var/log/clickhouse-server`. These folders must belong to the `clickhouse` user and group, and no other system user must have access to them. | Default passwords, ports and rules are "open doors". ([Fail securely & use secure defaults](https://about.gitlab.com/handbook/security/architecture/#fail-securely--use-secure-defaults) principle) | | ||||||
|  | | Encryption | Use an encrypted storage for logs and data if RED data is processed. On Kubernetes, the [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) used must be encrypted. | Encrypt data at rest. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth)) | | ||||||
|  | 
 | ||||||
|  | ### Logging | ||||||
|  | 
 | ||||||
|  | | Topic | Security Requirement | Reason | | ||||||
|  | | ----- | -------------------- | ------ | | ||||||
|  | | `logger` | `Log` and `errorlog` **must** be defined and writable by `clickhouse`. | Make sure logs are stored. | | ||||||
|  | | SIEM  | If hosted on GitLab.com, the ClickHouse instance or cluster **must** report [logs to our SIEM](https://internal-handbook.gitlab.io/handbook/security/infrastructure_security_logging/tooling/devo/) (internal link). | [GitLab logs critical information system activity](https://about.gitlab.com/handbook/security/audit-logging-policy.html). | | ||||||
|  | | Log sensitive data | Query masking rules **must** be used if sensitive data can be logged. See [example masking rules](#example-masking-rules). | [Column level encryption](https://clickhouse.com/docs/en/sql-reference/functions/encryption-functions/) can be used and leak sensitive data (keys) in logs. | | ||||||
|  | 
 | ||||||
|  | #### Example masking rules | ||||||
|  | 
 | ||||||
|  | ```xml | ||||||
|  | <query_masking_rules> | ||||||
|  |     <rule> | ||||||
|  |         <name>hide SSN</name> | ||||||
|  |         <regexp>(^|\D)\d{3}-\d{2}-\d{4}($|\D)</regexp> | ||||||
|  |         <replace>000-00-0000</replace> | ||||||
|  |     </rule> | ||||||
|  |     <rule> | ||||||
|  |         <name>hide encrypt/decrypt arguments</name> | ||||||
|  |         <regexp> | ||||||
|  |            ((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\) | ||||||
|  |         </regexp> | ||||||
|  |         <replace>\1(???)</replace> | ||||||
|  |     </rule> | ||||||
|  | </query_masking_rules> | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | @ -81,7 +81,7 @@ This worker imports the pull requests' _merged-by_ user information. The | ||||||
| [_List pull requests_](https://docs.github.com/en/rest/pulls#list-pull-requests) | [_List pull requests_](https://docs.github.com/en/rest/pulls#list-pull-requests) | ||||||
| API doesn't provide this information. Therefore, this stage must fetch each merged pull request | API doesn't provide this information. Therefore, this stage must fetch each merged pull request | ||||||
| individually to import this information. A | individually to import this information. A | ||||||
| `Gitlab::GithubImport::ImportPullRequestMergedByWorker` job is scheduled for each fetched pull | `Gitlab::GithubImport::PullRequests::ImportMergedByWorker` job is scheduled for each fetched pull | ||||||
| request. | request. | ||||||
| 
 | 
 | ||||||
| ### 7. Stage::ImportPullRequestsReviewRequestsWorker | ### 7. Stage::ImportPullRequestsReviewRequestsWorker | ||||||
|  |  | ||||||
|  | @ -1989,20 +1989,6 @@ For more information, refer to [security report validation](https://docs.gitlab. | ||||||
| 
 | 
 | ||||||
| <div class="deprecation breaking-change" data-milestone="16.0"> | <div class="deprecation breaking-change" data-milestone="16.0"> | ||||||
| 
 | 
 | ||||||
| ### Self-monitoring project is removed |  | ||||||
| 
 |  | ||||||
| <div class="deprecation-notes"> |  | ||||||
| - Announced in: GitLab <span class="milestone">14.9</span> |  | ||||||
| - This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). |  | ||||||
| - To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/groups/gitlab-org/-/epics/10030). |  | ||||||
| </div> |  | ||||||
| 
 |  | ||||||
| GitLab self-monitoring project was meant to enable self-hosted GitLab administrators to visualize performance metrics of GitLab within GitLab itself. This feature relied on GitLab Metrics dashboards. With metrics dashboard being removed, self-monitoring project is also removed. We recommended that self-hosted users monitor their GitLab instance with alternative visualization tools, such as Grafana. |  | ||||||
| 
 |  | ||||||
| </div> |  | ||||||
| 
 |  | ||||||
| <div class="deprecation breaking-change" data-milestone="16.0"> |  | ||||||
| 
 |  | ||||||
| ### Shimo integration | ### Shimo integration | ||||||
| 
 | 
 | ||||||
| <div class="deprecation-notes"> | <div class="deprecation-notes"> | ||||||
|  |  | ||||||
|  | @ -442,6 +442,14 @@ Review the details carefully before upgrading. | ||||||
| Version 14.x.x [security report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas) have been removed. | Version 14.x.x [security report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas) have been removed. | ||||||
| Security reports that use schema version 14.x.x will cause an error in the pipeline's **Security** tab. For more information, refer to [security report validation](https://docs.gitlab.com/ee/user/application_security/#security-report-validation). | Security reports that use schema version 14.x.x will cause an error in the pipeline's **Security** tab. For more information, refer to [security report validation](https://docs.gitlab.com/ee/user/application_security/#security-report-validation). | ||||||
| 
 | 
 | ||||||
|  | ### Self-monitoring project is removed | ||||||
|  | 
 | ||||||
|  | WARNING: | ||||||
|  | This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). | ||||||
|  | Review the details carefully before upgrading. | ||||||
|  | 
 | ||||||
|  | GitLab self-monitoring project was meant to enable self-hosted GitLab administrators to visualize performance metrics of GitLab within GitLab itself. This feature relied on GitLab Metrics dashboards. With metrics dashboard being removed, self-monitoring project is also removed. We recommended that self-hosted users monitor their GitLab instance with alternative visualization tools, such as Grafana. | ||||||
|  | 
 | ||||||
| ### Starboard directive in the config for the GitLab agent for Kubernetes removed | ### Starboard directive in the config for the GitLab agent for Kubernetes removed | ||||||
| 
 | 
 | ||||||
| WARNING: | WARNING: | ||||||
|  | @ -539,6 +547,14 @@ The predefined CI/CD variables that start with `CI_BUILD_*` were deprecated in G | ||||||
| | `CI_BUILD_TOKEN`      | `CI_JOB_TOKEN`          | | | `CI_BUILD_TOKEN`      | `CI_JOB_TOKEN`          | | ||||||
| | `CI_BUILD_TRIGGERED`  | `CI_PIPELINE_TRIGGERED` | | | `CI_BUILD_TRIGGERED`  | `CI_PIPELINE_TRIGGERED` | | ||||||
| 
 | 
 | ||||||
|  | ### `POST ci/lint` API endpoint removed | ||||||
|  | 
 | ||||||
|  | WARNING: | ||||||
|  | This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). | ||||||
|  | Review the details carefully before upgrading. | ||||||
|  | 
 | ||||||
|  | The `POST ci/lint` API endpoint was deprecated in 15.7, and removed in 16.0. This endpoint did not validate the full range of CI/CD configuration options. Instead, use [`POST /projects/:id/ci/lint`](https://docs.gitlab.com/ee/api/lint.html#validate-a-ci-yaml-configuration-with-a-namespace), which properly validates CI/CD configuration. | ||||||
|  | 
 | ||||||
| ### vulnerabilityFindingDismiss GraphQL mutation | ### vulnerabilityFindingDismiss GraphQL mutation | ||||||
| 
 | 
 | ||||||
| WARNING: | WARNING: | ||||||
|  |  | ||||||
|  | @ -458,7 +458,7 @@ For multi-node systems we recommend ingesting the logs into services like Elasti | ||||||
| 
 | 
 | ||||||
| | Log file                | Contents | | | Log file                | Contents | | ||||||
| |:------------------------|:---------| | |:------------------------|:---------| | ||||||
| | `application.log`       | GitLab user activity | | | `application_json.log`  | GitLab user activity | | ||||||
| | `git_json.log`          | Failed GitLab interaction with Git repositories | | | `git_json.log`          | Failed GitLab interaction with Git repositories | | ||||||
| | `production.log`        | Requests received from Puma, and the actions taken to serve those requests | | | `production.log`        | Requests received from Puma, and the actions taken to serve those requests | | ||||||
| | `sidekiq.log`           | Background jobs | | | `sidekiq.log`           | Background jobs | | ||||||
|  |  | ||||||
|  | @ -50,6 +50,11 @@ configured for personal access tokens. | ||||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days and default role of Guest is populated in the UI. | > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days and default role of Guest is populated in the UI. | ||||||
| > - Ability to create non-expiring group access tokens [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0. | > - Ability to create non-expiring group access tokens [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0. | ||||||
| 
 | 
 | ||||||
|  | WARNING: | ||||||
|  | Project access tokens are treated as [internal users](../../../development/internal_users.md). | ||||||
|  | If an internal user creates a project access token, that token is able to access | ||||||
|  | all projects that have visibility level set to [Internal](../../public_access.md). | ||||||
|  | 
 | ||||||
| To create a group access token: | To create a group access token: | ||||||
| 
 | 
 | ||||||
| 1. On the top bar, select **Main menu > Groups** and find your group. | 1. On the top bar, select **Main menu > Groups** and find your group. | ||||||
|  | @ -66,11 +71,6 @@ To create a group access token: | ||||||
| 
 | 
 | ||||||
| A group access token is displayed. Save the group access token somewhere safe. After you leave or refresh the page, you can't view it again. | A group access token is displayed. Save the group access token somewhere safe. After you leave or refresh the page, you can't view it again. | ||||||
| 
 | 
 | ||||||
| WARNING: |  | ||||||
| Group access tokens are treated as [internal users](../../../development/internal_users.md). |  | ||||||
| If an internal user creates a group access token, that token is able to access all |  | ||||||
| groups that have visibility level set to [Internal](../../public_access.md). |  | ||||||
| 
 |  | ||||||
| ## Create a group access token using Rails console | ## Create a group access token using Rails console | ||||||
| 
 | 
 | ||||||
| GitLab 14.6 and earlier doesn't support creating group access tokens using the UI | GitLab 14.6 and earlier doesn't support creating group access tokens using the UI | ||||||
|  |  | ||||||
|  | @ -50,6 +50,11 @@ configured for personal access tokens. | ||||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days and default role of Guest is populated in the UI. | > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days and default role of Guest is populated in the UI. | ||||||
| > - Ability to create non-expiring project access tokens [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0. | > - Ability to create non-expiring project access tokens [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0. | ||||||
| 
 | 
 | ||||||
|  | WARNING: | ||||||
|  | Project access tokens are treated as [internal users](../../../development/internal_users.md). | ||||||
|  | If an internal user creates a project access token, that token is able to access | ||||||
|  | all projects that have visibility level set to [Internal](../../public_access.md). | ||||||
|  | 
 | ||||||
| To create a project access token: | To create a project access token: | ||||||
| 
 | 
 | ||||||
| 1. On the top bar, select **Main menu > Projects** and find your project. | 1. On the top bar, select **Main menu > Projects** and find your project. | ||||||
|  | @ -66,11 +71,6 @@ To create a project access token: | ||||||
| 
 | 
 | ||||||
| A project access token is displayed. Save the project access token somewhere safe. After you leave or refresh the page, you can't view it again. | A project access token is displayed. Save the project access token somewhere safe. After you leave or refresh the page, you can't view it again. | ||||||
| 
 | 
 | ||||||
| WARNING: |  | ||||||
| Project access tokens are treated as [internal users](../../../development/internal_users.md). |  | ||||||
| If an internal user creates a project access token, that token is able to access |  | ||||||
| all projects that have visibility level set to [Internal](../../public_access.md). |  | ||||||
| 
 |  | ||||||
| ## Revoke a project access token | ## Revoke a project access token | ||||||
| 
 | 
 | ||||||
| To revoke a project access token: | To revoke a project access token: | ||||||
|  |  | ||||||
|  | @ -51,6 +51,7 @@ module.exports = (path, options = {}) => { | ||||||
|         experimentalCSSCompile: false, |         experimentalCSSCompile: false, | ||||||
|         compiler: require.resolve('./config/vue3migration/compiler'), |         compiler: require.resolve('./config/vue3migration/compiler'), | ||||||
|         compilerOptions: { |         compilerOptions: { | ||||||
|  |           whitespace: 'preserve', | ||||||
|           compatConfig: { |           compatConfig: { | ||||||
|             MODE: 2, |             MODE: 2, | ||||||
|           }, |           }, | ||||||
|  |  | ||||||
|  | @ -1,69 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module Gitlab |  | ||||||
|   module GithubImport |  | ||||||
|     module Importer |  | ||||||
|       class PullRequestMergedByImporter |  | ||||||
|         # pull_request - An instance of |  | ||||||
|         #                `Gitlab::GithubImport::Representation::PullRequest` |  | ||||||
|         # project - An instance of `Project` |  | ||||||
|         # client - An instance of `Gitlab::GithubImport::Client` |  | ||||||
|         def initialize(pull_request, project, client) |  | ||||||
|           @pull_request = pull_request |  | ||||||
|           @project = project |  | ||||||
|           @client = client |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def execute |  | ||||||
|           user_finder = GithubImport::UserFinder.new(project, client) |  | ||||||
| 
 |  | ||||||
|           gitlab_user_id = user_finder.user_id_for(pull_request.merged_by) |  | ||||||
| 
 |  | ||||||
|           metrics_upsert(gitlab_user_id) |  | ||||||
| 
 |  | ||||||
|           add_note! |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         private |  | ||||||
| 
 |  | ||||||
|         attr_reader :project, :pull_request, :client |  | ||||||
| 
 |  | ||||||
|         def metrics_upsert(gitlab_user_id) |  | ||||||
|           MergeRequest::Metrics.upsert({ |  | ||||||
|             target_project_id: project.id, |  | ||||||
|             merge_request_id: merge_request.id, |  | ||||||
|             merged_by_id: gitlab_user_id, |  | ||||||
|             merged_at: pull_request.merged_at, |  | ||||||
|             created_at: timestamp, |  | ||||||
|             updated_at: timestamp |  | ||||||
|           }, unique_by: :merge_request_id) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def add_note! |  | ||||||
|           merge_request.notes.create!( |  | ||||||
|             importing: true, |  | ||||||
|             note: missing_author_note, |  | ||||||
|             author_id: project.creator_id, |  | ||||||
|             project: project, |  | ||||||
|             created_at: pull_request.merged_at |  | ||||||
|           ) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def merge_request |  | ||||||
|           @merge_request ||= project.merge_requests.find_by_iid(pull_request.iid) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def timestamp |  | ||||||
|           @timestamp ||= Time.new.utc |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def missing_author_note |  | ||||||
|           s_("GitHubImporter|*Merged by: %{author} at %{timestamp}*") % { |  | ||||||
|             author: pull_request.merged_by&.login || 'ghost', |  | ||||||
|             timestamp: pull_request.merged_at |  | ||||||
|           } |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -0,0 +1,59 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Gitlab | ||||||
|  |   module GithubImport | ||||||
|  |     module Importer | ||||||
|  |       module PullRequests | ||||||
|  |         class AllMergedByImporter | ||||||
|  |           include ParallelScheduling | ||||||
|  | 
 | ||||||
|  |           def importer_class | ||||||
|  |             MergedByImporter | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def representation_class | ||||||
|  |             Gitlab::GithubImport::Representation::PullRequest | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def sidekiq_worker_class | ||||||
|  |             Gitlab::GithubImport::PullRequests::ImportMergedByWorker | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def collection_method | ||||||
|  |             :pull_requests_merged_by | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def object_type | ||||||
|  |             :pull_request_merged_by | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def id_for_already_imported_cache(merge_request) | ||||||
|  |             merge_request.id | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def each_object_to_import | ||||||
|  |             merge_requests_to_import.find_each do |merge_request| | ||||||
|  |               Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched) | ||||||
|  | 
 | ||||||
|  |               pull_request = client.pull_request(project.import_source, merge_request.iid) | ||||||
|  |               yield(pull_request) | ||||||
|  | 
 | ||||||
|  |               mark_as_imported(merge_request) | ||||||
|  |             end | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           private | ||||||
|  | 
 | ||||||
|  |           # Returns only the merge requests that still have merged_by to be imported. | ||||||
|  |           def merge_requests_to_import | ||||||
|  |             project.merge_requests.id_not_in(already_imported_objects).with_state(:merged) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def already_imported_objects | ||||||
|  |             Gitlab::Cache::Import::Caching.values_from_set(already_imported_cache_key) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1,71 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Gitlab | ||||||
|  |   module GithubImport | ||||||
|  |     module Importer | ||||||
|  |       module PullRequests | ||||||
|  |         class MergedByImporter | ||||||
|  |           # pull_request - An instance of | ||||||
|  |           #                `Gitlab::GithubImport::Representation::PullRequest` | ||||||
|  |           # project - An instance of `Project` | ||||||
|  |           # client - An instance of `Gitlab::GithubImport::Client` | ||||||
|  |           def initialize(pull_request, project, client) | ||||||
|  |             @pull_request = pull_request | ||||||
|  |             @project = project | ||||||
|  |             @client = client | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def execute | ||||||
|  |             user_finder = GithubImport::UserFinder.new(project, client) | ||||||
|  | 
 | ||||||
|  |             gitlab_user_id = user_finder.user_id_for(pull_request.merged_by) | ||||||
|  | 
 | ||||||
|  |             metrics_upsert(gitlab_user_id) | ||||||
|  | 
 | ||||||
|  |             add_note! | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           private | ||||||
|  | 
 | ||||||
|  |           attr_reader :project, :pull_request, :client | ||||||
|  | 
 | ||||||
|  |           def metrics_upsert(gitlab_user_id) | ||||||
|  |             MergeRequest::Metrics.upsert({ | ||||||
|  |               target_project_id: project.id, | ||||||
|  |               merge_request_id: merge_request.id, | ||||||
|  |               merged_by_id: gitlab_user_id, | ||||||
|  |               merged_at: pull_request.merged_at, | ||||||
|  |               created_at: timestamp, | ||||||
|  |               updated_at: timestamp | ||||||
|  |             }, unique_by: :merge_request_id) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def add_note! | ||||||
|  |             merge_request.notes.create!( | ||||||
|  |               importing: true, | ||||||
|  |               note: missing_author_note, | ||||||
|  |               author_id: project.creator_id, | ||||||
|  |               project: project, | ||||||
|  |               created_at: pull_request.merged_at | ||||||
|  |             ) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def merge_request | ||||||
|  |             @merge_request ||= project.merge_requests.find_by_iid(pull_request.iid) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def timestamp | ||||||
|  |             @timestamp ||= Time.new.utc | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def missing_author_note | ||||||
|  |             format(s_("GitHubImporter|*Merged by: %{author} at %{timestamp}*"), | ||||||
|  |               author: pull_request.merged_by&.login || 'ghost', | ||||||
|  |               timestamp: pull_request.merged_at | ||||||
|  |             ) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -1,57 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| module Gitlab |  | ||||||
|   module GithubImport |  | ||||||
|     module Importer |  | ||||||
|       class PullRequestsMergedByImporter |  | ||||||
|         include ParallelScheduling |  | ||||||
| 
 |  | ||||||
|         def importer_class |  | ||||||
|           PullRequestMergedByImporter |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def representation_class |  | ||||||
|           Gitlab::GithubImport::Representation::PullRequest |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def sidekiq_worker_class |  | ||||||
|           ImportPullRequestMergedByWorker |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def collection_method |  | ||||||
|           :pull_requests_merged_by |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def object_type |  | ||||||
|           :pull_request_merged_by |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def id_for_already_imported_cache(merge_request) |  | ||||||
|           merge_request.id |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def each_object_to_import |  | ||||||
|           merge_requests_to_import.find_each do |merge_request| |  | ||||||
|             Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched) |  | ||||||
| 
 |  | ||||||
|             pull_request = client.pull_request(project.import_source, merge_request.iid) |  | ||||||
|             yield(pull_request) |  | ||||||
| 
 |  | ||||||
|             mark_as_imported(merge_request) |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         private |  | ||||||
| 
 |  | ||||||
|         # Returns only the merge requests that still have merged_by to be imported. |  | ||||||
|         def merge_requests_to_import |  | ||||||
|           project.merge_requests.id_not_in(already_imported_objects).with_state(:merged) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         def already_imported_objects |  | ||||||
|           Gitlab::Cache::Import::Caching.values_from_set(already_imported_cache_key) |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -2375,6 +2375,9 @@ msgstr "" | ||||||
| msgid "AccountValidation|Verification is required to discourage and reduce the abuse on GitLab infrastructure. If you verify with a credit or debit card, %{strong_start}GitLab will not charge your card, it will only be used for validation.%{strong_end} %{learn_more_link}" | msgid "AccountValidation|Verification is required to discourage and reduce the abuse on GitLab infrastructure. If you verify with a credit or debit card, %{strong_start}GitLab will not charge your card, it will only be used for validation.%{strong_end} %{learn_more_link}" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Achievements" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Achievements|%{namespace_full_path} awarded you the %{achievement_name} achievement" | msgid "Achievements|%{namespace_full_path} awarded you the %{achievement_name} achievement" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -649,6 +649,8 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       it 'loads togglable usage ping payload on click', :js do |       it 'loads togglable usage ping payload on click', :js do | ||||||
|  |         allow(Gitlab::Usage::ServicePingReport).to receive(:for).and_return({ uuid: '12345678', hostname: '127.0.0.1' }) | ||||||
|  | 
 | ||||||
|         stub_usage_data_connections |         stub_usage_data_connections | ||||||
|         stub_database_flavor_check |         stub_database_flavor_check | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| /* eslint-disable no-return-assign, no-new, no-underscore-dangle */ | /* eslint-disable no-return-assign, no-new, no-underscore-dangle */ | ||||||
| 
 |  | ||||||
| import $ from 'jquery'; | import $ from 'jquery'; | ||||||
| import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | import htmlStaticLineHighlighter from 'test_fixtures_static/line_highlighter.html'; | ||||||
|  | import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | ||||||
| import LineHighlighter from '~/blob/line_highlighter'; | import LineHighlighter from '~/blob/line_highlighter'; | ||||||
| import * as utils from '~/lib/utils/common_utils'; | import * as utils from '~/lib/utils/common_utils'; | ||||||
| 
 | 
 | ||||||
|  | @ -17,7 +17,7 @@ describe('LineHighlighter', () => { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     loadHTMLFixture('static/line_highlighter.html'); |     setHTMLFixture(htmlStaticLineHighlighter); | ||||||
|     testContext.class = new LineHighlighter(); |     testContext.class = new LineHighlighter(); | ||||||
|     testContext.css = testContext.class.highlightLineClass; |     testContext.css = testContext.class.highlightLineClass; | ||||||
|     return (testContext.spies = { |     return (testContext.spies = { | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ beforeEach(() => { | ||||||
| describe('CompareVersions', () => { | describe('CompareVersions', () => { | ||||||
|   let wrapper; |   let wrapper; | ||||||
|   let store; |   let store; | ||||||
|  |   let dispatchMock; | ||||||
|   const targetBranchName = 'tmp-wine-dev'; |   const targetBranchName = 'tmp-wine-dev'; | ||||||
|   const { commit } = getDiffWithCommit; |   const { commit } = getDiffWithCommit; | ||||||
| 
 | 
 | ||||||
|  | @ -29,6 +30,8 @@ describe('CompareVersions', () => { | ||||||
|       store.state.diffs.commit = { ...store.state.diffs.commit, ...commitArgs }; |       store.state.diffs.commit = { ...store.state.diffs.commit, ...commitArgs }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     dispatchMock = jest.spyOn(store, 'dispatch'); | ||||||
|  | 
 | ||||||
|     wrapper = mount(CompareVersionsComponent, { |     wrapper = mount(CompareVersionsComponent, { | ||||||
|       store, |       store, | ||||||
|       propsData: { |       propsData: { | ||||||
|  | @ -146,7 +149,7 @@ describe('CompareVersions', () => { | ||||||
| 
 | 
 | ||||||
|     it('renders short commit ID', () => { |     it('renders short commit ID', () => { | ||||||
|       expect(wrapper.text()).toContain('Viewing commit'); |       expect(wrapper.text()).toContain('Viewing commit'); | ||||||
|       expect(wrapper.text()).toContain(wrapper.vm.commit.short_id); |       expect(wrapper.text()).toContain(commit.short_id); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  | @ -204,10 +207,6 @@ describe('CompareVersions', () => { | ||||||
|         setWindowLocation(`?commit_id=${mrCommit.id}`); |         setWindowLocation(`?commit_id=${mrCommit.id}`); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       beforeEach(() => { |  | ||||||
|         jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {}); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       it('uses the correct href', () => { |       it('uses the correct href', () => { | ||||||
|         const link = getPrevCommitNavElement(); |         const link = getPrevCommitNavElement(); | ||||||
| 
 | 
 | ||||||
|  | @ -219,7 +218,7 @@ describe('CompareVersions', () => { | ||||||
| 
 | 
 | ||||||
|         link.trigger('click'); |         link.trigger('click'); | ||||||
|         await nextTick(); |         await nextTick(); | ||||||
|         expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({ |         expect(dispatchMock).toHaveBeenCalledWith('diffs/moveToNeighboringCommit', { | ||||||
|           direction: 'previous', |           direction: 'previous', | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|  | @ -238,10 +237,6 @@ describe('CompareVersions', () => { | ||||||
|         setWindowLocation(`?commit_id=${mrCommit.id}`); |         setWindowLocation(`?commit_id=${mrCommit.id}`); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       beforeEach(() => { |  | ||||||
|         jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {}); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       it('uses the correct href', () => { |       it('uses the correct href', () => { | ||||||
|         const link = getNextCommitNavElement(); |         const link = getNextCommitNavElement(); | ||||||
| 
 | 
 | ||||||
|  | @ -253,7 +248,9 @@ describe('CompareVersions', () => { | ||||||
| 
 | 
 | ||||||
|         link.trigger('click'); |         link.trigger('click'); | ||||||
|         await nextTick(); |         await nextTick(); | ||||||
|         expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({ direction: 'next' }); |         expect(dispatchMock).toHaveBeenCalledWith('diffs/moveToNeighboringCommit', { | ||||||
|  |           direction: 'next', | ||||||
|  |         }); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       it('renders a disabled button when there is no next commit', () => { |       it('renders a disabled button when there is no next commit', () => { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html'; |  | ||||||
| import MockAdapter from 'axios-mock-adapter'; | import MockAdapter from 'axios-mock-adapter'; | ||||||
|  | import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html'; | ||||||
| import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
| import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html'; |  | ||||||
| import MockAdapter from 'axios-mock-adapter'; | import MockAdapter from 'axios-mock-adapter'; | ||||||
|  | import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html'; | ||||||
| import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
|  |  | ||||||
|  | @ -10,8 +10,6 @@ describe('MenuSection component', () => { | ||||||
|   const findButton = () => wrapper.find('button'); |   const findButton = () => wrapper.find('button'); | ||||||
|   const findCollapse = () => wrapper.getComponent(GlCollapse); |   const findCollapse = () => wrapper.getComponent(GlCollapse); | ||||||
|   const findNavItems = () => wrapper.findAllComponents(NavItem); |   const findNavItems = () => wrapper.findAllComponents(NavItem); | ||||||
|   const findSectionTitle = () => wrapper.findByTestId('section-title'); |  | ||||||
| 
 |  | ||||||
|   const createWrapper = (item, otherProps) => { |   const createWrapper = (item, otherProps) => { | ||||||
|     wrapper = shallowMountExtended(MenuSection, { |     wrapper = shallowMountExtended(MenuSection, { | ||||||
|       propsData: { item, ...otherProps }, |       propsData: { item, ...otherProps }, | ||||||
|  | @ -70,17 +68,6 @@ describe('MenuSection component', () => { | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   describe('`collectionStyle` prop', () => { |  | ||||||
|     const newClasses = 'gl-font-sm gl-font-weight-semibold'.split(' '); |  | ||||||
| 
 |  | ||||||
|     it('applies new classes when using new styles', () => { |  | ||||||
|       createWrapper({ title: 'Asdf' }, { collectionStyle: true }); |  | ||||||
|       const classes = findSectionTitle().classes(); |  | ||||||
| 
 |  | ||||||
|       newClasses.forEach((newClass) => expect(classes).toContain(newClass)); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('`separated` prop', () => { |   describe('`separated` prop', () => { | ||||||
|     describe('by default (false)', () => { |     describe('by default (false)', () => { | ||||||
|       it('does not render a separator', () => { |       it('does not render a separator', () => { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| import { mountExtended } from 'helpers/vue_test_utils_helper'; | import { mountExtended } from 'helpers/vue_test_utils_helper'; | ||||||
| import SidebarMenu from '~/super_sidebar/components/sidebar_menu.vue'; | import SidebarMenu from '~/super_sidebar/components/sidebar_menu.vue'; | ||||||
| import MenuSection from '~/super_sidebar/components/menu_section.vue'; |  | ||||||
| import { PANELS_WITH_PINS } from '~/super_sidebar/constants'; | import { PANELS_WITH_PINS } from '~/super_sidebar/constants'; | ||||||
| import { sidebarData } from '../mock_data'; | import { sidebarData } from '../mock_data'; | ||||||
| 
 | 
 | ||||||
|  | @ -102,10 +101,6 @@ describe('SidebarMenu component', () => { | ||||||
|             'Also with subitems', |             'Also with subitems', | ||||||
|           ]); |           ]); | ||||||
|         }); |         }); | ||||||
| 
 |  | ||||||
|         it('passes `supportsPin` to menu sections', () => { |  | ||||||
|           expect(wrapper.findAllComponents(MenuSection).at(1).props('collectionStyle')).toBe(true); |  | ||||||
|         }); |  | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       describe('when the sidebar does not support pins', () => { |       describe('when the sidebar does not support pins', () => { | ||||||
|  | @ -120,10 +115,6 @@ describe('SidebarMenu component', () => { | ||||||
|         it('keeps all items as non-static', () => { |         it('keeps all items as non-static', () => { | ||||||
|           expect(wrapper.vm.nonStaticItems).toEqual(menuItems); |           expect(wrapper.vm.nonStaticItems).toEqual(menuItems); | ||||||
|         }); |         }); | ||||||
| 
 |  | ||||||
|         it('passes `supportsPin` to menu sections', () => { |  | ||||||
|           expect(wrapper.findAllComponents(MenuSection).at(1).props('collectionStyle')).toBe(false); |  | ||||||
|         }); |  | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | import { mount } from '@vue/test-utils'; | ||||||
|  | 
 | ||||||
|  | import SlotsWithSameName from './components/slots_with_same_name.vue'; | ||||||
|  | import VOnceInsideVIf from './components/v_once_inside_v_if.vue'; | ||||||
|  | import KeyInsideTemplate from './components/key_inside_template.vue'; | ||||||
|  | import CommentsOnRootLevel from './components/comments_on_root_level.vue'; | ||||||
|  | import SlotWithComment from './components/slot_with_comment.vue'; | ||||||
|  | import DefaultSlotWithComment from './components/default_slot_with_comment.vue'; | ||||||
|  | 
 | ||||||
|  | describe('Vue.js 3 compiler edge cases', () => { | ||||||
|  |   it('workarounds issue #6063 when same slot is used with whitespace preserve', () => { | ||||||
|  |     expect(() => mount(SlotsWithSameName)).not.toThrow(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('workarounds issue #7725 when v-once is used inside v-if', () => { | ||||||
|  |     expect(() => mount(VOnceInsideVIf)).not.toThrow(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('renders vue.js 2 component when key is inside template', () => { | ||||||
|  |     const wrapper = mount(KeyInsideTemplate); | ||||||
|  |     expect(wrapper.text()).toBe('12345'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('passes attributes to component with trailing comments on root level', () => { | ||||||
|  |     const wrapper = mount(CommentsOnRootLevel, { propsData: { 'data-testid': 'test' } }); | ||||||
|  |     expect(wrapper.html()).toBe('<div data-testid="test"></div>'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('treats empty slots with comments as empty', () => { | ||||||
|  |     const wrapper = mount(SlotWithComment); | ||||||
|  |     expect(wrapper.html()).toBe('<div>Simple</div>'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('treats empty default slot with comments as empty', () => { | ||||||
|  |     const wrapper = mount(DefaultSlotWithComment); | ||||||
|  |     expect(wrapper.html()).toBe('<div>Simple</div>'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | <template> | ||||||
|  |   <!-- root level comment --> | ||||||
|  |   <div><slot></slot></div> | ||||||
|  |   <!-- root level comment --> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | <script> | ||||||
|  | import Simple from './simple.vue'; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   components: { | ||||||
|  |     Simple, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <simple> | ||||||
|  |     <!-- slot comment typical for gitlab-ui, for example --> | ||||||
|  |     <!-- slot comment typical for gitlab-ui, for example --> | ||||||
|  |     <slot></slot> | ||||||
|  |     <!-- slot comment typical for gitlab-ui, for example --> | ||||||
|  |     <!-- slot comment typical for gitlab-ui, for example --> | ||||||
|  |   </simple> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <template v-for="count in 5" | ||||||
|  |       ><span :key="count">{{ count }}</span></template | ||||||
|  |     > | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   name: 'Simple', | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <slot>{{ $options.name }}</slot> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | <script> | ||||||
|  | import Simple from './simple.vue'; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   components: { | ||||||
|  |     Simple, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <simple> | ||||||
|  |     <template #default> | ||||||
|  |       <!-- slot comment typical for gitlab-ui, for example --> | ||||||
|  |       <!-- slot comment typical for gitlab-ui, for example --> | ||||||
|  |       <slot></slot> | ||||||
|  |       <!-- slot comment typical for gitlab-ui, for example --> | ||||||
|  |       <!-- slot comment typical for gitlab-ui, for example --> | ||||||
|  |     </template> | ||||||
|  |   </simple> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | <script> | ||||||
|  | import Simple from './simple.vue'; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   name: 'SlotsWithSameName', | ||||||
|  |   components: { Simple }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <simple> | ||||||
|  |     <template v-if="true" #default>{{ $options.name }}</template> | ||||||
|  |     <template v-else #default>{{ $options.name }}</template> | ||||||
|  |   </simple> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   name: 'VOnceInsideVIf', | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <template v-if="true"> | ||||||
|  |       <div v-once>{{ $options.name }}</div> | ||||||
|  |     </template> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | @ -63,7 +63,7 @@ if (global.document) { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   let compatH; |   let compatH; | ||||||
|   Vue.config.compilerOptions.whitespace = 'condense'; |   Vue.config.compilerOptions.whitespace = 'preserve'; | ||||||
|   Vue.createApp({ |   Vue.createApp({ | ||||||
|     compatConfig: { |     compatConfig: { | ||||||
|       MODE: 3, |       MODE: 3, | ||||||
|  |  | ||||||
|  | @ -1,74 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| require 'spec_helper' |  | ||||||
| require_relative '../../config/initializers/doorkeeper_openid_connect_patch' |  | ||||||
| 
 |  | ||||||
| RSpec.describe 'doorkeeper_openid_connect_patch', feature_category: :integrations do |  | ||||||
|   describe '.signing_key' do |  | ||||||
|     let(:config) { Doorkeeper::OpenidConnect::Config.new } |  | ||||||
| 
 |  | ||||||
|     before do |  | ||||||
|       allow(config).to receive(:signing_key).and_return(key) |  | ||||||
|       allow(config).to receive(:signing_algorithm).and_return(algorithm) |  | ||||||
|       allow(Doorkeeper::OpenidConnect).to receive(:configuration).and_return(config) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'with RS256 algorithm' do |  | ||||||
|       let(:algorithm) { :RS256 } |  | ||||||
|       # Taken from https://github.com/doorkeeper-gem/doorkeeper-openid_connect/blob/01903c81a2b6237a3bf576ed45864f69ef20184e/spec/dummy/config/initializers/doorkeeper_openid_connect.rb#L6-L34 |  | ||||||
|       let(:key) do |  | ||||||
|         <<~KEY |  | ||||||
|           -----BEGIN RSA PRIVATE KEY----- |  | ||||||
|           MIIEpgIBAAKCAQEAsjdnSA6UWUQQHf6BLIkIEUhMRNBJC1NN/pFt1EJmEiI88GS0 |  | ||||||
|           ceROO5B5Ooo9Y3QOWJ/n+u1uwTHBz0HCTN4wgArWd1TcqB5GQzQRP4eYnWyPfi4C |  | ||||||
|           feqAHzQp+v4VwbcK0LW4FqtW5D0dtrFtI281FDxLhARzkhU2y7fuYhL8fVw5rUhE |  | ||||||
|           8uwvHRZ5CEZyxf7BSHxIvOZAAymhuzNLATt2DGkDInU1BmF75tEtBJAVLzWG/j4L |  | ||||||
|           PZh1EpSdfezqaXQlcy9PJi916UzTl0P7Yy+ulOdUsMlB6yo8qKTY1+AbZ5jzneHb |  | ||||||
|           GDU/O8QjYvii1WDmJ60t0jXicmOkGrOhruOptwIDAQABAoIBAQChYNwMeu9IugJi |  | ||||||
|           NsEf4+JDTBWMRpOuRrwcpfIvQAUPrKNEB90COPvCoju0j9OxCDmpdPtq1K/zD6xx |  | ||||||
|           khlw485FVAsKufSp4+g6GJ75yT6gZtq1JtKo1L06BFFzb7uh069eeP7+wB6JxPHw |  | ||||||
|           KlAqwxvsfADhxeolQUKCTMb3Vjv/Aw2cO/nn6RAOeftw2aDmFy8Xl+oTUtSxyib0 |  | ||||||
|           YCdU9cK8MxsxDdmowwHp04xRTm/wfG5hLEn7HMz1PP86iP9BiFsCqTId9dxEUTS1 |  | ||||||
|           K+VAt9FbxRAq5JlBocxUMHNxLigb94Ca2FOMR7F6l/tronLfHD801YoObF0fN9qW |  | ||||||
|           Cgw4aTO5AoGBAOR79hiZVM7/l1cBid7hKSeMWKUZ/nrwJsVfNpu1H9xt9uDu+79U |  | ||||||
|           mcGfM7pm7L2qCNGg7eeWBHq2CVg/XQacRNtcTlomFrw4tDXUkFN1hE56t1iaTs9m |  | ||||||
|           dN9IDr6jFgf6UaoOxxoPT9Q1ZtO46l043Nzrkoz8cBEBaBY20bUDwCYjAoGBAMet |  | ||||||
|           tt1ImGF1cx153KbOfjl8v54VYUVkmRNZTa1E821nL/EMpoONSqJmRVsX7grLyPL1 |  | ||||||
|           QyZe245NOvn63YM0ng0rn2osoKsMVJwYBEYjHL61iF6dPtW5p8FIs7auRnC3NrG0 |  | ||||||
|           XxHATZ4xhHD0iIn14iXh0XIhUVk+nGktHU1gbmVdAoGBANniwKdqqS6RHKBTDkgm |  | ||||||
|           Dhnxw6MGa+CO3VpA1xGboxuRHeoY3KfzpIC5MhojBsZDvQ8zWUwMio7+w2CNZEfm |  | ||||||
|           g99wYiOjyPCLXocrAssj+Rzh97AdzuQHf5Jh4/W2Dk9jTbdPSl02ltj2Z+2lnJFz |  | ||||||
|           pWNjnqimHrSI09rDQi5NulJjAoGBAImquujVpDmNQFCSNA7NTzlTSMk09FtjgCZW |  | ||||||
|           67cKUsqa2fLXRfZs84gD+s1TMks/NMxNTH6n57e0h3TSAOb04AM0kDQjkKJdXfhA |  | ||||||
|           lrHEg4z4m4yf3TJ9Tat09HJ+tRIBPzRFp0YVz23Btg4qifiUDdcQWdbWIb/l6vCY |  | ||||||
|           qhsu4O4BAoGBANbceYSDYRdT7a5QjJGibkC90Z3vFe4rDTBgZWg7xG0cpSU4JNg7 |  | ||||||
|           SFR3PjWQyCg7aGGXiooCM38YQruACTj0IFub24MFRA4ZTXvrACvpsVokJlQiG0Z4 |  | ||||||
|           tuQKYki41JvYqPobcq/rLE/AM7PKJftW35nqFuj0MrsUwPacaVwKBf5J |  | ||||||
|           -----END RSA PRIVATE KEY----- |  | ||||||
|         KEY |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it 'returns the private key as JWK instance' do |  | ||||||
|         expect(Doorkeeper::OpenidConnect.signing_key).to be_a ::JWT::JWK::KeyBase |  | ||||||
|         expect(Doorkeeper::OpenidConnect.signing_key.kid).to eq 'IqYwZo2cE6hsyhs48cU8QHH4GanKIx0S4Dc99kgTIMA' |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it 'matches json-jwt implementation' do |  | ||||||
|         json_jwt_key = OpenSSL::PKey::RSA.new(key).public_key.to_jwk.slice(:kty, :kid, :e, :n) |  | ||||||
|         expect(Doorkeeper::OpenidConnect.signing_key.export.sort.to_json).to eq(json_jwt_key.sort.to_json) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'with HS512 algorithm' do |  | ||||||
|       let(:algorithm) { :HS512 } |  | ||||||
|       let(:key) { 'the_greatest_secret_key' } |  | ||||||
| 
 |  | ||||||
|       it 'returns the HMAC public key parameters' do |  | ||||||
|         expect(Doorkeeper::OpenidConnect.signing_key_normalized).to eq( |  | ||||||
|           kty: 'oct', |  | ||||||
|           kid: 'lyAW7LdxryFWQtLdgxZpOrI87APHrzJKgWLT0BkWVog' |  | ||||||
|         ) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| require 'spec_helper' | require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do | RSpec.describe Gitlab::GithubImport::Importer::PullRequests::AllMergedByImporter, feature_category: :importers do | ||||||
|   let(:client) { double } |   let(:client) { double } | ||||||
| 
 | 
 | ||||||
|   let_it_be(:project) { create(:project, import_source: 'http://somegithub.com') } |   let_it_be(:project) { create(:project, import_source: 'http://somegithub.com') } | ||||||
|  | @ -16,7 +16,11 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#importer_class' do |   describe '#importer_class' do | ||||||
|     it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequestMergedByImporter) } |     it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequests::MergedByImporter) } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe '#sidekiq_worker_class' do | ||||||
|  |     it { expect(subject.sidekiq_worker_class).to eq(Gitlab::GithubImport::PullRequests::ImportMergedByWorker) } | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#collection_method' do |   describe '#collection_method' do | ||||||
|  | @ -24,7 +28,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#id_for_already_imported_cache' do |   describe '#id_for_already_imported_cache' do | ||||||
|     it { expect(subject.id_for_already_imported_cache(double(id: 1))).to eq(1) } |     it { expect(subject.id_for_already_imported_cache(instance_double(MergeRequest, id: 1))).to eq(1) } | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#each_object_to_import', :clean_gitlab_redis_cache do |   describe '#each_object_to_import', :clean_gitlab_redis_cache do | ||||||
|  | @ -44,7 +48,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do | ||||||
|       expect { |b| subject.each_object_to_import(&b) } |       expect { |b| subject.each_object_to_import(&b) } | ||||||
|         .to yield_with_args(pull_request) |         .to yield_with_args(pull_request) | ||||||
| 
 | 
 | ||||||
|       subject.each_object_to_import {} |       subject.each_object_to_import | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'skips cached merge requests' do |     it 'skips cached merge requests' do | ||||||
|  | @ -55,7 +59,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do | ||||||
| 
 | 
 | ||||||
|       expect(client).not_to receive(:pull_request) |       expect(client).not_to receive(:pull_request) | ||||||
| 
 | 
 | ||||||
|       subject.each_object_to_import {} |       subject.each_object_to_import | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  | @ -2,12 +2,16 @@ | ||||||
| 
 | 
 | ||||||
| require 'spec_helper' | require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :clean_gitlab_redis_cache do | RSpec.describe Gitlab::GithubImport::Importer::PullRequests::MergedByImporter, | ||||||
|  |   :clean_gitlab_redis_cache, feature_category: :importers do | ||||||
|   let_it_be(:merge_request) { create(:merged_merge_request) } |   let_it_be(:merge_request) { create(:merged_merge_request) } | ||||||
| 
 | 
 | ||||||
|   let(:project) { merge_request.project } |   let(:project) { merge_request.project } | ||||||
|   let(:merged_at) { Time.new(2017, 1, 1, 12, 00).utc } |   let(:merged_at) { Time.utc(2017, 1, 1, 12) } | ||||||
|   let(:client_double) { double(user: { id: 999, login: 'merger', email: 'merger@email.com' } ) } |   let(:client_double) do | ||||||
|  |     instance_double(Gitlab::GithubImport::Client, user: { id: 999, login: 'merger', email: 'merger@email.com' }) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   let(:merger_user) { { id: 999, login: 'merger' } } |   let(:merger_user) { { id: 999, login: 'merger' } } | ||||||
| 
 | 
 | ||||||
|   let(:pull_request) do |   let(:pull_request) do | ||||||
|  | @ -25,7 +29,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :cle | ||||||
|   shared_examples 'adds a note referencing the merger user' do |   shared_examples 'adds a note referencing the merger user' do | ||||||
|     it 'adds a note referencing the merger user' do |     it 'adds a note referencing the merger user' do | ||||||
|       expect { subject.execute } |       expect { subject.execute } | ||||||
|         .to change(Note, :count).by(1) |         .to change { Note.count }.by(1) | ||||||
|         .and not_change(merge_request, :updated_at) |         .and not_change(merge_request, :updated_at) | ||||||
| 
 | 
 | ||||||
|       metrics = merge_request.metrics.reload |       metrics = merge_request.metrics.reload | ||||||
|  | @ -68,7 +72,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :cle | ||||||
| 
 | 
 | ||||||
|     it 'adds a note referencing the merger user' do |     it 'adds a note referencing the merger user' do | ||||||
|       expect { subject.execute } |       expect { subject.execute } | ||||||
|         .to change(Note, :count).by(1) |         .to change { Note.count }.by(1) | ||||||
|         .and not_change(merge_request, :updated_at) |         .and not_change(merge_request, :updated_at) | ||||||
| 
 | 
 | ||||||
|       metrics = merge_request.metrics.reload |       metrics = merge_request.metrics.reload | ||||||
|  | @ -0,0 +1,78 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe Groups::AchievementsController, feature_category: :user_profile do | ||||||
|  |   let_it_be(:user) { create(:user) } | ||||||
|  | 
 | ||||||
|  |   shared_examples 'response with 404 status' do | ||||||
|  |     it 'returns 404' do | ||||||
|  |       subject | ||||||
|  | 
 | ||||||
|  |       expect(response).to have_gitlab_http_status(:not_found) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   shared_examples 'ok response with index template' do | ||||||
|  |     it 'renders the index template' do | ||||||
|  |       subject | ||||||
|  | 
 | ||||||
|  |       expect(response).to have_gitlab_http_status(:ok) | ||||||
|  |       expect(response).to render_template(:index) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   shared_examples 'ok response with index template if authorized' do | ||||||
|  |     context 'with a private group' do | ||||||
|  |       let(:group) { create(:group, :private) } | ||||||
|  | 
 | ||||||
|  |       context 'with authorized user' do | ||||||
|  |         before do | ||||||
|  |           group.add_guest(user) | ||||||
|  |           sign_in(user) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         it_behaves_like 'ok response with index template' | ||||||
|  | 
 | ||||||
|  |         context 'when achievements ff is disabled' do | ||||||
|  |           before do | ||||||
|  |             stub_feature_flags(achievements: false) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           it_behaves_like 'response with 404 status' | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'with unauthorized user' do | ||||||
|  |         before do | ||||||
|  |           sign_in(user) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         it_behaves_like 'response with 404 status' | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'with anonymous user' do | ||||||
|  |         it 'redirects to sign_in page' do | ||||||
|  |           subject | ||||||
|  | 
 | ||||||
|  |           expect(response).to have_gitlab_http_status(:found) | ||||||
|  |           expect(response).to redirect_to(new_user_session_path) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'with a public group' do | ||||||
|  |       let(:group) { create(:group, :public) } | ||||||
|  | 
 | ||||||
|  |       context 'with anonymous user' do | ||||||
|  |         it_behaves_like 'ok response with index template' | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe 'GET #index' do | ||||||
|  |     subject { get group_achievements_path(group) } | ||||||
|  | 
 | ||||||
|  |     it_behaves_like 'ok response with index template if authorized' | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -62,7 +62,7 @@ RSpec.describe Import::GithubFailureEntity, feature_category: :importers do | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it_behaves_like 'import failure entity' do |     it_behaves_like 'import failure entity' do | ||||||
|       let(:source) { 'Gitlab::GithubImport::Importer::PullRequestMergedByImporter' } |       let(:source) { 'Gitlab::GithubImport::Importer::PullRequests::MergedByImporter' } | ||||||
|       let(:title) { 'Pull request 2 merger' } |       let(:title) { 'Pull request 2 merger' } | ||||||
|       let(:provider_url) { 'https://github.com/example/repo/pull/2' } |       let(:provider_url) { 'https://github.com/example/repo/pull/2' } | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -277,6 +277,7 @@ RSpec.describe 'Every Sidekiq worker', feature_category: :shared do | ||||||
|         'Gitlab::GithubImport::ImportPullRequestReviewWorker' => 5, |         'Gitlab::GithubImport::ImportPullRequestReviewWorker' => 5, | ||||||
|         'Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker' => 5, |         'Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker' => 5, | ||||||
|         'Gitlab::GithubImport::PullRequests::ImportReviewWorker' => 5, |         'Gitlab::GithubImport::PullRequests::ImportReviewWorker' => 5, | ||||||
|  |         'Gitlab::GithubImport::PullRequests::ImportMergedByWorker' => 5, | ||||||
|         'Gitlab::GithubImport::ImportPullRequestWorker' => 5, |         'Gitlab::GithubImport::ImportPullRequestWorker' => 5, | ||||||
|         'Gitlab::GithubImport::RefreshImportJidWorker' => 5, |         'Gitlab::GithubImport::RefreshImportJidWorker' => 5, | ||||||
|         'Gitlab::GithubImport::Stage::FinishImportWorker' => 5, |         'Gitlab::GithubImport::Stage::FinishImportWorker' => 5, | ||||||
|  |  | ||||||
|  | @ -10,6 +10,6 @@ RSpec.describe Gitlab::GithubImport::ImportPullRequestMergedByWorker, feature_ca | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#importer_class' do |   describe '#importer_class' do | ||||||
|     it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequestMergedByImporter) } |     it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequests::MergedByImporter) } | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe Gitlab::GithubImport::PullRequests::ImportMergedByWorker, feature_category: :importers do | ||||||
|  |   it { is_expected.to include_module(Gitlab::GithubImport::ObjectImporter) } | ||||||
|  | 
 | ||||||
|  |   describe '#representation_class' do | ||||||
|  |     it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::PullRequest) } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe '#importer_class' do | ||||||
|  |     it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequests::MergedByImporter) } | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe '#object_type' do | ||||||
|  |     it { expect(subject.object_type).to eq(:pull_request_merged_by) } | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -13,7 +13,7 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportPullRequestsMergedByWorker, fe | ||||||
|       client = double(:client) |       client = double(:client) | ||||||
|       waiter = Gitlab::JobWaiter.new(2, '123') |       waiter = Gitlab::JobWaiter.new(2, '123') | ||||||
| 
 | 
 | ||||||
|       expect(Gitlab::GithubImport::Importer::PullRequestsMergedByImporter) |       expect(Gitlab::GithubImport::Importer::PullRequests::AllMergedByImporter) | ||||||
|         .to receive(:new) |         .to receive(:new) | ||||||
|         .with(project, client) |         .with(project, client) | ||||||
|         .and_return(importer) |         .and_return(importer) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue