Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
687bfbe932
commit
f6e2d0776a
26 changed files with 828 additions and 488 deletions
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"ee/*": { "type": "ee" },
|
"ee/*": { "type": "ee" },
|
||||||
|
"app/*": { "type": "ce" },
|
||||||
|
"lib/*": { "type": "ce" },
|
||||||
"config/initializers/*.rb": {
|
"config/initializers/*.rb": {
|
||||||
"alternate": "spec/initializers/{}_spec.rb",
|
"alternate": "spec/initializers/{}_spec.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
|
@ -57,37 +59,40 @@
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"app/presenters/*.rb": {
|
"app/presenters/*.rb": {
|
||||||
|
"alternate": "spec/app/presenters/{}_spec.rb",
|
||||||
"related": "ee/app/presenters/ee/{}.rb",
|
"related": "ee/app/presenters/ee/{}.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"app/serializers/*.rb": {
|
"app/serializers/*.rb": {
|
||||||
|
"alternate": "spec/app/serializers/{}_spec.rb",
|
||||||
"related": "ee/app/serializers/ee/{}.rb",
|
"related": "ee/app/serializers/ee/{}.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"app/services/*.rb": {
|
"app/services/*.rb": {
|
||||||
|
"alternate": "spec/app/services/{}_spec.rb",
|
||||||
"related": "ee/app/services/ee/{}.rb",
|
"related": "ee/app/services/ee/{}.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"app/uploaders/*.rb": {
|
"app/uploaders/*.rb": {
|
||||||
|
"alternate": "spec/app/uploaders/{}_spec.rb",
|
||||||
"related": "ee/app/uploaders/ee/{}.rb",
|
"related": "ee/app/uploaders/ee/{}.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"app/validators/*.rb": {
|
"app/validators/*.rb": {
|
||||||
|
"alternate": "spec/app/validators/{}_spec.rb",
|
||||||
"related": "ee/app/validators/ee/{}.rb",
|
"related": "ee/app/validators/ee/{}.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"app/views/*.rb": {
|
"app/views/*.rb": {
|
||||||
|
"alternate": "spec/app/views/{}_spec.rb",
|
||||||
"related": "ee/app/views/ee/{}.rb",
|
"related": "ee/app/views/ee/{}.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"app/workers/*.rb": {
|
"app/workers/*.rb": {
|
||||||
|
"alternate": "spec/app/workers/{}_spec.rb",
|
||||||
"related": "ee/app/workers/ee/{}.rb",
|
"related": "ee/app/workers/ee/{}.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"app/*.rb": {
|
|
||||||
"alternate": "spec/{}_spec.rb",
|
|
||||||
"type": "source"
|
|
||||||
},
|
|
||||||
"spec/*_spec.rb": {
|
"spec/*_spec.rb": {
|
||||||
"alternate": "app/{}.rb",
|
"alternate": "app/{}.rb",
|
||||||
"type": "test"
|
"type": "test"
|
||||||
|
@ -124,8 +129,79 @@
|
||||||
"alternate": "ee/lib/api/{}.rb",
|
"alternate": "ee/lib/api/{}.rb",
|
||||||
"type": "test"
|
"type": "test"
|
||||||
},
|
},
|
||||||
|
"ee/app/controllers/ee/*.rb": {
|
||||||
|
"alternate": "ee/spec/{}_spec.rb",
|
||||||
|
"related": "app/controllers/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/finders/ee/*.rb": {
|
||||||
|
"alternate": "ee/spec/{}_spec.rb",
|
||||||
|
"related": "app/finders/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/graphql/ee/*.rb": {
|
||||||
|
"alternate": "ee/spec/{}_spec.rb",
|
||||||
|
"related": "app/graphql/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/helpers/ee/*.rb": {
|
||||||
|
"alternate": "ee/spec/{}_spec.rb",
|
||||||
|
"related": "app/helpers/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/mailers/ee/*.rb": {
|
||||||
|
"alternate": "ee/spec/{}_spec.rb",
|
||||||
|
"related": "app/mailers/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/models/ee/*.rb": {
|
||||||
|
"alternate": "ee/spec/{}_spec.rb",
|
||||||
|
"related": "app/models/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/policies/ee/*.rb": {
|
||||||
|
"alternate": "ee/spec/{}_spec.rb",
|
||||||
|
"related": "app/policies/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/presenters/ee/*.rb": {
|
||||||
|
"alternate": "ee/spec/{}_spec.rb",
|
||||||
|
"related": "app/presenters/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/serializers/ee/*.rb": {
|
||||||
|
"alternate": "spec/app/serializers/{}_spec.rb",
|
||||||
|
"related": "app/serializers/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/services/ee/*.rb": {
|
||||||
|
"alternate": "spec/app/services/{}_spec.rb",
|
||||||
|
"related": "app/services/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/uploaders/ee/*.rb": {
|
||||||
|
"alternate": "spec/app/uploaders/{}_spec.rb",
|
||||||
|
"related": "app/uploaders/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/validators/ee/*.rb": {
|
||||||
|
"alternate": "spec/app/validators/{}_spec.rb",
|
||||||
|
"related": "app/validators/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/views/ee/*.rb": {
|
||||||
|
"alternate": "spec/app/views/{}_spec.rb",
|
||||||
|
"related": "app/views/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
|
"ee/app/workers/ee/*.rb": {
|
||||||
|
"alternate": "spec/app/workers/{}_spec.rb",
|
||||||
|
"related": "app/workers/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
"ee/app/*.rb": {
|
"ee/app/*.rb": {
|
||||||
"alternate": "ee/spec/{}_spec.rb",
|
"alternate": "ee/spec/{}_spec.rb",
|
||||||
|
"related": "app/{}.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"ee/spec/*_spec.rb": {
|
"ee/spec/*_spec.rb": {
|
||||||
|
@ -136,6 +212,11 @@
|
||||||
"alternate": "ee/spec/lib/{}_spec.rb",
|
"alternate": "ee/spec/lib/{}_spec.rb",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
|
"ee/lib/ee/*.rb": {
|
||||||
|
"alternate": "ee/spec/lib/{}_spec.rb",
|
||||||
|
"related": "lib/{}.rb",
|
||||||
|
"type": "source"
|
||||||
|
},
|
||||||
"ee/spec/lib/*_spec.rb": {
|
"ee/spec/lib/*_spec.rb": {
|
||||||
"alternate": "ee/lib/{}.rb",
|
"alternate": "ee/lib/{}.rb",
|
||||||
"type": "test"
|
"type": "test"
|
||||||
|
@ -154,16 +235,18 @@
|
||||||
},
|
},
|
||||||
"ee/app/assets/javascripts/*.js": {
|
"ee/app/assets/javascripts/*.js": {
|
||||||
"alternate": "ee/spec/frontend/{}_spec.js",
|
"alternate": "ee/spec/frontend/{}_spec.js",
|
||||||
|
"related": "app/assets/javascripts/{}.js",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"ee/app/assets/javascripts/*.vue": {
|
"ee/app/assets/javascripts/*.vue": {
|
||||||
"alternate": "ee/spec/frontend/{}_spec.js",
|
"alternate": "ee/spec/frontend/{}_spec.js",
|
||||||
|
"related": "app/assets/javascripts/{}.vue",
|
||||||
"type": "source"
|
"type": "source"
|
||||||
},
|
},
|
||||||
"ee/spec/frontend/*_spec.js": {
|
"ee/spec/frontend/*_spec.js": {
|
||||||
"alternate": ["ee/app/assets/javascripts/{}.vue", "ee/app/assets/javascripts/{}.js"],
|
"alternate": ["ee/app/assets/javascripts/{}.vue", "ee/app/assets/javascripts/{}.js"],
|
||||||
"type": "test"
|
"type": "test"
|
||||||
},
|
},
|
||||||
"*.rb": {"dispatch": "bundle exec rubocop {file}"},
|
"*.rb": { "dispatch": "bundle exec rubocop {file}" },
|
||||||
"*_spec.rb": {"dispatch": "bundle exec rspec {file}"}
|
"*_spec.rb": { "dispatch": "bundle exec rspec {file}" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
isReference() {
|
isReference() {
|
||||||
return this.nodeType === 'reference';
|
return this.nodeType.startsWith('reference');
|
||||||
},
|
},
|
||||||
|
|
||||||
isCommand() {
|
isCommand() {
|
||||||
|
@ -96,7 +96,7 @@ export default {
|
||||||
getText(item) {
|
getText(item) {
|
||||||
if (this.isEmoji) return item.e;
|
if (this.isEmoji) return item.e;
|
||||||
|
|
||||||
switch (this.nodeType === 'reference' && this.nodeProps.referenceType) {
|
switch (this.isReference && this.nodeProps.referenceType) {
|
||||||
case 'user':
|
case 'user':
|
||||||
return `${this.char}${item.username}`;
|
return `${this.char}${item.username}`;
|
||||||
case 'issue':
|
case 'issue':
|
||||||
|
@ -105,12 +105,13 @@ export default {
|
||||||
case 'snippet':
|
case 'snippet':
|
||||||
return `${this.char}${item.id}`;
|
return `${this.char}${item.id}`;
|
||||||
case 'milestone':
|
case 'milestone':
|
||||||
case 'label':
|
|
||||||
return `${this.char}${item.title}`;
|
return `${this.char}${item.title}`;
|
||||||
|
case 'label':
|
||||||
|
return item.title;
|
||||||
case 'command':
|
case 'command':
|
||||||
return `${this.char}${item.name} `;
|
return `${this.char}${item.name}`;
|
||||||
case 'epic':
|
case 'epic':
|
||||||
return `${item.reference}`;
|
return item.reference;
|
||||||
case 'vulnerability':
|
case 'vulnerability':
|
||||||
return `[vulnerability:${item.id}]`;
|
return `[vulnerability:${item.id}]`;
|
||||||
default:
|
default:
|
||||||
|
@ -119,17 +120,35 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
getProps(item) {
|
getProps(item) {
|
||||||
|
const props = {};
|
||||||
|
|
||||||
if (this.isEmoji) {
|
if (this.isEmoji) {
|
||||||
return {
|
Object.assign(props, {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
unicodeVersion: item.u,
|
unicodeVersion: item.u,
|
||||||
title: item.d,
|
title: item.d,
|
||||||
moji: item.e,
|
moji: item.e,
|
||||||
...this.nodeProps,
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.nodeProps;
|
if (this.isLabel || this.isMilestone) {
|
||||||
|
Object.assign(props, {
|
||||||
|
originalText: `${this.char}${
|
||||||
|
/\W/.test(item.title) ? JSON.stringify(item.title) : item.title
|
||||||
|
}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isLabel) {
|
||||||
|
Object.assign(props, {
|
||||||
|
text: item.title,
|
||||||
|
color: item.color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(props, this.nodeProps);
|
||||||
|
|
||||||
|
return props;
|
||||||
},
|
},
|
||||||
|
|
||||||
onKeyDown({ event }) {
|
onKeyDown({ event }) {
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<script>
|
||||||
|
import { NodeViewWrapper } from '@tiptap/vue-2';
|
||||||
|
import { GlLabel } from '@gitlab/ui';
|
||||||
|
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DetailsWrapper',
|
||||||
|
components: {
|
||||||
|
NodeViewWrapper,
|
||||||
|
GlLabel,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
node: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isScopedLabel() {
|
||||||
|
return isScopedLabel({ title: this.node.attrs.originalText });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<node-view-wrapper class="gl-display-inline-block">
|
||||||
|
<gl-label
|
||||||
|
size="sm"
|
||||||
|
:scoped="isScopedLabel"
|
||||||
|
:background-color="node.attrs.color"
|
||||||
|
:title="node.attrs.text"
|
||||||
|
/>
|
||||||
|
</node-view-wrapper>
|
||||||
|
</template>
|
|
@ -46,22 +46,10 @@ export default Node.create({
|
||||||
tag: 'a.gfm:not([data-link=true])',
|
tag: 'a.gfm:not([data-link=true])',
|
||||||
priority: PARSE_HTML_PRIORITY_HIGHEST,
|
priority: PARSE_HTML_PRIORITY_HIGHEST,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
tag: 'span.gl-label',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHTML({ node }) {
|
renderHTML({ node }) {
|
||||||
return [
|
return ['a', { href: '#' }, node.attrs.text];
|
||||||
'a',
|
|
||||||
{
|
|
||||||
class: node.attrs.className,
|
|
||||||
href: '#',
|
|
||||||
'data-reference-type': node.attrs.referenceType,
|
|
||||||
'data-original': node.attrs.originalText,
|
|
||||||
},
|
|
||||||
node.attrs.text,
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { VueNodeViewRenderer } from '@tiptap/vue-2';
|
||||||
|
import { SCOPED_LABEL_DELIMITER } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
|
||||||
|
import LabelWrapper from '../components/wrappers/label.vue';
|
||||||
|
import Reference from './reference';
|
||||||
|
|
||||||
|
export default Reference.extend({
|
||||||
|
name: 'reference_label',
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
...this.parent(),
|
||||||
|
text: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => {
|
||||||
|
const text = element.querySelector('.gl-label-text').textContent;
|
||||||
|
const scopedText = element.querySelector('.gl-label-text-scoped')?.textContent;
|
||||||
|
if (!scopedText) return text;
|
||||||
|
return `${text}${SCOPED_LABEL_DELIMITER}${scopedText}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => element.querySelector('.gl-label-text').style.backgroundColor,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [{ tag: 'span.gl-label' }];
|
||||||
|
},
|
||||||
|
|
||||||
|
addNodeView() {
|
||||||
|
return new VueNodeViewRenderer(LabelWrapper);
|
||||||
|
},
|
||||||
|
});
|
|
@ -34,7 +34,10 @@ function createSuggestionPlugin({
|
||||||
tiptapEditor
|
tiptapEditor
|
||||||
.chain()
|
.chain()
|
||||||
.focus()
|
.focus()
|
||||||
.insertContentAt(range, [{ type: nodeType, attrs: props }])
|
.insertContentAt(range, [
|
||||||
|
{ type: nodeType, attrs: props },
|
||||||
|
{ type: 'text', text: ' ' },
|
||||||
|
])
|
||||||
.run();
|
.run();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -82,7 +85,7 @@ function createSuggestionPlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpdate(props) {
|
onUpdate(props) {
|
||||||
component.updateProps(props);
|
component?.updateProps(props);
|
||||||
|
|
||||||
if (!props.clientRect) {
|
if (!props.clientRect) {
|
||||||
return;
|
return;
|
||||||
|
@ -100,12 +103,12 @@ function createSuggestionPlugin({
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return component.ref?.onKeyDown(props);
|
return component?.ref?.onKeyDown(props);
|
||||||
},
|
},
|
||||||
|
|
||||||
onExit() {
|
onExit() {
|
||||||
popup?.[0].destroy();
|
popup?.[0].destroy();
|
||||||
component.destroy();
|
component?.destroy();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -151,7 +154,7 @@ export default Node.create({
|
||||||
editor: this.editor,
|
editor: this.editor,
|
||||||
char: '~',
|
char: '~',
|
||||||
dataSource: gl.GfmAutoComplete?.dataSources.labels,
|
dataSource: gl.GfmAutoComplete?.dataSources.labels,
|
||||||
nodeType: 'reference',
|
nodeType: 'reference_label',
|
||||||
nodeProps: {
|
nodeProps: {
|
||||||
referenceType: 'label',
|
referenceType: 'label',
|
||||||
},
|
},
|
||||||
|
|
|
@ -43,6 +43,7 @@ import OrderedList from '../extensions/ordered_list';
|
||||||
import Paragraph from '../extensions/paragraph';
|
import Paragraph from '../extensions/paragraph';
|
||||||
import PasteMarkdown from '../extensions/paste_markdown';
|
import PasteMarkdown from '../extensions/paste_markdown';
|
||||||
import Reference from '../extensions/reference';
|
import Reference from '../extensions/reference';
|
||||||
|
import ReferenceLabel from '../extensions/reference_label';
|
||||||
import ReferenceDefinition from '../extensions/reference_definition';
|
import ReferenceDefinition from '../extensions/reference_definition';
|
||||||
import Sourcemap from '../extensions/sourcemap';
|
import Sourcemap from '../extensions/sourcemap';
|
||||||
import Strike from '../extensions/strike';
|
import Strike from '../extensions/strike';
|
||||||
|
@ -132,6 +133,7 @@ export const createContentEditor = ({
|
||||||
Paragraph,
|
Paragraph,
|
||||||
PasteMarkdown.configure({ eventHub, renderMarkdown }),
|
PasteMarkdown.configure({ eventHub, renderMarkdown }),
|
||||||
Reference,
|
Reference,
|
||||||
|
ReferenceLabel,
|
||||||
ReferenceDefinition,
|
ReferenceDefinition,
|
||||||
Sourcemap,
|
Sourcemap,
|
||||||
Strike,
|
Strike,
|
||||||
|
|
|
@ -33,6 +33,7 @@ import MathInline from '../extensions/math_inline';
|
||||||
import OrderedList from '../extensions/ordered_list';
|
import OrderedList from '../extensions/ordered_list';
|
||||||
import Paragraph from '../extensions/paragraph';
|
import Paragraph from '../extensions/paragraph';
|
||||||
import Reference from '../extensions/reference';
|
import Reference from '../extensions/reference';
|
||||||
|
import ReferenceLabel from '../extensions/reference_label';
|
||||||
import ReferenceDefinition from '../extensions/reference_definition';
|
import ReferenceDefinition from '../extensions/reference_definition';
|
||||||
import Strike from '../extensions/strike';
|
import Strike from '../extensions/strike';
|
||||||
import Subscript from '../extensions/subscript';
|
import Subscript from '../extensions/subscript';
|
||||||
|
@ -61,6 +62,7 @@ import {
|
||||||
renderHTMLNode,
|
renderHTMLNode,
|
||||||
renderContent,
|
renderContent,
|
||||||
renderBulletList,
|
renderBulletList,
|
||||||
|
renderReference,
|
||||||
preserveUnchanged,
|
preserveUnchanged,
|
||||||
bold,
|
bold,
|
||||||
italic,
|
italic,
|
||||||
|
@ -184,9 +186,8 @@ const defaultSerializerConfig = {
|
||||||
[ListItem.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.list_item),
|
[ListItem.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.list_item),
|
||||||
[OrderedList.name]: preserveUnchanged(renderOrderedList),
|
[OrderedList.name]: preserveUnchanged(renderOrderedList),
|
||||||
[Paragraph.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.paragraph),
|
[Paragraph.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.paragraph),
|
||||||
[Reference.name]: (state, node) => {
|
[Reference.name]: renderReference,
|
||||||
state.write(node.attrs.originalText || node.attrs.text);
|
[ReferenceLabel.name]: renderReference,
|
||||||
},
|
|
||||||
[ReferenceDefinition.name]: preserveUnchanged({
|
[ReferenceDefinition.name]: preserveUnchanged({
|
||||||
render: (state, node, parent, index, same, sourceMarkdown) => {
|
render: (state, node, parent, index, same, sourceMarkdown) => {
|
||||||
const nextSibling = parent.maybeChild(index + 1);
|
const nextSibling = parent.maybeChild(index + 1);
|
||||||
|
|
|
@ -423,6 +423,10 @@ export function renderOrderedList(state, node) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function renderReference(state, node) {
|
||||||
|
state.write(node.attrs.originalText || node.attrs.text);
|
||||||
|
}
|
||||||
|
|
||||||
const generateBoldTags = (wrapTagName = openTag) => {
|
const generateBoldTags = (wrapTagName = openTag) => {
|
||||||
return (_, mark) => {
|
return (_, mark) => {
|
||||||
const type = /^(\*\*|__|<strong|<b).*/.exec(mark.attrs.sourceMarkdown)?.[1];
|
const type = /^(\*\*|__|<strong|<b).*/.exec(mark.attrs.sourceMarkdown)?.[1];
|
||||||
|
|
|
@ -866,3 +866,36 @@ To enable debug output in the rails console, [enter the rails console](#rails-co
|
||||||
```ruby
|
```ruby
|
||||||
Rails.logger.level = Logger::DEBUG
|
Rails.logger.level = Logger::DEBUG
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Get all error messages associated with groups, subgroups, members, and requesters
|
||||||
|
|
||||||
|
Collect error messages associated with groups, subgroups, members, and requesters. This
|
||||||
|
captures error messages that may not appear in the Web interface. This can be especially helpful
|
||||||
|
for troubleshooting issues with [LDAP group sync](ldap_synchronization.md#group-sync)
|
||||||
|
and unexpected behavior with users and their membership in groups and subgroups.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Find the group and subgroup
|
||||||
|
group = Group.find_by_full_path("parent_group")
|
||||||
|
subgroup = Group.find_by_full_path("parent_group/child_group")
|
||||||
|
|
||||||
|
# Group and subgroup errors
|
||||||
|
group.valid?
|
||||||
|
group.errors.map(&:full_messages)
|
||||||
|
|
||||||
|
subgroup.valid?
|
||||||
|
subgroup.errors.map(&:full_messages)
|
||||||
|
|
||||||
|
# Group and subgroup errors for the members AND requesters
|
||||||
|
group.requesters.map(&:valid?)
|
||||||
|
group.requesters.map(&:errors).map(&:full_messages)
|
||||||
|
group.members.map(&:valid?)
|
||||||
|
group.members.map(&:errors).map(&:full_messages)
|
||||||
|
group.members_and_requesters.map(&:errors).map(&:full_messages)
|
||||||
|
|
||||||
|
subgroup.requesters.map(&:valid?)
|
||||||
|
subgroup.requesters.map(&:errors).map(&:full_messages)
|
||||||
|
subgroup.members.map(&:valid?)
|
||||||
|
subgroup.members.map(&:errors).map(&:full_messages)
|
||||||
|
subgroup.members_and_requesters.map(&:errors).map(&:full_messages)
|
||||||
|
```
|
||||||
|
|
|
@ -265,135 +265,6 @@ group = Group.find_by_full_path 'group'
|
||||||
user.max_member_access_for_group group.id
|
user.max_member_access_for_group group.id
|
||||||
```
|
```
|
||||||
|
|
||||||
## Groups
|
|
||||||
|
|
||||||
### Transfer group to another location
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
user = User.find_by_username('<username>')
|
|
||||||
group = Group.find_by_name("<group_name>")
|
|
||||||
parent_group = Group.find_by(id: "<group_id>")
|
|
||||||
service = ::Groups::TransferService.new(group, user)
|
|
||||||
service.execute(parent_group)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Count unique users in a group and subgroups
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
group = Group.find_by_path_or_name("groupname")
|
|
||||||
members = []
|
|
||||||
for member in group.members_with_descendants
|
|
||||||
members.push(member.user_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
members.uniq.length
|
|
||||||
```
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
group = Group.find_by_path_or_name("groupname")
|
|
||||||
|
|
||||||
# Count users from subgroup and up (inherited)
|
|
||||||
group.members_with_parents.count
|
|
||||||
|
|
||||||
# Count users from the parent group and down (specific grants)
|
|
||||||
parent.members_with_descendants.count
|
|
||||||
```
|
|
||||||
|
|
||||||
### Find groups that are pending deletion
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
#
|
|
||||||
# This section lists all the groups which are pending deletion
|
|
||||||
#
|
|
||||||
Group.all.each do |g|
|
|
||||||
if g.marked_for_deletion?
|
|
||||||
puts "Group ID: #{g.id}"
|
|
||||||
puts "Group name: #{g.name}"
|
|
||||||
puts "Group path: #{g.full_path}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Delete a group
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
GroupDestroyWorker.perform_async(group_id, user_id)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Modify group project creation
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
# Project creation levels: 0 - No one, 1 - Maintainers, 2 - Developers + Maintainers
|
|
||||||
group = Group.find_by_path_or_name('group-name')
|
|
||||||
group.project_creation_level=0
|
|
||||||
```
|
|
||||||
|
|
||||||
### Modify group - disable 2FA requirement
|
|
||||||
|
|
||||||
WARNING:
|
|
||||||
When disabling the 2FA Requirement on a subgroup, the whole parent group (including all subgroups) is affected by this change.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
group = Group.find_by_path_or_name('group-name')
|
|
||||||
group.require_two_factor_authentication=false
|
|
||||||
group.save
|
|
||||||
```
|
|
||||||
|
|
||||||
### Check and toggle a feature for all projects in a group
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
projects = Group.find_by_name('_group_name').projects
|
|
||||||
projects.each do |p|
|
|
||||||
state = p.<feature-name>?
|
|
||||||
|
|
||||||
if state
|
|
||||||
puts "#{p.name} has <feature-name> already enabled. Skipping..."
|
|
||||||
else
|
|
||||||
puts "#{p.name} didn't have <feature-name> enabled. Enabling..."
|
|
||||||
p.project_feature.update!(builds_access_level: ProjectFeature::PRIVATE)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
To find features that can be toggled, run `pp p.project_feature`.
|
|
||||||
Available permission levels are listed in
|
|
||||||
[concerns/featurable.rb](https://gitlab.com/gitlab-org/gitlab/blob/master/app/models/concerns/featurable.rb).
|
|
||||||
|
|
||||||
### Get all error messages associated with groups, subgroups, members, and requesters
|
|
||||||
|
|
||||||
Collect error messages associated with groups, subgroups, members, and requesters. This
|
|
||||||
captures error messages that may not appear in the Web interface. This can be especially helpful
|
|
||||||
for troubleshooting issues with [LDAP group sync](../auth/ldap/ldap_synchronization.md#group-sync)
|
|
||||||
and unexpected behavior with users and their membership in groups and subgroups.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
# Find the group and subgroup
|
|
||||||
group = Group.find_by_full_path("parent_group")
|
|
||||||
subgroup = Group.find_by_full_path("parent_group/child_group")
|
|
||||||
|
|
||||||
# Group and subgroup errors
|
|
||||||
group.valid?
|
|
||||||
group.errors.map(&:full_messages)
|
|
||||||
|
|
||||||
subgroup.valid?
|
|
||||||
subgroup.errors.map(&:full_messages)
|
|
||||||
|
|
||||||
# Group and subgroup errors for the members AND requesters
|
|
||||||
group.requesters.map(&:valid?)
|
|
||||||
group.requesters.map(&:errors).map(&:full_messages)
|
|
||||||
group.members.map(&:valid?)
|
|
||||||
group.members.map(&:errors).map(&:full_messages)
|
|
||||||
group.members_and_requesters.map(&:errors).map(&:full_messages)
|
|
||||||
|
|
||||||
subgroup.requesters.map(&:valid?)
|
|
||||||
subgroup.requesters.map(&:errors).map(&:full_messages)
|
|
||||||
subgroup.members.map(&:valid?)
|
|
||||||
subgroup.members.map(&:errors).map(&:full_messages)
|
|
||||||
subgroup.members_and_requesters.map(&:errors).map(&:full_messages)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Routes
|
|
||||||
|
|
||||||
## Merge requests
|
## Merge requests
|
||||||
|
|
||||||
### Close a merge request
|
### Close a merge request
|
||||||
|
|
|
@ -153,7 +153,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
|
||||||
Trigger a pipeline by using a pipeline [trigger token](../ci/triggers/index.md#create-a-trigger-token)
|
Trigger a pipeline by using a pipeline [trigger token](../ci/triggers/index.md#create-a-trigger-token)
|
||||||
or a [CI/CD job token](../ci/jobs/ci_job_token.md) for authentication.
|
or a [CI/CD job token](../ci/jobs/ci_job_token.md) for authentication.
|
||||||
|
|
||||||
With a CI/CD job token, the [triggered pipeline is a multi-project pipeline](../ci/jobs/ci_job_token.md#trigger-a-multi-project-pipeline-by-using-a-cicd-job-token).
|
With a CI/CD job token, the [triggered pipeline is a multi-project pipeline](../ci/pipelines/downstream_pipelines.md#trigger-a-multi-project-pipeline-by-using-the-api).
|
||||||
The job that authenticates the request becomes associated with the upstream pipeline,
|
The job that authenticates the request becomes associated with the upstream pipeline,
|
||||||
which is visible on the [pipeline graph](../ci/pipelines/downstream_pipelines.md#view-multi-project-pipelines-in-pipeline-graphs).
|
which is visible on the [pipeline graph](../ci/pipelines/downstream_pipelines.md#view-multi-project-pipelines-in-pipeline-graphs).
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,18 @@ identifying abstract concepts and are subject to changes as we refine the design
|
||||||
- **Catalog** is the collection of projects that are set to contain components.
|
- **Catalog** is the collection of projects that are set to contain components.
|
||||||
- **Version** is the release name of a tag in the project, which allows components to be pinned to a specific revision.
|
- **Version** is the release name of a tag in the project, which allows components to be pinned to a specific revision.
|
||||||
|
|
||||||
## Characteristics of a component
|
## Definition of pipeline component
|
||||||
|
|
||||||
|
A pipeline component is a reusable single-purpose building block that abstracts away a single pipeline configuration unit. Components are used to compose a part or entire pipeline configuration.
|
||||||
|
It can optionally take input parameters and set output data to be adaptable and reusable in different pipeline contexts,
|
||||||
|
while encapsulating and isolating implementation details.
|
||||||
|
|
||||||
|
Components allow a pipeline to be assembled by using abstractions instead of having all the details defined in one place.
|
||||||
|
When using a component in a pipeline, a user shouldn't need to know the implementation details of the component and should
|
||||||
|
only rely on the provided interface. The interface will have a version / revision, so that users understand which revision they are interfacing with.
|
||||||
|
|
||||||
|
A pipeline component defines its type which indicates in which context of the pipeline configuration the component can be used.
|
||||||
|
For example, a component of type X can only be used according to the type X use-case.
|
||||||
|
|
||||||
For best experience with any systems made of components it's fundamental that components are single purpose,
|
For best experience with any systems made of components it's fundamental that components are single purpose,
|
||||||
isolated, reusable and resolvable.
|
isolated, reusable and resolvable.
|
||||||
|
@ -118,7 +129,7 @@ isolated, reusable and resolvable.
|
||||||
- **Reusability:** a component is designed to be used in different pipelines.
|
- **Reusability:** a component is designed to be used in different pipelines.
|
||||||
Depending on the assumptions it's built on a component can be more or less generic.
|
Depending on the assumptions it's built on a component can be more or less generic.
|
||||||
Generic components are more reusable but may require more customization.
|
Generic components are more reusable but may require more customization.
|
||||||
- **Resolvable:** When a component depends on another component, this dependency needs to be explicit and trackable. Hidden dependencies can lead to myriads of problems.
|
- **Resolvable:** When a component depends on another component, this dependency must be explicit and trackable.
|
||||||
|
|
||||||
## Proposal
|
## Proposal
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,8 @@ You can use a GitLab CI/CD job token to authenticate with specific API endpoints
|
||||||
(scoped to the job's project, when the `ci_job_token_scope` feature flag is enabled).
|
(scoped to the job's project, when the `ci_job_token_scope` feature flag is enabled).
|
||||||
- [Get job artifacts](../../api/job_artifacts.md#get-job-artifacts).
|
- [Get job artifacts](../../api/job_artifacts.md#get-job-artifacts).
|
||||||
- [Get job token's job](../../api/jobs.md#get-job-tokens-job).
|
- [Get job token's job](../../api/jobs.md#get-job-tokens-job).
|
||||||
- [Pipeline triggers](../../api/pipeline_triggers.md), using the `token=` parameter.
|
- [Pipeline triggers](../../api/pipeline_triggers.md), using the `token=` parameter
|
||||||
|
to [trigger a multi-project pipeline](../pipelines/downstream_pipelines.md#trigger-a-multi-project-pipeline-by-using-the-api).
|
||||||
- [Releases](../../api/releases/index.md) and [Release links](../../api/releases/links.md).
|
- [Releases](../../api/releases/index.md) and [Release links](../../api/releases/links.md).
|
||||||
- [Terraform plan](../../user/infrastructure/index.md).
|
- [Terraform plan](../../user/infrastructure/index.md).
|
||||||
|
|
||||||
|
@ -99,28 +100,6 @@ The job token scope is only for controlling access to private projects.
|
||||||
There is [a proposal](https://gitlab.com/groups/gitlab-org/-/epics/3559) to improve
|
There is [a proposal](https://gitlab.com/groups/gitlab-org/-/epics/3559) to improve
|
||||||
the feature with more strategic control of the access permissions.
|
the feature with more strategic control of the access permissions.
|
||||||
|
|
||||||
## Trigger a multi-project pipeline by using a CI/CD job token
|
|
||||||
|
|
||||||
> `CI_JOB_TOKEN` for multi-project pipelines was [moved](https://gitlab.com/gitlab-org/gitlab/-/issues/31573) from GitLab Premium to GitLab Free in 12.4.
|
|
||||||
|
|
||||||
You can use the `CI_JOB_TOKEN` to [trigger multi-project pipelines](../../api/pipeline_triggers.md#trigger-a-pipeline-with-a-token)
|
|
||||||
from a CI/CD job.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
trigger_pipeline:
|
|
||||||
stage: deploy
|
|
||||||
script:
|
|
||||||
- curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=main "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_TAG
|
|
||||||
environment: production
|
|
||||||
```
|
|
||||||
|
|
||||||
If you use the `CI_PIPELINE_SOURCE` [predefined CI/CD variable](../variables/predefined_variables.md)
|
|
||||||
in a pipeline triggered this way, [the value is `pipeline` (not `triggered`)](../triggers/index.md#configure-cicd-jobs-to-run-in-triggered-pipelines).
|
|
||||||
|
|
||||||
## Download an artifact from a different pipeline **(PREMIUM)**
|
## Download an artifact from a different pipeline **(PREMIUM)**
|
||||||
|
|
||||||
> `CI_JOB_TOKEN` for artifacts download with the API was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2346) in GitLab 9.5.
|
> `CI_JOB_TOKEN` for artifacts download with the API was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2346) in GitLab 9.5.
|
||||||
|
|
|
@ -7,33 +7,59 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
# Downstream pipelines **(FREE)**
|
# Downstream pipelines **(FREE)**
|
||||||
|
|
||||||
A downstream pipeline is any GitLab CI/CD pipeline triggered by another pipeline.
|
A downstream pipeline is any GitLab CI/CD pipeline triggered by another pipeline.
|
||||||
A downstream pipeline can be either:
|
Downstream pipelines run independently and concurrently to the upstream pipeline
|
||||||
|
that triggered them.
|
||||||
|
|
||||||
- A [parent-child pipeline](downstream_pipelines.md#parent-child-pipelines), which is a downstream pipeline triggered
|
- A [parent-child pipeline](downstream_pipelines.md#parent-child-pipelines) is a downstream pipeline
|
||||||
in the same project as the first pipeline.
|
triggered in the *same* project as the first pipeline.
|
||||||
- A [multi-project pipeline](#multi-project-pipelines), which is a downstream pipeline triggered
|
- A [multi-project pipeline](#multi-project-pipelines) is a downstream pipeline triggered
|
||||||
in a different project than the first pipeline.
|
in a *different* project than the first pipeline.
|
||||||
|
|
||||||
Parent-child pipelines and multi-project pipelines can sometimes be used for similar purposes,
|
You can sometimes use parent-child pipelines and multi-project pipelines for similar purposes,
|
||||||
but there are some key differences.
|
but there are [key differences](pipeline_architectures.md).
|
||||||
|
|
||||||
Parent-child pipelines:
|
## Parent-child pipelines
|
||||||
|
|
||||||
|
A parent pipeline is one that triggers a downstream pipeline in the same project.
|
||||||
|
The downstream pipeline is called a child pipeline. Child pipelines:
|
||||||
|
|
||||||
- Run under the same project, ref, and commit SHA as the parent pipeline.
|
- Run under the same project, ref, and commit SHA as the parent pipeline.
|
||||||
- Affect the overall status of the ref the pipeline runs against. For example,
|
- Do not directly affect the overall status of the ref the pipeline runs against. For example,
|
||||||
if a pipeline fails for the main branch, it's common to say that "main is broken".
|
if a pipeline fails for the main branch, it's common to say that "main is broken".
|
||||||
The status of child pipelines don't directly affect the status of the ref, unless the child
|
The status of child pipelines only affects the status of the ref if the child
|
||||||
pipeline is triggered with [`strategy:depend`](../yaml/index.md#triggerstrategy).
|
pipeline is triggered with [`strategy:depend`](../yaml/index.md#triggerstrategy).
|
||||||
- Are automatically canceled if the pipeline is configured with [`interruptible`](../yaml/index.md#interruptible)
|
- Are automatically canceled if the pipeline is configured with [`interruptible`](../yaml/index.md#interruptible)
|
||||||
when a new pipeline is created for the same ref.
|
when a new pipeline is created for the same ref.
|
||||||
- Display only the parent pipelines in the pipeline index page. Child pipelines are
|
- Are not displayed in the pipeline index page. You can only view child pipelines on
|
||||||
visible when visiting their parent pipeline's page.
|
their parent pipeline's page.
|
||||||
- Are limited to 2 levels of nesting. A parent pipeline can trigger multiple child pipelines,
|
|
||||||
and those child pipeline can trigger multiple child pipelines (`A -> B -> C`).
|
### Nested child pipelines
|
||||||
|
|
||||||
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29651) in GitLab 13.4.
|
||||||
|
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/243747) in GitLab 13.5.
|
||||||
|
|
||||||
|
Parent and child pipelines were introduced with a maximum depth of one level of child
|
||||||
|
pipelines, which was later increased to two. A parent pipeline can trigger many child
|
||||||
|
pipelines, and these child pipelines can trigger their own child pipelines. It's not
|
||||||
|
possible to trigger another level of child pipelines.
|
||||||
|
|
||||||
|
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||||
|
For an overview, see [Nested Dynamic Pipelines](https://youtu.be/C5j3ju9je2M).
|
||||||
|
|
||||||
|
## Multi-project pipelines
|
||||||
|
|
||||||
|
A pipeline in one project can trigger downstream pipelines in another project,
|
||||||
|
called multi-project pipelines. The user triggering the upstream pipeline must be able to
|
||||||
|
start pipelines in the downstream project, otherwise [the downstream pipeline fails to start](#trigger-job-fails-and-does-not-create-multi-project-pipeline).
|
||||||
|
|
||||||
|
For example, you might deploy your web application from three different GitLab projects.
|
||||||
|
With multi-project pipelines you can trigger a pipeline in each project, where each
|
||||||
|
has its own build, test, and deploy process. You can visualize the connected pipelines
|
||||||
|
in one place, including all cross-project interdependencies.
|
||||||
|
|
||||||
Multi-project pipelines:
|
Multi-project pipelines:
|
||||||
|
|
||||||
- Are triggered from another pipeline, but the upstream (triggering) pipeline does
|
- Are triggered from another project's pipeline, but the upstream (triggering) pipeline does
|
||||||
not have much control over the downstream (triggered) pipeline. However, it can
|
not have much control over the downstream (triggered) pipeline. However, it can
|
||||||
choose the ref of the downstream pipeline, and pass CI/CD variables to it.
|
choose the ref of the downstream pipeline, and pass CI/CD variables to it.
|
||||||
- Affect the overall status of the ref of the project it runs in, but does not
|
- Affect the overall status of the ref of the project it runs in, but does not
|
||||||
|
@ -46,75 +72,86 @@ Multi-project pipelines:
|
||||||
that happened to be triggered by an external project. They are all visible on the pipeline index page.
|
that happened to be triggered by an external project. They are all visible on the pipeline index page.
|
||||||
- Are independent, so there are no nesting limits.
|
- Are independent, so there are no nesting limits.
|
||||||
|
|
||||||
## Multi-project pipelines
|
Learn more in the "Cross-project Pipeline Triggering and Visualization" demo at
|
||||||
|
[GitLab@learn](https://about.gitlab.com/learn/), in the Continuous Integration section.
|
||||||
|
|
||||||
> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/199224) to GitLab Free in 12.8.
|
If you use a public project to trigger downstream pipelines in a private project,
|
||||||
|
make sure there are no confidentiality problems. The upstream project's pipelines page
|
||||||
|
always displays:
|
||||||
|
|
||||||
You can set up [GitLab CI/CD](../index.md) across multiple projects, so that a pipeline
|
- The name of the downstream project.
|
||||||
in one project can trigger a downstream pipeline in another project. You can visualize the entire pipeline
|
|
||||||
in one place, including all cross-project interdependencies.
|
|
||||||
|
|
||||||
For example, you might deploy your web application from three different projects in GitLab.
|
|
||||||
Each project has its own build, test, and deploy process. With multi-project pipelines you can
|
|
||||||
visualize the entire pipeline, including all build and test stages for all three projects.
|
|
||||||
|
|
||||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
|
||||||
For an overview, see the [Multi-project pipelines demo](https://www.youtube.com/watch?v=g_PIwBM1J84).
|
|
||||||
|
|
||||||
Multi-project pipelines are also useful for larger products that require cross-project interdependencies, like those
|
|
||||||
with a [microservices architecture](https://about.gitlab.com/blog/2016/08/16/trends-in-version-control-land-microservices/).
|
|
||||||
Learn more in the [Cross-project Pipeline Triggering and Visualization demo](https://about.gitlab.com/learn/)
|
|
||||||
at GitLab@learn, in the Continuous Integration section.
|
|
||||||
|
|
||||||
If you trigger a pipeline in a downstream private project, on the upstream project's pipelines page,
|
|
||||||
you can view:
|
|
||||||
|
|
||||||
- The name of the project.
|
|
||||||
- The status of the pipeline.
|
- The status of the pipeline.
|
||||||
|
|
||||||
If you have a public project that can trigger downstream pipelines in a private project,
|
## Trigger a downstream pipeline from a job in the `.gitlab-ci.yml` file
|
||||||
make sure there are no confidentiality problems.
|
|
||||||
|
|
||||||
### Trigger a multi-project pipeline from a job in your `.gitlab-ci.yml` file
|
Use the [`trigger`](../yaml/index.md#trigger) keyword in your `.gitlab-ci.yml` file
|
||||||
|
to create a job that triggers a downstream pipeline. This job is called a trigger job.
|
||||||
|
|
||||||
> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/199224) to GitLab Free in 12.8.
|
After the trigger job starts, the initial status of the job is `pending` while GitLab
|
||||||
|
attempts to create the downstream pipeline. If the downstream pipeline is created,
|
||||||
|
GitLab marks the job as passed, otherwise the job failed. Alternatively,
|
||||||
|
you can [set the trigger job to show the downstream pipeline's status](#mirror-the-status-of-a-downstream-pipeline-in-the-trigger-job)
|
||||||
|
instead.
|
||||||
|
|
||||||
When you use the [`trigger`](../yaml/index.md#trigger) keyword to create a multi-project
|
For example:
|
||||||
pipeline in your `.gitlab-ci.yml` file, you create what is called a *trigger job*. For example:
|
|
||||||
|
::Tabs
|
||||||
|
|
||||||
|
:::TabTitle Multi-project pipeline
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
rspec:
|
trigger_job:
|
||||||
stage: test
|
trigger:
|
||||||
script: bundle exec rspec
|
project: project-group/my-downstream-project
|
||||||
|
|
||||||
staging:
|
|
||||||
variables:
|
|
||||||
ENVIRONMENT: staging
|
|
||||||
stage: deploy
|
|
||||||
trigger: my/deployment
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, after the `rspec` job succeeds in the `test` stage,
|
:::TabTitle Parent-child pipeline
|
||||||
the `staging` trigger job starts. The initial status of this
|
|
||||||
job is `pending`.
|
|
||||||
|
|
||||||
GitLab then creates a downstream pipeline in the
|
```yaml
|
||||||
`my/deployment` project and, as soon as the pipeline is created, the
|
trigger_job:
|
||||||
`staging` job succeeds. The full path to the project is `my/deployment`.
|
trigger:
|
||||||
|
include:
|
||||||
|
- local: path/to/child-pipeline.yml
|
||||||
|
```
|
||||||
|
|
||||||
You can view the status for the pipeline, or you can display
|
::EndTabs
|
||||||
[the downstream pipeline's status instead](#mirror-the-status-of-a-downstream-pipeline-in-the-trigger-job).
|
|
||||||
|
|
||||||
The user that creates the upstream pipeline must be able to create pipelines in the
|
### Use `rules` to control downstream pipeline jobs
|
||||||
downstream project (`my/deployment`) too. If the downstream project is not found,
|
|
||||||
or the user does not have [permission](../../user/permissions.md) to create a pipeline there,
|
|
||||||
the `staging` job is marked as _failed_.
|
|
||||||
|
|
||||||
#### Specify a downstream pipeline branch
|
You can use CI/CD variables or the [`rules`](../yaml/index.md#rulesif) keyword to
|
||||||
|
[control job behavior](../jobs/job_control.md) for downstream pipelines.
|
||||||
|
|
||||||
You can specify a branch name for the downstream pipeline to use.
|
When a downstream pipeline is triggered with the [`trigger`](../yaml/index.md#trigger) keyword,
|
||||||
GitLab uses the commit on the head of the branch to
|
the value of the [`$CI_PIPELINE_SOURCE` predefined variable](../variables/predefined_variables.md)
|
||||||
create the downstream pipeline.
|
for all jobs is:
|
||||||
|
|
||||||
|
- `pipeline` for multi-project pipelines.
|
||||||
|
- `parent` for parent-child pipelines.
|
||||||
|
|
||||||
|
For example, with a multi-project pipeline:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
job1:
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "pipeline"
|
||||||
|
script: echo "This job runs in multi-project pipelines only"
|
||||||
|
|
||||||
|
job2:
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
script: echo "This job runs in merge request pipelines only"
|
||||||
|
|
||||||
|
job3:
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "pipeline"
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
script: echo "This job runs in both multi-project and merge request pipelines"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specify a branch for multi-project pipelines
|
||||||
|
|
||||||
|
You can specify a branch name for a multi-project pipeline to use. GitLab uses
|
||||||
|
the commit on the head of the branch to create the downstream pipeline:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
rspec:
|
rspec:
|
||||||
|
@ -137,112 +174,11 @@ Use:
|
||||||
In [GitLab 12.4 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/10126), variable expansion is
|
In [GitLab 12.4 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/10126), variable expansion is
|
||||||
supported.
|
supported.
|
||||||
|
|
||||||
Pipelines triggered on a protected branch in a downstream project use the [role](../../user/permissions.md)
|
### Use a child pipeline configuration file in a different project
|
||||||
of the user that ran the trigger job in the upstream project. If the user does not
|
|
||||||
have permission to run CI/CD pipelines against the protected branch, the pipeline fails. See
|
|
||||||
[pipeline security for protected branches](index.md#pipeline-security-on-protected-branches).
|
|
||||||
|
|
||||||
#### Use `rules` or `only`/`except` with multi-project pipelines
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/205157) in GitLab 13.5.
|
||||||
|
|
||||||
You can use CI/CD variables or the [`rules`](../yaml/index.md#rulesif) keyword to
|
You can use [`include:file`](../yaml/index.md#includefile) to trigger child pipelines
|
||||||
[control job behavior](../jobs/job_control.md) for multi-project pipelines. When a
|
|
||||||
downstream pipeline is triggered with the [`trigger`](../yaml/index.md#trigger) keyword,
|
|
||||||
the value of the [`$CI_PIPELINE_SOURCE` predefined variable](../variables/predefined_variables.md)
|
|
||||||
is `pipeline` for all its jobs.
|
|
||||||
|
|
||||||
If you use [`only/except`](../yaml/index.md#only--except) to control job behavior, use the
|
|
||||||
[`pipelines`](../yaml/index.md#onlyrefs--exceptrefs) keyword.
|
|
||||||
|
|
||||||
### Trigger a multi-project pipeline by using the API
|
|
||||||
|
|
||||||
> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/31573) to GitLab Free in 12.4.
|
|
||||||
|
|
||||||
When you use the [`CI_JOB_TOKEN` to trigger pipelines](../jobs/ci_job_token.md),
|
|
||||||
GitLab recognizes the source of the job token. The pipelines become related,
|
|
||||||
so you can visualize their relationships on pipeline graphs.
|
|
||||||
|
|
||||||
These relationships are displayed in the pipeline graph by showing inbound and
|
|
||||||
outbound connections for upstream and downstream pipeline dependencies.
|
|
||||||
|
|
||||||
When using:
|
|
||||||
|
|
||||||
- CI/CD variables or [`rules`](../yaml/index.md#rulesif) to control job behavior, the value of
|
|
||||||
the [`$CI_PIPELINE_SOURCE` predefined variable](../variables/predefined_variables.md) is
|
|
||||||
`pipeline` for multi-project pipeline triggered through the API with `CI_JOB_TOKEN`.
|
|
||||||
- [`only/except`](../yaml/index.md#only--except) to control job behavior, use the
|
|
||||||
`pipelines` keyword.
|
|
||||||
|
|
||||||
## Parent-child pipelines
|
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16094) in GitLab 12.7.
|
|
||||||
|
|
||||||
As pipelines grow more complex, a few related problems start to emerge:
|
|
||||||
|
|
||||||
- The staged structure, where all steps in a stage must be completed before the first
|
|
||||||
job in next stage begins, causes arbitrary waits, slowing things down.
|
|
||||||
- Configuration for the single global pipeline becomes very long and complicated,
|
|
||||||
making it hard to manage.
|
|
||||||
- Imports with [`include`](../yaml/index.md#include) increase the complexity of the configuration, and create the potential
|
|
||||||
for namespace collisions where jobs are unintentionally duplicated.
|
|
||||||
- Pipeline UX can become unwieldy with so many jobs and stages to work with.
|
|
||||||
|
|
||||||
Additionally, sometimes the behavior of a pipeline needs to be more dynamic. The ability
|
|
||||||
to choose to start sub-pipelines (or not) is a powerful ability, especially if the
|
|
||||||
YAML is dynamically generated.
|
|
||||||
|
|
||||||
![Parent pipeline graph expanded](img/parent_pipeline_graph_expanded_v14_3.png)
|
|
||||||
|
|
||||||
Similarly to [multi-project pipelines](#multi-project-pipelines), a pipeline can trigger a
|
|
||||||
set of concurrently running downstream child pipelines, but in the same project:
|
|
||||||
|
|
||||||
- Child pipelines still execute each of their jobs according to a stage sequence, but
|
|
||||||
would be free to continue forward through their stages without waiting for unrelated
|
|
||||||
jobs in the parent pipeline to finish.
|
|
||||||
- The configuration is split up into smaller child pipeline configurations. Each child pipeline contains only relevant steps which are
|
|
||||||
easier to understand. This reduces the cognitive load to understand the overall configuration.
|
|
||||||
- Imports are done at the child pipeline level, reducing the likelihood of collisions.
|
|
||||||
|
|
||||||
Child pipelines work well with other GitLab CI/CD features:
|
|
||||||
|
|
||||||
- Use [`rules: changes`](../yaml/index.md#ruleschanges) to trigger pipelines only when
|
|
||||||
certain files change. This is useful for monorepos, for example.
|
|
||||||
- Since the parent pipeline in `.gitlab-ci.yml` and the child pipeline run as normal
|
|
||||||
pipelines, they can have their own behaviors and sequencing in relation to triggers.
|
|
||||||
|
|
||||||
See the [`trigger`](../yaml/index.md#trigger) keyword documentation for full details on how to
|
|
||||||
include the child pipeline configuration.
|
|
||||||
|
|
||||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
|
||||||
For an overview, see [Parent-Child Pipelines feature demo](https://youtu.be/n8KpBSqZNbk).
|
|
||||||
|
|
||||||
NOTE:
|
|
||||||
The artifact containing the generated YAML file must not be [larger than 5MB](https://gitlab.com/gitlab-org/gitlab/-/issues/249140).
|
|
||||||
|
|
||||||
### Trigger a parent-child pipeline
|
|
||||||
|
|
||||||
The simplest case is [triggering a child pipeline](../yaml/index.md#trigger) using a
|
|
||||||
local YAML file to define the pipeline configuration. In this case, the parent pipeline
|
|
||||||
triggers the child pipeline, and continues without waiting:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
microservice_a:
|
|
||||||
trigger:
|
|
||||||
include: path/to/microservice_a.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
You can include multiple files when defining a child pipeline. The child pipeline's
|
|
||||||
configuration is composed of all configuration files merged together:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
microservice_a:
|
|
||||||
trigger:
|
|
||||||
include:
|
|
||||||
- local: path/to/microservice_a.yml
|
|
||||||
- template: Security/SAST.gitlab-ci.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
In [GitLab 13.5 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/205157),
|
|
||||||
you can use [`include:file`](../yaml/index.md#includefile) to trigger child pipelines
|
|
||||||
with a configuration file in a different project:
|
with a configuration file in a different project:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -254,119 +190,150 @@ microservice_a:
|
||||||
file: '/path/to/child-pipeline.yml'
|
file: '/path/to/child-pipeline.yml'
|
||||||
```
|
```
|
||||||
|
|
||||||
The maximum number of entries that are accepted for `trigger:include` is three.
|
### Combine multiple child pipeline configuration files
|
||||||
|
|
||||||
### Merge request child pipelines
|
You can include up to three configuration files when defining a child pipeline. The child pipeline's
|
||||||
|
configuration is composed of all configuration files merged together:
|
||||||
To trigger a child pipeline as a [merge request pipeline](merge_request_pipelines.md) we need to:
|
|
||||||
|
|
||||||
- Set the trigger job to run on merge requests:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# parent .gitlab-ci.yml
|
|
||||||
microservice_a:
|
microservice_a:
|
||||||
trigger:
|
trigger:
|
||||||
include: path/to/microservice_a.yml
|
include:
|
||||||
rules:
|
- local: path/to/microservice_a.yml
|
||||||
- if: $CI_MERGE_REQUEST_ID
|
- template: Security/SAST.gitlab-ci.yml
|
||||||
|
- project: 'my-group/my-pipeline-library'
|
||||||
|
ref: 'main'
|
||||||
|
file: '/path/to/child-pipeline.yml'
|
||||||
```
|
```
|
||||||
|
|
||||||
- Configure the child pipeline by either:
|
|
||||||
|
|
||||||
- Setting all jobs in the child pipeline to evaluate in the context of a merge request:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# child path/to/microservice_a.yml
|
|
||||||
workflow:
|
|
||||||
rules:
|
|
||||||
- if: $CI_MERGE_REQUEST_ID
|
|
||||||
|
|
||||||
job1:
|
|
||||||
script: ...
|
|
||||||
|
|
||||||
job2:
|
|
||||||
script: ...
|
|
||||||
```
|
|
||||||
|
|
||||||
- Alternatively, setting the rule per job. For example, to create only `job1` in
|
|
||||||
the context of merge request pipelines:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# child path/to/microservice_a.yml
|
|
||||||
job1:
|
|
||||||
script: ...
|
|
||||||
rules:
|
|
||||||
- if: $CI_MERGE_REQUEST_ID
|
|
||||||
|
|
||||||
job2:
|
|
||||||
script: ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dynamic child pipelines
|
### Dynamic child pipelines
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35632) in GitLab 12.9.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35632) in GitLab 12.9.
|
||||||
|
|
||||||
Instead of running a child pipeline from a static YAML file, you can define a job that runs
|
You can trigger a child pipeline from a YAML file generated in a job, instead of a
|
||||||
your own script to generate a YAML file, which is then used to trigger a child pipeline.
|
static file saved in your project. This technique can be very powerful for generating pipelines
|
||||||
|
targeting content that changed or to build a matrix of targets and architectures.
|
||||||
|
|
||||||
This technique can be very powerful in generating pipelines targeting content that changed or to
|
The artifact containing the generated YAML file must not be [larger than 5MB](https://gitlab.com/gitlab-org/gitlab/-/issues/249140).
|
||||||
build a matrix of targets and architectures.
|
|
||||||
|
|
||||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||||
For an overview, see [Create child pipelines using dynamically generated configurations](https://youtu.be/nMdfus2JWHM).
|
For an overview, see [Create child pipelines using dynamically generated configurations](https://youtu.be/nMdfus2JWHM).
|
||||||
|
|
||||||
We also have an example project using
|
For an example project that generates a dynamic child pipeline, see
|
||||||
[Dynamic Child Pipelines with Jsonnet](https://gitlab.com/gitlab-org/project-templates/jsonnet)
|
[Dynamic Child Pipelines with Jsonnet](https://gitlab.com/gitlab-org/project-templates/jsonnet).
|
||||||
which shows how to use a data templating language to generate your `.gitlab-ci.yml` at runtime.
|
This project shows how to use a data templating language to generate your `.gitlab-ci.yml` at runtime.
|
||||||
You could use a similar process for other templating languages like
|
You can use a similar process for other templating languages like
|
||||||
[Dhall](https://dhall-lang.org/) or [ytt](https://get-ytt.io/).
|
[Dhall](https://dhall-lang.org/) or [ytt](https://get-ytt.io/).
|
||||||
|
|
||||||
|
#### Trigger a dynamic child pipeline
|
||||||
|
|
||||||
|
To trigger a child pipeline from a dynamically generated configuration file:
|
||||||
|
|
||||||
|
1. Generate the configuration file in a job and save it as an [artifact](../yaml/index.md#artifactspaths):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
generate-config:
|
||||||
|
stage: build
|
||||||
|
script: generate-ci-config > generated-config.yml
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- generated-config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Configure the trigger job to run after the job that generated the configuration file,
|
||||||
|
and set `include: artifact` to the generated artifact:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
child-pipeline:
|
||||||
|
stage: test
|
||||||
|
trigger:
|
||||||
|
include:
|
||||||
|
- artifact: generated-config.yml
|
||||||
|
job: generate-config
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, `generated-config.yml` is extracted from the artifacts and used as the configuration
|
||||||
|
for triggering the child pipeline.
|
||||||
|
|
||||||
The artifact path is parsed by GitLab, not the runner, so the path must match the
|
The artifact path is parsed by GitLab, not the runner, so the path must match the
|
||||||
syntax for the OS running GitLab. If GitLab is running on Linux but using a Windows
|
syntax for the OS running GitLab. If GitLab is running on Linux but using a Windows
|
||||||
runner for testing, the path separator for the trigger job would be `/`. Other CI/CD
|
runner for testing, the path separator for the trigger job is `/`. Other CI/CD
|
||||||
configuration for jobs, like scripts, that use the Windows runner would use `\`.
|
configuration for jobs that use the Windows runner, like scripts, use `\`.
|
||||||
|
|
||||||
For example, to trigger a child pipeline from a dynamically generated configuration file:
|
### Run child pipelines with merge request pipelines
|
||||||
|
|
||||||
|
To trigger a child pipeline as a [merge request pipeline](merge_request_pipelines.md):
|
||||||
|
|
||||||
|
1. Set the trigger job to run on merge requests:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# parent .gitlab-ci.yml
|
||||||
|
microservice_a:
|
||||||
|
trigger:
|
||||||
|
include: path/to/microservice_a.yml
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Configure the child pipeline jobs to run in merge request pipelines:
|
||||||
|
|
||||||
|
- With [`workflow:rules`](../yaml/index.md#workflowrules):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# child path/to/microservice_a.yml
|
||||||
|
workflow:
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
|
||||||
|
job1:
|
||||||
|
script: ...
|
||||||
|
|
||||||
|
job2:
|
||||||
|
script: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
- By configuring [rules](../yaml/index.md#rules) for each job:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# child path/to/microservice_a.yml
|
||||||
|
job1:
|
||||||
|
script: ...
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
|
||||||
|
job2:
|
||||||
|
script: ...
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Trigger a multi-project pipeline by using the API
|
||||||
|
|
||||||
|
You can use the [CI/CD job token (`CI_JOB_TOKEN`)](../jobs/ci_job_token.md) with the
|
||||||
|
[pipeline trigger API endpoint](../../api/pipeline_triggers.md#trigger-a-pipeline-with-a-token)
|
||||||
|
to trigger multi-project pipelines from a CI/CD job. GitLab recognizes the source of the job token
|
||||||
|
and marks the pipelines as related. In the pipeline graph, the relationships are displayed
|
||||||
|
as inbound and outbound connections for upstream and downstream pipeline dependencies.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
generate-config:
|
trigger_pipeline:
|
||||||
stage: build
|
stage: deploy
|
||||||
script: generate-ci-config > generated-config.yml
|
script:
|
||||||
artifacts:
|
- curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=main "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
|
||||||
paths:
|
rules:
|
||||||
- generated-config.yml
|
- if: $CI_COMMIT_TAG
|
||||||
|
environment: production
|
||||||
child-pipeline:
|
|
||||||
stage: test
|
|
||||||
trigger:
|
|
||||||
include:
|
|
||||||
- artifact: generated-config.yml
|
|
||||||
job: generate-config
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `generated-config.yml` is extracted from the artifacts and used as the configuration
|
|
||||||
for triggering the child pipeline.
|
|
||||||
|
|
||||||
In GitLab 12.9, the child pipeline could fail to be created in certain cases, causing the parent pipeline to fail.
|
|
||||||
This is [resolved](https://gitlab.com/gitlab-org/gitlab/-/issues/209070) in GitLab 12.10.
|
|
||||||
|
|
||||||
### Nested child pipelines
|
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29651) in GitLab 13.4.
|
|
||||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/243747) in GitLab 13.5.
|
|
||||||
|
|
||||||
Parent and child pipelines were introduced with a maximum depth of one level of child
|
|
||||||
pipelines, which was later increased to two. A parent pipeline can trigger many child
|
|
||||||
pipelines, and these child pipelines can trigger their own child pipelines. It's not
|
|
||||||
possible to trigger another level of child pipelines.
|
|
||||||
|
|
||||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
|
||||||
For an overview, see [Nested Dynamic Pipelines](https://youtu.be/C5j3ju9je2M).
|
|
||||||
|
|
||||||
## View a downstream pipeline
|
## View a downstream pipeline
|
||||||
|
|
||||||
|
> Hover behavior for pipeline cards [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/197140/) in GitLab 13.2.
|
||||||
|
|
||||||
In the [pipeline graph view](index.md#view-full-pipeline-graph), downstream pipelines display
|
In the [pipeline graph view](index.md#view-full-pipeline-graph), downstream pipelines display
|
||||||
as a list of cards on the right of the graph.
|
as a list of cards on the right of the graph. Hover over the pipeline's card to view
|
||||||
|
which job triggered the downstream pipeline.
|
||||||
|
|
||||||
### Retry a downstream pipeline
|
### Retry a downstream pipeline
|
||||||
|
|
||||||
|
@ -390,9 +357,6 @@ To cancel a downstream pipeline that is still running, select **Cancel** (**{can
|
||||||
|
|
||||||
### Mirror the status of a downstream pipeline in the trigger job
|
### Mirror the status of a downstream pipeline in the trigger job
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11238) in GitLab Premium 12.3.
|
|
||||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/199224) to GitLab Free in 12.8.
|
|
||||||
|
|
||||||
You can mirror the pipeline status from the triggered pipeline to the source trigger job
|
You can mirror the pipeline status from the triggered pipeline to the source trigger job
|
||||||
by using [`strategy: depend`](../yaml/index.md#triggerstrategy):
|
by using [`strategy: depend`](../yaml/index.md#triggerstrategy):
|
||||||
|
|
||||||
|
@ -549,8 +513,9 @@ The `ENVIRONMENT` variable is passed to every job defined in a downstream
|
||||||
pipeline. It is available as a variable when GitLab Runner picks a job.
|
pipeline. It is available as a variable when GitLab Runner picks a job.
|
||||||
|
|
||||||
In the following configuration, the `MY_VARIABLE` variable is passed to the downstream pipeline
|
In the following configuration, the `MY_VARIABLE` variable is passed to the downstream pipeline
|
||||||
that is created when the `trigger-downstream` job is queued. This is because `trigger-downstream`
|
that is created when the `trigger-downstream` job is queued. This behavior is because `trigger-downstream`
|
||||||
job inherits variables declared in global variables blocks, and then we pass these variables to a downstream pipeline.
|
job inherits variables declared in [global `variables`](../yaml/index.md#variables) blocks,
|
||||||
|
and then GitLab passes these variables to the downstream pipeline.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
variables:
|
variables:
|
||||||
|
@ -562,7 +527,7 @@ trigger-downstream:
|
||||||
trigger: my/project
|
trigger: my/project
|
||||||
```
|
```
|
||||||
|
|
||||||
### Prevent global variables from being passed
|
#### Prevent global variables from being passed
|
||||||
|
|
||||||
You can stop global variables from reaching the downstream pipeline by using the [`inherit:variables` keyword](../yaml/index.md#inheritvariables).
|
You can stop global variables from reaching the downstream pipeline by using the [`inherit:variables` keyword](../yaml/index.md#inheritvariables).
|
||||||
For example, in a [multi-project pipeline](#multi-project-pipelines):
|
For example, in a [multi-project pipeline](#multi-project-pipelines):
|
||||||
|
@ -645,3 +610,16 @@ For example, in a [multi-project pipeline](#multi-project-pipelines):
|
||||||
ref: master
|
ref: master
|
||||||
artifacts: true
|
artifacts: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Trigger job fails and does not create multi-project pipeline
|
||||||
|
|
||||||
|
With multi-project pipelines, the trigger job fails and does not create the downstream pipeline if:
|
||||||
|
|
||||||
|
- The downstream project is not found.
|
||||||
|
- The user that creates the upstream pipeline does not have [permission](../../user/permissions.md)
|
||||||
|
to create pipelines in the downstream project.
|
||||||
|
- The downstream pipeline targets a protected branch and the user does not have permission
|
||||||
|
to run pipelines against the protected branch. See [pipeline security for protected branches](index.md#pipeline-security-on-protected-branches)
|
||||||
|
for more information.
|
||||||
|
|
|
@ -10,15 +10,21 @@ type: reference
|
||||||
Pipelines are the fundamental building blocks for CI/CD in GitLab. This page documents
|
Pipelines are the fundamental building blocks for CI/CD in GitLab. This page documents
|
||||||
some of the important concepts related to them.
|
some of the important concepts related to them.
|
||||||
|
|
||||||
There are three main ways to structure your pipelines, each with their
|
You can structure your pipelines with different methods, each with their
|
||||||
own advantages. These methods can be mixed and matched if needed:
|
own advantages. These methods can be mixed and matched if needed:
|
||||||
|
|
||||||
- [Basic](#basic-pipelines): Good for straightforward projects where all the configuration is in one easy to find place.
|
- [Basic](#basic-pipelines): Good for straightforward projects where all the configuration is in one easy to find place.
|
||||||
- [Directed Acyclic Graph](#directed-acyclic-graph-pipelines): Good for large, complex projects that need efficient execution.
|
- [Directed Acyclic Graph](#directed-acyclic-graph-pipelines): Good for large, complex projects that need efficient execution.
|
||||||
- [Child/Parent Pipelines](#child--parent-pipelines): Good for monorepos and projects with lots of independently defined components.
|
- [Parent-child pipelines](#parent-child-pipelines): Good for monorepos and projects with lots of independently defined components.
|
||||||
|
|
||||||
For more details about
|
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||||
any of the keywords used below, check out our [CI YAML reference](../yaml/index.md) for details.
|
For an overview, see the [Parent-Child Pipelines feature demo](https://youtu.be/n8KpBSqZNbk).
|
||||||
|
|
||||||
|
- [Multi-project pipelines](downstream_pipelines.md#multi-project-pipelines): Good for larger products that require cross-project interdependencies,
|
||||||
|
like those with a [microservices architecture](https://about.gitlab.com/blog/2016/08/16/trends-in-version-control-land-microservices/).
|
||||||
|
|
||||||
|
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||||
|
For an overview, see the [Multi-project pipelines demo](https://www.youtube.com/watch?v=g_PIwBM1J84).
|
||||||
|
|
||||||
## Basic Pipelines
|
## Basic Pipelines
|
||||||
|
|
||||||
|
@ -163,12 +169,29 @@ deploy_b:
|
||||||
environment: production
|
environment: production
|
||||||
```
|
```
|
||||||
|
|
||||||
## Child / Parent Pipelines
|
## Parent-child pipelines
|
||||||
|
|
||||||
In the examples above, it's clear we've got two types of things that could be built independently.
|
As pipelines grow more complex, a few related problems start to emerge:
|
||||||
This is an ideal case for using [Child / Parent Pipelines](downstream_pipelines.md#parent-child-pipelines)) via
|
|
||||||
the [`trigger` keyword](../yaml/index.md#trigger). It separates out the configuration
|
- The staged structure, where all steps in a stage must complete before the first
|
||||||
into multiple files, keeping things very simple. You can also combine this with:
|
job in next stage begins, causes waits that slow things down.
|
||||||
|
- Configuration for the single global pipeline becomes
|
||||||
|
hard to manage.
|
||||||
|
- Imports with [`include`](../yaml/index.md#include) increase the complexity of the configuration, and can cause
|
||||||
|
namespace collisions where jobs are unintentionally duplicated.
|
||||||
|
- Pipeline UX has too many jobs and stages to work with.
|
||||||
|
|
||||||
|
Additionally, sometimes the behavior of a pipeline needs to be more dynamic. The ability
|
||||||
|
to choose to start sub-pipelines (or not) is a powerful ability, especially if the
|
||||||
|
YAML is dynamically generated.
|
||||||
|
|
||||||
|
![Parent pipeline graph expanded](img/parent_pipeline_graph_expanded_v14_3.png)
|
||||||
|
|
||||||
|
In the [basic pipeline](#basic-pipelines) and [directed acyclic graph](#directed-acyclic-graph-pipelines)
|
||||||
|
examples above, there are two packages that could be built independently.
|
||||||
|
These cases are ideal for using [parent-child pipelines](downstream_pipelines.md#parent-child-pipelines).
|
||||||
|
It separates out the configuration into multiple files, keeping things simpler.
|
||||||
|
You can combine parent-child pipelines with:
|
||||||
|
|
||||||
- The [`rules` keyword](../yaml/index.md#rules): For example, have the child pipelines triggered only
|
- The [`rules` keyword](../yaml/index.md#rules): For example, have the child pipelines triggered only
|
||||||
when there are changes to that area.
|
when there are changes to that area.
|
||||||
|
|
|
@ -3978,7 +3978,7 @@ trigger-multi-project-pipeline:
|
||||||
|
|
||||||
**Related topics**:
|
**Related topics**:
|
||||||
|
|
||||||
- [Multi-project pipeline configuration examples](../pipelines/downstream_pipelines.md#trigger-a-multi-project-pipeline-from-a-job-in-your-gitlab-ciyml-file).
|
- [Multi-project pipeline configuration examples](../pipelines/downstream_pipelines.md#trigger-a-downstream-pipeline-from-a-job-in-the-gitlab-ciyml-file).
|
||||||
- To run a pipeline for a specific branch, tag, or commit, you can use a [trigger token](../triggers/index.md)
|
- To run a pipeline for a specific branch, tag, or commit, you can use a [trigger token](../triggers/index.md)
|
||||||
to authenticate with the [pipeline triggers API](../../api/pipeline_triggers.md).
|
to authenticate with the [pipeline triggers API](../../api/pipeline_triggers.md).
|
||||||
The trigger token is different than the `trigger` keyword.
|
The trigger token is different than the `trigger` keyword.
|
||||||
|
@ -4006,7 +4006,7 @@ trigger-child-pipeline:
|
||||||
|
|
||||||
**Related topics**:
|
**Related topics**:
|
||||||
|
|
||||||
- [Child pipeline configuration examples](../pipelines/downstream_pipelines.md#trigger-a-parent-child-pipeline).
|
- [Child pipeline configuration examples](../pipelines/downstream_pipelines.md#trigger-a-downstream-pipeline-from-a-job-in-the-gitlab-ciyml-file).
|
||||||
|
|
||||||
#### `trigger:project`
|
#### `trigger:project`
|
||||||
|
|
||||||
|
@ -4042,7 +4042,7 @@ trigger-multi-project-pipeline:
|
||||||
|
|
||||||
**Related topics**:
|
**Related topics**:
|
||||||
|
|
||||||
- [Multi-project pipeline configuration examples](../pipelines/downstream_pipelines.md#trigger-a-multi-project-pipeline-from-a-job-in-your-gitlab-ciyml-file).
|
- [Multi-project pipeline configuration examples](../pipelines/downstream_pipelines.md#trigger-a-downstream-pipeline-from-a-job-in-the-gitlab-ciyml-file).
|
||||||
- To run a pipeline for a specific branch, tag, or commit, you can also use a [trigger token](../triggers/index.md)
|
- To run a pipeline for a specific branch, tag, or commit, you can also use a [trigger token](../triggers/index.md)
|
||||||
to authenticate with the [pipeline triggers API](../../api/pipeline_triggers.md).
|
to authenticate with the [pipeline triggers API](../../api/pipeline_triggers.md).
|
||||||
The trigger token is different than the `trigger` keyword.
|
The trigger token is different than the `trigger` keyword.
|
||||||
|
|
|
@ -805,3 +805,45 @@ To find and store an array of groups based on an SQL query in the [rails console
|
||||||
Group.find_by_sql("SELECT * FROM namespaces WHERE name LIKE '%oup'")
|
Group.find_by_sql("SELECT * FROM namespaces WHERE name LIKE '%oup'")
|
||||||
=> [#<Group id:3 @test-group>, #<Group id:4 @template-group/template-subgroup>]
|
=> [#<Group id:3 @test-group>, #<Group id:4 @template-group/template-subgroup>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Transfer subgroup to another location using Rails console
|
||||||
|
|
||||||
|
If transferring a group doesn't work through the UI or API, you may want to attempt the transfer in a [Rails console session](../../administration/operations/rails_console.md#starting-a-rails-console-session):
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
Any command that changes data directly could be damaging if not run correctly, or under the right conditions. We highly recommend running them in a test environment with a backup of the instance ready to be restored, just in case.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
user = User.find_by_username('<username>')
|
||||||
|
group = Group.find_by_name("<group_name>")
|
||||||
|
## Set parent_group = nil to make the subgroup a top-level group
|
||||||
|
parent_group = Group.find_by(id: "<group_id>")
|
||||||
|
service = ::Groups::TransferService.new(group, user)
|
||||||
|
service.execute(parent_group)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Find groups pending deletion using Rails console
|
||||||
|
|
||||||
|
If you need to find all the groups that are pending deletion, you can use the following command in a [Rails console session](../../administration/operations/rails_console.md#starting-a-rails-console-session):
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Group.all.each do |g|
|
||||||
|
if g.marked_for_deletion?
|
||||||
|
puts "Group ID: #{g.id}"
|
||||||
|
puts "Group name: #{g.name}"
|
||||||
|
puts "Group path: #{g.full_path}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Delete a group using Rails console
|
||||||
|
|
||||||
|
At times, a group deletion may get stuck. If needed, in a [Rails console session](../../administration/operations/rails_console.md#starting-a-rails-console-session),
|
||||||
|
you can attempt to delete a group using the following command:
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
Any command that changes data directly could be damaging if not run correctly, or under the right conditions. We highly recommend running them in a test environment with a backup of the instance ready to be restored, just in case.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
GroupDestroyWorker.new.perform(group_id, user_id)
|
||||||
|
```
|
||||||
|
|
139
doc/user/project/remote_development/index.md
Normal file
139
doc/user/project/remote_development/index.md
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
---
|
||||||
|
stage: Create
|
||||||
|
group: Editor
|
||||||
|
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||||
|
---
|
||||||
|
|
||||||
|
# Remote Development **(FREE)**
|
||||||
|
|
||||||
|
DISCLAIMER:
|
||||||
|
This page contains information related to upcoming products, features, and functionality.
|
||||||
|
It is important to note that the information presented is for informational purposes only.
|
||||||
|
Please do not rely on this information for purchasing or planning purposes.
|
||||||
|
As with all projects, the items mentioned on this page are subject to change or delay.
|
||||||
|
The development, release, and timing of any products, features, or functionality remain at the
|
||||||
|
sole discretion of GitLab Inc.
|
||||||
|
|
||||||
|
You can use the [Web IDE](../web_ide/index.md) to commit changes to a project directly from your web browser without installing any dependencies or cloning any repositories. The Web IDE, however, lacks a native runtime environment on which you would compile code, run tests, or generate real-time feedback in the IDE. For a more complete IDE experience, you can pair the Web IDE with a Remote Development environment that has been properly configured to run as a host.
|
||||||
|
|
||||||
|
## Connect a remote machine to the Web IDE
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- A remote virtual machine with root access
|
||||||
|
- A domain address resolving to that machine
|
||||||
|
- Docker installation
|
||||||
|
|
||||||
|
To connect a remote machine to the Web IDE, you must:
|
||||||
|
|
||||||
|
1. [Generate Let's Encrypt certificates](#generate-lets-encrypt-certificates).
|
||||||
|
1. [Connect a development environment to the Web IDE](#connect-a-development-environment-to-the-web-ide).
|
||||||
|
|
||||||
|
### Generate Let's Encrypt certificates
|
||||||
|
|
||||||
|
To generate Let's Encrypt certificates:
|
||||||
|
|
||||||
|
1. [Point a domain to your remote machine](#point-a-domain-to-your-remote-machine).
|
||||||
|
1. [Install Certbot](#install-certbot).
|
||||||
|
1. [Generate the certificates](#generate-the-certificates).
|
||||||
|
|
||||||
|
#### Point a domain to your remote machine
|
||||||
|
|
||||||
|
To point a domain to your remote machine, create an `A` record from `example.remote.gitlab.dev` to `1.2.3.4`.
|
||||||
|
|
||||||
|
#### Install Certbot
|
||||||
|
|
||||||
|
[Certbot](https://certbot.eff.org/) is a free and open-source software tool that automatically uses Let's Encrypt certificates on manually administrated websites to enable HTTPS.
|
||||||
|
|
||||||
|
To install Certbot, run the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install certbot
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Generate the certificates
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export EMAIL="YOUR_EMAIL@example.com"
|
||||||
|
export DOMAIN="example.remote.gitlab.dev"
|
||||||
|
|
||||||
|
certbot -d "${DOMAIN}" \
|
||||||
|
-m "${EMAIL}" \
|
||||||
|
--config-dir ~/.certbot/config \
|
||||||
|
--logs-dir ~/.certbot/logs \
|
||||||
|
--work-dir ~/.certbot/work \
|
||||||
|
--manual \
|
||||||
|
--preferred-challenges dns certonly
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connect a development environment to the Web IDE
|
||||||
|
|
||||||
|
To connect a development environment to the Web IDE:
|
||||||
|
|
||||||
|
1. [Create a development environment](#manage-a-development-environment).
|
||||||
|
1. [Fetch a token](#fetch-a-token).
|
||||||
|
1. [Connect to the Web IDE](#connect-to-the-web-ide).
|
||||||
|
|
||||||
|
#### Manage a development environment
|
||||||
|
|
||||||
|
**Create a development environment**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export CERTS_DIR="/home/ubuntu/.certbot/config/live/${DOMAIN}"
|
||||||
|
export PROJECTS_DIR="/home/ubuntu"
|
||||||
|
|
||||||
|
docker run -d \
|
||||||
|
--name my-environment \
|
||||||
|
-p 3443:3443 \
|
||||||
|
-v "${CERTS_DIR}/fullchain.pem:/gitlab-rd-web-ide/certs/fullchain.pem" \
|
||||||
|
-v "${CERTS_DIR}/privkey.pem:/gitlab-rd-web-ide/certs/privkey.pem" \
|
||||||
|
-v "${PROJECTS_DIR}:/projects" \
|
||||||
|
registry.gitlab.com/gitlab-com/create-stage/editor-poc/remote-development/gitlab-rd-web-ide-docker:0.1 \
|
||||||
|
--log-level warn --domain "${DOMAIN}" --ignore-version-mismatch
|
||||||
|
```
|
||||||
|
|
||||||
|
The new development environment starts automatically.
|
||||||
|
|
||||||
|
**Stop a development environment**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker container stop my-environment
|
||||||
|
```
|
||||||
|
|
||||||
|
**Start a development environment**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker container start my-environment
|
||||||
|
```
|
||||||
|
|
||||||
|
The token changes every time you restart the development environment.
|
||||||
|
|
||||||
|
**Remove a development environment**
|
||||||
|
|
||||||
|
To remove a development environment:
|
||||||
|
|
||||||
|
1. Stop the development environment.
|
||||||
|
1. Run the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker container rm my-environment
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fetch a token
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker exec my-environment cat TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Connect to the Web IDE
|
||||||
|
|
||||||
|
To connect to the Web IDE:
|
||||||
|
|
||||||
|
1. Run the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "https://gitlab-org.gitlab.io/gitlab-web-ide?remoteHost=${DOMAIN}:3443&hostPath=/projects"
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Go to that URL and enter the [token you fetched](#fetch-a-token).
|
|
@ -559,3 +559,33 @@ If this fails, display why it doesn't work with:
|
||||||
project = Project.find_by_full_path('<project_path>')
|
project = Project.find_by_full_path('<project_path>')
|
||||||
project.delete_error
|
project.delete_error
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Toggle a feature for all projects within a group
|
||||||
|
|
||||||
|
While toggling a feature in a project can be done through the [projects API](../../api/projects.md),
|
||||||
|
you may need to do this for a large number of projects.
|
||||||
|
|
||||||
|
To toggle a specific feature, you can [start a Rails console session](../../administration/operations/rails_console.md#starting-a-rails-console-session)
|
||||||
|
and run the following function:
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
Any command that changes data directly could be damaging if not run correctly, or under the right conditions. We highly recommend running them in a test environment with a backup of the instance ready to be restored, just in case.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
projects = Group.find_by_name('_group_name').projects
|
||||||
|
projects.each do |p|
|
||||||
|
## replace <feature-name> with the appropriate feature name in all instances
|
||||||
|
state = p.<feature-name>
|
||||||
|
|
||||||
|
if state != 0
|
||||||
|
puts "#{p.name} has <feature-name> already enabled. Skipping..."
|
||||||
|
else
|
||||||
|
puts "#{p.name} didn't have <feature-name> enabled. Enabling..."
|
||||||
|
p.project_feature.update!(<feature-name>: ProjectFeature::PRIVATE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
To find features that can be toggled, run `pp p.project_feature`.
|
||||||
|
Available permission levels are listed in
|
||||||
|
[concerns/featurable.rb](https://gitlab.com/gitlab-org/gitlab/blob/master/app/models/concerns/featurable.rb).
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'gitlab-qa', '~> 8', '>= 8.7.0', require: 'gitlab/qa'
|
gem 'gitlab-qa', '~> 8', '>= 8.8.0', require: 'gitlab/qa'
|
||||||
gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
|
gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
|
||||||
gem 'allure-rspec', '~> 2.18.0'
|
gem 'allure-rspec', '~> 2.18.0'
|
||||||
gem 'capybara', '~> 3.35.0'
|
gem 'capybara', '~> 3.37.1'
|
||||||
gem 'capybara-screenshot', '~> 1.0.26'
|
gem 'capybara-screenshot', '~> 1.0.26'
|
||||||
gem 'rake', '~> 13'
|
gem 'rake', '~> 13'
|
||||||
gem 'rspec', '~> 3.11'
|
gem 'rspec', '~> 3.11'
|
||||||
|
|
|
@ -27,8 +27,9 @@ GEM
|
||||||
binding_ninja (0.2.3)
|
binding_ninja (0.2.3)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
byebug (11.1.3)
|
byebug (11.1.3)
|
||||||
capybara (3.35.3)
|
capybara (3.37.1)
|
||||||
addressable
|
addressable
|
||||||
|
matrix
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
rack (>= 1.6.0)
|
rack (>= 1.6.0)
|
||||||
|
@ -99,14 +100,15 @@ GEM
|
||||||
gitlab (4.18.0)
|
gitlab (4.18.0)
|
||||||
httparty (~> 0.18)
|
httparty (~> 0.18)
|
||||||
terminal-table (>= 1.5.1)
|
terminal-table (>= 1.5.1)
|
||||||
gitlab-qa (8.7.0)
|
gitlab-qa (8.8.0)
|
||||||
activesupport (~> 6.1)
|
activesupport (~> 6.1)
|
||||||
gitlab (~> 4.18.0)
|
gitlab (~> 4.18.0)
|
||||||
http (~> 5.0)
|
http (~> 5.0)
|
||||||
nokogiri (~> 1.10)
|
nokogiri (~> 1.10)
|
||||||
rainbow (~> 3.0.0)
|
rainbow (>= 3, < 4)
|
||||||
table_print (= 1.5.7)
|
table_print (= 1.5.7)
|
||||||
zeitwerk (~> 2.4)
|
toxiproxy (~> 2.0.2)
|
||||||
|
zeitwerk (>= 2, < 3)
|
||||||
google-apis-compute_v1 (0.51.0)
|
google-apis-compute_v1 (0.51.0)
|
||||||
google-apis-core (>= 0.7.2, < 2.a)
|
google-apis-core (>= 0.7.2, < 2.a)
|
||||||
google-apis-core (0.9.0)
|
google-apis-core (0.9.0)
|
||||||
|
@ -165,6 +167,7 @@ GEM
|
||||||
rake (~> 13.0)
|
rake (~> 13.0)
|
||||||
macaddr (1.7.2)
|
macaddr (1.7.2)
|
||||||
systemu (~> 2.6.5)
|
systemu (~> 2.6.5)
|
||||||
|
matrix (0.4.2)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mime-types (3.4.1)
|
mime-types (3.4.1)
|
||||||
|
@ -266,6 +269,7 @@ GEM
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
timecop (0.9.5)
|
timecop (0.9.5)
|
||||||
|
toxiproxy (2.0.2)
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
tzinfo (2.0.5)
|
tzinfo (2.0.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
|
@ -300,7 +304,7 @@ DEPENDENCIES
|
||||||
activesupport (~> 6.1.4.7)
|
activesupport (~> 6.1.4.7)
|
||||||
airborne (~> 0.3.7)
|
airborne (~> 0.3.7)
|
||||||
allure-rspec (~> 2.18.0)
|
allure-rspec (~> 2.18.0)
|
||||||
capybara (~> 3.35.0)
|
capybara (~> 3.37.1)
|
||||||
capybara-screenshot (~> 1.0.26)
|
capybara-screenshot (~> 1.0.26)
|
||||||
chemlab (~> 0.10)
|
chemlab (~> 0.10)
|
||||||
chemlab-library-www-gitlab-com (~> 0.1)
|
chemlab-library-www-gitlab-com (~> 0.1)
|
||||||
|
@ -310,7 +314,7 @@ DEPENDENCIES
|
||||||
faraday-retry (~> 2.0)
|
faraday-retry (~> 2.0)
|
||||||
fog-core (= 2.1.0)
|
fog-core (= 2.1.0)
|
||||||
fog-google (~> 1.19)
|
fog-google (~> 1.19)
|
||||||
gitlab-qa (~> 8, >= 8.7.0)
|
gitlab-qa (~> 8, >= 8.8.0)
|
||||||
influxdb-client (~> 1.17)
|
influxdb-client (~> 1.17)
|
||||||
knapsack (~> 4.0)
|
knapsack (~> 4.0)
|
||||||
nokogiri (~> 1.13, >= 1.13.9)
|
nokogiri (~> 1.13, >= 1.13.9)
|
||||||
|
|
|
@ -770,18 +770,18 @@
|
||||||
# responsibility of unit tests. These tests are about the structure of the HTML.
|
# responsibility of unit tests. These tests are about the structure of the HTML.
|
||||||
uri_substitution: *uri_substitution
|
uri_substitution: *uri_substitution
|
||||||
data_attribute_id_substitution:
|
data_attribute_id_substitution:
|
||||||
- regex: '(data-user|data-project|data-issue|data-iid|data-merge-request|data-milestone)(=")(\d+?)(")'
|
- regex: '(data-user|data-project|data-issue|data-iid|data-merge-request|data-milestone|data-label)(=")(\d+?)(")'
|
||||||
replacement: '\1\2ID\4'
|
replacement: '\1\2ID\4'
|
||||||
text_attribute_substitution:
|
text_attribute_substitution:
|
||||||
- regex: '(title)(=")(.+?)(")'
|
- regex: '(title)(=")([^"]*)(")'
|
||||||
replacement: '\1\2TEXT\4'
|
replacement: '\1\2TEXT\4'
|
||||||
path_attribute_id_substitution:
|
path_attribute_id_substitution:
|
||||||
- regex: '(group|project)(\d+)'
|
- regex: '(group|project)(\d+)'
|
||||||
replacement: '\1ID'
|
replacement: '\1ID'
|
||||||
markdown: |-
|
markdown: |-
|
||||||
Hi @gfm_user - thank you for reporting this bug (#1) we hope to fix it in %1.1 as part of !1
|
Hi @gfm_user - thank you for reporting this ~"UX bug" (#1) we hope to fix it in %1.1 as part of !1
|
||||||
html: |-
|
html: |-
|
||||||
<p data-sourcepos="1:1-1:92" dir="auto">Hi <a href="/gfm_user" data-reference-type="user" data-user="1" data-container="body" data-placement="top" class="gfm gfm-project_member js-user-link" title="John Doe1">@gfm_user</a> - thank you for reporting this bug (<a href="/group1/project1/-/issues/1" data-reference-type="issue" data-original="#1" data-link="false" data-link-reference="false" data-project="11" data-issue="11" data-project-path="group1/project1" data-iid="1" data-issue-type="issue" data-container="body" data-placement="top" title="My title 1" class="gfm gfm-issue">#1</a>) we hope to fix it in <a href="/group1/project1/-/milestones/1" data-reference-type="milestone" data-original="%1.1" data-link="false" data-link-reference="false" data-project="11" data-milestone="11" data-container="body" data-placement="top" title="" class="gfm gfm-milestone has-tooltip">%1.1</a> as part of <a href="/group1/project1/-/merge_requests/1" data-reference-type="merge_request" data-original="!1" data-link="false" data-link-reference="false" data-project="11" data-merge-request="11" data-project-path="group1/project1" data-iid="1" data-container="body" data-placement="top" title="My title 2" class="gfm gfm-merge_request">!1</a></p>
|
<p data-sourcepos="1:1-1:98" dir="auto">Hi <a href="/gfm_user" data-reference-type="user" data-user="1" data-container="body" data-placement="top" class="gfm gfm-project_member js-user-link" title="John Doe1">@gfm_user</a> - thank you for reporting this <span class="gl-label gl-label-sm"><a href="/groupID/projectID/-/issues?label_name=UX+bug" data-reference-type="label" data-original='~"UX bug"' data-link="false" data-link-reference="false" data-project="ID" data-label="2" data-container="body" data-placement="top" title="TEXT" class="gfm gfm-label has-tooltip gl-link gl-label-link"><span class="gl-label-text gl-label-text-light" data-container="body" data-html="true" style="background-color: #990000">UX bug</span></a></span> (<a href="/group1/project1/-/issues/1" data-reference-type="issue" data-original="#1" data-link="false" data-link-reference="false" data-project="11" data-issue="11" data-project-path="group1/project1" data-iid="1" data-issue-type="issue" data-container="body" data-placement="top" title="My title 1" class="gfm gfm-issue">#1</a>) we hope to fix it in <a href="/group1/project1/-/milestones/1" data-reference-type="milestone" data-original="%1.1" data-link="false" data-link-reference="false" data-project="11" data-milestone="11" data-container="body" data-placement="top" title="" class="gfm gfm-milestone has-tooltip">%1.1</a> as part of <a href="/group1/project1/-/merge_requests/1" data-reference-type="merge_request" data-original="!1" data-link="false" data-link-reference="false" data-project="11" data-merge-request="11" data-project-path="group1/project1" data-iid="1" data-container="body" data-placement="top" title="My title 2" class="gfm gfm-merge_request">!1</a></p>
|
||||||
- name: strike
|
- name: strike
|
||||||
markdown: |-
|
markdown: |-
|
||||||
~~del~~
|
~~del~~
|
||||||
|
|
|
@ -21,7 +21,9 @@ describe('~/content_editor/components/suggestions_dropdown', () => {
|
||||||
const exampleUser = { username: 'root', avatar_url: 'root_avatar.png', type: 'User' };
|
const exampleUser = { username: 'root', avatar_url: 'root_avatar.png', type: 'User' };
|
||||||
const exampleIssue = { iid: 123, title: 'Test Issue' };
|
const exampleIssue = { iid: 123, title: 'Test Issue' };
|
||||||
const exampleMergeRequest = { iid: 224, title: 'Test MR' };
|
const exampleMergeRequest = { iid: 224, title: 'Test MR' };
|
||||||
const exampleMilestone = { iid: 21, title: '1.3' };
|
const exampleMilestone1 = { iid: 21, title: '13' };
|
||||||
|
const exampleMilestone2 = { iid: 24, title: 'Milestone with spaces' };
|
||||||
|
|
||||||
const exampleCommand = {
|
const exampleCommand = {
|
||||||
name: 'due',
|
name: 'due',
|
||||||
description: 'Set due date',
|
description: 'Set due date',
|
||||||
|
@ -32,7 +34,19 @@ describe('~/content_editor/components/suggestions_dropdown', () => {
|
||||||
title: '❓ Remote Development | Solution validation',
|
title: '❓ Remote Development | Solution validation',
|
||||||
reference: 'gitlab-org&8884',
|
reference: 'gitlab-org&8884',
|
||||||
};
|
};
|
||||||
const exampleLabel = {
|
const exampleLabel1 = {
|
||||||
|
title: 'Create',
|
||||||
|
color: '#E44D2A',
|
||||||
|
type: 'GroupLabel',
|
||||||
|
textColor: '#FFFFFF',
|
||||||
|
};
|
||||||
|
const exampleLabel2 = {
|
||||||
|
title: 'Weekly Team Announcement',
|
||||||
|
color: '#E44D2A',
|
||||||
|
type: 'GroupLabel',
|
||||||
|
textColor: '#FFFFFF',
|
||||||
|
};
|
||||||
|
const exampleLabel3 = {
|
||||||
title: 'devops::create',
|
title: 'devops::create',
|
||||||
color: '#E44D2A',
|
color: '#E44D2A',
|
||||||
type: 'GroupLabel',
|
type: 'GroupLabel',
|
||||||
|
@ -67,10 +81,13 @@ describe('~/content_editor/components/suggestions_dropdown', () => {
|
||||||
${'reference'} | ${'user'} | ${'@'} | ${exampleUser} | ${`@root`} | ${{}}
|
${'reference'} | ${'user'} | ${'@'} | ${exampleUser} | ${`@root`} | ${{}}
|
||||||
${'reference'} | ${'issue'} | ${'#'} | ${exampleIssue} | ${`#123`} | ${{}}
|
${'reference'} | ${'issue'} | ${'#'} | ${exampleIssue} | ${`#123`} | ${{}}
|
||||||
${'reference'} | ${'merge_request'} | ${'!'} | ${exampleMergeRequest} | ${`!224`} | ${{}}
|
${'reference'} | ${'merge_request'} | ${'!'} | ${exampleMergeRequest} | ${`!224`} | ${{}}
|
||||||
${'reference'} | ${'milestone'} | ${'%'} | ${exampleMilestone} | ${`%1.3`} | ${{}}
|
${'reference'} | ${'milestone'} | ${'%'} | ${exampleMilestone1} | ${`%13`} | ${{}}
|
||||||
${'reference'} | ${'command'} | ${'/'} | ${exampleCommand} | ${'/due '} | ${{}}
|
${'reference'} | ${'milestone'} | ${'%'} | ${exampleMilestone2} | ${`%Milestone with spaces`} | ${{ originalText: '%"Milestone with spaces"' }}
|
||||||
|
${'reference'} | ${'command'} | ${'/'} | ${exampleCommand} | ${'/due'} | ${{}}
|
||||||
${'reference'} | ${'epic'} | ${'&'} | ${exampleEpic} | ${`gitlab-org&8884`} | ${{}}
|
${'reference'} | ${'epic'} | ${'&'} | ${exampleEpic} | ${`gitlab-org&8884`} | ${{}}
|
||||||
${'reference'} | ${'label'} | ${'~'} | ${exampleLabel} | ${`~devops::create`} | ${{}}
|
${'reference'} | ${'label'} | ${'~'} | ${exampleLabel1} | ${`Create`} | ${{}}
|
||||||
|
${'reference'} | ${'label'} | ${'~'} | ${exampleLabel2} | ${`Weekly Team Announcement`} | ${{ originalText: '~"Weekly Team Announcement"' }}
|
||||||
|
${'reference'} | ${'label'} | ${'~'} | ${exampleLabel3} | ${`devops::create`} | ${{ originalText: '~"devops::create"', text: 'devops::create' }}
|
||||||
${'reference'} | ${'vulnerability'} | ${'[vulnerability:'} | ${exampleVulnerability} | ${`[vulnerability:60850147]`} | ${{}}
|
${'reference'} | ${'vulnerability'} | ${'[vulnerability:'} | ${exampleVulnerability} | ${`[vulnerability:60850147]`} | ${{}}
|
||||||
${'reference'} | ${'snippet'} | ${'$'} | ${exampleSnippet} | ${`$2420859`} | ${{}}
|
${'reference'} | ${'snippet'} | ${'$'} | ${exampleSnippet} | ${`$2420859`} | ${{}}
|
||||||
${'emoji'} | ${'emoji'} | ${':'} | ${exampleEmoji} | ${`😃`} | ${insertedEmojiProps}
|
${'emoji'} | ${'emoji'} | ${':'} | ${exampleEmoji} | ${`😃`} | ${insertedEmojiProps}
|
||||||
|
@ -130,7 +147,7 @@ describe('~/content_editor/components/suggestions_dropdown', () => {
|
||||||
referenceType | char | reference | displaysID
|
referenceType | char | reference | displaysID
|
||||||
${'issue'} | ${'#'} | ${exampleIssue} | ${true}
|
${'issue'} | ${'#'} | ${exampleIssue} | ${true}
|
||||||
${'merge_request'} | ${'!'} | ${exampleMergeRequest} | ${true}
|
${'merge_request'} | ${'!'} | ${exampleMergeRequest} | ${true}
|
||||||
${'milestone'} | ${'%'} | ${exampleMilestone} | ${false}
|
${'milestone'} | ${'%'} | ${exampleMilestone1} | ${false}
|
||||||
`('rendering $referenceType references', ({ referenceType, char, reference, displaysID }) => {
|
`('rendering $referenceType references', ({ referenceType, char, reference, displaysID }) => {
|
||||||
it(`displays ${referenceType} ID and title`, () => {
|
it(`displays ${referenceType} ID and title`, () => {
|
||||||
buildWrapper({
|
buildWrapper({
|
||||||
|
@ -172,20 +189,26 @@ describe('~/content_editor/components/suggestions_dropdown', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rendering label references', () => {
|
describe('rendering label references', () => {
|
||||||
it('displays label title and color', () => {
|
it.each`
|
||||||
|
label | displayedTitle | displayedColor
|
||||||
|
${exampleLabel1} | ${'Create'} | ${'rgb(228, 77, 42)' /* #E44D2A */}
|
||||||
|
${exampleLabel2} | ${'Weekly Team Announcement'} | ${'rgb(228, 77, 42)' /* #E44D2A */}
|
||||||
|
${exampleLabel3} | ${'devops::create'} | ${'rgb(228, 77, 42)' /* #E44D2A */}
|
||||||
|
`('displays label title and color', ({ label, displayedTitle, displayedColor }) => {
|
||||||
buildWrapper({
|
buildWrapper({
|
||||||
propsData: {
|
propsData: {
|
||||||
char: '~',
|
char: '~',
|
||||||
nodeProps: {
|
nodeProps: {
|
||||||
referenceType: 'label',
|
referenceType: 'label',
|
||||||
},
|
},
|
||||||
items: [exampleLabel],
|
items: [label],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(wrapper.text()).toContain(`${exampleLabel.title}`);
|
expect(wrapper.text()).toContain(displayedTitle);
|
||||||
|
expect(wrapper.text()).not.toContain('"'); // no quotes in the dropdown list
|
||||||
expect(wrapper.findByTestId('label-color-box').attributes().style).toEqual(
|
expect(wrapper.findByTestId('label-color-box').attributes().style).toEqual(
|
||||||
`background-color: rgb(228, 77, 42);`, // #E44D2A
|
`background-color: ${displayedColor};`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { GlLabel } from '@gitlab/ui';
|
||||||
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
|
import LabelWrapper from '~/content_editor/components/wrappers/label.vue';
|
||||||
|
|
||||||
|
describe('content/components/wrappers/label', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const createWrapper = async (node = {}) => {
|
||||||
|
wrapper = shallowMountExtended(LabelWrapper, {
|
||||||
|
propsData: { node },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders a GlLabel with the node's text and color", () => {
|
||||||
|
createWrapper({ attrs: { color: '#ff0000', text: 'foo bar', originalText: '~"foo bar"' } });
|
||||||
|
|
||||||
|
const glLabel = wrapper.findComponent(GlLabel);
|
||||||
|
|
||||||
|
expect(glLabel.props()).toMatchObject(
|
||||||
|
expect.objectContaining({
|
||||||
|
title: 'foo bar',
|
||||||
|
backgroundColor: '#ff0000',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a scoped label if there is a "::" in the label', () => {
|
||||||
|
createWrapper({ attrs: { color: '#ff0000', text: 'foo::bar', originalText: '~"foo::bar"' } });
|
||||||
|
|
||||||
|
expect(wrapper.findComponent(GlLabel).props().scoped).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -13,6 +13,8 @@ RSpec.shared_context 'API::Markdown Golden Master shared context' do |markdown_y
|
||||||
let_it_be(:project) { create(:project, :public, :repository, group: group) }
|
let_it_be(:project) { create(:project, :public, :repository, group: group) }
|
||||||
|
|
||||||
let_it_be(:label) { create(:label, project: project, title: 'bug') }
|
let_it_be(:label) { create(:label, project: project, title: 'bug') }
|
||||||
|
let_it_be(:label2) { create(:label, project: project, title: 'UX bug') }
|
||||||
|
|
||||||
let_it_be(:milestone) { create(:milestone, project: project, title: '1.1') }
|
let_it_be(:milestone) { create(:milestone, project: project, title: '1.1') }
|
||||||
let_it_be(:issue) { create(:issue, project: project) }
|
let_it_be(:issue) { create(:issue, project: project) }
|
||||||
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
|
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
|
||||||
|
|
Loading…
Reference in a new issue