diff options
67 files changed, 820 insertions, 415 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 45a7924fb1e..5f0b59af9d7 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -1463,14 +1463,15 @@ .qa:rules:package-and-test-sidebar: rules: - - !reference [".qa:rules:package-and-test-common", rules] - - <<: *if-dot-com-gitlab-org-schedule + - <<: *if-merge-request + changes: *code-patterns + when: manual + allow_failure: true + - <<: *if-default-branch-schedule-nightly allow_failure: true variables: - SKIP_REPORT_IN_ISSUES: "true" - PROCESS_TEST_RESULTS: "false" - KNAPSACK_GENERATE_REPORT: "false" - UPDATE_QA_CACHE: "false" + SKIP_REPORT_IN_ISSUES: "false" + PROCESS_TEST_RESULTS: "true" QA_SAVE_TEST_METRICS: "true" QA_EXPORT_TEST_METRICS: "false" @@ -46,7 +46,7 @@ gem 'devise', '~> 4.8.1' gem 'devise-pbkdf2-encryptable', '~> 0.0.0', path: 'vendor/gems/devise-pbkdf2-encryptable' gem 'bcrypt', '~> 3.1', '>= 3.1.14' 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 'ruby-saml', '~> 1.13.0' gem 'omniauth', '~> 2.1.0' diff --git a/Gemfile.checksum b/Gemfile.checksum index ee74a5c948e..4b24bc11b39 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -123,7 +123,7 @@ {"name":"docile","version":"1.4.0","platform":"ruby","checksum":"5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3"}, {"name":"domain_name","version":"0.5.20190701","platform":"ruby","checksum":"000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851"}, {"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":"dry-configurable","version":"0.12.0","platform":"ruby","checksum":"87a9579a04dfbae73e401d694282800d64bbdb8631cb3e987bfb79b673df7c67"}, {"name":"dry-container","version":"0.7.2","platform":"ruby","checksum":"a071824ba3451048b23500210f96a2b9facd6e46ac687f65e49c75d18786f6da"}, diff --git a/Gemfile.lock b/Gemfile.lock index b85499d79ec..199c806a854 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -393,7 +393,7 @@ GEM unf (>= 0.0.5, < 1.0.0) doorkeeper (5.6.6) railties (>= 5) - doorkeeper-openid_connect (1.8.5) + doorkeeper-openid_connect (1.8.6) doorkeeper (>= 5.5, < 5.7) jwt (>= 2.5) dotenv (2.7.6) @@ -1714,7 +1714,7 @@ DEPENDENCIES diffy (~> 3.4) discordrb-webhooks (~> 3.4) 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) ed25519 (~> 1.3.0) elasticsearch-api (= 7.13.3) diff --git a/app/assets/javascripts/achievements/components/achievements_app.vue b/app/assets/javascripts/achievements/components/achievements_app.vue new file mode 100644 index 00000000000..5f40231856f --- /dev/null +++ b/app/assets/javascripts/achievements/components/achievements_app.vue @@ -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> diff --git a/app/assets/javascripts/achievements/constants.js b/app/assets/javascripts/achievements/constants.js new file mode 100644 index 00000000000..82a56588c96 --- /dev/null +++ b/app/assets/javascripts/achievements/constants.js @@ -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', +}; diff --git a/app/assets/javascripts/achievements/routes.js b/app/assets/javascripts/achievements/routes.js new file mode 100644 index 00000000000..12aa17d73b6 --- /dev/null +++ b/app/assets/javascripts/achievements/routes.js @@ -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', + }, +]; diff --git a/app/assets/javascripts/pages/groups/achievements/index.js b/app/assets/javascripts/pages/groups/achievements/index.js new file mode 100644 index 00000000000..d964b0feb96 --- /dev/null +++ b/app/assets/javascripts/pages/groups/achievements/index.js @@ -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(); diff --git a/app/assets/javascripts/super_sidebar/components/menu_section.vue b/app/assets/javascripts/super_sidebar/components/menu_section.vue index b57a4e3013c..5de6e04d827 100644 --- a/app/assets/javascripts/super_sidebar/components/menu_section.vue +++ b/app/assets/javascripts/super_sidebar/components/menu_section.vue @@ -30,11 +30,6 @@ export default { required: false, default: 'div', }, - collectionStyle: { - type: Boolean, - required: false, - default: false, - }, }, data() { return { @@ -94,13 +89,7 @@ export default { </slot> </span> - <span - class="gl-pr-3 gl-truncate-end gl-text-gray-900" - :class="{ - 'gl-font-sm gl-font-weight-semibold': collectionStyle, - }" - data-testid="section-title" - > + <span class="gl-pr-3 gl-text-gray-900 gl-truncate-end"> {{ item.title }} </span> diff --git a/app/assets/javascripts/super_sidebar/components/pinned_section.vue b/app/assets/javascripts/super_sidebar/components/pinned_section.vue index 93d9cf71a18..4fc86e41ef2 100644 --- a/app/assets/javascripts/super_sidebar/components/pinned_section.vue +++ b/app/assets/javascripts/super_sidebar/components/pinned_section.vue @@ -70,7 +70,6 @@ export default { :item="sectionItem" :expanded="expanded" :separated="true" - collection-style @collapse-toggle="expanded = !expanded" > <draggable diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue index 055886621f1..12abd727ef0 100644 --- a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue +++ b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue @@ -150,7 +150,6 @@ export default { v-for="item in nonStaticItems" :key="item.id" :item="item" - :collection-style="supportsPins" tag="li" @pin-add="createPin" @pin-remove="destroyPin" diff --git a/app/controllers/groups/achievements_controller.rb b/app/controllers/groups/achievements_controller.rb new file mode 100644 index 00000000000..52d63761819 --- /dev/null +++ b/app/controllers/groups/achievements_controller.rb @@ -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 diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3c4120d899e..ad3b79b604c 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -613,8 +613,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo Feature.enabled?(:summarize_my_code_review, current_user) && namespace.group_namespace? && namespace.licensed_feature_available?(:summarize_my_mr_code_review) && - namespace.experiment_features_enabled && - namespace.third_party_ai_features_enabled && + Gitlab::Llm::StageCheck.available?(namespace, :summarize_my_mr_code_review) && merge_request.send_to_ai? end end diff --git a/app/views/groups/achievements/index.html.haml b/app/views/groups/achievements/index.html.haml new file mode 100644 index 00000000000..d93448b430a --- /dev/null +++ b/app/views/groups/achievements/index.html.haml @@ -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] + }) } } diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 1149f64314e..50b301b2fc3 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1272,6 +1272,15 @@ :weight: 1 :idempotent: false :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 :worker_name: Gitlab::GithubImport::PullRequests::ImportReviewWorker :feature_category: :importers diff --git a/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb b/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb index ab0cb81249b..94472fdf6db 100644 --- a/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb +++ b/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# TODO: remove in 16.1 milestone +# https://gitlab.com/gitlab-org/gitlab/-/issues/409706 module Gitlab module GithubImport class ImportPullRequestMergedByWorker # rubocop:disable Scalability/IdempotentWorker @@ -12,7 +14,7 @@ module Gitlab end def importer_class - Importer::PullRequestMergedByImporter + Importer::PullRequests::MergedByImporter end def object_type diff --git a/app/workers/gitlab/github_import/import_release_attachments_worker.rb b/app/workers/gitlab/github_import/import_release_attachments_worker.rb index bf901f2f7b8..0d3831789bf 100644 --- a/app/workers/gitlab/github_import/import_release_attachments_worker.rb +++ b/app/workers/gitlab/github_import/import_release_attachments_worker.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# TODO: remove in 16.X milestone -# https://gitlab.com/gitlab-org/gitlab/-/issues/377059 +# TODO: remove in 16.1 milestone +# https://gitlab.com/gitlab-org/gitlab/-/issues/409706 module Gitlab module GithubImport class ImportReleaseAttachmentsWorker # rubocop:disable Scalability/IdempotentWorker diff --git a/app/workers/gitlab/github_import/pull_requests/import_merged_by_worker.rb b/app/workers/gitlab/github_import/pull_requests/import_merged_by_worker.rb new file mode 100644 index 00000000000..2c9b2cdffac --- /dev/null +++ b/app/workers/gitlab/github_import/pull_requests/import_merged_by_worker.rb @@ -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 diff --git a/app/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker.rb b/app/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker.rb index 9b123b5776a..329bf8f84b1 100644 --- a/app/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker.rb +++ b/app/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker.rb @@ -15,7 +15,7 @@ module Gitlab # client - An instance of Gitlab::GithubImport::Client. # project - An instance of Project. def import(client, project) - waiter = Importer::PullRequestsMergedByImporter + waiter = Importer::PullRequests::AllMergedByImporter .new(project, client) .execute diff --git a/config/feature_flags/development/namespace_storage_limit_bypass_date_check.yml b/config/feature_flags/development/namespace_storage_limit_bypass_date_check.yml deleted file mode 100644 index f70e60d14b5..00000000000 --- a/config/feature_flags/development/namespace_storage_limit_bypass_date_check.yml +++ /dev/null @@ -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 diff --git a/config/initializers/doorkeeper_openid_connect_patch.rb b/config/initializers/doorkeeper_openid_connect_patch.rb deleted file mode 100644 index d61b70eaa31..00000000000 --- a/config/initializers/doorkeeper_openid_connect_patch.rb +++ /dev/null @@ -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 diff --git a/config/routes/group.rb b/config/routes/group.rb index ef49e53d4d3..9b346867f78 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -158,6 +158,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do resources :contacts, only: [:index, :new, :edit] resources :organizations, only: [:index, :new, :edit] end + + resources :achievements, only: [:index, :new, :edit] end scope( diff --git a/config/vue3migration/compiler.js b/config/vue3migration/compiler.js index a2c82584227..d6b6e1e7533 100644 --- a/config/vue3migration/compiler.js +++ b/config/vue3migration/compiler.js @@ -2,16 +2,20 @@ const { parse, compile: compilerDomCompile } = require('@vue/compiler-dom'); 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) { + if (!templateNode.tag === 'template' || !hasProp(templateNode, 'for')) { + return; + } + let keyCandidate = null; for (const node of templateNode.children) { const keyBindingIndex = node.props ? node.props.findIndex((prop) => prop.arg && prop.arg.content === 'key') : -1; - if (keyBindingIndex !== -1 && getPropIndex(node, 'for') === -1) { + if (keyBindingIndex !== -1 && !hasProp(node, 'for')) { if (!keyCandidate) { 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 = { parse, compile(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 + const pendingNodes = [rootNode]; + while (pendingNodes.length) { + const currentNode = pendingNodes.pop(); + if (Array.isArray(currentNode.children)) { + // This one will be dropped all together with compiler when we drop Vue.js 2 support + modifyKeysInsideTemplateTag(currentNode); - // 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 + dropVOnceForChildrenInsideVIfBecauseOfIssue7725(currentNode); - // See https://github.com/vuejs/core/issues/7909 for details + // See https://github.com/vuejs/core/issues/7909 for details + // 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); - // To fix that we simply drop all component comments only on top-level - rootNode.children = rootNode.children.filter((n) => n.type !== COMMENT_NODE_TYPE); + fixSameSlotsInsideTemplateFailingWhenUsingWhitespacePreserveDueToIssue6063(currentNode); - const pendingNodes = [rootNode]; - while (pendingNodes.length) { - const currentNode = pendingNodes.pop(); - if (getPropIndex(currentNode, 'for') !== -1) { - if (currentNode.tag === 'template') { - // This one will be dropped all together with compiler when we drop Vue.js 2 support - modifyKeysInsideTemplateTag(currentNode); - } - - // This one will be dropped when https://github.com/vuejs/core/issues/7725 will be fixed - const vOncePropIndex = getPropIndex(currentNode, 'once'); - if (vOncePropIndex !== -1) { - currentNode.props.splice(vOncePropIndex, 1); - } + currentNode.children.forEach((child) => pendingNodes.push(child)); } - - currentNode.children?.forEach((child) => pendingNodes.push(child)); } return compilerDomCompile(rootNode, options); diff --git a/data/removals/16_0/16-0-api-lint-removal.yml b/data/removals/16_0/16-0-api-lint-removal.yml new file mode 100644 index 00000000000..86e3f70a4bf --- /dev/null +++ b/data/removals/16_0/16-0-api-lint-removal.yml @@ -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 diff --git a/data/deprecations/16-0-self-monitor-removal.yml b/data/removals/16_0/16-0-self-monitor-removal.yml index 3d97d1a417d..3d97d1a417d 100644 --- a/data/deprecations/16-0-self-monitor-removal.yml +++ b/data/removals/16_0/16-0-self-monitor-removal.yml diff --git a/doc/administration/auth/ldap/ldap-troubleshooting.md b/doc/administration/auth/ldap/ldap-troubleshooting.md index 9c925e0a40e..909ab5245ca 100644 --- a/doc/administration/auth/ldap/ldap-troubleshooting.md +++ b/doc/administration/auth/ldap/ldap-troubleshooting.md @@ -761,7 +761,7 @@ users, [see what to do when no users are found](#no-users-are-found). ### GitLab logs 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, timeout), the sign-in is rejected and a message is [logged to `production.log`](../../logs/index.md#productionlog). diff --git a/doc/administration/logs/index.md b/doc/administration/logs/index.md index 82675c4ce39..7fab97f76da 100644 --- a/doc/administration/logs/index.md +++ b/doc/administration/logs/index.md @@ -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 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: diff --git a/doc/ci/examples/authenticating-with-hashicorp-vault/index.md b/doc/ci/examples/authenticating-with-hashicorp-vault/index.md index a5289ca0a4d..f45c60bdd1f 100644 --- a/doc/ci/examples/authenticating-with-hashicorp-vault/index.md +++ b/doc/ci/examples/authenticating-with-hashicorp-vault/index.md @@ -29,7 +29,7 @@ You must replace the `vault.example.com` URL below with the URL of your Vault se ## 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: @@ -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). -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: -```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-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)" -``` +- `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. -NOTE: -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`. - -![read secrets staging example](img/vault-read-secrets-staging.png) - -The following job is able to authenticate using the `myproject-production` role and read secrets under `/secret/myproject/production/`: +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 -read_secrets: - image: vault:latest +job_with_secrets: + 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: - # 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 + - access-staging-db.sh --token $STAGING_DB_PASSWORD ``` -![read secrets production example](img/vault-read-secrets-production.png) +In this example: + +- `@secrets` - The vault name, where your Secrets Engines are enabled. +- `secret/myproject/staging/db` - The path location of the secret in Vault. +- `password` The field to be fetched within the referenced secret. ### 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: - Using Vault [bound claims](https://developer.hashicorp.com/vault/docs/auth/jwt#bound-claims) diff --git a/doc/ci/yaml/yaml_optimization.md b/doc/ci/yaml/yaml_optimization.md index 07019a2776f..2cfda1116fe 100644 --- a/doc/ci/yaml/yaml_optimization.md +++ b/doc/ci/yaml/yaml_optimization.md @@ -65,7 +65,7 @@ test2: `&` sets up the name of the anchor (`job_configuration`), `<<` means "merge the 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 .job_template: @@ -123,7 +123,7 @@ test:mysql: services: *mysql_configuration ``` -The expanded version is: +The [expanded](../pipeline_editor/index.md#view-full-configuration) version is: ```yaml .job_template: diff --git a/doc/development/database/clickhouse/index.md b/doc/development/database/clickhouse/index.md index a26bac261fd..032e4f5f6ee 100644 --- a/doc/development/database/clickhouse/index.md +++ b/doc/development/database/clickhouse/index.md @@ -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 > 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. + +## 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> +``` diff --git a/doc/development/github_importer.md b/doc/development/github_importer.md index 7f9dbf94d9b..7fc2ee3a880 100644 --- a/doc/development/github_importer.md +++ b/doc/development/github_importer.md @@ -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) API doesn't provide this information. Therefore, this stage must fetch each merged pull request 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. ### 7. Stage::ImportPullRequestsReviewRequestsWorker diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index e92bfa96a4b..f563e16b778 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -1989,20 +1989,6 @@ For more information, refer to [security report validation](https://docs.gitlab. <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 <div class="deprecation-notes"> diff --git a/doc/update/removals.md b/doc/update/removals.md index 3e1fb620fcd..abe1ec6e2e6 100644 --- a/doc/update/removals.md +++ b/doc/update/removals.md @@ -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. 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 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_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 WARNING: diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index 7227da3ce0d..71c2468c97f 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -458,7 +458,7 @@ For multi-node systems we recommend ingesting the logs into services like Elasti | Log file | Contents | |:------------------------|:---------| -| `application.log` | GitLab user activity | +| `application_json.log` | GitLab user activity | | `git_json.log` | Failed GitLab interaction with Git repositories | | `production.log` | Requests received from Puma, and the actions taken to serve those requests | | `sidekiq.log` | Background jobs | diff --git a/doc/user/group/settings/group_access_tokens.md b/doc/user/group/settings/group_access_tokens.md index be9821e1b68..4a2bfd4c4b4 100644 --- a/doc/user/group/settings/group_access_tokens.md +++ b/doc/user/group/settings/group_access_tokens.md @@ -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. > - 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: 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. -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 GitLab 14.6 and earlier doesn't support creating group access tokens using the UI diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md index a9201f57155..178500093e2 100644 --- a/doc/user/project/settings/project_access_tokens.md +++ b/doc/user/project/settings/project_access_tokens.md @@ -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. > - 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: 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. -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 To revoke a project access token: diff --git a/jest.config.base.js b/jest.config.base.js index 3cbf2fdd61b..d11b3a5c1e6 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -51,6 +51,7 @@ module.exports = (path, options = {}) => { experimentalCSSCompile: false, compiler: require.resolve('./config/vue3migration/compiler'), compilerOptions: { + whitespace: 'preserve', compatConfig: { MODE: 2, }, diff --git a/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb b/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb deleted file mode 100644 index 51a72a80268..00000000000 --- a/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb +++ /dev/null @@ -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 diff --git a/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer.rb b/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer.rb new file mode 100644 index 00000000000..9aa55fd3eae --- /dev/null +++ b/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer.rb @@ -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 diff --git a/lib/gitlab/github_import/importer/pull_requests/merged_by_importer.rb b/lib/gitlab/github_import/importer/pull_requests/merged_by_importer.rb new file mode 100644 index 00000000000..19880716832 --- /dev/null +++ b/lib/gitlab/github_import/importer/pull_requests/merged_by_importer.rb @@ -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 diff --git a/lib/gitlab/github_import/importer/pull_requests_merged_by_importer.rb b/lib/gitlab/github_import/importer/pull_requests_merged_by_importer.rb deleted file mode 100644 index c56b391cbec..00000000000 --- a/lib/gitlab/github_import/importer/pull_requests_merged_by_importer.rb +++ /dev/null @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 99ac0af463e..ca8143c5e3f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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}" msgstr "" +msgid "Achievements" +msgstr "" + msgid "Achievements|%{namespace_full_path} awarded you the %{achievement_name} achievement" msgstr "" diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 2808b6181f3..1f43caf37e7 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -649,6 +649,8 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do end 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_database_flavor_check diff --git a/spec/frontend/blob/line_highlighter_spec.js b/spec/frontend/blob/line_highlighter_spec.js index 21d4e8db503..b2e1a29b84f 100644 --- a/spec/frontend/blob/line_highlighter_spec.js +++ b/spec/frontend/blob/line_highlighter_spec.js @@ -1,7 +1,7 @@ /* eslint-disable no-return-assign, no-new, no-underscore-dangle */ - 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 * as utils from '~/lib/utils/common_utils'; @@ -17,7 +17,7 @@ describe('LineHighlighter', () => { }; beforeEach(() => { - loadHTMLFixture('static/line_highlighter.html'); + setHTMLFixture(htmlStaticLineHighlighter); testContext.class = new LineHighlighter(); testContext.css = testContext.class.highlightLineClass; return (testContext.spies = { diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js index 23da1a3601b..47a266c2e36 100644 --- a/spec/frontend/diffs/components/compare_versions_spec.js +++ b/spec/frontend/diffs/components/compare_versions_spec.js @@ -21,6 +21,7 @@ beforeEach(() => { describe('CompareVersions', () => { let wrapper; let store; + let dispatchMock; const targetBranchName = 'tmp-wine-dev'; const { commit } = getDiffWithCommit; @@ -29,6 +30,8 @@ describe('CompareVersions', () => { store.state.diffs.commit = { ...store.state.diffs.commit, ...commitArgs }; } + dispatchMock = jest.spyOn(store, 'dispatch'); + wrapper = mount(CompareVersionsComponent, { store, propsData: { @@ -146,7 +149,7 @@ describe('CompareVersions', () => { it('renders short commit ID', () => { 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}`); }); - beforeEach(() => { - jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {}); - }); - it('uses the correct href', () => { const link = getPrevCommitNavElement(); @@ -219,7 +218,7 @@ describe('CompareVersions', () => { link.trigger('click'); await nextTick(); - expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({ + expect(dispatchMock).toHaveBeenCalledWith('diffs/moveToNeighboringCommit', { direction: 'previous', }); }); @@ -238,10 +237,6 @@ describe('CompareVersions', () => { setWindowLocation(`?commit_id=${mrCommit.id}`); }); - beforeEach(() => { - jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {}); - }); - it('uses the correct href', () => { const link = getNextCommitNavElement(); @@ -253,7 +248,9 @@ describe('CompareVersions', () => { link.trigger('click'); 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', () => { diff --git a/spec/frontend/prometheus_metrics/custom_metrics_spec.js b/spec/frontend/prometheus_metrics/custom_metrics_spec.js index 823caec0211..706f932aa8d 100644 --- a/spec/frontend/prometheus_metrics/custom_metrics_spec.js +++ b/spec/frontend/prometheus_metrics/custom_metrics_spec.js @@ -1,5 +1,5 @@ -import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html'; import MockAdapter from 'axios-mock-adapter'; +import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; diff --git a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js index bc796ac53ca..64cf69b7f5b 100644 --- a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js +++ b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js @@ -1,5 +1,5 @@ -import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html'; import MockAdapter from 'axios-mock-adapter'; +import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; diff --git a/spec/frontend/super_sidebar/components/menu_section_spec.js b/spec/frontend/super_sidebar/components/menu_section_spec.js index 203a65d6a0d..556e07a2e31 100644 --- a/spec/frontend/super_sidebar/components/menu_section_spec.js +++ b/spec/frontend/super_sidebar/components/menu_section_spec.js @@ -10,8 +10,6 @@ describe('MenuSection component', () => { const findButton = () => wrapper.find('button'); const findCollapse = () => wrapper.getComponent(GlCollapse); const findNavItems = () => wrapper.findAllComponents(NavItem); - const findSectionTitle = () => wrapper.findByTestId('section-title'); - const createWrapper = (item, otherProps) => { wrapper = shallowMountExtended(MenuSection, { 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('by default (false)', () => { it('does not render a separator', () => { diff --git a/spec/frontend/super_sidebar/components/sidebar_menu_spec.js b/spec/frontend/super_sidebar/components/sidebar_menu_spec.js index 55483c46732..26b146f0c8b 100644 --- a/spec/frontend/super_sidebar/components/sidebar_menu_spec.js +++ b/spec/frontend/super_sidebar/components/sidebar_menu_spec.js @@ -1,6 +1,5 @@ import { mountExtended } from 'helpers/vue_test_utils_helper'; 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 { sidebarData } from '../mock_data'; @@ -102,10 +101,6 @@ describe('SidebarMenu component', () => { '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', () => { @@ -120,10 +115,6 @@ describe('SidebarMenu component', () => { it('keeps all items as non-static', () => { expect(wrapper.vm.nonStaticItems).toEqual(menuItems); }); - - it('passes `supportsPin` to menu sections', () => { - expect(wrapper.findAllComponents(MenuSection).at(1).props('collectionStyle')).toBe(false); - }); }); }); diff --git a/spec/frontend/vue3migration/compiler_spec.js b/spec/frontend/vue3migration/compiler_spec.js new file mode 100644 index 00000000000..3623f69fe07 --- /dev/null +++ b/spec/frontend/vue3migration/compiler_spec.js @@ -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>'); + }); +}); diff --git a/spec/frontend/vue3migration/components/comments_on_root_level.vue b/spec/frontend/vue3migration/components/comments_on_root_level.vue new file mode 100644 index 00000000000..78222c059d5 --- /dev/null +++ b/spec/frontend/vue3migration/components/comments_on_root_level.vue @@ -0,0 +1,5 @@ +<template> + <!-- root level comment --> + <div><slot></slot></div> + <!-- root level comment --> +</template> diff --git a/spec/frontend/vue3migration/components/default_slot_with_comment.vue b/spec/frontend/vue3migration/components/default_slot_with_comment.vue new file mode 100644 index 00000000000..d2589104a5d --- /dev/null +++ b/spec/frontend/vue3migration/components/default_slot_with_comment.vue @@ -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> diff --git a/spec/frontend/vue3migration/components/key_inside_template.vue b/spec/frontend/vue3migration/components/key_inside_template.vue new file mode 100644 index 00000000000..af1f46c44e6 --- /dev/null +++ b/spec/frontend/vue3migration/components/key_inside_template.vue @@ -0,0 +1,7 @@ +<template> + <div> + <template v-for="count in 5" + ><span :key="count">{{ count }}</span></template + > + </div> +</template> diff --git a/spec/frontend/vue3migration/components/simple.vue b/spec/frontend/vue3migration/components/simple.vue new file mode 100644 index 00000000000..1d9854b5b4d --- /dev/null +++ b/spec/frontend/vue3migration/components/simple.vue @@ -0,0 +1,10 @@ +<script> +export default { + name: 'Simple', +}; +</script> +<template> + <div> + <slot>{{ $options.name }}</slot> + </div> +</template> diff --git a/spec/frontend/vue3migration/components/slot_with_comment.vue b/spec/frontend/vue3migration/components/slot_with_comment.vue new file mode 100644 index 00000000000..56bb41e432f --- /dev/null +++ b/spec/frontend/vue3migration/components/slot_with_comment.vue @@ -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> diff --git a/spec/frontend/vue3migration/components/slots_with_same_name.vue b/spec/frontend/vue3migration/components/slots_with_same_name.vue new file mode 100644 index 00000000000..37604cd9f6e --- /dev/null +++ b/spec/frontend/vue3migration/components/slots_with_same_name.vue @@ -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> diff --git a/spec/frontend/vue3migration/components/v_once_inside_v_if.vue b/spec/frontend/vue3migration/components/v_once_inside_v_if.vue new file mode 100644 index 00000000000..708aa7a96c2 --- /dev/null +++ b/spec/frontend/vue3migration/components/v_once_inside_v_if.vue @@ -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> diff --git a/spec/frontend/vue_compat_test_setup.js b/spec/frontend/vue_compat_test_setup.js index 8c0346e6198..6eba9465c80 100644 --- a/spec/frontend/vue_compat_test_setup.js +++ b/spec/frontend/vue_compat_test_setup.js @@ -63,7 +63,7 @@ if (global.document) { }; let compatH; - Vue.config.compilerOptions.whitespace = 'condense'; + Vue.config.compilerOptions.whitespace = 'preserve'; Vue.createApp({ compatConfig: { MODE: 3, diff --git a/spec/initializers/doorkeeper_openid_connect_patch_spec.rb b/spec/initializers/doorkeeper_openid_connect_patch_spec.rb deleted file mode 100644 index c04d7d95de6..00000000000 --- a/spec/initializers/doorkeeper_openid_connect_patch_spec.rb +++ /dev/null @@ -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 diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_merged_by_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer_spec.rb index b6c162aafa9..8e13b35eb6b 100644 --- a/spec/lib/gitlab/github_import/importer/pull_requests_merged_by_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer_spec.rb @@ -2,7 +2,7 @@ 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_it_be(:project) { create(:project, import_source: 'http://somegithub.com') } @@ -16,7 +16,11 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do end 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 describe '#collection_method' do @@ -24,7 +28,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do end 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 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) } .to yield_with_args(pull_request) - subject.each_object_to_import {} + subject.each_object_to_import end it 'skips cached merge requests' do @@ -55,7 +59,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do expect(client).not_to receive(:pull_request) - subject.each_object_to_import {} + subject.each_object_to_import end end end diff --git a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests/merged_by_importer_spec.rb index 01d706beea2..25381594632 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_requests/merged_by_importer_spec.rb @@ -2,12 +2,16 @@ 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(:project) { merge_request.project } - let(:merged_at) { Time.new(2017, 1, 1, 12, 00).utc } - let(:client_double) { double(user: { id: 999, login: 'merger', email: 'merger@email.com' } ) } + let(:merged_at) { Time.utc(2017, 1, 1, 12) } + 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(:pull_request) do @@ -25,7 +29,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :cle shared_examples 'adds a note referencing the merger user' do it 'adds a note referencing the merger user' do expect { subject.execute } - .to change(Note, :count).by(1) + .to change { Note.count }.by(1) .and not_change(merge_request, :updated_at) 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 expect { subject.execute } - .to change(Note, :count).by(1) + .to change { Note.count }.by(1) .and not_change(merge_request, :updated_at) metrics = merge_request.metrics.reload diff --git a/spec/requests/groups/achievements_controller_spec.rb b/spec/requests/groups/achievements_controller_spec.rb new file mode 100644 index 00000000000..26ca0039984 --- /dev/null +++ b/spec/requests/groups/achievements_controller_spec.rb @@ -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 diff --git a/spec/serializers/import/github_failure_entity_spec.rb b/spec/serializers/import/github_failure_entity_spec.rb index 357eae91b28..0de710f22cc 100644 --- a/spec/serializers/import/github_failure_entity_spec.rb +++ b/spec/serializers/import/github_failure_entity_spec.rb @@ -62,7 +62,7 @@ RSpec.describe Import::GithubFailureEntity, feature_category: :importers do end 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(:provider_url) { 'https://github.com/example/repo/pull/2' } end diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index 2c2cd7e9960..9fada09263c 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -277,6 +277,7 @@ RSpec.describe 'Every Sidekiq worker', feature_category: :shared do 'Gitlab::GithubImport::ImportPullRequestReviewWorker' => 5, 'Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker' => 5, 'Gitlab::GithubImport::PullRequests::ImportReviewWorker' => 5, + 'Gitlab::GithubImport::PullRequests::ImportMergedByWorker' => 5, 'Gitlab::GithubImport::ImportPullRequestWorker' => 5, 'Gitlab::GithubImport::RefreshImportJidWorker' => 5, 'Gitlab::GithubImport::Stage::FinishImportWorker' => 5, diff --git a/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb index 6f771a1b79d..4fbdfb1903f 100644 --- a/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb @@ -10,6 +10,6 @@ RSpec.describe Gitlab::GithubImport::ImportPullRequestMergedByWorker, feature_ca end 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 diff --git a/spec/workers/gitlab/github_import/pull_requests/import_merged_by_worker_spec.rb b/spec/workers/gitlab/github_import/pull_requests/import_merged_by_worker_spec.rb new file mode 100644 index 00000000000..23631c76a61 --- /dev/null +++ b/spec/workers/gitlab/github_import/pull_requests/import_merged_by_worker_spec.rb @@ -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 diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb index 4ffc5a956a3..0debabda0cc 100644 --- a/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportPullRequestsMergedByWorker, fe client = double(:client) waiter = Gitlab::JobWaiter.new(2, '123') - expect(Gitlab::GithubImport::Importer::PullRequestsMergedByImporter) + expect(Gitlab::GithubImport::Importer::PullRequests::AllMergedByImporter) .to receive(:new) .with(project, client) .and_return(importer) |