Add latest changes from gitlab-org/gitlab@master
|
|
@ -10,7 +10,6 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapGetters(['isNotesFetched']),
|
||||
...mapGetters('batchComments', ['draftsCount']),
|
||||
},
|
||||
watch: {
|
||||
isNotesFetched() {
|
||||
|
|
@ -25,7 +24,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div v-show="draftsCount > 0">
|
||||
<div>
|
||||
<nav class="review-bar-component" data-testid="review_bar_component">
|
||||
<div
|
||||
class="review-bar-content d-flex gl-justify-content-end"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import store from '~/mr_notes/stores';
|
||||
import ReviewBar from './components/review_bar.vue';
|
||||
|
||||
export const initReviewBar = () => {
|
||||
const el = document.getElementById('js-review-bar');
|
||||
|
|
@ -10,6 +9,12 @@ export const initReviewBar = () => {
|
|||
new Vue({
|
||||
el,
|
||||
store,
|
||||
components: {
|
||||
ReviewBar: () => import('./components/review_bar.vue'),
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('batchComments', ['draftsCount']),
|
||||
},
|
||||
mounted() {
|
||||
this.fetchDrafts();
|
||||
},
|
||||
|
|
@ -17,7 +22,9 @@ export const initReviewBar = () => {
|
|||
...mapActions('batchComments', ['fetchDrafts']),
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(ReviewBar);
|
||||
if (this.draftsCount === 0) return null;
|
||||
|
||||
return createElement('review-bar');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import { initPipelineCountListener } from './utils';
|
||||
|
||||
/**
|
||||
* Used in:
|
||||
|
|
@ -12,13 +13,7 @@ export default () => {
|
|||
|
||||
if (pipelineTableViewEl) {
|
||||
// Update MR and Commits tabs
|
||||
pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => {
|
||||
if (event.detail.pipelineCount) {
|
||||
const badge = document.querySelector('.js-pipelines-mr-count');
|
||||
|
||||
badge.textContent = event.detail.pipelineCount;
|
||||
}
|
||||
});
|
||||
initPipelineCountListener(pipelineTableViewEl);
|
||||
|
||||
if (pipelineTableViewEl.dataset.disableInitialization === undefined) {
|
||||
const table = new Vue({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
export function initPipelineCountListener(el) {
|
||||
if (!el) return;
|
||||
|
||||
el.addEventListener('update-pipelines-count', (event) => {
|
||||
if (event.detail.pipelineCount) {
|
||||
const badge = document.querySelector('.js-pipelines-mr-count');
|
||||
|
||||
badge.textContent = event.detail.pipelineCount;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { ContentEditor } from './index';
|
||||
|
||||
export default {
|
||||
component: ContentEditor,
|
||||
title: 'Components/Content Editor',
|
||||
};
|
||||
|
||||
const Template = (_, { argTypes }) => ({
|
||||
components: { ContentEditor },
|
||||
props: Object.keys(argTypes),
|
||||
template: '<content-editor v-bind="$props" @initialized="loadContent" />',
|
||||
methods: {
|
||||
loadContent(contentEditor) {
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
contentEditor.setSerializedContent('Hello content editor');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
renderMarkdown: () => '<p>Hello content editor</p>',
|
||||
uploadsPath: '/uploads/',
|
||||
serializerConfig: {},
|
||||
extensions: [],
|
||||
};
|
||||
|
|
@ -17,7 +17,6 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
this.width = this.$el.parentNode.offsetWidth;
|
||||
window.test = this;
|
||||
|
||||
this.$_itemsWithSizeWatcher = this.$watch('vscrollParent.itemsWithSize', async () => {
|
||||
await this.$nextTick();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import Vue from 'vue';
|
|||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { getParameterValues } from '~/lib/utils/url_utility';
|
||||
import FindFile from '~/vue_shared/components/file_finder/index.vue';
|
||||
import eventHub from '../notes/event_hub';
|
||||
import diffsApp from './components/app.vue';
|
||||
|
||||
|
|
@ -12,51 +11,7 @@ import { getReviewsForMergeRequest } from './utils/file_reviews';
|
|||
import { getDerivedMergeRequestInformation } from './utils/merge_request';
|
||||
|
||||
export default function initDiffsApp(store) {
|
||||
const fileFinderEl = document.getElementById('js-diff-file-finder');
|
||||
|
||||
if (fileFinderEl) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: fileFinderEl,
|
||||
store,
|
||||
computed: {
|
||||
...mapState('diffs', ['fileFinderVisible', 'isLoading']),
|
||||
...mapGetters('diffs', ['flatBlobsList']),
|
||||
},
|
||||
watch: {
|
||||
fileFinderVisible(newVal, oldVal) {
|
||||
if (newVal && !oldVal && !this.flatBlobsList.length) {
|
||||
eventHub.$emit('fetchDiffData');
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['toggleFileFinder', 'scrollToFile']),
|
||||
openFile(file) {
|
||||
window.mrTabs.tabShown('diffs');
|
||||
this.scrollToFile(file.path);
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(FindFile, {
|
||||
props: {
|
||||
files: this.flatBlobsList,
|
||||
visible: this.fileFinderVisible,
|
||||
loading: this.isLoading,
|
||||
showDiffStats: true,
|
||||
clearSearchOnClose: false,
|
||||
},
|
||||
on: {
|
||||
toggle: this.toggleFileFinder,
|
||||
click: this.openFile,
|
||||
},
|
||||
class: ['diff-file-finder'],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return new Vue({
|
||||
const vm = new Vue({
|
||||
el: '#js-diffs-app',
|
||||
name: 'MergeRequestDiffs',
|
||||
components: {
|
||||
|
|
@ -157,4 +112,53 @@ export default function initDiffsApp(store) {
|
|||
});
|
||||
},
|
||||
});
|
||||
|
||||
const fileFinderEl = document.getElementById('js-diff-file-finder');
|
||||
|
||||
if (fileFinderEl) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: fileFinderEl,
|
||||
store,
|
||||
components: {
|
||||
FindFile: () => import('~/vue_shared/components/file_finder/index.vue'),
|
||||
},
|
||||
computed: {
|
||||
...mapState('diffs', ['fileFinderVisible', 'isLoading']),
|
||||
...mapGetters('diffs', ['flatBlobsList']),
|
||||
},
|
||||
watch: {
|
||||
fileFinderVisible(newVal, oldVal) {
|
||||
if (newVal && !oldVal && !this.flatBlobsList.length) {
|
||||
eventHub.$emit('fetchDiffData');
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['toggleFileFinder', 'scrollToFile']),
|
||||
openFile(file) {
|
||||
window.mrTabs.tabShown('diffs');
|
||||
this.scrollToFile(file.path);
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('find-file', {
|
||||
props: {
|
||||
files: this.flatBlobsList,
|
||||
visible: this.fileFinderVisible,
|
||||
loading: this.isLoading,
|
||||
showDiffStats: true,
|
||||
clearSearchOnClose: false,
|
||||
},
|
||||
on: {
|
||||
toggle: this.toggleFileFinder,
|
||||
click: this.openFile,
|
||||
},
|
||||
class: ['diff-file-finder'],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
/* eslint-disable no-new */
|
||||
|
||||
import { mountSidebarLabels, getSidebarOptions } from '~/sidebar/mount_sidebar';
|
||||
import { getSidebarOptions } from '~/sidebar/mount_sidebar';
|
||||
import IssuableContext from './issuable_context';
|
||||
import LabelsSelect from './labels_select';
|
||||
import MilestoneSelect from './milestone_select';
|
||||
import Sidebar from './right_sidebar';
|
||||
|
||||
export default () => {
|
||||
|
|
@ -13,12 +11,6 @@ export default () => {
|
|||
|
||||
const sidebarOptions = getSidebarOptions(sidebarOptEl);
|
||||
|
||||
new MilestoneSelect({
|
||||
full_path: sidebarOptions.fullPath,
|
||||
});
|
||||
new LabelsSelect();
|
||||
new IssuableContext(sidebarOptions.currentUser);
|
||||
Sidebar.initialize();
|
||||
|
||||
mountSidebarLabels();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,19 +69,20 @@ export function bytesToGiB(number) {
|
|||
* representation (e.g., giving it 1500 yields 1.5 KB).
|
||||
*
|
||||
* @param {Number} size
|
||||
* @param {Number} digits - The number of digits to appear after the decimal point
|
||||
* @returns {String}
|
||||
*/
|
||||
export function numberToHumanSize(size) {
|
||||
export function numberToHumanSize(size, digits = 2) {
|
||||
const abs = Math.abs(size);
|
||||
|
||||
if (abs < BYTES_IN_KIB) {
|
||||
return sprintf(__('%{size} bytes'), { size });
|
||||
} else if (abs < BYTES_IN_KIB ** 2) {
|
||||
return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(2) });
|
||||
return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(digits) });
|
||||
} else if (abs < BYTES_IN_KIB ** 3) {
|
||||
return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(2) });
|
||||
return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(digits) });
|
||||
}
|
||||
return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(2) });
|
||||
return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(digits) });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,11 +19,9 @@ function MergeRequest(opts) {
|
|||
this.opts = opts != null ? opts : {};
|
||||
this.submitNoteForm = this.submitNoteForm.bind(this);
|
||||
this.$el = $('.merge-request');
|
||||
this.$('.show-all-commits').on('click', () => this.showAllCommits());
|
||||
|
||||
this.initTabs();
|
||||
this.initMRBtnListeners();
|
||||
this.initCommitMessageListeners();
|
||||
|
||||
if ($('.description.js-task-list-container').length) {
|
||||
this.taskList = new TaskList({
|
||||
|
|
@ -59,11 +57,6 @@ MergeRequest.prototype.initTabs = function () {
|
|||
window.mrTabs = new MergeRequestTabs(this.opts);
|
||||
};
|
||||
|
||||
MergeRequest.prototype.showAllCommits = function () {
|
||||
this.$('.first-commits').remove();
|
||||
return this.$('.all-commits').removeClass('hide');
|
||||
};
|
||||
|
||||
MergeRequest.prototype.initMRBtnListeners = function () {
|
||||
const _this = this;
|
||||
const draftToggles = document.querySelectorAll('.js-draft-toggle-button');
|
||||
|
|
@ -128,26 +121,6 @@ MergeRequest.prototype.submitNoteForm = function (form, $button) {
|
|||
}
|
||||
};
|
||||
|
||||
MergeRequest.prototype.initCommitMessageListeners = function () {
|
||||
$(document).on('click', 'a.js-with-description-link', (e) => {
|
||||
const textarea = $('textarea.js-commit-message');
|
||||
e.preventDefault();
|
||||
|
||||
textarea.val(textarea.data('messageWithDescription'));
|
||||
$('.js-with-description-hint').hide();
|
||||
$('.js-without-description-hint').show();
|
||||
});
|
||||
|
||||
$(document).on('click', 'a.js-without-description-link', (e) => {
|
||||
const textarea = $('textarea.js-commit-message');
|
||||
e.preventDefault();
|
||||
|
||||
textarea.val(textarea.data('messageWithoutDescription'));
|
||||
$('.js-with-description-hint').show();
|
||||
$('.js-without-description-hint').hide();
|
||||
});
|
||||
};
|
||||
|
||||
MergeRequest.decreaseCounter = function (by = 1) {
|
||||
const $el = $('.js-merge-counter');
|
||||
const count = Math.max(parseInt($el.text().replace(/[^\d]/, ''), 10) - by, 0);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export default function initMrNotes() {
|
|||
action: mrShowNode.dataset.mrAction,
|
||||
});
|
||||
|
||||
initDiffsApp(store);
|
||||
initNotesApp();
|
||||
|
||||
document.addEventListener('merged:UpdateActions', () => {
|
||||
|
|
@ -26,20 +27,25 @@ export default function initMrNotes() {
|
|||
initCherryPickCommitModal();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: '#js-vue-discussion-counter',
|
||||
name: 'DiscussionCounter',
|
||||
components: {
|
||||
discussionCounter,
|
||||
},
|
||||
store,
|
||||
render(createElement) {
|
||||
return createElement('discussion-counter');
|
||||
},
|
||||
});
|
||||
requestIdleCallback(() => {
|
||||
const el = document.getElementById('js-vue-discussion-counter');
|
||||
|
||||
initDiscussionFilters(store);
|
||||
initSortDiscussions(store);
|
||||
initDiffsApp(store);
|
||||
if (el) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
name: 'DiscussionCounter',
|
||||
components: {
|
||||
discussionCounter,
|
||||
},
|
||||
store,
|
||||
render(createElement) {
|
||||
return createElement('discussion-counter');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
initDiscussionFilters(store);
|
||||
initSortDiscussions(store);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { store } from '~/notes/stores';
|
||||
import initRelatedIssues from '~/related_issues';
|
||||
import initSidebarBundle from '~/sidebar/sidebar_bundle';
|
||||
import initShow from '../show';
|
||||
|
||||
initShow();
|
||||
initSidebarBundle();
|
||||
initSidebarBundle(store);
|
||||
initRelatedIssues();
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ import Vue from 'vue';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import loadAwardsHandler from '~/awards_handler';
|
||||
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
|
||||
import initPipelines from '~/commit/pipelines/pipelines_bundle';
|
||||
import { initPipelineCountListener } from '~/commit/pipelines/utils';
|
||||
import initIssuableSidebar from '~/init_issuable_sidebar';
|
||||
import StatusBox from '~/issuable/components/status_box.vue';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import { handleLocationHash } from '~/lib/utils/common_utils';
|
||||
import initSourcegraph from '~/sourcegraph';
|
||||
import ZenMode from '~/zen_mode';
|
||||
import getStateQuery from './queries/get_state.query.graphql';
|
||||
|
|
@ -15,11 +14,10 @@ export default function initMergeRequestShow() {
|
|||
const awardEmojiEl = document.getElementById('js-vue-awards-block');
|
||||
|
||||
new ZenMode(); // eslint-disable-line no-new
|
||||
initIssuableSidebar();
|
||||
initPipelines();
|
||||
initPipelineCountListener(document.querySelector('#commit-pipeline-table-view'));
|
||||
new ShortcutsIssuable(true); // eslint-disable-line no-new
|
||||
handleLocationHash();
|
||||
initSourcegraph();
|
||||
initIssuableSidebar();
|
||||
if (awardEmojiEl) {
|
||||
import('~/emoji/awards_app')
|
||||
.then((m) => m.default(awardEmojiEl))
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import initSidebarBundle from '~/sidebar/sidebar_bundle';
|
|||
import initIssuableHeaderWarning from '~/vue_shared/components/issuable/init_issuable_header_warning';
|
||||
import initShow from '../init_merge_request_show';
|
||||
|
||||
initShow();
|
||||
initSidebarBundle();
|
||||
initMrNotes();
|
||||
initReviewBar();
|
||||
initIssuableHeaderWarning(store);
|
||||
initShow();
|
||||
|
||||
requestIdleCallback(() => {
|
||||
initSidebarBundle(store);
|
||||
initReviewBar();
|
||||
initIssuableHeaderWarning(store);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
<script>
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import BranchSwitcher from './branch_switcher.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BranchSwitcher,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
computed: {
|
||||
showBranchSwitcher() {
|
||||
return this.glFeatures.pipelineEditorBranchSwitcher;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="gl-mb-4">
|
||||
<branch-switcher v-if="showBranchSwitcher" v-on="$listeners" />
|
||||
<branch-switcher v-on="$listeners" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
showFileNav() {
|
||||
return this.glFeatures.pipelineEditorBranchSwitcher;
|
||||
},
|
||||
showCTAButton() {
|
||||
return this.glFeatures.pipelineEditorEmptyStateAction;
|
||||
},
|
||||
|
|
@ -40,7 +37,7 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<pipeline-editor-file-nav v-if="showFileNav" v-on="$listeners" />
|
||||
<pipeline-editor-file-nav v-on="$listeners" />
|
||||
<div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11">
|
||||
<img :src="emptyStateIllustrationPath" />
|
||||
<h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
|
||||
|
|
|
|||
|
|
@ -1,57 +1,7 @@
|
|||
import { memoize } from 'lodash';
|
||||
import { createNodeDict } from '../utils';
|
||||
import { createSankey } from './dag/drawing_utils';
|
||||
|
||||
/*
|
||||
The following functions are the main engine in transforming the data as
|
||||
received from the endpoint into the format the d3 graph expects.
|
||||
|
||||
Input is of the form:
|
||||
[nodes]
|
||||
nodes: [{category, name, jobs, size}]
|
||||
category is the stage name
|
||||
name is a group name; in the case that the group has one job, it is
|
||||
also the job name
|
||||
size is the number of parallel jobs
|
||||
jobs: [{ name, needs}]
|
||||
job name is either the same as the group name or group x/y
|
||||
needs: [job-names]
|
||||
needs is an array of job-name strings
|
||||
|
||||
Output is of the form:
|
||||
{ nodes: [node], links: [link] }
|
||||
node: { name, category }, + unused info passed through
|
||||
link: { source, target, value }, with source & target being node names
|
||||
and value being a constant
|
||||
|
||||
We create nodes in the GraphQL update function, and then here we create the node dictionary,
|
||||
then create links, and then dedupe the links, so that in the case where
|
||||
job 4 depends on job 1 and job 2, and job 2 depends on job 1, we show only a single link
|
||||
from job 1 to job 2 then another from job 2 to job 4.
|
||||
|
||||
CREATE LINKS
|
||||
nodes.name -> target
|
||||
nodes.name.needs.each -> source (source is the name of the group, not the parallel job)
|
||||
10 -> value (constant)
|
||||
*/
|
||||
|
||||
export const createNodeDict = (nodes) => {
|
||||
return nodes.reduce((acc, node) => {
|
||||
const newNode = {
|
||||
...node,
|
||||
needs: node.jobs.map((job) => job.needs || []).flat(),
|
||||
};
|
||||
|
||||
if (node.size > 1) {
|
||||
node.jobs.forEach((job) => {
|
||||
acc[job.name] = newNode;
|
||||
});
|
||||
}
|
||||
|
||||
acc[node.name] = newNode;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
/*
|
||||
A peformant alternative to lodash's isEqual. Because findIndex always finds
|
||||
the first instance of a match, if the found index is not the first, we know
|
||||
|
|
|
|||
|
|
@ -1,8 +1,58 @@
|
|||
import * as Sentry from '@sentry/browser';
|
||||
import { pickBy } from 'lodash';
|
||||
import { createNodeDict } from './components/parsing_utils';
|
||||
import { SUPPORTED_FILTER_PARAMETERS } from './constants';
|
||||
|
||||
/*
|
||||
The following functions are the main engine in transforming the data as
|
||||
received from the endpoint into the format the d3 graph expects.
|
||||
|
||||
Input is of the form:
|
||||
[nodes]
|
||||
nodes: [{category, name, jobs, size}]
|
||||
category is the stage name
|
||||
name is a group name; in the case that the group has one job, it is
|
||||
also the job name
|
||||
size is the number of parallel jobs
|
||||
jobs: [{ name, needs}]
|
||||
job name is either the same as the group name or group x/y
|
||||
needs: [job-names]
|
||||
needs is an array of job-name strings
|
||||
|
||||
Output is of the form:
|
||||
{ nodes: [node], links: [link] }
|
||||
node: { name, category }, + unused info passed through
|
||||
link: { source, target, value }, with source & target being node names
|
||||
and value being a constant
|
||||
|
||||
We create nodes in the GraphQL update function, and then here we create the node dictionary,
|
||||
then create links, and then dedupe the links, so that in the case where
|
||||
job 4 depends on job 1 and job 2, and job 2 depends on job 1, we show only a single link
|
||||
from job 1 to job 2 then another from job 2 to job 4.
|
||||
|
||||
CREATE LINKS
|
||||
nodes.name -> target
|
||||
nodes.name.needs.each -> source (source is the name of the group, not the parallel job)
|
||||
10 -> value (constant)
|
||||
*/
|
||||
|
||||
export const createNodeDict = (nodes) => {
|
||||
return nodes.reduce((acc, node) => {
|
||||
const newNode = {
|
||||
...node,
|
||||
needs: node.jobs.map((job) => job.needs || []).flat(),
|
||||
};
|
||||
|
||||
if (node.size > 1) {
|
||||
node.jobs.forEach((job) => {
|
||||
acc[job.name] = newNode;
|
||||
});
|
||||
}
|
||||
|
||||
acc[node.name] = newNode;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const validateParams = (params) => {
|
||||
return pickBy(params, (val, key) => SUPPORTED_FILTER_PARAMETERS.includes(key) && val);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,17 +1,30 @@
|
|||
<script>
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { GlAlert, GlLink, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { sprintf } from '~/locale';
|
||||
import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue';
|
||||
import {
|
||||
ERROR_MESSAGE,
|
||||
LEARN_MORE_LABEL,
|
||||
USAGE_QUOTAS_LABEL,
|
||||
TOTAL_USAGE_TITLE,
|
||||
TOTAL_USAGE_SUBTITLE,
|
||||
TOTAL_USAGE_DEFAULT_TEXT,
|
||||
HELP_LINK_ARIA_LABEL,
|
||||
} from '../constants';
|
||||
import getProjectStorageCount from '../queries/project_storage.query.graphql';
|
||||
import { parseGetProjectStorageResults } from '../utils';
|
||||
import StorageTable from './storage_table.vue';
|
||||
|
||||
export default {
|
||||
name: 'StorageCounterApp',
|
||||
components: {
|
||||
GlAlert,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
StorageTable,
|
||||
UsageGraph,
|
||||
},
|
||||
inject: ['projectPath'],
|
||||
inject: ['projectPath', 'helpLinks'],
|
||||
apollo: {
|
||||
project: {
|
||||
query: getProjectStorageCount,
|
||||
|
|
@ -20,11 +33,11 @@ export default {
|
|||
fullPath: this.projectPath,
|
||||
};
|
||||
},
|
||||
update: parseGetProjectStorageResults,
|
||||
update(data) {
|
||||
return parseGetProjectStorageResults(data, this.helpLinks);
|
||||
},
|
||||
error() {
|
||||
this.error = s__(
|
||||
'UsageQuota|Something went wrong while fetching project storage statistics',
|
||||
);
|
||||
this.error = ERROR_MESSAGE;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -34,24 +47,60 @@ export default {
|
|||
error: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
totalUsage() {
|
||||
return this.project?.storage?.totalUsage || TOTAL_USAGE_DEFAULT_TEXT;
|
||||
},
|
||||
storageTypes() {
|
||||
return this.project?.storage?.storageTypes || [];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearError() {
|
||||
this.error = '';
|
||||
},
|
||||
helpLinkAriaLabel(linkTitle) {
|
||||
return sprintf(HELP_LINK_ARIA_LABEL, {
|
||||
linkTitle,
|
||||
});
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
placeholder: s__('UsageQuota|Usage'),
|
||||
},
|
||||
LEARN_MORE_LABEL,
|
||||
USAGE_QUOTAS_LABEL,
|
||||
TOTAL_USAGE_TITLE,
|
||||
TOTAL_USAGE_SUBTITLE,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<gl-alert v-if="error" variant="danger" @dismiss="clearError">
|
||||
{{ error }}
|
||||
</gl-alert>
|
||||
<div v-else>{{ $options.i18n.placeholder }}</div>
|
||||
<gl-loading-icon v-if="$apollo.queries.project.loading" class="gl-mt-5" size="md" />
|
||||
<gl-alert v-else-if="error" variant="danger" @dismiss="clearError">
|
||||
{{ error }}
|
||||
</gl-alert>
|
||||
<div v-else>
|
||||
<div class="gl-pt-5 gl-px-3">
|
||||
<div class="gl-display-flex gl-justify-content-space-between gl-align-items-center">
|
||||
<div>
|
||||
<p class="gl-m-0 gl-font-lg gl-font-weight-bold">{{ $options.TOTAL_USAGE_TITLE }}</p>
|
||||
<p class="gl-m-0 gl-text-gray-400">
|
||||
{{ $options.TOTAL_USAGE_SUBTITLE }}
|
||||
<gl-link
|
||||
:href="helpLinks.usageQuotasHelpPagePath"
|
||||
target="_blank"
|
||||
:aria-label="helpLinkAriaLabel($options.USAGE_QUOTAS_LABEL)"
|
||||
data-testid="usage-quotas-help-link"
|
||||
>
|
||||
{{ $options.LEARN_MORE_LABEL }}
|
||||
</gl-link>
|
||||
</p>
|
||||
</div>
|
||||
<p class="gl-m-0 gl-font-size-h-display gl-font-weight-bold" data-testid="total-usage">
|
||||
{{ totalUsage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="project.statistics" class="gl-w-full">
|
||||
<usage-graph :root-storage-statistics="project.statistics" :limit="0" />
|
||||
</div>
|
||||
<storage-table :storage-types="storageTypes" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
<script>
|
||||
import { GlLink, GlIcon, GlTable, GlSprintf } from '@gitlab/ui';
|
||||
import { thWidthClass } from '~/lib/utils/table_utility';
|
||||
import { sprintf } from '~/locale';
|
||||
import { PROJECT_TABLE_LABELS, HELP_LINK_ARIA_LABEL } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'StorageTable',
|
||||
components: {
|
||||
GlLink,
|
||||
GlIcon,
|
||||
GlTable,
|
||||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
storageTypes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
helpLinkAriaLabel(linkTitle) {
|
||||
return sprintf(HELP_LINK_ARIA_LABEL, {
|
||||
linkTitle,
|
||||
});
|
||||
},
|
||||
},
|
||||
projectTableFields: [
|
||||
{
|
||||
key: 'storageType',
|
||||
label: PROJECT_TABLE_LABELS.STORAGE_TYPE,
|
||||
thClass: thWidthClass(90),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: 'value',
|
||||
label: PROJECT_TABLE_LABELS.VALUE,
|
||||
thClass: thWidthClass(10),
|
||||
sortable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-table :items="storageTypes" :fields="$options.projectTableFields">
|
||||
<template #cell(storageType)="{ item }">
|
||||
<p class="gl-font-weight-bold gl-mb-0" :data-testid="`${item.storageType.id}-name`">
|
||||
{{ item.storageType.name }}
|
||||
<gl-link
|
||||
v-if="item.storageType.helpPath"
|
||||
:href="item.storageType.helpPath"
|
||||
target="_blank"
|
||||
:aria-label="helpLinkAriaLabel(item.storageType.name)"
|
||||
:data-testid="`${item.storageType.id}-help-link`"
|
||||
>
|
||||
<gl-icon name="question" :size="12" />
|
||||
</gl-link>
|
||||
</p>
|
||||
<p class="gl-mb-0" :data-testid="`${item.storageType.id}-description`">
|
||||
{{ item.storageType.description }}
|
||||
</p>
|
||||
<p v-if="item.storageType.warningMessage" class="gl-mb-0 gl-font-sm">
|
||||
<gl-icon name="warning" :size="12" />
|
||||
<gl-sprintf :message="item.storageType.warningMessage">
|
||||
<template #warningLink="{ content }">
|
||||
<gl-link :href="item.storageType.warningLink" target="_blank" class="gl-font-sm">{{
|
||||
content
|
||||
}}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
</template>
|
||||
</gl-table>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { s__, __ } from '~/locale';
|
||||
|
||||
export const PROJECT_STORAGE_TYPES = [
|
||||
{
|
||||
id: 'buildArtifactsSize',
|
||||
name: s__('UsageQuota|Artifacts'),
|
||||
description: s__('UsageQuota|Pipeline artifacts and job artifacts, created with CI/CD.'),
|
||||
warningMessage: s__(
|
||||
'UsageQuota|There is a known issue with Artifact storage where the total could be incorrect for some projects. More details and progress are available in %{warningLinkStart}the epic%{warningLinkEnd}.',
|
||||
),
|
||||
warningLink: 'https://gitlab.com/groups/gitlab-org/-/epics/5380',
|
||||
},
|
||||
{
|
||||
id: 'lfsObjectsSize',
|
||||
name: s__('UsageQuota|LFS Storage'),
|
||||
description: s__('UsageQuota|Audio samples, videos, datasets, and graphics.'),
|
||||
},
|
||||
{
|
||||
id: 'packagesSize',
|
||||
name: s__('UsageQuota|Packages'),
|
||||
description: s__('UsageQuota|Code packages and container images.'),
|
||||
},
|
||||
{
|
||||
id: 'repositorySize',
|
||||
name: s__('UsageQuota|Repository'),
|
||||
description: s__('UsageQuota|Git repository, managed by the Gitaly service.'),
|
||||
},
|
||||
{
|
||||
id: 'snippetsSize',
|
||||
name: s__('UsageQuota|Snippets'),
|
||||
description: s__('UsageQuota|Shared bits of code and text.'),
|
||||
},
|
||||
{
|
||||
id: 'uploadsSize',
|
||||
name: s__('UsageQuota|Uploads'),
|
||||
description: s__('UsageQuota|File attachments and smaller design graphics.'),
|
||||
},
|
||||
{
|
||||
id: 'wikiSize',
|
||||
name: s__('UsageQuota|Wiki'),
|
||||
description: s__('UsageQuota|Wiki content.'),
|
||||
},
|
||||
];
|
||||
|
||||
export const PROJECT_TABLE_LABELS = {
|
||||
STORAGE_TYPE: s__('UsageQuota|Storage type'),
|
||||
VALUE: s__('UsageQuota|Usage'),
|
||||
};
|
||||
|
||||
export const ERROR_MESSAGE = s__(
|
||||
'UsageQuota|Something went wrong while fetching project storage statistics',
|
||||
);
|
||||
|
||||
export const LEARN_MORE_LABEL = s__('Learn more.');
|
||||
export const USAGE_QUOTAS_LABEL = s__('UsageQuota|Usage Quotas');
|
||||
export const HELP_LINK_ARIA_LABEL = s__('UsageQuota|%{linkTitle} help link');
|
||||
export const TOTAL_USAGE_DEFAULT_TEXT = __('N/A');
|
||||
export const TOTAL_USAGE_TITLE = s__('UsageQuota|Usage Breakdown');
|
||||
export const TOTAL_USAGE_SUBTITLE = s__(
|
||||
'UsageQuota|Includes project registry, artifacts, packages, wiki, uploads and other items.',
|
||||
);
|
||||
|
|
@ -12,7 +12,17 @@ export default (containerId = 'js-project-storage-count-app') => {
|
|||
return false;
|
||||
}
|
||||
|
||||
const { projectPath } = el.dataset;
|
||||
const {
|
||||
projectPath,
|
||||
usageQuotasHelpPagePath,
|
||||
buildArtifactsHelpPagePath,
|
||||
lfsObjectsHelpPagePath,
|
||||
packagesHelpPagePath,
|
||||
repositoryHelpPagePath,
|
||||
snippetsHelpPagePath,
|
||||
uploadsHelpPagePath,
|
||||
wikiHelpPagePath,
|
||||
} = el.dataset;
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
|
||||
|
|
@ -23,6 +33,16 @@ export default (containerId = 'js-project-storage-count-app') => {
|
|||
apolloProvider,
|
||||
provide: {
|
||||
projectPath,
|
||||
helpLinks: {
|
||||
usageQuotasHelpPagePath,
|
||||
buildArtifactsHelpPagePath,
|
||||
lfsObjectsHelpPagePath,
|
||||
packagesHelpPagePath,
|
||||
repositoryHelpPagePath,
|
||||
snippetsHelpPagePath,
|
||||
uploadsHelpPagePath,
|
||||
wikiHelpPagePath,
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(StorageCounterApp);
|
||||
|
|
|
|||
|
|
@ -1,36 +1,5 @@
|
|||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
const projectStorageTypes = [
|
||||
{
|
||||
id: 'buildArtifactsSize',
|
||||
name: s__('UsageQuota|Artifacts'),
|
||||
},
|
||||
{
|
||||
id: 'lfsObjectsSize',
|
||||
name: s__('UsageQuota|LFS Storage'),
|
||||
},
|
||||
{
|
||||
id: 'packagesSize',
|
||||
name: s__('UsageQuota|Packages'),
|
||||
},
|
||||
{
|
||||
id: 'repositorySize',
|
||||
name: s__('UsageQuota|Repository'),
|
||||
},
|
||||
{
|
||||
id: 'snippetsSize',
|
||||
name: s__('UsageQuota|Snippets'),
|
||||
},
|
||||
{
|
||||
id: 'uploadsSize',
|
||||
name: s__('UsageQuota|Uploads'),
|
||||
},
|
||||
{
|
||||
id: 'wikiSize',
|
||||
name: s__('UsageQuota|Wiki'),
|
||||
},
|
||||
];
|
||||
import { PROJECT_STORAGE_TYPES } from './constants';
|
||||
|
||||
/**
|
||||
* This method parses the results from `getProjectStorageCount` call.
|
||||
|
|
@ -38,26 +7,32 @@ const projectStorageTypes = [
|
|||
* @param {Object} data graphql result
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const parseGetProjectStorageResults = (data) => {
|
||||
export const parseGetProjectStorageResults = (data, helpLinks) => {
|
||||
const projectStatistics = data?.project?.statistics;
|
||||
if (!projectStatistics) {
|
||||
return {};
|
||||
}
|
||||
const { storageSize, ...storageStatistics } = projectStatistics;
|
||||
const storageTypes = projectStorageTypes.reduce((types, currentType) => {
|
||||
const storageTypes = PROJECT_STORAGE_TYPES.reduce((types, currentType) => {
|
||||
if (!storageStatistics[currentType.id]) {
|
||||
return types;
|
||||
}
|
||||
|
||||
const helpPathKey = currentType.id.replace(`Size`, `HelpPagePath`);
|
||||
const helpPath = helpLinks[helpPathKey];
|
||||
|
||||
return types.concat({
|
||||
...currentType,
|
||||
value: numberToHumanSize(storageStatistics[currentType.id]),
|
||||
storageType: {
|
||||
...currentType,
|
||||
helpPath,
|
||||
},
|
||||
value: numberToHumanSize(storageStatistics[currentType.id], 1),
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
storage: {
|
||||
totalUsage: numberToHumanSize(storageSize),
|
||||
totalUsage: numberToHumanSize(storageSize, 1),
|
||||
storageTypes,
|
||||
},
|
||||
statistics: projectStatistics,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
|
||||
import AccessibilityIssueBody from '../accessibility_report/components/accessibility_issue_body.vue';
|
||||
import CodequalityIssueBody from '../codequality_report/components/codequality_issue_body.vue';
|
||||
import TestIssueBody from '../grouped_test_report/components/test_issue_body.vue';
|
||||
|
||||
export const components = {
|
||||
AccessibilityIssueBody,
|
||||
CodequalityIssueBody,
|
||||
TestIssueBody,
|
||||
AccessibilityIssueBody: () =>
|
||||
import('../accessibility_report/components/accessibility_issue_body.vue'),
|
||||
CodequalityIssueBody: () => import('../codequality_report/components/codequality_issue_body.vue'),
|
||||
TestIssueBody: () => import('../grouped_test_report/components/test_issue_body.vue'),
|
||||
};
|
||||
|
||||
export const componentNames = {
|
||||
AccessibilityIssueBody: AccessibilityIssueBody.name,
|
||||
CodequalityIssueBody: CodequalityIssueBody.name,
|
||||
TestIssueBody: TestIssueBody.name,
|
||||
AccessibilityIssueBody: 'AccessibilityIssueBody',
|
||||
CodequalityIssueBody: 'CodequalityIssueBody',
|
||||
TestIssueBody: 'TestIssueBody',
|
||||
};
|
||||
|
||||
export const iconComponents = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createFlash from '~/flash';
|
||||
import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
|
||||
|
|
@ -13,7 +12,6 @@ import {
|
|||
isInIncidentPage,
|
||||
parseBoolean,
|
||||
} from '~/lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assignee_list.vue';
|
||||
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
|
||||
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
|
||||
|
|
@ -363,10 +361,10 @@ function mountReferenceComponent() {
|
|||
});
|
||||
}
|
||||
|
||||
function mountLockComponent() {
|
||||
function mountLockComponent(store) {
|
||||
const el = document.getElementById('js-lock-entry-point');
|
||||
|
||||
if (!el) {
|
||||
if (!el || !store) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -375,37 +373,20 @@ function mountLockComponent() {
|
|||
const dataNode = document.getElementById('js-lock-issue-data');
|
||||
const initialData = JSON.parse(dataNode.innerHTML);
|
||||
|
||||
let importStore;
|
||||
if (isInIssuePage() || isInIncidentPage()) {
|
||||
importStore = import(/* webpackChunkName: 'notesStore' */ '~/notes/stores').then(
|
||||
({ store }) => store,
|
||||
);
|
||||
} else {
|
||||
importStore = import(/* webpackChunkName: 'mrNotesStore' */ '~/mr_notes/stores').then(
|
||||
(store) => store.default,
|
||||
);
|
||||
}
|
||||
|
||||
importStore
|
||||
.then(
|
||||
(store) =>
|
||||
new Vue({
|
||||
el,
|
||||
store,
|
||||
provide: {
|
||||
fullPath,
|
||||
},
|
||||
render: (createElement) =>
|
||||
createElement(IssuableLockForm, {
|
||||
props: {
|
||||
isEditable: initialData.is_editable,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.catch(() => {
|
||||
createFlash({ message: __('Failed to load sidebar lock status') });
|
||||
});
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
store,
|
||||
provide: {
|
||||
fullPath,
|
||||
},
|
||||
render: (createElement) =>
|
||||
createElement(IssuableLockForm, {
|
||||
props: {
|
||||
isEditable: initialData.is_editable,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function mountParticipantsComponent() {
|
||||
|
|
@ -537,7 +518,7 @@ function mountCopyEmailComponent() {
|
|||
const isAssigneesWidgetShown =
|
||||
(isInIssuePage() || isInDesignPage()) && gon.features.issueAssigneesWidget;
|
||||
|
||||
export function mountSidebar(mediator) {
|
||||
export function mountSidebar(mediator, store) {
|
||||
initInviteMembersModal();
|
||||
initInviteMembersTrigger();
|
||||
|
||||
|
|
@ -548,11 +529,12 @@ export function mountSidebar(mediator) {
|
|||
mountAssigneesComponentDeprecated(mediator);
|
||||
}
|
||||
mountReviewersComponent(mediator);
|
||||
mountSidebarLabels();
|
||||
mountMilestoneSelect();
|
||||
mountConfidentialComponent(mediator);
|
||||
mountDueDateComponent(mediator);
|
||||
mountReferenceComponent(mediator);
|
||||
mountLockComponent();
|
||||
mountLockComponent(store);
|
||||
mountParticipantsComponent();
|
||||
mountSubscriptionsComponent();
|
||||
mountCopyEmailComponent();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { mountSidebar, getSidebarOptions } from './mount_sidebar';
|
||||
import { mountSidebar, getSidebarOptions } from 'ee_else_ce/sidebar/mount_sidebar';
|
||||
import Mediator from './sidebar_mediator';
|
||||
|
||||
export default () => {
|
||||
export default (store) => {
|
||||
const mediator = new Mediator(getSidebarOptions());
|
||||
mediator.fetch();
|
||||
|
||||
mountSidebar(mediator);
|
||||
mountSidebar(mediator, store);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import VueApollo from 'vue-apollo';
|
|||
import MrWidgetOptions from 'ee_else_ce/vue_merge_request_widget/mr_widget_options.vue';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import Translate from '../vue_shared/translate';
|
||||
import { registerExtension } from './components/extensions';
|
||||
import issueExtension from './extensions/issues';
|
||||
|
||||
Vue.use(Translate);
|
||||
Vue.use(VueApollo);
|
||||
|
|
@ -28,8 +26,6 @@ export default () => {
|
|||
gl.mrWidgetData.gitlabLogo = gon.gitlab_logo;
|
||||
gl.mrWidgetData.defaultAvatarUrl = gon.default_avatar_url;
|
||||
|
||||
registerExtension(issueExtension);
|
||||
|
||||
const vm = new Vue({
|
||||
el: '#js-vue-mr-widget',
|
||||
provide: {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,6 @@ import { sprintf, s__, __ } from '~/locale';
|
|||
import Project from '~/pages/projects/project';
|
||||
import SmartInterval from '~/smart_interval';
|
||||
import { setFaviconOverlay } from '../lib/utils/favicon';
|
||||
import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue';
|
||||
import GroupedCodequalityReportsApp from '../reports/codequality_report/grouped_codequality_reports_app.vue';
|
||||
import GroupedTestReportsApp from '../reports/grouped_test_report/grouped_test_reports_app.vue';
|
||||
import Loading from './components/loading.vue';
|
||||
import MrWidgetAlertMessage from './components/mr_widget_alert_message.vue';
|
||||
import WidgetHeader from './components/mr_widget_header.vue';
|
||||
|
|
@ -42,7 +39,6 @@ import ShaMismatch from './components/states/sha_mismatch.vue';
|
|||
import UnresolvedDiscussionsState from './components/states/unresolved_discussions.vue';
|
||||
import WorkInProgressState from './components/states/work_in_progress.vue';
|
||||
// import ExtensionsContainer from './components/extensions/container';
|
||||
import TerraformPlan from './components/terraform/mr_widget_terraform_container.vue';
|
||||
import eventHub from './event_hub';
|
||||
import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variables';
|
||||
import getStateQuery from './queries/get_state.query.graphql';
|
||||
|
|
@ -84,10 +80,13 @@ export default {
|
|||
'mr-widget-auto-merge-failed': AutoMergeFailed,
|
||||
'mr-widget-rebase': RebaseState,
|
||||
SourceBranchRemovalStatus,
|
||||
GroupedCodequalityReportsApp,
|
||||
GroupedTestReportsApp,
|
||||
TerraformPlan,
|
||||
GroupedAccessibilityReportsApp,
|
||||
GroupedCodequalityReportsApp: () =>
|
||||
import('../reports/codequality_report/grouped_codequality_reports_app.vue'),
|
||||
GroupedTestReportsApp: () =>
|
||||
import('../reports/grouped_test_report/grouped_test_reports_app.vue'),
|
||||
TerraformPlan: () => import('./components/terraform/mr_widget_terraform_container.vue'),
|
||||
GroupedAccessibilityReportsApp: () =>
|
||||
import('../reports/accessibility_report/grouped_accessibility_reports_app.vue'),
|
||||
MrWidgetApprovals,
|
||||
SecurityReportsApp: () => import('~/vue_shared/security_reports/security_reports_app.vue'),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
|
|||
before_action :check_can_collaborate!
|
||||
before_action do
|
||||
push_frontend_feature_flag(:pipeline_editor_empty_state_action, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:pipeline_editor_branch_switcher, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:pipeline_editor_drawer, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:schema_linting, @project, default_enabled: :yaml)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,18 @@ class Projects::UsageQuotasController < Projects::ApplicationController
|
|||
|
||||
feature_category :utilization
|
||||
|
||||
def index
|
||||
@storage_app_data = {
|
||||
project_path: @project.full_path,
|
||||
usage_quotas_help_page_path: help_page_path('user/usage_quotas'),
|
||||
build_artifacts_help_page_path: help_page_path('ci/pipelines/job_artifacts', anchor: 'when-job-artifacts-are-deleted'),
|
||||
packages_help_page_path: help_page_path('user/packages/package_registry/index.md', anchor: 'delete-a-package'),
|
||||
repository_help_page_path: help_page_path('user/project/repository/reducing_the_repo_size_using_git'),
|
||||
snippets_help_page_path: help_page_path('user/snippets', anchor: 'reduce-snippets-repository-size'),
|
||||
wiki_help_page_path: help_page_path('administration/wikis/index.md', anchor: 'reduce-wiki-repository-size')
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_usage_quotas_enabled!
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
= f.label :default_artifacts_expire_in, _('Default artifacts expiration'), class: 'label-bold'
|
||||
= f.text_field :default_artifacts_expire_in, class: 'form-control gl-form-input'
|
||||
.form-text.text-muted
|
||||
= html_escape(_("The default expiration time for job artifacts. 0 for unlimited. The default unit is in seconds, but you can use other units, for example %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
= html_escape(_("Set the default expiration time for job artifacts in all projects. Set to %{code_open}0%{code_close} to never expire artifacts by default. If no unit is written, it defaults to seconds. For example, these are all equivalent: %{code_open}3600%{code_close}, %{code_open}60 minutes%{code_close}, or %{code_open}one hour%{code_close}.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
= link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
|
||||
.form-group
|
||||
.form-check
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
= s_('UsageQuota|Usage Quotas')
|
||||
|
||||
.row
|
||||
.col-sm-6
|
||||
= s_('UsageQuota|Usage of project resources across the %{strong_start}%{project_name}%{strong_end} project').html_safe % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe, project_name: @project.name }
|
||||
.col-sm-12
|
||||
= s_('UsageQuota|Usage of project resources across the %{strong_start}%{project_name}%{strong_end} project').html_safe % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe, project_name: @project.name } + '.'
|
||||
%a{ href: help_page_path('user/usage_quotas.md') }
|
||||
= s_('UsageQuota|Learn more about usage quotas') + '.'
|
||||
|
||||
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
|
||||
%ul.nav.nav-tabs.nav-links.scrolling-tabs.separator.js-usage-quota-tabs{ role: 'tablist' }
|
||||
|
|
@ -14,4 +16,4 @@
|
|||
= s_('UsageQuota|Storage')
|
||||
.tab-content
|
||||
.tab-pane#storage-quota-tab
|
||||
#js-project-storage-count-app{ data: { project_path: @project.full_path } }
|
||||
#js-project-storage-count-app{ data: @storage_app_data }
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: pipeline_editor_branch_switcher
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57562
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326189
|
||||
milestone: '13.11'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: true
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
name: vuln_report_new_project_filter
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55745
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334380
|
||||
milestone: '14.2'
|
||||
milestone: '14.3'
|
||||
type: development
|
||||
group: group::threat insights
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ ActiveRecord::Base.establish_connection(Gitlab::Database.main.db_config_with_def
|
|||
|
||||
Gitlab.ee do
|
||||
if Gitlab::Runtime.sidekiq? && Gitlab::Geo.geo_database_configured?
|
||||
Rails.configuration.geo_database['pool'] = Gitlab::Database.main.default_pool_size
|
||||
Rails.configuration.geo_database['pool'] = Gitlab::Database.default_pool_size
|
||||
Geo::TrackingBase.establish_connection(Rails.configuration.geo_database)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ in sync.
|
|||
|
||||
## Repository re-verification
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8550) in GitLab Enterprise Edition 11.6. Available in [GitLab Premium](https://about.gitlab.com/pricing/).
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8550) in GitLab 11.6.
|
||||
|
||||
Due to bugs or transient infrastructure failures, it is possible for Git
|
||||
repositories to change unexpectedly without being marked for verification.
|
||||
|
|
|
|||
|
|
@ -7,12 +7,6 @@ type: howto
|
|||
|
||||
# Geo **(PREMIUM SELF)**
|
||||
|
||||
> - Introduced in GitLab Enterprise Edition 8.9.
|
||||
> - Using Geo in combination with
|
||||
> [multi-node architectures](../reference_architectures/index.md)
|
||||
> is considered **Generally Available** (GA) in
|
||||
> [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
|
||||
|
||||
Geo is the solution for widely distributed development teams and for providing a warm-standby as part of a disaster recovery strategy.
|
||||
|
||||
## Overview
|
||||
|
|
@ -218,7 +212,7 @@ For information on how to update your Geo site(s) to the latest GitLab version,
|
|||
|
||||
### Pausing and resuming replication
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35913) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35913) in GitLab 13.2.
|
||||
|
||||
WARNING:
|
||||
In GitLab 13.2 and 13.3, promoting a secondary site to a primary while the
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ query.
|
|||
|
||||
## Can I `git push` to a **secondary** site?
|
||||
|
||||
Yes! Pushing directly to a **secondary** site (for both HTTP and SSH, including Git LFS) was [introduced](https://about.gitlab.com/releases/2018/09/22/gitlab-11-3-released/) in [GitLab Premium](https://about.gitlab.com/pricing/#self-managed) 11.3.
|
||||
Yes! Pushing directly to a **secondary** site (for both HTTP and SSH, including Git LFS) was [introduced](https://about.gitlab.com/releases/2018/09/22/gitlab-11-3-released/) in GitLab 11.3.
|
||||
|
||||
## How long does it take to have a commit replicated to a **secondary** site?
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ type: howto
|
|||
|
||||
After you set up the [database replication and configure the Geo nodes](../index.md#setup-instructions), use your closest GitLab site as you would do with the primary one.
|
||||
|
||||
You can push directly to a **secondary** site (for both HTTP, SSH including Git LFS), and the request will be proxied to the primary site instead ([introduced](https://about.gitlab.com/releases/2018/09/22/gitlab-11-3-released/) in [GitLab Premium](https://about.gitlab.com/pricing/#self-managed) 11.3).
|
||||
You can push directly to a **secondary** site (for both HTTP, SSH including Git LFS), and the request will be proxied to the primary site instead ([introduced](https://about.gitlab.com/releases/2018/09/22/gitlab-11-3-released/) in GitLab 11.3).
|
||||
|
||||
Example of the output you will see when pushing to a **secondary** site:
|
||||
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ the tracking database on port 5432.
|
|||
gitlab-rake geo:db:create
|
||||
```
|
||||
|
||||
1. The reconfigure should automatically migrate the database. You can migrate the database manually if needed, for example if `gitlab_rails['auto_migrate'] = false`:
|
||||
1. The reconfigure should automatically migrate the database. You can migrate the database manually if needed, for example if `geo_secondary['auto_migrate'] = false`:
|
||||
|
||||
```shell
|
||||
gitlab-rake geo:db:migrate
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# GitLab Maintenance Mode **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2149) in GitLab Premium 13.9.
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2149) in GitLab 13.9.
|
||||
|
||||
Maintenance Mode allows administrators to reduce write operations to a minimum while maintenance tasks are performed. The main goal is to block all external actions that change the internal state, including the PostgreSQL database, but especially files, Git repositories, Container repositories, and so on.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ GitLab provides administrators insights into the health of their GitLab instance
|
|||
To provide a native experience (similar interacting with an application deployed using GitLab), a
|
||||
project called **Monitoring** is created:
|
||||
|
||||
- With [internal visibility](../../../public_access/public_access.md#internal-projects).
|
||||
- With [internal visibility](../../../public_access/public_access.md#internal-projects-and-groups).
|
||||
- Under a group called **GitLab Instance**.
|
||||
|
||||
The project is created specifically for visualizing and configuring the monitoring of your GitLab
|
||||
|
|
|
|||
|
|
@ -196,43 +196,6 @@ to use the HTTPS protocol.
|
|||
WARNING:
|
||||
Multiple wildcards for one instance is not supported. Only one wildcard per instance can be assigned.
|
||||
|
||||
### Additional configuration for Docker container
|
||||
|
||||
The GitLab Pages daemon doesn't have permissions to bind mounts when it runs
|
||||
in a Docker container. To overcome this issue, you must change the `chroot`
|
||||
behavior:
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb`.
|
||||
1. Set the `inplace_chroot` to `true` for GitLab Pages:
|
||||
|
||||
```ruby
|
||||
gitlab_pages['inplace_chroot'] = true
|
||||
```
|
||||
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
|
||||
NOTE:
|
||||
`inplace_chroot` option might not work with the other features, such as [Pages Access Control](#access-control).
|
||||
The [GitLab Pages README](https://gitlab.com/gitlab-org/gitlab-pages#caveats) has more information about caveats and workarounds.
|
||||
|
||||
### Jailing mechanism disabled by default for API-based configuration
|
||||
|
||||
Starting from GitLab 14.1 the [jailing/chroot mechanism is disabled by default](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/589).
|
||||
If you are using API-based configuration and the new [Zip storage architecture](#zip-storage)
|
||||
there is nothing you need to do.
|
||||
|
||||
If you run into any problems, [open a new issue](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/new)
|
||||
and enable the jail again by setting the environment variable:
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb`.
|
||||
1. Set the `DAEMON_ENABLE_JAIL` environment variable to `true` for GitLab Pages:
|
||||
|
||||
```ruby
|
||||
gitlab_pages['env']['DAEMON_ENABLE_JAIL'] = "true"
|
||||
```
|
||||
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
|
||||
### Global settings
|
||||
|
||||
Below is a table of all configuration settings known to Pages in Omnibus GitLab,
|
||||
|
|
@ -270,7 +233,7 @@ control over how the Pages daemon runs and serves content in your environment.
|
|||
| `auth_scope` | The OAuth application scope to use for authentication. Must match GitLab Pages OAuth application settings. Leave blank to use `api` scope by default. |
|
||||
| `gitlab_server` | Server to use for authentication when access control is enabled; defaults to GitLab `external_url`. |
|
||||
| `headers` | Specify any additional http headers that should be sent to the client with each response. Multiple headers can be given as an array, header and value as one string, for example `['my-header: myvalue', 'my-other-header: my-other-value']` |
|
||||
| `inplace_chroot` | On [systems that don't support bind-mounts](index.md#additional-configuration-for-docker-container), this instructs GitLab Pages to `chroot` into its `pages_path` directory. Some caveats exist when using in-place `chroot`; refer to the GitLab Pages [README](https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md#caveats) for more information. |
|
||||
| `inplace_chroot` | [REMOVED in GitLab 14.3.](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/561) On [systems that don't support bind-mounts](index.md#gitlab-pages-fails-to-start-in-docker-container), this instructs GitLab Pages to `chroot` into its `pages_path` directory. Some caveats exist when using in-place `chroot`; refer to the GitLab Pages [README](https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md#caveats) for more information. |
|
||||
| `enable_disk` | Allows the GitLab Pages daemon to serve content from disk. Shall be disabled if shared disk storage isn't available. |
|
||||
| `insecure_ciphers` | Use default list of cipher suites, may contain insecure ones like 3DES and RC4. |
|
||||
| `internal_gitlab_server` | Internal GitLab server address used exclusively for API requests. Useful if you want to send that traffic over an internal load balancer. Defaults to GitLab `external_url`. |
|
||||
|
|
@ -300,7 +263,7 @@ control over how the Pages daemon runs and serves content in your environment.
|
|||
| `enable` | Include a virtual host `server{}` block for Pages inside NGINX. Needed for NGINX to proxy traffic back to the Pages daemon. Set to `false` if the Pages daemon should directly receive all requests, for example, when using [custom domains](index.md#custom-domains). |
|
||||
| `FF_ENABLE_REDIRECTS` | Feature flag to enable/disable redirects (enabled by default). Read the [redirects documentation](../../user/project/pages/redirects.md#feature-flag-for-redirects) for more information. |
|
||||
| `FF_ENABLE_PLACEHOLDERS` | Feature flag to enable/disable rewrites (disabled by default). Read the [redirects documentation](../../user/project/pages/redirects.md#feature-flag-for-rewrites) for more information. |
|
||||
| `use_legacy_storage` | Temporarily-introduced parameter allowing to use legacy domain configuration source and storage. [Will be removed in GitLab 14.3](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6166). |
|
||||
| `use_legacy_storage` | Temporarily-introduced parameter allowing to use legacy domain configuration source and storage. [Removed in 14.3](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6166). |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -1108,6 +1071,9 @@ You can also find the log file in `/var/log/gitlab/gitlab-pages/current`.
|
|||
|
||||
### `open /etc/ssl/ca-bundle.pem: permission denied`
|
||||
|
||||
WARNING:
|
||||
This issue is fixed in GitLab 14.3 and above, try upgrading GitLab first.
|
||||
|
||||
GitLab Pages runs inside a `chroot` jail, usually in a uniquely numbered directory like
|
||||
`/tmp/gitlab-pages-*`.
|
||||
|
||||
|
|
@ -1139,6 +1105,9 @@ sudo gitlab-ctl restart gitlab-pages
|
|||
|
||||
### `dial tcp: lookup gitlab.example.com` and `x509: certificate signed by unknown authority`
|
||||
|
||||
WARNING:
|
||||
This issue is fixed in GitLab 14.3 and above, try upgrading GitLab first.
|
||||
|
||||
When setting both `inplace_chroot` and `access_control` to `true`, you might encounter errors like:
|
||||
|
||||
```plaintext
|
||||
|
|
@ -1404,16 +1373,11 @@ both servers.
|
|||
GitLab 14.0 introduces a number of changes to GitLab Pages which may require manual intervention.
|
||||
|
||||
1. Firstly [follow the migration guide](#migrate-gitlab-pages-to-140).
|
||||
1. Try to upgrade to GitLab 14.3 or above. Some of the issues were fixed in GitLab 14.1, 14.2 and 14.3.
|
||||
1. If it doesn't work, see [GitLab Pages logs](#how-to-see-gitlab-pages-logs), and if you see any errors there then search them on this page.
|
||||
|
||||
The most common problem is when using [`inplace_chroot`](#dial-tcp-lookup-gitlabexamplecom-and-x509-certificate-signed-by-unknown-authority).
|
||||
|
||||
NOTE:
|
||||
Starting from 14.1, the chroot/jailing mechanism is
|
||||
[disabled by default for API-based configuration](#jailing-mechanism-disabled-by-default-for-api-based-configuration).
|
||||
|
||||
WARNING:
|
||||
As the last resort you can temporarily enable legacy storage and configuration mechanisms. Support for them [will be removed in GitLab 14.3](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6166), so GitLab Pages will stop working if don't resolve the underlying issue.
|
||||
In GitLab 14.0-14.2 you can temporarily enable legacy storage and configuration mechanisms.
|
||||
|
||||
To do that:
|
||||
|
||||
|
|
@ -1426,3 +1390,25 @@ To do that:
|
|||
```
|
||||
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
|
||||
### GitLab Pages fails to start in Docker container
|
||||
|
||||
WARNING:
|
||||
This issue is fixed in GitLab 14.3 and above, try upgrading GitLab first.
|
||||
|
||||
The GitLab Pages daemon doesn't have permissions to bind mounts when it runs
|
||||
in a Docker container. To overcome this issue, you must change the `chroot`
|
||||
behavior:
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb`.
|
||||
1. Set the `inplace_chroot` to `true` for GitLab Pages:
|
||||
|
||||
```ruby
|
||||
gitlab_pages['inplace_chroot'] = true
|
||||
```
|
||||
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
|
||||
NOTE:
|
||||
`inplace_chroot` option might not work with the other features, such as [Pages Access Control](#access-control).
|
||||
The [GitLab Pages README](https://gitlab.com/gitlab-org/gitlab-pages#caveats) has more information about caveats and workarounds.
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ group: Package
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Dependency Proxy API
|
||||
# Dependency Proxy API **(FREE)**
|
||||
|
||||
## Purge the dependency proxy for a group
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11631) in GitLab 12.10.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Free](https://about.gitlab.com/pricing/) in GitLab 13.6.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) from GitLab Premium to GitLab Free in 13.6.
|
||||
|
||||
Deletes the cached manifests and blobs for a group. This endpoint requires the [Owner role](../user/permissions.md)
|
||||
for the group.
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ group: Release
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Deploy keys API
|
||||
# Deploy keys API **(FREE)**
|
||||
|
||||
## List all deploy keys
|
||||
## List all deploy keys **(FREE SELF)**
|
||||
|
||||
Get a list of all deploy keys across all projects of the GitLab instance. This
|
||||
endpoint requires an administrator role and is not available on GitLab.com.
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ group: Release
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Deploy Tokens API
|
||||
# Deploy Tokens API **(FREE)**
|
||||
|
||||
## List all deploy tokens
|
||||
## List all deploy tokens **(FREE SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21811) in GitLab 12.9.
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: concepts, howto
|
||||
---
|
||||
|
||||
# Deployments API
|
||||
# Deployments API **(FREE)**
|
||||
|
||||
## List project deployments
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ type: reference, api
|
|||
|
||||
# DevOps Research and Assessment (DORA) key metrics API **(ULTIMATE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/279039) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.10.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/279039) in GitLab 13.10.
|
||||
> - The legacy key/value pair `{ "<date>" => "<value>" }` was removed from the payload in GitLab 14.0.
|
||||
|
||||
All methods require [reporter permissions and above](../../user/permissions.md).
|
||||
|
|
@ -52,7 +52,7 @@ Example response:
|
|||
|
||||
## Get group-level DORA metrics
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/279039) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.10.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/279039) in GitLab 13.10.
|
||||
|
||||
Get group-level DORA metrics.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ type: reference, api
|
|||
|
||||
# DORA4 Analytics Project API **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/279039) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.7.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/279039) in GitLab 13.7.
|
||||
|
||||
WARNING:
|
||||
These endpoints are deprecated and will be removed in GitLab 14.0. Use the [DORA metrics API](dora/metrics.md) instead.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Feature Flag Specs API **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9566) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.5.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9566) in GitLab 12.5.
|
||||
|
||||
This API was removed in [GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/213369).
|
||||
Please use [the new API](feature_flags.md) instead.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ group: Release
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Features flags API
|
||||
# Feature flags API **(FREE SELF)**
|
||||
|
||||
This API is for managing Flipper-based [feature flags used in development of GitLab](../development/feature_flags/index.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: concepts, howto
|
||||
---
|
||||
|
||||
# Freeze Periods API
|
||||
# Freeze Periods API **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29382) in GitLab 13.0.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,11 @@ type: concepts, howto
|
|||
|
||||
# Group-level protected environments API **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215888) in [GitLab Premium](https://about.gitlab.com/pricing/) 14.0. [Deployed behind the `group_level_protected_environments` flag](../administration/feature_flags.md), disabled by default.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215888) in GitLab 14.0. [Deployed behind the `group_level_protected_environments` flag](../administration/feature_flags.md), disabled by default.
|
||||
> - [Feature flag `group_level_protected_environments`](https://gitlab.com/gitlab-org/gitlab/-/issues/331085) removed in GitLab 14.3.
|
||||
> - [Generally Available](https://gitlab.com/gitlab-org/gitlab/-/issues/331085) on [GitLab Premium](https://about.gitlab.com/pricing/) and on GitLab.com in 14.3.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/331085) in GitLab 14.3.
|
||||
|
||||
This in-development feature might not be available for your use. There can be
|
||||
[risks when enabling features still in development](../administration/feature_flags.md#risks-when-enabling-features-still-in-development).
|
||||
Refer to this feature's version history for more details.
|
||||
|
||||
Read more about [group-level protected environments](../ci/environments/protected_environments.md#group-level-protected-environments),
|
||||
Read more about [group-level protected environments](../ci/environments/protected_environments.md#group-level-protected-environments).
|
||||
|
||||
## Valid access levels
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ group: Release
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Releases API
|
||||
# Releases API **(FREE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) in GitLab 11.7.
|
||||
> - Using this API you can manipulate GitLab [Release](../../user/project/releases/index.md) entries.
|
||||
|
|
@ -499,7 +499,7 @@ Example response:
|
|||
|
||||
### Group milestones **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235391) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.5.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235391) in GitLab 13.5.
|
||||
|
||||
Group milestones associated with the project may be specified in the `milestones`
|
||||
array for [Create a release](#create-a-release) and [Update a release](#update-a-release)
|
||||
|
|
@ -508,7 +508,7 @@ adding milestones for ancestor groups raises an error.
|
|||
|
||||
## Collect release evidence **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199065) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.10.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199065) in GitLab 12.10.
|
||||
|
||||
Create Evidence for an existing Release.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ group: Release
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Release links API
|
||||
# Release links API **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41766) in GitLab 11.7.
|
||||
|
||||
|
|
|
|||
|
|
@ -157,9 +157,9 @@ For more information, see [Deployment safety](deployment_safety.md).
|
|||
|
||||
## Group-level protected environments
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215888) in [GitLab Premium](https://about.gitlab.com/pricing/) 14.0. [Deployed behind the `group_level_protected_environments` flag](../../administration/feature_flags.md), disabled by default.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215888) in GitLab 14.0. [Deployed behind the `group_level_protected_environments` flag](../../administration/feature_flags.md), disabled by default.
|
||||
> - [Feature flag `group_level_protected_environments`](https://gitlab.com/gitlab-org/gitlab/-/issues/331085) removed in GitLab 14.3.
|
||||
> - [Generally Available](https://gitlab.com/gitlab-org/gitlab/-/issues/331085) on [GitLab Premium](https://about.gitlab.com/pricing/) and on GitLab.com in 14.3.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/331085) in GitLab 14.3.
|
||||
|
||||
This in-development feature might not be available for your use. There can be
|
||||
[risks when enabling features still in development](../../administration/feature_flags.md#risks-when-enabling-features-still-in-development).
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ access has been granted and your build environment configured, you must configur
|
|||
1. Specify the [image](macos/environment.md#vm-images) you want to use.
|
||||
1. Commit a change to your repository.
|
||||
|
||||
The runners automatically run your build. For more detailed setup instructions,
|
||||
view [how to set up macOS runners](macos/setup.md).
|
||||
The runners automatically run your build.
|
||||
|
||||
## Example `.gitlab-ci.yml` file
|
||||
|
||||
|
|
|
|||
|
|
@ -225,9 +225,17 @@ the contribution acceptance criteria below:
|
|||
|
||||
If you contribute to GitLab please know that changes involve more than just
|
||||
code. We use the following [definition of done](https://www.agilealliance.org/glossary/definition-of-done).
|
||||
To reach the definition of done, the merge request must create no regressions and meet all these criteria:
|
||||
|
||||
- Verified as working in production on GitLab.com.
|
||||
- Verified as working for self-managed instances.
|
||||
|
||||
If a regression occurs, we prefer you revert the change. We break the definition of done into two phases: [MR Merge](#mr-merge) and [Production use](#production-use).
|
||||
Your contribution is not *done* until you have made sure it meets all of these
|
||||
requirements.
|
||||
|
||||
### MR Merge
|
||||
|
||||
1. Clear description explaining the relevancy of the contribution.
|
||||
1. Working and clean code that is commented where needed.
|
||||
1. [Unit, integration, and system tests](../testing_guide/index.md) that all pass
|
||||
|
|
@ -239,16 +247,23 @@ requirements.
|
|||
1. [Documented](../documentation/index.md) in the `/doc` directory.
|
||||
1. [Changelog entry added](../changelog.md), if necessary.
|
||||
1. Reviewed by relevant reviewers and all concerns are addressed for Availability, Regressions, Security. Documentation reviews should take place as soon as possible, but they should not block a merge request.
|
||||
1. Merged by a project maintainer.
|
||||
1. Create an issue in the [infrastructure issue tracker](https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues) to inform the Infrastructure department when your contribution is changing default settings or introduces a new setting, if relevant.
|
||||
1. Confirmed to be working in the [Canary stage](https://about.gitlab.com/handbook/engineering/#canary-testing) with no new [Sentry](https://about.gitlab.com/handbook/engineering/#sentry) errors or on GitLab.com once the contribution is deployed.
|
||||
1. Added to the [release post](https://about.gitlab.com/handbook/marketing/blog/release-posts/),
|
||||
if relevant.
|
||||
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml), if relevant.
|
||||
1. [Black-box tests/end-to-end tests](../testing_guide/testing_levels.md#black-box-tests-at-the-system-level-aka-end-to-end-tests)
|
||||
added if required. Please contact [the quality team](https://about.gitlab.com/handbook/engineering/quality/#teams)
|
||||
with any questions.
|
||||
1. The new feature does not degrade the user experience of the product.
|
||||
1. An agreed upon rollout plan.
|
||||
1. Merged by a project maintainer.
|
||||
|
||||
### Production use
|
||||
|
||||
1. Confirmed to be working in the production with no new [Sentry](https://about.gitlab.com/handbook/engineering/#sentry) errors after the contribution is deployed.
|
||||
1. *If the merge request uses feature flags, per-project or per-group enablement, and a staged rollout:*
|
||||
- Confirmed to be working on GitLab projects.
|
||||
- Confirmed to be working at each stage for all projects added.
|
||||
1. Added to the [release post](https://about.gitlab.com/handbook/marketing/blog/release-posts/),
|
||||
if relevant.
|
||||
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml), if relevant.
|
||||
|
||||
Contributions do not require approval from the [Product team](https://about.gitlab.com/handbook/product/product-processes/#gitlab-pms-arent-the-arbiters-of-community-contributions).
|
||||
|
||||
|
|
|
|||
|
|
@ -67,10 +67,12 @@ When the state of a flag changes (for example, disabled by default to enabled by
|
|||
Possible version history entries are:
|
||||
|
||||
```markdown
|
||||
> - [Introduced](issue-link) in GitLab X.X. [Deployed behind the <flag name> flag](../../administration/feature_flags.md), disabled by default.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab X.X.
|
||||
> - [Enabled on GitLab.com](issue-link) in GitLab X.X. Available to GitLab.com administrators only.
|
||||
> - [Enabled on self-managed](issue-link) in GitLab X.X.
|
||||
> - [Feature flag <flag name> removed](issue-line) in GitLab X.X.
|
||||
> - [Feature flag <flag name> removed](issue-link) in GitLab X.X.
|
||||
> - [Generally available](issue-link) in GitLab X.X.
|
||||
```
|
||||
|
||||
## Feature flag documentation examples
|
||||
|
|
@ -78,7 +80,7 @@ Possible version history entries are:
|
|||
The following examples show the progression of a feature flag.
|
||||
|
||||
```markdown
|
||||
> Introduced in GitLab 13.7.
|
||||
> Introduced in GitLab 13.7. [Deployed behind the `forti_token_cloud` flag](../../administration/feature_flags.md), disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available,
|
||||
|
|
@ -89,7 +91,7 @@ The feature is not ready for production use.
|
|||
When the feature is enabled in production, you can update the version history:
|
||||
|
||||
```markdown
|
||||
> - Introduced in GitLab 13.7.
|
||||
> - Introduced in GitLab 13.7. [Deployed behind the `forti_token_cloud` flag](../../administration/feature_flags.md), disabled by default.
|
||||
> - [Enabled on self-managed](https://gitlab.com/issue/etc) GitLab 13.8.
|
||||
|
||||
FLAG:
|
||||
|
|
@ -100,8 +102,9 @@ ask an administrator to [disable the `forti_token_cloud` flag](../administration
|
|||
And, when the feature is done and fully available to all users:
|
||||
|
||||
```markdown
|
||||
> - Introduced in GitLab 13.7.
|
||||
> - Introduced in GitLab 13.7. [Deployed behind the `forti_token_cloud` flag](../../administration/feature_flags.md), disabled by default.
|
||||
> - [Enabled on self-managed](https://gitlab.com/issue/etc) GitLab 13.8.
|
||||
> - [Enabled on GitLab.com](https://gitlab.com/issue/etc) in GitLab 13.9.
|
||||
> - [Feature flag `forti_token_cloud`](https://gitlab.com/issue/etc) removed in GitLab 14.0.
|
||||
> - [Generally available](issue-link) in GitLab 14.0.
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1479,10 +1479,6 @@ tagged and released set of documentation for your installed version:
|
|||
When a feature is added or updated, you can include its version information
|
||||
either as a **Version history** item or as an inline text reference.
|
||||
|
||||
Version text shouldn't include information about the tier in which the feature
|
||||
is available. This information is provided by the [product badge](#product-tier-badges)
|
||||
displayed for the page or feature.
|
||||
|
||||
#### Version text in the **Version History**
|
||||
|
||||
If all content in a section is related, add version text after the header for
|
||||
|
|
@ -1498,6 +1494,8 @@ the section. The version information must:
|
|||
- Whenever possible, include a link to the completed issue, merge request, or epic
|
||||
that introduced the feature. An issue is preferred over a merge request, and
|
||||
a merge request is preferred over an epic.
|
||||
- Do not include information about the tier, or a link to the pricing page.
|
||||
The tier is provided by the [product badge](#product-tier-badges) on the heading.
|
||||
|
||||
```markdown
|
||||
## Feature name
|
||||
|
|
|
|||
|
|
@ -188,6 +188,10 @@ Do not use **e-mail** with a hyphen. When plural, use **emails** or **email mess
|
|||
See [the Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/e/enable-enables) for guidance.
|
||||
Use **active** or **on** instead. ([Vale](../testing.md#vale) rule: [`InclusionAbleism.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/InclusionAbleism.yml))
|
||||
|
||||
## enter
|
||||
|
||||
Use instead of **type** when talking about putting values into text boxes.
|
||||
|
||||
## epic
|
||||
|
||||
Lowercase.
|
||||
|
|
@ -581,6 +585,10 @@ You **turn on** or **turn off** a toggle. For example:
|
|||
|
||||
- Turn on the **blah** toggle.
|
||||
|
||||
## type
|
||||
|
||||
Do not use if you can avoid it. Use **enter** instead.
|
||||
|
||||
## useful
|
||||
|
||||
Do not use. If the user doesn't find the process to be useful, we lose their trust.
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: reference
|
||||
---
|
||||
|
||||
# Project visibility
|
||||
# Project and group visibility
|
||||
|
||||
GitLab allows [Owners](../user/permissions.md) to set a project's visibility as:
|
||||
GitLab allows [Owners](../user/permissions.md) to set a project's or group's visibility as:
|
||||
|
||||
- **Public**
|
||||
- **Internal**
|
||||
|
|
@ -18,7 +18,7 @@ for your GitLab instance). For example, <https://gitlab.com/public>.
|
|||
You can control the visibility of individual features with
|
||||
[project feature settings](../user/permissions.md#project-features).
|
||||
|
||||
## Public projects
|
||||
## Public projects and groups
|
||||
|
||||
Public projects can be cloned **without any** authentication over HTTPS.
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ By default, `/public` is visible to unauthenticated users. However, if the
|
|||
[**Public** visibility level](../user/admin_area/settings/visibility_and_access_controls.md#restrict-visibility-levels)
|
||||
is restricted, `/public` is visible only to signed-in users.
|
||||
|
||||
## Internal projects
|
||||
## Internal projects and groups
|
||||
|
||||
Internal projects can be cloned by any signed-in user except
|
||||
[external users](../user/permissions.md#external-users).
|
||||
|
|
@ -47,7 +47,7 @@ and snippets on GitLab.com. Existing projects, groups, and snippets using the `I
|
|||
visibility setting keep this setting. You can read more about the change in the
|
||||
[relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/12388).
|
||||
|
||||
## Private projects
|
||||
## Private projects and groups
|
||||
|
||||
Private projects can only be cloned and viewed by project members (except for guests).
|
||||
|
||||
|
|
@ -62,7 +62,19 @@ Prerequisite:
|
|||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Visibility, project features, permissions**.
|
||||
1. Change **Project visibility** to either Public, Internal, or Private.
|
||||
1. Change **Project visibility** to either **Private**, **Internal**, or **Public**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Change group visibility
|
||||
|
||||
Prerequisite:
|
||||
|
||||
- You must have the Owner role for a group.
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your project.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Naming, visibility**.
|
||||
1. Under **Visibility level** select either **Private**, **Internal**, or **Public**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Restrict use of public or internal projects
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ disqus_identifier: 'https://docs.gitlab.com/ee/workflow/gitlab_flow.html'
|
|||
|
||||
# Introduction to GitLab Flow **(FREE)**
|
||||
|
||||

|
||||
|
||||
Git allows a wide variety of branching strategies and workflows.
|
||||
Because of this, many organizations end up with workflows that are too complicated, not clearly defined, or not integrated with issue tracking systems.
|
||||
Therefore, we propose GitLab flow as a clearly defined set of best practices.
|
||||
|
|
@ -33,8 +31,6 @@ In Git, you add files from the working copy to the staging area. After that, you
|
|||
The third step is pushing to a shared remote repository.
|
||||
After getting used to these three steps, the next challenge is the branching model.
|
||||
|
||||

|
||||
|
||||
Because many organizations new to Git have no conventions for how to work with it, their repositories can quickly become messy.
|
||||
The biggest problem is that many long-running branches emerge that all contain part of the changes.
|
||||
People have a hard time figuring out which branch has the latest code, or which branch to deploy to production.
|
||||
|
|
@ -237,8 +233,6 @@ When you reopen an issue you need to create a new merge request.
|
|||
|
||||
## Issue tracking with GitLab flow
|
||||
|
||||

|
||||
|
||||
GitLab flow is a way to make the relation between the code and the issue tracker more transparent.
|
||||
|
||||
Any significant change to the code should start with an issue that describes the goal.
|
||||
|
|
@ -277,8 +271,6 @@ It is possible that one feature branch solves more than one issue.
|
|||
|
||||
## Linking and closing issues from merge requests
|
||||
|
||||

|
||||
|
||||
Link to issues by mentioning them in commit messages or the description of a merge request, for example, "Fixes #16" or "Duck typing is preferred. See #12."
|
||||
GitLab then creates links to the mentioned issues and creates comments in the issues linking back to the merge request.
|
||||
|
||||
|
|
@ -339,8 +331,6 @@ Git does not allow you to merge the code again otherwise.
|
|||
|
||||
## Reducing merge commits in feature branches
|
||||
|
||||

|
||||
|
||||
Having lots of merge commits can make your repository history messy.
|
||||
Therefore, you should try to avoid merge commits in feature branches.
|
||||
Often, people avoid merge commits by just using rebase to reorder their commits after the commits on the `main` branch.
|
||||
|
|
@ -427,8 +417,6 @@ Issue: gitlab.com/gitlab-org/gitlab/-/issues/1
|
|||
|
||||
## Testing before merging
|
||||
|
||||

|
||||
|
||||
In old workflows, the continuous integration (CI) server commonly ran tests on the `main` branch only.
|
||||
Developers had to ensure their code did not break the `main` branch.
|
||||
When using GitLab flow, developers create their branches from this `main` branch, so it is essential that it never breaks.
|
||||
|
|
@ -444,8 +432,6 @@ As said before, if you often have feature branches that last for more than a few
|
|||
|
||||
## Working with feature branches
|
||||
|
||||

|
||||
|
||||
When creating a feature branch, always branch from an up-to-date `main`.
|
||||
If you know before you start that your work depends on another branch, you can also branch from there.
|
||||
If you need to merge in another branch after starting, explain the reason in the merge commit.
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
|
@ -362,7 +362,7 @@ doesn't return the following headers:
|
|||
### Visibility settings
|
||||
|
||||
If created before GitLab 12.2 (July 2019), these items have the
|
||||
[Internal visibility](../../public_access/public_access.md#internal-projects)
|
||||
[Internal visibility](../../public_access/public_access.md#internal-projects-and-groups)
|
||||
setting [disabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/12388):
|
||||
|
||||
- Projects
|
||||
|
|
|
|||
|
|
@ -280,6 +280,7 @@ The following table lists group permissions available for each role:
|
|||
|--------------------------------------------------------|-------|----------|-----------|------------|-------|
|
||||
| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Edit SAML SSO Billing **(PREMIUM SAAS)** | ✓ | ✓ | ✓ | ✓ | ✓ (4) |
|
||||
| Pull a container image using the dependency proxy | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| View Contribution analytics | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| View group epic **(PREMIUM)** | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| View group wiki pages **(PREMIUM)** | ✓ (6) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
|
@ -301,7 +302,6 @@ The following table lists group permissions available for each role:
|
|||
| Create/edit/delete iterations | | | ✓ | ✓ | ✓ |
|
||||
| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ |
|
||||
| Enable/disable a dependency proxy | | | ✓ | ✓ | ✓ |
|
||||
| Pull a container image using the dependency proxy | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Purge the dependency proxy for a group | | | | | ✓ |
|
||||
| Publish [packages](packages/index.md) | | | ✓ | ✓ | ✓ |
|
||||
| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
|
||||
|
|
@ -314,6 +314,7 @@ The following table lists group permissions available for each role:
|
|||
| View/manage group-level Kubernetes cluster | | | | ✓ | ✓ |
|
||||
| Administer project compliance frameworks | | | | | ✓ |
|
||||
| Create/Delete group deploy tokens | | | | | ✓ |
|
||||
| Change group visibility level | | | | | ✓ |
|
||||
| Delete group | | | | | ✓ |
|
||||
| Delete group epic **(PREMIUM)** | | | | | ✓ |
|
||||
| Disable notification emails | | | | | ✓ |
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module Gitlab
|
|||
|
||||
MAIN_DATABASE_NAME = 'main'
|
||||
CI_DATABASE_NAME = 'ci'
|
||||
DEFAULT_POOL_HEADROOM = 10
|
||||
|
||||
# This constant is used when renaming tables concurrently.
|
||||
# If you plan to rename a table using the `rename_table_safely` method, add your table here one milestone before the rename.
|
||||
|
|
@ -62,6 +63,20 @@ module Gitlab
|
|||
DATABASES[PRIMARY_DATABASE_NAME]
|
||||
end
|
||||
|
||||
# We configure the database connection pool size automatically based on the
|
||||
# configured concurrency. We also add some headroom, to make sure we don't
|
||||
# run out of connections when more threads besides the 'user-facing' ones
|
||||
# are running.
|
||||
#
|
||||
# Read more about this in
|
||||
# doc/development/database/client_side_connection_pool.md
|
||||
def self.default_pool_size
|
||||
headroom =
|
||||
(ENV["DB_POOL_HEADROOM"].presence || DEFAULT_POOL_HEADROOM).to_i
|
||||
|
||||
Gitlab::Runtime.max_threads + headroom
|
||||
end
|
||||
|
||||
def self.has_config?(database_name)
|
||||
Gitlab::Application.config.database_configuration[Rails.env].include?(database_name.to_s)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ module Gitlab
|
|||
# Configuration settings and methods for interacting with a PostgreSQL
|
||||
# database, with support for multiple databases.
|
||||
class Connection
|
||||
DEFAULT_POOL_HEADROOM = 10
|
||||
|
||||
attr_reader :scope
|
||||
|
||||
# Initializes a new `Database`.
|
||||
|
|
@ -20,20 +18,6 @@ module Gitlab
|
|||
@open_transactions_baseline = 0
|
||||
end
|
||||
|
||||
# We configure the database connection pool size automatically based on
|
||||
# the configured concurrency. We also add some headroom, to make sure we
|
||||
# don't run out of connections when more threads besides the 'user-facing'
|
||||
# ones are running.
|
||||
#
|
||||
# Read more about this in
|
||||
# doc/development/database/client_side_connection_pool.md
|
||||
def default_pool_size
|
||||
headroom =
|
||||
(ENV["DB_POOL_HEADROOM"].presence || DEFAULT_POOL_HEADROOM).to_i
|
||||
|
||||
Gitlab::Runtime.max_threads + headroom
|
||||
end
|
||||
|
||||
def config
|
||||
# The result of this method must not be cached, as other methods may use
|
||||
# it after making configuration changes and expect those changes to be
|
||||
|
|
@ -48,7 +32,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def pool_size
|
||||
config[:pool] || default_pool_size
|
||||
config[:pool] || Database.default_pool_size
|
||||
end
|
||||
|
||||
def username
|
||||
|
|
@ -77,7 +61,9 @@ module Gitlab
|
|||
|
||||
def db_config_with_default_pool_size
|
||||
db_config_object = scope.connection_db_config
|
||||
config = db_config_object.configuration_hash.merge(pool: default_pool_size)
|
||||
config = db_config_object
|
||||
.configuration_hash
|
||||
.merge(pool: Database.default_pool_size)
|
||||
|
||||
ActiveRecord::DatabaseConfigurations::HashConfig.new(
|
||||
db_config_object.env_name,
|
||||
|
|
|
|||
|
|
@ -36,94 +36,42 @@ module Gitlab
|
|||
|
||||
# Returns a Hash containing the load balancing configuration.
|
||||
def self.configuration
|
||||
Gitlab::Database.main.config[:load_balancing] || {}
|
||||
end
|
||||
|
||||
# Returns the maximum replica lag size in bytes.
|
||||
def self.max_replication_difference
|
||||
(configuration['max_replication_difference'] || 8.megabytes).to_i
|
||||
end
|
||||
|
||||
# Returns the maximum lag time for a replica.
|
||||
def self.max_replication_lag_time
|
||||
(configuration['max_replication_lag_time'] || 60.0).to_f
|
||||
end
|
||||
|
||||
# Returns the interval (in seconds) to use for checking the status of a
|
||||
# replica.
|
||||
def self.replica_check_interval
|
||||
(configuration['replica_check_interval'] || 60).to_f
|
||||
end
|
||||
|
||||
# Returns the additional hosts to use for load balancing.
|
||||
def self.hosts
|
||||
configuration['hosts'] || []
|
||||
end
|
||||
|
||||
def self.service_discovery_enabled?
|
||||
configuration.dig('discover', 'record').present?
|
||||
end
|
||||
|
||||
def self.service_discovery_configuration
|
||||
conf = configuration['discover'] || {}
|
||||
|
||||
{
|
||||
nameserver: conf['nameserver'] || 'localhost',
|
||||
port: conf['port'] || 8600,
|
||||
record: conf['record'],
|
||||
record_type: conf['record_type'] || 'A',
|
||||
interval: conf['interval'] || 60,
|
||||
disconnect_timeout: conf['disconnect_timeout'] || 120,
|
||||
use_tcp: conf['use_tcp'] || false
|
||||
}
|
||||
end
|
||||
|
||||
def self.pool_size
|
||||
Gitlab::Database.main.pool_size
|
||||
@configuration ||= Configuration.for_model(ActiveRecord::Base)
|
||||
end
|
||||
|
||||
# Returns true if load balancing is to be enabled.
|
||||
def self.enable?
|
||||
return false if Gitlab::Runtime.rake?
|
||||
return false unless self.configured?
|
||||
|
||||
true
|
||||
configured?
|
||||
end
|
||||
|
||||
# Returns true if load balancing has been configured. Since
|
||||
# Sidekiq does not currently use load balancing, we
|
||||
# may want Web application servers to detect replication lag by
|
||||
# posting the write location of the database if load balancing is
|
||||
# configured.
|
||||
def self.configured?
|
||||
hosts.any? || service_discovery_enabled?
|
||||
configuration.load_balancing_enabled? ||
|
||||
configuration.service_discovery_enabled?
|
||||
end
|
||||
|
||||
def self.start_service_discovery
|
||||
return unless service_discovery_enabled?
|
||||
return unless configuration.service_discovery_enabled?
|
||||
|
||||
ServiceDiscovery
|
||||
.new(proxy.load_balancer, **service_discovery_configuration)
|
||||
.new(proxy.load_balancer, **configuration.service_discovery)
|
||||
.start
|
||||
end
|
||||
|
||||
# Configures proxying of requests.
|
||||
def self.configure_proxy
|
||||
lb = LoadBalancer.new(hosts, primary_only: !enable?)
|
||||
lb = LoadBalancer.new(configuration, primary_only: !enable?)
|
||||
ActiveRecord::Base.load_balancing_proxy = ConnectionProxy.new(lb)
|
||||
|
||||
# Populate service discovery immediately if it is configured
|
||||
if service_discovery_enabled?
|
||||
if configuration.service_discovery_enabled?
|
||||
ServiceDiscovery
|
||||
.new(lb, **service_discovery_configuration)
|
||||
.new(lb, **configuration.service_discovery)
|
||||
.perform_service_discovery
|
||||
end
|
||||
end
|
||||
|
||||
def self.active_record_models
|
||||
ActiveRecord::Base.descendants
|
||||
end
|
||||
|
||||
DB_ROLES = [
|
||||
ROLE_PRIMARY = :primary,
|
||||
ROLE_REPLICA = :replica,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Database
|
||||
module LoadBalancing
|
||||
# Configuration settings for a single LoadBalancer instance.
|
||||
class Configuration
|
||||
attr_accessor :hosts, :max_replication_difference,
|
||||
:max_replication_lag_time, :replica_check_interval,
|
||||
:service_discovery, :pool_size, :model
|
||||
|
||||
# Creates a configuration object for the given ActiveRecord model.
|
||||
def self.for_model(model)
|
||||
cfg = model.connection_db_config.configuration_hash
|
||||
lb_cfg = cfg[:load_balancing] || {}
|
||||
config = new(model)
|
||||
|
||||
if (size = cfg[:pool])
|
||||
config.pool_size = size
|
||||
end
|
||||
|
||||
if (diff = lb_cfg[:max_replication_difference])
|
||||
config.max_replication_difference = diff
|
||||
end
|
||||
|
||||
if (lag = lb_cfg[:max_replication_lag_time])
|
||||
config.max_replication_lag_time = lag.to_f
|
||||
end
|
||||
|
||||
if (interval = lb_cfg[:replica_check_interval])
|
||||
config.replica_check_interval = interval.to_f
|
||||
end
|
||||
|
||||
if (hosts = lb_cfg[:hosts])
|
||||
config.hosts = hosts
|
||||
end
|
||||
|
||||
discover = (lb_cfg[:discover] || {}).symbolize_keys
|
||||
|
||||
# We iterate over the known/default keys so we don't end up with
|
||||
# random keys in our configuration hash.
|
||||
config.service_discovery.each do |key, _|
|
||||
if (value = discover[key])
|
||||
config.service_discovery[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
config
|
||||
end
|
||||
|
||||
def initialize(model, hosts = [])
|
||||
@max_replication_difference = 8.megabytes
|
||||
@max_replication_lag_time = 60.0
|
||||
@replica_check_interval = 60.0
|
||||
@model = model
|
||||
@hosts = hosts
|
||||
@pool_size = Database.default_pool_size
|
||||
@service_discovery = {
|
||||
nameserver: 'localhost',
|
||||
port: 8600,
|
||||
record: nil,
|
||||
record_type: 'A',
|
||||
interval: 60,
|
||||
disconnect_timeout: 120,
|
||||
use_tcp: false
|
||||
}
|
||||
end
|
||||
|
||||
def load_balancing_enabled?
|
||||
hosts.any? || service_discovery_enabled?
|
||||
end
|
||||
|
||||
def service_discovery_enabled?
|
||||
service_discovery[:record].present?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -29,11 +29,15 @@ module Gitlab
|
|||
@host = host
|
||||
@port = port
|
||||
@load_balancer = load_balancer
|
||||
@pool = load_balancer.create_replica_connection_pool(::Gitlab::Database::LoadBalancing.pool_size, host, port)
|
||||
@pool = load_balancer.create_replica_connection_pool(
|
||||
load_balancer.configuration.pool_size,
|
||||
host,
|
||||
port
|
||||
)
|
||||
@online = true
|
||||
@last_checked_at = Time.zone.now
|
||||
|
||||
interval = ::Gitlab::Database::LoadBalancing.replica_check_interval
|
||||
interval = load_balancer.configuration.replica_check_interval
|
||||
@intervals = (interval..(interval * 2)).step(0.5).to_a
|
||||
end
|
||||
|
||||
|
|
@ -108,7 +112,7 @@ module Gitlab
|
|||
|
||||
def replication_lag_below_threshold?
|
||||
if (lag_time = replication_lag_time)
|
||||
lag_time <= ::Gitlab::Database::LoadBalancing.max_replication_lag_time
|
||||
lag_time <= load_balancer.configuration.max_replication_lag_time
|
||||
else
|
||||
false
|
||||
end
|
||||
|
|
@ -125,7 +129,7 @@ module Gitlab
|
|||
# only do this if we haven't replicated in a while so we only need
|
||||
# to connect to the primary when truly necessary.
|
||||
if (lag_size = replication_lag_size)
|
||||
lag_size <= ::Gitlab::Database::LoadBalancing.max_replication_difference
|
||||
lag_size <= load_balancer.configuration.max_replication_difference
|
||||
else
|
||||
false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,20 +12,21 @@ module Gitlab
|
|||
|
||||
REPLICA_SUFFIX = '_replica'
|
||||
|
||||
attr_reader :host_list
|
||||
attr_reader :host_list, :configuration
|
||||
|
||||
# hosts - The hostnames/addresses of the additional databases.
|
||||
# model - The ActiveRecord base model the load balancer is enabled for.
|
||||
# configuration - An instance of `LoadBalancing::Configuration` that
|
||||
# contains the configuration details (such as the hosts)
|
||||
# for this load balancer.
|
||||
# primary_only - If set, the replicas are ignored and the primary is
|
||||
# always used.
|
||||
def initialize(hosts = [], model = ActiveRecord::Base, primary_only: false)
|
||||
def initialize(configuration, primary_only: false)
|
||||
@configuration = configuration
|
||||
@primary_only = primary_only
|
||||
@model = model
|
||||
@host_list =
|
||||
if primary_only
|
||||
HostList.new([PrimaryHost.new(self)])
|
||||
else
|
||||
HostList.new(hosts.map { |addr| Host.new(addr, self) })
|
||||
HostList.new(configuration.hosts.map { |addr| Host.new(addr, self) })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -231,7 +232,7 @@ module Gitlab
|
|||
# leverage that.
|
||||
def pool
|
||||
ActiveRecord::Base.connection_handler.retrieve_connection_pool(
|
||||
@model.connection_specification_name,
|
||||
@configuration.model.connection_specification_name,
|
||||
role: ActiveRecord::Base.writing_role,
|
||||
shard: ActiveRecord::Base.default_shard
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13938,9 +13938,6 @@ msgstr ""
|
|||
msgid "Failed to load related branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load sidebar lock status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load stacktrace."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30659,6 +30656,9 @@ msgstr ""
|
|||
msgid "Set the default branch for this project. All merge requests and commits are made against this branch unless you specify a different one."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the default expiration time for job artifacts in all projects. Set to %{code_open}0%{code_close} to never expire artifacts by default. If no unit is written, it defaults to seconds. For example, these are all equivalent: %{code_open}3600%{code_close}, %{code_open}60 minutes%{code_close}, or %{code_open}one hour%{code_close}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the due date to %{due_date}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33334,9 +33334,6 @@ msgstr ""
|
|||
msgid "The default branch for this project has been changed. Please update your bookmarks."
|
||||
msgstr ""
|
||||
|
||||
msgid "The default expiration time for job artifacts. 0 for unlimited. The default unit is in seconds, but you can use other units, for example %{code_open}4 mins 2 sec%{code_close}, %{code_open}2h42min%{code_close}."
|
||||
msgstr ""
|
||||
|
||||
msgid "The dependency list details information about the components used within your project."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36156,6 +36153,9 @@ msgstr ""
|
|||
msgid "UsageQuota|%{help_link_start}Shared runners%{help_link_end} are disabled, so there are no limits set on pipeline usage"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|%{linkTitle} help link"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|%{percentageLeft} of purchased storage is available"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36165,6 +36165,9 @@ msgstr ""
|
|||
msgid "UsageQuota|Artifacts is a sum of build and pipeline artifacts."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Audio samples, videos, datasets, and graphics."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Buy additional minutes"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36174,9 +36177,21 @@ msgstr ""
|
|||
msgid "UsageQuota|CI minutes usage by project"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Code packages and container images."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Current period usage"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|File attachments and smaller design graphics."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Git repository, managed by the Gitaly service."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Includes project registry, artifacts, packages, wiki, uploads and other items."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Increase storage temporarily"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36195,6 +36210,9 @@ msgstr ""
|
|||
msgid "UsageQuota|Packages"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Pipeline artifacts and job artifacts, created with CI/CD."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Pipelines"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36213,6 +36231,9 @@ msgstr ""
|
|||
msgid "UsageQuota|Seats"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Shared bits of code and text."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Snippets"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36222,6 +36243,12 @@ msgstr ""
|
|||
msgid "UsageQuota|Storage"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Storage type"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|There is a known issue with Artifact storage where the total could be incorrect for some projects. More details and progress are available in %{warningLinkStart}the epic%{warningLinkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|This is the total amount of storage used across your projects within this namespace."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36261,6 +36288,9 @@ msgstr ""
|
|||
msgid "UsageQuota|Usage"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Usage Breakdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Usage Quotas"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36285,6 +36315,9 @@ msgstr ""
|
|||
msgid "UsageQuota|Wiki"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Wiki content."
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Wikis"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -58,14 +58,12 @@ module QA
|
|||
end
|
||||
|
||||
before do
|
||||
Runtime::Feature.enable(:pipeline_editor_branch_switcher)
|
||||
Flow::Login.sign_in
|
||||
project.visit!
|
||||
Page::Project::Menu.perform(&:go_to_pipeline_editor)
|
||||
end
|
||||
|
||||
after do
|
||||
Runtime::Feature.disable(:pipeline_editor_branch_switcher)
|
||||
project.remove_via_api!
|
||||
Page::Main::Menu.perform(&:sign_out)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,10 +25,6 @@ RSpec.describe 'Merge request > Batch comments', :js do
|
|||
visit_diffs
|
||||
end
|
||||
|
||||
it 'has review bar' do
|
||||
expect(page).to have_selector('[data-testid="review_bar_component"]', visible: false)
|
||||
end
|
||||
|
||||
it 'adds draft note' do
|
||||
write_diff_comment
|
||||
|
||||
|
|
|
|||
|
|
@ -27,10 +27,6 @@ RSpec.describe 'Pipeline Editor', :js do
|
|||
end
|
||||
|
||||
context 'branch switcher' do
|
||||
before do
|
||||
stub_feature_flags(pipeline_editor_branch_switcher: true)
|
||||
end
|
||||
|
||||
def switch_to_branch(branch)
|
||||
find('[data-testid="branch-selector"]').click
|
||||
|
||||
|
|
|
|||
|
|
@ -4,16 +4,10 @@ import PipelineEditorFileNav from '~/pipeline_editor/components/file_nav/pipelin
|
|||
|
||||
describe('Pipeline editor file nav', () => {
|
||||
let wrapper;
|
||||
const mockProvide = {
|
||||
glFeatures: {
|
||||
pipelineEditorBranchSwitcher: true,
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = ({ provide = {} } = {}) => {
|
||||
wrapper = shallowMount(PipelineEditorFileNav, {
|
||||
provide: {
|
||||
...mockProvide,
|
||||
...provide,
|
||||
},
|
||||
});
|
||||
|
|
@ -34,16 +28,4 @@ describe('Pipeline editor file nav', () => {
|
|||
expect(findBranchSwitcher().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with branch switcher feature flag OFF', () => {
|
||||
it('does not render the branch switcher', () => {
|
||||
createComponent({
|
||||
provide: {
|
||||
glFeatures: { pipelineEditorBranchSwitcher: false },
|
||||
},
|
||||
});
|
||||
|
||||
expect(findBranchSwitcher().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ describe('Pipeline editor empty state', () => {
|
|||
let wrapper;
|
||||
const defaultProvide = {
|
||||
glFeatures: {
|
||||
pipelineEditorBranchSwitcher: true,
|
||||
pipelineEditorEmptyStateAction: false,
|
||||
},
|
||||
emptyStateIllustrationPath: 'my/svg/path',
|
||||
|
|
@ -82,17 +81,5 @@ describe('Pipeline editor empty state', () => {
|
|||
await findConfirmButton().vm.$emit('click');
|
||||
expect(wrapper.emitted(expectedEvent)).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('with branch switcher feature flag OFF', () => {
|
||||
it('does not render the file nav', () => {
|
||||
createComponent({
|
||||
provide: {
|
||||
glFeatures: { pipelineEditorBranchSwitcher: false },
|
||||
},
|
||||
});
|
||||
|
||||
expect(findFileNav().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { createSankey } from '~/pipelines/components/dag/drawing_utils';
|
||||
import {
|
||||
createNodeDict,
|
||||
makeLinksFromNodes,
|
||||
filterByAncestors,
|
||||
generateColumnsFromLayersListBare,
|
||||
|
|
@ -9,6 +8,7 @@ import {
|
|||
removeOrphanNodes,
|
||||
getMaxNodes,
|
||||
} from '~/pipelines/components/parsing_utils';
|
||||
import { createNodeDict } from '~/pipelines/utils';
|
||||
|
||||
import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data';
|
||||
import { generateResponse, mockPipelineResponse } from './graph/mock_data';
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import { GlAlert } from '@gitlab/ui';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import StorageCounterApp from '~/projects/storage_counter/components/app.vue';
|
||||
import getProjectStorageCount from '~/projects/storage_counter/queries/project_storage.query.graphql';
|
||||
import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue';
|
||||
import { projectStorageCountResponse } from './mock_data';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
describe('Storage counter app', () => {
|
||||
let wrapper;
|
||||
|
||||
const createMockApolloProvider = ({ mutationMock }) => {
|
||||
localVue.use(VueApollo);
|
||||
|
||||
const requestHandlers = [[getProjectStorageCount, mutationMock]];
|
||||
|
||||
return createMockApollo(requestHandlers);
|
||||
};
|
||||
|
||||
const createComponent = ({ provide = {}, mockApollo } = {}) => {
|
||||
const defaultProvideValues = {
|
||||
projectPath: 'test-project',
|
||||
};
|
||||
|
||||
wrapper = shallowMount(StorageCounterApp, {
|
||||
localVue,
|
||||
apolloProvider: mockApollo,
|
||||
provide: {
|
||||
...defaultProvideValues,
|
||||
...provide,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findUsageGraph = () => wrapper.findComponent(UsageGraph);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders app successfully', () => {
|
||||
expect(wrapper.text()).toBe('Usage');
|
||||
});
|
||||
|
||||
describe('handling apollo fetching error', () => {
|
||||
const mutationMock = jest.fn().mockRejectedValue(new Error('GraphQL error'));
|
||||
|
||||
beforeEach(() => {
|
||||
const mockApollo = createMockApolloProvider({ mutationMock });
|
||||
createComponent({ mockApollo });
|
||||
});
|
||||
|
||||
it('renders gl-alert if there is an error', () => {
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering <usage-graph />', () => {
|
||||
const mutationMock = jest.fn().mockResolvedValue(projectStorageCountResponse);
|
||||
|
||||
beforeEach(() => {
|
||||
const mockApollo = createMockApolloProvider({ mutationMock });
|
||||
createComponent({ mockApollo });
|
||||
});
|
||||
|
||||
it('renders usage-graph component if project.statistics exists', () => {
|
||||
expect(findUsageGraph().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('passes project.statistics to usage-graph component', () => {
|
||||
const { __typename, ...statistics } = projectStorageCountResponse.data.project.statistics;
|
||||
expect(findUsageGraph().props('rootStorageStatistics')).toMatchObject(statistics);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import StorageCounterApp from '~/projects/storage_counter/components/app.vue';
|
||||
import { TOTAL_USAGE_DEFAULT_TEXT } from '~/projects/storage_counter/constants';
|
||||
import getProjectStorageCount from '~/projects/storage_counter/queries/project_storage.query.graphql';
|
||||
import UsageGraph from '~/vue_shared/components/storage_counter/usage_graph.vue';
|
||||
import {
|
||||
mockGetProjectStorageCountGraphQLResponse,
|
||||
mockEmptyResponse,
|
||||
projectData,
|
||||
defaultProvideValues,
|
||||
} from '../mock_data';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
describe('Storage counter app', () => {
|
||||
let wrapper;
|
||||
|
||||
const createMockApolloProvider = ({ reject = false, mockedValue } = {}) => {
|
||||
let response;
|
||||
|
||||
if (reject) {
|
||||
response = jest.fn().mockRejectedValue(mockedValue || new Error('GraphQL error'));
|
||||
} else {
|
||||
response = jest.fn().mockResolvedValue(mockedValue);
|
||||
}
|
||||
|
||||
const requestHandlers = [[getProjectStorageCount, response]];
|
||||
|
||||
return createMockApollo(requestHandlers);
|
||||
};
|
||||
|
||||
const createComponent = ({ provide = {}, mockApollo } = {}) => {
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(StorageCounterApp, {
|
||||
localVue,
|
||||
apolloProvider: mockApollo,
|
||||
provide: {
|
||||
...defaultProvideValues,
|
||||
...provide,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findUsagePercentage = () => wrapper.findByTestId('total-usage');
|
||||
const findUsageQuotasHelpLink = () => wrapper.findByTestId('usage-quotas-help-link');
|
||||
const findUsageGraph = () => wrapper.findComponent(UsageGraph);
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('with apollo fetching successful', () => {
|
||||
let mockApollo;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockApollo = createMockApolloProvider({
|
||||
mockedValue: mockGetProjectStorageCountGraphQLResponse,
|
||||
});
|
||||
createComponent({ mockApollo });
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders correct total usage', () => {
|
||||
expect(findUsagePercentage().text()).toBe(projectData.storage.totalUsage);
|
||||
});
|
||||
|
||||
it('renders correct usage quotas help link', () => {
|
||||
expect(findUsageQuotasHelpLink().attributes('href')).toBe(
|
||||
defaultProvideValues.helpLinks.usageQuotasHelpPagePath,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with apollo loading', () => {
|
||||
let mockApollo;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApollo = createMockApolloProvider({
|
||||
mockedValue: new Promise(() => {}),
|
||||
});
|
||||
createComponent({ mockApollo });
|
||||
});
|
||||
|
||||
it('should show loading icon', () => {
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with apollo returning empty data', () => {
|
||||
let mockApollo;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockApollo = createMockApolloProvider({
|
||||
mockedValue: mockEmptyResponse,
|
||||
});
|
||||
createComponent({ mockApollo });
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('shows default text for total usage', () => {
|
||||
expect(findUsagePercentage().text()).toBe(TOTAL_USAGE_DEFAULT_TEXT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with apollo fetching error', () => {
|
||||
let mockApollo;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApollo = createMockApolloProvider();
|
||||
createComponent({ mockApollo, reject: true });
|
||||
});
|
||||
|
||||
it('renders gl-alert', () => {
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering <usage-graph />', () => {
|
||||
let mockApollo;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockApollo = createMockApolloProvider({
|
||||
mockedValue: mockGetProjectStorageCountGraphQLResponse,
|
||||
});
|
||||
createComponent({ mockApollo });
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders usage-graph component if project.statistics exists', () => {
|
||||
expect(findUsageGraph().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('passes project.statistics to usage-graph component', () => {
|
||||
const {
|
||||
__typename,
|
||||
...statistics
|
||||
} = mockGetProjectStorageCountGraphQLResponse.data.project.statistics;
|
||||
expect(findUsageGraph().props('rootStorageStatistics')).toMatchObject(statistics);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { GlTable } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import StorageTable from '~/projects/storage_counter/components/storage_table.vue';
|
||||
import { projectData, defaultProvideValues } from '../mock_data';
|
||||
|
||||
describe('StorageTable', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
storageTypes: projectData.storage.storageTypes,
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = extendedWrapper(
|
||||
mount(StorageTable, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const findTable = () => wrapper.findComponent(GlTable);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('with storage types', () => {
|
||||
it.each(projectData.storage.storageTypes)(
|
||||
'renders table row correctly %o',
|
||||
({ storageType: { id, name, description } }) => {
|
||||
expect(wrapper.findByTestId(`${id}-name`).text()).toBe(name);
|
||||
expect(wrapper.findByTestId(`${id}-description`).text()).toBe(description);
|
||||
expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe(
|
||||
defaultProvideValues.helpLinks[id.replace(`Size`, `HelpPagePath`)]
|
||||
.replace(`Size`, ``)
|
||||
.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('without storage types', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ storageTypes: [] });
|
||||
});
|
||||
|
||||
it('should render the table header <th>', () => {
|
||||
expect(findTable().find('th').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should not render any table data <td>', () => {
|
||||
expect(findTable().find('td').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,19 +1,108 @@
|
|||
export const projectStorageCountResponse = {
|
||||
export const mockGetProjectStorageCountGraphQLResponse = {
|
||||
data: {
|
||||
project: {
|
||||
id: 'gid://gitlab/Project/20',
|
||||
statistics: {
|
||||
buildArtifactsSize: 400000,
|
||||
lfsObjectsSize: 4800000,
|
||||
packagesSize: 3800000,
|
||||
repositorySize: 39000000,
|
||||
snippetsSize: 0,
|
||||
storageSize: 39930000,
|
||||
uploadsSize: 0,
|
||||
wikiSize: 300000,
|
||||
buildArtifactsSize: 400000.0,
|
||||
lfsObjectsSize: 4800000.0,
|
||||
packagesSize: 3800000.0,
|
||||
repositorySize: 3900000.0,
|
||||
snippetsSize: 1200000.0,
|
||||
storageSize: 15300000.0,
|
||||
uploadsSize: 900000.0,
|
||||
wikiSize: 300000.0,
|
||||
__typename: 'ProjectStatistics',
|
||||
},
|
||||
__typename: 'Project',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockEmptyResponse = { data: { project: null } };
|
||||
|
||||
export const defaultProvideValues = {
|
||||
projectPath: '/project-path',
|
||||
helpLinks: {
|
||||
usageQuotasHelpPagePath: '/usage-quotas',
|
||||
buildArtifactsHelpPagePath: '/build-artifacts',
|
||||
lfsObjectsHelpPagePath: '/lsf-objects',
|
||||
packagesHelpPagePath: '/packages',
|
||||
repositoryHelpPagePath: '/repository',
|
||||
snippetsHelpPagePath: '/snippets',
|
||||
uploadsHelpPagePath: '/uploads',
|
||||
wikiHelpPagePath: '/wiki',
|
||||
},
|
||||
};
|
||||
|
||||
export const projectData = {
|
||||
storage: {
|
||||
totalUsage: '14.6 MiB',
|
||||
storageTypes: [
|
||||
{
|
||||
storageType: {
|
||||
id: 'buildArtifactsSize',
|
||||
name: 'Artifacts',
|
||||
description: 'Pipeline artifacts and job artifacts, created with CI/CD.',
|
||||
warningMessage:
|
||||
'There is a known issue with Artifact storage where the total could be incorrect for some projects. More details and progress are available in %{warningLinkStart}the epic%{warningLinkEnd}.',
|
||||
helpPath: '/build-artifacts',
|
||||
},
|
||||
value: '390.6 KiB',
|
||||
},
|
||||
{
|
||||
storageType: {
|
||||
id: 'lfsObjectsSize',
|
||||
name: 'LFS Storage',
|
||||
description: 'Audio samples, videos, datasets, and graphics.',
|
||||
helpPath: '/lsf-objects',
|
||||
},
|
||||
value: '4.6 MiB',
|
||||
},
|
||||
{
|
||||
storageType: {
|
||||
id: 'packagesSize',
|
||||
name: 'Packages',
|
||||
description: 'Code packages and container images.',
|
||||
helpPath: '/packages',
|
||||
},
|
||||
value: '3.6 MiB',
|
||||
},
|
||||
{
|
||||
storageType: {
|
||||
id: 'repositorySize',
|
||||
name: 'Repository',
|
||||
description: 'Git repository, managed by the Gitaly service.',
|
||||
helpPath: '/repository',
|
||||
},
|
||||
value: '3.7 MiB',
|
||||
},
|
||||
{
|
||||
storageType: {
|
||||
id: 'snippetsSize',
|
||||
name: 'Snippets',
|
||||
description: 'Shared bits of code and text.',
|
||||
helpPath: '/snippets',
|
||||
},
|
||||
value: '1.1 MiB',
|
||||
},
|
||||
{
|
||||
storageType: {
|
||||
id: 'uploadsSize',
|
||||
name: 'Uploads',
|
||||
description: 'File attachments and smaller design graphics.',
|
||||
helpPath: '/uploads',
|
||||
},
|
||||
value: '878.9 KiB',
|
||||
},
|
||||
{
|
||||
storageType: {
|
||||
id: 'wikiSize',
|
||||
name: 'Wiki',
|
||||
description: 'Wiki content.',
|
||||
helpPath: '/wiki',
|
||||
},
|
||||
value: '293.0 KiB',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import { parseGetProjectStorageResults } from '~/projects/storage_counter/utils';
|
||||
import {
|
||||
mockGetProjectStorageCountGraphQLResponse,
|
||||
projectData,
|
||||
defaultProvideValues,
|
||||
} from './mock_data';
|
||||
|
||||
describe('parseGetProjectStorageResults', () => {
|
||||
it('parses project statistics correctly', () => {
|
||||
expect(
|
||||
parseGetProjectStorageResults(
|
||||
mockGetProjectStorageCountGraphQLResponse.data,
|
||||
defaultProvideValues.helpLinks,
|
||||
),
|
||||
).toMatchObject(projectData);
|
||||
});
|
||||
});
|
||||
|
|
@ -5,29 +5,14 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::Database::Connection do
|
||||
let(:connection) { described_class.new }
|
||||
|
||||
describe '#default_pool_size' do
|
||||
before do
|
||||
allow(Gitlab::Runtime).to receive(:max_threads).and_return(7)
|
||||
end
|
||||
|
||||
it 'returns the max thread size plus a fixed headroom of 10' do
|
||||
expect(connection.default_pool_size).to eq(17)
|
||||
end
|
||||
|
||||
it 'returns the max thread size plus a DB_POOL_HEADROOM if this env var is present' do
|
||||
stub_env('DB_POOL_HEADROOM', '7')
|
||||
|
||||
expect(connection.default_pool_size).to eq(14)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#config' do
|
||||
it 'returns a HashWithIndifferentAccess' do
|
||||
expect(connection.config).to be_an_instance_of(HashWithIndifferentAccess)
|
||||
end
|
||||
|
||||
it 'returns a default pool size' do
|
||||
expect(connection.config).to include(pool: connection.default_pool_size)
|
||||
expect(connection.config)
|
||||
.to include(pool: Gitlab::Database.default_pool_size)
|
||||
end
|
||||
|
||||
it 'does not cache its results' do
|
||||
|
|
@ -43,7 +28,7 @@ RSpec.describe Gitlab::Database::Connection do
|
|||
it 'returns the default pool size' do
|
||||
expect(connection).to receive(:config).and_return({ pool: nil })
|
||||
|
||||
expect(connection.pool_size).to eq(connection.default_pool_size)
|
||||
expect(connection.pool_size).to eq(Gitlab::Database.default_pool_size)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -129,7 +114,7 @@ RSpec.describe Gitlab::Database::Connection do
|
|||
|
||||
describe '#db_config_with_default_pool_size' do
|
||||
it 'returns db_config with our default pool size' do
|
||||
allow(connection).to receive(:default_pool_size).and_return(9)
|
||||
allow(Gitlab::Database).to receive(:default_pool_size).and_return(9)
|
||||
|
||||
expect(connection.db_config_with_default_pool_size.pool).to eq(9)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::LoadBalancing::Configuration do
|
||||
describe '.for_model' do
|
||||
let(:model) do
|
||||
config = ActiveRecord::DatabaseConfigurations::HashConfig
|
||||
.new('main', 'test', configuration_hash)
|
||||
|
||||
double(:model, connection_db_config: config)
|
||||
end
|
||||
|
||||
context 'when load balancing is not configured' do
|
||||
let(:configuration_hash) { {} }
|
||||
|
||||
it 'uses the default settings' do
|
||||
config = described_class.for_model(model)
|
||||
|
||||
expect(config.hosts).to eq([])
|
||||
expect(config.max_replication_difference).to eq(8.megabytes)
|
||||
expect(config.max_replication_lag_time).to eq(60.0)
|
||||
expect(config.replica_check_interval).to eq(60.0)
|
||||
expect(config.service_discovery).to eq(
|
||||
nameserver: 'localhost',
|
||||
port: 8600,
|
||||
record: nil,
|
||||
record_type: 'A',
|
||||
interval: 60,
|
||||
disconnect_timeout: 120,
|
||||
use_tcp: false
|
||||
)
|
||||
expect(config.pool_size).to eq(Gitlab::Database.default_pool_size)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when load balancing is configured' do
|
||||
let(:configuration_hash) do
|
||||
{
|
||||
pool: 4,
|
||||
load_balancing: {
|
||||
max_replication_difference: 1,
|
||||
max_replication_lag_time: 2,
|
||||
replica_check_interval: 3,
|
||||
hosts: %w[foo bar],
|
||||
discover: {
|
||||
'record' => 'foo.example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'uses the custom configuration settings' do
|
||||
config = described_class.for_model(model)
|
||||
|
||||
expect(config.hosts).to eq(%w[foo bar])
|
||||
expect(config.max_replication_difference).to eq(1)
|
||||
expect(config.max_replication_lag_time).to eq(2.0)
|
||||
expect(config.replica_check_interval).to eq(3.0)
|
||||
expect(config.service_discovery).to eq(
|
||||
nameserver: 'localhost',
|
||||
port: 8600,
|
||||
record: 'foo.example.com',
|
||||
record_type: 'A',
|
||||
interval: 60,
|
||||
disconnect_timeout: 120,
|
||||
use_tcp: false
|
||||
)
|
||||
expect(config.pool_size).to eq(4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#load_balancing_enabled?' do
|
||||
it 'returns true when hosts are configured' do
|
||||
config = described_class.new(ActiveRecord::Base, %w[foo bar])
|
||||
|
||||
expect(config.load_balancing_enabled?).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns true when a service discovery record is configured' do
|
||||
config = described_class.new(ActiveRecord::Base)
|
||||
config.service_discovery[:record] = 'foo'
|
||||
|
||||
expect(config.load_balancing_enabled?).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false when no hosts are configured and service discovery is disabled' do
|
||||
config = described_class.new(ActiveRecord::Base)
|
||||
|
||||
expect(config.load_balancing_enabled?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#service_discovery_enabled?' do
|
||||
it 'returns true when a record is configured' do
|
||||
config = described_class.new(ActiveRecord::Base)
|
||||
config.service_discovery[:record] = 'foo'
|
||||
|
||||
expect(config.service_discovery_enabled?).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false when no record is configured' do
|
||||
config = described_class.new(ActiveRecord::Base)
|
||||
|
||||
expect(config.service_discovery_enabled?).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,7 +4,10 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::Database::LoadBalancing::ConnectionProxy do
|
||||
let(:proxy) do
|
||||
described_class.new(Gitlab::Database::LoadBalancing::LoadBalancer.new([]))
|
||||
config = Gitlab::Database::LoadBalancing::Configuration
|
||||
.new(ActiveRecord::Base)
|
||||
|
||||
described_class.new(Gitlab::Database::LoadBalancing::LoadBalancer.new(config))
|
||||
end
|
||||
|
||||
describe '#select' do
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::Database::LoadBalancing::HostList do
|
||||
let(:db_host) { ActiveRecord::Base.connection_pool.db_config.host }
|
||||
let(:load_balancer) { double(:load_balancer) }
|
||||
let(:load_balancer) do
|
||||
Gitlab::Database::LoadBalancing::LoadBalancer.new(
|
||||
Gitlab::Database::LoadBalancing::Configuration.new(ActiveRecord::Base)
|
||||
)
|
||||
end
|
||||
|
||||
let(:host_count) { 2 }
|
||||
let(:hosts) { Array.new(host_count) { Gitlab::Database::LoadBalancing::Host.new(db_host, load_balancer, port: 5432) } }
|
||||
let(:host_list) { described_class.new(hosts) }
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::LoadBalancing::Host do
|
||||
let(:load_balancer) { Gitlab::Database::LoadBalancing::LoadBalancer.new }
|
||||
let(:load_balancer) do
|
||||
Gitlab::Database::LoadBalancing::LoadBalancer
|
||||
.new(Gitlab::Database::LoadBalancing::Configuration.new(ActiveRecord::Base))
|
||||
end
|
||||
|
||||
let(:host) do
|
||||
Gitlab::Database::LoadBalancing::Host.new('localhost', load_balancer)
|
||||
|
|
@ -274,7 +277,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Host do
|
|||
end
|
||||
|
||||
it 'returns false when the data is not recent enough' do
|
||||
diff = Gitlab::Database::LoadBalancing.max_replication_difference * 2
|
||||
diff = load_balancer.configuration.max_replication_difference * 2
|
||||
|
||||
expect(host)
|
||||
.to receive(:query_and_release)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
|
||||
let(:conflict_error) { Class.new(RuntimeError) }
|
||||
let(:db_host) { ActiveRecord::Base.connection_pool.db_config.host }
|
||||
let(:lb) { described_class.new([db_host, db_host]) }
|
||||
let(:config) do
|
||||
Gitlab::Database::LoadBalancing::Configuration
|
||||
.new(ActiveRecord::Base, [db_host, db_host])
|
||||
end
|
||||
|
||||
let(:lb) { described_class.new(config) }
|
||||
let(:request_cache) { lb.send(:request_cache) }
|
||||
|
||||
before do
|
||||
|
|
@ -43,7 +48,9 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
|
|||
|
||||
describe '#initialize' do
|
||||
it 'ignores the hosts when the primary_only option is enabled' do
|
||||
lb = described_class.new([db_host], primary_only: true)
|
||||
config = Gitlab::Database::LoadBalancing::Configuration
|
||||
.new(ActiveRecord::Base, [db_host])
|
||||
lb = described_class.new(config, primary_only: true)
|
||||
hosts = lb.host_list.hosts
|
||||
|
||||
expect(hosts.length).to eq(1)
|
||||
|
|
@ -134,7 +141,9 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
|
|||
end
|
||||
|
||||
it 'uses the primary when the primary_only option is enabled' do
|
||||
lb = described_class.new(primary_only: true)
|
||||
config = Gitlab::Database::LoadBalancing::Configuration
|
||||
.new(ActiveRecord::Base)
|
||||
lb = described_class.new(config, primary_only: true)
|
||||
|
||||
# When no hosts are configured, we don't want to produce any warnings, as
|
||||
# they aren't useful/too noisy.
|
||||
|
|
@ -174,8 +183,11 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
|
|||
end
|
||||
|
||||
it 'does not create conflicts with other load balancers when caching hosts' do
|
||||
lb1 = described_class.new([db_host, db_host], ActiveRecord::Base)
|
||||
lb2 = described_class.new([db_host, db_host], Ci::CiDatabaseRecord)
|
||||
ci_config = Gitlab::Database::LoadBalancing::Configuration
|
||||
.new(Ci::CiDatabaseRecord, [db_host, db_host])
|
||||
|
||||
lb1 = described_class.new(config)
|
||||
lb2 = described_class.new(ci_config)
|
||||
|
||||
host1 = lb1.host
|
||||
host2 = lb2.host
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::LoadBalancing::PrimaryHost do
|
||||
let(:load_balancer) { Gitlab::Database::LoadBalancing::LoadBalancer.new }
|
||||
let(:load_balancer) do
|
||||
Gitlab::Database::LoadBalancing::LoadBalancer.new(
|
||||
Gitlab::Database::LoadBalancing::Configuration.new(ActiveRecord::Base)
|
||||
)
|
||||
end
|
||||
|
||||
let(:host) { Gitlab::Database::LoadBalancing::PrimaryHost.new(load_balancer) }
|
||||
|
||||
describe '#connection' do
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::LoadBalancing::ServiceDiscovery do
|
||||
let(:load_balancer) { Gitlab::Database::LoadBalancing::LoadBalancer.new([]) }
|
||||
let(:load_balancer) do
|
||||
Gitlab::Database::LoadBalancing::LoadBalancer.new(
|
||||
Gitlab::Database::LoadBalancing::Configuration.new(ActiveRecord::Base)
|
||||
)
|
||||
end
|
||||
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
load_balancer,
|
||||
|
|
@ -184,7 +189,10 @@ RSpec.describe Gitlab::Database::LoadBalancing::ServiceDiscovery do
|
|||
let(:address_bar) { described_class::Address.new('bar') }
|
||||
|
||||
let(:load_balancer) do
|
||||
Gitlab::Database::LoadBalancing::LoadBalancer.new([address_foo])
|
||||
Gitlab::Database::LoadBalancing::LoadBalancer.new(
|
||||
Gitlab::Database::LoadBalancing::Configuration
|
||||
.new(ActiveRecord::Base, [address_foo])
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
@ -307,7 +315,10 @@ RSpec.describe Gitlab::Database::LoadBalancing::ServiceDiscovery do
|
|||
|
||||
describe '#addresses_from_load_balancer' do
|
||||
let(:load_balancer) do
|
||||
Gitlab::Database::LoadBalancing::LoadBalancer.new(%w[b a])
|
||||
Gitlab::Database::LoadBalancing::LoadBalancer.new(
|
||||
Gitlab::Database::LoadBalancing::Configuration
|
||||
.new(ActiveRecord::Base, %w[b a])
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the ordered host names of the load balancer' do
|
||||
|
|
|
|||
|
|
@ -40,106 +40,25 @@ RSpec.describe Gitlab::Database::LoadBalancing do
|
|||
end
|
||||
|
||||
describe '.configuration' do
|
||||
it 'returns a Hash' do
|
||||
lb_config = { 'hosts' => %w(foo) }
|
||||
it 'returns the configuration for the load balancer' do
|
||||
raw = ActiveRecord::Base.connection_db_config.configuration_hash
|
||||
cfg = described_class.configuration
|
||||
|
||||
original_db_config = Gitlab::Database.main.config
|
||||
modified_db_config = original_db_config.merge(load_balancing: lb_config)
|
||||
expect(Gitlab::Database.main).to receive(:config).and_return(modified_db_config)
|
||||
|
||||
expect(described_class.configuration).to eq(lb_config)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.max_replication_difference' do
|
||||
context 'without an explicitly configured value' do
|
||||
it 'returns the default value' do
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return({})
|
||||
|
||||
expect(described_class.max_replication_difference).to eq(8.megabytes)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an explicitly configured value' do
|
||||
it 'returns the configured value' do
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return({ 'max_replication_difference' => 4 })
|
||||
|
||||
expect(described_class.max_replication_difference).to eq(4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.max_replication_lag_time' do
|
||||
context 'without an explicitly configured value' do
|
||||
it 'returns the default value' do
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return({})
|
||||
|
||||
expect(described_class.max_replication_lag_time).to eq(60)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an explicitly configured value' do
|
||||
it 'returns the configured value' do
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return({ 'max_replication_lag_time' => 4 })
|
||||
|
||||
expect(described_class.max_replication_lag_time).to eq(4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.replica_check_interval' do
|
||||
context 'without an explicitly configured value' do
|
||||
it 'returns the default value' do
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return({})
|
||||
|
||||
expect(described_class.replica_check_interval).to eq(60)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an explicitly configured value' do
|
||||
it 'returns the configured value' do
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return({ 'replica_check_interval' => 4 })
|
||||
|
||||
expect(described_class.replica_check_interval).to eq(4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.hosts' do
|
||||
it 'returns a list of hosts' do
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return({ 'hosts' => %w(foo bar baz) })
|
||||
|
||||
expect(described_class.hosts).to eq(%w(foo bar baz))
|
||||
end
|
||||
end
|
||||
|
||||
describe '.pool_size' do
|
||||
it 'returns a Fixnum' do
|
||||
expect(described_class.pool_size).to be_a_kind_of(Integer)
|
||||
# There isn't much to test here as the load balancing settings might not
|
||||
# (and likely aren't) set when running tests.
|
||||
expect(cfg.pool_size).to eq(raw[:pool])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.enable?' do
|
||||
before do
|
||||
allow(described_class).to receive(:hosts).and_return(%w(foo))
|
||||
allow(described_class.configuration)
|
||||
.to receive(:hosts)
|
||||
.and_return(%w(foo))
|
||||
end
|
||||
|
||||
it 'returns false when no hosts are specified' do
|
||||
allow(described_class).to receive(:hosts).and_return([])
|
||||
allow(described_class.configuration).to receive(:hosts).and_return([])
|
||||
|
||||
expect(described_class.enable?).to eq(false)
|
||||
end
|
||||
|
|
@ -163,10 +82,10 @@ RSpec.describe Gitlab::Database::LoadBalancing do
|
|||
end
|
||||
|
||||
it 'returns true when service discovery is enabled' do
|
||||
allow(described_class).to receive(:hosts).and_return([])
|
||||
allow(described_class.configuration).to receive(:hosts).and_return([])
|
||||
allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(false)
|
||||
|
||||
allow(described_class)
|
||||
allow(described_class.configuration)
|
||||
.to receive(:service_discovery_enabled?)
|
||||
.and_return(true)
|
||||
|
||||
|
|
@ -175,17 +94,17 @@ RSpec.describe Gitlab::Database::LoadBalancing do
|
|||
end
|
||||
|
||||
describe '.configured?' do
|
||||
it 'returns true when Sidekiq is being used' do
|
||||
allow(described_class).to receive(:hosts).and_return(%w(foo))
|
||||
allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
|
||||
it 'returns true when hosts are configured' do
|
||||
allow(described_class.configuration)
|
||||
.to receive(:hosts)
|
||||
.and_return(%w[foo])
|
||||
|
||||
expect(described_class.configured?).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns true when service discovery is enabled in Sidekiq' do
|
||||
allow(described_class).to receive(:hosts).and_return([])
|
||||
allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
|
||||
|
||||
allow(described_class)
|
||||
it 'returns true when service discovery is enabled' do
|
||||
allow(described_class.configuration).to receive(:hosts).and_return([])
|
||||
allow(described_class.configuration)
|
||||
.to receive(:service_discovery_enabled?)
|
||||
.and_return(true)
|
||||
|
||||
|
|
@ -193,9 +112,8 @@ RSpec.describe Gitlab::Database::LoadBalancing do
|
|||
end
|
||||
|
||||
it 'returns false when neither service discovery nor hosts are configured' do
|
||||
allow(described_class).to receive(:hosts).and_return([])
|
||||
|
||||
allow(described_class)
|
||||
allow(described_class.configuration).to receive(:hosts).and_return([])
|
||||
allow(described_class.configuration)
|
||||
.to receive(:service_discovery_enabled?)
|
||||
.and_return(false)
|
||||
|
||||
|
|
@ -219,9 +137,9 @@ RSpec.describe Gitlab::Database::LoadBalancing do
|
|||
it 'runs initial service discovery when configuring the connection proxy' do
|
||||
discover = instance_spy(Gitlab::Database::LoadBalancing::ServiceDiscovery)
|
||||
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return('discover' => { 'record' => 'foo' })
|
||||
allow(described_class.configuration)
|
||||
.to receive(:service_discovery)
|
||||
.and_return({ record: 'foo' })
|
||||
|
||||
expect(Gitlab::Database::LoadBalancing::ServiceDiscovery)
|
||||
.to receive(:new)
|
||||
|
|
@ -238,60 +156,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.active_record_models' do
|
||||
it 'returns an Array' do
|
||||
expect(described_class.active_record_models).to be_an_instance_of(Array)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.service_discovery_enabled?' do
|
||||
it 'returns true if service discovery is enabled' do
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return('discover' => { 'record' => 'foo' })
|
||||
|
||||
expect(described_class.service_discovery_enabled?).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false if service discovery is disabled' do
|
||||
expect(described_class.service_discovery_enabled?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.service_discovery_configuration' do
|
||||
context 'when no configuration is provided' do
|
||||
it 'returns a default configuration Hash' do
|
||||
expect(described_class.service_discovery_configuration).to eq(
|
||||
nameserver: 'localhost',
|
||||
port: 8600,
|
||||
record: nil,
|
||||
record_type: 'A',
|
||||
interval: 60,
|
||||
disconnect_timeout: 120,
|
||||
use_tcp: false
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when configuration is provided' do
|
||||
it 'returns a Hash including the custom configuration' do
|
||||
allow(described_class)
|
||||
.to receive(:configuration)
|
||||
.and_return('discover' => { 'record' => 'foo', 'record_type' => 'SRV' })
|
||||
|
||||
expect(described_class.service_discovery_configuration).to eq(
|
||||
nameserver: 'localhost',
|
||||
port: 8600,
|
||||
record: 'foo',
|
||||
record_type: 'SRV',
|
||||
interval: 60,
|
||||
disconnect_timeout: 120,
|
||||
use_tcp: false
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.start_service_discovery' do
|
||||
it 'does not start if service discovery is disabled' do
|
||||
expect(Gitlab::Database::LoadBalancing::ServiceDiscovery)
|
||||
|
|
@ -301,12 +165,14 @@ RSpec.describe Gitlab::Database::LoadBalancing do
|
|||
end
|
||||
|
||||
it 'starts service discovery if enabled' do
|
||||
allow(described_class)
|
||||
allow(described_class.configuration)
|
||||
.to receive(:service_discovery_enabled?)
|
||||
.and_return(true)
|
||||
|
||||
instance = double(:instance)
|
||||
lb = Gitlab::Database::LoadBalancing::LoadBalancer.new([])
|
||||
config = Gitlab::Database::LoadBalancing::Configuration
|
||||
.new(ActiveRecord::Base)
|
||||
lb = Gitlab::Database::LoadBalancing::LoadBalancer.new(config)
|
||||
proxy = Gitlab::Database::LoadBalancing::ConnectionProxy.new(lb)
|
||||
|
||||
allow(described_class)
|
||||
|
|
@ -345,7 +211,12 @@ RSpec.describe Gitlab::Database::LoadBalancing do
|
|||
|
||||
context 'when the load balancing is configured' do
|
||||
let(:db_host) { ActiveRecord::Base.connection_pool.db_config.host }
|
||||
let(:load_balancer) { described_class::LoadBalancer.new([db_host]) }
|
||||
let(:config) do
|
||||
Gitlab::Database::LoadBalancing::Configuration
|
||||
.new(ActiveRecord::Base, [db_host])
|
||||
end
|
||||
|
||||
let(:load_balancer) { described_class::LoadBalancer.new(config) }
|
||||
let(:proxy) { described_class::ConnectionProxy.new(load_balancer) }
|
||||
|
||||
context 'when a proxy connection is used' do
|
||||
|
|
|
|||