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:
|
||||
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"
|
||||
|
||||
|
|
|
|||
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 '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'
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
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>
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ export default {
|
|||
:item="sectionItem"
|
||||
:expanded="expanded"
|
||||
:separated="true"
|
||||
collection-style
|
||||
@collapse-toggle="expanded = !expanded"
|
||||
>
|
||||
<draggable
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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) &&
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
: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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
# project - An instance of Project.
|
||||
def import(client, project)
|
||||
waiter = Importer::PullRequestsMergedByImporter
|
||||
waiter = Importer::PullRequests::AllMergedByImporter
|
||||
.new(project, client)
|
||||
.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 :organizations, only: [:index, :new, :edit]
|
||||
end
|
||||
|
||||
resources :achievements, only: [:index, :new, :edit]
|
||||
end
|
||||
|
||||
scope(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
// 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];
|
||||
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);
|
||||
}
|
||||
if (Array.isArray(currentNode.children)) {
|
||||
// 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);
|
||||
}
|
||||
dropVOnceForChildrenInsideVIfBecauseOfIssue7725(currentNode);
|
||||
|
||||
// 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);
|
||||
|
||||
fixSameSlotsInsideTemplateFailingWhenUsingWhitespacePreserveDueToIssue6063(currentNode);
|
||||
|
||||
currentNode.children.forEach((child) => pendingNodes.push(child));
|
||||
}
|
||||
|
||||
currentNode.children?.forEach((child) => pendingNodes.push(child));
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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).
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
- `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
|
||||
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-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)"
|
||||
- access-staging-db.sh --token $STAGING_DB_PASSWORD
|
||||
```
|
||||
|
||||
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`.
|
||||
In this example:
|
||||
|
||||

|
||||
|
||||
The following job is able to authenticate using the `myproject-production` role and read secrets under `/secret/myproject/production/`:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||

|
||||
- `@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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ module.exports = (path, options = {}) => {
|
|||
experimentalCSSCompile: false,
|
||||
compiler: require.resolve('./config/vue3migration/compiler'),
|
||||
compilerOptions: {
|
||||
whitespace: 'preserve',
|
||||
compatConfig: {
|
||||
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}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Achievements"
|
||||
msgstr ""
|
||||
|
||||
msgid "Achievements|%{namespace_full_path} awarded you the %{achievement_name} achievement"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
Vue.config.compilerOptions.whitespace = 'condense';
|
||||
Vue.config.compilerOptions.whitespace = 'preserve';
|
||||
Vue.createApp({
|
||||
compatConfig: {
|
||||
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'
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue