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 {
<template>
<div class="line">
<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
}}</span>
</div>
......
<script>
import Icon from '~/vue_shared/components/icon.vue';
import LineNumber from './line_number.vue';
import DurationBadge from './duration_badge.vue';
export default {
components: {
Icon,
LineNumber,
DurationBadge,
},
props: {
line: {
......@@ -20,6 +22,11 @@ export default {
type: String,
required: true,
},
duration: {
type: String,
required: false,
default: '',
},
},
computed: {
iconName() {
......@@ -35,11 +42,16 @@ export default {
</script>
<template>
<div class="line collapsible-line" role="button" @click="handleOnClick">
<icon :name="iconName" class="arrow" />
<div
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" />
<span v-for="(content, i) in line.content" :key="i" class="line-text" :class="content.style">{{
content.text
}}</span>
<duration-badge v-if="duration" :duration="duration" />
</div>
</template>
......@@ -46,7 +46,10 @@ export default {
};
</script>
<template>
<gl-link :id="lineNumberId" class="line-number" :href="buildLineNumber">{{
parsedLineNumber
}}</gl-link>
<gl-link
:id="lineNumberId"
class="d-inline-block text-right position-absolute line-number"
:href="buildLineNumber"
>{{ parsedLineNumber }}</gl-link
>
</template>
......@@ -9,7 +9,7 @@ export default {
LogLineHeader,
},
computed: {
...mapState(['traceEndpoint', 'trace']),
...mapState(['traceEndpoint', 'trace', 'isTraceComplete']),
},
methods: {
...mapActions(['toggleCollapsibleLine']),
......@@ -20,12 +20,13 @@ export default {
};
</script>
<template>
<code class="job-log">
<code class="job-log d-block">
<template v-for="(section, index) in trace">
<template v-if="section.isHeader">
<log-line-header
:key="`collapsible-${index}`"
:line="section.line"
:duration="section.section_duration"
:path="traceEndpoint"
:is-closed="section.isClosed"
@toggleLine="handleOnClickCollapsibleLine(section)"
......@@ -41,5 +42,11 @@ export default {
</template>
<log-line v-else :key="section.offset" :line="section" :path="traceEndpoint" />
</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>
</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
*
......@@ -5,6 +15,7 @@
* - creates a new array to hold the lines that are collpasible,
* - adds a isClosed property to handle toggle
* - adds a isHeader property to handle template logic
* - adds the section_duration
* For each line:
* - adds the index as lineNumber
*
......@@ -14,27 +25,21 @@
export const logLinesParser = (lines = [], lineNumberStart) =>
lines.reduce((acc, line, index) => {
const lineNumber = lineNumberStart ? lineNumberStart + index : index;
const last = acc[acc.length - 1];
if (line.section_header) {
acc.push({
isClosed: true,
isHeader: true,
line: {
...line,
lineNumber,
},
line: parseLine(line, lineNumber),
lines: [],
});
} else if (acc.length && acc[acc.length - 1].isHeader) {
acc[acc.length - 1].lines.push({
...line,
lineNumber,
});
} else {
acc.push({
...line,
lineNumber,
});
} else if (acc.length && last.isHeader && !line.section_duration && line.content.length) {
last.lines.push(parseLine(line, lineNumber));
} else if (acc.length && last.isHeader && line.section_duration) {
last.section_duration = line.section_duration;
} else if (line.content.length) {
acc.push(parseLine(line, lineNumber));
}
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;
*/
$builds-trace-bg: #111;
$job-log-highlight-height: 18px;
$job-log-line-padding: 62px;
$job-line-number-width: 40px;
$job-arrow-margin: 50px;
/*
* Commit Page
......
......@@ -28,6 +28,12 @@ describe('Jobs Store Utils', () => {
content: [{ text: 'Pulling docker image postgres:9.6.14 ...', style: 'term-fg-l-green' }],
sections: ['prepare-executor'],
},
{
offset: 1005,
content: [],
sections: ['prepare-executor'],
section_duration: '10:00',
},
];
let result;
......@@ -58,6 +64,16 @@ describe('Jobs Store Utils', () => {
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', () => {
......
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 LineHeader from '~/jobs/components/log/line_header.vue';
import LineNumber from '~/jobs/components/log/line_number.vue';
import DurationBadge from '~/jobs/components/log/duration_badge.vue';
describe('Job Log Header Line', () => {
let wrapper;
......@@ -81,4 +82,14 @@ describe('Job Log Header Line', () => {
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