From f37240067301548a41a6257792d3926b68328e62 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Mon, 6 Mar 2017 16:56:05 +0100 Subject: [PATCH] Add GET /projects/:id/pipelines/:pipeline_id/jobs endpoint Add endpoint to get the jobs scoped to a pipeline. --- .../unreleased/tc-api-pipeline-jobs.yml | 4 + doc/api/jobs.md | 118 +++++++++++++++++- lib/api/jobs.rb | 17 +++ spec/requests/api/jobs_spec.rb | 72 +++++++++++ 4 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/tc-api-pipeline-jobs.yml 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..e503ee73164 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. @@ -115,6 +115,122 @@ Example of response ] ``` +## 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 | +| `pipelin_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%5B0%5D=pending&scope%5B1%5D=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": "" + } + } +] +``` + ## Get a single job Get a single job of a project diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 33c05e8aa63..af8f4b1e759 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -40,6 +40,23 @@ module API user_can_download_artifacts: can?(current_user, :read_build, user_project) 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, + user_can_download_artifacts: can?(current_user, :read_build, user_project) + end + desc 'Get a specific job of a project' do success Entities::Job end diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index a4d27734cc2..cfd7de7e4e9 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -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[0]' => 'pending', 'scope[1]' => '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[0]' => 'unknown', 'scope[1]' => '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)