Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-03-30 00:09:03 +00:00
parent ba537a9b5c
commit b72218d98e
51 changed files with 903 additions and 132 deletions

View file

@ -41,6 +41,7 @@
key:
files:
- GITALY_SERVER_VERSION
- lib/gitlab/setup_helper.rb
prefix: "gitaly-binaries-${DEBIAN-VERSION}"
paths:
- tmp/tests/gitaly/_build/bin/

View file

@ -161,6 +161,7 @@
.gitaly-patterns: &gitaly-patterns
- "GITALY_SERVER_VERSION"
- "lib/gitlab/setup_helper.rb"
.workhorse-patterns: &workhorse-patterns
- "GITLAB_WORKHORSE_VERSION"
@ -274,7 +275,9 @@
- "lib/gitlab/markdown_cache/active_record/**/*"
- "config/prometheus/common_metrics.yml" # Used by Gitlab::DatabaseImporters::CommonMetrics::Importer
- "{,ee/,jh/}app/models/project_statistics.rb" # Used to calculate sizes in migration specs
- "GITALY_SERVER_VERSION" # Has interactions with background migrations:https://gitlab.com/gitlab-org/gitlab/-/issues/336538
# Gitaly has interactions with background migrations: https://gitlab.com/gitlab-org/gitlab/-/issues/336538
- "GITALY_SERVER_VERSION"
- "lib/gitlab/setup_helper.rb"
# CI changes
- ".gitlab-ci.yml"
- ".gitlab/ci/**/*"

View file

@ -82,7 +82,9 @@ export default {
return 'bottom';
},
},
docsPage: helpPagePath('development/code_review.html'),
docsPage: helpPagePath('user/project/merge_requests/index.md', {
anchor: 'request-attention-to-a-merge-request',
}),
};
</script>

View file

@ -8,9 +8,6 @@ import { I18N_AGENT_TOKEN } from '../constants';
export default {
i18n: I18N_AGENT_TOKEN,
basicInstallPath: helpPagePath('user/clusters/agent/install/index', {
anchor: 'install-the-agent-into-the-cluster',
}),
advancedInstallPath: helpPagePath('user/clusters/agent/install/index', {
anchor: 'advanced-installation',
}),
@ -43,27 +40,7 @@ export default {
<template>
<div>
<p>
<strong>{{ $options.i18n.tokenTitle }}</strong>
</p>
<p>
<gl-sprintf :message="$options.i18n.tokenBody">
<template #link="{ content }">
<gl-link :href="$options.basicInstallPath" target="_blank"> {{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<p>
<gl-alert
:title="$options.i18n.tokenSingleUseWarningTitle"
variant="warning"
:dismissible="false"
>
{{ $options.i18n.tokenSingleUseWarningBody }}
</gl-alert>
</p>
<p class="gl-mb-3">{{ $options.i18n.tokenLabel }}</p>
<p>
<gl-form-input-group readonly :value="agentToken" :select-on-click="true">
@ -77,6 +54,14 @@ export default {
</gl-form-input-group>
</p>
<p>
{{ $options.i18n.tokenSubtitle }}
</p>
<gl-alert :dismissible="false" variant="warning" class="gl-mb-5">
{{ $options.i18n.tokenSingleUseWarningTitle }}
</gl-alert>
<p>
<strong>{{ $options.i18n.basicInstallTitle }}</strong>
</p>

View file

@ -90,26 +90,20 @@ export const I18N_AGENT_TABLE = {
export const I18N_AGENT_TOKEN = {
copyToken: s__('ClusterAgents|Copy token'),
copyCommand: s__('ClusterAgents|Copy command'),
tokenTitle: s__('ClusterAgents|Registration token'),
tokenBody: s__(
`ClusterAgents|The registration token will be used to connect the agent on your cluster to GitLab. %{linkStart}What are registration tokens?%{linkEnd}`,
),
tokenLabel: s__('ClusterAgents|Agent access token:'),
tokenSingleUseWarningTitle: s__(
'ClusterAgents|You cannot see this token again after you close this window.',
),
tokenSingleUseWarningBody: s__(
`ClusterAgents|The recommended installation method includes the token. If you want to follow the advanced installation method provided in the docs, make sure you save the token value before you close this window.`,
),
tokenSubtitle: s__('ClusterAgents|The agent uses the token to connect with GitLab.'),
basicInstallTitle: s__('ClusterAgents|Recommended installation method'),
basicInstallBody: __(
`Open a CLI and connect to the cluster you want to install the agent in. Use this installation method to minimize any manual steps. The token is already included in the command.`,
basicInstallBody: s__(
'ClusterAgents|From a terminal, connect to your cluster and run this command. The token is included.',
),
advancedInstallTitle: s__('ClusterAgents|Advanced installation methods'),
advancedInstallBody: s__(
'ClusterAgents|For the advanced installation method %{linkStart}see the documentation%{linkEnd}.',
'ClusterAgents|%{linkStart}View the documentation%{linkEnd} for advanced installation. Ensure you have your access token available.',
),
};
@ -118,7 +112,7 @@ export const I18N_AGENT_MODAL = {
close: __('Close'),
cancel: __('Cancel'),
modalTitle: s__('ClusterAgents|Connect a cluster through an agent'),
modalTitle: s__('ClusterAgents|Connect a Kubernetes cluster'),
modalBody: s__(
'ClusterAgents|Add an agent configuration file to %{linkStart}this repository%{linkEnd} and select it, or create a new one to register with GitLab:',
),
@ -127,11 +121,6 @@ export const I18N_AGENT_MODAL = {
),
altText: s__('ClusterAgents|GitLab Agent for Kubernetes'),
learnMoreLink: s__('ClusterAgents|How do I register an agent?'),
copyToken: s__('ClusterAgents|Copy token'),
tokenTitle: s__('ClusterAgents|Registration token'),
tokenBody: s__(
`ClusterAgents|The registration token will be used to connect the agent on your cluster to GitLab. %{linkStart}What are registration tokens?%{linkEnd}`,
),
registrationErrorTitle: s__('ClusterAgents|Failed to register an agent'),
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
};

View file

@ -385,7 +385,7 @@ export const fetchDashboardValidationWarnings = ({ state, dispatch, getters }) =
dashboardPath,
},
})
.then((resp) => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard)
.then((resp) => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard || undefined)
.then(({ schemaValidationWarnings } = {}) => {
const hasWarnings = schemaValidationWarnings && schemaValidationWarnings.length !== 0;
/**

View file

@ -7,10 +7,6 @@ class JiraConnect::EventsController < JiraConnect::ApplicationController
before_action :verify_asymmetric_atlassian_jwt!
def installed
unless Feature.enabled?(:jira_connect_installation_update, default_enabled: :yaml)
return head :ok if current_jira_installation
end
success = current_jira_installation ? update_installation : create_installation
if success

View file

@ -60,6 +60,18 @@ module DiffHelper
html.join.html_safe
end
def diff_nomappinginraw_line(line, first_line_num_class, second_line_num_class, content_line_class)
css_class = ''
css_class = 'old' if line.type == 'old-nomappinginraw'
css_class = 'new' if line.type == 'new-nomappinginraw'
html = [content_tag(:td, '', class: [*first_line_num_class, css_class])]
html << content_tag(:td, '', class: [*second_line_num_class, css_class]) if second_line_num_class
html << content_tag(:td, diff_line_content(line.rich_text), class: [*content_line_class, 'nomappinginraw', css_class])
html.join.html_safe
end
def diff_line_content(line)
if line.blank?
"&nbsp;".html_safe
@ -74,7 +86,7 @@ module DiffHelper
end
def diff_link_number(line_type, match, text)
line_type == match || text == 0 ? " " : text
line_type == match ? " " : text
end
def parallel_diff_discussions(left, right, diff_file)

View file

@ -32,10 +32,12 @@ module BulkImports
strong_memoize(:export_status) do
status = fetch_export_status
# Consider empty response as failed export
raise StandardError, 'Empty export status response' unless status&.present?
relation_export_status = status&.find { |item| item['relation'] == relation }
status.find { |item| item['relation'] == relation }
# Consider empty response as failed export
raise StandardError, 'Empty relation export status' unless relation_export_status&.present?
relation_export_status
end
rescue StandardError => e
{ 'status' => Export::FAILED, 'error' => e.message }

View file

@ -11,6 +11,8 @@ module Ci
end
scope :contains_any_of_namespaces, -> (ids) do
return none if ids.empty?
where('traversal_ids && ARRAY[?]::int[]', ids)
end

View file

@ -49,6 +49,33 @@ module Namespaces
before_commit :sync_traversal_ids, on: [:create], if: -> { sync_traversal_ids? }
end
class_methods do
# This method looks into a list of namespaces trying to optimise a returned traversal_ids
# into a list of shortest prefixes, due to fact that the shortest prefixes include all childrens.
# Example:
# INPUT: [[4909902], [4909902,51065789], [4909902,51065793], [7135830], [15599674, 1], [15599674, 1, 3], [15599674, 2]]
# RESULT: [[4909902], [7135830], [15599674, 1], [15599674, 2]]
def shortest_traversal_ids_prefixes
raise ArgumentError, 'Feature not supported since the `:use_traversal_ids` is disabled' unless use_traversal_ids?
prefixes = []
# The array needs to be sorted (O(nlogn)) to ensure shortest elements are always first
# This allows to do O(n) search of shortest prefixes
all_traversal_ids = all.order('namespaces.traversal_ids').pluck('namespaces.traversal_ids')
last_prefix = [nil]
all_traversal_ids.each do |traversal_ids|
next if last_prefix == traversal_ids[0..(last_prefix.count - 1)]
last_prefix = traversal_ids
prefixes << traversal_ids
end
prefixes
end
end
def sync_traversal_ids?
Feature.enabled?(:sync_traversal_ids, root_ancestor, default_enabled: :yaml)
end

View file

@ -4,9 +4,10 @@ class ProgrammingLanguage < ApplicationRecord
validates :name, presence: true
validates :color, allow_blank: false, color: true
# Returns all programming languages which match the given name (case
# Returns all programming languages which match any of the given names (case
# insensitively).
scope :with_name_case_insensitive, ->(name) do
where(arel_table[:name].matches(sanitize_sql_like(name)))
scope :with_name_case_insensitive, ->(*names) do
sanitized_names = names.map(&method(:sanitize_sql_like))
where(arel_table[:name].matches_any(sanitized_names))
end
end

View file

@ -1989,6 +1989,8 @@ class Project < ApplicationRecord
ProjectCacheWorker.perform_async(self.id, [], [:repository_size])
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(id)
enqueue_record_project_target_platforms
# The import assigns iid values on its own, e.g. by re-using GitHub ids.
# Flush existing InternalId records for this project for consistency reasons.
# Those records are going to be recreated with the next normal creation
@ -2848,6 +2850,13 @@ class Project < ApplicationRecord
group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self, default_enabled: :yaml)
end
def enqueue_record_project_target_platforms
return unless Gitlab.com?
return unless Feature.enabled?(:record_projects_target_platforms, self, default_enabled: :yaml)
Projects::RecordTargetPlatformsWorker.perform_async(id)
end
private
# overridden in EE

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class ProjectSetting < ApplicationRecord
ALLOWED_TARGET_PLATFORMS = %w(ios osx tvos watchos).freeze
belongs_to :project, inverse_of: :project_setting
enum squash_option: {
@ -14,6 +16,9 @@ class ProjectSetting < ApplicationRecord
validates :merge_commit_template, length: { maximum: Project::MAX_COMMIT_TEMPLATE_LENGTH }
validates :squash_commit_template, length: { maximum: Project::MAX_COMMIT_TEMPLATE_LENGTH }
validates :target_platforms, inclusion: { in: ALLOWED_TARGET_PLATFORMS }
validate :validates_mr_default_target_self
default_value_for(:legacy_open_source_license_available) do
Feature.enabled?(:legacy_open_source_license_available, default_enabled: :yaml, type: :ops)
@ -27,7 +32,9 @@ class ProjectSetting < ApplicationRecord
%w[always never].include?(squash_option)
end
validate :validates_mr_default_target_self
def target_platforms=(val)
super(val&.map(&:to_s)&.sort)
end
private

View file

@ -8,8 +8,8 @@ class RepositoryLanguage < ApplicationRecord
default_scope { includes(:programming_language) } # rubocop:disable Cop/DefaultScope
scope :with_programming_language, ->(name) do
joins(:programming_language).merge(ProgrammingLanguage.with_name_case_insensitive(name))
scope :with_programming_language, ->(*names) do
joins(:programming_language).merge(ProgrammingLanguage.with_name_case_insensitive(*names))
end
validates :project, presence: true

View file

@ -2244,9 +2244,23 @@ class User < ApplicationRecord
end
def ci_namespace_mirrors_for_group_members(level)
Ci::NamespaceMirror.contains_any_of_namespaces(
group_members.where('access_level >= ?', level).pluck(:source_id)
)
search_members = group_members.where('access_level >= ?', level)
# This reduces searched prefixes to only shortest ones
# to avoid querying descendants since they are already covered
# by ancestor namespaces. If the FF is not available fallback to
# inefficient search: https://gitlab.com/gitlab-org/gitlab/-/issues/336436
namespace_ids =
if Feature.enabled?(:use_traversal_ids, default_enabled: :yaml)
Group.joins(:all_group_members)
.merge(search_members)
.shortest_traversal_ids_prefixes
.map(&:last)
else
search_members.pluck(:source_id)
end
Ci::NamespaceMirror.contains_any_of_namespaces(namespace_ids)
end
end

View file

@ -24,6 +24,7 @@ module Git
enqueue_update_mrs
enqueue_detect_repository_languages
enqueue_record_project_target_platforms
execute_related_hooks
@ -53,6 +54,12 @@ module Git
DetectRepositoryLanguagesWorker.perform_async(project.id)
end
def enqueue_record_project_target_platforms
return unless default_branch?
project.enqueue_record_project_target_platforms
end
# Only stop environments if the ref is a branch that is being deleted
def stop_environments
return unless removing_branch?

View file

@ -0,0 +1,58 @@
# frozen_string_literal: true
module Projects
# Service class to detect target platforms of a project made for the Apple
# Ecosystem.
#
# This service searches project.pbxproj and *.xcconfig files (contains build
# settings) for the string "SDKROOT = <SDK_name>" where SDK_name can be
# 'iphoneos', 'macosx', 'appletvos' or 'watchos'. Currently, the service is
# intentionally limited (for performance reasons) to detect if a project
# targets iOS.
#
# Ref: https://developer.apple.com/documentation/xcode/build-settings-reference/
#
# Example usage:
# > AppleTargetPlatformDetectorService.new(a_project).execute
# => []
# > AppleTargetPlatformDetectorService.new(an_ios_project).execute
# => [:ios]
# > AppleTargetPlatformDetectorService.new(multiplatform_project).execute
# => [:ios, :osx, :tvos, :watchos]
class AppleTargetPlatformDetectorService < BaseService
BUILD_CONFIG_FILENAMES = %w(project.pbxproj *.xcconfig).freeze
# For the current iteration, we only want to detect when the project targets
# iOS. In the future, we can use the same logic to detect projects that
# target OSX, TvOS, and WatchOS platforms with SDK names 'macosx', 'appletvos',
# and 'watchos', respectively.
PLATFORM_SDK_NAMES = { ios: 'iphoneos' }.freeze
def execute
detect_platforms
end
private
def file_finder
@file_finder ||= ::Gitlab::FileFinder.new(project, project.default_branch)
end
def detect_platforms
# Return array of SDK names for which "SDKROOT = <sdk_name>" setting
# definition can be found in either project.pbxproj or *.xcconfig files.
PLATFORM_SDK_NAMES.select do |_, sdk|
config_files_containing_sdk_setting(sdk).present?
end.keys
end
# Return array of project.pbxproj and/or *.xcconfig files
# (Gitlab::Search::FoundBlob) that contain the setting definition string
# "SDKROOT = <sdk_name>"
def config_files_containing_sdk_setting(sdk)
BUILD_CONFIG_FILENAMES.map do |filename|
file_finder.find("SDKROOT = #{sdk} filename:#{filename}")
end.flatten
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
module Projects
class RecordTargetPlatformsService < BaseService
include Gitlab::Utils::StrongMemoize
def execute
record_target_platforms
end
private
def target_platforms
strong_memoize(:target_platforms) do
AppleTargetPlatformDetectorService.new(project).execute
end
end
def record_target_platforms
return unless target_platforms.present?
setting = ::ProjectSetting.find_or_initialize_by(project: project) # rubocop:disable CodeReuse/ActiveRecord
setting.target_platforms = target_platforms
setting.save
setting.target_platforms
end
end
end

View file

@ -10,6 +10,8 @@
- case line.type
- when 'match'
= diff_match_line line.old_pos, line.new_pos, text: line.text
- when 'old-nomappinginraw', 'new-nomappinginraw', 'unchanged-nomappinginraw'
= diff_nomappinginraw_line line, %w[old_line diff-line-num], %w[new_line diff-line-num], %w[line_content]
- when 'old-nonewline', 'new-nonewline'
%td.old_line.diff-line-num
%td.new_line.diff-line-num

View file

@ -11,6 +11,8 @@
- case left.type
- when 'match'
= diff_match_line left.old_pos, nil, text: left.text, view: :parallel
- when 'old-nomappinginraw', 'new-nomappinginraw', 'unchanged-nomappinginraw'
= diff_nomappinginraw_line left, %w[old_line diff-line-num], nil, %w[line_content parallel left-side]
- when 'old-nonewline', 'new-nonewline'
%td.old_line.diff-line-num
%td.line_content.match.left-side= left.text
@ -29,6 +31,8 @@
- case right.type
- when 'match'
= diff_match_line nil, right.new_pos, text: left.text, view: :parallel
- when 'old-nomappinginraw', 'new-nomappinginraw', 'unchanged-nomappinginraw'
= diff_nomappinginraw_line right, %w[new_line diff-line-num], nil, %w[line_content parallel right-side]
- when 'old-nonewline', 'new-nonewline'
%td.new_line.diff-line-num
%td.line_content.match.right-side= right.text

View file

@ -12,6 +12,8 @@
- case line.type
- when 'match'
= diff_match_line line.old_pos, line.new_pos, text: line.text
- when 'old-nomappinginraw', 'new-nomappinginraw', 'unchanged-nomappinginraw'
= diff_nomappinginraw_line line, %w[old_line diff-line-num], %w[new_line diff-line-num], %w[line_content]
- when 'old-nonewline', 'new-nonewline'
%td.old_line.diff-line-num
%td.new_line.diff-line-num

View file

@ -2812,6 +2812,15 @@
:weight: 1
:idempotent: true
:tags: []
- :name: projects_record_target_platforms
:worker_name: Projects::RecordTargetPlatformsWorker
:feature_category: :experimentation_activation
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: projects_refresh_build_artifacts_size_statistics
:worker_name: Projects::RefreshBuildArtifactsSizeStatisticsWorker
:feature_category: :build_artifacts

View file

@ -0,0 +1,55 @@
# frozen_string_literal: true
module Projects
class RecordTargetPlatformsWorker
include ApplicationWorker
include ExclusiveLeaseGuard
LEASE_TIMEOUT = 1.hour.to_i
APPLE_PLATFORM_LANGUAGES = %w(swift objective-c).freeze
feature_category :experimentation_activation
data_consistency :always
deduplicate :until_executed
urgency :low
idempotent!
def perform(project_id)
@project = Project.find_by_id(project_id)
return unless project
return unless uses_apple_platform_languages?
try_obtain_lease do
@target_platforms = Projects::RecordTargetPlatformsService.new(project).execute
log_target_platforms_metadata
end
end
private
attr_reader :target_platforms, :project
def uses_apple_platform_languages?
project.repository_languages.with_programming_language(*APPLE_PLATFORM_LANGUAGES).present?
end
def log_target_platforms_metadata
return unless target_platforms.present?
log_extra_metadata_on_done(:target_platforms, target_platforms)
end
def lease_key
@lease_key ||= "#{self.class.name.underscore}:#{project.id}"
end
def lease_timeout
LEASE_TIMEOUT
end
def lease_release?
false
end
end
end

View file

@ -1,8 +1,8 @@
---
name: jira_connect_installation_update
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83038
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/356083
milestone: '14.9'
name: record_projects_target_platforms
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80361
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354286
milestone: '14.10'
type: development
group: group::integrations
group: group::activation
default_enabled: false

View file

@ -361,6 +361,8 @@
- 1
- - projects_process_sync_events
- 1
- - projects_record_target_platforms
- 1
- - projects_refresh_build_artifacts_size_statistics
- 1
- - projects_schedule_bulk_repository_shard_moves

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddNotificationLevelToNamespaceRootStorageStatistics < Gitlab::Database::Migration[1.0]
enable_lock_retries!
def up
add_column :namespace_root_storage_statistics, :notification_level, :integer, limit: 2, default: 100, null: false
end
def down
remove_column :namespace_root_storage_statistics, :notification_level
end
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddTargetPlatformsToProjectSetting < Gitlab::Database::Migration[1.0]
def change
add_column :project_settings, :target_platforms, :string, array: true, default: [], null: false, if_not_exists: true
end
end

View file

@ -0,0 +1 @@
0bd78ce207cca13b5c4557ace87c135972ed69cd05ee8c6fcc60a9c060ba8b5f

View file

@ -0,0 +1 @@
19f25b2f373e7c2799812661baca1902c9c74df67f7a5e88116862fb078a5957

View file

@ -17331,7 +17331,8 @@ CREATE TABLE namespace_root_storage_statistics (
snippets_size bigint DEFAULT 0 NOT NULL,
pipeline_artifacts_size bigint DEFAULT 0 NOT NULL,
uploads_size bigint DEFAULT 0 NOT NULL,
dependency_proxy_size bigint DEFAULT 0 NOT NULL
dependency_proxy_size bigint DEFAULT 0 NOT NULL,
notification_level smallint DEFAULT 100 NOT NULL
);
CREATE TABLE namespace_settings (
@ -19371,6 +19372,7 @@ CREATE TABLE project_settings (
has_shimo boolean DEFAULT false NOT NULL,
squash_commit_template text,
legacy_open_source_license_available boolean DEFAULT true NOT NULL,
target_platforms character varying[] DEFAULT '{}'::character varying[] NOT NULL,
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
CONSTRAINT check_b09644994b CHECK ((char_length(squash_commit_template) <= 500)),
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL)),

View file

@ -9,8 +9,8 @@ module Gitlab
SERIALIZE_KEYS = %i(line_code rich_text text type index old_pos new_pos).freeze
attr_reader :marker_ranges
attr_writer :text, :rich_text, :discussable
attr_accessor :index, :type, :old_pos, :new_pos, :line_code
attr_writer :text, :rich_text
attr_accessor :index, :old_pos, :new_pos, :line_code, :type
def initialize(text, type, index, old_pos, new_pos, parent_file: nil, line_code: nil, rich_text: nil)
@text = text
@ -24,9 +24,7 @@ module Gitlab
# When line code is not provided from cache store we build it
# using the parent_file(Diff::File or Conflict::File).
@line_code = line_code || calculate_line_code
@marker_ranges = []
@discussable = true
end
def self.init_from_hash(hash)
@ -81,23 +79,28 @@ module Gitlab
end
def added?
%w[new new-nonewline].include?(type)
%w[new new-nonewline new-nomappinginraw].include?(type)
end
def removed?
%w[old old-nonewline].include?(type)
%w[old old-nonewline old-nomappinginraw].include?(type)
end
def meta?
%w[match new-nonewline old-nonewline].include?(type)
end
def has_mapping_in_raw?
# Used for rendered diff, when the displayed line doesn't have a matching line in the raw diff
!type&.ends_with?('nomappinginraw')
end
def match?
type == :match
end
def discussable?
@discussable && !meta?
has_mapping_in_raw? && !meta?
end
def suggestible?

View file

@ -44,7 +44,7 @@ module Gitlab
free_right_index = nil
i += 1
end
elsif line.meta? || line.unchanged?
elsif line.meta? || line.unchanged? || !line.has_mapping_in_raw?
# line in the right panel is the same as in the left one
lines << {
left: line,

View file

@ -87,10 +87,7 @@ module Gitlab
line.new_pos = removal_line_maps[line.old_pos] if line.new_pos == 0 && line.old_pos != 0
# Lines that do not appear on the original diff should not be commentable
unless addition_line_maps[line.new_pos] || removal_line_maps[line.old_pos]
line.discussable = false
end
line.type = "#{line.type || 'unchanged'}-nomappinginraw" unless addition_line_maps[line.new_pos] || removal_line_maps[line.old_pos]
line.line_code = line_code(line)
line
@ -113,8 +110,8 @@ module Gitlab
additions = {}
source_diff.highlighted_diff_lines.each do |line|
removals[line.old_pos] = line.new_pos
additions[line.new_pos] = line.old_pos
removals[line.old_pos] = line.new_pos unless source_diff.new_file?
additions[line.new_pos] = line.old_pos unless source_diff.deleted_file?
end
[removals, additions]

View file

@ -7807,6 +7807,9 @@ msgstr ""
msgid "Cluster type must be specified for Stages::ClusterEndpointInserter"
msgstr ""
msgid "ClusterAgents|%{linkStart}View the documentation%{linkEnd} for advanced installation. Ensure you have your access token available."
msgstr ""
msgid "ClusterAgents|%{name} successfully deleted"
msgstr ""
@ -7846,6 +7849,9 @@ msgstr ""
msgid "ClusterAgents|Agent %{strongStart}disconnected%{strongEnd}"
msgstr ""
msgid "ClusterAgents|Agent access token:"
msgstr ""
msgid "ClusterAgents|Agent might not be connected to GitLab"
msgstr ""
@ -7885,6 +7891,9 @@ msgstr ""
msgid "ClusterAgents|Configuration"
msgstr ""
msgid "ClusterAgents|Connect a Kubernetes cluster"
msgstr ""
msgid "ClusterAgents|Connect a cluster"
msgstr ""
@ -7897,9 +7906,6 @@ msgstr ""
msgid "ClusterAgents|Connect a cluster (deprecated)"
msgstr ""
msgid "ClusterAgents|Connect a cluster through an agent"
msgstr ""
msgid "ClusterAgents|Connect existing cluster"
msgstr ""
@ -7969,7 +7975,7 @@ msgstr ""
msgid "ClusterAgents|Failed to register an agent"
msgstr ""
msgid "ClusterAgents|For the advanced installation method %{linkStart}see the documentation%{linkEnd}."
msgid "ClusterAgents|From a terminal, connect to your cluster and run this command. The token is included."
msgstr ""
msgid "ClusterAgents|GitLab Agent"
@ -8032,9 +8038,6 @@ msgstr ""
msgid "ClusterAgents|Registering agent"
msgstr ""
msgid "ClusterAgents|Registration token"
msgstr ""
msgid "ClusterAgents|Requires a Maintainer or greater role to delete agents"
msgstr ""
@ -8065,15 +8068,12 @@ msgstr ""
msgid "ClusterAgents|The agent has not been connected in a long time. There might be a connectivity issue. Last contact was %{timeAgo}."
msgstr ""
msgid "ClusterAgents|The agent uses the token to connect with GitLab."
msgstr ""
msgid "ClusterAgents|The agent version do not match each other across your cluster's pods. This can happen when a new agent version was just deployed and Kubernetes is shutting down the old pods."
msgstr ""
msgid "ClusterAgents|The recommended installation method includes the token. If you want to follow the advanced installation method provided in the docs, make sure you save the token value before you close this window."
msgstr ""
msgid "ClusterAgents|The registration token will be used to connect the agent on your cluster to GitLab. %{linkStart}What are registration tokens?%{linkEnd}"
msgstr ""
msgid "ClusterAgents|There's no activity from the past day"
msgid_plural "ClusterAgents|There's no activity from the past %d days"
msgstr[0] ""
@ -26150,9 +26150,6 @@ msgstr ""
msgid "Open Selection"
msgstr ""
msgid "Open a CLI and connect to the cluster you want to install the agent in. Use this installation method to minimize any manual steps. The token is already included in the command."
msgstr ""
msgid "Open errors"
msgstr ""

View file

@ -114,17 +114,6 @@ RSpec.describe JiraConnect::EventsController do
base_url: base_url
)
end
context 'when the `jira_connect_installation_update` feature flag is disabled' do
before do
stub_feature_flags(jira_connect_installation_update: false)
end
it 'does not update the installation', :aggregate_failures do
expect { subject }.not_to change { installation.reload.attributes }
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'when the new base_url is invalid' do

View file

@ -27,17 +27,11 @@ RSpec.describe 'Multiple view Diffs', :js do
context 'when :rendered_diffs_viewer is off' do
context 'and diff does not have ipynb' do
include_examples "no multiple viewers", 'ddd0f15ae83993f5cb66a927a28673882e99100b'
it_behaves_like "no multiple viewers", 'ddd0f15ae83993f5cb66a927a28673882e99100b'
end
context 'and diff has ipynb' do
include_examples "no multiple viewers", '5d6ed1503801ca9dc28e95eeb85a7cf863527aee'
it 'shows the transformed diff' do
diff = page.find('.diff-file, .file-holder', match: :first)
expect(diff['innerHTML']).to include('%% Cell type:markdown id:0aac5da7-745c-4eda-847a-3d0d07a1bb9b tags:')
end
it_behaves_like "no multiple viewers", '5d6ed1503801ca9dc28e95eeb85a7cf863527aee'
end
end
@ -45,14 +39,28 @@ RSpec.describe 'Multiple view Diffs', :js do
let(:feature_flag_on) { true }
context 'and diff does not include ipynb' do
include_examples "no multiple viewers", 'ddd0f15ae83993f5cb66a927a28673882e99100b'
it_behaves_like "no multiple viewers", 'ddd0f15ae83993f5cb66a927a28673882e99100b'
context 'and in inline diff' do
let(:ref) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
it 'does not change display for non-ipynb' do
expect(page).to have_selector line_with_content('new', 1)
end
end
context 'and in parallel diff' do
let(:ref) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
it 'does not change display for non-ipynb' do
page.find('#parallel-diff-btn').click
expect(page).to have_selector line_with_content('new', 1)
end
end
end
context 'and opening a diff with ipynb' do
context 'but the changes are not renderable' do
include_examples "no multiple viewers", 'a867a602d2220e5891b310c07d174fbe12122830'
end
it 'loads the rendered diff as hidden' do
diff = page.find('.diff-file, .file-holder', match: :first)
@ -76,10 +84,55 @@ RSpec.describe 'Multiple view Diffs', :js do
expect(classes_for_element(diff, 'toHideBtn')).not_to include('selected')
expect(classes_for_element(diff, 'toShowBtn')).to include('selected')
end
it 'transforms the diff' do
diff = page.find('.diff-file, .file-holder', match: :first)
expect(diff['innerHTML']).to include('%% Cell type:markdown id:0aac5da7-745c-4eda-847a-3d0d07a1bb9b tags:')
end
context 'on parallel view' do
before do
page.find('#parallel-diff-btn').click
end
it 'lines without mapping cannot receive comments' do
expect(page).not_to have_selector('td.line_content.nomappinginraw ~ td.diff-line-num > .add-diff-note')
expect(page).to have_selector('td.line_content:not(.nomappinginraw) ~ td.diff-line-num > .add-diff-note')
end
it 'lines numbers without mapping are empty' do
expect(page).not_to have_selector('td.nomappinginraw + td.diff-line-num')
expect(page).to have_selector('td.nomappinginraw + td.diff-line-num', visible: false)
end
it 'transforms the diff' do
diff = page.find('.diff-file, .file-holder', match: :first)
expect(diff['innerHTML']).to include('%% Cell type:markdown id:0aac5da7-745c-4eda-847a-3d0d07a1bb9b tags:')
end
end
context 'on inline view' do
it 'lines without mapping cannot receive comments' do
expect(page).not_to have_selector('tr.line_holder[class$="nomappinginraw"] > td.diff-line-num > .add-diff-note')
expect(page).to have_selector('tr.line_holder:not([class$="nomappinginraw"]) > td.diff-line-num > .add-diff-note')
end
it 'lines numbers without mapping are empty' do
elements = page.all('tr.line_holder[class$="nomappinginraw"] > td.diff-line-num').map { |e| e.text(:all) }
expect(elements).to all(be == "")
end
end
end
end
def classes_for_element(node, data_diff_entity, visible: true)
node.find("[data-diff-toggle-entity=\"#{data_diff_entity}\"]", visible: visible)[:class]
end
def line_with_content(old_or_new, line_number)
"td.#{old_or_new}_line.diff-line-num[data-linenumber=\"#{line_number}\"] > a[data-linenumber=\"#{line_number}\"]"
end
end

View file

@ -53,7 +53,7 @@ describe('InstallAgentModal', () => {
});
it('shows agent token as an input value', () => {
expect(findInput().props('value')).toBe('agent-token');
expect(findInput().props('value')).toBe(agentToken);
});
it('renders a copy button', () => {
@ -65,12 +65,12 @@ describe('InstallAgentModal', () => {
});
it('shows warning alert', () => {
expect(findAlert().props('title')).toBe(I18N_AGENT_TOKEN.tokenSingleUseWarningTitle);
expect(findAlert().text()).toBe(I18N_AGENT_TOKEN.tokenSingleUseWarningTitle);
});
it('shows code block with agent installation command', () => {
expect(findCodeBlock().props('code')).toContain('--agent-token=agent-token');
expect(findCodeBlock().props('code')).toContain('--kas-address=kas.example.com');
expect(findCodeBlock().props('code')).toContain(`--agent-token=${agentToken}`);
expect(findCodeBlock().props('code')).toContain(`--kas-address=${kasAddress}`);
});
});
});

View file

@ -290,6 +290,53 @@ RSpec.describe DiffHelper do
end
end
describe "#diff_nomappinginraw_line" do
using RSpec::Parameterized::TableSyntax
let(:line) { double("line") }
let(:line_type) { 'line_type' }
before do
allow(line).to receive(:rich_text).and_return('line_text')
allow(line).to receive(:type).and_return(line_type)
end
it 'generates only single line num' do
output = diff_nomappinginraw_line(line, ['line_num_1'], nil, ['line_content'])
expect(output).to be_html_safe
expect(output).to have_css 'td:nth-child(1).line_num_1'
expect(output).to have_css 'td:nth-child(2).line_content', text: 'line_text'
expect(output).not_to have_css 'td:nth-child(3)'
end
it 'generates only both line nums' do
output = diff_nomappinginraw_line(line, ['line_num_1'], ['line_num_2'], ['line_content'])
expect(output).to be_html_safe
expect(output).to have_css 'td:nth-child(1).line_num_1'
expect(output).to have_css 'td:nth-child(2).line_num_2'
expect(output).to have_css 'td:nth-child(3).line_content', text: 'line_text'
end
where(:line_type, :added_class) do
'old-nomappinginraw' | '.old'
'new-nomappinginraw' | '.new'
'unchanged-nomappinginraw' | ''
end
with_them do
it "appends the correct class" do
output = diff_nomappinginraw_line(line, ['line_num_1'], ['line_num_2'], ['line_content'])
expect(output).to be_html_safe
expect(output).to have_css 'td:nth-child(1).line_num_1' + added_class
expect(output).to have_css 'td:nth-child(2).line_num_2' + added_class
expect(output).to have_css 'td:nth-child(3).line_content' + added_class, text: 'line_text'
end
end
end
describe '#render_overflow_warning?' do
using RSpec::Parameterized::TableSyntax

View file

@ -66,6 +66,12 @@ RSpec.describe Gitlab::Diff::File do
it 'does not have renderable viewer' do
expect(diff_file.has_renderable?).to be_falsey
end
it 'does not create a Notebook DiffFile' do
expect(diff_file.rendered).to be_nil
expect(::Gitlab::Diff::Rendered::Notebook::DiffFile).not_to receive(:new)
end
end
end

View file

@ -13,6 +13,10 @@ RSpec.describe BulkImports::ExportStatus do
double(parsed_response: [{ 'relation' => 'labels', 'status' => status, 'error' => 'error!' }])
end
let(:invalid_response_double) do
double(parsed_response: [{ 'relation' => 'not_a_real_relation', 'status' => status, 'error' => 'error!' }])
end
subject { described_class.new(tracker, relation) }
before do
@ -36,6 +40,18 @@ RSpec.describe BulkImports::ExportStatus do
it 'returns false' do
expect(subject.started?).to eq(false)
end
context 'when returned relation is invalid' do
before do
allow_next_instance_of(BulkImports::Clients::HTTP) do |client|
allow(client).to receive(:get).and_return(invalid_response_double)
end
end
it 'returns false' do
expect(subject.started?).to eq(false)
end
end
end
end
@ -63,7 +79,7 @@ RSpec.describe BulkImports::ExportStatus do
it 'returns true' do
expect(subject.failed?).to eq(true)
expect(subject.error).to eq('Empty export status response')
expect(subject.error).to eq('Empty relation export status')
end
end
end

View file

@ -535,6 +535,10 @@ RSpec.describe Group do
describe '#ancestors_upto' do
it { expect(group.ancestors_upto.to_sql).not_to include "WITH ORDINALITY" }
end
describe '.shortest_traversal_ids_prefixes' do
it { expect { described_class.shortest_traversal_ids_prefixes }.to raise_error /Feature not supported since the `:use_traversal_ids` is disabled/ }
end
end
context 'linear' do
@ -576,6 +580,90 @@ RSpec.describe Group do
it { expect(group.ancestors_upto.to_sql).to include "WITH ORDINALITY" }
end
describe '.shortest_traversal_ids_prefixes' do
subject { filter.shortest_traversal_ids_prefixes }
context 'for many top-level namespaces' do
let!(:top_level_groups) { create_list(:group, 4) }
context 'when querying all groups' do
let(:filter) { described_class.id_in(top_level_groups) }
it "returns all traversal_ids" do
is_expected.to contain_exactly(
*top_level_groups.map { |group| [group.id] }
)
end
end
context 'when querying selected groups' do
let(:filter) { described_class.id_in(top_level_groups.first) }
it "returns only a selected traversal_ids" do
is_expected.to contain_exactly([top_level_groups.first.id])
end
end
end
context 'for namespace hierarchy' do
let!(:group_a) { create(:group) }
let!(:group_a_sub_1) { create(:group, parent: group_a) }
let!(:group_a_sub_2) { create(:group, parent: group_a) }
let!(:group_b) { create(:group) }
let!(:group_b_sub_1) { create(:group, parent: group_b) }
let!(:group_c) { create(:group) }
context 'when querying all groups' do
let(:filter) { described_class.id_in([group_a, group_a_sub_1, group_a_sub_2, group_b, group_b_sub_1, group_c]) }
it 'returns only shortest prefixes of top-level groups' do
is_expected.to contain_exactly(
[group_a.id],
[group_b.id],
[group_c.id]
)
end
end
context 'when sub-group is reparented' do
let(:filter) { described_class.id_in([group_b_sub_1, group_c]) }
before do
group_b_sub_1.update!(parent: group_c)
end
it 'returns a proper shortest prefix of a new group' do
is_expected.to contain_exactly(
[group_c.id]
)
end
end
context 'when querying sub-groups' do
let(:filter) { described_class.id_in([group_a_sub_1, group_b_sub_1, group_c]) }
it 'returns sub-groups as they are shortest prefixes' do
is_expected.to contain_exactly(
[group_a.id, group_a_sub_1.id],
[group_b.id, group_b_sub_1.id],
[group_c.id]
)
end
end
context 'when querying group and sub-group of this group' do
let(:filter) { described_class.id_in([group_a, group_a_sub_1, group_c]) }
it 'returns parent groups as this contains all sub-groups' do
is_expected.to contain_exactly(
[group_a.id],
[group_c.id]
)
end
end
end
end
context 'when project namespace exists in the group' do
let!(:project) { create(:project, group: group) }
let!(:project_namespace) { project.project_namespace }

View file

@ -10,4 +10,22 @@ RSpec.describe ProgrammingLanguage do
it { is_expected.to allow_value("#000000").for(:color) }
it { is_expected.not_to allow_value("000000").for(:color) }
it { is_expected.not_to allow_value("#0z0000").for(:color) }
describe '.with_name_case_insensitive scope' do
let_it_be(:ruby) { create(:programming_language, name: 'Ruby') }
let_it_be(:python) { create(:programming_language, name: 'Python') }
let_it_be(:swift) { create(:programming_language, name: 'Swift') }
it 'accepts a single name parameter' do
expect(described_class.with_name_case_insensitive('swift')).to(
contain_exactly(swift)
)
end
it 'accepts multiple names' do
expect(described_class.with_name_case_insensitive('ruby', 'python')).to(
contain_exactly(ruby, python)
)
end
end
end

View file

@ -4,4 +4,34 @@ require 'spec_helper'
RSpec.describe ProjectSetting, type: :model do
it { is_expected.to belong_to(:project) }
describe 'validations' do
it { is_expected.not_to allow_value(nil).for(:target_platforms) }
it { is_expected.to allow_value([]).for(:target_platforms) }
it 'allows any combination of the allowed target platforms' do
valid_target_platform_combinations.each do |target_platforms|
expect(subject).to allow_value(target_platforms).for(:target_platforms)
end
end
[nil, 'not_allowed', :invalid].each do |invalid_value|
it { is_expected.not_to allow_value([invalid_value]).for(:target_platforms) }
end
end
describe 'target_platforms=' do
it 'stringifies and sorts' do
project_setting = build(:project_setting, target_platforms: [:watchos, :ios])
expect(project_setting.target_platforms).to eq %w(ios watchos)
end
end
def valid_target_platform_combinations
target_platforms = described_class::ALLOWED_TARGET_PLATFORMS
0.upto(target_platforms.size).flat_map do |n|
target_platforms.permutation(n).to_a
end
end
end

View file

@ -5622,6 +5622,18 @@ RSpec.describe Project, factory_default: :keep do
expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
end
end
describe 'project target platforms detection' do
before do
create(:import_state, :started, project: project)
end
it 'calls enqueue_record_project_target_platforms' do
expect(project).to receive(:enqueue_record_project_target_platforms)
project.after_import
end
end
end
describe '#update_project_counter_caches' do
@ -8091,6 +8103,44 @@ RSpec.describe Project, factory_default: :keep do
it_behaves_like 'blocks unsafe serialization'
end
describe '#enqueue_record_project_target_platforms' do
let_it_be(:project) { create(:project) }
let(:com) { true }
before do
allow(Gitlab).to receive(:com?).and_return(com)
end
it 'enqueues a Projects::RecordTargetPlatformsWorker' do
expect(Projects::RecordTargetPlatformsWorker).to receive(:perform_async).with(project.id)
project.enqueue_record_project_target_platforms
end
shared_examples 'does not enqueue a Projects::RecordTargetPlatformsWorker' do
it 'does not enqueue a Projects::RecordTargetPlatformsWorker' do
expect(Projects::RecordTargetPlatformsWorker).not_to receive(:perform_async)
project.enqueue_record_project_target_platforms
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(record_projects_target_platforms: false)
end
it_behaves_like 'does not enqueue a Projects::RecordTargetPlatformsWorker'
end
context 'when not in gitlab.com' do
let(:com) { false }
it_behaves_like 'does not enqueue a Projects::RecordTargetPlatformsWorker'
end
end
private
def finish_job(export_job)

View file

@ -4251,16 +4251,26 @@ RSpec.describe User do
end
end
it_behaves_like '#ci_owned_runners'
describe '#ci_owned_runners' do
it_behaves_like '#ci_owned_runners'
context 'when FF ci_owned_runners_cross_joins_fix is disabled' do
before do
skip_if_multiple_databases_are_setup
context 'when FF use_traversal_ids is disabled fallbacks to inefficient implementation' do
before do
stub_feature_flags(use_traversal_ids: false)
end
stub_feature_flags(ci_owned_runners_cross_joins_fix: false)
it_behaves_like '#ci_owned_runners'
end
it_behaves_like '#ci_owned_runners'
context 'when FF ci_owned_runners_cross_joins_fix is disabled' do
before do
skip_if_multiple_databases_are_setup
stub_feature_flags(ci_owned_runners_cross_joins_fix: false)
end
it_behaves_like '#ci_owned_runners'
end
end
describe '#projects_with_reporter_access_limited_to' do

View file

@ -148,6 +148,7 @@ project_setting:
- updated_at
- cve_id_request_enabled
- mr_default_target_self
- target_platforms
build_service_desk_setting: # service_desk_setting
unexposed_attributes:

View file

@ -721,4 +721,14 @@ RSpec.describe Git::BranchPushService, services: true do
it_behaves_like 'does not enqueue Jira sync worker'
end
end
describe 'project target platforms detection' do
subject(:execute) { execute_service(project, user, oldrev: blankrev, newrev: newrev, ref: ref) }
it 'calls enqueue_record_project_target_platforms on the project' do
expect(project).to receive(:enqueue_record_project_target_platforms)
execute
end
end
end

View file

@ -0,0 +1,61 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::AppleTargetPlatformDetectorService do
let_it_be(:project) { build(:project) }
subject { described_class.new(project).execute }
context 'when project is not an xcode project' do
before do
allow(Gitlab::FileFinder).to receive(:new) { instance_double(Gitlab::FileFinder, find: []) }
end
it 'returns an empty array' do
is_expected.to match_array []
end
end
context 'when project is an xcode project' do
using RSpec::Parameterized::TableSyntax
let(:finder) { instance_double(Gitlab::FileFinder) }
before do
allow(Gitlab::FileFinder).to receive(:new) { finder }
end
def search_query(sdk, filename)
"SDKROOT = #{sdk} filename:#{filename}"
end
context 'when setting string is found' do
where(:sdk, :filename, :result) do
'iphoneos' | 'project.pbxproj' | [:ios]
'iphoneos' | '*.xcconfig' | [:ios]
end
with_them do
before do
allow(finder).to receive(:find).with(anything) { [] }
allow(finder).to receive(:find).with(search_query(sdk, filename)) { [instance_double(Gitlab::Search::FoundBlob)] }
end
it 'returns an array of unique detected targets' do
is_expected.to match_array result
end
end
end
context 'when setting string is not found' do
before do
allow(finder).to receive(:find).with(anything) { [] }
end
it 'returns an empty array' do
is_expected.to match_array []
end
end
end
end

View file

@ -0,0 +1,66 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::RecordTargetPlatformsService, '#execute' do
let_it_be(:project) { create(:project) }
subject(:execute) { described_class.new(project).execute }
context 'when project is an XCode project' do
before do
double = instance_double(Projects::AppleTargetPlatformDetectorService, execute: [:ios, :osx])
allow(Projects::AppleTargetPlatformDetectorService).to receive(:new) { double }
end
it 'creates a new setting record for the project', :aggregate_failures do
expect { execute }.to change { ProjectSetting.count }.from(0).to(1)
expect(ProjectSetting.last.target_platforms).to match_array(%w(ios osx))
end
it 'returns array of detected target platforms' do
expect(execute).to match_array %w(ios osx)
end
context 'when a project has an existing setting record' do
before do
create(:project_setting, project: project, target_platforms: saved_target_platforms)
end
def project_setting
ProjectSetting.find_by_project_id(project.id)
end
context 'when target platforms changed' do
let(:saved_target_platforms) { %w(tvos) }
it 'updates' do
expect { execute }.to change { project_setting.target_platforms }.from(%w(tvos)).to(%w(ios osx))
end
it { is_expected.to match_array %w(ios osx) }
end
context 'when target platforms are the same' do
let(:saved_target_platforms) { %w(osx ios) }
it 'does not update' do
expect { execute }.not_to change { project_setting.updated_at }
end
end
end
end
context 'when project is not an XCode project' do
before do
double = instance_double(Projects::AppleTargetPlatformDetectorService, execute: [])
allow(Projects::AppleTargetPlatformDetectorService).to receive(:new).with(project) { double }
end
it 'does nothing' do
expect { execute }.not_to change { ProjectSetting.count }
end
it { is_expected.to be_nil }
end
end

View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::RecordTargetPlatformsWorker do
include ExclusiveLeaseHelpers
let_it_be(:swift) { create(:programming_language, name: 'Swift') }
let_it_be(:objective_c) { create(:programming_language, name: 'Objective-C') }
let_it_be(:project) { create(:project, :repository, detected_repository_languages: true) }
let(:worker) { described_class.new }
let(:service_result) { %w(ios osx watchos) }
let(:service_double) { instance_double(Projects::RecordTargetPlatformsService, execute: service_result) }
let(:lease_key) { "#{described_class.name.underscore}:#{project.id}" }
let(:lease_timeout) { described_class::LEASE_TIMEOUT }
subject(:perform) { worker.perform(project.id) }
before do
stub_exclusive_lease(lease_key, timeout: lease_timeout)
end
shared_examples 'performs detection' do
it 'creates and executes a Projects::RecordTargetPlatformService instance for the project', :aggregate_failures do
expect(Projects::RecordTargetPlatformsService).to receive(:new).with(project) { service_double }
expect(service_double).to receive(:execute)
perform
end
it 'logs extra metadata on done', :aggregate_failures do
expect(Projects::RecordTargetPlatformsService).to receive(:new).with(project) { service_double }
expect(worker).to receive(:log_extra_metadata_on_done).with(:target_platforms, service_result)
perform
end
end
shared_examples 'does nothing' do
it 'does nothing' do
expect(Projects::RecordTargetPlatformsService).not_to receive(:new)
perform
end
end
context 'when project uses Swift programming language' do
let!(:repository_language) { create(:repository_language, project: project, programming_language: swift) }
include_examples 'performs detection'
end
context 'when project uses Objective-C programming language' do
let!(:repository_language) { create(:repository_language, project: project, programming_language: objective_c) }
include_examples 'performs detection'
end
context 'when the project does not contain programming languages for Apple platforms' do
it_behaves_like 'does nothing'
end
context 'when project is not found' do
it 'does nothing' do
expect(Projects::RecordTargetPlatformsService).not_to receive(:new)
worker.perform(non_existing_record_id)
end
end
context 'when exclusive lease cannot be obtained' do
before do
stub_exclusive_lease_taken(lease_key)
end
it_behaves_like 'does nothing'
end
it 'has the `until_executed` deduplicate strategy' do
expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
end
it 'overrides #lease_release? to return false' do
expect(worker.send(:lease_release?)).to eq false
end
end