summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean McGivern <sean@gitlab.com>2019-06-17 17:03:41 +0000
committerSean McGivern <sean@gitlab.com>2019-06-17 17:03:41 +0000
commit2634cad695e41f4882a67a8cfa0e2579b870f1a3 (patch)
tree18dfebd24d0b63d0ad98cfb15cb105365ec01e03
parent89a89b3230717920410de6fb6d6b7152ef41a03e (diff)
parentd75a4ddab2cac69b7190c4a88adb8363c2950bdb (diff)
downloadgitlab-ce-2634cad695e41f4882a67a8cfa0e2579b870f1a3.tar.gz
Merge branch 'generate-spans-for-sections' into 'master'
Add collapsible sections to job log See merge request gitlab-org/gitlab-ce!28642
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue53
-rw-r--r--app/assets/stylesheets/pages/builds.scss4
-rw-r--r--changelogs/unreleased/generate-spans-for-sections.yml5
-rw-r--r--lib/gitlab/ci/ansi2html.rb87
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb2
-rw-r--r--spec/factories/ci/builds.rb20
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb46
-rw-r--r--spec/fixtures/trace/trace_with_duplicate_sections30
-rw-r--r--spec/javascripts/jobs/components/job_log_spec.js37
-rw-r--r--spec/javascripts/jobs/mock_data.js15
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb15
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb4
13 files changed, 323 insertions, 57 deletions
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
index 92e20e92d66..d611b370ab9 100644
--- a/app/assets/javascripts/jobs/components/job_log.vue
+++ b/app/assets/javascripts/jobs/components/job_log.vue
@@ -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>
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 6fc742871e7..6e98908eeed 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -124,6 +124,10 @@
float: left;
padding-left: $gl-padding-8;
}
+
+ .section-header ~ .section.line {
+ margin-left: $gl-padding;
+ }
}
.build-header {
diff --git a/changelogs/unreleased/generate-spans-for-sections.yml b/changelogs/unreleased/generate-spans-for-sections.yml
new file mode 100644
index 00000000000..e167d66490f
--- /dev/null
+++ b/changelogs/unreleased/generate-spans-for-sections.yml
@@ -0,0 +1,5 @@
+---
+title: Adds collapsible sections for job log
+merge_request: 28642
+author:
+type: added
diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb
index fba0de20ced..6109b45ffd2 100644
--- a/lib/gitlab/ci/ansi2html.rb
+++ b/lib/gitlab/ci/ansi2html.rb
@@ -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
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 490e9841492..0dabe27977a 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -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
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index a473136b57b..5f7c75a3a92 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -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(
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index 54b462da87a..fbe765d4c44 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -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) }
diff --git a/spec/fixtures/trace/trace_with_duplicate_sections b/spec/fixtures/trace/trace_with_duplicate_sections
new file mode 100644
index 00000000000..7a894e80e38
--- /dev/null
+++ b/spec/fixtures/trace/trace_with_duplicate_sections
@@ -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
+
diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js
index dc0f77ceb80..7e2ec2ec3f7 100644
--- a/spec/javascripts/jobs/components/job_log_spec.js
+++ b/spec/javascripts/jobs/components/job_log_spec.js
@@ -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);
+ });
+ });
});
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 3d40e94d219..c5022d3e93d 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -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,
+};
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index 5a5c071c639..ac4612dda92 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index e45ea1c2528..35250632e86 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -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
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index db935bcb388..f985b2dcbba 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -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