...@@ -17,6 +17,8 @@ $monokai-diff-border: #808080; ...@@ -17,6 +17,8 @@ $monokai-diff-border: #808080;
$monokai-highlight-bg: #ffe792; $monokai-highlight-bg: #ffe792;
$monokai-over-bg: #9f9ab5; $monokai-over-bg: #9f9ab5;
$monokai-expanded-bg: #3e3e3e; $monokai-expanded-bg: #3e3e3e;
$monokai-coverage: #a6e22e;
$monokai-no-coverage: #fd971f;
$monokai-new-bg: rgba(166, 226, 46, 0.1); $monokai-new-bg: rgba(166, 226, 46, 0.1);
$monokai-new-idiff: rgba(166, 226, 46, 0.15); $monokai-new-idiff: rgba(166, 226, 46, 0.15);
...@@ -124,12 +126,18 @@ $monokai-gi: #a6e22e; ...@@ -124,12 +126,18 @@ $monokai-gi: #a6e22e;
} }
td.diff-line-num.hll:not(.empty-cell), td.diff-line-num.hll:not(.empty-cell),
td.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) { td.line_content.hll:not(.empty-cell) {
background-color: $monokai-line-empty-bg; background-color: $monokai-line-empty-bg;
border-color: $monokai-line-empty-border; border-color: $monokai-line-empty-border;
} }
.line-coverage {
@include line-coverage-border-color($monokai-coverage, $monokai-no-coverage);
}
.diff-line-num.new, .diff-line-num.new,
.line-coverage.new,
.line_content.new { .line_content.new {
@include diff-background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border); @include diff-background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border);
...@@ -140,6 +148,7 @@ $monokai-gi: #a6e22e; ...@@ -140,6 +148,7 @@ $monokai-gi: #a6e22e;
} }
.diff-line-num.old, .diff-line-num.old,
.line-coverage.old,
.line_content.old { .line_content.old {
@include diff-background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border); @include diff-background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border);
...@@ -168,6 +177,7 @@ $monokai-gi: #a6e22e; ...@@ -168,6 +177,7 @@ $monokai-gi: #a6e22e;
&:not(.diff-expanded) + .diff-expanded, &:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) { &.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
border-top: 1px solid $black; border-top: 1px solid $black;
} }
...@@ -175,6 +185,7 @@ $monokai-gi: #a6e22e; ...@@ -175,6 +185,7 @@ $monokai-gi: #a6e22e;
&.diff-expanded { &.diff-expanded {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
background: $monokai-expanded-bg; background: $monokai-expanded-bg;
border-color: $monokai-expanded-bg; border-color: $monokai-expanded-bg;
... ...
......
...@@ -51,6 +51,15 @@ ...@@ -51,6 +51,15 @@
@include match-line; @include match-line;
} }
.line-coverage {
@include line-coverage-border-color($green-500, $orange-500);
&.old,
&.new {
background-color: $white-normal;
}
}
.diff-line-num { .diff-line-num {
&.old { &.old {
a { a {
...@@ -83,6 +92,7 @@ ...@@ -83,6 +92,7 @@
&:not(.diff-expanded) + .diff-expanded, &:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) { &.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
border-top: 1px solid $none-expanded-border; border-top: 1px solid $none-expanded-border;
} }
...@@ -90,6 +100,7 @@ ...@@ -90,6 +100,7 @@
&.diff-expanded { &.diff-expanded {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
background: $none-expanded-bg; background: $none-expanded-bg;
border-color: $none-expanded-bg; border-color: $none-expanded-bg;
... ...
......
...@@ -21,6 +21,8 @@ $solarized-dark-highlight: #094554; ...@@ -21,6 +21,8 @@ $solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652; $solarized-dark-hll-bg: #174652;
$solarized-dark-over-bg: #9f9ab5; $solarized-dark-over-bg: #9f9ab5;
$solarized-dark-expanded-bg: #010d10; $solarized-dark-expanded-bg: #010d10;
$solarized-dark-coverage: #859900;
$solarized-dark-no-coverage: #cb4b16;
$solarized-dark-c: #586e75; $solarized-dark-c: #586e75;
$solarized-dark-err: #93a1a1; $solarized-dark-err: #93a1a1;
$solarized-dark-g: #93a1a1; $solarized-dark-g: #93a1a1;
...@@ -128,12 +130,18 @@ $solarized-dark-il: #2aa198; ...@@ -128,12 +130,18 @@ $solarized-dark-il: #2aa198;
} }
td.diff-line-num.hll:not(.empty-cell), td.diff-line-num.hll:not(.empty-cell),
td.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) { td.line_content.hll:not(.empty-cell) {
background-color: $solarized-dark-hll-bg; background-color: $solarized-dark-hll-bg;
border-color: darken($solarized-dark-hll-bg, 15%); border-color: darken($solarized-dark-hll-bg, 15%);
} }
.line-coverage {
@include line-coverage-border-color($solarized-dark-coverage, $solarized-dark-no-coverage);
}
.diff-line-num.new, .diff-line-num.new,
.line-coverage.new,
.line_content.new { .line_content.new {
@include diff-background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border); @include diff-background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border);
...@@ -144,6 +152,7 @@ $solarized-dark-il: #2aa198; ...@@ -144,6 +152,7 @@ $solarized-dark-il: #2aa198;
} }
.diff-line-num.old, .diff-line-num.old,
.line-coverage.old,
.line_content.old { .line_content.old {
@include diff-background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border); @include diff-background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border);
...@@ -172,6 +181,7 @@ $solarized-dark-il: #2aa198; ...@@ -172,6 +181,7 @@ $solarized-dark-il: #2aa198;
&:not(.diff-expanded) + .diff-expanded, &:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) { &.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
border-top: 1px solid $black; border-top: 1px solid $black;
} }
...@@ -179,6 +189,7 @@ $solarized-dark-il: #2aa198; ...@@ -179,6 +189,7 @@ $solarized-dark-il: #2aa198;
&.diff-expanded { &.diff-expanded {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
background: $solarized-dark-expanded-bg; background: $solarized-dark-expanded-bg;
border-color: $solarized-dark-expanded-bg; border-color: $solarized-dark-expanded-bg;
... ...
......
...@@ -23,6 +23,8 @@ $solarized-light-hll-bg: #ddd8c5; ...@@ -23,6 +23,8 @@ $solarized-light-hll-bg: #ddd8c5;
$solarized-light-over-bg: #ded7fc; $solarized-light-over-bg: #ded7fc;
$solarized-light-expanded-border: #d2cdbd; $solarized-light-expanded-border: #d2cdbd;
$solarized-light-expanded-bg: #ece6d4; $solarized-light-expanded-bg: #ece6d4;
$solarized-light-coverage: #859900;
$solarized-light-no-coverage: #cb4b16;
$solarized-light-c: #93a1a1; $solarized-light-c: #93a1a1;
$solarized-light-err: #586e75; $solarized-light-err: #586e75;
$solarized-light-g: #586e75; $solarized-light-g: #586e75;
...@@ -135,12 +137,18 @@ $solarized-light-il: #2aa198; ...@@ -135,12 +137,18 @@ $solarized-light-il: #2aa198;
} }
td.diff-line-num.hll:not(.empty-cell), td.diff-line-num.hll:not(.empty-cell),
td.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) { td.line_content.hll:not(.empty-cell) {
background-color: $solarized-light-hll-bg; background-color: $solarized-light-hll-bg;
border-color: darken($solarized-light-hll-bg, 15%); border-color: darken($solarized-light-hll-bg, 15%);
} }
.line-coverage {
@include line-coverage-border-color($solarized-light-coverage, $solarized-light-no-coverage);
}
.diff-line-num.new, .diff-line-num.new,
.line-coverage.new,
.line_content.new { .line_content.new {
@include diff-background($solarized-light-new-bg, @include diff-background($solarized-light-new-bg,
$solarized-light-new-idiff, $solarized-light-border); $solarized-light-new-idiff, $solarized-light-border);
...@@ -152,6 +160,7 @@ $solarized-light-il: #2aa198; ...@@ -152,6 +160,7 @@ $solarized-light-il: #2aa198;
} }
.diff-line-num.old, .diff-line-num.old,
.line-coverage.old,
.line_content.old { .line_content.old {
@include diff-background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border); @include diff-background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border);
...@@ -180,6 +189,7 @@ $solarized-light-il: #2aa198; ...@@ -180,6 +189,7 @@ $solarized-light-il: #2aa198;
&:not(.diff-expanded) + .diff-expanded, &:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) { &.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
border-top: 1px solid $solarized-light-expanded-border; border-top: 1px solid $solarized-light-expanded-border;
} }
...@@ -187,6 +197,7 @@ $solarized-light-il: #2aa198; ...@@ -187,6 +197,7 @@ $solarized-light-il: #2aa198;
&.diff-expanded { &.diff-expanded {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
background: $solarized-light-expanded-bg; background: $solarized-light-expanded-bg;
border-color: $solarized-light-expanded-bg; border-color: $solarized-light-expanded-bg;
... ...
......
...@@ -151,6 +151,7 @@ pre.code, ...@@ -151,6 +151,7 @@ pre.code,
&:not(.diff-expanded) + .diff-expanded, &:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) { &.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
border-top: 1px solid $white-expanded-border; border-top: 1px solid $white-expanded-border;
} }
...@@ -158,6 +159,7 @@ pre.code, ...@@ -158,6 +159,7 @@ pre.code,
&.diff-expanded { &.diff-expanded {
> .diff-line-num, > .diff-line-num,
> .line-coverage,
> .line_content { > .line_content {
background: $white-expanded-bg; background: $white-expanded-bg;
border-color: $white-expanded-bg; border-color: $white-expanded-bg;
...@@ -197,6 +199,22 @@ pre.code, ...@@ -197,6 +199,22 @@ pre.code,
background-color: $line-select-yellow; background-color: $line-select-yellow;
} }
} }
.line-coverage {
@include line-coverage-border-color($green-500, $orange-500);
&.old {
background-color: $line-removed;
}
&.new {
background-color: $line-added;
}
&.hll:not(.empty-cell) {
background-color: $line-select-yellow;
}
}
} }
// highlight line via anchor // highlight line via anchor
... ...
......
...@@ -514,6 +514,10 @@ table.code { ...@@ -514,6 +514,10 @@ table.code {
position: absolute; position: absolute;
left: 0.5em; left: 0.5em;
} }
&.with-coverage::before {
left: 0;
}
} }
&.new { &.new {
...@@ -522,6 +526,10 @@ table.code { ...@@ -522,6 +526,10 @@ table.code {
position: absolute; position: absolute;
left: 0.5em; left: 0.5em;
} }
&.with-coverage::before {
left: 0;
}
} }
} }
} }
... ...
......
...@@ -14,7 +14,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -14,7 +14,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
skip_before_action :merge_request, only: [:index, :bulk_update] skip_before_action :merge_request, only: [:index, :bulk_update]
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update] before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :authorize_read_actual_head_pipeline!, only: [:test_reports, :exposed_artifacts] before_action :authorize_read_actual_head_pipeline!, only: [:test_reports, :exposed_artifacts, :coverage_reports]
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase] before_action :check_user_can_push_to_source_branch!, only: [:rebase]
...@@ -63,6 +63,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -63,6 +63,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar') @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
@current_user_data = UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json @current_user_data = UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs @show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
@coverage_path = coverage_reports_project_merge_request_path(@project, @merge_request, format: :json) if @merge_request.has_coverage_reports?
set_pipeline_variables set_pipeline_variables
...@@ -131,6 +132,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -131,6 +132,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
reports_response(@merge_request.compare_test_reports) reports_response(@merge_request.compare_test_reports)
end end
def coverage_reports
if @merge_request.has_coverage_reports?
reports_response(@merge_request.find_coverage_reports)
else
head :no_content
end
end
def exposed_artifacts def exposed_artifacts
if @merge_request.has_exposed_artifacts? if @merge_request.has_exposed_artifacts?
reports_response(@merge_request.find_exposed_artifacts) reports_response(@merge_request.find_exposed_artifacts)
... ...
......
...@@ -916,6 +916,14 @@ module Ci ...@@ -916,6 +916,14 @@ module Ci
end end
end end
def collect_coverage_reports!(coverage_report)
each_report(Ci::JobArtifact::COVERAGE_REPORT_FILE_TYPES) do |file_type, blob|
Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, coverage_report)
end
coverage_report
end
def report_artifacts def report_artifacts
job_artifacts.with_reports job_artifacts.with_reports
end end
... ...
......
...@@ -11,6 +11,7 @@ module Ci ...@@ -11,6 +11,7 @@ module Ci
NotSupportedAdapterError = Class.new(StandardError) NotSupportedAdapterError = Class.new(StandardError)
TEST_REPORT_FILE_TYPES = %w[junit].freeze TEST_REPORT_FILE_TYPES = %w[junit].freeze
COVERAGE_REPORT_FILE_TYPES = %w[cobertura].freeze
NON_ERASABLE_FILE_TYPES = %w[trace].freeze NON_ERASABLE_FILE_TYPES = %w[trace].freeze
DEFAULT_FILE_NAMES = { DEFAULT_FILE_NAMES = {
archive: nil, archive: nil,
...@@ -29,7 +30,8 @@ module Ci ...@@ -29,7 +30,8 @@ module Ci
performance: 'performance.json', performance: 'performance.json',
metrics: 'metrics.txt', metrics: 'metrics.txt',
lsif: 'lsif.json', lsif: 'lsif.json',
dotenv: '.env' dotenv: '.env',
cobertura: 'cobertura-coverage.xml'
}.freeze }.freeze
INTERNAL_TYPES = { INTERNAL_TYPES = {
...@@ -45,6 +47,7 @@ module Ci ...@@ -45,6 +47,7 @@ module Ci
network_referee: :gzip, network_referee: :gzip,
lsif: :gzip, lsif: :gzip,
dotenv: :gzip, dotenv: :gzip,
cobertura: :gzip,
# All these file formats use `raw` as we need to store them uncompressed # All these file formats use `raw` as we need to store them uncompressed
# for Frontend to fetch the files and do analysis # for Frontend to fetch the files and do analysis
...@@ -92,6 +95,10 @@ module Ci ...@@ -92,6 +95,10 @@ module Ci
with_file_types(TEST_REPORT_FILE_TYPES) with_file_types(TEST_REPORT_FILE_TYPES)
end end
scope :coverage_reports, -> do
with_file_types(COVERAGE_REPORT_FILE_TYPES)
end
scope :erasable, -> do scope :erasable, -> do
types = self.file_types.reject { |file_type| NON_ERASABLE_FILE_TYPES.include?(file_type) }.values types = self.file_types.reject { |file_type| NON_ERASABLE_FILE_TYPES.include?(file_type) }.values
...@@ -121,7 +128,8 @@ module Ci ...@@ -121,7 +128,8 @@ module Ci
metrics_referee: 13, ## runner referees metrics_referee: 13, ## runner referees
network_referee: 14, ## runner referees network_referee: 14, ## runner referees
lsif: 15, # LSIF data for code navigation lsif: 15, # LSIF data for code navigation
dotenv: 16 dotenv: 16,
cobertura: 17
} }
enum file_format: { enum file_format: {
... ...
......
...@@ -820,6 +820,14 @@ module Ci ...@@ -820,6 +820,14 @@ module Ci
end end
end end
def coverage_reports
Gitlab::Ci::Reports::CoverageReports.new.tap do |coverage_reports|
builds.latest.with_reports(Ci::JobArtifact.coverage_reports).each do |build|
build.collect_coverage_reports!(coverage_reports)
end
end
end
def has_exposed_artifacts? def has_exposed_artifacts?
complete? && builds.latest.with_exposed_artifacts.exists? complete? && builds.latest.with_exposed_artifacts.exists?
end end
... ...
......
...@@ -135,29 +135,11 @@ class Event < ApplicationRecord ...@@ -135,29 +135,11 @@ class Event < ApplicationRecord
super(presenter_class: ::EventPresenter) super(presenter_class: ::EventPresenter)
end end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def visible_to_user?(user = nil) def visible_to_user?(user = nil)
if push_action? || commit_note? return false unless capability.present?
Ability.allowed?(user, :download_code, project)
elsif membership_changed? Ability.allowed?(user, capability, permission_object)
Ability.allowed?(user, :read_project, project)
elsif created_project_action?
Ability.allowed?(user, :read_project, project)
elsif issue? || issue_note?
Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
elsif personal_snippet_note? || project_snippet_note?
Ability.allowed?(user, :read_snippet, note_target)
elsif milestone?
Ability.allowed?(user, :read_milestone, project)
else
false # No other event types are visible
end
end end
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
def resource_parent def resource_parent
project || group project || group
...@@ -364,8 +346,38 @@ class Event < ApplicationRecord ...@@ -364,8 +346,38 @@ class Event < ApplicationRecord
Event._to_partial_path Event._to_partial_path
end end
protected
def capability
@capability ||= begin
if push_action? || commit_note?
:download_code
elsif membership_changed? || created_project_action?
:read_project
elsif issue? || issue_note?
:read_issue
elsif merge_request? || merge_request_note?
:read_merge_request
elsif personal_snippet_note? || project_snippet_note?
:read_snippet
elsif milestone?
:read_milestone
end
end
end
private private
def permission_object
if note?
note_target
elsif target_id.present?
target
else
project
end
end
def push_action_name def push_action_name
if new_ref? if new_ref?
"pushed new" "pushed new"
... ...
......
...@@ -567,6 +567,10 @@ class MergeRequest < ApplicationRecord ...@@ -567,6 +567,10 @@ class MergeRequest < ApplicationRecord
diffs.modified_paths diffs.modified_paths
end end
def new_paths
diffs.diff_files.map(&:new_path)
end
def diff_base_commit def diff_base_commit
if merge_request_diff.persisted? if merge_request_diff.persisted?
merge_request_diff.base_commit merge_request_diff.base_commit
...@@ -1295,6 +1299,24 @@ class MergeRequest < ApplicationRecord ...@@ -1295,6 +1299,24 @@ class MergeRequest < ApplicationRecord
compare_reports(Ci::CompareTestReportsService) compare_reports(Ci::CompareTestReportsService)
end end
def has_coverage_reports?
return false unless Feature.enabled?(:coverage_report_view, project)
actual_head_pipeline&.has_reports?(Ci::JobArtifact.coverage_reports)
end
# TODO: this method and compare_test_reports use the same
# result type, which is handled by the controller's #reports_response.
# we should minimize mistakes by isolating the common parts.
# issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
def find_coverage_reports
unless has_coverage_reports?
return { status: :error, status_reason: 'This merge request does not have coverage reports' }
end
compare_reports(Ci::GenerateCoverageReportsService)
end
def has_exposed_artifacts? def has_exposed_artifacts?
return false unless Feature.enabled?(:ci_expose_arbitrary_artifacts_in_mr, default_enabled: true) return false unless Feature.enabled?(:ci_expose_arbitrary_artifacts_in_mr, default_enabled: true)
...@@ -1318,7 +1340,7 @@ class MergeRequest < ApplicationRecord ...@@ -1318,7 +1340,7 @@ class MergeRequest < ApplicationRecord
# issue: https://gitlab.com/gitlab-org/gitlab/issues/34224 # issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
def compare_reports(service_class, current_user = nil) def compare_reports(service_class, current_user = nil)
with_reactive_cache(service_class.name, current_user&.id) do |data| with_reactive_cache(service_class.name, current_user&.id) do |data|
unless service_class.new(project, current_user) unless service_class.new(project, current_user, id: id)
.latest?(base_pipeline, actual_head_pipeline, data) .latest?(base_pipeline, actual_head_pipeline, data)
raise InvalidateReactiveCache raise InvalidateReactiveCache
end end
...@@ -1335,7 +1357,7 @@ class MergeRequest < ApplicationRecord ...@@ -1335,7 +1357,7 @@ class MergeRequest < ApplicationRecord
raise NameError, service_class unless service_class < Ci::CompareReportsBaseService raise NameError, service_class unless service_class < Ci::CompareReportsBaseService
current_user = User.find_by(id: current_user_id) current_user = User.find_by(id: current_user_id)
service_class.new(project, current_user).execute(base_pipeline, actual_head_pipeline) service_class.new(project, current_user, id: id).execute(base_pipeline, actual_head_pipeline)
end end
def all_commits def all_commits
... ...
......
...@@ -1672,6 +1672,16 @@ class User < ApplicationRecord ...@@ -1672,6 +1672,16 @@ class User < ApplicationRecord
callouts.any? callouts.any?
end end
def gitlab_employee?
strong_memoize(:gitlab_employee) do
if Gitlab.com?
Mail::Address.new(email).domain == "gitlab.com"
else
false
end
end
end
# @deprecated # @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
... ...
......
# frozen_string_literal: true
module Projects
module ImportExport
class ProjectExportPresenter < Gitlab::View::Presenter::Delegated
include ActiveModel::Serializers::JSON
presents :project
def project_members
super + converted_group_members
end
def description
self.respond_to?(:override_description) ? override_description : super
end
private
def converted_group_members
group_members.each do |group_member|
group_member.source_type = 'Project' # Make group members project members of the future import
end
end
# rubocop: disable CodeReuse/ActiveRecord
def group_members
return [] unless current_user.can?(:admin_group, project.group)
# We need `.where.not(user_id: nil)` here otherwise when a group has an
# invitee, it would make the following query return 0 rows since a NULL
# user_id would be present in the subquery
# See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values
non_null_user_ids = project.project_members.where.not(user_id: nil).select(:user_id)
GroupMembersFinder.new(project.group).execute.where.not(user_id: non_null_user_ids)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
# frozen_string_literal: true
module Ci
# TODO: a couple of points with this approach:
# + reuses existing architecture and reactive caching
# - it's not a report comparison and some comparing features must be turned off.
# see CompareReportsBaseService for more notes.
# issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
class GenerateCoverageReportsService < CompareReportsBaseService
def execute(base_pipeline, head_pipeline)
merge_request = MergeRequest.find_by_id(params[:id])
{
status: :parsed,
key: key(base_pipeline, head_pipeline),
data: head_pipeline.coverage_reports.pick(merge_request.new_paths)
}
rescue => e
Gitlab::ErrorTracking.track_exception(e, project_id: project.id)
{
status: :error,
key: key(base_pipeline, head_pipeline),
status_reason: _('An error occurred while fetching coverage reports.')
}
end
def latest?(base_pipeline, head_pipeline, data)
data&.fetch(:key, nil) == key(base_pipeline, head_pipeline)
end
end
end
...@@ -54,7 +54,16 @@ module Projects ...@@ -54,7 +54,16 @@ module Projects
end end
def project_tree_saver def project_tree_saver
Gitlab::ImportExport::Project::TreeSaver.new(project: project, current_user: current_user, shared: shared, params: params) tree_saver_class.new(project: project, current_user: current_user, shared: shared, params: params)
end
def tree_saver_class
if ::Feature.enabled?(:streaming_serializer, project)
Gitlab::ImportExport::Project::TreeSaver
else
# Once we remove :streaming_serializer feature flag, Project::LegacyTreeSaver should be removed as well
Gitlab::ImportExport::Project::LegacyTreeSaver
end
end end
def uploads_saver def uploads_saver
... ...
......
...@@ -78,6 +78,7 @@ ...@@ -78,6 +78,7 @@
endpoint: diffs_project_merge_request_path(@project, @merge_request, 'json', request.query_parameters), endpoint: diffs_project_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
endpoint_metadata: diffs_metadata_project_json_merge_request_path(@project, @merge_request, 'json', request.query_parameters), endpoint_metadata: diffs_metadata_project_json_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
endpoint_batch: diffs_batch_project_json_merge_request_path(@project, @merge_request, 'json', request.query_parameters), endpoint_batch: diffs_batch_project_json_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
endpoint_coverage: @coverage_path,
help_page_path: suggest_changes_help_path, help_page_path: suggest_changes_help_path,
current_user_data: @current_user_data, current_user_data: @current_user_data,
project_path: project_path(@merge_request.project), project_path: project_path(@merge_request.project),
... ...
......
#!/bin/sh #!/bin/sh
cd $(dirname $0)/.. cd $(dirname $0)/..
app_root=$(pwd)
sidekiq_pidfile="$app_root/tmp/pids/sidekiq.pid"
sidekiq_logfile="$app_root/log/sidekiq.log"
sidekiq_config="$app_root/config/sidekiq_queues.yml"
gitlab_user=$(ls -l config.ru | awk '{print $3}')
warn() if [ -n "$SIDEKIQ_WORKERS" ] ; then
{ exec bin/background_jobs_sk_cluster "$@"
echo "$@" 1>&2 else
} exec bin/background_jobs_sk "$@"
stop()
{
bundle exec sidekiqctl stop $sidekiq_pidfile >> $sidekiq_logfile 2>&1
}
killall()
{
pkill -u $gitlab_user -f 'sidekiq [0-9]'
}
restart()
{
if [ -f $sidekiq_pidfile ]; then
stop
fi
killall
start_sidekiq -P $sidekiq_pidfile -d -L $sidekiq_logfile >> $sidekiq_logfile 2>&1
}
start_no_deamonize()
{
start_sidekiq >> $sidekiq_logfile 2>&1
}
start_sidekiq()
{
cmd="exec"
chpst=$(which chpst)
if [ -n "$chpst" ]; then
cmd="${cmd} ${chpst} -P"
fi fi
${cmd} bundle exec sidekiq -C "${sidekiq_config}" -e $RAILS_ENV "$@"
}
load_ok()
{
sidekiq_pid=$(cat $sidekiq_pidfile)
if [ -z "$sidekiq_pid" ] ; then
warn "Could not find a PID in $sidekiq_pidfile"
exit 0
fi
if (ps -p $sidekiq_pid -o args | grep '\([0-9]\+\) of \1 busy' 1>&2) ; then
warn "Too many busy Sidekiq workers"
exit 1
fi
exit 0
}
case "$1" in
stop)
stop
;;
start)
restart
;;
start_no_deamonize)
start_no_deamonize
;;
start_foreground)
start_sidekiq
;;
restart)
restart
;;
killall)
killall
;;
load_ok)
load_ok
;;
*)
echo "Usage: RAILS_ENV=your_env $0 {stop|start|start_no_deamonize|restart|killall|load_ok}"
esac
bin/background_jobs_sk 0 → 100755
#!/bin/sh
cd $(dirname $0)/..
app_root=$(pwd)
sidekiq_pidfile="$app_root/tmp/pids/sidekiq.pid"
sidekiq_logfile="$app_root/log/sidekiq.log"
sidekiq_config="$app_root/config/sidekiq_queues.yml"
gitlab_user=$(ls -l config.ru | awk '{print $3}')
warn()
{
echo "$@" 1>&2
}
stop()
{
bundle exec sidekiqctl stop $sidekiq_pidfile >> $sidekiq_logfile 2>&1
}
restart()
{
if [ -f $sidekiq_pidfile ]; then
stop
fi
pkill -u $gitlab_user -f 'sidekiq [0-9]'
start_sidekiq -P $sidekiq_pidfile -d -L $sidekiq_logfile >> $sidekiq_logfile 2>&1
}
# Starts on foreground but output to the logfile instead stdout.
start_silent()
{
start_sidekiq >> $sidekiq_logfile 2>&1
}
start_sidekiq()
{
cmd="exec"
chpst=$(which chpst)
if [ -n "$chpst" ]; then
cmd="${cmd} ${chpst} -P"
fi
${cmd} bundle exec sidekiq -C "${sidekiq_config}" -e $RAILS_ENV "$@"
}
case "$1" in
stop)
stop
;;
start)
restart
;;
start_silent)
warn "Deprecated: Will be removed at 13.0 (see https://gitlab.com/gitlab-org/gitlab/-/issues/196731)."
start_silent
;;
start_foreground)
start_sidekiq
;;
restart)
restart
;;
*)
echo "Usage: RAILS_ENV=<env> $0 {stop|start|start_silent|start_foreground|restart}"
esac
#!/bin/sh
cd $(dirname $0)/..
app_root=$(pwd)
sidekiq_pidfile="$app_root/tmp/pids/sidekiq-cluster.pid"
sidekiq_logfile="$app_root/log/sidekiq.log"
gitlab_user=$(ls -l config.ru | awk '{print $3}')
warn()
{
echo "$@" 1>&2
}
get_sidekiq_pid()
{
if [ ! -f $sidekiq_pidfile ]; then
warn "No pidfile found at $sidekiq_pidfile; is Sidekiq running?"
return
fi
cat $sidekiq_pidfile
}
stop()
{
sidekiq_pid=$(get_sidekiq_pid)
if [ $sidekiq_pid ]; then
kill -TERM $sidekiq_pid
fi
}
restart()
{
if [ -f $sidekiq_pidfile ]; then
stop
fi
warn "Sidekiq output will be written to $sidekiq_logfile"
start_sidekiq >> $sidekiq_logfile 2>&1
}
start_sidekiq()
{
cmd="exec"
chpst=$(which chpst)
if [ -n "$chpst" ]; then
cmd="${cmd} ${chpst} -P"
fi
# sidekiq-cluster expects '*' '*' arguments (one wildcard for each process).
for (( i=1; i<=$SIDEKIQ_WORKERS; i++ ))
do
processes_args+=("*")
done
${cmd} bin/sidekiq-cluster "${processes_args[@]}" -P $sidekiq_pidfile -e $RAILS_ENV
}
case "$1" in
stop)
stop
;;
start)
restart &
;;
start_foreground)
start_sidekiq
;;
restart)
restart &
;;
*)
echo "Usage: RAILS_ENV=<env> SIDEKIQ_WORKERS=<n> $0 {stop|start|start_foreground|restart}"
esac