Commit 8c2713b4 authored by Filipa Lacerda's avatar Filipa Lacerda Committed by Kushal Pandya

Updates jog log to add duration

Renders a duration badge, updates the parser
Updates the CSS
parent 22a3bde5
<script>
export default {
props: {
duration: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="duration rounded align-self-start px-2 ml-2 flex-shrink-0">{{ duration }}</div>
</template>
...@@ -21,7 +21,7 @@ export default { ...@@ -21,7 +21,7 @@ export default {
<template> <template>
<div class="line"> <div class="line">
<line-number :line-number="line.lineNumber" :path="path" /> <line-number :line-number="line.lineNumber" :path="path" />
<span v-for="(content, i) in line.content" :key="i" class="line-text" :class="content.style">{{ <span v-for="(content, i) in line.content" :key="i" :class="content.style">{{
content.text content.text
}}</span> }}</span>
</div> </div>
......
<script> <script>
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import LineNumber from './line_number.vue'; import LineNumber from './line_number.vue';
import DurationBadge from './duration_badge.vue';
export default { export default {
components: { components: {
Icon, Icon,
LineNumber, LineNumber,
DurationBadge,
}, },
props: { props: {
line: { line: {
...@@ -20,6 +22,11 @@ export default { ...@@ -20,6 +22,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
duration: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
iconName() { iconName() {
...@@ -35,11 +42,16 @@ export default { ...@@ -35,11 +42,16 @@ export default {
</script> </script>
<template> <template>
<div class="line collapsible-line" role="button" @click="handleOnClick"> <div
<icon :name="iconName" class="arrow" /> class="line collapsible-line d-flex justify-content-between"
role="button"
@click="handleOnClick"
>
<icon :name="iconName" class="arrow position-absolute" />
<line-number :line-number="line.lineNumber" :path="path" /> <line-number :line-number="line.lineNumber" :path="path" />
<span v-for="(content, i) in line.content" :key="i" class="line-text" :class="content.style">{{ <span v-for="(content, i) in line.content" :key="i" class="line-text" :class="content.style">{{
content.text content.text
}}</span> }}</span>
<duration-badge v-if="duration" :duration="duration" />
</div> </div>
</template> </template>
...@@ -46,7 +46,10 @@ export default { ...@@ -46,7 +46,10 @@ export default {
}; };
</script> </script>
<template> <template>
<gl-link :id="lineNumberId" class="line-number" :href="buildLineNumber">{{ <gl-link
parsedLineNumber :id="lineNumberId"
}}</gl-link> class="d-inline-block text-right position-absolute line-number"
:href="buildLineNumber"
>{{ parsedLineNumber }}</gl-link
>
</template> </template>
...@@ -9,7 +9,7 @@ export default { ...@@ -9,7 +9,7 @@ export default {
LogLineHeader, LogLineHeader,
}, },
computed: { computed: {
...mapState(['traceEndpoint', 'trace']), ...mapState(['traceEndpoint', 'trace', 'isTraceComplete']),
}, },
methods: { methods: {
...mapActions(['toggleCollapsibleLine']), ...mapActions(['toggleCollapsibleLine']),
...@@ -20,12 +20,13 @@ export default { ...@@ -20,12 +20,13 @@ export default {
}; };
</script> </script>
<template> <template>
<code class="job-log"> <code class="job-log d-block">
<template v-for="(section, index) in trace"> <template v-for="(section, index) in trace">
<template v-if="section.isHeader"> <template v-if="section.isHeader">
<log-line-header <log-line-header
:key="`collapsible-${index}`" :key="`collapsible-${index}`"
:line="section.line" :line="section.line"
:duration="section.section_duration"
:path="traceEndpoint" :path="traceEndpoint"
:is-closed="section.isClosed" :is-closed="section.isClosed"
@toggleLine="handleOnClickCollapsibleLine(section)" @toggleLine="handleOnClickCollapsibleLine(section)"
...@@ -41,5 +42,11 @@ export default { ...@@ -41,5 +42,11 @@ export default {
</template> </template>
<log-line v-else :key="section.offset" :line="section" :path="traceEndpoint" /> <log-line v-else :key="section.offset" :line="section" :path="traceEndpoint" />
</template> </template>
<div v-if="!isTraceComplete" class="js-log-animation loader-animation pt-3 pl-3">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</code> </code>
</template> </template>
/**
* Adds the line number property
* @param Object line
* @param Number lineNumber
*/
export const parseLine = (line = {}, lineNumber) => ({
...line,
lineNumber,
});
/** /**
* Parses the job log content into a structure usable by the template * Parses the job log content into a structure usable by the template
* *
...@@ -5,6 +15,7 @@ ...@@ -5,6 +15,7 @@
* - creates a new array to hold the lines that are collpasible, * - creates a new array to hold the lines that are collpasible,
* - adds a isClosed property to handle toggle * - adds a isClosed property to handle toggle
* - adds a isHeader property to handle template logic * - adds a isHeader property to handle template logic
* - adds the section_duration
* For each line: * For each line:
* - adds the index as lineNumber * - adds the index as lineNumber
* *
...@@ -14,27 +25,21 @@ ...@@ -14,27 +25,21 @@
export const logLinesParser = (lines = [], lineNumberStart) => export const logLinesParser = (lines = [], lineNumberStart) =>
lines.reduce((acc, line, index) => { lines.reduce((acc, line, index) => {
const lineNumber = lineNumberStart ? lineNumberStart + index : index; const lineNumber = lineNumberStart ? lineNumberStart + index : index;
const last = acc[acc.length - 1];
if (line.section_header) { if (line.section_header) {
acc.push({ acc.push({
isClosed: true, isClosed: true,
isHeader: true, isHeader: true,
line: { line: parseLine(line, lineNumber),
...line,
lineNumber,
},
lines: [], lines: [],
}); });
} else if (acc.length && acc[acc.length - 1].isHeader) { } else if (acc.length && last.isHeader && !line.section_duration && line.content.length) {
acc[acc.length - 1].lines.push({ last.lines.push(parseLine(line, lineNumber));
...line, } else if (acc.length && last.isHeader && line.section_duration) {
lineNumber, last.section_duration = line.section_duration;
}); } else if (line.content.length) {
} else { acc.push(parseLine(line, lineNumber));
acc.push({
...line,
lineNumber,
});
} }
return acc; return acc;
......
.job-log {
font-family: $monospace-font;
padding: $gl-padding-8 $input-horizontal-padding;
margin: 0 0 $gl-padding-8;
font-size: 13px;
word-break: break-all;
word-wrap: break-word;
color: $gl-text-color-inverted;
border-radius: $border-radius-small;
min-height: 42px;
background-color: $builds-trace-bg;
}
.line {
padding: 1px $gl-padding 1px $job-log-line-padding;
}
.line-number {
color: $gl-text-color-inverted;
padding: 0 $gl-padding-8;
min-width: $job-line-number-width;
margin-left: -$job-line-number-width;
padding-right: 1em;
&:hover,
&:active,
&:visited {
text-decoration: underline;
color: $gl-text-color-inverted;
}
}
.collapsible-line {
&:hover {
background-color: rgba($white-light, 0.2);
}
.arrow {
margin-left: -$job-arrow-margin;
}
}
.duration {
background: $gl-gray-400;
}
.loader-animation {
@include build-loader-animation;
}
...@@ -606,6 +606,9 @@ $blame-blue: #254e77; ...@@ -606,6 +606,9 @@ $blame-blue: #254e77;
*/ */
$builds-trace-bg: #111; $builds-trace-bg: #111;
$job-log-highlight-height: 18px; $job-log-highlight-height: 18px;
$job-log-line-padding: 62px;
$job-line-number-width: 40px;
$job-arrow-margin: 50px;
/* /*
* Commit Page * Commit Page
......
...@@ -28,6 +28,12 @@ describe('Jobs Store Utils', () => { ...@@ -28,6 +28,12 @@ describe('Jobs Store Utils', () => {
content: [{ text: 'Pulling docker image postgres:9.6.14 ...', style: 'term-fg-l-green' }], content: [{ text: 'Pulling docker image postgres:9.6.14 ...', style: 'term-fg-l-green' }],
sections: ['prepare-executor'], sections: ['prepare-executor'],
}, },
{
offset: 1005,
content: [],
sections: ['prepare-executor'],
section_duration: '10:00',
},
]; ];
let result; let result;
...@@ -58,6 +64,16 @@ describe('Jobs Store Utils', () => { ...@@ -58,6 +64,16 @@ describe('Jobs Store Utils', () => {
expect(result[1].lines[1].content).toEqual(mockData[3].content); expect(result[1].lines[1].content).toEqual(mockData[3].content);
}); });
}); });
describe('section duration', () => {
it('adds the section information to the header section', () => {
expect(result[1].section_duration).toEqual(mockData[4].section_duration);
});
it('does not add section duration as a line', () => {
expect(result[1].lines.includes(mockData[4])).toEqual(false);
});
});
}); });
describe('updateIncrementalTrace', () => { describe('updateIncrementalTrace', () => {
......
import { shallowMount } from '@vue/test-utils';
import DurationBadge from '~/jobs/components/log/duration_badge.vue';
describe('Job Log Duration Badge', () => {
let wrapper;
const data = {
duration: '00:30:01',
};
const createComponent = (props = {}) => {
wrapper = shallowMount(DurationBadge, {
sync: false,
propsData: {
...props,
},
});
};
beforeEach(() => {
createComponent(data);
});
afterEach(() => {
wrapper.destroy();
});
it('renders provided duration', () => {
expect(wrapper.text()).toBe(data.duration);
});
});
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import LineHeader from '~/jobs/components/log/line_header.vue'; import LineHeader from '~/jobs/components/log/line_header.vue';
import LineNumber from '~/jobs/components/log/line_number.vue'; import LineNumber from '~/jobs/components/log/line_number.vue';
import DurationBadge from '~/jobs/components/log/duration_badge.vue';
describe('Job Log Header Line', () => { describe('Job Log Header Line', () => {
let wrapper; let wrapper;
...@@ -81,4 +82,14 @@ describe('Job Log Header Line', () => { ...@@ -81,4 +82,14 @@ describe('Job Log Header Line', () => {
expect(wrapper.emitted().toggleLine.length).toBe(1); expect(wrapper.emitted().toggleLine.length).toBe(1);
}); });
}); });
describe('with duration', () => {
beforeEach(() => {
createComponent(Object.assign({}, data, { duration: '00:10' }));
});
it('renders the duration badge', () => {
expect(wrapper.contains(DurationBadge)).toBe(true);
});
});
}); });
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