Commit 9da9ed00 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents b15f88a6 2adc4ccf
<script>
import LineNumber from './line_number.vue';
export default {
components: {
LineNumber,
},
props: {
line: {
type: Object,
required: true,
},
path: {
type: String,
required: true,
},
},
};
</script>
<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">{{
content.text
}}</span>
</div>
</template>
<script>
import Icon from '~/vue_shared/components/icon.vue';
import LineNumber from './line_number.vue';
export default {
components: {
Icon,
LineNumber,
},
props: {
line: {
type: Object,
required: true,
},
isClosed: {
type: Boolean,
required: true,
},
path: {
type: String,
required: true,
},
},
computed: {
iconName() {
return this.isClosed ? 'angle-right' : 'angle-down';
},
},
methods: {
handleOnClick() {
this.$emit('toggleLine');
},
},
};
</script>
<template>
<div class="line collapsible-line" role="button" @click="handleOnClick">
<icon :name="iconName" class="arrow" />
<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>
</div>
</template>
<script>
import { GlLink } from '@gitlab/ui';
export default {
components: {
GlLink,
},
props: {
lineNumber: {
type: Number,
required: true,
},
path: {
type: String,
required: true,
},
},
computed: {
/**
* Builds the url for each line number
*
* @returns {String}
*/
buildLineNumber() {
return `${this.path}#${this.lineNumberId}`;
},
/**
* Array indexes start with 0, so we add 1
* to create the line number
*
* @returns {Number} the line number
*/
parsedLineNumber() {
return this.lineNumber + 1;
},
/**
* Creates the anchor for each link
*
* @returns {String}
*/
lineNumberId() {
return `L${this.parsedLineNumber}`;
},
},
};
</script>
<template>
<gl-link :id="lineNumberId" class="line-number" :href="buildLineNumber">{{
parsedLineNumber
}}</gl-link>
</template>
---
title: Creates base components for the new job log
merge_request:
author:
type: added
---
title: Persist `needs:` validation as config error
merge_request:
author:
type: fixed
......@@ -158,6 +158,7 @@ production: &base
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
# Please be aware that a placeholder is required for the Service Desk feature to work.
address: "gitlab-incoming+%{key}@gmail.com"
# Email account username
......
......@@ -5,7 +5,12 @@ module Gitlab
module Pipeline
module Chain
module Helpers
def error(message)
def error(message, config_error: false)
if config_error && command.save_incompleted
pipeline.yaml_errors = message
pipeline.drop!(:config_error)
end
pipeline.errors.add(:base, message)
end
end
......
......@@ -26,7 +26,7 @@ module Gitlab
# Gather all runtime build/stage errors
#
if seeds_errors = pipeline.stage_seeds.flat_map(&:errors).compact.presence
return error(seeds_errors.join("\n"))
return error(seeds_errors.join("\n"), config_error: true)
end
##
......
import { mount } from '@vue/test-utils';
import LineHeader from '~/jobs/components/log/line_header.vue';
import LineNumber from '~/jobs/components/log/line_number.vue';
describe('Job Log Header Line', () => {
let wrapper;
const data = {
line: {
content: [
{
text: 'Running with gitlab-runner 12.1.0 (de7731dd)',
style: 'term-fg-l-green',
},
],
lineNumber: 0,
},
isClosed: true,
path: '/jashkenas/underscore/-/jobs/335',
};
const createComponent = (props = {}) => {
wrapper = mount(LineHeader, {
sync: false,
propsData: {
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('line', () => {
beforeEach(() => {
createComponent(data);
});
it('renders the line number component', () => {
expect(wrapper.contains(LineNumber)).toBe(true);
});
it('renders a span the provided text', () => {
expect(wrapper.find('span').text()).toBe(data.line.content[0].text);
});
it('renders the provided style as a class attribute', () => {
expect(wrapper.find('span').classes()).toContain(data.line.content[0].style);
});
});
describe('when isCloses is true', () => {
beforeEach(() => {
createComponent({ ...data, isClosed: true });
});
it('sets icon name to be angle-right', () => {
expect(wrapper.vm.iconName).toEqual('angle-right');
});
});
describe('when isCloses is false', () => {
beforeEach(() => {
createComponent({ ...data, isClosed: false });
});
it('sets icon name to be angle-down', () => {
expect(wrapper.vm.iconName).toEqual('angle-down');
});
});
describe('on click', () => {
beforeEach(() => {
createComponent(data);
});
it('emits toggleLine event', () => {
wrapper.trigger('click');
expect(wrapper.emitted().toggleLine.length).toBe(1);
});
});
});
import { shallowMount } from '@vue/test-utils';
import LineNumber from '~/jobs/components/log/line_number.vue';
describe('Job Log Line Number', () => {
let wrapper;
const data = {
lineNumber: 0,
path: '/jashkenas/underscore/-/jobs/335',
};
const createComponent = (props = {}) => {
wrapper = shallowMount(LineNumber, {
sync: false,
propsData: {
...props,
},
});
};
beforeEach(() => {
createComponent(data);
});
afterEach(() => {
wrapper.destroy();
});
it('renders incremented lineNunber by 1', () => {
expect(wrapper.text()).toBe('1');
});
it('renders link with lineNumber as an ID', () => {
expect(wrapper.attributes().id).toBe('L1');
});
it('links to the provided path with line number as anchor', () => {
expect(wrapper.attributes().href).toBe(`${data.path}#L1`);
});
});
import { shallowMount } from '@vue/test-utils';
import Line from '~/jobs/components/log/line.vue';
import LineNumber from '~/jobs/components/log/line_number.vue';
describe('Job Log Line', () => {
let wrapper;
const data = {
line: {
content: [
{
text: 'Running with gitlab-runner 12.1.0 (de7731dd)',
style: 'term-fg-l-green',
},
],
lineNumber: 0,
},
path: '/jashkenas/underscore/-/jobs/335',
};
const createComponent = (props = {}) => {
wrapper = shallowMount(Line, {
sync: false,
propsData: {
...props,
},
});
};
beforeEach(() => {
createComponent(data);
});
afterEach(() => {
wrapper.destroy();
});
it('renders the line number component', () => {
expect(wrapper.contains(LineNumber)).toBe(true);
});
it('renders a span the provided text', () => {
expect(wrapper.find('span').text()).toBe(data.line.content[0].text);
});
it('renders the provided style as a class attribute', () => {
expect(wrapper.find('span').classes()).toContain(data.line.content[0].style);
});
});
......@@ -1140,10 +1140,26 @@ describe Ci::CreatePipelineService do
context 'when pipeline on feature is created' do
let(:ref_name) { 'refs/heads/feature' }
it 'does not create a pipeline as test_a depends on build_a' do
expect(pipeline).not_to be_persisted
expect(pipeline.builds).to be_empty
expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
context 'when save_on_errors is enabled' do
let(:pipeline) { execute_service(save_on_errors: true) }
it 'does create a pipeline as test_a depends on build_a' do
expect(pipeline).to be_persisted
expect(pipeline.builds).to be_empty
expect(pipeline.yaml_errors).to eq("test_a: needs 'build_a'")
expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
end
end
context 'when save_on_errors is disabled' do
let(:pipeline) { execute_service(save_on_errors: false) }
it 'does not create a pipeline as test_a depends on build_a' do
expect(pipeline).not_to be_persisted
expect(pipeline.builds).to be_empty
expect(pipeline.yaml_errors).to be_nil
expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
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