Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-09-09 15:09:24 +00:00
parent 7ce86c261b
commit a3596259bc
108 changed files with 1409 additions and 914 deletions

View File

@ -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"

View File

@ -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');
},
});
};

View File

@ -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({

View File

@ -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;
}
});
}

View File

@ -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: [],
};

View File

@ -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();

View File

@ -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;
}

View File

@ -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();
};

View File

@ -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) });
}
/**

View File

@ -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);

View File

@ -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);
});
}

View File

@ -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();

View File

@ -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))

View File

@ -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);
});

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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);
};

View File

@ -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>

View File

@ -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>

View File

@ -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.',
);

View File

@ -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);

View File

@ -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,

View File

@ -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 = {

View File

@ -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();

View File

@ -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);
};

View File

@ -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: {

View File

@ -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'),
},

View File

@ -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

View File

@ -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!

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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?

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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).

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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).

View File

@ -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

View 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).

View File

@ -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.
```

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -7,8 +7,6 @@ disqus_identifier: 'https://docs.gitlab.com/ee/workflow/gitlab_flow.html'
# Introduction to GitLab Flow **(FREE)**
![GitLab Flow](img/gitlab_flow.png)
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.
![Multiple long-running branches and merging in all directions](img/gitlab_flow_messy_flow.png)
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
![Merge request with the branch name "15-require-a-password-to-change-it" and assignee field shown](img/gitlab_flow_merge_request.png)
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
![Merge request showing the linked issues to close](img/gitlab_flow_close_issue_mr.png)
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
![List of sequential merge commits](img/gitlab_flow_merge_commits.png)
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
![Merge requests showing the test states: red, yellow, and green](img/gitlab_flow_ci_mr.png)
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
![Shell output showing git pull output](img/gitlab_flow_git_pull.png)
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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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

View File

@ -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 | | | | | ✓ |

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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
)

View File

@ -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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
});
});
});

View File

@ -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);
});
});
});
});

View File

@ -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';

View File

@ -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);
});
});
});

View File

@ -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);
});
});
});

View File

@ -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);
});
});
});

View File

@ -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',
},
],
},
};

View File

@ -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);
});
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More