Merge branch 'tc-api-pipeline-jobs' into 'master'

API: Get list of jobs for a pipeline

Closes #26843

See merge request !9727
This commit is contained in:
Douwe Maan 2017-03-08 01:59:16 +00:00
commit 9b2e827353
5 changed files with 229 additions and 34 deletions

View file

@ -0,0 +1,4 @@
---
title: Add GET /projects/:id/pipelines/:pipeline_id/jobs endpoint
merge_request: 9727
author:

View file

@ -1,6 +1,6 @@
# Jobs API
## List project jobs
## List project jobs
Get a list of jobs in a project.
@ -14,7 +14,123 @@ GET /projects/:id/jobs
| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all jobs if none provided |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope%5B0%5D=pending&scope%5B1%5D=running'
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope[]=pending&scope[]=running'
```
Example of response
```json
[
{
"commit": {
"author_email": "admin@example.com",
"author_name": "Administrator",
"created_at": "2015-12-24T16:51:14.000+01:00",
"id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
"message": "Test the CI integration.",
"short_id": "0ff3ae19",
"title": "Test the CI integration."
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.802Z",
"artifacts_file": {
"filename": "artifacts.zip",
"size": 1000
},
"finished_at": "2015-12-24T17:54:27.895Z",
"id": 7,
"name": "teaspoon",
"pipeline": {
"id": 6,
"ref": "master",
"sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
"status": "pending"
},
"ref": "master",
"runner": null,
"stage": "test",
"started_at": "2015-12-24T17:54:27.722Z",
"status": "failed",
"tag": false,
"user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null,
"created_at": "2015-12-21T13:14:24.077Z",
"id": 1,
"is_admin": true,
"linkedin": "",
"name": "Administrator",
"skype": "",
"state": "active",
"twitter": "",
"username": "root",
"web_url": "http://gitlab.dev/root",
"website_url": ""
}
},
{
"commit": {
"author_email": "admin@example.com",
"author_name": "Administrator",
"created_at": "2015-12-24T16:51:14.000+01:00",
"id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
"message": "Test the CI integration.",
"short_id": "0ff3ae19",
"title": "Test the CI integration."
},
"coverage": null,
"created_at": "2015-12-24T15:51:21.727Z",
"artifacts_file": null,
"finished_at": "2015-12-24T17:54:24.921Z",
"id": 6,
"name": "spinach:other",
"pipeline": {
"id": 6,
"ref": "master",
"sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
"status": "pending"
},
"ref": "master",
"runner": null,
"stage": "test",
"started_at": "2015-12-24T17:54:24.729Z",
"status": "failed",
"tag": false,
"user": {
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"bio": null,
"created_at": "2015-12-21T13:14:24.077Z",
"id": 1,
"is_admin": true,
"linkedin": "",
"name": "Administrator",
"skype": "",
"state": "active",
"twitter": "",
"username": "root",
"web_url": "http://gitlab.dev/root",
"website_url": ""
}
}
]
```
## List pipeline jobs
Get a list of jobs for a pipeline.
```
GET /projects/:id/pipeline/:pipeline_id/jobs
```
| Attribute | Type | Required | Description |
|---------------|--------------------------------|----------|----------------------|
| `id` | integer | yes | The ID of a project |
| `pipeline_id` | integer | yes | The ID of a pipeline |
| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all jobs if none provided |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/pipelines/6/jobs?scope[]=pending&scope[]=running'
```
Example of response

View file

@ -18,6 +18,8 @@ module API
[scope]
when Hashie::Mash
scope.values
when Hashie::Array
scope
else
['unknown']
end
@ -36,8 +38,23 @@ module API
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
present paginate(builds), with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present paginate(builds), with: Entities::Job
end
desc 'Get pipeline jobs' do
success Entities::Job
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
use :optional_scope
use :pagination
end
get ':id/pipelines/:pipeline_id/jobs' do
pipeline = user_project.pipelines.find(params[:pipeline_id])
builds = pipeline.builds
builds = filter_builds(builds, params[:scope])
present paginate(builds), with: Entities::Job
end
desc 'Get a specific job of a project' do
@ -51,8 +68,7 @@ module API
build = get_build!(params[:job_id])
present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: Entities::Job
end
desc 'Download the artifacts file from a job' do
@ -119,8 +135,7 @@ module API
build.cancel
present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: Entities::Job
end
desc 'Retry a specific build of a project' do
@ -137,8 +152,7 @@ module API
build = Ci::Build.retry(build, current_user)
present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: Entities::Job
end
desc 'Erase job (remove artifacts and the trace)' do
@ -154,8 +168,7 @@ module API
return forbidden!('Job is not erasable!') unless build.erasable?
build.erase(erased_by: current_user)
present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
present build, with: Entities::Job
end
desc 'Keep the artifacts to prevent them from being deleted' do
@ -173,8 +186,7 @@ module API
build.keep_artifacts!
status 200
present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: Entities::Job
end
desc 'Trigger a manual job' do
@ -194,8 +206,7 @@ module API
build.play(current_user)
status 200
present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: Entities::Job
end
end

View file

@ -36,8 +36,7 @@ module API
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
present paginate(builds), with: ::API::V3::Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present paginate(builds), with: ::API::V3::Entities::Build
end
desc 'Get builds for a specific commit of a project' do
@ -57,8 +56,7 @@ module API
builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
builds = filter_builds(builds, params[:scope])
present paginate(builds), with: ::API::V3::Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present paginate(builds), with: ::API::V3::Entities::Build
end
desc 'Get a specific build of a project' do
@ -72,8 +70,7 @@ module API
build = get_build!(params[:build_id])
present build, with: ::API::V3::Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: ::API::V3::Entities::Build
end
desc 'Download the artifacts file from build' do
@ -140,8 +137,7 @@ module API
build.cancel
present build, with: ::API::V3::Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: ::API::V3::Entities::Build
end
desc 'Retry a specific build of a project' do
@ -158,8 +154,7 @@ module API
build = Ci::Build.retry(build, current_user)
present build, with: ::API::V3::Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: ::API::V3::Entities::Build
end
desc 'Erase build (remove artifacts and build trace)' do
@ -175,8 +170,7 @@ module API
return forbidden!('Build is not erasable!') unless build.erasable?
build.erase(erased_by: current_user)
present build, with: ::API::V3::Entities::Build,
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
present build, with: ::API::V3::Entities::Build
end
desc 'Keep the artifacts to prevent them from being deleted' do
@ -194,8 +188,7 @@ module API
build.keep_artifacts!
status 200
present build, with: ::API::V3::Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: ::API::V3::Entities::Build
end
desc 'Trigger a manual build' do
@ -215,8 +208,7 @@ module API
build.play(current_user)
status 200
present build, with: ::API::V3::Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
present build, with: ::API::V3::Entities::Build
end
end

View file

@ -51,7 +51,7 @@ describe API::Jobs, api: true do
end
context 'filter project with array of scope elements' do
let(:query) { { 'scope[0]' => 'pending', 'scope[1]' => 'running' } }
let(:query) { { scope: %w(pending running) } }
it do
expect(response).to have_http_status(200)
@ -60,7 +60,7 @@ describe API::Jobs, api: true do
end
context 'respond 400 when scope contains invalid state' do
let(:query) { { 'scope[0]' => 'unknown', 'scope[1]' => 'running' } }
let(:query) { { scope: %w(unknown running) } }
it { expect(response).to have_http_status(400) }
end
@ -75,6 +75,78 @@ describe API::Jobs, api: true do
end
end
describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do
let(:query) { Hash.new }
before do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
end
context 'authorized user' do
it 'returns pipeline jobs' do
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
end
it 'returns correct values' do
expect(json_response).not_to be_empty
expect(json_response.first['commit']['id']).to eq project.commit.id
end
it 'returns pipeline data' do
json_build = json_response.first
expect(json_build['pipeline']).not_to be_empty
expect(json_build['pipeline']['id']).to eq build.pipeline.id
expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
expect(json_build['pipeline']['status']).to eq build.pipeline.status
end
context 'filter jobs with one scope element' do
let(:query) { { 'scope' => 'pending' } }
it do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
end
end
context 'filter jobs with array of scope elements' do
let(:query) { { scope: %w(pending running) } }
it do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
end
end
context 'respond 400 when scope contains invalid state' do
let(:query) { { scope: %w(unknown running) } }
it { expect(response).to have_http_status(400) }
end
context 'jobs in different pipelines' do
let!(:pipeline2) { create(:ci_empty_pipeline, project: project) }
let!(:build2) { create(:ci_build, pipeline: pipeline2) }
it 'excludes jobs from other pipelines' do
json_response.each { |job| expect(job['pipeline']['id']).to eq(pipeline.id) }
end
end
end
context 'unauthorized user' do
let(:api_user) { nil }
it 'does not return jobs' do
expect(response).to have_http_status(401)
end
end
end
describe 'GET /projects/:id/jobs/:job_id' do
before do
get api("/projects/#{project.id}/jobs/#{build.id}", api_user)