Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-07 21:09:26 +00:00
parent 4b7575da97
commit 17c8111494
184 changed files with 1645 additions and 2458 deletions

View file

@ -23,6 +23,8 @@ const Api = {
projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
projectRunnersPath: '/api/:version/projects/:id/runners',
projectProtectedBranchesPath: '/api/:version/projects/:id/protected_branches',
projectSearchPath: '/api/:version/projects/:id/search',
projectMilestonesPath: '/api/:version/projects/:id/milestones',
mergeRequestsPath: '/api/:version/merge_requests',
groupLabelsPath: '/groups/:namespace_path/-/labels',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
@ -75,13 +77,11 @@ const Api = {
const url = Api.buildUrl(Api.groupsPath);
return axios
.get(url, {
params: Object.assign(
{
search: query,
per_page: DEFAULT_PER_PAGE,
},
options,
),
params: {
search: query,
per_page: DEFAULT_PER_PAGE,
...options,
},
})
.then(({ data }) => {
callback(data);
@ -248,6 +248,23 @@ const Api = {
.then(({ data }) => data);
},
projectSearch(id, options = {}) {
const url = Api.buildUrl(Api.projectSearchPath).replace(':id', encodeURIComponent(id));
return axios.get(url, {
params: {
search: options.search,
scope: options.scope,
},
});
},
projectMilestones(id) {
const url = Api.buildUrl(Api.projectMilestonesPath).replace(':id', encodeURIComponent(id));
return axios.get(url);
},
mergeRequests(params = {}) {
const url = Api.buildUrl(Api.mergeRequestsPath);
@ -282,7 +299,7 @@ const Api = {
};
return axios
.get(url, {
params: Object.assign({}, defaults, options),
params: { ...defaults, ...options },
})
.then(({ data }) => callback(data))
.catch(() => flash(__('Something went wrong while fetching projects')));
@ -365,13 +382,11 @@ const Api = {
users(query, options) {
const url = Api.buildUrl(this.usersPath);
return axios.get(url, {
params: Object.assign(
{
search: query,
per_page: DEFAULT_PER_PAGE,
},
options,
),
params: {
search: query,
per_page: DEFAULT_PER_PAGE,
...options,
},
});
},
@ -402,7 +417,7 @@ const Api = {
};
return axios
.get(url, {
params: Object.assign({}, defaults, options),
params: { ...defaults, ...options },
})
.then(({ data }) => callback(data))
.catch(() => flash(__('Something went wrong while fetching projects')));

View file

@ -22,7 +22,7 @@ function eventHasModifierKeys(event) {
export default class ShortcutsBlob extends Shortcuts {
constructor(opts) {
const options = Object.assign({}, defaults, opts);
const options = { ...defaults, ...opts };
super(options.skipResetBindings);
this.options = options;

View file

@ -17,7 +17,7 @@ const defaults = {
class BlobForkSuggestion {
constructor(options) {
this.elementMap = Object.assign({}, defaults, options);
this.elementMap = { ...defaults, ...options };
this.onOpenButtonClick = this.onOpenButtonClick.bind(this);
this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
}

View file

@ -19,14 +19,15 @@ export function getBoardSortableDefaultOptions(obj) {
const touchEnabled =
'ontouchstart' in window || (window.DocumentTouch && document instanceof DocumentTouch);
const defaultSortOptions = Object.assign({}, sortableConfig, {
const defaultSortOptions = {
...sortableConfig,
filter: '.no-drag',
delay: touchEnabled ? 100 : 0,
scrollSensitivity: touchEnabled ? 60 : 100,
scrollSpeed: 20,
onStart: sortableStart,
onEnd: sortableEnd,
});
};
Object.keys(obj).forEach(key => {
defaultSortOptions[key] = obj[key];

View file

@ -2,7 +2,7 @@ import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = Object.assign({}, ISetter);
const InputSetter = { ...ISetter };
class CloseReopenReportToggle {
constructor(opts = {}) {

View file

@ -325,7 +325,7 @@ export default class Clusters {
handleClusterStatusSuccess(data) {
const prevStatus = this.store.state.status;
const prevApplicationMap = Object.assign({}, this.store.state.applications);
const prevApplicationMap = { ...this.store.state.applications };
this.store.updateStateFromServer(data.data);

View file

@ -2,7 +2,7 @@ import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = Object.assign({}, ISetter);
const InputSetter = { ...ISetter };
class CommentTypeToggle {
constructor(opts = {}) {

View file

@ -13,7 +13,7 @@ import {
import confidentialMergeRequestState from './confidential_merge_request/state';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = Object.assign({}, ISetter);
const InputSetter = { ...ISetter };
const CREATE_MERGE_REQUEST = 'create-mr';
const CREATE_BRANCH = 'create-branch';

View file

@ -84,7 +84,7 @@ export default {
events.forEach(item => {
if (!item) return;
const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
const eventItem = { ...DEFAULT_EVENT_OBJECTS[stage.slug], ...item };
eventItem.totalTime = eventItem.total_time;

View file

@ -28,9 +28,7 @@ export default {
return this.label === null;
},
pinStyle() {
return this.repositioning
? Object.assign({}, this.position, { cursor: 'move' })
: this.position;
return this.repositioning ? { ...this.position, cursor: 'move' } : this.position;
},
pinLabel() {
return this.isNewNote

View file

@ -233,7 +233,7 @@ export function trimFirstCharOfLineContent(line = {}) {
// eslint-disable-next-line no-param-reassign
delete line.text;
const parsedLine = Object.assign({}, line);
const parsedLine = { ...line };
if (line.rich_text) {
const firstChar = parsedLine.rich_text.charAt(0);

View file

@ -58,13 +58,14 @@ export default class EnvironmentsStore {
let filtered = {};
if (env.size > 1) {
filtered = Object.assign({}, env, {
filtered = {
...env,
isFolder: true,
isLoadingFolderContent: oldEnvironmentState.isLoading || false,
folderName: env.name,
isOpen: oldEnvironmentState.isOpen || false,
children: oldEnvironmentState.children || [],
});
};
}
if (env.latest) {
@ -166,7 +167,7 @@ export default class EnvironmentsStore {
let updated = env;
if (env.latest) {
updated = Object.assign({}, env, env.latest);
updated = { ...env, ...env.latest };
delete updated.latest;
} else {
updated = env;
@ -192,7 +193,7 @@ export default class EnvironmentsStore {
const { environments } = this.state;
const updatedEnvironments = environments.map(env => {
const updateEnv = Object.assign({}, env);
const updateEnv = { ...env };
if (env.id === environment.id) {
updateEnv[prop] = newValue;
}

View file

@ -120,7 +120,7 @@ export default class FilteredSearchDropdownManager {
filter: key,
};
const extraArguments = mappingKey.extraArguments || {};
const glArguments = Object.assign({}, defaultArguments, extraArguments);
const glArguments = { ...defaultArguments, ...extraArguments };
// Passing glArguments to `new glClass(<arguments>)`
mappingKey.reference = new (Function.prototype.bind.apply(glClass, [null, glArguments]))();

View file

@ -2,14 +2,12 @@ import { uniq } from 'lodash';
class RecentSearchesStore {
constructor(initialState = {}, allowedKeys) {
this.state = Object.assign(
{
isLocalStorageAvailable: true,
recentSearches: [],
allowedKeys,
},
initialState,
);
this.state = {
isLocalStorageAvailable: true,
recentSearches: [],
allowedKeys,
...initialState,
};
}
addRecentSearch(newSearch) {

View file

@ -595,13 +595,14 @@ class GitLabDropdown {
return renderItem({
instance: this,
options: Object.assign({}, this.options, {
options: {
...this.options,
icon: this.icon,
highlight: this.highlight,
highlightText: text => this.highlightTextMatches(text, this.filterInput.val()),
highlightTemplate: this.highlightTemplate.bind(this),
parent,
}),
},
data,
group,
index,

View file

@ -8,7 +8,7 @@ export default class GLForm {
constructor(form, enableGFM = {}) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = Object.assign({}, defaultAutocompleteConfig, enableGFM);
this.enableGFM = { ...defaultAutocompleteConfig, ...enableGFM };
// Disable autocomplete for keywords which do not have dataSources available
const dataSources = (gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources) || {};
Object.keys(this.enableGFM).forEach(item => {

View file

@ -2,7 +2,7 @@ import { visitUrl } from '../lib/utils/url_utility';
import DropLab from '../droplab/drop_lab';
import ISetter from '../droplab/plugins/input_setter';
const InputSetter = Object.assign({}, ISetter);
const InputSetter = { ...ISetter };
const NEW_PROJECT = 'new-project';
const NEW_SUBGROUP = 'new-subgroup';

View file

@ -25,13 +25,13 @@ export default {
<div class="ide-nav-form p-0">
<tabs v-if="showMergeRequests" stop-propagation>
<tab active>
<template slot="title">
<template #title>
{{ __('Branches') }}
</template>
<branches-search-list />
</tab>
<tab>
<template slot="title">
<template #title>
{{ __('Merge Requests') }}
</template>
<merge-request-search-list />

View file

@ -14,13 +14,12 @@ export const computeDiff = (originalContent, newContent) => {
endLineNumber: lineNumber + change.count - 1,
});
} else if ('added' in change || 'removed' in change) {
acc.push(
Object.assign({}, change, {
lineNumber,
modified: undefined,
endLineNumber: lineNumber + change.count - 1,
}),
);
acc.push({
...change,
lineNumber,
modified: undefined,
endLineNumber: lineNumber + change.count - 1,
});
}
if (!change.removed) {

View file

@ -16,9 +16,7 @@ export default {
});
Object.assign(state, {
projects: Object.assign({}, state.projects, {
[projectPath]: project,
}),
projects: { ...state.projects, [projectPath]: project },
});
},
[types.TOGGLE_EMPTY_STATE](state, { projectPath, value }) {

View file

@ -14,12 +14,13 @@ export default {
},
[types.CREATE_TREE](state, { treePath }) {
Object.assign(state, {
trees: Object.assign({}, state.trees, {
trees: {
...state.trees,
[treePath]: {
tree: [],
loading: true,
},
}),
},
});
},
[types.SET_DIRECTORY_DATA](state, { data, treePath }) {

View file

@ -32,9 +32,7 @@ export function removeCommentIndicator(imageFrameEl) {
commentIndicatorEl.remove();
}
return Object.assign({}, meta, {
removed: willRemove,
});
return { ...meta, removed: willRemove };
}
export function showCommentIndicator(imageFrameEl, coordinate) {

View file

@ -4,12 +4,7 @@ export function setPositionDataAttribute(el, options) {
const { x, y, width, height } = options;
const { position } = el.dataset;
const positionObject = Object.assign({}, JSON.parse(position), {
x,
y,
width,
height,
});
const positionObject = { ...JSON.parse(position), x, y, width, height };
el.setAttribute('data-position', JSON.stringify(positionObject));
}

View file

@ -75,9 +75,7 @@ export default class ImageDiff {
if (this.renderCommentBadge) {
imageDiffHelper.addImageCommentBadge(this.imageFrameEl, options);
} else {
const numberBadgeOptions = Object.assign({}, options, {
badgeText: index + 1,
});
const numberBadgeOptions = { ...options, badgeText: index + 1 };
imageDiffHelper.addImageBadge(this.imageFrameEl, numberBadgeOptions);
}

View file

@ -220,7 +220,7 @@ export const fetchJobsForStage = ({ dispatch }, stage = {}) => {
},
})
.then(({ data }) => {
const retriedJobs = data.retried.map(job => Object.assign({}, job, { retried: true }));
const retriedJobs = data.retried.map(job => ({ ...job, retried: true }));
const jobs = data.latest_statuses.concat(retriedJobs);
dispatch('receiveJobsForStageSuccess', jobs);
@ -236,7 +236,7 @@ export const receiveJobsForStageError = ({ commit }) => {
export const triggerManualJob = ({ state }, variables) => {
const parsedVariables = variables.map(variable => {
const copyVar = Object.assign({}, variable);
const copyVar = { ...variable };
delete copyVar.id;
return copyVar;
});

View file

@ -0,0 +1,228 @@
<script>
import {
GlNewDropdown,
GlNewDropdownDivider,
GlNewDropdownHeader,
GlNewDropdownItem,
GlLoadingIcon,
GlSearchBoxByType,
GlIcon,
} from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import Api from '~/api';
import createFlash from '~/flash';
import { intersection, debounce } from 'lodash';
export default {
components: {
GlNewDropdown,
GlNewDropdownDivider,
GlNewDropdownHeader,
GlNewDropdownItem,
GlLoadingIcon,
GlSearchBoxByType,
GlIcon,
},
model: {
prop: 'preselectedMilestones',
event: 'change',
},
props: {
projectId: {
type: String,
required: true,
},
preselectedMilestones: {
type: Array,
default: () => [],
required: false,
},
extraLinks: {
type: Array,
default: () => [],
required: false,
},
},
data() {
return {
searchQuery: '',
projectMilestones: [],
searchResults: [],
selectedMilestones: [],
requestCount: 0,
};
},
translations: {
milestone: __('Milestone'),
selectMilestone: __('Select milestone'),
noMilestone: __('No milestone'),
noResultsLabel: __('No matching results'),
searchMilestones: __('Search Milestones'),
},
computed: {
selectedMilestonesLabel() {
if (this.milestoneTitles.length === 1) {
return this.milestoneTitles[0];
}
if (this.milestoneTitles.length > 1) {
const firstMilestoneName = this.milestoneTitles[0];
const numberOfOtherMilestones = this.milestoneTitles.length - 1;
return sprintf(__('%{firstMilestoneName} + %{numberOfOtherMilestones} more'), {
firstMilestoneName,
numberOfOtherMilestones,
});
}
return this.$options.translations.noMilestone;
},
milestoneTitles() {
return this.preselectedMilestones.map(milestone => milestone.title);
},
dropdownItems() {
return this.searchResults.length ? this.searchResults : this.projectMilestones;
},
noResults() {
return this.searchQuery.length > 2 && this.searchResults.length === 0;
},
isLoading() {
return this.requestCount !== 0;
},
},
mounted() {
this.fetchMilestones();
},
methods: {
fetchMilestones() {
this.requestCount += 1;
Api.projectMilestones(this.projectId)
.then(({ data }) => {
this.projectMilestones = this.getTitles(data);
this.selectedMilestones = intersection(this.projectMilestones, this.milestoneTitles);
})
.catch(() => {
createFlash(__('An error occurred while loading milestones'));
})
.finally(() => {
this.requestCount -= 1;
});
},
searchMilestones: debounce(function searchMilestones() {
this.requestCount += 1;
const options = {
search: this.searchQuery,
scope: 'milestones',
};
if (this.searchQuery.length < 3) {
this.requestCount -= 1;
this.searchResults = [];
return;
}
Api.projectSearch(this.projectId, options)
.then(({ data }) => {
const searchResults = this.getTitles(data);
this.searchResults = searchResults.length ? searchResults : [];
})
.catch(() => {
createFlash(__('An error occurred while searching for milestones'));
})
.finally(() => {
this.requestCount -= 1;
});
}, 100),
toggleMilestoneSelection(clickedMilestone) {
if (!clickedMilestone) return [];
let milestones = [...this.preselectedMilestones];
const hasMilestone = this.milestoneTitles.includes(clickedMilestone);
if (hasMilestone) {
milestones = milestones.filter(({ title }) => title !== clickedMilestone);
} else {
milestones.push({ title: clickedMilestone });
}
return milestones;
},
onMilestoneClicked(clickedMilestone) {
const milestones = this.toggleMilestoneSelection(clickedMilestone);
this.$emit('change', milestones);
this.selectedMilestones = intersection(
this.projectMilestones,
milestones.map(milestone => milestone.title),
);
},
isSelectedMilestone(milestoneTitle) {
return this.selectedMilestones.includes(milestoneTitle);
},
getTitles(milestones) {
return milestones.filter(({ state }) => state === 'active').map(({ title }) => title);
},
},
};
</script>
<template>
<gl-new-dropdown>
<template slot="button-content">
<span ref="buttonText" class="flex-grow-1 ml-1 text-muted">{{
selectedMilestonesLabel
}}</span>
<gl-icon name="chevron-down" />
</template>
<gl-new-dropdown-header>
<span class="text-center d-block">{{ $options.translations.selectMilestone }}</span>
</gl-new-dropdown-header>
<gl-new-dropdown-divider />
<gl-search-box-by-type
v-model.trim="searchQuery"
class="m-2"
:placeholder="this.$options.translations.searchMilestones"
@input="searchMilestones"
/>
<gl-new-dropdown-item @click="onMilestoneClicked(null)">
<span :class="{ 'pl-4': true, 'selected-item': selectedMilestones.length === 0 }">
{{ $options.translations.noMilestone }}
</span>
</gl-new-dropdown-item>
<gl-new-dropdown-divider />
<template v-if="isLoading">
<gl-loading-icon />
<gl-new-dropdown-divider />
</template>
<template v-else-if="noResults">
<div class="dropdown-item-space">
<span ref="noResults" class="pl-4">{{ $options.translations.noResultsLabel }}</span>
</div>
<gl-new-dropdown-divider />
</template>
<template v-else-if="dropdownItems.length">
<gl-new-dropdown-item
v-for="item in dropdownItems"
:key="item"
role="milestone option"
@click="onMilestoneClicked(item)"
>
<span :class="{ 'pl-4': true, 'selected-item': isSelectedMilestone(item) }">
{{ item }}
</span>
</gl-new-dropdown-item>
<gl-new-dropdown-divider />
</template>
<gl-new-dropdown-item v-for="(item, idx) in extraLinks" :key="idx" :href="item.url">
<span class="pl-4">{{ item.text }}</span>
</gl-new-dropdown-item>
</gl-new-dropdown>
</template>

View file

@ -15,6 +15,19 @@ export default () => {
notesApp,
},
store,
data() {
const notesDataset = document.getElementById('js-vue-mr-discussions').dataset;
const noteableData = JSON.parse(notesDataset.noteableData);
noteableData.noteableType = notesDataset.noteableType;
noteableData.targetType = notesDataset.targetType;
return {
noteableData,
currentUserData: JSON.parse(notesDataset.currentUserData),
notesData: JSON.parse(notesDataset.notesData),
helpPagePath: notesDataset.helpPagePath,
};
},
computed: {
...mapGetters(['discussionTabCounter']),
...mapState({
@ -54,19 +67,6 @@ export default () => {
updateDiscussionTabCounter() {
this.notesCountBadge.text(this.discussionTabCounter);
},
dataset() {
const data = this.$el.dataset;
const noteableData = JSON.parse(data.noteableData);
noteableData.noteableType = data.noteableType;
noteableData.targetType = data.targetType;
return {
noteableData,
notesData: JSON.parse(data.notesData),
userData: JSON.parse(data.currentUserData),
helpPagePath: data.helpPagePath,
};
},
},
render(createElement) {
// NOTE: Even though `discussionKeyboardNavigator` is added to the `notes-app`,
@ -76,8 +76,11 @@ export default () => {
return createElement(discussionKeyboardNavigator, [
createElement('notes-app', {
props: {
...this.dataset(),
noteableData: this.noteableData,
notesData: this.notesData,
userData: this.currentUserData,
shouldShow: this.isShowTabActive,
helpPagePath: this.helpPagePath,
},
}),
]);

View file

@ -230,10 +230,11 @@ export default {
const defaultConfig = { path: this.getNotesDataByProp('discussionsPath') };
if (doesHashExistInUrl(constants.NOTE_UNDERSCORE)) {
return Object.assign({}, defaultConfig, {
return {
...defaultConfig,
filter: constants.DISCUSSION_FILTERS_DEFAULT_VALUE,
persistFilter: false,
});
};
}
return defaultConfig;
},

View file

@ -14,36 +14,38 @@ document.addEventListener('DOMContentLoaded', () => {
notesApp,
},
store,
methods: {
setData() {
const notesDataset = this.$el.dataset;
const parsedUserData = JSON.parse(notesDataset.currentUserData);
const noteableData = JSON.parse(notesDataset.noteableData);
let currentUserData = {};
data() {
const notesDataset = document.getElementById('js-vue-notes').dataset;
const parsedUserData = JSON.parse(notesDataset.currentUserData);
const noteableData = JSON.parse(notesDataset.noteableData);
let currentUserData = {};
noteableData.noteableType = notesDataset.noteableType;
noteableData.targetType = notesDataset.targetType;
noteableData.noteableType = notesDataset.noteableType;
noteableData.targetType = notesDataset.targetType;
if (parsedUserData) {
currentUserData = {
id: parsedUserData.id,
name: parsedUserData.name,
username: parsedUserData.username,
avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
path: parsedUserData.path,
};
}
return {
noteableData,
userData: currentUserData,
notesData: JSON.parse(notesDataset.notesData),
if (parsedUserData) {
currentUserData = {
id: parsedUserData.id,
name: parsedUserData.name,
username: parsedUserData.username,
avatar_url: parsedUserData.avatar_path || parsedUserData.avatar_url,
path: parsedUserData.path,
};
},
}
return {
noteableData,
currentUserData,
notesData: JSON.parse(notesDataset.notesData),
};
},
render(createElement) {
return createElement('notes-app', {
props: { ...this.setData() },
props: {
noteableData: this.noteableData,
notesData: this.notesData,
userData: this.currentUserData,
},
});
},
});

View file

@ -248,7 +248,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
const hasQuickActions = utils.hasQuickActions(placeholderText);
const replyId = noteData.data.in_reply_to_discussion_id;
let methodToDispatch;
const postData = Object.assign({}, noteData);
const postData = { ...noteData };
if (postData.isDraft === true) {
methodToDispatch = replyId
? 'batchComments/addDraftToDiscussion'

View file

@ -99,9 +99,10 @@ export default {
// 3. If GitLab user does not have avatar, they might have a Gravatar
} else if (this.pipeline.commit.author_gravatar_url) {
commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, {
commitAuthorInformation = {
...this.pipeline.commit.author,
avatar_url: this.pipeline.commit.author_gravatar_url,
});
};
}
// 4. If committer is not a GitLab User, they can have a Gravatar
} else {

View file

@ -29,7 +29,14 @@ export default {
successPercentage() {
// Returns a full number when the decimals equal .00.
// Otherwise returns a float to two decimal points
return Number(((this.report.success_count / this.report.total_count) * 100 || 0).toFixed(2));
// Do not include skipped tests as part of the total when doing success calculations.
const totalCompletedCount = this.report.total_count - this.report.skipped_count;
if (totalCompletedCount > 0) {
return Number(((this.report.success_count / totalCompletedCount) * 100 || 0).toFixed(2));
}
return 0;
},
formattedDuration() {
return formatTime(secondsToMilliseconds(this.report.total_time));

View file

@ -15,7 +15,7 @@ export default class PipelineStore {
* @param {Object} pipeline
*/
storePipeline(pipeline = {}) {
const pipelineCopy = Object.assign({}, pipeline);
const pipelineCopy = { ...pipeline };
if (pipelineCopy.triggered_by) {
pipelineCopy.triggered_by = [pipelineCopy.triggered_by];

View file

@ -21,7 +21,7 @@ export default {
state.original = Object.freeze(settings);
},
[types.RESET_SETTINGS](state) {
state.settings = Object.assign({}, state.original);
state.settings = { ...state.original };
},
[types.TOGGLE_LOADING](state) {
state.isLoading = !state.isLoading;

View file

@ -41,5 +41,5 @@ export const NAME_REGEX_KEEP_LABEL = s__(
);
export const NAME_REGEX_KEEP_PLACEHOLDER = '';
export const NAME_REGEX_KEEP_DESCRIPTION = s__(
'ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported',
'ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported',
);

View file

@ -9,6 +9,7 @@ import { BACK_URL_PARAM } from '~/releases/constants';
import { getParameterByName } from '~/lib/utils/common_utils';
import AssetLinksForm from './asset_links_form.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MilestoneCombobox from '~/milestones/project_milestone_combobox.vue';
export default {
name: 'ReleaseEditApp',
@ -18,6 +19,7 @@ export default {
GlButton,
MarkdownField,
AssetLinksForm,
MilestoneCombobox,
},
directives: {
autofocusonshow,
@ -32,6 +34,10 @@ export default {
'markdownPreviewPath',
'releasesPagePath',
'updateReleaseApiDocsPath',
'release',
'newMilestonePath',
'manageMilestonesPath',
'projectId',
]),
...mapGetters('detail', ['isValid']),
showForm() {
@ -82,6 +88,14 @@ export default {
this.updateReleaseNotes(notes);
},
},
releaseMilestones: {
get() {
return this.$store.state.detail.release.milestones;
},
set(milestones) {
this.updateReleaseMilestones(milestones);
},
},
cancelPath() {
return getParameterByName(BACK_URL_PARAM) || this.releasesPagePath;
},
@ -91,6 +105,18 @@ export default {
isSaveChangesDisabled() {
return this.isUpdatingRelease || !this.isValid;
},
milestoneComboboxExtraLinks() {
return [
{
text: __('Create new'),
url: this.newMilestonePath,
},
{
text: __('Manage milestones'),
url: this.manageMilestonesPath,
},
];
},
},
created() {
this.fetchRelease();
@ -101,6 +127,7 @@ export default {
'updateRelease',
'updateReleaseTitle',
'updateReleaseNotes',
'updateReleaseMilestones',
]),
},
};
@ -137,6 +164,16 @@ export default {
class="form-control"
/>
</gl-form-group>
<gl-form-group class="w-50">
<label>{{ __('Milestones') }}</label>
<div class="d-flex flex-column col-md-6 col-sm-10 pl-0">
<milestone-combobox
v-model="releaseMilestones"
:project-id="projectId"
:extra-links="milestoneComboboxExtraLinks"
/>
</div>
</gl-form-group>
<gl-form-group>
<label for="release-notes">{{ __('Release notes') }}</label>
<div class="bordered-box pr-3 pl-3">
@ -158,8 +195,7 @@ export default {
:placeholder="__('Write your release notes or drag your files here…')"
@keydown.meta.enter="updateRelease()"
@keydown.ctrl.enter="updateRelease()"
>
</textarea>
></textarea>
</markdown-field>
</div>
</gl-form-group>
@ -174,12 +210,9 @@ export default {
type="submit"
:aria-label="__('Save changes')"
:disabled="isSaveChangesDisabled"
>{{ __('Save changes') }}</gl-button
>
{{ __('Save changes') }}
</gl-button>
<gl-button :href="cancelPath" class="js-cancel-button">
{{ __('Cancel') }}
</gl-button>
<gl-button :href="cancelPath" class="js-cancel-button">{{ __('Cancel') }}</gl-button>
</div>
</form>
</div>

View file

@ -1,6 +1,6 @@
<script>
import { mapState, mapActions } from 'vuex';
import { GlSkeletonLoading, GlEmptyState, GlLink } from '@gitlab/ui';
import { GlSkeletonLoading, GlEmptyState, GlLink, GlButton } from '@gitlab/ui';
import {
getParameterByName,
historyPushState,
@ -18,6 +18,7 @@ export default {
ReleaseBlock,
TablePagination,
GlLink,
GlButton,
},
props: {
projectId: {
@ -69,14 +70,16 @@ export default {
</script>
<template>
<div class="flex flex-column mt-2">
<gl-link
<gl-button
v-if="newReleasePath"
:href="newReleasePath"
:aria-describedby="shouldRenderEmptyState && 'releases-description'"
class="btn btn-success align-self-end mb-2 js-new-release-btn"
category="primary"
variant="success"
class="align-self-end mb-2 js-new-release-btn"
>
{{ __('New release') }}
</gl-link>
</gl-button>
<gl-skeleton-loading v-if="isLoading" class="js-loading" />

View file

@ -1,5 +1,5 @@
<script>
import { GlTooltipDirective, GlLink, GlBadge } from '@gitlab/ui';
import { GlTooltipDirective, GlLink, GlBadge, GlButton } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import { BACK_URL_PARAM } from '~/releases/constants';
import { setUrlParams } from '~/lib/utils/url_utility';
@ -10,6 +10,7 @@ export default {
GlLink,
GlBadge,
Icon,
GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -50,14 +51,16 @@ export default {
__('Upcoming Release')
}}</gl-badge>
</h2>
<gl-link
<gl-button
v-if="editLink"
v-gl-tooltip
class="btn btn-default append-right-10 js-edit-button ml-2"
category="primary"
variant="default"
class="append-right-10 js-edit-button ml-2 pb-2"
:title="__('Edit this release')"
:href="editLink"
>
<icon name="pencil" />
</gl-link>
</gl-button>
</div>
</template>

View file

@ -18,7 +18,12 @@ export const fetchRelease = ({ dispatch, state }) => {
return api
.release(state.projectId, state.tagName)
.then(({ data: release }) => {
.then(({ data }) => {
const release = {
...data,
milestones: data.milestones || [],
};
dispatch('receiveReleaseSuccess', convertObjectPropsToCamelCase(release, { deep: true }));
})
.catch(error => {
@ -28,6 +33,8 @@ export const fetchRelease = ({ dispatch, state }) => {
export const updateReleaseTitle = ({ commit }, title) => commit(types.UPDATE_RELEASE_TITLE, title);
export const updateReleaseNotes = ({ commit }, notes) => commit(types.UPDATE_RELEASE_NOTES, notes);
export const updateReleaseMilestones = ({ commit }, milestones) =>
commit(types.UPDATE_RELEASE_MILESTONES, milestones);
export const requestUpdateRelease = ({ commit }) => commit(types.REQUEST_UPDATE_RELEASE);
export const receiveUpdateReleaseSuccess = ({ commit, state, rootState }) => {
@ -45,12 +52,14 @@ export const updateRelease = ({ dispatch, state, getters }) => {
dispatch('requestUpdateRelease');
const { release } = state;
const milestones = release.milestones ? release.milestones.map(milestone => milestone.title) : [];
return (
api
.updateRelease(state.projectId, state.tagName, {
name: release.name,
description: release.description,
milestones,
})
/**

View file

@ -4,6 +4,7 @@ export const RECEIVE_RELEASE_ERROR = 'RECEIVE_RELEASE_ERROR';
export const UPDATE_RELEASE_TITLE = 'UPDATE_RELEASE_TITLE';
export const UPDATE_RELEASE_NOTES = 'UPDATE_RELEASE_NOTES';
export const UPDATE_RELEASE_MILESTONES = 'UPDATE_RELEASE_MILESTONES';
export const REQUEST_UPDATE_RELEASE = 'REQUEST_UPDATE_RELEASE';
export const RECEIVE_UPDATE_RELEASE_SUCCESS = 'RECEIVE_UPDATE_RELEASE_SUCCESS';

View file

@ -28,6 +28,10 @@ export default {
state.release.description = notes;
},
[types.UPDATE_RELEASE_MILESTONES](state, milestones) {
state.release.milestones = milestones;
},
[types.REQUEST_UPDATE_RELEASE](state) {
state.isUpdatingRelease = true;
},

View file

@ -6,6 +6,8 @@ export default ({
markdownPreviewPath,
updateReleaseApiDocsPath,
releaseAssetsDocsPath,
manageMilestonesPath,
newMilestonePath,
}) => ({
projectId,
tagName,
@ -14,6 +16,8 @@ export default ({
markdownPreviewPath,
updateReleaseApiDocsPath,
releaseAssetsDocsPath,
manageMilestonesPath,
newMilestonePath,
/** The Release object */
release: null,

View file

@ -13,14 +13,11 @@ Terminal.applyAddon(webLinks);
export default class GLTerminal {
constructor(element, options = {}) {
this.options = Object.assign(
{},
{
cursorBlink: true,
screenKeys: true,
},
options,
);
this.options = {
cursorBlink: true,
screenKeys: true,
...options,
};
this.container = element;
this.onDispose = [];

View file

@ -164,7 +164,11 @@ export default {
'js-dropdown-button',
'js-btn-cancel-create',
'js-sidebar-dropdown-toggle',
].some(className => target?.classList.contains(className));
].some(
className =>
target?.classList.contains(className) ||
target?.parentElement.classList.contains(className),
);
const hadExceptionParent = ['.js-btn-back', '.js-labels-list'].some(
className => $(target).parents(className).length,

View file

@ -0,0 +1,13 @@
.selected-item::before {
content: '\f00c';
color: $green-500;
position: absolute;
left: 16px;
top: 16px;
transform: translateY(-50%);
font: 14px FontAwesome;
}
.dropdown-item-space {
padding: 8px 12px;
}

View file

@ -3,6 +3,7 @@
class Projects::AlertManagementController < Projects::ApplicationController
before_action :ensure_list_feature_enabled, only: :index
before_action :ensure_detail_feature_enabled, only: :details
before_action :authorize_read_alert_management_alert!
before_action do
push_frontend_feature_flag(:alert_list_status_filtering_enabled)
end

View file

@ -31,7 +31,7 @@ module AlertManagement
end
def authorized?
Ability.allowed?(current_user, :read_alert_management_alerts, project)
Ability.allowed?(current_user, :read_alert_management_alert, project)
end
end
end

View file

@ -18,7 +18,7 @@ module Mutations
null: true,
description: "The alert after mutation"
authorize :update_alert_management_alerts
authorize :update_alert_management_alert
private

View file

@ -6,7 +6,7 @@ module Types
graphql_name 'AlertManagementAlert'
description "Describes an alert from the project's Alert Management"
authorize :read_alert_management_alerts
authorize :read_alert_management_alert
field :iid,
GraphQL::ID_TYPE,

View file

@ -448,7 +448,7 @@ module ProjectsHelper
clusters: :read_cluster,
serverless: :read_cluster,
error_tracking: :read_sentry_issue,
alert_management: :read_alert_management,
alert_management: :read_alert_management_alert,
labels: :read_label,
issues: :read_issue,
project_members: :read_project_member,

View file

@ -30,7 +30,9 @@ module ReleasesHelper
markdown_docs_path: help_page_path('user/markdown'),
releases_page_path: project_releases_path(@project, anchor: @release.tag),
update_release_api_docs_path: help_page_path('api/releases/index.md', anchor: 'update-a-release'),
release_assets_docs_path: help_page(anchor: 'release-assets')
release_assets_docs_path: help_page(anchor: 'release-assets'),
manage_milestones_path: project_milestones_path(@project),
new_milestone_path: new_project_milestone_url(@project)
}
end
end

View file

@ -236,11 +236,8 @@ class ProjectPolicy < BasePolicy
enable :read_merge_request
enable :read_sentry_issue
enable :update_sentry_issue
enable :read_alert_management
enable :read_prometheus
enable :read_metrics_dashboard_annotation
enable :read_alert_management_alerts
enable :update_alert_management_alerts
enable :metrics_dashboard
end
@ -306,6 +303,8 @@ class ProjectPolicy < BasePolicy
enable :create_metrics_dashboard_annotation
enable :delete_metrics_dashboard_annotation
enable :update_metrics_dashboard_annotation
enable :read_alert_management_alert
enable :update_alert_management_alert
enable :create_design
enable :destroy_design
end

View file

@ -6,6 +6,9 @@ class SearchService
SEARCH_TERM_LIMIT = 64
SEARCH_CHAR_LIMIT = 4096
DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE
MAX_PER_PAGE = 200
def initialize(current_user, params = {})
@current_user = current_user
@params = params.dup
@ -60,11 +63,19 @@ class SearchService
end
def search_objects
@search_objects ||= redact_unauthorized_results(search_results.objects(scope, params[:page]))
@search_objects ||= redact_unauthorized_results(search_results.objects(scope, page: params[:page], per_page: per_page))
end
private
def per_page
per_page_param = params[:per_page].to_i
return DEFAULT_PER_PAGE unless per_page_param.positive?
[MAX_PER_PAGE, per_page_param].min
end
def visible_result?(object)
return true unless object.respond_to?(:to_ability_name) && DeclarativePolicy.has_policy?(object)
@ -75,13 +86,13 @@ class SearchService
results = results_collection.to_a
permitted_results = results.select { |object| visible_result?(object) }
filtered_results = (results - permitted_results).each_with_object({}) do |object, memo|
redacted_results = (results - permitted_results).each_with_object({}) do |object, memo|
memo[object.id] = { ability: :"read_#{object.to_ability_name}", id: object.id, class_name: object.class.name }
end
log_redacted_search_results(filtered_results.values) if filtered_results.any?
log_redacted_search_results(redacted_results.values) if redacted_results.any?
return results_collection.id_not_in(filtered_results.keys) if results_collection.is_a?(ActiveRecord::Relation)
return results_collection.id_not_in(redacted_results.keys) if results_collection.is_a?(ActiveRecord::Relation)
Kaminari.paginate_array(
permitted_results,

View file

@ -0,0 +1,5 @@
---
title: Update the example regex in the image expiration policy UI
merge_request: 31104
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Honor per_page in Search API
merge_request: 29197
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Update style of buttons on the Releases page
merge_request: 31129
author: Özgür Adem Işıklı @iozguradem
type: changed

View file

@ -0,0 +1,5 @@
---
title: 'Allow to assign milestones to a release on the "Edit Release page"'
merge_request: 28583
author:
type: added

View file

@ -0,0 +1,4 @@
---
title: Changed test success calculation to exclude skipped tests
merge_request: 31154
type: changed

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Audit Events **(STARTER)**
GitLab offers a way to view the changes made within the GitLab server for owners and administrators on a [paid plan](https://about.gitlab.com/pricing/).

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Log system
GitLab has an advanced log system where everything is logged, so you

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# GitLab exporter
>- Available since [Omnibus GitLab 8.17](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/1132).

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# GitLab Prometheus metrics
>**Note:**

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Monitoring GitLab with Prometheus
> **Notes:**

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Node exporter
>**Note:**

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# PgBouncer exporter
>**Note:**

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# PostgreSQL Server Exporter
>**Note:**

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Redis exporter
>**Note:**

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Registry exporter
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/2884) in GitLab 11.9.

View file

@ -50,7 +50,8 @@ For different cloud vendors, attempt to select options that best match the provi
## Up to 1,000 users
From 1 to 1,000 users, a [single-node setup with frequent backups](#automated-backups-core-only) is adequate.
> - **Supported users (approximate):** 1,000
> - **High Availability:** False
| Users | Configuration([8](#footnotes)) | GCP type | AWS type([9](#footnotes)) |
|-------|--------------------------------|---------------|---------------------------|
@ -58,9 +59,20 @@ From 1 to 1,000 users, a [single-node setup with frequent backups](#automated-ba
| 500 | 4 vCPU, 15GB Memory | n1-standard-4 | m5.xlarge |
| 1000 | 8 vCPU, 30GB Memory | n1-standard-8 | m5.2xlarge |
This solution is appropriate for many teams that have a single server at their disposal. With automatic backup of the GitLab repositories, configuration, and the database, this can be an optimal solution if you don't have strict availability requirements.
For situations where you need to serve up to 1,000 users, a single-node
solution with [frequent backups](#automated-backups-core-only) is appropriate
for many organizations. With automatic backup of the GitLab repositories,
configuration, and the database, if you don't have strict availability
requirements, this is the ideal solution.
You can also optionally configure GitLab to use an [external PostgreSQL service](../external_database.md) or an [external object storage service](../high_availability/object_storage.md) for added performance and reliability at a relatively low complexity cost.
For this default reference architecture, use the standard
[installation instructions](../../install/README.md) to install GitLab.
NOTE: **Note:**
You can also optionally configure GitLab to use an
[external PostgreSQL service](../external_database.md) or an
[external object storage service](../high_availability/object_storage.md) for
added performance and reliability at a reduced complexity cost.
## Up to 2,000 users
@ -70,12 +82,32 @@ You can also optionally configure GitLab to use an [external PostgreSQL service]
| Service | Nodes | Configuration ([8](#footnotes)) | GCP type | AWS type ([9](#footnotes)) |
|--------------------------------------------------------------|-------|---------------------------------|---------------|----------------------------|
| GitLab Rails, Sidekiq, Consul ([1](#footnotes)) | 2 | 8 vCPU, 7.2GB Memory | n1-highcpu-8 | c5.2xlarge |
| PostgreSQL | 1 | 2 vCPU, 7.5GB Memory | n1-standard-2 | m5.large |
| Gitaly ([2](#footnotes)) ([5](#footnotes)) ([7](#footnotes)) | X | 4 vCPU, 15GB Memory | n1-standard-4 | m5.xlarge |
| Cloud Object Storage ([4](#footnotes)) | - | - | - | - |
| NFS Server ([5](#footnotes)) ([7](#footnotes)) | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| External load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
| Object Storage ([4](#footnotes)) | - | - | - | - |
| NFS Server ([5](#footnotes)) ([7](#footnotes)) | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| PostgreSQL | 1 | 2 vCPU, 7.5GB Memory | n1-standard-2 | m5.large |
| Redis ([3](#footnotes)) | 1 | 1 vCPU, 3.75GB Memory | n1-standard-1 | m5.large |
| Gitaly ([5](#footnotes)) ([7](#footnotes)) | X ([2](#footnotes)) | 4 vCPU, 15GB Memory | n1-standard-4 | m5.xlarge |
| GitLab Rails ([1](#footnotes)) | 2 | 8 vCPU, 7.2GB Memory | n1-highcpu-8 | c5.2xlarge |
| Monitoring node | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
To set up GitLab for up to 2000 users:
1. [Configure the external load balancing node](../high_availability/load_balancer.md)
that will handle the load balancing of the two GitLab application services nodes.
1. [Configure the Object Storage](../object_storage.md) ([4](#footnotes)) used for shared data objects.
1. (Optional) [Configure NFS](../high_availability/nfs.md) to have
shared disk storage service as an alternative to Gitaly and/or
[Object Storage](../object_storage.md) (although not recommended).
NFS is required for GitLab Pages, you can skip this step if you're not using that feature.
1. [Configure PostgreSQL](../high_availability/load_balancer.md), the database for GitLab.
1. [Configure Redis](../high_availability/redis.md).
1. [Configure Gitaly](../gitaly/index.md#running-gitaly-on-its-own-server),
which is used to provide access to the Git repositories.
1. [Configure the main GitLab Rails application](../high_availability/gitlab.md)
to run Puma/Unicorn, Workhorse, GitLab Shell, and to serve all
frontend requests (UI, API, Git over HTTP/SSH).
1. [Configure Prometheus](../high_availability/monitoring_node.md) to monitor your GitLab environment.
## Up to 3,000 users
@ -83,9 +115,7 @@ NOTE: **Note:** The 3,000-user reference architecture documented below is
designed to help your organization achieve a highly-available GitLab deployment.
If you do not have the expertise or need to maintain a highly-available
environment, you can have a simpler and less costly-to-operate environment by
deploying two or more GitLab Rails servers, external load balancing, an NFS
server, a PostgreSQL server and a Redis server. A reference architecture with
this alternative in mind is [being worked on](https://gitlab.com/gitlab-org/quality/performance/-/issues/223).
following the [2,000-user reference architecture](#up-to-2000-users).
> - **Supported users (approximate):** 3,000
> - **High Availability:** True
@ -100,7 +130,7 @@ this alternative in mind is [being worked on](https://gitlab.com/gitlab-org/qual
| Redis ([3](#footnotes)) | 3 | 2 vCPU, 7.5GB Memory | n1-standard-2 | m5.large |
| Consul + Sentinel ([3](#footnotes)) | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
| Sidekiq | 4 | 2 vCPU, 7.5GB Memory | n1-standard-2 | m5.large |
| Cloud Object Storage ([4](#footnotes)) | - | - | - | - |
| Object Storage ([4](#footnotes)) | - | - | - | - |
| NFS Server ([5](#footnotes)) ([7](#footnotes)) | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| Monitoring node | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
| External load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
@ -121,7 +151,7 @@ this alternative in mind is [being worked on](https://gitlab.com/gitlab-org/qual
| Redis ([3](#footnotes)) | 3 | 2 vCPU, 7.5GB Memory | n1-standard-2 | m5.large |
| Consul + Sentinel ([3](#footnotes)) | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
| Sidekiq | 4 | 2 vCPU, 7.5GB Memory | n1-standard-2 | m5.large |
| Cloud Object Storage ([4](#footnotes)) | - | - | - | - |
| Object Storage ([4](#footnotes)) | - | - | - | - |
| NFS Server ([5](#footnotes)) ([7](#footnotes)) | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| Monitoring node | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
| External load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
@ -145,7 +175,7 @@ this alternative in mind is [being worked on](https://gitlab.com/gitlab-org/qual
| Redis Sentinel ([3](#footnotes)) - Queues / Shared State | 3 | 1 vCPU, 1.7GB Memory | g1-small | t2.small |
| Consul | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
| Sidekiq | 4 | 4 vCPU, 15GB Memory | n1-standard-4 | m5.xlarge |
| Cloud Object Storage ([4](#footnotes)) | - | - | - | - |
| Object Storage ([4](#footnotes)) | - | - | - | - |
| NFS Server ([5](#footnotes)) ([7](#footnotes)) | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| Monitoring node | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| External load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
@ -169,7 +199,7 @@ this alternative in mind is [being worked on](https://gitlab.com/gitlab-org/qual
| Redis Sentinel ([3](#footnotes)) - Queues / Shared State | 3 | 1 vCPU, 1.7GB Memory | g1-small | t2.small |
| Consul | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
| Sidekiq | 4 | 4 vCPU, 15GB Memory | n1-standard-4 | m5.xlarge |
| Cloud Object Storage ([4](#footnotes)) | - | - | - | - |
| Object Storage ([4](#footnotes)) | - | - | - | - |
| NFS Server ([5](#footnotes)) ([7](#footnotes)) | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| Monitoring node | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| External load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
@ -194,7 +224,7 @@ this alternative in mind is [being worked on](https://gitlab.com/gitlab-org/qual
| Consul | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
| Sidekiq | 4 | 4 vCPU, 15GB Memory | n1-standard-4 | m5.xlarge |
| NFS Server ([5](#footnotes)) ([7](#footnotes)) | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| Cloud Object Storage ([4](#footnotes)) | - | - | - | - |
| Object Storage ([4](#footnotes)) | - | - | - | - |
| Monitoring node | 1 | 4 vCPU, 3.6GB Memory | n1-highcpu-4 | c5.xlarge |
| External load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large |
| Internal load balancing node ([6](#footnotes)) | 1 | 8 vCPU, 7.2GB Memory | n1-highcpu-8 | c5.2xlarge |
@ -286,7 +316,7 @@ column.
| Component | Description | Configuration instructions | Bundled with Omnibus GitLab |
|-----------|-------------|----------------------------|
| Load balancer(s) ([6](#footnotes)) | Handles load balancing, typically when you have multiple GitLab application services nodes | [Load balancer configuration](../high_availability/load_balancer.md) ([6](#footnotes)) | No |
| Object storage service ([4](#footnotes)) | Recommended store for shared data objects | [Cloud Object Storage configuration](../object_storage.md) | No |
| Object storage service ([4](#footnotes)) | Recommended store for shared data objects | [Object Storage configuration](../object_storage.md) | No |
| NFS ([5](#footnotes)) ([7](#footnotes)) | Shared disk storage service. Can be used as an alternative for Gitaly or Object Storage. Required for GitLab Pages | [NFS configuration](../high_availability/nfs.md) | No |
| [Consul](../../development/architecture.md#consul) ([3](#footnotes)) | Service discovery and health checks/failover | [Consul HA configuration](../high_availability/consul.md) **(PREMIUM ONLY)** | Yes |
| [PostgreSQL](../../development/architecture.md#postgresql) | Database | [PostgreSQL configuration](https://docs.gitlab.com/omnibus/settings/database.html) | Yes |
@ -316,14 +346,15 @@ column.
with a review of expected data size and spread based on the recommendations above.
1. Recommended Redis setup differs depending on the size of the architecture.
For smaller architectures (less than 5,000 users), we suggest one Redis cluster for all
For smaller architectures (less than 3,000 users) a single instance should suffice.
For medium sized installs (3,000 - 5,000) we suggest one Redis cluster for all
classes and that Redis Sentinel is hosted alongside Consul.
For larger architectures (10,000 users or more) we suggest running a separate
[Redis Cluster](../high_availability/redis.md#running-multiple-redis-clusters) for the Cache class
and another for the Queues and Shared State classes respectively. We also recommend
that you run the Redis Sentinel clusters separately for each Redis Cluster.
1. For data objects such as LFS, Uploads, Artifacts, etc. We recommend a [Cloud Object Storage service](../object_storage.md)
1. For data objects such as LFS, Uploads, Artifacts, etc. We recommend an [Object Storage service](../object_storage.md)
over NFS where possible, due to better performance and availability.
1. NFS can be used as an alternative for both repository data (replacing Gitaly) and

View file

@ -798,7 +798,9 @@ GET /projects/:id
"enabled": false,
"keep_n": null,
"older_than": null,
"name_regex": null,
"name_regex": null, // to be deprecated in GitLab 13.0 in favor of `name_regex_delete`
"name_regex_delete": null,
"name_regex_keep": null,
"next_run_at": "2020-01-07T21:42:58.658Z"
},
"created_at": "2013-09-30T13:46:02Z",
@ -1033,7 +1035,7 @@ POST /projects
| `emails_disabled` | boolean | no | Disable email notifications |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) |
| `container_expiration_policy_attributes` | hash | no | Update the image expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `name_regex_delete` (string), `name_regex_keep` (string), `enabled` (boolean) |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
@ -1170,7 +1172,7 @@ PUT /projects/:id
| `emails_disabled` | boolean | no | Disable email notifications |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) |
| `container_expiration_policy_attributes` | hash | no | Update the image expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `name_regex_delete` (string), `name_regex_keep` (string), `enabled` (boolean) |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |

View file

@ -468,13 +468,13 @@ When a merge request author has been blocked for longer than
the `Review-response` SLO, they are free to remind the reviewer through Slack or assign
another reviewer.
#### Customer critical merge requests
### Customer critical merge requests
A merge request may benefit from being considered a customer critical priority because there is a significant benefit to the business in doing so.
Properties of customer critical merge requests:
- The [Senior Director of Development](https://about.gitlab.com/job-families/engineering/engineering-management/#senior-director-engineering) [@clefelhocz1](https://gitlab.com/clefelhocz1) is the DRI for deciding if a merge request will be customer critical.
- The [Senior Director of Development](https://about.gitlab.com/job-families/engineering/engineering-management/#senior-director-engineering) ([@clefelhocz1](https://gitlab.com/clefelhocz1)) is the DRI for deciding if a merge request will be customer critical.
- The DRI will assign the `customer-critical-merge-request` label to the merge request.
- It is required that the reviewer(s) and maintainer(s) involved with a customer critical merge request are engaged as soon as this decision is made.
- It is required to prioritize work for those involved on a customer critical merge request so that they have the time available necessary to focus on it.

View file

@ -1,5 +1,8 @@
---
description: "GitLab - Incident Management. GitLab offers solutions for handling incidents in your applications and services"
stage: Monitor
group: Health
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/#designated-technical-writers
---
# Incident Management

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View file

@ -505,6 +505,7 @@ then goes through a process of excluding tags from it until only the ones to be
1. Orders the remaining tags by `created_date`.
1. Excludes from the list the N tags based on the `keep_n` value (Number of tags to retain).
1. Excludes from the list the tags older than the `older_than` value (Expiration interval).
1. Excludes from the list any tags matching the `name_regex_keep` value (Images to preserve).
1. Finally, the remaining tags in the list are deleted from the Container Registry.
### Managing project expiration policy through the UI
@ -520,6 +521,7 @@ The UI allows you to configure the following:
- **Expiration schedule:** how often the cron job checking the tags should run.
- **Number of tags to retain:** how many tags to _always_ keep for each image.
- **Docker tags with names matching this regex pattern will expire:** the regex used to determine what tags should be expired. To qualify all tags for expiration, use the default value of `.*`.
- **Docker tags with names matching this regex pattern will be preserved:** the regex used to determine what tags should be preserved. To preserve all tags, use the default value of `.*`.
### Managing project expiration policy through the API
@ -527,16 +529,10 @@ You can set, update, and disable the expiration policies using the GitLab API.
Examples:
- Select all tags, keep at least 1 tag per image, expire any tag older than 14 days, run once a month, and the policy is enabled:
- Select all tags, keep at least 1 tag per image, expire any tag older than 14 days, run once a month, preserve any images with the name `master` and the policy is enabled:
```shell
curl --request PUT --header 'Content-Type: application/json;charset=UTF-8' --header "PRIVATE-TOKEN: <your_access_token>" --data-binary '{"container_expiration_policy_attributes":{"cadence":"1month","enabled":true,"keep_n":1,"older_than":"14d","name_regex":".*"}}' 'https://gitlab.example.com/api/v4/projects/2'
```
- Select only tags with a name that contains `stable`, keep at least 50 tag per image, expire any tag older than 7 days, run every day, and the policy is enabled:
```shell
curl --request PUT --header 'Content-Type: application/json;charset=UTF-8' --header "PRIVATE-TOKEN: <your_access_token>" --data-binary '{"container_expiration_policy_attributes":{"cadence":"1day","enabled":true,"keep_n":50"older_than":"7d","name_regex":"*stable"}}' 'https://gitlab.example.com/api/v4/projects/2'
curl --request PUT --header 'Content-Type: application/json;charset=UTF-8' --header "PRIVATE-TOKEN: <your_access_token>" --data-binary '{"container_expiration_policy_attributes":{"cadence":"1month","enabled":true,"keep_n":1,"older_than":"14d","name_regex":"","name_regex_delete":".*","name_regex_keep":".*-master"}}' 'https://gitlab.example.com/api/v4/projects/2'
```
See the API documentation for further details: [Edit project](../../../api/projects.md#edit-project).

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Kubernetes clusters
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/35954) in GitLab 10.1 for projects.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: Health
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/#designated-technical-writers
---
# Generic alerts integration
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/13203) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Prometheus integration
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/8935) in GitLab 9.0.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Monitoring AWS Resources
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/12621) in GitLab 9.4

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Monitoring HAProxy
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/12621) in GitLab 9.4

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Prometheus Metrics library
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/8935) in GitLab 9.0.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Monitoring Kubernetes
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/8935) in GitLab 9.0.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Monitoring NGINX
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/12621) in GitLab 9.4

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Monitoring NGINX Ingress Controller
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22133) in GitLab 11.7.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Monitoring NGINX Ingress Controller with VTS metrics
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13438) in GitLab 9.5.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Unit formats reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/201999) in GitLab 12.9.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: Health
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/#designated-technical-writers
---
# Error Tracking
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/169) in GitLab 11.8.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: APM
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/#designated-technical-writers
---
# Tracing **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/7903) in GitLab Ultimate 11.5.

View file

@ -1,3 +1,9 @@
---
stage: Monitor
group: Health
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/#designated-technical-writers
---
# GitLab Status Page
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2479) in GitLab 12.10.

View file

@ -4,8 +4,8 @@ module Gitlab
class GroupSearchResults < SearchResults
attr_reader :group
def initialize(current_user, limit_projects, group, query, default_project_filter: false, per_page: 20)
super(current_user, limit_projects, query, default_project_filter: default_project_filter, per_page: per_page)
def initialize(current_user, limit_projects, group, query, default_project_filter: false)
super(current_user, limit_projects, query, default_project_filter: default_project_filter)
@group = group
end

View file

@ -2,30 +2,29 @@
module Gitlab
class ProjectSearchResults < SearchResults
attr_reader :project, :repository_ref, :per_page
attr_reader :project, :repository_ref
def initialize(current_user, project, query, repository_ref = nil, per_page: 20)
def initialize(current_user, project, query, repository_ref = nil)
@current_user = current_user
@project = project
@repository_ref = repository_ref.presence
@query = query
@per_page = per_page
end
def objects(scope, page = nil)
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE)
case scope
when 'notes'
notes.page(page).per(per_page)
when 'blobs'
paginated_blobs(blobs(page), page)
paginated_blobs(blobs(limit: limit_up_to_page(page, per_page)), page, per_page)
when 'wiki_blobs'
paginated_blobs(wiki_blobs, page)
paginated_blobs(wiki_blobs(limit: limit_up_to_page(page, per_page)), page, per_page)
when 'commits'
Kaminari.paginate_array(commits).page(page).per(per_page)
when 'users'
users.page(page).per(per_page)
else
super(scope, page, false)
super(scope, page: page, per_page: per_page, without_count: false)
end
end
@ -49,7 +48,7 @@ module Gitlab
end
def limited_blobs_count
@limited_blobs_count ||= blobs.count
@limited_blobs_count ||= blobs(limit: count_limit).count
end
# rubocop: disable CodeReuse/ActiveRecord
@ -69,7 +68,7 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
def wiki_blobs_count
@wiki_blobs_count ||= wiki_blobs.count
@wiki_blobs_count ||= wiki_blobs(limit: count_limit).count
end
def commits_count
@ -87,7 +86,7 @@ module Gitlab
private
def paginated_blobs(blobs, page)
def paginated_blobs(blobs, page, per_page)
results = Kaminari.paginate_array(blobs).page(page).per(per_page)
Gitlab::Search::FoundBlob.preload_blobs(results)
@ -95,19 +94,19 @@ module Gitlab
results
end
def limit_up_to_page(page)
def limit_up_to_page(page, per_page)
current_page = page&.to_i || 1
offset = per_page * (current_page - 1)
count_limit + offset
end
def blobs(page = 1)
def blobs(limit: count_limit)
return [] unless Ability.allowed?(@current_user, :download_code, @project)
@blobs ||= Gitlab::FileFinder.new(project, repository_project_ref).find(query, content_match_cutoff: limit_up_to_page(page))
@blobs ||= Gitlab::FileFinder.new(project, repository_project_ref).find(query, content_match_cutoff: limit)
end
def wiki_blobs
def wiki_blobs(limit: count_limit)
return [] unless Ability.allowed?(@current_user, :read_wiki, @project)
@wiki_blobs ||= begin
@ -115,7 +114,7 @@ module Gitlab
if project.wiki.empty?
[]
else
Gitlab::WikiFileFinder.new(project, repository_wiki_ref).find(query)
Gitlab::WikiFileFinder.new(project, repository_wiki_ref).find(query, content_match_cutoff: limit)
end
else
[]

View file

@ -4,8 +4,10 @@ module Gitlab
class SearchResults
COUNT_LIMIT = 100
COUNT_LIMIT_MESSAGE = "#{COUNT_LIMIT - 1}+"
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 20
attr_reader :current_user, :query, :per_page
attr_reader :current_user, :query
# Limit search results by passed projects
# It allows us to search only for projects user has access to
@ -17,15 +19,14 @@ module Gitlab
# query
attr_reader :default_project_filter
def initialize(current_user, limit_projects, query, default_project_filter: false, per_page: 20)
def initialize(current_user, limit_projects, query, default_project_filter: false)
@current_user = current_user
@limit_projects = limit_projects || Project.all
@query = query
@default_project_filter = default_project_filter
@per_page = per_page
end
def objects(scope, page = nil, without_count = true)
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE, without_count: true)
collection = case scope
when 'projects'
projects
@ -39,7 +40,9 @@ module Gitlab
users
else
Kaminari.paginate_array([])
end.page(page).per(per_page)
end
collection = collection.page(page).per(per_page)
without_count ? collection.without_count : collection
end

View file

@ -19,6 +19,7 @@ module Gitlab
output[:message] = data
when Hash
convert_to_iso8601!(data)
convert_retry_to_integer!(data)
stringify_args!(data)
output.merge!(data)
end
@ -41,6 +42,20 @@ module Gitlab
Time.at(timestamp).utc.iso8601(3)
end
def convert_retry_to_integer!(payload)
payload['retry'] =
case payload['retry']
when Integer
payload['retry']
when false, nil
0
when true
Sidekiq::JobRetry::DEFAULT_MAX_RETRY_ATTEMPTS
else
-1
end
end
def stringify_args!(payload)
payload['args'] = Gitlab::Utils::LogLimitedArray.log_limited_array(payload['args'].map(&:to_s)) if payload['args']
end

View file

@ -11,8 +11,8 @@ module Gitlab
@query = query
end
def objects(scope, page = nil)
paginated_objects(snippet_titles, page)
def objects(scope, page: nil, per_page: DEFAULT_PER_PAGE)
paginated_objects(snippet_titles, page, per_page)
end
def formatted_count(scope)
@ -38,7 +38,7 @@ module Gitlab
snippets.search(query)
end
def paginated_objects(relation, page)
def paginated_objects(relation, page, per_page)
relation.page(page).per(per_page)
end

View file

@ -321,6 +321,9 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
msgid "%{firstMilestoneName} + %{numberOfOtherMilestones} more"
msgstr ""
msgid "%{global_id} is not a valid id for %{expected_type}."
msgstr ""
@ -2141,6 +2144,9 @@ msgstr ""
msgid "An error occurred while loading merge requests."
msgstr ""
msgid "An error occurred while loading milestones"
msgstr ""
msgid "An error occurred while loading terraform report"
msgstr ""
@ -2216,6 +2222,9 @@ msgstr ""
msgid "An error occurred while saving the template. Please check if the template exists."
msgstr ""
msgid "An error occurred while searching for milestones"
msgstr ""
msgid "An error occurred while subscribing to notifications."
msgstr ""
@ -5710,7 +5719,7 @@ msgstr ""
msgid "ContainerRegistry|Quick Start"
msgstr ""
msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported"
msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
msgstr ""
msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
@ -6213,6 +6222,9 @@ msgstr ""
msgid "Create milestone"
msgstr ""
msgid "Create new"
msgstr ""
msgid "Create new board"
msgstr ""
@ -6889,11 +6901,6 @@ msgid_plural "Dependencies|%d additional vulnerabilities not shown"
msgstr[0] ""
msgstr[1] ""
msgid "Dependencies|%d vulnerability"
msgid_plural "Dependencies|%d vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgid "Dependencies|%d vulnerability detected"
msgid_plural "Dependencies|%d vulnerabilities detected"
msgstr[0] ""
@ -6929,12 +6936,6 @@ msgstr ""
msgid "Dependencies|Packager"
msgstr ""
msgid "Dependencies|Safe"
msgstr ""
msgid "Dependencies|Status"
msgstr ""
msgid "Dependencies|The %{codeStartTag}dependency_scanning%{codeEndTag} job has failed and cannot generate the list. Please ensure the job is running properly and run the pipeline again."
msgstr ""
@ -6944,9 +6945,6 @@ msgstr ""
msgid "Dependencies|Unsupported file(s) detected"
msgstr ""
msgid "Dependencies|Version"
msgstr ""
msgid "Dependencies|Vulnerable components"
msgstr ""
@ -12739,6 +12737,9 @@ msgstr ""
msgid "Manage labels"
msgstr ""
msgid "Manage milestones"
msgstr ""
msgid "Manage project labels"
msgstr ""
@ -14005,6 +14006,9 @@ msgstr ""
msgid "No messages were logged"
msgstr ""
msgid "No milestone"
msgstr ""
msgid "No milestones to show"
msgstr ""
@ -18171,6 +18175,9 @@ msgstr ""
msgid "Search Button"
msgstr ""
msgid "Search Milestones"
msgstr ""
msgid "Search an environment spec"
msgstr ""

View file

@ -39,8 +39,8 @@
"@babel/plugin-syntax-import-meta": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.125.0",
"@gitlab/ui": "14.0.0",
"@gitlab/svgs": "1.127.0",
"@gitlab/ui": "14.2.1",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.2-2",
"@sentry/browser": "^5.10.2",

View file

@ -4,7 +4,7 @@ require 'spec_helper'
describe Projects::AlertManagementController do
let_it_be(:project) { create(:project) }
let_it_be(:role) { :reporter }
let_it_be(:role) { :developer }
let_it_be(:user) { create(:user) }
let_it_be(:id) { 1 }
@ -24,6 +24,16 @@ describe Projects::AlertManagementController do
expect(response).to have_gitlab_http_status(:ok)
end
context 'when user is unauthorized' do
let(:role) { :reporter }
it 'shows 404' do
get :index, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when alert_management_minimal is disabled' do
@ -50,6 +60,16 @@ describe Projects::AlertManagementController do
expect(response).to have_gitlab_http_status(:ok)
end
context 'when user is unauthorized' do
let(:role) { :reporter }
it 'shows 404' do
get :index, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when alert_management_detail is disabled' do

View file

@ -21,7 +21,7 @@ describe 'Global search' do
describe 'I search through the issues and I see pagination' do
before do
allow_next_instance_of(Gitlab::SearchResults) do |instance|
allow_next_instance_of(SearchService) do |instance|
allow(instance).to receive(:per_page).and_return(1)
end
create_list(:issue, 2, project: project, title: 'initial')

View file

@ -15,7 +15,7 @@ describe('Api', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
originalGon = window.gon;
window.gon = Object.assign({}, dummyGon);
window.gon = { ...dummyGon };
});
afterEach(() => {

View file

@ -38,7 +38,7 @@ describe('Blob Content component', () => {
it('renders error if there is any in the viewer', () => {
const renderError = 'Oops';
const viewer = Object.assign({}, SimpleViewerMock, { renderError });
const viewer = { ...SimpleViewerMock, renderError };
createComponent({}, viewer);
expect(wrapper.contains(GlLoadingIcon)).toBe(false);
expect(wrapper.contains(BlobContentError)).toBe(true);

View file

@ -15,7 +15,7 @@ describe('Blob Header Filepath', () => {
function createComponent(blobProps = {}, options = {}) {
wrapper = shallowMount(BlobHeaderFilepath, {
propsData: {
blob: Object.assign({}, MockBlob, blobProps),
blob: { ...MockBlob, ...blobProps },
},
...options,
});

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