Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ccc2dc45a3
commit
0ebbf19f2d
|
@ -742,11 +742,6 @@ Style/ExplicitBlockArgument:
|
|||
Style/FormatString:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 67
|
||||
# Cop supports --auto-correct.
|
||||
Style/GlobalStdStream:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 897
|
||||
# Configuration parameters: MinBodyLength.
|
||||
Style/GuardClause:
|
||||
|
|
|
@ -1 +1 @@
|
|||
8fd337f0f718f257ae72a66c464143a395af4c05
|
||||
df2eb006d241b399b8b6b877afab97713bb5c36a
|
||||
|
|
|
@ -30,6 +30,24 @@ let renderedMermaidBlocks = 0;
|
|||
|
||||
let mermaidModule = {};
|
||||
|
||||
// Whitelist pages where we won't impose any restrictions
|
||||
// on mermaid rendering
|
||||
const WHITELISTED_PAGES = [
|
||||
// Group wiki
|
||||
'groups:wikis:show',
|
||||
'groups:wikis:edit',
|
||||
'groups:wikis:create',
|
||||
|
||||
// Project wiki
|
||||
'projects:wikis:show',
|
||||
'projects:wikis:edit',
|
||||
'projects:wikis:create',
|
||||
|
||||
// Project files
|
||||
'projects:show',
|
||||
'projects:blob:show',
|
||||
];
|
||||
|
||||
export function initMermaid(mermaid) {
|
||||
let theme = 'neutral';
|
||||
|
||||
|
@ -120,8 +138,10 @@ function renderMermaidEl(el) {
|
|||
function renderMermaids($els) {
|
||||
if (!$els.length) return;
|
||||
|
||||
const pageName = document.querySelector('body').dataset.page;
|
||||
|
||||
// A diagram may have been truncated in search results which will cause errors, so abort the render.
|
||||
if (document.querySelector('body').dataset.page === 'search:show') return;
|
||||
if (pageName === 'search:show') return;
|
||||
|
||||
importMermaidModule()
|
||||
.then(() => {
|
||||
|
@ -140,10 +160,11 @@ function renderMermaids($els) {
|
|||
* up the entire thread and causing a DoS.
|
||||
*/
|
||||
if (
|
||||
(source && source.length > MAX_CHAR_LIMIT) ||
|
||||
renderedChars > MAX_CHAR_LIMIT ||
|
||||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
|
||||
shouldLazyLoadMermaidBlock(source)
|
||||
!WHITELISTED_PAGES.includes(pageName) &&
|
||||
((source && source.length > MAX_CHAR_LIMIT) ||
|
||||
renderedChars > MAX_CHAR_LIMIT ||
|
||||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
|
||||
shouldLazyLoadMermaidBlock(source))
|
||||
) {
|
||||
const html = `
|
||||
<div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlModal, GlAlert } from '@gitlab/ui';
|
||||
import { mapGetters, mapActions, mapState } from 'vuex';
|
||||
import ListLabel from '~/boards/models/label';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
|
@ -224,9 +225,6 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(['setError', 'unsetError']),
|
||||
setIteration(iterationId) {
|
||||
this.board.iteration_id = iterationId;
|
||||
},
|
||||
boardCreateResponse(data) {
|
||||
return data.createBoard.board.webPath;
|
||||
},
|
||||
|
@ -237,6 +235,9 @@ export default {
|
|||
: '';
|
||||
return `${path}${param}`;
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
async createOrUpdateBoard() {
|
||||
const response = await this.$apollo.mutate({
|
||||
mutation: this.currentMutation,
|
||||
|
@ -280,9 +281,6 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
resetFormState() {
|
||||
if (this.isNewForm) {
|
||||
// Clear the form when we open the "New board" modal
|
||||
|
@ -291,6 +289,25 @@ export default {
|
|||
this.board = { ...boardDefaults, ...this.currentBoard };
|
||||
}
|
||||
},
|
||||
setIteration(iterationId) {
|
||||
this.board.iteration_id = iterationId;
|
||||
},
|
||||
setBoardLabels(labels) {
|
||||
labels.forEach((label) => {
|
||||
if (label.set && !this.board.labels.find((l) => l.id === label.id)) {
|
||||
this.board.labels.push(
|
||||
new ListLabel({
|
||||
id: label.id,
|
||||
title: label.title,
|
||||
color: label.color,
|
||||
textColor: label.text_color,
|
||||
}),
|
||||
);
|
||||
} else if (!label.set) {
|
||||
this.board.labels = this.board.labels.filter((selected) => selected.id !== label.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -357,6 +374,7 @@ export default {
|
|||
:group-id="groupId"
|
||||
:weights="weights"
|
||||
@set-iteration="setIteration"
|
||||
@set-board-labels="setBoardLabels"
|
||||
/>
|
||||
</form>
|
||||
</gl-modal>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { normalizeHeaders } from '~/lib/utils/common_utils';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
import showToast from '~/vue_shared/plugins/global_toast';
|
||||
import {
|
||||
|
@ -16,7 +17,9 @@ export const fetchAwards = async ({ commit, dispatch, state }, page = '1') => {
|
|||
if (!window.gon?.current_user_id) return;
|
||||
|
||||
try {
|
||||
const { data, headers } = await axios.get(state.path, { params: { per_page: 100, page } });
|
||||
const { data, headers } = await axios.get(joinPaths(gon.relative_url_root || '', state.path), {
|
||||
params: { per_page: 100, page },
|
||||
});
|
||||
const normalizedHeaders = normalizeHeaders(headers);
|
||||
const nextPage = normalizedHeaders['X-NEXT-PAGE'];
|
||||
|
||||
|
@ -35,13 +38,15 @@ export const toggleAward = async ({ commit, state }, name) => {
|
|||
|
||||
try {
|
||||
if (award) {
|
||||
await axios.delete(`${state.path}/${award.id}`);
|
||||
await axios.delete(joinPaths(gon.relative_url_root || '', `${state.path}/${award.id}`));
|
||||
|
||||
commit(REMOVE_AWARD, award.id);
|
||||
|
||||
showToast(__('Award removed'));
|
||||
} else {
|
||||
const { data } = await axios.post(state.path, { name });
|
||||
const { data } = await axios.post(joinPaths(gon.relative_url_root || '', state.path), {
|
||||
name,
|
||||
});
|
||||
|
||||
commit(ADD_NEW_AWARD, data);
|
||||
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import $ from 'jquery';
|
||||
import LabelsSelect from '~/labels_select';
|
||||
import { __ } from '~/locale';
|
||||
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
|
||||
|
||||
import { DropdownVariant } from '../labels_select_vue/constants';
|
||||
import DropdownButton from './dropdown_button.vue';
|
||||
import DropdownCreateLabel from './dropdown_create_label.vue';
|
||||
import DropdownFooter from './dropdown_footer.vue';
|
||||
import DropdownHeader from './dropdown_header.vue';
|
||||
import DropdownSearchInput from './dropdown_search_input.vue';
|
||||
import DropdownTitle from './dropdown_title.vue';
|
||||
import DropdownValue from './dropdown_value.vue';
|
||||
import DropdownValueCollapsed from './dropdown_value_collapsed.vue';
|
||||
|
||||
export default {
|
||||
DropdownVariant,
|
||||
components: {
|
||||
DropdownTitle,
|
||||
DropdownValue,
|
||||
DropdownValueCollapsed,
|
||||
DropdownButton,
|
||||
DropdownHiddenInput,
|
||||
DropdownHeader,
|
||||
DropdownSearchInput,
|
||||
DropdownFooter,
|
||||
DropdownCreateLabel,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
showCreate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
isProject: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
abilityName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
context: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
namespace: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
labelsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelsWebUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
labelFilterBasePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
canEdit: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
enableScopedLabels: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: DropdownVariant.Sidebar,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hiddenInputName() {
|
||||
return this.showCreate ? `${this.abilityName}[label_names][]` : 'label_id[]';
|
||||
},
|
||||
createLabelTitle() {
|
||||
if (this.isProject) {
|
||||
return __('Create project label');
|
||||
}
|
||||
|
||||
return __('Create group label');
|
||||
},
|
||||
manageLabelsTitle() {
|
||||
if (this.isProject) {
|
||||
return __('Manage project labels');
|
||||
}
|
||||
|
||||
return __('Manage group labels');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.labelsDropdown = new LabelsSelect(this.$refs.dropdownButton, {
|
||||
handleClick: this.handleClick,
|
||||
});
|
||||
$(this.$refs.dropdown).on('hidden.gl.dropdown', this.handleDropdownHidden);
|
||||
},
|
||||
methods: {
|
||||
handleClick(label) {
|
||||
this.$emit('onLabelClick', label);
|
||||
},
|
||||
handleCollapsedValueClick() {
|
||||
this.$emit('toggleCollapse');
|
||||
},
|
||||
handleDropdownHidden() {
|
||||
this.$emit('onDropdownClose');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="block labels js-labels-block">
|
||||
<dropdown-value-collapsed
|
||||
v-if="showCreate && variant === $options.DropdownVariant.Sidebar"
|
||||
:labels="context.labels"
|
||||
@onValueClick="handleCollapsedValueClick"
|
||||
/>
|
||||
<dropdown-title :can-edit="canEdit" />
|
||||
<dropdown-value
|
||||
:labels="context.labels"
|
||||
:label-filter-base-path="labelFilterBasePath"
|
||||
:enable-scoped-labels="enableScopedLabels"
|
||||
>
|
||||
<slot></slot>
|
||||
</dropdown-value>
|
||||
<div v-if="canEdit" class="selectbox js-selectbox" style="display: none">
|
||||
<dropdown-hidden-input
|
||||
v-for="label in context.labels"
|
||||
:key="label.id"
|
||||
:name="hiddenInputName"
|
||||
:value="label.id"
|
||||
/>
|
||||
<div ref="dropdown" class="dropdown">
|
||||
<dropdown-button
|
||||
:ability-name="abilityName"
|
||||
:field-name="hiddenInputName"
|
||||
:update-path="updatePath"
|
||||
:labels-path="labelsPath"
|
||||
:namespace="namespace"
|
||||
:labels="context.labels"
|
||||
:show-extra-options="!showCreate || variant !== $options.DropdownVariant.Sidebar"
|
||||
:enable-scoped-labels="enableScopedLabels"
|
||||
/>
|
||||
<div
|
||||
class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable"
|
||||
>
|
||||
<div class="dropdown-page-one">
|
||||
<dropdown-header v-if="showCreate && variant === $options.DropdownVariant.Sidebar" />
|
||||
<dropdown-search-input />
|
||||
<div class="dropdown-content" data-qa-selector="labels_dropdown_content"></div>
|
||||
<div class="dropdown-loading">
|
||||
<gl-loading-icon
|
||||
class="gl-display-flex gl-justify-content-center gl-align-items-center gl-h-full"
|
||||
/>
|
||||
</div>
|
||||
<dropdown-footer
|
||||
v-if="showCreate"
|
||||
:labels-web-url="labelsWebUrl"
|
||||
:create-label-title="createLabelTitle"
|
||||
:manage-labels-title="manageLabelsTitle"
|
||||
/>
|
||||
</div>
|
||||
<dropdown-create-label
|
||||
v-if="showCreate"
|
||||
:is-project="isProject"
|
||||
:header-title="createLabelTitle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,86 +0,0 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
abilityName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fieldName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
updatePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
namespace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labels: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
showExtraOptions: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
enableScopedLabels: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dropdownToggleText() {
|
||||
if (this.labels.length === 0) {
|
||||
return __('Label');
|
||||
}
|
||||
|
||||
if (this.labels.length > 1) {
|
||||
return sprintf(s__('LabelSelect|%{firstLabelName} +%{remainingLabelCount} more'), {
|
||||
firstLabelName: this.labels[0].title,
|
||||
remainingLabelCount: this.labels.length - 1,
|
||||
});
|
||||
}
|
||||
|
||||
return this.labels[0].title;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- eslint-disable @gitlab/vue-no-data-toggle -->
|
||||
<button
|
||||
ref="dropdownButton"
|
||||
:class="{ 'js-extra-options': showExtraOptions }"
|
||||
:data-ability-name="abilityName"
|
||||
:data-field-name="fieldName"
|
||||
:data-issue-update="updatePath"
|
||||
:data-labels="labelsPath"
|
||||
:data-namespace-path="namespace"
|
||||
:data-show-any="showExtraOptions"
|
||||
:data-scoped-labels="enableScopedLabels"
|
||||
type="button"
|
||||
class="dropdown-menu-toggle wide js-label-select js-multiselect js-context-config-modal"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<span class="dropdown-toggle-text"> {{ dropdownToggleText }} </span>
|
||||
<gl-icon
|
||||
name="chevron-down"
|
||||
class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500"
|
||||
:size="16"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
|
@ -1,92 +0,0 @@
|
|||
<script>
|
||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
headerTitle: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => __('Create new label'),
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const rawLabelsColors = gon.suggested_label_colors;
|
||||
this.suggestedColors = Object.keys(rawLabelsColors).map((colorCode) => ({
|
||||
colorCode,
|
||||
title: rawLabelsColors[colorCode],
|
||||
}));
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown-page-two dropdown-new-label">
|
||||
<div
|
||||
class="dropdown-title gl-display-flex gl-justify-content-space-between gl-align-items-center"
|
||||
>
|
||||
<gl-button
|
||||
:aria-label="__('Go back')"
|
||||
category="tertiary"
|
||||
class="dropdown-menu-back"
|
||||
icon="arrow-left"
|
||||
size="small"
|
||||
/>
|
||||
{{ headerTitle }}
|
||||
<gl-button
|
||||
:aria-label="__('Close')"
|
||||
category="tertiary"
|
||||
class="dropdown-menu-close"
|
||||
icon="close"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div class="dropdown-content">
|
||||
<div class="dropdown-labels-error js-label-error"></div>
|
||||
<input
|
||||
id="new_label_name"
|
||||
:placeholder="__('Name new label')"
|
||||
type="text"
|
||||
class="default-dropdown-input"
|
||||
/>
|
||||
<div class="suggest-colors suggest-colors-dropdown">
|
||||
<a
|
||||
v-for="(color, index) in suggestedColors"
|
||||
:key="index"
|
||||
v-gl-tooltip
|
||||
:data-color="color.colorCode"
|
||||
:style="{
|
||||
backgroundColor: color.colorCode,
|
||||
}"
|
||||
:title="color.title"
|
||||
href="#"
|
||||
>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
<div class="dropdown-label-color-input">
|
||||
<div class="dropdown-label-color-preview js-dropdown-label-color-preview"></div>
|
||||
<input
|
||||
id="new_label_color"
|
||||
:placeholder="__('Assign custom color like #FF0000')"
|
||||
type="text"
|
||||
class="default-dropdown-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<gl-button category="secondary" class="float-left js-new-label-btn disabled">
|
||||
{{ __('Create') }}
|
||||
</gl-button>
|
||||
<gl-button category="secondary" class="float-right js-cancel-label-btn">
|
||||
{{ __('Cancel') }}
|
||||
</gl-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,37 +0,0 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
labelsWebUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
createLabelTitle: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => __('Create new label'),
|
||||
},
|
||||
manageLabelsTitle: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => __('Manage labels'),
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown-footer">
|
||||
<ul class="dropdown-footer-list">
|
||||
<li>
|
||||
<a href="#" class="dropdown-toggle-page"> {{ createLabelTitle }} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a :href="labelsWebUrl" data-is-link="true" class="dropdown-external-link">
|
||||
{{ manageLabelsTitle }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
|
@ -1,22 +0,0 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown-title gl-display-flex gl-justify-content-center">
|
||||
<span class="gl-ml-auto">{{ __('Assign labels') }}</span>
|
||||
<button
|
||||
:aria-label="__('Close')"
|
||||
type="button"
|
||||
class="dropdown-title-button dropdown-menu-close gl-ml-auto"
|
||||
>
|
||||
<gl-icon name="close" class="dropdown-menu-close-icon" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
|
@ -1,28 +0,0 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown-input">
|
||||
<input
|
||||
:placeholder="__('Search')"
|
||||
autocomplete="off"
|
||||
class="dropdown-input-field"
|
||||
type="search"
|
||||
/>
|
||||
<gl-icon
|
||||
name="search"
|
||||
class="dropdown-input-search gl-absolute gl-top-3 gl-right-5 gl-text-gray-300 gl-pointer-events-none"
|
||||
/>
|
||||
<gl-icon
|
||||
name="close"
|
||||
class="dropdown-input-clear js-dropdown-input-clear gl-absolute gl-top-3 gl-right-5 gl-text-gray-500"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -1,31 +0,0 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
canEdit: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="title hide-collapsed gl-mb-3">
|
||||
{{ __('Labels') }}
|
||||
<template v-if="canEdit">
|
||||
<gl-loading-icon inline class="align-text-top block-loading" />
|
||||
<button
|
||||
type="button"
|
||||
class="edit-link btn btn-blank float-right js-sidebar-dropdown-toggle"
|
||||
data-qa-selector="labels_edit_button"
|
||||
>
|
||||
{{ __('Edit') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -1,65 +0,0 @@
|
|||
<script>
|
||||
import { GlLabel } from '@gitlab/ui';
|
||||
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLabel,
|
||||
},
|
||||
props: {
|
||||
labels: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
labelFilterBasePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
enableScopedLabels: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isEmpty() {
|
||||
return this.labels.length === 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
labelFilterUrl(label) {
|
||||
return `${this.labelFilterBasePath}?label_name[]=${encodeURIComponent(label.title)}`;
|
||||
},
|
||||
scopedLabelsDescription({ description = '' }) {
|
||||
return `<span class="font-weight-bold scoped-label-tooltip-title">Scoped label</span><br />${description}`;
|
||||
},
|
||||
showScopedLabels(label) {
|
||||
return this.enableScopedLabels && isScopedLabel(label);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'has-labels': !isEmpty,
|
||||
}"
|
||||
class="hide-collapsed value issuable-show-labels js-value"
|
||||
>
|
||||
<span v-if="isEmpty" class="text-secondary">
|
||||
<slot>{{ __('None') }}</slot>
|
||||
</span>
|
||||
|
||||
<template v-for="label in labels" v-else>
|
||||
<gl-label
|
||||
:key="label.id"
|
||||
:target="labelFilterUrl(label)"
|
||||
:background-color="label.color"
|
||||
:title="label.title"
|
||||
:description="label.description"
|
||||
:scoped="showScopedLabels(label)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -29,7 +29,7 @@ export default {
|
|||
<gl-loading-icon v-show="labelsSelectInProgress" inline />
|
||||
<gl-button
|
||||
variant="link"
|
||||
class="float-right js-sidebar-dropdown-toggle"
|
||||
class="gl-text-gray-800! float-right js-sidebar-dropdown-toggle"
|
||||
data-qa-selector="labels_edit_button"
|
||||
@click="toggleDropdownContents"
|
||||
>{{ __('Edit') }}</gl-button
|
||||
|
|
|
@ -5,13 +5,12 @@ import Vuex, { mapState, mapActions, mapGetters } from 'vuex';
|
|||
import { isInViewport } from '~/lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
|
||||
|
||||
import { DropdownVariant } from './constants';
|
||||
import DropdownButton from './dropdown_button.vue';
|
||||
import DropdownContents from './dropdown_contents.vue';
|
||||
import DropdownTitle from './dropdown_title.vue';
|
||||
import DropdownValue from './dropdown_value.vue';
|
||||
import DropdownValueCollapsed from './dropdown_value_collapsed.vue';
|
||||
import labelsSelectModule from './store';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
@ -61,6 +60,11 @@ export default {
|
|||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
hideCollapsedView: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
labelsSelectInProgress: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -294,6 +298,7 @@ export default {
|
|||
>
|
||||
<template v-if="isDropdownVariantSidebar">
|
||||
<dropdown-value-collapsed
|
||||
v-if="!hideCollapsedView"
|
||||
ref="dropdownButtonCollapsed"
|
||||
:labels="selectedLabels"
|
||||
@onValueClick="handleCollapsedValueClick"
|
||||
|
|
|
@ -5,7 +5,7 @@ import Vuex, { mapState, mapActions, mapGetters } from 'vuex';
|
|||
import { isInViewport } from '~/lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
|
||||
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue';
|
||||
|
||||
import { DropdownVariant } from './constants';
|
||||
import DropdownButton from './dropdown_button.vue';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Resolvers
|
||||
class BoardListIssuesResolver < BaseResolver
|
||||
include BoardIssueFilterable
|
||||
include BoardItemFilterable
|
||||
|
||||
argument :filters, Types::Boards::BoardIssueInputType,
|
||||
required: false,
|
||||
|
@ -13,7 +13,7 @@ module Resolvers
|
|||
alias_method :list, :object
|
||||
|
||||
def resolve(**args)
|
||||
filter_params = issue_filters(args[:filters]).merge(board_id: list.board.id, id: list.id)
|
||||
filter_params = item_filters(args[:filters]).merge(board_id: list.board.id, id: list.id)
|
||||
service = ::Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params)
|
||||
|
||||
offset_pagination(service.execute)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Resolvers
|
||||
class BoardListsResolver < BaseResolver
|
||||
include BoardIssueFilterable
|
||||
include BoardItemFilterable
|
||||
include Gitlab::Graphql::Authorize::AuthorizeResource
|
||||
include LooksAhead
|
||||
|
||||
|
@ -22,7 +22,7 @@ module Resolvers
|
|||
|
||||
def resolve_with_lookahead(id: nil, issue_filters: {})
|
||||
lists = board_lists(id)
|
||||
context.scoped_set!(:issue_filters, issue_filters(issue_filters))
|
||||
context.scoped_set!(:issue_filters, item_filters(issue_filters))
|
||||
|
||||
List.preload_preferences_for_user(lists, current_user) if load_preferences?
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module BoardIssueFilterable
|
||||
module BoardItemFilterable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def issue_filters(args)
|
||||
def item_filters(args)
|
||||
filters = args.to_h
|
||||
|
||||
set_filter_values(filters)
|
||||
|
@ -32,4 +32,4 @@ module BoardIssueFilterable
|
|||
end
|
||||
end
|
||||
|
||||
::BoardIssueFilterable.prepend_mod_with('Resolvers::BoardIssueFilterable')
|
||||
::BoardItemFilterable.prepend_mod_with('Resolvers::BoardItemFilterable')
|
|
@ -7,6 +7,9 @@ module Ci
|
|||
include Ci::HasStatus
|
||||
include Gitlab::OptimisticLocking
|
||||
include Presentable
|
||||
include IgnorableColumns
|
||||
|
||||
ignore_column :id_convert_to_bigint, remove_with: '14.2', remove_after: '2021-08-22'
|
||||
|
||||
enum status: Ci::HasStatus::STATUSES_ENUM
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ module IssueAvailableFeatures
|
|||
def available_features_for_issue_types
|
||||
{
|
||||
assignee: %w(issue incident),
|
||||
confidentiality: %(issue incident),
|
||||
time_tracking: %(issue incident)
|
||||
confidentiality: %w(issue incident),
|
||||
time_tracking: %w(issue incident)
|
||||
}.with_indifferent_access
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,7 +31,11 @@ class ForkNamespaceEntity < Grape::Entity
|
|||
end
|
||||
|
||||
expose :can_create_project do |namespace, options|
|
||||
options[:current_user].can?(:create_projects, namespace)
|
||||
if Feature.enabled?(:fork_project_form, options[:project], default_enabled: :yaml)
|
||||
true
|
||||
else
|
||||
options[:current_user].can?(:create_projects, namespace)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AuthorizedProjectUpdate
|
||||
class RecalculateForUserRangeService
|
||||
def initialize(start_user_id, end_user_id)
|
||||
@start_user_id = start_user_id
|
||||
@end_user_id = end_user_id
|
||||
end
|
||||
|
||||
def execute
|
||||
User.where(id: start_user_id..end_user_id).select(:id).find_each do |user| # rubocop: disable CodeReuse/ActiveRecord
|
||||
Users::RefreshAuthorizedProjectsService.new(user, source: self.class.name).execute
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :start_user_id, :end_user_id
|
||||
end
|
||||
end
|
|
@ -46,7 +46,9 @@
|
|||
= form_tag profile_two_factor_auth_path, method: :post do |f|
|
||||
- if @error
|
||||
.gl-alert.gl-alert-danger.gl-mb-5
|
||||
= @error
|
||||
.gl-alert-container
|
||||
.gl-alert-content
|
||||
= @error
|
||||
.form-group
|
||||
= label_tag :pin_code, _('Pin code'), class: "label-bold"
|
||||
= text_field_tag :pin_code, nil, class: "form-control gl-form-input", required: true, data: { qa_selector: 'pin_code_field' }
|
||||
|
|
|
@ -8,21 +8,23 @@
|
|||
= render "projects/merge_requests/mr_box"
|
||||
|
||||
.gl-alert.gl-alert-danger
|
||||
= sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
%p
|
||||
We cannot render this merge request properly because
|
||||
- if @merge_request.for_fork? && !@merge_request.source_project
|
||||
fork project was removed
|
||||
- elsif !@merge_request.source_branch_exists?
|
||||
%span{ class: badge_inverse_css_classes }= @merge_request.source_branch
|
||||
does not exist in
|
||||
%span{ class: badge_info_css_classes }= @merge_request.source_project_path
|
||||
- elsif !@merge_request.target_branch_exists?
|
||||
%span{ class: badge_inverse_css_classes }= @merge_request.target_branch
|
||||
does not exist in
|
||||
%span{ class: badge_info_css_classes }= @merge_request.target_project_path
|
||||
- else
|
||||
of internal error
|
||||
.gl-alert-container
|
||||
= sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
.gl-alert-content{ role: 'alert' }
|
||||
%p
|
||||
We cannot render this merge request properly because
|
||||
- if @merge_request.for_fork? && !@merge_request.source_project
|
||||
fork project was removed
|
||||
- elsif !@merge_request.source_branch_exists?
|
||||
%span{ class: badge_inverse_css_classes }= @merge_request.source_branch
|
||||
does not exist in
|
||||
%span{ class: badge_info_css_classes }= @merge_request.source_project_path
|
||||
- elsif !@merge_request.target_branch_exists?
|
||||
%span{ class: badge_inverse_css_classes }= @merge_request.target_branch
|
||||
does not exist in
|
||||
%span{ class: badge_info_css_classes }= @merge_request.target_project_path
|
||||
- else
|
||||
of internal error
|
||||
|
||||
%strong
|
||||
Please close merge request or change branches with existing one
|
||||
%strong
|
||||
Please close merge request or change branches with existing one
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
module AuthorizedProjectUpdate
|
||||
class UserRefreshOverUserRangeWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
# When the feature flag named `periodic_project_authorization_update_via_replica` is enabled,
|
||||
# this worker checks if a specific user requires an update to their project_authorizations records.
|
||||
# This worker checks if users requires an update to their project_authorizations records.
|
||||
# This check is done via the data read from the database replica (and not from the primary).
|
||||
# If this check returns true, a completely new Sidekiq job is enqueued for this specific user
|
||||
# If this check returns true, a completely new Sidekiq job is enqueued for a specific user
|
||||
# so as to update its project_authorizations records.
|
||||
|
||||
# There is a possibility that the data in the replica is lagging behind the primary
|
||||
|
@ -24,27 +23,16 @@ module AuthorizedProjectUpdate
|
|||
# `data_consistency :delayed` and not `idempotent!`
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/325291
|
||||
deduplicate :until_executing, including_scheduled: true
|
||||
data_consistency :delayed, feature_flag: :delayed_consistency_for_user_refresh_over_range_worker
|
||||
data_consistency :delayed
|
||||
|
||||
def perform(start_user_id, end_user_id)
|
||||
if Feature.enabled?(:periodic_project_authorization_update_via_replica)
|
||||
User.where(id: start_user_id..end_user_id).find_each do |user| # rubocop: disable CodeReuse/ActiveRecord
|
||||
enqueue_project_authorizations_refresh(user) if project_authorizations_needs_refresh?(user)
|
||||
end
|
||||
else
|
||||
use_primary_database
|
||||
AuthorizedProjectUpdate::RecalculateForUserRangeService.new(start_user_id, end_user_id).execute
|
||||
User.where(id: start_user_id..end_user_id).find_each do |user| # rubocop: disable CodeReuse/ActiveRecord
|
||||
enqueue_project_authorizations_refresh(user) if project_authorizations_needs_refresh?(user)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def use_primary_database
|
||||
if ::Gitlab::Database::LoadBalancing.enable?
|
||||
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
|
||||
end
|
||||
end
|
||||
|
||||
def project_authorizations_needs_refresh?(user)
|
||||
AuthorizedProjectUpdate::FindRecordsDueForRefreshService.new(user).needs_refresh?
|
||||
end
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: delayed_consistency_for_user_refresh_over_range_worker
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61883
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327092
|
||||
milestone: '13.12'
|
||||
type: development
|
||||
group: group::access
|
||||
default_enabled: false
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: periodic_project_authorization_update_via_replica
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58752
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327092
|
||||
milestone: '13.11'
|
||||
type: development
|
||||
group: group::access
|
||||
default_enabled: false
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: honor_escaped_markdown
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45922
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300531
|
||||
milestone: '13.9'
|
||||
name: remove_release_notes_from_tags_api
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63392
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290311
|
||||
milestone: '14.0'
|
||||
type: development
|
||||
group: 'group::project management'
|
||||
group: group::release
|
||||
default_enabled: false
|
|
@ -1,16 +1,18 @@
|
|||
---
|
||||
key_path: reply_by_email_enabled
|
||||
description: Whether incoming email is setup
|
||||
product_section: growth
|
||||
product_stage: growth
|
||||
product_group: group::product intelligence
|
||||
product_section: dev
|
||||
product_stage: plan
|
||||
product_group: group::certify
|
||||
product_category: collection
|
||||
value_type: boolean
|
||||
status: data_available
|
||||
time_frame: none
|
||||
data_source: system
|
||||
distribution:
|
||||
- ce
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
skip_validation: true
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class InitializeConversionOfGeoJobArtifactDeletedEventsToBigint < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
TABLE = :geo_job_artifact_deleted_events
|
||||
COLUMNS = %i(job_artifact_id)
|
||||
|
||||
def up
|
||||
initialize_conversion_of_integer_to_bigint(TABLE, COLUMNS)
|
||||
end
|
||||
|
||||
def down
|
||||
revert_initialize_conversion_of_integer_to_bigint(TABLE, COLUMNS)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BackfillGeoJobArtifactDeletedEventsForBigintConversion < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
TABLE = :geo_job_artifact_deleted_events
|
||||
COLUMNS = %i(job_artifact_id)
|
||||
|
||||
def up
|
||||
backfill_conversion_of_integer_to_bigint TABLE, COLUMNS
|
||||
end
|
||||
|
||||
def down
|
||||
revert_backfill_conversion_of_integer_to_bigint TABLE, COLUMNS
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class InitializeConversionOfCiStagesToBigint < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
TABLE = :ci_stages
|
||||
COLUMNS = %i(id)
|
||||
|
||||
def up
|
||||
initialize_conversion_of_integer_to_bigint(TABLE, COLUMNS)
|
||||
end
|
||||
|
||||
def down
|
||||
revert_initialize_conversion_of_integer_to_bigint(TABLE, COLUMNS)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BackfillCiStagesForBigintConversion < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
TABLE = :ci_stages
|
||||
COLUMNS = %i(id)
|
||||
|
||||
def up
|
||||
backfill_conversion_of_integer_to_bigint(TABLE, COLUMNS)
|
||||
end
|
||||
|
||||
def down
|
||||
revert_backfill_conversion_of_integer_to_bigint(TABLE, COLUMNS)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ScheduleDisableExpirationPoliciesLinkedToNoContainerImages < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
BATCH_SIZE = 30_000
|
||||
DELAY = 2.minutes.freeze
|
||||
DOWNTIME = false
|
||||
MIGRATION = 'DisableExpirationPoliciesLinkedToNoContainerImages'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
define_batchable_model('container_expiration_policies').where(enabled: true),
|
||||
MIGRATION,
|
||||
DELAY,
|
||||
batch_size: BATCH_SIZE,
|
||||
track_jobs: false,
|
||||
primary_column_name: :project_id
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# this migration is irreversible
|
||||
|
||||
# we can't accuretaly know which policies were previously enabled during the background migration
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BackfillDraftStatusOnMergeRequests < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
INDEX_NAME = "tmp_index_merge_requests_draft_and_status"
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :merge_requests, :id,
|
||||
where: "draft = false AND state_id = 1 AND ((title)::text ~* '^\\[draft\\]|\\(draft\\)|draft:|draft|\\[WIP\\]|WIP:|WIP'::text)",
|
||||
name: INDEX_NAME
|
||||
|
||||
update_column_in_batches(:merge_requests, :draft, true, batch_size: 100) do |table, query|
|
||||
query
|
||||
.where(table[:state_id].eq(1))
|
||||
.where(table[:draft].eq(false))
|
||||
.where(table[:title].matches_regexp('^\\[draft\\]|\\(draft\\)|draft:|draft|\\[WIP\\]|WIP:|WIP', false))
|
||||
end
|
||||
|
||||
remove_concurrent_index_by_name :merge_requests, INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :merge_requests, INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
9eb5e68b0d79863687530ff22cbe6a2bffd2e2d31237e919134b9ce77810b1a0
|
|
@ -0,0 +1 @@
|
|||
6568aa11d3652fb7ee23d2e6622a1038d891914f629438608993ff0d8b46b748
|
|
@ -0,0 +1 @@
|
|||
1a877c384c1e4e9e28a64c8c521aa72965c54d528044b076efdc75aeeb83d796
|
|
@ -0,0 +1 @@
|
|||
f80787d85538cedaba34cb204c98df2d0bbbf85f438d4df8f1187d2f4d881588
|
|
@ -0,0 +1 @@
|
|||
c395f52ee34cd758df87ba0f74f4528a189704498e133fa53f0dd3f6f31a77b3
|
|
@ -0,0 +1 @@
|
|||
9f8ff974adc7c20908cd423b2d3f69d8ec16b0fcbb8bfbdb9347a9ff3f3a007a
|
|
@ -125,6 +125,15 @@ BEGIN
|
|||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_490d204c00b3() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW."id_convert_to_bigint" := NEW."id";
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_51ab7cef8934() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
@ -208,6 +217,15 @@ BEGIN
|
|||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_f1ca8ec18d78() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW."job_artifact_id_convert_to_bigint" := NEW."job_artifact_id";
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TABLE audit_events (
|
||||
id bigint NOT NULL,
|
||||
author_id integer NOT NULL,
|
||||
|
@ -11195,6 +11213,7 @@ CREATE TABLE ci_stages (
|
|||
status integer,
|
||||
lock_version integer DEFAULT 0,
|
||||
"position" integer,
|
||||
id_convert_to_bigint bigint DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT check_81b431e49b CHECK ((lock_version IS NOT NULL))
|
||||
);
|
||||
|
||||
|
@ -13054,7 +13073,8 @@ ALTER SEQUENCE geo_hashed_storage_migrated_events_id_seq OWNED BY geo_hashed_sto
|
|||
CREATE TABLE geo_job_artifact_deleted_events (
|
||||
id bigint NOT NULL,
|
||||
job_artifact_id integer NOT NULL,
|
||||
file_path character varying NOT NULL
|
||||
file_path character varying NOT NULL,
|
||||
job_artifact_id_convert_to_bigint bigint DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE geo_job_artifact_deleted_events_id_seq
|
||||
|
@ -25314,6 +25334,8 @@ CREATE TRIGGER trigger_21e7a2602957 BEFORE INSERT OR UPDATE ON ci_build_needs FO
|
|||
|
||||
CREATE TRIGGER trigger_3f6129be01d2 BEFORE INSERT OR UPDATE ON ci_builds FOR EACH ROW EXECUTE FUNCTION trigger_3f6129be01d2();
|
||||
|
||||
CREATE TRIGGER trigger_490d204c00b3 BEFORE INSERT OR UPDATE ON ci_stages FOR EACH ROW EXECUTE FUNCTION trigger_490d204c00b3();
|
||||
|
||||
CREATE TRIGGER trigger_51ab7cef8934 BEFORE INSERT OR UPDATE ON ci_builds_runner_session FOR EACH ROW EXECUTE FUNCTION trigger_51ab7cef8934();
|
||||
|
||||
CREATE TRIGGER trigger_69523443cc10 BEFORE INSERT OR UPDATE ON events FOR EACH ROW EXECUTE FUNCTION trigger_69523443cc10();
|
||||
|
@ -25332,6 +25354,8 @@ CREATE TRIGGER trigger_be1804f21693 BEFORE INSERT OR UPDATE ON ci_job_artifacts
|
|||
|
||||
CREATE TRIGGER trigger_cf2f9e35f002 BEFORE INSERT OR UPDATE ON ci_build_trace_chunks FOR EACH ROW EXECUTE FUNCTION trigger_cf2f9e35f002();
|
||||
|
||||
CREATE TRIGGER trigger_f1ca8ec18d78 BEFORE INSERT OR UPDATE ON geo_job_artifact_deleted_events FOR EACH ROW EXECUTE FUNCTION trigger_f1ca8ec18d78();
|
||||
|
||||
CREATE TRIGGER trigger_has_external_issue_tracker_on_delete AFTER DELETE ON services FOR EACH ROW WHEN ((((old.category)::text = 'issue_tracker'::text) AND (old.active = true) AND (old.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
|
||||
|
||||
CREATE TRIGGER trigger_has_external_issue_tracker_on_insert AFTER INSERT ON services FOR EACH ROW WHEN ((((new.category)::text = 'issue_tracker'::text) AND (new.active = true) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
|
||||
|
|
|
@ -79,7 +79,7 @@ require 'json'
|
|||
require 'mail'
|
||||
|
||||
# The incoming variables are in JSON format so we need to parse it first.
|
||||
ARGS = JSON.parse(STDIN.read)
|
||||
ARGS = JSON.parse($stdin.read)
|
||||
|
||||
# We only want to trigger this file hook on the event project_create
|
||||
return unless ARGS['event_name'] == 'project_create'
|
||||
|
|
|
@ -26,7 +26,7 @@ You can enable output of Active Record debug logging in the Rails console
|
|||
session by running:
|
||||
|
||||
```ruby
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
ActiveRecord::Base.logger = Logger.new($stdout)
|
||||
```
|
||||
|
||||
This will show information about database queries triggered by any Ruby code
|
||||
|
|
|
@ -100,7 +100,7 @@ Rails.cache.instance_variable_get(:@data).keys
|
|||
|
||||
```ruby
|
||||
# Before 11.6.0
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
admin_token = User.find_by_username('ADMIN_USERNAME').personal_access_tokens.first.token
|
||||
app.get("URL/?private_token=#{admin_token}")
|
||||
|
||||
|
@ -113,7 +113,7 @@ Gitlab::Profiler.with_user(admin) { app.get(url) }
|
|||
## Using the GitLab profiler inside console (used as of 10.5)
|
||||
|
||||
```ruby
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
admin = User.find_by_username('ADMIN_USERNAME')
|
||||
Gitlab::Profiler.profile('URL', logger: logger, user: admin)
|
||||
```
|
||||
|
|
|
@ -46,7 +46,7 @@ Let's enable debug logging for Active Record so we can see the underlying
|
|||
database queries made:
|
||||
|
||||
```ruby
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
ActiveRecord::Base.logger = Logger.new($stdout)
|
||||
```
|
||||
|
||||
Now, let's try retrieving a user from the database:
|
||||
|
|
|
@ -8570,6 +8570,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="epicboardlistsepicfilters"></a>`epicFilters` | [`EpicFilters`](#epicfilters) | Filters applied when getting epic metadata in the epic board list. |
|
||||
| <a id="epicboardlistsid"></a>`id` | [`BoardsEpicListID`](#boardsepiclistid) | Find an epic board list by ID. |
|
||||
|
||||
### `EpicDescendantCount`
|
||||
|
|
|
@ -255,12 +255,8 @@ batches instead of doing this one by one:
|
|||
class ScheduleExtractServicesUrl < ActiveRecord::Migration[4.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
class Service < ActiveRecord::Base
|
||||
self.table_name = 'services'
|
||||
end
|
||||
|
||||
def up
|
||||
Service.select(:id).in_batches do |relation|
|
||||
define_batchable_model('services').select(:id).in_batches do |relation|
|
||||
jobs = relation.pluck(:id).map do |id|
|
||||
['ExtractServicesUrl', [id]]
|
||||
end
|
||||
|
@ -286,18 +282,12 @@ this:
|
|||
class ConsumeRemainingExtractServicesUrlJobs < ActiveRecord::Migration[4.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
class Service < ActiveRecord::Base
|
||||
include ::EachBatch
|
||||
|
||||
self.table_name = 'services'
|
||||
end
|
||||
|
||||
def up
|
||||
# This must be included
|
||||
Gitlab::BackgroundMigration.steal('ExtractServicesUrl')
|
||||
|
||||
# This should be included, but can be skipped - see below
|
||||
Service.where(url: nil).each_batch(of: 50) do |batch|
|
||||
define_batchable_model('services').where(url: nil).each_batch(of: 50) do |batch|
|
||||
range = batch.pluck('MIN(id)', 'MAX(id)').first
|
||||
|
||||
Gitlab::BackgroundMigration::ExtractServicesUrl.new.perform(*range)
|
||||
|
|
|
@ -308,12 +308,15 @@ We choose to use GitLab major version upgrades as a safe time to remove
|
|||
backwards compatibility for indices that have not been fully migrated. We
|
||||
[document this in our upgrade
|
||||
documentation](../update/index.md#upgrading-to-a-new-major-version). We also
|
||||
choose to remove the migration code and tests so that:
|
||||
choose to replace the migration code with the halted migration
|
||||
and remove tests so that:
|
||||
|
||||
- We don't need to maintain any code that is called from our Advanced Search
|
||||
migrations.
|
||||
- We don't waste CI time running tests for migrations that we don't support
|
||||
anymore.
|
||||
- Operators who have not run this migration and who upgrade directly to the
|
||||
target version will see a message prompting them to reindex from scratch.
|
||||
|
||||
To be extra safe, we will not delete migrations that were created in the last
|
||||
minor version before the major upgrade. So, if we are upgrading to `%14.0`,
|
||||
|
@ -334,18 +337,10 @@ For every migration that was created 2 minor versions before the major version
|
|||
being upgraded to, we do the following:
|
||||
|
||||
1. Confirm the migration has actually completed successfully for GitLab.com.
|
||||
1. Replace the content of `migrate` and `completed?` methods as follows:
|
||||
1. Replace the content of the migration with:
|
||||
|
||||
```ruby
|
||||
def migrate
|
||||
log_raise "Migration has been deleted in the last major version upgrade." \
|
||||
"Migrations are supposed to be finished before upgrading major version https://docs.gitlab.com/ee/update/#upgrading-to-a-new-major-version ." \
|
||||
"To correct this issue, recreate your index from scratch: https://docs.gitlab.com/ee/integration/elasticsearch.html#last-resort-to-recreate-an-index."
|
||||
end
|
||||
|
||||
def completed?
|
||||
false
|
||||
end
|
||||
include Elastic::MigrationObsolete
|
||||
```
|
||||
|
||||
1. Delete any spec files to support this migration.
|
||||
|
|
|
@ -976,6 +976,9 @@ If using a model in the migrations, you should first
|
|||
[clear the column cache](https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-reset_column_information)
|
||||
using `reset_column_information`.
|
||||
|
||||
If using a model that leverages single table inheritance (STI), there are [special
|
||||
considerations](single_table_inheritance.md#in-migrations).
|
||||
|
||||
This avoids problems where a column that you are using was altered and cached
|
||||
in a previous migration.
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ ActiveRecord and ActionController log output to that logger. Further options are
|
|||
documented with the method source.
|
||||
|
||||
```ruby
|
||||
Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first, logger: Logger.new(STDOUT))
|
||||
Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first, logger: Logger.new($stdout))
|
||||
```
|
||||
|
||||
There is also a RubyProf printer available:
|
||||
|
|
|
@ -22,3 +22,42 @@ The solution is very simple: just use a separate table for every type you'd
|
|||
otherwise store in the same table. For example, instead of having a `keys` table
|
||||
with `type` set to either `Key` or `DeployKey` you'd have two separate tables:
|
||||
`keys` and `deploy_keys`.
|
||||
|
||||
## In migrations
|
||||
|
||||
Whenever a model is used in a migration, single table inheritance should be disabled.
|
||||
Due to the way Rails loads associations (even in migrations), failing to disable STI
|
||||
could result in loading unexpected code or associations which may cause unintended
|
||||
side effects or failures during upgrades.
|
||||
|
||||
```ruby
|
||||
class SomeMigration < ActiveRecord::Migration[6.0]
|
||||
class Services < ActiveRecord::Base
|
||||
self.table_name = 'services'
|
||||
self.inheritance_column = :_type_disabled
|
||||
end
|
||||
|
||||
def up
|
||||
...
|
||||
```
|
||||
|
||||
If nothing needs to be added to the model other than disabling STI or `EachBatch`,
|
||||
use the helper `define_batchable_model` instead of defining the class.
|
||||
This ensures that the migration loads the columns for the migration in isolation,
|
||||
and the helper disables STI by default.
|
||||
|
||||
```ruby
|
||||
class EnqueueSomeBackgroundMigration < ActiveRecord::Migration[6.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
define_batchable_model('services').select(:id).in_batches do |relation|
|
||||
jobs = relation.pluck(:id).map do |id|
|
||||
['ExtractServicesUrl', [id]]
|
||||
end
|
||||
|
||||
BackgroundMigrationWorker.bulk_perform_async(jobs)
|
||||
end
|
||||
end
|
||||
...
|
||||
```
|
||||
|
|
|
@ -11,19 +11,21 @@ in lieu of the standard Spec helper. Instead of `require 'spec_helper'`, use
|
|||
`require 'rake_helper'`. The helper includes `spec_helper` for you, and configures
|
||||
a few other things to make testing Rake tasks easier.
|
||||
|
||||
At a minimum, requiring the Rake helper redirects `stdout`, include the
|
||||
runtime task helpers, and include the `RakeHelpers` Spec support module.
|
||||
At a minimum, requiring the Rake helper includes the runtime task helpers, and
|
||||
includes the `RakeHelpers` Spec support module.
|
||||
|
||||
The `RakeHelpers` module exposes a `run_rake_task(<task>)` method to make
|
||||
executing tasks simple. See `spec/support/helpers/rake_helpers.rb` for all available
|
||||
methods.
|
||||
|
||||
`$stdout` can be redirected by adding `:silence_stdout`.
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
require 'rake_helper'
|
||||
|
||||
describe 'gitlab:shell rake tasks' do
|
||||
describe 'gitlab:shell rake tasks', :silence_stdout do
|
||||
before do
|
||||
Rake.application.rake_require 'tasks/gitlab/shell'
|
||||
|
||||
|
|
|
@ -15580,11 +15580,11 @@ Whether incoming email is setup
|
|||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/settings/20210204124916_reply_by_email_enabled.yml)
|
||||
|
||||
Group: `group::product intelligence`
|
||||
Group: `group::certify`
|
||||
|
||||
Status: `data_available`
|
||||
|
||||
Tiers: `free`
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `search_unique_visits.i_search_advanced`
|
||||
|
||||
|
|
|
@ -369,6 +369,16 @@ NOTE:
|
|||
Specific information that follow related to Ruby and Git versions do not apply to [Omnibus installations](https://docs.gitlab.com/omnibus/)
|
||||
and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with appropriate Ruby and Git versions and are not using system binaries for Ruby and Git. There is no need to install Ruby or Git when utilizing these two approaches.
|
||||
|
||||
### 14.0.0
|
||||
|
||||
In GitLab 13.3 some [pipeline processing methods were deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/218536)
|
||||
and this code was completely removed in GitLab 14.0. If you plan to upgrade from
|
||||
**GitLab 13.2 or older** directly to 14.0, you should not have any pipelines running
|
||||
when you upgrade. The pipelines might report the wrong status when the upgrade completes.
|
||||
You should shut down GitLab and wait for all pipelines on runners to complete, then upgrade
|
||||
GitLab to 14.0. Alternatively, you can first upgrade GitLab to a version between 13.3 and
|
||||
13.12, then upgrade to 14.0.
|
||||
|
||||
### 13.11.0
|
||||
|
||||
Git 2.31.x and later is required. We recommend you use the
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env ruby
|
||||
x = STDIN.read
|
||||
x = $stdin.read
|
||||
File.write('/tmp/rb-data.txt', x)
|
||||
|
|
|
@ -61,6 +61,8 @@ module API
|
|||
optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database (deprecated in GitLab 11.7)'
|
||||
end
|
||||
post ':id/repository/tags', :release_orchestration do
|
||||
deprecate_release_notes unless params[:release_description].blank?
|
||||
|
||||
authorize_admin_tag
|
||||
|
||||
result = ::Tags::CreateService.new(user_project, current_user)
|
||||
|
@ -119,6 +121,7 @@ module API
|
|||
requires :description, type: String, desc: 'Release notes with markdown support'
|
||||
end
|
||||
post ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
|
||||
deprecate_release_notes
|
||||
authorize_create_release!
|
||||
|
||||
##
|
||||
|
@ -151,6 +154,7 @@ module API
|
|||
requires :description, type: String, desc: 'Release notes with markdown support'
|
||||
end
|
||||
put ':id/repository/tags/:tag_name/release', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :release_orchestration do
|
||||
deprecate_release_notes
|
||||
authorize_update_release!
|
||||
|
||||
result = ::Releases::UpdateService
|
||||
|
@ -177,6 +181,12 @@ module API
|
|||
def release
|
||||
@release ||= user_project.releases.find_by_tag(params[:tag])
|
||||
end
|
||||
|
||||
def deprecate_release_notes
|
||||
return unless Feature.enabled?(:remove_release_notes_from_tags_api, user_project, default_enabled: :yaml)
|
||||
|
||||
render_api_error!("Release notes modification via tags API is deprecated, see https://gitlab.com/gitlab-org/gitlab/-/issues/290311", 400)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,8 +30,6 @@ module Banzai
|
|||
LITERAL_KEYWORD = 'cmliteral'
|
||||
|
||||
def call
|
||||
return @text unless Feature.enabled?(:honor_escaped_markdown, context[:group] || context[:project]&.group)
|
||||
|
||||
@text.gsub(ASCII_PUNCTUATION) do |match|
|
||||
# The majority of markdown does not have literals. If none
|
||||
# are found, we can bypass the post filter
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
BATCH_SIZE = 1000
|
||||
|
||||
# This background migration disables container expiration policies connected
|
||||
# to a project that has no container repositories
|
||||
class DisableExpirationPoliciesLinkedToNoContainerImages
|
||||
# rubocop: disable Style/Documentation
|
||||
class ContainerExpirationPolicy < ActiveRecord::Base
|
||||
include EachBatch
|
||||
|
||||
self.table_name = 'container_expiration_policies'
|
||||
end
|
||||
# rubocop: enable Style/Documentation
|
||||
|
||||
def perform(from_id, to_id)
|
||||
ContainerExpirationPolicy.where(enabled: true, project_id: from_id..to_id).each_batch(of: BATCH_SIZE) do |batch|
|
||||
sql = <<-SQL
|
||||
WITH batched_relation AS MATERIALIZED (#{batch.select(:project_id).limit(BATCH_SIZE).to_sql})
|
||||
UPDATE container_expiration_policies
|
||||
SET enabled = FALSE
|
||||
FROM batched_relation
|
||||
WHERE container_expiration_policies.project_id = batched_relation.project_id
|
||||
AND NOT EXISTS (SELECT 1 FROM "container_repositories" WHERE container_repositories.project_id = container_expiration_policies.project_id)
|
||||
SQL
|
||||
execute(sql)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute(sql)
|
||||
ActiveRecord::Base
|
||||
.connection
|
||||
.execute(sql)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -170,7 +170,7 @@ module Gitlab
|
|||
def self.print_by_total_time(result, options = {})
|
||||
default_options = { sort_method: :total_time, filter_by: :total_time }
|
||||
|
||||
RubyProf::FlatPrinter.new(result).print(STDOUT, default_options.merge(options))
|
||||
RubyProf::FlatPrinter.new(result).print($stdout, default_options.merge(options))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,7 +22,7 @@ module Gitlab
|
|||
|
||||
CommandError = Class.new(StandardError)
|
||||
|
||||
def initialize(log_output = STDERR)
|
||||
def initialize(log_output = $stderr)
|
||||
require_relative '../../../lib/gitlab/sidekiq_logging/json_formatter'
|
||||
|
||||
# As recommended by https://github.com/mperham/sidekiq/wiki/Advanced-Options#concurrency
|
||||
|
|
|
@ -13,6 +13,10 @@ module Gitlab
|
|||
@metrics = init_metrics
|
||||
|
||||
@metrics[:sidekiq_concurrency].set({}, Sidekiq.options[:concurrency].to_i)
|
||||
|
||||
if ::Gitlab::Database::LoadBalancing.enable?
|
||||
@metrics[:sidekiq_load_balancing_count] = ::Gitlab::Metrics.counter(:sidekiq_load_balancing_count, 'Sidekiq jobs with load balancing')
|
||||
end
|
||||
end
|
||||
|
||||
def call(worker, job, queue)
|
||||
|
@ -69,6 +73,15 @@ module Gitlab
|
|||
@metrics[:sidekiq_redis_requests_duration_seconds].observe(labels, get_redis_time(instrumentation))
|
||||
@metrics[:sidekiq_elasticsearch_requests_total].increment(labels, get_elasticsearch_calls(instrumentation))
|
||||
@metrics[:sidekiq_elasticsearch_requests_duration_seconds].observe(labels, get_elasticsearch_time(instrumentation))
|
||||
|
||||
if ::Gitlab::Database::LoadBalancing.enable? && job[:database_chosen]
|
||||
load_balancing_labels = {
|
||||
database_chosen: job[:database_chosen],
|
||||
data_consistency: job[:data_consistency]
|
||||
}
|
||||
|
||||
@metrics[:sidekiq_load_balancing_count].increment(labels.merge(load_balancing_labels), 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ module Gitlab
|
|||
def prompt(message, choices = nil)
|
||||
begin
|
||||
print(message)
|
||||
answer = STDIN.gets.chomp
|
||||
answer = $stdin.gets.chomp
|
||||
end while choices.present? && !choices.include?(answer)
|
||||
answer
|
||||
end
|
||||
|
@ -70,12 +70,12 @@ module Gitlab
|
|||
#
|
||||
# message - custom message to display before input
|
||||
def prompt_for_password(message = 'Enter password: ')
|
||||
unless STDIN.tty?
|
||||
unless $stdin.tty?
|
||||
print(message)
|
||||
return STDIN.gets.chomp
|
||||
return $stdin.gets.chomp
|
||||
end
|
||||
|
||||
STDIN.getpass(message)
|
||||
$stdin.getpass(message)
|
||||
end
|
||||
|
||||
# Runs the given command and matches the output against the given pattern
|
||||
|
|
|
@ -9,7 +9,7 @@ module Gitlab
|
|||
attr_writer :logger
|
||||
|
||||
def logger
|
||||
@logger ||= Logger.new(STDOUT)
|
||||
@logger ||= Logger.new($stdout)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -67,7 +67,7 @@ module Gitlab
|
|||
|
||||
def log_info(details)
|
||||
details = base_log_data.merge(details)
|
||||
details = details.to_yaml if ActiveSupport::Logger.logger_outputs_to?(Measuring.logger, STDOUT)
|
||||
details = details.to_yaml if ActiveSupport::Logger.logger_outputs_to?(Measuring.logger, $stdout)
|
||||
Measuring.logger.info(details)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ desc 'GitLab | Artifacts | Migrate files for artifacts to comply with new storag
|
|||
namespace :gitlab do
|
||||
namespace :artifacts do
|
||||
task migrate: :environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
|
||||
helper = Gitlab::LocalAndRemoteStorageMigration::ArtifactMigrater.new(logger)
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
task migrate_to_local: :environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
|
||||
helper = Gitlab::LocalAndRemoteStorageMigration::ArtifactMigrater.new(logger)
|
||||
|
||||
|
|
|
@ -178,7 +178,7 @@ namespace :gitlab do
|
|||
return @logger if defined?(@logger)
|
||||
|
||||
@logger = if Rails.env.development? || Rails.env.production?
|
||||
Logger.new(STDOUT).tap do |stdout_logger|
|
||||
Logger.new($stdout).tap do |stdout_logger|
|
||||
stdout_logger.extend(ActiveSupport::Logger.broadcast(Rails.logger))
|
||||
stdout_logger.level = debug? ? Logger::DEBUG : Logger::INFO
|
||||
end
|
||||
|
|
|
@ -209,7 +209,7 @@ namespace :gitlab do
|
|||
raise "Index not found or not supported: #{args[:index_name]}" if indexes.empty?
|
||||
end
|
||||
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT) if Gitlab::Utils.to_boolean(ENV['LOG_QUERIES_TO_CONSOLE'], default: false)
|
||||
ActiveRecord::Base.logger = Logger.new($stdout) if Gitlab::Utils.to_boolean(ENV['LOG_QUERIES_TO_CONSOLE'], default: false)
|
||||
|
||||
Gitlab::Database::Reindexing.perform(indexes)
|
||||
rescue StandardError => e
|
||||
|
|
|
@ -14,14 +14,14 @@ namespace :gitlab do
|
|||
old_path = args.old_path
|
||||
else
|
||||
puts '=> Enter the path of the OLD file:'
|
||||
old_path = STDIN.gets.chomp
|
||||
old_path = $stdin.gets.chomp
|
||||
end
|
||||
|
||||
if args.new_path
|
||||
new_path = args.new_path
|
||||
else
|
||||
puts '=> Enter the path of the NEW file:'
|
||||
new_path = STDIN.gets.chomp
|
||||
new_path = $stdin.gets.chomp
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace :gitlab do
|
|||
namespace :doctor do
|
||||
desc "GitLab | Check if the database encrypted values can be decrypted using current secrets"
|
||||
task secrets: :gitlab_environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
|
||||
logger.level = Gitlab::Utils.to_boolean(ENV['VERBOSE']) ? Logger::DEBUG : Logger::INFO
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace :gitlab do
|
|||
namespace :secret do
|
||||
desc 'GitLab | LDAP | Secret | Write LDAP secrets'
|
||||
task write: [:environment] do
|
||||
content = STDIN.tty? ? STDIN.gets : STDIN.read
|
||||
content = $stdin.tty? ? $stdin.gets : $stdin.read
|
||||
Gitlab::EncryptedLdapCommand.write(content)
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ desc "GitLab | LFS | Migrate LFS objects to remote storage"
|
|||
namespace :gitlab do
|
||||
namespace :lfs do
|
||||
task migrate: :environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
logger.info('Starting transfer of LFS files to object storage')
|
||||
|
||||
LfsObject.with_files_stored_locally
|
||||
|
@ -20,7 +20,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
task migrate_to_local: :environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
logger.info('Starting transfer of LFS files to local storage')
|
||||
|
||||
LfsObject.with_files_stored_remotely
|
||||
|
|
|
@ -6,7 +6,7 @@ desc "GitLab | Packages | Build composer cache"
|
|||
namespace :gitlab do
|
||||
namespace :packages do
|
||||
task build_composer_cache: :environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
logger.info('Starting to build composer cache files')
|
||||
|
||||
::Packages::Package.composer.find_in_batches do |packages|
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
task generate_counts: :environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
logger.info('Building list of package events...')
|
||||
|
||||
path = Gitlab::UsageDataCounters::PackageEventCounter::KNOWN_EVENTS_PATH
|
||||
|
@ -26,7 +26,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
task generate_unique: :environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
logger.info('Building list of package events...')
|
||||
|
||||
path = File.join(File.dirname(Gitlab::UsageDataCounters::HLLRedisCounter::KNOWN_EVENTS_PATH), 'package_events.yml')
|
||||
|
|
|
@ -6,7 +6,7 @@ desc "GitLab | Packages | Migrate packages files to remote storage"
|
|||
namespace :gitlab do
|
||||
namespace :packages do
|
||||
task migrate: :environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
logger.info('Starting transfer of package files to object storage')
|
||||
|
||||
unless ::Packages::PackageFileUploader.object_store_enabled?
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
def logger
|
||||
@logger ||= Logger.new(STDOUT)
|
||||
@logger ||= Logger.new($stdout)
|
||||
end
|
||||
|
||||
def migration_threads
|
||||
|
@ -60,7 +60,7 @@ namespace :gitlab do
|
|||
|
||||
namespace :deployments do
|
||||
task migrate_to_object_storage: :gitlab_environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
|
||||
helper = Gitlab::LocalAndRemoteStorageMigration::PagesDeploymentMigrater.new(logger)
|
||||
|
||||
|
@ -72,7 +72,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
task migrate_to_local: :gitlab_environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
|
||||
helper = Gitlab::LocalAndRemoteStorageMigration::PagesDeploymentMigrater.new(logger)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ desc "GitLab | Terraform | Migrate Terraform states to remote storage"
|
|||
namespace :gitlab do
|
||||
namespace :terraform_states do
|
||||
task migrate: :environment do
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
logger.info('Starting transfer of Terraform states to object storage')
|
||||
|
||||
begin
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace :gitlab do
|
|||
# category to object storage
|
||||
desc 'GitLab | Uploads | Migrate the uploaded files of specified type to object storage'
|
||||
task :migrate, [:uploader_class, :model_class, :mounted_as] => :environment do |_t, args|
|
||||
Gitlab::Uploads::MigrationHelper.new(args, Logger.new(STDOUT)).migrate_to_remote_storage
|
||||
Gitlab::Uploads::MigrationHelper.new(args, Logger.new($stdout)).migrate_to_remote_storage
|
||||
end
|
||||
|
||||
namespace :migrate_to_local do
|
||||
|
@ -31,7 +31,7 @@ namespace :gitlab do
|
|||
|
||||
desc 'GitLab | Uploads | Migrate the uploaded files of specified type to local storage'
|
||||
task :migrate_to_local, [:uploader_class, :model_class, :mounted_as] => :environment do |_t, args|
|
||||
Gitlab::Uploads::MigrationHelper.new(args, Logger.new(STDOUT)).migrate_to_local_storage
|
||||
Gitlab::Uploads::MigrationHelper.new(args, Logger.new($stdout)).migrate_to_local_storage
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace :gitlab do
|
|||
args.with_defaults(dry_run: 'true')
|
||||
args.with_defaults(sleep_time: 0.3)
|
||||
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
|
||||
sanitizer = Gitlab::Sanitizers::Exif.new(logger: logger)
|
||||
sanitizer.batch_clean(start_id: args.start_id, stop_id: args.stop_id,
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
def update_certificates
|
||||
logger = Logger.new(STDOUT)
|
||||
logger = Logger.new($stdout)
|
||||
|
||||
unless X509CommitSignature.exists?
|
||||
logger.info("Unable to find any x509 commit signatures. Exiting.")
|
||||
|
|
|
@ -38,7 +38,7 @@ class GithubImport
|
|||
puts "This will import GitHub #{@repo.full_name.bright} into GitLab #{@project_path.bright} as #{@current_user.name}"
|
||||
puts "Permission checks are ignored. Press any key to continue.".color(:red)
|
||||
|
||||
STDIN.getch
|
||||
$stdin.getch
|
||||
|
||||
puts 'Starting the import (this could take a while)'.color(:green)
|
||||
end
|
||||
|
@ -131,7 +131,7 @@ class GithubRepos
|
|||
end
|
||||
|
||||
def repo_id
|
||||
@repo_id ||= STDIN.gets.chomp.to_i
|
||||
@repo_id ||= $stdin.gets.chomp.to_i
|
||||
end
|
||||
|
||||
def repos
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace :tokens do
|
|||
def reset_all_users_token(reset_token_method)
|
||||
TmpUser.find_in_batches do |batch|
|
||||
puts "Processing batch starting with user ID: #{batch.first.id}"
|
||||
STDOUT.flush
|
||||
$stdout.flush
|
||||
|
||||
batch.each(&reset_token_method)
|
||||
end
|
||||
|
|
|
@ -11339,9 +11339,6 @@ msgstr ""
|
|||
msgid "DevopsAdoption|MRs"
|
||||
msgstr ""
|
||||
|
||||
msgid "DevopsAdoption|Maximum %{maxSegments} groups allowed"
|
||||
msgstr ""
|
||||
|
||||
msgid "DevopsAdoption|My group"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
"lodash": "^4.17.20",
|
||||
"marked": "^0.3.12",
|
||||
"mathjax": "3",
|
||||
"mermaid": "^8.9.2",
|
||||
"mermaid": "^8.10.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"monaco-editor-webpack-plugin": "^1.9.1",
|
||||
|
|
|
@ -30,7 +30,7 @@ module QA
|
|||
element :labels_dropdown_content
|
||||
end
|
||||
|
||||
base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue' do
|
||||
base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue' do
|
||||
element :labels_edit_button
|
||||
end
|
||||
|
||||
|
|
|
@ -24,11 +24,11 @@ module QA
|
|||
element :create_new_board_button
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue' do
|
||||
view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue' do
|
||||
element :labels_dropdown_content
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue' do
|
||||
view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue' do
|
||||
element :labels_edit_button
|
||||
end
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ module QA
|
|||
end
|
||||
|
||||
def run
|
||||
STDOUT.puts 'Running...'
|
||||
$stdout.puts 'Running...'
|
||||
|
||||
# Fetch group's id
|
||||
group_id = fetch_group_id
|
||||
|
@ -30,16 +30,16 @@ module QA
|
|||
|
||||
# Do not delete projects that are less than 4 days old (for debugging purposes)
|
||||
project_ids = fetch_project_ids(group_id, total_project_pages)
|
||||
STDOUT.puts "Number of projects to be deleted: #{project_ids.length}"
|
||||
$stdout.puts "Number of projects to be deleted: #{project_ids.length}"
|
||||
|
||||
delete_projects(project_ids) unless project_ids.empty?
|
||||
STDOUT.puts "\nDone"
|
||||
$stdout.puts "\nDone"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_projects(project_ids)
|
||||
STDOUT.puts "Deleting #{project_ids.length} projects..."
|
||||
$stdout.puts "Deleting #{project_ids.length} projects..."
|
||||
project_ids.each do |project_id|
|
||||
delete_response = delete Runtime::API::Request.new(@api_client, "/projects/#{project_id}").url
|
||||
dot_or_f = delete_response.code.between?(200, 300) ? "\e[32m.\e[0m" : "\e[31mF\e[0m"
|
||||
|
|
|
@ -20,7 +20,7 @@ module QA
|
|||
end
|
||||
|
||||
def run
|
||||
STDOUT.puts 'Running...'
|
||||
$stdout.puts 'Running...'
|
||||
|
||||
# Fetch group's id
|
||||
group_id = fetch_group_id
|
||||
|
@ -29,16 +29,16 @@ module QA
|
|||
total_sub_group_pages = sub_groups_head_response.headers[:x_total_pages]
|
||||
|
||||
sub_group_ids = fetch_subgroup_ids(group_id, total_sub_group_pages)
|
||||
STDOUT.puts "Number of Sub Groups not already marked for deletion: #{sub_group_ids.length}"
|
||||
$stdout.puts "Number of Sub Groups not already marked for deletion: #{sub_group_ids.length}"
|
||||
|
||||
delete_subgroups(sub_group_ids) unless sub_group_ids.empty?
|
||||
STDOUT.puts "\nDone"
|
||||
$stdout.puts "\nDone"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_subgroups(sub_group_ids)
|
||||
STDOUT.puts "Deleting #{sub_group_ids.length} subgroups..."
|
||||
$stdout.puts "Deleting #{sub_group_ids.length} subgroups..."
|
||||
sub_group_ids.each do |subgroup_id|
|
||||
delete_response = delete Runtime::API::Request.new(@api_client, "/groups/#{subgroup_id}").url
|
||||
dot_or_f = delete_response.code == 202 ? "\e[32m.\e[0m" : "\e[31mF\e[0m"
|
||||
|
|
|
@ -30,18 +30,18 @@ module QA
|
|||
end
|
||||
|
||||
def run
|
||||
STDOUT.puts 'Running...'
|
||||
$stdout.puts 'Running...'
|
||||
|
||||
keys_head_response = head Runtime::API::Request.new(@api_client, "/user/keys", per_page: ITEMS_PER_PAGE).url
|
||||
total_pages = keys_head_response.headers[:x_total_pages]
|
||||
|
||||
test_ssh_key_ids = fetch_test_ssh_key_ids(total_pages)
|
||||
STDOUT.puts "Number of test ssh keys to be deleted: #{test_ssh_key_ids.length}"
|
||||
$stdout.puts "Number of test ssh keys to be deleted: #{test_ssh_key_ids.length}"
|
||||
|
||||
return if dry_run?
|
||||
|
||||
delete_ssh_keys(test_ssh_key_ids) unless test_ssh_key_ids.empty?
|
||||
STDOUT.puts "\nDone"
|
||||
$stdout.puts "\nDone"
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -50,7 +50,7 @@ module QA
|
|||
alias_method :dry_run?, :dry_run
|
||||
|
||||
def delete_ssh_keys(ssh_key_ids)
|
||||
STDOUT.puts "Deleting #{ssh_key_ids.length} ssh keys..."
|
||||
$stdout.puts "Deleting #{ssh_key_ids.length} ssh keys..."
|
||||
ssh_key_ids.each do |key_id|
|
||||
delete_response = delete Runtime::API::Request.new(@api_client, "/user/keys/#{key_id}").url
|
||||
dot_or_f = delete_response.code == 204 ? "\e[32m.\e[0m" : "\e[31mF\e[0m"
|
||||
|
|
|
@ -26,7 +26,7 @@ module QA
|
|||
end
|
||||
|
||||
def all
|
||||
STDOUT.puts 'Running...'
|
||||
$stdout.puts 'Running...'
|
||||
group_id = create_group
|
||||
create_project(group_id)
|
||||
|
||||
|
@ -50,23 +50,23 @@ module QA
|
|||
end
|
||||
|
||||
threads_arr.each(&:join)
|
||||
STDOUT.puts "\nURLs: #{@urls}"
|
||||
$stdout.puts "\nURLs: #{@urls}"
|
||||
File.open("urls.yml", "w") { |file| file.puts @urls.stringify_keys.to_yaml }
|
||||
STDOUT.puts "\nDone"
|
||||
$stdout.puts "\nDone"
|
||||
end
|
||||
|
||||
def create_group
|
||||
group_search_response = create_a_group_api_req(@group_name, @visibility)
|
||||
group = JSON.parse(group_search_response.body)
|
||||
@urls[:group_page] = group["web_url"]
|
||||
STDOUT.puts "Created a group: #{@urls[:group_page]}"
|
||||
$stdout.puts "Created a group: #{@urls[:group_page]}"
|
||||
group["id"]
|
||||
end
|
||||
|
||||
def create_project(group_id)
|
||||
create_project_response = create_a_project_api_req(@project_name, group_id, @visibility)
|
||||
@urls[:project_page] = JSON.parse(create_project_response.body)["web_url"]
|
||||
STDOUT.puts "Created a project: #{@urls[:project_page]}"
|
||||
$stdout.puts "Created a project: #{@urls[:project_page]}"
|
||||
end
|
||||
|
||||
def create_many_issues
|
||||
|
@ -74,7 +74,7 @@ module QA
|
|||
create_an_issue_api_req("#{@group_name}%2F#{@project_name}", "issue#{i}", "desc#{i}")
|
||||
end
|
||||
@urls[:issues_list_page] = @urls[:project_page] + "/issues"
|
||||
STDOUT.puts "Created many issues: #{@urls[:issues_list_page]}"
|
||||
$stdout.puts "Created many issues: #{@urls[:issues_list_page]}"
|
||||
end
|
||||
|
||||
def create_many_todos
|
||||
|
@ -82,7 +82,7 @@ module QA
|
|||
create_a_todo_api_req("#{@group_name}%2F#{@project_name}", "#{i + 1}")
|
||||
end
|
||||
@urls[:todos_page] = ENV['GITLAB_ADDRESS'] + "/dashboard/todos"
|
||||
STDOUT.puts "Created many todos: #{@urls[:todos_page]}"
|
||||
$stdout.puts "Created many todos: #{@urls[:todos_page]}"
|
||||
end
|
||||
|
||||
def create_many_labels
|
||||
|
@ -90,7 +90,7 @@ module QA
|
|||
create_a_label_api_req("#{@group_name}%2F#{@project_name}", "label#{i}", "#{Faker::Color.hex_color}")
|
||||
end
|
||||
@urls[:labels_page] = @urls[:project_page] + "/labels"
|
||||
STDOUT.puts "Created many labels: #{@urls[:labels_page]}"
|
||||
$stdout.puts "Created many labels: #{@urls[:labels_page]}"
|
||||
end
|
||||
|
||||
def create_many_merge_requests
|
||||
|
@ -98,7 +98,7 @@ module QA
|
|||
create_a_merge_request_api_req("#{@group_name}%2F#{@project_name}", "branch#{i}", Runtime::Env.default_branch, "MR#{i}")
|
||||
end
|
||||
@urls[:mr_list_page] = @urls[:project_page] + "/merge_requests"
|
||||
STDOUT.puts "Created many MRs: #{@urls[:mr_list_page]}"
|
||||
$stdout.puts "Created many MRs: #{@urls[:mr_list_page]}"
|
||||
end
|
||||
|
||||
def create_many_new_files
|
||||
|
@ -109,7 +109,7 @@ module QA
|
|||
end
|
||||
|
||||
@urls[:files_page] = @urls[:project_page] + "/tree/#{Runtime::Env.default_branch}"
|
||||
STDOUT.puts "Added many new files: #{@urls[:files_page]}"
|
||||
$stdout.puts "Added many new files: #{@urls[:files_page]}"
|
||||
end
|
||||
|
||||
def create_many_branches
|
||||
|
@ -117,7 +117,7 @@ module QA
|
|||
create_a_branch_api_req("branch#{i}", "#{@group_name}%2F#{@project_name}")
|
||||
end
|
||||
@urls[:branches_page] = @urls[:project_page] + "/-/branches"
|
||||
STDOUT.puts "Created many branches: #{@urls[:branches_page]}"
|
||||
$stdout.puts "Created many branches: #{@urls[:branches_page]}"
|
||||
end
|
||||
|
||||
def create_an_issue_with_many_discussions
|
||||
|
@ -130,7 +130,7 @@ module QA
|
|||
# Add description and labels
|
||||
update_an_issue_api_req("#{@group_name}%2F#{@project_name}", issue_id, "#{Faker::Lorem.sentences(500).join(" ")}", labels_list)
|
||||
@urls[:large_issue] = @urls[:project_page] + "/issues/#{issue_id}"
|
||||
STDOUT.puts "Created an issue with many discussions: #{@urls[:large_issue]}"
|
||||
$stdout.puts "Created an issue with many discussions: #{@urls[:large_issue]}"
|
||||
end
|
||||
|
||||
def create_an_mr_with_large_files_and_many_mr_discussions
|
||||
|
@ -178,7 +178,7 @@ module QA
|
|||
create_a_discussion_on_mr_api_req("#{@group_name}%2F#{@project_name}", iid, "Let us discuss")
|
||||
end
|
||||
@urls[:large_mr] = JSON.parse(create_mr_response.body)["web_url"]
|
||||
STDOUT.puts "Created an MR with many discussions and many very large Files: #{@urls[:large_mr]}"
|
||||
$stdout.puts "Created an MR with many discussions and many very large Files: #{@urls[:large_mr]}"
|
||||
end
|
||||
|
||||
def create_diff_note(iid, file_count, line_count, head_sha, start_sha, base_sha, line_type)
|
||||
|
@ -205,7 +205,7 @@ module QA
|
|||
100.times do |i|
|
||||
update_file_api_req(file_name, branch_name, project_path, Faker::Lorem.sentences(5).join(" "), Faker::Lorem.sentences(500).join("\n"))
|
||||
end
|
||||
STDOUT.puts "Using branch: #{branch_name}, created an MR with many commits: #{@urls[:mr_with_many_commits]}"
|
||||
$stdout.puts "Using branch: #{branch_name}, created an MR with many commits: #{@urls[:mr_with_many_commits]}"
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -12,7 +12,7 @@ module QA
|
|||
def run
|
||||
do_run
|
||||
rescue Net::ReadTimeout
|
||||
STDOUT.puts 'Net::ReadTimeout during run. Trying again'
|
||||
$stdout.puts 'Net::ReadTimeout during run. Trying again'
|
||||
run
|
||||
end
|
||||
|
||||
|
@ -23,7 +23,7 @@ module QA
|
|||
raise ArgumentError, "Please provide GITLAB_PASSWORD" unless ENV['GITLAB_PASSWORD']
|
||||
raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
|
||||
|
||||
STDOUT.puts 'Running...'
|
||||
$stdout.puts 'Running...'
|
||||
|
||||
Runtime::Browser.visit(ENV['GITLAB_ADDRESS'], Page::Main::Login)
|
||||
Page::Main::Login.perform(&:sign_in_using_credentials)
|
||||
|
|
|
@ -419,7 +419,7 @@ module Trigger
|
|||
raise "#{self.class.unscoped_class_name} did not succeed!"
|
||||
end
|
||||
|
||||
STDOUT.flush
|
||||
$stdout.flush
|
||||
end
|
||||
|
||||
raise "#{self.class.unscoped_class_name} timed out after waiting for #{duration} minutes!"
|
||||
|
|
|
@ -211,11 +211,7 @@ RSpec.describe Projects::ForksController do
|
|||
|
||||
create(:group, :public).add_owner(user)
|
||||
|
||||
# TODO: There is another N+1 caused by user.can?(:create_projects, namespace)
|
||||
# Defined in ForkNamespaceEntity
|
||||
extra_count = 1
|
||||
|
||||
expect { do_request.call }.not_to exceed_query_limit(control.count + extra_count)
|
||||
expect { do_request.call }.not_to exceed_query_limit(control)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -247,6 +247,36 @@ RSpec.describe 'Mermaid rendering', :js do
|
|||
expect(page).to have_selector('.js-lazy-render-mermaid-container')
|
||||
end
|
||||
end
|
||||
|
||||
it 'renders without any limits on wiki page', :js do
|
||||
graph_edges = "A-->B;B-->A;"
|
||||
|
||||
description = <<~MERMAID
|
||||
```mermaid
|
||||
graph LR
|
||||
#{graph_edges}
|
||||
```
|
||||
MERMAID
|
||||
|
||||
description *= 51
|
||||
|
||||
project = create(:project, :public)
|
||||
|
||||
wiki_page = build(:wiki_page, { container: project, content: description })
|
||||
wiki_page.create message: 'mermaid test commit' # rubocop:disable Rails/SaveBang
|
||||
wiki_page = project.wiki.find_page(wiki_page.slug)
|
||||
|
||||
visit project_wiki_path(project, wiki_page)
|
||||
|
||||
wait_for_requests
|
||||
wait_for_mermaid
|
||||
|
||||
page.within('.js-wiki-page-content') do
|
||||
expect(page).not_to have_selector('.lazy-alert-shown')
|
||||
|
||||
expect(page).not_to have_selector('.js-lazy-render-mermaid-container')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def wait_for_mermaid
|
||||
|
|
|
@ -35,27 +35,36 @@ describe('Awards app actions', () => {
|
|||
});
|
||||
|
||||
describe('success', () => {
|
||||
beforeEach(() => {
|
||||
mock
|
||||
.onGet('/awards', { params: { per_page: 100, page: '1' } })
|
||||
.reply(200, ['thumbsup'], { 'x-next-page': '2' });
|
||||
mock.onGet('/awards', { params: { per_page: 100, page: '2' } }).reply(200, ['thumbsdown']);
|
||||
});
|
||||
describe.each`
|
||||
relativeRootUrl
|
||||
${null}
|
||||
${'/gitlab'}
|
||||
`('with relative_root_url as $relativeRootUrl', ({ relativeRootUrl }) => {
|
||||
beforeEach(() => {
|
||||
window.gon = { relative_url_root: relativeRootUrl };
|
||||
mock
|
||||
.onGet(`${relativeRootUrl || ''}/awards`, { params: { per_page: 100, page: '1' } })
|
||||
.reply(200, ['thumbsup'], { 'x-next-page': '2' });
|
||||
mock
|
||||
.onGet(`${relativeRootUrl || ''}/awards`, { params: { per_page: 100, page: '2' } })
|
||||
.reply(200, ['thumbsdown']);
|
||||
});
|
||||
|
||||
it('commits FETCH_AWARDS_SUCCESS', async () => {
|
||||
window.gon = { current_user_id: 1 };
|
||||
it('commits FETCH_AWARDS_SUCCESS', async () => {
|
||||
window.gon.current_user_id = 1;
|
||||
|
||||
await testAction(
|
||||
actions.fetchAwards,
|
||||
'1',
|
||||
{ path: '/awards' },
|
||||
[{ type: 'FETCH_AWARDS_SUCCESS', payload: ['thumbsup'] }],
|
||||
[{ type: 'fetchAwards', payload: '2' }],
|
||||
);
|
||||
});
|
||||
await testAction(
|
||||
actions.fetchAwards,
|
||||
'1',
|
||||
{ path: '/awards' },
|
||||
[{ type: 'FETCH_AWARDS_SUCCESS', payload: ['thumbsup'] }],
|
||||
[{ type: 'fetchAwards', payload: '2' }],
|
||||
);
|
||||
});
|
||||
|
||||
it('does not commit FETCH_AWARDS_SUCCESS when user signed out', async () => {
|
||||
await testAction(actions.fetchAwards, '1', { path: '/awards' }, [], []);
|
||||
it('does not commit FETCH_AWARDS_SUCCESS when user signed out', async () => {
|
||||
await testAction(actions.fetchAwards, '1', { path: '/awards' }, [], []);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -85,81 +94,91 @@ describe('Awards app actions', () => {
|
|||
mock.restore();
|
||||
});
|
||||
|
||||
describe('adding new award', () => {
|
||||
describe('success', () => {
|
||||
beforeEach(() => {
|
||||
mock.onPost('/awards').reply(200, { id: 1 });
|
||||
describe.each`
|
||||
relativeRootUrl
|
||||
${null}
|
||||
${'/gitlab'}
|
||||
`('with relative_root_url as $relativeRootUrl', ({ relativeRootUrl }) => {
|
||||
beforeEach(() => {
|
||||
window.gon = { relative_url_root: relativeRootUrl };
|
||||
});
|
||||
|
||||
describe('adding new award', () => {
|
||||
describe('success', () => {
|
||||
beforeEach(() => {
|
||||
mock.onPost(`${relativeRootUrl || ''}/awards`).reply(200, { id: 1 });
|
||||
});
|
||||
|
||||
it('commits ADD_NEW_AWARD', async () => {
|
||||
testAction(actions.toggleAward, null, { path: '/awards', awards: [] }, [
|
||||
{ type: 'ADD_NEW_AWARD', payload: { id: 1 } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('commits ADD_NEW_AWARD', async () => {
|
||||
testAction(actions.toggleAward, null, { path: '/awards', awards: [] }, [
|
||||
{ type: 'ADD_NEW_AWARD', payload: { id: 1 } },
|
||||
]);
|
||||
describe('error', () => {
|
||||
beforeEach(() => {
|
||||
mock.onPost(`${relativeRootUrl || ''}/awards`).reply(500);
|
||||
});
|
||||
|
||||
it('calls Sentry.captureException', async () => {
|
||||
await testAction(
|
||||
actions.toggleAward,
|
||||
null,
|
||||
{ path: '/awards', awards: [] },
|
||||
[],
|
||||
[],
|
||||
() => {
|
||||
expect(Sentry.captureException).toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('error', () => {
|
||||
beforeEach(() => {
|
||||
mock.onPost('/awards').reply(500);
|
||||
describe('removing a award', () => {
|
||||
const mockData = { id: 1, name: 'thumbsup', user: { id: 1 } };
|
||||
|
||||
describe('success', () => {
|
||||
beforeEach(() => {
|
||||
mock.onDelete(`${relativeRootUrl || ''}/awards/1`).reply(200);
|
||||
});
|
||||
|
||||
it('commits REMOVE_AWARD', async () => {
|
||||
testAction(
|
||||
actions.toggleAward,
|
||||
'thumbsup',
|
||||
{
|
||||
path: '/awards',
|
||||
currentUserId: 1,
|
||||
awards: [mockData],
|
||||
},
|
||||
[{ type: 'REMOVE_AWARD', payload: 1 }],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls Sentry.captureException', async () => {
|
||||
await testAction(
|
||||
actions.toggleAward,
|
||||
null,
|
||||
{ path: '/awards', awards: [] },
|
||||
[],
|
||||
[],
|
||||
() => {
|
||||
expect(Sentry.captureException).toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('error', () => {
|
||||
beforeEach(() => {
|
||||
mock.onDelete(`${relativeRootUrl || ''}/awards/1`).reply(500);
|
||||
});
|
||||
|
||||
describe('removing a award', () => {
|
||||
const mockData = { id: 1, name: 'thumbsup', user: { id: 1 } };
|
||||
|
||||
describe('success', () => {
|
||||
beforeEach(() => {
|
||||
mock.onDelete('/awards/1').reply(200);
|
||||
});
|
||||
|
||||
it('commits REMOVE_AWARD', async () => {
|
||||
testAction(
|
||||
actions.toggleAward,
|
||||
'thumbsup',
|
||||
{
|
||||
path: '/awards',
|
||||
currentUserId: 1,
|
||||
awards: [mockData],
|
||||
},
|
||||
[{ type: 'REMOVE_AWARD', payload: 1 }],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error', () => {
|
||||
beforeEach(() => {
|
||||
mock.onDelete('/awards/1').reply(500);
|
||||
});
|
||||
|
||||
it('calls Sentry.captureException', async () => {
|
||||
await testAction(
|
||||
actions.toggleAward,
|
||||
'thumbsup',
|
||||
{
|
||||
path: '/awards',
|
||||
currentUserId: 1,
|
||||
awards: [mockData],
|
||||
},
|
||||
[],
|
||||
[],
|
||||
() => {
|
||||
expect(Sentry.captureException).toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
it('calls Sentry.captureException', async () => {
|
||||
await testAction(
|
||||
actions.toggleAward,
|
||||
'thumbsup',
|
||||
{
|
||||
path: '/awards',
|
||||
currentUserId: 1,
|
||||
awards: [mockData],
|
||||
},
|
||||
[],
|
||||
[],
|
||||
() => {
|
||||
expect(Sentry.captureException).toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
|
||||
import LabelsSelect from '~/labels_select';
|
||||
import BaseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue';
|
||||
|
||||
import { mockConfig, mockLabels } from './mock_data';
|
||||
|
||||
const createComponent = (config = mockConfig) =>
|
||||
shallowMount(BaseComponent, {
|
||||
propsData: config,
|
||||
});
|
||||
|
||||
describe('BaseComponent', () => {
|
||||
let wrapper;
|
||||
let vm;
|
||||
|
||||
beforeEach((done) => {
|
||||
wrapper = createComponent();
|
||||
|
||||
({ vm } = wrapper);
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('hiddenInputName', () => {
|
||||
it('returns correct string when showCreate prop is `true`', () => {
|
||||
expect(vm.hiddenInputName).toBe('issue[label_names][]');
|
||||
});
|
||||
|
||||
it('returns correct string when showCreate prop is `false`', async () => {
|
||||
await wrapper.setProps({ showCreate: false });
|
||||
|
||||
expect(vm.hiddenInputName).toBe('label_id[]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createLabelTitle', () => {
|
||||
it('returns `Create project label` when `isProject` prop is true', () => {
|
||||
expect(vm.createLabelTitle).toBe('Create project label');
|
||||
});
|
||||
|
||||
it('return `Create group label` when `isProject` prop is false', async () => {
|
||||
await wrapper.setProps({ isProject: false });
|
||||
|
||||
expect(vm.createLabelTitle).toBe('Create group label');
|
||||
});
|
||||
});
|
||||
|
||||
describe('manageLabelsTitle', () => {
|
||||
it('returns `Manage project labels` when `isProject` prop is true', () => {
|
||||
expect(vm.manageLabelsTitle).toBe('Manage project labels');
|
||||
});
|
||||
|
||||
it('return `Manage group labels` when `isProject` prop is false', async () => {
|
||||
await wrapper.setProps({ isProject: false });
|
||||
|
||||
expect(vm.manageLabelsTitle).toBe('Manage group labels');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('handleClick', () => {
|
||||
it('emits onLabelClick event with label and list of labels as params', () => {
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
vm.handleClick(mockLabels[0]);
|
||||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCollapsedValueClick', () => {
|
||||
it('emits toggleCollapse event on component', () => {
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
vm.handleCollapsedValueClick();
|
||||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('toggleCollapse');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleDropdownHidden', () => {
|
||||
it('emits onDropdownClose event on component', () => {
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
vm.handleDropdownHidden();
|
||||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('onDropdownClose');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mounted', () => {
|
||||
it('creates LabelsSelect object and assigns it to `labelsDropdon` as prop', () => {
|
||||
expect(vm.labelsDropdown instanceof LabelsSelect).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders component container element with classes `block labels`', () => {
|
||||
expect(vm.$el.classList.contains('block')).toBe(true);
|
||||
expect(vm.$el.classList.contains('labels')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders `.selectbox` element', () => {
|
||||
expect(vm.$el.querySelector('.selectbox')).not.toBeNull();
|
||||
expect(vm.$el.querySelector('.selectbox').getAttribute('style')).toBe('display: none;');
|
||||
});
|
||||
|
||||
it('renders `.dropdown` element', () => {
|
||||
expect(vm.$el.querySelector('.dropdown')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders `.dropdown-menu` element', () => {
|
||||
const dropdownMenuEl = vm.$el.querySelector('.dropdown-menu');
|
||||
|
||||
expect(dropdownMenuEl).not.toBeNull();
|
||||
expect(dropdownMenuEl.querySelector('.dropdown-page-one')).not.toBeNull();
|
||||
expect(dropdownMenuEl.querySelector('.dropdown-content')).not.toBeNull();
|
||||
expect(dropdownMenuEl.querySelector('.dropdown-loading')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,90 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue';
|
||||
|
||||
import { mockConfig, mockLabels } from './mock_data';
|
||||
|
||||
const componentConfig = {
|
||||
...mockConfig,
|
||||
fieldName: 'label_id[]',
|
||||
labels: mockLabels,
|
||||
showExtraOptions: false,
|
||||
};
|
||||
|
||||
const createComponent = (config = componentConfig) => {
|
||||
const Component = Vue.extend(dropdownButtonComponent);
|
||||
|
||||
return mountComponent(Component, config);
|
||||
};
|
||||
|
||||
describe('DropdownButtonComponent', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
vm = createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('dropdownToggleText', () => {
|
||||
it('returns text as `Label` when `labels` prop is empty array', () => {
|
||||
const mockEmptyLabels = { ...componentConfig, labels: [] };
|
||||
const vmEmptyLabels = createComponent(mockEmptyLabels);
|
||||
|
||||
expect(vmEmptyLabels.dropdownToggleText).toBe('Label');
|
||||
vmEmptyLabels.$destroy();
|
||||
});
|
||||
|
||||
it('returns first label name with remaining label count when `labels` prop has more than one item', () => {
|
||||
const mockMoreLabels = { ...componentConfig, labels: mockLabels.concat(mockLabels) };
|
||||
const vmMoreLabels = createComponent(mockMoreLabels);
|
||||
|
||||
expect(vmMoreLabels.dropdownToggleText).toBe(
|
||||
`Foo Label +${mockMoreLabels.labels.length - 1} more`,
|
||||
);
|
||||
vmMoreLabels.$destroy();
|
||||
});
|
||||
|
||||
it('returns first label name when `labels` prop has only one item present', () => {
|
||||
const singleLabel = { ...componentConfig, labels: [mockLabels[0]] };
|
||||
const vmSingleLabel = createComponent(singleLabel);
|
||||
|
||||
expect(vmSingleLabel.dropdownToggleText).toBe(mockLabels[0].title);
|
||||
|
||||
vmSingleLabel.$destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders component container element of type `button`', () => {
|
||||
expect(vm.$el.nodeName).toBe('BUTTON');
|
||||
});
|
||||
|
||||
it('renders component container element with required data attributes', () => {
|
||||
expect(vm.$el.dataset.abilityName).toBe(vm.abilityName);
|
||||
expect(vm.$el.dataset.fieldName).toBe(vm.fieldName);
|
||||
expect(vm.$el.dataset.issueUpdate).toBe(vm.updatePath);
|
||||
expect(vm.$el.dataset.labels).toBe(vm.labelsPath);
|
||||
expect(vm.$el.dataset.namespacePath).toBe(vm.namespace);
|
||||
expect(vm.$el.dataset.showAny).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('renders dropdown toggle text element', () => {
|
||||
const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text');
|
||||
|
||||
expect(dropdownToggleTextEl).not.toBeNull();
|
||||
expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label +1 more');
|
||||
});
|
||||
|
||||
it('renders dropdown button icon', () => {
|
||||
const dropdownIconEl = vm.$el.querySelector('.dropdown-menu-toggle .gl-icon');
|
||||
|
||||
expect(dropdownIconEl).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,103 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue';
|
||||
|
||||
import { mockSuggestedColors } from './mock_data';
|
||||
|
||||
const createComponent = (headerTitle) => {
|
||||
const Component = Vue.extend(dropdownCreateLabelComponent);
|
||||
|
||||
return mountComponent(Component, {
|
||||
headerTitle,
|
||||
});
|
||||
};
|
||||
|
||||
describe('DropdownCreateLabelComponent', () => {
|
||||
const colorsCount = Object.keys(mockSuggestedColors).length;
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
gon.suggested_label_colors = mockSuggestedColors;
|
||||
vm = createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('created', () => {
|
||||
it('initializes `suggestedColors` prop on component from `gon.suggested_color_labels` object', () => {
|
||||
expect(vm.suggestedColors.length).toBe(colorsCount);
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders component container element with classes `dropdown-page-two dropdown-new-label`', () => {
|
||||
expect(vm.$el.classList.contains('dropdown-page-two', 'dropdown-new-label')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders `Go back` button on component header', () => {
|
||||
const backButtonEl = vm.$el.querySelector('.dropdown-title .dropdown-menu-back');
|
||||
|
||||
expect(backButtonEl).not.toBe(null);
|
||||
expect(backButtonEl.querySelector('[data-testid="arrow-left-icon"]')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('renders component header element as `Create new label` when `headerTitle` prop is not provided', () => {
|
||||
const headerEl = vm.$el.querySelector('.dropdown-title');
|
||||
|
||||
expect(headerEl.innerText.trim()).toContain('Create new label');
|
||||
});
|
||||
|
||||
it('renders component header element with value of `headerTitle` prop', () => {
|
||||
const headerTitle = 'Create project label';
|
||||
const vmWithHeaderTitle = createComponent(headerTitle);
|
||||
const headerEl = vmWithHeaderTitle.$el.querySelector('.dropdown-title');
|
||||
|
||||
expect(headerEl.innerText.trim()).toContain(headerTitle);
|
||||
vmWithHeaderTitle.$destroy();
|
||||
});
|
||||
|
||||
it('renders `Close` button on component header', () => {
|
||||
const closeButtonEl = vm.$el.querySelector('.dropdown-title .dropdown-menu-close');
|
||||
|
||||
expect(closeButtonEl).not.toBe(null);
|
||||
});
|
||||
|
||||
it('renders `Name new label` input element', () => {
|
||||
expect(vm.$el.querySelector('.dropdown-labels-error.js-label-error')).not.toBe(null);
|
||||
expect(vm.$el.querySelector('input#new_label_name.default-dropdown-input')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('renders suggested colors list elements', () => {
|
||||
const colorsListContainerEl = vm.$el.querySelector('.suggest-colors.suggest-colors-dropdown');
|
||||
|
||||
expect(colorsListContainerEl).not.toBe(null);
|
||||
expect(colorsListContainerEl.querySelectorAll('a').length).toBe(colorsCount);
|
||||
|
||||
const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0];
|
||||
|
||||
expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0].colorCode);
|
||||
expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 153, 102);');
|
||||
});
|
||||
|
||||
it('renders color input element', () => {
|
||||
expect(vm.$el.querySelector('.dropdown-label-color-input')).not.toBe(null);
|
||||
expect(
|
||||
vm.$el.querySelector('.dropdown-label-color-preview.js-dropdown-label-color-preview'),
|
||||
).not.toBe(null);
|
||||
|
||||
expect(vm.$el.querySelector('input#new_label_color.default-dropdown-input')).not.toBe(null);
|
||||
});
|
||||
|
||||
it('renders component action buttons', () => {
|
||||
const createBtnEl = vm.$el.querySelector('button.js-new-label-btn');
|
||||
const cancelBtnEl = vm.$el.querySelector('button.js-cancel-label-btn');
|
||||
|
||||
expect(createBtnEl).not.toBe(null);
|
||||
expect(createBtnEl.innerText.trim()).toBe('Create');
|
||||
expect(cancelBtnEl.innerText.trim()).toBe('Cancel');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,75 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue';
|
||||
|
||||
import { mockConfig } from './mock_data';
|
||||
|
||||
const createComponent = (
|
||||
labelsWebUrl = mockConfig.labelsWebUrl,
|
||||
createLabelTitle,
|
||||
manageLabelsTitle,
|
||||
) => {
|
||||
const Component = Vue.extend(dropdownFooterComponent);
|
||||
|
||||
return mountComponent(Component, {
|
||||
labelsWebUrl,
|
||||
createLabelTitle,
|
||||
manageLabelsTitle,
|
||||
});
|
||||
};
|
||||
|
||||
describe('DropdownFooterComponent', () => {
|
||||
const createLabelTitle = 'Create project label';
|
||||
const manageLabelsTitle = 'Manage project labels';
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
vm = createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders link element with `Create new label` when `createLabelTitle` prop is not provided', () => {
|
||||
const createLabelEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page');
|
||||
|
||||
expect(createLabelEl).not.toBeNull();
|
||||
expect(createLabelEl.innerText.trim()).toBe('Create new label');
|
||||
});
|
||||
|
||||
it('renders link element with value of `createLabelTitle` prop', () => {
|
||||
const vmWithCreateLabelTitle = createComponent(mockConfig.labelsWebUrl, createLabelTitle);
|
||||
const createLabelEl = vmWithCreateLabelTitle.$el.querySelector(
|
||||
'.dropdown-footer-list .dropdown-toggle-page',
|
||||
);
|
||||
|
||||
expect(createLabelEl.innerText.trim()).toBe(createLabelTitle);
|
||||
vmWithCreateLabelTitle.$destroy();
|
||||
});
|
||||
|
||||
it('renders link element with `Manage labels` when `manageLabelsTitle` prop is not provided', () => {
|
||||
const manageLabelsEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-external-link');
|
||||
|
||||
expect(manageLabelsEl).not.toBeNull();
|
||||
expect(manageLabelsEl.getAttribute('href')).toBe(vm.labelsWebUrl);
|
||||
expect(manageLabelsEl.innerText.trim()).toBe('Manage labels');
|
||||
});
|
||||
|
||||
it('renders link element with value of `manageLabelsTitle` prop', () => {
|
||||
const vmWithManageLabelsTitle = createComponent(
|
||||
mockConfig.labelsWebUrl,
|
||||
createLabelTitle,
|
||||
manageLabelsTitle,
|
||||
);
|
||||
const manageLabelsEl = vmWithManageLabelsTitle.$el.querySelector(
|
||||
'.dropdown-footer-list .dropdown-external-link',
|
||||
);
|
||||
|
||||
expect(manageLabelsEl.innerText.trim()).toBe(manageLabelsTitle);
|
||||
vmWithManageLabelsTitle.$destroy();
|
||||
});
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue