Commit 9e27f0d9 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 1bab0ba5
...@@ -104,13 +104,6 @@ export default { ...@@ -104,13 +104,6 @@ export default {
helpLink() { helpLink() {
return boardsStore.scopedLabels.helpLink; return boardsStore.scopedLabels.helpLink;
}, },
validIssueWeight() {
if (_.isNumber(this.issue.weight)) {
return this.issue.weight >= 0;
}
return false;
},
}, },
methods: { methods: {
isIndexLessThanlimit(index) { isIndexLessThanlimit(index) {
......
export default { export default {
computed: {
validIssueWeight() {
return false;
},
},
methods: { methods: {
filterByWeight() {}, filterByWeight() {},
}, },
......
...@@ -54,7 +54,7 @@ export default { ...@@ -54,7 +54,7 @@ export default {
:href="job.status.details_path" :href="job.status.details_path"
:title="tooltipText" :title="tooltipText"
data-boundary="viewport" data-boundary="viewport"
class="js-job-link" class="js-job-link d-flex"
> >
<icon <icon
v-if="isActive" v-if="isActive"
...@@ -64,7 +64,7 @@ export default { ...@@ -64,7 +64,7 @@ export default {
<ci-icon :status="job.status" /> <ci-icon :status="job.status" />
<span>{{ job.name ? job.name : job.id }}</span> <span class="text-truncate w-100">{{ job.name ? job.name : job.id }}</span>
<icon v-if="job.retried" name="retry" class="js-retry-icon" /> <icon v-if="job.retried" name="retry" class="js-retry-icon" />
</gl-link> </gl-link>
......
...@@ -308,12 +308,8 @@ ...@@ -308,12 +308,8 @@
} }
a { a {
display: block;
padding: $gl-padding 10px $gl-padding 40px; padding: $gl-padding 10px $gl-padding 40px;
width: 270px; width: 270px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:hover { &:hover {
color: $gl-text-color; color: $gl-text-color;
......
...@@ -1001,6 +1001,14 @@ pre.light-well { ...@@ -1001,6 +1001,14 @@ pre.light-well {
} }
} }
&:not(.with-pipeline-status) {
.icon-wrapper:first-of-type {
@include media-breakpoint-up(lg) {
margin-left: $gl-padding-32;
}
}
}
.ci-status-link { .ci-status-link {
display: inline-flex; display: inline-flex;
} }
......
...@@ -9,6 +9,7 @@ module Ci ...@@ -9,6 +9,7 @@ module Ci
# #
class Group class Group
include StaticModel include StaticModel
include Gitlab::Utils::StrongMemoize
attr_reader :stage, :name, :jobs attr_reader :stage, :name, :jobs
...@@ -21,7 +22,17 @@ module Ci ...@@ -21,7 +22,17 @@ module Ci
end end
def status def status
@status ||= commit_statuses.status strong_memoize(:status) do
if Feature.enabled?(:ci_composite_status, default_enabled: false)
Gitlab::Ci::Status::Composite
.new(@jobs)
.status
else
CommitStatus
.where(id: @jobs)
.legacy_status
end
end
end end
def detailed_status(current_user) def detailed_status(current_user)
...@@ -40,11 +51,5 @@ module Ci ...@@ -40,11 +51,5 @@ module Ci
self.new(stage, name: group_name, jobs: grouped_statuses) self.new(stage, name: group_name, jobs: grouped_statuses)
end end
end end
private
def commit_statuses
@commit_statuses ||= CommitStatus.where(id: jobs.map(&:id))
end
end end
end end
...@@ -14,7 +14,8 @@ module Ci ...@@ -14,7 +14,8 @@ module Ci
@pipeline = pipeline @pipeline = pipeline
@name = name @name = name
@status = status @status = status
@warnings = warnings # support ints and booleans
@has_warnings = ActiveRecord::Type::Boolean.new.cast(warnings)
end end
def groups def groups
...@@ -30,7 +31,7 @@ module Ci ...@@ -30,7 +31,7 @@ module Ci
end end
def status def status
@status ||= statuses.latest.status @status ||= statuses.latest.slow_composite_status
end end
def detailed_status(current_user) def detailed_status(current_user)
...@@ -52,11 +53,12 @@ module Ci ...@@ -52,11 +53,12 @@ module Ci
end end
def has_warnings? def has_warnings?
if @warnings.is_a?(Integer) # lazilly calculate the warnings
@warnings > 0 if @has_warnings.nil?
else @has_warnings = statuses.latest.failed_but_allowed.any?
statuses.latest.failed_but_allowed.any?
end end
@has_warnings
end end
def manual_playable? def manual_playable?
......
...@@ -386,13 +386,12 @@ module Ci ...@@ -386,13 +386,12 @@ module Ci
end end
end end
def legacy_stages def legacy_stages_using_sql
# TODO, this needs refactoring, see gitlab-foss#26481. # TODO, this needs refactoring, see gitlab-foss#26481.
stages_query = statuses stages_query = statuses
.group('stage').select(:stage).order('max(stage_idx)') .group('stage').select(:stage).order('max(stage_idx)')
status_sql = statuses.latest.where('stage=sg.stage').status_sql status_sql = statuses.latest.where('stage=sg.stage').legacy_status_sql
warnings_sql = statuses.latest.select('COUNT(*)') warnings_sql = statuses.latest.select('COUNT(*)')
.where('stage=sg.stage').failed_but_allowed.to_sql .where('stage=sg.stage').failed_but_allowed.to_sql
...@@ -405,6 +404,30 @@ module Ci ...@@ -405,6 +404,30 @@ module Ci
end end
end end
def legacy_stages_using_composite_status
stages = statuses.latest
.order(:stage_idx, :stage)
.group_by(&:stage)
stages.map do |stage_name, jobs|
composite_status = Gitlab::Ci::Status::Composite
.new(jobs)
Ci::LegacyStage.new(self,
name: stage_name,
status: composite_status.status,
warnings: composite_status.warnings?)
end
end
def legacy_stages
if Feature.enabled?(:ci_composite_status, default_enabled: false)
legacy_stages_using_composite_status
else
legacy_stages_using_sql
end
end
def valid_commit_sha def valid_commit_sha
if self.sha == Gitlab::Git::BLANK_SHA if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)") self.errors.add(:sha, " cant be 00000000 (branch removal)")
...@@ -635,7 +658,8 @@ module Ci ...@@ -635,7 +658,8 @@ module Ci
def update_status def update_status
retry_optimistic_lock(self) do retry_optimistic_lock(self) do
case latest_builds_status.to_s new_status = latest_builds_status.to_s
case new_status
when 'created' then nil when 'created' then nil
when 'preparing' then prepare when 'preparing' then prepare
when 'pending' then enqueue when 'pending' then enqueue
...@@ -648,7 +672,7 @@ module Ci ...@@ -648,7 +672,7 @@ module Ci
when 'scheduled' then delay when 'scheduled' then delay
else else
raise HasStatus::UnknownStatusError, raise HasStatus::UnknownStatusError,
"Unknown status `#{latest_builds_status}`" "Unknown status `#{new_status}`"
end end
end end
end end
...@@ -907,7 +931,7 @@ module Ci ...@@ -907,7 +931,7 @@ module Ci
def latest_builds_status def latest_builds_status
return 'failed' unless yaml_errors.blank? return 'failed' unless yaml_errors.blank?
statuses.latest.status || 'skipped' statuses.latest.slow_composite_status || 'skipped'
end end
def keep_around_commits def keep_around_commits
......
...@@ -78,7 +78,8 @@ module Ci ...@@ -78,7 +78,8 @@ module Ci
def update_status def update_status
retry_optimistic_lock(self) do retry_optimistic_lock(self) do
case statuses.latest.status new_status = latest_stage_status.to_s
case new_status
when 'created' then nil when 'created' then nil
when 'preparing' then prepare when 'preparing' then prepare
when 'pending' then enqueue when 'pending' then enqueue
...@@ -91,7 +92,7 @@ module Ci ...@@ -91,7 +92,7 @@ module Ci
when 'skipped', nil then skip when 'skipped', nil then skip
else else
raise HasStatus::UnknownStatusError, raise HasStatus::UnknownStatusError,
"Unknown status `#{statuses.latest.status}`" "Unknown status `#{new_status}`"
end end
end end
end end
...@@ -124,5 +125,9 @@ module Ci ...@@ -124,5 +125,9 @@ module Ci
def manual_playable? def manual_playable?
blocked? || skipped? blocked? || skipped?
end end
def latest_stage_status
statuses.latest.slow_composite_status || 'skipped'
end
end end
end end
...@@ -48,6 +48,10 @@ class CommitStatus < ApplicationRecord ...@@ -48,6 +48,10 @@ class CommitStatus < ApplicationRecord
scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) } scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) }
scope :for_ids, -> (ids) { where(id: ids) } scope :for_ids, -> (ids) { where(id: ids) }
scope :with_preloads, -> do
preload(:project, :user)
end
scope :with_needs, -> (names = nil) do scope :with_needs, -> (names = nil) do
needs = Ci::BuildNeed.scoped_build.select(1) needs = Ci::BuildNeed.scoped_build.select(1)
needs = needs.where(name: names) if names needs = needs.where(name: names) if names
...@@ -161,11 +165,11 @@ class CommitStatus < ApplicationRecord ...@@ -161,11 +165,11 @@ class CommitStatus < ApplicationRecord
end end
def self.status_for_prior_stages(index) def self.status_for_prior_stages(index)
before_stage(index).latest.status || 'success' before_stage(index).latest.slow_composite_status || 'success'
end end
def self.status_for_names(names) def self.status_for_names(names)
where(name: names).latest.status || 'success' where(name: names).latest.slow_composite_status || 'success'
end end
def locking_enabled? def locking_enabled?
......
...@@ -10,6 +10,8 @@ module HasStatus ...@@ -10,6 +10,8 @@ module HasStatus
ACTIVE_STATUSES = %w[preparing pending running].freeze ACTIVE_STATUSES = %w[preparing pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
ORDERED_STATUSES = %w[failed preparing pending running manual scheduled canceled success skipped created].freeze ORDERED_STATUSES = %w[failed preparing pending running manual scheduled canceled success skipped created].freeze
PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze
EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3, STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
failed: 4, canceled: 5, skipped: 6, manual: 7, failed: 4, canceled: 5, skipped: 6, manual: 7,
scheduled: 8, preparing: 9 }.freeze scheduled: 8, preparing: 9 }.freeze
...@@ -17,7 +19,7 @@ module HasStatus ...@@ -17,7 +19,7 @@ module HasStatus
UnknownStatusError = Class.new(StandardError) UnknownStatusError = Class.new(StandardError)
class_methods do class_methods do
def status_sql def legacy_status_sql
scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all
scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none
...@@ -53,8 +55,22 @@ module HasStatus ...@@ -53,8 +55,22 @@ module HasStatus
) )
end end
def status def legacy_status
all.pluck(status_sql).first all.pluck(legacy_status_sql).first
end
# This method should not be used.
# This method performs expensive calculation of status:
# 1. By plucking all related objects,
# 2. Or executes expensive SQL query
def slow_composite_status
if Feature.enabled?(:ci_composite_status, default_enabled: false)
Gitlab::Ci::Status::Composite
.new(all, with_allow_failure: columns_hash.key?('allow_failure'))
.status
else
legacy_status
end
end end
def started_at def started_at
......
...@@ -319,6 +319,12 @@ class Namespace < ApplicationRecord ...@@ -319,6 +319,12 @@ class Namespace < ApplicationRecord
private private
def all_projects_with_pages def all_projects_with_pages
if all_projects.pages_metadata_not_migrated.exists?
Gitlab::BackgroundMigration::MigratePagesMetadata.new.perform_on_relation(
all_projects.pages_metadata_not_migrated
)
end
all_projects.with_pages_deployed all_projects.with_pages_deployed
end end
......
...@@ -194,6 +194,16 @@ class PagesDomain < ApplicationRecord ...@@ -194,6 +194,16 @@ class PagesDomain < ApplicationRecord
private private
def pages_deployed? def pages_deployed?
# TODO: remove once `pages_metadatum` is migrated
# https://gitlab.com/gitlab-org/gitlab/issues/33106
unless project.pages_metadatum
Gitlab::BackgroundMigration::MigratePagesMetadata
.new
.perform_on_relation(Project.where(id: project_id))
project.reset
end
project.pages_metadatum&.deployed? project.pages_metadatum&.deployed?
end end
......
...@@ -433,6 +433,11 @@ class Project < ApplicationRecord ...@@ -433,6 +433,11 @@ class Project < ApplicationRecord
joins(:pages_metadatum).merge(ProjectPagesMetadatum.deployed) joins(:pages_metadatum).merge(ProjectPagesMetadatum.deployed)
end end
scope :pages_metadata_not_migrated, -> do
left_outer_joins(:pages_metadatum)
.where(project_pages_metadata: { project_id: nil })
end
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout, chronic_duration_attr :build_timeout_human_readable, :build_timeout,
......
...@@ -62,6 +62,7 @@ class IssueTrackerService < Service ...@@ -62,6 +62,7 @@ class IssueTrackerService < Service
end end
data_values.reject! { |key| data_fields.changed.include?(key) } data_values.reject! { |key| data_fields.changed.include?(key) }
data_values.slice!(*data_fields.attributes.keys)
data_fields.assign_attributes(data_values) if data_values.present? data_fields.assign_attributes(data_values) if data_values.present?
self.properties = {} self.properties = {}
...@@ -71,6 +72,10 @@ class IssueTrackerService < Service ...@@ -71,6 +72,10 @@ class IssueTrackerService < Service
@legacy_properties_data ||= {} @legacy_properties_data ||= {}
end end
def supports_data_fields?
true
end
def data_fields def data_fields
issue_tracker_data || self.build_issue_tracker_data issue_tracker_data || self.build_issue_tracker_data
end end
......
...@@ -291,6 +291,12 @@ class Service < ApplicationRecord ...@@ -291,6 +291,12 @@ class Service < ApplicationRecord
def self.build_from_template(project_id, template) def self.build_from_template(project_id, template)
service = template.dup service = template.dup
if template.supports_data_fields?
data_fields = template.data_fields.dup
data_fields.service = service
end
service.template = false service.template = false
service.project_id = project_id service.project_id = project_id
service.active = false if service.active? && !service.valid? service.active = false if service.active? && !service.valid?
...@@ -309,6 +315,11 @@ class Service < ApplicationRecord ...@@ -309,6 +315,11 @@ class Service < ApplicationRecord
find_by(template: true) find_by(template: true)
end end
# override if needed
def supports_data_fields?
false
end
private private
def cache_project_has_external_issue_tracker def cache_project_has_external_issue_tracker
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module Ci module Ci
class ProcessPipelineService < BaseService class ProcessPipelineService < BaseService
include Gitlab::Utils::StrongMemoize
attr_reader :pipeline attr_reader :pipeline
def execute(pipeline, trigger_build_ids = nil) def execute(pipeline, trigger_build_ids = nil)
...@@ -33,9 +35,9 @@ module Ci ...@@ -33,9 +35,9 @@ module Ci
return unless HasStatus::COMPLETED_STATUSES.include?(current_status) return unless HasStatus::COMPLETED_STATUSES.include?(current_status)
created_processables_in_stage_without_needs(index).select do |build| created_processables_in_stage_without_needs(index).find_each.select do |build|
process_build(build, current_status) process_build(build, current_status)
end end.any?
end end
def process_builds_with_needs(trigger_build_ids) def process_builds_with_needs(trigger_build_ids)
...@@ -92,6 +94,7 @@ module Ci ...@@ -92,6 +94,7 @@ module Ci
def created_processables_in_stage_without_needs(index) def created_processables_in_stage_without_needs(index)
created_processables_without_needs created_processables_without_needs
.with_preloads
.for_stage(index) .for_stage(index)
end end
......
...@@ -12,7 +12,9 @@ ...@@ -12,7 +12,9 @@
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = project_list_cache_key(project, pipeline_status: pipeline_status) - cache_key = project_list_cache_key(project, pipeline_status: pipeline_status)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date) - updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
- css_controls_class = compact_mode ? "" : "flex-lg-row justify-content-lg-between" - show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
- css_controls_class = compact_mode ? [] : ["flex-lg-row", "justify-content-lg-between"]
- css_controls_class << "with-pipeline-status" if show_pipeline_status_icon
- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar' - avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar'
%li.project-row.d-flex{ class: css_class } %li.project-row.d-flex{ class: css_class }
...@@ -58,9 +60,9 @@ ...@@ -58,9 +60,9 @@
.description.d-none.d-sm-block.append-right-default .description.d-none.d-sm-block.append-right-default
= markdown_field(project, :description) = markdown_field(project, :description)
.controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class } .controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class.join(" ") }
.icon-container.d-flex.align-items-center .icon-container.d-flex.align-items-center
- if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project) - if show_pipeline_status_icon
- pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref) - pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref)
%span.icon-wrapper.pipeline-status %span.icon-wrapper.pipeline-status
= render 'ci/status/icon', status: project.last_pipeline.detailed_status(current_user), tooltip_placement: 'top', path: pipeline_path = render 'ci/status/icon', status: project.last_pipeline.detailed_status(current_user), tooltip_placement: 'top', path: pipeline_path
......
---
title: Fixes job overflow in stages dropdown
merge_request:
author:
type: fixed
---
title: Add index on ci_builds for successful Pages deploys
merge_request: 17204
author:
type: added
---
title: Fix inline rendering of videos for uploads with uppercase file extensions
merge_request: 17581
author:
type: fixed
---
title: 'Project list: Align star icons'
merge_request: 17833
author:
type: other
---
title: Fix project imports for pipelines for merge requests
merge_request: 17799
author:
type: fixed
# frozen_string_literal: true
class AddSuccessfullPagesDeployPartialIndexOnCiBuilds < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
INDEX_NAME = 'index_ci_builds_on_project_id_for_successfull_pages_deploy'
def up
add_concurrent_index(
:ci_builds, :project_id,
name: INDEX_NAME,
where: "type='GenericCommitStatus' AND stage='deploy' AND name='pages:deploy' AND status = 'success'"
)
end
def down
remove_concurrent_index_by_name :ci_builds, INDEX_NAME
end
end
...@@ -643,6 +643,7 @@ ActiveRecord::Schema.define(version: 2019_09_27_074328) do ...@@ -643,6 +643,7 @@ ActiveRecord::Schema.define(version: 2019_09_27_074328) do
t.index ["name"], name: "index_ci_builds_on_name_for_security_products_values", where: "((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text]))" t.index ["name"], name: "index_ci_builds_on_name_for_security_products_values", where: "((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text]))"
t.index ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id" t.index ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id"
t.index ["project_id", "status"], name: "index_ci_builds_project_id_and_status_for_live_jobs_partial2", where: "(((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])))" t.index ["project_id", "status"], name: "index_ci_builds_project_id_and_status_for_live_jobs_partial2", where: "(((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])))"
t.index ["project_id"], name: "index_ci_builds_on_project_id_for_successfull_pages_deploy", where: "(((type)::text = 'GenericCommitStatus'::text) AND ((stage)::text = 'deploy'::text) AND ((name)::text = 'pages:deploy'::text) AND ((status)::text = 'success'::text))"
t.index ["protected"], name: "index_ci_builds_on_protected" t.index ["protected"], name: "index_ci_builds_on_protected"
t.index ["queued_at"], name: "index_ci_builds_on_queued_at" t.index ["queued_at"], name: "index_ci_builds_on_queued_at"
t.index ["runner_id"], name: "index_ci_builds_on_runner_id" t.index ["runner_id"], name: "index_ci_builds_on_runner_id"
......
...@@ -36,6 +36,9 @@ will be enabled: ...@@ -36,6 +36,9 @@ will be enabled:
### Protected paths throttle ### Protected paths throttle
NOTE: **Note:** Omnibus GitLab protected paths throttle is deprecated and is scheduled for removal in
GitLab 13.0. Please refer to [Migrate settings from GitLab 12.3 and earlier](../user/admin_area/settings/protected_paths.md#migrate-settings-from-gitlab-123-and-earlier).
GitLab responds with HTTP status code `429` to POST requests at protected paths GitLab responds with HTTP status code `429` to POST requests at protected paths
that exceed 10 requests per minute per IP address. that exceed 10 requests per minute per IP address.
...@@ -124,6 +127,9 @@ The following settings can be configured: ...@@ -124,6 +127,9 @@ The following settings can be configured:
**Installations from source** **Installations from source**
NOTE: **Note:** Rack Attack initializer was temporarily renamed to `rack_attack_new`, to
support backwards compatibility with the one [Omnibus initializer](https://docs.gitlab.com/omnibus/settings/configuration.html#setting-up-paths-to-be-protected-by-rack-attack). It'll be renamed back to `rack_attack.rb` once Omnibus throttle is removed. Please see the [GitLab issue](https://gitlab.com/gitlab-org/gitlab/issues/29952) for more information.
These settings can be found in `config/initializers/rack_attack.rb`. If you are These settings can be found in `config/initializers/rack_attack.rb`. If you are
missing `config/initializers/rack_attack.rb`, the following steps need to be missing `config/initializers/rack_attack.rb`, the following steps need to be
taken in order to enable protection for your GitLab instance: taken in order to enable protection for your GitLab instance:
......
...@@ -20,6 +20,7 @@ include: ...@@ -20,6 +20,7 @@ include:
- [Visibility and access controls](visibility_and_access_controls.md) - [Visibility and access controls](visibility_and_access_controls.md)
- [User and IP rate limits](user_and_ip_rate_limits.md) - [User and IP rate limits](user_and_ip_rate_limits.md)
- [Custom templates repository](instance_template_repository.md) **(PREMIUM)** - [Custom templates repository](instance_template_repository.md) **(PREMIUM)**
- [Protected paths](protected_paths.md) **(CORE ONLY)**
NOTE: **Note:** NOTE: **Note:**
You can change the [first day of the week](../../profile/preferences.md) for the entire GitLab instance You can change the [first day of the week](../../profile/preferences.md) for the entire GitLab instance
......
---
type: reference
---
# Protected paths **(CORE ONLY)**
GitLab protects the following paths with Rack Attack by default:
```
'/users/password',
'/users/sign_in',
'/api/#{API::API.version}/session.json',
'/api/#{API::API.version}/session',
'/users',
'/users/confirmation',
'/unsubscribes/',
'/import/github/personal_access_token'
```
GitLab responds with HTTP status code `429` to POST requests at protected paths
that exceed 10 requests per minute per IP address.
This header is included in responses to blocked requests:
```
Retry-After: 60
```
For example, the following are limited to a maximum 10 requests per minute:
- User sign-in
- User sign-up (if enabled)
- User password reset
After 10 requests, the client must wait 60 seconds before it can
try again.
## Configure using GitLab UI
> Introduced in [GitLab 12.4](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31246).
Throttling of protected paths is enabled by default and can be disabled or
customized on **Admin > Network > Protected Paths**, along with these options:
- Maximum number of requests per period per user.
- Rate limit period in seconds.
- Paths to be protected.
![protected-paths](img/protected_paths.png)
Requests over the rate limit are logged into `auth.log`.
## Migrate settings from GitLab 12.3 and earlier
Omnibus GitLab protected paths throttle is deprecated and is scheduled for removal in
GitLab 13.0. Please see the [GitLab issue](https://gitlab.com/gitlab-org/gitlab/issues/29952) and the [Omnibus GitLab issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4688) for more information.
NOTE: **Note:** If Omnibus settings are present, applications settings will be automatically ignored to avoid generating multiple requests blocks.
To migrate from Omnibus GitLab 12.3 and earlier settings:
1. Disable the Protected Paths throttle from Omnibus, by changing `rack_attack_enabled` value to `false` on [`rack_attack.rb.erb`](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/rack_attack.rb.erb#L18):
```ruby
rack_attack_enabled = false
```
1. Customize and enable your protected paths settings by following [Configure using GitLab UI](#configure-using-gitlab-ui) section.
1. Restart GitLab:
```bash
sudo gitlab-ctl restart
```
That's it. Protected paths throttle are now managed by GitLab admin settings.
...@@ -316,9 +316,7 @@ This header is included in responses to blocked requests: ...@@ -316,9 +316,7 @@ This header is included in responses to blocked requests:
Retry-After: 60 Retry-After: 60
``` ```
Source: See [Protected Paths](../admin_area/settings/protected_paths.md) for more details.
- Search for `rate_limit_requests_per_period`, `rate_limit_period`, and `rack_attack_protected_paths` in [GitLab.com's current Rails app settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
#### Git and container registry failed authentication ban #### Git and container registry failed authentication ban
......
...@@ -6,7 +6,7 @@ module API ...@@ -6,7 +6,7 @@ module API
# against the graphql API. Helper code for the graphql server implementation # against the graphql API. Helper code for the graphql server implementation
# should be in app/graphql/ or lib/gitlab/graphql/ # should be in app/graphql/ or lib/gitlab/graphql/
module GraphqlHelpers module GraphqlHelpers
def conditionally_graphql!(fallback:, query:, context: {}, transform: nil) def run_graphql!(query:, context: {}, transform: nil)
result = GitlabSchema.execute(query, context: context) result = GitlabSchema.execute(query, context: context)
if transform if transform
......
...@@ -19,11 +19,10 @@ module API ...@@ -19,11 +19,10 @@ module API
detail 'This feature was introduced in GitLab 8.13.' detail 'This feature was introduced in GitLab 8.13.'
end end
get '/version' do get '/version' do
conditionally_graphql!( run_graphql!(
query: METADATA_QUERY, query: METADATA_QUERY,
context: { current_user: current_user }, context: { current_user: current_user },
transform: ->(result) { result.dig('data', 'metadata') }, transform: ->(result) { result.dig('data', 'metadata') }
fallback: -> { { version: Gitlab::VERSION, revision: Gitlab.revision } }
) )
end end
end end
......
...@@ -8,8 +8,8 @@ module Banzai ...@@ -8,8 +8,8 @@ module Banzai
# a "Download" link in the case the video cannot be played. # a "Download" link in the case the video cannot be played.
class VideoLinkFilter < HTML::Pipeline::Filter class VideoLinkFilter < HTML::Pipeline::Filter
def call def call
doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |el| doc.xpath(query).each do |el|
el.replace(video_node(doc, el)) if has_video_extension?(el) el.replace(video_node(doc, el))
end end
doc doc
...@@ -17,10 +17,22 @@ module Banzai ...@@ -17,10 +17,22 @@ module Banzai
private private
def has_video_extension?(element) def query
src_attr = context[:asset_proxy_enabled] ? 'data-canonical-src' : 'src' @query ||= begin
src_query = UploaderHelper::SAFE_VIDEO_EXT.map do |ext|
"'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
end
if context[:asset_proxy_enabled].present?
src_query.concat(
UploaderHelper::SAFE_VIDEO_EXT.map do |ext|
"'.#{ext}' = substring(@data-canonical-src, string-length(@data-canonical-src) - #{ext.size})"
end
)
end
element.attr(src_attr).downcase.end_with?(*UploaderHelper::SAFE_VIDEO_EXT) "descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]"
end
end end
def video_node(doc, element) def video_node(doc, element)
......
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Class that will insert record into project_pages_metadata
# for each existing project
class MigratePagesMetadata
def perform(start_id, stop_id)
perform_on_relation(Project.where(id: start_id..stop_id))
end
def perform_on_relation(relation)
successful_pages_deploy = <<~SQL
SELECT TRUE
FROM ci_builds
WHERE ci_builds.type = 'GenericCommitStatus'
AND ci_builds.status = 'success'
AND ci_builds.stage = 'deploy'
AND ci_builds.name = 'pages:deploy'
AND ci_builds.project_id = projects.id
LIMIT 1
SQL
select_from = relation
.select("projects.id", "COALESCE((#{successful_pages_deploy}), FALSE)")
.to_sql
ActiveRecord::Base.connection_pool.with_connection do |connection|
connection.execute <<~SQL
INSERT INTO project_pages_metadata (project_id, deployed)
#{select_from}
ON CONFLICT (project_id) DO NOTHING
SQL
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module Status
class Composite
include Gitlab::Utils::StrongMemoize
# This class accepts an array of arrays/hashes/or objects
def initialize(all_statuses, with_allow_failure: true)
unless all_statuses.respond_to?(:pluck)
raise ArgumentError, "all_statuses needs to respond to `.pluck`"
end
@status_set = Set.new
@status_key = 0
@allow_failure_key = 1 if with_allow_failure
consume_all_statuses(all_statuses)
end
# The status calculation is order dependent,
# 1. In some cases we assume that that status is exact
# if the we only have given statues,
# 2. In other cases we assume that status is of that type
# based on what statuses are no longer valid based on the
# data set that we have
def status
return if none?
strong_memoize(:status) do
if only_of?(:skipped, :ignored)
'skipped'
elsif only_of?(:success, :skipped, :success_with_warnings, :ignored)
'success'
elsif only_of?(:created, :success_with_warnings, :ignored)
'created'
elsif only_of?(:preparing, :success_with_warnings, :ignored)
'preparing'
elsif only_of?(:canceled, :success, :skipped, :success_with_warnings, :ignored)
'canceled'
elsif only_of?(:pending, :created, :skipped, :success_with_warnings, :ignored)
'pending'
elsif any_of?(:running, :pending)
'running'
elsif any_of?(:manual)
'manual'
elsif any_of?(:scheduled)
'scheduled'
elsif any_of?(:preparing)
'preparing'
elsif any_of?(:created)
'running'
else
'failed'
end
end
end
def warnings?
@status_set.include?(:success_with_warnings)
end
private
def none?
@status_set.empty?
end
def any_of?(*names)
names.any? { |name| @status_set.include?(name) }
end
def only_of?(*names)
matching = names.count { |name| @status_set.include?(name) }
matching > 0 &&
matching == @status_set.size
end
def consume_all_statuses(all_statuses)
columns = []
columns[@status_key] = :status
columns[@allow_failure_key] = :allow_failure if @allow_failure_key
all_statuses
.pluck(*columns) # rubocop: disable CodeReuse/ActiveRecord
.each(&method(:consume_status))
end
def consume_status(description)
# convert `"status"` into `["status"]`
description = Array(description)
status =
if success_with_warnings?(description)
:success_with_warnings
elsif ignored_status?(description)
:ignored
else
description[@status_key].to_sym
end
@status_set.add(status)
end
def success_with_warnings?(status)
@allow_failure_key &&
status[@allow_failure_key] &&
HasStatus::PASSED_WITH_WARNINGS_STATUSES.include?(status[@status_key])
end
def ignored_status?(status)
@allow_failure_key &&
status[@allow_failure_key] &&
HasStatus::EXCLUDE_IGNORED_STATUSES.include?(status[@status_key])
end
end
end
end
end
...@@ -52,6 +52,11 @@ module Gitlab ...@@ -52,6 +52,11 @@ module Gitlab
project: restored_project) project: restored_project)
end end
# A Hash of the imported merge request ID -> imported ID.
def merge_requests_mapping
@merge_requests_mapping ||= {}
end
# Loops through the tree of models defined in import_export.yml and # Loops through the tree of models defined in import_export.yml and
# finds them in the imported JSON so they can be instantiated and saved # finds them in the imported JSON so they can be instantiated and saved
# in the DB. The structure and relationships between models are guessed from # in the DB. The structure and relationships between models are guessed from
...@@ -80,10 +85,26 @@ module Gitlab ...@@ -80,10 +85,26 @@ module Gitlab
@saved = false unless restored_project.append_or_update_attribute(relation_key, relation_hash) @saved = false unless restored_project.append_or_update_attribute(relation_key, relation_hash)
save_id_mappings(relation_key, relation_hash_batch, relation_hash)
# Restore the project again, extra query that skips holding the AR objects in memory # Restore the project again, extra query that skips holding the AR objects in memory
@restored_project = Project.find(@project_id) @restored_project = Project.find(@project_id)
end end
# Older, serialized CI pipeline exports may only have a
# merge_request_id and not the full hash of the merge request. To
# import these pipelines, we need to preserve the mapping between
# the old and new the merge request ID.
def save_id_mappings(relation_key, relation_hash_batch, relation_hash)
return unless relation_key == 'merge_requests'
relation_hash = Array(relation_hash)
Array(relation_hash_batch).each_with_index do |raw_data, index|
merge_requests_mapping[raw_data['id']] = relation_hash[index]['id']
end
end
# Remove project models that became group models as we found them at group level. # Remove project models that became group models as we found them at group level.
# This no longer required saving them at the root project level. # This no longer required saving them at the root project level.
# For example, in the case of an existing group label that matched the title. # For example, in the case of an existing group label that matched the title.
...@@ -222,6 +243,7 @@ module Gitlab ...@@ -222,6 +243,7 @@ module Gitlab
relation_sym: relation_key.to_sym, relation_sym: relation_key.to_sym,
relation_hash: relation_hash, relation_hash: relation_hash,
members_mapper: members_mapper, members_mapper: members_mapper,
merge_requests_mapping: merge_requests_mapping,
user: @user, user: @user,
project: @restored_project, project: @restored_project,
excluded_keys: excluded_keys_for_relation(relation_key)) excluded_keys: excluded_keys_for_relation(relation_key))
......
...@@ -55,10 +55,11 @@ module Gitlab ...@@ -55,10 +55,11 @@ module Gitlab
relation_name.to_s.constantize relation_name.to_s.constantize
end end
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:, excluded_keys: []) def initialize(relation_sym:, relation_hash:, members_mapper:, merge_requests_mapping:, user:, project:, excluded_keys: [])
@relation_name = self.class.overrides[relation_sym]&.to_sym || relation_sym @relation_name = self.class.overrides[relation_sym]&.to_sym || relation_sym
@relation_hash = relation_hash.except('noteable_id') @relation_hash = relation_hash.except('noteable_id')
@members_mapper = members_mapper @members_mapper = members_mapper
@merge_requests_mapping = merge_requests_mapping
@user = user @user = user
@project = project @project = project
@imported_object_retries = 0 @imported_object_retries = 0
...@@ -109,7 +110,10 @@ module Gitlab ...@@ -109,7 +110,10 @@ module Gitlab
update_group_references update_group_references
remove_duplicate_assignees remove_duplicate_assignees
setup_pipeline if @relation_name == :'Ci::Pipeline' if @relation_name == :'Ci::Pipeline'
update_merge_request_references
setup_pipeline
end
reset_tokens! reset_tokens!
remove_encrypted_attributes! remove_encrypted_attributes!
...@@ -194,6 +198,28 @@ module Gitlab ...@@ -194,6 +198,28 @@ module Gitlab
@relation_hash['group_id'] = @project.namespace_id @relation_hash['group_id'] = @project.namespace_id
end end
# This code is a workaround for broken project exports that don't
# export merge requests with CI pipelines (i.e. exports that were
# generated from
# https://gitlab.com/gitlab-org/gitlab/merge_requests/17844).
# This method can be removed in GitLab 12.6.
def update_merge_request_references
# If a merge request was properly created, we don't need to fix
# up this export.
return if @relation_hash['merge_request']
merge_request_id = @relation_hash['merge_request_id']
return unless merge_request_id
new_merge_request_id = @merge_requests_mapping[merge_request_id]
return unless new_merge_request_id
@relation_hash['merge_request_id'] = new_merge_request_id
parsed_relation_hash['merge_request_id'] = new_merge_request_id
end
def reset_tokens! def reset_tokens!
return unless Gitlab::ImportExport.reset_tokens? && TOKEN_RESET_MODELS.include?(@relation_name) return unless Gitlab::ImportExport.reset_tokens? && TOKEN_RESET_MODELS.include?(@relation_name)
......
...@@ -15,14 +15,14 @@ function retrieve_tests_metadata() { ...@@ -15,14 +15,14 @@ function retrieve_tests_metadata() {
function update_tests_metadata() { function update_tests_metadata() {
echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
scripts/merge-reports "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "knapsack/rspec*_pg9_*.json" scripts/merge-reports "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" knapsack/rspec*_pg9_*.json
if [[ -n "${TESTS_METADATA_S3_BUCKET}" ]]; then if [[ -n "${TESTS_METADATA_S3_BUCKET}" ]]; then
scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
fi fi
rm -f "knapsack/rspec*.json" rm -f knapsack/rspec*.json
scripts/merge-reports "${FLAKY_RSPEC_SUITE_REPORT_PATH}" "rspec_flaky/all_*.json" scripts/merge-reports "${FLAKY_RSPEC_SUITE_REPORT_PATH}" rspec_flaky/all_*.json
export FLAKY_RSPEC_GENERATE_REPORT="1" export FLAKY_RSPEC_GENERATE_REPORT="1"
scripts/prune-old-flaky-specs "${FLAKY_RSPEC_SUITE_REPORT_PATH}" scripts/prune-old-flaky-specs "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
...@@ -31,7 +31,7 @@ function update_tests_metadata() { ...@@ -31,7 +31,7 @@ function update_tests_metadata() {
scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${FLAKY_RSPEC_SUITE_REPORT_PATH}" scripts/sync-reports put "${TESTS_METADATA_S3_BUCKET}" "${FLAKY_RSPEC_SUITE_REPORT_PATH}"
fi fi
rm -f "rspec_flaky/all_*.json" "rspec_flaky/new_*.json" rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
scripts/insert-rspec-profiling-data scripts/insert-rspec-profiling-data
} }
......
...@@ -104,20 +104,20 @@ describe 'User browses a job', :js do ...@@ -104,20 +104,20 @@ describe 'User browses a job', :js do
it 'displays the failure reason' do it 'displays the failure reason' do
wait_for_all_requests wait_for_all_requests
within('.builds-container') do within('.builds-container') do
build_link = first('.build-job > a') expect(page).to have_selector(
expect(build_link['data-original-title']).to eq('test - failed - (unknown failure)') ".build-job > a[data-original-title='test - failed - (unknown failure)']")
end end
end end
end end
context 'when a failed job has been retried' do context 'when a failed job has been retried' do
let!(:build) { create(:ci_build, :failed, :retried, :trace_artifact, pipeline: pipeline) } let!(:build_retried) { create(:ci_build, :failed, :retried, :trace_artifact, pipeline: pipeline) }
it 'displays the failure reason and retried label' do it 'displays the failure reason and retried label' do
wait_for_all_requests wait_for_all_requests
within('.builds-container') do within('.builds-container') do
build_link = first('.build-job > a') expect(page).to have_selector(
expect(build_link['data-original-title']).to eq('test - failed - (unknown failure) (retried)') ".build-job > a[data-original-title='test - failed - (unknown failure) (retried)']")
end end
end end
end end
......
...@@ -6175,6 +6175,8 @@ ...@@ -6175,6 +6175,8 @@
"finished_at": null, "finished_at": null,
"user_id": 9999, "user_id": 9999,
"duration": null, "duration": null,
"source": "push",
"merge_request_id": null,
"notes": [ "notes": [
{ {
"id": 999, "id": 999,
......
...@@ -286,19 +286,4 @@ describe('Issue card component', () => { ...@@ -286,19 +286,4 @@ describe('Issue card component', () => {
.catch(done.fail); .catch(done.fail);
}); });
}); });
describe('weights', () => {
it('shows weight component is greater than 0', () => {
expect(component.$el.querySelector('.board-card-weight')).not.toBeNull();
});
it('shows weight component when weight is 0', done => {
component.issue.weight = 0;
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-card-weight')).not.toBeNull();
done();
});
});
});
}); });
# frozen_string_literal: true
require 'spec_helper'
describe API::Helpers::GraphqlHelpers do
describe 'run_graphql!' do
let(:query) { '{ metadata { version } }' }
let(:graphql_helper) do
Class.new do
include API::Helpers::GraphqlHelpers
end.new
end
context 'when transform function is provided' do
let(:result) { { 'data' => { 'metadata' => { 'version' => '1.0.0' } } } }
before do
allow(GitlabSchema).to receive(:execute).and_return(result)
end
it 'returns the expected result' do
expect(
graphql_helper.run_graphql!(
query: query,
transform: ->(result) { result.dig('data', 'metadata') }
)
).to eq({ 'version' => '1.0.0' })
end
end
context 'when a transform function is not provided' do
let(:result) { double('result') }
before do
allow(GitlabSchema).to receive(:execute).and_return(result)
end
it 'returns the expected result' do
expect(graphql_helper.run_graphql!(query: query)).to eq(result)
end
end
end
end
...@@ -17,7 +17,8 @@ describe Banzai::Filter::VideoLinkFilter do ...@@ -17,7 +17,8 @@ describe Banzai::Filter::VideoLinkFilter do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
shared_examples 'replaces the image tag with a video tag' do |ext| context 'when the element src has a video extension' do
UploaderHelper::SAFE_VIDEO_EXT.each do |ext|
it "replaces the image tag 'path/video.#{ext}' with a video tag" do it "replaces the image tag 'path/video.#{ext}' with a video tag" do
container = filter(link_to_image("/path/video.#{ext}")).children.first container = filter(link_to_image("/path/video.#{ext}")).children.first
...@@ -38,12 +39,6 @@ describe Banzai::Filter::VideoLinkFilter do ...@@ -38,12 +39,6 @@ describe Banzai::Filter::VideoLinkFilter do
expect(link['target']).to eq '_blank' expect(link['target']).to eq '_blank'
end end
end end
context 'when the element src has a video extension' do
UploaderHelper::SAFE_VIDEO_EXT.each do |ext|
it_behaves_like 'replaces the image tag with a video tag', ext
it_behaves_like 'replaces the image tag with a video tag', ext.upcase
end
end end
context 'when the element src is an image' do context 'when the element src is an image' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BackgroundMigration::MigratePagesMetadata, :migration, schema: 20190919040324 do
let(:projects) { table(:projects) }
subject(:migrate_pages_metadata) { described_class.new }
describe '#perform_on_relation' do
let(:namespaces) { table(:namespaces) }
let(:builds) { table(:ci_builds) }
let(:pages_metadata) { table(:project_pages_metadata) }
it 'marks specified projects with successful pages deployment' do
namespace = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
not_migrated_with_pages = projects.create!(namespace_id: namespace.id, name: 'Not Migrated With Pages')
builds.create!(project_id: not_migrated_with_pages.id, type: 'GenericCommitStatus', status: 'success', stage: 'deploy', name: 'pages:deploy')
migrated = projects.create!(namespace_id: namespace.id, name: 'Migrated')
pages_metadata.create!(project_id: migrated.id, deployed: true)
not_migrated_no_pages = projects.create!(namespace_id: namespace.id, name: 'Not Migrated No Pages')
project_not_in_relation_scope = projects.create!(namespace_id: namespace.id, name: 'Other')
projects_relation = projects.where(id: [not_migrated_with_pages, not_migrated_no_pages, migrated])
migrate_pages_metadata.perform_on_relation(projects_relation)
expect(pages_metadata.find_by_project_id(not_migrated_with_pages.id).deployed).to eq(true)
expect(pages_metadata.find_by_project_id(not_migrated_no_pages.id).deployed).to eq(false)
expect(pages_metadata.find_by_project_id(migrated.id).deployed).to eq(true)
expect(pages_metadata.find_by_project_id(project_not_in_relation_scope.id)).to be_nil
end
end
describe '#perform' do
it 'creates relation and delegates to #perform_on_relation' do
expect(migrate_pages_metadata).to receive(:perform_on_relation).with(projects.where(id: 3..5))
migrate_pages_metadata.perform(3, 5)
end
end
end
require 'spec_helper'
describe Gitlab::Ci::Status::Composite do
set(:pipeline) { create(:ci_pipeline) }
before(:all) do
@statuses = HasStatus::STATUSES_ENUM.map do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, importing: true)]
end.to_h
@statuses_with_allow_failure = HasStatus::STATUSES_ENUM.map do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, allow_failure: true, importing: true)]
end.to_h
end
describe '#status' do
shared_examples 'compares composite with SQL status' do
it 'returns exactly the same result' do
builds = Ci::Build.where(id: all_statuses)
expect(composite_status.status).to eq(builds.legacy_status)
expect(composite_status.warnings?).to eq(builds.failed_but_allowed.any?)
end
end
shared_examples 'validate all combinations' do |perms|
HasStatus::STATUSES_ENUM.keys.combination(perms).each do |statuses|
context "with #{statuses.join(",")}" do
it_behaves_like 'compares composite with SQL status' do
let(:all_statuses) do
statuses.map { |status| @statuses[status] }
end
let(:composite_status) do
described_class.new(all_statuses)
end
end
HasStatus::STATUSES_ENUM.each do |allow_failure_status, _|
context "and allow_failure #{allow_failure_status}" do
it_behaves_like 'compares composite with SQL status' do
let(:all_statuses) do
statuses.map { |status| @statuses[status] } +
[@statuses_with_allow_failure[allow_failure_status]]
end
let(:composite_status) do
described_class.new(all_statuses)
end
end
end
end
end
end
end
it_behaves_like 'validate all combinations', 0
it_behaves_like 'validate all combinations', 1
it_behaves_like 'validate all combinations', 2
end
end
...@@ -96,6 +96,17 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -96,6 +96,17 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(Ci::Pipeline.where(ref: nil)).not_to be_empty expect(Ci::Pipeline.where(ref: nil)).not_to be_empty
end end
it 'restores pipeline for merge request' do
pipeline = Ci::Pipeline.find_by_sha('048721d90c449b244b7b4c53a9186b04330174ec')
expect(pipeline).to be_valid
expect(pipeline.tag).to be_falsey
expect(pipeline.source).to eq('merge_request_event')
expect(pipeline.merge_request.id).to be > 0
expect(pipeline.merge_request.target_branch).to eq('feature')
expect(pipeline.merge_request.source_branch).to eq('feature_conflict')
end
it 'preserves updated_at on issues' do it 'preserves updated_at on issues' do
issue = Issue.where(description: 'Aliquam enim illo et possimus.').first issue = Issue.where(description: 'Aliquam enim illo et possimus.').first
......
...@@ -3,12 +3,14 @@ require 'spec_helper' ...@@ -3,12 +3,14 @@ require 'spec_helper'
describe Gitlab::ImportExport::RelationFactory do describe Gitlab::ImportExport::RelationFactory do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:members_mapper) { double('members_mapper').as_null_object } let(:members_mapper) { double('members_mapper').as_null_object }
let(:merge_requests_mapping) { {} }
let(:user) { create(:admin) } let(:user) { create(:admin) }
let(:excluded_keys) { [] } let(:excluded_keys) { [] }
let(:created_object) do let(:created_object) do
described_class.create(relation_sym: relation_sym, described_class.create(relation_sym: relation_sym,
relation_hash: relation_hash, relation_hash: relation_hash,
members_mapper: members_mapper, members_mapper: members_mapper,
merge_requests_mapping: merge_requests_mapping,
user: user, user: user,
project: project, project: project,
excluded_keys: excluded_keys) excluded_keys: excluded_keys)
......
...@@ -22,6 +22,32 @@ describe Ci::Group do ...@@ -22,6 +22,32 @@ describe Ci::Group do
end end
end end
describe '#status' do
let(:jobs) do
[create(:ci_build, :failed)]
end
context 'when ci_composite_status is enabled' do
before do
stub_feature_flags(ci_composite_status: true)
end
it 'returns a failed status' do
expect(subject.status).to eq('failed')
end
end
context 'when ci_composite_status is disabled' do
before do
stub_feature_flags(ci_composite_status: false)
end
it 'returns a failed status' do
expect(subject.status).to eq('failed')
end
end
end
describe '#detailed_status' do describe '#detailed_status' do
context 'when there is only one item in the group' do context 'when there is only one item in the group' do
it 'calls the status from the object itself' do it 'calls the status from the object itself' do
......
...@@ -216,7 +216,7 @@ describe Ci::LegacyStage do ...@@ -216,7 +216,7 @@ describe Ci::LegacyStage do
context 'when stage has warnings' do context 'when stage has warnings' do
context 'when using memoized warnings flag' do context 'when using memoized warnings flag' do
context 'when there are warnings' do context 'when there are warnings' do
let(:stage) { build(:ci_stage, warnings: 2) } let(:stage) { build(:ci_stage, warnings: true) }
it 'returns true using memoized value' do it 'returns true using memoized value' do
expect(stage).not_to receive(:statuses) expect(stage).not_to receive(:statuses)
...@@ -225,22 +225,13 @@ describe Ci::LegacyStage do ...@@ -225,22 +225,13 @@ describe Ci::LegacyStage do
end end
context 'when there are no warnings' do context 'when there are no warnings' do
let(:stage) { build(:ci_stage, warnings: 0) } let(:stage) { build(:ci_stage, warnings: false) }
it 'returns false using memoized value' do it 'returns false using memoized value' do
expect(stage).not_to receive(:statuses) expect(stage).not_to receive(:statuses)
expect(stage).not_to have_warnings expect(stage).not_to have_warnings
end end
end end
context 'when number of warnings is not a valid value' do
let(:stage) { build(:ci_stage, warnings: true) }
it 'calculates statuses using database queries' do
expect(stage).to receive(:statuses).and_call_original
expect(stage).not_to have_warnings
end
end
end end
context 'when calculating warnings from statuses' do context 'when calculating warnings from statuses' do
......
...@@ -1136,8 +1136,19 @@ describe Ci::Pipeline, :mailer do ...@@ -1136,8 +1136,19 @@ describe Ci::Pipeline, :mailer do
end end
describe '#legacy_stages' do describe '#legacy_stages' do
using RSpec::Parameterized::TableSyntax
subject { pipeline.legacy_stages } subject { pipeline.legacy_stages }
where(:ci_composite_status) do
[false, true]
end
with_them do
before do
stub_feature_flags(ci_composite_status: ci_composite_status)
end
context 'stages list' do context 'stages list' do
it 'returns ordered list of stages' do it 'returns ordered list of stages' do
expect(subject.map(&:name)).to eq(%w[build test deploy]) expect(subject.map(&:name)).to eq(%w[build test deploy])
...@@ -1192,6 +1203,7 @@ describe Ci::Pipeline, :mailer do ...@@ -1192,6 +1203,7 @@ describe Ci::Pipeline, :mailer do
end end
end end
end end
end
describe '#stages_count' do describe '#stages_count' do
it 'returns a valid number of stages' do it 'returns a valid number of stages' do
...@@ -2326,36 +2338,38 @@ describe Ci::Pipeline, :mailer do ...@@ -2326,36 +2338,38 @@ describe Ci::Pipeline, :mailer do
describe '#update_status' do describe '#update_status' do
context 'when pipeline is empty' do context 'when pipeline is empty' do
it 'updates does not change pipeline status' do it 'updates does not change pipeline status' do
expect(pipeline.statuses.latest.status).to be_nil expect(pipeline.statuses.latest.slow_composite_status).to be_nil
expect { pipeline.update_status } expect { pipeline.update_status }
.to change { pipeline.reload.status }.to 'skipped' .to change { pipeline.reload.status }
.from('created')
.to('skipped')
end end
end end
context 'when updating status to pending' do context 'when updating status to pending' do
before do before do
allow(pipeline) create(:ci_build, pipeline: pipeline, status: :running)
.to receive_message_chain(:statuses, :latest, :status)
.and_return(:running)
end end
it 'updates pipeline status to running' do it 'updates pipeline status to running' do
expect { pipeline.update_status } expect { pipeline.update_status }
.to change { pipeline.reload.status }.to 'running' .to change { pipeline.reload.status }
.from('created')
.to('running')
end end
end end
context 'when updating status to scheduled' do context 'when updating status to scheduled' do
before do before do
allow(pipeline) create(:ci_build, pipeline: pipeline, status: :scheduled)
.to receive_message_chain(:statuses, :latest, :status)
.and_return(:scheduled)
end end
it 'updates pipeline status to scheduled' do it 'updates pipeline status to scheduled' do
expect { pipeline.update_status } expect { pipeline.update_status }
.to change { pipeline.reload.status }.to 'scheduled' .to change { pipeline.reload.status }
.from('created')
.to('scheduled')
end end
end end
......
...@@ -130,7 +130,7 @@ describe Ci::Stage, :models do ...@@ -130,7 +130,7 @@ describe Ci::Stage, :models do
context 'when statuses status was not recognized' do context 'when statuses status was not recognized' do
before do before do
allow(stage) allow(stage)
.to receive_message_chain(:statuses, :latest, :status) .to receive(:latest_stage_status)
.and_return(:unknown) .and_return(:unknown)
end end
......
...@@ -321,7 +321,7 @@ describe CommitStatus do ...@@ -321,7 +321,7 @@ describe CommitStatus do
end end
it 'returns a correct compound status' do it 'returns a correct compound status' do
expect(described_class.all.status).to eq 'running' expect(described_class.all.slow_composite_status).to eq 'running'
end end
end end
...@@ -331,7 +331,7 @@ describe CommitStatus do ...@@ -331,7 +331,7 @@ describe CommitStatus do
end end
it 'returns status that indicates success' do it 'returns status that indicates success' do
expect(described_class.all.status).to eq 'success' expect(described_class.all.slow_composite_status).to eq 'success'
end end
end end
...@@ -342,7 +342,7 @@ describe CommitStatus do ...@@ -342,7 +342,7 @@ describe CommitStatus do
end end
it 'returns status according to the scope' do it 'returns status according to the scope' do
expect(described_class.latest.status).to eq 'success' expect(described_class.latest.slow_composite_status).to eq 'success'
end end
end end
end end
......
...@@ -3,12 +3,15 @@ ...@@ -3,12 +3,15 @@
require 'spec_helper' require 'spec_helper'
describe HasStatus do describe HasStatus do
describe '.status' do describe '.slow_composite_status' do
subject { CommitStatus.status } using RSpec::Parameterized::TableSyntax
subject { CommitStatus.slow_composite_status }
shared_examples 'build status summary' do shared_examples 'build status summary' do
context 'all successful' do context 'all successful' do
let!(:statuses) { Array.new(2) { create(type, status: :success) } } let!(:statuses) { Array.new(2) { create(type, status: :success) } }
it { is_expected.to eq 'success' } it { is_expected.to eq 'success' }
end end
...@@ -165,6 +168,15 @@ describe HasStatus do ...@@ -165,6 +168,15 @@ describe HasStatus do
end end
end end
where(:ci_composite_status) do
[false, true]
end
with_them do
before do
stub_feature_flags(ci_composite_status: ci_composite_status)
end
context 'ci build statuses' do context 'ci build statuses' do
let(:type) { :ci_build } let(:type) { :ci_build }
...@@ -177,6 +189,7 @@ describe HasStatus do ...@@ -177,6 +189,7 @@ describe HasStatus do
it_behaves_like 'build status summary' it_behaves_like 'build status summary'
end end
end end
end
context 'for scope with one status' do context 'for scope with one status' do
shared_examples 'having a job' do |status| shared_examples 'having a job' do |status|
...@@ -372,8 +385,8 @@ describe HasStatus do ...@@ -372,8 +385,8 @@ describe HasStatus do
end end
end end
describe '.status_sql' do describe '.legacy_status_sql' do
subject { Ci::Build.status_sql } subject { Ci::Build.legacy_status_sql }
it 'returns SQL' do it 'returns SQL' do
puts subject puts subject
......
...@@ -928,12 +928,34 @@ describe Namespace do ...@@ -928,12 +928,34 @@ describe Namespace do
let(:project) { create(:project, namespace: namespace) } let(:project) { create(:project, namespace: namespace) }
context 'when there are pages deployed for the project' do context 'when there are pages deployed for the project' do
context 'but pages metadata is not migrated' do
before do
generic_commit_status = create(:generic_commit_status, :success, stage: 'deploy', name: 'pages:deploy')
generic_commit_status.update!(project: project)
project.pages_metadatum.destroy!
end
it 'migrates pages metadata and returns the virual domain' do
virtual_domain = namespace.pages_virtual_domain
expect(project.reload.pages_metadatum.deployed).to eq(true)
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
expect(virtual_domain.lookup_paths).not_to be_empty
end
end
context 'and pages metadata is migrated' do
before do before do
project.mark_pages_as_deployed project.mark_pages_as_deployed
end end
it 'returns the virual domain' do it 'returns the virual domain' do
expect(namespace.pages_virtual_domain).to be_an_instance_of(Pages::VirtualDomain) virtual_domain = namespace.pages_virtual_domain
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
expect(virtual_domain.lookup_paths).not_to be_empty
end
end end
end end
end end
......
...@@ -569,7 +569,9 @@ describe PagesDomain do ...@@ -569,7 +569,9 @@ describe PagesDomain do
context 'when there are pages deployed for the project' do context 'when there are pages deployed for the project' do
before do before do
project.mark_pages_as_deployed generic_commit_status = create(:generic_commit_status, :success, stage: 'deploy', name: 'pages:deploy')
generic_commit_status.update!(project: project)
project.pages_metadatum.destroy!
project.reload project.reload
end end
...@@ -578,6 +580,12 @@ describe PagesDomain do ...@@ -578,6 +580,12 @@ describe PagesDomain do
expect(pages_domain.pages_virtual_domain).to be_an_instance_of(Pages::VirtualDomain) expect(pages_domain.pages_virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
end end
it 'migrates project pages metadata' do
expect { pages_domain.pages_virtual_domain }.to change {
project.reload.pages_metadatum&.deployed
}.from(nil).to(true)
end
end end
end end
end end
...@@ -282,7 +282,7 @@ describe JiraService do ...@@ -282,7 +282,7 @@ describe JiraService do
context 'when data are stored in properties' do context 'when data are stored in properties' do
let(:properties) { data_params.merge(title: title, description: description) } let(:properties) { data_params.merge(title: title, description: description) }
let!(:service) do let!(:service) do
create(:jira_service, :without_properties_callback, properties: properties) create(:jira_service, :without_properties_callback, properties: properties.merge(additional: 'something'))
end end
it_behaves_like 'issue tracker fields' it_behaves_like 'issue tracker fields'
......
...@@ -5107,6 +5107,16 @@ describe Project do ...@@ -5107,6 +5107,16 @@ describe Project do
end end
end end
describe '.pages_metadata_not_migrated' do
it 'returns only projects that have pages deployed' do
_project_with_pages_metadata_migrated = create(:project)
project_with_pages_metadata_not_migrated = create(:project)
project_with_pages_metadata_not_migrated.pages_metadatum.destroy!
expect(described_class.pages_metadata_not_migrated).to contain_exactly(project_with_pages_metadata_not_migrated)
end
end
describe '#pages_group_root?' do describe '#pages_group_root?' do
it 'returns returns true if pages_url is same as pages_group_url' do it 'returns returns true if pages_url is same as pages_group_url' do
project = build(:project) project = build(:project)
......
...@@ -78,10 +78,11 @@ describe Service do ...@@ -78,10 +78,11 @@ describe Service do
end end
describe "Template" do describe "Template" do
let(:project) { create(:project) }
describe '.build_from_template' do describe '.build_from_template' do
context 'when template is invalid' do context 'when template is invalid' do
it 'sets service template to inactive when template is invalid' do it 'sets service template to inactive when template is invalid' do
project = create(:project)
template = build(:prometheus_service, template: true, active: true, properties: {}) template = build(:prometheus_service, template: true, active: true, properties: {})
template.save(validate: false) template.save(validate: false)
...@@ -91,6 +92,64 @@ describe Service do ...@@ -91,6 +92,64 @@ describe Service do
expect(service.active).to be false expect(service.active).to be false
end end
end end
describe 'build issue tracker from a template' do
let(:title) { 'custom title' }
let(:description) { 'custom description' }
let(:url) { 'http://jira.example.com' }
let(:api_url) { 'http://api-jira.example.com' }
let(:username) { 'jira-username' }
let(:password) { 'jira-password' }
let(:data_params) do
{
url: url, api_url: api_url,
username: username, password: password
}
end
shared_examples 'service creation from a template' do
it 'creates a correct service' do
service = described_class.build_from_template(project.id, template)
expect(service).to be_active
expect(service.title).to eq(title)
expect(service.description).to eq(description)
expect(service.url).to eq(url)
expect(service.api_url).to eq(api_url)
expect(service.username).to eq(username)
expect(service.password).to eq(password)
end
end
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab-foss/issues/63084
context 'when data are stored in properties' do
let(:properties) { data_params.merge(title: title, description: description) }
let!(:template) do
create(:jira_service, :without_properties_callback, template: true, properties: properties.merge(additional: 'something'))
end
it_behaves_like 'service creation from a template'
end
context 'when data are stored in separated fields' do
let(:template) do
create(:jira_service, data_params.merge(properties: {}, title: title, description: description, template: true))
end
it_behaves_like 'service creation from a template'
end
context 'when data are stored in both properties and separated fields' do
let(:properties) { data_params.merge(title: title, description: description) }
let(:template) do
create(:jira_service, :without_properties_callback, active: true, template: true, properties: properties).tap do |service|
create(:jira_tracker_data, data_params.merge(service: service))
end
end
it_behaves_like 'service creation from a template'
end
end
end end
describe "for pushover service" do describe "for pushover service" do
...@@ -104,7 +163,6 @@ describe Service do ...@@ -104,7 +163,6 @@ describe Service do
api_key: '123456789' api_key: '123456789'
}) })
end end
let(:project) { create(:project) }
describe 'is prefilled for projects pushover service' do describe 'is prefilled for projects pushover service' do
it "has all fields prefilled" do it "has all fields prefilled" do
......
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