Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
811f549164
commit
6b19945915
|
@ -14,6 +14,7 @@
|
||||||
GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
|
GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
|
||||||
GITLAB_ADMIN_USERNAME: "root"
|
GITLAB_ADMIN_USERNAME: "root"
|
||||||
GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
|
GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
|
||||||
|
GITLAB_QA_ADMIN_ACCESS_TOKEN: "${REVIEW_APPS_ROOT_TOKEN}"
|
||||||
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
|
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
|
||||||
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
|
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
|
||||||
SIGNUP_DISABLED: "true"
|
SIGNUP_DISABLED: "true"
|
||||||
|
|
|
@ -54,6 +54,7 @@ export default class CreateMergeRequestDropdown {
|
||||||
this.isCreatingBranch = false;
|
this.isCreatingBranch = false;
|
||||||
this.isCreatingMergeRequest = false;
|
this.isCreatingMergeRequest = false;
|
||||||
this.isGettingRef = false;
|
this.isGettingRef = false;
|
||||||
|
this.refCancelToken = null;
|
||||||
this.mergeRequestCreated = false;
|
this.mergeRequestCreated = false;
|
||||||
this.refDebounce = debounce((value, target) => this.getRef(value, target), 500);
|
this.refDebounce = debounce((value, target) => this.getRef(value, target), 500);
|
||||||
this.refIsValid = true;
|
this.refIsValid = true;
|
||||||
|
@ -101,9 +102,18 @@ export default class CreateMergeRequestDropdown {
|
||||||
'click',
|
'click',
|
||||||
this.onClickCreateMergeRequestButton.bind(this),
|
this.onClickCreateMergeRequestButton.bind(this),
|
||||||
);
|
);
|
||||||
|
this.branchInput.addEventListener('input', this.onChangeInput.bind(this));
|
||||||
this.branchInput.addEventListener('keyup', this.onChangeInput.bind(this));
|
this.branchInput.addEventListener('keyup', this.onChangeInput.bind(this));
|
||||||
this.dropdownToggle.addEventListener('click', this.onClickSetFocusOnBranchNameInput.bind(this));
|
this.dropdownToggle.addEventListener('click', this.onClickSetFocusOnBranchNameInput.bind(this));
|
||||||
|
// Detect for example when user pastes ref using the mouse
|
||||||
|
this.refInput.addEventListener('input', this.onChangeInput.bind(this));
|
||||||
|
// Detect for example when user presses right arrow to apply the suggested ref
|
||||||
this.refInput.addEventListener('keyup', this.onChangeInput.bind(this));
|
this.refInput.addEventListener('keyup', this.onChangeInput.bind(this));
|
||||||
|
// Detect when user clicks inside the input to apply the suggested ref
|
||||||
|
this.refInput.addEventListener('click', this.onChangeInput.bind(this));
|
||||||
|
// Detect when user clicks outside the input to apply the suggested ref
|
||||||
|
this.refInput.addEventListener('blur', this.onChangeInput.bind(this));
|
||||||
|
// Detect when user presses tab to apply the suggested ref
|
||||||
this.refInput.addEventListener('keydown', CreateMergeRequestDropdown.processTab.bind(this));
|
this.refInput.addEventListener('keydown', CreateMergeRequestDropdown.processTab.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,8 +257,12 @@ export default class CreateMergeRequestDropdown {
|
||||||
getRef(ref, target = 'all') {
|
getRef(ref, target = 'all') {
|
||||||
if (!ref) return false;
|
if (!ref) return false;
|
||||||
|
|
||||||
|
this.refCancelToken = axios.CancelToken.source();
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.get(`${createEndpoint(this.projectPath, this.refsPath)}${encodeURIComponent(ref)}`)
|
.get(`${createEndpoint(this.projectPath, this.refsPath)}${encodeURIComponent(ref)}`, {
|
||||||
|
cancelToken: this.refCancelToken.token,
|
||||||
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
const branches = data[Object.keys(data)[0]];
|
const branches = data[Object.keys(data)[0]];
|
||||||
const tags = data[Object.keys(data)[1]];
|
const tags = data[Object.keys(data)[1]];
|
||||||
|
@ -267,7 +281,10 @@ export default class CreateMergeRequestDropdown {
|
||||||
|
|
||||||
return this.updateInputState(target, ref, result);
|
return this.updateInputState(target, ref, result);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((thrown) => {
|
||||||
|
if (axios.isCancel(thrown)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
this.unavailable();
|
this.unavailable();
|
||||||
this.disable();
|
this.disable();
|
||||||
createFlash({
|
createFlash({
|
||||||
|
@ -325,14 +342,23 @@ export default class CreateMergeRequestDropdown {
|
||||||
let target;
|
let target;
|
||||||
let value;
|
let value;
|
||||||
|
|
||||||
|
// User changed input, cancel to prevent previous request from interfering
|
||||||
|
if (this.refCancelToken !== null) {
|
||||||
|
this.refCancelToken.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
if (event.target === this.branchInput) {
|
if (event.target === this.branchInput) {
|
||||||
target = 'branch';
|
target = 'branch';
|
||||||
({ value } = this.branchInput);
|
({ value } = this.branchInput);
|
||||||
} else if (event.target === this.refInput) {
|
} else if (event.target === this.refInput) {
|
||||||
target = 'ref';
|
target = 'ref';
|
||||||
value =
|
if (event.target === document.activeElement) {
|
||||||
event.target.value.slice(0, event.target.selectionStart) +
|
value =
|
||||||
event.target.value.slice(event.target.selectionEnd);
|
event.target.value.slice(0, event.target.selectionStart) +
|
||||||
|
event.target.value.slice(event.target.selectionEnd);
|
||||||
|
} else {
|
||||||
|
value = event.target.value;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -358,6 +384,7 @@ export default class CreateMergeRequestDropdown {
|
||||||
|
|
||||||
this.enable();
|
this.enable();
|
||||||
this.showAvailableMessage(target);
|
this.showAvailableMessage(target);
|
||||||
|
this.refDebounce(value, target);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,7 +441,8 @@ export default class CreateMergeRequestDropdown {
|
||||||
if (!selectedText || this.refInput.dataset.value === this.suggestedRef) return;
|
if (!selectedText || this.refInput.dataset.value === this.suggestedRef) return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
window.getSelection().removeAllRanges();
|
const caretPositionEnd = this.refInput.value.length;
|
||||||
|
this.refInput.setSelectionRange(caretPositionEnd, caretPositionEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeMessage(target) {
|
removeMessage(target) {
|
||||||
|
|
|
@ -392,8 +392,6 @@ export default {
|
||||||
diffsApp.instrument();
|
diffsApp.instrument();
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.mergeRequestContainers = document.querySelectorAll('.merge-request-container');
|
|
||||||
|
|
||||||
this.adjustView();
|
this.adjustView();
|
||||||
this.subscribeToEvents();
|
this.subscribeToEvents();
|
||||||
|
|
||||||
|
@ -521,13 +519,6 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.removeEventListeners();
|
this.removeEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isFluidLayout && this.glFeatures.mrChangesFluidLayout) {
|
|
||||||
this.mergeRequestContainers.forEach((el) => {
|
|
||||||
el.classList.toggle('limit-container-width', !this.shouldShow);
|
|
||||||
el.classList.toggle('container-limited', !this.shouldShow);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setEventListeners() {
|
setEventListeners() {
|
||||||
Mousetrap.bind(keysFor(MR_PREVIOUS_FILE_IN_DIFF), () => this.jumpToFile(-1));
|
Mousetrap.bind(keysFor(MR_PREVIOUS_FILE_IN_DIFF), () => this.jumpToFile(-1));
|
||||||
|
|
|
@ -1,42 +1,58 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import eventHub from '~/projects/new/event_hub';
|
||||||
|
|
||||||
function setVisibilityOptions(namespaceSelector) {
|
// Values are from lib/gitlab/visibility_level.rb
|
||||||
if (!namespaceSelector || !('selectedIndex' in namespaceSelector)) {
|
const visibilityLevel = {
|
||||||
return;
|
private: 0,
|
||||||
}
|
internal: 10,
|
||||||
const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex];
|
public: 20,
|
||||||
const { name, visibility, visibilityLevel, showPath, editPath } = selectedNamespace.dataset;
|
};
|
||||||
|
|
||||||
|
function setVisibilityOptions({ name, visibility, showPath, editPath }) {
|
||||||
document.querySelectorAll('.visibility-level-setting .form-check').forEach((option) => {
|
document.querySelectorAll('.visibility-level-setting .form-check').forEach((option) => {
|
||||||
const optionInput = option.querySelector('input[type=radio]');
|
// Don't change anything if the option is restricted by admin
|
||||||
const optionValue = optionInput ? optionInput.value : 0;
|
if (option.classList.contains('restricted')) {
|
||||||
const optionTitle = option.querySelector('.option-title');
|
return;
|
||||||
const optionName = optionTitle ? optionTitle.innerText.toLowerCase() : '';
|
}
|
||||||
|
|
||||||
// don't change anything if the option is restricted by admin
|
const optionInput = option.querySelector('input[type=radio]');
|
||||||
if (!option.classList.contains('restricted')) {
|
const optionValue = optionInput ? parseInt(optionInput.value, 10) : 0;
|
||||||
if (visibilityLevel < optionValue) {
|
|
||||||
option.classList.add('disabled');
|
if (visibilityLevel[visibility] < optionValue) {
|
||||||
optionInput.disabled = true;
|
option.classList.add('disabled');
|
||||||
const reason = option.querySelector('.option-disabled-reason');
|
optionInput.disabled = true;
|
||||||
if (reason) {
|
const reason = option.querySelector('.option-disabled-reason');
|
||||||
reason.innerHTML = `This project cannot be ${optionName} because the visibility of
|
if (reason) {
|
||||||
|
const optionTitle = option.querySelector('.option-title');
|
||||||
|
const optionName = optionTitle ? optionTitle.innerText.toLowerCase() : '';
|
||||||
|
reason.innerHTML = `This project cannot be ${optionName} because the visibility of
|
||||||
<a href="${showPath}">${name}</a> is ${visibility}. To make this project
|
<a href="${showPath}">${name}</a> is ${visibility}. To make this project
|
||||||
${optionName}, you must first <a href="${editPath}">change the visibility</a>
|
${optionName}, you must first <a href="${editPath}">change the visibility</a>
|
||||||
of the parent group.`;
|
of the parent group.`;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
option.classList.remove('disabled');
|
|
||||||
optionInput.disabled = false;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
option.classList.remove('disabled');
|
||||||
|
optionInput.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSelect2DropdownChange(namespaceSelector) {
|
||||||
|
if (!namespaceSelector || !('selectedIndex' in namespaceSelector)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex];
|
||||||
|
setVisibilityOptions(selectedNamespace.dataset);
|
||||||
|
}
|
||||||
|
|
||||||
export default function initProjectVisibilitySelector() {
|
export default function initProjectVisibilitySelector() {
|
||||||
|
eventHub.$on('update-visibility', setVisibilityOptions);
|
||||||
|
|
||||||
const namespaceSelector = document.querySelector('select.js-select-namespace');
|
const namespaceSelector = document.querySelector('select.js-select-namespace');
|
||||||
if (namespaceSelector) {
|
if (namespaceSelector) {
|
||||||
$('.select2.js-select-namespace').on('change', () => setVisibilityOptions(namespaceSelector));
|
$('.select2.js-select-namespace').on('change', () =>
|
||||||
setVisibilityOptions(namespaceSelector);
|
handleSelect2DropdownChange(namespaceSelector),
|
||||||
|
);
|
||||||
|
handleSelect2DropdownChange(namespaceSelector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ import {
|
||||||
GlDropdownItem,
|
GlDropdownItem,
|
||||||
GlDropdownText,
|
GlDropdownText,
|
||||||
GlDropdownSectionHeader,
|
GlDropdownSectionHeader,
|
||||||
GlLoadingIcon,
|
|
||||||
GlSearchBoxByType,
|
GlSearchBoxByType,
|
||||||
} from '@gitlab/ui';
|
} from '@gitlab/ui';
|
||||||
|
import { joinPaths } from '~/lib/utils/url_utility';
|
||||||
import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
|
import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
|
||||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
import Tracking from '~/tracking';
|
import Tracking from '~/tracking';
|
||||||
|
@ -24,7 +24,6 @@ export default {
|
||||||
GlDropdownItem,
|
GlDropdownItem,
|
||||||
GlDropdownText,
|
GlDropdownText,
|
||||||
GlDropdownSectionHeader,
|
GlDropdownSectionHeader,
|
||||||
GlLoadingIcon,
|
|
||||||
GlSearchBoxByType,
|
GlSearchBoxByType,
|
||||||
},
|
},
|
||||||
mixins: [Tracking.mixin()],
|
mixins: [Tracking.mixin()],
|
||||||
|
@ -103,6 +102,15 @@ export default {
|
||||||
focusInput() {
|
focusInput() {
|
||||||
this.$refs.search.focusInput();
|
this.$refs.search.focusInput();
|
||||||
},
|
},
|
||||||
|
handleDropdownItemClick(namespace) {
|
||||||
|
eventHub.$emit('update-visibility', {
|
||||||
|
name: namespace.name,
|
||||||
|
visibility: namespace.visibility,
|
||||||
|
showPath: namespace.webUrl,
|
||||||
|
editPath: joinPaths(namespace.webUrl, '-', 'edit'),
|
||||||
|
});
|
||||||
|
this.setNamespace(namespace);
|
||||||
|
},
|
||||||
handleSelectTemplate(groupId) {
|
handleSelectTemplate(groupId) {
|
||||||
this.groupToFilterBy = this.userGroups.find(
|
this.groupToFilterBy = this.userGroups.find(
|
||||||
(group) => getIdFromGraphQLId(group.id) === groupId,
|
(group) => getIdFromGraphQLId(group.id) === groupId,
|
||||||
|
@ -134,23 +142,23 @@ export default {
|
||||||
<gl-search-box-by-type
|
<gl-search-box-by-type
|
||||||
ref="search"
|
ref="search"
|
||||||
v-model.trim="search"
|
v-model.trim="search"
|
||||||
|
:is-loading="$apollo.queries.currentUser.loading"
|
||||||
data-qa-selector="select_namespace_dropdown_search_field"
|
data-qa-selector="select_namespace_dropdown_search_field"
|
||||||
/>
|
/>
|
||||||
<gl-loading-icon v-if="$apollo.queries.currentUser.loading" />
|
<template v-if="!$apollo.queries.currentUser.loading">
|
||||||
<template v-else>
|
|
||||||
<template v-if="hasGroupMatches">
|
<template v-if="hasGroupMatches">
|
||||||
<gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header>
|
<gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header>
|
||||||
<gl-dropdown-item
|
<gl-dropdown-item
|
||||||
v-for="group of filteredGroups"
|
v-for="group of filteredGroups"
|
||||||
:key="group.id"
|
:key="group.id"
|
||||||
@click="setNamespace(group)"
|
@click="handleDropdownItemClick(group)"
|
||||||
>
|
>
|
||||||
{{ group.fullPath }}
|
{{ group.fullPath }}
|
||||||
</gl-dropdown-item>
|
</gl-dropdown-item>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="hasNamespaceMatches">
|
<template v-if="hasNamespaceMatches">
|
||||||
<gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header>
|
<gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header>
|
||||||
<gl-dropdown-item @click="setNamespace(userNamespace)">
|
<gl-dropdown-item @click="handleDropdownItemClick(userNamespace)">
|
||||||
{{ userNamespace.fullPath }}
|
{{ userNamespace.fullPath }}
|
||||||
</gl-dropdown-item>
|
</gl-dropdown-item>
|
||||||
</template>
|
</template>
|
||||||
|
@ -158,6 +166,11 @@ export default {
|
||||||
</template>
|
</template>
|
||||||
</gl-dropdown>
|
</gl-dropdown>
|
||||||
|
|
||||||
<input type="hidden" name="project[namespace_id]" :value="selectedNamespace.id" />
|
<input
|
||||||
|
id="project_namespace_id"
|
||||||
|
type="hidden"
|
||||||
|
name="project[namespace_id]"
|
||||||
|
:value="selectedNamespace.id"
|
||||||
|
/>
|
||||||
</gl-button-group>
|
</gl-button-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,6 +4,9 @@ query searchNamespacesWhereUserCanCreateProjects($search: String) {
|
||||||
nodes {
|
nodes {
|
||||||
id
|
id
|
||||||
fullPath
|
fullPath
|
||||||
|
name
|
||||||
|
visibility
|
||||||
|
webUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -69,8 +69,7 @@ export default {
|
||||||
if (isScopedLabel(candidateLabel)) {
|
if (isScopedLabel(candidateLabel)) {
|
||||||
const scopedKeyWithDelimiter = `${scopedLabelKey(candidateLabel)}${SCOPED_LABEL_DELIMITER}`;
|
const scopedKeyWithDelimiter = `${scopedLabelKey(candidateLabel)}${SCOPED_LABEL_DELIMITER}`;
|
||||||
const currentActiveScopedLabel = state.labels.find(
|
const currentActiveScopedLabel = state.labels.find(
|
||||||
({ set, title }) =>
|
({ title }) => title.startsWith(scopedKeyWithDelimiter) && title !== candidateLabel.title,
|
||||||
set && title.startsWith(scopedKeyWithDelimiter) && title !== candidateLabel.title,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (currentActiveScopedLabel) {
|
if (currentActiveScopedLabel) {
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'WorkItemRoot',
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div></div>
|
<div>
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const widgetTypes = {
|
||||||
|
title: 'TITLE',
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
{"__schema":{"types":[{"kind":"INTERFACE","name":"WorkItemWidget","possibleTypes":[{"name":"TitleWidget"}]}]}}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import VueApollo from 'vue-apollo';
|
||||||
|
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
|
||||||
|
import createDefaultClient from '~/lib/graphql';
|
||||||
|
import workItemQuery from './work_item.query.graphql';
|
||||||
|
import introspectionQueryResultData from './fragmentTypes.json';
|
||||||
|
|
||||||
|
const fragmentMatcher = new IntrospectionFragmentMatcher({
|
||||||
|
introspectionQueryResultData,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function createApolloProvider() {
|
||||||
|
Vue.use(VueApollo);
|
||||||
|
|
||||||
|
const defaultClient = createDefaultClient(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
cacheConfig: {
|
||||||
|
fragmentMatcher,
|
||||||
|
},
|
||||||
|
assumeImmutableResults: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
defaultClient.cache.writeQuery({
|
||||||
|
query: workItemQuery,
|
||||||
|
variables: {
|
||||||
|
id: '1',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
workItem: {
|
||||||
|
__typename: 'WorkItem',
|
||||||
|
id: '1',
|
||||||
|
type: 'FEATURE',
|
||||||
|
widgets: {
|
||||||
|
__typename: 'WorkItemWidgetConnection',
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
__typename: 'TitleWidget',
|
||||||
|
type: 'TITLE',
|
||||||
|
enabled: true,
|
||||||
|
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||||
|
contentText: 'Test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return new VueApollo({
|
||||||
|
defaultClient,
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
enum WorkItemType {
|
||||||
|
FEATURE
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WidgetType {
|
||||||
|
TITLE
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkItemWidget {
|
||||||
|
type: WidgetType!
|
||||||
|
}
|
||||||
|
|
||||||
|
# Replicating Relay connection type for client schema
|
||||||
|
type WorkItemWidgetEdge {
|
||||||
|
cursor: String!
|
||||||
|
node: WorkItemWidget
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkItemWidgetConnection {
|
||||||
|
edges: [WorkItemWidgetEdge]
|
||||||
|
nodes: [WorkItemWidget]
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TitleWidget implements WorkItemWidget {
|
||||||
|
type: WidgetType!
|
||||||
|
contentText: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkItem {
|
||||||
|
id: ID!
|
||||||
|
type: WorkItemType!
|
||||||
|
widgets: [WorkItemWidgetConnection]
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
workItem(id: ID!): WorkItem!
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
fragment WidgetBase on WorkItemWidget {
|
||||||
|
type
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
#import './widget.fragment.graphql'
|
||||||
|
|
||||||
|
query WorkItem($id: ID!) {
|
||||||
|
workItem(id: $id) @client {
|
||||||
|
id
|
||||||
|
type
|
||||||
|
widgets {
|
||||||
|
nodes {
|
||||||
|
...WidgetBase
|
||||||
|
... on TitleWidget {
|
||||||
|
contentText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import App from './components/app.vue';
|
import App from './components/app.vue';
|
||||||
|
import { createRouter } from './router';
|
||||||
|
import { createApolloProvider } from './graphql/provider';
|
||||||
|
|
||||||
export const initWorkItemsRoot = () => {
|
export const initWorkItemsRoot = () => {
|
||||||
const el = document.querySelector('#js-work-items');
|
const el = document.querySelector('#js-work-items');
|
||||||
|
|
||||||
return new Vue({
|
return new Vue({
|
||||||
el,
|
el,
|
||||||
|
router: createRouter(el.dataset.fullPath),
|
||||||
|
apolloProvider: createApolloProvider(),
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
return createElement(App);
|
return createElement(App);
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
<script>
|
||||||
|
import workItemQuery from '../graphql/work_item.query.graphql';
|
||||||
|
import { widgetTypes } from '../constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
workItem: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
workItem: {
|
||||||
|
query: workItemQuery,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
titleWidgetData() {
|
||||||
|
return this.workItem?.widgets?.nodes?.find((widget) => widget.type === widgetTypes.title);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<!-- Title widget placeholder -->
|
||||||
|
<div>
|
||||||
|
<h2 v-if="titleWidgetData" class="title" data-testid="title">
|
||||||
|
{{ titleWidgetData.contentText }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
|
@ -0,0 +1,14 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import VueRouter from 'vue-router';
|
||||||
|
import { joinPaths } from '~/lib/utils/url_utility';
|
||||||
|
import { routes } from './routes';
|
||||||
|
|
||||||
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
export function createRouter(fullPath) {
|
||||||
|
return new VueRouter({
|
||||||
|
routes,
|
||||||
|
mode: 'history',
|
||||||
|
base: joinPaths(fullPath, '-', 'work_items'),
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export const routes = [
|
||||||
|
{
|
||||||
|
path: '/:id',
|
||||||
|
name: 'work_item',
|
||||||
|
component: () => import('../pages/work_item_root.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
];
|
|
@ -163,7 +163,8 @@ class ApplicationController < ActionController::Base
|
||||||
|
|
||||||
payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
|
payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
|
||||||
payload[:metadata] = @current_context
|
payload[:metadata] = @current_context
|
||||||
|
payload[:request_urgency] = urgency&.name
|
||||||
|
payload[:target_duration_s] = urgency&.duration
|
||||||
logged_user = auth_user
|
logged_user = auth_user
|
||||||
if logged_user.present?
|
if logged_user.present?
|
||||||
payload[:user_id] = logged_user.try(:id)
|
payload[:user_id] = logged_user.try(:id)
|
||||||
|
|
|
@ -38,6 +38,6 @@ module WorkhorseAuthorization
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_extension_whitelist
|
def file_extension_whitelist
|
||||||
ImportExportUploader::EXTENSION_WHITELIST
|
ImportExportUploader::EXTENSION_ALLOWLIST
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -780,6 +780,10 @@ module Ci
|
||||||
strong_memoize(:legacy_trigger) { trigger_requests.first }
|
strong_memoize(:legacy_trigger) { trigger_requests.first }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def variables_builder
|
||||||
|
@variables_builder ||= ::Gitlab::Ci::Variables::Builder.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
def persisted_variables
|
def persisted_variables
|
||||||
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
||||||
break variables unless persisted?
|
break variables unless persisted?
|
||||||
|
@ -1254,6 +1258,12 @@ module Ci
|
||||||
self.builds.latest.build_matchers(project)
|
self.builds.latest.build_matchers(project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def predefined_vars_in_builder_enabled?
|
||||||
|
strong_memoize(:predefined_vars_in_builder_enabled) do
|
||||||
|
Feature.enabled?(:ci_predefined_vars_in_builder, project, default_enabled: :yaml)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def add_message(severity, content)
|
def add_message(severity, content)
|
||||||
|
|
|
@ -10,8 +10,10 @@ module Ci
|
||||||
# Variables in the environment name scope.
|
# Variables in the environment name scope.
|
||||||
#
|
#
|
||||||
def scoped_variables(environment: expanded_environment_name, dependencies: true)
|
def scoped_variables(environment: expanded_environment_name, dependencies: true)
|
||||||
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
track_duration do
|
||||||
variables.concat(predefined_variables)
|
variables = pipeline.variables_builder.scoped_variables(self, environment: environment, dependencies: dependencies)
|
||||||
|
|
||||||
|
variables.concat(predefined_variables) unless pipeline.predefined_vars_in_builder_enabled?
|
||||||
variables.concat(project.predefined_variables)
|
variables.concat(project.predefined_variables)
|
||||||
variables.concat(pipeline.predefined_variables)
|
variables.concat(pipeline.predefined_variables)
|
||||||
variables.concat(runner.predefined_variables) if runnable? && runner
|
variables.concat(runner.predefined_variables) if runnable? && runner
|
||||||
|
@ -25,9 +27,23 @@ module Ci
|
||||||
variables.concat(trigger_request.user_variables) if trigger_request
|
variables.concat(trigger_request.user_variables) if trigger_request
|
||||||
variables.concat(pipeline.variables)
|
variables.concat(pipeline.variables)
|
||||||
variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
|
variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
|
||||||
|
|
||||||
|
variables
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def track_duration
|
||||||
|
start_time = ::Gitlab::Metrics::System.monotonic_time
|
||||||
|
result = yield
|
||||||
|
duration = ::Gitlab::Metrics::System.monotonic_time - start_time
|
||||||
|
|
||||||
|
::Gitlab::Ci::Pipeline::Metrics
|
||||||
|
.pipeline_builder_scoped_variables_histogram
|
||||||
|
.observe({}, duration.seconds)
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# Variables that do not depend on the environment name.
|
# Variables that do not depend on the environment name.
|
||||||
#
|
#
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
module BulkImports
|
module BulkImports
|
||||||
class ExportUploader < ImportExportUploader
|
class ExportUploader < ImportExportUploader
|
||||||
EXTENSION_WHITELIST = %w[ndjson.gz].freeze
|
EXTENSION_ALLOWLIST = %w[ndjson.gz].freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ImportExportUploader < AttachmentUploader
|
class ImportExportUploader < AttachmentUploader
|
||||||
EXTENSION_WHITELIST = %w[tar.gz gz].freeze
|
EXTENSION_ALLOWLIST = %w[tar.gz gz].freeze
|
||||||
|
|
||||||
def self.workhorse_local_upload_path
|
def self.workhorse_local_upload_path
|
||||||
File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
|
File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
|
||||||
end
|
end
|
||||||
|
|
||||||
def extension_whitelist
|
def extension_whitelist
|
||||||
EXTENSION_WHITELIST
|
EXTENSION_ALLOWLIST
|
||||||
end
|
end
|
||||||
|
|
||||||
def move_to_cache
|
def move_to_cache
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
- page_title s_('WorkItem|Work Items')
|
- page_title s_('WorkItem|Work Items')
|
||||||
|
|
||||||
#js-work-items
|
#js-work-items{ data: { full_path: @project.full_path } }
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
= visibility_level_label(level)
|
= visibility_level_label(level)
|
||||||
.option-description
|
.option-description
|
||||||
= visibility_level_description(level, form_model)
|
= visibility_level_description(level, form_model)
|
||||||
|
.option-disabled-reason
|
||||||
|
|
||||||
.text-muted
|
.text-muted
|
||||||
- if all_visibility_levels_restricted?
|
- if all_visibility_levels_restricted?
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: ci_predefined_vars_in_builder
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72348
|
||||||
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/231300
|
||||||
|
milestone: '14.4'
|
||||||
|
type: development
|
||||||
|
group: group::pipeline authoring
|
||||||
|
default_enabled: false
|
|
@ -19,7 +19,10 @@ Marginalia::Comment.components = [:application, :correlation_id, :jid, :endpoint
|
||||||
# adding :line has some overhead because a regexp on the backtrace has
|
# adding :line has some overhead because a regexp on the backtrace has
|
||||||
# to be run on every SQL query. Only enable this in development because
|
# to be run on every SQL query. Only enable this in development because
|
||||||
# we've seen it slow things down.
|
# we've seen it slow things down.
|
||||||
Marginalia::Comment.components << :line if Rails.env.development?
|
if Rails.env.development?
|
||||||
|
Marginalia::Comment.components << :line
|
||||||
|
Marginalia::Comment.lines_to_ignore = Regexp.union(Gitlab::BacktraceCleaner::IGNORE_BACKTRACES + %w(lib/ruby/gems/ lib/gem_extensions/ lib/ruby/))
|
||||||
|
end
|
||||||
|
|
||||||
Gitlab::Marginalia.set_application_name
|
Gitlab::Marginalia.set_application_name
|
||||||
|
|
||||||
|
|
|
@ -358,7 +358,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
||||||
get 'details', on: :member
|
get 'details', on: :member
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :work_items, only: [:index]
|
get 'work_items/*work_items_path' => 'work_items#index', as: :work_items
|
||||||
|
|
||||||
resource :tracing, only: [:show]
|
resource :tracing, only: [:show]
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ The following metrics are available:
|
||||||
| `gitlab_cache_misses_total` | Counter | 10.2 | Cache read miss | `controller`, `action` |
|
| `gitlab_cache_misses_total` | Counter | 10.2 | Cache read miss | `controller`, `action` |
|
||||||
| `gitlab_cache_operation_duration_seconds` | Histogram | 10.2 | Cache access time | |
|
| `gitlab_cache_operation_duration_seconds` | Histogram | 10.2 | Cache access time | |
|
||||||
| `gitlab_cache_operations_total` | Counter | 12.2 | Cache operations by controller or action | `controller`, `action`, `operation` |
|
| `gitlab_cache_operations_total` | Counter | 12.2 | Cache operations by controller or action | `controller`, `action`, `operation` |
|
||||||
|
| `gitlab_ci_pipeline_builder_scoped_variables_duration` | Histogram | 14.5 | Time in seconds it takes to create the scoped variables for a CI/CD job
|
||||||
| `gitlab_ci_pipeline_creation_duration_seconds` | Histogram | 13.0 | Time in seconds it takes to create a CI/CD pipeline | |
|
| `gitlab_ci_pipeline_creation_duration_seconds` | Histogram | 13.0 | Time in seconds it takes to create a CI/CD pipeline | |
|
||||||
| `gitlab_ci_pipeline_size_builds` | Histogram | 13.1 | Total number of builds within a pipeline grouped by a pipeline source | `source` |
|
| `gitlab_ci_pipeline_size_builds` | Histogram | 13.1 | Total number of builds within a pipeline grouped by a pipeline source | `source` |
|
||||||
| `job_waiter_started_total` | Counter | 12.9 | Number of batches of jobs started where a web request is waiting for the jobs to complete | `worker` |
|
| `job_waiter_started_total` | Counter | 12.9 | Number of batches of jobs started where a web request is waiting for the jobs to complete | `worker` |
|
||||||
|
|
|
@ -4042,6 +4042,7 @@ Input type: `ScanExecutionPolicyCommitInput`
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="mutationscanexecutionpolicycommitclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
| <a id="mutationscanexecutionpolicycommitclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||||
|
| <a id="mutationscanexecutionpolicycommitname"></a>`name` | [`String`](#string) | Name of the policy. If the name is null, the `name` field from `policy_yaml` is used. |
|
||||||
| <a id="mutationscanexecutionpolicycommitoperationmode"></a>`operationMode` | [`MutationOperationMode!`](#mutationoperationmode) | Changes the operation mode. |
|
| <a id="mutationscanexecutionpolicycommitoperationmode"></a>`operationMode` | [`MutationOperationMode!`](#mutationoperationmode) | Changes the operation mode. |
|
||||||
| <a id="mutationscanexecutionpolicycommitpolicyyaml"></a>`policyYaml` | [`String!`](#string) | YAML snippet of the policy. |
|
| <a id="mutationscanexecutionpolicycommitpolicyyaml"></a>`policyYaml` | [`String!`](#string) | YAML snippet of the policy. |
|
||||||
| <a id="mutationscanexecutionpolicycommitprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
|
| <a id="mutationscanexecutionpolicycommitprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
|
||||||
|
|
|
@ -94,9 +94,9 @@ Parameters:
|
||||||
| `restrict_to_branch` | string | false | Comma-separated list of branches to be are automatically inspected. Leave blank to include all branches. |
|
| `restrict_to_branch` | string | false | Comma-separated list of branches to be are automatically inspected. Leave blank to include all branches. |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Asana integration
|
### Disable Asana integration
|
||||||
|
|
||||||
Delete Asana integration for a project.
|
Disable the Asana integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/asana
|
DELETE /projects/:id/integrations/asana
|
||||||
|
@ -130,9 +130,9 @@ Parameters:
|
||||||
| `subdomain` | string | false | The subdomain setting |
|
| `subdomain` | string | false | The subdomain setting |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Assembla integration
|
### Disable Assembla integration
|
||||||
|
|
||||||
Delete Assembla integration for a project.
|
Disable the Assembla integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/assembla
|
DELETE /projects/:id/integrations/assembla
|
||||||
|
@ -170,9 +170,9 @@ Parameters:
|
||||||
| `password` | string | true | Password of the user |
|
| `password` | string | true | Password of the user |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Atlassian Bamboo CI integration
|
### Disable Atlassian Bamboo CI integration
|
||||||
|
|
||||||
Delete Atlassian Bamboo CI integration for a project.
|
Disable the Atlassian Bamboo CI integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/bamboo
|
DELETE /projects/:id/integrations/bamboo
|
||||||
|
@ -209,9 +209,9 @@ Parameters:
|
||||||
| `title` | string | false | Title |
|
| `title` | string | false | Title |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Bugzilla integration
|
### Disable Bugzilla integration
|
||||||
|
|
||||||
Delete Bugzilla integration for a project.
|
Disable the Bugzilla integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/bugzilla
|
DELETE /projects/:id/integrations/bugzilla
|
||||||
|
@ -246,9 +246,9 @@ Parameters:
|
||||||
| `enable_ssl_verification` | boolean | false | DEPRECATED: This parameter has no effect since SSL verification is always enabled |
|
| `enable_ssl_verification` | boolean | false | DEPRECATED: This parameter has no effect since SSL verification is always enabled |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Buildkite integration
|
### Disable Buildkite integration
|
||||||
|
|
||||||
Delete Buildkite integration for a project.
|
Disable the Buildkite integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/buildkite
|
DELETE /projects/:id/integrations/buildkite
|
||||||
|
@ -284,9 +284,9 @@ Parameters:
|
||||||
| `room` | string | false | Campfire room. The last part of the URL when you're in a room. |
|
| `room` | string | false | Campfire room. The last part of the URL when you're in a room. |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events. |
|
| `push_events` | boolean | false | Enable notifications for push events. |
|
||||||
|
|
||||||
### Delete Campfire integration
|
### Disable Campfire integration
|
||||||
|
|
||||||
Delete Campfire integration for a project.
|
Disable the Campfire integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/campfire
|
DELETE /projects/:id/integrations/campfire
|
||||||
|
@ -322,9 +322,9 @@ Parameters:
|
||||||
| `datadog_service` | string | false | Name of this GitLab instance that all data will be tagged with |
|
| `datadog_service` | string | false | Name of this GitLab instance that all data will be tagged with |
|
||||||
| `datadog_env` | string | false | The environment tag that traces will be tagged with |
|
| `datadog_env` | string | false | The environment tag that traces will be tagged with |
|
||||||
|
|
||||||
### Delete Datadog integration
|
### Disable Datadog integration
|
||||||
|
|
||||||
Delete Datadog integration for a project.
|
Disable the Datadog integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/datadog
|
DELETE /projects/:id/integrations/datadog
|
||||||
|
@ -367,9 +367,9 @@ Parameters:
|
||||||
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
||||||
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
||||||
|
|
||||||
### Delete Unify Circuit integration
|
### Disable Unify Circuit integration
|
||||||
|
|
||||||
Delete Unify Circuit integration for a project.
|
Disable the Unify Circuit integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/unify-circuit
|
DELETE /projects/:id/integrations/unify-circuit
|
||||||
|
@ -412,9 +412,9 @@ Parameters:
|
||||||
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
||||||
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
||||||
|
|
||||||
### Delete Webex Teams integration
|
### Disable Webex Teams integration
|
||||||
|
|
||||||
Delete Webex Teams integration for a project.
|
Disable the Webex Teams integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/webex-teams
|
DELETE /projects/:id/integrations/webex-teams
|
||||||
|
@ -451,9 +451,9 @@ Parameters:
|
||||||
| `title` | string | false | Title |
|
| `title` | string | false | Title |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Custom Issue Tracker integration
|
### Disable Custom Issue Tracker integration
|
||||||
|
|
||||||
Delete Custom Issue Tracker integration for a project.
|
Disable the Custom Issue Tracker integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/custom-issue-tracker
|
DELETE /projects/:id/integrations/custom-issue-tracker
|
||||||
|
@ -485,9 +485,9 @@ Parameters:
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `webhook` | string | true | Discord webhook. For example, `https://discord.com/api/webhooks/…` |
|
| `webhook` | string | true | Discord webhook. For example, `https://discord.com/api/webhooks/…` |
|
||||||
|
|
||||||
### Delete Discord integration
|
### Disable Discord integration
|
||||||
|
|
||||||
Delete Discord integration for a project.
|
Disable the Discord integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/discord
|
DELETE /projects/:id/integrations/discord
|
||||||
|
@ -524,9 +524,9 @@ Parameters:
|
||||||
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
||||||
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
||||||
|
|
||||||
### Delete Drone CI integration
|
### Disable Drone CI integration
|
||||||
|
|
||||||
Delete Drone CI integration for a project.
|
Disable the Drone CI integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/drone-ci
|
DELETE /projects/:id/integrations/drone-ci
|
||||||
|
@ -563,9 +563,9 @@ Parameters:
|
||||||
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
||||||
| `branches_to_be_notified` | string | false | Branches to send notifications for. Valid options are "all", "default", "protected", and "default_and_protected". Notifications are always fired for tag pushes. The default value is "all" |
|
| `branches_to_be_notified` | string | false | Branches to send notifications for. Valid options are "all", "default", "protected", and "default_and_protected". Notifications are always fired for tag pushes. The default value is "all" |
|
||||||
|
|
||||||
### Delete Emails on Push integration
|
### Disable Emails on Push integration
|
||||||
|
|
||||||
Delete Emails on Push integration for a project.
|
Disable the Emails on Push integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/emails-on-push
|
DELETE /projects/:id/integrations/emails-on-push
|
||||||
|
@ -599,9 +599,9 @@ Parameters:
|
||||||
| `project_url` | string | true | The URL to the project in EWM |
|
| `project_url` | string | true | The URL to the project in EWM |
|
||||||
| `issues_url` | string | true | The URL to view an issue in EWM. Must contain `:id` |
|
| `issues_url` | string | true | The URL to view an issue in EWM. Must contain `:id` |
|
||||||
|
|
||||||
### Delete EWM integration
|
### Disable EWM integration
|
||||||
|
|
||||||
Delete EWM integration for a project.
|
Disable the EWM integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/ewm
|
DELETE /projects/:id/integrations/ewm
|
||||||
|
@ -635,9 +635,9 @@ Parameters:
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `confluence_url` | string | true | The URL of the Confluence Cloud Workspace hosted on atlassian.net. |
|
| `confluence_url` | string | true | The URL of the Confluence Cloud Workspace hosted on atlassian.net. |
|
||||||
|
|
||||||
### Delete Confluence integration
|
### Disable Confluence integration
|
||||||
|
|
||||||
Delete Confluence integration for a project.
|
Disable the Confluence integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/confluence
|
DELETE /projects/:id/integrations/confluence
|
||||||
|
@ -669,9 +669,9 @@ Parameters:
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `external_wiki_url` | string | true | The URL of the external wiki |
|
| `external_wiki_url` | string | true | The URL of the external wiki |
|
||||||
|
|
||||||
### Delete External wiki integration
|
### Disable External wiki integration
|
||||||
|
|
||||||
Delete External wiki integration for a project.
|
Disable the External wiki integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/external-wiki
|
DELETE /projects/:id/integrations/external-wiki
|
||||||
|
@ -706,9 +706,9 @@ Parameters:
|
||||||
| `token` | string | true | Flowdock Git source token |
|
| `token` | string | true | Flowdock Git source token |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Flowdock integration
|
### Disable Flowdock integration
|
||||||
|
|
||||||
Delete Flowdock integration for a project.
|
Disable the Flowdock integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/flowdock
|
DELETE /projects/:id/integrations/flowdock
|
||||||
|
@ -742,9 +742,9 @@ Parameters:
|
||||||
| `repository_url` | string | true | GitHub repository URL |
|
| `repository_url` | string | true | GitHub repository URL |
|
||||||
| `static_context` | boolean | false | Append instance name instead of branch to [status check name](../user/project/integrations/github.md#static--dynamic-status-check-names) |
|
| `static_context` | boolean | false | Append instance name instead of branch to [status check name](../user/project/integrations/github.md#static--dynamic-status-check-names) |
|
||||||
|
|
||||||
### Delete GitHub integration
|
### Disable GitHub integration
|
||||||
|
|
||||||
Delete GitHub integration for a project.
|
Disable the GitHub integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/github
|
DELETE /projects/:id/integrations/github
|
||||||
|
@ -788,9 +788,9 @@ Parameters:
|
||||||
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
||||||
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
||||||
|
|
||||||
### Delete Hangouts Chat integration
|
### Disable Hangouts Chat integration
|
||||||
|
|
||||||
Delete Hangouts Chat integration for a project.
|
Disable the Hangouts Chat integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/hangouts-chat
|
DELETE /projects/:id/integrations/hangouts-chat
|
||||||
|
@ -829,9 +829,9 @@ Parameters:
|
||||||
| `colorize_messages` | boolean | false | Colorize messages |
|
| `colorize_messages` | boolean | false | Colorize messages |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Irker (IRC gateway) integration
|
### Disable Irker (IRC gateway) integration
|
||||||
|
|
||||||
Delete Irker (IRC gateway) integration for a project.
|
Disable the Irker (IRC gateway) integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/irker
|
DELETE /projects/:id/integrations/irker
|
||||||
|
@ -880,9 +880,9 @@ Parameters:
|
||||||
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
||||||
| `comment_on_event_enabled` | boolean | false | Enable comments inside Jira issues on each GitLab event (commit / merge request) |
|
| `comment_on_event_enabled` | boolean | false | Enable comments inside Jira issues on each GitLab event (commit / merge request) |
|
||||||
|
|
||||||
### Delete Jira integration
|
### Disable Jira integration
|
||||||
|
|
||||||
Remove all previously Jira integrations from a project.
|
Disable the Jira integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/jira
|
DELETE /projects/:id/integrations/jira
|
||||||
|
@ -939,9 +939,9 @@ Parameters:
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `token` | string | yes | The Slack token |
|
| `token` | string | yes | The Slack token |
|
||||||
|
|
||||||
### Delete Slack Slash Command integration
|
### Disable Slack Slash Command integration
|
||||||
|
|
||||||
Delete Slack Slash Command integration for a project.
|
Disable the Slack Slash Command integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/slack-slash-commands
|
DELETE /projects/:id/integrations/slack-slash-commands
|
||||||
|
@ -974,9 +974,9 @@ Parameters:
|
||||||
| `token` | string | yes | The Mattermost token |
|
| `token` | string | yes | The Mattermost token |
|
||||||
| `username` | string | no | The username to use to post the message |
|
| `username` | string | no | The username to use to post the message |
|
||||||
|
|
||||||
### Delete Mattermost Slash Command integration
|
### Disable Mattermost Slash Command integration
|
||||||
|
|
||||||
Delete Mattermost Slash Command integration for a project.
|
Disable the Mattermost Slash Command integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/mattermost-slash-commands
|
DELETE /projects/:id/integrations/mattermost-slash-commands
|
||||||
|
@ -1005,9 +1005,9 @@ Parameters:
|
||||||
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
|
||||||
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
|
||||||
|
|
||||||
### Delete Packagist integration
|
### Disable Packagist integration
|
||||||
|
|
||||||
Delete Packagist integration for a project.
|
Disable the Packagist integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/packagist
|
DELETE /projects/:id/integrations/packagist
|
||||||
|
@ -1044,9 +1044,9 @@ Parameters:
|
||||||
| `notify_only_default_branch` | boolean | no | Send notifications only for the default branch ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/28271)) |
|
| `notify_only_default_branch` | boolean | no | Send notifications only for the default branch ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/28271)) |
|
||||||
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
||||||
|
|
||||||
### Delete Pipeline-Emails integration
|
### Disable Pipeline-Emails integration
|
||||||
|
|
||||||
Delete Pipeline-Emails integration for a project.
|
Disable the Pipeline-Emails integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/pipelines-email
|
DELETE /projects/:id/integrations/pipelines-email
|
||||||
|
@ -1082,9 +1082,9 @@ Parameters:
|
||||||
| `restrict_to_branch` | boolean | false | Comma-separated list of branches to automatically inspect. Leave blank to include all branches. |
|
| `restrict_to_branch` | boolean | false | Comma-separated list of branches to automatically inspect. Leave blank to include all branches. |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Pivotal Tracker integration
|
### Disable Pivotal Tracker integration
|
||||||
|
|
||||||
Delete Pivotal Tracker integration for a project.
|
Disable the Pivotal Tracker integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/pivotaltracker
|
DELETE /projects/:id/integrations/pivotaltracker
|
||||||
|
@ -1118,9 +1118,9 @@ Parameters:
|
||||||
| `google_iap_audience_client_id` | string | false | Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com) |
|
| `google_iap_audience_client_id` | string | false | Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com) |
|
||||||
| `google_iap_service_account_json` | string | false | `credentials.json` file for your service account, like { "type": "service_account", "project_id": ... } |
|
| `google_iap_service_account_json` | string | false | `credentials.json` file for your service account, like { "type": "service_account", "project_id": ... } |
|
||||||
|
|
||||||
### Delete Prometheus integration
|
### Disable Prometheus integration
|
||||||
|
|
||||||
Delete Prometheus integration for a project.
|
Disable the Prometheus integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/prometheus
|
DELETE /projects/:id/integrations/prometheus
|
||||||
|
@ -1157,9 +1157,9 @@ Parameters:
|
||||||
| `sound` | string | false | The sound of the notification |
|
| `sound` | string | false | The sound of the notification |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Pushover integration
|
### Disable Pushover integration
|
||||||
|
|
||||||
Delete Pushover integration for a project.
|
Disable the Pushover integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/pushover
|
DELETE /projects/:id/integrations/pushover
|
||||||
|
@ -1195,9 +1195,9 @@ Parameters:
|
||||||
| `description` | string | false | Description |
|
| `description` | string | false | Description |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete Redmine integration
|
### Disable Redmine integration
|
||||||
|
|
||||||
Delete Redmine integration for a project.
|
Disable the Redmine integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/redmine
|
DELETE /projects/:id/integrations/redmine
|
||||||
|
@ -1256,9 +1256,9 @@ Parameters:
|
||||||
| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications |
|
| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications |
|
||||||
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
||||||
|
|
||||||
### Delete Slack integration
|
### Disable Slack integration
|
||||||
|
|
||||||
Delete Slack integration for a project.
|
Disable the Slack integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/slack
|
DELETE /projects/:id/integrations/slack
|
||||||
|
@ -1302,9 +1302,9 @@ Parameters:
|
||||||
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
|
||||||
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
|
||||||
|
|
||||||
### Delete Microsoft Teams integration
|
### Disable Microsoft Teams integration
|
||||||
|
|
||||||
Delete Microsoft Teams integration for a project.
|
Disable the Microsoft Teams integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/microsoft-teams
|
DELETE /projects/:id/integrations/microsoft-teams
|
||||||
|
@ -1359,9 +1359,9 @@ Parameters:
|
||||||
| `pipeline_channel` | string | false | The name of the channel to receive pipeline events notifications |
|
| `pipeline_channel` | string | false | The name of the channel to receive pipeline events notifications |
|
||||||
| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications |
|
| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications |
|
||||||
|
|
||||||
### Delete Mattermost notifications integration
|
### Disable Mattermost notifications integration
|
||||||
|
|
||||||
Delete Mattermost notifications integration for a project.
|
Disable the Mattermost notifications integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/mattermost
|
DELETE /projects/:id/integrations/mattermost
|
||||||
|
@ -1399,9 +1399,9 @@ Parameters:
|
||||||
| `password` | string | true | The password of the user |
|
| `password` | string | true | The password of the user |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete JetBrains TeamCity CI integration
|
### Disable JetBrains TeamCity CI integration
|
||||||
|
|
||||||
Delete JetBrains TeamCity CI integration for a project.
|
Disable the JetBrains TeamCity CI integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/teamcity
|
DELETE /projects/:id/integrations/teamcity
|
||||||
|
@ -1439,9 +1439,9 @@ Parameters:
|
||||||
| `merge_requests_events` | boolean | false | Enable notifications for merge request events. |
|
| `merge_requests_events` | boolean | false | Enable notifications for merge request events. |
|
||||||
| `tag_push_events` | boolean | false | Enable notifications for tag push events. |
|
| `tag_push_events` | boolean | false | Enable notifications for tag push events. |
|
||||||
|
|
||||||
### Delete Jenkins CI integration
|
### Disable Jenkins CI integration
|
||||||
|
|
||||||
Delete Jenkins CI integration for a project.
|
Disable the Jenkins CI integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/jenkins
|
DELETE /projects/:id/integrations/jenkins
|
||||||
|
@ -1476,9 +1476,9 @@ Parameters:
|
||||||
- `multiproject_enabled` (optional) - Multi-project mode is configured in Jenkins GitLab Hook plugin
|
- `multiproject_enabled` (optional) - Multi-project mode is configured in Jenkins GitLab Hook plugin
|
||||||
- `pass_unstable` (optional) - Unstable builds are treated as passing
|
- `pass_unstable` (optional) - Unstable builds are treated as passing
|
||||||
|
|
||||||
### Delete Jenkins CI (Deprecated) integration
|
### Disable Jenkins CI (Deprecated) integration
|
||||||
|
|
||||||
Delete Jenkins CI (Deprecated) integration for a project.
|
Disable the Jenkins CI (Deprecated) integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/jenkins-deprecated
|
DELETE /projects/:id/integrations/jenkins-deprecated
|
||||||
|
@ -1512,9 +1512,9 @@ Parameters:
|
||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `mock_service_url` | string | true | `http://localhost:4004` |
|
| `mock_service_url` | string | true | `http://localhost:4004` |
|
||||||
|
|
||||||
### Delete MockCI integration
|
### Disable MockCI integration
|
||||||
|
|
||||||
Delete MockCI integration for a project.
|
Disable the MockCI integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/mock-ci
|
DELETE /projects/:id/integrations/mock-ci
|
||||||
|
@ -1549,9 +1549,9 @@ Parameters:
|
||||||
| `description` | string | false | Description |
|
| `description` | string | false | Description |
|
||||||
| `push_events` | boolean | false | Enable notifications for push events |
|
| `push_events` | boolean | false | Enable notifications for push events |
|
||||||
|
|
||||||
### Delete YouTrack integration
|
### Disable YouTrack integration
|
||||||
|
|
||||||
Delete YouTrack integration for a project.
|
Disable the YouTrack integration for a project. Integration settings are preserved.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
DELETE /projects/:id/integrations/youtrack
|
DELETE /projects/:id/integrations/youtrack
|
||||||
|
|
|
@ -27,7 +27,8 @@ module API
|
||||||
Gitlab::GrapeLogging::Loggers::PerfLogger.new,
|
Gitlab::GrapeLogging::Loggers::PerfLogger.new,
|
||||||
Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new,
|
Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new,
|
||||||
Gitlab::GrapeLogging::Loggers::ContextLogger.new,
|
Gitlab::GrapeLogging::Loggers::ContextLogger.new,
|
||||||
Gitlab::GrapeLogging::Loggers::ContentLogger.new
|
Gitlab::GrapeLogging::Loggers::ContentLogger.new,
|
||||||
|
Gitlab::GrapeLogging::Loggers::UrgencyLogger.new
|
||||||
]
|
]
|
||||||
|
|
||||||
allow_access_with_scope :api
|
allow_access_with_scope :api
|
||||||
|
|
|
@ -5,7 +5,7 @@ module API
|
||||||
module FileUploadHelpers
|
module FileUploadHelpers
|
||||||
def file_is_valid?
|
def file_is_valid?
|
||||||
filename = params[:file]&.original_filename
|
filename = params[:file]&.original_filename
|
||||||
filename && ImportExportUploader::EXTENSION_WHITELIST.include?(File.extname(filename).delete('.'))
|
filename && ImportExportUploader::EXTENSION_ALLOWLIST.include?(File.extname(filename).delete('.'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_file!
|
def validate_file!
|
||||||
|
|
|
@ -51,6 +51,15 @@ module Gitlab
|
||||||
::Gitlab::Metrics.histogram(name, comment, labels, buckets)
|
::Gitlab::Metrics.histogram(name, comment, labels, buckets)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.pipeline_builder_scoped_variables_histogram
|
||||||
|
name = :gitlab_ci_pipeline_builder_scoped_variables_duration
|
||||||
|
comment = 'Pipeline variables builder scoped_variables duration'
|
||||||
|
labels = {}
|
||||||
|
buckets = [0.01, 0.05, 0.1, 0.3, 0.5, 1, 2, 5, 10, 30, 60, 120]
|
||||||
|
|
||||||
|
::Gitlab::Metrics.histogram(name, comment, labels, buckets)
|
||||||
|
end
|
||||||
|
|
||||||
def self.pipeline_processing_events_counter
|
def self.pipeline_processing_events_counter
|
||||||
name = :gitlab_ci_pipeline_processing_events_total
|
name = :gitlab_ci_pipeline_processing_events_total
|
||||||
comment = 'Total amount of pipeline processing events'
|
comment = 'Total amount of pipeline processing events'
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Ci
|
||||||
|
module Variables
|
||||||
|
class Builder
|
||||||
|
include ::Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
|
def initialize(pipeline)
|
||||||
|
@pipeline = pipeline
|
||||||
|
end
|
||||||
|
|
||||||
|
def scoped_variables(job, environment:, dependencies:)
|
||||||
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
||||||
|
variables.concat(predefined_variables(job)) if pipeline.predefined_vars_in_builder_enabled?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :pipeline
|
||||||
|
|
||||||
|
def predefined_variables(job)
|
||||||
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
||||||
|
variables.append(key: 'CI_JOB_NAME', value: job.name)
|
||||||
|
variables.append(key: 'CI_JOB_STAGE', value: job.stage)
|
||||||
|
variables.append(key: 'CI_JOB_MANUAL', value: 'true') if job.action?
|
||||||
|
variables.append(key: 'CI_PIPELINE_TRIGGERED', value: 'true') if job.trigger_request
|
||||||
|
|
||||||
|
variables.append(key: 'CI_NODE_INDEX', value: job.options[:instance].to_s) if job.options&.include?(:instance)
|
||||||
|
variables.append(key: 'CI_NODE_TOTAL', value: ci_node_total_value(job).to_s)
|
||||||
|
|
||||||
|
# legacy variables
|
||||||
|
variables.append(key: 'CI_BUILD_NAME', value: job.name)
|
||||||
|
variables.append(key: 'CI_BUILD_STAGE', value: job.stage)
|
||||||
|
variables.append(key: 'CI_BUILD_TRIGGERED', value: 'true') if job.trigger_request
|
||||||
|
variables.append(key: 'CI_BUILD_MANUAL', value: 'true') if job.action?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ci_node_total_value(job)
|
||||||
|
parallel = job.options&.dig(:parallel)
|
||||||
|
parallel = parallel.dig(:total) if parallel.is_a?(Hash)
|
||||||
|
parallel || 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module GrapeLogging
|
||||||
|
module Loggers
|
||||||
|
class UrgencyLogger < ::GrapeLogging::Loggers::Base
|
||||||
|
def parameters(request, _)
|
||||||
|
endpoint = request.env['api.endpoint']
|
||||||
|
return {} unless endpoint
|
||||||
|
|
||||||
|
urgency = endpoint.options[:for].try(:urgency_for_app, endpoint)
|
||||||
|
return {} unless urgency
|
||||||
|
|
||||||
|
{ request_urgency: urgency.name, target_duration_s: urgency.duration }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,31 +4,7 @@ module Gitlab
|
||||||
module HealthChecks
|
module HealthChecks
|
||||||
module Redis
|
module Redis
|
||||||
class CacheCheck
|
class CacheCheck
|
||||||
extend SimpleAbstractCheck
|
extend RedisAbstractCheck
|
||||||
|
|
||||||
class << self
|
|
||||||
def check_up
|
|
||||||
check
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def metric_prefix
|
|
||||||
'redis_cache_ping'
|
|
||||||
end
|
|
||||||
|
|
||||||
def successful?(result)
|
|
||||||
result == 'PONG'
|
|
||||||
end
|
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def check
|
|
||||||
catch_timeout 10.seconds do
|
|
||||||
Gitlab::Redis::Cache.with(&:ping)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,31 +4,7 @@ module Gitlab
|
||||||
module HealthChecks
|
module HealthChecks
|
||||||
module Redis
|
module Redis
|
||||||
class QueuesCheck
|
class QueuesCheck
|
||||||
extend SimpleAbstractCheck
|
extend RedisAbstractCheck
|
||||||
|
|
||||||
class << self
|
|
||||||
def check_up
|
|
||||||
check
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def metric_prefix
|
|
||||||
'redis_queues_ping'
|
|
||||||
end
|
|
||||||
|
|
||||||
def successful?(result)
|
|
||||||
result == 'PONG'
|
|
||||||
end
|
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def check
|
|
||||||
catch_timeout 10.seconds do
|
|
||||||
Gitlab::Redis::Queues.with(&:ping)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,31 +4,7 @@ module Gitlab
|
||||||
module HealthChecks
|
module HealthChecks
|
||||||
module Redis
|
module Redis
|
||||||
class RateLimitingCheck
|
class RateLimitingCheck
|
||||||
extend SimpleAbstractCheck
|
extend RedisAbstractCheck
|
||||||
|
|
||||||
class << self
|
|
||||||
def check_up
|
|
||||||
check
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def metric_prefix
|
|
||||||
'redis_rate_limiting_ping'
|
|
||||||
end
|
|
||||||
|
|
||||||
def successful?(result)
|
|
||||||
result == 'PONG'
|
|
||||||
end
|
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def check
|
|
||||||
catch_timeout 10.seconds do
|
|
||||||
Gitlab::Redis::RateLimiting.with(&:ping)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module HealthChecks
|
||||||
|
module Redis
|
||||||
|
module RedisAbstractCheck
|
||||||
|
include SimpleAbstractCheck
|
||||||
|
|
||||||
|
def check_up
|
||||||
|
successful?(check)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def redis_instance_class_name
|
||||||
|
Gitlab::Redis.const_get(redis_instance_name.camelize, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def metric_prefix
|
||||||
|
"redis_#{redis_instance_name}_ping"
|
||||||
|
end
|
||||||
|
|
||||||
|
def redis_instance_name
|
||||||
|
name.sub(/_check$/, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
def successful?(result)
|
||||||
|
result == 'PONG'
|
||||||
|
end
|
||||||
|
|
||||||
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
|
def check
|
||||||
|
catch_timeout 10.seconds do
|
||||||
|
redis_instance_class_name.with(&:ping)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# rubocop: enable CodeReuse/ActiveRecord
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,16 +14,22 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def successful?(result)
|
def successful?(result)
|
||||||
result == 'PONG'
|
result == true
|
||||||
end
|
end
|
||||||
|
|
||||||
def check
|
def check
|
||||||
::Gitlab::HealthChecks::Redis::CacheCheck.check_up &&
|
redis_health_checks.all?(&:check_up)
|
||||||
::Gitlab::HealthChecks::Redis::QueuesCheck.check_up &&
|
end
|
||||||
::Gitlab::HealthChecks::Redis::SharedStateCheck.check_up &&
|
|
||||||
::Gitlab::HealthChecks::Redis::TraceChunksCheck.check_up &&
|
def redis_health_checks
|
||||||
::Gitlab::HealthChecks::Redis::RateLimitingCheck.check_up &&
|
[
|
||||||
::Gitlab::HealthChecks::Redis::SessionsCheck.check_up
|
Gitlab::HealthChecks::Redis::CacheCheck,
|
||||||
|
Gitlab::HealthChecks::Redis::QueuesCheck,
|
||||||
|
Gitlab::HealthChecks::Redis::SharedStateCheck,
|
||||||
|
Gitlab::HealthChecks::Redis::TraceChunksCheck,
|
||||||
|
Gitlab::HealthChecks::Redis::RateLimitingCheck,
|
||||||
|
Gitlab::HealthChecks::Redis::SessionsCheck
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,31 +4,7 @@ module Gitlab
|
||||||
module HealthChecks
|
module HealthChecks
|
||||||
module Redis
|
module Redis
|
||||||
class SessionsCheck
|
class SessionsCheck
|
||||||
extend SimpleAbstractCheck
|
extend RedisAbstractCheck
|
||||||
|
|
||||||
class << self
|
|
||||||
def check_up
|
|
||||||
check
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def metric_prefix
|
|
||||||
'redis_sessions_ping'
|
|
||||||
end
|
|
||||||
|
|
||||||
def successful?(result)
|
|
||||||
result == 'PONG'
|
|
||||||
end
|
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def check
|
|
||||||
catch_timeout 10.seconds do
|
|
||||||
Gitlab::Redis::Sessions.with(&:ping)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,31 +4,7 @@ module Gitlab
|
||||||
module HealthChecks
|
module HealthChecks
|
||||||
module Redis
|
module Redis
|
||||||
class SharedStateCheck
|
class SharedStateCheck
|
||||||
extend SimpleAbstractCheck
|
extend RedisAbstractCheck
|
||||||
|
|
||||||
class << self
|
|
||||||
def check_up
|
|
||||||
check
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def metric_prefix
|
|
||||||
'redis_shared_state_ping'
|
|
||||||
end
|
|
||||||
|
|
||||||
def successful?(result)
|
|
||||||
result == 'PONG'
|
|
||||||
end
|
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def check
|
|
||||||
catch_timeout 10.seconds do
|
|
||||||
Gitlab::Redis::SharedState.with(&:ping)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,31 +4,7 @@ module Gitlab
|
||||||
module HealthChecks
|
module HealthChecks
|
||||||
module Redis
|
module Redis
|
||||||
class TraceChunksCheck
|
class TraceChunksCheck
|
||||||
extend SimpleAbstractCheck
|
extend RedisAbstractCheck
|
||||||
|
|
||||||
class << self
|
|
||||||
def check_up
|
|
||||||
check
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def metric_prefix
|
|
||||||
'redis_trace_chunks_ping'
|
|
||||||
end
|
|
||||||
|
|
||||||
def successful?(result)
|
|
||||||
result == 'PONG'
|
|
||||||
end
|
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
|
||||||
def check
|
|
||||||
catch_timeout 10.seconds do
|
|
||||||
Gitlab::Redis::TraceChunks.with(&:ping)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,10 +56,20 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def download(url, upload_path)
|
def download(url, upload_path)
|
||||||
File.open(upload_path, 'w') do |file|
|
File.open(upload_path, 'wb') do |file|
|
||||||
# Download (stream) file from the uploader's location
|
Gitlab::HTTP.get(url, stream_body: true) do |fragment|
|
||||||
IO.copy_stream(URI.parse(url).open, file)
|
if [301, 302, 307].include?(fragment.code)
|
||||||
|
Gitlab::Import::Logger.warn(message: "received redirect fragment", fragment_code: fragment.code)
|
||||||
|
elsif fragment.code == 200
|
||||||
|
file.write(fragment)
|
||||||
|
else
|
||||||
|
raise Gitlab::ImportExport::Error, "unsupported response downloading fragment #{fragment.code}"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
rescue StandardError => e
|
||||||
|
@shared.error(e) # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||||
|
raise e
|
||||||
end
|
end
|
||||||
|
|
||||||
def tar_with_options(archive:, dir:, options:)
|
def tar_with_options(archive:, dir:, options:)
|
||||||
|
|
|
@ -7,6 +7,8 @@ module Gitlab
|
||||||
|
|
||||||
LIMITED_ARRAY_SENTINEL = { key: 'truncated', value: '...' }.freeze
|
LIMITED_ARRAY_SENTINEL = { key: 'truncated', value: '...' }.freeze
|
||||||
IGNORE_PARAMS = Set.new(%w(controller action format)).freeze
|
IGNORE_PARAMS = Set.new(%w(controller action format)).freeze
|
||||||
|
KNOWN_PAYLOAD_PARAMS = [:remote_ip, :user_id, :username, :ua, :queue_duration_s,
|
||||||
|
:etag_route, :request_urgency, :target_duration_s] + CLOUDFLARE_CUSTOM_HEADERS.values
|
||||||
|
|
||||||
def self.call(event)
|
def self.call(event)
|
||||||
params = event
|
params = event
|
||||||
|
@ -14,24 +16,17 @@ module Gitlab
|
||||||
.each_with_object([]) { |(k, v), array| array << { key: k, value: v } unless IGNORE_PARAMS.include?(k) }
|
.each_with_object([]) { |(k, v), array| array << { key: k, value: v } unless IGNORE_PARAMS.include?(k) }
|
||||||
payload = {
|
payload = {
|
||||||
time: Time.now.utc.iso8601(3),
|
time: Time.now.utc.iso8601(3),
|
||||||
params: Gitlab::Utils::LogLimitedArray.log_limited_array(params, sentinel: LIMITED_ARRAY_SENTINEL),
|
params: Gitlab::Utils::LogLimitedArray.log_limited_array(params, sentinel: LIMITED_ARRAY_SENTINEL)
|
||||||
remote_ip: event.payload[:remote_ip],
|
|
||||||
user_id: event.payload[:user_id],
|
|
||||||
username: event.payload[:username],
|
|
||||||
ua: event.payload[:ua]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.merge!(event.payload[:metadata]) if event.payload[:metadata]
|
payload.merge!(event.payload[:metadata]) if event.payload[:metadata]
|
||||||
|
optional_payload_params = event.payload.slice(*KNOWN_PAYLOAD_PARAMS).compact
|
||||||
|
payload.merge!(optional_payload_params)
|
||||||
|
|
||||||
::Gitlab::InstrumentationHelper.add_instrumentation_data(payload)
|
::Gitlab::InstrumentationHelper.add_instrumentation_data(payload)
|
||||||
|
|
||||||
payload[:queue_duration_s] = event.payload[:queue_duration_s] if event.payload[:queue_duration_s]
|
|
||||||
payload[:etag_route] = event.payload[:etag_route] if event.payload[:etag_route]
|
|
||||||
payload[Labkit::Correlation::CorrelationId::LOG_KEY] = event.payload[Labkit::Correlation::CorrelationId::LOG_KEY] || Labkit::Correlation::CorrelationId.current_id
|
payload[Labkit::Correlation::CorrelationId::LOG_KEY] = event.payload[Labkit::Correlation::CorrelationId::LOG_KEY] || Labkit::Correlation::CorrelationId.current_id
|
||||||
|
|
||||||
CLOUDFLARE_CUSTOM_HEADERS.each do |_, value|
|
|
||||||
payload[value] = event.payload[value] if event.payload[value]
|
|
||||||
end
|
|
||||||
|
|
||||||
# https://github.com/roidrage/lograge#logging-errors--exceptions
|
# https://github.com/roidrage/lograge#logging-errors--exceptions
|
||||||
exception = event.payload[:exception_object]
|
exception = event.payload[:exception_object]
|
||||||
|
|
||||||
|
|
|
@ -30,10 +30,12 @@ module Gitlab
|
||||||
endpoint_id = API::Base.endpoint_id_for_route(route)
|
endpoint_id = API::Base.endpoint_id_for_route(route)
|
||||||
route_class = route.app.options[:for]
|
route_class = route.app.options[:for]
|
||||||
feature_category = route_class.feature_category_for_app(route.app)
|
feature_category = route_class.feature_category_for_app(route.app)
|
||||||
|
request_urgency = route_class.urgency_for_app(route.app)
|
||||||
|
|
||||||
{
|
{
|
||||||
endpoint_id: endpoint_id,
|
endpoint_id: endpoint_id,
|
||||||
feature_category: feature_category
|
feature_category: feature_category,
|
||||||
|
request_urgency: request_urgency.name
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -42,7 +44,8 @@ module Gitlab
|
||||||
Gitlab::RequestEndpoints.all_controller_actions.map do |controller, action|
|
Gitlab::RequestEndpoints.all_controller_actions.map do |controller, action|
|
||||||
{
|
{
|
||||||
endpoint_id: controller.endpoint_id_for_action(action),
|
endpoint_id: controller.endpoint_id_for_action(action),
|
||||||
feature_category: controller.feature_category_for_action(action)
|
feature_category: controller.feature_category_for_action(action),
|
||||||
|
request_urgency: controller.urgency_for_action(action).name
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -116,9 +116,11 @@ module Gitlab
|
||||||
def record_apdex_if_needed(env, elapsed)
|
def record_apdex_if_needed(env, elapsed)
|
||||||
return unless Gitlab::Metrics::RailsSlis.request_apdex_counters_enabled?
|
return unless Gitlab::Metrics::RailsSlis.request_apdex_counters_enabled?
|
||||||
|
|
||||||
|
urgency = urgency_for_env(env)
|
||||||
|
|
||||||
Gitlab::Metrics::RailsSlis.request_apdex.increment(
|
Gitlab::Metrics::RailsSlis.request_apdex.increment(
|
||||||
labels: labels_from_context,
|
labels: labels_from_context.merge(request_urgency: urgency.name),
|
||||||
success: satisfactory?(env, elapsed)
|
success: elapsed < urgency.duration
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -129,17 +131,15 @@ module Gitlab
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def satisfactory?(env, elapsed)
|
def urgency_for_env(env)
|
||||||
target =
|
endpoint_urgency =
|
||||||
if env['api.endpoint'].present?
|
if env['api.endpoint'].present?
|
||||||
env['api.endpoint'].options[:for].try(:urgency_for_app, env['api.endpoint'])
|
env['api.endpoint'].options[:for].try(:urgency_for_app, env['api.endpoint'])
|
||||||
elsif env['action_controller.instance'].present? && env['action_controller.instance'].respond_to?(:urgency)
|
elsif env['action_controller.instance'].present? && env['action_controller.instance'].respond_to?(:urgency)
|
||||||
env['action_controller.instance'].urgency
|
env['action_controller.instance'].urgency
|
||||||
end
|
end
|
||||||
|
|
||||||
target ||= Gitlab::EndpointAttributes::DEFAULT_URGENCY
|
endpoint_urgency || Gitlab::EndpointAttributes::DEFAULT_URGENCY
|
||||||
|
|
||||||
elapsed < target.duration
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -81,7 +81,7 @@ module QA
|
||||||
result = yield.tap do
|
result = yield.tap do
|
||||||
fabrication_time = Time.now - start
|
fabrication_time = Time.now - start
|
||||||
|
|
||||||
Support::FabricationTracker.save_fabrication(:"#{method}_fabrication", fabrication_time * 1000)
|
Support::FabricationTracker.save_fabrication(:"#{method}_fabrication", fabrication_time)
|
||||||
Runtime::Logger.debug do
|
Runtime::Logger.debug do
|
||||||
msg = ["==#{'=' * parents.size}>"]
|
msg = ["==#{'=' * parents.size}>"]
|
||||||
msg << "Built a #{name}"
|
msg << "Built a #{name}"
|
||||||
|
|
|
@ -27,7 +27,9 @@ module QA
|
||||||
:import_error
|
:import_error
|
||||||
|
|
||||||
attribute :group do
|
attribute :group do
|
||||||
Group.fabricate!
|
Group.fabricate! do |group|
|
||||||
|
group.api_client = api_client
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attribute :path_with_namespace do
|
attribute :path_with_namespace do
|
||||||
|
|
|
@ -104,7 +104,10 @@ module QA
|
||||||
source_issue # fabricate source group, project, issue
|
source_issue # fabricate source group, project, issue
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'successfully imports issue' do
|
it(
|
||||||
|
'successfully imports issue',
|
||||||
|
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/2325'
|
||||||
|
) do
|
||||||
expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
|
expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
|
||||||
|
|
||||||
aggregate_failures do
|
aggregate_failures do
|
||||||
|
|
|
@ -501,11 +501,16 @@ RSpec.describe ApplicationController do
|
||||||
describe '#append_info_to_payload' do
|
describe '#append_info_to_payload' do
|
||||||
controller(described_class) do
|
controller(described_class) do
|
||||||
attr_reader :last_payload
|
attr_reader :last_payload
|
||||||
|
urgency :high, [:foo]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render html: 'authenticated'
|
render html: 'authenticated'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def foo
|
||||||
|
render html: ''
|
||||||
|
end
|
||||||
|
|
||||||
def append_info_to_payload(payload)
|
def append_info_to_payload(payload)
|
||||||
super
|
super
|
||||||
|
|
||||||
|
@ -513,6 +518,13 @@ RSpec.describe ApplicationController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
routes.draw do
|
||||||
|
get 'index' => 'anonymous#index'
|
||||||
|
get 'foo' => 'anonymous#foo'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'does not log errors with a 200 response' do
|
it 'does not log errors with a 200 response' do
|
||||||
get :index
|
get :index
|
||||||
|
|
||||||
|
@ -534,6 +546,22 @@ RSpec.describe ApplicationController do
|
||||||
|
|
||||||
expect(controller.last_payload[:metadata]).to include('meta.user' => user.username)
|
expect(controller.last_payload[:metadata]).to include('meta.user' => user.username)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'urgency information' do
|
||||||
|
it 'adds default urgency information to the payload' do
|
||||||
|
get :index
|
||||||
|
|
||||||
|
expect(controller.last_payload[:request_urgency]).to eq(:default)
|
||||||
|
expect(controller.last_payload[:target_duration_s]).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds customized urgency information to the payload' do
|
||||||
|
get :foo
|
||||||
|
|
||||||
|
expect(controller.last_payload[:request_urgency]).to eq(:high)
|
||||||
|
expect(controller.last_payload[:target_duration_s]).to eq(0.25)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#access_denied' do
|
describe '#access_denied' do
|
||||||
|
|
|
@ -46,7 +46,10 @@ describe('CreateMergeRequestDropdown', () => {
|
||||||
dropdown
|
dropdown
|
||||||
.getRef('contains#hash')
|
.getRef('contains#hash')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect(axios.get).toHaveBeenCalledWith(endpoint);
|
expect(axios.get).toHaveBeenCalledWith(
|
||||||
|
endpoint,
|
||||||
|
expect.objectContaining({ cancelToken: expect.anything() }),
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.then(done)
|
.then(done)
|
||||||
.catch(done.fail);
|
.catch(done.fail);
|
||||||
|
|
|
@ -702,23 +702,4 @@ describe('diffs/components/app', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fluid layout', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
setFixtures(
|
|
||||||
'<div><div class="merge-request-container limit-container-width container-limited"></div></div>',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes limited container classes when on diffs tab', () => {
|
|
||||||
createComponent({ isFluidLayout: false, shouldShow: true }, () => {}, {
|
|
||||||
glFeatures: { mrChangesFluidLayout: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
const containerClassList = document.querySelector('.merge-request-container').classList;
|
|
||||||
|
|
||||||
expect(containerClassList).not.toContain('container-limited');
|
|
||||||
expect(containerClassList).not.toContain('limit-container-width');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,14 +24,23 @@ describe('NewProjectUrlSelect component', () => {
|
||||||
{
|
{
|
||||||
id: 'gid://gitlab/Group/26',
|
id: 'gid://gitlab/Group/26',
|
||||||
fullPath: 'flightjs',
|
fullPath: 'flightjs',
|
||||||
|
name: 'Flight JS',
|
||||||
|
visibility: 'public',
|
||||||
|
webUrl: 'http://127.0.0.1:3000/flightjs',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gid://gitlab/Group/28',
|
id: 'gid://gitlab/Group/28',
|
||||||
fullPath: 'h5bp',
|
fullPath: 'h5bp',
|
||||||
|
name: 'H5BP',
|
||||||
|
visibility: 'public',
|
||||||
|
webUrl: 'http://127.0.0.1:3000/h5bp',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gid://gitlab/Group/30',
|
id: 'gid://gitlab/Group/30',
|
||||||
fullPath: 'h5bp/subgroup',
|
fullPath: 'h5bp/subgroup',
|
||||||
|
name: 'H5BP Subgroup',
|
||||||
|
visibility: 'private',
|
||||||
|
webUrl: 'http://127.0.0.1:3000/h5bp/subgroup',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -79,6 +88,10 @@ describe('NewProjectUrlSelect component', () => {
|
||||||
const findDropdown = () => wrapper.findComponent(GlDropdown);
|
const findDropdown = () => wrapper.findComponent(GlDropdown);
|
||||||
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
|
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
|
||||||
const findHiddenInput = () => wrapper.find('input');
|
const findHiddenInput = () => wrapper.find('input');
|
||||||
|
const clickDropdownItem = async () => {
|
||||||
|
wrapper.findComponent(GlDropdownItem).vm.$emit('click');
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
};
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
|
@ -127,7 +140,6 @@ describe('NewProjectUrlSelect component', () => {
|
||||||
|
|
||||||
it('focuses on the input when the dropdown is opened', async () => {
|
it('focuses on the input when the dropdown is opened', async () => {
|
||||||
wrapper = mountComponent({ mountFn: mount });
|
wrapper = mountComponent({ mountFn: mount });
|
||||||
|
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
@ -140,7 +152,6 @@ describe('NewProjectUrlSelect component', () => {
|
||||||
|
|
||||||
it('renders expected dropdown items', async () => {
|
it('renders expected dropdown items', async () => {
|
||||||
wrapper = mountComponent({ mountFn: mount });
|
wrapper = mountComponent({ mountFn: mount });
|
||||||
|
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
@ -160,7 +171,6 @@ describe('NewProjectUrlSelect component', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
wrapper = mountComponent({ mountFn: mount });
|
wrapper = mountComponent({ mountFn: mount });
|
||||||
|
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
@ -195,23 +205,38 @@ describe('NewProjectUrlSelect component', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
wrapper = mountComponent({ search: 'no matches', queryResponse, mountFn: mount });
|
wrapper = mountComponent({ search: 'no matches', queryResponse, mountFn: mount });
|
||||||
|
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
expect(wrapper.find('li').text()).toBe('No matches found');
|
expect(wrapper.find('li').text()).toBe('No matches found');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates hidden input with selected namespace', async () => {
|
it('emits `update-visibility` event to update the visibility radio options', async () => {
|
||||||
wrapper = mountComponent();
|
wrapper = mountComponent();
|
||||||
|
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
wrapper.findComponent(GlDropdownItem).vm.$emit('click');
|
const spy = jest.spyOn(eventHub, '$emit');
|
||||||
|
|
||||||
|
await clickDropdownItem();
|
||||||
|
|
||||||
|
const namespace = data.currentUser.groups.nodes[0];
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledWith('update-visibility', {
|
||||||
|
name: namespace.name,
|
||||||
|
visibility: namespace.visibility,
|
||||||
|
showPath: namespace.webUrl,
|
||||||
|
editPath: `${namespace.webUrl}/-/edit`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates hidden input with selected namespace', async () => {
|
||||||
|
wrapper = mountComponent();
|
||||||
|
jest.runOnlyPendingTimers();
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
await clickDropdownItem();
|
||||||
|
|
||||||
expect(findHiddenInput().attributes()).toMatchObject({
|
expect(findHiddenInput().attributes()).toMatchObject({
|
||||||
name: 'project[namespace_id]',
|
name: 'project[namespace_id]',
|
||||||
value: getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(),
|
value: getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(),
|
||||||
|
|
|
@ -159,9 +159,8 @@ describe('LabelsSelect Mutations', () => {
|
||||||
labels = [
|
labels = [
|
||||||
{ id: 1, title: 'scoped' },
|
{ id: 1, title: 'scoped' },
|
||||||
{ id: 2, title: 'scoped::one', set: false },
|
{ id: 2, title: 'scoped::one', set: false },
|
||||||
{ id: 3, title: 'scoped::two', set: false },
|
{ id: 3, title: 'scoped::test', set: true },
|
||||||
{ id: 4, title: 'scoped::three', set: true },
|
{ id: 4, title: '' },
|
||||||
{ id: 5, title: '' },
|
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -192,9 +191,8 @@ describe('LabelsSelect Mutations', () => {
|
||||||
expect(state.labels).toEqual([
|
expect(state.labels).toEqual([
|
||||||
{ id: 1, title: 'scoped' },
|
{ id: 1, title: 'scoped' },
|
||||||
{ id: 2, title: 'scoped::one', set: true, touched: true },
|
{ id: 2, title: 'scoped::one', set: true, touched: true },
|
||||||
{ id: 3, title: 'scoped::two', set: false },
|
{ id: 3, title: 'scoped::test', set: false },
|
||||||
{ id: 4, title: 'scoped::three', set: false },
|
{ id: 4, title: '' },
|
||||||
{ id: 5, title: '' },
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import App from '~/work_items/components/app.vue';
|
||||||
|
|
||||||
|
describe('Work Items Application', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const createComponent = () => {
|
||||||
|
wrapper = shallowMount(App, {
|
||||||
|
stubs: {
|
||||||
|
'router-view': true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a component', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
expect(wrapper.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
export const workItemQueryResponse = {
|
||||||
|
workItem: {
|
||||||
|
__typename: 'WorkItem',
|
||||||
|
id: '1',
|
||||||
|
type: 'FEATURE',
|
||||||
|
widgets: {
|
||||||
|
__typename: 'WorkItemWidgetConnection',
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
__typename: 'TitleWidget',
|
||||||
|
type: 'TITLE',
|
||||||
|
contentText: 'Test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||||
|
import VueApollo from 'vue-apollo';
|
||||||
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||||
|
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
|
||||||
|
import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
|
||||||
|
import { workItemQueryResponse } from '../mock_data';
|
||||||
|
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
localVue.use(VueApollo);
|
||||||
|
|
||||||
|
const WORK_ITEM_ID = '1';
|
||||||
|
|
||||||
|
describe('Work items root component', () => {
|
||||||
|
let wrapper;
|
||||||
|
let fakeApollo;
|
||||||
|
|
||||||
|
const findTitle = () => wrapper.find('[data-testid="title"]');
|
||||||
|
|
||||||
|
const createComponent = ({ queryResponse = workItemQueryResponse } = {}) => {
|
||||||
|
fakeApollo = createMockApollo();
|
||||||
|
fakeApollo.clients.defaultClient.cache.writeQuery({
|
||||||
|
query: workItemQuery,
|
||||||
|
variables: {
|
||||||
|
id: WORK_ITEM_ID,
|
||||||
|
},
|
||||||
|
data: queryResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper = shallowMount(WorkItemsRoot, {
|
||||||
|
propsData: {
|
||||||
|
id: WORK_ITEM_ID,
|
||||||
|
},
|
||||||
|
localVue,
|
||||||
|
apolloProvider: fakeApollo,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
fakeApollo = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the title if title is in the widgets list', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
expect(findTitle().exists()).toBe(true);
|
||||||
|
expect(findTitle().text()).toBe('Test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render the title if title is not in the widgets list', () => {
|
||||||
|
const queryResponse = {
|
||||||
|
workItem: {
|
||||||
|
...workItemQueryResponse.workItem,
|
||||||
|
widgets: {
|
||||||
|
__typename: 'WorkItemWidgetConnection',
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
__typename: 'SomeOtherWidget',
|
||||||
|
type: 'OTHER',
|
||||||
|
contentText: 'Test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
createComponent({ queryResponse });
|
||||||
|
|
||||||
|
expect(findTitle().exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import App from '~/work_items/components/app.vue';
|
||||||
|
import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
|
||||||
|
import { createRouter } from '~/work_items/router';
|
||||||
|
|
||||||
|
describe('Work items router', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const createComponent = async (routeArg) => {
|
||||||
|
const router = createRouter('/work_item');
|
||||||
|
if (routeArg !== undefined) {
|
||||||
|
await router.push(routeArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper = mount(App, {
|
||||||
|
router,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
window.location.hash = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders work item on `/1` route', async () => {
|
||||||
|
await createComponent('/1');
|
||||||
|
|
||||||
|
expect(wrapper.find(WorkItemsRoot).exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Ci::Variables::Builder do
|
||||||
|
let(:builder) { described_class.new(pipeline) }
|
||||||
|
let(:pipeline) { create(:ci_pipeline) }
|
||||||
|
let(:job) { create(:ci_build, pipeline: pipeline) }
|
||||||
|
|
||||||
|
describe '#scoped_variables' do
|
||||||
|
let(:environment) { job.expanded_environment_name }
|
||||||
|
let(:dependencies) { true }
|
||||||
|
|
||||||
|
subject { builder.scoped_variables(job, environment: environment, dependencies: dependencies) }
|
||||||
|
|
||||||
|
it 'returns the expected variables' do
|
||||||
|
keys = %w[CI_JOB_NAME
|
||||||
|
CI_JOB_STAGE
|
||||||
|
CI_NODE_TOTAL
|
||||||
|
CI_BUILD_NAME
|
||||||
|
CI_BUILD_STAGE]
|
||||||
|
|
||||||
|
subject.map { |env| env[:key] }.tap do |names|
|
||||||
|
expect(names).to include(*keys)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'feature flag disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(ci_predefined_vars_in_builder: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns no variables' do
|
||||||
|
expect(subject.map { |env| env[:key] }).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,48 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::GrapeLogging::Loggers::UrgencyLogger do
|
||||||
|
def endpoint(options, namespace: '')
|
||||||
|
Struct.new(:options, :namespace).new(options, namespace)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:api_class) do
|
||||||
|
Class.new(API::Base) do
|
||||||
|
namespace 'testing' do
|
||||||
|
# rubocop:disable Rails/HttpPositionalArguments
|
||||||
|
# This is not the get that performs a request, but the one from Grape
|
||||||
|
get 'test', urgency: :high do
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
# rubocop:enable Rails/HttpPositionalArguments
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".parameters" do
|
||||||
|
where(:request_env, :expected_parameters) do
|
||||||
|
[
|
||||||
|
[{}, {}],
|
||||||
|
[{ 'api.endpoint' => endpoint({}) }, {}],
|
||||||
|
[{ 'api.endpoint' => endpoint({ for: 'something weird' }) }, {}],
|
||||||
|
[
|
||||||
|
{ 'api.endpoint' => endpoint({ for: api_class, path: [] }) },
|
||||||
|
{ request_urgency: :default, target_duration_s: 1 }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ 'api.endpoint' => endpoint({ for: api_class, path: ['test'] }, namespace: '/testing') },
|
||||||
|
{ request_urgency: :high, target_duration_s: 0.25 }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
let(:request) { double('request', env: request_env) }
|
||||||
|
|
||||||
|
subject { described_class.new.parameters(request, nil) }
|
||||||
|
|
||||||
|
it { is_expected.to eq(expected_parameters) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,5 +4,5 @@ require 'spec_helper'
|
||||||
require_relative '../simple_check_shared'
|
require_relative '../simple_check_shared'
|
||||||
|
|
||||||
RSpec.describe Gitlab::HealthChecks::Redis::RedisCheck do
|
RSpec.describe Gitlab::HealthChecks::Redis::RedisCheck do
|
||||||
include_examples 'simple_check', 'redis_ping', 'Redis', 'PONG'
|
include_examples 'simple_check', 'redis_ping', 'Redis', true
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,6 +17,10 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do
|
||||||
def initialize
|
def initialize
|
||||||
@shared = Gitlab::ImportExport::Shared.new(nil)
|
@shared = Gitlab::ImportExport::Shared.new(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def download(url, upload_path)
|
||||||
|
super(url, upload_path)
|
||||||
|
end
|
||||||
end.new
|
end.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -101,4 +105,44 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#download' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, loc)
|
||||||
|
.to_return(
|
||||||
|
status: 200,
|
||||||
|
body: content
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'a non-localhost uri' do
|
||||||
|
let(:loc) { 'https://gitlab.com' }
|
||||||
|
let(:content) { File.open('spec/fixtures/rails_sample.tif') }
|
||||||
|
|
||||||
|
it 'gets the contents' do
|
||||||
|
Tempfile.create("foo") do |f|
|
||||||
|
subject.download(loc, f.path)
|
||||||
|
expect(f.read).to eq(File.open('spec/fixtures/rails_sample.tif').read)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'streams the contents' do
|
||||||
|
expect(Gitlab::HTTP).to receive(:get).with(loc, hash_including(stream_body: true))
|
||||||
|
Tempfile.create("foo") do |f|
|
||||||
|
subject.download(loc, f.path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'a localhost uri' do
|
||||||
|
let(:loc) { 'https://localhost:8081/foo/bar' }
|
||||||
|
let(:content) { 'foo' }
|
||||||
|
|
||||||
|
it 'throws a blocked url error' do
|
||||||
|
Tempfile.create("foo") do |f|
|
||||||
|
expect { subject.download(loc, f.path) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,13 @@ RSpec.describe Gitlab::Lograge::CustomOptions do
|
||||||
user_id: 'test',
|
user_id: 'test',
|
||||||
cf_ray: SecureRandom.hex,
|
cf_ray: SecureRandom.hex,
|
||||||
cf_request_id: SecureRandom.hex,
|
cf_request_id: SecureRandom.hex,
|
||||||
metadata: { 'meta.user' => 'jane.doe' }
|
metadata: { 'meta.user' => 'jane.doe' },
|
||||||
|
request_urgency: :default,
|
||||||
|
target_duration_s: 1,
|
||||||
|
remote_ip: '192.168.1.2',
|
||||||
|
ua: 'Nyxt',
|
||||||
|
queue_duration_s: 0.2,
|
||||||
|
etag_route: '/etag'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -66,6 +72,18 @@ RSpec.describe Gitlab::Lograge::CustomOptions do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'trusted payload' do
|
||||||
|
it { is_expected.to include(event_payload.slice(*described_class::KNOWN_PAYLOAD_PARAMS)) }
|
||||||
|
|
||||||
|
context 'payload with rejected fields' do
|
||||||
|
let(:event_payload) { { params: {}, request_urgency: :high, something: 'random', username: nil } }
|
||||||
|
|
||||||
|
it { is_expected.to include({ request_urgency: :high }) }
|
||||||
|
it { is_expected.not_to include({ something: 'random' }) }
|
||||||
|
it { is_expected.not_to include({ username: nil }) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when correlation_id is overridden' do
|
context 'when correlation_id is overridden' do
|
||||||
let(:correlation_id_key) { Labkit::Correlation::CorrelationId::LOG_KEY }
|
let(:correlation_id_key) { Labkit::Correlation::CorrelationId::LOG_KEY }
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,13 @@ RSpec.describe Gitlab::Metrics::RailsSlis do
|
||||||
possible_labels = [
|
possible_labels = [
|
||||||
{
|
{
|
||||||
endpoint_id: "GET /api/:version/version",
|
endpoint_id: "GET /api/:version/version",
|
||||||
feature_category: :not_owned
|
feature_category: :not_owned,
|
||||||
|
request_urgency: :default
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpoint_id: "ProjectsController#show",
|
endpoint_id: "ProjectsController#show",
|
||||||
feature_category: :projects
|
feature_category: :projects,
|
||||||
|
request_urgency: :default
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,8 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
|
||||||
it 'tracks request count and duration' do
|
it 'tracks request count and duration' do
|
||||||
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
|
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
|
||||||
expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ method: 'get' }, a_positive_execution_time)
|
expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ method: 'get' }, a_positive_execution_time)
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(labels: { feature_category: 'unknown', endpoint_id: 'unknown' }, success: true)
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment)
|
||||||
|
.with(labels: { feature_category: 'unknown', endpoint_id: 'unknown', request_urgency: :default }, success: true)
|
||||||
|
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
end
|
end
|
||||||
|
@ -122,7 +123,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
|
||||||
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'issue_tracking')
|
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'issue_tracking')
|
||||||
expect(described_class).not_to receive(:http_health_requests_total)
|
expect(described_class).not_to receive(:http_health_requests_total)
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex)
|
expect(Gitlab::Metrics::RailsSlis.request_apdex)
|
||||||
.to receive(:increment).with(labels: { feature_category: 'issue_tracking', endpoint_id: 'IssuesController#show' }, success: true)
|
.to receive(:increment).with(labels: { feature_category: 'issue_tracking', endpoint_id: 'IssuesController#show', request_urgency: :default }, success: true)
|
||||||
|
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
end
|
end
|
||||||
|
@ -156,7 +157,8 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
|
||||||
it 'sets the required labels to unknown' do
|
it 'sets the required labels to unknown' do
|
||||||
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
|
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
|
||||||
expect(described_class).not_to receive(:http_health_requests_total)
|
expect(described_class).not_to receive(:http_health_requests_total)
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(labels: { feature_category: 'unknown', endpoint_id: 'unknown' }, success: true)
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment)
|
||||||
|
.with(labels: { feature_category: 'unknown', endpoint_id: 'unknown', request_urgency: :default }, success: true)
|
||||||
|
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
end
|
end
|
||||||
|
@ -206,7 +208,11 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
|
||||||
|
|
||||||
it "captures SLI metrics" do
|
it "captures SLI metrics" do
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
||||||
labels: { feature_category: 'hello_world', endpoint_id: 'GET /projects/:id/archive' },
|
labels: {
|
||||||
|
feature_category: 'hello_world',
|
||||||
|
endpoint_id: 'GET /projects/:id/archive',
|
||||||
|
request_urgency: request_urgency_name
|
||||||
|
},
|
||||||
success: success
|
success: success
|
||||||
)
|
)
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
|
@ -235,7 +241,11 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
|
||||||
|
|
||||||
it "captures SLI metrics" do
|
it "captures SLI metrics" do
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
||||||
labels: { feature_category: 'hello_world', endpoint_id: 'AnonymousController#index' },
|
labels: {
|
||||||
|
feature_category: 'hello_world',
|
||||||
|
endpoint_id: 'AnonymousController#index',
|
||||||
|
request_urgency: request_urgency_name
|
||||||
|
},
|
||||||
success: success
|
success: success
|
||||||
)
|
)
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
|
@ -255,17 +265,25 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
|
||||||
|
|
||||||
let(:api_handler) { Class.new(::API::Base) }
|
let(:api_handler) { Class.new(::API::Base) }
|
||||||
|
|
||||||
it "falls back request's expectation to medium (1 second)" do
|
it "falls back request's expectation to default (1 second)" do
|
||||||
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
|
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
||||||
labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
|
labels: {
|
||||||
|
feature_category: 'unknown',
|
||||||
|
endpoint_id: 'unknown',
|
||||||
|
request_urgency: :default
|
||||||
|
},
|
||||||
success: true
|
success: true
|
||||||
)
|
)
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
|
|
||||||
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
|
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
||||||
labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
|
labels: {
|
||||||
|
feature_category: 'unknown',
|
||||||
|
endpoint_id: 'unknown',
|
||||||
|
request_urgency: :default
|
||||||
|
},
|
||||||
success: false
|
success: false
|
||||||
)
|
)
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
|
@ -281,17 +299,25 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
|
||||||
{ 'action_controller.instance' => controller_instance, 'REQUEST_METHOD' => 'GET' }
|
{ 'action_controller.instance' => controller_instance, 'REQUEST_METHOD' => 'GET' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it "falls back request's expectation to medium (1 second)" do
|
it "falls back request's expectation to default (1 second)" do
|
||||||
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
|
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
||||||
labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
|
labels: {
|
||||||
|
feature_category: 'unknown',
|
||||||
|
endpoint_id: 'unknown',
|
||||||
|
request_urgency: :default
|
||||||
|
},
|
||||||
success: true
|
success: true
|
||||||
)
|
)
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
|
|
||||||
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
|
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
||||||
labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
|
labels: {
|
||||||
|
feature_category: 'unknown',
|
||||||
|
endpoint_id: 'unknown',
|
||||||
|
request_urgency: :default
|
||||||
|
},
|
||||||
success: false
|
success: false
|
||||||
)
|
)
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
|
@ -303,17 +329,25 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
|
||||||
{ 'REQUEST_METHOD' => 'GET' }
|
{ 'REQUEST_METHOD' => 'GET' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it "falls back request's expectation to medium (1 second)" do
|
it "falls back request's expectation to default (1 second)" do
|
||||||
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
|
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
||||||
labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
|
labels: {
|
||||||
|
feature_category: 'unknown',
|
||||||
|
endpoint_id: 'unknown',
|
||||||
|
request_urgency: :default
|
||||||
|
},
|
||||||
success: true
|
success: true
|
||||||
)
|
)
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
|
|
||||||
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
|
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
|
||||||
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
|
||||||
labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
|
labels: {
|
||||||
|
feature_category: 'unknown',
|
||||||
|
endpoint_id: 'unknown',
|
||||||
|
request_urgency: :default
|
||||||
|
},
|
||||||
success: false
|
success: false
|
||||||
)
|
)
|
||||||
subject.call(env)
|
subject.call(env)
|
||||||
|
|
|
@ -2759,7 +2759,10 @@ RSpec.describe Ci::Build do
|
||||||
let(:job_dependency_var) { { key: 'job_dependency', value: 'value', public: true, masked: false } }
|
let(:job_dependency_var) { { key: 'job_dependency', value: 'value', public: true, masked: false } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(build).to receive(:predefined_variables) { [build_pre_var] }
|
allow_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder|
|
||||||
|
allow(builder).to receive(:predefined_variables) { [build_pre_var] }
|
||||||
|
end
|
||||||
|
|
||||||
allow(build).to receive(:yaml_variables) { [build_yaml_var] }
|
allow(build).to receive(:yaml_variables) { [build_yaml_var] }
|
||||||
allow(build).to receive(:persisted_variables) { [] }
|
allow(build).to receive(:persisted_variables) { [] }
|
||||||
allow(build).to receive(:job_jwt_variables) { [job_jwt_var] }
|
allow(build).to receive(:job_jwt_variables) { [job_jwt_var] }
|
||||||
|
@ -3411,75 +3414,122 @@ RSpec.describe Ci::Build do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#scoped_variables' do
|
describe '#scoped_variables' do
|
||||||
context 'when build has not been persisted yet' do
|
before do
|
||||||
let(:build) do
|
pipeline.clear_memoization(:predefined_vars_in_builder_enabled)
|
||||||
described_class.new(
|
end
|
||||||
name: 'rspec',
|
|
||||||
stage: 'test',
|
|
||||||
ref: 'feature',
|
|
||||||
project: project,
|
|
||||||
pipeline: pipeline,
|
|
||||||
scheduling_type: :stage
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
|
it 'records a prometheus metric' do
|
||||||
|
histogram = double(:histogram)
|
||||||
|
expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_builder_scoped_variables_histogram)
|
||||||
|
.and_return(histogram)
|
||||||
|
|
||||||
it 'does not persist the build' do
|
expect(histogram).to receive(:observe)
|
||||||
expect(build).to be_valid
|
.with({}, a_kind_of(ActiveSupport::Duration))
|
||||||
expect(build).not_to be_persisted
|
|
||||||
|
|
||||||
build.scoped_variables
|
build.scoped_variables
|
||||||
|
end
|
||||||
|
|
||||||
expect(build).not_to be_persisted
|
shared_examples 'calculates scoped_variables' do
|
||||||
end
|
context 'when build has not been persisted yet' do
|
||||||
|
let(:build) do
|
||||||
it 'returns static predefined variables' do
|
described_class.new(
|
||||||
keys = %w[CI_JOB_NAME
|
name: 'rspec',
|
||||||
CI_COMMIT_SHA
|
stage: 'test',
|
||||||
CI_COMMIT_SHORT_SHA
|
ref: 'feature',
|
||||||
CI_COMMIT_REF_NAME
|
project: project,
|
||||||
CI_COMMIT_REF_SLUG
|
pipeline: pipeline,
|
||||||
CI_JOB_STAGE]
|
scheduling_type: :stage
|
||||||
|
)
|
||||||
variables = build.scoped_variables
|
|
||||||
|
|
||||||
variables.map { |env| env[:key] }.tap do |names|
|
|
||||||
expect(names).to include(*keys)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(variables)
|
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
|
||||||
.to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
|
|
||||||
|
it 'does not persist the build' do
|
||||||
|
expect(build).to be_valid
|
||||||
|
expect(build).not_to be_persisted
|
||||||
|
|
||||||
|
build.scoped_variables
|
||||||
|
|
||||||
|
expect(build).not_to be_persisted
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns static predefined variables' do
|
||||||
|
keys = %w[CI_JOB_NAME
|
||||||
|
CI_COMMIT_SHA
|
||||||
|
CI_COMMIT_SHORT_SHA
|
||||||
|
CI_COMMIT_REF_NAME
|
||||||
|
CI_COMMIT_REF_SLUG
|
||||||
|
CI_JOB_STAGE]
|
||||||
|
|
||||||
|
variables = build.scoped_variables
|
||||||
|
|
||||||
|
variables.map { |env| env[:key] }.tap do |names|
|
||||||
|
expect(names).to include(*keys)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(variables)
|
||||||
|
.to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return prohibited variables' do
|
||||||
|
keys = %w[CI_JOB_ID
|
||||||
|
CI_JOB_URL
|
||||||
|
CI_JOB_TOKEN
|
||||||
|
CI_BUILD_ID
|
||||||
|
CI_BUILD_TOKEN
|
||||||
|
CI_REGISTRY_USER
|
||||||
|
CI_REGISTRY_PASSWORD
|
||||||
|
CI_REPOSITORY_URL
|
||||||
|
CI_ENVIRONMENT_URL
|
||||||
|
CI_DEPLOY_USER
|
||||||
|
CI_DEPLOY_PASSWORD]
|
||||||
|
|
||||||
|
build.scoped_variables.map { |env| env[:key] }.tap do |names|
|
||||||
|
expect(names).not_to include(*keys)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not return prohibited variables' do
|
context 'with dependency variables' do
|
||||||
keys = %w[CI_JOB_ID
|
let!(:prepare) { create(:ci_build, name: 'prepare', pipeline: pipeline, stage_idx: 0) }
|
||||||
CI_JOB_URL
|
let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare'] }) }
|
||||||
CI_JOB_TOKEN
|
|
||||||
CI_BUILD_ID
|
|
||||||
CI_BUILD_TOKEN
|
|
||||||
CI_REGISTRY_USER
|
|
||||||
CI_REGISTRY_PASSWORD
|
|
||||||
CI_REPOSITORY_URL
|
|
||||||
CI_ENVIRONMENT_URL
|
|
||||||
CI_DEPLOY_USER
|
|
||||||
CI_DEPLOY_PASSWORD]
|
|
||||||
|
|
||||||
build.scoped_variables.map { |env| env[:key] }.tap do |names|
|
let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) }
|
||||||
expect(names).not_to include(*keys)
|
|
||||||
|
it 'inherits dependent variables' do
|
||||||
|
expect(build.scoped_variables.to_hash).to include(job_variable.key => job_variable.value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with dependency variables' do
|
it_behaves_like 'calculates scoped_variables'
|
||||||
let!(:prepare) { create(:ci_build, name: 'prepare', pipeline: pipeline, stage_idx: 0) }
|
|
||||||
let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare'] }) }
|
|
||||||
|
|
||||||
let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) }
|
it 'delegates to the variable builders' do
|
||||||
|
expect_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder|
|
||||||
|
expect(builder)
|
||||||
|
.to receive(:scoped_variables).with(build, hash_including(:environment, :dependencies))
|
||||||
|
.and_call_original
|
||||||
|
|
||||||
it 'inherits dependent variables' do
|
expect(builder).to receive(:predefined_variables).and_call_original
|
||||||
expect(build.scoped_variables.to_hash).to include(job_variable.key => job_variable.value)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
build.scoped_variables
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ci builder feature flag is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(ci_predefined_vars_in_builder: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not delegate to the variable builders' do
|
||||||
|
expect_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder|
|
||||||
|
expect(builder).not_to receive(:predefined_variables)
|
||||||
|
end
|
||||||
|
|
||||||
|
build.scoped_variables
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'calculates scoped_variables'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue