Initial implementation for real time job view
Added the needed keys and paths to a new entity, BuildDetailsEntity. Not renaming BuildEntity to BuildBasicEntity on explicit request. Most code now has test coverage, but not all. This will be added on later commits on this branch. Resolves gitlab-org/gitlab-ce#31397
This commit is contained in:
parent
f06daa26ef
commit
47a0276e53
|
@ -45,6 +45,17 @@ class Projects::JobsController < Projects::ApplicationController
|
||||||
@builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
|
@builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
|
||||||
@builds = @builds.where("id not in (?)", @build.id)
|
@builds = @builds.where("id not in (?)", @build.id)
|
||||||
@pipeline = @build.pipeline
|
@pipeline = @build.pipeline
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html
|
||||||
|
format.json do
|
||||||
|
Gitlab::PollingInterval.set_header(response, interval: 10_000)
|
||||||
|
|
||||||
|
render json: BuildSerializer
|
||||||
|
.new(project: @project, current_user: @current_user)
|
||||||
|
.represent_status(@build, {}, BuildDetailsEntity)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def trace
|
def trace
|
||||||
|
|
|
@ -204,14 +204,17 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_request
|
def merge_request
|
||||||
merge_requests = MergeRequest.includes(:merge_request_diff)
|
@merge_request ||=
|
||||||
.where(source_branch: ref,
|
begin
|
||||||
source_project: pipeline.project)
|
merge_requests = MergeRequest.includes(:merge_request_diff)
|
||||||
.reorder(iid: :asc)
|
.where(source_branch: ref,
|
||||||
|
source_project: pipeline.project)
|
||||||
|
.reorder(iid: :asc)
|
||||||
|
|
||||||
merge_requests.find do |merge_request|
|
merge_requests.find do |merge_request|
|
||||||
merge_request.commits_sha.include?(pipeline.sha)
|
merge_request.commits_sha.include?(pipeline.sha)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def repo_url
|
def repo_url
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
class BuildDetailsEntity < BuildEntity
|
||||||
|
expose :coverage, :erased_at, :duration
|
||||||
|
expose :tag_list, as: :tags
|
||||||
|
|
||||||
|
expose :artifacts, using: BuildArtifactEntity
|
||||||
|
expose :runner, using: RunnerEntity
|
||||||
|
expose :pipeline, using: PipelineEntity
|
||||||
|
|
||||||
|
expose :merge_request_path do |build|
|
||||||
|
merge_request = build.merge_request
|
||||||
|
project = build.project
|
||||||
|
|
||||||
|
if merge_request.nil? || !can?(request.current_user, :read_merge_request, project)
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
namespace_project_merge_request_path(project.namespace, project, merge_request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
expose :new_issue_path do |build|
|
||||||
|
project = build.project
|
||||||
|
|
||||||
|
unless build.failed? && can?(request.current_user, :create_issue, project)
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
new_namespace_project_issue_path(project.namespace, project)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
expose :raw_path do |build|
|
||||||
|
project = build.project
|
||||||
|
raw_namespace_project_build_path(project.namespace, project, build)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,8 +1,10 @@
|
||||||
class BuildSerializer < BaseSerializer
|
class BuildSerializer < BaseSerializer
|
||||||
entity BuildEntity
|
entity BuildEntity
|
||||||
|
|
||||||
def represent_status(resource)
|
def represent_status(resource, opts = {}, entity_class = nil)
|
||||||
data = represent(resource, { only: [:status] })
|
data = represent(resource, { only: [:status] })
|
||||||
data.fetch(:status, {})
|
data.fetch(:status, {})
|
||||||
|
|
||||||
|
represent(resource, opts, entity_class)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
class RunnerEntity < Grape::Entity
|
||||||
|
expose :id, :name, :description
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Job details page update real time
|
||||||
|
merge_request: 11651
|
||||||
|
author:
|
|
@ -9,7 +9,9 @@ module Gitlab
|
||||||
# - Ending in `noteable/issue/<id>/notes` for the `issue_notes` route
|
# - Ending in `noteable/issue/<id>/notes` for the `issue_notes` route
|
||||||
# - Ending in `issues/id`/realtime_changes` for the `issue_title` route
|
# - Ending in `issues/id`/realtime_changes` for the `issue_title` route
|
||||||
USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes
|
USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes
|
||||||
commit pipelines merge_requests new].freeze
|
commit pipelines merge_requests builds
|
||||||
|
new].freeze
|
||||||
|
|
||||||
RESERVED_WORDS = Gitlab::PathRegex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES
|
RESERVED_WORDS = Gitlab::PathRegex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES
|
||||||
RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS.map(&Regexp.method(:escape)))
|
RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS.map(&Regexp.method(:escape)))
|
||||||
ROUTES = [
|
ROUTES = [
|
||||||
|
@ -40,6 +42,10 @@ module Gitlab
|
||||||
Gitlab::EtagCaching::Router::Route.new(
|
Gitlab::EtagCaching::Router::Route.new(
|
||||||
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/pipelines/\d+\.json\z),
|
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/pipelines/\d+\.json\z),
|
||||||
'project_pipeline'
|
'project_pipeline'
|
||||||
|
),
|
||||||
|
Gitlab::EtagCaching::Router::Route.new(
|
||||||
|
%r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/builds/\d+\.json\z),
|
||||||
|
'project_build'
|
||||||
)
|
)
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
|
|
|
@ -101,26 +101,48 @@ describe Projects::JobsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET show' do
|
describe 'GET show' do
|
||||||
context 'when build exists' do
|
let!(:build) { create(:ci_build, :failed, pipeline: pipeline) }
|
||||||
let!(:build) { create(:ci_build, pipeline: pipeline) }
|
|
||||||
|
|
||||||
before do
|
context 'when requesting HTML' do
|
||||||
get_show(id: build.id)
|
context 'when build exists' do
|
||||||
|
before do
|
||||||
|
get_show(id: build.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has a build' do
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(assigns(:build).id).to eq(build.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has a build' do
|
context 'when build does not exist' do
|
||||||
expect(response).to have_http_status(:ok)
|
before do
|
||||||
expect(assigns(:build).id).to eq(build.id)
|
get_show(id: 1234)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders not_found' do
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when build does not exist' do
|
context 'when requesting JSON' do
|
||||||
|
let(:merge_request) { create(:merge_request, source_project: project) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
get_show(id: 1234)
|
project.add_developer(user)
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
|
||||||
|
|
||||||
|
get_show(id: build.id, format: :json)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders not_found' do
|
it 'exposes needed information' do
|
||||||
expect(response).to have_http_status(:not_found)
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(json_response['new_issue_path']).to end_with('/issues/new')
|
||||||
|
expect(json_response['raw_path']).to match(/builds\/\d+\/raw\z/)
|
||||||
|
expect(json_response['merge_request_path']).to match(/merge_requests\/\d+\z/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,17 @@ describe Gitlab::EtagCaching::Router do
|
||||||
expect(result.name).to eq 'merge_request_pipelines'
|
expect(result.name).to eq 'merge_request_pipelines'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'matches build endpoint' do
|
||||||
|
env = build_env(
|
||||||
|
'/my-group/my-project/builds/234.json'
|
||||||
|
)
|
||||||
|
|
||||||
|
result = described_class.match(env)
|
||||||
|
|
||||||
|
expect(result).to be_present
|
||||||
|
expect(result.name).to eq 'project_build'
|
||||||
|
end
|
||||||
|
|
||||||
it 'does not match blob with confusing name' do
|
it 'does not match blob with confusing name' do
|
||||||
env = build_env(
|
env = build_env(
|
||||||
'/my-group/my-project/blob/master/pipelines.json'
|
'/my-group/my-project/blob/master/pipelines.json'
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe BuildDetailsEntity do
|
||||||
|
it 'inherits from BuildEntity' do
|
||||||
|
expect(described_class).to be < BuildEntity
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#as_json' do
|
||||||
|
let(:project) { create(:project, :repository) }
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let!(:build) { create(:ci_build, :failed, project: project) }
|
||||||
|
let(:request) { double('request') }
|
||||||
|
let(:entity) { described_class.new(build, request: request, current_user: user, project: project) }
|
||||||
|
subject { entity.as_json }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(request).to receive(:current_user).and_return(user)
|
||||||
|
|
||||||
|
project.add_master(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user has access to issues and merge requests' do
|
||||||
|
let!(:merge_request) { create(:merge_request, source_project: project) }
|
||||||
|
|
||||||
|
it 'contains the needed key value pairs' do
|
||||||
|
expect(subject).to include(:coverage, :erased_at, :duration)
|
||||||
|
expect(subject).to include(:artifacts, :runner, :pipeline)
|
||||||
|
expect(subject).to include(:raw_path, :merge_request_path, :new_issue_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user can only read the build' do
|
||||||
|
it "won't display the paths to issues and merge requests" do
|
||||||
|
expect(subject['new_issue_path']).to be_nil
|
||||||
|
expect(subject['merge_request_path']).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe RunnerEntity do
|
||||||
|
let(:runner) { build(:ci_runner) }
|
||||||
|
let(:entity) { described_class.represent(runner) }
|
||||||
|
|
||||||
|
describe '#as_json' do
|
||||||
|
subject { entity.as_json }
|
||||||
|
|
||||||
|
it 'contains required fields' do
|
||||||
|
expect(subject).to include(:id, :name, :description)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue