Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4b7575da97
commit
17c8111494
184 changed files with 1645 additions and 2458 deletions
|
@ -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')));
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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 = {}) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 = {}) {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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]))();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
228
app/assets/javascripts/milestones/project_milestone_combobox.vue
Normal file
228
app/assets/javascripts/milestones/project_milestone_combobox.vue
Normal 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>
|
|
@ -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,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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,
|
||||
|
|
13
app/assets/stylesheets/components/milestone_combobox.scss
Normal file
13
app/assets/stylesheets/components/milestone_combobox.scss
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -18,7 +18,7 @@ module Mutations
|
|||
null: true,
|
||||
description: "The alert after mutation"
|
||||
|
||||
authorize :update_alert_management_alerts
|
||||
authorize :update_alert_management_alert
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
5
changelogs/unreleased/207267-expiration-policy-copy.yml
Normal file
5
changelogs/unreleased/207267-expiration-policy-copy.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update the example regex in the image expiration policy UI
|
||||
merge_request: 31104
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Honor per_page in Search API
|
||||
merge_request: 29197
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/216477-gllink-updates.yml
Normal file
5
changelogs/unreleased/216477-gllink-updates.yml
Normal 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
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Allow to assign milestones to a release on the "Edit Release page"'
|
||||
merge_request: 28583
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Changed test success calculation to exclude skipped tests
|
||||
merge_request: 31154
|
||||
type: changed
|
|
@ -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/).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 |
|
@ -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).
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
[]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -15,7 +15,7 @@ describe('Api', () => {
|
|||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
originalGon = window.gon;
|
||||
window.gon = Object.assign({}, dummyGon);
|
||||
window.gon = { ...dummyGon };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue