Merge branch 'generate-spans-for-sections' into 'master'

Add collapsible sections to job log

See merge request gitlab-org/gitlab-ce!28642
This commit is contained in:
Sean McGivern 2019-06-17 17:03:41 +00:00
commit 2634cad695
13 changed files with 323 additions and 57 deletions

View File

@ -17,10 +17,19 @@ export default {
...mapState(['isScrolledToBottomBeforeReceivingTrace']),
},
updated() {
this.$nextTick(() => this.handleScrollDown());
this.$nextTick(() => {
this.handleScrollDown();
this.handleCollapsibleRows();
});
},
mounted() {
this.$nextTick(() => this.handleScrollDown());
this.$nextTick(() => {
this.handleScrollDown();
this.handleCollapsibleRows();
});
},
destroyed() {
this.removeEventListener();
},
methods: {
...mapActions(['scrollBottom']),
@ -38,21 +47,45 @@ export default {
}, 0);
}
},
removeEventListener() {
this.$el
.querySelectorAll('.js-section-start')
.forEach(el => el.removeEventListener('click', this.handleSectionClick));
},
/**
* The collapsible rows are sent in HTML from the backend
* We need tos add a onclick handler for the divs that match `.js-section-start`
*
*/
handleCollapsibleRows() {
this.$el
.querySelectorAll('.js-section-start')
.forEach(el => el.addEventListener('click', this.handleSectionClick));
},
/**
* On click, we toggle the hidden class of
* all the rows that match the `data-section` selector
*/
handleSectionClick(evt) {
const clickedArrow = evt.currentTarget;
// toggle the arrow class
clickedArrow.classList.toggle('fa-caret-right');
clickedArrow.classList.toggle('fa-caret-down');
const { section } = clickedArrow.dataset;
const sibilings = this.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`);
sibilings.forEach(row => row.classList.toggle('hidden'));
},
},
};
</script>
<template>
<pre class="js-build-trace build-trace qa-build-trace">
<code
class="bash"
v-html="trace"
>
<code class="bash" v-html="trace">
</code>
<div
v-if="!isComplete"
class="js-log-animation build-loader-animation"
>
<div v-if="!isComplete" class="js-log-animation build-loader-animation">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>

View File

@ -124,6 +124,10 @@
float: left;
padding-left: $gl-padding-8;
}
.section-header ~ .section.line {
margin-left: $gl-padding;
}
}
.build-header {

View File

@ -0,0 +1,5 @@
---
title: Adds collapsible sections for job log
merge_request: 28642
author:
type: added

View File

@ -131,9 +131,9 @@ module Gitlab
def on_109(_) set_bg_color(9, 'l') end
attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask
attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask, :sections, :lineno_in_section
STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask].freeze
STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask, :sections, :lineno_in_section].freeze
def convert(stream, new_state)
reset_state
@ -153,10 +153,9 @@ module Gitlab
start_offset = @offset
open_new_tag
stream.each_line do |line|
s = StringScanner.new(line)
until s.eos?
if s.scan(Gitlab::Regex.build_trace_section_regex)
@ -166,11 +165,11 @@ module Gitlab
elsif s.scan(/\e(([@-_])(.*?)?)?$/)
break
elsif s.scan(/</)
@out << '&lt;'
write_in_tag '&lt;'
elsif s.scan(/\r?\n/)
@out << '<br>'
handle_new_line
else
@out << s.scan(/./m)
write_in_tag s.scan(/./m)
end
@offset += s.matched_size
@ -190,13 +189,59 @@ module Gitlab
)
end
def section_to_class_name(section)
section.to_s.downcase.gsub(/[^a-z0-9]/, '-')
end
def handle_new_line
css_classes = []
if @sections.any?
css_classes = %w[section line] + sections.map { |section| "s_#{section}" }
end
write_in_tag %{<br/>}
write_raw %{<span class="#{css_classes.join(' ')}"></span>} if css_classes.any?
@lineno_in_section += 1
open_new_tag
end
def handle_section(scanner)
action = scanner[1]
timestamp = scanner[2]
section = scanner[3]
line = scanner.matched[0...-5] # strips \r\033[0K
@out << %{<div class="hidden" data-action="#{action}" data-timestamp="#{timestamp}" data-section="#{section}">#{line}</div>}
normalized_section = section_to_class_name(section)
if action == "start"
handle_section_start(normalized_section, timestamp)
elsif action == "end"
handle_section_end(normalized_section, timestamp)
end
end
def handle_section_start(section, timestamp)
return if @sections.include?(section)
@sections << section
write_raw %{<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="#{timestamp}" data-section="#{data_section_names}" role="button"></div>}
@lineno_in_section = 0
end
def handle_section_end(section, timestamp)
return unless @sections.include?(section)
# close all sections up to section
until @sections.empty?
write_raw %{<div class="section-end" data-section="#{data_section_names}"></div>}
last_section = @sections.pop
break if section == last_section
end
end
def data_section_names
@sections.join(" ")
end
def handle_sequence(scanner)
@ -217,8 +262,6 @@ module Gitlab
end
evaluate_command_stack(commands)
open_new_tag
end
def evaluate_command_stack(stack)
@ -231,6 +274,20 @@ module Gitlab
evaluate_command_stack(stack)
end
def write_in_tag(data)
ensure_open_new_tag
@out << data
end
def write_raw(data)
close_open_tags
@out << data
end
def ensure_open_new_tag
open_new_tag if @n_open_tags == 0
end
def open_new_tag
css_classes = []
@ -251,7 +308,11 @@ module Gitlab
css_classes << "term-#{css_class}" if @style_mask & flag != 0
end
return if css_classes.empty?
if @sections.any?
css_classes << "section"
css_classes << "js-section-header" if @lineno_in_section == 0
css_classes += sections.map { |section| "js-s-#{section}" }
end
@out << %{<span class="#{css_classes.join(' ')}">}
@n_open_tags += 1
@ -268,6 +329,8 @@ module Gitlab
@offset = 0
@n_open_tags = 0
@out = +''
@sections = []
@lineno_in_section = 0
reset
end

View File

@ -540,7 +540,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state 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('BUILD TRACE')
expect(json_response['html']).to eq('<span class="">BUILD TRACE</span>')
end
end

View File

@ -228,6 +228,26 @@ FactoryBot.define do
end
end
trait :trace_with_duplicate_sections do
after(:create) do |build, evaluator|
trace = File.binread(
File.expand_path(
Rails.root.join('spec/fixtures/trace/trace_with_duplicate_sections')))
build.trace.set(trace)
end
end
trait :trace_with_sections do
after(:create) do |build, evaluator|
trace = File.binread(
File.expand_path(
Rails.root.join('spec/fixtures/trace/trace_with_sections')))
build.trace.set(trace)
end
end
trait :unicode_trace_live do
after(:create) do |build, evaluator|
trace = File.binread(

View File

@ -34,6 +34,52 @@ describe 'User browses a job', :js do
expect(page).to have_content('Job has been erased')
end
shared_examples 'has collapsible sections' do
it 'collapses the section clicked' do
wait_for_requests
text_to_hide = "Cloning into '/nolith/ci-tests'"
text_to_show = 'Waiting for pod'
expect(page).to have_content(text_to_hide)
expect(page).to have_content(text_to_show)
first('.js-section-start[data-section="get-sources"]').click
expect(page).not_to have_content(text_to_hide)
expect(page).to have_content(text_to_show)
end
end
context 'when job trace contains sections' do
let!(:build) { create(:ci_build, :success, :trace_with_sections, :coverage, pipeline: pipeline) }
it_behaves_like 'has collapsible sections'
end
context 'when job trace contains duplicate sections' do
let!(:build) { create(:ci_build, :success, :trace_with_duplicate_sections, :coverage, pipeline: pipeline) }
it_behaves_like 'has collapsible sections'
end
context 'when job trace contains sections' do
let!(:build) { create(:ci_build, :success, :trace_with_duplicate_sections, :coverage, pipeline: pipeline) }
it 'collapses a section' do
wait_for_requests
text_to_hide = "Cloning into '/nolith/ci-tests'"
text_to_show = 'Waiting for pod'
expect(page).to have_content(text_to_hide)
expect(page).to have_content(text_to_show)
first('.js-section-start[data-section="get-sources"]').click
expect(page).not_to have_content(text_to_hide)
expect(page).to have_content(text_to_show)
end
end
context 'with a failed job' do
let!(:build) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) }

View File

@ -0,0 +1,30 @@
Running with gitlab-runner dev (HEAD)
on kitsune minikube (a21b584f)
WARNING: Namespace is empty, therefore assuming 'default'.
Using Kubernetes namespace: default
Using Kubernetes executor with image alpine:3.4 ...
section_start:1506004954:prepare_script Waiting for pod default/runner-a21b584f-project-1208199-concurrent-0sg03f to be running, status is Pending
Running on runner-a21b584f-project-1208199-concurrent-0sg03f via kitsune.local...
section_end:1506004957:prepare_script section_start:1506004957:get_sources Cloning repository...
Cloning into '/nolith/ci-tests'...
Checking out dddd7a6e as master...
Skipping Git submodules setup
section_end:1506004958:get_sources section_start:1506004958:restore_cache section_end:1506004958:restore_cache section_start:1506004958:download_artifacts section_end:1506004958:download_artifacts section_start:1506004958:build_script $ whoami
root
section_end:1506004959:build_script section_start:1506004959:after_script section_end:1506004959:after_script section_start:1506004959:archive_cache section_end:1506004959:archive_cache section_start:1506004959:upload_artifacts section_end:1506004959:upload_artifacts Job succeeded

Running with gitlab-runner dev (HEAD)
on kitsune minikube (a21b584f)
WARNING: Namespace is empty, therefore assuming 'default'.
Using Kubernetes namespace: default
Using Kubernetes executor with image alpine:3.4 ...
section_start:1506004954:prepare_script Waiting for pod default/runner-a21b584f-project-1208199-concurrent-0sg03f to be running, status is Pending
Running on runner-a21b584f-project-1208199-concurrent-0sg03f via kitsune.local...
section_end:1506004957:prepare_script section_start:1506004957:get_sources Cloning repository...
Cloning into '/nolith/ci-tests'...
Checking out dddd7a6e as master...
Skipping Git submodules setup
section_end:1506004958:get_sources section_start:1506004958:restore_cache section_end:1506004958:restore_cache section_start:1506004958:download_artifacts section_end:1506004958:download_artifacts section_start:1506004958:build_script $ whoami
root
section_end:1506004959:build_script section_start:1506004959:after_script section_end:1506004959:after_script section_start:1506004959:archive_cache section_end:1506004959:archive_cache section_start:1506004959:upload_artifacts section_end:1506004959:upload_artifacts Job succeeded


View File

@ -3,6 +3,7 @@ import component from '~/jobs/components/job_log.vue';
import createStore from '~/jobs/store';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../store/helpers';
import { logWithCollapsibleSections } from '../mock_data';
describe('Job Log', () => {
const Component = Vue.extend(component);
@ -62,4 +63,40 @@ describe('Job Log', () => {
expect(vm.$el.querySelector('.js-log-animation')).toBeNull();
});
});
describe('Collapsible sections', () => {
beforeEach(() => {
vm = mountComponentWithStore(Component, {
props: {
trace: logWithCollapsibleSections.html,
isComplete: true,
},
store,
});
});
it('renders open arrow', () => {
expect(vm.$el.querySelector('.fa-caret-down')).not.toBeNull();
});
it('toggles hidden class to the sibilings rows when arrow is clicked', done => {
vm.$nextTick()
.then(() => {
const { section } = vm.$el.querySelector('.js-section-start').dataset;
vm.$el.querySelector('.js-section-start').click();
vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
expect(el.classList.contains('hidden')).toEqual(true);
});
vm.$el.querySelector('.js-section-start').click();
vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
expect(el.classList.contains('hidden')).toEqual(false);
});
})
.then(done)
.catch(done.fail);
});
});
});

View File

@ -1189,3 +1189,18 @@ export const jobsInStage = {
path: '/gitlab-org/gitlab-shell/pipelines/27#build',
dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build',
};
export const logWithCollapsibleSections = {
append: false,
complete: true,
html:
'<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1559571405" data-section="after-script" role="button"></div><span class="term-fg-l-green term-bold section js-section-header js-s-after-script">Running after script...</span><span class="section js-section-header js-s-after-script"><br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script"></span><span class="term-fg-l-green term-bold section js-s-after-script">$ date</span><span class="section js-s-after-script"><br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script">Mon Jun 3 14:16:46 UTC 2019<br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script"></span><div class="section-end" data-section="after-script"></div><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer"data-timestamp="1559571408" data-section="archive-cache" role="button" ></div><span class="term-fg-l-green term-bold section js-section-header js-s-archive-cache">Not uploading cache debian-stretch-ruby-2.6.3-node-10.x-3 due to policy</span><span class="section js-section-header js-s-archive-cache"><br /></span><span class="section s_archive-cache line"></span><span class="section js-s-archive-cache"></span><div class="section-end" data-section="archive-cache"></div><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1559571409" data-section="upload-artifacts-on-success" role="button"></div><span class="term-fg-l-green term-bold section js-section-header js-s-upload-artifacts-on-success">Uploading artifacts...</span><span class="section js-section-header js-s-upload-artifacts-on-success"><br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">coverage/: found 5 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">knapsack/: found 4 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">rspec_flaky/: found 4 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">rspec_profiling/: found 1 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><span class="term-fg-yellow section js-s-upload-artifacts-on-success">WARNING: tmp/capybara/: no matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">Uploading artifacts to coordinator... ok </span><span class="section js-s-upload-artifacts-on-success"> id</span><span class="section js-s-upload-artifacts-on-success">=224162288 responseStatus</span><span class="section js-s-upload-artifacts-on-success">=201 Created token</span><span class="section js-s-upload-artifacts-on-success">=bBmyXJNW<br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><span class="term-fg-l-green term-bold section js-s-upload-artifacts-on-success">Uploading artifacts...</span><span class="section js-s-upload-artifacts-on-success"><br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">junit_rspec.xml: found 1 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">Uploading artifacts to coordinator... ok </span><span class="section js-s-upload-artifacts-on-success"> id</span><span class="section js-s-upload-artifacts-on-success">=224162288 responseStatus</span><span class="section js-s-upload-artifacts-on-success">=201 Created token</span><span class="section js-s-upload-artifacts-on-success">=bBmyXJNW<br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><div class="section-end" data-section="upload-artifacts-on-success"></div><span class="term-fg-l-green term-bold">Job succeeded<br /><span class="term-fg-l-green term-bold"></span></span>',
id: 1385,
offset: 0,
size: 78815,
state:
'eyJvZmZzZXQiOjc4ODE1LCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowLCJzZWN0aW9ucyI6W10sImxpbmVub19pbl9zZWN0aW9uIjoxMX0=',
status: 'success',
total: 78815,
truncated: false,
};

View File

@ -4,11 +4,11 @@ describe Gitlab::Ci::Ansi2html do
subject { described_class }
it "prints non-ansi as-is" do
expect(convert_html("Hello")).to eq('Hello')
expect(convert_html("Hello")).to eq('<span class="">Hello</span>')
end
it "strips non-color-changing control sequences" do
expect(convert_html("Hello \e[2Kworld")).to eq('Hello world')
expect(convert_html("Hello \e[2Kworld")).to eq('<span class="">Hello world</span>')
end
it "prints simply red" do
@ -32,7 +32,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets colors after red on blue" do
expect(convert_html("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span> world')
expect(convert_html("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span class=""> world</span>')
end
it "performs color change from red/blue to yellow/blue" do
@ -44,11 +44,11 @@ describe Gitlab::Ci::Ansi2html do
end
it "performs color change from red/blue to reset to yellow/green" do
expect(convert_html("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span> <span class="term-fg-yellow term-bg-green">world</span>')
expect(convert_html("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span class=""> </span><span class="term-fg-yellow term-bg-green">world</span>')
end
it "ignores unsupported codes" do
expect(convert_html("\e[51mHello\e[0m")).to eq('Hello')
expect(convert_html("\e[51mHello\e[0m")).to eq('<span class="">Hello</span>')
end
it "prints light red" do
@ -72,8 +72,8 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets bold text" do
expect(convert_html("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span> world')
expect(convert_html("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span> world')
expect(convert_html("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span><span class=""> world</span>')
expect(convert_html("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span><span class=""> world</span>')
end
it "prints italic text" do
@ -81,7 +81,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets italic text" do
expect(convert_html("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span> world')
expect(convert_html("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span><span class=""> world</span>')
end
it "prints underlined text" do
@ -89,7 +89,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets underlined text" do
expect(convert_html("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span> world')
expect(convert_html("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span><span class=""> world</span>')
end
it "prints concealed text" do
@ -97,7 +97,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets concealed text" do
expect(convert_html("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span> world')
expect(convert_html("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span><span class=""> world</span>')
end
it "prints crossed-out text" do
@ -105,7 +105,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets crossed-out text" do
expect(convert_html("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span> world')
expect(convert_html("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span><span class=""> world</span>')
end
it "can print 256 xterm fg colors" do
@ -137,15 +137,15 @@ describe Gitlab::Ci::Ansi2html do
end
it "prints &lt;" do
expect(convert_html("<")).to eq('&lt;')
expect(convert_html("<")).to eq('<span class="">&lt;</span>')
end
it "replaces newlines with line break tags" do
expect(convert_html("\n")).to eq('<br>')
expect(convert_html("\n")).to eq('<span class=""><br/><span class=""></span></span>')
end
it "groups carriage returns with newlines" do
expect(convert_html("\r\n")).to eq('<br>')
expect(convert_html("\r\n")).to eq('<span class=""><br/><span class=""></span></span>')
end
describe "incremental update" do
@ -166,14 +166,14 @@ describe Gitlab::Ci::Ansi2html do
let(:pre_text) { "\e[1mHello" }
let(:pre_html) { "<span class=\"term-bold\">Hello</span>" }
let(:text) { "\e[1mWorld" }
let(:html) { "<span class=\"term-bold\"></span><span class=\"term-bold\">World</span>" }
let(:html) { "<span class=\"term-bold\">World</span>" }
it_behaves_like 'stateable converter'
end
context "with split sequence" do
let(:pre_text) { "\e[1m" }
let(:pre_html) { "<span class=\"term-bold\"></span>" }
let(:pre_html) { "" }
let(:text) { "Hello" }
let(:html) { "<span class=\"term-bold\">Hello</span>" }
@ -182,7 +182,7 @@ describe Gitlab::Ci::Ansi2html do
context "with partial sequence" do
let(:pre_text) { "Hello\e" }
let(:pre_html) { "Hello" }
let(:pre_html) { "<span class=\"\">Hello</span>" }
let(:text) { "[1m World" }
let(:html) { "<span class=\"term-bold\"> World</span>" }
@ -191,9 +191,9 @@ describe Gitlab::Ci::Ansi2html do
context 'with new line' do
let(:pre_text) { "Hello\r" }
let(:pre_html) { "Hello\r" }
let(:pre_html) { "<span class=\"\">Hello\r</span>" }
let(:text) { "\nWorld" }
let(:html) { "<br>World" }
let(:html) { "<span class=\"\"><br/><span class=\"\">World</span></span>" }
it_behaves_like 'stateable converter'
end
@ -207,20 +207,20 @@ describe Gitlab::Ci::Ansi2html do
let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"}
let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"}
let(:section_start_html) do
'<div class="hidden" data-action="start"'\
" data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{section_name}\">"\
"#{section_start[0...-5]}</div>"
'<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer"' \
" data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{class_name(section_name)}\"" \
' role="button"></div>'
end
let(:section_end_html) do
'<div class="hidden" data-action="end"'\
" data-timestamp=\"#{section_end_time.to_i}\" data-section=\"#{section_name}\">"\
"#{section_end[0...-5]}</div>"
"<div class=\"section-end\" data-section=\"#{class_name(section_name)}\"></div>"
end
shared_examples 'forbidden char in section_name' do
it 'ignores sections' do
text = "#{section_start}Some text#{section_end}"
html = text.gsub("\033[0K", '').gsub('<', '&lt;')
class_name_start = section_start.gsub("\033[0K", '').gsub('<', '&lt;')
class_name_end = section_end.gsub("\033[0K", '').gsub('<', '&lt;')
html = %{<span class="">#{class_name_start}Some text#{class_name_end}</span>}
expect(convert_html(text)).to eq(html)
end
@ -231,7 +231,11 @@ describe Gitlab::Ci::Ansi2html do
it 'prints light red' do
text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}}
header = %{<span class="term-fg-l-red section js-section-header js-s-#{class_name(section_name)}">Hello</span>}
line_break = %{<span class="section js-section-header js-s-#{class_name(section_name)}"><br/></span>}
line = %{<span class="section line s_#{class_name(section_name)}"></span>}
empty_line = %{<span class="section js-s-#{class_name(section_name)}"></span>}
html = "#{section_start_html}#{header}#{line_break}#{line}#{empty_line}#{section_end_html}"
expect(convert_html(text)).to eq(html)
end
@ -294,4 +298,8 @@ describe Gitlab::Ci::Ansi2html do
stream = StringIO.new(data)
subject.convert(stream).html
end
def class_name(section)
subject::Converter.new.section_to_class_name(section)
end
end

View File

@ -64,7 +64,10 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
result = stream.html
expect(result).to eq("ヾ(´༎ຶД༎ຶ`)ノ<br><span class=\"term-fg-green\">許功蓋</span><br>")
expect(result).to eq(
"<span class=\"\">ヾ(´༎ຶД༎ຶ`)ノ<br/><span class=\"\"></span></span>"\
"<span class=\"term-fg-green\">許功蓋</span><span class=\"\"><br/>"\
"<span class=\"\"></span></span>")
expect(result.encoding).to eq(Encoding.default_external)
end
end
@ -250,7 +253,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
it 'returns html content with state' do
result = stream.html_with_state
expect(result.html).to eq("1234")
expect(result.html).to eq("<span class=\"\">1234</span>")
end
context 'follow-up state' do
@ -266,7 +269,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
result = stream.html_with_state(last_result.state)
expect(result.append).to be_truthy
expect(result.html).to eq("5678")
expect(result.html).to eq("<span class=\"\">5678</span>")
end
end
end
@ -302,11 +305,13 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
describe '#html' do
shared_examples_for 'htmls' do
it "returns html" do
expect(stream.html).to eq("12<br>34<br>56")
expect(stream.html).to eq(
"<span class=\"\">12<br/><span class=\"\">34<br/>"\
"<span class=\"\">56</span></span></span>")
end
it "returns html for last line only" do
expect(stream.html(last_lines: 1)).to eq("56")
expect(stream.html(last_lines: 1)).to eq("<span class=\"\">56</span>")
end
end

View File

@ -5,11 +5,11 @@ shared_examples_for 'common trace features' do
end
it "returns formatted html" do
expect(trace.html).to eq("12<br>34")
expect(trace.html).to eq("<span class=\"\">12<br/><span class=\"\">34</span></span>")
end
it "returns last line of formatted html" do
expect(trace.html(last_lines: 1)).to eq("34")
expect(trace.html(last_lines: 1)).to eq("<span class=\"\">34</span>")
end
end