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:
commit
9b2e827353
5 changed files with 229 additions and 34 deletions
4
changelogs/unreleased/tc-api-pipeline-jobs.yml
Normal file
4
changelogs/unreleased/tc-api-pipeline-jobs.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Add GET /projects/:id/pipelines/:pipeline_id/jobs endpoint
|
||||
merge_request: 9727
|
||||
author:
|
120
doc/api/jobs.md
120
doc/api/jobs.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue