Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-12 15:13:54 +00:00
parent da646aac6c
commit 462b603802
60 changed files with 721 additions and 159 deletions

View File

@ -18,16 +18,6 @@ Rails/SaveBang:
- ee/spec/models/visible_approvable_spec.rb - ee/spec/models/visible_approvable_spec.rb
- ee/spec/models/vulnerabilities/feedback_spec.rb - ee/spec/models/vulnerabilities/feedback_spec.rb
- ee/spec/models/vulnerabilities/issue_link_spec.rb - ee/spec/models/vulnerabilities/issue_link_spec.rb
- ee/spec/services/ee/merge_requests/update_service_spec.rb
- ee/spec/services/ee/notes/quick_actions_service_spec.rb
- ee/spec/services/ee/notification_service_spec.rb
- ee/spec/services/epic_links/create_service_spec.rb
- ee/spec/services/epics/close_service_spec.rb
- ee/spec/services/epics/issue_promote_service_spec.rb
- ee/spec/services/epics/reopen_service_spec.rb
- ee/spec/services/epics/tree_reorder_service_spec.rb
- ee/spec/services/epics/update_dates_service_spec.rb
- ee/spec/services/epics/update_service_spec.rb
- ee/spec/services/geo/blob_verification_secondary_service_spec.rb - ee/spec/services/geo/blob_verification_secondary_service_spec.rb
- ee/spec/services/geo/files_expire_service_spec.rb - ee/spec/services/geo/files_expire_service_spec.rb
- ee/spec/services/geo/metrics_update_service_spec.rb - ee/spec/services/geo/metrics_update_service_spec.rb

View File

@ -25,9 +25,7 @@ Style/OpenStructUse:
- spec/graphql/mutations/commits/create_spec.rb - spec/graphql/mutations/commits/create_spec.rb
- spec/helpers/application_settings_helper_spec.rb - spec/helpers/application_settings_helper_spec.rb
- spec/helpers/profiles_helper_spec.rb - spec/helpers/profiles_helper_spec.rb
- spec/initializers/doorkeeper_spec.rb
- spec/lib/gitlab/auth/o_auth/provider_spec.rb - spec/lib/gitlab/auth/o_auth/provider_spec.rb
- spec/lib/gitlab/database/migrations/runner_spec.rb
- spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb - spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb
- spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb - spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
- spec/lib/gitlab/legacy_github_import/project_creator_spec.rb - spec/lib/gitlab/legacy_github_import/project_creator_spec.rb

View File

@ -1 +1 @@
e02b0d67e48ed5a4493b073c9836d376a780f34d 7055518ce76486791c3450a8a47b673891b6e2d6

View File

@ -313,7 +313,7 @@ gem 'pg_query', '~> 2.1'
gem 'premailer-rails', '~> 1.10.3' gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation # LabKit: Tracing and Correlation
gem 'gitlab-labkit', '~> 0.21.1' gem 'gitlab-labkit', '~> 0.21.3'
# Thrift is a dependency of gitlab-labkit, we want a version higher than 0.14.0 # Thrift is a dependency of gitlab-labkit, we want a version higher than 0.14.0
# because of https://gitlab.com/gitlab-org/gitlab/-/issues/321900 # because of https://gitlab.com/gitlab-org/gitlab/-/issues/321900
gem 'thrift', '>= 0.14.0' gem 'thrift', '>= 0.14.0'
@ -485,14 +485,14 @@ end
gem 'spamcheck', '~> 0.1.0' gem 'spamcheck', '~> 0.1.0'
# Gitaly GRPC protocol definitions # Gitaly GRPC protocol definitions
gem 'gitaly', '~> 14.4.0.pre.rc43' gem 'gitaly', '~> 14.6.0.pre.rc1'
# KAS GRPC protocol definitions # KAS GRPC protocol definitions
gem 'kas-grpc', '~> 0.0.2' gem 'kas-grpc', '~> 0.0.2'
gem 'grpc', '~> 1.30.2' gem 'grpc', '~> 1.42.0'
gem 'google-protobuf', '~> 3.17.1' gem 'google-protobuf', '~> 3.19.0'
gem 'toml-rb', '~> 2.0' gem 'toml-rb', '~> 2.0'

View File

@ -452,7 +452,7 @@ GEM
rails (>= 3.2.0) rails (>= 3.2.0)
git (1.7.0) git (1.7.0)
rchardet (~> 1.8) rchardet (~> 1.8)
gitaly (14.4.0.pre.rc43) gitaly (14.6.0.pre.rc1)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab (4.16.1) gitlab (4.16.1)
@ -474,10 +474,10 @@ GEM
fog-json (~> 1.2.0) fog-json (~> 1.2.0)
mime-types mime-types
ms_rest_azure (~> 0.12.0) ms_rest_azure (~> 0.12.0)
gitlab-labkit (0.21.1) gitlab-labkit (0.21.3)
actionpack (>= 5.0.0, < 7.0.0) actionpack (>= 5.0.0, < 7.0.0)
activesupport (>= 5.0.0, < 7.0.0) activesupport (>= 5.0.0, < 7.0.0)
grpc (~> 1.30.2) grpc (>= 1.37)
jaeger-client (~> 1.1) jaeger-client (~> 1.1)
opentracing (~> 0.4) opentracing (~> 0.4)
pg_query (~> 2.1) pg_query (~> 2.1)
@ -531,8 +531,8 @@ GEM
signet (~> 0.12) signet (~> 0.12)
google-cloud-env (1.5.0) google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
google-protobuf (3.17.3) google-protobuf (3.19.1)
googleapis-common-protos-types (1.1.0) googleapis-common-protos-types (1.3.0)
google-protobuf (~> 3.14) google-protobuf (~> 3.14)
googleauth (0.14.0) googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
@ -580,8 +580,8 @@ GEM
graphql (~> 1.6) graphql (~> 1.6)
html-pipeline (~> 2.8) html-pipeline (~> 2.8)
sass (~> 3.4) sass (~> 3.4)
grpc (1.30.2) grpc (1.42.0)
google-protobuf (~> 3.12) google-protobuf (~> 3.18)
googleapis-common-protos-types (~> 1.0) googleapis-common-protos-types (~> 1.0)
gssapi (1.2.0) gssapi (1.2.0)
ffi (>= 1.0.1) ffi (>= 1.0.1)
@ -1473,13 +1473,13 @@ DEPENDENCIES
gettext (~> 3.3) gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly (~> 14.4.0.pre.rc43) gitaly (~> 14.6.0.pre.rc1)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 2.6.1) gitlab-dangerfiles (~> 2.6.1)
gitlab-experiment (~> 0.6.5) gitlab-experiment (~> 0.6.5)
gitlab-fog-azure-rm (~> 1.2.0) gitlab-fog-azure-rm (~> 1.2.0)
gitlab-labkit (~> 0.21.1) gitlab-labkit (~> 0.21.3)
gitlab-license (~> 2.0) gitlab-license (~> 2.0)
gitlab-license_finder (~> 6.0) gitlab-license_finder (~> 6.0)
gitlab-mail_room (~> 0.0.9) gitlab-mail_room (~> 0.0.9)
@ -1492,7 +1492,7 @@ DEPENDENCIES
gitlab_omniauth-ldap (~> 2.1.1) gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.4.0) gon (~> 6.4.0)
google-api-client (~> 0.33) google-api-client (~> 0.33)
google-protobuf (~> 3.17.1) google-protobuf (~> 3.19.0)
gpgme (~> 2.0.19) gpgme (~> 2.0.19)
grape (~> 1.5.2) grape (~> 1.5.2)
grape-entity (~> 0.10.0) grape-entity (~> 0.10.0)
@ -1502,7 +1502,7 @@ DEPENDENCIES
graphlient (~> 0.4.0) graphlient (~> 0.4.0)
graphql (~> 1.11.10) graphql (~> 1.11.10)
graphql-docs (~> 1.6.0) graphql-docs (~> 1.6.0)
grpc (~> 1.30.2) grpc (~> 1.42.0)
gssapi gssapi
guard-rspec guard-rspec
haml_lint (~> 0.36.0) haml_lint (~> 0.36.0)

View File

@ -1,6 +1,7 @@
<script> <script>
import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { JOB_SIDEBAR } from '../../constants'; import { JOB_SIDEBAR } from '../../constants';
import CommitBlock from '../../components/commit_block.vue'; import CommitBlock from '../../components/commit_block.vue';
@ -25,6 +26,7 @@ export default {
GlDropdownItem, GlDropdownItem,
TooltipOnTruncate, TooltipOnTruncate,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
bridgeJob: { bridgeJob: {
type: Object, type: Object,
@ -54,7 +56,10 @@ export default {
</h4> </h4>
</tooltip-on-truncate> </tooltip-on-truncate>
<!-- TODO: implement retry actions --> <!-- TODO: implement retry actions -->
<div class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right"> <div
v-if="glFeatures.triggerJobRetryAction"
class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right"
>
<gl-dropdown <gl-dropdown
:text="$options.i18n.retryButton" :text="$options.i18n.retryButton"
category="primary" category="primary"

View File

@ -26,6 +26,7 @@ import trackShowInviteMemberLink from '~/sidebar/track_invite_members';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants'; import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue'; import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { LabelType } from '~/vue_shared/components/sidebar/labels_select_widget/constants'; import { LabelType } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import eventHub from '~/sidebar/event_hub';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue'; import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
import CopyEmailToClipboard from './components/copy_email_to_clipboard.vue'; import CopyEmailToClipboard from './components/copy_email_to_clipboard.vue';
@ -600,6 +601,12 @@ export function mountSidebar(mediator, store) {
mountTimeTrackingComponent(); mountTimeTrackingComponent();
mountSeverityComponent(); mountSeverityComponent();
if (window.gon?.features?.mrAttentionRequests) {
eventHub.$on('removeCurrentUserAttentionRequested', () =>
mediator.removeCurrentUserAttentionRequested(),
);
}
} }
export { getSidebarOptions }; export { getSidebarOptions };

View File

@ -30,7 +30,7 @@ export default class SidebarMediator {
this.store.addAssignee(this.store.currentUser); this.store.addAssignee(this.store.currentUser);
} }
saveAssignees(field) { async saveAssignees(field) {
const selected = this.store.assignees.map((u) => u.id); const selected = this.store.assignees.map((u) => u.id);
// If there are no ids, that means we have to unassign (which is id = 0) // If there are no ids, that means we have to unassign (which is id = 0)
@ -38,10 +38,22 @@ export default class SidebarMediator {
const assignees = selected.length === 0 ? [0] : selected; const assignees = selected.length === 0 ? [0] : selected;
const data = { assignee_ids: assignees }; const data = { assignee_ids: assignees };
return this.service.update(field, data); try {
const res = await this.service.update(field, data);
this.store.overwrite('assignees', res.data.assignees);
if (res.data.reviewers) {
this.store.overwrite('reviewers', res.data.reviewers);
}
return Promise.resolve(res);
} catch (e) {
return Promise.reject(e);
}
} }
saveReviewers(field) { async saveReviewers(field) {
const selected = this.store.reviewers.map((u) => u.id); const selected = this.store.reviewers.map((u) => u.id);
// If there are no ids, that means we have to unassign (which is id = 0) // If there are no ids, that means we have to unassign (which is id = 0)
@ -49,7 +61,16 @@ export default class SidebarMediator {
const reviewers = selected.length === 0 ? [0] : selected; const reviewers = selected.length === 0 ? [0] : selected;
const data = { reviewer_ids: reviewers }; const data = { reviewer_ids: reviewers };
return this.service.update(field, data); try {
const res = await this.service.update(field, data);
this.store.overwrite('reviewers', res.data.reviewers);
this.store.overwrite('assignees', res.data.assignees);
return Promise.resolve(res);
} catch (e) {
return Promise.reject();
}
} }
requestReview({ userId, callback }) { requestReview({ userId, callback }) {
@ -63,6 +84,19 @@ export default class SidebarMediator {
.catch(() => callback(userId, false)); .catch(() => callback(userId, false));
} }
removeCurrentUserAttentionRequested() {
const currentUserId = gon.current_user_id;
const currentUserReviewer = this.store.findReviewer({ id: currentUserId });
const currentUserAssignee = this.store.findAssignee({ id: currentUserId });
if (currentUserReviewer?.attention_requested || currentUserAssignee?.attention_requested) {
// Update current users attention_requested state
this.store.updateReviewer(currentUserId, 'attention_requested');
this.store.updateAssignee(currentUserId, 'attention_requested');
}
}
async toggleAttentionRequested(type, { user, callback }) { async toggleAttentionRequested(type, { user, callback }) {
try { try {
const isReviewer = type === 'reviewer'; const isReviewer = type === 'reviewer';
@ -82,15 +116,7 @@ export default class SidebarMediator {
const currentUserId = gon.current_user_id; const currentUserId = gon.current_user_id;
if (currentUserId !== user.id) { if (currentUserId !== user.id) {
const currentUserReviewerOrAssignee = isReviewer this.removeCurrentUserAttentionRequested();
? this.store.findReviewer({ id: currentUserId })
: this.store.findAssignee({ id: currentUserId });
if (currentUserReviewerOrAssignee?.attention_requested) {
// Update current users attention_requested state
this.store.updateReviewer(currentUserId, 'attention_requested');
this.store.updateAssignee(currentUserId, 'attention_requested');
}
} }
toast(sprintf(__('Requested attention from @%{username}'), { username: user.username })); toast(sprintf(__('Requested attention from @%{username}'), { username: user.username }));

View File

@ -98,6 +98,10 @@ export default class SidebarStore {
} }
} }
overwrite(key, newData) {
this[key] = newData;
}
findAssignee(findAssignee) { findAssignee(findAssignee) {
return this.assignees.find(({ id }) => id === findAssignee.id); return this.assignees.find(({ id }) => id === findAssignee.id);
} }

View File

@ -3,6 +3,7 @@ import { GlButton } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { BV_SHOW_MODAL } from '~/lib/utils/constants'; import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import approvalsMixin from '../../mixins/approvals'; import approvalsMixin from '../../mixins/approvals';
import MrWidgetContainer from '../mr_widget_container.vue'; import MrWidgetContainer from '../mr_widget_container.vue';
@ -172,6 +173,7 @@ export default {
this.mr.setApprovals(data); this.mr.setApprovals(data);
eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('ApprovalUpdated'); eventHub.$emit('ApprovalUpdated');
sidebarEventHub.$emit('removeCurrentUserAttentionRequested');
this.$emit('updated'); this.$emit('updated');
}) })
.catch(errFn) .catch(errFn)

View File

@ -41,7 +41,7 @@ class Import::GitlabController < Import::BaseController
override :importable_repos override :importable_repos
def importable_repos def importable_repos
client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS) client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS).to_a
end end
override :incompatible_repos override :incompatible_repos

View File

@ -19,6 +19,7 @@ class Projects::JobsController < Projects::ApplicationController
before_action do before_action do
push_frontend_feature_flag(:infinitely_collapsible_sections, @project, default_enabled: :yaml) push_frontend_feature_flag(:infinitely_collapsible_sections, @project, default_enabled: :yaml)
push_frontend_feature_flag(:trigger_job_retry_action, @project, default_enabled: :yaml)
end end
layout 'project' layout 'project'

View File

@ -18,4 +18,14 @@ module SshKeysHelper
container: 'body' container: 'body'
} }
end end
def ssh_key_allowed_algorithms
allowed_algorithms = Gitlab::CurrentSettings.allowed_key_types.flat_map do |ssh_key_type_name|
Gitlab::SSHPublicKey.supported_algorithms_for_name(ssh_key_type_name)
end
quoted_allowed_algorithms = allowed_algorithms.map { |name| "'#{name}'" }
Gitlab::Utils.to_exclusive_sentence(quoted_allowed_algorithms)
end
end end

View File

@ -1292,6 +1292,12 @@ module Ci
end end
end end
def use_variables_builder_definitions?
strong_memoize(:use_variables_builder_definitions) do
::Feature.enabled?(:ci_use_variables_builder_definitions, project, default_enabled: :yaml)
end
end
private private
def add_message(severity, content) def add_message(severity, content)

View File

@ -13,6 +13,8 @@ module Ci
track_duration do track_duration do
variables = pipeline.variables_builder.scoped_variables(self, environment: environment, dependencies: dependencies) variables = pipeline.variables_builder.scoped_variables(self, environment: environment, dependencies: dependencies)
next variables if pipeline.use_variables_builder_definitions?
variables.concat(project.predefined_variables) variables.concat(project.predefined_variables)
variables.concat(pipeline.predefined_variables) variables.concat(pipeline.predefined_variables)
variables.concat(runner.predefined_variables) if runnable? && runner variables.concat(runner.predefined_variables) if runnable? && runner
@ -60,49 +62,27 @@ module Ci
end end
def user_variables def user_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables| pipeline.variables_builder.user_variables(user)
break variables if user.blank?
variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
variables.append(key: 'GITLAB_USER_NAME', value: user.name)
end
end end
def kubernetes_variables def kubernetes_variables
::Gitlab::Ci::Variables::Collection.new.tap do |collection| pipeline.variables_builder.kubernetes_variables(self)
# Should get merged with the cluster kubeconfig in deployment_variables, see
# https://gitlab.com/gitlab-org/gitlab/-/issues/335089
template = ::Ci::GenerateKubeconfigService.new(self).execute
if template.valid?
collection.append(key: 'KUBECONFIG', value: template.to_yaml, public: false, file: true)
end
end
end end
def deployment_variables(environment:) def deployment_variables(environment:)
return [] unless environment pipeline.variables_builder.deployment_variables(job: self, environment: environment)
project.deployment_variables(
environment: environment,
kubernetes_namespace: expanded_kubernetes_namespace
)
end end
def secret_instance_variables def secret_instance_variables
project.ci_instance_variables_for(ref: git_ref) pipeline.variables_builder.secret_instance_variables(ref: git_ref)
end end
def secret_group_variables(environment: expanded_environment_name) def secret_group_variables(environment: expanded_environment_name)
return [] unless project.group pipeline.variables_builder.secret_group_variables(environment: environment, ref: git_ref)
project.group.ci_variables_for(git_ref, project, environment: environment)
end end
def secret_project_variables(environment: expanded_environment_name) def secret_project_variables(environment: expanded_environment_name)
project.ci_variables_for(ref: git_ref, environment: environment) pipeline.variables_builder.secret_project_variables(environment: environment, ref: git_ref)
end end
end end
end end

View File

@ -627,14 +627,13 @@ class Group < Namespace
end end
end end
def group_member(user) def member(user)
if group_members.loaded? if group_members.loaded?
group_members.find { |gm| gm.user_id == user.id } group_members.find { |gm| gm.user_id == user.id }
else else
group_members.find_by(user_id: user) group_members.find_by(user_id: user)
end end
end end
alias_method :resource_member, :group_member
def highest_group_member(user) def highest_group_member(user)
GroupMember.where(source_id: self_and_ancestors_ids, user_id: user.id).order(:access_level).last GroupMember.where(source_id: self_and_ancestors_ids, user_id: user.id).order(:access_level).last

View File

@ -22,7 +22,7 @@ module Namespaces
unscoped.where(id: root_ids) unscoped.where(id: root_ids)
end end
def self_and_ancestors(include_self: true, hierarchy_order: nil) def self_and_ancestors(include_self: true, upto: nil, hierarchy_order: nil)
return super unless use_traversal_ids_for_ancestor_scopes? return super unless use_traversal_ids_for_ancestor_scopes?
ancestors_cte, base_cte = ancestor_ctes ancestors_cte, base_cte = ancestor_ctes
@ -35,11 +35,15 @@ module Namespaces
.where(namespaces[:id].eq(ancestors_cte.table[:ancestor_id])) .where(namespaces[:id].eq(ancestors_cte.table[:ancestor_id]))
.order_by_depth(hierarchy_order) .order_by_depth(hierarchy_order)
if include_self unless include_self
records records = records.where(ancestors_cte.table[:base_id].not_eq(ancestors_cte.table[:ancestor_id]))
else
records.where(ancestors_cte.table[:base_id].not_eq(ancestors_cte.table[:ancestor_id]))
end end
if upto
records = records.where.not(id: unscoped.where(id: upto).select('unnest(traversal_ids)'))
end
records
end end
def self_and_ancestor_ids(include_self: true) def self_and_ancestor_ids(include_self: true)

View File

@ -17,8 +17,8 @@ module Namespaces
.where(namespaces: { parent_id: nil }) .where(namespaces: { parent_id: nil })
end end
def self_and_ancestors(include_self: true, hierarchy_order: nil) def self_and_ancestors(include_self: true, upto: nil, hierarchy_order: nil)
records = Gitlab::ObjectHierarchy.new(all).base_and_ancestors(hierarchy_order: hierarchy_order) records = Gitlab::ObjectHierarchy.new(all).base_and_ancestors(upto: upto, hierarchy_order: hierarchy_order)
if include_self if include_self
records records

View File

@ -1661,14 +1661,13 @@ class Project < ApplicationRecord
attrs attrs
end end
def project_member(user) def member(user)
if project_members.loaded? if project_members.loaded?
project_members.find { |member| member.user_id == user.id } project_members.find { |member| member.user_id == user.id }
else else
project_members.find_by(user_id: user) project_members.find_by(user_id: user)
end end
end end
alias_method :resource_member, :project_member
def membership_locked? def membership_locked?
false false

View File

@ -1336,7 +1336,7 @@ class User < ApplicationRecord
def can_leave_project?(project) def can_leave_project?(project)
project.namespace != namespace && project.namespace != namespace &&
project.project_member(self) project.member(self)
end end
def full_website_url def full_website_url

View File

@ -5,5 +5,9 @@ class MergeRequestSidebarBasicEntity < IssuableSidebarBasicEntity
expose :can_merge do |merge_request| expose :can_merge do |merge_request|
merge_request.can_be_merged_by?(current_user) merge_request.can_be_merged_by?(current_user)
end end
expose :can_update_merge_request do |merge_request|
current_user.can?(:update_merge_request, merge_request)
end
end end
end end

View File

@ -59,7 +59,9 @@ module MergeRequests
merge_request_activity_counter.track_users_review_requested(users: new_reviewers) merge_request_activity_counter.track_users_review_requested(users: new_reviewers)
merge_request_activity_counter.track_reviewers_changed_action(user: current_user) merge_request_activity_counter.track_reviewers_changed_action(user: current_user)
remove_attention_requested(merge_request, current_user) unless new_reviewers.include?(current_user)
remove_attention_requested(merge_request, current_user)
end
end end
def cleanup_environments(merge_request) def cleanup_environments(merge_request)

View File

@ -23,7 +23,9 @@ module MergeRequests
execute_assignees_hooks(merge_request, old_assignees) if options[:execute_hooks] execute_assignees_hooks(merge_request, old_assignees) if options[:execute_hooks]
remove_attention_requested(merge_request, current_user) unless new_assignees.include?(current_user)
remove_attention_requested(merge_request, current_user)
end
end end
private private

View File

@ -17,6 +17,7 @@ module MergeRequests
reset_approvals_cache(merge_request) reset_approvals_cache(merge_request)
create_note(merge_request) create_note(merge_request)
merge_request_activity_counter.track_unapprove_mr_action(user: current_user) merge_request_activity_counter.track_unapprove_mr_action(user: current_user)
remove_attention_requested(merge_request, current_user)
end end
success success

View File

@ -43,13 +43,9 @@ module ResourceAccessTokens
def find_member def find_member
strong_memoize(:member) do strong_memoize(:member) do
if resource.is_a?(Project) next false unless resource.is_a?(Project) || resource.is_a?(Group)
resource.project_member(bot_user)
elsif resource.is_a?(Group) resource.member(bot_user)
resource.group_member(bot_user)
else
false
end
end end
end end

View File

@ -0,0 +1,8 @@
- return unless registration_features_can_be_prompted?
.form-group
= f.label :disabled_ip_restriction_ranges, class: 'label-bold' do
= _('Allow access to the following IP addresses')
= f.text_field :disabled_ip_restriction_ranges, value: '', class: 'form-control', disabled: true
%span.form-text.text-muted
= render 'shared/registration_features_discovery_message'

View File

@ -31,6 +31,7 @@
= render 'groups/settings/project_access_token_creation', f: f, group: @group = render 'groups/settings/project_access_token_creation', f: f, group: @group
= render_if_exists 'groups/settings/delayed_project_removal', f: f, group: @group = render_if_exists 'groups/settings/delayed_project_removal', f: f, group: @group
= render 'groups/settings/ip_restriction_registration_features_cta', f: f
= render_if_exists 'groups/settings/ip_restriction', f: f, group: @group = render_if_exists 'groups/settings/ip_restriction', f: f, group: @group
= render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group = render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group
= render 'groups/settings/lfs', f: f = render 'groups/settings/lfs', f: f

View File

@ -12,7 +12,7 @@
= _('Add a GPG key') = _('Add a GPG key')
%p.profile-settings-content %p.profile-settings-content
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/repository/gpg_signed_commits/index.md') } - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/repository/gpg_signed_commits/index.md') }
= _('Before you can add a GPG key you need to %{help_link_start}Generate it.%{help_link_end}'.html_safe) % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe } = _('Add a GPG key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}').html_safe % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
= render 'form' = render 'form'
%hr %hr
%h5 %h5

View File

@ -5,8 +5,8 @@
.form-group .form-group
= f.label :key, s_('Profiles|Key'), class: 'label-bold' = f.label :key, s_('Profiles|Key'), class: 'label-bold'
%p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity.") = f.text_area :key, class: "form-control gl-form-input js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true
= f.text_area :key, class: "form-control gl-form-input js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-ed25519 …" or "ssh-rsa …"') %p.form-text.text-muted= s_('Profiles|Begins with %{ssh_key_algorithms}.') % { ssh_key_algorithms: ssh_key_allowed_algorithms }
.form-row .form-row
.col.form-group .col.form-group
= f.label :title, _('Title'), class: 'label-bold' = f.label :title, _('Title'), class: 'label-bold'

View File

@ -11,11 +11,8 @@
%h5.gl-mt-0 %h5.gl-mt-0
= _('Add an SSH key') = _('Add an SSH key')
%p.profile-settings-content %p.profile-settings-content
- generate_link_url = help_page_path("ssh/index", anchor: 'generate-an-ssh-key-pair') - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('ssh/index.md') }
- existing_link_url = help_page_path("ssh/index", anchor: 'see-if-you-have-an-existing-ssh-key-pair') = _('Add an SSH key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}').html_safe % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
- generate_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: generate_link_url }
- existing_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: existing_link_url }
= _('To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}.').html_safe % { generate_link_start: generate_link_start, existing_link_start: existing_link_start, link_end: '</a>'.html_safe }
= render 'form' = render 'form'
%hr %hr
%h5 %h5

View File

@ -55,7 +55,7 @@
- else - else
%span.token-never-expires-label= _('Never') %span.token-never-expires-label= _('Never')
- if project - if project
%td= project.project_member(token.user).human_access %td= project.member(token.user).human_access
%td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: "gl-button btn btn-danger btn-sm float-right qa-revoke-button #{'btn-danger-secondary' unless token.expires?}", data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } } %td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: "gl-button btn btn-danger btn-sm float-right qa-revoke-button #{'btn-danger-secondary' unless token.expires?}", data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
- else - else
.settings-message.text-center .settings-message.text-center

View File

@ -0,0 +1,8 @@
---
name: ci_skip_require_credit_card_for_addon_ci_minutes
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77829
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349841
milestone: '14.7'
type: development
group: group::fulfillment
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: ci_use_variables_builder_definitions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75254
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349049
milestone: '14.7'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: trigger_job_retry_action
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77951
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349966
milestone: '14.7'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -2941,6 +2941,27 @@ Input type: `IssueSetEpicInput`
| <a id="mutationissuesetepicerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationissuesetepicerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationissuesetepicissue"></a>`issue` | [`Issue`](#issue) | Issue after mutation. | | <a id="mutationissuesetepicissue"></a>`issue` | [`Issue`](#issue) | Issue after mutation. |
### `Mutation.issueSetEscalationPolicy`
Input type: `IssueSetEscalationPolicyInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationissuesetescalationpolicyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationissuesetescalationpolicyescalationpolicyid"></a>`escalationPolicyId` | [`IncidentManagementEscalationPolicyID`](#incidentmanagementescalationpolicyid) | Global ID of the escalation policy to assign to the issue. Policy will be removed if absent or set to null. |
| <a id="mutationissuesetescalationpolicyiid"></a>`iid` | [`String!`](#string) | IID of the issue to mutate. |
| <a id="mutationissuesetescalationpolicyprojectpath"></a>`projectPath` | [`ID!`](#id) | Project the issue to mutate is in. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationissuesetescalationpolicyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationissuesetescalationpolicyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationissuesetescalationpolicyissue"></a>`issue` | [`Issue`](#issue) | Issue after mutation. |
### `Mutation.issueSetIteration` ### `Mutation.issueSetIteration`
Input type: `IssueSetIterationInput` Input type: `IssueSetIterationInput`
@ -10294,6 +10315,7 @@ Relationship between an epic and an issue.
| <a id="epicissueemailsdisabled"></a>`emailsDisabled` | [`Boolean!`](#boolean) | Indicates if a project has email notifications disabled: `true` if email notifications are disabled. | | <a id="epicissueemailsdisabled"></a>`emailsDisabled` | [`Boolean!`](#boolean) | Indicates if a project has email notifications disabled: `true` if email notifications are disabled. |
| <a id="epicissueepic"></a>`epic` | [`Epic`](#epic) | Epic to which this issue belongs. | | <a id="epicissueepic"></a>`epic` | [`Epic`](#epic) | Epic to which this issue belongs. |
| <a id="epicissueepicissueid"></a>`epicIssueId` | [`ID!`](#id) | ID of the epic-issue relation. | | <a id="epicissueepicissueid"></a>`epicIssueId` | [`ID!`](#id) | ID of the epic-issue relation. |
| <a id="epicissueescalationpolicy"></a>`escalationPolicy` | [`EscalationPolicyType`](#escalationpolicytype) | Escalation policy associated with the issue. Available for issues which support escalation. |
| <a id="epicissuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | Current health status. | | <a id="epicissuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | Current health status. |
| <a id="epicissuehidden"></a>`hidden` | [`Boolean`](#boolean) | Indicates the issue is hidden because the author has been banned. Will always return `null` if `ban_user_feature_flag` feature flag is disabled. | | <a id="epicissuehidden"></a>`hidden` | [`Boolean`](#boolean) | Indicates the issue is hidden because the author has been banned. Will always return `null` if `ban_user_feature_flag` feature flag is disabled. |
| <a id="epicissuehumantimeestimate"></a>`humanTimeEstimate` | [`String`](#string) | Human-readable time estimate of the issue. | | <a id="epicissuehumantimeestimate"></a>`humanTimeEstimate` | [`String`](#string) | Human-readable time estimate of the issue. |
@ -11476,6 +11498,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="issueduedate"></a>`dueDate` | [`Time`](#time) | Due date of the issue. | | <a id="issueduedate"></a>`dueDate` | [`Time`](#time) | Due date of the issue. |
| <a id="issueemailsdisabled"></a>`emailsDisabled` | [`Boolean!`](#boolean) | Indicates if a project has email notifications disabled: `true` if email notifications are disabled. | | <a id="issueemailsdisabled"></a>`emailsDisabled` | [`Boolean!`](#boolean) | Indicates if a project has email notifications disabled: `true` if email notifications are disabled. |
| <a id="issueepic"></a>`epic` | [`Epic`](#epic) | Epic to which this issue belongs. | | <a id="issueepic"></a>`epic` | [`Epic`](#epic) | Epic to which this issue belongs. |
| <a id="issueescalationpolicy"></a>`escalationPolicy` | [`EscalationPolicyType`](#escalationpolicytype) | Escalation policy associated with the issue. Available for issues which support escalation. |
| <a id="issuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | Current health status. | | <a id="issuehealthstatus"></a>`healthStatus` | [`HealthStatus`](#healthstatus) | Current health status. |
| <a id="issuehidden"></a>`hidden` | [`Boolean`](#boolean) | Indicates the issue is hidden because the author has been banned. Will always return `null` if `ban_user_feature_flag` feature flag is disabled. | | <a id="issuehidden"></a>`hidden` | [`Boolean`](#boolean) | Indicates the issue is hidden because the author has been banned. Will always return `null` if `ban_user_feature_flag` feature flag is disabled. |
| <a id="issuehumantimeestimate"></a>`humanTimeEstimate` | [`String`](#string) | Human-readable time estimate of the issue. | | <a id="issuehumantimeestimate"></a>`humanTimeEstimate` | [`String`](#string) | Human-readable time estimate of the issue. |

View File

@ -8,7 +8,7 @@ module API
if options[:project_members] if options[:project_members]
options[:project_members].find { |member| member.source_id == project.id } options[:project_members].find { |member| member.source_id == project.id }
else else
project.project_member(options[:current_user]) project.member(options[:current_user])
end end
end end

View File

@ -4,7 +4,7 @@ module API
module Entities module Entities
class ResourceAccessToken < Entities::PersonalAccessToken class ResourceAccessToken < Entities::PersonalAccessToken
expose :access_level do |token, options| expose :access_level do |token, options|
options[:resource].resource_member(token.user).access_level options[:resource].member(token.user).access_level
end end
end end
end end

View File

@ -13,12 +13,76 @@ module Gitlab
def scoped_variables(job, environment:, dependencies:) def scoped_variables(job, environment:, dependencies:)
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.concat(predefined_variables(job)) variables.concat(predefined_variables(job))
next variables unless pipeline.use_variables_builder_definitions?
variables.concat(project.predefined_variables)
variables.concat(pipeline.predefined_variables)
variables.concat(job.runner.predefined_variables) if job.runnable? && job.runner
variables.concat(kubernetes_variables(job))
variables.concat(deployment_variables(environment: environment, job: job))
variables.concat(job.yaml_variables)
variables.concat(user_variables(job.user))
variables.concat(job.dependency_variables) if dependencies
variables.concat(secret_instance_variables(ref: job.git_ref))
variables.concat(secret_group_variables(environment: environment, ref: job.git_ref))
variables.concat(secret_project_variables(environment: environment, ref: job.git_ref))
variables.concat(job.trigger_request.user_variables) if job.trigger_request
variables.concat(pipeline.variables)
variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
end end
end end
def kubernetes_variables(job)
::Gitlab::Ci::Variables::Collection.new.tap do |collection|
# Should get merged with the cluster kubeconfig in deployment_variables, see
# https://gitlab.com/gitlab-org/gitlab/-/issues/335089
template = ::Ci::GenerateKubeconfigService.new(job).execute
if template.valid?
collection.append(key: 'KUBECONFIG', value: template.to_yaml, public: false, file: true)
end
end
end
def deployment_variables(environment:, job:)
return [] unless environment
project.deployment_variables(
environment: environment,
kubernetes_namespace: job.expanded_kubernetes_namespace
)
end
def user_variables(user)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables if user.blank?
variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
variables.append(key: 'GITLAB_USER_NAME', value: user.name)
end
end
def secret_instance_variables(ref:)
project.ci_instance_variables_for(ref: ref)
end
def secret_group_variables(environment:, ref:)
return [] unless project.group
project.group.ci_variables_for(ref, project, environment: environment)
end
def secret_project_variables(environment:, ref:)
project.ci_variables_for(ref: ref, environment: environment)
end
private private
attr_reader :pipeline attr_reader :pipeline
delegate :project, to: :pipeline
def predefined_variables(job) def predefined_variables(job)
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|

View File

@ -2,13 +2,15 @@
module Gitlab module Gitlab
class SSHPublicKey class SSHPublicKey
Technology = Struct.new(:name, :key_class, :supported_sizes) Technology = Struct.new(:name, :key_class, :supported_sizes, :supported_algorithms)
# See https://man.openbsd.org/sshd#AUTHORIZED_KEYS_FILE_FORMAT for the list of
# supported algorithms.
TECHNOLOGIES = [ TECHNOLOGIES = [
Technology.new(:rsa, OpenSSL::PKey::RSA, [1024, 2048, 3072, 4096]), Technology.new(:rsa, OpenSSL::PKey::RSA, [1024, 2048, 3072, 4096], %w(ssh-rsa)),
Technology.new(:dsa, OpenSSL::PKey::DSA, [1024, 2048, 3072]), Technology.new(:dsa, OpenSSL::PKey::DSA, [1024, 2048, 3072], %w(ssh-dss)),
Technology.new(:ecdsa, OpenSSL::PKey::EC, [256, 384, 521]), Technology.new(:ecdsa, OpenSSL::PKey::EC, [256, 384, 521], %w(ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521)),
Technology.new(:ed25519, Net::SSH::Authentication::ED25519::PubKey, [256]) Technology.new(:ed25519, Net::SSH::Authentication::ED25519::PubKey, [256], %w(ssh-ed25519))
].freeze ].freeze
def self.technology(name) def self.technology(name)
@ -24,7 +26,15 @@ module Gitlab
end end
def self.supported_sizes(name) def self.supported_sizes(name)
technology(name)&.supported_sizes technology(name).supported_sizes
end
def self.supported_algorithms
TECHNOLOGIES.flat_map { |tech| tech.supported_algorithms }
end
def self.supported_algorithms_for_name(name)
technology(name).supported_algorithms
end end
def self.sanitize(key_content) def self.sanitize(key_content)

View File

@ -179,7 +179,7 @@ namespace :gitlab do
task reindex: :environment do task reindex: :environment do
unless Gitlab::Database::Reindexing.enabled? unless Gitlab::Database::Reindexing.enabled?
puts "This feature (database_reindexing) is currently disabled.".color(:yellow) puts "This feature (database_reindexing) is currently disabled.".color(:yellow)
next exit
end end
Gitlab::Database::Reindexing.invoke Gitlab::Database::Reindexing.invoke
@ -193,7 +193,7 @@ namespace :gitlab do
task database_name => :environment do task database_name => :environment do
unless Gitlab::Database::Reindexing.enabled? unless Gitlab::Database::Reindexing.enabled?
puts "This feature (database_reindexing) is currently disabled.".color(:yellow) puts "This feature (database_reindexing) is currently disabled.".color(:yellow)
next exit
end end
Gitlab::Database::Reindexing.invoke(database_name) Gitlab::Database::Reindexing.invoke(database_name)

View File

@ -2009,6 +2009,9 @@ msgstr ""
msgid "Add a GPG key" msgid "Add a GPG key"
msgstr "" msgstr ""
msgid "Add a GPG key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}"
msgstr ""
msgid "Add a Jaeger URL to replace this page with a link to your Jaeger server. You first need to %{link_start_tag}install Jaeger%{link_end_tag}." msgid "Add a Jaeger URL to replace this page with a link to your Jaeger server. You first need to %{link_start_tag}install Jaeger%{link_end_tag}."
msgstr "" msgstr ""
@ -2075,6 +2078,9 @@ msgstr ""
msgid "Add an SSH key" msgid "Add an SSH key"
msgstr "" msgstr ""
msgid "Add an SSH key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}"
msgstr ""
msgid "Add an existing issue" msgid "Add an existing issue"
msgstr "" msgstr ""
@ -4620,6 +4626,9 @@ msgstr ""
msgid "Are you sure you want to %{action} %{name}?" msgid "Are you sure you want to %{action} %{name}?"
msgstr "" msgstr ""
msgid "Are you sure you want to approve %{user}?"
msgstr ""
msgid "Are you sure you want to attempt to merge?" msgid "Are you sure you want to attempt to merge?"
msgstr "" msgstr ""
@ -5535,9 +5544,15 @@ msgstr ""
msgid "Billings|Your account has been validated" msgid "Billings|Your account has been validated"
msgstr "" msgstr ""
msgid "Billing|%{user} was successfully approved"
msgstr ""
msgid "Billing|An email address is only visible for users with public emails." msgid "Billing|An email address is only visible for users with public emails."
msgstr "" msgstr ""
msgid "Billing|An error occurred while approving %{user}"
msgstr ""
msgid "Billing|An error occurred while getting a billable member details" msgid "Billing|An error occurred while getting a billable member details"
msgstr "" msgstr ""
@ -9070,6 +9085,9 @@ msgstr ""
msgid "Confirm" msgid "Confirm"
msgstr "" msgstr ""
msgid "Confirm approval"
msgstr ""
msgid "Confirm new password" msgid "Confirm new password"
msgstr "" msgstr ""
@ -25702,9 +25720,6 @@ msgstr ""
msgid "Paste this DSN into your Sentry SDK" msgid "Paste this DSN into your Sentry SDK"
msgstr "" msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity."
msgstr ""
msgid "Patch to apply" msgid "Patch to apply"
msgstr "" msgstr ""
@ -27109,6 +27124,9 @@ msgstr ""
msgid "Profiles|Avatar will be removed. Are you sure?" msgid "Profiles|Avatar will be removed. Are you sure?"
msgstr "" msgstr ""
msgid "Profiles|Begins with %{ssh_key_algorithms}."
msgstr ""
msgid "Profiles|Bio" msgid "Profiles|Bio"
msgstr "" msgstr ""
@ -27358,9 +27376,6 @@ msgstr ""
msgid "Profiles|Type your %{confirmationValue} to confirm:" msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr "" msgstr ""
msgid "Profiles|Typically starts with \"ssh-ed25519 …\" or \"ssh-rsa …\""
msgstr ""
msgid "Profiles|Update profile settings" msgid "Profiles|Update profile settings"
msgstr "" msgstr ""
@ -37077,9 +37092,6 @@ msgstr ""
msgid "To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}" msgid "To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}"
msgstr "" msgstr ""
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr ""
msgid "To add the entry manually, provide the following details to the application on your phone." msgid "To add the entry manually, provide the following details to the application on your phone."
msgstr "" msgstr ""

View File

@ -30,6 +30,14 @@ RSpec.describe Import::GitlabController do
expect(session[:gitlab_access_token]).to eq(token) expect(session[:gitlab_access_token]).to eq(token)
expect(controller).to redirect_to(status_import_gitlab_url) expect(controller).to redirect_to(status_import_gitlab_url)
end end
it "importable_repos should return an array" do
allow_next_instance_of(Gitlab::GitlabImport::Client) do |instance|
allow(instance).to receive(:projects).and_return([{ "id": 1 }].to_enum)
end
expect(controller.send(:importable_repos)).to be_an_instance_of(Array)
end
end end
describe "GET status" do describe "GET status" do

View File

@ -7,12 +7,16 @@ import { mockCommit, mockJob } from '../mock_data';
describe('Bridge Sidebar', () => { describe('Bridge Sidebar', () => {
let wrapper; let wrapper;
const createComponent = (props) => { const createComponent = ({ featureFlag } = {}) => {
wrapper = shallowMount(BridgeSidebar, { wrapper = shallowMount(BridgeSidebar, {
provide: {
glFeatures: {
triggerJobRetryAction: featureFlag,
},
},
propsData: { propsData: {
bridgeJob: mockJob, bridgeJob: mockJob,
commit: mockCommit, commit: mockCommit,
...props,
}, },
}); });
}; };
@ -35,10 +39,6 @@ describe('Bridge Sidebar', () => {
expect(findJobTitle().text()).toBe(mockJob.name); expect(findJobTitle().text()).toBe(mockJob.name);
}); });
it('renders retry dropdown', () => {
expect(findRetryDropdown().exists()).toBe(true);
});
it('renders commit information', () => { it('renders commit information', () => {
expect(findCommitBlock().exists()).toBe(true); expect(findCommitBlock().exists()).toBe(true);
}); });
@ -57,4 +57,24 @@ describe('Bridge Sidebar', () => {
expect(wrapper.emitted('toggleSidebar')).toHaveLength(1); expect(wrapper.emitted('toggleSidebar')).toHaveLength(1);
}); });
}); });
describe('retry action', () => {
describe('when feature flag is ON', () => {
beforeEach(() => {
createComponent({ featureFlag: true });
});
it('renders retry dropdown', () => {
expect(findRetryDropdown().exists()).toBe(true);
});
});
describe('when feature flag is OFF', () => {
it('does not render retry dropdown', () => {
createComponent({ featureFlag: false });
expect(findRetryDropdown().exists()).toBe(false);
});
});
});
}); });

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe SshKeysHelper do
describe '#ssh_key_allowed_algorithms' do
it 'returns string with the names of allowed algorithms that are quoted and joined by commas' do
allowed_algorithms = Gitlab::CurrentSettings.allowed_key_types.flat_map do |ssh_key_type_name|
Gitlab::SSHPublicKey.supported_algorithms_for_name(ssh_key_type_name)
end
quoted_allowed_algorithms = allowed_algorithms.map { |name| "'#{name}'" }
expected_string = Gitlab::Utils.to_exclusive_sentence(quoted_allowed_algorithms)
expect(ssh_key_allowed_algorithms).to eq(expected_string)
end
it 'returns only allowed algorithms' do
expect(ssh_key_allowed_algorithms).to match('ed25519')
stub_application_setting(ed25519_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE)
expect(ssh_key_allowed_algorithms).not_to match('ed25519')
end
end
end

View File

@ -24,7 +24,7 @@ RSpec.describe Doorkeeper.configuration do
before do before do
allow(controller).to receive(:current_user).and_return(current_user) allow(controller).to receive(:current_user).and_return(current_user)
allow(controller).to receive(:session).and_return({}) allow(controller).to receive(:session).and_return({})
allow(controller).to receive(:request).and_return(OpenStruct.new(fullpath: '/return-path')) allow(controller).to receive(:request).and_return(double('request', fullpath: '/return-path'))
allow(controller).to receive(:redirect_to) allow(controller).to receive(:redirect_to)
allow(controller).to receive(:new_user_session_url).and_return('/login') allow(controller).to receive(:new_user_session_url).and_return('/login')
end end

View File

@ -3,25 +3,201 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Ci::Variables::Builder do RSpec.describe Gitlab::Ci::Variables::Builder do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:user) { project.owner }
let_it_be(:job) do
create(:ci_build,
pipeline: pipeline,
user: user,
yaml_variables: [{ key: 'YAML_VARIABLE', value: 'value' }]
)
end
let(:builder) { described_class.new(pipeline) } let(:builder) { described_class.new(pipeline) }
let(:pipeline) { create(:ci_pipeline) }
let(:job) { create(:ci_build, pipeline: pipeline) }
describe '#scoped_variables' do describe '#scoped_variables' do
let(:environment) { job.expanded_environment_name } let(:environment) { job.expanded_environment_name }
let(:dependencies) { true } let(:dependencies) { true }
let(:predefined_variables) do
[
{ key: 'CI_JOB_NAME',
value: job.name },
{ key: 'CI_JOB_STAGE',
value: job.stage },
{ key: 'CI_NODE_TOTAL',
value: '1' },
{ key: 'CI_BUILD_NAME',
value: job.name },
{ key: 'CI_BUILD_STAGE',
value: job.stage },
{ key: 'CI',
value: 'true' },
{ key: 'GITLAB_CI',
value: 'true' },
{ key: 'CI_SERVER_URL',
value: Gitlab.config.gitlab.url },
{ key: 'CI_SERVER_HOST',
value: Gitlab.config.gitlab.host },
{ key: 'CI_SERVER_PORT',
value: Gitlab.config.gitlab.port.to_s },
{ key: 'CI_SERVER_PROTOCOL',
value: Gitlab.config.gitlab.protocol },
{ key: 'CI_SERVER_NAME',
value: 'GitLab' },
{ key: 'CI_SERVER_VERSION',
value: Gitlab::VERSION },
{ key: 'CI_SERVER_VERSION_MAJOR',
value: Gitlab.version_info.major.to_s },
{ key: 'CI_SERVER_VERSION_MINOR',
value: Gitlab.version_info.minor.to_s },
{ key: 'CI_SERVER_VERSION_PATCH',
value: Gitlab.version_info.patch.to_s },
{ key: 'CI_SERVER_REVISION',
value: Gitlab.revision },
{ key: 'GITLAB_FEATURES',
value: project.licensed_features.join(',') },
{ key: 'CI_PROJECT_ID',
value: project.id.to_s },
{ key: 'CI_PROJECT_NAME',
value: project.path },
{ key: 'CI_PROJECT_TITLE',
value: project.title },
{ key: 'CI_PROJECT_PATH',
value: project.full_path },
{ key: 'CI_PROJECT_PATH_SLUG',
value: project.full_path_slug },
{ key: 'CI_PROJECT_NAMESPACE',
value: project.namespace.full_path },
{ key: 'CI_PROJECT_ROOT_NAMESPACE',
value: project.namespace.root_ancestor.path },
{ key: 'CI_PROJECT_URL',
value: project.web_url },
{ key: 'CI_PROJECT_VISIBILITY',
value: "private" },
{ key: 'CI_PROJECT_REPOSITORY_LANGUAGES',
value: project.repository_languages.map(&:name).join(',').downcase },
{ key: 'CI_PROJECT_CLASSIFICATION_LABEL',
value: project.external_authorization_classification_label },
{ key: 'CI_DEFAULT_BRANCH',
value: project.default_branch },
{ key: 'CI_CONFIG_PATH',
value: project.ci_config_path_or_default },
{ key: 'CI_PAGES_DOMAIN',
value: Gitlab.config.pages.host },
{ key: 'CI_PAGES_URL',
value: project.pages_url },
{ key: 'CI_API_V4_URL',
value: API::Helpers::Version.new('v4').root_url },
{ key: 'CI_PIPELINE_IID',
value: pipeline.iid.to_s },
{ key: 'CI_PIPELINE_SOURCE',
value: pipeline.source },
{ key: 'CI_PIPELINE_CREATED_AT',
value: pipeline.created_at.iso8601 },
{ key: 'CI_COMMIT_SHA',
value: job.sha },
{ key: 'CI_COMMIT_SHORT_SHA',
value: job.short_sha },
{ key: 'CI_COMMIT_BEFORE_SHA',
value: job.before_sha },
{ key: 'CI_COMMIT_REF_NAME',
value: job.ref },
{ key: 'CI_COMMIT_REF_SLUG',
value: job.ref_slug },
{ key: 'CI_COMMIT_BRANCH',
value: job.ref },
{ key: 'CI_COMMIT_MESSAGE',
value: pipeline.git_commit_message },
{ key: 'CI_COMMIT_TITLE',
value: pipeline.git_commit_title },
{ key: 'CI_COMMIT_DESCRIPTION',
value: pipeline.git_commit_description },
{ key: 'CI_COMMIT_REF_PROTECTED',
value: (!!pipeline.protected_ref?).to_s },
{ key: 'CI_COMMIT_TIMESTAMP',
value: pipeline.git_commit_timestamp },
{ key: 'CI_COMMIT_AUTHOR',
value: pipeline.git_author_full_text },
{ key: 'CI_BUILD_REF',
value: job.sha },
{ key: 'CI_BUILD_BEFORE_SHA',
value: job.before_sha },
{ key: 'CI_BUILD_REF_NAME',
value: job.ref },
{ key: 'CI_BUILD_REF_SLUG',
value: job.ref_slug },
{ key: 'YAML_VARIABLE',
value: 'value' },
{ key: 'GITLAB_USER_ID',
value: user.id.to_s },
{ key: 'GITLAB_USER_EMAIL',
value: user.email },
{ key: 'GITLAB_USER_LOGIN',
value: user.username },
{ key: 'GITLAB_USER_NAME',
value: user.name }
].map { |var| var.merge(public: true, masked: false) }
end
subject { builder.scoped_variables(job, environment: environment, dependencies: dependencies) } subject { builder.scoped_variables(job, environment: environment, dependencies: dependencies) }
it 'returns the expected variables' do it { is_expected.to be_instance_of(Gitlab::Ci::Variables::Collection) }
keys = %w[CI_JOB_NAME
CI_JOB_STAGE
CI_NODE_TOTAL
CI_BUILD_NAME
CI_BUILD_STAGE]
subject.map { |env| env[:key] }.tap do |names| it { expect(subject.to_runner_variables).to eq(predefined_variables) }
expect(names).to include(*keys)
context 'variables ordering' do
def var(name, value)
{ key: name, value: value.to_s, public: true, masked: false }
end
before do
allow(builder).to receive(:predefined_variables) { [var('A', 1), var('B', 1)] }
allow(project).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] }
allow(pipeline).to receive(:predefined_variables) { [var('C', 3), var('D', 3)] }
allow(job).to receive(:runner) { double(predefined_variables: [var('D', 4), var('E', 4)]) }
allow(builder).to receive(:kubernetes_variables) { [var('E', 5), var('F', 5)] }
allow(builder).to receive(:deployment_variables) { [var('F', 6), var('G', 6)] }
allow(job).to receive(:yaml_variables) { [var('G', 7), var('H', 7)] }
allow(builder).to receive(:user_variables) { [var('H', 8), var('I', 8)] }
allow(job).to receive(:dependency_variables) { [var('I', 9), var('J', 9)] }
allow(builder).to receive(:secret_instance_variables) { [var('J', 10), var('K', 10)] }
allow(builder).to receive(:secret_group_variables) { [var('K', 11), var('L', 11)] }
allow(builder).to receive(:secret_project_variables) { [var('L', 12), var('M', 12)] }
allow(job).to receive(:trigger_request) { double(user_variables: [var('M', 13), var('N', 13)]) }
allow(pipeline).to receive(:variables) { [var('N', 14), var('O', 14)] }
allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('O', 15), var('P', 15)]) }
end
it 'returns variables in order depending on resource hierarchy' do
expect(subject.to_runner_variables).to eq(
[var('A', 1), var('B', 1),
var('B', 2), var('C', 2),
var('C', 3), var('D', 3),
var('D', 4), var('E', 4),
var('E', 5), var('F', 5),
var('F', 6), var('G', 6),
var('G', 7), var('H', 7),
var('H', 8), var('I', 8),
var('I', 9), var('J', 9),
var('J', 10), var('K', 10),
var('K', 11), var('L', 11),
var('L', 12), var('M', 12),
var('M', 13), var('N', 13),
var('N', 14), var('O', 14),
var('O', 15), var('P', 15)])
end
it 'overrides duplicate keys depending on resource hierarchy' do
expect(subject.to_hash).to match(
'A' => '1', 'B' => '2',
'C' => '3', 'D' => '4',
'E' => '5', 'F' => '6',
'G' => '7', 'H' => '8',
'I' => '9', 'J' => '10',
'K' => '11', 'L' => '12',
'M' => '13', 'N' => '14',
'O' => '15', 'P' => '15')
end end
end end
end end

View File

@ -28,7 +28,7 @@ RSpec.describe Gitlab::Database::Migrations::Runner do
allow(ActiveRecord::Migrator).to receive(:new) do |dir, _all_migrations, _schema_migration_class, version_to_migrate| allow(ActiveRecord::Migrator).to receive(:new) do |dir, _all_migrations, _schema_migration_class, version_to_migrate|
migrator = double(ActiveRecord::Migrator) migrator = double(ActiveRecord::Migrator)
expect(migrator).to receive(:run) do expect(migrator).to receive(:run) do
migration_runs << OpenStruct.new(dir: dir, version_to_migrate: version_to_migrate) migration_runs << double('migrator', dir: dir, version_to_migrate: version_to_migrate)
end end
migrator migrator
end end

View File

@ -39,14 +39,43 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true do
] ]
end end
subject { described_class.supported_sizes(name) }
with_them do with_them do
it { expect(described_class.supported_sizes(name)).to eq(sizes) } it { expect(described_class.supported_sizes(name)).to eq(sizes) }
it { expect(described_class.supported_sizes(name.to_s)).to eq(sizes) } it { expect(described_class.supported_sizes(name.to_s)).to eq(sizes) }
end end
end end
describe '.supported_algorithms' do
it 'returns all supported algorithms' do
expect(described_class.supported_algorithms).to eq(
%w(
ssh-rsa
ssh-dss
ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521
ssh-ed25519
)
)
end
end
describe '.supported_algorithms_for_name' do
where(:name, :algorithms) do
[
[:rsa, %w(ssh-rsa)],
[:dsa, %w(ssh-dss)],
[:ecdsa, %w(ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521)],
[:ed25519, %w(ssh-ed25519)]
]
end
with_them do
it "returns all supported algorithms for #{params[:name]}" do
expect(described_class.supported_algorithms_for_name(name)).to eq(algorithms)
expect(described_class.supported_algorithms_for_name(name.to_s)).to eq(algorithms)
end
end
end
describe '.sanitize(key_content)' do describe '.sanitize(key_content)' do
let(:content) { build(:key).key } let(:content) { build(:key).key }

View File

@ -2824,7 +2824,7 @@ RSpec.describe Ci::Build do
allow(build).to receive(:dependency_variables) { [job_dependency_var] } allow(build).to receive(:dependency_variables) { [job_dependency_var] }
allow(build).to receive(:dependency_proxy_variables) { [dependency_proxy_var] } allow(build).to receive(:dependency_proxy_variables) { [dependency_proxy_var] }
allow(build.project) allow(build.pipeline.project)
.to receive(:predefined_variables) { [project_pre_var] } .to receive(:predefined_variables) { [project_pre_var] }
project.variables.create!(key: 'secret', value: 'value') project.variables.create!(key: 'secret', value: 'value')
@ -3124,7 +3124,7 @@ RSpec.describe Ci::Build do
context 'when the branch is protected' do context 'when the branch is protected' do
before do before do
allow(build.project).to receive(:protected_for?).with(ref).and_return(true) allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end end
it { is_expected.to include(protected_variable) } it { is_expected.to include(protected_variable) }
@ -3132,7 +3132,7 @@ RSpec.describe Ci::Build do
context 'when the tag is protected' do context 'when the tag is protected' do
before do before do
allow(build.project).to receive(:protected_for?).with(ref).and_return(true) allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end end
it { is_expected.to include(protected_variable) } it { is_expected.to include(protected_variable) }
@ -3171,7 +3171,7 @@ RSpec.describe Ci::Build do
context 'when the branch is protected' do context 'when the branch is protected' do
before do before do
allow(build.project).to receive(:protected_for?).with(ref).and_return(true) allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end end
it { is_expected.to include(protected_variable) } it { is_expected.to include(protected_variable) }
@ -3179,7 +3179,7 @@ RSpec.describe Ci::Build do
context 'when the tag is protected' do context 'when the tag is protected' do
before do before do
allow(build.project).to receive(:protected_for?).with(ref).and_return(true) allow(build.pipeline.project).to receive(:protected_for?).with(ref).and_return(true)
end end
it { is_expected.to include(protected_variable) } it { is_expected.to include(protected_variable) }
@ -3566,6 +3566,20 @@ RSpec.describe Ci::Build do
build.scoped_variables build.scoped_variables
end end
context 'when variables builder is used' do
it 'returns the same variables' do
build.user = create(:user)
allow(build.pipeline).to receive(:use_variables_builder_definitions?).and_return(false)
legacy_variables = build.scoped_variables.to_hash
allow(build.pipeline).to receive(:use_variables_builder_definitions?).and_return(true)
new_variables = build.scoped_variables.to_hash
expect(new_variables).to eq(legacy_variables)
end
end
end end
describe '#simple_variables_without_dependencies' do describe '#simple_variables_without_dependencies' do
@ -3578,7 +3592,8 @@ RSpec.describe Ci::Build do
shared_examples "secret CI variables" do shared_examples "secret CI variables" do
context 'when ref is branch' do context 'when ref is branch' do
let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, ref: 'master', tag: false, pipeline: pipeline, project: project) }
context 'when ref is protected' do context 'when ref is protected' do
before do before do
@ -3594,7 +3609,8 @@ RSpec.describe Ci::Build do
end end
context 'when ref is tag' do context 'when ref is tag' do
let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, pipeline: pipeline, project: project) }
context 'when ref is protected' do context 'when ref is protected' do
before do before do
@ -3692,8 +3708,6 @@ RSpec.describe Ci::Build do
.and_return(project_variables) .and_return(project_variables)
end end
it { is_expected.to eq(project_variables) }
context 'environment is nil' do context 'environment is nil' do
let(:environment) { nil } let(:environment) { nil }
@ -3701,6 +3715,35 @@ RSpec.describe Ci::Build do
end end
end end
describe '#user_variables' do
subject { build.user_variables.to_hash }
context 'with user' do
let(:expected_variables) do
{
'GITLAB_USER_EMAIL' => user.email,
'GITLAB_USER_ID' => user.id.to_s,
'GITLAB_USER_LOGIN' => user.username,
'GITLAB_USER_NAME' => user.name
}
end
before do
build.user = user
end
it { is_expected.to eq(expected_variables) }
end
context 'without user' do
before do
expect(build).to receive(:user).and_return(nil)
end
it { is_expected.to be_empty }
end
end
describe '#any_unmet_prerequisites?' do describe '#any_unmet_prerequisites?' do
let(:build) { create(:ci_build, :created) } let(:build) { create(:ci_build, :created) }

View File

@ -425,7 +425,7 @@ RSpec.describe API::MavenPackages do
context 'internal project' do context 'internal project' do
before do before do
group.group_member(user).destroy! group.member(user).destroy!
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end end

View File

@ -244,7 +244,7 @@ RSpec.describe 'merge requests discussions' do
context 'when current_user role changes' do context 'when current_user role changes' do
before do before do
Members::UpdateService.new(owner, access_level: Gitlab::Access::GUEST).execute(project.project_member(user)) Members::UpdateService.new(owner, access_level: Gitlab::Access::GUEST).execute(project.member(user))
end end
it_behaves_like 'cache miss' do it_behaves_like 'cache miss' do

View File

@ -50,7 +50,7 @@ RSpec.shared_examples 'project access tokens available #create' do
expect(created_token.name).to eq(access_token_params[:name]) expect(created_token.name).to eq(access_token_params[:name])
expect(created_token.scopes).to eq(access_token_params[:scopes]) expect(created_token.scopes).to eq(access_token_params[:scopes])
expect(created_token.expires_at).to eq(access_token_params[:expires_at]) expect(created_token.expires_at).to eq(access_token_params[:expires_at])
expect(project.project_member(created_token.user).access_level).to eq(access_level) expect(project.member(created_token.user).access_level).to eq(access_level)
end end
it 'creates project bot user' do it 'creates project bot user' do

View File

@ -233,7 +233,7 @@ RSpec.shared_examples 'snippet visibility' do
project.update!(visibility_level: Gitlab::VisibilityLevel.level_value(project_visibility.to_s), snippets_access_level: feature_visibility) project.update!(visibility_level: Gitlab::VisibilityLevel.level_value(project_visibility.to_s), snippets_access_level: feature_visibility)
if user_type == :external if user_type == :external
member = project.project_member(external) member = project.member(external)
if project.private? if project.private?
project.add_developer(external) unless member project.add_developer(external) unless member

View File

@ -3,7 +3,7 @@
RSpec.shared_examples 'inherited access level as a member of entity' do RSpec.shared_examples 'inherited access level as a member of entity' do
let(:parent_entity) { create(:group) } let(:parent_entity) { create(:group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:member) { entity.is_a?(Group) ? entity.group_member(user) : entity.project_member(user) } let(:member) { entity.member(user) }
context 'with root parent_entity developer member' do context 'with root parent_entity developer member' do
before do before do
@ -49,7 +49,7 @@ RSpec.shared_examples 'inherited access level as a member of entity' do
entity.add_maintainer(non_member_user) entity.add_maintainer(non_member_user)
non_member = entity.is_a?(Group) ? entity.group_member(non_member_user) : entity.project_member(non_member_user) non_member = entity.member(non_member_user)
expect { non_member.update!(access_level: Gitlab::Access::GUEST) } expect { non_member.update!(access_level: Gitlab::Access::GUEST) }
.to change { non_member.reload.access_level } .to change { non_member.reload.access_level }

View File

@ -130,6 +130,12 @@ RSpec.shared_examples 'namespace traversal scopes' do
it { is_expected.to contain_exactly(group_2, nested_group_2, deep_nested_group_2) } it { is_expected.to contain_exactly(group_2, nested_group_2, deep_nested_group_2) }
end end
context 'with upto' do
subject { described_class.where(id: deep_nested_group_1).self_and_ancestors(upto: nested_group_1.id) }
it { is_expected.to contain_exactly(deep_nested_group_1) }
end
end end
describe '.self_and_ancestors' do describe '.self_and_ancestors' do

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
RSpec.shared_examples 'renders registration features prompt' do |disabled_field, feature_title|
it 'renders a placeholder input with registration features message' do
render
if disabled_field
expect(rendered).to have_field(disabled_field, disabled: true)
end
expect(rendered).to have_content(s_("RegistrationFeatures|Want to %{feature_title} for free?") % { feature_title: feature_title || s_('RegistrationFeatures|use this feature') })
expect(rendered).to have_link(s_('RegistrationFeatures|Registration Features Program'))
end
end
RSpec.shared_examples 'does not render registration features prompt' do |disabled_field, feature_title|
it 'does not render a placeholder input with registration features message' do
render
if disabled_field
expect(rendered).not_to have_field(disabled_field, disabled: true)
end
expect(rendered).not_to have_content(s_("RegistrationFeatures|Want to %{feature_title} for free?") % { feature_title: feature_title || s_('RegistrationFeatures|use this feature') })
expect(rendered).not_to have_link(s_('RegistrationFeatures|Registration Features Program'))
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
SystemExitDetected = Class.new(RuntimeError)
RSpec.configure do |config|
config.around do |example|
example.run
rescue SystemExit
# In any cases, we cannot raise SystemExit in the tests,
# because it'll skip any following tests from running.
# Convert it to something that won't skip everything.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/350060
raise SystemExitDetected, "SystemExit should be rescued in the tests!"
end
end

View File

@ -214,7 +214,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
expect(Gitlab::Database::Reindexing).to receive(:enabled?).and_return(false) expect(Gitlab::Database::Reindexing).to receive(:enabled?).and_return(false)
expect(Gitlab::Database::Reindexing).not_to receive(:invoke) expect(Gitlab::Database::Reindexing).not_to receive(:invoke)
run_rake_task('gitlab:db:reindex') expect { run_rake_task('gitlab:db:reindex') }.to raise_error(SystemExit)
end end
end end
end end
@ -233,7 +233,7 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
expect(Gitlab::Database::Reindexing).to receive(:enabled?).and_return(false) expect(Gitlab::Database::Reindexing).to receive(:enabled?).and_return(false)
expect(Gitlab::Database::Reindexing).not_to receive(:invoke).with(database_name) expect(Gitlab::Database::Reindexing).not_to receive(:invoke).with(database_name)
run_rake_task("gitlab:db:reindex:#{database_name}") expect { run_rake_task("gitlab:db:reindex:#{database_name}") }.to raise_error(SystemExit)
end end
end end
end end

View File

@ -115,4 +115,40 @@ RSpec.describe 'groups/edit.html.haml' do
end end
end end
end end
context 'ip_restriction' do
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
group.add_owner(user)
assign(:group, group)
allow(view).to receive(:current_user) { user }
end
context 'prompt user about registration features' do
before do
if Gitlab.ee?
allow(License).to receive(:current).and_return(nil)
end
end
context 'with service ping disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
end
it_behaves_like 'renders registration features prompt', :group_disabled_ip_restriction_ranges
end
context 'with service ping enabled' do
before do
stub_application_setting(usage_ping_enabled: true)
end
it_behaves_like 'does not render registration features prompt', :group_disabled_ip_restriction_ranges
end
end
end
end end

View File

@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'profiles/keys/_form.html.haml' do RSpec.describe 'profiles/keys/_form.html.haml' do
include SshKeysHelper
let_it_be(:key) { Key.new } let_it_be(:key) { Key.new }
let(:page) { Capybara::Node::Simple.new(rendered) } let(:page) { Capybara::Node::Simple.new(rendered) }
@ -23,8 +25,8 @@ RSpec.describe 'profiles/keys/_form.html.haml' do
end end
it 'has the key field', :aggregate_failures do it 'has the key field', :aggregate_failures do
expect(rendered).to have_field('Key', type: 'textarea', placeholder: 'Typically starts with "ssh-ed25519 …" or "ssh-rsa …"') expect(rendered).to have_field('Key', type: 'textarea')
expect(rendered).to have_text("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity.") expect(rendered).to have_text(s_('Profiles|Begins with %{ssh_key_algorithms}.') % { ssh_key_algorithms: ssh_key_allowed_algorithms })
end end
it 'has the title field', :aggregate_failures do it 'has the title field', :aggregate_failures do