Commit 1363ca12 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 6763d278
...@@ -750,7 +750,7 @@ GEM ...@@ -750,7 +750,7 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (1.0.0) os (1.0.0)
parallel (1.19.1) parallel (1.19.1)
parser (2.6.5.0) parser (2.7.0.4)
ast (~> 2.4.0) ast (~> 2.4.0)
parslet (1.8.2) parslet (1.8.2)
peek (1.1.0) peek (1.1.0)
...@@ -1094,13 +1094,13 @@ GEM ...@@ -1094,13 +1094,13 @@ GEM
uniform_notifier (1.13.0) uniform_notifier (1.13.0)
unleash (0.1.5) unleash (0.1.5)
murmurhash3 (~> 0.1.6) murmurhash3 (~> 0.1.6)
unparser (0.4.5) unparser (0.4.7)
abstract_type (~> 0.0.7) abstract_type (~> 0.0.7)
adamantium (~> 0.2.0) adamantium (~> 0.2.0)
concord (~> 0.1.5) concord (~> 0.1.5)
diff-lcs (~> 1.3) diff-lcs (~> 1.3)
equalizer (~> 0.0.9) equalizer (~> 0.0.9)
parser (~> 2.6.3) parser (>= 2.6.5)
procto (~> 0.0.2) procto (~> 0.0.2)
validate_email (0.1.6) validate_email (0.1.6)
activemodel (>= 3.0) activemodel (>= 3.0)
......
<script> <script>
import { GlDropdown, GlDropdownDivider, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownDivider, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui';
import { joinPaths } from '~/lib/utils/url_utility'; import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import { __ } from '../../locale'; import { __ } from '../../locale';
import Icon from '../../vue_shared/components/icon.vue'; import Icon from '../../vue_shared/components/icon.vue';
import getRefMixin from '../mixins/get_ref'; import getRefMixin from '../mixins/get_ref';
...@@ -103,7 +103,7 @@ export default { ...@@ -103,7 +103,7 @@ export default {
.filter(p => p !== '') .filter(p => p !== '')
.reduce( .reduce(
(acc, name, i) => { (acc, name, i) => {
const path = joinPaths(i > 0 ? acc[i].path : '', encodeURIComponent(name)); const path = joinPaths(i > 0 ? acc[i].path : '', escapeFileUrl(name));
return acc.concat({ return acc.concat({
name, name,
......
<script> <script>
import { escapeRegExp } from 'lodash'; import { escapeRegExp } from 'lodash';
import { GlBadge, GlLink, GlSkeletonLoading, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; import { GlBadge, GlLink, GlSkeletonLoading, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl, escapeFileUrl } from '~/lib/utils/url_utility';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { getIconName } from '../../utils/icon'; import { getIconName } from '../../utils/icon';
...@@ -92,7 +92,7 @@ export default { ...@@ -92,7 +92,7 @@ export default {
computed: { computed: {
routerLinkTo() { routerLinkTo() {
return this.isFolder return this.isFolder
? { path: `/-/tree/${escape(this.ref)}/${encodeURIComponent(this.path)}` } ? { path: `/-/tree/${escape(this.ref)}/${escapeFileUrl(this.path)}` }
: null; : null;
}, },
iconName() { iconName() {
......
...@@ -42,11 +42,13 @@ module Projects ...@@ -42,11 +42,13 @@ module Projects
def schedule_import(params) def schedule_import(params)
import_data = @project.create_or_update_import_data(data: {}).becomes(JiraImportData) import_data = @project.create_or_update_import_data(data: {}).becomes(JiraImportData)
import_data << JiraImportData::JiraProjectDetails.new( jira_project_details = JiraImportData::JiraProjectDetails.new(
params[:jira_project_key], params[:jira_project_key],
Time.now.strftime('%Y-%m-%d %H:%M:%S'), Time.now.strftime('%Y-%m-%d %H:%M:%S'),
{ user_id: current_user.id, name: current_user.name } { user_id: current_user.id, name: current_user.name }
) )
import_data << jira_project_details
import_data.force_import!
@project.import_type = 'jira' @project.import_type = 'jira'
@project.import_state.schedule if @project.save @project.import_state.schedule if @project.save
......
...@@ -344,8 +344,8 @@ module BlobHelper ...@@ -344,8 +344,8 @@ module BlobHelper
def show_suggest_pipeline_creation_celebration? def show_suggest_pipeline_creation_celebration?
experiment_enabled?(:suggest_pipeline) && experiment_enabled?(:suggest_pipeline) &&
@blob.auxiliary_viewer.valid?(project: @project, sha: @commit.sha, user: current_user) &&
@blob.path == Gitlab::FileDetector::PATTERNS[:gitlab_ci] && @blob.path == Gitlab::FileDetector::PATTERNS[:gitlab_ci] &&
@blob.auxiliary_viewer.valid?(project: @project, sha: @commit.sha, user: current_user) &&
@project.uses_default_ci_config? && @project.uses_default_ci_config? &&
cookies[suggest_pipeline_commit_cookie_name].present? cookies[suggest_pipeline_commit_cookie_name].present?
end end
......
...@@ -45,6 +45,6 @@ module CiVariablesHelper ...@@ -45,6 +45,6 @@ module CiVariablesHelper
end end
def ci_variable_maskable_regex def ci_variable_maskable_regex
Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(/^\//, '').sub(/\/[a-z]*$/, '').gsub('\/', '/') Ci::Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(/^\//, '').sub(/\/[a-z]*$/, '').gsub('\/', '/')
end end
end end
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
module Ci module Ci
class GroupVariable < ApplicationRecord class GroupVariable < ApplicationRecord
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include HasVariable include Ci::HasVariable
include Presentable include Presentable
include Maskable include Ci::Maskable
belongs_to :group, class_name: "::Group" belongs_to :group, class_name: "::Group"
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Ci module Ci
class JobVariable < ApplicationRecord class JobVariable < ApplicationRecord
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include NewHasVariable include Ci::NewHasVariable
include BulkInsertSafe include BulkInsertSafe
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Ci module Ci
class PipelineScheduleVariable < ApplicationRecord class PipelineScheduleVariable < ApplicationRecord
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include HasVariable include Ci::HasVariable
belongs_to :pipeline_schedule belongs_to :pipeline_schedule
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module Ci module Ci
class PipelineVariable < ApplicationRecord class PipelineVariable < ApplicationRecord
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include HasVariable include Ci::HasVariable
belongs_to :pipeline belongs_to :pipeline
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
module Ci module Ci
class Variable < ApplicationRecord class Variable < ApplicationRecord
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
include HasVariable include Ci::HasVariable
include Presentable include Presentable
include Maskable include Ci::Maskable
prepend HasEnvironmentScope prepend HasEnvironmentScope
belongs_to :project belongs_to :project
......
# frozen_string_literal: true
module Ci
module HasVariable
extend ActiveSupport::Concern
included do
enum variable_type: {
env_var: 1,
file: 2
}
validates :key,
presence: true,
length: { maximum: 255 },
format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." }
scope :order_key_asc, -> { reorder(key: :asc) }
attr_encrypted :value,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
key: Settings.attr_encrypted_db_key_base,
algorithm: 'aes-256-cbc'
def key=(new_key)
super(new_key.to_s.strip)
end
end
def to_runner_variable
{ key: key, value: value, public: false, file: file? }
end
end
end
# frozen_string_literal: true
module Ci
module Maskable
extend ActiveSupport::Concern
# * Single line
# * No escape characters
# * No variables
# * No spaces
# * Minimal length of 8 characters
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of @ and :
# * Absolutely no fun is allowed
REGEX = /\A[a-zA-Z0-9_+=\/@:-]{8,}\z/.freeze
included do
validates :masked, inclusion: { in: [true, false] }
validates :value, format: { with: REGEX }, if: :masked?
end
def to_runner_variable
super.merge(masked: masked?)
end
end
end
# frozen_string_literal: true
module Ci
module NewHasVariable
extend ActiveSupport::Concern
include Ci::HasVariable
included do
attr_encrypted :value,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_32,
insecure_mode: false
end
end
end
# frozen_string_literal: true
module HasVariable
extend ActiveSupport::Concern
included do
enum variable_type: {
env_var: 1,
file: 2
}
validates :key,
presence: true,
length: { maximum: 255 },
format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." }
scope :order_key_asc, -> { reorder(key: :asc) }
attr_encrypted :value,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
key: Settings.attr_encrypted_db_key_base,
algorithm: 'aes-256-cbc'
def key=(new_key)
super(new_key.to_s.strip)
end
end
def to_runner_variable
{ key: key, value: value, public: false, file: file? }
end
end
# frozen_string_literal: true
module Maskable
extend ActiveSupport::Concern
# * Single line
# * No escape characters
# * No variables
# * No spaces
# * Minimal length of 8 characters
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of @ and :
# * Absolutely no fun is allowed
REGEX = /\A[a-zA-Z0-9_+=\/@:-]{8,}\z/.freeze
included do
validates :masked, inclusion: { in: [true, false] }
validates :value, format: { with: REGEX }, if: :masked?
end
def to_runner_variable
super.merge(masked: masked?)
end
end
# frozen_string_literal: true
module NewHasVariable
extend ActiveSupport::Concern
include HasVariable
included do
attr_encrypted :value,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_32,
insecure_mode: false
end
end
...@@ -3,17 +3,40 @@ ...@@ -3,17 +3,40 @@
class JiraImportData < ProjectImportData class JiraImportData < ProjectImportData
JiraProjectDetails = Struct.new(:key, :scheduled_at, :scheduled_by) JiraProjectDetails = Struct.new(:key, :scheduled_at, :scheduled_by)
FORCE_IMPORT_KEY = 'force-import'
def projects def projects
return [] unless data return [] unless data
projects = data.dig('jira', 'projects').map do |p| projects = data.dig('jira', 'projects')&.map do |p|
JiraProjectDetails.new(p['key'], p['scheduled_at'], p['scheduled_by']) JiraProjectDetails.new(p['key'], p['scheduled_at'], p['scheduled_by'])
end end
projects.sort_by { |jp| jp.scheduled_at }
projects&.sort_by { |jp| jp.scheduled_at } || []
end end
def <<(project) def <<(project)
self.data ||= { jira: { projects: [] } } self.data ||= { 'jira' => { 'projects' => [] } }
self.data['jira']['projects'] << project.to_h.deep_stringify_keys! self.data['jira'] ||= { 'projects' => [] }
self.data['jira']['projects'] = [] if data['jira']['projects'].blank? || !data['jira']['projects'].is_a?(Array)
self.data['jira']['projects'] << project.to_h
self.data.deep_stringify_keys!
end
def force_import!
self.data ||= {}
self.data.deep_merge!({ 'jira' => { FORCE_IMPORT_KEY => true } })
self.data.deep_stringify_keys!
end
def force_import?
!!data&.dig('jira', FORCE_IMPORT_KEY) && !projects.blank?
end
def finish_import!
return if data&.dig('jira', FORCE_IMPORT_KEY).nil?
data['jira'].delete(FORCE_IMPORT_KEY)
end end
end end
...@@ -868,6 +868,8 @@ class Project < ApplicationRecord ...@@ -868,6 +868,8 @@ class Project < ApplicationRecord
elsif gitlab_project_import? elsif gitlab_project_import?
# Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-foss/issues/26189 is solved. # Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-foss/issues/26189 is solved.
RepositoryImportWorker.set(retry: false).perform_async(self.id) RepositoryImportWorker.set(retry: false).perform_async(self.id)
elsif jira_import?
Gitlab::JiraImport::Stage::StartImportWorker.perform_async(self.id)
else else
RepositoryImportWorker.perform_async(self.id) RepositoryImportWorker.perform_async(self.id)
end end
...@@ -900,7 +902,7 @@ class Project < ApplicationRecord ...@@ -900,7 +902,7 @@ class Project < ApplicationRecord
# This method is overridden in EE::Project model # This method is overridden in EE::Project model
def remove_import_data def remove_import_data
import_data&.destroy import_data&.destroy unless jira_import?
end end
def ci_config_path=(value) def ci_config_path=(value)
...@@ -947,7 +949,7 @@ class Project < ApplicationRecord ...@@ -947,7 +949,7 @@ class Project < ApplicationRecord
end end
def import? def import?
external_import? || forked? || gitlab_project_import? || bare_repository_import? external_import? || forked? || gitlab_project_import? || jira_import? || bare_repository_import?
end end
def external_import? def external_import?
...@@ -962,6 +964,14 @@ class Project < ApplicationRecord ...@@ -962,6 +964,14 @@ class Project < ApplicationRecord
import_type == 'bare_repository' import_type == 'bare_repository'
end end
def jira_import?
import_type == 'jira' && Feature.enabled?(:jira_issue_import, self)
end
def jira_force_import?
jira_import? && import_data&.becomes(JiraImportData)&.force_import?
end
def gitlab_project_import? def gitlab_project_import?
import_type == 'gitlab_project' import_type == 'gitlab_project'
end end
......
...@@ -46,7 +46,7 @@ module Milestones ...@@ -46,7 +46,7 @@ module Milestones
Milestone.joins(:issues) Milestone.joins(:issues)
.where( .where(
issues: { project_id: project.id }, issues: { project_id: project.id },
group_id: old_group.id group_id: old_group.self_and_ancestors
) )
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -56,7 +56,7 @@ module Milestones ...@@ -56,7 +56,7 @@ module Milestones
Milestone.joins(:merge_requests) Milestone.joins(:merge_requests)
.where( .where(
merge_requests: { target_project_id: project.id }, merge_requests: { target_project_id: project.id },
group_id: old_group.id group_id: old_group.self_and_ancestors
) )
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -528,6 +528,55 @@ ...@@ -528,6 +528,55 @@
:resource_boundary: :unknown :resource_boundary: :unknown
:weight: 2 :weight: 2
:idempotent: :idempotent:
- :name: jira_importer:jira_import_advance_stage
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_finish_import
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_import_attachments
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_import_issues
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_import_labels
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_import_notes
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: jira_importer:jira_import_stage_start_import
:feature_category: :importers
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: mail_scheduler:mail_scheduler_issue_due - :name: mail_scheduler:mail_scheduler_issue_due
:feature_category: :issue_tracking :feature_category: :issue_tracking
:has_external_dependencies: :has_external_dependencies:
......
# frozen_string_literal: true
module Gitlab
module JiraImport
module ImportWorker
extend ActiveSupport::Concern
included do
include ApplicationWorker
include Gitlab::JiraImport::QueueOptions
end
def perform(project_id)
project = Project.find_by(id: project_id) # rubocop: disable CodeReuse/ActiveRecord
return unless can_import?(project)
import(project)
end
private
def import(project)
raise NotImplementedError
end
def can_import?(project)
return false unless project
return false if Feature.disabled?(:jira_issue_import, project)
project.import_state.started?
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module QueueOptions
extend ActiveSupport::Concern
included do
queue_namespace :jira_importer
feature_category :importers
sidekiq_options retry: 5
end
end
end
end
...@@ -13,8 +13,6 @@ module Gitlab ...@@ -13,8 +13,6 @@ module Gitlab
sidekiq_options dead: false sidekiq_options dead: false
feature_category :importers feature_category :importers
private
# The known importer stages and their corresponding Sidekiq workers. # The known importer stages and their corresponding Sidekiq workers.
STAGES = { STAGES = {
issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker, issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker,
...@@ -23,6 +21,8 @@ module Gitlab ...@@ -23,6 +21,8 @@ module Gitlab
finish: Stage::FinishImportWorker finish: Stage::FinishImportWorker
}.freeze }.freeze
private
def next_stage_worker(next_stage) def next_stage_worker(next_stage)
STAGES.fetch(next_stage.to_sym) STAGES.fetch(next_stage.to_sym)
end end
......
# frozen_string_literal: true
module Gitlab
module JiraImport
class AdvanceStageWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include QueueOptions
include ::Gitlab::Import::AdvanceStage
# The known importer stages and their corresponding Sidekiq workers.
STAGES = {
labels: Gitlab::JiraImport::Stage::ImportLabelsWorker,
issues: Gitlab::JiraImport::Stage::ImportIssuesWorker,
attachments: Gitlab::JiraImport::Stage::ImportAttachmentsWorker,
notes: Gitlab::JiraImport::Stage::ImportNotesWorker,
finish: Gitlab::JiraImport::Stage::FinishImportWorker
}.freeze
private
def next_stage_worker(next_stage)
STAGES.fetch(next_stage.to_sym)
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class FinishImportWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
project.after_import
ensure
project.import_data.becomes(JiraImportData).finish_import!
project.import_data.save!
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class ImportAttachmentsWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
# fake a attahcments import workers for now.
# new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage
fake_waiter = JobWaiter.new
project.import_state.refresh_jid_expiration
Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { fake_waiter.key => fake_waiter.jobs_remaining }, :notes)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class ImportIssuesWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
# fake issues import workers for now
# new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage
jobs_waiter = JobWaiter.new
project.import_state.refresh_jid_expiration
Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { jobs_waiter.key => jobs_waiter.jobs_remaining }, :attachments)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class ImportLabelsWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
# fake labels import workers for now
# new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage
fake_waiter = JobWaiter.new
Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { fake_waiter.key => fake_waiter.jobs_remaining }, :issues)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class ImportNotesWorker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::JiraImport::ImportWorker
private
def import(project)
# fake notes import workers for now
# new job waiter will have zero jobs_remaining by default, so it will just pass on to next stage
jobs_waiter = JobWaiter.new
project.import_state.refresh_jid_expiration
Gitlab::JiraImport::AdvanceStageWorker.perform_async(project.id, { jobs_waiter.key => jobs_waiter.jobs_remaining }, :finish)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module JiraImport
module Stage
class StartImportWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include ProjectStartImport
include ProjectImportOptions
include Gitlab::JiraImport::QueueOptions
attr_reader :project
def perform(project_id)
@project = Project.find_by(id: project_id) # rubocop: disable CodeReuse/ActiveRecord
return unless start_import
Gitlab::Import::SetAsyncJid.set_jid(project)
Gitlab::JiraImport::Stage::ImportLabelsWorker.perform_async(project.id)
end
private
def start_import
return false unless project
return false if Feature.disabled?(:jira_issue_import, project)
return true if start(project.import_state)
Gitlab::Import::Logger.info(
{
project_id: project.id,
project_path: project.full_path,
state: project&.import_status,
message: 'inconsistent state while importing'
}
)
false
end
end
end
end
end
---
title: Fix managed_free_namespaces scope to only groups without a license or a free license
merge_request: 27356
author:
type: fixed
---
title: Optimize projects_mirrored_with_pipelines_enabled query performance in usage data
merge_request: 27110
author:
type: performance
---
title: Fix invalid ancestor group milestones when moving projects
merge_request: 27262
author:
type: fixed
...@@ -459,6 +459,11 @@ production: &base ...@@ -459,6 +459,11 @@ production: &base
elastic_index_bulk_cron_worker: elastic_index_bulk_cron_worker:
cron: "*/1 * * * *" cron: "*/1 * * * *"
# Elasticsearch metrics
# NOTE: This will only take effect if Elasticsearch is enabled.
elastic_metrics_update_worker:
cron: "*/1 * * * *"
registry: registry:
# enabled: true # enabled: true
# host: registry.example.com # host: registry.example.com
......
...@@ -546,6 +546,9 @@ Gitlab.ee do ...@@ -546,6 +546,9 @@ Gitlab.ee do
Settings.cron_jobs['elastic_index_bulk_cron_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['elastic_index_bulk_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['elastic_index_bulk_cron_worker']['cron'] ||= '*/1 * * * *' Settings.cron_jobs['elastic_index_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['elastic_index_bulk_cron_worker']['job_class'] ||= 'ElasticIndexBulkCronWorker' Settings.cron_jobs['elastic_index_bulk_cron_worker']['job_class'] ||= 'ElasticIndexBulkCronWorker'
Settings.cron_jobs['elastic_metrics_update_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['elastic_metrics_update_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['elastic_metrics_update_worker']['job_class'] ||= 'ElasticMetricsUpdateWorker'
Settings.cron_jobs['sync_seat_link_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['sync_seat_link_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['sync_seat_link_worker']['cron'] ||= "#{rand(60)} 0 * * *" Settings.cron_jobs['sync_seat_link_worker']['cron'] ||= "#{rand(60)} 0 * * *"
Settings.cron_jobs['sync_seat_link_worker']['job_class'] = 'SyncSeatLinkWorker' Settings.cron_jobs['sync_seat_link_worker']['job_class'] = 'SyncSeatLinkWorker'
......
...@@ -128,6 +128,8 @@ ...@@ -128,6 +128,8 @@
- 1 - 1
- - jira_connect - - jira_connect
- 1 - 1
- - jira_importer
- 1
- - ldap_group_sync - - ldap_group_sync
- 2 - 2
- - mail_scheduler - - mail_scheduler
......
# frozen_string_literal: true # frozen_string_literal: true
class InsertCiPipelineSchedulesPlanLimits < ActiveRecord::Migration[6.0] class InsertCiPipelineSchedulesPlanLimits < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false DOWNTIME = false
def change def change
......
# frozen_string_literal: true
class AddIndexOnMirrorAndIdToProjects < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
OLD_INDEX_NAME = 'index_projects_on_mirror_and_mirror_trigger_builds_both_true'
NEW_INDEX_NAME = 'index_projects_on_mirror_id_where_mirror_and_trigger_builds'
disable_ddl_transaction!
def up
add_concurrent_index :projects, :id, where: 'mirror = TRUE AND mirror_trigger_builds = TRUE', name: NEW_INDEX_NAME
remove_concurrent_index_by_name :projects, OLD_INDEX_NAME
end
def down
add_concurrent_index :projects, :id, where: 'mirror IS TRUE AND mirror_trigger_builds IS TRUE', name: OLD_INDEX_NAME
remove_concurrent_index_by_name :projects, NEW_INDEX_NAME
end
end
...@@ -3507,7 +3507,7 @@ ActiveRecord::Schema.define(version: 2020_03_13_123934) do ...@@ -3507,7 +3507,7 @@ ActiveRecord::Schema.define(version: 2020_03_13_123934) do
t.index ["id"], name: "index_on_id_partial_with_legacy_storage", where: "((storage_version < 2) OR (storage_version IS NULL))" t.index ["id"], name: "index_on_id_partial_with_legacy_storage", where: "((storage_version < 2) OR (storage_version IS NULL))"
t.index ["id"], name: "index_projects_on_id_partial_for_visibility", unique: true, where: "(visibility_level = ANY (ARRAY[10, 20]))" t.index ["id"], name: "index_projects_on_id_partial_for_visibility", unique: true, where: "(visibility_level = ANY (ARRAY[10, 20]))"
t.index ["id"], name: "index_projects_on_id_service_desk_enabled", where: "(service_desk_enabled = true)" t.index ["id"], name: "index_projects_on_id_service_desk_enabled", where: "(service_desk_enabled = true)"
t.index ["id"], name: "index_projects_on_mirror_and_mirror_trigger_builds_both_true", where: "((mirror IS TRUE) AND (mirror_trigger_builds IS TRUE))" t.index ["id"], name: "index_projects_on_mirror_id_where_mirror_and_trigger_builds", where: "((mirror = true) AND (mirror_trigger_builds = true))"
t.index ["last_activity_at", "id"], name: "index_projects_api_last_activity_at_id_desc", order: { id: :desc } t.index ["last_activity_at", "id"], name: "index_projects_api_last_activity_at_id_desc", order: { id: :desc }
t.index ["last_activity_at", "id"], name: "index_projects_api_vis20_last_activity_at", where: "(visibility_level = 20)" t.index ["last_activity_at", "id"], name: "index_projects_api_vis20_last_activity_at", where: "(visibility_level = 20)"
t.index ["last_activity_at", "id"], name: "index_projects_on_last_activity_at_and_id" t.index ["last_activity_at", "id"], name: "index_projects_on_last_activity_at_and_id"
......
...@@ -17,7 +17,13 @@ GitLab monitors its own internal service metrics, and makes them available at th ...@@ -17,7 +17,13 @@ GitLab monitors its own internal service metrics, and makes them available at th
`/-/metrics` endpoint. Unlike other [Prometheus](https://prometheus.io) exporters, in order to access `/-/metrics` endpoint. Unlike other [Prometheus](https://prometheus.io) exporters, in order to access
it, the client IP needs to be [included in a whitelist](../ip_whitelist.md). it, the client IP needs to be [included in a whitelist](../ip_whitelist.md).
For Omnibus and Chart installations, these metrics are automatically enabled and collected as of [GitLab 9.4](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/1702). For source installations or earlier versions, these metrics will need to be enabled manually and collected by a Prometheus server. For Omnibus and Chart installations, these metrics are automatically enabled
and collected as of [GitLab
9.4](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/1702). For
source installations or earlier versions, these metrics will need to be enabled
manually and collected by a Prometheus server.
See also [Sidekiq metrics](#sidekiq-metrics) for how to enable and view metrics from Sidekiq nodes.
## Metrics available ## Metrics available
...@@ -105,10 +111,12 @@ The following metrics can be controlled by feature flags: ...@@ -105,10 +111,12 @@ The following metrics can be controlled by feature flags:
| `gitlab_method_call_duration_seconds` | `prometheus_metrics_method_instrumentation` | | `gitlab_method_call_duration_seconds` | `prometheus_metrics_method_instrumentation` |
| `gitlab_view_rendering_duration_seconds` | `prometheus_metrics_view_instrumentation` | | `gitlab_view_rendering_duration_seconds` | `prometheus_metrics_view_instrumentation` |
## Sidekiq Metrics available for Geo **(PREMIUM)** ## Sidekiq metrics
Sidekiq jobs may also gather metrics, and these metrics can be accessed if the Sidekiq exporter is enabled (e.g. via Sidekiq jobs may also gather metrics, and these metrics can be accessed if the
the `monitoring.sidekiq_exporter` configuration option in `gitlab.yml`. Sidekiq exporter is enabled (for example, using the `monitoring.sidekiq_exporter`
configuration option in `gitlab.yml`. These metrics are served from the
`/metrics` path on the configured port.
| Metric | Type | Since | Description | Labels | | Metric | Type | Since | Description | Labels |
|:---------------------------------------------- |:------- |:----- |:----------- |:------ | |:---------------------------------------------- |:------- |:----- |:----------- |:------ |
...@@ -145,6 +153,7 @@ the `monitoring.sidekiq_exporter` configuration option in `gitlab.yml`. ...@@ -145,6 +153,7 @@ the `monitoring.sidekiq_exporter` configuration option in `gitlab.yml`.
| `geo_repositories_checked_failed_count` | Gauge | 11.1 | Number of repositories that have a failure from `git fsck` | url | | `geo_repositories_checked_failed_count` | Gauge | 11.1 | Number of repositories that have a failure from `git fsck` | url |
| `geo_repositories_retrying_verification_count` | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | url | | `geo_repositories_retrying_verification_count` | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | url |
| `geo_wikis_retrying_verification_count` | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | url | | `geo_wikis_retrying_verification_count` | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | url |
| `global_search_bulk_cron_queue_size` | Gauge | 12.10 | Number of database records waiting to be synchronized to Elasticsearch | |
## Database load balancing metrics **(PREMIUM ONLY)** ## Database load balancing metrics **(PREMIUM ONLY)**
......
...@@ -51,8 +51,6 @@ module Gitlab ...@@ -51,8 +51,6 @@ module Gitlab
end end
def hash_of_the_latest_changes def hash_of_the_latest_changes
return unless Feature.enabled?(:ci_file_based_cache, @pipeline.project, default_enabled: true)
ids = files.map { |path| last_commit_id_for_path(path) } ids = files.map { |path| last_commit_id_for_path(path) }
ids = ids.compact.sort.uniq ids = ids.compact.sort.uniq
......
...@@ -37,7 +37,7 @@ module Gitlab ...@@ -37,7 +37,7 @@ module Gitlab
case resource case resource
when Hash when Hash
self.new(resource.symbolize_keys) self.new(resource.symbolize_keys)
when ::HasVariable when ::Ci::HasVariable
self.new(resource.to_runner_variable) self.new(resource.to_runner_variable)
when self when self
resource.dup resource.dup
......
...@@ -4,7 +4,10 @@ import { visitUrl } from '~/lib/utils/url_utility'; ...@@ -4,7 +4,10 @@ import { visitUrl } from '~/lib/utils/url_utility';
import TableRow from '~/repository/components/table/row.vue'; import TableRow from '~/repository/components/table/row.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
jest.mock('~/lib/utils/url_utility'); jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
visitUrl: jest.fn(),
}));
let vm; let vm;
let $router; let $router;
......
...@@ -204,7 +204,6 @@ describe BlobHelper do ...@@ -204,7 +204,6 @@ describe BlobHelper do
end end
describe '#show_suggest_pipeline_creation_celebration?' do describe '#show_suggest_pipeline_creation_celebration?' do
let(:blob) { fake_blob(path: Gitlab::FileDetector::PATTERNS[:gitlab_ci]) }
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
before do before do
...@@ -212,52 +211,68 @@ describe BlobHelper do ...@@ -212,52 +211,68 @@ describe BlobHelper do
assign(:blob, blob) assign(:blob, blob)
assign(:commit, double('Commit', sha: 'whatever')) assign(:commit, double('Commit', sha: 'whatever'))
helper.request.cookies["suggest_gitlab_ci_yml_commit_#{project.id}"] = 'true' helper.request.cookies["suggest_gitlab_ci_yml_commit_#{project.id}"] = 'true'
allow(blob).to receive(:auxiliary_viewer).and_return(double('viewer', valid?: true))
allow(helper).to receive(:current_user).and_return(current_user) allow(helper).to receive(:current_user).and_return(current_user)
end end
context 'experiment enabled' do context 'when file is a pipeline config file' do
before do let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
allow(helper).to receive(:experiment_enabled?).and_return(true) let(:blob) { fake_blob(path: Gitlab::FileDetector::PATTERNS[:gitlab_ci], data: data) }
end
it 'is true' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_truthy
end
context 'file is invalid format' do context 'experiment enabled' do
before do before do
allow(blob).to receive(:auxiliary_viewer).and_return(double('viewer', valid?: false)) allow(helper).to receive(:experiment_enabled?).and_return(true)
end end
it 'is false' do it 'is true' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey expect(helper.show_suggest_pipeline_creation_celebration?).to be_truthy
end end
end
context 'path is not a ci file' do context 'file is invalid format' do
before do let(:data) { 'foo' }
allow(blob).to receive(:path).and_return('something_bad')
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end end
it 'is false' do context 'does not use the default ci config' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey before do
project.ci_config_path = 'something_bad'
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
context 'does not have the needed cookie' do
before do
helper.request.cookies.delete "suggest_gitlab_ci_yml_commit_#{project.id}"
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end end
end end
context 'does not use the default ci config' do context 'experiment disabled' do
before do before do
project.ci_config_path = 'something_bad' allow(helper).to receive(:experiment_enabled?).and_return(false)
end end
it 'is false' do it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end end
end end
end
context 'does not have the needed cookie' do context 'when file is not a pipeline config file' do
let(:blob) { fake_blob(path: 'LICENSE') }
context 'experiment enabled' do
before do before do
helper.request.cookies.delete "suggest_gitlab_ci_yml_commit_#{project.id}" allow(helper).to receive(:experiment_enabled?).and_return(true)
end end
it 'is false' do it 'is false' do
...@@ -265,16 +280,6 @@ describe BlobHelper do ...@@ -265,16 +280,6 @@ describe BlobHelper do
end end
end end
end end
context 'experiment disabled' do
before do
allow(helper).to receive(:experiment_enabled?).and_return(false)
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
end end
end end
......
...@@ -83,16 +83,6 @@ describe Gitlab::Ci::Pipeline::Seed::Build::Cache do ...@@ -83,16 +83,6 @@ describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it_behaves_like 'version and gemfile files' it_behaves_like 'version and gemfile files'
end end
context 'with feature flag disabled' do
let(:files) { ['VERSION', 'Gemfile.zip'] }
before do
stub_feature_flags(ci_file_based_cache: false)
end
it_behaves_like 'default key'
end
context 'with files ending with /' do context 'with files ending with /' do
let(:files) { ['Gemfile.zip/'] } let(:files) { ['Gemfile.zip/'] }
......
...@@ -8,7 +8,7 @@ describe Ci::GroupVariable do ...@@ -8,7 +8,7 @@ describe Ci::GroupVariable do
it_behaves_like "CI variable" it_behaves_like "CI variable"
it { is_expected.to include_module(Presentable) } it { is_expected.to include_module(Presentable) }
it { is_expected.to include_module(Maskable) } it { is_expected.to include_module(Ci::Maskable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id).with_message(/\(\w+\) has already been taken/) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id).with_message(/\(\w+\) has already been taken/) }
describe '.unprotected' do describe '.unprotected' do
......
...@@ -9,7 +9,7 @@ describe Ci::Variable do ...@@ -9,7 +9,7 @@ describe Ci::Variable do
describe 'validations' do describe 'validations' do
it { is_expected.to include_module(Presentable) } it { is_expected.to include_module(Presentable) }
it { is_expected.to include_module(Maskable) } it { is_expected.to include_module(Ci::Maskable) }
it { is_expected.to include_module(HasEnvironmentScope) } it { is_expected.to include_module(HasEnvironmentScope) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) }
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe HasVariable do describe Ci::HasVariable do
subject { build(:ci_variable) } subject { build(:ci_variable) }
it { is_expected.to validate_presence_of(:key) } it { is_expected.to validate_presence_of(:key) }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe Maskable do describe Ci::Maskable do
let(:variable) { build(:ci_variable) } let(:variable) { build(:ci_variable) }
describe 'masked value validations' do describe 'masked value validations' do
...@@ -34,7 +34,7 @@ describe Maskable do ...@@ -34,7 +34,7 @@ describe Maskable do
end end
describe 'REGEX' do describe 'REGEX' do
subject { Maskable::REGEX } subject { Ci::Maskable::REGEX }
it 'does not match strings shorter than 8 letters' do it 'does not match strings shorter than 8 letters' do
expect(subject.match?('hello')).to eq(false) expect(subject.match?('hello')).to eq(false)
......
# frozen_string_literal: true
require 'spec_helper'
describe JiraImportData do
let(:symbol_keys_project) do
{ key: 'AA', scheduled_at: 2.days.ago.strftime('%Y-%m-%d %H:%M:%S'), scheduled_by: { 'user_id' => 1, 'name' => 'tester1' } }
end
let(:string_keys_project) do
{ 'key': 'BB', 'scheduled_at': 1.hour.ago.strftime('%Y-%m-%d %H:%M:%S'), 'scheduled_by': { 'user_id': 2, 'name': 'tester2' } }
end
let(:jira_project_details) do
JiraImportData::JiraProjectDetails.new('CC', 1.day.ago.strftime('%Y-%m-%d %H:%M:%S'), { user_id: 3, name: 'tester3' })
end
describe '#projects' do
it 'returns empty array if no data' do
expect(described_class.new.projects).to eq([])
end
it 'returns empty array if no projects' do
import_data = described_class.new(data: { 'some-key' => 10 })
expect(import_data.projects).to eq([])
end
it 'returns JiraProjectDetails sorted by scheduled_at time' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project, string_keys_project, jira_project_details] } })
expect(import_data.projects.size).to eq 3
expect(import_data.projects.map(&:key)).to eq(%w(AA CC BB))
expect(import_data.projects.map(&:scheduled_by).map {|e| e['name']}).to eq %w(tester1 tester3 tester2)
expect(import_data.projects.map(&:scheduled_by).map {|e| e['user_id']}).to eq [1, 3, 2]
end
end
describe 'add projects' do
it 'adds project when data is nil' do
import_data = described_class.new
expect(import_data.data).to be nil
import_data << string_keys_project
expect(import_data.data).to eq({ 'jira' => { 'projects' => [string_keys_project] } })
end
it 'adds project when data has some random info' do
import_data = described_class.new(data: { 'one-key': 10 })
expect(import_data.data).to eq({ 'one-key' => 10 })
import_data << string_keys_project
expect(import_data.data).to eq({ 'one-key' => 10, 'jira' => { 'projects' => [string_keys_project] } })
end
it 'adds project when data already has some jira projects' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project] } })
expect(import_data.projects.map(&:to_h)).to eq [symbol_keys_project]
import_data << string_keys_project
expect(import_data.data['jira']['projects'].size).to eq 2
expect(import_data.projects.map(&:key)).to eq(%w(AA BB))
expect(import_data.projects.map(&:scheduled_by).map {|e| e['name']}).to eq %w(tester1 tester2)
expect(import_data.projects.map(&:scheduled_by).map {|e| e['user_id']}).to eq [1, 2]
end
end
describe '#force_import!' do
it 'sets force import when data is nil' do
import_data = described_class.new
import_data.force_import!
expect(import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
expect(import_data.force_import?).to be false
end
it 'sets force import when data is present but no jira key' do
import_data = described_class.new(data: { 'some-key': 'some-data' })
import_data.force_import!
expect(import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
expect(import_data.data).to eq({ 'some-key' => 'some-data', 'jira' => { JiraImportData::FORCE_IMPORT_KEY => true } })
expect(import_data.force_import?).to be false
end
it 'sets force import when data and jira keys exist' do
import_data = described_class.new(data: { 'some-key': 'some-data', 'jira': {} })
import_data.force_import!
expect(import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
expect(import_data.data).to eq({ 'some-key' => 'some-data', 'jira' => { JiraImportData::FORCE_IMPORT_KEY => true } })
expect(import_data.force_import?).to be false
end
it 'sets force import when data and jira project data exist' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project], JiraImportData::FORCE_IMPORT_KEY => false }, 'some-key': 'some-data' })
import_data.force_import!
expect(import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
expect(import_data.data).to eq({ 'some-key' => 'some-data', 'jira' => { 'projects' => [symbol_keys_project.deep_stringify_keys!], JiraImportData::FORCE_IMPORT_KEY => true } })
expect(import_data.force_import?).to be true
end
end
describe '#force_import?' do
it 'returns false when data blank' do
expect(described_class.new.force_import?).to be false
end
it 'returns false if there is no project data present' do
import_data = described_class.new(data: { jira: { JiraImportData::FORCE_IMPORT_KEY => true }, 'one-key': 10 })
expect(import_data.force_import?).to be false
end
it 'returns false when force import set to false' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project], JiraImportData::FORCE_IMPORT_KEY => false }, 'one-key': 10 })
expect(import_data.force_import?).to be false
end
it 'returns true when force import set to true' do
import_data = described_class.new(data: { jira: { projects: [symbol_keys_project], JiraImportData::FORCE_IMPORT_KEY => true } })
expect(import_data.force_import?).to be true
end
end
end
...@@ -2353,6 +2353,63 @@ describe Project do ...@@ -2353,6 +2353,63 @@ describe Project do
expect(project.add_import_job).to eq(import_jid) expect(project.add_import_job).to eq(import_jid)
end end
end end
context 'jira import' do
it 'schedules a jira import job' do
project = create(:project, import_type: 'jira')
expect(Gitlab::JiraImport::Stage::StartImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
expect(project.add_import_job).to eq(import_jid)
end
end
end
describe '#jira_import?' do
subject(:project) { build(:project, import_type: 'jira') }
it { expect(project.jira_import?).to be true }
it { expect(project.import?).to be true }
end
describe '#jira_force_import?' do
let(:imported_jira_project) do
JiraImportData::JiraProjectDetails.new('xx', Time.now.strftime('%Y-%m-%d %H:%M:%S'), { user_id: 1, name: 'root' })
end
let(:jira_import_data) do
data = JiraImportData.new
data << imported_jira_project
data.force_import!
data
end
subject(:project) { build(:project, import_type: 'jira', import_data: jira_import_data) }
it { expect(project.jira_force_import?).to be true }
end
describe '#remove_import_data' do
let(:import_data) { ProjectImportData.new(data: { 'test' => 'some data' }) }
context 'when jira import' do
let!(:project) { create(:project, import_type: 'jira', import_data: import_data) }
it 'does not remove import data' do
expect(project.mirror?).to be false
expect(project.jira_import?).to be true
expect { project.remove_import_data }.not_to change { ProjectImportData.count }
end
end
context 'when not mirror neither jira import' do
let(:user) { create(:user) }
let!(:project) { create(:project, import_type: 'github', import_data: import_data) }
it 'removes import data' do
expect(project.mirror?).to be false
expect(project.jira_import?).to be false
expect { project.remove_import_data }.to change { ProjectImportData.count }.by(-1)
end
end
end end
describe '#gitlab_project_import?' do describe '#gitlab_project_import?' do
......
...@@ -40,6 +40,16 @@ describe Milestones::TransferService do ...@@ -40,6 +40,16 @@ describe Milestones::TransferService do
expect(new_milestone.project_milestone?).to be_truthy expect(new_milestone.project_milestone?).to be_truthy
end end
context 'when milestone is from an ancestor group' do
let(:old_group_ancestor) { create(:group) }
let(:old_group) { create(:group, parent: old_group_ancestor) }
let(:group_milestone) { create(:milestone, group: old_group_ancestor)}
it 'recreates the missing group milestones at project level' do
expect { service.execute }.to change(project.milestones, :count).by(1)
end
end
it 'deletes milestone issue counters cache for both milestones' do it 'deletes milestone issue counters cache for both milestones' do
new_milestone = create(:milestone, project: project, title: group_milestone.title) new_milestone = create(:milestone, project: project, title: group_milestone.title)
......
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_examples 'CI variable' do RSpec.shared_examples 'CI variable' do
it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(Ci::HasVariable) }
describe "variable type" do describe "variable type" do
it 'defines variable types' do it 'defines variable types' do
......
# frozen_string_literal: true
shared_examples 'include import workers modules' do
it { expect(described_class).to include_module(ApplicationWorker) }
it { expect(described_class).to include_module(Gitlab::JiraImport::QueueOptions) }
if described_class == Gitlab::JiraImport::Stage::StartImportWorker
it { expect(described_class).to include_module(ProjectStartImport) }
it { expect(described_class).to include_module(ProjectImportOptions) }
else
it { expect(described_class).to include_module(Gitlab::JiraImport::ImportWorker) }
end
end
shared_examples 'exit import not started' do
it 'does nothing, and exits' do
expect(Gitlab::JiraImport::AdvanceStageWorker).not_to receive(:perform_async)
worker.perform(project.id)
end
end
shared_examples 'advance to next stage' do |next_stage|
let(:job_waiter) { Gitlab::JobWaiter.new(2, 'some-job-key') }
it "advances to #{next_stage} stage" do
expect(Gitlab::JobWaiter).to receive(:new).and_return(job_waiter)
expect(Gitlab::JiraImport::AdvanceStageWorker).to receive(:perform_async).with(project.id, { job_waiter.key => job_waiter.jobs_remaining }, next_stage.to_sym)
worker.perform(project.id)
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::FinishImportWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let(:imported_jira_project) do
JiraImportData::JiraProjectDetails.new('xx', Time.now.strftime('%Y-%m-%d %H:%M:%S'), { user_id: 1, name: 'root' })
end
let(:jira_import_data) do
data = JiraImportData.new
data << imported_jira_project
data.force_import!
data
end
let(:import_state) { create(:import_state, status: :started) }
let(:project) { create(:project, import_type: 'jira', import_data: jira_import_data, import_state: import_state) }
it 'changes import state to finished' do
worker.perform(project.id)
expect(project.reload.import_state.status).to eq "finished"
end
it 'removes force-import flag' do
expect(project.reload.import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be true
worker.perform(project.id)
expect(project.reload.import_data.data['jira'][JiraImportData::FORCE_IMPORT_KEY]).to be nil
expect(project.reload.import_data.data['jira']).not_to be nil
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::ImportAttachmentsWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let!(:import_state) { create(:import_state, status: :started, project: project) }
it_behaves_like 'advance to next stage', :notes
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::ImportIssuesWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let!(:import_state) { create(:import_state, status: :started, project: project) }
it_behaves_like 'advance to next stage', :attachments
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::ImportLabelsWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let!(:import_state) { create(:import_state, status: :started, project: project) }
it_behaves_like 'advance to next stage', :issues
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::ImportNotesWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it_behaves_like 'exit import not started'
end
context 'when feature flag enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import did not start' do
let!(:import_state) { create(:import_state, project: project) }
it_behaves_like 'exit import not started'
end
context 'when import started' do
let!(:import_state) { create(:import_state, status: :started, project: project) }
it_behaves_like 'advance to next stage', :finish
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::JiraImport::Stage::StartImportWorker do
let(:project) { create(:project) }
let(:worker) { described_class.new }
let(:jid) { '12345678' }
describe 'modules' do
it_behaves_like 'include import workers modules'
end
describe '#perform' do
context 'when feature flag not enabled' do
before do
stub_feature_flags(jira_issue_import: false)
end
it 'exits because import not allowed' do
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).not_to receive(:perform_async)
worker.perform(project.id)
end
end
context 'when feature flag not enabled' do
before do
stub_feature_flags(jira_issue_import: true)
end
context 'when import is not scheudled' do
let!(:import_state) { create(:import_state, project: project, status: :none, jid: jid) }
it 'exits because import not started' do
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).not_to receive(:perform_async)
worker.perform(project.id)
end
end
context 'when import is scheduled' do
let!(:import_state) { create(:import_state, project: project, status: :scheduled, jid: jid) }
it 'advances to importing labels' do
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).to receive(:perform_async)
worker.perform(project.id)
end
end
context 'when import is started' do
let!(:import_state) { create(:import_state, project: project, status: :started, jid: jid) }
context 'when this is the same worker that stated import' do
it 'advances to importing labels' do
allow(worker).to receive(:jid).and_return(jid)
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).to receive(:perform_async)
worker.perform(project.id)
end
end
context 'when this is a different worker that stated import' do
it 'advances to importing labels' do
allow(worker).to receive(:jid).and_return('87654321')
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).not_to receive(:perform_async)
worker.perform(project.id)
end
end
end
context 'when import is finished' do
let!(:import_state) { create(:import_state, project: project, status: :finished, jid: jid) }
it 'advances to importing labels' do
allow(worker).to receive(:jid).and_return(jid)
expect(Gitlab::JiraImport::Stage::ImportLabelsWorker).not_to receive(:perform_async)
worker.perform(project.id)
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment