Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/reports/store/actions.js9
-rw-r--r--app/controllers/projects/merge_requests_controller.rb17
-rw-r--r--app/models/ci/build.rb28
-rw-r--r--app/models/ci/job_artifact.rb20
-rw-r--r--app/models/ci/pipeline.rb12
-rw-r--r--app/models/merge_request.rb42
-rw-r--r--app/serializers/merge_request_widget_entity.rb6
-rw-r--r--app/serializers/test_case_entity.rb7
-rw-r--r--app/serializers/test_reports_comparer_entity.rb11
-rw-r--r--app/serializers/test_reports_comparer_serializer.rb3
-rw-r--r--app/serializers/test_suite_comparer_entity.rb14
11 files changed, 169 insertions, 0 deletions
diff --git a/app/assets/javascripts/reports/store/actions.js b/app/assets/javascripts/reports/store/actions.js
index 15c077b0fd8..edbf860ecc6 100644
--- a/app/assets/javascripts/reports/store/actions.js
+++ b/app/assets/javascripts/reports/store/actions.js
@@ -1,4 +1,5 @@
import Visibility from 'visibilityjs';
+import $ from 'jquery';
import axios from '../../lib/utils/axios_utils';
import Poll from '../../lib/utils/poll';
import * as types from './mutation_types';
@@ -63,5 +64,13 @@ export const receiveReportsSuccess = ({ commit }, response) =>
export const receiveReportsError = ({ commit }) => commit(types.RECEIVE_REPORTS_ERROR);
+export const openModal = ({ dispatch }, payload) => {
+ dispatch('setModalData', payload);
+
+ $('#modal-mrwidget-reports').modal('show');
+};
+
+export const setModalData = ({ commit }, payload) => commit(types.SET_ISSUE_MODAL_DATA, payload);
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index dc6551fc761..410fb92eb7c 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -99,6 +99,23 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
}
end
+ def test_reports
+ result = @merge_request.compare_test_reports
+
+ Gitlab::PollingInterval.set_header(response, interval: 10_000)
+
+ case result[:status]
+ when :parsing
+ render json: '', status: :no_content
+ when :parsed
+ render json: result[:data], status: :ok
+ when :error
+ render json: { status_reason: result[:status_reason] }, status: :bad_request
+ else
+ render json: { status_reason: 'Unknown error' }, status: :internal_server_error
+ end
+ end
+
def edit
define_edit_vars
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 93bbee49c09..bf931a04592 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -69,6 +69,11 @@ module Ci
where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
end
+ scope :with_test_reports, ->() do
+ includes(:job_artifacts_junit) # Prevent N+1 problem when iterating each ci_job_artifact row
+ .where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').test_reports)
+ end
+
scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
@@ -627,8 +632,31 @@ module Ci
running? && runner_session_url.present?
end
+ def collect_test_reports!(test_reports)
+ raise ArgumentError, 'build does not have test reports' unless has_test_reports?
+
+ test_reports.get_suite(group_name).tap do |test_suite|
+ each_test_report do |file_type, blob|
+ parse_test_report!(test_suite, file_type, blob)
+ end
+ end
+ end
+
private
+ def each_test_report
+ Ci::JobArtifact::TEST_REPORT_FILE_TYPES.each do |file_type|
+ public_send("job_artifacts_#{file_type}").each_blob do |blob| # rubocop:disable GitlabSecurity/PublicSend
+ yield file_type, blob
+ end
+ end
+ end
+
+ def parse_test_report!(test_suite, file_type, blob)
+ "Gitlab::Ci::Parsers::#{file_type.capitalize}Parser".constantize
+ .new(blob).parse!(test_suite)
+ end
+
def update_artifacts_size
self.artifacts_size = legacy_artifacts_file&.size
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 054b714f8ac..50f375f0804 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -4,6 +4,8 @@ module Ci
include ObjectStorage::BackgroundMove
extend Gitlab::Ci::Model
+ NotSupportedAdapterError = Class.new(StandardError)
+
TEST_REPORT_FILE_TYPES = %w[junit].freeze
DEFAULT_FILE_NAMES = { junit: 'junit.xml' }.freeze
TYPE_AND_FORMAT_PAIRS = { archive: :zip, metadata: :gzip, trace: :raw, junit: :gzip }.freeze
@@ -44,6 +46,10 @@ module Ci
gzip: 3
}
+ FILE_FORMAT_ADAPTERS = {
+ gzip: Gitlab::Ci::Build::Artifacts::GzipFileAdapter
+ }.freeze
+
def valid_file_format?
unless TYPE_AND_FORMAT_PAIRS[self.file_type&.to_sym] == self.file_format&.to_sym
errors.add(:file_format, 'Invalid file format with specified file type')
@@ -75,8 +81,22 @@ module Ci
end
end
+ def each_blob(&blk)
+ unless file_format_adapter_class
+ raise NotSupportedAdapterError, 'This file format requires a dedicated adapter'
+ end
+
+ file.open do |stream|
+ file_format_adapter_class.new(stream).each_blob(&blk)
+ end
+ end
+
private
+ def file_format_adapter_class
+ FILE_FORMAT_ADAPTERS[file_format.to_sym]
+ end
+
def set_size
self.size = file.size
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index e5caa3ffa41..f06a6416307 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -603,6 +603,18 @@ module Ci
@latest_builds_with_artifacts ||= builds.latest.with_artifacts_archive.to_a
end
+ def has_test_reports?
+ complete? && builds.with_test_reports.any?
+ end
+
+ def test_reports
+ Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
+ builds.with_test_reports.each do |build|
+ build.collect_test_reports!(test_reports)
+ end
+ end
+ end
+
private
def ci_yaml_from_repo
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 06642c15585..9f8ebd9b4ca 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -13,6 +13,11 @@ class MergeRequest < ActiveRecord::Base
include ThrottledTouch
include Gitlab::Utils::StrongMemoize
include LabelEventable
+ include ReactiveCaching
+
+ self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
+ self.reactive_cache_refresh_interval = 1.hour
+ self.reactive_cache_lifetime = 1.hour
ignore_column :locked_at,
:ref_fetched,
@@ -1012,6 +1017,37 @@ class MergeRequest < ActiveRecord::Base
.order(id: :desc)
end
+ def has_test_reports?
+ actual_head_pipeline&.has_test_reports?
+ end
+
+ def compare_test_reports
+ unless actual_head_pipeline && actual_head_pipeline.has_test_reports?
+ return { status: :error, status_reason: 'head pipeline does not have test reports' }
+ end
+
+ with_reactive_cache(base_pipeline&.iid, actual_head_pipeline.iid) { |data| data } || { status: :parsing }
+ end
+
+ def calculate_reactive_cache(base_pipeline_iid, head_pipeline_iid)
+ begin
+ base_pipeline = project.pipelines.find_by_iid(base_pipeline_iid)
+ head_pipeline = project.pipelines.find_by_iid(head_pipeline_iid)
+
+ comparer = Gitlab::Ci::Reports::TestReportsComparer
+ .new(base_pipeline&.test_reports, head_pipeline.test_reports)
+
+ {
+ status: :parsed,
+ data: TestReportsComparerSerializer
+ .new(project: project)
+ .represent(comparer).to_json
+ }
+ rescue => e
+ { status: :error, status_reason: e.message }
+ end
+ end
+
def all_commits
# MySQL doesn't support LIMIT in a subquery.
diffs_relation = if Gitlab::Database.postgresql?
@@ -1124,6 +1160,12 @@ class MergeRequest < ActiveRecord::Base
true
end
+ def base_pipeline
+ @base_pipeline ||= project.pipelines
+ .order(id: :desc)
+ .find_by(sha: diff_base_sha)
+ end
+
def discussions_rendered_on_frontend?
true
end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 63fd9d63ec4..f55d448235a 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -231,6 +231,12 @@ class MergeRequestWidgetEntity < IssuableEntity
end
end
+ expose :test_reports_path do |merge_request|
+ if merge_request.has_test_reports?
+ test_reports_project_merge_request_path(merge_request.project, merge_request, format: :json)
+ end
+ end
+
private
delegate :current_user, to: :request
diff --git a/app/serializers/test_case_entity.rb b/app/serializers/test_case_entity.rb
new file mode 100644
index 00000000000..5c1cbf37182
--- /dev/null
+++ b/app/serializers/test_case_entity.rb
@@ -0,0 +1,7 @@
+class TestCaseEntity < Grape::Entity
+ expose :status
+ expose :name
+ expose :execution_time
+ expose :system_output
+ expose :stack_trace
+end
diff --git a/app/serializers/test_reports_comparer_entity.rb b/app/serializers/test_reports_comparer_entity.rb
new file mode 100644
index 00000000000..b95d820d093
--- /dev/null
+++ b/app/serializers/test_reports_comparer_entity.rb
@@ -0,0 +1,11 @@
+class TestReportsComparerEntity < Grape::Entity
+ expose :total_status, as: :status
+
+ expose :summary do
+ expose :total_count, as: :total
+ expose :resolved_count, as: :resolved
+ expose :failed_count, as: :failed
+ end
+
+ expose :suite_comparers, as: :suites, using: TestSuiteComparerEntity
+end
diff --git a/app/serializers/test_reports_comparer_serializer.rb b/app/serializers/test_reports_comparer_serializer.rb
new file mode 100644
index 00000000000..a739858efb2
--- /dev/null
+++ b/app/serializers/test_reports_comparer_serializer.rb
@@ -0,0 +1,3 @@
+class TestReportsComparerSerializer < BaseSerializer
+ entity TestReportsComparerEntity
+end
diff --git a/app/serializers/test_suite_comparer_entity.rb b/app/serializers/test_suite_comparer_entity.rb
new file mode 100644
index 00000000000..a3965ba3930
--- /dev/null
+++ b/app/serializers/test_suite_comparer_entity.rb
@@ -0,0 +1,14 @@
+class TestSuiteComparerEntity < Grape::Entity
+ expose :name
+ expose :total_status, as: :status
+
+ expose :summary do
+ expose :total_count, as: :total
+ expose :resolved_count, as: :resolved
+ expose :failed_count, as: :failed
+ end
+
+ expose :new_failures, using: TestCaseEntity
+ expose :resolved_failures, using: TestCaseEntity
+ expose :existing_failures, using: TestCaseEntity
+end