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
orm_adapter (0.5.0)
os (1.0.0)
parallel (1.19.1)
parser (2.6.5.0)
parser (2.7.0.4)
ast (~> 2.4.0)
parslet (1.8.2)
peek (1.1.0)
......@@ -1094,13 +1094,13 @@ GEM
uniform_notifier (1.13.0)
unleash (0.1.5)
murmurhash3 (~> 0.1.6)
unparser (0.4.5)
unparser (0.4.7)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
concord (~> 0.1.5)
diff-lcs (~> 1.3)
equalizer (~> 0.0.9)
parser (~> 2.6.3)
parser (>= 2.6.5)
procto (~> 0.0.2)
validate_email (0.1.6)
activemodel (>= 3.0)
......
<script>
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 Icon from '../../vue_shared/components/icon.vue';
import getRefMixin from '../mixins/get_ref';
......@@ -103,7 +103,7 @@ export default {
.filter(p => p !== '')
.reduce(
(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({
name,
......
<script>
import { escapeRegExp } from 'lodash';
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 Icon from '~/vue_shared/components/icon.vue';
import { getIconName } from '../../utils/icon';
......@@ -92,7 +92,7 @@ export default {
computed: {
routerLinkTo() {
return this.isFolder
? { path: `/-/tree/${escape(this.ref)}/${encodeURIComponent(this.path)}` }
? { path: `/-/tree/${escape(this.ref)}/${escapeFileUrl(this.path)}` }
: null;
},
iconName() {
......
......@@ -42,11 +42,13 @@ module Projects
def schedule_import(params)
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],
Time.now.strftime('%Y-%m-%d %H:%M:%S'),
{ user_id: current_user.id, name: current_user.name }
)
import_data << jira_project_details
import_data.force_import!
@project.import_type = 'jira'
@project.import_state.schedule if @project.save
......
......@@ -344,8 +344,8 @@ module BlobHelper
def show_suggest_pipeline_creation_celebration?
experiment_enabled?(:suggest_pipeline) &&
@blob.auxiliary_viewer.valid?(project: @project, sha: @commit.sha, user: current_user) &&
@blob.path == Gitlab::FileDetector::PATTERNS[:gitlab_ci] &&
@blob.auxiliary_viewer.valid?(project: @project, sha: @commit.sha, user: current_user) &&
@project.uses_default_ci_config? &&
cookies[suggest_pipeline_commit_cookie_name].present?
end
......
......@@ -45,6 +45,6 @@ module CiVariablesHelper
end
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
......@@ -3,9 +3,9 @@
module Ci
class GroupVariable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
include Ci::HasVariable
include Presentable
include Maskable
include Ci::Maskable
belongs_to :group, class_name: "::Group"
......
......@@ -3,7 +3,7 @@
module Ci
class JobVariable < ApplicationRecord
extend Gitlab::Ci::Model
include NewHasVariable
include Ci::NewHasVariable
include BulkInsertSafe
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
......
......@@ -3,7 +3,7 @@
module Ci
class PipelineScheduleVariable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
include Ci::HasVariable
belongs_to :pipeline_schedule
......
......@@ -3,7 +3,7 @@
module Ci
class PipelineVariable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
include Ci::HasVariable
belongs_to :pipeline
......
......@@ -3,9 +3,9 @@
module Ci
class Variable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
include Ci::HasVariable
include Presentable
include Maskable
include Ci::Maskable
prepend HasEnvironmentScope
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 @@
class JiraImportData < ProjectImportData
JiraProjectDetails = Struct.new(:key, :scheduled_at, :scheduled_by)
FORCE_IMPORT_KEY = 'force-import'
def projects
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'])
end
projects.sort_by { |jp| jp.scheduled_at }
projects&.sort_by { |jp| jp.scheduled_at } || []
end
def <<(project)
self.data ||= { jira: { projects: [] } }
self.data['jira']['projects'] << project.to_h.deep_stringify_keys!
self.data ||= { 'jira' => { 'projects' => [] } }
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
......@@ -868,6 +868,8 @@ class Project < ApplicationRecord
elsif gitlab_project_import?
# 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)
elsif jira_import?
Gitlab::JiraImport::Stage::StartImportWorker.perform_async(self.id)
else
RepositoryImportWorker.perform_async(self.id)
end
......@@ -900,7 +902,7 @@ class Project < ApplicationRecord
# This method is overridden in EE::Project model
def remove_import_data
import_data&.destroy
import_data&.destroy unless jira_import?
end
def ci_config_path=(value)
......@@ -947,7 +949,7 @@ class Project < ApplicationRecord
end
def import?
external_import? || forked? || gitlab_project_import? || bare_repository_import?
external_import? || forked? || gitlab_project_import? || jira_import? || bare_repository_import?
end
def external_import?
......@@ -962,6 +964,14 @@ class Project < ApplicationRecord
import_type == 'bare_repository'
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?
import_type == 'gitlab_project'
end
......
......@@ -46,7 +46,7 @@ module Milestones
Milestone.joins(:issues)
.where(
issues: { project_id: project.id },
group_id: old_group.id
group_id: old_group.self_and_ancestors
)
end
# rubocop: enable CodeReuse/ActiveRecord
......@@ -56,7 +56,7 @@ module Milestones
Milestone.joins(:merge_requests)
.where(
merge_requests: { target_project_id: project.id },
group_id: old_group.id
group_id: old_group.self_and_ancestors
)
end
# rubocop: enable CodeReuse/ActiveRecord
......
......@@ -528,6 +528,55 @@
:resource_boundary: :unknown
:weight: 2
: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
:feature_category: :issue_tracking
: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
sidekiq_options dead: false
feature_category :importers
private
# The known importer stages and their corresponding Sidekiq workers.
STAGES = {
issues_and_diff_notes: Stage::ImportIssuesAndDiffNotesWorker,
......@@ -23,6 +21,8 @@ module Gitlab
finish: Stage::FinishImportWorker
}.freeze
private
def next_stage_worker(next_stage)
STAGES.fetch(next_stage.to_sym)
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
elastic_index_bulk_cron_worker:
cron: "*/1 * * * *"
# Elasticsearch metrics
# NOTE: This will only take effect if Elasticsearch is enabled.
elastic_metrics_update_worker:
cron: "*/1 * * * *"
registry:
# enabled: true
# host: registry.example.com
......
......@@ -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']['cron'] ||= '*/1 * * * *'
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']['cron'] ||= "#{rand(60)} 0 * * *"
Settings.cron_jobs['sync_seat_link_worker']['job_class'] = 'SyncSeatLinkWorker'
......
......@@ -128,6 +128,8 @@
- 1
- - jira_connect
- 1
- - jira_importer
- 1
- - ldap_group_sync
- 2
- - mail_scheduler
......
# frozen_string_literal: true
class InsertCiPipelineSchedulesPlanLimits < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
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
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_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_vis20_last_activity_at", where: "(visibility_level = 20)"
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
`/-/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).
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
......@@ -105,10 +111,12 @@ The following metrics can be controlled by feature flags:
| `gitlab_method_call_duration_seconds` | `prometheus_metrics_method_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
the `monitoring.sidekiq_exporter` configuration option in `gitlab.yml`.
Sidekiq jobs may also gather metrics, and these metrics can be accessed if the
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 |
|:---------------------------------------------- |:------- |:----- |:----------- |:------ |
......@@ -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_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 |
| `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)**
......
......@@ -51,8 +51,6 @@ module Gitlab
end
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 = ids.compact.sort.uniq
......
......@@ -37,7 +37,7 @@ module Gitlab
case resource
when Hash
self.new(resource.symbolize_keys)
when ::HasVariable
when ::Ci::HasVariable
self.new(resource.to_runner_variable)
when self
resource.dup
......
......@@ -4,7 +4,10 @@ import { visitUrl } from '~/lib/utils/url_utility';
import TableRow from '~/repository/components/table/row.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 $router;
......
......@@ -204,7 +204,6 @@ describe BlobHelper do
end
describe '#show_suggest_pipeline_creation_celebration?' do
let(:blob) { fake_blob(path: Gitlab::FileDetector::PATTERNS[:gitlab_ci]) }
let(:current_user) { create(:user) }
before do
......@@ -212,52 +211,68 @@ describe BlobHelper do
assign(:blob, blob)
assign(:commit, double('Commit', sha: 'whatever'))
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)
end
context 'experiment enabled' do
before do
allow(helper).to receive(:experiment_enabled?).and_return(true)
end
it 'is true' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_truthy
end
context 'when file is a pipeline config file' do
let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
let(:blob) { fake_blob(path: Gitlab::FileDetector::PATTERNS[:gitlab_ci], data: data) }
context 'file is invalid format' do
context 'experiment enabled' 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
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
it 'is true' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_truthy
end
end
context 'path is not a ci file' do
before do
allow(blob).to receive(:path).and_return('something_bad')
context 'file is invalid format' do
let(:data) { 'foo' }
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
context 'does not use the default ci config' do
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
context 'does not use the default ci config' do
context 'experiment disabled' do
before do
project.ci_config_path = 'something_bad'
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
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
helper.request.cookies.delete "suggest_gitlab_ci_yml_commit_#{project.id}"
allow(helper).to receive(:experiment_enabled?).and_return(true)
end
it 'is false' do
......@@ -265,16 +280,6 @@ describe BlobHelper do
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
......
......@@ -83,16 +83,6 @@ describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it_behaves_like 'version and gemfile files'
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
let(:files) { ['Gemfile.zip/'] }
......
......@@ -8,7 +8,7 @@ describe Ci::GroupVariable do
it_behaves_like "CI variable"
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/) }
describe '.unprotected' do
......
......@@ -9,7 +9,7 @@ describe Ci::Variable do
describe 'validations' do
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 validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) }
end
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe HasVariable do
describe Ci::HasVariable do
subject { build(:ci_variable) }
it { is_expected.to validate_presence_of(:key) }
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe Maskable do
describe Ci::Maskable do
let(:variable) { build(:ci_variable) }
describe 'masked value validations' do
......@@ -34,7 +34,7 @@ describe Maskable do
end
describe 'REGEX' do
subject { Maskable::REGEX }
subject { Ci::Maskable::REGEX }
it 'does not match strings shorter than 8 letters' do
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
expect(project.add_import_job).to eq(import_jid)
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
describe '#gitlab_project_import?' do
......
......@@ -40,6 +40,16 @@ describe Milestones::TransferService do
expect(new_milestone.project_milestone?).to be_truthy
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
new_milestone = create(:milestone, project: project, title: group_milestone.title)
......
# frozen_string_literal: true
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
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