Commit 9ad5bb11 authored by Fabio Pitino's avatar Fabio Pitino Committed by Shinya Maeda

Update FE JobLog feature to always be true

This is to help the API remove the feature flag
for job log with minimal FE changes. Set the
`isNewJobLog` to always return true
and removes the unneccessary tests.
parent 37bd477b
...@@ -17,7 +17,7 @@ import UnmetPrerequisitesBlock from './unmet_prerequisites_block.vue'; ...@@ -17,7 +17,7 @@ import UnmetPrerequisitesBlock from './unmet_prerequisites_block.vue';
import Sidebar from './sidebar.vue'; import Sidebar from './sidebar.vue';
import { sprintf } from '~/locale'; import { sprintf } from '~/locale';
import delayedJobMixin from '../mixins/delayed_job_mixin'; import delayedJobMixin from '../mixins/delayed_job_mixin';
import { isNewJobLogActive } from '../store/utils'; import Log from './log/log.vue';
export default { export default {
name: 'JobPageApp', name: 'JobPageApp',
...@@ -28,7 +28,7 @@ export default { ...@@ -28,7 +28,7 @@ export default {
EnvironmentsBlock, EnvironmentsBlock,
ErasedBlock, ErasedBlock,
Icon, Icon,
Log: () => (isNewJobLogActive() ? import('./log/log.vue') : import('./job_log.vue')), Log,
LogTopBar, LogTopBar,
StuckBlock, StuckBlock,
UnmetPrerequisitesBlock, UnmetPrerequisitesBlock,
......
<script>
import { mapState, mapActions } from 'vuex';
export default {
name: 'JobLog',
props: {
trace: {
type: String,
required: true,
},
isComplete: {
type: Boolean,
required: true,
},
},
computed: {
...mapState(['isScrolledToBottomBeforeReceivingTrace']),
},
updated() {
this.$nextTick(() => {
this.handleScrollDown();
});
},
mounted() {
this.$nextTick(() => {
this.handleScrollDown();
});
},
methods: {
...mapActions(['scrollBottom']),
/**
* The job log is sent in HTML, which means we need to use `v-html` to render it
* Using the updated hook with $nextTick is not enough to wait for the DOM to be updated
* in this case because it runs before `v-html` has finished running, since there's no
* Vue binding.
* In order to scroll the page down after `v-html` has finished, we need to use setTimeout
*/
handleScrollDown() {
if (this.isScrolledToBottomBeforeReceivingTrace) {
setTimeout(() => {
this.scrollBottom();
}, 0);
}
},
},
};
</script>
<template>
<pre class="js-build-trace build-trace qa-build-trace">
<code class="bash" v-html="trace">
</code>
<div v-if="!isComplete" class="js-log-animation build-loader-animation">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</pre>
</template>
import Vue from 'vue'; import Vue from 'vue';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { logLinesParser, updateIncrementalTrace, isNewJobLogActive } from './utils'; import { logLinesParser, updateIncrementalTrace } from './utils';
export default { export default {
[types.SET_JOB_ENDPOINT](state, endpoint) { [types.SET_JOB_ENDPOINT](state, endpoint) {
...@@ -25,22 +25,16 @@ export default { ...@@ -25,22 +25,16 @@ export default {
} }
if (log.append) { if (log.append) {
if (isNewJobLogActive()) { state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
} else {
state.trace += log.html;
}
state.traceSize += log.size; state.traceSize += log.size;
} else { } else {
// When the job still does not have a trace // When the job still does not have a trace
// the trace response will not have a defined // the trace response will not have a defined
// html or size. We keep the old value otherwise these // html or size. We keep the old value otherwise these
// will be set to `null` // will be set to `null`
if (isNewJobLogActive()) { state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
} else {
state.trace = log.html || state.trace;
}
state.traceSize = log.size || state.traceSize; state.traceSize = log.size || state.traceSize;
} }
......
import { isNewJobLogActive } from './utils';
export default () => ({ export default () => ({
jobEndpoint: null, jobEndpoint: null,
traceEndpoint: null, traceEndpoint: null,
...@@ -18,7 +16,7 @@ export default () => ({ ...@@ -18,7 +16,7 @@ export default () => ({
// Used to check if we should keep the automatic scroll // Used to check if we should keep the automatic scroll
isScrolledToBottomBeforeReceivingTrace: true, isScrolledToBottomBeforeReceivingTrace: true,
trace: isNewJobLogActive() ? [] : '', trace: [],
isTraceComplete: false, isTraceComplete: false,
traceSize: 0, traceSize: 0,
isTraceSizeVisible: false, isTraceSizeVisible: false,
......
...@@ -177,5 +177,3 @@ export const updateIncrementalTrace = (newLog = [], oldParsed = []) => { ...@@ -177,5 +177,3 @@ export const updateIncrementalTrace = (newLog = [], oldParsed = []) => {
return logLinesParser(newLog, parsedLog); return logLinesParser(newLog, parsedLog);
}; };
export const isNewJobLogActive = () => gon && gon.features && gon.features.jobLogJson;
...@@ -11,9 +11,6 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -11,9 +11,6 @@ class Projects::JobsController < Projects::ApplicationController
before_action :authorize_erase_build!, only: [:erase] before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize] before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action only: [:show] do
push_frontend_feature_flag(:job_log_json, project, default_enabled: true)
end
before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize before_action :authorize_create_proxy_build!, only: :proxy_websocket_authorize
before_action :verify_proxy_request!, only: :proxy_websocket_authorize before_action :verify_proxy_request!, only: :proxy_websocket_authorize
...@@ -55,15 +52,10 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -55,15 +52,10 @@ class Projects::JobsController < Projects::ApplicationController
format.json do format.json do
build.trace.being_watched! build.trace.being_watched!
# TODO: when the feature flag is removed we should not pass
# content_format to serialize method.
content_format = Feature.enabled?(:job_log_json, @project, default_enabled: true) ? :json : :html
build_trace = Ci::BuildTrace.new( build_trace = Ci::BuildTrace.new(
build: @build, build: @build,
stream: stream, stream: stream,
state: params[:state], state: params[:state])
content_format: content_format)
render json: BuildTraceSerializer render json: BuildTraceSerializer
.new(project: @project, current_user: @current_user) .new(project: @project, current_user: @current_user)
......
...@@ -2,40 +2,22 @@ ...@@ -2,40 +2,22 @@
module Ci module Ci
class BuildTrace class BuildTrace
CONVERTERS = {
html: Gitlab::Ci::Ansi2html,
json: Gitlab::Ci::Ansi2json
}.freeze
attr_reader :trace, :build attr_reader :trace, :build
delegate :state, :append, :truncated, :offset, :size, :total, to: :trace, allow_nil: true delegate :state, :append, :truncated, :offset, :size, :total, to: :trace, allow_nil: true
delegate :id, :status, :complete?, to: :build, prefix: true delegate :id, :status, :complete?, to: :build, prefix: true
def initialize(build:, stream:, state:, content_format:) def initialize(build:, stream:, state:)
@build = build @build = build
@content_format = content_format
if stream.valid? if stream.valid?
stream.limit stream.limit
@trace = CONVERTERS.fetch(content_format).convert(stream.stream, state) @trace = Gitlab::Ci::Ansi2json.convert(stream.stream, state)
end end
end end
def json? def lines
@content_format == :json @trace&.lines
end
def html?
@content_format == :html
end
def json_lines
@trace&.lines if json?
end
def html_lines
@trace&.html if html?
end end
end end
end end
...@@ -12,6 +12,5 @@ class BuildTraceEntity < Grape::Entity ...@@ -12,6 +12,5 @@ class BuildTraceEntity < Grape::Entity
expose :size expose :size
expose :total expose :total
expose :json_lines, as: :lines, if: ->(*) { object.json? } expose :lines
expose :html_lines, as: :html, if: ->(*) { object.html? }
end end
---
title: Remove legacy job log rendering
merge_request: 34538
author:
type: other
...@@ -13,8 +13,6 @@ module QA ...@@ -13,8 +13,6 @@ module QA
after do after do
@runner.remove_via_api! if @runner @runner.remove_via_api! if @runner
Runtime::Feature.enable('job_log_json') if @job_log_json_flag_enabled
end end
before do before do
......
...@@ -13,8 +13,6 @@ module QA ...@@ -13,8 +13,6 @@ module QA
after(:all) do after(:all) do
@runner.remove_via_api! @runner.remove_via_api!
Runtime::Feature.enable('job_log_json') if @job_log_json_flag_enabled
end end
before(:all) do before(:all) do
......
...@@ -646,109 +646,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -646,109 +646,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end end
end end
describe 'GET legacy trace.json' do
before do
stub_feature_flags(job_log_json: false)
get_trace
end
context 'when job has a trace artifact' do
let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
it 'returns a trace' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq job.id
expect(json_response['status']).to eq job.status
expect(json_response['state']).to be_present
expect(json_response['append']).not_to be_nil
expect(json_response['truncated']).not_to be_nil
expect(json_response['size']).to be_present
expect(json_response['total']).to be_present
expect(json_response['html']).to eq(job.trace.html)
end
end
context 'when job has a trace' do
let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
it 'returns a trace' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq job.id
expect(json_response['status']).to eq job.status
expect(json_response['html']).to eq('<span>BUILD TRACE</span>')
end
end
context 'when job has no traces' do
let(:job) { create(:ci_build, pipeline: pipeline) }
it 'returns no traces' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq job.id
expect(json_response['status']).to eq job.status
expect(json_response['html']).to be_nil
end
end
context 'when job has a trace with ANSI sequence and Unicode' do
let(:job) { create(:ci_build, :unicode_trace_live, pipeline: pipeline) }
it 'returns a trace with Unicode' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq job.id
expect(json_response['status']).to eq job.status
expect(json_response['html']).to include("ヾ(´༎ຶД༎ຶ`)ノ")
end
end
context 'when trace artifact is in ObjectStorage' do
let(:url) { 'http://object-storage/trace' }
let(:file_path) { expand_fixture_path('trace/sample_trace') }
let!(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
before do
allow_any_instance_of(JobArtifactUploader).to receive(:file_storage?) { false }
allow_any_instance_of(JobArtifactUploader).to receive(:url) { url }
allow_any_instance_of(JobArtifactUploader).to receive(:size) { File.size(file_path) }
end
context 'when there are no network issues' do
before do
stub_remote_url_206(url, file_path)
get_trace
end
it 'returns a trace' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq job.id
expect(json_response['status']).to eq job.status
expect(json_response['html']).to eq(job.trace.html)
end
end
context 'when there is a network issue' do
before do
stub_remote_url_500(url)
end
it 'returns a trace' do
expect { get_trace }.to raise_error(Gitlab::HttpIO::FailedToGetChunkError)
end
end
end
def get_trace
get :trace,
params: {
namespace_id: project.namespace,
project_id: project,
id: job.id
},
format: :json
end
end
describe 'GET status.json' do describe 'GET status.json' do
let(:job) { create(:ci_build, pipeline: pipeline) } let(:job) { create(:ci_build, pipeline: pipeline) }
let(:status) { job.detailed_status(double('user')) } let(:status) { job.detailed_status(double('user')) }
......
...@@ -837,7 +837,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do ...@@ -837,7 +837,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders empty state' do it 'renders empty state' do
expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).not_to have_selector('.js-build-trace') expect(page).not_to have_selector('.job-log')
expect(page).to have_content('This job has been canceled') expect(page).to have_content('This job has been canceled')
end end
end end
...@@ -852,7 +852,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do ...@@ -852,7 +852,7 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'renders empty state' do it 'renders empty state' do
expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).not_to have_selector('.js-build-trace') expect(page).not_to have_selector('.job-log')
expect(page).to have_content('This job has been skipped') expect(page).to have_content('This job has been skipped')
end end
end end
......
...@@ -397,132 +397,31 @@ describe('Job App', () => { ...@@ -397,132 +397,31 @@ describe('Job App', () => {
}); });
}); });
describe('trace output', () => { describe('trace controls', () => {
describe('with append flag', () => { beforeEach(() =>
it('appends the log content to the existing one', () => setupAndMount({
setupAndMount({ traceData: {
traceData: { html: '<span>Update</span>',
html: '<span>More<span>', status: 'success',
status: 'running', append: false,
state: 'newstate', size: 50,
append: true, total: 100,
complete: true, complete: true,
}, },
}) }),
.then(() => { );
store.state.trace = 'Update';
it('should render scroll buttons', () => {
return wrapper.vm.$nextTick(); expect(wrapper.find('.js-scroll-top').exists()).toBe(true);
}) expect(wrapper.find('.js-scroll-bottom').exists()).toBe(true);
.then(() => {
expect(
wrapper
.find('.js-build-trace')
.text()
.trim(),
).toEqual('Update');
}));
}); });
describe('without append flag', () => { it('should render link to raw ouput', () => {
it('replaces the trace', () => expect(wrapper.find('.js-raw-link-controller').exists()).toBe(true);
setupAndMount({
traceData: {
html: '<span>Different<span>',
status: 'running',
append: false,
complete: true,
},
}).then(() => {
expect(
wrapper
.find('.js-build-trace')
.text()
.trim(),
).toEqual('Different');
}));
});
describe('truncated information', () => {
describe('when size is less than total', () => {
it('shows information about truncated log', () => {
mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
html: '<span>Update</span>',
status: 'success',
append: false,
size: 50,
total: 100,
complete: true,
});
return setupAndMount({
traceData: {
html: '<span>Update</span>',
status: 'success',
append: false,
size: 50,
total: 100,
complete: true,
},
}).then(() => {
expect(
wrapper
.find('.js-truncated-info')
.text()
.trim(),
).toContain('Showing last 50 bytes');
});
});
});
describe('when size is equal than total', () => {
it('does not show the truncated information', () =>
setupAndMount({
traceData: {
html: '<span>Update</span>',
status: 'success',
append: false,
size: 100,
total: 100,
complete: true,
},
}).then(() => {
expect(
wrapper
.find('.js-truncated-info')
.text()
.trim(),
).toEqual('');
}));
});
}); });
describe('trace controls', () => { it('should render link to erase job', () => {
beforeEach(() => expect(wrapper.find('.js-erase-link').exists()).toBe(true);
setupAndMount({
traceData: {
html: '<span>Update</span>',
status: 'success',
append: false,
size: 50,
total: 100,
complete: true,
},
}),
);
it('should render scroll buttons', () => {
expect(wrapper.find('.js-scroll-top').exists()).toBe(true);
expect(wrapper.find('.js-scroll-bottom').exists()).toBe(true);
});
it('should render link to raw ouput', () => {
expect(wrapper.find('.js-raw-link-controller').exists()).toBe(true);
});
it('should render link to erase job', () => {
expect(wrapper.find('.js-erase-link').exists()).toBe(true);
});
}); });
}); });
}); });
import Vue from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import component from '~/jobs/components/job_log.vue';
import createStore from '~/jobs/store';
import { resetStore } from '../store/helpers';
describe('Job Log', () => {
const Component = Vue.extend(component);
let store;
let vm;
const trace =
'<span>Running with gitlab-runner 12.1.0 (de7731dd)<br/></span><span> on docker-auto-scale-com d5ae8d25<br/></span><div class="gl-mr-3" data-timestamp="1565502765" data-section="prepare-executor" role="button"></div><span class="section section-header js-s-prepare-executor">Using Docker executor with image ruby:2.6 ...<br/></span>';
beforeEach(() => {
store = createStore();
});
afterEach(() => {
resetStore(store);
vm.$destroy();
});
it('renders provided trace', () => {
vm = mountComponentWithStore(Component, {
props: {
trace,
isComplete: true,
},
store,
});
expect(vm.$el.querySelector('code').textContent).toContain(
'Running with gitlab-runner 12.1.0 (de7731dd)',
);
});
describe('while receiving trace', () => {
it('renders animation', () => {
vm = mountComponentWithStore(Component, {
props: {
trace,
isComplete: false,
},
store,
});
expect(vm.$el.querySelector('.js-log-animation')).not.toBeNull();
});
});
describe('when build trace has finishes', () => {
it('does not render animation', () => {
vm = mountComponentWithStore(Component, {
props: {
trace,
isComplete: true,
},
store,
});
expect(vm.$el.querySelector('.js-log-animation')).toBeNull();
});
});
});
...@@ -76,28 +76,15 @@ describe('Jobs Store Mutations', () => { ...@@ -76,28 +76,15 @@ describe('Jobs Store Mutations', () => {
lines: [], lines: [],
}); });
expect(stateCopy.trace).toEqual(html);
expect(stateCopy.traceSize).toEqual(511846); expect(stateCopy.traceSize).toEqual(511846);
expect(stateCopy.isTraceComplete).toEqual(true); expect(stateCopy.isTraceComplete).toEqual(true);
}); });
describe('with new job log', () => { describe('with new job log', () => {
let stateWithNewLog;
beforeEach(() => {
gon.features = gon.features || {};
gon.features.jobLogJson = true;
stateWithNewLog = state();
});
afterEach(() => {
gon.features.jobLogJson = false;
});
describe('log.lines', () => { describe('log.lines', () => {
describe('when append is true', () => { describe('when append is true', () => {
it('sets the parsed log ', () => { it('sets the parsed log ', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, { mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: true, append: true,
size: 511846, size: 511846,
complete: true, complete: true,
...@@ -109,7 +96,7 @@ describe('Jobs Store Mutations', () => { ...@@ -109,7 +96,7 @@ describe('Jobs Store Mutations', () => {
], ],
}); });
expect(stateWithNewLog.trace).toEqual([ expect(stateCopy.trace).toEqual([
{ {
offset: 1, offset: 1,
content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }], content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
...@@ -121,7 +108,7 @@ describe('Jobs Store Mutations', () => { ...@@ -121,7 +108,7 @@ describe('Jobs Store Mutations', () => {
describe('when it is defined', () => { describe('when it is defined', () => {
it('sets the parsed log ', () => { it('sets the parsed log ', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, { mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: false, append: false,
size: 511846, size: 511846,
complete: true, complete: true,
...@@ -130,7 +117,7 @@ describe('Jobs Store Mutations', () => { ...@@ -130,7 +117,7 @@ describe('Jobs Store Mutations', () => {
], ],
}); });
expect(stateWithNewLog.trace).toEqual([ expect(stateCopy.trace).toEqual([
{ {
offset: 0, offset: 0,
content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }], content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }],
...@@ -142,7 +129,7 @@ describe('Jobs Store Mutations', () => { ...@@ -142,7 +129,7 @@ describe('Jobs Store Mutations', () => {
describe('when it is null', () => { describe('when it is null', () => {
it('sets the default value', () => { it('sets the default value', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, { mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: true, append: true,
html, html,
size: 511846, size: 511846,
...@@ -150,7 +137,7 @@ describe('Jobs Store Mutations', () => { ...@@ -150,7 +137,7 @@ describe('Jobs Store Mutations', () => {
lines: null, lines: null,
}); });
expect(stateWithNewLog.trace).toEqual([]); expect(stateCopy.trace).toEqual([]);
}); });
}); });
}); });
......
...@@ -11,7 +11,7 @@ RSpec.describe Ci::BuildTrace do ...@@ -11,7 +11,7 @@ RSpec.describe Ci::BuildTrace do
Gitlab::Ci::Trace::Stream.new { data } Gitlab::Ci::Trace::Stream.new { data }
end end
subject { described_class.new(build: build, stream: stream, state: state, content_format: content_format) } subject { described_class.new(build: build, stream: stream, state: state) }
shared_examples 'delegates methods' do shared_examples 'delegates methods' do
it { is_expected.to delegate_method(:state).to(:trace) } it { is_expected.to delegate_method(:state).to(:trace) }
...@@ -25,29 +25,11 @@ RSpec.describe Ci::BuildTrace do ...@@ -25,29 +25,11 @@ RSpec.describe Ci::BuildTrace do
it { is_expected.to delegate_method(:complete?).to(:build).with_prefix } it { is_expected.to delegate_method(:complete?).to(:build).with_prefix }
end end
context 'with :json content format' do it_behaves_like 'delegates methods'
let(:content_format) { :json }
it_behaves_like 'delegates methods' it 'returns formatted trace' do
expect(subject.lines).to eq([
it { is_expected.to be_json } { offset: 0, content: [{ text: 'the-stream' }] }
])
it 'returns formatted trace' do
expect(subject.trace.lines).to eq([
{ offset: 0, content: [{ text: 'the-stream' }] }
])
end
end
context 'with :html content format' do
let(:content_format) { :html }
it_behaves_like 'delegates methods'
it { is_expected.to be_html }
it 'returns formatted trace' do
expect(subject.trace.html).to eq('<span>the-stream</span>')
end
end end
end end
...@@ -13,7 +13,7 @@ RSpec.describe BuildTraceEntity do ...@@ -13,7 +13,7 @@ RSpec.describe BuildTraceEntity do
end end
let(:build_trace) do let(:build_trace) do
Ci::BuildTrace.new(build: build, stream: stream, content_format: content_format, state: nil) Ci::BuildTrace.new(build: build, stream: stream, state: nil)
end end
let(:entity) do let(:entity) do
...@@ -22,42 +22,24 @@ RSpec.describe BuildTraceEntity do ...@@ -22,42 +22,24 @@ RSpec.describe BuildTraceEntity do
subject { entity.as_json } subject { entity.as_json }
shared_examples 'includes build and trace metadata' do it 'includes build attributes' do
it 'includes build attributes' do expect(subject[:id]).to eq(build.id)
expect(subject[:id]).to eq(build.id) expect(subject[:status]).to eq(build.status)
expect(subject[:status]).to eq(build.status) expect(subject[:complete]).to eq(build.complete?)
expect(subject[:complete]).to eq(build.complete?)
end
it 'includes trace metadata' do
expect(subject).to include(:state)
expect(subject).to include(:append)
expect(subject).to include(:truncated)
expect(subject).to include(:offset)
expect(subject).to include(:size)
expect(subject).to include(:total)
end
end end
context 'when content format is :json' do it 'includes trace metadata' do
let(:content_format) { :json } expect(subject).to include(:state)
expect(subject).to include(:append)
it_behaves_like 'includes build and trace metadata' expect(subject).to include(:truncated)
expect(subject).to include(:offset)
it 'includes the trace content in json' do expect(subject).to include(:size)
expect(subject[:lines]).to eq([ expect(subject).to include(:total)
{ offset: 0, content: [{ text: 'the-trace' }] }
])
end
end end
context 'when content format is :html' do it 'includes the trace content in json' do
let(:content_format) { :html } expect(subject[:lines]).to eq([
{ offset: 0, content: [{ text: 'the-trace' }] }
it_behaves_like 'includes build and trace metadata' ])
it 'includes the trace content in json' do
expect(subject[:html]).to eq('<span>the-trace</span>')
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment