Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ba537a9b5c
commit
b72218d98e
51 changed files with 903 additions and 132 deletions
|
@ -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/
|
||||
|
|
|
@ -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/**/*"
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.'),
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
" ".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)
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
29
app/services/projects/record_target_platforms_service.rb
Normal file
29
app/services/projects/record_target_platforms_service.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
55
app/workers/projects/record_target_platforms_worker.rb
Normal file
55
app/workers/projects/record_target_platforms_worker.rb
Normal 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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
1
db/schema_migrations/20220317170122
Normal file
1
db/schema_migrations/20220317170122
Normal file
|
@ -0,0 +1 @@
|
|||
0bd78ce207cca13b5c4557ace87c135972ed69cd05ee8c6fcc60a9c060ba8b5f
|
1
db/schema_migrations/20220318120802
Normal file
1
db/schema_migrations/20220318120802
Normal file
|
@ -0,0 +1 @@
|
|||
19f25b2f373e7c2799812661baca1902c9c74df67f7a5e88116862fb078a5957
|
|
@ -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)),
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
87
spec/workers/projects/record_target_platforms_worker_spec.rb
Normal file
87
spec/workers/projects/record_target_platforms_worker_spec.rb
Normal 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
|
Loading…
Reference in a new issue