summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@gitlab.com>2017-03-08 01:59:16 +0000
committerDouwe Maan <douwe@gitlab.com>2017-03-08 01:59:16 +0000
commit9b2e82735317b81c661d47f6fe9486c4fe66b8a1 (patch)
treeb011d483f7105b439bc97560fce7c8a468a3c04b
parent2995f48ef3158252446c5e03c138feb6c1889941 (diff)
parentae9218a7fd04c0d1c5b3266b43514bf34fc63d4b (diff)
downloadgitlab-ce-9b2e82735317b81c661d47f6fe9486c4fe66b8a1.tar.gz
Merge branch 'tc-api-pipeline-jobs' into 'master'
API: Get list of jobs for a pipeline Closes #26843 See merge request !9727
-rw-r--r--changelogs/unreleased/tc-api-pipeline-jobs.yml4
-rw-r--r--doc/api/jobs.md120
-rw-r--r--lib/api/jobs.rb39
-rw-r--r--lib/api/v3/builds.rb24
-rw-r--r--spec/requests/api/jobs_spec.rb76
5 files changed, 229 insertions, 34 deletions
diff --git a/changelogs/unreleased/tc-api-pipeline-jobs.yml b/changelogs/unreleased/tc-api-pipeline-jobs.yml
new file mode 100644
index 00000000000..993c1b6526a
--- /dev/null
+++ b/changelogs/unreleased/tc-api-pipeline-jobs.yml
@@ -0,0 +1,4 @@
+---
+title: Add GET /projects/:id/pipelines/:pipeline_id/jobs endpoint
+merge_request: 9727
+author:
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 296f1d025dd..7340123e09d 100644
--- a/doc/api/jobs.md
+++ b/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
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 33c05e8aa63..44118522abe 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -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
diff --git a/lib/api/v3/builds.rb b/lib/api/v3/builds.rb
index c8feba13527..6f97102c6ef 100644
--- a/lib/api/v3/builds.rb
+++ b/lib/api/v3/builds.rb
@@ -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
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index a4d27734cc2..9450701064b 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -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)