From 7b143a405dac3f184b2f6253afb90b83dcb39c1a Mon Sep 17 00:00:00 2001
From: Albert Vaca Cintora <albertvaka@gmail.com>
Date: Wed, 1 Dec 2021 22:30:30 +0000
Subject: [PATCH] Return job failure reason in API responses

Changelog: added
---
 doc/api/job_artifacts.md          |  1 +
 doc/api/jobs.md                   |  5 +++++
 lib/api/entities/ci/job_basic.rb  |  1 +
 spec/requests/api/ci/jobs_spec.rb | 20 ++++++++++++++++++++
 4 files changed, 27 insertions(+)

diff --git a/doc/api/job_artifacts.md b/doc/api/job_artifacts.md
index 6d8c256d5aa26..7c7847bf36885 100644
--- a/doc/api/job_artifacts.md
+++ b/doc/api/job_artifacts.md
@@ -252,6 +252,7 @@ Example response:
   "finished_at": "2016-01-11T10:15:10.506Z",
   "duration": 97.0,
   "status": "failed",
+  "failure_reason": "script_failure",
   "tag": false,
   "web_url": "https://example.com/foo/bar/-/jobs/42",
   "user": null
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 2a07e2d92c5ab..f3971fb573252 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -71,6 +71,7 @@ Example of response
     "runner": null,
     "stage": "test",
     "status": "failed",
+    "failure_reason": "script_failure",
     "tag": false,
     "web_url": "https://example.com/foo/bar/-/jobs/7",
     "user": {
@@ -126,6 +127,7 @@ Example of response
     "runner": null,
     "stage": "test",
     "status": "failed",
+    "failure_reason": "stuck_or_timeout_failure",
     "tag": false,
     "web_url": "https://example.com/foo/bar/-/jobs/6",
     "user": {
@@ -207,6 +209,7 @@ Example of response
     "runner": null,
     "stage": "test",
     "status": "failed",
+    "failure_reason": "stuck_or_timeout_failure",
     "tag": false,
     "web_url": "https://example.com/foo/bar/-/jobs/6",
     "user": {
@@ -271,6 +274,7 @@ Example of response
     "runner": null,
     "stage": "test",
     "status": "failed",
+    "failure_reason": "script_failure",
     "tag": false,
     "web_url": "https://example.com/foo/bar/-/jobs/7",
     "user": {
@@ -443,6 +447,7 @@ Example of response
   "runner": null,
   "stage": "test",
   "status": "failed",
+  "failure_reason": "script_failure",
   "tag": false,
   "web_url": "https://example.com/foo/bar/-/jobs/8",
   "user": {
diff --git a/lib/api/entities/ci/job_basic.rb b/lib/api/entities/ci/job_basic.rb
index c31340f1ff079..0badde4089ee0 100644
--- a/lib/api/entities/ci/job_basic.rb
+++ b/lib/api/entities/ci/job_basic.rb
@@ -13,6 +13,7 @@ class JobBasic < Grape::Entity
         expose :user, with: ::API::Entities::User
         expose :commit, with: ::API::Entities::Commit
         expose :pipeline, with: ::API::Entities::Ci::PipelineBasic
+        expose :failure_reason, if: -> (job) { job.failed? }
 
         expose :web_url do |job, _options|
           Gitlab::Routing.url_helpers.project_job_url(job.project, job)
diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb
index 4978630b5effc..7c85cbc31a5bf 100644
--- a/spec/requests/api/ci/jobs_spec.rb
+++ b/spec/requests/api/ci/jobs_spec.rb
@@ -428,6 +428,26 @@ def go
       end
     end
 
+    context 'when job succeeded' do
+      it 'does not return failure_reason' do
+        get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
+
+        expect(json_response).not_to include('failure_reason')
+      end
+    end
+
+    context 'when job failed' do
+      let(:job) do
+        create(:ci_build, :failed, :tags, pipeline: pipeline)
+      end
+
+      it 'returns failure_reason' do
+        get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
+
+        expect(json_response).to include('failure_reason')
+      end
+    end
+
     context 'when trace artifact record exists with no stored file', :skip_before_request do
       before do
         create(:ci_job_artifact, :unarchived_trace_artifact, job: job, project: job.project)
-- 
GitLab